rust #10

Merged
peperunas merged 127 commits from rust into master 2021-02-18 09:42:16 +00:00
8 changed files with 495 additions and 317 deletions
Showing only changes of commit 2c2f164e18 - Show all commits

View File

@ -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)
}
}

View File

@ -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,
}
}
}

View File

@ -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 {

View File

@ -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?)
}

View File

@ -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!()
}
}

View File

@ -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,

View File

@ -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))
// }
}

View File

@ -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);
// }
// }