trading-maid 1.0.2

A high-fidelity crypto futures backtesting and live trading framework with matching, margin, leverage and liquidation simulation.
Documentation
use std::sync::Arc;
use trading_maid::prelude::*;

struct MyStrategy {
    ema_cache144: EMACache,
    ema_cache169: EMACache,
    count: usize,
}

impl MyStrategy {
    pub fn new() -> Self {
        MyStrategy {
            ema_cache144: EMACache::with_ema(144, 80871.2),
            ema_cache169: EMACache::with_ema(169, 78705.2),
            count: 0,
        }
    }
}

#[async_trait(?Send)]
impl Strategy for MyStrategy {
    // 如果连续 50 根 K 线收盘价都在 EMA 之下,且当前 K 线收盘价突破 EMA,则开空单
    async fn next(&mut self, cx: &Context) -> anyhow::Result<()> {
        let ema144 = self.ema_cache144.update(cx.close);
        let ema169 = self.ema_cache169.update(cx.close);

        if self.count >= 50
            && (cx.close >= ema144 || cx.close >= ema169)
            && cx.get_position("BTCUSDT").await?.is_none()
        {
            println!("place_order: {}", t2s(cx.time));

            cx.cancel_all_order("BTCUSDT").await?;
            cx.sell("BTCUSDT", 0.01).await?;
            cx.buy_limit_reduce_only("BTCUSDT", cx.close - 1000.0, 0.01)
                .await?;
            cx.buy_trigger_market_reduce_only("BTCUSDT", cx.close + 1000.0, 0.01)
                .await?;
        }

        if cx.close <= ema144 && cx.close <= ema169 {
            self.count += 1;
        } else {
            self.count = 0;
        }

        Ok(())
    }
}

async fn my_hook(_: KLine, exchange: Arc<dyn Exchange + 'static>) -> anyhow::Result<()> {
    if let Some(v) = exchange
        .get_history_order_list("BTCUSDT")
        .await?
        .iter()
        .find(|v| v.status == Status::Rejected || v.kind == Kind::Liquidation)
    {
        anyhow::bail!(
            "rejected/liquidation {}: cash: {}",
            t2s(v.update_time),
            exchange.get_cash().await?
        );
    }

    Ok(())
}

#[tokio::main]
async fn main() {
    let path = get_or_download("BTCUSDT/1m", 12).await.unwrap();

    let data_source_1m = DataSource::from_file_metadata(
        path,
        Metadata {
            symbol: "BTCUSDT".to_string(),
            level: Level::Minute1,
            min_size: 0.01,
            min_notional: 0.0,
            tick_size: 0.1,
            maker_fee: 0.0002,
            taker_fee: 0.0005,
            maintenance: 0.004,
        },
    )
    .unwrap();

    let exchange = LocalExchange::new(data_source_1m.clone())
        .cash(10000.0)
        .leverage(10)
        .slippage(0.0);

    let mut engine = Engine::new(exchange.clone(), MyStrategy::new());

    engine.hook(my_hook);

    if let Err(v) = engine.run("BTCUSDT", Level::Minute5).await {
        println!("{:#?}", v);
    }

    let history_position = exchange.get_history_position_list("BTCUSDT").await.unwrap();
    let history_order = exchange.get_history_order_list("BTCUSDT").await.unwrap();
    let summary = summarize(&history_position);

    println!("history summary: {:#?}", summary);

    let data_source_5m = data_source_1m.resample(Level::Minute5).unwrap();
    let data_source_1h = data_source_1m.resample(Level::Hour1).unwrap();

    open_in_browser(
        [data_source_5m, data_source_1m, data_source_1h],
        history_position,
        history_order,
    )
    .unwrap();
}