mbinary/
backtest.rs

1use crate::{Error, Result};
2use bytemuck;
3use serde::{Deserialize, Serialize};
4use sqlx::FromRow;
5use std::{io::Read, u16};
6
7#[cfg(feature = "python")]
8use pyo3::pyclass;
9
10/// Helper to write a string as 2-byte length-prefixed UTF-8
11fn write_string(buffer: &mut Vec<u8>, string: &str) {
12    let length = string.len() as u16; // Convert length to u16
13    buffer.extend(&length.to_le_bytes()); // Write the 2-byte length
14    buffer.extend(string.as_bytes()); // Write the UTF-8 bytes
15}
16
17/// Helper to read a 2-byte length-prefixed UTF-8 string
18fn read_string<R: Read>(cursor: &mut R) -> Result<String> {
19    let mut len_buf = [0u8; 2]; // Buffer to store the 2-byte length
20    cursor
21        .read_exact(&mut len_buf)
22        .map_err(|_| Error::CustomError("Failed to read string length".to_string()))?;
23    let len = u16::from_le_bytes(len_buf) as usize; // Convert length to usize
24
25    let mut string_buf = vec![0u8; len]; // Buffer to store the string bytes
26    cursor
27        .read_exact(&mut string_buf)
28        .map_err(|_| Error::CustomError("Failed to read string bytes".to_string()))?;
29    String::from_utf8(string_buf)
30        .map_err(|_| Error::CustomError("Invalid UTF-8 string".to_string()))
31}
32
33/// Helper function to read fixed-size data (e.g., i32, i64) from the cursor.
34fn read_fixed<T: Sized + Copy, R: Read>(cursor: &mut R) -> Result<T>
35where
36    T: bytemuck::Pod + bytemuck::Zeroable,
37{
38    let mut buffer = vec![0u8; std::mem::size_of::<T>()];
39    cursor.read_exact(&mut buffer).map_err(|_| {
40        Error::CustomError(format!("Failed to read {} bytes", std::mem::size_of::<T>()))
41    })?;
42    Ok(bytemuck::cast_slice(&buffer)[0])
43}
44
45/// Trait to define encoding for individual items
46pub trait Encode {
47    fn encode(&self, buffer: &mut Vec<u8>);
48}
49
50pub trait Decode<R: Read>: Sized {
51    fn decode(cursor: &mut R) -> Result<Self>;
52}
53
54#[repr(C)]
55#[cfg_attr(
56    feature = "python",
57    pyclass(get_all, set_all, dict, module = "mbinary")
58)]
59#[derive(Deserialize, Serialize, FromRow, Debug, Clone, PartialEq)]
60pub struct BacktestData {
61    pub metadata: BacktestMetaData,
62    pub period_timeseries_stats: Vec<TimeseriesStats>,
63    pub daily_timeseries_stats: Vec<TimeseriesStats>,
64    pub trades: Vec<Trades>,
65    pub signals: Vec<Signals>,
66}
67
68#[repr(C)]
69#[cfg_attr(
70    feature = "python",
71    pyclass(get_all, set_all, dict, module = "mbinary")
72)]
73#[derive(Deserialize, Serialize, FromRow, Debug, Clone, PartialEq)]
74pub struct BacktestMetaData {
75    pub backtest_id: u16,
76    pub backtest_name: String,
77    pub parameters: Parameters,
78    pub static_stats: StaticStats,
79}
80
81impl BacktestMetaData {
82    pub fn new(
83        mut backtest_id: Option<u16>,
84        backtest_name: &str,
85        parameters: Parameters,
86        static_stats: StaticStats,
87    ) -> Self {
88        if None == backtest_id {
89            backtest_id = Some(u16::MAX); // Sentinel value
90        };
91        Self {
92            backtest_id: backtest_id.unwrap(),
93            backtest_name: backtest_name.to_string(),
94            parameters,
95            static_stats,
96        }
97    }
98}
99
100impl Encode for BacktestMetaData {
101    fn encode(&self, buffer: &mut Vec<u8>) {
102        buffer.extend(&self.backtest_id.to_le_bytes());
103        write_string(buffer, &self.backtest_name);
104        self.parameters.encode(buffer);
105        self.static_stats.encode(buffer);
106    }
107}
108
109impl<R: Read> Decode<R> for BacktestMetaData {
110    fn decode(cursor: &mut R) -> Result<Self> {
111        let backtest_id: u16 = read_fixed(cursor)?;
112        let backtest_name: String = read_string(cursor)?;
113        let parameters: Parameters = Parameters::decode(cursor)?;
114        let static_stats: StaticStats = StaticStats::decode(cursor)?;
115
116        Ok(Self {
117            backtest_id,
118            backtest_name,
119            parameters,
120            static_stats,
121        })
122    }
123}
124
125#[repr(C)]
126#[cfg_attr(
127    feature = "python",
128    pyclass(get_all, set_all, dict, module = "mbinary")
129)]
130#[derive(Deserialize, Serialize, FromRow, Debug, Clone, PartialEq)]
131pub struct Parameters {
132    pub strategy_name: String,
133    pub capital: i64,
134    pub schema: String,
135    pub data_type: String,
136    pub start: i64,
137    pub end: i64,
138    pub tickers: Vec<String>,
139}
140
141impl Encode for Parameters {
142    fn encode(&self, buffer: &mut Vec<u8>) {
143        write_string(buffer, &self.strategy_name);
144        buffer.extend(&self.capital.to_le_bytes());
145        write_string(buffer, &self.schema);
146        write_string(buffer, &self.data_type);
147        buffer.extend(&self.start.to_le_bytes());
148        buffer.extend(&self.end.to_le_bytes());
149
150        // Encode tickers length as prefix to list
151        let tickers_len = self.tickers.len() as u32;
152        buffer.extend(&tickers_len.to_le_bytes());
153
154        for ticker in &self.tickers {
155            write_string(buffer, ticker);
156        }
157    }
158}
159
160impl<R: Read> Decode<R> for Parameters {
161    fn decode(cursor: &mut R) -> Result<Self> {
162        // fn decode(cursor: &mut Cursor<&[u8]>) -> Result<Self> {
163        // let mut cursor = std::io::Cursor::new(buffer);
164
165        let strategy_name: String = read_string(cursor)?;
166        let capital: i64 = read_fixed(cursor)?;
167        let schema: String = read_string(cursor)?;
168        let data_type: String = read_string(cursor)?;
169        let start: i64 = read_fixed(cursor)?;
170        let end: i64 = read_fixed(cursor)?;
171
172        // Decode tickers length, 4 b/c stored as u32(4 bytes)
173        let mut tickers_len_buf = [0u8; 4];
174        cursor
175            .read_exact(&mut tickers_len_buf)
176            .map_err(|_| Error::CustomError("Failed to read tickers length".to_string()))?;
177        let tickers_len = u32::from_le_bytes(tickers_len_buf) as usize;
178
179        //Decode tickers
180        let mut tickers = Vec::new();
181        for _ in 0..tickers_len {
182            tickers.push(read_string(cursor)?);
183        }
184
185        Ok(Self {
186            strategy_name,
187            capital,
188            schema,
189            data_type,
190            start,
191            end,
192            tickers,
193        })
194    }
195}
196
197#[repr(C)]
198#[cfg_attr(
199    feature = "python",
200    pyclass(get_all, set_all, dict, module = "mbinary")
201)]
202#[derive(Deserialize, Serialize, FromRow, Debug, Clone, PartialEq)]
203pub struct StaticStats {
204    pub total_trades: i32,
205    pub total_winning_trades: i32,
206    pub total_losing_trades: i32,
207    pub avg_profit: i64,                           // Scaled by 1e9
208    pub avg_profit_percent: i64,                   // Scaled by 1e9
209    pub avg_gain: i64,                             // Scaled by 1e9
210    pub avg_gain_percent: i64,                     // Scaled by 1e9
211    pub avg_loss: i64,                             // Scaled by 1e9
212    pub avg_loss_percent: i64,                     // Scaled by 1e9
213    pub profitability_ratio: i64,                  // Scaled by 1e9
214    pub profit_factor: i64,                        // Scaled by 1e9
215    pub profit_and_loss_ratio: i64,                // Scaled by 1e9
216    pub total_fees: i64,                           // Scaled by 1e9
217    pub net_profit: i64,                           // Scaled by 1e9
218    pub beginning_equity: i64,                     // Scaled by 1e9
219    pub ending_equity: i64,                        // Scaled by 1e9
220    pub total_return: i64,                         // Scaled by 1e9
221    pub annualized_return: i64,                    // Scaled by 1e9
222    pub daily_standard_deviation_percentage: i64,  // Scaled by 1e9
223    pub annual_standard_deviation_percentage: i64, // Scaled by 1e9
224    pub max_drawdown_percentage_period: i64,       // Scaled by 1e9
225    pub max_drawdown_percentage_daily: i64,        // Scaled by 1e9
226    pub sharpe_ratio: i64,                         // Scaled by 1e9
227    pub sortino_ratio: i64,                        // Scaled by 1e9
228}
229
230impl Encode for StaticStats {
231    fn encode(&self, buffer: &mut Vec<u8>) {
232        buffer.extend(unsafe {
233            std::slice::from_raw_parts(
234                (self as *const StaticStats) as *const u8,
235                std::mem::size_of::<StaticStats>(),
236            )
237        });
238    }
239}
240
241impl<R: Read> Decode<R> for StaticStats {
242    fn decode(cursor: &mut R) -> Result<Self> {
243        // Read the required bytes into a temporary buffer
244        let mut buffer = vec![0u8; std::mem::size_of::<StaticStats>()];
245        cursor.read_exact(&mut buffer)?;
246
247        // Create a pointer to the buffer and cast it to a `StaticStats` pointer
248        let ptr = buffer.as_ptr() as *const StaticStats;
249
250        // Dereference the pointer to create a `StaticStats` instance
251        unsafe { Ok(ptr.read()) }
252    }
253}
254
255#[repr(C)]
256#[cfg_attr(
257    feature = "python",
258    pyclass(get_all, set_all, dict, module = "mbinary")
259)]
260#[derive(Deserialize, Serialize, FromRow, Debug, Clone, PartialEq, Eq)]
261pub struct TimeseriesStats {
262    pub timestamp: i64,
263    pub equity_value: i64,      // Scaled by 1e9
264    pub percent_drawdown: i64,  // Scaled by 1e9
265    pub cumulative_return: i64, // Scaled by 1e9
266    pub period_return: i64,     // Scaled by 1e9
267}
268
269impl Encode for TimeseriesStats {
270    fn encode(&self, buffer: &mut Vec<u8>) {
271        buffer.extend(unsafe {
272            // Serialize the `TimeseriesStats` struct as a slice of bytes.
273            std::slice::from_raw_parts(
274                (self as *const TimeseriesStats) as *const u8,
275                std::mem::size_of::<TimeseriesStats>(),
276            )
277        });
278    }
279}
280
281impl<R: Read> Decode<R> for TimeseriesStats {
282    fn decode(cursor: &mut R) -> Result<Self> {
283        // Read the required bytes into a temporary buffer
284        let mut buffer = vec![0u8; std::mem::size_of::<TimeseriesStats>()];
285        cursor.read_exact(&mut buffer)?;
286
287        // Create a pointer to the buffer and cast it to a `StaticStats` pointer
288        let ptr = buffer.as_ptr() as *const TimeseriesStats;
289
290        // Dereference the pointer to create a `StaticStats` instance
291        unsafe { Ok(ptr.read()) }
292    }
293}
294
295#[repr(C)]
296#[cfg_attr(
297    feature = "python",
298    pyclass(get_all, set_all, dict, module = "mbinary")
299)]
300#[derive(Deserialize, Serialize, FromRow, Debug, Clone, PartialEq, Eq)]
301pub struct Trades {
302    pub trade_id: i32,
303    pub signal_id: i32,
304    pub timestamp: i64,
305    pub ticker: String,
306    pub quantity: i64,    // Scaled by 1e9
307    pub avg_price: i64,   // Scaled by 1e9
308    pub trade_value: i64, // Scaled by 1e9
309    pub trade_cost: i64,  // Scaled by 1e9
310    pub action: String,
311    pub fees: i64, // Scaled by 1e9
312}
313
314impl Encode for Trades {
315    fn encode(&self, buffer: &mut Vec<u8>) {
316        buffer.extend(&self.trade_id.to_le_bytes());
317        buffer.extend(&self.signal_id.to_le_bytes());
318        buffer.extend(&self.timestamp.to_le_bytes());
319        write_string(buffer, &self.ticker);
320        buffer.extend(&self.quantity.to_le_bytes());
321        buffer.extend(&self.avg_price.to_le_bytes());
322        buffer.extend(&self.trade_value.to_le_bytes());
323        buffer.extend(&self.trade_cost.to_le_bytes());
324        write_string(buffer, &self.action);
325        buffer.extend(&self.fees.to_le_bytes());
326    }
327}
328
329impl<R: Read> Decode<R> for Trades {
330    fn decode(cursor: &mut R) -> Result<Self> {
331        let trade_id: i32 = read_fixed(cursor)?;
332        let signal_id: i32 = read_fixed(cursor)?;
333        let timestamp: i64 = read_fixed(cursor)?;
334        let ticker: String = read_string(cursor)?;
335        let quantity: i64 = read_fixed(cursor)?;
336        let avg_price: i64 = read_fixed(cursor)?;
337        let trade_value: i64 = read_fixed(cursor)?;
338        let trade_cost: i64 = read_fixed(cursor)?;
339        let action: String = read_string(cursor)?;
340        let fees: i64 = read_fixed(cursor)?;
341
342        Ok(Self {
343            trade_id,
344            signal_id,
345            timestamp,
346            ticker,
347            quantity,
348            avg_price,
349            trade_value,
350            trade_cost,
351            action,
352            fees,
353        })
354    }
355}
356
357#[repr(C)]
358#[cfg_attr(
359    feature = "python",
360    pyclass(get_all, set_all, dict, module = "mbinary")
361)]
362#[derive(Deserialize, Serialize, FromRow, Debug, Clone, PartialEq, Eq)]
363pub struct Signals {
364    pub timestamp: i64,
365    pub trade_instructions: Vec<SignalInstructions>,
366}
367
368impl Encode for Signals {
369    fn encode(&self, buffer: &mut Vec<u8>) {
370        buffer.extend(&self.timestamp.to_le_bytes());
371
372        // Encode trade_instructions length as prefix to list
373        let trade_len = self.trade_instructions.len() as u32;
374        buffer.extend(&trade_len.to_le_bytes());
375
376        for t in &self.trade_instructions {
377            t.encode(buffer);
378        }
379    }
380}
381
382impl<R: Read> Decode<R> for Signals {
383    fn decode(cursor: &mut R) -> Result<Self> {
384        let timestamp: i64 = read_fixed(cursor)?;
385
386        // Decode tickers length, 4 b/c stored as u32(4 bytes)
387        let instruction_len: u32 = read_fixed(cursor)?;
388
389        //Decode tickers
390        let mut trade_instructions = Vec::new();
391        for _ in 0..instruction_len {
392            trade_instructions.push(SignalInstructions::decode(cursor)?);
393        }
394
395        Ok(Self {
396            timestamp,
397            trade_instructions,
398        })
399    }
400}
401
402#[repr(C)]
403#[cfg_attr(
404    feature = "python",
405    pyclass(get_all, set_all, dict, module = "mbinary")
406)]
407#[derive(Deserialize, Serialize, FromRow, Debug, Clone, PartialEq, Eq)]
408pub struct SignalInstructions {
409    pub ticker: String,
410    pub order_type: String,
411    pub action: String,
412    pub signal_id: i32,
413    pub weight: i64, // Scaled by 1e9
414    pub quantity: i32,
415    pub limit_price: String, // Maybe int scale by 1e9
416    pub aux_price: String,   // Myabe int scale by 1e9
417}
418
419impl Encode for SignalInstructions {
420    fn encode(&self, buffer: &mut Vec<u8>) {
421        write_string(buffer, &self.ticker);
422        write_string(buffer, &self.order_type);
423        write_string(buffer, &self.action);
424        buffer.extend(&self.signal_id.to_le_bytes());
425        buffer.extend(&self.weight.to_le_bytes());
426        buffer.extend(&self.quantity.to_le_bytes());
427        write_string(buffer, &self.limit_price);
428        write_string(buffer, &self.aux_price);
429    }
430}
431impl<R: Read> Decode<R> for SignalInstructions {
432    fn decode(cursor: &mut R) -> Result<Self> {
433        let ticker: String = read_string(cursor)?;
434        let order_type: String = read_string(cursor)?;
435        let action: String = read_string(cursor)?;
436        let signal_id: i32 = read_fixed(cursor)?;
437        let weight: i64 = read_fixed(cursor)?;
438        let quantity: i32 = read_fixed(cursor)?;
439        let limit_price: String = read_string(cursor)?;
440        let aux_price: String = read_string(cursor)?;
441
442        Ok(Self {
443            ticker,
444            order_type,
445            action,
446            signal_id,
447            weight,
448            quantity,
449            limit_price,
450            aux_price,
451        })
452    }
453}
454
455#[cfg(test)]
456mod tests {
457    use super::*;
458    use std::io::Cursor;
459
460    #[test]
461    fn test_encode_valid_string() -> anyhow::Result<()> {
462        // Encode String
463        let text = "testing123";
464        let mut buffer = Vec::new();
465        write_string(&mut buffer, &text);
466
467        // Decode String
468        let bytes: &[u8] = &buffer;
469        let mut cursor = Cursor::new(bytes);
470        let decoded = read_string(&mut cursor)?;
471
472        //Validate
473        assert_eq!(text, &decoded);
474
475        Ok(())
476    }
477
478    #[test]
479    fn test_encode_empty_string() -> anyhow::Result<()> {
480        // Encode String
481        let text = "";
482        let mut buffer = Vec::new();
483        write_string(&mut buffer, &text);
484
485        // Decode String
486        let bytes: &[u8] = &buffer;
487        let mut cursor = Cursor::new(bytes);
488        let decoded = read_string(&mut cursor)?;
489
490        //Validate
491        assert_eq!(text, &decoded);
492
493        Ok(())
494    }
495
496    #[test]
497    fn parameters_encode_decode() -> anyhow::Result<()> {
498        let params = Parameters {
499            strategy_name: "Testing".to_string(),
500            capital: 10000,
501            schema: "Ohlcv-1s".to_string(),
502            data_type: "BAR".to_string(),
503            start: 1730160814000000000,
504            end: 1730160814000000000,
505            tickers: vec!["HE.n.0".to_string(), "AAPL".to_string()],
506        };
507
508        // Encode
509        let mut bytes = Vec::new();
510        params.encode(&mut bytes);
511
512        // Decode
513        let mut cursor = Cursor::new(bytes.as_slice());
514        let decoded = Parameters::decode(&mut cursor)?;
515
516        // Validate
517        assert_eq!(params, decoded);
518
519        Ok(())
520    }
521
522    #[test]
523    fn staticstats_encode_decode() -> anyhow::Result<()> {
524        let static_stats = StaticStats {
525            total_trades: 100,
526            total_winning_trades: 50,
527            total_losing_trades: 50,
528            avg_profit: 1000000000000,
529            avg_profit_percent: 10383783337737,
530            avg_gain: 23323212233,
531            avg_gain_percent: 24323234,
532            avg_loss: 203982828,
533            avg_loss_percent: 23432134323,
534            profitability_ratio: 130213212323,
535            profit_factor: 12342123431,
536            profit_and_loss_ratio: 1234321343,
537            total_fees: 123453234,
538            net_profit: 1234323,
539            beginning_equity: 12343234323,
540            ending_equity: 12343234,
541            total_return: 234532345,
542            annualized_return: 234532345,
543            daily_standard_deviation_percentage: 23453234,
544            annual_standard_deviation_percentage: 34543443,
545            max_drawdown_percentage_period: 234543234,
546            max_drawdown_percentage_daily: 23432345,
547            sharpe_ratio: 23432343,
548            sortino_ratio: 123453234543,
549        };
550
551        // Encode
552        let mut bytes = Vec::new();
553        static_stats.encode(&mut bytes);
554
555        // Decode
556        let mut cursor = Cursor::new(bytes.as_slice());
557        let decoded = StaticStats::decode(&mut cursor)?;
558
559        // Validate
560        assert_eq!(static_stats, decoded);
561
562        Ok(())
563    }
564
565    #[test]
566    fn timeseriesstats_encode_decode() -> anyhow::Result<()> {
567        let timeseries = TimeseriesStats {
568            timestamp: 123700000000000,
569            equity_value: 9999999,
570            percent_drawdown: 2343234,
571            cumulative_return: 2343234,
572            period_return: 2345432345,
573        };
574
575        let stats: Vec<TimeseriesStats> = vec![timeseries.clone(), timeseries.clone()];
576
577        // Encode
578        let mut bytes = Vec::new();
579        for s in &stats {
580            s.encode(&mut bytes);
581        }
582
583        // Decode
584        let mut cursor = Cursor::new(bytes.as_slice());
585        let mut decoded = Vec::new();
586
587        while (cursor.position() as usize) < cursor.get_ref().len() {
588            let trade = TimeseriesStats::decode(&mut cursor)?;
589            decoded.push(trade);
590        }
591
592        // Validate
593        assert_eq!(stats, decoded);
594
595        Ok(())
596    }
597
598    #[test]
599    fn trades_encode_decode() -> anyhow::Result<()> {
600        let trade = Trades {
601            trade_id: 1,
602            signal_id: 1,
603            timestamp: 1704903000,
604            ticker: "AAPL".to_string(),
605            quantity: 4,
606            avg_price: 13074,
607            trade_value: -52296,
608            trade_cost: -52296,
609            action: "BUY".to_string(),
610            fees: 100,
611        };
612
613        let vec: Vec<Trades> = vec![trade.clone(), trade.clone()];
614
615        // Encode all trades into a buffer
616        let mut buffer = Vec::new();
617        for t in &vec {
618            t.encode(&mut buffer);
619        }
620
621        // Validate
622        let mut cursor = Cursor::new(buffer.as_slice());
623        let mut decoded = Vec::new();
624
625        while (cursor.position() as usize) < cursor.get_ref().len() {
626            let trade = Trades::decode(&mut cursor)?;
627            decoded.push(trade);
628        }
629
630        // Validate
631        assert_eq!(vec, decoded);
632
633        Ok(())
634    }
635
636    #[test]
637    fn signal_instructions_encode_decode() -> anyhow::Result<()> {
638        let instructions = SignalInstructions {
639            ticker: "AAPL".to_string(),
640            order_type: "MKT".to_string(),
641            action: "BUY".to_string(),
642            signal_id: 1,
643            weight: 13213432,
644            quantity: 2343,
645            limit_price: "12341".to_string(),
646            aux_price: "1233212".to_string(),
647        };
648
649        let vec: Vec<SignalInstructions> = vec![instructions.clone(), instructions.clone()];
650
651        // Encode all timeseries stats into a buffer
652        let mut buffer = Vec::new();
653        for s in &vec {
654            s.encode(&mut buffer);
655        }
656
657        // Validate
658        let mut cursor = Cursor::new(buffer.as_slice());
659        let mut decoded = Vec::new();
660
661        while (cursor.position() as usize) < cursor.get_ref().len() {
662            let trade = SignalInstructions::decode(&mut cursor)?;
663            decoded.push(trade);
664        }
665
666        // Validate
667        assert_eq!(vec, decoded);
668
669        Ok(())
670    }
671
672    #[test]
673    fn signals_encode_decode() -> anyhow::Result<()> {
674        let instructions = SignalInstructions {
675            ticker: "AAPL".to_string(),
676            order_type: "MKT".to_string(),
677            action: "BUY".to_string(),
678            signal_id: 1,
679            weight: 13213432,
680            quantity: 2343,
681            limit_price: "12341".to_string(),
682            aux_price: "1233212".to_string(),
683        };
684
685        let vec: Vec<SignalInstructions> = vec![instructions.clone(), instructions.clone()];
686        let signal = Signals {
687            timestamp: 1234565432345,
688            trade_instructions: vec,
689        };
690
691        let signals: Vec<Signals> = vec![signal.clone(), signal.clone()];
692
693        // Encode all timeseries stats into a buffer
694        let mut buffer = Vec::new();
695        for s in &signals {
696            s.encode(&mut buffer);
697        }
698
699        // Decode
700        let mut cursor = Cursor::new(buffer.as_slice());
701        let mut decoded = Vec::new();
702
703        while (cursor.position() as usize) < cursor.get_ref().len() {
704            let trade = Signals::decode(&mut cursor)?;
705            decoded.push(trade);
706        }
707
708        // Validate
709        assert_eq!(signals, decoded);
710
711        Ok(())
712    }
713
714    #[test]
715    fn backtestmetdata_encode_decode() -> anyhow::Result<()> {
716        let params = Parameters {
717            strategy_name: "Testing".to_string(),
718            capital: 10000,
719            schema: "Ohlcv-1s".to_string(),
720            data_type: "BAR".to_string(),
721            start: 1730160814000000000,
722            end: 1730160814000000000,
723            tickers: vec!["HE.n.0".to_string(), "AAPL".to_string()],
724        };
725
726        let static_stats = StaticStats {
727            total_trades: 100,
728            total_winning_trades: 50,
729            total_losing_trades: 50,
730            avg_profit: 1000000000000,
731            avg_profit_percent: 10383783337737,
732            avg_gain: 23323212233,
733            avg_gain_percent: 24323234,
734            avg_loss: 203982828,
735            avg_loss_percent: 23432134323,
736            profitability_ratio: 130213212323,
737            profit_factor: 12342123431,
738            profit_and_loss_ratio: 1234321343,
739            total_fees: 123453234,
740            net_profit: 1234323,
741            beginning_equity: 12343234323,
742            ending_equity: 12343234,
743            total_return: 234532345,
744            annualized_return: 234532345,
745            daily_standard_deviation_percentage: 23453234,
746            annual_standard_deviation_percentage: 34543443,
747            max_drawdown_percentage_period: 234543234,
748            max_drawdown_percentage_daily: 23432345,
749            sharpe_ratio: 23432343,
750            sortino_ratio: 123453234543,
751        };
752        let bt_metadata = BacktestMetaData::new(None, "testing", params, static_stats);
753
754        // Encode
755        let mut buffer = Vec::new();
756        bt_metadata.encode(&mut buffer);
757
758        // Decode
759        let mut cursor = Cursor::new(buffer.as_slice());
760        let mut decoded = Vec::new();
761        decoded.extend(BacktestMetaData::decode(&mut cursor));
762
763        // Validate
764        assert_eq!(bt_metadata, decoded[0]);
765
766        Ok(())
767    }
768}