diff --git a/rustybot/Cargo.lock b/rustybot/Cargo.lock index 90fdb3c..1acf9c5 100644 --- a/rustybot/Cargo.lock +++ b/rustybot/Cargo.lock @@ -15,6 +15,15 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e" +[[package]] +name = "aho-corasick" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5" +dependencies = [ + "memchr", +] + [[package]] name = "async-trait" version = "0.1.42" @@ -958,6 +967,24 @@ version = "0.1.57" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" +[[package]] +name = "regex" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38cf2c13ed4745de91a5eb834e11c00bcc3709e773173b2ce4c56c9fbde04b9c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", + "thread_local", +] + +[[package]] +name = "regex-syntax" +version = "0.6.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b181ba2dcf07aaccad5448e8ead58db5b742cf85dfe035e2227f137a539a189" + [[package]] name = "remove_dir_all" version = "0.5.3" @@ -1030,6 +1057,7 @@ dependencies = [ "async-trait", "bitfinex", "futures-util", + "regex", "tokio 0.2.24", "tokio-tungstenite", ] @@ -1195,6 +1223,15 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "thread_local" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14" +dependencies = [ + "lazy_static", +] + [[package]] name = "tinyvec" version = "1.1.0" diff --git a/rustybot/Cargo.toml b/rustybot/Cargo.toml index fd2d334..4f37fe1 100644 --- a/rustybot/Cargo.toml +++ b/rustybot/Cargo.toml @@ -11,4 +11,5 @@ bitfinex = { path= "/home/giulio/dev/bitfinex-rs" } tokio = { version = "0.2", features=["full"]} tokio-tungstenite = "*" futures-util = { version = "0.3", default-features = false, features = ["async-await", "sink", "std"] } -async-trait = "0.1" \ No newline at end of file +async-trait = "0.1" +regex = "1" \ No newline at end of file diff --git a/rustybot/src/currency.rs b/rustybot/src/currency.rs index c057b6e..97a8594 100644 --- a/rustybot/src/currency.rs +++ b/rustybot/src/currency.rs @@ -1,19 +1,26 @@ use core::fmt; use std::borrow::Cow; -use std::fmt::{Display, Formatter}; +use std::convert::TryFrom; +use std::fmt::{Display, Error, Formatter}; + +use regex::Regex; + +use crate::BoxError; #[derive(Clone, PartialEq, Hash)] pub struct Symbol { - name: Cow<'static, str> + name: Cow<'static, str>, } -impl From for Symbol where S: Into { +impl From for Symbol +where + S: Into, +{ fn from(item: S) -> Self { Symbol::new(item.into()) } } - impl Symbol { pub const XMR: Symbol = Symbol::new_static("XMR"); pub const BTC: Symbol = Symbol::new_static("BTC"); @@ -24,11 +31,15 @@ impl Symbol { pub const EUR: Symbol = Symbol::new_static("EUR"); pub fn new(name: String) -> Self { - Symbol { name: Cow::from(name) } + Symbol { + name: Cow::from(name), + } } pub const fn new_static(name: &'static str) -> Self { - Symbol { name: Cow::Borrowed(name) } + Symbol { + name: Cow::Borrowed(name), + } } pub fn name(&self) -> &str { @@ -72,6 +83,23 @@ impl Into for SymbolPair { } } +impl TryFrom<&str> for SymbolPair { + type Error = BoxError; + + fn try_from(value: &str) -> Result { + const REGEX: &str = r"^[t|f](?P\w{3,4}):?(?P\w{3,4})"; + + let captures = Regex::new(REGEX)?.captures(&value).ok_or("Invalid input")?; + let quote = captures.name("quote").ok_or("Quote not found")?.as_str(); + let base = captures.name("base").ok_or("Base not found")?.as_str(); + + Ok(SymbolPair { + quote: quote.into(), + base: base.into(), + }) + } +} + impl Display for SymbolPair { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { write!(f, "{}{}", self.base, self.quote) @@ -96,7 +124,13 @@ struct Balance { impl Balance { pub fn new(pair: SymbolPair, base_price: f64, base_amount: f64, wallet: WalletKind) -> Self { - Balance { pair, base_price, base_amount, quote_equivalent: base_amount * base_price, wallet } + Balance { + pair, + base_price, + base_amount, + quote_equivalent: base_amount * base_price, + wallet, + } } pub fn pair(&self) -> &SymbolPair { @@ -123,7 +157,10 @@ struct BalanceGroup { impl BalanceGroup { pub fn new() -> Self { - BalanceGroup { balances: Vec::new(), quote_equivalent: 0f64 } + BalanceGroup { + balances: Vec::new(), + quote_equivalent: 0f64, + } } pub fn add_balance(&mut self, balance: &Balance) { @@ -133,7 +170,8 @@ impl BalanceGroup { } pub fn currency_names(&self) -> Vec { - self.balances.iter() + self.balances + .iter() .map(|x| x.pair().base().name().into()) .collect() } @@ -142,6 +180,3 @@ impl BalanceGroup { &self.balances } } - - - diff --git a/rustybot/src/events.rs b/rustybot/src/events.rs index 5ef52e5..9cb3bd0 100644 --- a/rustybot/src/events.rs +++ b/rustybot/src/events.rs @@ -5,7 +5,7 @@ use tokio::stream::StreamExt; use tokio::task::JoinHandle; use crate::pairs::PairStatus; -use crate::positions::{Position, PositionState}; +use crate::positions::{Position, PositionProfitState, PositionState}; use crate::BoxError; #[derive(Copy, Clone)] @@ -78,20 +78,20 @@ impl Event { pub struct EventDispatcher { event_handlers: HashMap JoinHandle<()>>>>, - position_state_handlers: - HashMap JoinHandle<()>>>>, + profit_state_handlers: + HashMap JoinHandle<()>>>>, on_any_event_handlers: Vec JoinHandle<()>>>, - on_any_position_state_handlers: Vec JoinHandle<()>>>, + on_any_profit_state_handlers: Vec JoinHandle<()>>>, } impl EventDispatcher { pub fn new() -> Self { EventDispatcher { event_handlers: HashMap::new(), - position_state_handlers: HashMap::new(), + profit_state_handlers: HashMap::new(), on_any_event_handlers: Vec::new(), - on_any_position_state_handlers: Vec::new(), + on_any_profit_state_handlers: Vec::new(), } } @@ -108,13 +108,15 @@ impl EventDispatcher { } pub fn call_position_state_handlers(&self, position: &Position, status: &PairStatus) { - if let Some(functions) = self.position_state_handlers.get(&position.state()) { - for f in functions { - f(position, status); + if let Some(profit_state) = &position.profit_state() { + if let Some(functions) = self.profit_state_handlers.get(profit_state) { + for f in functions { + f(position, status); + } } } - for f in &self.on_any_position_state_handlers { + for f in &self.on_any_profit_state_handlers { f(position, status); } } @@ -138,18 +140,18 @@ impl EventDispatcher { pub fn register_positionstate_handler( &mut self, - state: PositionState, + state: PositionProfitState, f: F, ) where F: Fn(&Position, &PairStatus) -> Fut, Fut: Future + Send, { match state { - PositionState::Any => self - .on_any_position_state_handlers - .push(Box::new(move |p, s| tokio::spawn(f(&p, s)))), + // PositionProfitState::Any => self + // .on_any_position_state_handlers + // .push(Box::new(move |p, s| tokio::spawn(f(&p, s)))), _ => self - .position_state_handlers + .profit_state_handlers .entry(state) .or_default() .push(Box::new(move |p, s| tokio::spawn(f(&p, s)))), diff --git a/rustybot/src/positions.rs b/rustybot/src/positions.rs index 8d3655e..444ad94 100644 --- a/rustybot/src/positions.rs +++ b/rustybot/src/positions.rs @@ -4,6 +4,7 @@ use crate::currency::{Symbol, SymbolPair}; pub struct Position { pair: SymbolPair, state: PositionState, + profit_state: Option, amount: f64, base_price: f64, pl: f64, @@ -36,12 +37,22 @@ impl Position { position_id, creation_date: None, creation_update: None, + profit_state: None, } } - pub fn with_creation_date(mut self, creation_date: u64) -> Self { - self.creation_date = Some(creation_date); - self.creation_update = Some(creation_date); + pub fn with_creation_date(mut self, creation_date: Option) -> Self { + self.creation_date = creation_date; + self + } + + pub fn with_creation_update(mut self, creation_update: Option) -> Self { + self.creation_update = creation_update; + self + } + + pub fn with_profit_state(mut self, profit_state: Option) -> Self { + self.profit_state = profit_state; self } @@ -69,6 +80,9 @@ impl Position { pub fn position_id(&self) -> u64 { self.position_id } + pub fn profit_state(&self) -> Option { + self.profit_state + } pub fn creation_date(&self) -> Option { self.creation_date } @@ -78,25 +92,27 @@ impl Position { } #[derive(Copy, Clone, Eq, PartialEq, Hash)] -pub enum PositionState { +pub enum PositionProfitState { Critical, Loss, BreakEven, MinimumProfit, Profit, - Closed, - Any, } -impl PositionState { +impl PositionProfitState { fn color(self) -> String { match self { - PositionState::Any => "blue", - PositionState::Critical | PositionState::Loss => "red", - PositionState::BreakEven => "yellow", - PositionState::MinimumProfit | PositionState::Profit => "green", - PositionState::Closed => "gray", + PositionProfitState::Critical | PositionProfitState::Loss => "red", + PositionProfitState::BreakEven => "yellow", + PositionProfitState::MinimumProfit | PositionProfitState::Profit => "green", } .into() } } + +#[derive(Copy, Clone, Eq, PartialEq, Hash)] +pub enum PositionState { + Closed, + Open, +} diff --git a/rustybot/src/strategy.rs b/rustybot/src/strategy.rs index 7251a98..24f3fdb 100644 --- a/rustybot/src/strategy.rs +++ b/rustybot/src/strategy.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; use crate::events::{Event, EventKind, EventMetadata, SignalKind}; use crate::pairs::PairStatus; -use crate::positions::{Position, PositionState}; +use crate::positions::{Position, PositionProfitState, PositionState}; pub trait Strategy { fn position_on_new_tick( @@ -47,18 +47,18 @@ impl Strategy for TrailingStop { let state = { if pl_perc > TrailingStop::GOOD_PROFIT_PERC { - PositionState::Profit + PositionProfitState::Profit } else if TrailingStop::MIN_PROFIT_PERC <= pl_perc && pl_perc < TrailingStop::GOOD_PROFIT_PERC { - PositionState::MinimumProfit + PositionProfitState::MinimumProfit } else if 0.0 <= pl_perc && pl_perc < TrailingStop::MIN_PROFIT_PERC { - PositionState::BreakEven + PositionProfitState::BreakEven } else if TrailingStop::MAX_LOSS_PERC < pl_perc && pl_perc < 0.0 { - PositionState::Loss + PositionProfitState::Loss } else { signals.push(SignalKind::ClosePosition); - PositionState::Critical + PositionProfitState::Critical } }; @@ -69,7 +69,7 @@ impl Strategy for TrailingStop { match opt_pre_pw { Some(prev) => { - if prev.state() == state { + if prev.profit_state() == Some(state) { return (pw, events, signals); } } @@ -79,25 +79,25 @@ impl Strategy for TrailingStop { let events = { let mut events = vec![]; - if state == PositionState::Profit { + if state == PositionProfitState::Profit { events.push(Event::new( EventKind::ReachedGoodProfit, status.current_tick(), Some(event_metadata), )); - } else if state == PositionState::MinimumProfit { + } else if state == PositionProfitState::MinimumProfit { events.push(Event::new( EventKind::ReachedMinProfit, status.current_tick(), Some(event_metadata), )); - } else if state == PositionState::BreakEven { + } else if state == PositionProfitState::BreakEven { events.push(Event::new( EventKind::ReachedBreakEven, status.current_tick(), Some(event_metadata), )); - } else if state == PositionState::Loss { + } else if state == PositionProfitState::Loss { events.push(Event::new( EventKind::ReachedLoss, status.current_tick(),