策略模拟和扫描是整个框架的核心,需要理解的概念略多一些,我会尽量用最简单的方法描述清楚:
首先,我们需要理解一些基本的概念和理念
回测 vs 扫描
回测:就是用您定义好的一套交易规则在历史数据里测试它是不是达到了您要的交易目标
扫描:就是用您定义的策略跑到最新的数据(需要通过 DataSource 模块拉取最近数据)上去,对所有您觉得有价值的股票扫描一遍,看看有没有现成的机会
多层回测模拟
机会枚举(enumerator):在当前股票池里对每个股票的每根K线进行推进,凡是满足我的策略认定的机会我都记录下来,并且模拟完成这个机会。枚举就是在整个参与回测的股票池中找到所有出现的机会并跟踪它们到完成。这个回测步骤是其他三个步骤的先决条件。
价格因子模拟(price factor):按照时间推进,但凡股票池的股票的出现机会就投资1股,一只到投资完整个时间轴。看看这1个股票的价格是怎么波动的,ROI(回报率)是不是正的。投资中的股票不能再投资。这个回测的主要作用是快速验证策略是不是个有效策略。
资金模拟(portfolio):模拟实盘交易。回测中开始加入资金池,并且按照市场规则进行买卖。资金回测就是模拟这个策略在实盘中是不是能盈利,您的资金分配策略是不是能把上一步价格因子的结果发挥出来。
决策者模拟(decision):其实就是资金模拟的手动控制。整个回测中您只能决定每个机会您要不要投资或者出现多个机会您怎么选择,完成后会和您默认的回测进行比对。这个回撤的目的是:身临其境地进入NTQ实盘模拟,承受回撤的压力并做出决策,看看这个策略是不是适合您或者一定程度能帮助您改善策略。
请注意:当前四层模拟的重要(参考)的含义是:
机会枚举:看看我的策略能不能找到机会,找到的多不多,是如何分布的。(机会枚举的中间数据对机器学习也比较友好)
价格因子模拟:初步看看我的策略是不是对目标股票有效,但注意这里得到的ROI与您的实际策略投资回报不是一回事,这里只能证明您的策略是不是对目标股票产生正向收益,正向收益是不是足够大。
资金模拟:这个模拟才是更接近实际的投资模拟,用来验证您的仓位管理策略是不是有效。您还需要加入仓位管理才能真正实现盈利。有可能我的策略普遍对大部分股票都是盈利的,但我运气不好就是分配了大部分资金去了亏损的股票,那您的总体收益很可能是负的。
从 0 开始创建一个新策略
步骤1:先建一个策略目录和必须文件
例如创建:
userspace/strategies/my_strategy/
├── settings.py
└── strategy.py
步骤2:修改 settings.py(决定“怎么跑”)
您可以拷贝strategies/_template里的文件,然后再修改里边的内容。最少建议包含:
meta - 这个策略的基本信息,比如名字或者描述之类的。(需注意您的meta.key需要在所有策略中保持唯一)
is_enabled - 这个策略是不是开启的
core - 您所有策略里的阈值或者配置都可以放进这里,如果放入就可以在UI上出现并直接通过UI修改进行调试。当然空的也行。
data.base - 声明您的策略需要什么数据,框架会自动注入,这里您注入的都必须是数据契约。
goal - 策略的止损,止赢,投资时间限制等等目标
注意:settings文件是一个策略运行的核心,强烈建议您先打开userspace/strategies下的settings_example.py文件并阅读里边的注释。在了解settings的基本属性和用法后再更改您自己的配置文件。
例如(最小可读示例):
Settings = {
"is_enabled": True,
"meta": {
"key": my_strategy, // 策略的唯一标识符
"display_name": "空白策略模板", // UI上显示的策略名
"description": "my first strategy", // 策略的描述
},
"core": {
// 您自己策略里要用到的阈值或者参数,比如:
// "rsi_oversold_threshold": 20, 策略里超卖的信号就是RSI低于20,这里的20就是超卖阈值。
},
"data": {
"base": {
"data_key": "stock.kline.daily", // 策略中需要注入日K线用于基础的回测日期推进
},
"required": [], //您的回测如果还需要其他的什么数据契约在这里注入,声明方式和base一致,可以声明多个
"min_required_records": 30, // 您的策略需要至少多少根K线才能开始生效。
},
"goal": {
"expiration": {
"fixed_window_in_days": 30, // 30天自动过期平仓
"mode": "open_day" // 这30天是只开盘日的30天,不是自然日
},
"stop_loss": {
// 止损配置,是可以配置多个阶段的
"stages": [
{
"ratio": -0.1, // 负数代表亏损。这里-0.1代表当亏损达到买入价格的10%的时候触发
"close_invest": True // 这里是触发止损后的行为,当前配置是全部平仓。当然也可以配置类似 exit_ratio: 0.5 来配置平仓50%
}
]
},
"take_profit": {
// 止盈配置,当然也可以配置多个阶段
"stages": [
{
"ratio": 0.2, // 正数代表盈利。这里0.2指盈利20%。
"exit_ratio": 0.6 // 这里指盈利超过20%立刻平仓60%,剩下仓位会进入下一个阶段。
},
{
"ratio": 0.3, // 正数代表盈利。这里0.2指盈利20%。
"close_invest": True // 这里指盈利超过30%立即全部平仓,也就是平仓剩余的40%。也可以配置 exit_ratio: 0.4
}
]
},
},
}
步骤3:写 strategy.py(决定“什么时机发信号”)
最核心就是实现 has_opportunity(self, ctx),这个函数是对每个回测单位时间的每只股票进行一次调用。如果返回True代表是机会,否则会跳过当次。如果我是回测5个股票的10个交易日,那么这个函数会调用 5 * 10 = 50次。
class EmptyStrategy(StrategyHooks):
def has_opportunity(self, ctx: StrategyContext) -> bool:
# 当日这只股票的日 K(含 settings 里声明的指标);最后一根就是今天
klines_daily = ctx.data.items.get("stock.kline.daily") or []
if not klines_daily:
return False
# 从所有K线中取最后一根,也就是今天的
kline_today = klines_daily[-1]
# 从今日的K线中读取RSI参数
rsi = kline_today.get("rsi14")
# (可选步骤)把当日的RSI数值记录下来,后续会对结果进行归因的时候使用
ctx.capture("rsi", rsi)
# 如果 RSI 存在且小于 20 就返回 True 代表有机会
# 通常来说,这个20是定义在settings.core里的,这样UI就可以看到并通过修改参数得到不同的结果
return rsi is not None and rsi < 20
步骤4:运行并验证
您可以在UI里直接运行三步回测或者在命令行里使用下边命令:
python cli.py se --strategy my_strategy // 枚举:se = strategy enumerate
python cli.py sp --strategy my_strategy // 价格回测:sp = strategy price factor
python cli.py so --strategy my_strategy // 资金回测:so = strategy portfolio注:--strategy 后带的参数就是您在settings.py 里的meta.key
步骤5:看结果目录
结果通常在:
userspace/strategies/my_strategy/results/
这个目录默认是本地产物目录,不建议提交大结果文件到 Git