Add remaining RQAlpha extension helpers

This commit is contained in:
boris
2026-04-23 21:44:42 -07:00
parent beb9c7a7ae
commit ed8ac385e4
7 changed files with 689 additions and 21 deletions

View File

@@ -1,13 +1,13 @@
use std::collections::{BTreeMap, BTreeSet};
use chrono::NaiveDate;
use chrono::{Datelike, NaiveDate};
use serde::Serialize;
use thiserror::Error;
use crate::broker::{BrokerExecutionReport, BrokerSimulator, MatchingType};
use crate::cost::CostModel;
use crate::data::{BenchmarkSnapshot, DataSet, DataSetError, PriceField};
use crate::event_bus::ProcessEventBus;
use crate::event_bus::{BacktestProcessMod, ProcessEventBus};
use crate::events::{
AccountEvent, FillEvent, OrderEvent, OrderSide, OrderStatus, PositionEvent, ProcessEvent,
ProcessEventKind,
@@ -104,11 +104,41 @@ pub struct AnalyzerPositionRow {
pub transaction_cost: f64,
}
#[derive(Debug, Clone, Serialize)]
pub struct AnalyzerMonthlyReturnRow {
pub year: i32,
pub month: u32,
pub portfolio_return: f64,
pub benchmark_return: f64,
pub excess_return: f64,
}
#[derive(Debug, Clone, Serialize)]
pub struct AnalyzerRiskSummary {
pub total_return: f64,
pub annual_return: f64,
pub benchmark_cumulative_return: f64,
pub excess_cumulative_return: f64,
pub alpha: f64,
pub beta: f64,
pub sharpe: f64,
pub sortino: f64,
pub information_ratio: f64,
pub tracking_error: f64,
pub volatility: f64,
pub max_drawdown: f64,
pub max_drawdown_duration_days: usize,
pub win_rate: f64,
pub excess_win_rate: f64,
}
#[derive(Debug, Clone, Serialize)]
pub struct AnalyzerReport {
pub strategy_name: String,
pub trades: Vec<AnalyzerTradeRow>,
pub positions: Vec<AnalyzerPositionRow>,
pub monthly_returns: Vec<AnalyzerMonthlyReturnRow>,
pub risk_summary: AnalyzerRiskSummary,
pub equity_curve: Vec<DailyEquityPoint>,
pub benchmark_series: Vec<BenchmarkSnapshot>,
pub metrics: BacktestMetrics,
@@ -149,6 +179,8 @@ impl BacktestResult {
transaction_cost: holding.transaction_cost,
})
.collect(),
monthly_returns: self.analyzer_monthly_returns(),
risk_summary: self.analyzer_risk_summary(),
equity_curve: self.equity_curve.clone(),
benchmark_series: self.benchmark_series.clone(),
metrics: self.metrics.clone(),
@@ -158,6 +190,61 @@ impl BacktestResult {
pub fn analyzer_report_json(&self) -> Result<String, serde_json::Error> {
serde_json::to_string_pretty(&self.analyzer_report())
}
pub fn analyzer_monthly_returns(&self) -> Vec<AnalyzerMonthlyReturnRow> {
let mut month_points = BTreeMap::<(i32, u32), (f64, f64, f64, f64)>::new();
for point in &self.equity_curve {
let key = (point.date.year(), point.date.month());
month_points
.entry(key)
.and_modify(|(_, _, end_equity, end_benchmark)| {
*end_equity = point.total_equity;
*end_benchmark = point.benchmark_close;
})
.or_insert((
point.total_equity,
point.benchmark_close,
point.total_equity,
point.benchmark_close,
));
}
month_points
.into_iter()
.map(
|((year, month), (start_equity, start_benchmark, end_equity, end_benchmark))| {
let portfolio_return = analyzer_ratio_change(start_equity, end_equity);
let benchmark_return = analyzer_ratio_change(start_benchmark, end_benchmark);
AnalyzerMonthlyReturnRow {
year,
month,
portfolio_return,
benchmark_return,
excess_return: portfolio_return - benchmark_return,
}
},
)
.collect()
}
pub fn analyzer_risk_summary(&self) -> AnalyzerRiskSummary {
AnalyzerRiskSummary {
total_return: self.metrics.total_return,
annual_return: self.metrics.annual_return,
benchmark_cumulative_return: self.metrics.benchmark_cumulative_return,
excess_cumulative_return: self.metrics.excess_cumulative_return,
alpha: self.metrics.alpha,
beta: self.metrics.beta,
sharpe: self.metrics.sharpe,
sortino: self.metrics.sortino,
information_ratio: self.metrics.information_ratio,
tracking_error: self.metrics.tracking_error,
volatility: self.metrics.volatility,
max_drawdown: self.metrics.max_drawdown,
max_drawdown_duration_days: self.metrics.max_drawdown_duration_days,
win_rate: self.metrics.win_rate,
excess_win_rate: self.metrics.excess_win_rate,
}
}
}
#[derive(Debug, Clone, Serialize)]
@@ -307,6 +394,13 @@ impl<S, C, R> BacktestEngine<S, C, R> {
{
self.process_event_bus.add_any_listener(listener);
}
pub fn install_process_mod<M>(&mut self, module: &mut M)
where
M: BacktestProcessMod,
{
self.process_event_bus.install_mod(module);
}
}
impl<S, C, R> BacktestEngine<S, C, R>
@@ -3042,6 +3136,14 @@ fn merge_futures_execution_report(
target.diagnostics.extend(incoming.diagnostics);
}
fn analyzer_ratio_change(start: f64, end: f64) -> f64 {
if start.abs() <= f64::EPSILON {
0.0
} else {
end / start - 1.0
}
}
fn futures_limit_satisfied(side: OrderSide, price: f64, limit_price: Option<f64>) -> bool {
let Some(limit_price) = limit_price else {
return price.is_finite() && price > 0.0;