Backtesting made easy!
Rusty Backtest follows a simple concept. The enter - exit testing strategy.
This is where there is some set of logic returns a boolean (True/False) action to enter and to exit the market. This could be a buy-sell pair or a sell-buy pair.
A verbal example of a exit enter test:
enter the market when the RSI is above 70
exit the market if your up 30% or if its been 1 month or if the current price is 50% down.
You can chain logical test together on both the entry and exit points.
In this case we passed the backtest an array 2, 7. Think of it as a table with 7 rows and 2 columns.
The first column is the price and is required for the backtest. It is the "market" prices that the backtest uses to track price.
The second column and onwards are custom data values that can be used to infor the enter exit logic.
In this case the second column is some calculated metric - lets say this is the DSI (david's special indicator). It could be the RSI or EMA or other well known technical indicator.
let mydata = vec![
vec![1.0, 5.3, 0.5, 5.3, 1.0, 5.3, 1.0],
vec![0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0],
];
Now that we have this information, we want to only enter the market when the DSI is above 0.5
. Looking at the data above - we can see this only happends once (good indicator). Note: here we access the second column the "DSI" with market_info.data[1]
fn enter_market_function(market_info: EnterMarketInfo) -> bool {
// if second row is above or equal to the limit we care about
if market_info.data[1][market_info.index as usize] >= 0.5 {
return true;
}
return false;
}
This function is run on every "row" of data and allows the user to set the strategy of their choice. If the function returns true and the portfolio has money then the backtest will enter the market. If the function returns false, then no action will be taken.
Now we have a way to enter the market. We need a way to exit it! Here we can set some logic to exit the market. Here we just want to exit if the trade is more then 2 days old. This is a silly strategy but you can chain any boolean statements together to make a much more complex - successful strategy.
fn exit_market_function(market_info: ExitMarketInfo) -> bool {
// if trade in for certain time
if market_info.index - market_info.index_in >= 2 {
return true;
}
return false;
}
We pull together the above enter and exit logic and run a full test. We can see that this test is very small - but also runs very fast 57 nano seconds
.
eg. running this same program on about 1500 daily equity prices ran <2ms
Copy and run the following example using
cargo run --release
use std::time::Instant;
extern crate rusty_backtest;
use crate::rusty_backtest::backtest;
use crate::rusty_backtest::EnterMarketInfo;
use crate::rusty_backtest::ExitMarketInfo;
use crate::rusty_backtest::TradeInputResults;
fn enter_market_function(market_info: EnterMarketInfo) -> bool {
// if second row indicates
if market_info.data[1][market_info.index as usize] >= 0.5 {
return true;
}
return false;
}
fn exit_market_function(market_info: ExitMarketInfo) -> bool {
// if trade in for certain time
if market_info.index - market_info.index_in >= 2 {
return true;
}
return false;
}
fn main() {
let _start = Instant::now();
let mydata = vec![
vec![1.0, 5.3, 0.5, 5.3, 1.0, 5.3, 1.0],
vec![0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0],
];
// backtest arguments
// values
// holding
// default_amt
// enter_market_function
// exit_market_function
let _returns = backtest(
mydata,
100,
5,
&enter_market_function,
&exit_market_function,
);
let _out = TradeInputResults { returns: _returns };
let duration = _start.elapsed();
println!("{:#?}", _out);
println!("{:#?}", duration);
}
// CONSOLE OUTPUT
// [1.0, 5.3, 0.5, 5.3, 1.0, 5.3, 1.0]
// TradeInputResults {
// returns: BacktestResults {
// calculated_returns: 0.5,
// tradesin: [],
// tradesout: [
// TradeActionOut {
// index_in: 2,
// price_in: 0.5,
// amt: 5,
// index_out: 4,
// price_out: 1.0,
// diff: 0.5,
// },
// ],
// },
// }
// 57.715µs