: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 It also cross-leverages positions so the whole margin balance is used as collateral.
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
Currently limit and stopmarket orders may not work as expected. I advise you to only use market orders or fix limit and stoporder margin calculation and create a pull request.
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
Some of these metric may behave differently from what you would expect, so make sure to take a look at the code.
To use this crate in your project, add the following to your Cargo.toml:
[dependencies]
lfest = "^0.7.0"
Then proceed to use it in your code.
Here is a basic example: ```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, OrderError, Side}; use loadtrades::loadpricesfromcsv; 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 prices = load_prices_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, p) in prices.iter().enumerate() {
let (exec_orders, liq) = exchange.update_state(*p, *p, i as u64);
if liq {
// check liquidation
}
println!("executed orders: {:?}", exec_orders);
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.account().margin().wallet_balance() * 0.1;
let order: Order = if r > 0.5 {
Order::market(Side::Sell, order_size).unwrap() // Sell using market order
} else {
Order::market(Side::Buy, order_size).unwrap() // 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.account().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.account().acctracker().winratio(); let profitlossratio = e.account().acctracker().profitlossratio(); let rpnl = e.account().acctracker().totalrpnl(); let sharpe = e.account().acctracker().sharpe(); let sortino = e.account().acctracker().sortino(); let sterlingratio = e.account().acctracker().sharpesterlingratio(); let maxdrawdown = e.account().acctracker().maxdrawdown(); let maxupnldrawdown = e.account().acctracker().maxupnldrawdown(); let numtrades = e.account().acctracker().numtrades(); let turnover = e.account().acctracker().turnover(); let buyratio = e.account().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, ); } ```
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/.