merge rust
This commit is contained in:
commit
b8f239ec31
@ -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 } => {
|
||||
let order_form = {
|
||||
let pre_leverage = {
|
||||
match order.kind() {
|
||||
OrderKind::Limit { price } => {
|
||||
bitfinex::orders::OrderForm::new(symbol_name, price, amount, order.into())
|
||||
}
|
||||
OrderKind::Market { amount } => {
|
||||
OrderKind::Market => {
|
||||
bitfinex::orders::OrderForm::new(symbol_name, 0.0, amount, order.into())
|
||||
}
|
||||
OrderKind::Stop { price, amount } => {
|
||||
OrderKind::Stop { price } => {
|
||||
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 } => {
|
||||
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, amount } => {
|
||||
OrderKind::FillOrKill { price } => {
|
||||
bitfinex::orders::OrderForm::new(symbol_name, price, amount, order.into())
|
||||
}
|
||||
OrderKind::ImmediateOrCancel { price, amount } => {
|
||||
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,
|
||||
}
|
||||
};
|
||||
|
||||
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()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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");
|
||||
|
@ -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)]
|
||||
|
@ -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?)
|
||||
|
@ -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());
|
||||
@ -507,7 +622,6 @@ impl OrderManager {
|
||||
let avg = (bid + ask) / 2.0;
|
||||
let delta = (ask - bid) / 10.0;
|
||||
|
||||
let closing_price = {
|
||||
let closing_price = {
|
||||
if position.is_short() {
|
||||
bid - delta
|
||||
@ -525,9 +639,6 @@ impl OrderManager {
|
||||
} else {
|
||||
closing_price
|
||||
}
|
||||
};
|
||||
|
||||
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(())
|
||||
}
|
||||
|
@ -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,
|
||||
},
|
||||
}
|
||||
|
@ -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,35 +67,34 @@ 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 => {
|
||||
if let PositionProfitState::MinimumProfit | PositionProfitState::Profit =
|
||||
profit_state
|
||||
{
|
||||
match self.stop_percentages.get(&position.id()) {
|
||||
None => {
|
||||
self.stop_percentages
|
||||
@ -111,12 +108,13 @@ impl TrailingStop {
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
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 => {
|
||||
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 < ¤t_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)))
|
||||
// }
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user