traits and shit

This commit is contained in:
Giulio De Pasquale 2021-01-05 12:58:47 +00:00
parent ea7c8394a3
commit 78b57b3899
9 changed files with 179 additions and 115 deletions

12
rustybot/Cargo.lock generated
View File

@ -15,6 +15,17 @@ version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e" checksum = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e"
[[package]]
name = "async-trait"
version = "0.1.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d3a45e77e34375a7923b1e8febb049bb011f064714a8e17a1a616fef01da13d"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "autocfg" name = "autocfg"
version = "1.0.1" version = "1.0.1"
@ -1016,6 +1027,7 @@ checksum = "6e3bad0ee36814ca07d7968269dd4b7ec89ec2da10c4bb613928d3077083c232"
name = "rustybot" name = "rustybot"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"async-trait",
"bitfinex", "bitfinex",
"futures-util", "futures-util",
"tokio 0.2.24", "tokio 0.2.24",

View File

@ -11,3 +11,4 @@ bitfinex = { path= "/home/giulio/dev/bitfinex-rs" }
tokio = { version = "0.2", features=["full"]} tokio = { version = "0.2", features=["full"]}
tokio-tungstenite = "*" 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"

View File

@ -3,34 +3,17 @@ use std::collections::HashMap;
use bitfinex::api::Bitfinex; use bitfinex::api::Bitfinex;
use bitfinex::positions::Position; use bitfinex::positions::Position;
use bitfinex::ticker::TradingPairTicker;
use tokio::time::delay_for; use tokio::time::delay_for;
use crate::BoxError; use crate::connectors::Connector;
use crate::currency::{Symbol, SymbolPair}; use crate::currency::{Symbol, SymbolPair};
use crate::pairs::PairStatus; use crate::pairs::PairStatus;
use crate::ticker::Ticker; use crate::ticker::Ticker;
use bitfinex::ticker::TradingPairTicker; use crate::BoxError;
pub struct BfxWrapper { pub struct BfxBot<'a> {
bfx: Bitfinex connector: Box<dyn Connector + 'a>,
}
impl BfxWrapper {
pub fn new(api_key: &str, api_secret: &str) -> Self {
BfxWrapper {
bfx: Bitfinex::new(Some(api_key.into()), Some(api_secret.into()))
}
}
pub async fn current_prices(&self, pair: &SymbolPair) -> Result<TradingPairTicker, BoxError> {
let ticker: TradingPairTicker = self.bfx.ticker.trading_pair(pair.clone()).await?;
Ok(ticker)
}
}
pub struct BfxBot {
pub bfx: BfxWrapper,
ticker: Ticker, ticker: Ticker,
pair_status: Vec<PairStatus>, pair_status: Vec<PairStatus>,
quote: Symbol, quote: Symbol,
@ -39,10 +22,15 @@ pub struct BfxBot {
// ledger: String, // ledger: String,
} }
impl BfxBot { impl<'a> BfxBot<'a> {
pub fn new<S: Into<String>>(api_key: S, api_secret: S, trading_symbols: Vec<Symbol>, quote: Symbol, tick_duration: Duration) -> Self { pub fn new<C: Connector + 'a>(
connector: C,
trading_symbols: Vec<Symbol>,
quote: Symbol,
tick_duration: Duration,
) -> Self {
BfxBot { BfxBot {
bfx: BfxWrapper::new(&api_key.into(), &api_secret.into()), connector: Box::new(connector),
ticker: Ticker::new(tick_duration), ticker: Ticker::new(tick_duration),
pair_status: trading_symbols pair_status: trading_symbols
.iter() .iter()
@ -59,11 +47,11 @@ impl BfxBot {
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());
if !self.trading_symbols.contains(&symbol){ if !self.trading_symbols.contains(&symbol) {
return Err("Symbol not supported.".into()); return Err("Symbol not supported.".into());
} }
self.bfx.current_prices(&trading_pair).await self.connector.current_prices(&trading_pair).await
} }
pub async fn update(&mut self) { pub async fn update(&mut self) {
@ -80,4 +68,3 @@ impl BfxBot {
// for p in active_positions {} // for p in active_positions {}
// } // }
} }

View File

@ -1,12 +1,11 @@
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use std::future::Future; use std::future::Future;
use bitfinex::positions::Position;
use tokio::stream::StreamExt; use tokio::stream::StreamExt;
use tokio::task::JoinHandle; use tokio::task::JoinHandle;
use crate::pairs::PairStatus; use crate::pairs::PairStatus;
use crate::positions::{PositionState, PositionWrapper}; use crate::positions::{Position, PositionState};
use crate::BoxError; use crate::BoxError;
#[derive(Copy, Clone)] #[derive(Copy, Clone)]
@ -80,11 +79,10 @@ impl Event {
pub struct EventDispatcher { pub struct EventDispatcher {
event_handlers: HashMap<EventKind, Vec<Box<dyn Fn(&Event, &PairStatus) -> JoinHandle<()>>>>, event_handlers: HashMap<EventKind, Vec<Box<dyn Fn(&Event, &PairStatus) -> JoinHandle<()>>>>,
position_state_handlers: position_state_handlers:
HashMap<PositionState, Vec<Box<dyn Fn(&PositionWrapper, &PairStatus) -> JoinHandle<()>>>>, HashMap<PositionState, Vec<Box<dyn Fn(&Position, &PairStatus) -> JoinHandle<()>>>>,
on_any_event_handlers: Vec<Box<dyn Fn(&Event, &PairStatus) -> JoinHandle<()>>>, on_any_event_handlers: Vec<Box<dyn Fn(&Event, &PairStatus) -> JoinHandle<()>>>,
on_any_position_state_handlers: on_any_position_state_handlers: Vec<Box<dyn Fn(&Position, &PairStatus) -> JoinHandle<()>>>,
Vec<Box<dyn Fn(&PositionWrapper, &PairStatus) -> JoinHandle<()>>>,
} }
impl EventDispatcher { impl EventDispatcher {
@ -109,17 +107,15 @@ impl EventDispatcher {
} }
} }
pub fn call_position_state_handlers(&self, pw: &PositionWrapper, status: &PairStatus) { pub fn call_position_state_handlers(&self, position: &Position, status: &PairStatus) {
if let Some(state) = pw.state() { if let Some(functions) = self.position_state_handlers.get(&position.state()) {
if let Some(functions) = self.position_state_handlers.get(&state) {
for f in functions { for f in functions {
f(pw, status); f(position, status);
}
} }
} }
for f in &self.on_any_position_state_handlers { for f in &self.on_any_position_state_handlers {
f(pw, status); f(position, status);
} }
} }
@ -145,18 +141,18 @@ impl EventDispatcher {
state: PositionState, state: PositionState,
f: F, f: F,
) where ) where
F: Fn(&PositionWrapper, &PairStatus) -> Fut, F: Fn(&Position, &PairStatus) -> Fut,
Fut: Future<Output = ()> + Send, Fut: Future<Output = ()> + Send,
{ {
match state { match state {
PositionState::Any => self PositionState::Any => self
.on_any_position_state_handlers .on_any_position_state_handlers
.push(Box::new(move |pw, s| tokio::spawn(f(&pw, s)))), .push(Box::new(move |p, s| tokio::spawn(f(&p, s)))),
_ => self _ => self
.position_state_handlers .position_state_handlers
.entry(state) .entry(state)
.or_default() .or_default()
.push(Box::new(move |pw, s| tokio::spawn(f(&pw, s)))), .push(Box::new(move |p, s| tokio::spawn(f(&p, s)))),
} }
} }
} }

View File

@ -1,35 +1,40 @@
use bitfinex::api::Bitfinex; use tokio::time::{delay_for, Duration};
use bitfinex::ticker::TradingPairTicker;
use tokio::time::{Duration, delay_for};
use crate::bot::BfxBot; use crate::bot::BfxBot;
use crate::currency::{Symbol, SymbolPair}; use crate::currency::{Symbol, SymbolPair};
mod ticker; mod bot;
mod connectors;
mod currency;
mod events; mod events;
mod orders;
mod pairs; mod pairs;
mod positions; mod positions;
mod strategy; mod strategy;
mod bot; mod ticker;
mod currency;
pub type BoxError = Box<dyn std::error::Error + Send + Sync>; 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(test_api_key, test_api_secret, vec![Symbol::BTC, Symbol::ETH, Symbol::XMR], Symbol::USD, Duration::new(20, 0)); // let mut bot = BfxBot::new(
// test_api_key,
loop { // test_api_secret,
let ticker = bot.current_prices("ETH".into()).await?; // vec![Symbol::BTC, Symbol::ETH, Symbol::XMR],
bot.update().await; // Symbol::USD,
// Duration::new(20, 0),
// );
//
// loop {
// let ticker = bot.current_prices("ETH".into()).await?; // let ticker = bot.current_prices("ETH".into()).await?;
println!("{:?}", ticker); // bot.update().await;
} //
// // let ticker = bot.current_prices("ETH".into()).await?;
// println!("{:?}", ticker);
// }
Ok(()) Ok(())
} }

21
rustybot/src/orders.rs Normal file
View File

@ -0,0 +1,21 @@
pub struct Order {
pub id: i64,
pub group_id: Option<i32>,
pub client_id: i64,
pub symbol: String,
pub creation_timestamp: i64,
pub update_timestamp: i64,
pub amount: f64,
pub amount_original: f64,
pub order_type: String,
pub previous_order_type: Option<String>,
pub flags: Option<i32>,
pub order_status: Option<String>,
pub price: f64,
pub price_avg: f64,
pub price_trailing: Option<f64>,
pub price_aux_limit: Option<f64>,
pub notify: i32,
pub hidden: i32,
pub placed_id: Option<i32>,
}

View File

@ -1,10 +1,9 @@
use std::collections::HashMap; use std::collections::HashMap;
use bitfinex::positions::Position;
use crate::currency::SymbolPair; use crate::currency::SymbolPair;
use crate::events::{Event, EventDispatcher}; use crate::events::{Event, EventDispatcher};
use crate::positions::PositionWrapper; use crate::orders::Order;
use crate::positions::Position;
use crate::strategy::Strategy; use crate::strategy::Strategy;
pub struct PairStatus { pub struct PairStatus {
@ -12,8 +11,8 @@ pub struct PairStatus {
dispatcher: EventDispatcher, dispatcher: EventDispatcher,
prices: HashMap<u64, f64>, prices: HashMap<u64, f64>,
events: Vec<Event>, events: Vec<Event>,
// orders: HashMap<u64, Vec<Order>>, orders: HashMap<u64, Vec<Order>>,
positions: HashMap<u64, Vec<PositionWrapper>>, positions: HashMap<u64, Vec<Position>>,
current_tick: u64, current_tick: u64,
strategy: Option<Box<dyn Strategy>>, strategy: Option<Box<dyn Strategy>>,
} }
@ -28,6 +27,7 @@ impl PairStatus {
positions: HashMap::new(), positions: HashMap::new(),
current_tick, current_tick,
strategy, strategy,
orders: HashMap::new(),
} }
} }
@ -36,7 +36,8 @@ impl PairStatus {
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 => (
PositionWrapper::new(position.clone(), position.pl(), position.pl_perc(), None), position,
// PositionWrapper::new(position.clone(), position.pl(), position.pl_perc(), None),
vec![], vec![],
vec![], vec![],
), ),
@ -67,9 +68,9 @@ impl PairStatus {
self.current_tick self.current_tick
} }
pub fn previous_pw(&self, id: u64) -> Option<&PositionWrapper> { pub fn previous_pw(&self, id: u64) -> Option<&Position> {
self.positions self.positions
.get(&(self.current_tick - 1)) .get(&(self.current_tick - 1))
.and_then(|x| x.iter().find(|x| x.position().position_id() == id)) .and_then(|x| x.iter().find(|x| x.position_id() == id))
} }
} }

View File

@ -1,4 +1,81 @@
use bitfinex::positions::Position; use crate::currency::{Symbol, SymbolPair};
#[derive(Clone)]
pub struct Position {
pair: SymbolPair,
state: PositionState,
amount: f64,
base_price: f64,
pl: f64,
pl_perc: f64,
price_liq: f64,
position_id: u64,
creation_date: Option<u64>,
creation_update: Option<u64>,
}
impl Position {
pub fn new(
pair: SymbolPair,
state: PositionState,
amount: f64,
base_price: f64,
pl: f64,
pl_perc: f64,
price_liq: f64,
position_id: u64,
) -> Self {
Position {
pair,
state,
amount,
base_price,
pl,
pl_perc,
price_liq,
position_id,
creation_date: None,
creation_update: None,
}
}
pub fn with_creation_date(mut self, creation_date: u64) -> Self {
self.creation_date = Some(creation_date);
self.creation_update = Some(creation_date);
self
}
pub fn pair(&self) -> &SymbolPair {
&self.pair
}
pub fn state(&self) -> PositionState {
self.state
}
pub fn amount(&self) -> f64 {
self.amount
}
pub fn base_price(&self) -> f64 {
self.base_price
}
pub fn pl(&self) -> f64 {
self.pl
}
pub fn pl_perc(&self) -> f64 {
self.pl_perc
}
pub fn price_liq(&self) -> f64 {
self.price_liq
}
pub fn position_id(&self) -> u64 {
self.position_id
}
pub fn creation_date(&self) -> Option<u64> {
self.creation_date
}
pub fn creation_update(&self) -> Option<u64> {
self.creation_update
}
}
#[derive(Copy, Clone, Eq, PartialEq, Hash)] #[derive(Copy, Clone, Eq, PartialEq, Hash)]
pub enum PositionState { pub enum PositionState {
@ -7,6 +84,7 @@ pub enum PositionState {
BreakEven, BreakEven,
MinimumProfit, MinimumProfit,
Profit, Profit,
Closed,
Any, Any,
} }
@ -17,44 +95,8 @@ impl PositionState {
PositionState::Critical | PositionState::Loss => "red", PositionState::Critical | PositionState::Loss => "red",
PositionState::BreakEven => "yellow", PositionState::BreakEven => "yellow",
PositionState::MinimumProfit | PositionState::Profit => "green", PositionState::MinimumProfit | PositionState::Profit => "green",
PositionState::Closed => "gray",
} }
.into() .into()
} }
} }
#[derive(Clone)]
pub struct PositionWrapper {
position: Position,
net_pl: f64,
net_pl_perc: f64,
state: Option<PositionState>,
}
impl PositionWrapper {
pub fn new(
position: Position,
net_pl: f64,
net_pl_perc: f64,
state: Option<PositionState>,
) -> Self {
PositionWrapper {
position,
net_pl,
net_pl_perc,
state,
}
}
pub fn position(&self) -> &Position {
&self.position
}
pub fn net_pl(&self) -> f64 {
self.net_pl
}
pub fn net_pl_perc(&self) -> f64 {
self.net_pl_perc
}
pub fn state(&self) -> Option<PositionState> {
self.state
}
}

View File

@ -1,17 +1,15 @@
use std::collections::HashMap; use std::collections::HashMap;
use bitfinex::positions::Position;
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::{PositionState, PositionWrapper}; use crate::positions::{Position, PositionState};
pub trait Strategy { pub trait Strategy {
fn position_on_new_tick( fn position_on_new_tick(
&self, &self,
position: &Position, position: &Position,
status: &PairStatus, status: &PairStatus,
) -> (PositionWrapper, Vec<Event>, Vec<SignalKind>); ) -> (Position, Vec<Event>, Vec<SignalKind>);
} }
struct TrailingStop { struct TrailingStop {
@ -42,7 +40,7 @@ impl Strategy for TrailingStop {
&self, &self,
position: &Position, position: &Position,
status: &PairStatus, status: &PairStatus,
) -> (PositionWrapper, Vec<Event>, Vec<SignalKind>) { ) -> (Position, Vec<Event>, Vec<SignalKind>) {
let mut signals = vec![]; let mut signals = vec![];
let pl_perc = TrailingStop::net_pl_percentage(position.pl_perc(), TrailingStop::TAKER_FEE); let pl_perc = TrailingStop::net_pl_percentage(position.pl_perc(), TrailingStop::TAKER_FEE);
let events = vec![]; let events = vec![];
@ -66,11 +64,12 @@ impl Strategy for TrailingStop {
let opt_pre_pw = status.previous_pw(position.position_id()); let opt_pre_pw = status.previous_pw(position.position_id());
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 pw = PositionWrapper::new(position.clone(), position.pl(), pl_perc, Some(state));
let pw = position.clone();
match opt_pre_pw { match opt_pre_pw {
Some(prev) => { Some(prev) => {
if prev.state() == Some(state) { if prev.state() == state {
return (pw, events, signals); return (pw, events, signals);
} }
} }