Manus에서 모든 스킬 실행
원클릭으로
원클릭으로
원클릭으로 Manus에서 모든 스킬 실행
시작하기$pwd:
$ git log --oneline --stat
stars:4
forks:0
updated:2026년 4월 1일 02:52
SKILL.md
| name | crypto-backtest |
| description | 加密货币回测器,当用户希望评估加密交易策略效果并进行回测与结果查看时使用。 |
用于在本仓库中完成“加密策略回测 -> 结果解读 -> 风险复盘 -> 可视化展示”的完整闭环。
默认偏保守:优先降低噪声信号与过度标注风险,避免给出看起来漂亮但不可复现的结论。
当用户出现以下意图时应触发:
在外部项目中使用时,第一步必须先创建工程:
cargo new my-backtest
cd my-backtest
若需要异步运行策略,建议使用 tokio 运行时。
适用于在你自己的策略工程中复用 trading-maid。
使用 crates.io 版本。
cargo add trading-maid
或在 Cargo.toml 中添加:
[dependencies]
trading-maid = "1.0.0"
最小使用流程:
use trading_maid::prelude::*;get_or_download 或 DataSource::from_file_metadataLocalExchange::new(...).cash(...).leverage(...).slippage(...)Engine::new(exchange.clone(), my_strategy)engine.run(symbol, level).awaitsummarize(...) 或 open_in_browser(...)在动手前先锁定以下信息,缺失则先补齐:
若用户未指定,按仓库示例的保守默认值执行,并在结果中明确写出默认假设。
本仓库的关键能力与入口:
优先在现有示例上最小改动,不重复造轮子。
回测结果展示必须二选一:使用 #sym:summarize 输出统计结果,或者使用浏览器可视化(open_in_browser / to_html)。
每次回测结果按以下结构输出,避免只给单一收益数字:
建议字段:
参数优化必须遵守:
在开始写策略代码前,先写清楚以下 5 项:
若用户未给出完整条件,优先采用“低频、强确认、少噪声”策略版本。
为了减少乱标注和噪声信号,默认采用严格信号:
当用户要求“信号更积极”时,再逐步放宽条件,并在结果里说明放宽项。
策略实现必须避免以下错误:
当用户要求“写策略并给结论”时,输出至少包含:
以下示例来自 README,可直接作为完整策略模板使用:
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();
}
输出结论时必须提醒以下交易仿真约束(若涉及):
当回测失败或结果异常时,按顺序排查:
若仍无法定位,输出最小复现配置(symbol、区间、参数、命令)后再继续迭代。