positions with strategy working

This commit is contained in:
Giulio De Pasquale 2021-01-05 19:50:14 +00:00
parent ae648b70a4
commit 0e2673837c
9 changed files with 133 additions and 73 deletions

7
rustybot/Cargo.lock generated
View File

@ -223,6 +223,12 @@ dependencies = [
"generic-array 0.14.4", "generic-array 0.14.4",
] ]
[[package]]
name = "dyn-clone"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee2626afccd7561a06cf1367e2950c4718ea04565e20fb5029b6c7d8ad09abcf"
[[package]] [[package]]
name = "encoding_rs" name = "encoding_rs"
version = "0.8.26" version = "0.8.26"
@ -1056,6 +1062,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"bitfinex", "bitfinex",
"dyn-clone",
"futures-util", "futures-util",
"regex", "regex",
"tokio 0.2.24", "tokio 0.2.24",

View File

@ -13,3 +13,4 @@ tokio-tungstenite = "*"
futures-util = { version = "0.3", default-features = false, features = ["async-await", "sink", "std"] } futures-util = { version = "0.3", default-features = false, features = ["async-await", "sink", "std"] }
async-trait = "0.1" async-trait = "0.1"
regex = "1" regex = "1"
dyn-clone = "1"

View File

@ -9,17 +9,16 @@ use tokio::time::delay_for;
use crate::connectors::Connector; use crate::connectors::Connector;
use crate::currency::{Symbol, SymbolPair}; use crate::currency::{Symbol, SymbolPair};
use crate::pairs::PairStatus; use crate::pairs::PairStatus;
use crate::strategy::Strategy;
use crate::ticker::Ticker; use crate::ticker::Ticker;
use crate::BoxError; use crate::BoxError;
pub struct BfxBot<'a> { pub struct BfxBot<'a> {
connector: Box<dyn Connector + 'a>, connector: Box<dyn Connector + 'a>,
ticker: Ticker, ticker: Ticker,
pair_status: Vec<PairStatus>, pair_statuses: Vec<PairStatus<'a>>,
quote: Symbol, quote: Symbol,
trading_symbols: Vec<Symbol>, trading_symbols: Vec<Symbol>,
// account_info: String,
// ledger: String,
} }
impl<'a> BfxBot<'a> { impl<'a> BfxBot<'a> {
@ -32,18 +31,24 @@ impl<'a> BfxBot<'a> {
BfxBot { BfxBot {
connector: Box::new(connector), connector: Box::new(connector),
ticker: Ticker::new(tick_duration), ticker: Ticker::new(tick_duration),
pair_status: trading_symbols pair_statuses: trading_symbols
.iter() .iter()
.map(|x| SymbolPair::new(quote.clone(), x.clone())) .map(|x| SymbolPair::new(quote.clone(), x.clone()))
.map(|x| PairStatus::new(x, 1, None)) .map(|x| PairStatus::new(x, 1, None))
.collect(), .collect(),
quote, quote,
// account_info: String::new(),
// ledger: String::new(),
trading_symbols, trading_symbols,
} }
} }
pub fn with_strategy(mut self, strategy: Box<dyn Strategy>) -> Self {
self.pair_statuses
.iter_mut()
.for_each(|x| x.set_strategy(dyn_clone::clone_box(&*strategy)));
self
}
pub async fn current_prices(&self, symbol: Symbol) -> Result<TradingPairTicker, BoxError> { pub async fn current_prices(&self, symbol: Symbol) -> Result<TradingPairTicker, BoxError> {
let trading_pair = SymbolPair::new(self.quote.clone(), symbol.clone()); let trading_pair = SymbolPair::new(self.quote.clone(), symbol.clone());
@ -54,17 +59,35 @@ impl<'a> BfxBot<'a> {
self.connector.current_prices(&trading_pair).await self.connector.current_prices(&trading_pair).await
} }
pub async fn update(&mut self) { pub async fn start_loop(&mut self) -> Result<(), BoxError> {
println!("Updating..."); if let Err(e) = self.update_pair_statuses().await {
delay_for(self.ticker.duration()).await; println!("Error while updating pairs at first start: {}", e);
self.ticker.inc(); }
// self.update_pairs().await;
println!("Done!"); loop {
self.update().await;
}
} }
// async fn update_pairs(&mut self) { async fn update(&mut self) {
// let active_positions = self.bfx.positions.active_positions().await?; delay_for(self.ticker.duration()).await;
// self.ticker.inc();
// for p in active_positions {}
// } if let Err(e) = self.update_pair_statuses().await {
println!("Error while updating pairs: {}", e);
}
}
async fn update_pair_statuses(&mut self) -> Result<(), BoxError> {
for status in &mut self.pair_statuses {
// add positions for each pair
self.connector
.active_positions(status.pair())
.await?
.into_iter()
.for_each(|x| status.add_position(x));
}
Ok(())
}
} }

View File

@ -7,7 +7,7 @@ use regex::Regex;
use crate::BoxError; use crate::BoxError;
#[derive(Clone, PartialEq, Hash)] #[derive(Clone, PartialEq, Hash, Debug, Eq)]
pub struct Symbol { pub struct Symbol {
name: Cow<'static, str>, name: Cow<'static, str>,
} }
@ -26,6 +26,8 @@ impl Symbol {
pub const BTC: Symbol = Symbol::new_static("BTC"); pub const BTC: Symbol = Symbol::new_static("BTC");
pub const ETH: Symbol = Symbol::new_static("ETH"); pub const ETH: Symbol = Symbol::new_static("ETH");
pub const LTC: Symbol = Symbol::new_static("LTC"); pub const LTC: Symbol = Symbol::new_static("LTC");
pub const TESTBTC: Symbol = Symbol::new_static("TESTBTC");
pub const TESTUSD: Symbol = Symbol::new_static("TESTUSD");
pub const USD: Symbol = Symbol::new_static("USD"); pub const USD: Symbol = Symbol::new_static("USD");
pub const GBP: Symbol = Symbol::new_static("GBP"); pub const GBP: Symbol = Symbol::new_static("GBP");
pub const EUR: Symbol = Symbol::new_static("EUR"); pub const EUR: Symbol = Symbol::new_static("EUR");
@ -53,7 +55,7 @@ impl Display for Symbol {
} }
} }
#[derive(Clone)] #[derive(Clone, Debug, Eq, PartialEq)]
pub struct SymbolPair { pub struct SymbolPair {
quote: Symbol, quote: Symbol,
base: Symbol, base: Symbol,
@ -79,7 +81,7 @@ impl SymbolPair {
impl Into<String> for SymbolPair { impl Into<String> for SymbolPair {
fn into(self) -> String { fn into(self) -> String {
format!("{}{}", self.base, self.quote) format!("{}/{}", self.base, self.quote)
} }
} }
@ -87,7 +89,7 @@ impl TryFrom<&str> for SymbolPair {
type Error = BoxError; type Error = BoxError;
fn try_from(value: &str) -> Result<Self, Self::Error> { fn try_from(value: &str) -> Result<Self, Self::Error> {
const REGEX: &str = r"^[t|f](?P<base>\w{3,4}):?(?P<quote>\w{3,4})"; const REGEX: &str = r"^[t|f](?P<base>\w{3,7}):?(?P<quote>\w{3,7})";
let captures = Regex::new(REGEX)?.captures(&value).ok_or("Invalid input")?; let captures = Regex::new(REGEX)?.captures(&value).ok_or("Invalid input")?;
let quote = captures.name("quote").ok_or("Quote not found")?.as_str(); let quote = captures.name("quote").ok_or("Quote not found")?.as_str();
@ -102,7 +104,7 @@ impl TryFrom<&str> for SymbolPair {
impl Display for SymbolPair { impl Display for SymbolPair {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{}{}", self.base, self.quote) write!(f, "{}/{}", self.base, self.quote)
} }
} }

View File

@ -8,13 +8,13 @@ use crate::pairs::PairStatus;
use crate::positions::{Position, PositionProfitState, PositionState}; use crate::positions::{Position, PositionProfitState, PositionState};
use crate::BoxError; use crate::BoxError;
#[derive(Copy, Clone)] #[derive(Copy, Clone, Debug)]
pub enum SignalKind { pub enum SignalKind {
ClosePosition, ClosePosition,
OpenPosition, OpenPosition,
} }
#[derive(Copy, Clone)] #[derive(Copy, Clone, Debug)]
pub struct EventMetadata { pub struct EventMetadata {
position_id: Option<u64>, position_id: Option<u64>,
order_id: Option<u64>, order_id: Option<u64>,
@ -29,7 +29,7 @@ impl EventMetadata {
} }
} }
#[derive(Copy, Clone, PartialEq, Eq, Hash)] #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub enum EventKind { pub enum EventKind {
NewMinimum, NewMinimum,
NewMaximum, NewMaximum,
@ -45,7 +45,7 @@ pub enum EventKind {
Any, Any,
} }
#[derive(Copy, Clone)] #[derive(Copy, Clone, Debug)]
pub struct Event { pub struct Event {
kind: EventKind, kind: EventKind,
tick: u64, tick: u64,

View File

@ -1,7 +1,9 @@
use tokio::time::{delay_for, Duration}; use tokio::time::{delay_for, Duration};
use crate::bot::BfxBot; use crate::bot::BfxBot;
use crate::connectors::BfxWrapper;
use crate::currency::{Symbol, SymbolPair}; use crate::currency::{Symbol, SymbolPair};
use crate::strategy::TrailingStop;
mod bot; mod bot;
mod connectors; mod connectors;
@ -17,24 +19,17 @@ pub type BoxError = Box<dyn std::error::Error + Send + Sync>;
#[tokio::main] #[tokio::main]
async fn main() -> Result<(), BoxError> { async fn main() -> Result<(), BoxError> {
// let test_api_key = "P1EVE68DJByDAkGQvpIkTwfrbYXd2Vo2ZaIhTYb9vx2"; let test_api_key = "P1EVE68DJByDAkGQvpIkTwfrbYXd2Vo2ZaIhTYb9vx2";
// let test_api_secret = "1nicg8z0zKVEt5Rb7ZDpIYjVYVTgvCaCPMZqB0niFli"; let test_api_secret = "1nicg8z0zKVEt5Rb7ZDpIYjVYVTgvCaCPMZqB0niFli";
// //
// let mut bot = BfxBot::new( let bfx = BfxWrapper::new(test_api_key, test_api_secret);
// test_api_key, let mut bot = BfxBot::new(
// test_api_secret, bfx,
// vec![Symbol::BTC, Symbol::ETH, Symbol::XMR], vec![Symbol::TESTBTC],
// Symbol::USD, Symbol::TESTUSD,
// Duration::new(20, 0), Duration::new(20, 0),
// ); )
// .with_strategy(Box::new(TrailingStop::new()));
// loop {
// let ticker = bot.current_prices("ETH".into()).await?;
// bot.update().await;
//
// // let ticker = bot.current_prices("ETH".into()).await?;
// println!("{:?}", ticker);
// }
Ok(()) Ok(bot.start_loop().await?)
} }

View File

@ -1,12 +1,12 @@
use std::collections::HashMap; use std::collections::HashMap;
use crate::currency::SymbolPair; use crate::currency::SymbolPair;
use crate::events::{Event, EventDispatcher}; use crate::events::{Event, EventDispatcher, SignalKind};
use crate::orders::Order; use crate::orders::Order;
use crate::positions::Position; use crate::positions::Position;
use crate::strategy::Strategy; use crate::strategy::Strategy;
pub struct PairStatus { pub struct PairStatus<'a> {
pair: SymbolPair, pair: SymbolPair,
dispatcher: EventDispatcher, dispatcher: EventDispatcher,
prices: HashMap<u64, f64>, prices: HashMap<u64, f64>,
@ -14,10 +14,11 @@ pub struct PairStatus {
orders: HashMap<u64, Vec<Order>>, orders: HashMap<u64, Vec<Order>>,
positions: HashMap<u64, Vec<Position>>, positions: HashMap<u64, Vec<Position>>,
current_tick: u64, current_tick: u64,
strategy: Option<Box<dyn Strategy>>, strategy: Option<Box<dyn Strategy + 'a>>,
signals: HashMap<u64, SignalKind>,
} }
impl PairStatus { impl<'a> PairStatus<'a> {
pub fn new(pair: SymbolPair, current_tick: u64, strategy: Option<Box<dyn Strategy>>) -> Self { pub fn new(pair: SymbolPair, current_tick: u64, strategy: Option<Box<dyn Strategy>>) -> Self {
PairStatus { PairStatus {
pair, pair,
@ -25,52 +26,82 @@ impl PairStatus {
prices: HashMap::new(), prices: HashMap::new(),
events: Vec::new(), events: Vec::new(),
positions: HashMap::new(), positions: HashMap::new(),
orders: HashMap::new(),
signals: HashMap::new(),
current_tick, current_tick,
strategy, strategy,
orders: HashMap::new(),
} }
} }
pub fn add_position(&mut self, position: Position) { pub fn add_position(&mut self, position: Position) {
let (pw, events, signals) = { let (new_position, events, signals) = {
match &self.strategy { match &self.strategy {
Some(strategy) => strategy.position_on_new_tick(&position, &self), Some(strategy) => strategy.position_on_new_tick(&position, &self),
None => ( None => (position, vec![], vec![]),
position,
// PositionWrapper::new(position.clone(), position.pl(), position.pl_perc(), None),
vec![],
vec![],
),
} }
}; };
self.positions self.positions
.entry(self.current_tick) .entry(self.current_tick)
.or_default() .or_default()
.push(pw.clone()); .push(new_position.clone());
// calling position state callbacks // calling position state callbacks
self.dispatcher.call_position_state_handlers(&pw, &self); self.dispatcher
.call_position_state_handlers(&new_position, &self);
// adding events and calling callbacks // adding events and calling callbacks
for e in events { for e in events {
self.add_event(e); self.add_event(e);
} }
// adding signals to current tick vector
for s in signals {
self.add_signal(s);
}
println!(
"EVENTS: {:?} | SIGNALS: {:?} | POSITION: {:?}",
self.events, self.signals, new_position
);
} }
pub fn add_event(&mut self, event: Event) { fn add_event(&mut self, event: Event) {
self.events.push(event); self.events.push(event);
self.dispatcher.call_event_handlers(&event, &self); self.dispatcher.call_event_handlers(&event, &self);
} }
fn add_signal(&mut self, signal: SignalKind) {
self.signals.insert(self.current_tick(), signal);
}
pub fn current_tick(&self) -> u64 { pub fn current_tick(&self) -> u64 {
self.current_tick self.current_tick
} }
pub fn previous_pw(&self, id: u64) -> Option<&Position> { pub fn set_strategy(&mut self, strategy: Box<dyn Strategy + 'a>) {
self.strategy = Some(strategy);
}
pub fn pair(&self) -> &SymbolPair {
&self.pair
}
pub fn position_previous_tick(&self, id: u64, tick: Option<u64>) -> Option<&Position> {
let tick = match tick {
Some(tick) => {
if tick < 1 {
1
} else {
tick
}
}
None => self.current_tick() - 1,
};
self.positions self.positions
.get(&(self.current_tick - 1)) .get(&tick)
.and_then(|x| x.iter().find(|x| x.position_id() == id)) .and_then(|x| x.iter().find(|x| x.position_id() == id))
} }
} }

View File

@ -1,6 +1,6 @@
use crate::currency::{Symbol, SymbolPair}; use crate::currency::{Symbol, SymbolPair};
#[derive(Clone)] #[derive(Clone, Debug)]
pub struct Position { pub struct Position {
pair: SymbolPair, pair: SymbolPair,
state: PositionState, state: PositionState,
@ -91,7 +91,7 @@ impl Position {
} }
} }
#[derive(Copy, Clone, Eq, PartialEq, Hash)] #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
pub enum PositionProfitState { pub enum PositionProfitState {
Critical, Critical,
Loss, Loss,
@ -111,7 +111,7 @@ impl PositionProfitState {
} }
} }
#[derive(Copy, Clone, Eq, PartialEq, Hash)] #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
pub enum PositionState { pub enum PositionState {
Closed, Closed,
Open, Open,

View File

@ -3,8 +3,9 @@ use std::collections::HashMap;
use crate::events::{Event, EventKind, EventMetadata, SignalKind}; use crate::events::{Event, EventKind, EventMetadata, SignalKind};
use crate::pairs::PairStatus; use crate::pairs::PairStatus;
use crate::positions::{Position, PositionProfitState, PositionState}; use crate::positions::{Position, PositionProfitState, PositionState};
use dyn_clone::DynClone;
pub trait Strategy { pub trait Strategy: DynClone {
fn position_on_new_tick( fn position_on_new_tick(
&self, &self,
position: &Position, position: &Position,
@ -12,7 +13,8 @@ pub trait Strategy {
) -> (Position, Vec<Event>, Vec<SignalKind>); ) -> (Position, Vec<Event>, Vec<SignalKind>);
} }
struct TrailingStop { #[derive(Clone, Debug)]
pub struct TrailingStop {
stop_percentages: HashMap<u64, f64>, stop_percentages: HashMap<u64, f64>,
} }
@ -62,18 +64,17 @@ impl Strategy for TrailingStop {
} }
}; };
let opt_pre_pw = status.previous_pw(position.position_id()); let opt_pre_pw = status.position_previous_tick(position.position_id(), None);
let event_metadata = EventMetadata::new(Some(position.position_id()), None); let event_metadata = EventMetadata::new(Some(position.position_id()), None);
// let pw = PositionWrapper::new(position.clone(), position.pl(), pl_perc, Some(state)); let new_position = position.clone().with_profit_state(Some(state));
let pw = position.clone();
match opt_pre_pw { match opt_pre_pw {
Some(prev) => { Some(prev) => {
if prev.profit_state() == Some(state) { if prev.profit_state() == Some(state) {
return (pw, events, signals); return (new_position, events, signals);
} }
} }
None => return (pw, events, signals), None => return (new_position, events, signals),
}; };
let events = { let events = {
@ -114,6 +115,6 @@ impl Strategy for TrailingStop {
events events
}; };
return (pw, events, signals); return (new_position, events, signals);
} }
} }