From 99c87f6f5fc039592afe22cf883eb9fb666409ea Mon Sep 17 00:00:00 2001 From: "philippe.geraldeli" Date: Thu, 26 Dec 2024 08:59:49 -0300 Subject: [PATCH 01/15] draft: add initial code for vwap strategy --- src/strategies/custom/mod.rs | 1 + src/strategies/custom/vwap_strategy.rs | 68 ++++++++++++++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 src/strategies/custom/vwap_strategy.rs diff --git a/src/strategies/custom/mod.rs b/src/strategies/custom/mod.rs index d2e58b2..d3da077 100644 --- a/src/strategies/custom/mod.rs +++ b/src/strategies/custom/mod.rs @@ -1,3 +1,4 @@ pub mod dummy_strategy; pub mod reversal_momentum_strategy; pub mod stormer_scalper_strategy; +mod vwap_strategy; diff --git a/src/strategies/custom/vwap_strategy.rs b/src/strategies/custom/vwap_strategy.rs new file mode 100644 index 0000000..943a6e3 --- /dev/null +++ b/src/strategies/custom/vwap_strategy.rs @@ -0,0 +1,68 @@ +use chrono::NaiveTime; +use crate::indicators::simple_moving_average::SimpleMovingAverage; +use crate::indicators::volume_weighted_average_price::VolumeWeightedAveragePrice; +use crate::models::action::TradeAction; +use crate::models::asset::Asset; +use crate::models::candlestick::{TimeCandlestickGenerator, TimeUnit}; +use crate::models::exchange::Exchange::B3; +use crate::models::trade::Trade; +use crate::strategies::strategy::HighResolutionTradingStrategy; + +const SMA_PERIOD: usize = 5; +const CANDLESTICK_PERIOD: u64 = 15; +const MAXIMUM_TICKS_STOP_LOSS: usize = 5; +const TICKS_FIRST_TARGET_SIGNAL: usize = 15; + + +pub fn get_points_per_tick_asset(asset_name: String) -> Option { + if asset_name.eq("WIN") { + return Some(5.0) + } + println!("INFO: default case, using WIN ticker points per asset"); + Some(5.0) +} + +pub struct VWAPDayInitStrategy { + points_per_tick_asset: Option, + cut_off_time_sending_orders: NaiveTime, + time_force_close_open_positions: NaiveTime, + min_candles: usize, + candles_elapsed_open_position: usize, + candle_parser: TimeCandlestickGenerator, + sma: SimpleMovingAverage, + vwap: VolumeWeightedAveragePrice +} + +impl VWAPDayInitStrategy { + +} + +impl HighResolutionTradingStrategy for VWAPDayInitStrategy { + fn new() -> Self + where + Self: Sized + { + VWAPDayInitStrategy { + points_per_tick_asset: get_points_per_tick_asset(Asset::get_asset("WIN", B3, )), + cut_off_time_sending_orders: NaiveTime::from_hms_opt(9, 30, 00).unwrap(), + time_force_close_open_positions: NaiveTime::from_hms_opt(10, 00, 00).unwrap(), + min_candles: 10, + candles_elapsed_open_position: 0, + candle_parser: TimeCandlestickGenerator::new(TimeUnit::Seconds, CANDLESTICK_PERIOD), + sma: SimpleMovingAverage::new(5).unwrap(), + vwap: VolumeWeightedAveragePrice::new().unwrap(), + } + } + + fn name(&self) -> &str { + "VWAP_init_trading_day_strategy" + } + + fn init_trading_day(&mut self, asset: &Asset, context_trades: &[Trade]) { + todo!() + } + + fn process_new_trade(&mut self, new_trade: &Trade) -> TradeAction { + todo!() + } +} \ No newline at end of file From 5d7da7e0ca6df6c21a725ff266539a8834e2c15c Mon Sep 17 00:00:00 2001 From: "philippe.geraldeli" Date: Fri, 27 Dec 2024 22:02:37 -0300 Subject: [PATCH 02/15] draft: add open position and pre signal on vwap strategy --- src/models/mod.rs | 3 +- src/models/open_position.rs | 69 +++++++++++ src/strategies/custom/vwap_strategy.rs | 157 +++++++++++++++++++++---- 3 files changed, 206 insertions(+), 23 deletions(-) create mode 100644 src/models/open_position.rs diff --git a/src/models/mod.rs b/src/models/mod.rs index b7fc3cd..e1a3d2c 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -8,4 +8,5 @@ pub mod pnl; pub mod side; pub mod trade; pub mod order; -pub mod order_status; \ No newline at end of file +pub mod order_status; +pub mod open_position; \ No newline at end of file diff --git a/src/models/open_position.rs b/src/models/open_position.rs new file mode 100644 index 0000000..751cf54 --- /dev/null +++ b/src/models/open_position.rs @@ -0,0 +1,69 @@ +use chrono::{NaiveTime, Timelike}; +use crate::models::action::{market_order_from_side, OrderAction}; +use crate::models::side::Side; +use crate::models::trade::Trade; +use crate::services::trailing_stop::TrailingStop; +use crate::utils::date; + +pub struct OpenPosition { + pub(crate) side: Side, + pub(crate) open_quantity: usize, + quantity_per_chunk: Vec, + action_per_chunk: Vec +} + +impl OpenPosition { + pub fn new( + side: Side, + quantity_to_trade: usize, + number_of_orders: usize, + action_per_chunk: Vec, + ) -> OpenPosition { + assert_eq!(quantity_to_trade % number_of_orders, 0, "{} quantity must be divisible by the number of orders", quantity_to_trade); + assert_eq!(action_per_chunk.len(), number_of_orders, "Action per chunk must have the same number of orders"); + let quantity_per_chunk_unit = quantity_to_trade / number_of_orders; + + OpenPosition { + side, + open_quantity: quantity_to_trade, + quantity_per_chunk: vec![quantity_per_chunk_unit; number_of_orders], + action_per_chunk + } + } + + pub fn new_not_automatic_trailing( + side: Side, + quantity_to_trade: usize, + number_of_orders: usize, + entry_price: f64, + points_per_tick: f64 + ) -> OpenPosition { + assert_eq!(quantity_to_trade % number_of_orders, 0, "{} quantity must be divisible by the number of orders", quantity_to_trade); + let quantity_per_chunk_unit = quantity_to_trade / number_of_orders; + + OpenPosition { + side, + open_quantity: quantity_to_trade, + quantity_per_chunk: vec![quantity_per_chunk_unit; number_of_orders], + action_per_chunk: vec![TrailingStop::new_not_automatic_trailing(entry_price, side, points_per_tick); number_of_orders] + } + } + + + pub(crate) fn should_force_close_position_end_trading_day(new_trade: &Trade, side: Side, open_quantity: usize, time_force_close_open_positions: NaiveTime) -> Option { + let current_time = date::convert_to_naive_time(new_trade.time); + assert!( + current_time.is_some(), + "should never fail when converting a numeric time from an trade into NaiveTime. time from trade: {}", new_trade.time + ); + let current_time = current_time?; + + if current_time.num_seconds_from_midnight() >= time_force_close_open_positions.num_seconds_from_midnight() { + return Some(market_order_from_side( + side.opposite_side(), + open_quantity) + ); + } + None + } +} \ No newline at end of file diff --git a/src/strategies/custom/vwap_strategy.rs b/src/strategies/custom/vwap_strategy.rs index 943a6e3..ed33088 100644 --- a/src/strategies/custom/vwap_strategy.rs +++ b/src/strategies/custom/vwap_strategy.rs @@ -1,40 +1,124 @@ -use chrono::NaiveTime; +use chrono::{NaiveTime, Timelike}; +use ta::Next; use crate::indicators::simple_moving_average::SimpleMovingAverage; use crate::indicators::volume_weighted_average_price::VolumeWeightedAveragePrice; -use crate::models::action::TradeAction; +use crate::models::action::{OrderAction, TradeAction}; use crate::models::asset::Asset; -use crate::models::candlestick::{TimeCandlestickGenerator, TimeUnit}; -use crate::models::exchange::Exchange::B3; +use crate::models::candlestick::{CandleDirection, Candlestick, CandlestickGenerator, TimeCandlestickGenerator, TimeUnit}; +use crate::models::open_position::OpenPosition; +use crate::models::side::Side; use crate::models::trade::Trade; use crate::strategies::strategy::HighResolutionTradingStrategy; const SMA_PERIOD: usize = 5; -const CANDLESTICK_PERIOD: u64 = 15; -const MAXIMUM_TICKS_STOP_LOSS: usize = 5; -const TICKS_FIRST_TARGET_SIGNAL: usize = 15; - - -pub fn get_points_per_tick_asset(asset_name: String) -> Option { - if asset_name.eq("WIN") { - return Some(5.0) - } - println!("INFO: default case, using WIN ticker points per asset"); - Some(5.0) -} +const CANDLESTICK_PERIOD: u64 = 1; +const QUANTITY_PER_POSITION: usize = 2; +const CANDLESTICK_SIGNAL_BUFFER_SIZE: usize = 4; +const MAXIMUM_TICKS_STOP_LOSS: usize = 20; +const TICKS_FIRST_TARGET_SIGNAL: usize = 20; +const TICKS_SECOND_TARGET_SIGNAL: usize = 80; pub struct VWAPDayInitStrategy { points_per_tick_asset: Option, cut_off_time_sending_orders: NaiveTime, time_force_close_open_positions: NaiveTime, min_candles: usize, + candle_count: usize, + quadrant_changed_signal_buffer: usize, + current_side: CandleDirection, candles_elapsed_open_position: usize, candle_parser: TimeCandlestickGenerator, sma: SimpleMovingAverage, - vwap: VolumeWeightedAveragePrice + vwap: VolumeWeightedAveragePrice, + open_position: Option } impl VWAPDayInitStrategy { + fn get_current_operation_side(&self, candle: &Candlestick, vwap_value: f64) -> CandleDirection { + match vwap_value > candle.close { + true => CandleDirection::LONG, + false => CandleDirection::SHORT + } + } + + fn quadrant_changed_signal_buffer_calculation(&mut self, candlestick: &Candlestick, vwap_value: f64) -> bool { + let current_side = self.get_current_operation_side(&candlestick, vwap_value); + let is_side_changed = current_side != self.current_side; + + self.current_side = current_side; + + if is_side_changed { + self.quadrant_changed_signal_buffer = 1; + } else { + match self.quadrant_changed_signal_buffer { + 1..=CANDLESTICK_SIGNAL_BUFFER_SIZE => { + self.quadrant_changed_signal_buffer += 1; + }, + _ if self.quadrant_changed_signal_buffer > CANDLESTICK_SIGNAL_BUFFER_SIZE => { + self.quadrant_changed_signal_buffer = 0; + } + _ => {} + } + } + self.quadrant_changed_signal_buffer < CANDLESTICK_SIGNAL_BUFFER_SIZE && self.quadrant_changed_signal_buffer != 0 + } + + fn should_open_position(&mut self, vwap: f64, sma: f64, candlestick: &Candlestick) -> Option { + let pre_signal = self.quadrant_changed_signal_buffer_calculation(&candlestick, vwap); + + if self.candle_count < self.min_candles || !pre_signal || self.open_position.is_some() || + candlestick.close_time.expect("candlestick should be closed") + .num_seconds_from_midnight() > self.cut_off_time_sending_orders.num_seconds_from_midnight() { + return None; + } + + // Keeping it simple. Maybe validate the distance between vwap and sma, or the sma angle in the future; + if self.current_side == CandleDirection::SHORT && sma < vwap { + self.open_position = Some(OpenPosition::new_not_automatic_trailing( + Side::SELL, + 2, + 2, + candlestick.close, + self.points_per_tick_asset.unwrap() + )); + return Some(OrderAction::MarketShort(QUANTITY_PER_POSITION)) + } + + if self.current_side == CandleDirection::LONG && sma > vwap { + self.open_position = Some(OpenPosition::new_not_automatic_trailing( + Side::BUY, + 2, + 2, + candlestick.close, + self.points_per_tick_asset.unwrap() + )); + return Some(OrderAction::MarketLong(QUANTITY_PER_POSITION)) + } + + None + } + fn should_close_position(&mut self, new_trade: &Trade) -> Option { + if self.open_position.is_none() { + return None; + } + if let Some(force_exit) = OpenPosition::should_force_close_position_end_trading_day( + new_trade, + self.open_position.as_ref().unwrap().side, + self.open_position.as_ref().unwrap().open_quantity, + self.time_force_close_open_positions + ) { + self.open_position = None; + return Some(force_exit); + } + + + // Se atingir 100 pontos ativa trailing + // Se bater 100 pontos negativos, stop + // Se bater 400 pontos vende + + None + } } impl HighResolutionTradingStrategy for VWAPDayInitStrategy { @@ -43,14 +127,18 @@ impl HighResolutionTradingStrategy for VWAPDayInitStrategy { Self: Sized { VWAPDayInitStrategy { - points_per_tick_asset: get_points_per_tick_asset(Asset::get_asset("WIN", B3, )), + points_per_tick_asset: None, cut_off_time_sending_orders: NaiveTime::from_hms_opt(9, 30, 00).unwrap(), time_force_close_open_positions: NaiveTime::from_hms_opt(10, 00, 00).unwrap(), min_candles: 10, candles_elapsed_open_position: 0, - candle_parser: TimeCandlestickGenerator::new(TimeUnit::Seconds, CANDLESTICK_PERIOD), + candle_count: 0, + quadrant_changed_signal_buffer: 0, + candle_parser: TimeCandlestickGenerator::new(TimeUnit::Minutes, CANDLESTICK_PERIOD), sma: SimpleMovingAverage::new(5).unwrap(), vwap: VolumeWeightedAveragePrice::new().unwrap(), + current_side: CandleDirection::UNDEFINED, + open_position: None, } } @@ -58,11 +146,36 @@ impl HighResolutionTradingStrategy for VWAPDayInitStrategy { "VWAP_init_trading_day_strategy" } - fn init_trading_day(&mut self, asset: &Asset, context_trades: &[Trade]) { - todo!() + fn init_trading_day(&mut self, asset: &Asset, trades: &[Trade]) { + self.points_per_tick_asset = Some(asset.min_point_variation); + self.candles_elapsed_open_position = 0; + self.candle_count = 0; + self.quadrant_changed_signal_buffer = 0; + + self.sma.reset(); + self.vwap.reset(); + + for trade in trades { + if let Some(candle) = self.candle_parser.parse_only_closed_candles(trade) { + self.sma.next(candle); + self.update_candles(candle); + } + } } fn process_new_trade(&mut self, new_trade: &Trade) -> TradeAction { - todo!() + let mut order_open_position: Option = None; + let order_close_position: Option = self.should_close_position(new_trade); + + if let Some(candlestick) = self.candle_parser.parse_only_closed_candles(new_trade) { + self.candle_count += 1; + + let sma = self.sma.next(candlestick); + let vwap = self.vwap.next(&candlestick); + + order_open_position = self.should_open_position(vwap, sma, &candlestick); + } + + TradeAction::custom_order(order_close_position, order_open_position, None, None) } } \ No newline at end of file From c5abd28c9cd4e00d2bdbd71619afddc00ad07d80 Mon Sep 17 00:00:00 2001 From: "philippe.geraldeli" Date: Fri, 27 Dec 2024 22:06:52 -0300 Subject: [PATCH 03/15] draft: add open position and pre signal on vwap strategy --- src/services/trailing_stop.rs | 2 ++ src/strategies/custom/vwap_strategy.rs | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/services/trailing_stop.rs b/src/services/trailing_stop.rs index 6c20727..61f98b5 100644 --- a/src/services/trailing_stop.rs +++ b/src/services/trailing_stop.rs @@ -7,6 +7,8 @@ use crate::models::side::Side; /// /// **important:** you must call the function [TrailingStop::should_close_position] with every new price update /// to allow the trailing stop to properly **adjust** the desired **stop loss** + +#[derive(Clone)] pub struct TrailingStop { entry_price: f64, stop_loss_price: f64, diff --git a/src/strategies/custom/vwap_strategy.rs b/src/strategies/custom/vwap_strategy.rs index ed33088..6e01f2c 100644 --- a/src/strategies/custom/vwap_strategy.rs +++ b/src/strategies/custom/vwap_strategy.rs @@ -158,7 +158,6 @@ impl HighResolutionTradingStrategy for VWAPDayInitStrategy { for trade in trades { if let Some(candle) = self.candle_parser.parse_only_closed_candles(trade) { self.sma.next(candle); - self.update_candles(candle); } } } From 3da9773e42d14d152c0241486d3d178c8c2d132b Mon Sep 17 00:00:00 2001 From: "philippe.geraldeli" Date: Sat, 28 Dec 2024 15:30:32 -0300 Subject: [PATCH 04/15] feat: add turns_up and turns_down on sma --- src/indicators/simple_moving_average.rs | 84 ++++++++++++++++++++++++- 1 file changed, 83 insertions(+), 1 deletion(-) diff --git a/src/indicators/simple_moving_average.rs b/src/indicators/simple_moving_average.rs index a0407f4..be9f4ce 100644 --- a/src/indicators/simple_moving_average.rs +++ b/src/indicators/simple_moving_average.rs @@ -30,6 +30,36 @@ impl SimpleMovingAverage { }) } + pub fn turns_up(&self) -> bool { + if self.period < 3 { + unimplemented!(); + } + + let last_but_one_value = self.results[2]; + let last_value = self.results[1]; + let current_value = self.results[0]; + + if self.count >= 3 && last_but_one_value >= last_value && current_value > last_value { + return true; + } + false + } + + pub fn turns_down(&self) -> bool { + if self.period < 3 { + unimplemented!(); + } + + let last_but_one_value = self.results[2]; + let last_value = self.results[1]; + let current_value = self.results[0]; + + if self.count >= 3 && last_but_one_value <= last_value && current_value < last_value { + return true; + } + false + } + pub fn value(&self) -> f64 { self.current_value } @@ -114,7 +144,10 @@ impl Next for SimpleMovingAverage { self.sum = self.sum - old_val + input; self.current_value = self.sum / (self.count as f64); - self.results[initial_index] = self.current_value; + + self.results.rotate_right(1); + self.results[0] = self.current_value; + self.current_value } } @@ -214,6 +247,55 @@ mod tests { assert_eq!(sma.max_value_on_period(), 31.0); } + #[test] + fn test_turn_up(){ + let mut sma = SimpleMovingAverage::new(3).unwrap(); + sma.next(3.0); + sma.next(1.0); + sma.next(5.0); + assert_eq!(sma.turns_up(), true); + + sma.next(6.0); + assert_eq!(sma.turns_up(), false); + // + sma.next(7.0); + assert_eq!(sma.turns_up(), false); + + sma.next(1.0); + assert_eq!(sma.turns_up(), false); + + sma.next(6.0); + assert_eq!(sma.turns_up(), false); + + sma.next(10.0); + assert_eq!(sma.turns_up(), true); + } + + #[test] + fn test_turn_down(){ + // Values of result [ 3, 2, 3, 4, 5, 4.66, 4.66, 2,66 ] + let mut sma = SimpleMovingAverage::new(3).unwrap(); + sma.next(3.0); + sma.next(1.0); + sma.next(5.0); + assert_eq!(sma.turns_down(), false); + + sma.next(6.0); + assert_eq!(sma.turns_down(), false); + // + sma.next(7.0); + assert_eq!(sma.turns_down(), false); + + sma.next(1.0); + assert_eq!(sma.turns_down(), true); + + sma.next(6.0); + assert_eq!(sma.turns_down(), false); + + sma.next(1.0); + assert_eq!(sma.turns_down(), true); + } + fn new_candle(close: f64) -> Candlestick { Candlestick{ open: 0.0, From ca9a542c8ba252161897aa43dcbe2d6f078398b7 Mon Sep 17 00:00:00 2001 From: "philippe.geraldeli" Date: Sat, 28 Dec 2024 17:01:27 -0300 Subject: [PATCH 05/15] chore: add new verification on turns down and up sma --- src/indicators/simple_moving_average.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/indicators/simple_moving_average.rs b/src/indicators/simple_moving_average.rs index be9f4ce..caa3ef9 100644 --- a/src/indicators/simple_moving_average.rs +++ b/src/indicators/simple_moving_average.rs @@ -35,6 +35,8 @@ impl SimpleMovingAverage { unimplemented!(); } + assert!(self.count >= 3, "The SimpleMovingAverage needs to have at least 3 valid values."); + let last_but_one_value = self.results[2]; let last_value = self.results[1]; let current_value = self.results[0]; @@ -50,6 +52,8 @@ impl SimpleMovingAverage { unimplemented!(); } + assert!(self.count >= 3, "The SimpleMovingAverage needs to have at least 3 valid values."); + let last_but_one_value = self.results[2]; let last_value = self.results[1]; let current_value = self.results[0]; From 79c8690fb079d2c5dd09081c7da5d646774bbe00 Mon Sep 17 00:00:00 2001 From: Geraldeli Date: Tue, 7 Jan 2025 08:20:23 -0300 Subject: [PATCH 06/15] feat: initial strategy vwap with sma --- src/indicators/exponential_moving_average.rs | 55 ++++++++++ src/indicators/simple_moving_average.rs | 1 + .../volume_weighted_average_price.rs | 16 +-- src/models/mod.rs | 3 +- src/models/open_position.rs | 69 ------------ src/strategies/custom/mod.rs | 2 +- src/strategies/custom/vwap_strategy.rs | 100 ++++++++++++------ src/strategies/strategy.rs | 2 + 8 files changed, 134 insertions(+), 114 deletions(-) delete mode 100644 src/models/open_position.rs diff --git a/src/indicators/exponential_moving_average.rs b/src/indicators/exponential_moving_average.rs index c004a03..e56affa 100644 --- a/src/indicators/exponential_moving_average.rs +++ b/src/indicators/exponential_moving_average.rs @@ -4,9 +4,11 @@ use crate::models::candlestick::Candlestick; pub struct ExponentialMovingAverage { period: usize, + count: usize, k: f64, current: f64, is_new: bool, + results: Box<[f64]>, } #[allow(unused)] @@ -17,7 +19,9 @@ impl ExponentialMovingAverage { period, k: weight, current: 0.0, + count: 0, is_new: true, + results: vec![0.0; period].into_boxed_slice(), } } @@ -26,7 +30,9 @@ impl ExponentialMovingAverage { period, k: 2.0 / (period + 1) as f64, current: 0.0, + count: 0, is_new: true, + results: vec![0.0; period].into_boxed_slice(), } } @@ -40,20 +46,69 @@ impl ExponentialMovingAverage { pub fn reset(&mut self) { self.current = 0.0; + self.count = 0; self.is_new = true; + + for i in 0..self.period { + self.results[i] = 0.0; + } + } + + pub fn turns_up(&self) -> bool { + if self.period < 3 { + unimplemented!(); + } + + assert!(self.count >= 3, "The ExponentialMovingAverage turns_up method needs to have at least 3 candle period."); + + let last_but_one_value = self.results[2]; + let last_value = self.results[1]; + let current_value = self.results[0]; + + if self.count >= 3 && last_but_one_value >= last_value && current_value > last_value { + return true; + } + false + } + + pub fn turns_down(&self) -> bool { + if self.period < 3 { + unimplemented!(); + } + + assert!(self.count >= 3, "The ExponentialMovingAverage turns_down method needs to have at least 3 candle period"); + + let last_but_one_value = self.results[2]; + let last_value = self.results[1]; + let current_value = self.results[0]; + + if self.count >= 3 && last_but_one_value <= last_value && current_value < last_value { + return true; + } + false } + } impl Next for ExponentialMovingAverage { type Output = f64; fn next(&mut self, close_value: f64) -> Self::Output { + + if self.count < self.period { + self.count += 1; + } + if self.is_new { self.is_new = false; self.current = close_value; } else { self.current = self.k * close_value + (1.0 - self.k) * self.current; } + + self.results.rotate_right(1); + self.results[0] = self.current; + self.current } } diff --git a/src/indicators/simple_moving_average.rs b/src/indicators/simple_moving_average.rs index caa3ef9..f7d1feb 100644 --- a/src/indicators/simple_moving_average.rs +++ b/src/indicators/simple_moving_average.rs @@ -117,6 +117,7 @@ impl SimpleMovingAverage { self.current_value = 0.0; for i in 0..self.period { self.deque[i] = 0.0; + self.results[i] = 0.0; } } } diff --git a/src/indicators/volume_weighted_average_price.rs b/src/indicators/volume_weighted_average_price.rs index 2fa5a10..fa958a9 100644 --- a/src/indicators/volume_weighted_average_price.rs +++ b/src/indicators/volume_weighted_average_price.rs @@ -16,7 +16,7 @@ use ta::{Close, High, Low, Next, Volume}; pub struct VolumeWeightedAveragePrice { index: usize, current_value: f64, - cumulative_typical_price: f64, + cumulative_price_volume: f64, cumulative_volume: f64 } @@ -26,7 +26,7 @@ impl VolumeWeightedAveragePrice { Ok(Self { index: 0, current_value: 0.0, - cumulative_typical_price: 0.0, + cumulative_price_volume: 0.0, cumulative_volume: 0.0 }) } @@ -42,7 +42,7 @@ impl VolumeWeightedAveragePrice { pub fn reset(&mut self) { self.index = 0; self.current_value = 0.0; - self.cumulative_typical_price = 0.0; + self.cumulative_price_volume = 0.0; self.cumulative_volume = 0.0; } } @@ -51,11 +51,11 @@ impl Next<&T> for VolumeWeightedAveragePrice { type Output = f64; fn next(&mut self, input: &T) -> Self::Output { - let typical_price = (input.high() + input.low() + input.close()) / 3.0; - self.cumulative_typical_price += typical_price; + let typical_price_volume = ((input.high() + input.low() + input.close()) / 3.0) * input.volume(); + self.cumulative_price_volume += typical_price_volume; self.cumulative_volume += input.volume(); - self.current_value = self.cumulative_typical_price * input.volume() / self.cumulative_volume; + self.current_value = self.cumulative_price_volume / self.cumulative_volume; self.index += 1; self.current_value @@ -78,7 +78,7 @@ mod tests { fn test_new() { let vwap = VolumeWeightedAveragePrice::new().unwrap(); assert_eq!(vwap.index, 0); - assert_eq!(vwap.cumulative_typical_price, 0.0); + assert_eq!(vwap.cumulative_price_volume, 0.0); assert_eq!(vwap.cumulative_volume, 0.0); } @@ -121,7 +121,7 @@ mod tests { vwap.reset(); assert_eq!(vwap.current_value, 0.0); - assert_eq!(vwap.cumulative_typical_price, 0.0); + assert_eq!(vwap.cumulative_price_volume, 0.0); assert_eq!(vwap.cumulative_volume, 0.0); } } \ No newline at end of file diff --git a/src/models/mod.rs b/src/models/mod.rs index e1a3d2c..b7fc3cd 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -8,5 +8,4 @@ pub mod pnl; pub mod side; pub mod trade; pub mod order; -pub mod order_status; -pub mod open_position; \ No newline at end of file +pub mod order_status; \ No newline at end of file diff --git a/src/models/open_position.rs b/src/models/open_position.rs deleted file mode 100644 index 751cf54..0000000 --- a/src/models/open_position.rs +++ /dev/null @@ -1,69 +0,0 @@ -use chrono::{NaiveTime, Timelike}; -use crate::models::action::{market_order_from_side, OrderAction}; -use crate::models::side::Side; -use crate::models::trade::Trade; -use crate::services::trailing_stop::TrailingStop; -use crate::utils::date; - -pub struct OpenPosition { - pub(crate) side: Side, - pub(crate) open_quantity: usize, - quantity_per_chunk: Vec, - action_per_chunk: Vec -} - -impl OpenPosition { - pub fn new( - side: Side, - quantity_to_trade: usize, - number_of_orders: usize, - action_per_chunk: Vec, - ) -> OpenPosition { - assert_eq!(quantity_to_trade % number_of_orders, 0, "{} quantity must be divisible by the number of orders", quantity_to_trade); - assert_eq!(action_per_chunk.len(), number_of_orders, "Action per chunk must have the same number of orders"); - let quantity_per_chunk_unit = quantity_to_trade / number_of_orders; - - OpenPosition { - side, - open_quantity: quantity_to_trade, - quantity_per_chunk: vec![quantity_per_chunk_unit; number_of_orders], - action_per_chunk - } - } - - pub fn new_not_automatic_trailing( - side: Side, - quantity_to_trade: usize, - number_of_orders: usize, - entry_price: f64, - points_per_tick: f64 - ) -> OpenPosition { - assert_eq!(quantity_to_trade % number_of_orders, 0, "{} quantity must be divisible by the number of orders", quantity_to_trade); - let quantity_per_chunk_unit = quantity_to_trade / number_of_orders; - - OpenPosition { - side, - open_quantity: quantity_to_trade, - quantity_per_chunk: vec![quantity_per_chunk_unit; number_of_orders], - action_per_chunk: vec![TrailingStop::new_not_automatic_trailing(entry_price, side, points_per_tick); number_of_orders] - } - } - - - pub(crate) fn should_force_close_position_end_trading_day(new_trade: &Trade, side: Side, open_quantity: usize, time_force_close_open_positions: NaiveTime) -> Option { - let current_time = date::convert_to_naive_time(new_trade.time); - assert!( - current_time.is_some(), - "should never fail when converting a numeric time from an trade into NaiveTime. time from trade: {}", new_trade.time - ); - let current_time = current_time?; - - if current_time.num_seconds_from_midnight() >= time_force_close_open_positions.num_seconds_from_midnight() { - return Some(market_order_from_side( - side.opposite_side(), - open_quantity) - ); - } - None - } -} \ No newline at end of file diff --git a/src/strategies/custom/mod.rs b/src/strategies/custom/mod.rs index d3da077..ff1d5f9 100644 --- a/src/strategies/custom/mod.rs +++ b/src/strategies/custom/mod.rs @@ -1,4 +1,4 @@ pub mod dummy_strategy; pub mod reversal_momentum_strategy; pub mod stormer_scalper_strategy; -mod vwap_strategy; +pub mod vwap_strategy; diff --git a/src/strategies/custom/vwap_strategy.rs b/src/strategies/custom/vwap_strategy.rs index 6e01f2c..740b422 100644 --- a/src/strategies/custom/vwap_strategy.rs +++ b/src/strategies/custom/vwap_strategy.rs @@ -1,19 +1,20 @@ use chrono::{NaiveTime, Timelike}; use ta::Next; +use crate::indicators::exponential_moving_average::ExponentialMovingAverage; use crate::indicators::simple_moving_average::SimpleMovingAverage; use crate::indicators::volume_weighted_average_price::VolumeWeightedAveragePrice; use crate::models::action::{OrderAction, TradeAction}; use crate::models::asset::Asset; use crate::models::candlestick::{CandleDirection, Candlestick, CandlestickGenerator, TimeCandlestickGenerator, TimeUnit}; -use crate::models::open_position::OpenPosition; use crate::models::side::Side; use crate::models::trade::Trade; +use crate::services::trailing_stop::TrailingStop; use crate::strategies::strategy::HighResolutionTradingStrategy; const SMA_PERIOD: usize = 5; const CANDLESTICK_PERIOD: u64 = 1; const QUANTITY_PER_POSITION: usize = 2; -const CANDLESTICK_SIGNAL_BUFFER_SIZE: usize = 4; +const CANDLESTICK_SIGNAL_BUFFER_SIZE: usize = 10; const MAXIMUM_TICKS_STOP_LOSS: usize = 20; const TICKS_FIRST_TARGET_SIGNAL: usize = 20; const TICKS_SECOND_TARGET_SIGNAL: usize = 80; @@ -29,6 +30,7 @@ pub struct VWAPDayInitStrategy { candles_elapsed_open_position: usize, candle_parser: TimeCandlestickGenerator, sma: SimpleMovingAverage, + ema: ExponentialMovingAverage, vwap: VolumeWeightedAveragePrice, open_position: Option } @@ -63,7 +65,7 @@ impl VWAPDayInitStrategy { self.quadrant_changed_signal_buffer < CANDLESTICK_SIGNAL_BUFFER_SIZE && self.quadrant_changed_signal_buffer != 0 } - fn should_open_position(&mut self, vwap: f64, sma: f64, candlestick: &Candlestick) -> Option { + fn should_open_position(&mut self, vwap: f64, candlestick: &Candlestick) -> Option { let pre_signal = self.quadrant_changed_signal_buffer_calculation(&candlestick, vwap); if self.candle_count < self.min_candles || !pre_signal || self.open_position.is_some() || @@ -73,24 +75,22 @@ impl VWAPDayInitStrategy { } // Keeping it simple. Maybe validate the distance between vwap and sma, or the sma angle in the future; - if self.current_side == CandleDirection::SHORT && sma < vwap { - self.open_position = Some(OpenPosition::new_not_automatic_trailing( + if self.current_side == CandleDirection::SHORT && self.sma.turns_down() { + self.open_position = Some(OpenPosition::new( Side::SELL, - 2, - 2, candlestick.close, - self.points_per_tick_asset.unwrap() + self.points_per_tick_asset.unwrap(), + QUANTITY_PER_POSITION )); return Some(OrderAction::MarketShort(QUANTITY_PER_POSITION)) } - if self.current_side == CandleDirection::LONG && sma > vwap { - self.open_position = Some(OpenPosition::new_not_automatic_trailing( + if self.current_side == CandleDirection::LONG && self.sma.turns_up() { + self.open_position = Some(OpenPosition::new( Side::BUY, - 2, - 2, candlestick.close, - self.points_per_tick_asset.unwrap() + self.points_per_tick_asset.unwrap(), + QUANTITY_PER_POSITION )); return Some(OrderAction::MarketLong(QUANTITY_PER_POSITION)) } @@ -102,20 +102,20 @@ impl VWAPDayInitStrategy { if self.open_position.is_none() { return None; } - if let Some(force_exit) = OpenPosition::should_force_close_position_end_trading_day( - new_trade, - self.open_position.as_ref().unwrap().side, - self.open_position.as_ref().unwrap().open_quantity, - self.time_force_close_open_positions - ) { - self.open_position = None; - return Some(force_exit); - } + if let Some(position) = self.open_position.as_mut() { + self.candles_elapsed_open_position += 1; - // Se atingir 100 pontos ativa trailing - // Se bater 100 pontos negativos, stop - // Se bater 400 pontos vende + if self.current_side == CandleDirection::SHORT && self.sma.turns_up() { + self.open_position = None; + return Some(OrderAction::MarketLong(QUANTITY_PER_POSITION)) + } + + if self.current_side == CandleDirection::LONG && self.sma.turns_down() { + self.open_position = None; + return Some(OrderAction::MarketShort(QUANTITY_PER_POSITION)) + } + } None } @@ -128,14 +128,15 @@ impl HighResolutionTradingStrategy for VWAPDayInitStrategy { { VWAPDayInitStrategy { points_per_tick_asset: None, - cut_off_time_sending_orders: NaiveTime::from_hms_opt(9, 30, 00).unwrap(), - time_force_close_open_positions: NaiveTime::from_hms_opt(10, 00, 00).unwrap(), + cut_off_time_sending_orders: NaiveTime::from_hms_opt(11, 30, 00).unwrap(), + time_force_close_open_positions: NaiveTime::from_hms_opt(17, 00, 00).unwrap(), min_candles: 10, candles_elapsed_open_position: 0, candle_count: 0, quadrant_changed_signal_buffer: 0, candle_parser: TimeCandlestickGenerator::new(TimeUnit::Minutes, CANDLESTICK_PERIOD), - sma: SimpleMovingAverage::new(5).unwrap(), + sma: SimpleMovingAverage::new(9).unwrap(), + ema: ExponentialMovingAverage::new(9, 1.0), vwap: VolumeWeightedAveragePrice::new().unwrap(), current_side: CandleDirection::UNDEFINED, open_position: None, @@ -153,13 +154,15 @@ impl HighResolutionTradingStrategy for VWAPDayInitStrategy { self.quadrant_changed_signal_buffer = 0; self.sma.reset(); + self.ema.reset(); self.vwap.reset(); - for trade in trades { - if let Some(candle) = self.candle_parser.parse_only_closed_candles(trade) { - self.sma.next(candle); - } - } + // for trade in trades { + // if let Some(candle) = self.candle_parser.parse_only_closed_candles(trade) { + // self.sma.next(candle); + // self + // } + // } } fn process_new_trade(&mut self, new_trade: &Trade) -> TradeAction { @@ -170,11 +173,40 @@ impl HighResolutionTradingStrategy for VWAPDayInitStrategy { self.candle_count += 1; let sma = self.sma.next(candlestick); + let ema = self.ema.next(candlestick); let vwap = self.vwap.next(&candlestick); - order_open_position = self.should_open_position(vwap, sma, &candlestick); + order_open_position = self.should_open_position(vwap, &candlestick); } TradeAction::custom_order(order_close_position, order_open_position, None, None) } +} + +struct OpenPosition { + side: Side, + open_quantity: usize, + quantity_per_chunk: [usize; 2], + first_chunk: TrailingStop, +} + +impl OpenPosition { + fn new( + side: Side, + entry_price: f64, + points_per_tick: f64, + quantity_to_trade: usize, + ) -> Self { + assert!(quantity_to_trade % 2 == 0, "{} quantity must be divisible by 2", quantity_to_trade); + OpenPosition { + side, + open_quantity: quantity_to_trade, + quantity_per_chunk: [quantity_to_trade / 2, quantity_to_trade / 2], + first_chunk: TrailingStop::new_not_automatic_trailing( + entry_price, + side, + points_per_tick, + ), + } + } } \ No newline at end of file diff --git a/src/strategies/strategy.rs b/src/strategies/strategy.rs index 6b7f366..952c942 100644 --- a/src/strategies/strategy.rs +++ b/src/strategies/strategy.rs @@ -6,6 +6,7 @@ use crate::models::trade::Trade; use crate::strategies::custom::dummy_strategy::{DummyCandlestickStrategy, DummyHighResolutionStrategy}; use crate::strategies::custom::reversal_momentum_strategy::{ClassicReversalMomentumCandlestickStrategy, ReversalMomentumCandlestickStrategy}; use crate::strategies::custom::stormer_scalper_strategy::StormerScalperStrategy; +use crate::strategies::custom::vwap_strategy::VWAPDayInitStrategy; /// algorithmic trading strategy to operate in the capital markets using raw Trade data pub trait HighResolutionTradingStrategy { @@ -73,6 +74,7 @@ pub fn list_all_high_resolution_strategies() -> Vec Date: Tue, 7 Jan 2025 08:43:05 -0300 Subject: [PATCH 07/15] chore: linting code --- src/indicators/simple_moving_average.rs | 1 - .../backtest/exchange_event_handler.rs | 2 +- src/strategies/custom/vwap_strategy.rs | 73 +++++++++++++------ 3 files changed, 52 insertions(+), 24 deletions(-) diff --git a/src/indicators/simple_moving_average.rs b/src/indicators/simple_moving_average.rs index f7d1feb..9346f2c 100644 --- a/src/indicators/simple_moving_average.rs +++ b/src/indicators/simple_moving_average.rs @@ -134,7 +134,6 @@ impl Next for SimpleMovingAverage { fn next(&mut self, input: f64) -> Self::Output { let old_val = self.deque[self.index]; self.deque[self.index] = input; - let initial_index = self.index; self.index = if self.index + 1 < self.period { diff --git a/src/services/backtest/exchange_event_handler.rs b/src/services/backtest/exchange_event_handler.rs index 0a766ff..49f775d 100644 --- a/src/services/backtest/exchange_event_handler.rs +++ b/src/services/backtest/exchange_event_handler.rs @@ -13,7 +13,7 @@ use crate::services::backtest::logger::log_lack_of_margin; use crate::strategies::strategy::{TradingStrategy}; use crate::utils::date; -pub const INITIAL_BALANCE: f64 = 40_000.0; +pub const INITIAL_BALANCE: f64 = 10_000.0; pub const MINIMUM_BALANCE: f64 = 800.0; const DEFAULT_SLIPPAGE_MULTIPLIER: f64 = 0.0001; const COMPULSORY_CLOSE_FEE: f64 = 25.0; diff --git a/src/strategies/custom/vwap_strategy.rs b/src/strategies/custom/vwap_strategy.rs index 740b422..a939f5a 100644 --- a/src/strategies/custom/vwap_strategy.rs +++ b/src/strategies/custom/vwap_strategy.rs @@ -9,15 +9,18 @@ use crate::models::candlestick::{CandleDirection, Candlestick, CandlestickGenera use crate::models::side::Side; use crate::models::trade::Trade; use crate::services::trailing_stop::TrailingStop; -use crate::strategies::strategy::HighResolutionTradingStrategy; +use crate::strategies::strategy::{TradingStrategy}; +use crate::utils::date; -const SMA_PERIOD: usize = 5; +const SMA_PERIOD: usize = 9; +const EMA_PERIOD: usize = 9; const CANDLESTICK_PERIOD: u64 = 1; const QUANTITY_PER_POSITION: usize = 2; const CANDLESTICK_SIGNAL_BUFFER_SIZE: usize = 10; -const MAXIMUM_TICKS_STOP_LOSS: usize = 20; -const TICKS_FIRST_TARGET_SIGNAL: usize = 20; -const TICKS_SECOND_TARGET_SIGNAL: usize = 80; + +// const MAXIMUM_TICKS_STOP_LOSS: usize = 20; +// const TICKS_FIRST_TARGET_SIGNAL: usize = 20; +// const TICKS_SECOND_TARGET_SIGNAL: usize = 80; pub struct VWAPDayInitStrategy { points_per_tick_asset: Option, @@ -103,7 +106,16 @@ impl VWAPDayInitStrategy { return None; } - if let Some(position) = self.open_position.as_mut() { + if let Some(force_exit) = self.should_force_close_position_end_trading_day( + new_trade, + self.open_position.as_ref()?.side, + self.open_position.as_ref()?.open_quantity, + ) { + self.open_position = None; + return Some(force_exit); + } + + if let Some(_position) = self.open_position.as_mut() { self.candles_elapsed_open_position += 1; if self.current_side == CandleDirection::SHORT && self.sma.turns_up() { @@ -119,9 +131,31 @@ impl VWAPDayInitStrategy { None } + + fn should_force_close_position_end_trading_day(&self, new_trade: &Trade, side: Side, open_quantity: usize) -> Option { + let current_time = date::convert_to_naive_time(new_trade.time); + assert!( + current_time.is_some(), + "should never fail when converting a numeric time from an trade into NaiveTime. time from trade: {}", new_trade.time + ); + let current_time = current_time?; + + // we have an open position be the end of trading day is approaching, we must close it + let current_time = current_time.num_seconds_from_midnight(); + let force_close_time = self.time_force_close_open_positions.num_seconds_from_midnight(); + + if current_time >= force_close_time { + return if side == Side::BUY { + Some(OrderAction::MarketShort(open_quantity)) + } else { + Some(OrderAction::MarketLong(open_quantity)) + } + } + None + } } -impl HighResolutionTradingStrategy for VWAPDayInitStrategy { +impl TradingStrategy for VWAPDayInitStrategy { fn new() -> Self where Self: Sized @@ -135,8 +169,8 @@ impl HighResolutionTradingStrategy for VWAPDayInitStrategy { candle_count: 0, quadrant_changed_signal_buffer: 0, candle_parser: TimeCandlestickGenerator::new(TimeUnit::Minutes, CANDLESTICK_PERIOD), - sma: SimpleMovingAverage::new(9).unwrap(), - ema: ExponentialMovingAverage::new(9, 1.0), + sma: SimpleMovingAverage::new(SMA_PERIOD).unwrap(), + ema: ExponentialMovingAverage::default(EMA_PERIOD), vwap: VolumeWeightedAveragePrice::new().unwrap(), current_side: CandleDirection::UNDEFINED, open_position: None, @@ -147,7 +181,7 @@ impl HighResolutionTradingStrategy for VWAPDayInitStrategy { "VWAP_init_trading_day_strategy" } - fn init_trading_day(&mut self, asset: &Asset, trades: &[Trade]) { + fn init_trading_day(&mut self, asset: &Asset, _trades: &[Trade]) { self.points_per_tick_asset = Some(asset.min_point_variation); self.candles_elapsed_open_position = 0; self.candle_count = 0; @@ -156,13 +190,6 @@ impl HighResolutionTradingStrategy for VWAPDayInitStrategy { self.sma.reset(); self.ema.reset(); self.vwap.reset(); - - // for trade in trades { - // if let Some(candle) = self.candle_parser.parse_only_closed_candles(trade) { - // self.sma.next(candle); - // self - // } - // } } fn process_new_trade(&mut self, new_trade: &Trade) -> TradeAction { @@ -172,8 +199,8 @@ impl HighResolutionTradingStrategy for VWAPDayInitStrategy { if let Some(candlestick) = self.candle_parser.parse_only_closed_candles(new_trade) { self.candle_count += 1; - let sma = self.sma.next(candlestick); - let ema = self.ema.next(candlestick); + let _sma = self.sma.next(candlestick); + let _ema = self.ema.next(candlestick); let vwap = self.vwap.next(&candlestick); order_open_position = self.should_open_position(vwap, &candlestick); @@ -183,13 +210,15 @@ impl HighResolutionTradingStrategy for VWAPDayInitStrategy { } } +#[allow(unused)] struct OpenPosition { side: Side, open_quantity: usize, - quantity_per_chunk: [usize; 2], + quantity_per_chunk: [usize; 1], first_chunk: TrailingStop, } +#[allow(unused)] impl OpenPosition { fn new( side: Side, @@ -197,11 +226,11 @@ impl OpenPosition { points_per_tick: f64, quantity_to_trade: usize, ) -> Self { - assert!(quantity_to_trade % 2 == 0, "{} quantity must be divisible by 2", quantity_to_trade); + // assert_eq!(quantity_to_trade % 2, 0, "{} quantity must be divisible by 2", quantity_to_trade); OpenPosition { side, open_quantity: quantity_to_trade, - quantity_per_chunk: [quantity_to_trade / 2, quantity_to_trade / 2], + quantity_per_chunk: [quantity_to_trade], first_chunk: TrailingStop::new_not_automatic_trailing( entry_price, side, From bda90525dd0ab083403800172548281106890db9 Mon Sep 17 00:00:00 2001 From: Geraldeli Date: Tue, 7 Jan 2025 08:56:21 -0300 Subject: [PATCH 08/15] fix: tests --- src/indicators/volume_weighted_average_price.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/indicators/volume_weighted_average_price.rs b/src/indicators/volume_weighted_average_price.rs index fa958a9..d69b5ef 100644 --- a/src/indicators/volume_weighted_average_price.rs +++ b/src/indicators/volume_weighted_average_price.rs @@ -87,10 +87,8 @@ mod tests { let test_data = vec![ // high, low , close, volume, expected (150.5, 145.0, 148.0, 5000, 147.83), - (155.0, 147.5, 153.0, 4000, 133.19), - (160.0, 150.0, 155.0, 6000, 181.87), - (158.5, 151.0, 154.5, 4500, 140.62), - (162.0, 153.5, 159.0, 5500, 168.85) + (155.0, 147.5, 153.0, 4000, 149.61), + (160.0, 150.0, 155.0, 6000, 151.77) ]; let mut expected_index = 0usize; From 1daf8555f73bbf3a5446b745764b403276dd54d4 Mon Sep 17 00:00:00 2001 From: Geraldeli Date: Wed, 8 Jan 2025 07:41:31 -0300 Subject: [PATCH 09/15] fix: tests --- src/services/backtest/exchange_event_handler.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/backtest/exchange_event_handler.rs b/src/services/backtest/exchange_event_handler.rs index 49f775d..0a766ff 100644 --- a/src/services/backtest/exchange_event_handler.rs +++ b/src/services/backtest/exchange_event_handler.rs @@ -13,7 +13,7 @@ use crate::services::backtest::logger::log_lack_of_margin; use crate::strategies::strategy::{TradingStrategy}; use crate::utils::date; -pub const INITIAL_BALANCE: f64 = 10_000.0; +pub const INITIAL_BALANCE: f64 = 40_000.0; pub const MINIMUM_BALANCE: f64 = 800.0; const DEFAULT_SLIPPAGE_MULTIPLIER: f64 = 0.0001; const COMPULSORY_CLOSE_FEE: f64 = 25.0; From 2778bbbb9c214bf131f136340dbe7a45a76bfa76 Mon Sep 17 00:00:00 2001 From: Geraldeli Date: Thu, 9 Jan 2025 11:36:42 -0300 Subject: [PATCH 10/15] fix: fixing bug and tweak strategy --- src/indicators/simple_moving_average.rs | 36 ++++++++++- src/strategies/custom/vwap_strategy.rs | 80 +++++++++++++++++++------ 2 files changed, 96 insertions(+), 20 deletions(-) diff --git a/src/indicators/simple_moving_average.rs b/src/indicators/simple_moving_average.rs index 9346f2c..f69f113 100644 --- a/src/indicators/simple_moving_average.rs +++ b/src/indicators/simple_moving_average.rs @@ -30,12 +30,44 @@ impl SimpleMovingAverage { }) } + pub fn trending_up(&mut self) -> bool { + if self.period < 3 { + unimplemented!(); + } + assert!(self.count >= 3, "The SimpleMovingAverage needs to have at least 3 valid values for function trending_up()."); + + let last_but_one_value = self.results[2]; + let last_value = self.results[1]; + let current_value = self.results[0]; + + if last_but_one_value < last_value && last_value < current_value { + return true; + } + false + } + + pub fn trending_down(&mut self) -> bool { + if self.period < 3 { + unimplemented!(); + } + assert!(self.count >= 3, "The SimpleMovingAverage needs to have at least 3 valid values for function trending_down()."); + + let last_but_one_value = self.results[2]; + let last_value = self.results[1]; + let current_value = self.results[0]; + + if last_but_one_value > last_value && last_value > current_value { + return true; + } + false + } + pub fn turns_up(&self) -> bool { if self.period < 3 { unimplemented!(); } - assert!(self.count >= 3, "The SimpleMovingAverage needs to have at least 3 valid values."); + assert!(self.count >= 3, "The SimpleMovingAverage needs to have at least 3 valid values for function turns_up()."); let last_but_one_value = self.results[2]; let last_value = self.results[1]; @@ -52,7 +84,7 @@ impl SimpleMovingAverage { unimplemented!(); } - assert!(self.count >= 3, "The SimpleMovingAverage needs to have at least 3 valid values."); + assert!(self.count >= 3, "The SimpleMovingAverage needs to have at least 3 valid values for function turns_down()."); let last_but_one_value = self.results[2]; let last_value = self.results[1]; diff --git a/src/strategies/custom/vwap_strategy.rs b/src/strategies/custom/vwap_strategy.rs index a939f5a..7a6b70f 100644 --- a/src/strategies/custom/vwap_strategy.rs +++ b/src/strategies/custom/vwap_strategy.rs @@ -3,7 +3,7 @@ use ta::Next; use crate::indicators::exponential_moving_average::ExponentialMovingAverage; use crate::indicators::simple_moving_average::SimpleMovingAverage; use crate::indicators::volume_weighted_average_price::VolumeWeightedAveragePrice; -use crate::models::action::{OrderAction, TradeAction}; +use crate::models::action::{market_order_from_side, OrderAction, TradeAction}; use crate::models::asset::Asset; use crate::models::candlestick::{CandleDirection, Candlestick, CandlestickGenerator, TimeCandlestickGenerator, TimeUnit}; use crate::models::side::Side; @@ -14,13 +14,13 @@ use crate::utils::date; const SMA_PERIOD: usize = 9; const EMA_PERIOD: usize = 9; -const CANDLESTICK_PERIOD: u64 = 1; +const CANDLESTICK_PERIOD: u64 = 10; const QUANTITY_PER_POSITION: usize = 2; const CANDLESTICK_SIGNAL_BUFFER_SIZE: usize = 10; -// const MAXIMUM_TICKS_STOP_LOSS: usize = 20; +// const TICKS_STOP_LOSS: usize = 15; // const TICKS_FIRST_TARGET_SIGNAL: usize = 20; -// const TICKS_SECOND_TARGET_SIGNAL: usize = 80; +// const TICKS_SECOND_TARGET_SIGNAL: usize = 30; pub struct VWAPDayInitStrategy { points_per_tick_asset: Option, @@ -30,7 +30,6 @@ pub struct VWAPDayInitStrategy { candle_count: usize, quadrant_changed_signal_buffer: usize, current_side: CandleDirection, - candles_elapsed_open_position: usize, candle_parser: TimeCandlestickGenerator, sma: SimpleMovingAverage, ema: ExponentialMovingAverage, @@ -71,14 +70,19 @@ impl VWAPDayInitStrategy { fn should_open_position(&mut self, vwap: f64, candlestick: &Candlestick) -> Option { let pre_signal = self.quadrant_changed_signal_buffer_calculation(&candlestick, vwap); - if self.candle_count < self.min_candles || !pre_signal || self.open_position.is_some() || + if self.open_position.is_some() { + return None; + } + + if self.candle_count < self.min_candles || candlestick.close_time.expect("candlestick should be closed") .num_seconds_from_midnight() > self.cut_off_time_sending_orders.num_seconds_from_midnight() { return None; } // Keeping it simple. Maybe validate the distance between vwap and sma, or the sma angle in the future; - if self.current_side == CandleDirection::SHORT && self.sma.turns_down() { + // if self.current_side == CandleDirection::SHORT && self.sma.turns_down() { + if pre_signal && self.current_side == CandleDirection::SHORT && (self.sma.turns_down() || self.sma.trending_down() ) { self.open_position = Some(OpenPosition::new( Side::SELL, candlestick.close, @@ -88,7 +92,8 @@ impl VWAPDayInitStrategy { return Some(OrderAction::MarketShort(QUANTITY_PER_POSITION)) } - if self.current_side == CandleDirection::LONG && self.sma.turns_up() { + // if self.current_side == CandleDirection::LONG && self.sma.turns_up() { + if pre_signal && self.current_side == CandleDirection::LONG && (self.sma.turns_up() || self.sma.trending_up()) { self.open_position = Some(OpenPosition::new( Side::BUY, candlestick.close, @@ -115,20 +120,47 @@ impl VWAPDayInitStrategy { return Some(force_exit); } - if let Some(_position) = self.open_position.as_mut() { - self.candles_elapsed_open_position += 1; + // let stop_position = self.open_position.as_mut()?.first_chunk.should_close_position(new_trade.price); + // + // if stop_position { + // if self.open_position.as_ref()?.side == Side::SELL { + // self.open_position = None; + // return Some(OrderAction::MarketLong(QUANTITY_PER_POSITION)) + // } + // + // if self.open_position.as_ref()?.side == Side::BUY { + // self.open_position = None; + // return Some(OrderAction::MarketShort(QUANTITY_PER_POSITION)) + // } + // } - if self.current_side == CandleDirection::SHORT && self.sma.turns_up() { + if let Some(_position) = self.open_position.as_mut() { + if self.open_position.as_ref()?.side == Side::SELL && self.sma.turns_up() { self.open_position = None; return Some(OrderAction::MarketLong(QUANTITY_PER_POSITION)) } - - if self.current_side == CandleDirection::LONG && self.sma.turns_down() { + if self.open_position.as_ref()?.side == Side::BUY && self.sma.turns_down() { self.open_position = None; return Some(OrderAction::MarketShort(QUANTITY_PER_POSITION)) } } + // if let Some(_position) = self.open_position.as_mut() { + // if self.open_position.as_ref()?.side == Side::SELL && self.sma.turns_up() { + // if new_trade.price < self.open_position.as_ref()?.first_chunk.entry_price - (self.open_position.as_ref()?.first_chunk.entry_price * self.points_per_tick_asset.unwrap()) { + // self.open_position = None; + // return Some(OrderAction::MarketLong(QUANTITY_PER_POSITION)) + // } + // } + // + // if self.open_position.as_ref()?.side == Side::BUY && self.sma.turns_down() { + // if new_trade.price > self.open_position.as_ref()?.first_chunk.entry_price + (self.open_position.as_ref()?.first_chunk.entry_price * self.points_per_tick_asset.unwrap()) { + // self.open_position = None; + // return Some(OrderAction::MarketShort(QUANTITY_PER_POSITION)) + // } + // } + // } + None } @@ -162,13 +194,12 @@ impl TradingStrategy for VWAPDayInitStrategy { { VWAPDayInitStrategy { points_per_tick_asset: None, - cut_off_time_sending_orders: NaiveTime::from_hms_opt(11, 30, 00).unwrap(), - time_force_close_open_positions: NaiveTime::from_hms_opt(17, 00, 00).unwrap(), + cut_off_time_sending_orders: NaiveTime::from_hms_opt(10, 30, 00).unwrap(), + time_force_close_open_positions: NaiveTime::from_hms_opt(17, 30, 00).unwrap(), min_candles: 10, - candles_elapsed_open_position: 0, candle_count: 0, quadrant_changed_signal_buffer: 0, - candle_parser: TimeCandlestickGenerator::new(TimeUnit::Minutes, CANDLESTICK_PERIOD), + candle_parser: TimeCandlestickGenerator::new(TimeUnit::Seconds, CANDLESTICK_PERIOD), sma: SimpleMovingAverage::new(SMA_PERIOD).unwrap(), ema: ExponentialMovingAverage::default(EMA_PERIOD), vwap: VolumeWeightedAveragePrice::new().unwrap(), @@ -183,7 +214,6 @@ impl TradingStrategy for VWAPDayInitStrategy { fn init_trading_day(&mut self, asset: &Asset, _trades: &[Trade]) { self.points_per_tick_asset = Some(asset.min_point_variation); - self.candles_elapsed_open_position = 0; self.candle_count = 0; self.quadrant_changed_signal_buffer = 0; @@ -204,6 +234,10 @@ impl TradingStrategy for VWAPDayInitStrategy { let vwap = self.vwap.next(&candlestick); order_open_position = self.should_open_position(vwap, &candlestick); + + if let Some(position) = self.open_position.as_mut() { + position.first_chunk.set_stop_loss(vwap); + } } TradeAction::custom_order(order_close_position, order_open_position, None, None) @@ -236,6 +270,16 @@ impl OpenPosition { side, points_per_tick, ), + // first_chunk: TrailingStop::new( + // entry_price, + // side, + // points_per_tick, + // TICKS_STOP_LOSS, + // TICKS_FIRST_TARGET_SIGNAL, + // 1, + // 1, + // 1 + // ) } } } \ No newline at end of file From b331a6900663f04917684340fa3e0d4a11faaa49 Mon Sep 17 00:00:00 2001 From: Geraldeli Date: Thu, 9 Jan 2025 14:24:45 -0300 Subject: [PATCH 11/15] feat: add sma 20 to filter bad trades --- .../backtest/exchange_event_handler.rs | 2 +- src/strategies/custom/vwap_strategy.rs | 46 +++++++++++-------- 2 files changed, 28 insertions(+), 20 deletions(-) diff --git a/src/services/backtest/exchange_event_handler.rs b/src/services/backtest/exchange_event_handler.rs index 0a766ff..49f775d 100644 --- a/src/services/backtest/exchange_event_handler.rs +++ b/src/services/backtest/exchange_event_handler.rs @@ -13,7 +13,7 @@ use crate::services::backtest::logger::log_lack_of_margin; use crate::strategies::strategy::{TradingStrategy}; use crate::utils::date; -pub const INITIAL_BALANCE: f64 = 40_000.0; +pub const INITIAL_BALANCE: f64 = 10_000.0; pub const MINIMUM_BALANCE: f64 = 800.0; const DEFAULT_SLIPPAGE_MULTIPLIER: f64 = 0.0001; const COMPULSORY_CLOSE_FEE: f64 = 25.0; diff --git a/src/strategies/custom/vwap_strategy.rs b/src/strategies/custom/vwap_strategy.rs index 7a6b70f..537de3d 100644 --- a/src/strategies/custom/vwap_strategy.rs +++ b/src/strategies/custom/vwap_strategy.rs @@ -12,10 +12,12 @@ use crate::services::trailing_stop::TrailingStop; use crate::strategies::strategy::{TradingStrategy}; use crate::utils::date; -const SMA_PERIOD: usize = 9; +const SHORT_SMA_PERIOD: usize = 9; + +const LONG_SMA_PERIOD: usize = 9; const EMA_PERIOD: usize = 9; -const CANDLESTICK_PERIOD: u64 = 10; -const QUANTITY_PER_POSITION: usize = 2; +const CANDLESTICK_PERIOD: u64 = 1; +const QUANTITY_PER_POSITION: usize = 8; const CANDLESTICK_SIGNAL_BUFFER_SIZE: usize = 10; // const TICKS_STOP_LOSS: usize = 15; @@ -31,7 +33,8 @@ pub struct VWAPDayInitStrategy { quadrant_changed_signal_buffer: usize, current_side: CandleDirection, candle_parser: TimeCandlestickGenerator, - sma: SimpleMovingAverage, + short_sma: SimpleMovingAverage, + long_sma: SimpleMovingAverage, ema: ExponentialMovingAverage, vwap: VolumeWeightedAveragePrice, open_position: Option @@ -80,9 +83,10 @@ impl VWAPDayInitStrategy { return None; } - // Keeping it simple. Maybe validate the distance between vwap and sma, or the sma angle in the future; - // if self.current_side == CandleDirection::SHORT && self.sma.turns_down() { - if pre_signal && self.current_side == CandleDirection::SHORT && (self.sma.turns_down() || self.sma.trending_down() ) { + // Keeping it simple. Maybe validate the distance between vwap and short_sma, or the short_sma angle in the future; + // if self.current_side == CandleDirection::SHORT && self.short_sma.turns_down() { + // if pre_signal && self.current_side == CandleDirection::SHORT && (self.short_sma.turns_down() || self.short_sma.trending_down() ) && (self.long_sma.turns_down() || self.long_sma.trending_down() ) { + if pre_signal && self.current_side == CandleDirection::SHORT && (self.short_sma.trending_down() ) && (self.long_sma.trending_down() ) { self.open_position = Some(OpenPosition::new( Side::SELL, candlestick.close, @@ -92,8 +96,9 @@ impl VWAPDayInitStrategy { return Some(OrderAction::MarketShort(QUANTITY_PER_POSITION)) } - // if self.current_side == CandleDirection::LONG && self.sma.turns_up() { - if pre_signal && self.current_side == CandleDirection::LONG && (self.sma.turns_up() || self.sma.trending_up()) { + // if self.current_side == CandleDirection::LONG && self.short_sma.turns_up() { + // if pre_signal && self.current_side == CandleDirection::LONG && (self.short_sma.turns_up() || self.short_sma.trending_up()) && (self.long_sma.turns_up() || self.long_sma.trending_up()) { + if pre_signal && self.current_side == CandleDirection::LONG && (self.short_sma.trending_up()) && (self.long_sma.trending_up()) { self.open_position = Some(OpenPosition::new( Side::BUY, candlestick.close, @@ -135,25 +140,25 @@ impl VWAPDayInitStrategy { // } if let Some(_position) = self.open_position.as_mut() { - if self.open_position.as_ref()?.side == Side::SELL && self.sma.turns_up() { + if self.open_position.as_ref()?.side == Side::SELL && self.short_sma.turns_up() { self.open_position = None; return Some(OrderAction::MarketLong(QUANTITY_PER_POSITION)) } - if self.open_position.as_ref()?.side == Side::BUY && self.sma.turns_down() { + if self.open_position.as_ref()?.side == Side::BUY && self.short_sma.turns_down() { self.open_position = None; return Some(OrderAction::MarketShort(QUANTITY_PER_POSITION)) } } // if let Some(_position) = self.open_position.as_mut() { - // if self.open_position.as_ref()?.side == Side::SELL && self.sma.turns_up() { + // if self.open_position.as_ref()?.side == Side::SELL && self.short_sma.turns_up() { // if new_trade.price < self.open_position.as_ref()?.first_chunk.entry_price - (self.open_position.as_ref()?.first_chunk.entry_price * self.points_per_tick_asset.unwrap()) { // self.open_position = None; // return Some(OrderAction::MarketLong(QUANTITY_PER_POSITION)) // } // } // - // if self.open_position.as_ref()?.side == Side::BUY && self.sma.turns_down() { + // if self.open_position.as_ref()?.side == Side::BUY && self.short_sma.turns_down() { // if new_trade.price > self.open_position.as_ref()?.first_chunk.entry_price + (self.open_position.as_ref()?.first_chunk.entry_price * self.points_per_tick_asset.unwrap()) { // self.open_position = None; // return Some(OrderAction::MarketShort(QUANTITY_PER_POSITION)) @@ -196,12 +201,13 @@ impl TradingStrategy for VWAPDayInitStrategy { points_per_tick_asset: None, cut_off_time_sending_orders: NaiveTime::from_hms_opt(10, 30, 00).unwrap(), time_force_close_open_positions: NaiveTime::from_hms_opt(17, 30, 00).unwrap(), - min_candles: 10, + min_candles: 20, candle_count: 0, quadrant_changed_signal_buffer: 0, - candle_parser: TimeCandlestickGenerator::new(TimeUnit::Seconds, CANDLESTICK_PERIOD), - sma: SimpleMovingAverage::new(SMA_PERIOD).unwrap(), - ema: ExponentialMovingAverage::default(EMA_PERIOD), + candle_parser: TimeCandlestickGenerator::new(TimeUnit::Minutes, CANDLESTICK_PERIOD), + short_sma: SimpleMovingAverage::new(SHORT_SMA_PERIOD).unwrap(), + long_sma: SimpleMovingAverage::new(LONG_SMA_PERIOD).unwrap(), + ema: ExponentialMovingAverage::default(SHORT_SMA_PERIOD), vwap: VolumeWeightedAveragePrice::new().unwrap(), current_side: CandleDirection::UNDEFINED, open_position: None, @@ -217,7 +223,8 @@ impl TradingStrategy for VWAPDayInitStrategy { self.candle_count = 0; self.quadrant_changed_signal_buffer = 0; - self.sma.reset(); + self.short_sma.reset(); + self.long_sma.reset(); self.ema.reset(); self.vwap.reset(); } @@ -229,7 +236,8 @@ impl TradingStrategy for VWAPDayInitStrategy { if let Some(candlestick) = self.candle_parser.parse_only_closed_candles(new_trade) { self.candle_count += 1; - let _sma = self.sma.next(candlestick); + let _short_sma = self.short_sma.next(candlestick); + let _long_sma = self.long_sma.next(candlestick); let _ema = self.ema.next(candlestick); let vwap = self.vwap.next(&candlestick); From b96a5df258a528ac34c51c2709c104b040b8aa07 Mon Sep 17 00:00:00 2001 From: Geraldeli Date: Thu, 9 Jan 2025 14:30:08 -0300 Subject: [PATCH 12/15] fix: fixing multiple entries whitin buffer --- src/strategies/custom/vwap_strategy.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/strategies/custom/vwap_strategy.rs b/src/strategies/custom/vwap_strategy.rs index 537de3d..53a7c75 100644 --- a/src/strategies/custom/vwap_strategy.rs +++ b/src/strategies/custom/vwap_strategy.rs @@ -121,6 +121,7 @@ impl VWAPDayInitStrategy { self.open_position.as_ref()?.side, self.open_position.as_ref()?.open_quantity, ) { + self.quadrant_changed_signal_buffer = 0; self.open_position = None; return Some(force_exit); } @@ -142,10 +143,12 @@ impl VWAPDayInitStrategy { if let Some(_position) = self.open_position.as_mut() { if self.open_position.as_ref()?.side == Side::SELL && self.short_sma.turns_up() { self.open_position = None; + self.quadrant_changed_signal_buffer = 0; return Some(OrderAction::MarketLong(QUANTITY_PER_POSITION)) } if self.open_position.as_ref()?.side == Side::BUY && self.short_sma.turns_down() { self.open_position = None; + self.quadrant_changed_signal_buffer = 0; return Some(OrderAction::MarketShort(QUANTITY_PER_POSITION)) } } From e17e49b6580ba94b03fbf8aa4aa09a72db4930b1 Mon Sep 17 00:00:00 2001 From: Geraldeli Date: Fri, 10 Jan 2025 08:18:04 -0300 Subject: [PATCH 13/15] fix: comment unused ema --- src/strategies/custom/vwap_strategy.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/strategies/custom/vwap_strategy.rs b/src/strategies/custom/vwap_strategy.rs index 53a7c75..ae6227a 100644 --- a/src/strategies/custom/vwap_strategy.rs +++ b/src/strategies/custom/vwap_strategy.rs @@ -7,6 +7,7 @@ use crate::models::action::{market_order_from_side, OrderAction, TradeAction}; use crate::models::asset::Asset; use crate::models::candlestick::{CandleDirection, Candlestick, CandlestickGenerator, TimeCandlestickGenerator, TimeUnit}; use crate::models::side::Side; +use crate::models::side::Side::BUY; use crate::models::trade::Trade; use crate::services::trailing_stop::TrailingStop; use crate::strategies::strategy::{TradingStrategy}; @@ -35,9 +36,9 @@ pub struct VWAPDayInitStrategy { candle_parser: TimeCandlestickGenerator, short_sma: SimpleMovingAverage, long_sma: SimpleMovingAverage, - ema: ExponentialMovingAverage, vwap: VolumeWeightedAveragePrice, open_position: Option + // ema: ExponentialMovingAverage, } impl VWAPDayInitStrategy { @@ -210,7 +211,7 @@ impl TradingStrategy for VWAPDayInitStrategy { candle_parser: TimeCandlestickGenerator::new(TimeUnit::Minutes, CANDLESTICK_PERIOD), short_sma: SimpleMovingAverage::new(SHORT_SMA_PERIOD).unwrap(), long_sma: SimpleMovingAverage::new(LONG_SMA_PERIOD).unwrap(), - ema: ExponentialMovingAverage::default(SHORT_SMA_PERIOD), + // ema: ExponentialMovingAverage::default(SHORT_SMA_PERIOD), vwap: VolumeWeightedAveragePrice::new().unwrap(), current_side: CandleDirection::UNDEFINED, open_position: None, @@ -228,7 +229,7 @@ impl TradingStrategy for VWAPDayInitStrategy { self.short_sma.reset(); self.long_sma.reset(); - self.ema.reset(); + // self.ema.reset(); self.vwap.reset(); } @@ -238,10 +239,11 @@ impl TradingStrategy for VWAPDayInitStrategy { if let Some(candlestick) = self.candle_parser.parse_only_closed_candles(new_trade) { self.candle_count += 1; + // println!("IMBALANCE: {}, CUMULATIVE DELTA: {}, STR: {}", candlestick.trade_imbalance, candlestick.cumulative_volume_delta, candlestick.directional_strength); let _short_sma = self.short_sma.next(candlestick); let _long_sma = self.long_sma.next(candlestick); - let _ema = self.ema.next(candlestick); + // let _ema = self.ema.next(candlestick); let vwap = self.vwap.next(&candlestick); order_open_position = self.should_open_position(vwap, &candlestick); @@ -260,7 +262,7 @@ struct OpenPosition { side: Side, open_quantity: usize, quantity_per_chunk: [usize; 1], - first_chunk: TrailingStop, + first_chunk: TrailingStop } #[allow(unused)] @@ -280,7 +282,7 @@ impl OpenPosition { entry_price, side, points_per_tick, - ), + ) // first_chunk: TrailingStop::new( // entry_price, // side, From 936632ac7bea04c0c29bb9073c5a8df22f31a1ba Mon Sep 17 00:00:00 2001 From: Geraldeli Date: Fri, 10 Jan 2025 08:19:38 -0300 Subject: [PATCH 14/15] chore: linting code --- src/strategies/custom/vwap_strategy.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/strategies/custom/vwap_strategy.rs b/src/strategies/custom/vwap_strategy.rs index ae6227a..1720d4d 100644 --- a/src/strategies/custom/vwap_strategy.rs +++ b/src/strategies/custom/vwap_strategy.rs @@ -1,13 +1,11 @@ use chrono::{NaiveTime, Timelike}; use ta::Next; -use crate::indicators::exponential_moving_average::ExponentialMovingAverage; use crate::indicators::simple_moving_average::SimpleMovingAverage; use crate::indicators::volume_weighted_average_price::VolumeWeightedAveragePrice; -use crate::models::action::{market_order_from_side, OrderAction, TradeAction}; +use crate::models::action::{OrderAction, TradeAction}; use crate::models::asset::Asset; use crate::models::candlestick::{CandleDirection, Candlestick, CandlestickGenerator, TimeCandlestickGenerator, TimeUnit}; use crate::models::side::Side; -use crate::models::side::Side::BUY; use crate::models::trade::Trade; use crate::services::trailing_stop::TrailingStop; use crate::strategies::strategy::{TradingStrategy}; @@ -16,7 +14,7 @@ use crate::utils::date; const SHORT_SMA_PERIOD: usize = 9; const LONG_SMA_PERIOD: usize = 9; -const EMA_PERIOD: usize = 9; +// const EMA_PERIOD: usize = 9; const CANDLESTICK_PERIOD: u64 = 1; const QUANTITY_PER_POSITION: usize = 8; const CANDLESTICK_SIGNAL_BUFFER_SIZE: usize = 10; From 0ecb8c4ccc0349be4afb523c59d6276b99726b42 Mon Sep 17 00:00:00 2001 From: Geraldeli Date: Fri, 10 Jan 2025 08:24:48 -0300 Subject: [PATCH 15/15] chore: change initial balance --- src/services/backtest/exchange_event_handler.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/backtest/exchange_event_handler.rs b/src/services/backtest/exchange_event_handler.rs index 49f775d..0a766ff 100644 --- a/src/services/backtest/exchange_event_handler.rs +++ b/src/services/backtest/exchange_event_handler.rs @@ -13,7 +13,7 @@ use crate::services::backtest::logger::log_lack_of_margin; use crate::strategies::strategy::{TradingStrategy}; use crate::utils::date; -pub const INITIAL_BALANCE: f64 = 10_000.0; +pub const INITIAL_BALANCE: f64 = 40_000.0; pub const MINIMUM_BALANCE: f64 = 800.0; const DEFAULT_SLIPPAGE_MULTIPLIER: f64 = 0.0001; const COMPULSORY_CLOSE_FEE: f64 = 25.0;