rusty_backtest/
lib.rs

1use serde::{Serialize};
2
3fn first<T>(v: &Vec<T>) -> Option<&T> {
4    v.first()
5}
6
7#[derive(Debug, Clone, Serialize)]
8pub struct TradeActionOut {
9    pub index_in: i32,
10    pub price_in: f64,
11    pub amt: i32,
12    pub index_out: i32,
13    pub price_out: f64,
14    pub diff: f64,
15}
16
17#[derive(Debug, Serialize)]
18pub struct TradeActionIn {
19    pub index_in: i32,
20    pub price_in: f64,
21    pub amt: i32,
22}
23
24#[derive(Debug, Serialize)]
25pub struct TradeInputResults {
26    pub returns: BacktestResults,
27}
28
29#[derive(Debug, Serialize)]
30pub struct BacktestResults {
31    pub calculated_returns: f64,
32    pub tradesin: Vec<TradeActionIn>,
33    pub tradesout: Vec<TradeActionOut>,
34}
35
36#[derive(Debug, Serialize)]
37pub struct EnterMarketInfo<'a> {
38    pub index: i32,
39    pub current_price: f64,
40    pub holding: f64,
41    pub inmarket: f64,
42    pub data: &'a Vec<Vec<f64>>,
43}
44
45#[derive(Debug, Serialize)]
46pub struct ExitMarketInfo<'a> {
47    pub index: i32,
48    pub current_price: f64,
49    pub index_in: i32,
50    pub price_in: f64,
51    pub holding: f64,
52    pub inmarket: f64,
53    pub data: &'a Vec<Vec<f64>>,
54    pub diff: f64,
55}
56
57#[derive(Debug, Serialize)]
58pub struct Portfolio {
59    pub holding: i32,
60    pub inmarket: i32,
61}
62
63pub fn backtest(
64    values: Vec<Vec<f64>>,
65    holding: i32,
66    default_amt: i32,
67    enter_market_function: &dyn Fn(EnterMarketInfo) -> bool,
68    exit_market_function: &dyn Fn(ExitMarketInfo) -> bool,
69) -> BacktestResults {
70    let mut calculated_returns: f64 = 0.0;
71
72    let close_prices: Vec<f64> = first(&values).unwrap().to_vec();
73
74    let mut portfolio = Portfolio {
75        holding: holding,
76        inmarket: 0,
77    };
78
79    let mut tradesin: Vec<TradeActionIn> = vec![];
80    let mut tradesout: Vec<TradeActionOut> = vec![];
81
82    println!("{:?}", close_prices);
83    {
84        for n in 0..close_prices.len() as i32 {
85            let amt: i32 = default_amt;
86            let have_money = portfolio.holding > amt;
87
88            let to_make_enter_decision_on = EnterMarketInfo {
89                index: n,
90                current_price: close_prices[n as usize],
91                holding: portfolio.holding.clone() as f64,
92                inmarket: portfolio.inmarket.clone() as f64,
93                data: &values,
94            };
95
96            if have_money && enter_market_function(to_make_enter_decision_on) {
97                // WE ARE GOING TO ENTER THE MARKET AT THE CURRENT PRICE
98                // AND WE WILL BUY OUR DEFAULT AMOUNT
99                let action = TradeActionIn {
100                    index_in: n,
101                    price_in: close_prices[n as usize],
102                    amt: amt,
103                };
104                tradesin.push(action);
105                portfolio.holding = portfolio.holding - amt;
106                portfolio.inmarket = portfolio.inmarket + amt;
107            }
108
109            tradesin.retain(|item| {
110                let difference = close_prices[n as usize] - item.price_in;
111
112                let to_make_exit_decision_on = ExitMarketInfo {
113                    index: n,
114                    index_in: item.index_in,
115                    price_in: item.price_in,
116                    current_price: close_prices[n as usize],
117                    holding: portfolio.holding.clone() as f64,
118                    inmarket: portfolio.inmarket.clone() as f64,
119                    diff: difference,
120                    data: &values,
121                };
122
123                if exit_market_function(to_make_exit_decision_on) {
124                    let action_out = TradeActionOut {
125                        index_in: item.index_in,
126                        price_in: item.price_in,
127                        amt: item.amt,
128                        index_out: n,
129                        price_out: close_prices[n as usize],
130                        diff: difference,
131                    };
132                    tradesout.push(action_out);
133                    portfolio.holding = portfolio.holding + amt;
134                    portfolio.inmarket = portfolio.inmarket - amt;
135                    return false;
136                }
137                return true;
138            });
139        }
140
141        // add up all the diffs for a scoring metric
142        for trade in tradesout.clone() {
143            calculated_returns += trade.diff;
144        }
145    }
146
147    BacktestResults {
148        calculated_returns: calculated_returns,
149        tradesin: tradesin,
150        tradesout: tradesout,
151    }
152}