(文末有福利)
追涨杀跌还是买跌卖涨?无论是投资者还是投机客,这可能是个永恒的问题。 之前刷华尔街见闻的时候发现一个简单的右侧交易策略
当目前的价格 > 过去12个月的平均价格,那么保持投资(如果没有投资,那么就买入); 当目前的价格 < 过去12个月的平均价格,那么就全部卖出,持有现金(或债券)。
很有意思,回测也很不错。感觉原则就是,趋势来了我贪心,趋势走了我就离场,而且坚决不回来。不过对于我这种左侧交易者来说,在上升时期宁可hold也不愿意加仓。这个策略很简单,应可以作为一个出发点,后面按自己的理解来进行一些修正。尤其对于我这种喜欢左侧交易和网格策略的~
Updated 2018-01-23
正巧需要构建自己的Mercurius交易系统,不妨先实现几个策略做回测试试,标的的话,就选黄金好了,当然一般来说,黄金并不是什么好的投资标的。不过俺们就是这么俗,土豪金谁人不爱~
一共写了三套策略:
回测采用日均值,还没来得及写年化收益,最大回撤、夏普ratio等统计指标,先简单看图吧。首先我们以满仓10000美元的buy and hold策略,来看黄金自2000年以来的表现情况:
很明显,黄金从00年开始,随着全球的货币大宽松走出了一个长期牛市,主升浪在2008金融危机之后。到2011年上半年,净值翻了六倍多,随后由于美国逐渐退出QE,走熊直到2016年初(怎知16-now这波不是反弹?嘿嘿)。
作为对策略的压力测试,当然先看熊市表现啦。
结果还是很有趣的,从投资(机)总效果来看,up in and down out相比于佛系的buyNhold最终表现仅仅略微好一点点,大概也就少损失个一两百刀。过程中我们可以看到金价多次出现假突破,从而使得策略不断进出,在不计交易费用的情况下相比佛系投资还有一点优势,如果存在交易成本,那怕是必然亏更多的。 当然改进的方法也很简单,比如每隔一个period进行一次检测,这样能够避免频繁假突破时的频繁交易。
值得注意的是,这一贪心策略可以确保在市场急剧下跌期间保持极高的(100%)持现比例,你不爱我绝不回头~一手现金dry powder。充沛的流动性在手给了标的走熊期间去发掘其他资产的机会。
如果我们cover牛市,从2008年到现在呢?看这十年间会发生什么。
Oops!居然无法跑赢佛系buy and hold。这还算在木有交易费用的情况下。矬了吧~主要问题在于前期高频箱体振荡期间出现频繁买卖,而买点恰巧错过一批疾速上涨期,随后基本一直落后buy and hold。
我们再看所谓的网格提款机的效果。我喜欢的左侧交易策略表现又如何呢?
嘿嘿!大网格熊市各种睥睨佛系一把梭,我们看到几个酷炫的买点基本都出现在市场底部,然后前期两次出网很酷炫得锁定了收益,随后剧烈走熊阶段不断加仓压低成本,可惜13年中即已满仓,一直到16的市场底部只能不断观望。但是最终到18年初,我们可以看到网格策略仅仅承担了不到10%的账面亏损,而佛系一把梭却有接近20%的账面亏损。
那么牛市呢?
哎哟不错诶。虽然赶不上一把梭的痴长,但是基本保持了数量级相当的收益。不过值得注意的是,在牛市启动阶段,网格卖出所有的quota之后即因成本持续低于市值而按兵不动,所以后续基本相当于大半仓位下buy and hold。注意到09年的turmoil阶段网格抓到了几次低位买点,那么我们将时间尺度继续拉长,是否还能获得如此不错的表现呢?
显然,从00年到现在,网格策略远远落后于buy and hold,主要原因也很简单,人家满仓,我们却基本半仓走了快二十年,最终大概只能获得佛系一把梭一半多点的收益。
简单总结一下:
总而言之,眼光决定一切。基本面得抓得准呀。不说了,默默啃书。
Left or right, This is a problem.
要Code?没问题!瞎捣鼓的交易系统都是完全开源的啦!几个简单的策略在strategy目录下。欢迎star/fork/diss
Updated 2018-01-30
Please check my GitHub Repo for the most recent code implementation in CESM1.2.2.
在之前MM高原试验基础上,应黄老师要求增加北大西洋SST restoring的试验。因通量计算在海洋模块进行,所以应该在海洋模块计算之前修改并update。
查到论坛帖子,以及HC师兄提供的code.
看帖子似乎有namelist变量控制。找到一个NCAR的github主页内容,不过是针对只转海洋模式的情况。
经查HC师兄的code,发现思路比较简单,就是在海洋模块每一次积分step后,用人工读入的文件做restoring。不过貌似还有可以优化的地方,读文件模块应该可以放到其他module,避免模式每一积分步都在读写。
测试编译一次通过。Nice.关键是拿到对应的文件,并进一步优化一下程序流程。
POP的history文件比较诡异,不仅有.h文件,还有.h.nday1文件。
在faq找到了关于nday1的说明
By default, the daily averaged fields are bundled into monthly timeseries files. These files are relatively small, because they contain only a handful of 2D fields. Out of the box, these fields are: Surface Potential Temperature (SST), SST2, Mixed-Layer Depth, and Maximum Mixed-Layer Depth.
这个文件在之前run ctrl的时候并没有存下,那么接下来的问题就是月平均文件是否足以提供full layer海温,包括SST呢?
瞄了一下h文件,发现第一层代表10m深的水,那么应该是没问题的了,直接restore 10m水深。
Updated 2018-01-28
继续看,存储位温的变量是TRACER,但这个变量比较诡异,是一个6维的数据,0 1 2 容易理解,是经纬度,和层次数,第1层为10m水深。3不知道是什么,4是previous/current时间帧,最后一维是iblock用于MPI分片。 问题就是神奇的第3维。
检查了下程序,发现第3维的循环控制变量bound为nt,step_mod.F90内有以下语句:
SBUFF_SUM(:,:,iblock,index_o2x_So_t ) = &
SBUFF_SUM(:,:,iblock,index_o2x_So_t ) + delt* &
TRACER(:,:,1,1,curtime,iblock)
SBUFF_SUM(:,:,iblock,index_o2x_So_s ) = &
SBUFF_SUM(:,:,iblock,index_o2x_So_s ) + delt* &
TRACER(:,:,1,2,curtime,iblock)
进一步查找,发现prognostic.F90有以下定义
type (tracer_field), dimension(nt) :: &
tracer_d ! descriptors for each tracer
!......
tracer_d(1)%short_name = 'TEMP'
tracer_d(1)%long_name = 'Potential temperature'
tracer_d(1)%units = 'degC'
tracer_d(1)%tend_units = 'degC/s'
tracer_d(1)%flux_units = 'degC cm/s'
tracer_d(1)%scale_factor = 1.0_rtavg
tracer_d(2)%short_name = 'SALT'
tracer_d(2)%long_name = 'Salinity'
tracer_d(2)%units = 'msu (g/g)'
tracer_d(2)%tend_units = 'gram/kilogram/s'
tracer_d(2)%flux_units = 'gram/kilogram cm/s'
tracer_d(2)%scale_factor = 1000.0_rtavg
明白啦,1是位温,2是盐度。所以修改思路就很明确了:
逐一解决!首先看怎么找个合适的地方把文件送进来。这种任务当然在模式initial的时候干。
以read为关键词搜索,锁定文件IO位置。发现不少东西,比如pop设置了专门的forcing模块用于外强迫驱动。但因为forcing模块只有在单独海洋的时候才调用,为保证代码具有较高的可复用性,安全起见,我们还是首先考虑在initial module找读取外强迫场的语句。 然后呢,在initial.F90发现了这么一段可爱的内容:
!-----------------------------------------------------------------------
!
! read full 3-d t,s from input file
!
!-----------------------------------------------------------------------
case ('ccsm_startup', 'file')
first_step = .true.
if (my_task == master_task) then
write(stdout,'(a31,a)') 'Initial 3-d T,S read from file:', &
trim(init_ts_file)
call POP_IOUnitsFlush(POP_stdout) ; call POP_IOUnitsFlush(stdout)
endif
allocate(TEMP_DATA(nx_block,ny_block,km,max_blocks_clinic))
in_file = construct_file(init_ts_file_fmt, &
full_name=trim(init_ts_file), &
record_length = rec_type_dbl, &
recl_words=nx_global*ny_global)
call data_set(in_file,'open_read')
i_dim = construct_io_dim('i',nx_global)
j_dim = construct_io_dim('j',ny_global)
k_dim = construct_io_dim('k',km)
io_temp = construct_io_field('TEMPERATURE', &
dim1=i_dim, dim2=j_dim, dim3=k_dim, &
field_loc = field_loc_center, &
field_type = field_type_scalar, &
d3d_array=TEMP_DATA)
io_salt = construct_io_field('SALINITY', &
dim1=i_dim, dim2=j_dim, dim3=k_dim, &
field_loc = field_loc_center, &
field_type = field_type_scalar, &
d3d_array=TEMP_DATA)
call data_set(in_file,'define',io_temp)
call data_set(in_file,'define',io_salt)
call data_set(in_file,'read' ,io_temp)
do iblock=1,nblocks_clinic
TRACER(:,:,:,1,curtime,iblock) = TEMP_DATA(:,:,:,iblock)
end do
call data_set(in_file,'read' ,io_salt)
do iblock=1,nblocks_clinic
TRACER(:,:,:,2,curtime,iblock) = TEMP_DATA(:,:,:,iblock)
end do
call destroy_io_field(io_temp)
call destroy_io_field(io_salt)
deallocate(TEMP_DATA)
call data_set(in_file,'close')
call destroy_file(in_file)
if (my_task == master_task) then
write(stdout,blank_fmt)
write(stdout,'(a12,a)') ' file read: ', trim(init_ts_file)
call POP_IOUnitsFlush(POP_stdout) ; call POP_IOUnitsFlush(stdout)
endif
嘿嘿,和HC师兄的程序对比,如出一辙。那么我们仅仅需要在某一个合适的地方给出读取forcing场的语句就可以了。注意initial读取的时候是没有时间帧的,所以我们需要读入时间帧,并且把变量传参到step_mod或者main driver程序中。 接下来我们看如何解决这两个问题。时间问题容易解决,HC师兄的程序里给出了,直接通过dim来指定时间维即可,关键是construct_io_field这个语句,查下这个语句的定义。在io_types.F90:
function construct_io_field ( &
short_name, &
dim1, dim2, &
dim3, &
time_dim, &
long_name, &
units, &
coordinates, &
grid_loc, &
valid_range, &
field_loc, &
field_id, &
field_type, &
i0d_array, &
i1d_array, &
i2d_array, &
i3d_array, &
r0d_array, &
r1d_array, &
r2d_array, &
r3d_array, &
d0d_array, &
d1d_array, &
d2d_array, &
d3d_array) &
result (descriptor)
dim123时间都给考虑好了,意不意外,惊不惊喜?依葫芦画瓢往里送就行了。
几个神奇的参数:
character(4), intent(in), optional :: &
grid_loc ! position of field in staggered grid
integer (i4), intent(in), optional :: & ! for ghost cell updates
field_loc, &! staggering location
field_type, &! field type (scalar,vector,angle)
field_id ! previously defined id
首先的神奇之处是grid_loc,这什么鬼,虽然模板里给了3111代号,但是毕竟好奇,搜索下发现tavg.F90有如下语句:
if (present(grid_loc)) then
!*** try to decode field location from grid_loc
if (grid_loc(2:2) == '1' .and. grid_loc(3:3) == '1') then
tavg_field%field_loc = field_loc_center
else if (grid_loc(2:2) == '2' .and. grid_loc(3:3) == '2') then
tavg_field%field_loc = field_loc_NEcorner
else if (grid_loc(2:2) == '1' .and. grid_loc(3:3) == '2') then
tavg_field%field_loc = field_loc_Nface
else if (grid_loc(2:2) == '2' .and. grid_loc(3:3) == '1') then
tavg_field%field_loc = field_loc_Eface
endif
endif
东西南北?看起来是位于格点什么位置的指示。还有那些r2d_array, d2d_array代表的是数据精度real,double之类(居然没写注释!也是醉了) 查pop log发现了这个东西
2 30
________ 31
y ^ | | 32
| 3| ijk |1 33
+---> | | 34
x |________| 35
4 36
居然有这么可爱的图示……
Updated 2018-01-29
下面我们测试在main driver函数ocn_comp_mct.F90里读入一个既存的NC文件。为了效率,当然不能在run的时候每一步都读入,仅仅在initial的subroutine读入。initial和run都在ocn_mct的大module中,因此变量彼此应当可见互用。
写的时候发现一堆全局变量满天飞,POP的软件工程做得真是醉醉的……
增加namelist变量需要让bld namelist程序认识,根据这个帖子操作
增加两个subroutine,一个负责在initial的时候读取forcing文件,另一个负责在pop积分步后给出reset。
写好后发现一个大坑,由于IO module默认没有对3D var+时间的变量进行处理,如果手动增加这一功能需要改动非常多的模块,只能退而求其次,仅仅对第一层temp进行处理了。具体代码:
! ***LZN: MOD START***
subroutine ptempforcing_read
integer (i4) :: nml_error
integer (i4) :: decoupling_days=365
character (char_len) :: &
ptempf_file_name, &
ptempf_file_fmt
type (datafile) :: &
ptempf_file
type (io_field_desc) :: &
io_temp, io_ce
type (io_dim) :: &
t_dim, i_dim, j_dim, k_dim
namelist /ptempf_file_nml/ ptempf_file_name, ptempf_file_fmt
allocate(TEMP_DATA(nx_block, ny_block, decoupling_days, max_blocks_clinic))
allocate(WGT_DATA(nx_block, ny_block, decoupling_days, max_blocks_clinic))
WGT_DATA=1.0
! Read forcing file path from namelist file
if (my_task == master_task) then
open (nml_in, file=nml_filename, status='old', iostat=nml_error)
if (nml_error /= 0) then
nml_error = -1
else
nml_error = 1
endif
do while (nml_error > 0)
read(nml_in, nml=ptempf_file_nml,iostat=nml_error)
end do
if (nml_error == 0) close(nml_in)
end if
! broadcast the scalar to all MPI processors
call broadcast_scalar(ptempf_file_name, master_task)
call broadcast_scalar(ptempf_file_fmt, master_task)
ptempf_file = construct_file(ptempf_file_fmt, &
full_name=trim(ptempf_file_name),&
record_length=rec_type_real,&
recl_words=nx_global*ny_global)
call data_set(ptempf_file, 'open_read')
i_dim = construct_io_dim('i',nx_global)
j_dim = construct_io_dim('j',ny_global)
t_dim = construct_io_dim('time',decoupling_days)
io_temp = construct_io_field('TEMP_365',dim1=i_dim,dim2=j_dim,dim3=t_dim,&
long_name='Potential Temperature', units='degC', grid_loc='3111',&
field_loc = field_loc_center, &
field_type = field_type_scalar, r3d_array = TEMP_DATA)
call data_set(ptempf_file, 'define', io_temp)
call data_set(ptempf_file, 'read', io_temp)
! ru_data = construct_io_field('ru', dim1=i_dim, dim2=j_dim,&
! long_name='nudging coefficients', units='1',&
! grid_loc=' 3111', field_loc = field_loc_center,&
! field_type = field_type_scalar, r2d_array = ru)
! call data_set(sstf_file, 'define', ru_data)
! call data_set(sstf_file, 'read', ru_data)
call data_set(ptempf_file, 'close')
call destroy_io_field(io_temp)
! call destroy_io_field(ru_data)
call destroy_file(ptempf_file)
end subroutine ptempforcing_read
! fixing the SST!
subroutine ptempforcing_fix
integer (int_kind) :: iblock
! debug info
!write(stdout,*) iday_of_year, TEMP_DATA(50,50,iday_of_year,1)
! Operation: override the runtime tracer(temp)
!$OMP PARALLEL DO PRIVATE(iblock)
do iblock = 1, nblocks_clinic
TRACER(:,:,1,1,curtime,iblock) = &
(TEMP_DATA(:,:,iday_of_year,iblock)*WGT_DATA(:,:,iday_of_year, iblock)- &
TRACER(:,:,1,1,curtime,iblock)*(1-WGT_DATA(:,:,iday_of_year, iblock)))
end do
!$OMP END PARALLEL DO
end subroutine ptempforcing_fix
! ***LZN: MOD END***
经测试一个月积分没有问题。
Updated 2018-01-29 Updated 2020-07-31
写交易系统时为了让credentials在代码里不可见,用pandas读取csv获取,使用中发现一坑,把index_col设置后,竟然无法用pd['$IND_NAME']
去引用对应idx的行,估计又是各种坑爹对象类型不匹配。
解决方法也很简单,用pandas的loc方法:
print(pd_cre.loc['quandl','cred'])
Updated 2018-01-25