mbinary/
backtest_decoder.rs

1use crate::backtest::BacktestMetaData;
2use crate::backtest::Decode;
3use crate::backtest::Signals;
4use crate::backtest::TimeseriesStats;
5use crate::backtest::Trades;
6use crate::{Error, Result};
7use std::io::Read;
8
9/// Helper function to decode a vector with length prepended
10fn decode_vector<T, R>(reader: &mut R) -> Result<Vec<T>>
11where
12    R: Read,      // Now works with anything implementing Read
13    T: Decode<R>, // T must implement Decode<R>
14{
15    // Read the vector length (u32)
16    let mut length_buf = [0u8; 4];
17    reader
18        .read_exact(&mut length_buf)
19        .map_err(|_| Error::CustomError("Failed to read vector length".to_string()))?;
20    let length = u32::from_le_bytes(length_buf) as usize;
21
22    // Decode each element in the vector
23    let mut result = Vec::with_capacity(length);
24    for _ in 0..length {
25        result.push(T::decode(reader)?);
26    }
27
28    Ok(result)
29}
30
31pub struct BacktestDecoder<R: Read> {
32    cursor: R,
33}
34
35impl<R: Read> BacktestDecoder<R> {
36    pub fn new(reader: R) -> Self {
37        BacktestDecoder { cursor: reader }
38    }
39
40    pub fn decode_metadata(&mut self) -> Result<BacktestMetaData> {
41        BacktestMetaData::decode(&mut self.cursor)
42    }
43
44    pub fn decode_timeseries(&mut self) -> Result<Vec<TimeseriesStats>> {
45        decode_vector(&mut self.cursor)
46    }
47
48    pub fn decode_trades(&mut self) -> Result<Vec<Trades>> {
49        decode_vector(&mut self.cursor)
50    }
51
52    pub fn decode_signals(&mut self) -> Result<Vec<Signals>> {
53        decode_vector(&mut self.cursor)
54    }
55}
56
57#[cfg(test)]
58mod tests {
59
60    use super::*;
61    use crate::{
62        backtest::{BacktestData, Parameters, SignalInstructions, StaticStats},
63        backtest_encode::BacktestEncoder,
64    };
65
66    #[test]
67    fn backtestencoder() -> anyhow::Result<()> {
68        let params = Parameters {
69            strategy_name: "Testing".to_string(),
70            capital: 10000,
71            schema: "Ohlcv-1s".to_string(),
72            data_type: "BAR".to_string(),
73            start: 1730160814000000000,
74            end: 1730160814000000000,
75            tickers: vec!["HE.n.0".to_string(), "AAPL".to_string()],
76        };
77
78        let static_stats = StaticStats {
79            total_trades: 100,
80            total_winning_trades: 50,
81            total_losing_trades: 50,
82            avg_profit: 1000000000000,
83            avg_profit_percent: 10383783337737,
84            avg_gain: 23323212233,
85            avg_gain_percent: 24323234,
86            avg_loss: 203982828,
87            avg_loss_percent: 23432134323,
88            profitability_ratio: 130213212323,
89            profit_factor: 12342123431,
90            profit_and_loss_ratio: 1234321343,
91            total_fees: 123453234,
92            net_profit: 1234323,
93            beginning_equity: 12343234323,
94            ending_equity: 12343234,
95            total_return: 234532345,
96            annualized_return: 234532345,
97            daily_standard_deviation_percentage: 23453234,
98            annual_standard_deviation_percentage: 34543443,
99            max_drawdown_percentage_period: 234543234,
100            max_drawdown_percentage_daily: 23432345,
101            sharpe_ratio: 23432343,
102            sortino_ratio: 123453234543,
103        };
104        let bt_metadata = BacktestMetaData::new(None, "testing", params, static_stats);
105
106        let timeseries = TimeseriesStats {
107            timestamp: 123700000000000,
108            equity_value: 9999999,
109            percent_drawdown: 2343234,
110            cumulative_return: 2343234,
111            period_return: 2345432345,
112        };
113
114        let stats: Vec<TimeseriesStats> = vec![timeseries.clone(), timeseries.clone()];
115
116        let trade = Trades {
117            trade_id: 1,
118            leg_id: 1,
119            timestamp: 1704903000,
120            ticker: "AAPL".to_string(),
121            quantity: 4,
122            avg_price: 13074,
123            trade_value: -52296,
124            trade_cost: -52296,
125            action: "BUY".to_string(),
126            fees: 100,
127        };
128
129        let trades: Vec<Trades> = vec![trade.clone(), trade.clone()];
130
131        let instructions = SignalInstructions {
132            ticker: "AAPL".to_string(),
133            order_type: "MKT".to_string(),
134            action: "BUY".to_string(),
135            trade_id: 1,
136            leg_id: 2,
137            weight: 13213432,
138            quantity: 2343,
139            limit_price: "12341".to_string(),
140            aux_price: "1233212".to_string(),
141        };
142
143        let vec: Vec<SignalInstructions> = vec![instructions.clone(), instructions.clone()];
144        let signal = Signals {
145            timestamp: 1234565432345,
146            trade_instructions: vec,
147        };
148
149        let signals: Vec<Signals> = vec![signal.clone(), signal.clone()];
150        let backtest = BacktestData {
151            metadata: bt_metadata,
152            daily_timeseries_stats: stats.clone(),
153            period_timeseries_stats: stats,
154            trades,
155            signals,
156        };
157
158        // Encode
159        let mut bytes = Vec::new();
160        let mut encoder = BacktestEncoder::new(&mut bytes);
161        encoder.encode_metadata(&backtest.metadata);
162        encoder.encode_timeseries(&backtest.period_timeseries_stats);
163        encoder.encode_timeseries(&backtest.daily_timeseries_stats);
164        encoder.encode_trades(&backtest.trades);
165        encoder.encode_signals(&backtest.signals);
166
167        // Decode
168        let decode = bytes.as_slice();
169        let mut decoder = BacktestDecoder::new(decode);
170
171        let metadata = decoder.decode_metadata()?;
172        let period_stats = decoder.decode_timeseries()?;
173        let daily_stats = decoder.decode_timeseries()?;
174        let trades = decoder.decode_trades()?;
175        let signals = decoder.decode_signals()?;
176
177        let decoded_backtest = BacktestData {
178            metadata,
179            period_timeseries_stats: period_stats,
180            daily_timeseries_stats: daily_stats,
181            trades,
182            signals,
183        };
184
185        // Validate
186        assert_eq!(backtest, decoded_backtest);
187
188        Ok(())
189    }
190}