This commit is contained in:
Giulio De Pasquale 2021-01-16 11:43:16 +00:00
parent 0d48d3768a
commit dfd676612e
7 changed files with 271 additions and 102 deletions

1
rustybot/Cargo.lock generated
View File

@ -1128,6 +1128,7 @@ version = "0.1.0"
dependencies = [
"async-trait",
"bitfinex",
"byteorder",
"chrono",
"dyn-clone",
"fern",

View File

@ -17,3 +17,4 @@ dyn-clone = "1"
log = "0.4"
fern = {version = "0.6", features = ["colored"]}
chrono = "0.4"
byteorder = "1"

View File

@ -4,11 +4,11 @@ use std::sync::Arc;
use async_trait::async_trait;
use bitfinex::api::Bitfinex;
use bitfinex::orders::{OrderForm, OrderMeta};
use bitfinex::orders::{ActiveOrder, OrderMeta};
use bitfinex::ticker::TradingPairTicker;
use crate::currency::SymbolPair;
use crate::models::{Order, OrderKind, Position, PositionState, PriceTicker};
use crate::models::{ExecutedOrder, OrderForm, OrderKind, Position, PositionState, PriceTicker};
use crate::BoxError;
#[derive(Eq, PartialEq, Hash, Clone, Debug)]
@ -50,18 +50,12 @@ impl Client {
self.inner.current_prices(pair).await
}
pub async fn active_orders(&self, pair: &SymbolPair) -> Result<Vec<Order>, BoxError> {
pub async fn active_orders(&self, pair: &SymbolPair) -> Result<Vec<ExecutedOrder>, BoxError> {
self.inner.active_orders(pair).await
}
pub async fn submit_order(
&self,
pair: &SymbolPair,
amount: f64,
price: f64,
kind: &OrderKind,
) -> Result<(), BoxError> {
self.inner.submit_order(pair, amount, price, kind).await
pub async fn submit_order(&self, order: OrderForm) -> Result<ExecutedOrder, BoxError> {
self.inner.submit_order(order).await
}
}
@ -70,14 +64,8 @@ pub trait Connector: Send + Sync {
fn name(&self) -> String;
async fn active_positions(&self, pair: &SymbolPair) -> Result<Option<Vec<Position>>, BoxError>;
async fn current_prices(&self, pair: &SymbolPair) -> Result<TradingPairTicker, BoxError>;
async fn active_orders(&self, pair: &SymbolPair) -> Result<Vec<Order>, BoxError>;
async fn submit_order(
&self,
pair: &SymbolPair,
amount: f64,
price: f64,
kind: &OrderKind,
) -> Result<(), BoxError>;
async fn active_orders(&self, pair: &SymbolPair) -> Result<Vec<ExecutedOrder>, BoxError>;
async fn submit_order(&self, order: OrderForm) -> Result<ExecutedOrder, BoxError>;
}
impl Debug for dyn Connector {
@ -148,24 +136,28 @@ impl Connector for BitfinexConnector {
Ok(ticker)
}
async fn active_orders(&self, pair: &SymbolPair) -> Result<Vec<Order>, BoxError> {
async fn active_orders(&self, pair: &SymbolPair) -> Result<Vec<ExecutedOrder>, BoxError> {
unimplemented!()
}
async fn submit_order(
&self,
pair: &SymbolPair,
amount: f64,
price: f64,
kind: &OrderKind,
) -> Result<(), BoxError> {
async fn submit_order(&self, order: OrderForm) -> Result<ExecutedOrder, BoxError> {
let order_form = match &self.affiliate_code {
Some(affiliate_code) => OrderForm::new(pair.trading_repr(), price, amount, kind.into())
Some(affiliate_code) => bitfinex::orders::OrderForm::new(
order.pair().trading_repr(),
*order.price(),
*order.amount(),
order.kind().into(),
)
.with_meta(OrderMeta::new(affiliate_code.clone())),
None => OrderForm::new(pair.trading_repr(), price, amount, kind.into()),
None => bitfinex::orders::OrderForm::new(
order.pair().trading_repr(),
*order.price(),
*order.amount(),
order.kind().into(),
),
};
Ok(self.bfx.orders.submit_order(&order_form).await?)
Ok(self.bfx.orders.submit_order(&order_form).await?.into())
}
}
@ -216,6 +208,26 @@ impl From<&OrderKind> for bitfinex::orders::OrderKind {
}
}
impl From<OrderKind> for bitfinex::orders::OrderKind {
fn from(o: OrderKind) -> Self {
match o {
OrderKind::Limit => Self::Limit,
OrderKind::ExchangeLimit => Self::ExchangeLimit,
OrderKind::Market => Self::Market,
OrderKind::ExchangeMarket => Self::ExchangeMarket,
OrderKind::Stop => Self::Stop,
OrderKind::ExchangeStop => Self::ExchangeStop,
OrderKind::StopLimit => Self::StopLimit,
OrderKind::ExchangeStopLimit => Self::ExchangeStopLimit,
OrderKind::TrailingStop => Self::TrailingStop,
OrderKind::Fok => Self::Fok,
OrderKind::ExchangeFok => Self::ExchangeFok,
OrderKind::Ioc => Self::Ioc,
OrderKind::ExchangeIoc => Self::ExchangeIoc,
}
}
}
impl From<TradingPairTicker> for PriceTicker {
fn from(t: TradingPairTicker) -> Self {
Self {
@ -232,3 +244,29 @@ impl From<TradingPairTicker> for PriceTicker {
}
}
}
impl From<bitfinex::orders::ActiveOrder> for ExecutedOrder {
fn from(o: ActiveOrder) -> Self {
Self {
id: o.id,
group_id: o.group_id,
client_id: o.client_id,
symbol: o.symbol,
creation_timestamp: o.creation_timestamp,
update_timestamp: o.update_timestamp,
amount: o.amount,
amount_original: o.amount_original,
order_type: o.order_type,
previous_order_type: o.previous_order_type,
flags: o.flags,
order_status: o.order_status,
price: o.price,
price_avg: o.price_avg,
price_trailing: o.price_trailing,
price_aux_limit: o.price_aux_limit,
notify: o.notify,
hidden: o.hidden,
placed_id: o.placed_id,
}
}
}

View File

@ -3,12 +3,12 @@ use std::future::Future;
use tokio::task::JoinHandle;
use crate::managers::PriceManager;
use crate::managers::{OrderManager, PositionManager, PriceManager};
use crate::models::{Position, PositionProfitState};
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub enum SignalKind {
ClosePosition { position_id: u64 },
ClosePosition(Position),
OpenPosition,
}
@ -74,18 +74,19 @@ impl Event {
}
}
pub struct EventDispatcher {
pub struct Dispatcher {
event_handlers: HashMap<EventKind, Vec<Box<dyn Fn(&Event, &PriceManager) -> JoinHandle<()>>>>,
profit_state_handlers:
HashMap<PositionProfitState, Vec<Box<dyn Fn(&Position, &PriceManager) -> JoinHandle<()>>>>,
signal_handlers: HashMap<SignalKind, Vec<Box<dyn Fn(&SignalKind) -> JoinHandle<()>>>>,
on_any_event_handlers: Vec<Box<dyn Fn(&Event, &PriceManager) -> JoinHandle<()>>>,
on_any_profit_state_handlers: Vec<Box<dyn Fn(&Position, &PriceManager) -> JoinHandle<()>>>,
}
impl EventDispatcher {
impl Dispatcher {
pub fn new() -> Self {
EventDispatcher {
Dispatcher {
event_handlers: HashMap::new(),
profit_state_handlers: HashMap::new(),
signal_handlers: HashMap::new(),

View File

@ -1,10 +1,14 @@
use std::collections::HashMap;
use std::ops::Neg;
use bitfinex::ticker::TradingPairTicker;
use log::error;
use crate::connectors::{Client, ExchangeKind};
use crate::currency::SymbolPair;
use crate::events::{Event, SignalKind};
use crate::models::{Order, OrderForm, Position, PriceTicker};
use crate::strategy::PositionStrategy;
use crate::events::{Dispatcher, Event, SignalKind};
use crate::models::{ExecutedOrder, OrderForm, OrderKind, Position, PriceTicker};
use crate::strategy::{FastOrderStrategy, OrderStrategy, PositionStrategy};
use crate::BoxError;
pub struct EventManager {
@ -43,34 +47,6 @@ impl PriceManager {
))
}
// pub fn add_position(&mut self, position: Position) {
// let (new_position, events, signals) = {
// match &self.strategy {
// Some(strategy) => strategy.on_new_tick(&position, &self),
// None => (position, vec![], vec![]),
// }
// };
//
// self.positions
// .entry(self.current_tick)
// .or_default()
// .push(new_position.clone());
//
// // calling position state callbacks
// self.dispatcher
// .call_position_state_handlers(&new_position, &self);
//
// // adding events and calling callbacks
// for e in events {
// self.add_event(e);
// }
//
// // adding signals to current tick vector
// for s in signals {
// self.add_signal(s);
// }
// }
// fn add_event(&mut self, event: Event) {
// self.events.push(event);
//
@ -190,7 +166,7 @@ impl PositionManager {
match &self.strategy {
Some(strategy) => {
let (pos, strategy_events, _) =
strategy.on_new_tick(&position, &self);
strategy.on_new_tick(position, &self);
events.extend(strategy_events);
@ -225,29 +201,81 @@ impl PositionManager {
self.positions_history
.get(&tick)
.filter(|x| x.position_id() == id)
.filter(|x| x.id() == id)
.and_then(|x| Some(x))
}
}
pub struct OrderManager {
tracked_positions: HashMap<u64, ExecutedOrder>,
pair: SymbolPair,
open_orders: Vec<Order>,
open_orders: Vec<ExecutedOrder>,
client: Client,
strategy: Box<dyn OrderStrategy>,
}
impl OrderManager {
pub fn new(pair: SymbolPair, client: Client) -> Self {
const UNDERCUT_PERC: f64 = 0.005;
pub fn new(pair: SymbolPair, client: Client, strategy: Box<dyn OrderStrategy>) -> Self {
OrderManager {
pair,
open_orders: Vec::new(),
client,
strategy,
tracked_positions: HashMap::new(),
}
}
pub async fn close_position(&mut self, position: &Position) -> Result<(), BoxError> {
// checking if the position has an open order.
// If so, the strategy method is called, otherwise we open
// an undercut limit order at the best current price.
match self.tracked_positions.get(&position.id()) {
Some(open_order) => self.strategy.on_position_close(open_order, &self),
None => {
let current_prices = self.client.current_prices(&self.pair).await?;
let closing_price = self.best_closing_price(&position, &current_prices)?;
// submitting order
let order_form = OrderForm::new(
&self.pair,
closing_price,
position.amount().neg(),
OrderKind::Limit,
);
match self.client.submit_order(order_form).await {
Err(e) => error!("Could not submit order: {}", e),
Ok(o) => {
self.tracked_positions.insert(position.id(), o);
}
};
}
}
Ok(())
}
pub fn update(&self) -> Option<Vec<Event>> {
unimplemented!()
}
pub fn best_closing_price(
&self,
position: &Position,
price_ticker: &TradingPairTicker,
) -> Result<f64, BoxError> {
let price = {
if position.is_short() {
price_ticker.ask
} else {
price_ticker.bid
}
};
Ok(price * (1.0 - OrderManager::UNDERCUT_PERC))
}
}
pub struct ExchangeManager {
@ -255,6 +283,7 @@ pub struct ExchangeManager {
price_managers: Vec<PriceManager>,
order_managers: Vec<OrderManager>,
position_managers: Vec<PositionManager>,
dispatcher: Dispatcher,
client: Client,
}
@ -268,7 +297,11 @@ impl ExchangeManager {
for p in pairs {
position_managers.push(PositionManager::new(p.clone(), client.clone()));
order_managers.push(OrderManager::new(p.clone(), client.clone()));
order_managers.push(OrderManager::new(
p.clone(),
client.clone(),
Box::new(FastOrderStrategy {}),
));
price_managers.push(PriceManager::new(p.clone(), client.clone()));
}
@ -278,6 +311,7 @@ impl ExchangeManager {
order_managers,
price_managers,
client,
dispatcher: Dispatcher::new(),
}
}

View File

@ -1,7 +1,10 @@
use std::fmt::Display;
use chrono::{DateTime, TimeZone};
use crate::currency::SymbolPair;
use crate::BoxError;
use chrono::{DateTime, TimeZone};
use std::fmt::Display;
use std::hash::{Hash, Hasher};
/***************
* Prices
@ -26,7 +29,7 @@ pub struct PriceTicker {
***************/
#[derive(Clone, Debug)]
pub struct Order {
pub struct ExecutedOrder {
pub id: i64,
pub group_id: Option<i32>,
pub client_id: i64,
@ -48,7 +51,13 @@ pub struct Order {
pub placed_id: Option<i32>,
}
#[derive(Copy, Clone, Debug)]
impl Hash for ExecutedOrder {
fn hash<H: Hasher>(&self, state: &mut H) {
state.write(&self.id.to_le_bytes())
}
}
#[derive(Copy, Clone, Debug, Hash)]
pub enum OrderKind {
Limit,
ExchangeLimit,
@ -73,11 +82,11 @@ pub struct OrderForm {
/// EXCHANGE FOK, IOC, EXCHANGE IOC
kind: OrderKind,
/// Symbol for desired pair
symbol: SymbolPair,
pair: SymbolPair,
/// Price of order
price: String,
price: f64,
/// Amount of order (positive for buy, negative for sell)
amount: String,
amount: f64,
/// Set the leverage for a derivative order, supported by derivative symbol orders only.
/// The value should be between 1 and 100 inclusive.
/// The field is optional, if omitted the default leverage value of 10 will be used.
@ -91,12 +100,12 @@ pub struct OrderForm {
}
impl OrderForm {
pub fn new(symbol: &SymbolPair, price: f64, amount: f64, kind: OrderKind) -> Self {
pub fn new(pair: &SymbolPair, price: f64, amount: f64, kind: OrderKind) -> Self {
OrderForm {
kind,
symbol: symbol.clone(),
price: price.to_string(),
amount: amount.to_string(),
pair: pair.clone(),
price,
amount,
leverage: None,
price_trailing: None,
price_aux_limit: None,
@ -119,7 +128,7 @@ impl OrderForm {
}
}
pub fn price_aux_limit(mut self, limit: f64) -> Result<Self, BoxError> {
pub fn with_price_aux_limit(mut self, limit: f64) -> Result<Self, BoxError> {
match self.kind {
OrderKind::StopLimit | OrderKind::ExchangeStopLimit => {
self.price_aux_limit = Some(limit.to_string());
@ -136,13 +145,38 @@ impl OrderForm {
self.tif = Some(tif.format("%Y-%m-%d %H:%M:%S").to_string());
self
}
pub fn kind(&self) -> OrderKind {
self.kind
}
pub fn pair(&self) -> &SymbolPair {
&self.pair
}
pub fn price(&self) -> &f64 {
&self.price
}
pub fn amount(&self) -> &f64 {
&self.amount
}
pub fn leverage(&self) -> Option<u32> {
self.leverage
}
pub fn price_trailing(&self) -> &Option<String> {
&self.price_trailing
}
pub fn price_aux_limit(&self) -> &Option<String> {
&self.price_aux_limit
}
pub fn tif(&self) -> &Option<String> {
&self.tif
}
}
/***************
* Positions
***************/
#[derive(Clone, Debug, PartialEq)]
#[derive(Clone, Debug)]
pub struct Position {
pair: SymbolPair,
state: PositionState,
@ -219,7 +253,7 @@ impl Position {
pub fn price_liq(&self) -> f64 {
self.price_liq
}
pub fn position_id(&self) -> u64 {
pub fn id(&self) -> u64 {
self.position_id
}
pub fn profit_state(&self) -> Option<PositionProfitState> {
@ -231,7 +265,26 @@ impl Position {
pub fn creation_update(&self) -> Option<u64> {
self.creation_update
}
pub fn is_short(&self) -> bool {
self.amount.is_sign_negative()
}
pub fn is_long(&self) -> bool {
self.amount.is_sign_positive()
}
}
impl Hash for Position {
fn hash<H: Hasher>(&self, state: &mut H) {
state.write(&self.id().to_le_bytes())
}
}
impl PartialEq for Position {
fn eq(&self, other: &Self) -> bool {
self.id() == other.id()
}
}
impl Eq for Position {}
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
pub enum PositionProfitState {

View File

@ -4,16 +4,20 @@ use std::fmt::{Debug, Formatter};
use dyn_clone::DynClone;
use crate::events::{Event, EventKind, EventMetadata, SignalKind};
use crate::managers::PositionManager;
use crate::models::{Position, PositionProfitState};
use crate::managers::{OrderManager, PositionManager};
use crate::models::{ExecutedOrder, OrderForm, Position, PositionProfitState};
/***************
* DEFINITIONS
***************/
pub trait PositionStrategy: DynClone {
fn name(&self) -> String;
fn on_new_tick(
&self,
position: &Position,
position: Position,
manager: &PositionManager,
) -> (Position, Vec<Event>, Vec<SignalKind>);
) -> (Position, Vec<Event>, Option<OrderForm>);
}
impl Debug for dyn PositionStrategy {
@ -22,6 +26,27 @@ impl Debug for dyn PositionStrategy {
}
}
pub trait OrderStrategy: DynClone {
/// The name of the strategy, used for debugging purposes
fn name(&self) -> String;
/// This method is called when the OrderManager checks the open orders on a new tick.
/// It should manage if some orders have to be closed or keep open.
fn on_update(&self);
/// This method is called when the OrderManager is requested to close
/// a position that has an open order associated to it.
fn on_position_close(&self, order: &ExecutedOrder, manager: &mut OrderManager);
}
impl Debug for dyn OrderStrategy {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
write!(f, "{}", self.name())
}
}
/***************
* IMPLEMENTATIONS
***************/
#[derive(Clone, Debug)]
pub struct TrailingStop {
stop_percentages: HashMap<u64, f64>,
@ -53,12 +78,13 @@ impl PositionStrategy for TrailingStop {
fn on_new_tick(
&self,
position: &Position,
position: Position,
manager: &PositionManager,
) -> (Position, Vec<Event>, Vec<SignalKind>) {
) -> (Position, Vec<Event>, Option<OrderForm>) {
let mut signals = vec![];
let pl_perc = TrailingStop::net_pl_percentage(position.pl_perc(), TrailingStop::TAKER_FEE);
let events = vec![];
let mut order = None;
let state = {
if pl_perc > TrailingStop::GOOD_PROFIT_PERC {
@ -72,24 +98,22 @@ impl PositionStrategy for TrailingStop {
} else if TrailingStop::MAX_LOSS_PERC < pl_perc && pl_perc < 0.0 {
PositionProfitState::Loss
} else {
signals.push(SignalKind::ClosePosition {
position_id: position.position_id(),
});
signals.push(SignalKind::ClosePosition(position.clone()));
PositionProfitState::Critical
}
};
let opt_pre_pw = manager.position_previous_tick(position.position_id(), None);
let event_metadata = EventMetadata::new(Some(position.position_id()), None);
let opt_pre_pw = manager.position_previous_tick(position.id(), None);
let event_metadata = EventMetadata::new(Some(position.id()), None);
let new_position = position.clone().with_profit_state(Some(state));
match opt_pre_pw {
Some(prev) => {
if prev.profit_state() == Some(state) {
return (new_position, events, signals);
return (new_position, events, order);
}
}
None => return (new_position, events, signals),
None => return (new_position, events, order),
};
let events = {
@ -130,6 +154,23 @@ impl PositionStrategy for TrailingStop {
events
};
return (new_position, events, signals);
return (new_position, events, order);
}
}
#[derive(Clone, Debug)]
pub struct FastOrderStrategy {}
impl OrderStrategy for FastOrderStrategy {
fn name(&self) -> String {
"Fast order strategy".into()
}
fn on_update(&self) {
unimplemented!()
}
fn on_position_close(&self, order: &ExecutedOrder, manager: &mut OrderManager) {
unimplemented!()
}
}