#![cfg(feature = "mexc")]
use anyhow::Error;
use chrono::{DateTime, TimeZone as _, Utc};
use dotenv::dotenv;
use futures::{
FutureExt,
future::{BoxFuture, join_all},
};
use rust_decimal::Decimal;
use std::time::Duration;
use surrealdb::{
Surreal,
engine::local::{Db, RocksDb},
};
use tokio::{select, time::sleep};
use scuriolus::{
app::App,
clock::{RunningCheatClock, RunningCheatClockFactory},
core::market::simulated::{Simulated, SimulatedFactory},
generics::order::Crypto,
provider::{Interval, Provider, source::mexc_kline_source::MexcKlineSource},
strategy::{
Strategy, StrategyFactory, strict_buy::StrictBuyFactory, trend_tunnel::TrendTunnelFactory,
},
};
const QUOTE: Crypto = Crypto::USDT;
const ASSET: Crypto = Crypto::BTC;
const AMOUNT: Decimal = Decimal::from_parts(1000, 0, 0, false, 0);
const SECS_PER_YEAR: Decimal = Decimal::from_parts(60 * 60 * 24 * 365, 0, 0, false, 0);
#[tokio::main]
async fn main() -> anyhow::Result<()> {
dotenv().ok();
let db = Surreal::new::<RocksDb>("db/perfo/".to_string())
.await
.expect("error creating db");
db.use_ns("test")
.use_db("test")
.await
.expect("error using db");
let source = MexcKlineSource::new().await;
let provider = Provider::new(source.clone(), db.clone());
{
let start = Utc.with_ymd_and_hms(2024, 1, 1, 0, 0, 0).unwrap();
let end = Utc.with_ymd_and_hms(2025, 1, 1, 0, 0, 0).unwrap();
let futures = vec![
test_perf(
"trend tunnel 5%".to_string(),
TrendTunnelFactory::new(QUOTE, AMOUNT, ASSET, start, Interval::OneDay, 5)
.with_provider(provider.clone()),
source.clone(),
db.clone(),
start,
end,
),
test_perf(
"trend tunnel 10%".to_string(),
TrendTunnelFactory::new(QUOTE, AMOUNT, ASSET, start, Interval::OneDay, 10)
.with_provider(provider.clone()),
source.clone(),
db.clone(),
start,
end,
),
test_perf(
"trend tunnel 20%".to_string(),
TrendTunnelFactory::new(QUOTE, AMOUNT, ASSET, start, Interval::OneDay, 20)
.with_provider(provider.clone()),
source.clone(),
db.clone(),
start,
end,
),
test_perf(
"trend tunnel 30%".to_string(),
TrendTunnelFactory::new(QUOTE, AMOUNT, ASSET, start, Interval::OneDay, 30)
.with_provider(provider.clone()),
source.clone(),
db.clone(),
start,
end,
),
test_perf(
"strict buy".to_string(),
StrictBuyFactory::new(QUOTE, AMOUNT, ASSET),
source.clone(),
db.clone(),
start,
end,
),
];
let results = join_all(futures)
.await
.into_iter()
.map(|x| x.unwrap())
.collect::<Vec<_>>();
for (apy, name) in results {
println!(
"{}: {}%",
name,
(apy * SECS_PER_YEAR
/ (Decimal::from(end.timestamp() - start.timestamp()) * AMOUNT)
* Decimal::ONE_HUNDRED)
.round_dp(2)
);
}
}
Ok(())
}
fn test_perf<
'a,
S: Strategy<
_Market = Simulated<Provider<MexcKlineSource>, MexcKlineSource, RunningCheatClock>,
_Clock = RunningCheatClock,
>,
ST: StrategyFactory<_Strategy = S> + 'a,
>(
name: String,
strat_factory: ST,
source: MexcKlineSource,
db: Surreal<Db>,
start: DateTime<Utc>,
end: DateTime<Utc>,
) -> BoxFuture<'a, Result<(Decimal, String), Error>> {
async move {
tracing::debug!("Building up market");
let market_factory =
SimulatedFactory::<Provider<MexcKlineSource>, MexcKlineSource, RunningCheatClock>::new(
name.clone(),
)
.with_provider(Provider::new(source.clone(), db.clone()))
.with_balance(QUOTE, AMOUNT);
tracing::debug!("Creating kline store");
let clock_factory = RunningCheatClockFactory { date: start };
tracing::debug!("Creating app");
let mut app = App::new(
name.clone(),
market_factory,
strat_factory,
clock_factory
)
.await
.unwrap();
let early_stat = app.get_stats().await?;
app.start();
app.advance_toward(end);
{
let mut future = app.wait_for_the_bot().fuse();
loop {
select! {
_ = &mut future => break,
_ = sleep(Duration::from_secs(3)) => {
let percents = ((app.get_time().timestamp() - start.timestamp()) as f64) / ((end.timestamp() - start.timestamp()) as f64) * 100.;
tracing::info!("time inside {} : {} ({}%)", app.get_name(),app.get_time(), percents.round());
}
}
}
}
let final_stat = app.terminate().await?;
Ok((final_stat.quote.free - early_stat.quote.free, name))
}.boxed()
}