ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

在 dYdX v4 上使用 NautilusTrader 运行短期订单链上网格做市(GridMarketMaker 实战指南)

在 dYdX v4 上使用 NautilusTrader 运行短期订单链上网格做市(GridMarketMaker 实战指南) 在 dYdX v4 上使用 NautilusTrader 运行短期订单链上网格做市GridMarketMaker 实战指南【免费下载链接】nautilus_traderProduction-grade Rust-native trading engine with deterministic event-driven architecture项目地址: https://gitcode.com/GitHub_Trending/na/nautilus_trader本指南以 NautilusTrader 仓库内自带的GridMarketMaker策略与 dYdX v4 实时交易示例为核心讲解如何通过 RustLiveNode在去中心化永续合约交易所上运行一套「围绕中间价对称挂单、按库存倾斜网格、依赖区块高度过期替代显式撤单」的链上网格做市系统。读完本文你将掌握几何网格定价公式、库存偏移与仓位上限的实现原理、dYdX 短时订单short-term order与 GTD 过期机制的正确用法以及从环境配置、运行示例到日志监控、参数调优的完整实战流程。策略工作原理网格做市策略Grid Market Maker会在当前中间价mid上下、以固定价格间隔挂出一串买方与卖方限价单。每当某一侧订单成交策略即可赚取买卖档位之间的价差收益。库存管理负责把净敞口控制在max_position之内防止网格累积成方向性仓位。GridMarketMaker是仓库内自带的示例策略源码位于 crates/trading/src/examples/strategies/grid_mm/strategy.rs其核心事件流如下库存偏移Avellaneda-Stoikov 启发当仓位转为净多头时整个网格会向下平移买价更低、卖价也更低促使下一笔成交落在卖出一侧当仓位转为净空头时网格向上平移。这与 Avellaneda-Stoikov 做市框架的思想一致只是被适配到了离散网格上skew skew_factor * net_position为什么选择 dYdX v4dYdX v4 天然适合做市场景短时订单约 20 秒过期下单延迟低且无需链上存储成本约 0.5 秒的出块时间确认周期短撤单无 gas 费用短时订单在 GTBGood-Til-Block重放保护下撤单免费链上订单簿逐区块确定性撮合批量撤单一次MsgBatchCancel即可清空全部短时订单。前置条件已注资的 dYdX 账户需要一个以 USDC 作为抵押品的 dYdX 账户。测试网账户的创建与注资方法参见 dYdX 集成指南中的 Testnet setup 章节。测试网钱包还需在 dYdX UI 中注册一个 API 交易密钥API Trading Key。集成指南同时提醒dYdX v4 交易账户子账户 0只有在钱包完成首次充值或交易后才会创建在此之前 gRPC 与 Indexer 账户查询会返回 not-found导致DydxExecutionClient.connect()在初始化交易序列时失败。环境变量# Mainnet export DYDX_PRIVATE_KEY0x... export DYDX_WALLET_ADDRESSdydx1... # Testnet export DYDX_TESTNET_PRIVATE_KEY0x... export DYDX_TESTNET_WALLET_ADDRESSdydx1...策略概览几何网格定价每一档都距离中间价一个固定的百分比以基点表示Buy level N: mid * (1 - bps/10000)^N - skew Sell level N: mid * (1 bps/10000)^N - skew其中skew skew_factor * net_position。以 3 档网格、grid_step_bps1001%、中间价 1000.00 为例Sell 3: 1030.30 Sell 2: 1020.10 Sell 1: 1010.00 ─── Mid: 1000.00 ─── Buy 1: 990.00 Buy 2: 980.10 Buy 3: 970.30当净持仓为多 2 手、skew_factor1.0时整个网格下移 2.0Sell 3: 1028.30 Sell 2: 1018.10 Sell 1: 1008.00 ─── Mid: 1000.00 ─── Buy 1: 988.00 Buy 2: 978.10 Buy 3: 968.30库存管理策略通过两种机制强制仓位上限max_position净敞口多或空的硬上限。当加入下一档网格后预估敞口会突破该上限时该档会被跳过。投影敞口跟踪projected exposure tracking在放置每一档之前策略跟踪每侧的最坏情况敞口当前仓位 所有挂单/在途买单或卖单避免过度承诺。由于cancel_all_orders是异步的撤单请求与确认之间挂单仍可能成交。在撤单-重挂cancel-requote过渡期间跟踪最坏情况的单侧敞口可以防止瞬时超仓。从源码看strategy.rson_quote会从缓存中聚合所有未平仓与在途订单并按leaves_qty去重累加买卖两侧的待成交数量再与当前仓位相加得到worst_long/worst_short作为网格定价的输入。重挂阈值requote_threshold_bps控制中间价需要移动多少策略才会撤掉全部在挂订单并重新放置一整张网格较低阈值5 bps响应更快但撤单/下单交易更多较高阈值50 bps交易更少但订单可能离当前价格更远。should_requote的实现strategy.rs会在last_quoted_mid为空或为 0 时直接返回 true首次报价必重挂否则比较(mid - last_mid).abs() / last_mid requote_threshold_bps / 10000。单元测试 tests.rs 中的test_should_requote_*系列用例验证了阈值边界1000.30 不触发5 bps 内、1000.50 恰好触发、999.40 反向触发。配置GridMarketMakerConfig定义于 crates/trading/src/examples/strategies/grid_mm/config.rs各参数说明如下参数类型默认值说明instrument_idInstrumentId必填交易的标的如ETH-USD-PERP.DYDX。max_positionQuantity必填最大净敞口多头或空头。trade_sizeQuantityNone每个网格档位的数量。若为None则使用标的的min_quantity或 1.0。num_levelsusize3买、卖两侧的档位数。grid_step_bpsu3210网格间距基点10 0.1%。skew_factorf640.0根据库存偏移网格的激进程度。requote_threshold_bpsu325触发重挂的最小中间价变动bps。expire_time_secsOptionu64None订单过期时间秒。设置后使用 GTD否则使用 GTC。on_cancel_resubmitboolfalse收到非策略发起的撤单事件后在下一个报价时重新提交整张网格。其中base字段StrategyConfig的默认值为strategy_id GRID_MM-001、order_id_tag 001这已被 tests.rs 中的test_config_defaults用例锁定验证。参数选择建议grid_step_bps波动市况下取 50-100 bps平静市况下取 5-20 bps。网格越宽每笔成交捕获的价差越大但成交频率越低。skew_factor从0.0起步。取0.5意味着每单位净仓位使网格偏移 0.5 个价格单位。过于激进的偏移可能让整个网格完全移到中间价上方或下方。expire_time_secs对 dYdX 短时订单建议设为8秒。这落在 40 区块约 20 秒短时窗口之内让订单走快速短时通道设为None时订单使用 GTC 并走长时通道。on_cancel_resubmit在收到策略未发起的撤单事件后如 indexer 发出的短时订单过期、自成交保护、风控限制重置重挂锚点使下一个报价即使中间价未越过requote_threshold_bps也会重建网格。dYdX 的 indexer 会在短时订单过期后不久为每一笔订单发出 cancel 事件正是该标志让网格得以持续更新。dYdX 特有考量短时订单过期当expire_time_secs8时适配器会把订单归类为短时订单适配器检查8s max_short_term_secs40 区块 × 约 0.5 秒 ≈ 20 秒。订单以短时方式提交GoodTilBlock current_height N。约 8 秒后若未成交订单在链上过期。过期不消耗 gasGTB 重放保护在链上处理但 indexer 仍会在过期区块后不久为每笔过期订单发出OrderCanceled事件因此策略通过常规的撤单事件路径感知到过期。这是做市的推荐配置原因在于短时订单延迟更低过期无链上 gas 成本由 indexer 发出的撤单事件驱动配合on_cancel_resubmittrue持续替换过期订单。更完整的订单分类逻辑短时/长时/条件单的自动路由规则、max_short_term_secs 40 blocks × seconds_per_block的计算方式参见 dYdX 集成指南的 order classification 章节。意外撤单与on_cancel_resubmitpending_self_cancels集合用于区分自我发起的撤单与意外撤单策略调用cancel_all_orders时会把所有在挂订单 ID 记录进pending_self_cancels。on_order_canceled触发时若订单 ID 在pending_self_cancels中属于自我撤单无需处理否则说明不是策略发起的短时订单过期、自成交保护或风控限制重置last_quoted_mid让下一个报价触发完整网格重挂。这样既避免策略在自身撤单潮期间无谓重挂又能对外部事件做出响应。on_order_filled也会从pending_self_cancels中移除订单——如果订单在撤单确认到达前成交可防止陈旧条目累积源码见 strategy.rs。值得注意的细节只有当订单完全成交order.is_closed()时才移除 ID部分成交必须保留该 ID否则后续的自我撤单会被误判为外部撤单。此外on_order_rejected与on_order_expired都会清掉对应 ID 并重置last_quoted_mid使下一个报价可以重试整张网格。这些行为均有对应的单元测试覆盖tests.rs。订单量化dYdX 市场的价格与数量量化由适配器的OrderMessageBuilder自动处理无需手动取整或换算。dYdX 使用基于整数的价格/数量量化atomic_resolution、quantum_conversion_exponent、step_base_quantums、subticks_per_tick等参数详见 dYdX 集成指南的 Price and size quantization 章节。Post-only 订单网格内所有订单都以post_onlytrue提交。交易所会在撮合时拒绝任何穿越价差的订单因此每笔成交都落在 maker 费率档网格也不会在重挂过渡期间意外吃下自己的挂单。dYdX 对 post-only 订单的具体行为是「先接受、再立即取消」而非带原因的拒绝详见 dYdX 集成指南。运行与停止环境设置凭据从环境变量或项目根目录的.env文件加载通过dotenvy自动加载# Direct export export DYDX_PRIVATE_KEY0x... export DYDX_WALLET_ADDRESSdydx1...# .env equivalent DYDX_PRIVATE_KEY0x... DYDX_WALLET_ADDRESSdydx1...运行示例cargo run --example dydx-grid-mm --package nautilus-dydx --features examples示例默认面向主网。如需测试网把示例文件顶部附近的DYDX_NETWORK常量改为DydxNetwork::Testnet需要测试网 API 交易密钥后重新编译。优雅关闭按CtrlC停止节点关闭序列如下收到 SIGINTtrader 停止触发on_stop策略撤掉所有订单并平掉仓位5 秒宽限期delay_post_stop_secs处理残留事件客户端断开节点退出。代码走读main函数位于 crates/adapters/dydx/examples/node_grid_mm.rsconst DYDX_NETWORK: DydxNetwork DydxNetwork::Mainnet; #[tokio::main] async fn main() - Result(), Boxdyn std::error::Error { dotenvy::dotenv().ok(); let network DYDX_NETWORK; let environment Environment::Live; let trader_id TraderId::from(TESTER-001); let account_id AccountId::from(DYDX-001); let node_name DYDX-GRID-MM-001.to_string(); let instrument_id InstrumentId::from(ETH-USD-PERP.DYDX); let data_config DydxDataClientConfig { network, ..Default::default() }; let exec_config DydxExecutionClientConfig { account_id, network, ..Default::default() }; let data_factory DydxDataClientFactory::new(); let exec_factory DydxExecutionClientFactory::new(); let log_config LoggerConfig { stdout_level: LevelFilter::Info, ..Default::default() }; let mut node LiveNode::builder(trader_id, environment)? .with_name(node_name) .with_logging(log_config) .add_data_client(None, Box::new(data_factory), Box::new(data_config))? .add_exec_client(None, Box::new(exec_factory), Box::new(exec_config))? .with_reconciliation(false) .with_delay_post_stop_secs(5) .build()?; let config GridMarketMakerConfig::builder() .instrument_id(instrument_id) .max_position(Quantity::from(0.10)) .num_levels(3) .grid_step_bps(100) .skew_factor(0.5) .requote_threshold_bps(10) .expire_time_secs(8) .on_cancel_resubmit(true) .build(); let strategy GridMarketMaker::new(config); node.add_strategy(strategy)?; node.run().await?; Ok(()) }配置要点dotenvy::dotenv().ok()若项目根目录存在.env则加载with_reconciliation(false)为简化示例而关闭生产环境建议开启以便跨重启恢复状态with_delay_post_stop_secs(5)关闭期间为待处理的撤单与平仓事件预留的宽限期。事件流策略内部实现以下关键 Rust 片段取自grid_mm模块源码crates/trading/src/examples/strategies/grid_mm/strategy.rs。交易数量解析on_start交易数量按「配置值 → 标的min_quantity→ 1.0」的顺序从标的缓存解析fn on_start(mut self) - anyhow::Result() { let instrument_id self.config.instrument_id; let (instrument, size_precision, min_quantity) { let cache self.cache(); let instrument cache .instrument(instrument_id) .ok_or_else(|| anyhow::anyhow!(Instrument {instrument_id} not found in cache))?; ( instrument.clone(), instrument.size_precision(), instrument.min_quantity(), ) }; self.price_precision Some(instrument.price_precision()); self.instrument Some(instrument); if self.trade_size.is_none() { self.trade_size Some(min_quantity.unwrap_or_else(|| Quantity::new(1.0, size_precision))); } self.subscribe_quotes(instrument_id, None, None); Ok(()) }报价处理器on_quote节选fn on_quote(mut self, quote: QuoteTick) - anyhow::Result() { let mid_f64 (quote.bid_price.as_f64() quote.ask_price.as_f64()) / 2.0; let mid Price::new( mid_f64, self.price_precision .expect(price_precision should be resolved in on_start), ); if !self.should_requote(mid) { return Ok(()); // Mid hasnt moved enough, keep existing grid } self.cancel_all_orders( instrument_id, None, None, true, // Restrict cancellation to this strategy. None, )?; let (net_position, worst_long, worst_short) { /* ... */ }; let grid self.grid_orders(mid, net_position, worst_long, worst_short); if grid.is_empty() { return Ok(()); // Dont advance requote anchor when fully constrained } let (tif, expire_time) match self.config.expire_time_secs { Some(secs) { let now_ns self.clock().timestamp_ns(); let expire_ns now_ns secs * 1_000_000_000; (Some(TimeInForce::Gtd), Some(expire_ns)) } None (None, None), }; for (side, price) in grid { let order self.order().limit( instrument_id, side, trade_size, price, tif, expire_time, Some(true), // post_only ); self.submit_order(order, None, None)?; } self.last_quoted_mid Some(mid); Ok(()) }实现中还包含一个文档之外的重要防御逻辑即使中间价未越过阈值只要当前没有任何在挂或在途订单网格为空策略也会立即重挂避免「网格被清空后因中间价波动不足而停摆」的边界情况。网格定价grid_orders计算几何网格价格并在每一档强制执行max_positionfn grid_orders( self, mid: Price, net_position: f64, worst_long: Decimal, worst_short: Decimal, ) - Vec(OrderSide, Price) { let instrument self .instrument .as_ref() .expect(instrument should be resolved in on_start); let mid_f64 mid.as_f64(); let skew_f64 self.config.skew_factor * net_position; let pct self.config.grid_step_bps as f64 / 10_000.0; let trade_size self .trade_size .expect(trade_size should be resolved in on_start) .as_decimal(); let max_pos self.config.max_position.as_decimal(); let mut projected_long worst_long; let mut projected_short worst_short; let mut orders Vec::new(); for level in 1..self.config.num_levels { let buy_f64 mid_f64 * (1.0 - pct).powi(level as i32) - skew_f64; let sell_f64 mid_f64 * (1.0 pct).powi(level as i32) - skew_f64; let buy_price instrument.next_bid_price(buy_f64, 0); let sell_price instrument.next_ask_price(sell_f64, 0); if let Some(buy_price) buy_price projected_long trade_size max_pos { orders.push((OrderSide::Buy, buy_price)); projected_long trade_size; } if let Some(sell_price) sell_price projected_short - trade_size -max_pos { orders.push((OrderSide::Sell, sell_price)); projected_short - trade_size; } } orders }next_bid_price向下取整到最近的有效买盘 tick≤ 理论买价next_ask_price向上取整到最近的有效卖盘 tick≥ 理论卖价防止在粗 tick 标的上发生自成交。单元测试tests.rs验证了取整行为1% 几何网格下第三档买价 1000×0.99³≈970.299 被取整为 970.29第三档卖价 1000×1.01³≈1030.301 被取整为 1030.31。max_position约束同样有测试覆盖净持仓 9.9、trade_size0.1、max_position10.0时只允许 1 档买单、3 档卖单当净持仓已达上限 10.0 时买单全部被屏蔽。若max_position小于单档数量如 0.05 vs 0.1整张网格为空此时策略不会推进重挂锚点等待后续报价继续尝试。一次 35 秒主网运行的结果使用示例配置grid_step_bps100、num_levels3、skew_factor0.5、requote_threshold_bps10、expire_time_secs8在ETH-USD-PERP.DYDX上运行 35 秒共捕获 47 次重挂事件、276 次订单提交、67 次接受与 54 次撤单。ETH 当时交易价接近 2,281 美元价格从未移动超过 10 bps 的重挂阈值因此大多数周期由周期性的 8 秒短时订单过期驱动而非价格波动触发。图 1.每次重挂时的 ETH-USD-PERP 中间价与六条理论网格带每侧 3 档、100 bps 步长叠加。中间价约在 2,281 美元附近内侧买档与卖档约在 2,258 与 2,304 美元。图 2.每笔短时订单从OrderAccepted到OrderCanceled的存活时间秒。约 7-8 秒处的密集分布与expire_time_secs8设置吻合6 秒以下的小簇是重挂过渡期间策略主动发起的撤单。重新生成面板从源码构建 NautilusTrader 后在仓库根目录执行以下命令make sync # Capture a 35-second mainnet run. timeout 35 ./target/release/examples/dydx-grid-mm /tmp/dydx_main.log 21 DYDX_LOG/tmp/dydx_main.log \ uv run --project python --no-sync \ python docs/tutorials/assets/grid_market_maker_dydx/render_panels.py监控与理解输出日志消息日志消息含义Requoting grid: midX, last_midY中间价移动超过阈值正在刷新网格。Submit short-term order N订单已通过短时广播路径提交。BatchCancel N short-term orders已为过期/陈旧订单执行批量撤单。benign cancel error, treating as success对已成交或已过期订单的撤单正常情况。Sequence mismatch detected, will resync and retryCosmos SDK 序列错误正在自动恢复。预期行为模式启动加载标的信息WebSocket 连接建立首个报价触发初始网格。稳态网格在 tick 之间持续存在仅当中间价移动超过requote_threshold_bps时重挂。成交仓位更新偏移随之调整下一次重挂会平移网格。过期短时订单约 8 秒后在链上过期indexer 为每笔订单发出撤单事件下一个报价刷新网格。关闭所有订单被撤仓位被平WebSocket 断开。定制建议高波动 vs 低波动条件调整策略高波动更宽的grid_step_bps100-200、更少的num_levels、更低的skew_factor。低波动更紧的grid_step_bps10-30、更多的num_levels、更高的skew_factor。流动性薄提高requote_threshold_bps以减少撤单频率。多标的运行为每个标运行独立的GridMarketMaker实例每个实例独立管理自己的网格、仓位与撤单状态let btc_config GridMarketMakerConfig::builder() .instrument_id(InstrumentId::from(BTC-USD-PERP.DYDX)) .max_position(Quantity::from(0.001)) .base( StrategyConfig::builder() .strategy_id(StrategyId::from(GRID_MM-BTC)) .order_id_tag(BTC.to_string()) .build(), ) .grid_step_bps(50) .build(); let eth_config GridMarketMakerConfig::builder() .instrument_id(InstrumentId::from(ETH-USD-PERP.DYDX)) .max_position(Quantity::from(0.10)) .base( StrategyConfig::builder() .strategy_id(StrategyId::from(GRID_MM-ETH)) .order_id_tag(ETH.to_string()) .build(), ) .grid_step_bps(100) .build(); node.add_strategy(GridMarketMaker::new(btc_config))?; node.add_strategy(GridMarketMaker::new(eth_config))?;主网与测试网切换示例通过文件顶部的DYDX_NETWORK常量选择网络默认DydxNetwork::Mainnet。改为DydxNetwork::Testnet并重新编译即可在测试网上运行。测试网的端点HTTP、WebSocket、gRPC、faucet会自动解析详见 dYdX 集成指南的 Testnet setup 章节。延伸阅读dYdX v4 集成指南完整的适配器参考涵盖订单分类、广播与重试策略、批量撤单分区、费率、限流、子账户与 API 交易密钥等。GridMarketMaker 策略模块策略配置表、调优指南及 Python/Rust 两种用法。grid_mm 单元测试验证默认配置、重挂阈值、几何网格取整、库存偏移、max_position约束与pending_self_cancels行为的完整测试集。【免费下载链接】nautilus_traderProduction-grade Rust-native trading engine with deterministic event-driven architecture项目地址: https://gitcode.com/GitHub_Trending/na/nautilus_trader创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表