:warning: This is a personal project, use a your own risk.
:warning: The results may not represent real trading results on any given exchange.
lfest-rs is a blazingly fast simulated exchange capable of leveraged positions. It gets fed external data either as a trade or a candle to update the internal state and check for order execution. For simplicity's sake (and performance) the exchange does not use an order book
The supported order types are: - market - aggressively execute against the best bid / ask - limit - passively place an order into the orderbook - stop_market - A protective but aggressive market order which is triggered at a specific price
To use raw trade data to update the exchanges state, the Trade struct
from trade-aggregation-rs
is used.
Each data point must have the following fields:
rust
/// Defines a taker trade
pub struct Trade {
/// Timestamp usually denoted in milliseconds
pub timestamp: i64,
/// Price of the asset
pub price: f64,
/// Size of the trade denoted in QUOTE currency
/// negative values indicate a taker Sell order
pub size: f64,
}
When constructing the Trade struct pay careful attention to have a timestamp in milliseconds
and a size denoted in QUOTE currency. Sell taker trades have a negative size.
Then, update exchange state using consume_trade(&trade) method.
To use candle data to update the exchange state, the Candle struct
from trade-aggregation-rs
is used.
To aggregate candles from a &Vecrust
// aggregate candles using a volume threshold of 100 BTC, therefore By::Base is used
let mut aggregator = VolumeAggregator::new(100.0, By::Base);
let candles = aggregate_all_trades(&trades, &mut aggregator);
Use exchange.consume_candle(&candle) method to update the exchanges state
The following performance metrics are available through AccTracker struct: - winratio - profitlossratio - totalrpnl - sharpe - sharpedailyreturns - sortino - cumulative fees - sharpesterlingratio - maxdrawdown - maxupnldrawdown - numtrades - turnover - tradepercentage - buyratio - limitorderfillratio - limitordercancellationratio
To use this crate in your project, add the following to your Cargo.toml:
[dependencies]
lfest = "0.4.5
Then proceed to use it in your code.
The following example uses trades to update the bid and ask price of the exchange.
See examples folder for all the examples.
Run the following example using:
shell script
cargo run --example use_trades --release
```rust //! Example usage of Exchange using external trade data. //! A randomly acting agent places market buy / sell orders every 100 candles
mod load_trades;
extern crate log;
use lfest::{Config, Exchange, Order, Side, OrderError}; use loadtrades::loadtradesfromcsv; use rand::{thread_rng, Rng}; use std::time::Instant;
fn main() { let t0 = Instant::now();
let config = Config {
fee_maker: -0.00025,
fee_taker: 0.001,
starting_balance_base: 1.0,
use_candles: false,
leverage: 1.0,
};
let mut exchange = Exchange::new(config);
// load trades from csv file
let trades = load_trades_from_csv("./data/Bitmex_XBTUSD_1M.csv").unwrap();
// use random action every 100 trades to buy or sell
let mut rng = thread_rng();
for (i, t) in trades.iter().enumerate() {
let liq = exchange.consume_trade(t);
if liq {
println!(
"position liquidated, \
but there could still be enough wallet_balance to open a new position"
);
}
if i % 100 == 0 {
// randomly buy or sell using a market order
let r = rng.gen::<f64>();
// Trade a fraction of the available wallet balance
let order_size: f64 = exchange.margin().wallet_balance() * 0.1;
let order: Order = if r > 0.5 {
Order::market(Side::Sell, order_size) // Sell using market order
} else {
Order::market(Side::Buy, order_size) // Buy using market order
};
// Handle order error here if needed
let response: Result<Order, OrderError> = exchange.submit_order(order);
match response {
Ok(order) => println!("succesfully submitted order: {:?}", order),
Err(order_err) => match order_err {
OrderError::MaxActiveOrders => error!("maximum number of active orders reached"),
OrderError::InvalidLimitPrice => error!("invalid limit price of order"),
OrderError::InvalidTriggerPrice => error!("invalid trigger price of order"),
OrderError::InvalidOrderSize => error!("invalid order size"),
OrderError::NotEnoughAvailableBalance => error!("not enough available balance in account"),
}
}
}
}
println!(
"time to simulate 1 million historical trades and {} orders: {}ms",
exchange.acc_tracker().num_trades(),
t0.elapsed().as_millis()
);
analyze_results(&exchange);
}
/// analyze the resulting performance metrics of the traded orders fn analyzeresults(e: &Exchange) { let winratio = e.acctracker().winratio(); let profitlossratio = e.acctracker().profitlossratio(); let rpnl = e.acctracker().totalrpnl(); let sharpe = e.acctracker().sharpe(); let sortino = e.acctracker().sortino(); let sterlingratio = e.acctracker().sharpesterlingratio(); let maxdrawdown = e.acctracker().maxdrawdown(); let maxupnldrawdown = e.acctracker().maxupnldrawdown(); let numtrades = e.acctracker().numtrades(); let turnover = e.acctracker().turnover(); let buyratio = e.acctracker().buyratio(); println!("winratio: {:.2}, profitlossratio: {:.2}, rpnl: {:.2}, sharpe: {:.2}, sortino: {:.2}, sr: {:.2}, \ dd: {:.2}, upnldd: {:.2}, #trades: {}, turnover: {}, buyratio: {:.2},", winratio, profitlossratio, rpnl, sharpe, sortino, sterlingratio, maxdrawdown, maxupnldrawdown, numtrades, turnover, buyratio, ); } ```
See the example use_trades.rs and compile in release mode to see that the Exchange is capable of simulating 1 million historical trades and executing ~40k market orders in ~470ms.
A non trivial dependency is trade_aggregation as the exchange relies on the Trade and Candle struct.
If you find a bug or would like to help out, feel free to create a pull-request.
I you would like to support the development of this crate, feel free to send over a donation:
Monero (XMR) address:
plain
47xMvxNKsCKMt2owkDuN1Bci2KMiqGrAFCQFSLijWLs49ua67222Wu3LZryyopDVPYgYmAnYkSZSz9ZW2buaDwdyKTWGwwb
Copyright (C) 2020
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License along with this program. If not, see https://www.gnu.org/licenses/.