原创文章第242篇,专注“个人成长与财富自由、世界运作的逻辑与投资"。
开始之前,先说说感受。
把整个框架与思路都在社群里开源出来,就是希望大家看懂思路,而不是拿一两个策略。说实话,投资哪有这种高确定性的“圣杯”。
投资是一个“概率”游戏。我们要做的,尽量提升决策的正概率而已。因此,多一些有效因子,模型判断更准确一些。所以,方法论和思路比一个策略本身要重要得多。这是我们这个社群的目标——以盈利为目的开发策略,而且是批量的有效实战策略。
微信群现在的讨论氛围还不错,还没有加入的成员请扫码进入。一般加入之初,我都有发私信给大家,如果没有,请再星球里私信我。
今天要抽空实现“前向滚动”训练回测。
从20日动量来看,我们选择29个行业作为候选集合是对的。
因为市场是按行业分化的,尤其是市场情绪震荡有分歧之时:

29个行业等权回测:

年化收益 0.075,最大回测 -0.318,夏普比 0.441。
等权加周再平衡:

年化收益 0.146,最大回测 -0.283,夏普比 0.748。再平衡适合相关性低的组合,正当的行业比较明显。
使用lightGBM的WFA滚动训练:

年化收益 0.219,最大回测 -0.259,夏普比 0.861。
etfs = [
'159870.SZ',
'512400.SH',
'515220.SH',
'515210.SH',
'516950.SH',
'562800.SH',
'515170.SH',
'512690.SH',
'159996.SZ',
'159865.SZ',
'159766.SZ',
'515950.SH',
'159992.SZ',
'159839.SZ',
'512170.SH',
'159883.SZ',
'512980.SH',
'159869.SZ',
'515050.SH',
'515000.SH',
'515880.SH',
'512480.SH',
'515230.SH',
'512670.SH',
'515790.SH',
'159757.SZ',
'516110.SH',
'512800.SH',
'512200.SH',
]
import numpy as np
import pandas as pd
from tqdm import tqdm
from engine.env import Env
from engine.datafeed.dataset import DataSet, AlphaLit, Alpha
ds = DataSet(etfs, start_date='20190101', handler=Alpha(), cache=True)
# ds = DataSet(['000300.SH', '399006.SZ'], start_date='20120101', handler=Alpha())
env = Env(ds.data)
from engine.context import ExecContext
from engine.algo.algos import *
from engine.algo.algo_model import ModelWFA
from engine.models.gbdt_l2r import LGBModel
model = LGBModel(load_model=False, feature_cols=ds.get_feature_names())
env.set_algos([
RunWeekly(),
ModelWFA(model=model),
SelectTopK(K=2, order_by='pred_score', b_ascending=False),
WeightEqually()
])
env.backtest_loop()
env.show_results()
下面是因子集的定义代码:
class Alpha(AlphaBase):
def __init__(self):
pass
@staticmethod
def parse_config_to_fields():
# ['CORD30', 'STD30', 'CORR5', 'RESI10', 'CORD60', 'STD5', 'LOW0',
# 'WVMA30', 'RESI5', 'ROC5', 'KSFT', 'STD20', 'RSV5', 'STD60', 'KLEN']
fields = []
names = []
# kbar
fields += [
"(close-open)/open",
"(high-low)/open",
"(close-open)/(high-low+1e-12)",
"(high-greater(open, close))/open",
"(high-greater(open, close))/(high-low+1e-12)",
"(less(open, close)-low)/open",
"(less(open, close)-low)/(high-low+1e-12)",
"(2*close-high-low)/open",
"(2*close-high-low)/(high-low+1e-12)",
]
names += [
"KMID",
"KLEN",
"KMID2",
"KUP",
"KUP2",
"KLOW",
"KLOW2",
"KSFT",
"KSFT2",
]
# =========== price ==========
feature = ["OPEN", "HIGH", "LOW", "CLOSE"]
windows = range(5)
for field in feature:
field = field.lower()
fields += ["shift(%s, %d)/close" % (field, d) if d != 0 else "%s/close" % field for d in windows]
names += [field.upper() + str(d) for d in windows]
# ================ volume ===========
fields += ["shift(volume, %d)/(volume+1e-12)" % d if d != 0 else "volume/(volume+1e-12)" for d in windows]
names += ["VOLUME" + str(d) for d in windows]
# ================= rolling ====================
windows = [5, 10, 20, 30, 60]
fields += ["shift(close, %d)/close" % d for d in windows]
names += ["ROC%d" % d for d in windows]
fields += ["mean(close, %d)/close" % d for d in windows]
names += ["MA%d" % d for d in windows]
fields += ["std(close, %d)/close" % d for d in windows]
names += ["STD%d" % d for d in windows]
fields += ["slope(close, %d)/close" % d for d in windows]
names += ["BETA%d" % d for d in windows]
fields += ["max(high, %d)/close" % d for d in windows]
names += ["MAX%d" % d for d in windows]
fields += ["min(low, %d)/close" % d for d in windows]
names += ["MIN%d" % d for d in windows]
fields += ["corr(close/shift(close,1), log(volume/shift(volume, 1)+1), %d)" % d for d in windows]
names += ["CORD%d" % d for d in windows]
fields += ['close/shift(close,20)-1']
names += ['roc_20']
return fields, names
所有代码,数据均打包发布至星球,请大家前往下载:
我的开源项目及知识星球
欢迎大家提建议,bug,可以在专属微信群里发起讨论。



















