Gekko 边界与核心设计解析:市场数据、策略接口与订单执行机制
2026/9/24 22:57:21 网站建设 项目流程
  • 金融科技
  • 后端

【免费下载链接】gekko

A bitcoin trading bot written in node - https://gekko.wizb.it/

项目地址:https://gitcode.com/gh_mirrors/ge/gekko
点击查看免费下载

Gekko 是一个用 Node.js 编写的免费开源加密货币自动化交易套件,本文以官方 Scope 文档 为骨架,深入讲解 Gekko 的三个核心设计决策:市场数据为何统一为分钟级 K 线、策略为何采用“蜡烛图 + 指标进、信号出”的极简接口、实盘订单为何保守地挂在订单簿自己一侧,并结合仓库源码揭示其底层实现。读完本文,你将清晰理解 Gekko 能做什么、不能做什么,以及这些边界背后对应的代码事实。

Gekko 的定位:为自研策略准备的“启动套件”

Gekko 不是万能的交易系统,而是一个刻意保持低门槛的自动化交易启动套件(starters kit)。它的目标用户是希望在加密货币市场上运行自己策略的开发者:只要具备基础的 JavaScript 脚本能力,就可以编写自己的交易策略(参考 docs/strategies/creating_a_strategy.md),并在三种模式下运行:

  • 回测(backtest):在历史数据上模拟策略,统计会产生哪些交易、整体表现与风险指标;
  • 纸面交易(paper trader):用虚拟资金实时模拟交易,观察策略在真实行情下的表现;
  • 实盘交易机器人(tradebot):实时运行策略,并根据信号自动在交易所下单。

Scope 文档的核心,正是解释这三个模块各自“边界在哪里、为什么这样设计”。下面逐一展开。

市场数据:一切聚合为分钟级 K 线

Gekko 的底层数据哲学非常明确:把交易所的原始成交流(trades)聚合为分钟级 K 线(candles),只持久化 K 线,其余数据一律不保留

每条 K 线包含五个基本字段:OHLC(开盘、最高、最低、收盘)、VWP(成交量加权价格)以及成交笔数(trades)。这样做的直接收益是磁盘占用可预测——Gekko 只存储固定粒度的蜡烛图,而不是无限增长的逐笔成交明细。Scope 文档同时强调:实盘交易机器人执行订单时会额外使用**订单簿(orderbook)**数据,但这份数据“不会在系统的任何其他位置可见”,即订单簿只服务于下单环节,既不落盘、也不提供给策略。

源码证据一:从成交批次到 1 分钟 K 线

core/budfox/candleCreator.js是这条聚合链路的起点。它接收交易所的 trade 批次,按分钟分桶(fillBuckets中以'YYYY-MM-DD HH:mm'为桶键),再对每个桶内的成交计算 K 线:

var candle = { start: first.date.clone().startOf('minute'), open: f(first.price), high: f(first.price), low: f(first.price), close: f(_.last(trades).price), vwp: 0, volume: 0, trades: _.size(trades) }; _.each(trades, function(trade) { candle.high = _.max([candle.high, f(trade.price)]); candle.low = _.min([candle.low, f(trade.price)]); candle.volume += f(trade.amount); candle.vwp += f(trade.price) * f(trade.amount); }); candle.vwp /= candle.volume;

注意 VWP 的计算方式:先累加“价格 × 数量”得到总成交额,再除以总成交量得到成交量加权均价——这与文档中“OHLC、VWP、成交笔数”的数据契约完全一致。

更值得留意的是addEmptyCandles:当某一分钟没有任何成交时,Gekko 会主动补齐空 K 线,其 OHLC、VWP 均沿用上一根 K 线的收盘价,成交量和笔数记 0。这保证了“Gekko 期望每一分钟都有一根 K 线”的不变式,让下游消费者(策略、指标)永远面对连续的时间轴,不必处理数据缺失。

源码证据二:内部只用 1 分钟,按需聚合更大周期

core/candleBatcher.js的开头注释直接点明了设计意图:

// internally we only use 1m // candles, this can easily // convert them to any desired // size.

Gekko 内部统一使用 1 分钟 K 线,CandleBatcher负责把它们聚合成任意candleSize(例如 5 分钟、1 小时)。它的calculate()_.reduce逐根合并小 K 线:high 取最大、low 取最小、close 取最后一根、volume 累加、vwp 按“总额/总成交量”重算:

candle.high = _.max([candle.high, m.high]); candle.low = _.min([candle.low, m.low]); candle.close = m.close; candle.volume += m.volume; candle.vwp += m.vwp * m.volume; candle.trades += m.trades;

plugins/tradingAdvisor/tradingAdvisor.js正是用new CandleBatcher(config.tradingAdvisor.candleSize)把 1 分钟流转换成策略所需的 K 线周期后,才喂给策略引擎(emitStratCandlestrategy.tick)。

由此得出的第一个边界结论:由于一切策略输入都源自分钟级 K 线,Gekko 的策略无法在小于 1 分钟的时间尺度上决策——这是数据模型的直接推论,而非性能取舍。

策略:蜡烛图 + 指标进,LONG/SHORT 出

Scope 文档对策略的抽象极其精简:策略是处理新市场数据(OHLC K 线)与指标计算结果(策略自行声明需要哪些指标及参数)的简单脚本。每来一个新数据,策略可以发出 LONG 或 SHORT 信号。仅此而已。

这个“蜡烛图 + 指标值进 → 信号出”的极简设计,配合上百个可用指标(内置指标见strategies/indicators/,Talib/Tulip 指标接入方式见 docs/strategies/talib_indicators.md 与 docs/strategies/tulip_indicators.md),已经足够强大。

策略的生命周期:以 MACD 为例

以内置示例策略strategies/MACD.js为例,一个策略只需实现几个钩子函数:

  • init():注册指标(this.addIndicator('macd', 'MACD', this.settings))、记录requiredHistory(直接取this.tradingAdvisor.historySize,即预热所需的历史 K 线数);
  • update(candle):每根新 K 线触发,通常用于更新内部状态(MACD 示例中为空实现);
  • check(candle):核心决策点,比较指标结果与阈值,决定是否发信号。

MACD 策略在check()里把macddiffsettings.thresholds.up/down比较,并引入趋势持续时间(persistence)去抖:只有当差值连续persistence根 K 线保持在阈值之外时,才真正发出信号:

if(this.trend.duration >= this.settings.thresholds.persistence) this.trend.persisted = true; if(this.trend.persisted && !this.trend.adviced) { this.trend.adviced = true; this.advice('long'); }

对应的默认参数在 config/strategies/MACD.toml:

short = 10 long = 21 signal = 9 [thresholds] down = -0.025 up = 0.025 persistence = 1

更多内置策略(DEMA、PPO、RSI、StochRSI、CCI、talib-macd、tulip-macd 等)及其参数说明见 docs/strategies/introduction.md。

底层执行引擎

策略钩子函数由plugins/tradingAdvisor/baseTradingMethod.js中的Base类统一调度:tick()接收 K 线 →calculateSyncIndicators()逐指标更新(input === 'price'的喂收盘价,input === 'candle'的喂整根 K 线)→propogateTick()调用updatecheck,并在requiredHistory预热完成后发出stratWarmupCompleted。策略调用的this.advice('long'/'short')最终被包装成{ id, recommendation }的 advice 对象向外发出;若传入带trigger的对象,还会携带 trailing stop 等触发条件。

简洁设计带来的五条边界

Scope 文档明确列出了这套极简接口的局限,每一条都能在数据流与代码结构中找到依据:

  1. 策略只能基于 1 分钟及以上周期的 K 线:输入源就是candleBatcher聚合出的 K 线,candleCreator的粒度决定了更小时间尺度不存在;
  2. 策略看不到逐笔成交(trades):成交数据在candleCreator处已被聚合成 K 线,此后只以trades计数字段存在;
  3. 策略无法读取订单簿:如 Scope 文档所述,订单簿仅被实盘执行器使用,不进入策略数据流;
  4. 策略不知道当前持仓:策略是纯函数式的“行情 → 信号”映射,portfolio 状态由portfolioManager/ paper trader 维护,不注入策略上下文;
  5. 策略只能发 LONG/SHORT,语义为“全仓”:从baseTradingMethod.jsadvice()到 paper trader 的updatePosition,信号都对应“用全部币种买入资产”或“卖出全部资产”(详见下节)。

执行策略:保守地挂在自己一侧的订单簿上

Scope 文档用很大篇幅说明了实盘下单的核心理念:当策略发出 LONG 信号时,Gekko 会用你持有的全部 currency 尽可能多地买入 asset(例如在 USD/BTC 市场上用全部 USD 买入 BTC);SHORT 则反之卖出全部。关键在于下单方式是保守的:Gekko 始终把限价单挂在自己一侧的订单簿(own side of the orderbook),从而不承担点差(spread)、滑点(slippage)或 taker 手续费

文档给出的场景是:当策略发出 LONG 且当前订单簿的卖一价为4336.29时,Gekko 会以4336.29挂出买入限价单,等待有人卖出成交;此后每隔几秒重新调整订单价格,确保自己始终站在订单簿顶端(become the best bid/ask),从而最大概率成交且不付出跨越盘口的代价。

源码证据:Sticky Order 就是“挂在顶端并持续跟随”

exchange/orders/sticky.js中的StickyOrder正是这一行为的实现。它的注释说明了设计意图:以 BBO(best bid/offer,最优买卖价)为基准创建限价单,价格移动时“粘”在顶端(stick to the top),并可在支持 outbid 的交易所上主动出价超过当前 BBO 一个最小价位:

// It is created at a limit price of X // - if limit is not specified always at bbo. // - if limit is specified the price is either limit or the bbo (whichever is more favorable) // it will readjust the order: // - if outbid is true it will outbid the current bbo (on supported exchanges) // - if outbid is false it will stick to current bbo when this moves // If the price moves away from the order it will "stick to" the top

核心逻辑在checkOrder():每隔checkInterval轮询一次订单状态,若订单仍挂单(result.open),就拉取最新 ticker,比较当前 BBO 与订单价格是否一致,不一致则调用move(this.calculatePrice(ticker))——先取消旧单,再按新价格重新挂单。calculatePrice()则综合了limit(用户指定的最高/最低限价)、outbid开关与当前 bid/ask,保证“永远不吃盘口对面”:

if(this.side === 'buy') { if(!this.noLimit && ticker.bid >= this.limit) return r(this.limit); if(!this.outbid) return r(ticker.bid); ... }

这与 Scope 文档“每隔几秒重新调整订单以保持在订单簿顶端、不吃点差”的描述逐条对应。

一点诚实的技术现状说明

仓库中还有一份更朴素的exchange/orders/limit.js(简单限价单,支持 postOnly、movePrice/moveAmount),但其文件首部目前以throw ':(';直接中断并标注“currently broken”(关联上游 issue),因此当前实际承担“保守挂单 + 持续对齐顶端”职责的是上述StickyOrder。阅读该目录时可留意这一点(相关入口见 exchange/orders/index.js)。

回测与纸面交易:不模拟订单簿的成交

Scope 文档特别提示:订单模拟(paper trader 与 backtester)的处理方式与实盘不同,因为模拟中不使用订单簿。这避免了在模拟环境里重现撮合队列的复杂性——只要策略发出信号,就假设能以当前 K 线价格立即成交。

纸面交易:按收盘价全仓成交并扣费

plugins/paperTrader/paperTrader.jsupdatePosition()展示了“全仓”语义与费用模型:

if(what === 'long') { cost = (1 - this.fee) * this.portfolio.currency; this.portfolio.asset += this.extractFee(this.portfolio.currency / this.price); ... }

即 LONG 时把全部 currency 按当前价格(candle.close)折算成 asset,并用feeMaker/feeTaker(对应配置 config/plugins/paperTrader.toml 中的feeMaker/feeTakersimulationBalance.asset/currency)扣除手续费;extractFee以 1e8 精度向下取整,模拟真实交易所的精度约束。整个过程不涉及任何订单簿概念。

回测:从适配器批量读取历史 K 线

core/markets/backtest.js则是一个 Readable 流:按config.backtest.batchSize从数据适配器(mongodb/sqlite/postgresql 等,见 config/adapters/)的Reader分批读取历史 K 线,逐根推送给下游。它还会校验daterange.from/to的合法性,并对数据不完整的区间输出警告:

log.warn(`Simulation based on incomplete market data (${this.batchSize - amount} missing between ${from} and ${to}).`);

整个回测管线的构成可进一步阅读 docs/internals/architecture.md 与 core/pipeline.js。

总结:理解边界,才能用好 Gekko

Gekko 的设计哲学可以概括为三句话:

  1. 数据只保留分钟级 K 线——磁盘占用可预测,所有下游组件共享同一条时间轴;
  2. 策略接口刻意极简——“K 线 + 指标进,LONG/SHORT 出”,任何 JavaScript 开发者都能上手,代价是放弃盘中微观数据与组合层面的控制;
  3. 实盘执行刻意保守——挂单永远留在自己一侧,避免点差、滑点与 taker 费用,并通过周期性地“重新对齐订单簿顶端”来保证成交概率。

这些边界不是缺陷,而是 Gekko 作为“策略开发启动套件”的主动取舍:它把复杂的行情聚合、指标计算与下单细节封装起来,让开发者专注于最核心的问题——如何从 K 线数据中判断趋势。官方也明确提示:Gekko 并不适合 HFT、套利等追求极致速度的场景(见 docs/introduction/about_gekko.md 的 Limitations 一节),请基于本文梳理的能力边界评估它是否符合你的用途。

  • 金融科技
  • 后端

【免费下载链接】gekko

A bitcoin trading bot written in node - https://gekko.wizb.it/

项目地址:https://gitcode.com/gh_mirrors/ge/gekko
点击查看免费下载
上一篇:3个关键步骤让经典游戏重获新生:Windows DirectX兼容性解决方案
下一篇:暗黑破坏神2现代PC优化指南:用D2DX让经典游戏焕发新生

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询