positions with strategy working
This commit is contained in:
parent
ae648b70a4
commit
0e2673837c
7
rustybot/Cargo.lock
generated
7
rustybot/Cargo.lock
generated
@ -223,6 +223,12 @@ dependencies = [
|
||||
"generic-array 0.14.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dyn-clone"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee2626afccd7561a06cf1367e2950c4718ea04565e20fb5029b6c7d8ad09abcf"
|
||||
|
||||
[[package]]
|
||||
name = "encoding_rs"
|
||||
version = "0.8.26"
|
||||
@ -1056,6 +1062,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"bitfinex",
|
||||
"dyn-clone",
|
||||
"futures-util",
|
||||
"regex",
|
||||
"tokio 0.2.24",
|
||||
|
@ -12,4 +12,5 @@ tokio = { version = "0.2", features=["full"]}
|
||||
tokio-tungstenite = "*"
|
||||
futures-util = { version = "0.3", default-features = false, features = ["async-await", "sink", "std"] }
|
||||
async-trait = "0.1"
|
||||
regex = "1"
|
||||
regex = "1"
|
||||
dyn-clone = "1"
|
@ -9,17 +9,16 @@ use tokio::time::delay_for;
|
||||
use crate::connectors::Connector;
|
||||
use crate::currency::{Symbol, SymbolPair};
|
||||
use crate::pairs::PairStatus;
|
||||
use crate::strategy::Strategy;
|
||||
use crate::ticker::Ticker;
|
||||
use crate::BoxError;
|
||||
|
||||
pub struct BfxBot<'a> {
|
||||
connector: Box<dyn Connector + 'a>,
|
||||
ticker: Ticker,
|
||||
pair_status: Vec<PairStatus>,
|
||||
pair_statuses: Vec<PairStatus<'a>>,
|
||||
quote: Symbol,
|
||||
trading_symbols: Vec<Symbol>,
|
||||
// account_info: String,
|
||||
// ledger: String,
|
||||
}
|
||||
|
||||
impl<'a> BfxBot<'a> {
|
||||
@ -32,18 +31,24 @@ impl<'a> BfxBot<'a> {
|
||||
BfxBot {
|
||||
connector: Box::new(connector),
|
||||
ticker: Ticker::new(tick_duration),
|
||||
pair_status: trading_symbols
|
||||
pair_statuses: trading_symbols
|
||||
.iter()
|
||||
.map(|x| SymbolPair::new(quote.clone(), x.clone()))
|
||||
.map(|x| PairStatus::new(x, 1, None))
|
||||
.collect(),
|
||||
quote,
|
||||
// account_info: String::new(),
|
||||
// ledger: String::new(),
|
||||
trading_symbols,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_strategy(mut self, strategy: Box<dyn Strategy>) -> Self {
|
||||
self.pair_statuses
|
||||
.iter_mut()
|
||||
.for_each(|x| x.set_strategy(dyn_clone::clone_box(&*strategy)));
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
pub async fn current_prices(&self, symbol: Symbol) -> Result<TradingPairTicker, BoxError> {
|
||||
let trading_pair = SymbolPair::new(self.quote.clone(), symbol.clone());
|
||||
|
||||
@ -54,17 +59,35 @@ impl<'a> BfxBot<'a> {
|
||||
self.connector.current_prices(&trading_pair).await
|
||||
}
|
||||
|
||||
pub async fn update(&mut self) {
|
||||
println!("Updating...");
|
||||
delay_for(self.ticker.duration()).await;
|
||||
self.ticker.inc();
|
||||
// self.update_pairs().await;
|
||||
println!("Done!");
|
||||
pub async fn start_loop(&mut self) -> Result<(), BoxError> {
|
||||
if let Err(e) = self.update_pair_statuses().await {
|
||||
println!("Error while updating pairs at first start: {}", e);
|
||||
}
|
||||
|
||||
loop {
|
||||
self.update().await;
|
||||
}
|
||||
}
|
||||
|
||||
// async fn update_pairs(&mut self) {
|
||||
// let active_positions = self.bfx.positions.active_positions().await?;
|
||||
//
|
||||
// for p in active_positions {}
|
||||
// }
|
||||
async fn update(&mut self) {
|
||||
delay_for(self.ticker.duration()).await;
|
||||
self.ticker.inc();
|
||||
|
||||
if let Err(e) = self.update_pair_statuses().await {
|
||||
println!("Error while updating pairs: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
async fn update_pair_statuses(&mut self) -> Result<(), BoxError> {
|
||||
for status in &mut self.pair_statuses {
|
||||
// add positions for each pair
|
||||
self.connector
|
||||
.active_positions(status.pair())
|
||||
.await?
|
||||
.into_iter()
|
||||
.for_each(|x| status.add_position(x));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ use regex::Regex;
|
||||
|
||||
use crate::BoxError;
|
||||
|
||||
#[derive(Clone, PartialEq, Hash)]
|
||||
#[derive(Clone, PartialEq, Hash, Debug, Eq)]
|
||||
pub struct Symbol {
|
||||
name: Cow<'static, str>,
|
||||
}
|
||||
@ -26,6 +26,8 @@ impl Symbol {
|
||||
pub const BTC: Symbol = Symbol::new_static("BTC");
|
||||
pub const ETH: Symbol = Symbol::new_static("ETH");
|
||||
pub const LTC: Symbol = Symbol::new_static("LTC");
|
||||
pub const TESTBTC: Symbol = Symbol::new_static("TESTBTC");
|
||||
pub const TESTUSD: Symbol = Symbol::new_static("TESTUSD");
|
||||
pub const USD: Symbol = Symbol::new_static("USD");
|
||||
pub const GBP: Symbol = Symbol::new_static("GBP");
|
||||
pub const EUR: Symbol = Symbol::new_static("EUR");
|
||||
@ -53,7 +55,7 @@ impl Display for Symbol {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct SymbolPair {
|
||||
quote: Symbol,
|
||||
base: Symbol,
|
||||
@ -79,7 +81,7 @@ impl SymbolPair {
|
||||
|
||||
impl Into<String> for SymbolPair {
|
||||
fn into(self) -> String {
|
||||
format!("{}{}", self.base, self.quote)
|
||||
format!("{}/{}", self.base, self.quote)
|
||||
}
|
||||
}
|
||||
|
||||
@ -87,7 +89,7 @@ impl TryFrom<&str> for SymbolPair {
|
||||
type Error = BoxError;
|
||||
|
||||
fn try_from(value: &str) -> Result<Self, Self::Error> {
|
||||
const REGEX: &str = r"^[t|f](?P<base>\w{3,4}):?(?P<quote>\w{3,4})";
|
||||
const REGEX: &str = r"^[t|f](?P<base>\w{3,7}):?(?P<quote>\w{3,7})";
|
||||
|
||||
let captures = Regex::new(REGEX)?.captures(&value).ok_or("Invalid input")?;
|
||||
let quote = captures.name("quote").ok_or("Quote not found")?.as_str();
|
||||
@ -102,7 +104,7 @@ impl TryFrom<&str> for SymbolPair {
|
||||
|
||||
impl Display for SymbolPair {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}{}", self.base, self.quote)
|
||||
write!(f, "{}/{}", self.base, self.quote)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -8,13 +8,13 @@ use crate::pairs::PairStatus;
|
||||
use crate::positions::{Position, PositionProfitState, PositionState};
|
||||
use crate::BoxError;
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum SignalKind {
|
||||
ClosePosition,
|
||||
OpenPosition,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct EventMetadata {
|
||||
position_id: Option<u64>,
|
||||
order_id: Option<u64>,
|
||||
@ -29,7 +29,7 @@ impl EventMetadata {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
|
||||
pub enum EventKind {
|
||||
NewMinimum,
|
||||
NewMaximum,
|
||||
@ -45,7 +45,7 @@ pub enum EventKind {
|
||||
Any,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct Event {
|
||||
kind: EventKind,
|
||||
tick: u64,
|
||||
|
@ -1,7 +1,9 @@
|
||||
use tokio::time::{delay_for, Duration};
|
||||
|
||||
use crate::bot::BfxBot;
|
||||
use crate::connectors::BfxWrapper;
|
||||
use crate::currency::{Symbol, SymbolPair};
|
||||
use crate::strategy::TrailingStop;
|
||||
|
||||
mod bot;
|
||||
mod connectors;
|
||||
@ -17,24 +19,17 @@ pub type BoxError = Box<dyn std::error::Error + Send + Sync>;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), BoxError> {
|
||||
// let test_api_key = "P1EVE68DJByDAkGQvpIkTwfrbYXd2Vo2ZaIhTYb9vx2";
|
||||
// let test_api_secret = "1nicg8z0zKVEt5Rb7ZDpIYjVYVTgvCaCPMZqB0niFli";
|
||||
let test_api_key = "P1EVE68DJByDAkGQvpIkTwfrbYXd2Vo2ZaIhTYb9vx2";
|
||||
let test_api_secret = "1nicg8z0zKVEt5Rb7ZDpIYjVYVTgvCaCPMZqB0niFli";
|
||||
//
|
||||
// let mut bot = BfxBot::new(
|
||||
// test_api_key,
|
||||
// test_api_secret,
|
||||
// vec![Symbol::BTC, Symbol::ETH, Symbol::XMR],
|
||||
// Symbol::USD,
|
||||
// Duration::new(20, 0),
|
||||
// );
|
||||
//
|
||||
// loop {
|
||||
// let ticker = bot.current_prices("ETH".into()).await?;
|
||||
// bot.update().await;
|
||||
//
|
||||
// // let ticker = bot.current_prices("ETH".into()).await?;
|
||||
// println!("{:?}", ticker);
|
||||
// }
|
||||
let bfx = BfxWrapper::new(test_api_key, test_api_secret);
|
||||
let mut bot = BfxBot::new(
|
||||
bfx,
|
||||
vec![Symbol::TESTBTC],
|
||||
Symbol::TESTUSD,
|
||||
Duration::new(20, 0),
|
||||
)
|
||||
.with_strategy(Box::new(TrailingStop::new()));
|
||||
|
||||
Ok(())
|
||||
Ok(bot.start_loop().await?)
|
||||
}
|
||||
|
@ -1,12 +1,12 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::currency::SymbolPair;
|
||||
use crate::events::{Event, EventDispatcher};
|
||||
use crate::events::{Event, EventDispatcher, SignalKind};
|
||||
use crate::orders::Order;
|
||||
use crate::positions::Position;
|
||||
use crate::strategy::Strategy;
|
||||
|
||||
pub struct PairStatus {
|
||||
pub struct PairStatus<'a> {
|
||||
pair: SymbolPair,
|
||||
dispatcher: EventDispatcher,
|
||||
prices: HashMap<u64, f64>,
|
||||
@ -14,10 +14,11 @@ pub struct PairStatus {
|
||||
orders: HashMap<u64, Vec<Order>>,
|
||||
positions: HashMap<u64, Vec<Position>>,
|
||||
current_tick: u64,
|
||||
strategy: Option<Box<dyn Strategy>>,
|
||||
strategy: Option<Box<dyn Strategy + 'a>>,
|
||||
signals: HashMap<u64, SignalKind>,
|
||||
}
|
||||
|
||||
impl PairStatus {
|
||||
impl<'a> PairStatus<'a> {
|
||||
pub fn new(pair: SymbolPair, current_tick: u64, strategy: Option<Box<dyn Strategy>>) -> Self {
|
||||
PairStatus {
|
||||
pair,
|
||||
@ -25,52 +26,82 @@ impl PairStatus {
|
||||
prices: HashMap::new(),
|
||||
events: Vec::new(),
|
||||
positions: HashMap::new(),
|
||||
orders: HashMap::new(),
|
||||
signals: HashMap::new(),
|
||||
current_tick,
|
||||
strategy,
|
||||
orders: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_position(&mut self, position: Position) {
|
||||
let (pw, events, signals) = {
|
||||
let (new_position, events, signals) = {
|
||||
match &self.strategy {
|
||||
Some(strategy) => strategy.position_on_new_tick(&position, &self),
|
||||
None => (
|
||||
position,
|
||||
// PositionWrapper::new(position.clone(), position.pl(), position.pl_perc(), None),
|
||||
vec![],
|
||||
vec![],
|
||||
),
|
||||
None => (position, vec![], vec![]),
|
||||
}
|
||||
};
|
||||
|
||||
self.positions
|
||||
.entry(self.current_tick)
|
||||
.or_default()
|
||||
.push(pw.clone());
|
||||
.push(new_position.clone());
|
||||
|
||||
// calling position state callbacks
|
||||
self.dispatcher.call_position_state_handlers(&pw, &self);
|
||||
self.dispatcher
|
||||
.call_position_state_handlers(&new_position, &self);
|
||||
|
||||
// adding events and calling callbacks
|
||||
for e in events {
|
||||
self.add_event(e);
|
||||
}
|
||||
|
||||
// adding signals to current tick vector
|
||||
for s in signals {
|
||||
self.add_signal(s);
|
||||
}
|
||||
|
||||
println!(
|
||||
"EVENTS: {:?} | SIGNALS: {:?} | POSITION: {:?}",
|
||||
self.events, self.signals, new_position
|
||||
);
|
||||
}
|
||||
|
||||
pub fn add_event(&mut self, event: Event) {
|
||||
fn add_event(&mut self, event: Event) {
|
||||
self.events.push(event);
|
||||
|
||||
self.dispatcher.call_event_handlers(&event, &self);
|
||||
}
|
||||
|
||||
fn add_signal(&mut self, signal: SignalKind) {
|
||||
self.signals.insert(self.current_tick(), signal);
|
||||
}
|
||||
|
||||
pub fn current_tick(&self) -> u64 {
|
||||
self.current_tick
|
||||
}
|
||||
|
||||
pub fn previous_pw(&self, id: u64) -> Option<&Position> {
|
||||
pub fn set_strategy(&mut self, strategy: Box<dyn Strategy + 'a>) {
|
||||
self.strategy = Some(strategy);
|
||||
}
|
||||
|
||||
pub fn pair(&self) -> &SymbolPair {
|
||||
&self.pair
|
||||
}
|
||||
|
||||
pub fn position_previous_tick(&self, id: u64, tick: Option<u64>) -> Option<&Position> {
|
||||
let tick = match tick {
|
||||
Some(tick) => {
|
||||
if tick < 1 {
|
||||
1
|
||||
} else {
|
||||
tick
|
||||
}
|
||||
}
|
||||
None => self.current_tick() - 1,
|
||||
};
|
||||
|
||||
self.positions
|
||||
.get(&(self.current_tick - 1))
|
||||
.get(&tick)
|
||||
.and_then(|x| x.iter().find(|x| x.position_id() == id))
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
use crate::currency::{Symbol, SymbolPair};
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Position {
|
||||
pair: SymbolPair,
|
||||
state: PositionState,
|
||||
@ -91,7 +91,7 @@ impl Position {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
|
||||
pub enum PositionProfitState {
|
||||
Critical,
|
||||
Loss,
|
||||
@ -111,7 +111,7 @@ impl PositionProfitState {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
|
||||
pub enum PositionState {
|
||||
Closed,
|
||||
Open,
|
||||
|
@ -3,8 +3,9 @@ use std::collections::HashMap;
|
||||
use crate::events::{Event, EventKind, EventMetadata, SignalKind};
|
||||
use crate::pairs::PairStatus;
|
||||
use crate::positions::{Position, PositionProfitState, PositionState};
|
||||
use dyn_clone::DynClone;
|
||||
|
||||
pub trait Strategy {
|
||||
pub trait Strategy: DynClone {
|
||||
fn position_on_new_tick(
|
||||
&self,
|
||||
position: &Position,
|
||||
@ -12,7 +13,8 @@ pub trait Strategy {
|
||||
) -> (Position, Vec<Event>, Vec<SignalKind>);
|
||||
}
|
||||
|
||||
struct TrailingStop {
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct TrailingStop {
|
||||
stop_percentages: HashMap<u64, f64>,
|
||||
}
|
||||
|
||||
@ -62,18 +64,17 @@ impl Strategy for TrailingStop {
|
||||
}
|
||||
};
|
||||
|
||||
let opt_pre_pw = status.previous_pw(position.position_id());
|
||||
let opt_pre_pw = status.position_previous_tick(position.position_id(), None);
|
||||
let event_metadata = EventMetadata::new(Some(position.position_id()), None);
|
||||
// let pw = PositionWrapper::new(position.clone(), position.pl(), pl_perc, Some(state));
|
||||
let pw = position.clone();
|
||||
let new_position = position.clone().with_profit_state(Some(state));
|
||||
|
||||
match opt_pre_pw {
|
||||
Some(prev) => {
|
||||
if prev.profit_state() == Some(state) {
|
||||
return (pw, events, signals);
|
||||
return (new_position, events, signals);
|
||||
}
|
||||
}
|
||||
None => return (pw, events, signals),
|
||||
None => return (new_position, events, signals),
|
||||
};
|
||||
|
||||
let events = {
|
||||
@ -114,6 +115,6 @@ impl Strategy for TrailingStop {
|
||||
events
|
||||
};
|
||||
|
||||
return (pw, events, signals);
|
||||
return (new_position, events, signals);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user