在 《最简回测框架实现》 中实现了最简框架,在该基础上,进行一些改进。
输入输出优化
输入输出的标准化及可读易用,是回测服务平台化的必要条件。
此前策略回调函数通过返回 tradingItems
来实现交易操作的记录,这不如直接提供「买入」「卖出」「平仓」方法来得直观
可以用面向对象的方式,实现 StockAccount
类,在 AccountInfo
协议的基础上,提供「交易提交」「平仓」方法
1 2 3 4 5 6 7 8 9
| export class StockAccount implements AccountInfo { ……
public submitTrading(...tradingItems: TradingItem[]) { …… }
public closePosition(price: number, date: string) { …… } }
|
策略定义,结构体包含策略 ID 及执行函数。策略执行方法可持有 BackTesting
对象,进而利用其工具方法,如 testMA
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| export interface StrategyPerformer { strategy: Strategy handler: (backTesting: BackTesting) => Promise<RawStrategyResult> }
export class BackTesting { …… public async performStrategy(performer: StrategyPerformer) { …… const { accountInfo, kLines } = await performer.handler(this) const params: StrategyResultParams = { …… } return params } }
|
策略开发者根据 StrategyPerformer 结构实现策略执行方法即可,如此前提及的 MA20 均线策略代码的实现如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
|
export const SP_MA20_1: StrategyPerformer = { strategy: Strategy.MA20_1, handler: (backTesting: BackTesting) => backTesting.testMA({ period: KLinePeriod.DAY, length: 20 }, ({ kline, curMA, account }) => { if (kline.close > curMA && account.position <= 0) { account.submitTrading({ date: kline.time, action: 'BUY', price: kline.close, quantity: 1 }) } else if (kline.close < curMA && account.position > 0) { account.closePosition(kline.close, kline.time) } }), }
const backTesting = new BackTesting({ stockCode: 'HSImain', startTime: '2022-01-01', endTime: '2024-01-24' }) const accountInfo = await backTesting.testMA( { period: KLinePeriod.DAY, length: 20 }, ({ kline, curMA, account }) => { const tradingItems: TradingItem[] = [] if (kline.close > curMA && account.position <= 0) { tradingItems.push({ date: kline.time, action: 'BUY', price: kline.close, quantity: 1 }) } else if (kline.close < curMA && account.position > 0) { tradingItems.push({ date: kline.time, action: 'SELL', price: kline.close, quantity: account.position }) } return tradingItems } ) console.info(accountInfo) console.info(`Trade ${accountInfo.tradingItems.length} times`)
|
平台数据联动
回测程序持续运行,通过图形化界面维护测试计划及相关结果
执行代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
| export class QuantSystem { public static async getTestingStockCodeList() { const items = await StockDatawich.getAllRecords<DatawichStockInfo>(RetainStockModelId.Stock, { [`${RetainStockModelId.Stock}.tags.$includeAny`]: 'BackTesting', }) return items.map((item) => item.code) }
public static async getStrategyPlanList() { return await StockDatawich.getAllRecords<StrategyPlan>(RetainStockModelId.strategyPlan) }
public static async getStrategyResultList(planId: string) { return await StockDatawich.getAllRecords<StrategyResult>(RetainStockModelId.strategyResult, { [`${RetainStockModelId.strategyResult}.plan_id`]: planId, }) }
public static getPerformerForStrategy(strategy: Strategy) { switch (strategy) { case Strategy.MA20_1: return SP_MA20_1 case Strategy.MA20_2: return SP_MA20_2 case Strategy.MA20_3: return SP_MA20_3 } }
public static async performAllTestingPlans(forceRefresh?: boolean) { const codeList = await this.getTestingStockCodeList() const planList = await this.getStrategyPlanList()
for (const plan of planList) { let todoCodeList = codeList if (!forceRefresh) { const resultList = await this.getStrategyResultList(plan.plan_id) const markedMap = resultList.reduce((result, cur) => { result[cur.code] = true return result }, {}) todoCodeList = codeList.filter((code) => !markedMap[code]) }
const performer = this.getPerformerForStrategy(plan.strategy)
for (const stockCode of todoCodeList) { const backTesting = new BackTesting({ stockCode: stockCode, startTime: plan.start_date, endTime: plan.end_date, })
const params = await backTesting.performStrategy(performer) params.plan_id = plan.plan_id await StockDatawich.createRecord(RetainStockModelId.strategyResult, params) } } } }
|
程序持续/定时运行 QuantSystem.performAllTestingPlans
。
至此已实现回测系统所需的基础功能支持及数据展示,后续专注策略研发即可。