stuff...
This commit is contained in:
parent
0d48d3768a
commit
dfd676612e
1
rustybot/Cargo.lock
generated
1
rustybot/Cargo.lock
generated
@ -1128,6 +1128,7 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"bitfinex",
|
"bitfinex",
|
||||||
|
"byteorder",
|
||||||
"chrono",
|
"chrono",
|
||||||
"dyn-clone",
|
"dyn-clone",
|
||||||
"fern",
|
"fern",
|
||||||
|
@ -17,3 +17,4 @@ dyn-clone = "1"
|
|||||||
log = "0.4"
|
log = "0.4"
|
||||||
fern = {version = "0.6", features = ["colored"]}
|
fern = {version = "0.6", features = ["colored"]}
|
||||||
chrono = "0.4"
|
chrono = "0.4"
|
||||||
|
byteorder = "1"
|
@ -4,11 +4,11 @@ use std::sync::Arc;
|
|||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use bitfinex::api::Bitfinex;
|
use bitfinex::api::Bitfinex;
|
||||||
use bitfinex::orders::{OrderForm, OrderMeta};
|
use bitfinex::orders::{ActiveOrder, OrderMeta};
|
||||||
use bitfinex::ticker::TradingPairTicker;
|
use bitfinex::ticker::TradingPairTicker;
|
||||||
|
|
||||||
use crate::currency::SymbolPair;
|
use crate::currency::SymbolPair;
|
||||||
use crate::models::{Order, OrderKind, Position, PositionState, PriceTicker};
|
use crate::models::{ExecutedOrder, OrderForm, OrderKind, Position, PositionState, PriceTicker};
|
||||||
use crate::BoxError;
|
use crate::BoxError;
|
||||||
|
|
||||||
#[derive(Eq, PartialEq, Hash, Clone, Debug)]
|
#[derive(Eq, PartialEq, Hash, Clone, Debug)]
|
||||||
@ -50,18 +50,12 @@ impl Client {
|
|||||||
self.inner.current_prices(pair).await
|
self.inner.current_prices(pair).await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn active_orders(&self, pair: &SymbolPair) -> Result<Vec<Order>, BoxError> {
|
pub async fn active_orders(&self, pair: &SymbolPair) -> Result<Vec<ExecutedOrder>, BoxError> {
|
||||||
self.inner.active_orders(pair).await
|
self.inner.active_orders(pair).await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn submit_order(
|
pub async fn submit_order(&self, order: OrderForm) -> Result<ExecutedOrder, BoxError> {
|
||||||
&self,
|
self.inner.submit_order(order).await
|
||||||
pair: &SymbolPair,
|
|
||||||
amount: f64,
|
|
||||||
price: f64,
|
|
||||||
kind: &OrderKind,
|
|
||||||
) -> Result<(), BoxError> {
|
|
||||||
self.inner.submit_order(pair, amount, price, kind).await
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,14 +64,8 @@ pub trait Connector: Send + Sync {
|
|||||||
fn name(&self) -> String;
|
fn name(&self) -> String;
|
||||||
async fn active_positions(&self, pair: &SymbolPair) -> Result<Option<Vec<Position>>, BoxError>;
|
async fn active_positions(&self, pair: &SymbolPair) -> Result<Option<Vec<Position>>, BoxError>;
|
||||||
async fn current_prices(&self, pair: &SymbolPair) -> Result<TradingPairTicker, BoxError>;
|
async fn current_prices(&self, pair: &SymbolPair) -> Result<TradingPairTicker, BoxError>;
|
||||||
async fn active_orders(&self, pair: &SymbolPair) -> Result<Vec<Order>, BoxError>;
|
async fn active_orders(&self, pair: &SymbolPair) -> Result<Vec<ExecutedOrder>, BoxError>;
|
||||||
async fn submit_order(
|
async fn submit_order(&self, order: OrderForm) -> Result<ExecutedOrder, BoxError>;
|
||||||
&self,
|
|
||||||
pair: &SymbolPair,
|
|
||||||
amount: f64,
|
|
||||||
price: f64,
|
|
||||||
kind: &OrderKind,
|
|
||||||
) -> Result<(), BoxError>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Debug for dyn Connector {
|
impl Debug for dyn Connector {
|
||||||
@ -148,24 +136,28 @@ impl Connector for BitfinexConnector {
|
|||||||
Ok(ticker)
|
Ok(ticker)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn active_orders(&self, pair: &SymbolPair) -> Result<Vec<Order>, BoxError> {
|
async fn active_orders(&self, pair: &SymbolPair) -> Result<Vec<ExecutedOrder>, BoxError> {
|
||||||
unimplemented!()
|
unimplemented!()
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn submit_order(
|
async fn submit_order(&self, order: OrderForm) -> Result<ExecutedOrder, BoxError> {
|
||||||
&self,
|
|
||||||
pair: &SymbolPair,
|
|
||||||
amount: f64,
|
|
||||||
price: f64,
|
|
||||||
kind: &OrderKind,
|
|
||||||
) -> Result<(), BoxError> {
|
|
||||||
let order_form = match &self.affiliate_code {
|
let order_form = match &self.affiliate_code {
|
||||||
Some(affiliate_code) => OrderForm::new(pair.trading_repr(), price, amount, kind.into())
|
Some(affiliate_code) => bitfinex::orders::OrderForm::new(
|
||||||
|
order.pair().trading_repr(),
|
||||||
|
*order.price(),
|
||||||
|
*order.amount(),
|
||||||
|
order.kind().into(),
|
||||||
|
)
|
||||||
.with_meta(OrderMeta::new(affiliate_code.clone())),
|
.with_meta(OrderMeta::new(affiliate_code.clone())),
|
||||||
None => OrderForm::new(pair.trading_repr(), price, amount, kind.into()),
|
None => bitfinex::orders::OrderForm::new(
|
||||||
|
order.pair().trading_repr(),
|
||||||
|
*order.price(),
|
||||||
|
*order.amount(),
|
||||||
|
order.kind().into(),
|
||||||
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(self.bfx.orders.submit_order(&order_form).await?)
|
Ok(self.bfx.orders.submit_order(&order_form).await?.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -216,6 +208,26 @@ impl From<&OrderKind> for bitfinex::orders::OrderKind {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<OrderKind> for bitfinex::orders::OrderKind {
|
||||||
|
fn from(o: OrderKind) -> Self {
|
||||||
|
match o {
|
||||||
|
OrderKind::Limit => Self::Limit,
|
||||||
|
OrderKind::ExchangeLimit => Self::ExchangeLimit,
|
||||||
|
OrderKind::Market => Self::Market,
|
||||||
|
OrderKind::ExchangeMarket => Self::ExchangeMarket,
|
||||||
|
OrderKind::Stop => Self::Stop,
|
||||||
|
OrderKind::ExchangeStop => Self::ExchangeStop,
|
||||||
|
OrderKind::StopLimit => Self::StopLimit,
|
||||||
|
OrderKind::ExchangeStopLimit => Self::ExchangeStopLimit,
|
||||||
|
OrderKind::TrailingStop => Self::TrailingStop,
|
||||||
|
OrderKind::Fok => Self::Fok,
|
||||||
|
OrderKind::ExchangeFok => Self::ExchangeFok,
|
||||||
|
OrderKind::Ioc => Self::Ioc,
|
||||||
|
OrderKind::ExchangeIoc => Self::ExchangeIoc,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<TradingPairTicker> for PriceTicker {
|
impl From<TradingPairTicker> for PriceTicker {
|
||||||
fn from(t: TradingPairTicker) -> Self {
|
fn from(t: TradingPairTicker) -> Self {
|
||||||
Self {
|
Self {
|
||||||
@ -232,3 +244,29 @@ impl From<TradingPairTicker> for PriceTicker {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<bitfinex::orders::ActiveOrder> for ExecutedOrder {
|
||||||
|
fn from(o: ActiveOrder) -> Self {
|
||||||
|
Self {
|
||||||
|
id: o.id,
|
||||||
|
group_id: o.group_id,
|
||||||
|
client_id: o.client_id,
|
||||||
|
symbol: o.symbol,
|
||||||
|
creation_timestamp: o.creation_timestamp,
|
||||||
|
update_timestamp: o.update_timestamp,
|
||||||
|
amount: o.amount,
|
||||||
|
amount_original: o.amount_original,
|
||||||
|
order_type: o.order_type,
|
||||||
|
previous_order_type: o.previous_order_type,
|
||||||
|
flags: o.flags,
|
||||||
|
order_status: o.order_status,
|
||||||
|
price: o.price,
|
||||||
|
price_avg: o.price_avg,
|
||||||
|
price_trailing: o.price_trailing,
|
||||||
|
price_aux_limit: o.price_aux_limit,
|
||||||
|
notify: o.notify,
|
||||||
|
hidden: o.hidden,
|
||||||
|
placed_id: o.placed_id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -3,12 +3,12 @@ use std::future::Future;
|
|||||||
|
|
||||||
use tokio::task::JoinHandle;
|
use tokio::task::JoinHandle;
|
||||||
|
|
||||||
use crate::managers::PriceManager;
|
use crate::managers::{OrderManager, PositionManager, PriceManager};
|
||||||
use crate::models::{Position, PositionProfitState};
|
use crate::models::{Position, PositionProfitState};
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
|
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||||
pub enum SignalKind {
|
pub enum SignalKind {
|
||||||
ClosePosition { position_id: u64 },
|
ClosePosition(Position),
|
||||||
OpenPosition,
|
OpenPosition,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,18 +74,19 @@ impl Event {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct EventDispatcher {
|
pub struct Dispatcher {
|
||||||
event_handlers: HashMap<EventKind, Vec<Box<dyn Fn(&Event, &PriceManager) -> JoinHandle<()>>>>,
|
event_handlers: HashMap<EventKind, Vec<Box<dyn Fn(&Event, &PriceManager) -> JoinHandle<()>>>>,
|
||||||
profit_state_handlers:
|
profit_state_handlers:
|
||||||
HashMap<PositionProfitState, Vec<Box<dyn Fn(&Position, &PriceManager) -> JoinHandle<()>>>>,
|
HashMap<PositionProfitState, Vec<Box<dyn Fn(&Position, &PriceManager) -> JoinHandle<()>>>>,
|
||||||
signal_handlers: HashMap<SignalKind, Vec<Box<dyn Fn(&SignalKind) -> JoinHandle<()>>>>,
|
signal_handlers: HashMap<SignalKind, Vec<Box<dyn Fn(&SignalKind) -> JoinHandle<()>>>>,
|
||||||
|
|
||||||
on_any_event_handlers: Vec<Box<dyn Fn(&Event, &PriceManager) -> JoinHandle<()>>>,
|
on_any_event_handlers: Vec<Box<dyn Fn(&Event, &PriceManager) -> JoinHandle<()>>>,
|
||||||
on_any_profit_state_handlers: Vec<Box<dyn Fn(&Position, &PriceManager) -> JoinHandle<()>>>,
|
on_any_profit_state_handlers: Vec<Box<dyn Fn(&Position, &PriceManager) -> JoinHandle<()>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EventDispatcher {
|
impl Dispatcher {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
EventDispatcher {
|
Dispatcher {
|
||||||
event_handlers: HashMap::new(),
|
event_handlers: HashMap::new(),
|
||||||
profit_state_handlers: HashMap::new(),
|
profit_state_handlers: HashMap::new(),
|
||||||
signal_handlers: HashMap::new(),
|
signal_handlers: HashMap::new(),
|
||||||
|
@ -1,10 +1,14 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::ops::Neg;
|
||||||
|
|
||||||
|
use bitfinex::ticker::TradingPairTicker;
|
||||||
|
use log::error;
|
||||||
|
|
||||||
use crate::connectors::{Client, ExchangeKind};
|
use crate::connectors::{Client, ExchangeKind};
|
||||||
use crate::currency::SymbolPair;
|
use crate::currency::SymbolPair;
|
||||||
use crate::events::{Event, SignalKind};
|
use crate::events::{Dispatcher, Event, SignalKind};
|
||||||
use crate::models::{Order, OrderForm, Position, PriceTicker};
|
use crate::models::{ExecutedOrder, OrderForm, OrderKind, Position, PriceTicker};
|
||||||
use crate::strategy::PositionStrategy;
|
use crate::strategy::{FastOrderStrategy, OrderStrategy, PositionStrategy};
|
||||||
use crate::BoxError;
|
use crate::BoxError;
|
||||||
|
|
||||||
pub struct EventManager {
|
pub struct EventManager {
|
||||||
@ -43,34 +47,6 @@ impl PriceManager {
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
// pub fn add_position(&mut self, position: Position) {
|
|
||||||
// let (new_position, events, signals) = {
|
|
||||||
// match &self.strategy {
|
|
||||||
// Some(strategy) => strategy.on_new_tick(&position, &self),
|
|
||||||
// None => (position, vec![], vec![]),
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
//
|
|
||||||
// self.positions
|
|
||||||
// .entry(self.current_tick)
|
|
||||||
// .or_default()
|
|
||||||
// .push(new_position.clone());
|
|
||||||
//
|
|
||||||
// // calling position state callbacks
|
|
||||||
// self.dispatcher
|
|
||||||
// .call_position_state_handlers(&new_position, &self);
|
|
||||||
//
|
|
||||||
// // adding events and calling callbacks
|
|
||||||
// for e in events {
|
|
||||||
// self.add_event(e);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // adding signals to current tick vector
|
|
||||||
// for s in signals {
|
|
||||||
// self.add_signal(s);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// fn add_event(&mut self, event: Event) {
|
// fn add_event(&mut self, event: Event) {
|
||||||
// self.events.push(event);
|
// self.events.push(event);
|
||||||
//
|
//
|
||||||
@ -190,7 +166,7 @@ impl PositionManager {
|
|||||||
match &self.strategy {
|
match &self.strategy {
|
||||||
Some(strategy) => {
|
Some(strategy) => {
|
||||||
let (pos, strategy_events, _) =
|
let (pos, strategy_events, _) =
|
||||||
strategy.on_new_tick(&position, &self);
|
strategy.on_new_tick(position, &self);
|
||||||
|
|
||||||
events.extend(strategy_events);
|
events.extend(strategy_events);
|
||||||
|
|
||||||
@ -225,29 +201,81 @@ impl PositionManager {
|
|||||||
|
|
||||||
self.positions_history
|
self.positions_history
|
||||||
.get(&tick)
|
.get(&tick)
|
||||||
.filter(|x| x.position_id() == id)
|
.filter(|x| x.id() == id)
|
||||||
.and_then(|x| Some(x))
|
.and_then(|x| Some(x))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct OrderManager {
|
pub struct OrderManager {
|
||||||
|
tracked_positions: HashMap<u64, ExecutedOrder>,
|
||||||
pair: SymbolPair,
|
pair: SymbolPair,
|
||||||
open_orders: Vec<Order>,
|
open_orders: Vec<ExecutedOrder>,
|
||||||
client: Client,
|
client: Client,
|
||||||
|
strategy: Box<dyn OrderStrategy>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OrderManager {
|
impl OrderManager {
|
||||||
pub fn new(pair: SymbolPair, client: Client) -> Self {
|
const UNDERCUT_PERC: f64 = 0.005;
|
||||||
|
|
||||||
|
pub fn new(pair: SymbolPair, client: Client, strategy: Box<dyn OrderStrategy>) -> Self {
|
||||||
OrderManager {
|
OrderManager {
|
||||||
pair,
|
pair,
|
||||||
open_orders: Vec::new(),
|
open_orders: Vec::new(),
|
||||||
client,
|
client,
|
||||||
|
strategy,
|
||||||
|
tracked_positions: HashMap::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn close_position(&mut self, position: &Position) -> Result<(), BoxError> {
|
||||||
|
// checking if the position has an open order.
|
||||||
|
// If so, the strategy method is called, otherwise we open
|
||||||
|
// an undercut limit order at the best current price.
|
||||||
|
match self.tracked_positions.get(&position.id()) {
|
||||||
|
Some(open_order) => self.strategy.on_position_close(open_order, &self),
|
||||||
|
None => {
|
||||||
|
let current_prices = self.client.current_prices(&self.pair).await?;
|
||||||
|
let closing_price = self.best_closing_price(&position, ¤t_prices)?;
|
||||||
|
|
||||||
|
// submitting order
|
||||||
|
let order_form = OrderForm::new(
|
||||||
|
&self.pair,
|
||||||
|
closing_price,
|
||||||
|
position.amount().neg(),
|
||||||
|
OrderKind::Limit,
|
||||||
|
);
|
||||||
|
|
||||||
|
match self.client.submit_order(order_form).await {
|
||||||
|
Err(e) => error!("Could not submit order: {}", e),
|
||||||
|
Ok(o) => {
|
||||||
|
self.tracked_positions.insert(position.id(), o);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn update(&self) -> Option<Vec<Event>> {
|
pub fn update(&self) -> Option<Vec<Event>> {
|
||||||
unimplemented!()
|
unimplemented!()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn best_closing_price(
|
||||||
|
&self,
|
||||||
|
position: &Position,
|
||||||
|
price_ticker: &TradingPairTicker,
|
||||||
|
) -> Result<f64, BoxError> {
|
||||||
|
let price = {
|
||||||
|
if position.is_short() {
|
||||||
|
price_ticker.ask
|
||||||
|
} else {
|
||||||
|
price_ticker.bid
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(price * (1.0 - OrderManager::UNDERCUT_PERC))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ExchangeManager {
|
pub struct ExchangeManager {
|
||||||
@ -255,6 +283,7 @@ pub struct ExchangeManager {
|
|||||||
price_managers: Vec<PriceManager>,
|
price_managers: Vec<PriceManager>,
|
||||||
order_managers: Vec<OrderManager>,
|
order_managers: Vec<OrderManager>,
|
||||||
position_managers: Vec<PositionManager>,
|
position_managers: Vec<PositionManager>,
|
||||||
|
dispatcher: Dispatcher,
|
||||||
client: Client,
|
client: Client,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -268,7 +297,11 @@ impl ExchangeManager {
|
|||||||
|
|
||||||
for p in pairs {
|
for p in pairs {
|
||||||
position_managers.push(PositionManager::new(p.clone(), client.clone()));
|
position_managers.push(PositionManager::new(p.clone(), client.clone()));
|
||||||
order_managers.push(OrderManager::new(p.clone(), client.clone()));
|
order_managers.push(OrderManager::new(
|
||||||
|
p.clone(),
|
||||||
|
client.clone(),
|
||||||
|
Box::new(FastOrderStrategy {}),
|
||||||
|
));
|
||||||
price_managers.push(PriceManager::new(p.clone(), client.clone()));
|
price_managers.push(PriceManager::new(p.clone(), client.clone()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -278,6 +311,7 @@ impl ExchangeManager {
|
|||||||
order_managers,
|
order_managers,
|
||||||
price_managers,
|
price_managers,
|
||||||
client,
|
client,
|
||||||
|
dispatcher: Dispatcher::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
|
use chrono::{DateTime, TimeZone};
|
||||||
|
|
||||||
use crate::currency::SymbolPair;
|
use crate::currency::SymbolPair;
|
||||||
use crate::BoxError;
|
use crate::BoxError;
|
||||||
use chrono::{DateTime, TimeZone};
|
use std::hash::{Hash, Hasher};
|
||||||
use std::fmt::Display;
|
|
||||||
|
|
||||||
/***************
|
/***************
|
||||||
* Prices
|
* Prices
|
||||||
@ -26,7 +29,7 @@ pub struct PriceTicker {
|
|||||||
***************/
|
***************/
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Order {
|
pub struct ExecutedOrder {
|
||||||
pub id: i64,
|
pub id: i64,
|
||||||
pub group_id: Option<i32>,
|
pub group_id: Option<i32>,
|
||||||
pub client_id: i64,
|
pub client_id: i64,
|
||||||
@ -48,7 +51,13 @@ pub struct Order {
|
|||||||
pub placed_id: Option<i32>,
|
pub placed_id: Option<i32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug)]
|
impl Hash for ExecutedOrder {
|
||||||
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||||
|
state.write(&self.id.to_le_bytes())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, Hash)]
|
||||||
pub enum OrderKind {
|
pub enum OrderKind {
|
||||||
Limit,
|
Limit,
|
||||||
ExchangeLimit,
|
ExchangeLimit,
|
||||||
@ -73,11 +82,11 @@ pub struct OrderForm {
|
|||||||
/// EXCHANGE FOK, IOC, EXCHANGE IOC
|
/// EXCHANGE FOK, IOC, EXCHANGE IOC
|
||||||
kind: OrderKind,
|
kind: OrderKind,
|
||||||
/// Symbol for desired pair
|
/// Symbol for desired pair
|
||||||
symbol: SymbolPair,
|
pair: SymbolPair,
|
||||||
/// Price of order
|
/// Price of order
|
||||||
price: String,
|
price: f64,
|
||||||
/// Amount of order (positive for buy, negative for sell)
|
/// Amount of order (positive for buy, negative for sell)
|
||||||
amount: String,
|
amount: f64,
|
||||||
/// Set the leverage for a derivative order, supported by derivative symbol orders only.
|
/// Set the leverage for a derivative order, supported by derivative symbol orders only.
|
||||||
/// The value should be between 1 and 100 inclusive.
|
/// The value should be between 1 and 100 inclusive.
|
||||||
/// The field is optional, if omitted the default leverage value of 10 will be used.
|
/// The field is optional, if omitted the default leverage value of 10 will be used.
|
||||||
@ -91,12 +100,12 @@ pub struct OrderForm {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl OrderForm {
|
impl OrderForm {
|
||||||
pub fn new(symbol: &SymbolPair, price: f64, amount: f64, kind: OrderKind) -> Self {
|
pub fn new(pair: &SymbolPair, price: f64, amount: f64, kind: OrderKind) -> Self {
|
||||||
OrderForm {
|
OrderForm {
|
||||||
kind,
|
kind,
|
||||||
symbol: symbol.clone(),
|
pair: pair.clone(),
|
||||||
price: price.to_string(),
|
price,
|
||||||
amount: amount.to_string(),
|
amount,
|
||||||
leverage: None,
|
leverage: None,
|
||||||
price_trailing: None,
|
price_trailing: None,
|
||||||
price_aux_limit: None,
|
price_aux_limit: None,
|
||||||
@ -119,7 +128,7 @@ impl OrderForm {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn price_aux_limit(mut self, limit: f64) -> Result<Self, BoxError> {
|
pub fn with_price_aux_limit(mut self, limit: f64) -> Result<Self, BoxError> {
|
||||||
match self.kind {
|
match self.kind {
|
||||||
OrderKind::StopLimit | OrderKind::ExchangeStopLimit => {
|
OrderKind::StopLimit | OrderKind::ExchangeStopLimit => {
|
||||||
self.price_aux_limit = Some(limit.to_string());
|
self.price_aux_limit = Some(limit.to_string());
|
||||||
@ -136,13 +145,38 @@ impl OrderForm {
|
|||||||
self.tif = Some(tif.format("%Y-%m-%d %H:%M:%S").to_string());
|
self.tif = Some(tif.format("%Y-%m-%d %H:%M:%S").to_string());
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn kind(&self) -> OrderKind {
|
||||||
|
self.kind
|
||||||
|
}
|
||||||
|
pub fn pair(&self) -> &SymbolPair {
|
||||||
|
&self.pair
|
||||||
|
}
|
||||||
|
pub fn price(&self) -> &f64 {
|
||||||
|
&self.price
|
||||||
|
}
|
||||||
|
pub fn amount(&self) -> &f64 {
|
||||||
|
&self.amount
|
||||||
|
}
|
||||||
|
pub fn leverage(&self) -> Option<u32> {
|
||||||
|
self.leverage
|
||||||
|
}
|
||||||
|
pub fn price_trailing(&self) -> &Option<String> {
|
||||||
|
&self.price_trailing
|
||||||
|
}
|
||||||
|
pub fn price_aux_limit(&self) -> &Option<String> {
|
||||||
|
&self.price_aux_limit
|
||||||
|
}
|
||||||
|
pub fn tif(&self) -> &Option<String> {
|
||||||
|
&self.tif
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/***************
|
/***************
|
||||||
* Positions
|
* Positions
|
||||||
***************/
|
***************/
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Position {
|
pub struct Position {
|
||||||
pair: SymbolPair,
|
pair: SymbolPair,
|
||||||
state: PositionState,
|
state: PositionState,
|
||||||
@ -219,7 +253,7 @@ impl Position {
|
|||||||
pub fn price_liq(&self) -> f64 {
|
pub fn price_liq(&self) -> f64 {
|
||||||
self.price_liq
|
self.price_liq
|
||||||
}
|
}
|
||||||
pub fn position_id(&self) -> u64 {
|
pub fn id(&self) -> u64 {
|
||||||
self.position_id
|
self.position_id
|
||||||
}
|
}
|
||||||
pub fn profit_state(&self) -> Option<PositionProfitState> {
|
pub fn profit_state(&self) -> Option<PositionProfitState> {
|
||||||
@ -231,8 +265,27 @@ impl Position {
|
|||||||
pub fn creation_update(&self) -> Option<u64> {
|
pub fn creation_update(&self) -> Option<u64> {
|
||||||
self.creation_update
|
self.creation_update
|
||||||
}
|
}
|
||||||
|
pub fn is_short(&self) -> bool {
|
||||||
|
self.amount.is_sign_negative()
|
||||||
|
}
|
||||||
|
pub fn is_long(&self) -> bool {
|
||||||
|
self.amount.is_sign_positive()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Hash for Position {
|
||||||
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||||
|
state.write(&self.id().to_le_bytes())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for Position {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.id() == other.id()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Eq for Position {}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
|
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
|
||||||
pub enum PositionProfitState {
|
pub enum PositionProfitState {
|
||||||
Critical,
|
Critical,
|
||||||
|
@ -4,16 +4,20 @@ use std::fmt::{Debug, Formatter};
|
|||||||
use dyn_clone::DynClone;
|
use dyn_clone::DynClone;
|
||||||
|
|
||||||
use crate::events::{Event, EventKind, EventMetadata, SignalKind};
|
use crate::events::{Event, EventKind, EventMetadata, SignalKind};
|
||||||
use crate::managers::PositionManager;
|
use crate::managers::{OrderManager, PositionManager};
|
||||||
use crate::models::{Position, PositionProfitState};
|
use crate::models::{ExecutedOrder, OrderForm, Position, PositionProfitState};
|
||||||
|
|
||||||
|
/***************
|
||||||
|
* DEFINITIONS
|
||||||
|
***************/
|
||||||
|
|
||||||
pub trait PositionStrategy: DynClone {
|
pub trait PositionStrategy: DynClone {
|
||||||
fn name(&self) -> String;
|
fn name(&self) -> String;
|
||||||
fn on_new_tick(
|
fn on_new_tick(
|
||||||
&self,
|
&self,
|
||||||
position: &Position,
|
position: Position,
|
||||||
manager: &PositionManager,
|
manager: &PositionManager,
|
||||||
) -> (Position, Vec<Event>, Vec<SignalKind>);
|
) -> (Position, Vec<Event>, Option<OrderForm>);
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Debug for dyn PositionStrategy {
|
impl Debug for dyn PositionStrategy {
|
||||||
@ -22,6 +26,27 @@ impl Debug for dyn PositionStrategy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait OrderStrategy: DynClone {
|
||||||
|
/// The name of the strategy, used for debugging purposes
|
||||||
|
fn name(&self) -> String;
|
||||||
|
/// This method is called when the OrderManager checks the open orders on a new tick.
|
||||||
|
/// It should manage if some orders have to be closed or keep open.
|
||||||
|
fn on_update(&self);
|
||||||
|
/// This method is called when the OrderManager is requested to close
|
||||||
|
/// a position that has an open order associated to it.
|
||||||
|
fn on_position_close(&self, order: &ExecutedOrder, manager: &mut OrderManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for dyn OrderStrategy {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
|
||||||
|
write!(f, "{}", self.name())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/***************
|
||||||
|
* IMPLEMENTATIONS
|
||||||
|
***************/
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct TrailingStop {
|
pub struct TrailingStop {
|
||||||
stop_percentages: HashMap<u64, f64>,
|
stop_percentages: HashMap<u64, f64>,
|
||||||
@ -53,12 +78,13 @@ impl PositionStrategy for TrailingStop {
|
|||||||
|
|
||||||
fn on_new_tick(
|
fn on_new_tick(
|
||||||
&self,
|
&self,
|
||||||
position: &Position,
|
position: Position,
|
||||||
manager: &PositionManager,
|
manager: &PositionManager,
|
||||||
) -> (Position, Vec<Event>, Vec<SignalKind>) {
|
) -> (Position, Vec<Event>, Option<OrderForm>) {
|
||||||
let mut signals = vec![];
|
let mut signals = vec![];
|
||||||
let pl_perc = TrailingStop::net_pl_percentage(position.pl_perc(), TrailingStop::TAKER_FEE);
|
let pl_perc = TrailingStop::net_pl_percentage(position.pl_perc(), TrailingStop::TAKER_FEE);
|
||||||
let events = vec![];
|
let events = vec![];
|
||||||
|
let mut order = None;
|
||||||
|
|
||||||
let state = {
|
let state = {
|
||||||
if pl_perc > TrailingStop::GOOD_PROFIT_PERC {
|
if pl_perc > TrailingStop::GOOD_PROFIT_PERC {
|
||||||
@ -72,24 +98,22 @@ impl PositionStrategy for TrailingStop {
|
|||||||
} else if TrailingStop::MAX_LOSS_PERC < pl_perc && pl_perc < 0.0 {
|
} else if TrailingStop::MAX_LOSS_PERC < pl_perc && pl_perc < 0.0 {
|
||||||
PositionProfitState::Loss
|
PositionProfitState::Loss
|
||||||
} else {
|
} else {
|
||||||
signals.push(SignalKind::ClosePosition {
|
signals.push(SignalKind::ClosePosition(position.clone()));
|
||||||
position_id: position.position_id(),
|
|
||||||
});
|
|
||||||
PositionProfitState::Critical
|
PositionProfitState::Critical
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let opt_pre_pw = manager.position_previous_tick(position.position_id(), None);
|
let opt_pre_pw = manager.position_previous_tick(position.id(), None);
|
||||||
let event_metadata = EventMetadata::new(Some(position.position_id()), None);
|
let event_metadata = EventMetadata::new(Some(position.id()), None);
|
||||||
let new_position = position.clone().with_profit_state(Some(state));
|
let new_position = position.clone().with_profit_state(Some(state));
|
||||||
|
|
||||||
match opt_pre_pw {
|
match opt_pre_pw {
|
||||||
Some(prev) => {
|
Some(prev) => {
|
||||||
if prev.profit_state() == Some(state) {
|
if prev.profit_state() == Some(state) {
|
||||||
return (new_position, events, signals);
|
return (new_position, events, order);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => return (new_position, events, signals),
|
None => return (new_position, events, order),
|
||||||
};
|
};
|
||||||
|
|
||||||
let events = {
|
let events = {
|
||||||
@ -130,6 +154,23 @@ impl PositionStrategy for TrailingStop {
|
|||||||
events
|
events
|
||||||
};
|
};
|
||||||
|
|
||||||
return (new_position, events, signals);
|
return (new_position, events, order);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct FastOrderStrategy {}
|
||||||
|
|
||||||
|
impl OrderStrategy for FastOrderStrategy {
|
||||||
|
fn name(&self) -> String {
|
||||||
|
"Fast order strategy".into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_update(&self) {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_position_close(&self, order: &ExecutedOrder, manager: &mut OrderManager) {
|
||||||
|
unimplemented!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user