Skip to content
4 changes: 4 additions & 0 deletions src/indicators/simple_moving_average.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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!();
Expand Down
4 changes: 2 additions & 2 deletions src/models/candlestick.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion src/services/configuration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
6 changes: 3 additions & 3 deletions src/services/trailing_stop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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;
}
}
Expand Down
139 changes: 139 additions & 0 deletions src/strategies/custom/bollinger_bands_reversion.rs
Original file line number Diff line number Diff line change
@@ -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<Position>,
}

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()
// }
}
Loading