债券双均线交易策略

X-Bond 交易围绕债券买卖报价、日内价格变化与持仓风险展开,交易者通过行情判断、头寸约束与止损机制管理日内敞口。本文先阐述策略的基本思路,再以双均线驱动的日内交易案例,说明数据准备、交易逻辑与回测分析方法。

1. 策略介绍

债券交易需统筹报价策略与持仓管理:买卖价差提供潜在收益,持仓则使交易者暴露于价格波动风险。行情判断决定交易倾向,风险约束决定可承担的头寸规模,二者共同影响交易决策。

1.1 核心因子

设第 k 个有效收盘样本为 Ck,快慢均线分别采用 10 个和 100 个样本计算移动平均线:

F k = 1 10 ∑ i = 0 9 C k − i F_k=\frac{1}{10}\sum_{i=0}^{9}C_{k-i} ,

S k = 1 100 ∑ i = 0 99 C k − i S_k=\frac{1}{100}\sum_{i=0}^{99}C_{k-i}

当有效样本数达到 100 个后,开始更新均线。首次获得有效的快慢均线值时,仅保存当前均线值作为前值,不产生交叉信号。

  • 多头信号:前一次快线不高于慢线,本次快线高于慢线,且最新收盘价不低于慢线加 0.005。

  • 空头信号:前一次快线不低于慢线,本次快线低于慢线,且最新收盘价不高于慢线减 0.005。

其中,0.005 表示以每百元债券报价为单位的绝对价格差值,并非收益率或百分比。

均线计算所使用的历史样本跨交易日保留,不会在每个交易日重新预热。行情按来源分组后,同一分钟可能产生多条 bar,而回调没有区分行情来源。因此,均线窗口严格来说分别为最近 10 个和 100 个回调样本;只有在每分钟恰好收到一条有效 bar 且数据连续的情况下,才分别对应 10 分钟和 100 分钟。

1.2 交易标的

本文案例所用的交易标的是银行间债券 250210。引擎使用的证券代码为 250210.T1,订单来源固定为 X-Bond。策略支持多空交易,并配置了债券借贷功能。订单数量按脚本中的盈亏公式采用面额口径,价格按每百元面额报价理解;实际使用时,需核对部署版本中债券数量的定义及相关计算口径。标的基本信息由脚本硬编码,不会自动从证券主数据中读取。

策略使用的信号价格并非直接来自成交分钟线,而是由快照行情构造:快照中的买卖一价均为正数时,取两者的中间价,否则取两者中的较大值,再聚合为一分钟 OHLC 数据。成交消息仅用于更新最近成交价,该价格用于估算快照中的浮动盈亏。

1.3 交易规则

策略按以下顺序处理快照、信号与日终平仓:

  1. 收到快照时,读取买卖一价和引擎当前持仓,以多头数量减空头持仓数量,计算净头寸。

  2. 当单日估算已实现盈亏与浮盈之和不大于 -10 万元时,触发当日停机;当累计估算已实现盈亏不大于 -100 万元时,触发永久停机。累计盈亏条件不包含当前浮动盈亏。

  3. 若已触发停机,则先提交平掉全部多头和空头持仓的订单,再结束本次快照回调。

  4. 未停机时,仅在 09:00:00 至 12:00:00(含端点)以及 13:30:00 至 16:25:00(不含端点)的时段内执行正常交易逻辑。午间休市及其他非交易时段,尚未消费的信号会继续保留。

  5. 收到多头信号时,先提交平空订单,再按卖一价提交开多订单;收到空头信号时,先提交平多订单,再按买一价提交开空订单。开仓数量固定为 5,000 万,提交前检查推算后的净头寸是否处于 -5 亿至 5 亿的范围内。

  6. 日终回调使用最近保存的买一价和卖一价,为全部多头和空头持仓提交平仓订单。

信号由 onBar 回调根据一分钟 bar 生成,订单在快照回调提交。同一次交叉信号不会在每次快照到达时重复触发开仓:信号在消费前会被清零。若未出现新的交叉,onBar 不会清除已有信号;若出现新的交叉,则新信号可以覆盖旧信号。需要注意,触发止损后,策略不会立即清除已有信号,而是优先执行停机分支中的平仓逻辑。永久停机后,正常交易分支不再执行。

2. 策略回测

本案例的回测输入由深度快照、快照聚合的一分钟 bar 和债券成交三类消息组成。回测前需要导入附件 CSV 文件,并加载 Backtest 和 MatchingEngineSimulator 插件。本案例已在 DolphinDB Server 3.00.6.1、Backtest 3.00.6.9 与 MatchingEngineSimulator 3.00.6.9 上运行。服务器需要安装与 DolphinDB Server 版本匹配的插件;创建引擎前,脚本会检查并加载所需插件。

2.1 准备数据

本案例使用附录提供的三份模拟数据。先将配套附件保存到 DolphinDB 服务器可读取的目录,再将路径变量修改为对应 CSV 文件在服务器上的实际路径。

snapshotCsvPath = "xbondsnapshot.csv"
ohlcCsvPath = "xbond_ohlc.csv"
transactionCsvPath = "xbond_transaction.csv"
snapshotSchema = table(["symbol", "timestamp", "datetime", "bidQty", "bidPrice", "askQty", "askPrice", "source", "preSettlementPrice", "prevPrice", "openPrice", "closePrice", "settlementPrice", "HighPrice", "LowPrice", "lastPrice", "volume", "turnover", "openInterest", "askYTM", "lastYTM", "bidYTM"] as name, ["STRING", "LONG", "TIMESTAMP", "LONG[]", "DOUBLE[]", "LONG[]", "DOUBLE[]", "STRING", "DOUBLE", "DOUBLE", "DOUBLE", "DOUBLE", "DOUBLE", "DOUBLE", "DOUBLE", "DOUBLE", "LONG", "DOUBLE", "LONG", "DOUBLE[]", "DOUBLE", "DOUBLE[]"] as type)
ohlcSchema = table(["symbol", "timestamp", "datetime", "source", "period", "priceType", "open", "high", "low", "close", "volume", "openYTM", "highYTM", "lowYTM", "closeYTM"] as name, ["STRING", "LONG", "TIMESTAMP", "STRING", "INT", "INT", "DOUBLE", "DOUBLE", "DOUBLE", "DOUBLE", "LONG", "DOUBLE", "DOUBLE", "DOUBLE", "DOUBLE"] as type)
transactionSchema = table(["timestamp", "symbol", "source", "qty", "price", "ytm", "openInterest", "turnover", "openClose", "marketOrderId", "direction"] as name, ["TIMESTAMP", "STRING", "STRING", "LONG", "DOUBLE", "DOUBLE", "LONG", "DOUBLE", "INT", "INT", "INT"] as type)
snapshot = loadText(snapshotCsvPath, ",", snapshotSchema, containHeader=true, arrayDelimiter=",")
ohlc = loadText(ohlcCsvPath, ",", ohlcSchema, containHeader=true)
txnMsg = loadText(transactionCsvPath, ",", transactionSchema, containHeader=true)

快照 CSV 中的买卖数量和价格字段以数组形式存储,导入时分别指定为 LONG[] 和 DOUBLE[]。价格按每百元面额报价,数量按本案例的模拟面额口径,收益率按百分数形式提供,并非根据债券价格通过定价模型反算得到。快照和一分钟 bar 数据均包含 symbol、source 和 datetime 字段,可据此识别对应的标的、行情来源和时间;成交数据则通过 symbol、source 和 timestamp 识别记录。上述字段用于标识数据记录,不代表已在表中建立数据库主键约束。

表 1. 表 2-1 快照模拟数据字段类型说明
字段 类型
symbol STRING
timestamp LONG
datetime TIMESTAMP
bidQty LONG[]
bidPrice DOUBLE[]
askQty LONG[]
askPrice DOUBLE[]
source STRING
preSettlementPrice DOUBLE
prevPrice DOUBLE
openPrice DOUBLE
closePrice DOUBLE
settlementPrice DOUBLE
HighPrice DOUBLE
LowPrice DOUBLE
lastPrice DOUBLE
volume LONG
turnover DOUBLE
openInterest LONG
askYTM DOUBLE[]
lastYTM DOUBLE
bidYTM DOUBLE[]

每根一分钟 bar 的 OHLC 均根据该分钟内的两条快照数据生成,volume 是该分钟内的快照条数,最后一根下午 bar 的时间为 16:30:00。

表 2. 表 2-2 OHLC 模拟数据字段类型说明
字段 类型
symbol STRING
timestamp LONG
datetime TIMESTAMP
source STRING
period INT
priceType INT
open DOUBLE
high DOUBLE
low DOUBLE
close DOUBLE
volume LONG
openYTM DOUBLE
highYTM DOUBLE
lowYTM DOUBLE
closeYTM DOUBLE

成交消息比对应快照晚 1 秒,成交数量固定为 100 万,成交价格取对应快照的买卖一档中间价,成交金额为 qty * price / 100。openInterest 与 openClose 均设为 0,direction=1 为模拟方向标签,marketOrderId 为递增 INT 编号。上述成交消息用于模拟市场成交行情,不代表策略订单的实际成交记录。

表 3. 表 2-3 成交模拟数据字段类型说明
字段 类型
timestamp TIMESTAMP
symbol STRING
source STRING
qty LONG
price DOUBLE
ytm DOUBLE
openInterest LONG
turnover DOUBLE
openClose INT
marketOrderId INT
direction INT

2.2 编写策略逻辑

策略由均线信号计算、初始化、盘前处理、分钟行情处理、快照交易处理、成交行情处理和盘后处理组成。分钟行情回调用于更新均线并生成交易信号;快照回调负责读取盘口、检查停机条件和交易时段,并根据待处理信号提交订单。成交行情回调仅更新最近成交价,不用于确认策略订单的成交结果。

均线信号函数 calcMaSignal

参数 closes 为 DOUBLE 向量,lastPx 与 entryFilter 为数值标量,prevFast 与 prevSlow 分别表示前一次计算得到的快、慢均线值,初始时可以为 NULL。由 onBar 调用,当有效收盘价样本不足 100 个时,函数不计算均线,并返回原有均线值;样本数达到 100 个后,计算最近 10 个和 100 个样本的均值。只有在前一次均线值已初始化的情况下,函数才判断金叉或死叉,并结合最新收盘价与慢均线之间的价差过滤信号。函数返回包含信号标志和更新后均线值的 dict(STRING, ANY) 字典。

def calcMaSignal(closes, lastPx, entryFilter, prevFast, prevSlow){
    out = dict(STRING, ANY)
    out["golden"] = false
    out["death"] = false
    out["prevFast"] = prevFast
    out["prevSlow"] = prevSlow
    if (size(closes) < 100) return out
    fast = avg(tail(closes, 10))
    slow = avg(tail(closes, 100))
    if (!isNull(prevFast) and !isNull(prevSlow)){
        if (prevFast <= prevSlow and fast > slow and lastPx >= slow + entryFilter) out["golden"] = true
        if (prevFast >= prevSlow and fast < slow and lastPx <= slow - entryFilter) out["death"] = true
    }
    out["prevFast"] = fast
    out["prevSlow"] = slow
    return out
}

初始化回调 initialize

唯一形参 context 为可变 DICTIONARY。引擎初始化时,回调将交易标的、单笔开仓数量、净持仓限额、盈亏停机阈值、信号过滤参数以及行情缓存、均线历史、盈亏估算值和停机标志等状态写入上下文。当前代码未创建独立的日志表。

def initialize(mutable context){
    context["tradeSym"] = "250210.T1"
    context["Lot"] = 50000000l
    context["MaxPos"] = 500000000l
    context["dayStop"] = -100000.0
    context["cumStop"] = -1000000.0
    context["filter"] = 0.005
    context["sl"] = 0.10
    context["tp"] = 0.20
    context["bid1"] = NULL
    context["ask1"] = NULL
    context["lastPx"] = NULL
    context["entryPx"] = NULL
    context["closes"] = array(DOUBLE, 0)
    context["prevFast"] = NULL
    context["prevSlow"] = NULL
    context["sig"] = 0
    context["dayPnl"] = 0.0
    context["cumPnl"] = 0.0
    context["dayHalt"] = false
    context["dead"] = false
}

盘前回调 beforeTrading

唯一形参 context 为可变 DICTIONARY。每日开盘前,回调将当日盈亏估算值清零,重置当日停机标志和待处理信号,并清空买一价、卖一价缓存。均线历史、前次均线值、最近成交价、入场价格锚点、累计盈亏估算值和永久停机标志均予以保留。

def beforeTrading(mutable context){
    context["dayPnl"] = 0.0
    context["dayHalt"] = false
    context["sig"] = 0
    context["bid1"] = NULL
    context["ask1"] = NULL
}

分钟行情回调 onBar

context 为可变 DICTIONARY,data 为引擎传入。回调从引擎传入的 bar 记录中读取 close,更新最近价格并追加到收盘价历史向量,再调用 calcMaSignal 计算均线和交叉信号。计算结果用于更新上下文中的前次均线值及待处理信号。该回调只生成信号,不直接提交订单。

def onBar(mutable context, data){
    context["lastPx"] = data.close
    context["closes"].append!(context["lastPx"])
    s = calcMaSignal(context["closes"], context["lastPx"], context["filter"], context["prevFast"], context["prevSlow"])
    context["prevFast"] = s["prevFast"]
    context["prevSlow"] = s["prevSlow"]
    if (s["golden"]) context["sig"] = 1
    if (s["death"]) context["sig"] = -1
}

成交行情回调 onTransaction

context 为可变 DICTIONARY,data 为成交记录。回调从成交记录中读取 price,更新上下文中的最近成交价。该价格用于后续浮盈估算;由于回调处理的是市场成交消息,而非策略订单成交确认,因此不会据此维护策略订单的实际成交成本或已实现盈亏。

def onTransaction(mutable context, data){
    context["lastPx"] = data.price
}

快照交易回调 onSnapshot

context 为可变 DICTIONARY,data 为快照记录。回调读取快照中的买卖一档价格并更新盘口缓存,然后查询当前多空持仓,估算浮动盈亏,并检查当日及累计盈亏停机条件。若触发当日或累计盈亏停机条件,则提交现有多空持仓的平仓订单并返回;否则,继续检查交易时段,并根据待处理的均线信号决定是否提交订单。

当日浮动盈亏根据最近成交价 lastPx、入场价格锚点 entryPx 和当前净持仓估算。若当日盈亏估算值与浮动盈亏之和不高于 dayStop,则触发当日停机;若累计盈亏估算值不高于 cumStop,则触发永久停机。触发停机后,回调会尝试为现有多空持仓提交平仓订单,并清空 entryPx。由于清空该变量和提交平仓订单均不代表订单已成交,实际持仓是否归零仍取决于撮合结果。

def onSnapshot(mutable context, data){
    bid1 = data.bidPrice[0]
    ask1 = data.askPrice[0]
    context["bid1"] = bid1
    context["ask1"] = ask1
    sym = context["tradeSym"]
    lot = context["Lot"]
    t = second(data.datetime)
    lastPx = context["lastPx"]

    pos = Backtest::getPosition(context["engine"], sym)
    longQ = long(pos.longPosition)
    shortQ = long(pos.shortPosition)
    net = longQ - shortQ
    upnl = 0.0
    if (net > 0 and !isNull(context["entryPx"])) upnl = (lastPx - context["entryPx"]) * double(net) / 100.0
    if (net < 0 and !isNull(context["entryPx"])) upnl = (context["entryPx"] - lastPx) * double(-net) / 100.0
    if (context["dayPnl"] + upnl <= context["dayStop"]) context["dayHalt"] = true
    if (context["cumPnl"] <= context["cumStop"]) context["dead"] = true

    // 强平:止损停机
    if (context["dead"] or context["dayHalt"]){
        if (longQ > 0){
            Backtest::submitOrder(context["engine"], dict(`symbol`source`qty`orderType`direction`openClose`price, (sym, "XBOND", longQ, 5, 2, 2, bid1)), "强平")
            if (!isNull(context["entryPx"])) {
                pnl = (bid1 - context["entryPx"]) * double(longQ) / 100.0
                context["dayPnl"] += pnl
                context["cumPnl"] += pnl
            }
        }
        if (shortQ > 0){
            Backtest::submitOrder(context["engine"], dict(`symbol`source`qty`orderType`direction`openClose`price, (sym, "XBOND", shortQ, 5, 1, 2, ask1)), "强平")
            if (!isNull(context["entryPx"])) {
                pnl = (context["entryPx"] - ask1) * double(shortQ) / 100.0
                context["dayPnl"] += pnl
                context["cumPnl"] += pnl
            }
        }
        context["entryPx"] = NULL
        return
    }

    if (!((t >= 09:00:00 and t <= 12:00:00) or (t >= 13:30:00 and t < 16:25:00))) return

    // 均线信号:先平反向,再开 Lot
    sig = context["sig"]
    if (sig == 0) return
    context["sig"] = 0

    if (sig == 1){
        if (shortQ > 0){
            Backtest::submitOrder(context["engine"], dict(`symbol`source`qty`orderType`direction`openClose`price, (sym, "XBOND", shortQ, 5, 1, 2, ask1)), "平空")
            if (!isNull(context["entryPx"])) {
                pnl = (context["entryPx"] - ask1) * double(shortQ) / 100.0
                context["dayPnl"] += pnl
                context["cumPnl"] += pnl
            }
            context["entryPx"] = NULL
            net = longQ
        }
        if (net + lot <= context["MaxPos"]){
            Backtest::submitOrder(context["engine"], dict(`symbol`source`qty`orderType`direction`openClose`price, (sym, "XBOND", lot, 5, 1, 1, ask1)), "开多")
            context["entryPx"] = ask1
        }
    }
    if (sig == -1){
        if (longQ > 0){
            Backtest::submitOrder(context["engine"], dict(`symbol`source`qty`orderType`direction`openClose`price, (sym, "XBOND", longQ, 5, 2, 2, bid1)), "平多")
            if (!isNull(context["entryPx"])) {
                pnl = (bid1 - context["entryPx"]) * double(longQ) / 100.0
                context["dayPnl"] += pnl
                context["cumPnl"] += pnl
            }
            context["entryPx"] = NULL
            net = -shortQ
        }
        if (net - lot >= -context["MaxPos"]){
            Backtest::submitOrder(context["engine"], dict(`symbol`source`qty`orderType`direction`openClose`price, (sym, "XBOND", lot, 5, 2, 1, bid1)), "开空")
            context["entryPx"] = bid1
        }
    }
}

盘后回调 afterTrading

唯一形参 context 为可变 DICTIONARY。回调查询当前多空持仓,并使用上下文中缓存的买一价和卖一价提交平仓订单,随后清空入场价格锚点。该回调不等待订单成交,也不更新当日或累计盈亏估算值,因此盘后提交平仓订单并不代表持仓已经全部平掉。

def afterTrading(mutable context){
    sym = context["tradeSym"]
    pos = Backtest::getPosition(context["engine"], sym)
    if (long(pos.longPosition) > 0)
        Backtest::submitOrder(context["engine"], dict(`symbol`source`qty`orderType`direction`openClose`price, (sym, "XBOND", long(pos.longPosition), 5, 2, 2, context["bid1"])), "收盘")
    if (long(pos.shortPosition) > 0)
        Backtest::submitOrder(context["engine"], dict(`symbol`source`qty`orderType`direction`openClose`price, (sym, "XBOND", long(pos.shortPosition), 5, 1, 2, context["ask1"])), "收盘")
    context["entryPx"] = NULL
}

本案例中的多头浮盈按 (lastPx - entryPx) * net / 100 估算,空头浮盈按反向价差乘以空头面额再除以 100 估算。平仓盈亏估算分别使用买一价或卖一价。上述估算未扣除佣金、债券借贷费及其他交易成本,且其准确性取决于入场价格锚点与实际持仓是否一致。

2.3 设置回测参数

以下配置包括标的池、数据读取范围和回测引擎参数。universe 是长度为 1 的 ANY 向量,其元素为交易标的;数据库查询使用不含结算后缀的 SYMBOL 向量 codes,而标的池和债券基础信息使用带后缀的代码。迁移至其他标的时,需同步修改相关配置和基础信息。

// 设置标的池
universe = array(ANY, 1)
universe[0] = ["250210.T1"]
// 设置回测日期、数据读取时段及引擎参数
begt = 2026.05.18
endt = 2026.05.29
config = dict(STRING, ANY)
config["startDate"]=begt; 
config["endDate"]=endt;
config["strategyGroup"]="FICC"
config["cash"]=1e10
config["dataType"]=3 
config["matchingMode"]=3 
config["universe"]=universe
config["enableBondLending"]=true
config["outputOrderInfo"]=true
  • begt、endt:DATE 标量,分别为数据查询和回测的起始日期与结束日期。本例设置为 2026 年 5 月 18 日至 2026 年 5 月 29 日。

  • codes:SYMBOL 向量,用于筛选数据库中的债券数据。本例仅包含 250210。

  • startDate、endDate:DATE,分别引用 begt 和 endt,确保回测日期与数据查询范围一致。

  • strategyGroup:STRING,表示策略分组标识,本例设置为 “FICC”。

  • cash:DOUBLE,初始资金,设置为 100 亿元。

  • dataType:INT,行情数据类型配置。本例设置为 3,其具体含义应以当前 Backtest 版本的接口定义为准。

  • matchingMode:INT,撮合模式配置。本例设置为 3(表示见价成交模式,此模式不使用模拟撮合引擎,而是回测本身立即成交)。具体成交规则应以当前 Backtest 版本的接口定义为准。

  • universe:标的池,ANY 向量,本例仅包含 250210.T1 标的组。

  • enableBondLending:BOOL,设置为 true,表示允许债券 openClose 为 2。

  • outputOrderInfo:BOOL,设置为 true,表示在订单交易明细中输出风控日志。

基础信息表完整配置如下:

bondRef = table(1:0,
    `symbol`multiplier`priceTick`couponRate`frequency`valueDate`maturityDate`bondLendingRate`commission`marginRatio`bondType`issuePrice`faceValue,
    [STRING,DOUBLE,DOUBLE,DOUBLE,INT,DATE,DATE,DOUBLE,DOUBLE,DOUBLE,STRING,DOUBLE,DOUBLE])
insert into bondRef values("250210.T1",100.0,0.0001,1.80,1,2025.04.02,2035.04.02,0.0,0.0001,0.0,"FixedRateBond",100.0,100.0)
securityReference = {"xbond": bondRef}

2.4 创建回测引擎

将前文定义的回调函数注册到 callbacks 字典中,再调用 Backtest::createBacktester 创建回测引擎。创建前,脚本会尝试删除同名引擎,因此在同一节点上运行多个回测任务时,应使用不同的引擎名称,避免相互干扰。

// 注册策略回调
callbacks = dict(STRING, ANY)
callbacks["initialize"]=initialize
callbacks["beforeTrading"]=beforeTrading
callbacks["onSnapshot"]=onSnapshot
callbacks["onBar"]=onBar
callbacks["onTransaction"]=onTransaction
callbacks["afterTrading"]=afterTrading
// 创建回测引擎
strategyName = "XBOND_intraday2"
try{ Backtest::dropBacktestEngine(strategyName) }catch(ex){}
go
engine = Backtest::createBacktester(strategyName, config, callbacks, false, securityReference)

createBacktester 的第四个参数设置为 false,表示本例采用非 JIT 模式;securityReference 用于提供债券基础信息。

2.5 执行回测

将 2.1 节准备好的 snapshot、ohlc、transaction 数据传入 msg 字典,并通过 Backtest::appendQuotationMsg 提交给回测引擎。

所有行情提交完成后,调用 Backtest::appendEndMarker 发送结束标记,以结束行情输入并触发盘后处理。

msg = dict(STRING, ANY)
msg["snapshot"] = snapshot
msg["ohlc"] = ohlc
msg["transaction"] = txnMsg
Backtest::appendQuotationMsg(engine, msg)
Backtest::appendEndMarker(engine)

2.6 获取回测结果

行情提交结束后,通过以下接口查询回测交易明细、收益统计、每日组合权益和当前持仓,并将结果保存到会话变量中。

tradeDetails = select * from Backtest::getTradeDetails(engine)
returnSummary = Backtest::getReturnSummary(engine)
TotalPortfolios = Backtest::getDailyTotalPortfolios(engine)
//print(TotalPortfolios)
position = Backtest::getPosition(engine)

各接口的详细说明参见 Backtest 结果查询接口。

3. 回测结果分析

在上述软件版本和非 JIT 模式下,使用附录提供的模拟数据进行回测。本次回测处理 8,640 条行情记录,初始权益为 10,000,000,000.00 元,区间总收益为 -1.0417%。

由于输入数据为模拟数据,且回测结果受行情生成方式、成交价格假设和成本配置影响,这些指标仅用于说明策略在本案例数据上的运行结果,不能据此评价策略在真实市场中的表现。

表 4. 表 3-1 回测结果
指标 本次结果 解读
区间总收益 -1.0417% 期末权益相对初始资金的累计变化
年化收益 -23.1929% 引擎按回测日收益年化后的结果
年化波动率 0.8445% 价格路径波动幅度
夏普比率 -27.4619 去掉风险波动后的收益
最大回撤 1.0417% 期内净值相对先前高点的最大下跌
回撤比率 -22.2655 引擎返回的收益与回撤相关统计
日胜率 0% 每日收益为正的交易日占比

4. 总结

本案例以债券快照生成的一分钟价格序列计算双均线方向信号,并在快照回调中根据买卖一档价格提交开平仓订单。策略通过当日和累计盈亏阈值控制停机,并将均线历史、行情缓存、入场价格锚点及风险状态保存在上下文中,展示了从行情输入、信号生成、订单提交到回测结果查询的完整流程。

需要注意的是,策略中的盈亏和停机判断基于估算值,平仓订单提交也不代表订单已成交。将该示例用于真实历史数据研究前,应结合实际数据核对信号与行情的时间对应关系、成交价格和交易成本口径、未成交订单对持仓限额的影响,以及日终平仓订单的成交情况。