《最简回测框架实现》 中实现了最简框架,在该基础上,进行一些改进。

输入输出优化

输入输出的标准化及可读易用,是回测服务平台化的必要条件。

此前策略回调函数通过返回 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
// 2.0 版,相比 1.0 版本,形式上为声明式编程
// 通过直接调用账户的开仓平仓方法达成交易触发目的
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)
}
}),
}

// 以下为 1.0 版代码
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


至此已实现回测系统所需的基础功能支持及数据展示,后续专注策略研发即可。