HFT仿真进阶2: 策略初始化
source: wtcpp/folder03/file04.md
既然是进阶篇, 基础的内容就不赘述了. 本文的环境配置承接上篇环境准备篇
解决目标
本文主要解决的问题是策略初始化时 on_init
中调用的三个方法及相关的判断逻辑
ctx->stra_get_ticks
ctx->stra_sub_ticks
ctx->stra_get_bars
stra_get_ticks
1.加载Tick切片数据路径
HftStraBaseCtx::stra_get_ticks
-> WtEngine::get_tick_slice
-> WtDtMgr::get_tick_slice
-> WtDataReader::readTickSlice
2.WtDataReader
加载Tick数据逻辑
// 加载Tick切片数据
WTSTickSlice* WtDataReader::readTickSlice(const char* stdCode, uint32_t count, uint64_t etime /* = 0 */)
{
// 1. 获取合约标准码(合约类)
CodeHelper::CodeInfo cInfo = CodeHelper::extractStdCode(stdCode);
// 2. 获取合约对应的商品信息
WTSCommodityInfo* commInfo = _base_data_mgr->getCommodity(cInfo._exchg, cInfo._product);
// 3. 获取合约名称
std::string stdPID = StrUtil::printf("%s.%s", cInfo._exchg, cInfo._product);
// 4. 获取当前时间, 精确到毫秒
uint32_t curDate, curTime, curSecs;
if (etime == 0)
{
curDate = _sink->get_date();
curTime = _sink->get_min_time();
curSecs = _sink->get_secs();
etime = (uint64_t)curDate * 1000000000 + curTime * 100000 + curSecs;
}
else
{
//20190807124533900
curDate = (uint32_t)(etime / 1000000000);
curTime = (uint32_t)(etime % 1000000000) / 100000;
curSecs = (uint32_t)(etime % 100000);
}
/*
如果不指定etime, 默认为 0, curDate和curTime是最新时间和日期,
endTDate 会获取最新时间的交易日期
curTDate也会获取最新时间的交易日期
如果指定e_time, curDate和curTime是历史时间和日期
endTDate 是历史时间的交易日期
curTDate 是最新时间的交易日期
之后如果两者不在同一天则从dsb加载历史Tick数据, 若在同一天则从 dmb 加载缓存Tick数据
*/
uint32_t endTDate = _base_data_mgr->calcTradingDate(stdPID.c_str(), curDate, curTime, false);
uint32_t curTDate = _base_data_mgr->calcTradingDate(stdPID.c_str(), 0, 0, false);
bool isToday = (endTDate == curTDate);
// 6. 主力合约单独处理
std::string curCode = cInfo._code;
if (cInfo.isHot() && commInfo->isFuture())
curCode = _hot_mgr->getRawCode(cInfo._exchg, cInfo._product, endTDate);
else if (cInfo.isSecond() && commInfo->isFuture())
curCode = _hot_mgr->getSecondRawCode(cInfo._exchg, cInfo._product, endTDate);
//比较时间的对象
WTSTickStruct eTick;
eTick.action_date = curDate;
eTick.action_time = curTime * 100000 + curSecs;
// 如果在同一天(盘中只有dmb缓存文件)
if (isToday)
{
// 7. 获取dmb文件的Tick数据映射, 如果没有就返回NULL, 如果有就返回切片数据
TickBlockPair* tPair = getRTTickBlock(cInfo._exchg, curCode.c_str());
if (tPair == NULL)
return NULL;
// ... 代码略
}
// 若不在同一天(收盘后会有dsb历史文件)
else
{
std::string key = StrUtil::printf("%s-%d", stdCode, endTDate);
// 9. 从dsb中加载数据切片
auto it = _his_tick_map.find(key);
// 代码略...
3.加载完Tick数据之后
如果加载成功, 就通过引擎订阅tick行情, 如果没有tick数据则tick行情不会被订阅
WTSTickSlice* HftStraBaseCtx::stra_get_ticks(const char* stdCode, uint32_t count)
{
WTSTickSlice* ticks = _engine->get_tick_slice(_context_id, stdCode, count);
if (ticks)
_engine->sub_tick(id(), stdCode);
return ticks;
}
stra_sub_ticks
stra_sub_ticks
是主动订阅Tick行情, 但是和通过 stra_get_ticks
被动订阅行情略有不同, stra_sub_ticks
会首先将品种名称添加到 _tick_subs
中, 如此在策略其他地方都可以通过 ctx->
接口调用
void HftStraBaseCtx::stra_sub_ticks(const char* stdCode)
{
/*
* By Wesley @ 2022.03.01
* 主动订阅tick会在本地记一下
* tick数据回调的时候先检查一下
*/
_tick_subs.insert(stdCode);
_engine->sub_tick(id(), stdCode);
log_info("Market Data subscribed: %s", stdCode);
}
stra_get_bars
1.加载Bar切片数据路径
HftStraBaseCtx::stra_get_bars
-> WtEngine::get_kline_slice
-> WtDtMgr::get_kline_slice
-> WtDataReader::readKlineSlice
和Tick数据加载路径一致
2.WtDataReader
加载Bar数据逻辑
// 获取K线数据切片
WTSKlineSlice* WtDataReader::readKlineSlice(const char* stdCode, WTSKlinePeriod period, uint32_t count, uint64_t etime /* = 0 */)
{
// 获取合约类, 商品信息, 标准码
CodeHelper::CodeInfo cInfo = CodeHelper::extractStdCode(stdCode);
WTSCommodityInfo* commInfo = _base_data_mgr->getCommodity(cInfo._exchg, cInfo._product);
std::string stdPID = StrUtil::printf("%s.%s", cInfo._exchg, cInfo._product);
// 判断是否可以加载历史Bar数据
std::string key = StrUtil::printf("%s#%u", stdCode, period);
auto it = _bars_cache.find(key);
bool bHasHisData = false;
if (it == _bars_cache.end())
{
/*
* By Wesley @ 2021.12.20
* 先从extloader加载最终的K线数据(如果是复权)
* 如果加载失败,则再从文件加载K线数据
*/
bHasHisData = cacheFinalBarsFromLoader(key, stdCode, period);
// 1. 判断是否可以从文件正常加载 dsb 数据(这一步会填充_bars_cache)
if(!bHasHisData)
bHasHisData = cacheHisBarsFromFile(key, stdCode, period);
}
else
{
bHasHisData = true;
}
// endTDate 和 Tick数据同样的的判断逻辑
uint32_t curDate, curTime;
// ...代码略
//是否获取最新数据
bool bHasToday = (endTDate == curTDate);
// 主力合约判断逻辑
if (cInfo.isHot() && commInfo->isFuture())
// ...代码略
// 若在同一天, 则读取 dmb 数据
if (bHasToday)
{
WTSBarStruct bar;
bar.date = curDate;
bar.time = (curDate - 19900000) * 10000 + curTime;
const char* curCode = _bars_cache[key]._raw_code.c_str();
// 2. 读取实时的 dmb 数据
RTKlineBlockPair* kPair = getRTKilneBlock(cInfo._exchg, curCode, period);
if (kPair != NULL)
{
//读取当日的数据
WTSBarStruct* pBar = NULL;
// ...代码略
}
}
// 3. 缓存数据不够, 判断需要从历史数据加载多少
if (left > 0 && bHasHisData)
{
hisCnt = left;
//历史数据,直接从缓存的历史数据尾部截取
BarsList& barList = _bars_cache[key];
hisCnt = min(hisCnt, (uint32_t)barList._bars.size());
hisHead = &barList._bars[barList._bars.size() - hisCnt];//indexBarFromCache(key, etime, hisCnt, period == KP_DAY);
}
pipe_reader_log(_sink, LL_DEBUG, "His {} bars of {} loaded, {} from history, {} from realtime", PERIOD_NAME[period], stdCode, hisCnt, rtCnt);
// 3. 返回数据切片
if (hisCnt + rtCnt > 0)
{
// 3.1 创建历史数据切片
WTSKlineSlice* slice = WTSKlineSlice::create(stdCode, period, 1, hisHead, hisCnt);
// 3.2 添加缓存数据切片
if (rtCnt > 0)
slice->appendBlock(rtHead, rtCnt);
return slice;
}
return NULL;
}
Bar数据加载逻辑和tick数据加载逻辑非常相似, 应该很容易理解.
- 加载完Bar数据之后, 如果成功, 也会通过引擎订阅tick行情
WTSKlineSlice* HftStraBaseCtx::stra_get_bars(const char* stdCode, const char* period, uint32_t count)
{
std::string basePeriod = "";
uint32_t times = 1;
if (strlen(period) > 1)
{
basePeriod.append(period, 1);
times = strtoul(period + 1, NULL, 10);
}
else
{
basePeriod = period;
}
// 加载Bar数据
WTSKlineSlice* ret = _engine->get_kline_slice(_context_id, stdCode, basePeriod.c_str(), count, times);
// 订阅tick
if (ret)
_engine->sub_tick(id(), stdCode);
return ret;
}
总结
stra_get_ticks
和stra_get_bars
会分别从历史dsb和当天dmb文件加载数据, 没有数据则会加载失败stra_get_ticks
和stra_get_bars
如果加载数据成功则会被动订阅Tick行情stra_sub_ticks
主动订阅某品种Tick行情, 且会保留在订阅列表中