Leveraged Futures Exchange for Simulated Trading (LFEST)

: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.

Order Types

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.

Performance Metrics:

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.

How to use

To use this crate in your project, add the following to your Cargo.toml: [dependencies] lfest = "0.8.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;

[macro_use]

extern crate log;

use lfest::{Config, Exchange, FuturesType, Order, OrderError, Side}; use loadtrades::loadpricesfromcsv; use rand::{thread_rng, Rng}; use std::time::Instant;

fn main() { let t0 = Instant::now();

let config = Config { feemaker: -0.00025, feetaker: 0.001, startingbalance: 1.0, leverage: 1.0, futurestype: FuturesType::Inverse, }; let mut exchange = Exchange::new(config);

// load trades from csv file let prices = loadpricesfromcsv("./data/BitmexXBTUSD_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 (execorders, liq) = exchange.updatestate(*p, *p, i as u64, *p, *p); 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::(); // Trade a fraction of the available wallet balance let ordersize: f64 = exchange.account().margin().walletbalance() * 0.1; let order: Order = if r > 0.5 { Order::market(Side::Sell, ordersize).unwrap() // Sell using market order } else { Order::market(Side::Buy, ordersize).unwrap() // Buy using market order }; // Handle order error here if needed let response: Result = exchange.submitorder(order); match response { Ok(order) => println!("succesfully submitted order: {:?}", order), Err(ordererr) => match ordererr { 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().acctracker().numtrades(), t0.elapsed().asmillis() ); 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, ); } ```

TODOs:

Contributions

If you find a bug or would like to help out, feel free to create a pull-request.

Donations :moneybag: :moneywithwings:

I you would like to support the development of this crate, feel free to send over a donation:

Monero (XMR) address: plain 47xMvxNKsCKMt2owkDuN1Bci2KMiqGrAFCQFSLijWLs49ua67222Wu3LZryyopDVPYgYmAnYkSZSz9ZW2buaDwdyKTWGwwb

monero

License

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/.

GNU AGPLv3