merge rust

This commit is contained in:
Giulio De Pasquale 2021-02-17 17:19:49 +00:00
commit b8f239ec31
7 changed files with 941 additions and 455 deletions

View File

@ -17,7 +17,7 @@ use tokio::time::Duration;
use crate::currency::{Symbol, SymbolPair};
use crate::models::{
ActiveOrder, OrderBook, OrderBookEntry, OrderDetails, OrderFee, OrderForm, OrderKind, Position,
PositionState, PriceTicker, Trade, TradingPlatform, WalletKind,
PositionState, PriceTicker, Trade, TradingFees, TradingPlatform, WalletKind,
};
use crate::BoxError;
@ -57,21 +57,56 @@ impl Client {
pair: &SymbolPair,
) -> Result<Option<Vec<Position>>, BoxError> {
// retrieving open positions and order book to calculate effective profit/loss
let (positions, order_book) = tokio::join!(
let (positions, order_book, fees) = tokio::join!(
self.inner.active_positions(pair),
self.inner.order_book(pair)
self.inner.order_book(pair),
self.inner.trading_fees()
);
let (mut positions, order_book) = (positions?, order_book?);
let (mut positions, order_book, fees) = (positions?, order_book?, fees?);
let (best_ask, best_bid) = (order_book.lowest_ask(), order_book.highest_bid());
if positions.is_none() {
return Ok(None);
}
let derivative_taker = fees
.iter()
.filter_map(|x| match x {
TradingFees::Taker {
platform,
percentage,
} if platform == &TradingPlatform::Derivative => Some(percentage),
_ => None,
})
.next()
.ok_or("Could not retrieve derivative taker fee!")?;
let margin_taker = fees
.iter()
.filter_map(|x| match x {
TradingFees::Taker {
platform,
percentage,
} if platform == &TradingPlatform::Margin => Some(percentage),
_ => None,
})
.next()
.ok_or("Could not retrieve margin taker fee!")?;
// updating positions with effective profit/loss
// TODO: change fee with account's taker fee
positions.iter_mut().flatten().for_each(|x| {
let fee = match x.platform() {
TradingPlatform::Funding | TradingPlatform::Exchange => {
unimplemented!()
}
TradingPlatform::Margin => margin_taker,
TradingPlatform::Derivative => derivative_taker,
};
if x.is_short() {
x.update_profit_loss(best_ask, 0.2);
x.update_profit_loss(best_ask, *fee);
} else {
x.update_profit_loss(best_bid, 0.2);
x.update_profit_loss(best_bid, *fee);
}
});
@ -88,7 +123,7 @@ impl Client {
.active_orders(pair)
.await?
.into_iter()
.filter(|x| &x.symbol == pair)
.filter(|x| &x.pair() == &pair)
.collect())
}
@ -153,6 +188,7 @@ pub trait Connector: Send + Sync {
&self,
pair: &SymbolPair,
) -> Result<Option<Vec<OrderDetails>>, BoxError>;
async fn trading_fees(&self) -> Result<Vec<TradingFees>, BoxError>;
}
impl Debug for dyn Connector {
@ -176,7 +212,7 @@ impl BitfinexConnector {
if e.to_string().contains("nonce: small") {
return RetryPolicy::WaitRetry(Duration::from_millis(1));
}
return RetryPolicy::ForwardError(e);
RetryPolicy::ForwardError(e)
}
pub fn new(api_key: &str, api_secret: &str) -> Self {
@ -186,7 +222,9 @@ impl BitfinexConnector {
}
fn format_trading_pair(pair: &SymbolPair) -> String {
if pair.to_string().to_lowercase().contains("test") {
if pair.to_string().to_lowercase().contains("test")
|| pair.to_string().to_lowercase().contains("f0")
{
format!("{}:{}", pair.base(), pair.quote())
} else {
format!("{}{}", pair.base(), pair.quote())
@ -275,37 +313,48 @@ impl Connector for BitfinexConnector {
async fn submit_order(&self, order: &OrderForm) -> Result<ActiveOrder, BoxError> {
let symbol_name = format!("t{}", BitfinexConnector::format_trading_pair(order.pair()));
let amount = order.amount();
let order_form = match order.kind() {
OrderKind::Limit { price, amount } => {
bitfinex::orders::OrderForm::new(symbol_name, price, amount, order.into())
let order_form = {
let pre_leverage = {
match order.kind() {
OrderKind::Limit { price } => {
bitfinex::orders::OrderForm::new(symbol_name, price, amount, order.into())
}
OrderKind::Market => {
bitfinex::orders::OrderForm::new(symbol_name, 0.0, amount, order.into())
}
OrderKind::Stop { price } => {
bitfinex::orders::OrderForm::new(symbol_name, price, amount, order.into())
}
OrderKind::StopLimit { price, limit_price } => {
bitfinex::orders::OrderForm::new(symbol_name, price, amount, order.into())
.with_price_aux_limit(limit_price)?
}
OrderKind::TrailingStop { distance } => {
bitfinex::orders::OrderForm::new(symbol_name, 0.0, amount, order.into())
.with_price_trailing(distance)?
}
OrderKind::FillOrKill { price } => {
bitfinex::orders::OrderForm::new(symbol_name, price, amount, order.into())
}
OrderKind::ImmediateOrCancel { price } => {
bitfinex::orders::OrderForm::new(symbol_name, price, amount, order.into())
}
}
.with_meta(OrderMeta::new(
BitfinexConnector::AFFILIATE_CODE.to_string(),
))
};
// adding leverage, if any
match order.leverage() {
// TODO: CHANGEME!!!!
Some(_leverage) => pre_leverage.with_leverage(15),
// Some(leverage) => pre_leverage.with_leverage(leverage.round() as u32),
None => pre_leverage,
}
OrderKind::Market { amount } => {
bitfinex::orders::OrderForm::new(symbol_name, 0.0, amount, order.into())
}
OrderKind::Stop { price, amount } => {
bitfinex::orders::OrderForm::new(symbol_name, price, amount, order.into())
}
OrderKind::StopLimit {
price,
amount,
limit_price,
} => bitfinex::orders::OrderForm::new(symbol_name, price, amount, order.into())
.with_price_aux_limit(limit_price)?,
OrderKind::TrailingStop { distance, amount } => {
bitfinex::orders::OrderForm::new(symbol_name, 0.0, amount, order.into())
.with_price_trailing(distance)?
}
OrderKind::FillOrKill { price, amount } => {
bitfinex::orders::OrderForm::new(symbol_name, price, amount, order.into())
}
OrderKind::ImmediateOrCancel { price, amount } => {
bitfinex::orders::OrderForm::new(symbol_name, price, amount, order.into())
}
}
.with_meta(OrderMeta::new(
BitfinexConnector::AFFILIATE_CODE.to_string(),
));
};
let response =
BitfinexConnector::retry_nonce(|| self.bfx.orders.submit_order(&order_form)).await?;
@ -371,11 +420,55 @@ impl Connector for BitfinexConnector {
Ok((!mapped_vec.is_empty()).then_some(mapped_vec))
}
async fn trading_fees(&self) -> Result<Vec<TradingFees>, BoxError> {
let mut fees = vec![];
let accountfees =
BitfinexConnector::retry_nonce(|| self.bfx.account.account_summary()).await?;
// Derivatives
let derivative_taker = TradingFees::Taker {
platform: TradingPlatform::Derivative,
percentage: accountfees.derivative_taker() * 100.0,
};
let derivative_maker = TradingFees::Maker {
platform: TradingPlatform::Derivative,
percentage: accountfees.derivative_rebate() * 100.0,
};
fees.push(derivative_taker);
fees.push(derivative_maker);
// Exchange
let exchange_taker = TradingFees::Taker {
platform: TradingPlatform::Exchange,
percentage: accountfees.taker_to_fiat() * 100.0,
};
let exchange_maker = TradingFees::Maker {
platform: TradingPlatform::Exchange,
percentage: accountfees.maker_fee() * 100.0,
};
fees.push(exchange_taker);
fees.push(exchange_maker);
// Margin
let margin_taker = TradingFees::Taker {
platform: TradingPlatform::Margin,
percentage: accountfees.taker_to_fiat() * 100.0,
};
let margin_maker = TradingFees::Maker {
platform: TradingPlatform::Margin,
percentage: accountfees.maker_fee() * 100.0,
};
fees.push(margin_taker);
fees.push(margin_maker);
Ok(fees)
}
}
impl From<&ActiveOrder> for CancelOrderForm {
fn from(o: &ActiveOrder) -> Self {
Self::from_id(o.id)
Self::from_id(o.id())
}
}
@ -383,20 +476,18 @@ impl TryFrom<&bitfinex::responses::OrderResponse> for ActiveOrder {
type Error = BoxError;
fn try_from(response: &OrderResponse) -> Result<Self, Self::Error> {
Ok(Self {
exchange: Exchange::Bitfinex,
id: response.id(),
group_id: response.gid(),
client_id: Some(response.cid()),
symbol: SymbolPair::from_str(response.symbol())?,
details: OrderForm::new(
SymbolPair::from_str(response.symbol())?,
response.into(),
response.into(),
),
creation_timestamp: 0,
update_timestamp: 0,
})
let pair = SymbolPair::from_str(response.symbol())?;
Ok(ActiveOrder::new(
Exchange::Bitfinex,
response.id(),
pair.clone(),
OrderForm::new(pair, response.into(), response.into(), response.amount()),
response.mts_create(),
response.mts_update(),
)
.with_group_id(response.gid())
.with_client_id(Some(response.cid())))
}
}
@ -412,6 +503,15 @@ impl TryInto<Position> for bitfinex::positions::Position {
}
};
let platform = {
if self.symbol().to_ascii_lowercase().contains("f0") {
TradingPlatform::Derivative
} else {
TradingPlatform::Margin
}
};
println!("leverage: {}", self.leverage());
Ok(Position::new(
SymbolPair::from_str(self.symbol())?,
state,
@ -421,6 +521,8 @@ impl TryInto<Position> for bitfinex::positions::Position {
self.pl_perc(),
self.price_liq(),
self.position_id(),
platform,
self.leverage(),
)
.with_creation_date(self.mts_create())
.with_creation_update(self.mts_update()))
@ -439,7 +541,7 @@ impl From<&OrderForm> for bitfinex::orders::OrderKind {
OrderKind::FillOrKill { .. } => bitfinex::orders::OrderKind::ExchangeFok,
OrderKind::ImmediateOrCancel { .. } => bitfinex::orders::OrderKind::ExchangeIoc,
},
TradingPlatform::Margin => match o.kind() {
TradingPlatform::Margin | TradingPlatform::Derivative => match o.kind() {
OrderKind::Limit { .. } => bitfinex::orders::OrderKind::Limit,
OrderKind::Market { .. } => bitfinex::orders::OrderKind::Market,
OrderKind::Stop { .. } => bitfinex::orders::OrderKind::Stop,
@ -489,41 +591,33 @@ impl From<&bitfinex::responses::OrderResponse> for OrderKind {
bitfinex::orders::OrderKind::Limit | bitfinex::orders::OrderKind::ExchangeLimit => {
Self::Limit {
price: response.price(),
amount: response.amount(),
}
}
bitfinex::orders::OrderKind::Market | bitfinex::orders::OrderKind::ExchangeMarket => {
Self::Market {
amount: response.amount(),
}
Self::Market
}
bitfinex::orders::OrderKind::Stop | bitfinex::orders::OrderKind::ExchangeStop => {
Self::Stop {
price: response.price(),
amount: response.amount(),
}
}
bitfinex::orders::OrderKind::StopLimit
| bitfinex::orders::OrderKind::ExchangeStopLimit => Self::StopLimit {
price: response.price(),
amount: response.amount(),
limit_price: response.price_aux_limit().expect("Limit price not found!"),
},
bitfinex::orders::OrderKind::TrailingStop
| bitfinex::orders::OrderKind::ExchangeTrailingStop => Self::TrailingStop {
distance: response.price_trailing().expect("Distance not found!"),
amount: response.amount(),
},
bitfinex::orders::OrderKind::Fok | bitfinex::orders::OrderKind::ExchangeFok => {
Self::FillOrKill {
price: response.price(),
amount: response.amount(),
}
}
bitfinex::orders::OrderKind::Ioc | bitfinex::orders::OrderKind::ExchangeIoc => {
Self::ImmediateOrCancel {
price: response.price(),
amount: response.amount(),
}
}
}
@ -536,41 +630,33 @@ impl From<&bitfinex::orders::ActiveOrder> for OrderKind {
bitfinex::orders::OrderKind::Limit | bitfinex::orders::OrderKind::ExchangeLimit => {
Self::Limit {
price: response.price(),
amount: response.amount(),
}
}
bitfinex::orders::OrderKind::Market | bitfinex::orders::OrderKind::ExchangeMarket => {
Self::Market {
amount: response.amount(),
}
Self::Market {}
}
bitfinex::orders::OrderKind::Stop | bitfinex::orders::OrderKind::ExchangeStop => {
Self::Stop {
price: response.price(),
amount: response.amount(),
}
}
bitfinex::orders::OrderKind::StopLimit
| bitfinex::orders::OrderKind::ExchangeStopLimit => Self::StopLimit {
price: response.price(),
amount: response.amount(),
limit_price: response.price_aux_limit().expect("Limit price not found!"),
},
bitfinex::orders::OrderKind::TrailingStop
| bitfinex::orders::OrderKind::ExchangeTrailingStop => Self::TrailingStop {
distance: response.price_trailing().expect("Distance not found!"),
amount: response.amount(),
},
bitfinex::orders::OrderKind::Fok | bitfinex::orders::OrderKind::ExchangeFok => {
Self::FillOrKill {
price: response.price(),
amount: response.amount(),
}
}
bitfinex::orders::OrderKind::Ioc | bitfinex::orders::OrderKind::ExchangeIoc => {
Self::ImmediateOrCancel {
price: response.price(),
amount: response.amount(),
}
}
}
@ -581,16 +667,16 @@ impl From<&bitfinex::orders::ActiveOrder> for ActiveOrder {
fn from(order: &bitfinex::orders::ActiveOrder) -> Self {
let pair = SymbolPair::from_str(&order.symbol()).expect("Invalid symbol!");
Self {
exchange: Exchange::Bitfinex,
id: order.id(),
group_id: order.group_id().map(|x| x as u64),
client_id: Some(order.client_id()),
symbol: pair.clone(),
details: OrderForm::new(pair, order.into(), order.into()),
creation_timestamp: order.creation_timestamp(),
update_timestamp: order.update_timestamp(),
}
ActiveOrder::new(
Exchange::Bitfinex,
order.id(),
pair.clone(),
OrderForm::new(pair, order.into(), order.into(), order.amount()),
order.creation_timestamp(),
order.update_timestamp(),
)
.with_client_id(Some(order.client_id()))
.with_group_id(order.group_id())
}
}
@ -637,23 +723,23 @@ impl From<&bitfinex::orders::ActiveOrder> for OrderDetails {
// TODO: fields are hardcoded, to fix
impl From<&bitfinex::responses::TradeResponse> for Trade {
fn from(response: &TradeResponse) -> Self {
let pair = SymbolPair::from_str(&response.symbol).unwrap();
let pair = SymbolPair::from_str(&response.symbol()).unwrap();
let fee = {
if response.is_maker {
OrderFee::Maker(response.fee)
if response.is_maker() {
OrderFee::Maker(response.fee())
} else {
OrderFee::Taker(response.fee)
OrderFee::Taker(response.fee())
}
};
Self {
trade_id: response.trade_id,
trade_id: response.trade_id(),
pair,
execution_timestamp: response.execution_timestamp,
price: response.execution_price,
amount: response.execution_amount,
execution_timestamp: response.execution_timestamp(),
price: response.execution_price(),
amount: response.execution_amount(),
fee,
fee_currency: Symbol::new(response.symbol.clone()),
fee_currency: Symbol::new(response.symbol().to_owned()),
}
}
}

View File

@ -28,10 +28,17 @@ impl Symbol {
pub const LTC: Symbol = Symbol::new_static("LTC");
pub const DOT: Symbol = Symbol::new_static("DOT");
pub const DERIV_BTC: Symbol = Symbol::new_static("BTCF0");
pub const DERIV_ETH: Symbol = Symbol::new_static("ETHF0");
pub const DERIV_USDT: Symbol = Symbol::new_static("USTF0");
// Paper trading
pub const TESTBTC: Symbol = Symbol::new_static("TESTBTC");
pub const TESTUSD: Symbol = Symbol::new_static("TESTUSD");
pub const DERIV_TESTBTC: Symbol = Symbol::new_static("TESTBTCF0");
pub const DERIV_TESTUSDT: Symbol = Symbol::new_static("TESTUSDTF0");
// Fiat coins
pub const USD: Symbol = Symbol::new_static("USD");
pub const GBP: Symbol = Symbol::new_static("GBP");

View File

@ -5,15 +5,16 @@ use crate::models::OrderForm;
#[derive(Debug)]
pub struct ActorMessage {
pub(crate) message: Message,
pub(crate) message: ActionMessage,
pub(crate) respond_to: oneshot::Sender<OptionUpdate>,
}
#[derive(Debug)]
pub enum Message {
pub enum ActionMessage {
Update { tick: u64 },
ClosePosition { position_id: u64 },
SubmitOrder { order: OrderForm },
ClosePositionOrders { position_id: u64 },
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]

View File

@ -1,6 +1,8 @@
#![feature(drain_filter)]
#![feature(bool_to_option)]
use std::env;
use fern::colors::{Color, ColoredLevelConfig};
use log::LevelFilter::{Debug, Trace};
use tokio::time::Duration;
@ -23,33 +25,27 @@ pub type BoxError = Box<dyn std::error::Error + Send + Sync>;
#[tokio::main]
async fn main() -> Result<(), BoxError> {
setup_logger()?;
dotenv::dotenv()?;
// TEST
let test_api_key = "P1EVE68DJByDAkGQvpIkTwfrbYXd2Vo2ZaIhTYb9vx2";
let test_api_secret = "1nicg8z0zKVEt5Rb7ZDpIYjVYVTgvCaCPMZqB0niFli";
// REAL
// let orders_api_key = "hc5nDvYbFYJZMKdnzYq8P4AzCSwjxfQHnMyrg69Sf4c";
// let orders_api_secret = "53x9goIOpbOtBoPi7dmigK5Cq5e0282EUO2qRIMEXlh";
// let prices_api_key = "gTfFZUCwRBE0Z9FZjyk9HNe4lZ7XuiZY9rrW71SyUr9";
// let prices_api_secret = "zWbxvoFZad3BPIiXK4DKfEvC0YsAuaApbeAyI8OBXgN";
// let positions_api_key = "PfR7BadPZPNdVZnkHFBfAjsg7gjt8pAecMj5B8eRPFi";
// let positions_api_secret = "izzvxtE3XsBBRpVCHGJ8f60UA56SmPNbBvJGVd67aqD";
let api_key = env::vars()
.find(|(k, v)| k == "API_KEY")
.map(|(k, v)| v)
.ok_or("API_KEY not set!")?;
let api_secret = env::vars()
.find(|(k, v)| k == "API_SECRET")
.map(|(k, v)| v)
.ok_or("API_SECRET not set!")?;
let bitfinex = ExchangeDetails::Bitfinex {
prices_api_key: test_api_key.into(),
prices_api_secret: test_api_secret.into(),
orders_api_key: test_api_key.into(),
orders_api_secret: test_api_secret.into(),
positions_api_key: test_api_key.into(),
positions_api_secret: test_api_secret.into(),
api_key: api_key.into(),
api_secret: api_secret.into(),
};
let mut bot = BfxBot::new(
vec![bitfinex],
vec![Symbol::BTC, Symbol::ETH, Symbol::XMR],
Symbol::USD,
Duration::new(1, 0),
vec![Symbol::DERIV_ETH, Symbol::DERIV_BTC],
Symbol::DERIV_USDT,
Duration::new(10, 0),
);
Ok(bot.start_loop().await?)

View File

@ -12,14 +12,14 @@ use tokio::time::Duration;
use crate::connectors::{Client, ExchangeDetails};
use crate::currency::SymbolPair;
use crate::events::{ActorMessage, Event, Message};
use crate::events::{ActionMessage, ActorMessage, Event};
use crate::models::{
ActiveOrder, OrderBook, OrderForm, OrderKind, Position, PriceTicker, TradingPlatform,
ActiveOrder, OrderBook, OrderForm, OrderKind, Position, PriceTicker,
};
use crate::strategy::{FastOrderStrategy, OrderStrategy, PositionStrategy, TrailingStop};
use crate::strategy::{HiddenTrailingStop, MarketEnforce, OrderStrategy, PositionStrategy};
use crate::BoxError;
pub type OptionUpdate = (Option<Vec<Event>>, Option<Vec<Message>>);
pub type OptionUpdate = (Option<Vec<Event>>, Option<Vec<ActionMessage>>);
/******************
* PRICES
@ -33,6 +33,48 @@ pub struct PriceManager {
client: Client,
}
impl PriceManager {
pub fn new(receiver: Receiver<ActorMessage>, pair: SymbolPair, client: Client) -> Self {
PriceManager {
receiver,
pair,
prices: Vec::new(),
client,
}
}
pub async fn handle_message(&mut self, message: ActorMessage) -> Result<(), BoxError> {
if let ActionMessage::Update { tick } = message.message {
let a = self.update(tick).await?;
self.add_entry(a);
}
Ok(message
.respond_to
.send((None, None))
.map_err(|_| BoxError::from("Could not send message."))?)
}
pub fn add_entry(&mut self, entry: PriceEntry) {
self.prices.push(entry);
}
pub async fn update(&mut self, tick: u64) -> Result<PriceEntry, BoxError> {
let current_prices = self.client.current_prices(&self.pair).await?.into();
Ok(PriceEntry::new(
tick,
current_prices,
self.pair.clone(),
None,
))
}
pub fn pair(&self) -> &SymbolPair {
&self.pair
}
}
pub struct PriceManagerHandle {
sender: Sender<ActorMessage>,
}
@ -58,7 +100,7 @@ impl PriceManagerHandle {
self.sender
.send(ActorMessage {
message: Message::Update { tick },
message: ActionMessage::Update { tick },
respond_to: send,
})
.await?;
@ -67,51 +109,6 @@ impl PriceManagerHandle {
}
}
impl PriceManager {
pub fn new(receiver: Receiver<ActorMessage>, pair: SymbolPair, client: Client) -> Self {
PriceManager {
receiver,
pair,
prices: Vec::new(),
client,
}
}
pub async fn handle_message(&mut self, message: ActorMessage) -> Result<(), BoxError> {
match message.message {
Message::Update { tick } => {
let a = self.update(tick).await?;
self.add_entry(a);
}
_ => {}
}
Ok(message
.respond_to
.send((None, None))
.map_err(|_| BoxError::from("Could not send message."))?)
}
pub fn add_entry(&mut self, entry: PriceEntry) {
self.prices.push(entry);
}
pub async fn update(&mut self, tick: u64) -> Result<PriceEntry, BoxError> {
let current_prices = self.client.current_prices(&self.pair).await?.into();
Ok(PriceEntry::new(
tick,
current_prices,
self.pair.clone(),
None,
))
}
pub fn pair(&self) -> &SymbolPair {
&self.pair
}
}
#[derive(Clone, Debug)]
pub struct PriceEntry {
tick: u64,
@ -179,7 +176,7 @@ impl PositionManagerHandle {
self.sender
.send(ActorMessage {
message: Message::Update { tick },
message: ActionMessage::Update { tick },
respond_to: send,
})
.await?;
@ -224,7 +221,7 @@ impl PositionManager {
pub async fn handle_message(&mut self, msg: ActorMessage) -> Result<(), BoxError> {
let (events, messages) = match msg.message {
Message::Update { tick } => self.update(tick).await?,
ActionMessage::Update { tick } => self.update(tick).await?,
_ => (None, None),
};
@ -247,11 +244,7 @@ impl PositionManager {
Some(positions) => {
// checking if there are positions open for our pair
match positions
.into_iter()
.filter(|x| x.pair() == &self.pair)
.next()
{
match positions.into_iter().find(|x| x.pair() == &self.pair) {
// no open positions for our pair, setting active position to none
None => {
self.active_position = None;
@ -300,10 +293,7 @@ impl PositionManager {
None => self.current_tick() - 1,
};
self.positions_history
.get(&tick)
.filter(|x| x.id() == id)
.and_then(|x| Some(x))
self.positions_history.get(&tick).filter(|x| x.id() == id)
}
}
@ -312,7 +302,7 @@ impl PositionManager {
******************/
// Position ID: Order ID
pub type TrackedPositionsMap = HashMap<u64, u64>;
pub type TrackedPositionsMap = HashMap<u64, Vec<u64>>;
pub struct OrderManagerHandle {
sender: Sender<ActorMessage>,
@ -354,7 +344,36 @@ impl OrderManagerHandle {
self.sender
.send(ActorMessage {
message: Message::ClosePosition { position_id },
message: ActionMessage::ClosePosition { position_id },
respond_to: send,
})
.await?;
Ok(recv.await?)
}
pub async fn close_position_orders(
&mut self,
position_id: u64,
) -> Result<OptionUpdate, BoxError> {
let (send, recv) = oneshot::channel();
self.sender
.send(ActorMessage {
message: ActionMessage::ClosePositionOrders { position_id },
respond_to: send,
})
.await?;
Ok(recv.await?)
}
pub async fn submit_order(&mut self, order_form: OrderForm) -> Result<OptionUpdate, BoxError> {
let (send, recv) = oneshot::channel();
self.sender
.send(ActorMessage {
message: ActionMessage::SubmitOrder { order: order_form },
respond_to: send,
})
.await?;
@ -391,9 +410,14 @@ impl OrderManager {
pub async fn handle_message(&mut self, msg: ActorMessage) -> Result<(), BoxError> {
let (events, messages) = match msg.message {
Message::Update { .. } => self.update().await?,
Message::ClosePosition { position_id } => self.close_position(position_id).await?,
_ => (None, None),
ActionMessage::Update { .. } => self.update().await?,
ActionMessage::ClosePosition { position_id } => {
self.close_position(position_id).await?
}
ActionMessage::ClosePositionOrders { position_id } => {
self.close_position_orders(position_id).await?
}
ActionMessage::SubmitOrder { order } => self.submit_order(&order).await?,
};
Ok(msg
@ -402,6 +426,53 @@ impl OrderManager {
.map_err(|_| BoxError::from("Could not send message."))?)
}
pub async fn close_position_orders(&self, position_id: u64) -> Result<OptionUpdate, BoxError> {
info!("Closing outstanding orders for position #{}", position_id);
if let Some(position_orders) = self.tracked_positions.get(&position_id) {
// retrieving open orders
let open_orders = self.client.active_orders(&self.pair).await?;
let position_orders: Vec<_> = position_orders
.iter()
.filter_map(|&x| open_orders.iter().find(|y| y.id() == x))
.collect();
for order in position_orders {
match self.client.cancel_order(order).await {
Ok(_) => info!("Order #{} closed successfully.", order.id()),
Err(e) => error!("Could not close order #{}: {}", order.id(), e),
}
}
}
// TODO: return valid messages and events!
Ok((None, None))
}
pub async fn submit_order(&mut self, order_form: &OrderForm) -> Result<OptionUpdate, BoxError> {
info!("Submiting {}", order_form.kind());
let active_order = self.client.submit_order(order_form).await?;
debug!("Adding order to tracked orders.");
if let Some(metadata) = order_form.metadata() {
if let Some(position_id) = metadata.position_id() {
match self.tracked_positions.get_mut(&position_id) {
None => {
self.tracked_positions
.insert(position_id, vec![active_order.id()]);
}
Some(position_orders) => {
position_orders.push(active_order.id());
}
}
}
};
// TODO: return valid messages and events!111!!!1!
Ok((None, None))
}
pub async fn close_position(&mut self, position_id: u64) -> Result<OptionUpdate, BoxError> {
info!("Closing position #{}", position_id);
@ -421,25 +492,28 @@ impl OrderManager {
if let Some(position) = open_positions.into_iter().find(|x| x.id() == position_id) {
let opt_position_order = open_orders
.iter()
.find(|x| x.details.amount().neg() == position.amount());
// avoid using direct equality, using error margin instead
.find(|x| {
(x.order_form().amount().neg() - position.amount()).abs() < 0.0000001
});
// checking if the position has an open order.
// If so, don't do anything since the order is taken care of
// in the update phase.
// If no order is open, send an undercut limit order at the best current price.
if let None = opt_position_order {
if opt_position_order.is_none() {
// No open order, undercutting best price with limit order
let closing_price = self.best_closing_price(&position, &order_book);
// TODO: hardcoded platform to Margin!
let order_form = OrderForm::new(
self.pair.clone(),
OrderKind::Limit {
price: closing_price,
amount: position.amount().neg(),
},
TradingPlatform::Margin,
);
position.platform(),
position.amount().neg(),
)
.with_leverage(Some(position.leverage()));
info!("Submitting {} order", order_form.kind());
if let Err(e) = self.client.submit_order(&order_form).await {
@ -458,8 +532,8 @@ impl OrderManager {
Ok((None, None))
}
pub async fn update(&self) -> Result<OptionUpdate, BoxError> {
trace!("\t[OrderManager] Updating {}", self.pair);
pub async fn update(&mut self) -> Result<OptionUpdate, BoxError> {
debug!("\t[OrderManager] Updating {}", self.pair);
let (res_open_orders, res_order_book) = tokio::join!(
self.client.active_orders(&self.pair),
@ -468,8 +542,49 @@ impl OrderManager {
let (open_orders, order_book) = (res_open_orders?, res_order_book?);
// retrieving open positions to check whether the positions have open orders.
// we need to update our internal mapping in that case.
if !open_orders.is_empty() {
let open_positions = self.client.active_positions(&self.pair).await?;
if let Some(positions) = open_positions {
// currently, we are only trying to match orders with an amount equal to
// a position amount.
for position in positions {
let matching_order = open_orders
.iter()
.find(|x| x.order_form().amount().abs() == position.amount().abs());
// if an order is found, we insert the order to our internal mapping, if not already present
if let Some(matching_order) = matching_order {
match self.tracked_positions.get_mut(&position.id()) {
Some(position_orders) => {
if !position_orders.contains(&matching_order.id()) {
trace!(
"Mapped order #{} to position #{}",
position.id(),
matching_order.id()
);
position_orders.push(matching_order.id());
}
}
None => {
trace!(
"Mapped order #{} to position #{}",
position.id(),
matching_order.id()
);
self.tracked_positions
.insert(position.id(), vec![matching_order.id()]);
}
}
}
}
}
}
for active_order in open_orders {
debug!(
trace!(
"Found open order, calling \"{}\" strategy.",
self.strategy.name()
);
@ -479,9 +594,9 @@ impl OrderManager {
if let Some(messages) = strat_messages {
for m in messages {
match m {
Message::SubmitOrder { order: order_form } => {
ActionMessage::SubmitOrder { order: order_form } => {
info!("Closing open order...");
info!("\tCancelling open order #{}", &active_order.id);
info!("\tCancelling open order #{}", &active_order.id());
self.client.cancel_order(&active_order).await?;
info!("\tSubmitting {}...", order_form.kind());
@ -508,26 +623,22 @@ impl OrderManager {
let delta = (ask - bid) / 10.0;
let closing_price = {
let closing_price = {
if position.is_short() {
bid - delta
} else {
ask + delta
}
};
if avg > 9999.0 {
if position.is_short() {
closing_price.ceil()
} else {
closing_price.floor()
}
if position.is_short() {
bid - delta
} else {
closing_price
ask + delta
}
};
closing_price
if avg > 9999.0 {
if position.is_short() {
closing_price.ceil()
} else {
closing_price.floor()
}
} else {
closing_price
}
}
}
@ -546,12 +657,12 @@ impl PairManager {
order_manager: OrderManagerHandle::new(
pair.clone(),
client.clone(),
Box::new(FastOrderStrategy::default()),
Box::new(MarketEnforce::default()),
),
position_manager: PositionManagerHandle::new(
pair.clone(),
client.clone(),
Box::new(TrailingStop::new()),
pair,
client,
Box::new(HiddenTrailingStop::default()),
),
}
}
@ -578,10 +689,18 @@ impl PairManager {
if let Some(messages) = messages {
for m in messages {
match m {
Message::ClosePosition { position_id } => {
ActionMessage::Update { .. } => {}
ActionMessage::ClosePosition { position_id } => {
self.order_manager.close_position(position_id).await?;
}
_ => {}
ActionMessage::SubmitOrder { order } => {
self.order_manager.submit_order(order).await?;
}
ActionMessage::ClosePositionOrders { position_id } => {
self.order_manager
.close_position_orders(position_id)
.await?;
}
}
}
}
@ -593,21 +712,19 @@ impl PairManager {
pub struct ExchangeManager {
kind: ExchangeDetails,
pair_managers: Vec<PairManager>,
client: Client,
}
impl ExchangeManager {
pub fn new(kind: &ExchangeDetails, pairs: &Vec<SymbolPair>) -> Self {
pub fn new(kind: &ExchangeDetails, pairs: &[SymbolPair]) -> Self {
let client = Client::new(kind);
let pair_managers = pairs
.into_iter()
.iter()
.map(|x| PairManager::new(x.clone(), client.clone()))
.collect();
Self {
kind: kind.clone(),
pair_managers,
client,
}
}
@ -619,7 +736,7 @@ impl ExchangeManager {
.collect();
// execute the futures
while let Some(_) = futures.next().await {}
while futures.next().await.is_some() {}
Ok(())
}

View File

@ -139,18 +139,6 @@ impl OrderDetails {
}
}
pub fn exchange(&self) -> Exchange {
self.exchange
}
pub fn platform(&self) -> TradingPlatform {
self.platform
}
pub fn kind(&self) -> OrderKind {
self.kind
}
pub fn execution_timestamp(&self) -> u64 {
self.execution_timestamp
}
pub fn id(&self) -> u64 {
self.id
}
@ -162,14 +150,71 @@ impl OrderDetails {
#[derive(Clone, Debug)]
pub struct ActiveOrder {
pub(crate) exchange: Exchange,
pub(crate) id: u64,
pub(crate) group_id: Option<u64>,
pub(crate) client_id: Option<u64>,
pub(crate) symbol: SymbolPair,
pub(crate) details: OrderForm,
pub(crate) creation_timestamp: u64,
pub(crate) update_timestamp: u64,
exchange: Exchange,
id: u64,
group_id: Option<u64>,
client_id: Option<u64>,
pair: SymbolPair,
order_form: OrderForm,
creation_timestamp: u64,
update_timestamp: u64,
}
impl ActiveOrder {
pub fn new(
exchange: Exchange,
id: u64,
pair: SymbolPair,
order_form: OrderForm,
creation_timestamp: u64,
update_timestamp: u64,
) -> Self {
Self {
exchange,
id,
group_id: None,
client_id: None,
pair,
order_form,
creation_timestamp,
update_timestamp,
}
}
pub fn with_group_id(mut self, group_id: Option<u64>) -> Self {
self.group_id = group_id;
self
}
pub fn with_client_id(mut self, client_id: Option<u64>) -> Self {
self.client_id = client_id;
self
}
pub fn exchange(&self) -> Exchange {
self.exchange
}
pub fn id(&self) -> u64 {
self.id
}
pub fn group_id(&self) -> Option<u64> {
self.group_id
}
pub fn client_id(&self) -> Option<u64> {
self.client_id
}
pub fn pair(&self) -> &SymbolPair {
&self.pair
}
pub fn order_form(&self) -> &OrderForm {
&self.order_form
}
pub fn creation_timestamp(&self) -> u64 {
self.creation_timestamp
}
pub fn update_timestamp(&self) -> u64 {
self.update_timestamp
}
}
impl Hash for ActiveOrder {
@ -186,7 +231,7 @@ impl PartialEq for ActiveOrder {
impl Eq for ActiveOrder {}
#[derive(Debug, Clone, Copy)]
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum TradingPlatform {
Exchange,
Derivative,
@ -213,34 +258,13 @@ impl Display for TradingPlatform {
#[derive(Copy, Clone, Debug)]
pub enum OrderKind {
Limit {
price: f64,
amount: f64,
},
Market {
amount: f64,
},
Stop {
price: f64,
amount: f64,
},
StopLimit {
price: f64,
amount: f64,
limit_price: f64,
},
TrailingStop {
distance: f64,
amount: f64,
},
FillOrKill {
price: f64,
amount: f64,
},
ImmediateOrCancel {
price: f64,
amount: f64,
},
Limit { price: f64 },
Market,
Stop { price: f64 },
StopLimit { price: f64, limit_price: f64 },
TrailingStop { distance: f64 },
FillOrKill { price: f64 },
ImmediateOrCancel { price: f64 },
}
impl OrderKind {
@ -260,67 +284,32 @@ impl OrderKind {
impl Display for OrderKind {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
OrderKind::Limit { price, amount } => {
OrderKind::Limit { price } => {
write!(f, "[{} | Price: {:0.5}]", self.as_str(), price,)
}
OrderKind::Market => {
write!(f, "[{}]", self.as_str())
}
OrderKind::Stop { price } => {
write!(f, "[{} | Price: {:0.5}", self.as_str(), price,)
}
OrderKind::StopLimit { price, limit_price } => {
write!(
f,
"[{} | Price: {:0.5}, Amount: {:0.5}]",
"[{} | Price: {:0.5}, Limit Price: {:0.5}]",
self.as_str(),
price,
amount
)
}
OrderKind::Market { amount } => {
write!(f, "[{} | Amount: {:0.5}]", self.as_str(), amount)
}
OrderKind::Stop { price, amount } => {
write!(
f,
"[{} | Price: {:0.5}, Amount: {:0.5}]",
self.as_str(),
price,
amount
)
}
OrderKind::StopLimit {
price,
amount,
limit_price,
} => {
write!(
f,
"[{} | Price: {:0.5}, Amount: {:0.5}, Limit Price: {:0.5}]",
self.as_str(),
price,
amount,
limit_price
)
}
OrderKind::TrailingStop { distance, amount } => {
write!(
f,
"[{} | Distance: {:0.5}, Amount: {:0.5}]",
self.as_str(),
distance,
amount
)
OrderKind::TrailingStop { distance } => {
write!(f, "[{} | Distance: {:0.5}]", self.as_str(), distance,)
}
OrderKind::FillOrKill { price, amount } => {
write!(
f,
"[{} | Price: {:0.5}, Amount: {:0.5}]",
self.as_str(),
price,
amount
)
OrderKind::FillOrKill { price } => {
write!(f, "[{} | Price: {:0.5}]", self.as_str(), price,)
}
OrderKind::ImmediateOrCancel { price, amount } => {
write!(
f,
"[{} | Price: {:0.5}, Amount: {:0.5}]",
self.as_str(),
price,
amount
)
OrderKind::ImmediateOrCancel { price } => {
write!(f, "[{} | Price: {:0.5}]", self.as_str(), price,)
}
}
}
@ -331,17 +320,38 @@ pub struct OrderForm {
pair: SymbolPair,
kind: OrderKind,
platform: TradingPlatform,
amount: f64,
leverage: Option<f64>,
metadata: Option<OrderMetadata>,
}
impl OrderForm {
pub fn new(pair: SymbolPair, order_kind: OrderKind, platform: TradingPlatform) -> Self {
pub fn new(
pair: SymbolPair,
order_kind: OrderKind,
platform: TradingPlatform,
amount: f64,
) -> Self {
Self {
pair,
kind: order_kind,
platform,
amount,
leverage: None,
metadata: None,
}
}
pub fn with_leverage(mut self, leverage: Option<f64>) -> Self {
self.leverage = leverage;
self
}
pub fn with_metadata(mut self, metadata: Option<OrderMetadata>) -> Self {
self.metadata = metadata;
self
}
pub fn pair(&self) -> &SymbolPair {
&self.pair
}
@ -355,15 +365,7 @@ impl OrderForm {
}
pub fn amount(&self) -> f64 {
match self.kind {
OrderKind::Limit { amount, .. } => amount,
OrderKind::Market { amount } => amount,
OrderKind::Stop { amount, .. } => amount,
OrderKind::StopLimit { amount, .. } => amount,
OrderKind::TrailingStop { amount, .. } => amount,
OrderKind::FillOrKill { amount, .. } => amount,
OrderKind::ImmediateOrCancel { amount, .. } => amount,
}
self.amount
}
pub fn price(&self) -> Option<f64> {
@ -377,6 +379,31 @@ impl OrderForm {
OrderKind::ImmediateOrCancel { price, .. } => Some(price),
}
}
pub fn leverage(&self) -> Option<f64> {
self.leverage
}
pub fn metadata(&self) -> &Option<OrderMetadata> {
&self.metadata
}
}
#[derive(Debug, Clone)]
pub struct OrderMetadata {
position_id: Option<u64>,
}
impl OrderMetadata {
pub fn with_position_id(position_id: u64) -> Self {
OrderMetadata {
position_id: Some(position_id),
}
}
pub fn position_id(&self) -> Option<u64> {
self.position_id
}
}
/***************
@ -396,6 +423,8 @@ pub struct Position {
position_id: u64,
creation_date: Option<u64>,
creation_update: Option<u64>,
platform: TradingPlatform,
leverage: f64,
}
impl Position {
@ -408,6 +437,8 @@ impl Position {
pl_perc: f64,
price_liq: f64,
position_id: u64,
platform: TradingPlatform,
leverage: f64,
) -> Self {
Position {
pair,
@ -421,6 +452,8 @@ impl Position {
creation_date: None,
creation_update: None,
profit_state: None,
platform,
leverage,
}
}
@ -505,6 +538,12 @@ impl Position {
pub fn is_long(&self) -> bool {
self.amount.is_sign_positive()
}
pub fn platform(&self) -> TradingPlatform {
self.platform
}
pub fn leverage(&self) -> f64 {
self.leverage
}
}
impl Hash for Position {
@ -530,17 +569,6 @@ pub enum PositionProfitState {
Profit,
}
impl PositionProfitState {
fn color(self) -> String {
match self {
PositionProfitState::Critical | PositionProfitState::Loss => "red",
PositionProfitState::BreakEven => "yellow",
PositionProfitState::MinimumProfit | PositionProfitState::Profit => "green",
}
.into()
}
}
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
pub enum PositionState {
Closed,
@ -563,3 +591,15 @@ pub struct Trade {
pub fee: OrderFee,
pub fee_currency: Symbol,
}
#[derive(Debug)]
pub enum TradingFees {
Maker {
platform: TradingPlatform,
percentage: f64,
},
Taker {
platform: TradingPlatform,
percentage: f64,
},
}

View File

@ -4,12 +4,10 @@ use std::fmt::{Debug, Formatter};
use dyn_clone::DynClone;
use log::info;
use crate::events::{Event, EventKind, EventMetadata, Message};
use crate::connectors::Connector;
use crate::events::{ActionMessage, Event, EventKind, EventMetadata};
use crate::managers::OptionUpdate;
use crate::models::{
ActiveOrder, OrderBook, OrderBookEntry, OrderForm, OrderKind, Position, PositionProfitState,
PositionState, TradingPlatform,
};
use crate::models::{ActiveOrder, OrderBook, OrderForm, OrderKind, Position, PositionProfitState};
use crate::BoxError;
/***************
@ -23,13 +21,13 @@ pub trait PositionStrategy: DynClone + Send + Sync {
position: Position,
current_tick: u64,
positions_history: &HashMap<u64, Position>,
) -> (Position, Option<Vec<Event>>, Option<Vec<Message>>);
) -> (Position, Option<Vec<Event>>, Option<Vec<ActionMessage>>);
fn post_tick(
&mut self,
position: Position,
current_tick: u64,
positions_history: &HashMap<u64, Position>,
) -> (Position, Option<Vec<Event>>, Option<Vec<Message>>);
) -> (Position, Option<Vec<Event>>, Option<Vec<ActionMessage>>);
}
impl Debug for dyn PositionStrategy {
@ -69,54 +67,54 @@ impl Debug for dyn OrderStrategy {
***************/
#[derive(Clone, Debug)]
pub struct TrailingStop {
pub struct HiddenTrailingStop {
stop_percentages: HashMap<u64, f64>,
capital_max_loss: f64,
capital_min_profit: f64,
capital_good_profit: f64,
min_profit_trailing_delta: f64,
good_profit_trailing_delta: f64,
leverage: f64,
min_profit_percentage: f64,
good_profit_percentage: f64,
max_loss_percentage: f64,
}
impl TrailingStop {
const BREAK_EVEN_PERC: f64 = 0.1;
const MIN_PROFIT_PERC: f64 = 0.5;
const GOOD_PROFIT_PERC: f64 = TrailingStop::MIN_PROFIT_PERC * 1.75;
const MAX_LOSS_PERC: f64 = -4.0;
pub fn new() -> Self {
TrailingStop {
stop_percentages: HashMap::new(),
}
}
impl HiddenTrailingStop {
fn update_stop_percentage(&mut self, position: &Position) {
if let Some(profit_state) = position.profit_state() {
let profit_state_delta = match profit_state {
PositionProfitState::MinimumProfit => Some(0.2),
PositionProfitState::Profit => Some(0.1),
PositionProfitState::MinimumProfit => Some(self.min_profit_trailing_delta),
PositionProfitState::Profit => Some(self.good_profit_trailing_delta),
_ => None,
};
if let Some(profit_state_delta) = profit_state_delta {
let current_stop_percentage = position.pl_perc() - profit_state_delta;
match profit_state {
PositionProfitState::MinimumProfit | PositionProfitState::Profit => {
match self.stop_percentages.get(&position.id()) {
None => {
if let PositionProfitState::MinimumProfit | PositionProfitState::Profit =
profit_state
{
match self.stop_percentages.get(&position.id()) {
None => {
self.stop_percentages
.insert(position.id(), current_stop_percentage);
}
Some(existing_threshold) => {
if existing_threshold < &current_stop_percentage {
self.stop_percentages
.insert(position.id(), current_stop_percentage);
}
Some(existing_threshold) => {
if existing_threshold < &current_stop_percentage {
self.stop_percentages
.insert(position.id(), current_stop_percentage);
}
}
}
}
_ => {}
}
}
info!(
"\tState: {:?} | PL%: {:0.2} | Stop: {:0.2}",
"\tState: {:?} | PL: {:0.2}{} ({:0.2}%) | Stop: {:0.2}",
position.profit_state().unwrap(),
position.pl(),
position.pair().quote(),
position.pl_perc(),
self.stop_percentages.get(&position.id()).unwrap_or(&0.0)
);
@ -124,9 +122,43 @@ impl TrailingStop {
}
}
impl PositionStrategy for TrailingStop {
impl Default for HiddenTrailingStop {
fn default() -> Self {
let leverage = 5.0;
// in percentage
let capital_max_loss = 15.0;
let capital_min_profit = 9.0;
let capital_good_profit = capital_min_profit * 2.0;
let weighted_min_profit = capital_min_profit / leverage;
let weighted_good_profit = capital_good_profit / leverage;
let weighted_max_loss = capital_max_loss / leverage;
let min_profit_trailing_delta = weighted_min_profit * 0.17;
let good_profit_trailing_delta = weighted_good_profit * 0.08;
let min_profit_percentage = weighted_min_profit + min_profit_trailing_delta;
let good_profit_percentage = weighted_good_profit + good_profit_trailing_delta;
let max_loss_percentage = -weighted_max_loss;
HiddenTrailingStop {
stop_percentages: Default::default(),
capital_max_loss,
capital_min_profit,
capital_good_profit,
min_profit_trailing_delta,
good_profit_trailing_delta,
leverage,
min_profit_percentage,
good_profit_percentage,
max_loss_percentage,
}
}
}
impl PositionStrategy for HiddenTrailingStop {
fn name(&self) -> String {
"Trailing stop".into()
"Hidden Trailing Stop".into()
}
/// Sets the profit state of an open position
@ -135,19 +167,17 @@ impl PositionStrategy for TrailingStop {
position: Position,
current_tick: u64,
positions_history: &HashMap<u64, Position>,
) -> (Position, Option<Vec<Event>>, Option<Vec<Message>>) {
) -> (Position, Option<Vec<Event>>, Option<Vec<ActionMessage>>) {
let pl_perc = position.pl_perc();
let state = {
if pl_perc > TrailingStop::GOOD_PROFIT_PERC {
if pl_perc > self.good_profit_percentage {
PositionProfitState::Profit
} else if TrailingStop::MIN_PROFIT_PERC <= pl_perc
&& pl_perc < TrailingStop::GOOD_PROFIT_PERC
{
} else if (self.min_profit_percentage..self.good_profit_percentage).contains(&pl_perc) {
PositionProfitState::MinimumProfit
} else if 0.0 <= pl_perc && pl_perc < TrailingStop::MIN_PROFIT_PERC {
} else if (0.0..self.min_profit_percentage).contains(&pl_perc) {
PositionProfitState::BreakEven
} else if TrailingStop::MAX_LOSS_PERC < pl_perc && pl_perc < 0.0 {
} else if (self.max_loss_percentage..0.0).contains(&pl_perc) {
PositionProfitState::Loss
} else {
PositionProfitState::Critical
@ -156,7 +186,7 @@ impl PositionStrategy for TrailingStop {
let opt_prev_position = positions_history.get(&(current_tick - 1));
let event_metadata = EventMetadata::new(Some(position.id()), None);
let new_position = position.clone().with_profit_state(Some(state));
let new_position = position.with_profit_state(Some(state));
match opt_prev_position {
Some(prev) => {
@ -205,7 +235,7 @@ impl PositionStrategy for TrailingStop {
events
};
return (new_position, Some(events), None);
(new_position, Some(events), None)
}
fn post_tick(
@ -213,20 +243,15 @@ impl PositionStrategy for TrailingStop {
position: Position,
_: u64,
_: &HashMap<u64, Position>,
) -> (Position, Option<Vec<Event>>, Option<Vec<Message>>) {
let close_message = Message::ClosePosition {
) -> (Position, Option<Vec<Event>>, Option<Vec<ActionMessage>>) {
let close_message = ActionMessage::ClosePosition {
position_id: position.id(),
};
// if critical, early return with close position
if let Some(profit_state) = position.profit_state() {
match profit_state {
PositionProfitState::Critical => {
info!("Maximum loss reached. Closing position.");
return (position, None, Some(vec![close_message]));
}
_ => {}
}
if let Some(PositionProfitState::Critical) = position.profit_state() {
info!("Maximum loss reached. Closing position.");
return (position, None, Some(vec![close_message]));
};
// let's check if we surpassed an existing stop percentage
@ -243,28 +268,281 @@ impl PositionStrategy for TrailingStop {
}
}
// #[derive(Clone, Debug)]
// pub struct TrailingStop {
// stop_percentages: HashMap<u64, f64>,
// capital_max_loss: f64,
// capital_min_profit: f64,
// capital_good_profit: f64,
// min_profit_trailing_delta: f64,
// good_profit_trailing_delta: f64,
// leverage: f64,
// min_profit_percentage: f64,
// good_profit_percentage: f64,
// max_loss_percentage: f64,
// }
//
// impl TrailingStop {
// fn update_stop_percentage(&mut self, position: &Position) -> Option<OrderForm> {
// let mut order_form = None;
//
// if let Some(profit_state) = position.profit_state() {
// let profit_state_delta = match profit_state {
// PositionProfitState::MinimumProfit => Some(self.min_profit_trailing_delta),
// PositionProfitState::Profit => Some(self.good_profit_trailing_delta),
// _ => None,
// };
//
// if let Some(profit_state_delta) = profit_state_delta {
// let current_stop_percentage = position.pl_perc() - profit_state_delta;
// let price_percentage_delta = {
// if position.is_short() {
// // 1.0 is the base price
// 1.0 - current_stop_percentage / 100.0
// } else {
// 1.0 + current_stop_percentage / 100.0
// }
// };
//
// println!("Delta: {}", price_percentage_delta);
//
// if let PositionProfitState::MinimumProfit | PositionProfitState::Profit =
// profit_state
// {
// match self.stop_percentages.get(&position.id()) {
// None => {
// self.stop_percentages
// .insert(position.id(), current_stop_percentage);
//
// trace!("Setting trailing stop, asking order manager to cancel previous orders.");
// order_form = Some(
// OrderForm::new(
// position.pair().clone(),
// OrderKind::Limit {
// price: position.base_price() * price_percentage_delta,
// },
// position.platform(),
// position.amount().neg(),
// )
// .with_metadata(OrderMetadata::with_position_id(position.id())),
// );
// }
// Some(existing_threshold) => {
// // follow and update trailing stop
// if existing_threshold < &current_stop_percentage {
// self.stop_percentages
// .insert(position.id(), current_stop_percentage);
//
// trace!("Updating threshold, asking order manager to cancel previous orders.");
// order_form = Some(
// OrderForm::new(
// position.pair().clone(),
// OrderKind::Limit {
// price: position.base_price() * price_percentage_delta,
// },
// position.platform(),
// position.amount().neg(),
// )
// .with_metadata(OrderMetadata::with_position_id(position.id())),
// );
// }
// }
// }
// }
// }
//
// info!(
// "\tState: {:?} | PL: {:0.2}{} ({:0.2}%) | Stop: {:0.2}",
// position.profit_state().unwrap(),
// position.pl(),
// position.pair().quote(),
// position.pl_perc(),
// self.stop_percentages.get(&position.id()).unwrap_or(&0.0)
// );
// }
//
// order_form
// }
// }
//
// impl Default for TrailingStop {
// fn default() -> Self {
// let leverage = 5.0;
//
// // in percentage
// let capital_max_loss = 15.0;
// let capital_min_profit = 1.0;
// let capital_good_profit = 6.0;
//
// let weighted_min_profit = capital_min_profit / leverage;
// let weighted_good_profit = capital_good_profit / leverage;
// let weighted_max_loss = capital_max_loss / leverage;
//
// let min_profit_trailing_delta = weighted_min_profit * 0.2;
// let good_profit_trailing_delta = weighted_good_profit * 0.08;
//
// let min_profit_percentage = weighted_min_profit + min_profit_trailing_delta;
// let good_profit_percentage = weighted_good_profit + good_profit_trailing_delta;
// let max_loss_percentage = -weighted_max_loss;
//
// TrailingStop {
// stop_percentages: Default::default(),
// capital_max_loss,
// capital_min_profit,
// capital_good_profit,
// min_profit_trailing_delta,
// good_profit_trailing_delta,
// leverage,
// min_profit_percentage,
// good_profit_percentage,
// max_loss_percentage,
// }
// }
// }
// impl PositionStrategy for TrailingStop {
// fn name(&self) -> String {
// "Trailing Stop".into()
// }
//
// /// Sets the profit state of an open position
// fn on_tick(
// &mut self,
// position: Position,
// current_tick: u64,
// positions_history: &HashMap<u64, Position>,
// ) -> (Position, Option<Vec<Event>>, Option<Vec<ActionMessage>>) {
// let pl_perc = position.pl_perc();
//
// let state = {
// if pl_perc > self.good_profit_percentage {
// PositionProfitState::Profit
// } else if (self.min_profit_percentage..self.good_profit_percentage).contains(&pl_perc) {
// PositionProfitState::MinimumProfit
// } else if (0.0..self.min_profit_percentage).contains(&pl_perc) {
// PositionProfitState::BreakEven
// } else if (self.max_loss_percentage..0.0).contains(&pl_perc) {
// PositionProfitState::Loss
// } else {
// PositionProfitState::Critical
// }
// };
//
// let opt_prev_position = positions_history.get(&(current_tick - 1));
// let event_metadata = EventMetadata::new(Some(position.id()), None);
// let new_position = position.with_profit_state(Some(state));
//
// match opt_prev_position {
// Some(prev) => {
// if prev.profit_state() == Some(state) {
// return (new_position, None, None);
// }
// }
// None => return (new_position, None, None),
// };
//
// let events = {
// let mut events = vec![];
//
// if state == PositionProfitState::Profit {
// events.push(Event::new(
// EventKind::ReachedGoodProfit,
// current_tick,
// Some(event_metadata),
// ));
// } else if state == PositionProfitState::MinimumProfit {
// events.push(Event::new(
// EventKind::ReachedMinProfit,
// current_tick,
// Some(event_metadata),
// ));
// } else if state == PositionProfitState::BreakEven {
// events.push(Event::new(
// EventKind::ReachedBreakEven,
// current_tick,
// Some(event_metadata),
// ));
// } else if state == PositionProfitState::Loss {
// events.push(Event::new(
// EventKind::ReachedLoss,
// current_tick,
// Some(event_metadata),
// ));
// } else {
// events.push(Event::new(
// EventKind::ReachedMaxLoss,
// current_tick,
// Some(event_metadata),
// ));
// }
//
// events
// };
//
// (new_position, Some(events), None)
// }
//
// fn post_tick(
// &mut self,
// position: Position,
// _: u64,
// _: &HashMap<u64, Position>,
// ) -> (Position, Option<Vec<Event>>, Option<Vec<ActionMessage>>) {
// let close_message = ActionMessage::ClosePosition {
// position_id: position.id(),
// };
//
// // if critical, early return with close position
// if let Some(PositionProfitState::Critical) = position.profit_state() {
// info!("Maximum loss reached. Closing position.");
// return (position, None, Some(vec![close_message]));
// };
//
// // let's check if we surpassed an existing stop percentage
// if let Some(existing_stop_percentage) = self.stop_percentages.get(&position.id()) {
// if &position.pl_perc() <= existing_stop_percentage {
// info!("Stop percentage surpassed. Closing position.");
// return (position, None, Some(vec![close_message]));
// }
// }
//
// // updated or new trailing stop. should cancel orders and submit new one
// if let Some(order_form) = self.update_stop_percentage(&position) {
// let mut messages = vec![];
//
// messages.push(ActionMessage::ClosePositionOrders {
// position_id: position.id(),
// });
// messages.push(ActionMessage::SubmitOrder { order: order_form });
//
// return (position, None, Some(messages));
// }
//
// (position, None, None)
// }
// }
/*
* ORDER STRATEGIES
*/
#[derive(Clone, Debug)]
pub struct FastOrderStrategy {
pub struct MarketEnforce {
// threshold (%) for which we trigger a market order
// to close an open position
threshold: f64,
}
impl Default for FastOrderStrategy {
impl Default for MarketEnforce {
fn default() -> Self {
Self { threshold: 0.2 }
Self {
threshold: 1.0 / 15.0,
}
}
}
impl FastOrderStrategy {
pub fn new(threshold: f64) -> Self {
Self { threshold }
}
}
impl OrderStrategy for FastOrderStrategy {
impl OrderStrategy for MarketEnforce {
fn name(&self) -> String {
"Fast order strategy".into()
"Market Enforce".into()
}
fn on_open_order(
@ -276,7 +554,7 @@ impl OrderStrategy for FastOrderStrategy {
// long
let offer_comparison = {
if order.details.amount() > 0.0 {
if order.order_form().amount() > 0.0 {
order_book.highest_bid()
} else {
order_book.lowest_ask()
@ -286,63 +564,24 @@ impl OrderStrategy for FastOrderStrategy {
// if the best offer is higher than our threshold,
// ask the manager to close the position with a market order
let order_price = order
.details
.order_form()
.price()
.ok_or("The active order does not have a price!")?;
let delta = (1.0 - (offer_comparison / order_price)).abs() * 100.0;
if delta > self.threshold {
messages.push(Message::SubmitOrder {
messages.push(ActionMessage::SubmitOrder {
order: OrderForm::new(
order.symbol.clone(),
OrderKind::Market {
amount: order.details.amount(),
},
order.details.platform().clone(),
),
order.pair().clone(),
OrderKind::Market,
*order.order_form().platform(),
order.order_form().amount(),
)
.with_leverage(order.order_form().leverage())
.with_metadata(order.order_form().metadata().clone()),
})
}
Ok((None, (!messages.is_empty()).then_some(messages)))
}
// fn on_position_order(
// &self,
// order: &ActiveOrder,
// _: &Position,
// order_book: &OrderBook,
// ) -> Result<OptionUpdate, BoxError> {
// let mut messages = vec![];
//
// // long
// let offer_comparison = {
// if order.current_form.amount() > 0.0 {
// order_book.highest_bid()
// } else {
// order_book.lowest_ask()
// }
// };
//
// // if the best offer is higher than our threshold,
// // ask the manager to close the position with a market order
// let order_price = order
// .current_form
// .price()
// .ok_or("The active order does not have a price!")?;
// let delta = (1.0 - (offer_comparison / order_price)).abs() * 100.0;
//
// if delta > self.threshold {
// messages.push(Message::SubmitOrder {
// order: OrderForm::new(
// order.symbol.clone(),
// OrderKind::Market {
// amount: order.current_form.amount(),
// },
// order.current_form.platform().clone(),
// ),
// })
// }
//
// Ok((None, (!messages.is_empty()).then_some(messages)))
// }
}