rust #10
@ -4,93 +4,107 @@ use std::collections::HashMap;
|
||||
use bitfinex::api::Bitfinex;
|
||||
use bitfinex::positions::Position;
|
||||
use bitfinex::ticker::TradingPairTicker;
|
||||
use futures_util::stream::FuturesUnordered;
|
||||
use tokio::time::delay_for;
|
||||
|
||||
use crate::connectors::Connector;
|
||||
use crate::connectors::{Client, Connector, ExchangeKind};
|
||||
use crate::currency::{Symbol, SymbolPair};
|
||||
use crate::events::EventKind;
|
||||
use crate::pairs::PairStatus;
|
||||
use crate::events::{Event, EventKind};
|
||||
use crate::managers::{OrderManager, PositionManager};
|
||||
use crate::pairs::PriceManager;
|
||||
use crate::strategy::PositionStrategy;
|
||||
use crate::ticker::Ticker;
|
||||
use crate::BoxError;
|
||||
use tokio::stream::StreamExt;
|
||||
|
||||
pub struct BfxBot<'a> {
|
||||
connector: Box<dyn Connector + 'a>,
|
||||
pub struct BfxBot {
|
||||
ticker: Ticker,
|
||||
pair_statuses: Vec<PairStatus<'a>>,
|
||||
quote: Symbol,
|
||||
trading_symbols: Vec<Symbol>,
|
||||
price_managers: Vec<PriceManager>,
|
||||
order_managers: Vec<OrderManager>,
|
||||
pos_managers: Vec<PositionManager>,
|
||||
}
|
||||
|
||||
impl<'a> BfxBot<'a> {
|
||||
pub fn new<C: Connector + 'a>(
|
||||
connector: C,
|
||||
impl BfxBot {
|
||||
pub fn new(
|
||||
exchanges: Vec<ExchangeKind>,
|
||||
trading_symbols: Vec<Symbol>,
|
||||
quote: Symbol,
|
||||
tick_duration: Duration,
|
||||
) -> Self {
|
||||
let pair_statuses = trading_symbols
|
||||
.iter()
|
||||
.map(|x| SymbolPair::new(quote.clone(), x.clone()))
|
||||
.map(|x| PairStatus::new(x, 1, None))
|
||||
.collect();
|
||||
let clients: Vec<_> = exchanges.iter().map(|x| Client::new(x)).collect();
|
||||
|
||||
let mut pos_managers = Vec::new();
|
||||
let mut order_managers = Vec::new();
|
||||
let mut pair_statuses = Vec::new();
|
||||
|
||||
for c in clients {
|
||||
pos_managers.push(PositionManager::new(c.clone()));
|
||||
order_managers.push(OrderManager::new(c.clone()));
|
||||
pair_statuses.extend(
|
||||
trading_symbols
|
||||
.iter()
|
||||
.map(|x| SymbolPair::new(quote.clone(), x.clone()))
|
||||
.map(|x| PriceManager::new(x, c.clone()))
|
||||
.collect::<Vec<PriceManager>>(),
|
||||
)
|
||||
}
|
||||
|
||||
BfxBot {
|
||||
connector: Box::new(connector),
|
||||
ticker: Ticker::new(tick_duration),
|
||||
pair_statuses,
|
||||
price_managers: pair_statuses,
|
||||
quote,
|
||||
trading_symbols,
|
||||
order_managers,
|
||||
pos_managers,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_strategy(mut self, strategy: Box<dyn PositionStrategy>) -> 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> {
|
||||
let trading_pair = SymbolPair::new(self.quote.clone(), symbol.clone());
|
||||
|
||||
if !self.trading_symbols.contains(&symbol) {
|
||||
return Err("Symbol not supported.".into());
|
||||
}
|
||||
|
||||
self.connector.current_prices(&trading_pair).await
|
||||
}
|
||||
|
||||
pub async fn start_loop(&mut self) -> Result<(), BoxError> {
|
||||
if let Err(e) = self.update_pair_statuses().await {
|
||||
println!("Error while updating pairs at first start: {}", e);
|
||||
}
|
||||
|
||||
loop {
|
||||
self.update().await;
|
||||
}
|
||||
}
|
||||
|
||||
async fn update(&mut self) {
|
||||
async fn update<'a>(&'a mut self) {
|
||||
delay_for(self.ticker.duration()).await;
|
||||
self.ticker.inc();
|
||||
|
||||
if let Err(e) = self.update_pair_statuses().await {
|
||||
println!("Error while updating pairs: {}", e);
|
||||
}
|
||||
self.update_price_managers().await.unwrap();
|
||||
}
|
||||
|
||||
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));
|
||||
async fn update_price_managers(&mut self) -> Result<Option<Vec<Event>>, BoxError> {
|
||||
let futures: Vec<_> = self
|
||||
.price_managers
|
||||
.clone()
|
||||
.into_iter()
|
||||
// the only reason you need the async block is that the future
|
||||
// returned by x.update(tick) borrows from x
|
||||
// so we create a future that first takes ownership of x, then uses it to call x.update
|
||||
.map(|mut x| {
|
||||
let tick = self.ticker.current_tick();
|
||||
async move { x.update(tick).await }
|
||||
})
|
||||
.map(tokio::spawn)
|
||||
.collect();
|
||||
|
||||
let mut price_entries = vec![];
|
||||
|
||||
for f in futures {
|
||||
price_entries.push(f.await??);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
for mut manager in &mut self.price_managers {
|
||||
let prices: Vec<_> = price_entries
|
||||
.drain_filter(|x| x.pair() == manager.pair())
|
||||
.collect();
|
||||
|
||||
for p in prices {
|
||||
manager.add_entry(p);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
use std::sync::Arc;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use bitfinex::api::Bitfinex;
|
||||
@ -6,11 +7,11 @@ use bitfinex::orders::{OrderForm, OrderMeta};
|
||||
use bitfinex::ticker::TradingPairTicker;
|
||||
|
||||
use crate::currency::{Symbol, SymbolPair};
|
||||
use crate::models::{Order, OrderKind, Position, PositionState};
|
||||
use crate::models::{Order, OrderKind, Position, PositionState, PriceTicker};
|
||||
use crate::BoxError;
|
||||
use std::sync::Arc;
|
||||
use std::fmt::{Debug, Formatter};
|
||||
|
||||
#[derive(Eq, PartialEq, Hash, Clone)]
|
||||
#[derive(Eq, PartialEq, Hash, Clone, Debug)]
|
||||
pub enum ExchangeKind {
|
||||
Bitfinex {
|
||||
api_key: String,
|
||||
@ -19,31 +20,56 @@ pub enum ExchangeKind {
|
||||
},
|
||||
}
|
||||
|
||||
/// You do **not** have to wrap the `Client` it in an [`Rc`] or [`Arc`] to **reuse** it,
|
||||
/// You do **not** have to wrap the `Client` in an [`Rc`] or [`Arc`] to **reuse** it,
|
||||
/// because it already uses an [`Arc`] internally.
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Client {
|
||||
exchange: ExchangeKind,
|
||||
inner: Arc<Box<dyn Connector>>,
|
||||
}
|
||||
|
||||
impl Client {
|
||||
pub fn new(exchange: ExchangeKind) -> Self {
|
||||
let inner = match exchange {
|
||||
pub fn new(exchange: &ExchangeKind) -> Self {
|
||||
let inner = match &exchange {
|
||||
ExchangeKind::Bitfinex {
|
||||
api_key,
|
||||
api_secret,
|
||||
affiliate_code,
|
||||
} => BitfinexConnector::new(&api_key, &api_secret).with_affiliate_code(affiliate_code),
|
||||
} => BitfinexConnector::new(&api_key, &api_secret)
|
||||
.with_affiliate_code(affiliate_code.clone()),
|
||||
};
|
||||
|
||||
Client {
|
||||
exchange: exchange.clone(),
|
||||
inner: Arc::new(Box::new(inner)),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn active_positions(&self, pair: &SymbolPair) -> Result<Vec<Position>, BoxError> {
|
||||
self.inner.active_positions(pair).await
|
||||
}
|
||||
|
||||
pub async fn current_prices(&self, pair: &SymbolPair) -> Result<TradingPairTicker, BoxError> {
|
||||
self.inner.current_prices(pair).await
|
||||
}
|
||||
|
||||
pub async fn active_orders(&self, pair: &SymbolPair) -> Result<Vec<Order>, 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
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait Connector {
|
||||
pub trait Connector: Send + Sync {
|
||||
async fn active_positions(&self, pair: &SymbolPair) -> Result<Vec<Position>, BoxError>;
|
||||
async fn current_prices(&self, pair: &SymbolPair) -> Result<TradingPairTicker, BoxError>;
|
||||
async fn active_orders(&self, pair: &SymbolPair) -> Result<Vec<Order>, BoxError>;
|
||||
@ -56,6 +82,16 @@ pub trait Connector {
|
||||
) -> Result<(), BoxError>;
|
||||
}
|
||||
|
||||
impl Debug for dyn Connector {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
|
||||
write!(f, "Connector")
|
||||
}
|
||||
}
|
||||
|
||||
/**************
|
||||
* BITFINEX
|
||||
**************/
|
||||
|
||||
pub struct BitfinexConnector {
|
||||
bfx: Bitfinex,
|
||||
affiliate_code: Option<String>,
|
||||
@ -75,6 +111,57 @@ impl BitfinexConnector {
|
||||
self.affiliate_code = affiliate_code;
|
||||
self
|
||||
}
|
||||
|
||||
fn format_trading_pair(&self, pair: &SymbolPair) -> String {
|
||||
if pair.to_string().to_lowercase().contains("test") {
|
||||
format!("{}:{}", pair.base(), pair.quote())
|
||||
} else {
|
||||
format!("{}{}", pair.base(), pair.quote())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Connector for BitfinexConnector {
|
||||
async fn active_positions(&self, pair: &SymbolPair) -> Result<Vec<Position>, BoxError> {
|
||||
let active_positions = self.bfx.positions.active_positions().await?;
|
||||
|
||||
Ok(active_positions
|
||||
.into_iter()
|
||||
.filter_map(|x| x.try_into().ok())
|
||||
.filter(|x: &Position| x.pair() == pair)
|
||||
.collect())
|
||||
}
|
||||
|
||||
async fn current_prices(&self, pair: &SymbolPair) -> Result<TradingPairTicker, BoxError> {
|
||||
let ticker: TradingPairTicker = self
|
||||
.bfx
|
||||
.ticker
|
||||
.trading_pair(self.format_trading_pair(pair))
|
||||
.await?;
|
||||
|
||||
Ok(ticker)
|
||||
}
|
||||
|
||||
async fn active_orders(&self, pair: &SymbolPair) -> Result<Vec<Order>, BoxError> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
async fn submit_order(
|
||||
&self,
|
||||
pair: &SymbolPair,
|
||||
amount: f64,
|
||||
price: f64,
|
||||
kind: &OrderKind,
|
||||
) -> Result<(), BoxError> {
|
||||
let order_form = match &self.affiliate_code {
|
||||
Some(affiliate_code) => OrderForm::new(pair.trading_repr(), price, amount, kind.into())
|
||||
.with_meta(OrderMeta::new(affiliate_code.clone())),
|
||||
None => OrderForm::new(pair.trading_repr(), price, amount, kind.into()),
|
||||
};
|
||||
|
||||
Ok(self.bfx.orders.submit_order(&order_form).await?)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryInto<Position> for bitfinex::positions::Position {
|
||||
@ -124,41 +211,19 @@ impl From<&OrderKind> for bitfinex::orders::OrderKind {
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Connector for BitfinexConnector {
|
||||
async fn active_positions(&self, pair: &SymbolPair) -> Result<Vec<Position>, BoxError> {
|
||||
let active_positions = self.bfx.positions.active_positions().await?;
|
||||
|
||||
Ok(active_positions
|
||||
.into_iter()
|
||||
.filter_map(|x| x.try_into().ok())
|
||||
.filter(|x: &Position| x.pair() == pair)
|
||||
.collect())
|
||||
}
|
||||
|
||||
async fn current_prices(&self, pair: &SymbolPair) -> Result<TradingPairTicker, BoxError> {
|
||||
let ticker: TradingPairTicker = self.bfx.ticker.trading_pair(pair.clone()).await?;
|
||||
|
||||
Ok(ticker)
|
||||
}
|
||||
|
||||
async fn active_orders(&self, pair: &SymbolPair) -> Result<Vec<Order>, BoxError> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
async fn submit_order(
|
||||
&self,
|
||||
pair: &SymbolPair,
|
||||
amount: f64,
|
||||
price: f64,
|
||||
kind: &OrderKind,
|
||||
) -> Result<(), BoxError> {
|
||||
let order_form = match &self.affiliate_code {
|
||||
Some(affiliate_code) => OrderForm::new(pair.trading_repr(), price, amount, kind.into())
|
||||
.with_meta(OrderMeta::new(affiliate_code.clone())),
|
||||
None => OrderForm::new(pair.trading_repr(), price, amount, kind.into()),
|
||||
};
|
||||
|
||||
Ok(self.bfx.orders.submit_order(&order_form).await?)
|
||||
impl From<TradingPairTicker> for PriceTicker {
|
||||
fn from(t: TradingPairTicker) -> Self {
|
||||
Self {
|
||||
bid: t.bid,
|
||||
bid_size: t.bid_size,
|
||||
ask: t.ask,
|
||||
ask_size: t.ask_size,
|
||||
daily_change: t.daily_change,
|
||||
daily_change_perc: t.daily_change_perc,
|
||||
last_price: t.last_price,
|
||||
volume: t.volume,
|
||||
high: t.high,
|
||||
low: t.low,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ use tokio::task::JoinHandle;
|
||||
|
||||
use crate::bot::BfxBot;
|
||||
use crate::models::{Position, PositionProfitState};
|
||||
use crate::pairs::PairStatus;
|
||||
use crate::pairs::PriceManager;
|
||||
use crate::BoxError;
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
|
||||
@ -78,12 +78,12 @@ impl Event {
|
||||
}
|
||||
|
||||
pub struct EventDispatcher {
|
||||
event_handlers: HashMap<EventKind, Vec<Box<dyn Fn(&Event, &PairStatus) -> JoinHandle<()>>>>,
|
||||
event_handlers: HashMap<EventKind, Vec<Box<dyn Fn(&Event, &PriceManager) -> JoinHandle<()>>>>,
|
||||
profit_state_handlers:
|
||||
HashMap<PositionProfitState, Vec<Box<dyn Fn(&Position, &PairStatus) -> JoinHandle<()>>>>,
|
||||
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, &PairStatus) -> JoinHandle<()>>>,
|
||||
on_any_profit_state_handlers: Vec<Box<dyn Fn(&Position, &PairStatus) -> 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 {
|
||||
@ -105,7 +105,7 @@ impl EventDispatcher {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn call_event_handlers(&self, event: &Event, status: &PairStatus) {
|
||||
pub fn call_event_handlers(&self, event: &Event, status: &PriceManager) {
|
||||
if let Some(functions) = self.event_handlers.get(&event.kind()) {
|
||||
for f in functions {
|
||||
f(event, status);
|
||||
@ -117,7 +117,7 @@ impl EventDispatcher {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn call_position_state_handlers(&self, position: &Position, status: &PairStatus) {
|
||||
pub fn call_position_state_handlers(&self, position: &Position, status: &PriceManager) {
|
||||
if let Some(profit_state) = &position.profit_state() {
|
||||
if let Some(functions) = self.profit_state_handlers.get(profit_state) {
|
||||
for f in functions {
|
||||
@ -133,7 +133,7 @@ impl EventDispatcher {
|
||||
|
||||
pub fn register_event_handler<F: 'static, Fut: 'static>(&mut self, event: EventKind, f: F)
|
||||
where
|
||||
F: Fn(&Event, &PairStatus) -> Fut,
|
||||
F: Fn(&Event, &PriceManager) -> Fut,
|
||||
Fut: Future<Output = ()> + Send,
|
||||
{
|
||||
match event {
|
||||
@ -153,7 +153,7 @@ impl EventDispatcher {
|
||||
state: PositionProfitState,
|
||||
f: F,
|
||||
) where
|
||||
F: Fn(&Position, &PairStatus) -> Fut,
|
||||
F: Fn(&Position, &PriceManager) -> Fut,
|
||||
Fut: Future<Output = ()> + Send,
|
||||
{
|
||||
match state {
|
||||
|
@ -1,11 +1,13 @@
|
||||
#![feature(drain_filter)]
|
||||
|
||||
use std::thread::JoinHandle;
|
||||
|
||||
use tokio::time::{delay_for, Duration};
|
||||
|
||||
use crate::bot::BfxBot;
|
||||
use crate::connectors::BitfinexConnector;
|
||||
use crate::connectors::{BitfinexConnector, ExchangeKind};
|
||||
use crate::currency::{Symbol, SymbolPair};
|
||||
use crate::events::SignalKind;
|
||||
use crate::strategy::TrailingStop;
|
||||
use std::thread::JoinHandle;
|
||||
|
||||
mod bot;
|
||||
mod connectors;
|
||||
@ -23,16 +25,20 @@ pub type BoxError = Box<dyn std::error::Error + Send + Sync>;
|
||||
async fn main() -> Result<(), BoxError> {
|
||||
let test_api_key = "P1EVE68DJByDAkGQvpIkTwfrbYXd2Vo2ZaIhTYb9vx2";
|
||||
let test_api_secret = "1nicg8z0zKVEt5Rb7ZDpIYjVYVTgvCaCPMZqB0niFli";
|
||||
let bfx = BitfinexConnector::new(test_api_key, test_api_secret)
|
||||
.with_affiliate_code(Some("XPebOgHxA".into()));
|
||||
let affiliate_code = "XPebOgHxA";
|
||||
|
||||
let bitfinex = ExchangeKind::Bitfinex {
|
||||
api_key: test_api_key.into(),
|
||||
api_secret: test_api_secret.into(),
|
||||
affiliate_code: Some(affiliate_code.into()),
|
||||
};
|
||||
|
||||
let mut bot = BfxBot::new(
|
||||
bfx,
|
||||
vec![bitfinex],
|
||||
vec![Symbol::TESTBTC],
|
||||
Symbol::TESTUSD,
|
||||
Duration::new(20, 0),
|
||||
)
|
||||
.with_strategy(Box::new(TrailingStop::new()));
|
||||
Duration::new(1, 0),
|
||||
);
|
||||
|
||||
Ok(bot.start_loop().await?)
|
||||
}
|
||||
|
@ -1,21 +1,58 @@
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
|
||||
use crate::connectors::{Client, Connector};
|
||||
use crate::events::Event;
|
||||
use crate::models::{Order, Position};
|
||||
use crate::strategy::PositionStrategy;
|
||||
use crate::ticker::Ticker;
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
|
||||
struct EventManager {
|
||||
pub struct EventManager {
|
||||
events: Vec<Event>,
|
||||
}
|
||||
|
||||
struct PositionManager {
|
||||
pub struct PositionManager {
|
||||
queue: VecDeque<Position>,
|
||||
open_positions: Vec<Position>,
|
||||
client: Client,
|
||||
strategy: Option<Box<dyn PositionStrategy>>,
|
||||
}
|
||||
|
||||
struct OrderManager {
|
||||
impl PositionManager {
|
||||
pub fn new(client: Client) -> Self {
|
||||
PositionManager {
|
||||
queue: VecDeque::new(),
|
||||
open_positions: Vec::new(),
|
||||
client,
|
||||
strategy: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_strategy(mut self, strategy: Box<dyn PositionStrategy>) -> Self {
|
||||
self.strategy = Some(strategy);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn update(&self) -> Option<Vec<Event>> {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct OrderManager {
|
||||
queue: VecDeque<Order>,
|
||||
open_orders: Vec<Order>,
|
||||
client: Client,
|
||||
}
|
||||
|
||||
impl OrderManager {
|
||||
pub fn new(client: Client) -> Self {
|
||||
OrderManager {
|
||||
queue: VecDeque::new(),
|
||||
open_orders: Vec::new(),
|
||||
client,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(&self) -> Option<Vec<Event>> {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,28 @@
|
||||
use crate::currency::SymbolPair;
|
||||
|
||||
/***************
|
||||
* Prices
|
||||
***************/
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct PriceTicker {
|
||||
pub bid: f64,
|
||||
pub bid_size: f64,
|
||||
pub ask: f64,
|
||||
pub ask_size: f64,
|
||||
pub daily_change: f64,
|
||||
pub daily_change_perc: f64,
|
||||
pub last_price: f64,
|
||||
pub volume: f64,
|
||||
pub high: f64,
|
||||
pub low: f64,
|
||||
}
|
||||
|
||||
/***************
|
||||
* Orders
|
||||
***************/
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Order {
|
||||
pub id: i64,
|
||||
pub group_id: Option<i32>,
|
||||
@ -22,6 +45,7 @@ pub struct Order {
|
||||
pub placed_id: Option<i32>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum OrderKind {
|
||||
Limit,
|
||||
ExchangeLimit,
|
||||
@ -38,6 +62,10 @@ pub enum OrderKind {
|
||||
ExchangeIoc,
|
||||
}
|
||||
|
||||
/***************
|
||||
* Positions
|
||||
***************/
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Position {
|
||||
pair: SymbolPair,
|
||||
|
@ -1,117 +1,145 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
// use crate::strategy::PositionStrategy;
|
||||
use crate::connectors::Client;
|
||||
use crate::currency::SymbolPair;
|
||||
use crate::events::{Event, EventDispatcher, SignalKind};
|
||||
use crate::models::{Order, Position};
|
||||
use crate::strategy::PositionStrategy;
|
||||
use crate::models::{Order, Position, PriceTicker};
|
||||
use crate::BoxError;
|
||||
|
||||
pub struct PairStatus<'a> {
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct PriceManager {
|
||||
pair: SymbolPair,
|
||||
dispatcher: EventDispatcher,
|
||||
prices: HashMap<u64, f64>,
|
||||
events: Vec<Event>,
|
||||
orders: HashMap<u64, Vec<Order>>,
|
||||
positions: HashMap<u64, Vec<Position>>,
|
||||
current_tick: u64,
|
||||
strategy: Option<Box<dyn PositionStrategy + 'a>>,
|
||||
signals: HashMap<u64, SignalKind>,
|
||||
prices: Vec<PriceEntry>,
|
||||
client: Client,
|
||||
}
|
||||
|
||||
impl<'a> PairStatus<'a> {
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct PriceEntry {
|
||||
tick: u64,
|
||||
pair: SymbolPair,
|
||||
price: PriceTicker,
|
||||
events: Option<Vec<Event>>,
|
||||
signals: Option<Vec<SignalKind>>,
|
||||
}
|
||||
|
||||
impl PriceEntry {
|
||||
pub fn tick(&self) -> u64 {
|
||||
self.tick
|
||||
}
|
||||
pub fn pair(&self) -> &SymbolPair {
|
||||
&self.pair
|
||||
}
|
||||
pub fn price(&self) -> PriceTicker {
|
||||
self.price
|
||||
}
|
||||
pub fn events(&self) -> &Option<Vec<Event>> {
|
||||
&self.events
|
||||
}
|
||||
pub fn signals(&self) -> &Option<Vec<SignalKind>> {
|
||||
&self.signals
|
||||
}
|
||||
}
|
||||
|
||||
impl PriceEntry {
|
||||
pub fn new(
|
||||
tick: u64,
|
||||
price: PriceTicker,
|
||||
pair: SymbolPair,
|
||||
current_tick: u64,
|
||||
strategy: Option<Box<dyn PositionStrategy>>,
|
||||
events: Option<Vec<Event>>,
|
||||
signals: Option<Vec<SignalKind>>,
|
||||
) -> Self {
|
||||
PairStatus {
|
||||
PriceEntry {
|
||||
tick,
|
||||
pair,
|
||||
dispatcher: EventDispatcher::new(),
|
||||
prices: HashMap::new(),
|
||||
events: Vec::new(),
|
||||
positions: HashMap::new(),
|
||||
orders: HashMap::new(),
|
||||
signals: HashMap::new(),
|
||||
current_tick,
|
||||
strategy,
|
||||
price,
|
||||
events,
|
||||
signals,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PriceManager {
|
||||
pub fn new(pair: SymbolPair, client: Client) -> Self {
|
||||
PriceManager {
|
||||
pair,
|
||||
prices: Vec::new(),
|
||||
client,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dispatcher(&self) -> &EventDispatcher {
|
||||
&self.dispatcher
|
||||
}
|
||||
pub fn dispatcher_mut(&mut self) -> &mut EventDispatcher {
|
||||
&mut self.dispatcher
|
||||
pub fn add_entry(&mut self, entry: PriceEntry) {
|
||||
self.prices.push(entry);
|
||||
}
|
||||
|
||||
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![]),
|
||||
}
|
||||
};
|
||||
pub async fn update(&mut self, tick: u64) -> Result<PriceEntry, BoxError> {
|
||||
let current_prices = self.client.current_prices(&self.pair).await?.into();
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
println!(
|
||||
"EVENTS: {:?} | SIGNALS: {:?} | POSITION: {:?}",
|
||||
self.events, self.signals, new_position
|
||||
);
|
||||
Ok(PriceEntry::new(
|
||||
tick,
|
||||
current_prices,
|
||||
self.pair.clone(),
|
||||
None,
|
||||
None,
|
||||
))
|
||||
}
|
||||
|
||||
fn add_event(&mut self, event: Event) {
|
||||
self.events.push(event);
|
||||
// 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);
|
||||
// }
|
||||
// }
|
||||
|
||||
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 {
|
||||
self.current_tick
|
||||
}
|
||||
|
||||
pub fn set_strategy(&mut self, strategy: Box<dyn PositionStrategy + 'a>) {
|
||||
self.strategy = Some(strategy);
|
||||
}
|
||||
// fn add_event(&mut self, event: Event) {
|
||||
// self.events.push(event);
|
||||
//
|
||||
// self.dispatcher.call_event_handlers(&event, &self);
|
||||
// }
|
||||
//
|
||||
// fn add_signal(&mut self, signal: SignalKind) {
|
||||
// self.signals.insert(self.current_tick(), signal);
|
||||
// }
|
||||
|
||||
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
|
||||
.get(&tick)
|
||||
.and_then(|x| x.iter().find(|x| x.position_id() == id))
|
||||
}
|
||||
// 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
|
||||
// .get(&tick)
|
||||
// .and_then(|x| x.iter().find(|x| x.position_id() == id))
|
||||
// }
|
||||
}
|
||||
|
@ -2,121 +2,121 @@ use std::collections::HashMap;
|
||||
|
||||
use crate::events::{Event, EventKind, EventMetadata, SignalKind};
|
||||
use crate::models::{Position, PositionProfitState};
|
||||
use crate::pairs::PairStatus;
|
||||
use crate::pairs::PriceManager;
|
||||
use dyn_clone::DynClone;
|
||||
|
||||
pub trait PositionStrategy: DynClone {
|
||||
fn on_new_tick(
|
||||
&self,
|
||||
position: &Position,
|
||||
status: &PairStatus,
|
||||
status: &PriceManager,
|
||||
) -> (Position, Vec<Event>, Vec<SignalKind>);
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct TrailingStop {
|
||||
stop_percentages: HashMap<u64, f64>,
|
||||
}
|
||||
|
||||
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 PositionStrategy for TrailingStop {
|
||||
fn on_new_tick(
|
||||
&self,
|
||||
position: &Position,
|
||||
status: &PairStatus,
|
||||
) -> (Position, Vec<Event>, Vec<SignalKind>) {
|
||||
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 {
|
||||
PositionProfitState::Profit
|
||||
} else if TrailingStop::MIN_PROFIT_PERC <= pl_perc
|
||||
&& pl_perc < TrailingStop::GOOD_PROFIT_PERC
|
||||
{
|
||||
PositionProfitState::MinimumProfit
|
||||
} else if 0.0 <= pl_perc && pl_perc < TrailingStop::MIN_PROFIT_PERC {
|
||||
PositionProfitState::BreakEven
|
||||
} else if TrailingStop::MAX_LOSS_PERC < pl_perc && pl_perc < 0.0 {
|
||||
PositionProfitState::Loss
|
||||
} else {
|
||||
signals.push(SignalKind::ClosePosition {
|
||||
position_id: position.position_id(),
|
||||
});
|
||||
PositionProfitState::Critical
|
||||
}
|
||||
};
|
||||
|
||||
let opt_pre_pw = status.position_previous_tick(position.position_id(), None);
|
||||
let event_metadata = EventMetadata::new(Some(position.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);
|
||||
}
|
||||
}
|
||||
None => return (new_position, events, signals),
|
||||
};
|
||||
|
||||
let events = {
|
||||
let mut events = vec![];
|
||||
|
||||
if state == PositionProfitState::Profit {
|
||||
events.push(Event::new(
|
||||
EventKind::ReachedGoodProfit,
|
||||
status.current_tick(),
|
||||
Some(event_metadata),
|
||||
));
|
||||
} else if state == PositionProfitState::MinimumProfit {
|
||||
events.push(Event::new(
|
||||
EventKind::ReachedMinProfit,
|
||||
status.current_tick(),
|
||||
Some(event_metadata),
|
||||
));
|
||||
} else if state == PositionProfitState::BreakEven {
|
||||
events.push(Event::new(
|
||||
EventKind::ReachedBreakEven,
|
||||
status.current_tick(),
|
||||
Some(event_metadata),
|
||||
));
|
||||
} else if state == PositionProfitState::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 (new_position, events, signals);
|
||||
}
|
||||
}
|
||||
// #[derive(Clone, Debug)]
|
||||
// pub struct TrailingStop {
|
||||
// stop_percentages: HashMap<u64, f64>,
|
||||
// }
|
||||
//
|
||||
// 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 PositionStrategy for TrailingStop {
|
||||
// fn on_new_tick(
|
||||
// &self,
|
||||
// position: &Position,
|
||||
// status: &PairStatus,
|
||||
// ) -> (Position, Vec<Event>, Vec<SignalKind>) {
|
||||
// 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 {
|
||||
// PositionProfitState::Profit
|
||||
// } else if TrailingStop::MIN_PROFIT_PERC <= pl_perc
|
||||
// && pl_perc < TrailingStop::GOOD_PROFIT_PERC
|
||||
// {
|
||||
// PositionProfitState::MinimumProfit
|
||||
// } else if 0.0 <= pl_perc && pl_perc < TrailingStop::MIN_PROFIT_PERC {
|
||||
// PositionProfitState::BreakEven
|
||||
// } else if TrailingStop::MAX_LOSS_PERC < pl_perc && pl_perc < 0.0 {
|
||||
// PositionProfitState::Loss
|
||||
// } else {
|
||||
// signals.push(SignalKind::ClosePosition {
|
||||
// position_id: position.position_id(),
|
||||
// });
|
||||
// PositionProfitState::Critical
|
||||
// }
|
||||
// };
|
||||
//
|
||||
// let opt_pre_pw = status.position_previous_tick(position.position_id(), None);
|
||||
// let event_metadata = EventMetadata::new(Some(position.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);
|
||||
// }
|
||||
// }
|
||||
// None => return (new_position, events, signals),
|
||||
// };
|
||||
//
|
||||
// let events = {
|
||||
// let mut events = vec![];
|
||||
//
|
||||
// if state == PositionProfitState::Profit {
|
||||
// events.push(Event::new(
|
||||
// EventKind::ReachedGoodProfit,
|
||||
// status.current_tick(),
|
||||
// Some(event_metadata),
|
||||
// ));
|
||||
// } else if state == PositionProfitState::MinimumProfit {
|
||||
// events.push(Event::new(
|
||||
// EventKind::ReachedMinProfit,
|
||||
// status.current_tick(),
|
||||
// Some(event_metadata),
|
||||
// ));
|
||||
// } else if state == PositionProfitState::BreakEven {
|
||||
// events.push(Event::new(
|
||||
// EventKind::ReachedBreakEven,
|
||||
// status.current_tick(),
|
||||
// Some(event_metadata),
|
||||
// ));
|
||||
// } else if state == PositionProfitState::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 (new_position, events, signals);
|
||||
// }
|
||||
// }
|
||||
|
Loading…
Reference in New Issue
Block a user