Skip to content

Commit cccc811

Browse files
committed
Fix CI: resolve formatting and clippy warnings
1 parent 87caf84 commit cccc811

40 files changed

Lines changed: 605 additions & 401 deletions

File tree

crates/trading-backtest/src/engine.rs

Lines changed: 58 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ use trading_core::traits::{Broker, Strategy};
1010
use trading_core::types::{Bar, BarSeries, Side, SignalType, Timeframe};
1111
use trading_risk::{RiskConfig, RiskManager};
1212

13-
use crate::statistics::{BacktestStats, TradeRecord};
1413
use crate::report::BacktestReport;
14+
use crate::statistics::{BacktestStats, TradeRecord};
1515

1616
/// Backtest configuration.
1717
#[derive(Debug, Clone, Serialize, Deserialize)]
@@ -101,65 +101,73 @@ impl BacktestEngine {
101101
if skip {
102102
// Don't process this signal, but continue processing the bar
103103
} else {
104-
// Evaluate with risk manager
105-
let current_price = Decimal::try_from(bar.close).unwrap_or(dec!(0));
106-
let portfolio = broker.get_account().await.unwrap();
107-
let decision = risk_manager.evaluate_signal(&portfolio, &signal, current_price);
104+
// Evaluate with risk manager
105+
let current_price = Decimal::try_from(bar.close).unwrap_or(dec!(0));
106+
let portfolio = broker.get_account().await.unwrap();
107+
let decision =
108+
risk_manager.evaluate_signal(&portfolio, &signal, current_price);
108109

109-
if let Some(order_request) = decision.order() {
110-
// Submit and execute order
111-
if let Ok(order) = broker.submit_order(order_request.clone()).await {
112-
if let Ok(filled) = broker.execute_at_price(order.id, current_price) {
113-
let fill_price = filled.filled_avg_price.unwrap_or(current_price);
114-
let fill_qty = filled.filled_quantity;
110+
if let Some(order_request) = decision.order() {
111+
// Submit and execute order
112+
if let Ok(order) = broker.submit_order(order_request.clone()).await {
113+
if let Ok(filled) = broker.execute_at_price(order.id, current_price)
114+
{
115+
let fill_price =
116+
filled.filled_avg_price.unwrap_or(current_price);
117+
let fill_qty = filled.filled_quantity;
115118

116-
// Calculate P&L for closing trades
117-
let pnl = match order_request.side {
118-
Side::Buy => {
119-
// Opening a long position
120-
let entry = open_positions.entry(symbol.clone()).or_insert((Decimal::ZERO, Decimal::ZERO));
121-
// Weighted average entry price
122-
if entry.1 + fill_qty > Decimal::ZERO {
123-
entry.0 = (entry.0 * entry.1 + fill_price * fill_qty) / (entry.1 + fill_qty);
119+
// Calculate P&L for closing trades
120+
let pnl = match order_request.side {
121+
Side::Buy => {
122+
// Opening a long position
123+
let entry = open_positions
124+
.entry(symbol.clone())
125+
.or_insert((Decimal::ZERO, Decimal::ZERO));
126+
// Weighted average entry price
127+
if entry.1 + fill_qty > Decimal::ZERO {
128+
entry.0 = (entry.0 * entry.1
129+
+ fill_price * fill_qty)
130+
/ (entry.1 + fill_qty);
131+
}
132+
entry.1 += fill_qty;
133+
None
124134
}
125-
entry.1 += fill_qty;
126-
None
127-
}
128-
Side::Sell => {
129-
// Closing (or reducing) a long position
130-
if let Some(entry) = open_positions.get_mut(&symbol) {
131-
if entry.1 > Decimal::ZERO {
132-
let close_qty = fill_qty.min(entry.1);
133-
let trade_pnl = (fill_price - entry.0) * close_qty;
134-
entry.1 -= close_qty;
135-
if entry.1 <= Decimal::ZERO {
136-
open_positions.remove(&symbol);
135+
Side::Sell => {
136+
// Closing (or reducing) a long position
137+
if let Some(entry) = open_positions.get_mut(&symbol) {
138+
if entry.1 > Decimal::ZERO {
139+
let close_qty = fill_qty.min(entry.1);
140+
let trade_pnl =
141+
(fill_price - entry.0) * close_qty;
142+
entry.1 -= close_qty;
143+
if entry.1 <= Decimal::ZERO {
144+
open_positions.remove(&symbol);
145+
}
146+
Some(trade_pnl)
147+
} else {
148+
None
137149
}
138-
Some(trade_pnl)
139150
} else {
140151
None
141152
}
142-
} else {
143-
None
144153
}
145-
}
146-
};
154+
};
147155

148-
// Record trade
149-
let trade = TradeRecord {
150-
symbol: symbol.clone(),
151-
side: order_request.side,
152-
quantity: fill_qty,
153-
price: fill_price,
154-
timestamp: DateTime::from_timestamp_millis(timestamp)
155-
.unwrap_or_else(|| Utc::now()),
156-
signal_type: signal.signal_type,
157-
pnl,
158-
};
159-
stats.add_trade(trade);
156+
// Record trade
157+
let trade = TradeRecord {
158+
symbol: symbol.clone(),
159+
side: order_request.side,
160+
quantity: fill_qty,
161+
price: fill_price,
162+
timestamp: DateTime::from_timestamp_millis(timestamp)
163+
.unwrap_or_else(Utc::now),
164+
signal_type: signal.signal_type,
165+
pnl,
166+
};
167+
stats.add_trade(trade);
168+
}
160169
}
161170
}
162-
}
163171
} // else (not skipped)
164172
}
165173
}
@@ -192,7 +200,7 @@ impl BacktestEngine {
192200
quantity: *quantity,
193201
price: close_price,
194202
timestamp: DateTime::from_timestamp_millis(last_bar.timestamp)
195-
.unwrap_or_else(|| Utc::now()),
203+
.unwrap_or_else(Utc::now),
196204
signal_type: SignalType::CloseLong,
197205
pnl: Some(pnl),
198206
};

crates/trading-backtest/src/lib.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
//! Backtesting engine.
22
33
mod engine;
4-
mod statistics;
54
mod report;
5+
mod statistics;
66

7-
pub use engine::{BacktestEngine, BacktestConfig};
8-
pub use statistics::{BacktestStats, TradeRecord};
7+
pub use engine::{BacktestConfig, BacktestEngine};
98
pub use report::BacktestReport;
9+
pub use statistics::{BacktestStats, TradeRecord};

crates/trading-backtest/src/report.rs

Lines changed: 72 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -27,36 +27,87 @@ impl BacktestReport {
2727

2828
s.push_str("PERFORMANCE\n");
2929
s.push_str("───────────────────────────────────────────────────────────\n");
30-
s.push_str(&format!(" Initial Capital: ${:.2}\n", self.stats.initial_capital));
31-
s.push_str(&format!(" Final Equity: ${:.2}\n", self.stats.final_equity));
32-
s.push_str(&format!(" Total Return: {:.2}%\n", self.stats.total_return_pct));
33-
s.push_str(&format!(" Annualized Return: {:.2}%\n", self.stats.annualized_return_pct));
34-
s.push_str(&format!(" Max Drawdown: {:.2}%\n", self.stats.max_drawdown_pct));
35-
s.push_str("\n");
30+
s.push_str(&format!(
31+
" Initial Capital: ${:.2}\n",
32+
self.stats.initial_capital
33+
));
34+
s.push_str(&format!(
35+
" Final Equity: ${:.2}\n",
36+
self.stats.final_equity
37+
));
38+
s.push_str(&format!(
39+
" Total Return: {:.2}%\n",
40+
self.stats.total_return_pct
41+
));
42+
s.push_str(&format!(
43+
" Annualized Return: {:.2}%\n",
44+
self.stats.annualized_return_pct
45+
));
46+
s.push_str(&format!(
47+
" Max Drawdown: {:.2}%\n",
48+
self.stats.max_drawdown_pct
49+
));
50+
s.push('\n');
3651

3752
s.push_str("RISK METRICS\n");
3853
s.push_str("───────────────────────────────────────────────────────────\n");
39-
s.push_str(&format!(" Sharpe Ratio: {:.2}\n", self.stats.sharpe_ratio));
40-
s.push_str(&format!(" Sortino Ratio: {:.2}\n", self.stats.sortino_ratio));
41-
s.push_str(&format!(" Profit Factor: {:.2}\n", self.stats.profit_factor));
42-
s.push_str("\n");
54+
s.push_str(&format!(
55+
" Sharpe Ratio: {:.2}\n",
56+
self.stats.sharpe_ratio
57+
));
58+
s.push_str(&format!(
59+
" Sortino Ratio: {:.2}\n",
60+
self.stats.sortino_ratio
61+
));
62+
s.push_str(&format!(
63+
" Profit Factor: {:.2}\n",
64+
self.stats.profit_factor
65+
));
66+
s.push('\n');
4367

4468
s.push_str("TRADE STATISTICS\n");
4569
s.push_str("───────────────────────────────────────────────────────────\n");
46-
s.push_str(&format!(" Total Trades: {}\n", self.stats.total_trades));
47-
s.push_str(&format!(" Winning Trades: {}\n", self.stats.winning_trades));
48-
s.push_str(&format!(" Losing Trades: {}\n", self.stats.losing_trades));
49-
s.push_str(&format!(" Breakeven Trades: {}\n", self.stats.breakeven_trades));
50-
s.push_str(&format!(" Win Rate: {:.2}%\n", self.stats.win_rate_pct));
51-
s.push_str(&format!(" Avg Win: ${:.2}\n", self.stats.avg_win));
52-
s.push_str(&format!(" Avg Loss: ${:.2}\n", self.stats.avg_loss));
53-
s.push_str("\n");
70+
s.push_str(&format!(
71+
" Total Trades: {}\n",
72+
self.stats.total_trades
73+
));
74+
s.push_str(&format!(
75+
" Winning Trades: {}\n",
76+
self.stats.winning_trades
77+
));
78+
s.push_str(&format!(
79+
" Losing Trades: {}\n",
80+
self.stats.losing_trades
81+
));
82+
s.push_str(&format!(
83+
" Breakeven Trades: {}\n",
84+
self.stats.breakeven_trades
85+
));
86+
s.push_str(&format!(
87+
" Win Rate: {:.2}%\n",
88+
self.stats.win_rate_pct
89+
));
90+
s.push_str(&format!(
91+
" Avg Win: ${:.2}\n",
92+
self.stats.avg_win
93+
));
94+
s.push_str(&format!(
95+
" Avg Loss: ${:.2}\n",
96+
self.stats.avg_loss
97+
));
98+
s.push('\n');
5499

55100
s.push_str("EXECUTION\n");
56101
s.push_str("───────────────────────────────────────────────────────────\n");
57-
s.push_str(&format!(" Bars Processed: {}\n", self.stats.bars_processed));
58-
s.push_str(&format!(" Equity Points: {}\n", self.stats.equity_curve.len()));
59-
s.push_str("\n");
102+
s.push_str(&format!(
103+
" Bars Processed: {}\n",
104+
self.stats.bars_processed
105+
));
106+
s.push_str(&format!(
107+
" Equity Points: {}\n",
108+
self.stats.equity_curve.len()
109+
));
110+
s.push('\n');
60111

61112
s.push_str("═══════════════════════════════════════════════════════════\n");
62113

crates/trading-backtest/src/statistics.rs

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,12 @@ impl BacktestStats {
138138
// Annualized return (assuming daily bars)
139139
if !self.equity_curve.is_empty() {
140140
let days = self.equity_curve.len() as f64;
141-
let total_return = self.total_return_pct.to_string().parse::<f64>().unwrap_or(0.0) / 100.0;
141+
let total_return = self
142+
.total_return_pct
143+
.to_string()
144+
.parse::<f64>()
145+
.unwrap_or(0.0)
146+
/ 100.0;
142147
let annualized = ((1.0 + total_return).powf(252.0 / days) - 1.0) * 100.0;
143148
self.annualized_return_pct = Decimal::try_from(annualized).unwrap_or(Decimal::ZERO);
144149
}
@@ -165,8 +170,8 @@ impl BacktestStats {
165170

166171
// Win rate
167172
if self.total_trades > 0 {
168-
self.win_rate_pct = Decimal::from(self.winning_trades * 100)
169-
/ Decimal::from(self.total_trades);
173+
self.win_rate_pct =
174+
Decimal::from(self.winning_trades * 100) / Decimal::from(self.total_trades);
170175
}
171176

172177
// Average win/loss
@@ -184,26 +189,32 @@ impl BacktestStats {
184189

185190
// Sharpe ratio
186191
if !self.daily_returns.is_empty() {
187-
let mean: f64 = self.daily_returns.iter().sum::<f64>() / self.daily_returns.len() as f64;
188-
let variance: f64 = self.daily_returns.iter()
192+
let mean: f64 =
193+
self.daily_returns.iter().sum::<f64>() / self.daily_returns.len() as f64;
194+
let variance: f64 = self
195+
.daily_returns
196+
.iter()
189197
.map(|r| (r - mean).powi(2))
190-
.sum::<f64>() / self.daily_returns.len() as f64;
198+
.sum::<f64>()
199+
/ self.daily_returns.len() as f64;
191200
let std_dev = variance.sqrt();
192201

193202
if std_dev > 0.0 {
194203
self.sharpe_ratio = (mean * 252.0_f64.sqrt()) / std_dev;
195204
}
196205

197206
// Sortino ratio (only downside deviation)
198-
let negative_returns: Vec<f64> = self.daily_returns.iter()
207+
let negative_returns: Vec<f64> = self
208+
.daily_returns
209+
.iter()
199210
.filter(|&&r| r < 0.0)
200211
.copied()
201212
.collect();
202213

203214
if !negative_returns.is_empty() {
204-
let downside_variance: f64 = negative_returns.iter()
205-
.map(|r| r.powi(2))
206-
.sum::<f64>() / negative_returns.len() as f64;
215+
let downside_variance: f64 =
216+
negative_returns.iter().map(|r| r.powi(2)).sum::<f64>()
217+
/ negative_returns.len() as f64;
207218
let downside_dev = downside_variance.sqrt();
208219

209220
if downside_dev > 0.0 {

0 commit comments

Comments
 (0)