diff --git a/src/indicators/simple_moving_average.rs b/src/indicators/simple_moving_average.rs index 657da0b..0c90e59 100644 --- a/src/indicators/simple_moving_average.rs +++ b/src/indicators/simple_moving_average.rs @@ -30,6 +30,10 @@ impl SimpleMovingAverage { }) } + pub fn previous_value(&self) -> f64 { + self.results[1] + } + pub fn trending_up(&mut self) -> bool { if self.period < 3 { unimplemented!(); diff --git a/src/models/candlestick.rs b/src/models/candlestick.rs index 7155003..ea160f8 100644 --- a/src/models/candlestick.rs +++ b/src/models/candlestick.rs @@ -401,10 +401,10 @@ impl TimeCandlestickGenerator { let mut new_candle = Candlestick::new(new_trade); update_tick_metrics(&mut new_candle, new_trade, self.last_price); new_candle.start_time = round_start_time(new_candle.start_time, self.unit, self.period); - + let result_candle = self.last_candle.clone()?; self.add_close_time_to_time_based_candlestick(&mut new_candle); self.last_candle = Some(new_candle); - return Some(self.last_candle.clone()?); + return Some(result_candle); } self.last_day = new_trade.date; diff --git a/src/services/configuration.rs b/src/services/configuration.rs index 08df60e..5810b58 100644 --- a/src/services/configuration.rs +++ b/src/services/configuration.rs @@ -8,7 +8,7 @@ pub const S3_BUCKET_HISTORICAL_DATA: &'static str = "profitpirates"; const CONFIGURATION_FOLDER: &'static str = ".kate"; const CONFIGURATION_FILE: &'static str = "config.json"; const DEFAULT_MARKET_CONNECTOR_URL: &'static str = "http://market-connector.grinply.com"; -const DEFAULT_MARKET_CONNECTOR_SOCKET_URL: &'static str = "http://market-connector.grinply.com:8022"; +const DEFAULT_MARKET_CONNECTOR_SOCKET_URL: &'static str = "market-connector.grinply.com:8022"; #[derive(Clone, Serialize, Deserialize)] pub struct KateConfiguration { diff --git a/src/services/trailing_stop.rs b/src/services/trailing_stop.rs index 61f98b5..c513b1e 100644 --- a/src/services/trailing_stop.rs +++ b/src/services/trailing_stop.rs @@ -8,7 +8,7 @@ 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)] +#[derive(Clone, Debug)] pub struct TrailingStop { entry_price: f64, stop_loss_price: f64, @@ -51,7 +51,7 @@ impl TrailingStop { let trailing_activation_price = current_price + (points_per_tick * ticks_trigger_start_trailing as f64); TrailingStop { entry_price: current_price, - stop_loss_price: current_price - points_per_tick * ticks_stop_loss as f64, + stop_loss_price: current_price - (points_per_tick * ticks_stop_loss as f64), points_per_tick, direction, is_finished: false, @@ -149,7 +149,7 @@ impl TrailingStop { Side::BUY => { if current_price > self.entry_price { let diff = (current_price - self.entry_price) / self.points_per_tick; - if diff > self.max_ticks_in_positive_direction as f64 { + if diff > self.max_ticks_in_positive_direction as f64 { self.max_ticks_in_positive_direction= diff as usize; } } diff --git a/src/strategies/custom/bollinger_bands_reversion.rs b/src/strategies/custom/bollinger_bands_reversion.rs new file mode 100644 index 0000000..cd36c91 --- /dev/null +++ b/src/strategies/custom/bollinger_bands_reversion.rs @@ -0,0 +1,139 @@ +use crate::models::action::{OrderAction, TradeAction, market_order_from_side}; +use crate::models::asset::Asset; +use crate::models::candlestick::{CandlestickGenerator, TimeCandlestickGenerator, TimeUnit}; +use crate::models::trade::Trade; +use ta::indicators::BollingerBands; +use ta::{Next, Reset}; +use crate::models::side::Side; +use crate::strategies::strategy::TradingStrategy; + +pub struct BollingerBandReversion { + bollinger_bands: BollingerBands, + candle_parser: TimeCandlestickGenerator, + open_position: Option, +} + +struct Position { + side: Side, + entry_price: f64, + quantity: usize, +} + +impl TradingStrategy for BollingerBandReversion { + fn new() -> Self { + BollingerBandReversion { + bollinger_bands: BollingerBands::new(20, 2.0).unwrap(), + candle_parser: TimeCandlestickGenerator::new(TimeUnit::Minutes, 1), + open_position: None, + } + } + + fn name(&self) -> &str { + "bollinger_band_reversion" + } + + fn days_of_context(&self) -> u32 { + 0 + } + + fn init_trading_day(&mut self, _asset: &Asset, context_trades: &[Trade]) { + self.bollinger_bands.reset(); + for trade in context_trades { + if let Some(candle) = self.candle_parser.parse_only_closed_candles(trade) { + self.bollinger_bands.next(candle.close); + } + } + } + + fn process_new_trade(&mut self, new_trade: &Trade) -> TradeAction { //Original ChatGPT + if let Some(candle) = self.candle_parser.parse_only_closed_candles(new_trade) { + let bands = self.bollinger_bands.next(candle.close); + + match &mut self.open_position { + // Exit logic + Some(position) => { + if (position.side == Side::BUY && candle.close >= bands.average) + || (position.side == Side::SELL && candle.close <= bands.average) + || (candle.close > bands.upper || candle.close < bands.lower) + { + let quantity_to_close = position.quantity; + let exit_action = market_order_from_side( + position.side.opposite_side(), + quantity_to_close, + ); + self.open_position = None; + return TradeAction::custom_order(Some(exit_action), None, None, None); + } + } + // Entry logic + None => { + if candle.close <= bands.lower { + self.open_position = Some(Position { + side: Side::BUY, + entry_price: candle.close, + quantity: 1, + }); + let entry_action = OrderAction::MarketLong(1); + return TradeAction::custom_order(None, Some(entry_action), None, None); + } else if candle.close >= bands.upper { + self.open_position = Some(Position { + side: Side::SELL, + entry_price: candle.close, + quantity: 1, + }); + let entry_action = OrderAction::MarketShort(1); + return TradeAction::custom_order(None, Some(entry_action), None, None); + } + } + } + } + + TradeAction::do_nothing() + } + + // fn process_new_trade(&mut self, new_trade: &Trade) -> TradeAction { //Randomly changed signals +// if let Some(candle) = self.candle_parser.parse_only_closed_candles(new_trade) { +// let bands = self.bollinger_bands.next(candle.close); +// +// match &mut self.open_position { +// // Exit logic +// Some(position) => { +// if (position.side == Side::BUY && candle.close <= bands.average) +// || (position.side == Side::SELL && candle.close >= bands.average) +// || (candle.close < bands.upper || candle.close > bands.lower) +// { +// let quantity_to_close = position.quantity; +// let exit_action = market_order_from_side( +// position.side.opposite_side(), +// quantity_to_close, +// ); +// self.open_position = None; +// return TradeAction::custom_order(Some(exit_action), None, None, None); +// } +// } +// // Entry logic +// None => { +// if candle.close <= bands.lower { +// self.open_position = Some(Position { +// side: Side::BUY, +// entry_price: candle.close, +// quantity: 1, +// }); +// let entry_action = OrderAction::MarketShort(1); +// return TradeAction::custom_order(None, Some(entry_action), None, None); +// } else if candle.close >= bands.upper { +// self.open_position = Some(Position { +// side: Side::SELL, +// entry_price: candle.close, +// quantity: 1, +// }); +// let entry_action = OrderAction::MarketLong(1); +// return TradeAction::custom_order(None, Some(entry_action), None, None); +// } +// } +// } +// } +// +// TradeAction::do_nothing() +// } +} diff --git a/src/strategies/custom/cross_sma_strategy.rs b/src/strategies/custom/cross_sma_strategy.rs new file mode 100644 index 0000000..6d25320 --- /dev/null +++ b/src/strategies/custom/cross_sma_strategy.rs @@ -0,0 +1,259 @@ +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::{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::trade::Trade; +use crate::services::trailing_stop::TrailingStop; +use crate::strategies::strategy::{TradingStrategy}; +use crate::utils::{date, end_trading_day}; + +const SHORT_SMA_PERIOD: usize = 9; +const LONG_SMA_PERIOD: usize = 50; +const CANDLESTICK_PERIOD: u64 = 1; +const CANDLESTICK_PERIOD_UNIT: TimeUnit = TimeUnit::Minutes; +const QUANTITY_PER_POSITION: usize = 8; +const TICKS_STOP_LOSS: usize = 10; +const TICKS_RP_FIRST_CHUNK: usize = 30; +const MIN_CANDLES_TO_START: usize = 50; +const TICKS_FIRST_TRAILING_TRIGGER: usize = 0; +const TICKS_DISTANCE_WHEN_TRAILING: usize = 0; +const TICKS_FREQUENCY_TRAILING_UPDATE: usize = 1; + +pub struct CrossSMAStrategy { + points_per_tick_asset: Option, + cut_off_time_sending_orders: NaiveTime, + time_force_close_open_positions: NaiveTime, + candle_count: usize, + candle_parser: TimeCandlestickGenerator, + short_sma: SimpleMovingAverage, + long_sma: SimpleMovingAverage, + // exit_sma: SimpleMovingAverage, + open_position: Option +} + +impl CrossSMAStrategy { + + fn crossed_sma(&self, previous_short_sma: f64, previous_long_sma: f64, current_short_sma: f64, current_long_sma: f64) -> bool { + if (previous_short_sma <= previous_long_sma) && (current_short_sma > current_long_sma) { + return true; + } + + if previous_short_sma >= previous_long_sma && current_short_sma < current_long_sma { + return true; + } + + false + } + + fn should_open_position(&mut self, candlestick: &Candlestick) -> Option { + if self.open_position.is_some() { + return None; + } + + if self.candle_count < MIN_CANDLES_TO_START || + candlestick.close_time.expect("candlestick should be closed") + .num_seconds_from_midnight() > self.cut_off_time_sending_orders.num_seconds_from_midnight() { + return None; + } + + if self.crossed_sma( + self.short_sma.previous_value(), + self.long_sma.previous_value(), + self.short_sma.value(), + self.long_sma.value() + ) { + println!("SMA has Crossed on {}", candlestick.start_time); + if self.short_sma.value() > self.long_sma.value() { + self.open_position = Some(OpenPosition::new( + Side::BUY, + candlestick.close, + self.points_per_tick_asset.unwrap(), + QUANTITY_PER_POSITION + )); + return Some(OrderAction::MarketLong(QUANTITY_PER_POSITION)); + } + + if self.short_sma.value() < self.long_sma.value() { + self.open_position = Some(OpenPosition::new( + Side::SELL, + candlestick.close, + self.points_per_tick_asset.unwrap(), + QUANTITY_PER_POSITION + )); + return Some(OrderAction::MarketShort(QUANTITY_PER_POSITION)); + } + } + None + } + + // fn should_close_position(&mut self, candlestick: &Candlestick) -> Option { + fn should_close_position(&mut self, new_trade: &Trade) -> Option { + if self.open_position.is_none() { + return None; + } + + if let Some(force_exit) = end_trading_day::should_force_close_position_end_trading_day( + date::convert_to_naive_time(new_trade.time)?.num_seconds_from_midnight(), + // candlestick.close_time.unwrap().num_seconds_from_midnight(), + self.open_position.as_ref()?.side, + self.open_position.as_ref()?.open_quantity, + self.time_force_close_open_positions.num_seconds_from_midnight() + ) { + self.open_position = None; + return Some(force_exit); + } + + let mut quantity_to_close: usize = 0; + + let is_first_chunk_finished = self.open_position.as_ref()?.first_chunk.is_finished(); + // let should_close_first_chunk = self.open_position.as_mut()?.first_chunk.should_close_position(candlestick.close); + let should_close_first_chunk = self.open_position.as_mut()?.first_chunk.should_close_position(new_trade.price); + + if !is_first_chunk_finished && should_close_first_chunk { + self.open_position.as_mut()?.first_chunk.finish_trailing(); + quantity_to_close += self.open_position.as_mut()?.quantity_per_chunk[0]; + } + + if quantity_to_close > 0 { + let side = self.open_position.as_ref()?.side; + self.open_position = None; + + let order = market_order_from_side( + side.opposite_side(), + quantity_to_close, + ); + + return Some(order); + } + + None + } + + +} + +impl TradingStrategy for CrossSMAStrategy { + fn new() -> Self + where + Self: Sized + { + CrossSMAStrategy { + points_per_tick_asset: Some(5.0), + cut_off_time_sending_orders: NaiveTime::from_hms_opt(17, 20, 00).unwrap(), + time_force_close_open_positions: NaiveTime::from_hms_opt(17, 30, 00).unwrap(), + candle_count: 0, + candle_parser: TimeCandlestickGenerator::new(CANDLESTICK_PERIOD_UNIT, CANDLESTICK_PERIOD), + short_sma: SimpleMovingAverage::new(SHORT_SMA_PERIOD).unwrap(), + long_sma: SimpleMovingAverage::new(LONG_SMA_PERIOD).unwrap(), + // exit_sma: SimpleMovingAverage::new(EXIT_SMA_PERIOD).unwrap(), + open_position: None, + } + } + + fn name(&self) -> &str { + "cross_sma_strategy" + } + + fn init_trading_day(&mut self, asset: &Asset, trades: &[Trade]) { + // self.points_per_tick_asset = Some(asset.min_point_variation); + // self.short_sma.reset(); + // self.long_sma.reset(); + // + // for trade in trades { + // if let Some(candle) = self.candle_parser.parse_only_closed_candles(trade) { + // self.short_sma.next(candle.close); + // self.long_sma.next(candle.close); + // } + // } + + } + + fn process_new_trade(&mut self, new_trade: &Trade) -> TradeAction { + let mut order_open_position: Option = None; + let mut order_close_position: Option = None; + order_close_position = self.should_close_position(new_trade); + + if let Some(candlestick) = self.candle_parser.parse_only_closed_candles(new_trade) { + self.candle_count += 1; + + let _short_sma = self.short_sma.next(candlestick); + let _long_sma = self.long_sma.next(candlestick); + // let exit_sma = self.exit_sma.next(candlestick); + + println!("Candle count: {}", self.candle_count); + println!("Short sma: {:?}", _short_sma); + println!("Long sma: {:?}", _long_sma); + + // order_close_position = self.should_close_position(&candlestick); + order_open_position = self.should_open_position(&candlestick); + + + // if let Some(position) = self.open_position.as_mut() { + // position.first_chunk.set_stop_loss(exit_sma); + // } + + } + + TradeAction::custom_order(order_close_position, order_open_position, None, None) + } +} + +#[allow(unused)] +#[derive(Debug)] +struct OpenPosition { + side: Side, + open_quantity: usize, + quantity_per_chunk: [usize; 1], + first_chunk: TrailingStop, +} + +#[allow(unused)] +impl OpenPosition { + fn new( + side: Side, + entry_price: f64, + points_per_tick: f64, + quantity_to_trade: usize, + ) -> Self { + // 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], + first_chunk: TrailingStop::new( + entry_price, + side, + points_per_tick, + TICKS_STOP_LOSS, + TICKS_RP_FIRST_CHUNK, + TICKS_FIRST_TRAILING_TRIGGER, + TICKS_DISTANCE_WHEN_TRAILING, + TICKS_FREQUENCY_TRAILING_UPDATE + ) + } + } + fn new_not_automatic_trailing( + side: Side, + entry_price: f64, + points_per_tick: f64, + quantity_to_trade: usize, + ) -> Self { + // 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], + first_chunk: TrailingStop::new_not_automatic_trailing( + entry_price, + side, + points_per_tick, + ) + } + } + + +} \ No newline at end of file diff --git a/src/strategies/custom/inverted_cross_sma_strategy.rs b/src/strategies/custom/inverted_cross_sma_strategy.rs new file mode 100644 index 0000000..c4851a3 --- /dev/null +++ b/src/strategies/custom/inverted_cross_sma_strategy.rs @@ -0,0 +1,259 @@ +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::{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::trade::Trade; +use crate::services::trailing_stop::TrailingStop; +use crate::strategies::strategy::{TradingStrategy}; +use crate::utils::{date, end_trading_day}; + +const SHORT_SMA_PERIOD: usize = 21; +const LONG_SMA_PERIOD: usize = 120; +const CANDLESTICK_PERIOD: u64 = 1; +const CANDLESTICK_PERIOD_UNIT: TimeUnit = TimeUnit::Minutes; +const QUANTITY_PER_POSITION: usize = 8; +const TICKS_STOP_LOSS: usize = 15; +const TICKS_RP_FIRST_CHUNK: usize = 15; +const MIN_CANDLES_TO_START: usize = 120; +const TICKS_FIRST_TRAILING_TRIGGER: usize = 0; +const TICKS_DISTANCE_WHEN_TRAILING: usize = 0; +const TICKS_FREQUENCY_TRAILING_UPDATE: usize = 1; + +pub struct InvertedCrossSMAStrategy { + points_per_tick_asset: Option, + cut_off_time_sending_orders: NaiveTime, + time_force_close_open_positions: NaiveTime, + candle_count: usize, + candle_parser: TimeCandlestickGenerator, + short_sma: SimpleMovingAverage, + long_sma: SimpleMovingAverage, + // exit_sma: SimpleMovingAverage, + open_position: Option +} + +impl InvertedCrossSMAStrategy { + + fn crossed_sma(&self, previous_short_sma: f64, previous_long_sma: f64, current_short_sma: f64, current_long_sma: f64) -> bool { + if (previous_short_sma <= previous_long_sma) && (current_short_sma > current_long_sma) { + return true; + } + + if previous_short_sma >= previous_long_sma && current_short_sma < current_long_sma { + return true; + } + + false + } + + fn should_open_position(&mut self, candlestick: &Candlestick) -> Option { + if self.open_position.is_some() { + return None; + } + + if self.candle_count < MIN_CANDLES_TO_START || + candlestick.close_time.expect("candlestick should be closed") + .num_seconds_from_midnight() > self.cut_off_time_sending_orders.num_seconds_from_midnight() { + return None; + } + + if self.crossed_sma( + self.short_sma.previous_value(), + self.long_sma.previous_value(), + self.short_sma.value(), + self.long_sma.value() + ) { + // println!("SMA has Crossed on {} {}", candlestick.date, candlestick.start_time); + if self.short_sma.value() > self.long_sma.value() { + self.open_position = Some(OpenPosition::new( + Side::SELL, + candlestick.close, + self.points_per_tick_asset.unwrap(), + QUANTITY_PER_POSITION + )); + return Some(OrderAction::MarketShort(QUANTITY_PER_POSITION)); + } + + if self.short_sma.value() < self.long_sma.value() { + self.open_position = Some(OpenPosition::new( + Side::BUY, + candlestick.close, + self.points_per_tick_asset.unwrap(), + QUANTITY_PER_POSITION + )); + return Some(OrderAction::MarketLong(QUANTITY_PER_POSITION)); + } + } + None + } + + // fn should_close_position(&mut self, candlestick: &Candlestick) -> Option { + fn should_close_position(&mut self, new_trade: &Trade) -> Option { + if self.open_position.is_none() { + return None; + } + + if let Some(force_exit) = end_trading_day::should_force_close_position_end_trading_day( + date::convert_to_naive_time(new_trade.time)?.num_seconds_from_midnight(), + // candlestick.close_time.unwrap().num_seconds_from_midnight(), + self.open_position.as_ref()?.side, + self.open_position.as_ref()?.open_quantity, + self.time_force_close_open_positions.num_seconds_from_midnight() + ) { + self.open_position = None; + return Some(force_exit); + } + + let mut quantity_to_close: usize = 0; + + let is_first_chunk_finished = self.open_position.as_ref()?.first_chunk.is_finished(); + // let should_close_first_chunk = self.open_position.as_mut()?.first_chunk.should_close_position(candlestick.close); + let should_close_first_chunk = self.open_position.as_mut()?.first_chunk.should_close_position(new_trade.price); + + if !is_first_chunk_finished && should_close_first_chunk { + self.open_position.as_mut()?.first_chunk.finish_trailing(); + quantity_to_close += self.open_position.as_mut()?.quantity_per_chunk[0]; + } + + if quantity_to_close > 0 { + let side = self.open_position.as_ref()?.side; + self.open_position = None; + + let order = market_order_from_side( + side.opposite_side(), + quantity_to_close, + ); + + return Some(order); + } + + None + } + + +} + +impl TradingStrategy for InvertedCrossSMAStrategy { + fn new() -> Self + where + Self: Sized + { + InvertedCrossSMAStrategy { + points_per_tick_asset: Some(5.0), + cut_off_time_sending_orders: NaiveTime::from_hms_opt(17, 20, 00).unwrap(), + time_force_close_open_positions: NaiveTime::from_hms_opt(17, 30, 00).unwrap(), + candle_count: 0, + candle_parser: TimeCandlestickGenerator::new(CANDLESTICK_PERIOD_UNIT, CANDLESTICK_PERIOD), + short_sma: SimpleMovingAverage::new(SHORT_SMA_PERIOD).unwrap(), + long_sma: SimpleMovingAverage::new(LONG_SMA_PERIOD).unwrap(), + // exit_sma: SimpleMovingAverage::new(EXIT_SMA_PERIOD).unwrap(), + open_position: None, + } + } + + fn name(&self) -> &str { + "inverted_cross_sma_strategy" + } + + fn init_trading_day(&mut self, asset: &Asset, trades: &[Trade]) { + // self.points_per_tick_asset = Some(asset.min_point_variation); + // self.short_sma.reset(); + // self.long_sma.reset(); + // + // for trade in trades { + // if let Some(candle) = self.candle_parser.parse_only_closed_candles(trade) { + // self.short_sma.next(candle.close); + // self.long_sma.next(candle.close); + // } + // } + + } + + fn process_new_trade(&mut self, new_trade: &Trade) -> TradeAction { + let mut order_open_position: Option = None; + let mut order_close_position: Option = None; + order_close_position = self.should_close_position(new_trade); + + if let Some(candlestick) = self.candle_parser.parse_only_closed_candles(new_trade) { + self.candle_count += 1; + + let _short_sma = self.short_sma.next(candlestick); + let _long_sma = self.long_sma.next(candlestick); + // let exit_sma = self.exit_sma.next(candlestick); + + // println!("Candle count: {}", self.candle_count); + // println!("Short sma: {:?}", _short_sma); + // println!("Long sma: {:?}", _long_sma); + + // order_close_position = self.should_close_position(&candlestick); + order_open_position = self.should_open_position(&candlestick); + + + // if let Some(position) = self.open_position.as_mut() { + // position.first_chunk.set_stop_loss(exit_sma); + // } + + } + + TradeAction::custom_order(order_close_position, order_open_position, None, None) + } +} + +#[allow(unused)] +#[derive(Debug)] +struct OpenPosition { + side: Side, + open_quantity: usize, + quantity_per_chunk: [usize; 1], + first_chunk: TrailingStop, +} + +#[allow(unused)] +impl OpenPosition { + fn new( + side: Side, + entry_price: f64, + points_per_tick: f64, + quantity_to_trade: usize, + ) -> Self { + // 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], + first_chunk: TrailingStop::new( + entry_price, + side, + points_per_tick, + TICKS_STOP_LOSS, + TICKS_RP_FIRST_CHUNK, + TICKS_FIRST_TRAILING_TRIGGER, + TICKS_DISTANCE_WHEN_TRAILING, + TICKS_FREQUENCY_TRAILING_UPDATE + ) + } + } + fn new_not_automatic_trailing( + side: Side, + entry_price: f64, + points_per_tick: f64, + quantity_to_trade: usize, + ) -> Self { + // 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], + first_chunk: TrailingStop::new_not_automatic_trailing( + entry_price, + side, + points_per_tick, + ) + } + } + + +} \ No newline at end of file diff --git a/src/strategies/custom/mod.rs b/src/strategies/custom/mod.rs index dff774a..678a171 100644 --- a/src/strategies/custom/mod.rs +++ b/src/strategies/custom/mod.rs @@ -1,3 +1,6 @@ pub mod dummy_strategy; pub mod stormer_scalper_strategy; pub mod vwap_strategy; +pub mod cross_sma_strategy; +pub mod inverted_cross_sma_strategy; +pub mod bollinger_bands_reversion; \ No newline at end of file diff --git a/src/strategies/strategy.rs b/src/strategies/strategy.rs index e665c79..e4d5593 100644 --- a/src/strategies/strategy.rs +++ b/src/strategies/strategy.rs @@ -1,7 +1,10 @@ use crate::models::action::TradeAction; use crate::models::asset::Asset; use crate::models::trade::Trade; +use crate::strategies::custom::bollinger_bands_reversion::BollingerBandReversion; +use crate::strategies::custom::cross_sma_strategy::CrossSMAStrategy; use crate::strategies::custom::dummy_strategy::{DummyHighResolutionStrategy}; +use crate::strategies::custom::inverted_cross_sma_strategy::InvertedCrossSMAStrategy; use crate::strategies::custom::stormer_scalper_strategy::StormerScalperStrategy; use crate::strategies::custom::vwap_strategy::VWAPDayInitStrategy; @@ -31,6 +34,9 @@ pub fn list_all_trading_strategies() -> Vec> { Box::new(DummyHighResolutionStrategy::new()), Box::new(StormerScalperStrategy::new()), Box::new(VWAPDayInitStrategy::new()), + Box::new(CrossSMAStrategy::new()), + Box::new(InvertedCrossSMAStrategy::new()), + Box::new(BollingerBandReversion::new()) //Generated by ChatGPT ] } diff --git a/src/utils/end_trading_day.rs b/src/utils/end_trading_day.rs new file mode 100644 index 0000000..79a973f --- /dev/null +++ b/src/utils/end_trading_day.rs @@ -0,0 +1,14 @@ +use crate::models::action::OrderAction; +use crate::models::side::Side; + +pub(crate) fn should_force_close_position_end_trading_day(current_time: u32, side: Side, open_quantity: usize, force_close_time: u32) -> Option { + + if current_time >= force_close_time { + return if side == Side::BUY { + Some(OrderAction::MarketShort(open_quantity)) + } else { + Some(OrderAction::MarketLong(open_quantity)) + } + } + None +} \ No newline at end of file diff --git a/src/utils/mod.rs b/src/utils/mod.rs index d839d14..2d9bf5d 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -2,3 +2,4 @@ pub mod statistics; pub mod date; pub mod math; pub mod list; +pub mod end_trading_day;