From ea7c8394a3eec773b18014c6fe49e845dd8c75e8 Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Mon, 4 Jan 2021 19:29:01 +0000 Subject: [PATCH] a lot of stuff --- rustybot/src/events.rs | 9 +++ rustybot/src/pairs.rs | 13 ++++- rustybot/src/strategy.rs | 119 +++++++++++++++++++++++++++++++++++++-- 3 files changed, 136 insertions(+), 5 deletions(-) diff --git a/rustybot/src/events.rs b/rustybot/src/events.rs index ad7f0ee..e3c9ff2 100644 --- a/rustybot/src/events.rs +++ b/rustybot/src/events.rs @@ -21,6 +21,15 @@ pub struct EventMetadata { order_id: Option, } +impl EventMetadata { + pub fn new(position_id: Option, order_id: Option) -> Self { + EventMetadata { + position_id, + order_id, + } + } +} + #[derive(Copy, Clone, PartialEq, Eq, Hash)] pub enum EventKind { NewMinimum, diff --git a/rustybot/src/pairs.rs b/rustybot/src/pairs.rs index fd507d3..e24e1a3 100644 --- a/rustybot/src/pairs.rs +++ b/rustybot/src/pairs.rs @@ -32,12 +32,13 @@ impl PairStatus { } pub fn add_position(&mut self, position: Position) { - let (pw, events) = { + let (pw, events, signals) = { match &self.strategy { Some(strategy) => strategy.position_on_new_tick(&position, &self), None => ( PositionWrapper::new(position.clone(), position.pl(), position.pl_perc(), None), vec![], + vec![], ), } }; @@ -61,4 +62,14 @@ impl PairStatus { self.dispatcher.call_event_handlers(&event, &self); } + + pub fn current_tick(&self) -> u64 { + self.current_tick + } + + pub fn previous_pw(&self, id: u64) -> Option<&PositionWrapper> { + self.positions + .get(&(self.current_tick - 1)) + .and_then(|x| x.iter().find(|x| x.position().position_id() == id)) + } } diff --git a/rustybot/src/strategy.rs b/rustybot/src/strategy.rs index 8a5b482..5b70ccd 100644 --- a/rustybot/src/strategy.rs +++ b/rustybot/src/strategy.rs @@ -1,9 +1,120 @@ +use std::collections::HashMap; + use bitfinex::positions::Position; -use crate::events::Event; -use crate::positions::PositionWrapper; +use crate::events::{Event, EventKind, EventMetadata, SignalKind}; use crate::pairs::PairStatus; +use crate::positions::{PositionState, PositionWrapper}; pub trait Strategy { - fn position_on_new_tick(&self, position: &Position, status: &PairStatus) -> (PositionWrapper, Vec); -} \ No newline at end of file + fn position_on_new_tick( + &self, + position: &Position, + status: &PairStatus, + ) -> (PositionWrapper, Vec, Vec); +} + +struct TrailingStop { + stop_percentages: HashMap, +} + +impl TrailingStop { + const BREAK_EVEN_PERC: f64 = 0.2; + const MIN_PROFIT_PERC: f64 = TrailingStop::BREAK_EVEN_PERC + 0.3; + const GOOD_PROFIT_PERC: f64 = TrailingStop::MIN_PROFIT_PERC * 2.5; + const MAX_LOSS_PERC: f64 = -1.7; + + const TAKER_FEE: f64 = 0.2; + + pub fn new() -> Self { + TrailingStop { + stop_percentages: HashMap::new(), + } + } + + fn net_pl_percentage(pl: f64, fee: f64) -> f64 { + pl - fee + } +} + +impl Strategy for TrailingStop { + fn position_on_new_tick( + &self, + position: &Position, + status: &PairStatus, + ) -> (PositionWrapper, Vec, Vec) { + let mut signals = vec![]; + let pl_perc = TrailingStop::net_pl_percentage(position.pl_perc(), TrailingStop::TAKER_FEE); + let events = vec![]; + + let state = { + if pl_perc > TrailingStop::GOOD_PROFIT_PERC { + PositionState::Profit + } else if TrailingStop::MIN_PROFIT_PERC <= pl_perc + && pl_perc < TrailingStop::GOOD_PROFIT_PERC + { + PositionState::MinimumProfit + } else if 0.0 <= pl_perc && pl_perc < TrailingStop::MIN_PROFIT_PERC { + PositionState::BreakEven + } else if TrailingStop::MAX_LOSS_PERC < pl_perc && pl_perc < 0.0 { + PositionState::Loss + } else { + signals.push(SignalKind::ClosePosition); + PositionState::Critical + } + }; + + let opt_pre_pw = status.previous_pw(position.position_id()); + let event_metadata = EventMetadata::new(Some(position.position_id()), None); + let pw = PositionWrapper::new(position.clone(), position.pl(), pl_perc, Some(state)); + + match opt_pre_pw { + Some(prev) => { + if prev.state() == Some(state) { + return (pw, events, signals); + } + } + None => return (pw, events, signals), + }; + + let events = { + let mut events = vec![]; + + if state == PositionState::Profit { + events.push(Event::new( + EventKind::ReachedGoodProfit, + status.current_tick(), + Some(event_metadata), + )); + } else if state == PositionState::MinimumProfit { + events.push(Event::new( + EventKind::ReachedMinProfit, + status.current_tick(), + Some(event_metadata), + )); + } else if state == PositionState::BreakEven { + events.push(Event::new( + EventKind::ReachedBreakEven, + status.current_tick(), + Some(event_metadata), + )); + } else if state == PositionState::Loss { + events.push(Event::new( + EventKind::ReachedLoss, + status.current_tick(), + Some(event_metadata), + )); + } else { + events.push(Event::new( + EventKind::ReachedMaxLoss, + status.current_tick(), + Some(event_metadata), + )); + } + + events + }; + + return (pw, events, signals); + } +}