策略报告
运行策略脚本后,Navi 会生成一份 StrategyReport,包含性能指标、交易历史、权益曲线和配置详情。
报告结构
StrategyReport 是 instance.strategy_report() 返回的顶层类型:
rust
pub struct StrategyReport {
/// 策略配置快照(初始资金、手续费等)
pub config: StrategyConfigReport,
/// 所有交易的综合性能指标
pub performance_all: PerformanceMetrics,
/// 仅多头交易的性能指标
pub performance_long: PerformanceMetrics,
/// 仅空头交易的性能指标
pub performance_short: PerformanceMetrics,
/// 已平仓交易列表(按平仓时间排序)
pub closed_trades: Vec<ClosedTradeReport>,
/// 当前未平仓(浮动盈亏)的交易
pub open_trades: Vec<OpenTradeReport>,
/// 每根 K 线的权益值(索引 0 = 第一根 K 线)
pub equity_curve: Vec<f64>,
/// 每根 K 线相对峰值权益的回撤(始终 ≥ 0)
pub drawdown_curve: Vec<f64>,
/// 每根 K 线的买入持有权益(initial_capital × close / first_close)
pub buy_hold_curve: Vec<f64>,
/// 用于计算 Sharpe/Sortino 的日回报率序列
pub daily_returns: Vec<DailyReturnReport>,
/// 从首次入场到最后一笔平仓的时间范围(无已平仓交易时为 None)
pub trading_range: Option<TradingRangeReport>,
}策略配置
StrategyConfigReport 记录了策略在 strategy() 中声明的参数:
| 字段 | 类型 | 说明 |
|---|---|---|
initial_capital | f64 | 初始账户资金 |
commission_type | CommissionType | PerContract、PerTrade 或 PercentOfValue |
commission_value | f64 | 对应手续费类型的手续费金额 |
margin_long | f64 | 多头保证金要求(%) |
margin_short | f64 | 空头保证金要求(%) |
slippage | f64 | 订单滑点(以 tick 为单位) |
pyramiding | u32 | 同方向最大同时入场次数 |
risk_free_rate | f64 | 用于 Sharpe/Sortino 的年化无风险利率(%) |
process_orders_on_close | bool | 在当根 K 线收盘而非次根开盘时成交订单 |
calc_on_order_fills | bool | 每笔订单成交后重新执行脚本 |
close_entries_rule | CloseEntriesRule | Fifo 或 Any |
性能指标
PerformanceMetrics 分三种维度计算:所有交易(performance_all)、仅多头(performance_long)、仅空头(performance_short)。
以下字段仅在 performance_all 中有意义 — 它们基于整体权益曲线,无法按方向拆分。在 performance_long 和 performance_short 中这些字段固定为 0.0 或 None:
max_drawdown/max_drawdown_percentmax_runup/max_runup_percentbuy_hold_return/buy_hold_return_percentsharpe_ratio/sortino_rationum_even_trades
盈亏
| 字段 | 说明 |
|---|---|
net_profit | 以账户货币计的净利润 |
net_profit_percent | 净利润占初始资金的百分比 |
gross_profit | 获利交易的利润总和 |
gross_profit_percent | 总利润百分比 |
gross_loss | 亏损交易的亏损总和(正数) |
gross_loss_percent | 总亏损百分比 |
profit_factor | 总利润 ÷ 总亏损(无亏损交易时为 None) |
buy_hold_return | 买入持有的收益(账户货币,仅 performance_all) |
buy_hold_return_percent | 买入持有收益百分比(仅 performance_all) |
回撤与上升
仅在
performance_all中有值,在performance_long/performance_short中始终为0.0。
| 字段 | 说明 |
|---|---|
max_drawdown | 最大权益回撤(账户货币) |
max_drawdown_percent | 最大回撤百分比 |
max_runup | 最大权益上升(账户货币) |
max_runup_percent | 最大上升百分比 |
风险调整收益
仅在
performance_all中有值,在performance_long/performance_short中始终为None。
| 字段 | 说明 |
|---|---|
sharpe_ratio | 年化 Sharpe 比率(数据不足时为 None) |
sortino_ratio | 年化 Sortino 比率(数据不足时为 None) |
交易统计
| 字段 | 说明 |
|---|---|
total_closed_trades | 已完成的交易总数 |
total_open_trades | 回测结束时未平仓的交易数 |
num_winning_trades | 盈利(profit > 0)的交易数 |
num_losing_trades | 亏损(profit < 0)的交易数 |
num_even_trades | 平本(profit == 0)的交易数(仅 performance_all,其余始终为 0) |
percent_profitable | 获利交易数 ÷ 总交易数 × 100(无已平仓交易时为 None) |
平均交易
| 字段 | 说明 |
|---|---|
avg_trade | 每笔交易的平均盈亏 |
avg_trade_percent | 每笔交易的平均盈亏百分比 |
avg_winning_trade | 获利交易的平均利润 |
avg_winning_trade_percent | 获利交易平均利润百分比 |
avg_losing_trade | 亏损交易的平均亏损 |
avg_losing_trade_percent | 亏损交易平均亏损百分比 |
ratio_avg_win_loss | 平均获利 ÷ 平均亏损(无亏损交易时为 None) |
largest_winning_trade | 单笔最大利润 |
largest_winning_trade_percent | 单笔最大利润百分比 |
largest_losing_trade | 单笔最大亏损 |
largest_losing_trade_percent | 单笔最大亏损百分比 |
持仓时间
| 字段 | 说明 |
|---|---|
avg_bars_in_trades | 所有交易的平均持仓 K 线数 |
avg_bars_in_winning_trades | 获利交易的平均持仓 K 线数 |
avg_bars_in_losing_trades | 亏损交易的平均持仓 K 线数 |
其他
| 字段 | 说明 |
|---|---|
commission_paid | 已支付的手续费总额 |
max_contracts_held | 同时持仓的最大合约/股数 |
margin_calls | 触发的追缴保证金次数 |
交易历史
已平仓交易
report.closed_trades 中每条 ClosedTradeReport 对应一笔已完成的交易:
| 字段 | 类型 | 说明 |
|---|---|---|
trade_num | usize | 交易编号(从 0 开始) |
entry_id | String | 入场订单 ID(如 "Long Entry") |
entry_comment | String | 入场订单备注 |
entry_side | Direction | Long 或 Short |
entry_price | f64 | 入场成交价 |
entry_bar | usize | 入场成交的 K 线索引 |
entry_time | i64 | 入场时间戳(Unix 毫秒) |
exit_id | String | 出场订单 ID |
exit_comment | String | 出场订单备注 |
exit_price | f64 | 出场成交价 |
exit_bar | usize | 出场成交的 K 线索引 |
exit_time | i64 | 出场时间戳(Unix 毫秒) |
quantity | f64 | 交易数量(合约/股数) |
profit | f64 | 已实现盈亏(扣除手续费) |
profit_percent | f64 | 已实现盈亏百分比(相对入场价值) |
cumulative_profit | f64 | 累计盈亏(含本笔交易) |
cumulative_profit_percent | f64 | 累计盈亏百分比(相对初始资金) |
max_runup | f64 | 最大顺向波动(账户货币) |
max_runup_percent | f64 | 最大顺向波动百分比 |
max_drawdown | f64 | 最大逆向波动(账户货币) |
max_drawdown_percent | f64 | 最大逆向波动百分比 |
commission | f64 | 本笔交易的手续费总额(入场 + 出场) |
未平仓交易
report.open_trades 中每条 OpenTradeReport 对应一个未结仓位:
| 字段 | 类型 | 说明 |
|---|---|---|
trade_num | usize | 交易编号(从 0 开始) |
entry_id | String | 入场订单 ID |
entry_comment | String | 入场订单备注 |
entry_side | Direction | Long 或 Short |
entry_price | f64 | 入场成交价 |
entry_bar | usize | 入场成交的 K 线索引 |
entry_time | i64 | 入场时间戳(Unix 毫秒) |
quantity | f64 | 持仓数量(合约/股数) |
profit | f64 | 当前浮动盈亏(扣除入场手续费) |
profit_percent | f64 | 当前浮动盈亏百分比 |
max_runup | f64 | 迄今最大浮动盈利 |
max_runup_percent | f64 | 迄今最大浮动盈利百分比 |
max_drawdown | f64 | 迄今最大浮动亏损 |
max_drawdown_percent | f64 | 迄今最大浮动亏损百分比 |
commission | f64 | 已支付的入场手续费 |
权益曲线
三组 Vec<f64> 均以 K 线为索引(索引 0 = 第一根 K 线):
| 字段 | 说明 |
|---|---|
equity_curve | 每根 K 线收盘时的账户权益 |
drawdown_curve | 每根 K 线相对峰值权益的回撤(peak - equity,始终 ≥ 0) |
buy_hold_curve | 每根 K 线的买入持有假设权益 |
rust
// 找到最大回撤发生的 K 线
if let Some((bar, dd)) = report.drawdown_curve
.iter()
.enumerate()
.max_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap())
{
println!("最大回撤发生在第 {} 根 K 线:{:.2}", bar, dd);
}日回报率
report.daily_returns 提供用于 Sharpe/Sortino 计算的逐日回报序列:
rust
pub struct DailyReturnReport {
pub date: String, // "YYYY-MM-DD"
pub return_percent: f64, // 当日净盈亏百分比
}交易时间范围
report.trading_range 在至少有一笔已平仓交易时为 Some,涵盖从首次入场到最后一笔平仓的时间段:
rust
pub struct TradingRangeReport {
pub start_time: i64, // Unix 毫秒
pub end_time: i64, // Unix 毫秒
}完整示例
rust
use navi_vm::{Candlestick, Instance, TimeFrame};
async fn backtest(candles: Vec<Candlestick>) -> Result<(), navi_vm::Error> {
let source = r#"
strategy("均线交叉策略",
initial_capital = 10000,
commission_type = strategy.commission.percent,
commission_value = 0.1)
fast = ta.sma(close, 14)
slow = ta.sma(close, 28)
if ta.crossover(fast, slow)
strategy.entry("Long", strategy.long)
if ta.crossunder(fast, slow)
strategy.entry("Short", strategy.short)
"#;
let tf = TimeFrame::days(1);
let symbol = "NASDAQ:AAPL";
let instance = Instance::builder(candles, source, tf, symbol)
.build().await?
.run_to_end(symbol, tf).await?;
let report = instance.strategy_report();
let p = &report.performance_all;
println!("=== 策略报告 ===");
println!("净利润: {:.2}({:.2}%)", p.net_profit, p.net_profit_percent);
println!("最大回撤: {:.2}({:.2}%)", p.max_drawdown, p.max_drawdown_percent);
println!("盈亏比: {:.2}", p.profit_factor.unwrap_or(0.0));
println!("Sharpe 比率: {:.2}", p.sharpe_ratio.unwrap_or(0.0));
println!("总交易次数: {}", p.total_closed_trades);
println!("胜率: {:.1}%", p.percent_profitable.unwrap_or(0.0));
println!("总手续费: {:.2}", p.commission_paid);
println!("\n=== 交易明细 ===");
for trade in &report.closed_trades {
println!(
"#{:3} {:5} 入场={:.2} 出场={:.2} 盈亏={:.2}({:.2}%)",
trade.trade_num,
if trade.entry_side == navi_vm::Direction::Long { "多头" } else { "空头" },
trade.entry_price,
trade.exit_price,
trade.profit,
trade.profit_percent,
);
}
Ok(())
}