1+ use chrono:: { DateTime , Utc } ;
2+
13use crate :: {
24 ctx:: { Ctx , CtxSkip } ,
35 legacy:: Float64Utils ,
46 metrics:: {
5- avg_losing_trade, avg_trade, avg_win_loss_ratio, avg_winning_trade, gross_loss_pct ,
6- gross_profit_pct, net_profit_pct, profit_factor, sharpe_ratio_from_returns ,
7- sortino_ratio_from_returns, win_rate,
7+ annualization_factor , avg_losing_trade, avg_trade, avg_win_loss_ratio, avg_winning_trade,
8+ expectancy , gross_loss_pct , gross_profit_pct, net_profit_pct, profit_factor,
9+ sharpe_ratio_from_returns , sortino_ratio_from_returns, win_rate,
810 } ,
911 orderbook:: {
1012 order_size_for_equity_pct, round_contracts, round_to_min_tick, validate_contracts,
@@ -13,6 +15,7 @@ use crate::{
1315 signal:: { Signal , SignalKind } ,
1416 stats:: returns,
1517 sym:: Sym ,
18+ timeframe:: Timeframe ,
1619 trade:: { Trade , TradeError , TradeEvent } ,
1720 utils:: with_suffix,
1821} ;
@@ -37,6 +40,8 @@ pub struct BacktestConfig {
3740 initial_capital : f64 ,
3841 process_orders_on_close : bool ,
3942 debug : bool ,
43+ risk_free_rate : f64 ,
44+ annualization_factor : f64 ,
4045}
4146
4247impl Default for BacktestConfig {
@@ -45,6 +50,8 @@ impl Default for BacktestConfig {
4550 initial_capital : 1000.0 ,
4651 process_orders_on_close : false ,
4752 debug : false ,
53+ risk_free_rate : f64:: NAN ,
54+ annualization_factor : f64:: NAN ,
4855 }
4956 }
5057}
@@ -55,6 +62,8 @@ impl BacktestConfig {
5562 initial_capital,
5663 process_orders_on_close,
5764 debug : false ,
65+ risk_free_rate : f64:: NAN ,
66+ annualization_factor : f64:: NAN ,
5867 } ;
5968 }
6069
@@ -87,6 +96,16 @@ impl BacktestConfig {
8796 pub fn set_debug ( & mut self , debug : bool ) {
8897 self . debug = debug;
8998 }
99+
100+ #[ inline]
101+ pub fn set_risk_free_rate ( & mut self , risk_free_rate : f64 ) {
102+ self . risk_free_rate = risk_free_rate;
103+ }
104+
105+ #[ inline]
106+ pub fn set_annualization_factor ( & mut self , annualization_factor : f64 ) {
107+ self . annualization_factor = annualization_factor;
108+ }
90109}
91110
92111pub struct Backtest {
@@ -117,12 +136,19 @@ pub struct Backtest {
117136
118137impl Backtest {
119138 #[ inline]
120- pub fn new ( ctx : Rc < RefCell < Ctx > > , config : BacktestConfig ) -> Self {
139+ pub fn new ( ctx : Rc < RefCell < Ctx > > , mut config : BacktestConfig ) -> Self {
121140 let sym = ctx. borrow ( ) . sym ( ) . clone ( ) ;
122141 assert ! (
123142 !f64 :: is_nan( sym. min_qty( ) ) && !f64 :: is_nan( sym. min_tick( ) ) ,
124143 "Ctx Symbol is not suitable for backtesting, min_qty is NaN or min_tick is NaN"
125144 ) ;
145+ if config. risk_free_rate . is_nan ( ) {
146+ config. risk_free_rate = 0.0 ;
147+ }
148+ if config. annualization_factor . is_nan ( ) {
149+ config. annualization_factor =
150+ annualization_factor ( ctx. borrow ( ) . ohlcv ( ) . timeframe ( ) , sym. kind ( ) . periods ( ) ) ;
151+ }
126152 let initial_capital = config. initial_capital ;
127153 Self {
128154 ctx,
@@ -273,13 +299,20 @@ impl Backtest {
273299 }
274300
275301 #[ inline]
276- pub fn sharpe_ratio ( & self , rfr : f64 ) -> f64 {
277- sharpe_ratio_from_returns ( & self . returns_list ( ) , rfr)
302+ pub fn sharpe_ratio ( & self ) -> f64 {
303+ sharpe_ratio_from_returns ( & self . returns_list ( ) , self . config . risk_free_rate )
304+ * self . config . annualization_factor
278305 }
279306
280307 #[ inline]
281- pub fn sortino_ratio ( & self , rfr : f64 ) -> f64 {
282- sortino_ratio_from_returns ( & self . returns_list ( ) , rfr)
308+ pub fn sortino_ratio ( & self ) -> f64 {
309+ sortino_ratio_from_returns ( & self . returns_list ( ) , self . config . risk_free_rate )
310+ * self . config . annualization_factor
311+ }
312+
313+ #[ inline]
314+ pub fn expectancy ( & self ) -> f64 {
315+ expectancy ( & self . pnl_list ( ) )
283316 }
284317
285318 #[ inline]
@@ -411,8 +444,18 @@ impl Backtest {
411444 return trade;
412445 }
413446
447+ // @TODO
448+ // pub fn maybe_reset_equity_pct(&mut self) {
449+ // if self.position_size() == 0.0 {
450+ // self.prev_equity_pct = 0.0;
451+ // }
452+ // }
453+
414454 pub fn compute_equity_pct ( & mut self , equity_pct : f64 ) -> Option < OrderConfig > {
415455 let ctx = self . ctx . borrow ( ) ;
456+ // if self.bar_index() == 21454 || self.bar_index() == 21293 {
457+ // println!("[{} -> compute_equity_pct]: equity(): {:?} | equity_pct: {:?} | prev_equity_pct: {:?} ", self.bar_index(), self.equity(), equity_pct, self.prev_equity_pct);
458+ // }
416459 if self . equity ( ) > 0.0 {
417460 if !equity_pct. compare ( self . prev_equity_pct ) {
418461 // if true {
@@ -429,7 +472,7 @@ impl Backtest {
429472 let order_size =
430473 round_contracts ( base_order_size, ctx. sym ( ) . min_qty ( ) , ctx. sym ( ) . qty_scale ( ) ) ;
431474
432- // if self.config.debug {
475+ // if self.bar_index() == 21454 || self.bar_index() == 21293 {
433476 // println!("[{} -> compute_equity_pct]: equity_pct: {:?} | prev_equity_pct: {:?} | base_order_size: {:?} | order_size: {:?} | min_qty: {:?} | price_scale: {:?}", self.bar_index(), equity_pct, self.prev_equity_pct, base_order_size, order_size, ctx.sym().min_qty(), ctx.sym().price_scale());
434477 // }
435478
@@ -656,10 +699,16 @@ impl Backtest {
656699
657700 #[ inline]
658701 pub fn signal ( & mut self , signal : Signal ) {
702+ // if self.config.debug {
703+ // println!("[{} ->raw signal]: {:?}", self.bar_index(), &signal);
704+ // }
659705 let order: Option < OrderConfig > = match signal. kind ( ) {
660706 SignalKind :: EquityPct ( pct) => self . compute_equity_pct ( * pct) ,
661707 SignalKind :: Size ( size) => Some ( OrderConfig :: new ( * size, None ) ) ,
662- SignalKind :: CloseAll ( ) => Some ( OrderConfig :: new ( -self . position_size , None ) ) ,
708+ SignalKind :: CloseAll ( ) => {
709+ self . prev_equity_pct = 0.0 ;
710+ Some ( OrderConfig :: new ( -self . position_size , None ) )
711+ }
663712 _ => None ,
664713 } ;
665714 if self . config . debug {
@@ -801,7 +850,6 @@ for i = 0 to array.size(trades) - 1
801850
802851 #[ cfg( feature = "pretty_table" ) ]
803852 pub fn print_table ( & self ) {
804- let rfr = 0.0 ;
805853 let sym = self . ctx . borrow ( ) . sym ( ) . clone ( ) ;
806854 let f_price = with_suffix ( & format ! ( " {}" , sym. _currency( ) ) ) ;
807855 let f_percent = with_suffix ( "%" ) ;
@@ -830,12 +878,12 @@ for i = 0 to array.size(trades) - 1
830878
831879 table. add_row ( Row :: from ( vec ! [
832880 Cell :: new( "Sharpe Ratio" ) ,
833- Cell :: new( format!( "{:0.3}" , self . sharpe_ratio( rfr ) ) ) ,
881+ Cell :: new( format!( "{:0.3}" , self . sharpe_ratio( ) ) ) ,
834882 ] ) ) ;
835883
836884 table. add_row ( Row :: from ( vec ! [
837885 Cell :: new( "Sortino Ratio" ) ,
838- Cell :: new( format!( "{:0.3}" , self . sortino_ratio( rfr ) ) ) ,
886+ Cell :: new( format!( "{:0.3}" , self . sortino_ratio( ) ) ) ,
839887 ] ) ) ;
840888
841889 table. add_row ( Row :: from ( vec ! [
@@ -888,6 +936,11 @@ for i = 0 to array.size(trades) - 1
888936 Cell :: new( f_raw( self . avg_win_loss_ratio( ) ) ) ,
889937 ] ) ) ;
890938
939+ table. add_row ( Row :: from ( vec ! [
940+ Cell :: new( "Expectancy" ) ,
941+ Cell :: new( f_raw( self . expectancy( ) ) ) ,
942+ ] ) ) ;
943+
891944 // Print the table
892945 println ! ( "{}" , table) ;
893946 }
0 commit comments