CTA仿真进阶篇1: 环境部署
source: wtcpp/folder03/file06.md
相比HFT仿真, CTA的仿真更加复杂点, 因此我们之前先讲解了HFT仿真交易, 如果对此不熟悉的小伙伴建议看之前的文章. 只有熟悉了HFT仿真逻辑之后才能较容易理解CTA仿真逻辑.
本系列文章建立在你对WT项目已有一定了解的基础上. 我将会逐步探索CTA仿真交易的各个细节, 包括风控管理, 佣金配置, 执行器配置, 过滤器配置, 开平策略配置等. 因此对其他基础的配置包括文件加载等将会一笔带过.
启动数据落地程序
CTA仿真策略一般需要获取K线数据, 这就要求必须打开数据落地程序. (即接收交易所数据保存到本地, 并将数据广播到本地端口, 供其他程序使用)
其实有两个程序都可以实现这一功能, 一个是 QuoteFactory
, 另一个是 TestDtPorter
, 而 TestDtPorter
主要是方便上层调用的, 因此建议大家使用 QuoteFactory
打开数据落地程序.
文件配置
这里给出我使用的相关配置(7*24小时, openctp).
commodities.json
{
"CFFEX": {
"IC": {
"covermode": 0,
"pricemode": 0,
"category": 1,
"precision": 1,
"pricetick": 0.2,
"volscale": 200,
"name": "中证֤",
"exchg": "CFFEX",
"session": "TTS24",
"holiday": "CHINA"
},
"IF": {
"covermode": 0,
"pricemode": 0,
"category": 1,
"precision": 1,
"pricetick": 0.2,
"volscale": 300,
"name": "沪深",
"exchg": "CFFEX",
"session": "TTS24",
"holiday": "CHINA"
},
"IH": {
"covermode": 0,
"pricemode": 0,
"category": 1,
"precision": 1,
"pricetick": 0.2,
"volscale": 300,
"name": "上证",
"exchg": "CFFEX",
"session": "TTS24",
"holiday": "CHINA"
}
}
}
contracts.json
{
"CFFEX": {
"IC2203": {
"name": "中证2203",
"code": "IC2203",
"exchg": "CFFEX",
"product": "IC",
"maxlimitqty": 20,
"maxmarketqty": 10
},
"IC2206": {
"name": "中证֤2206",
"code": "IC2206",
"exchg": "CFFEX",
"product": "IC",
"maxlimitqty": 20,
"maxmarketqty": 10
},
"IC2209": {
"name": "中证֤2209",
"code": "IC2209",
"exchg": "CFFEX",
"product": "IC",
"maxlimitqty": 20,
"maxmarketqty": 10
},
"IF2203": {
"name": "沪深2203",
"code": "IF2203",
"exchg": "CFFEX",
"product": "IF",
"maxlimitqty": 20,
"maxmarketqty": 10
},
"IF2206": {
"name": "沪深2206",
"code": "IF2206",
"exchg": "CFFEX",
"product": "IF",
"maxlimitqty": 20,
"maxmarketqty": 10
},
"IF2209": {
"name": "沪深2209",
"code": "IF2209",
"exchg": "CFFEX",
"product": "IF",
"maxlimitqty": 20,
"maxmarketqty": 10
},
"IH2203": {
"name": "上证֤2203",
"code": "IH2203",
"exchg": "CFFEX",
"product": "IH",
"maxlimitqty": 20,
"maxmarketqty": 10
},
"IH2206": {
"name": "上证֤2206",
"code": "IH2206",
"exchg": "CFFEX",
"product": "IH",
"maxlimitqty": 20,
"maxmarketqty": 10
},
"IH2209": {
"name": "上证֤֤2209",
"code": "IH2209",
"exchg": "CFFEX",
"product": "IH",
"maxlimitqty": 20,
"maxmarketqty": 10
}
}
}
sessions.json
{
"TTS24":{
"name":"TTS24测试",
"offset": -480,
"auction":{
"from": 802,
"to": 803
},
"sections":[
{
"from": 803,
"to": 758
}
]
},
"ALLDAY":{
"name":"全天候盘",
"offset": -480,
"sections":[
{
"from": 800,
"to": 800
}
]
}
}
QFConfig.yaml
basefiles:
commodity: ./common/commodities.json
contract: ./common/contracts.json
holiday: ./common/holidays.json
session: ./common/sessions.json
broadcaster:
active: true
bport: 3997
broadcast:
- host: 255.255.255.255
port: 9001
type: 2
multicast_:
- host: 224.169.169.169
port: 9002
sendport: 8997
type: 0
- host: 224.169.169.169
port: 9003
sendport: 8998
type: 1
- host: 224.169.169.169
port: 9004
sendport: 8999
type: 2
allday: true # 使用全天候环境
parsers:
- active: true
broker:
id: tts24
module: ParserCTP
front: tcp://122.51.136.165:20004
localtime: true # 使用本地时间戳
ctpmodule: tts_thostmduserapi_se
pass: ******
user: ******
# code: SHFE.au2206,SHFE.au2208
filter: CFFEX
statemonitor: statemonitor.yaml
writer:
module: WtDataStorage
async: true
groupsize: 1000
path: ../Storage
savelog: false
这里提醒两点
QFConfig.yaml
文件一个是启用了allday
字段, 这样就可以避免状态机干扰测试环境- 在
parsers
中启用了localtime
字段, 这在使用openctp时是必须做的. writer
中设置数据保存目录是../Storage
, 这个必须和接下来的CTP仿真时数据读取目录保持一致
成功截图如下
若出现warning, 则将 "Storage" 文件目录删掉然后重新启动即可
重写CTA策略
为了方便调试, 我对 "WtStraDualThrust" 做了细微改动, 主要是打印回调记录, 源码如下
WtStraDualThrust.h
#pragma once
#include "../Includes/CtaStrategyDefs.h"
class WtStraDualThrust : public CtaStrategy
{
public:
WtStraDualThrust(const char* id);
virtual ~WtStraDualThrust();
public:
virtual const char* getFactName() override;
virtual const char* getName() override;
virtual bool init(WTSVariant* cfg) override;
virtual void on_schedule(ICtaStraCtx* ctx, uint32_t curDate, uint32_t curTime) override;
virtual void on_init(ICtaStraCtx* ctx) override;
virtual void on_tick(ICtaStraCtx* ctx, const char* stdCode, WTSTickData* newTick) override;
virtual void on_bar(ICtaStraCtx* ctx, const char* stdCode, const char* period, WTSBarStruct* newBar) override;
private:
//指标参数
double _k1;
double _k2;
uint32_t _days;
//数据周期
std::string _period;
//K线条数
uint32_t _count;
//合约代码
std::string _code;
bool _isstk;
};
WtStraDualThrust.h
#include "WtStraDualThrust.h"
#include "../Includes/ICtaStraCtx.h"
#include "../Includes/WTSContractInfo.hpp"
#include "../Includes/WTSVariant.hpp"
#include "../Includes/WTSDataDef.hpp"
#include "../Share/decimal.h"
extern const char* FACT_NAME;
//By Wesley @ 2022.01.05
#include "../Share/fmtlib.h"
WtStraDualThrust::WtStraDualThrust(const char* id)
: CtaStrategy(id)
{
}
WtStraDualThrust::~WtStraDualThrust()
{
}
const char* WtStraDualThrust::getFactName()
{
return FACT_NAME;
}
const char* WtStraDualThrust::getName()
{
return "DualThrust";
}
bool WtStraDualThrust::init(WTSVariant* cfg)
{
if (cfg == NULL)
return false;
_days = cfg->getUInt32("days");
_k1 = cfg->getDouble("k1");
_k2 = cfg->getDouble("k2");
_period = cfg->getCString("period");
_count = cfg->getUInt32("count");
_code = cfg->getCString("code");
_isstk = cfg->getBoolean("stock");
return true;
}
void WtStraDualThrust::on_schedule(ICtaStraCtx* ctx, uint32_t curDate, uint32_t curTime)
{
ctx->stra_log_info(fmt::format("回调 on_schedule, date: {}, time: {}", ctx->stra_get_date(), ctx->stra_get_time()).c_str());
// 如果本地无数据, 继续向下执行就会报错
return;
std::string code = _code;
if (_isstk)
code += "-";
WTSKlineSlice *kline = ctx->stra_get_bars(code.c_str(), _period.c_str(), _count, true);
if(kline == NULL)
{
//这里可以输出一些日志
return;
}
if (kline->size() == 0)
{
kline->release();
return;
}
uint32_t trdUnit = 1;
if (_isstk)
trdUnit = 100;
int32_t days = (int32_t)_days;
double hh = kline->maxprice(-days, -2);
double ll = kline->minprice(-days, -2);
WTSValueArray* closes = kline->extractData(KFT_CLOSE);
double hc = closes->maxvalue(-days, -2);
double lc = closes->minvalue(-days, -2);
double curPx = closes->at(-1);
closes->release();///!!!这个释放一定要做
double openPx = kline->at(-1)->open;
double highPx = kline->at(-1)->high;
double lowPx = kline->at(-1)->low;
double upper_bound = openPx + _k1 * (std::max(hh - lc, hc - ll));
double lower_bound = openPx - _k2 * std::max(hh - lc, hc - ll);
WTSCommodityInfo* commInfo = ctx->stra_get_comminfo(_code.c_str());
double curPos = ctx->stra_get_position(_code.c_str()) / trdUnit;
if(decimal::eq(curPos,0))
{
if(highPx >= upper_bound)
{
ctx->stra_enter_long(_code.c_str(), 2 * trdUnit, "DT_EnterLong");
//向上突破
ctx->stra_log_info(fmt::format("向上突破{}>={},多仓进场", highPx, upper_bound).c_str());
}
else if (lowPx <= lower_bound && !_isstk)
{
ctx->stra_enter_short(_code.c_str(), 2 * trdUnit, "DT_EnterShort");
//向下突破
ctx->stra_log_info(fmt::format("向下突破{}<={},空仓进场", lowPx, lower_bound).c_str());
}
}
//else if(curPos > 0)
else if (decimal::gt(curPos, 0))
{
if(lowPx <= lower_bound)
{
//多仓出场
ctx->stra_exit_long(_code.c_str(), 2 * trdUnit, "DT_ExitLong");
ctx->stra_log_info(fmt::format("向下突破{}<={},多仓出场", lowPx, lower_bound).c_str());
}
}
//else if(curPos < 0)
else if (decimal::lt(curPos, 0))
{
if (highPx >= upper_bound && !_isstk)
{
//空仓出场
ctx->stra_exit_short(_code.c_str(), 2 * trdUnit, "DT_ExitShort");
ctx->stra_log_info(fmt::format("向上突破{}>={},空仓出场", highPx, upper_bound).c_str());
}
}
ctx->stra_save_user_data("test", "waht");
//这个释放一定要做
kline->release();
}
void WtStraDualThrust::on_init(ICtaStraCtx* ctx)
{
ctx->stra_log_info(fmt::format("回调 on_init, date: {}, time: {}", ctx->stra_get_date(), ctx->stra_get_time()).c_str());
std::string code = _code;
if (_isstk)
code += "-";
// 获取 CFFEX.IC.2203 的tick数据
WTSTickSlice* ticks = ctx->stra_get_ticks(_code.c_str(), 30);
if (ticks)
ticks->release();
// 获取 CFFEX.IC.2203 的bar数据
WTSKlineSlice *kline1 = ctx->stra_get_bars(code.c_str(), _period.c_str(), _count, true);
if (kline1 == NULL)
{
//这里可以输出一些日志
return;
}
kline1->release();
// 主动订阅 CFFEX.IH.2203 tick数据
ctx->stra_sub_ticks("CFFEX.IH.2203");
}
void WtStraDualThrust::on_tick(ICtaStraCtx* ctx, const char* stdCode, WTSTickData* newTick)
{
//没有什么要处理
ctx->stra_log_info(fmt::format("回调 on_tick, code: {}, date: {}, time: {}", newTick->code(), ctx->stra_get_date(), ctx->stra_get_time()).c_str());
}
void WtStraDualThrust::on_bar(ICtaStraCtx* ctx, const char* stdCode, const char* period, WTSBarStruct* newBar)
{
//没有什么要处理
ctx->stra_log_info(fmt::format("回调 on_bar, code: {}, date: {}, time: {}", stdCode, ctx->stra_get_date(), ctx->stra_get_time()).c_str());
}
- 修改完之后记得重新生成 dll文件并放到 "WtRunner/cta" 目录下
- 确保 "WtRunner/executer" 下有CTA执行器dll文件 "WtExeFact.dll"
CTA策略配置
1."common" 文件夹内容和 "QuoteFactory/common" 保持一致即可
2.actpolicy.yaml
default:
order:
- action: close
limit: 0
- action: open
limit: 0
stockindex:
filters:
- CFFEX.IF
- CFFEX.IC
- CFFEX.IH
order:
- action: closeyestoday
limit: 0
- action: open
limit: 500
- action: closetoday
limit: 0
3.filters.yaml
code_filters:
CFFEX.IF0:
action: ignore
target: 0
strategy_filters:
Q3LS00_if0:
action: ignore
target: 0
4.executers.yaml
# 一个组合可以配置多个执行器,所以executers是一个list
executers:
- active: true #是否启用
id: exec #执行器id,不可重复
trader: tts24 #执行器绑定的交易通道id,如果不存在,无法执行
scale: 1 #数量放大倍数,即该执行器的目标仓位,是组合理论目标仓位的多少倍,可以为小数
policy: #执行单元分配策略,系统根据该策略创建对一个的执行单元
default: #默认策略,根据品种ID设置,如SHFE.rb,如果没有针对品种设置,则使用默认策略
name: WtExeFact.WtMinImpactExeUnit, #执行单元名称
offset: 0, #委托价偏移跳数
expire: 5, #订单超时没秒数
pricemode: 1, #基础价格模式,-1-己方最优,0-最新价,1-对手价
span: 500, #下单时间间隔(tick驱动的)
byrate: false, #是否按对手盘挂单量的比例挂单,配合rate使用
lots: 1, #固定数量
rate: 0 #挂单比例,配合byrate使用
clear: #过期主力自动清理配置
active: false #是否启用
excludes: #排除列表
# - CFFEX.IF
- CFFEX.IC
includes: #包含列表
# - SHFE.au
5.tdparsers.yaml
parsers:
- active: true
bport: 9001
host: 127.0.0.1
id: TTS24
module: ParserUDP
sport: 3997
6.tdtraders.yaml
traders:
- active: true
id: TTS24
module: TraderCTP
ctpmodule: tts_thosttraderapi_se
front: tcp://122.51.136.165:20002
broker: ""
user: ******
pass: ******
appid:
authcode:
quick: true
riskmon:
active: false
policy:
default:
order_times_boundary: 20
order_stat_timespan: 10
cancel_times_boundary: 20
cancel_stat_timespan: 10
cancel_total_limits: 470
7.config.yaml
#基础配置文件
basefiles:
utf-8: true
commodity: ./common/commodities.json #品种列表
contract: ./common/contracts.json #合约列表
holiday: ./common/holidays.json #节假日列表
hot: ./common/hots.json #主力合约映射表
session: ./common/sessions.json #交易时间模板
#数据存储
data:
store:
module: WtDataStorage #模块名
path: ../Storage/ #数据存储根目录
#环境配置
env:
name: cta #引擎名称:cta/hft/sel
product:
session: TTS24 #驱动交易时间模板,TRADING是一个覆盖国内全部交易品种的最大的交易时间模板,从夜盘21点到凌晨1点,再到第二天15:15,详见sessions.json
riskmon: #组合风控设置
active: false #是否开启
module: WtRiskMonFact #风控模块名,会根据平台自动补齐模块前缀和后缀
name: SimpleRiskMon #风控策略名,会自动创建对应的风控策略
#以下为风控指标参数,该风控策略的主要逻辑就是日内和多日的跟踪止损风控,如果回撤超过阈值,则降低仓位
base_amount: 5000000 #组合基础资金,WonderTrader只记录资金的增量,基础资金是用来模拟组合的基本资金用的,和增量相加得到动态权益
basic_ratio: 101 #日内高点百分比,即当日最高动态权益是上一次的101%才会触发跟踪侄止损
calc_span: 5 #计算时间间隔,单位s
inner_day_active: true #日内跟踪止损是否启用
inner_day_fd: 20.0 #日内跟踪止损阈值,即如果收益率从高点回撤20%,则触发风控
multi_day_active: false #多日跟踪止损是否启用
multi_day_fd: 60.0 #多日跟踪止损阈值
risk_scale: 0.3 #风控系数,即组合给执行器的目标仓位,是组合理论仓位的0.3倍,即真实仓位是三成仓
risk_span: 30 #风控触发时间间隔,单位s。因为风控计算很频繁,如果已经触发风控,不需要每次重算都输出风控日志,加一个时间间隔,友好一些
strategies:
cta: # 策略工厂配置
- active: true
id: cta_demo # 策略id
name: WtCtaStraFact.DualThrust # 策略名
params: # 策略参数
code: CFFEX.IC.2203
count: 30
period: m1
days: 30
k1: 0.6
k2: 0.6
stock: false
fees: ./common/fees.json #佣金配置文件
executers: executers.yaml #执行器配置文件
filters: filters.yaml #过滤器配置文件,这个主要是用于盘中不停机干预的
parsers: tdparsers.yaml #行情通达配置文件
traders: tdtraders.yaml #交易通道配置文件
bspolicy: actpolicy.yaml #开平策略配置文件
配置概述
- "tdparsers.yaml", 不在直接从仿真服务器获取数据, 而是本地端口获取数据, 这就是行情落地程序必须打开的直接原因.
- "tdparsers.yaml", 这里不再需要设置
localtime
字段. - "config.yaml", 也不在需要
allday
字段. - "config.yaml", 中的
strategies/name
字段策略名称必须和WtCtaStraFact.cpp
中的名称保持一致.
全部配置成功示例如下
不仅要保证 on_tick
回调成功, 更要保证分钟闭合后 on_schedule
和 on_bar
回调成功