输入/搜索内容
内置函数
结构体
内置变量与常量
通用约定
交易所
能力矩阵
中心化交易所
去中心化交易所
Hyperliquid
Lighter
edgeX
Aster
Aevo
Pendle
WOOFi Pro
Uniswap / PancakeSwap
Aerodrome / Velodrome
KyberSwap
1inch
EVM DEX通用指令
Jupiter
OKX DEX
DEX策略示例
LP自动调仓
Gauge质押复投
DEX与CEX搬砖
Jupiter限价单网格
DEX定投
pump.fun打新策略
预测市场
证券与期货
链上与自定义接入

定期买入同一币种以摊平持仓成本:在Jupiter上使用原生定投,在其他DEX上由策略定时市价买入;价格低于均线时,当期买入金额加倍。

做法

策略根据交易所对象自动选择执行方式:

  1. Jupiter且NATIVE为true:使用exchange.IO("dca.*", ...)的原生定投。dca.create一次性存入总额TOTAL,由Jupiter的keeper每隔INTERVAL_HOURS小时买入一份PER_ORDER,无需策略持续运行;MIN_PRICE、MAX_PRICE限定仅在该价格区间内买入(价格在区间外时,该轮跳过或顺延)。策略启动时先用dca.list查询该交易对是否有进行中的定投,没有时才创建(创建得到的ID同时存入_G,防止重复创建),之后每分钟用dca.list显示执行进度。Recurring v1(未配置API Key时)每份至少50美元、至少2份;Trigger V2(已配置API Key且计价币为USDC或USDT)每份至少10美元,资金存放在Jupiter托管的vault中。
  2. 其他DEX(Uniswap、PancakeSwap、Aerodrome、Velodrome等),或NATIVE为false:策略每隔INTERVAL_HOURS小时用市价单(exchange.CreateOrder(交易对, "buy", -1, 金额),市价买单的数量以计价币计)买入PER_ORDER;当卖一价低于MA_DAYS日均线DIP_PCT%时,当期金额乘以DIP_MULT(逢低多买),累计花费不超过TOTAL。通过exchange.GetOrder确认成交后记账,进度存入_G(),重启后可继续执行。

交易所对象

一个DEX交易所对象(建议使用专用钱包),钱包中需存入计价币以及少量原生币用于支付手续费。SYMBOL为null时,使用交易所对象配置的交易对。

参数
参数默认值说明
SYMBOLnull交易对;null表示使用交易所对象配置的交易对
TOTAL500总预算(以计价币计)
PER_ORDER50每份金额(以计价币计)
INTERVAL_HOURS24每份买入的间隔(小时)
NATIVEtrue在Jupiter上使用原生定投(dca.create)
MIN_PRICEnull原生定投:价格低于该值时跳过本轮(null表示不限)
MAX_PRICEnull原生定投:价格高于该值时跳过本轮(null表示不限)
DIP_PCT5策略自行买入:价格低于均线该百分比时加倍买入
DIP_MULT2策略自行买入:逢低买入的倍数
MA_DAYS20策略自行买入:均线天数
DRY_RUNtrue为true时仅模拟,不实际花费资金

风险
  • 定投只能摊平买入成本,无法避免下跌带来的亏损。
  • 链上每笔交易都需支付gas或网络费用,小额高频买入并不划算。
  • 市价单存在滑点(参见exchange.IO("slippage"))。

DRY_RUN与实盘

DRY_RUN为true(默认)时不实际花费资金:原生定投使用dca.simulate模拟创建交易(reason给出未通过的原因);策略自行买入的方式使用exchange.IO("route")询价并记录纸面成交,EVM DEX还会使用exchange.IO("simulate")模拟兑换交易(钱包余额不足时模拟会报错,属正常现象)。将DRY_RUN改为false即进入实盘。原生定投创建后由Jupiter执行,停止机器人并不会停止定投,如需撤销,请使用dca.cancel。

示例

完整策略代码(DRY_RUN = true:仅模拟运行,不实际花费资金)。

javascript
/* * DEX 定投(DCA,范例策略) * * 两种做法,按交易所对象自动选择: * 1. Jupiter(Solana)且 NATIVE = true:用 Jupiter 原生定投 exchange.IO("dca.create", …)——总额一次存入, * 由 Jupiter 的 keeper 每隔 INTERVAL_HOURS 小时买一份,不需要策略一直运行;MIN_PRICE / MAX_PRICE 可限定 * 只在价格区间内买(区间外的那一轮跳过或顺延)。策略只负责创建和用 dca.list 显示进度。 * Recurring v1(没有 API Key 时)每份至少 50 美元、至少 2 份;Trigger V2(有 API Key 且计价币为 * USDC / USDT)每份至少 10 美元,资金存放在 Jupiter 托管的 vault。 * 2. 其它 DEX(Uniswap、PancakeSwap、Aerodrome、Velodrome 等),或 NATIVE = false:策略自己每隔 * INTERVAL_HOURS 小时用市价单买入 PER_ORDER 计价币的币;价格低于 MA_DAYS 日均线 DIP_PCT% 时这一份 * 乘以 DIP_MULT(逢低多买)。累计花费不超过 TOTAL。 * 进度存进 _G,重启后接着来。 * * DRY_RUN = true(默认)不花钱:原生定投用 IO("dca.simulate", …) 模拟创建交易;自己买的做法用 * IO("route", …) 询价记纸面成交,EVM DEX 另用 IO("simulate", …) 模拟兑换交易(钱包余额不够时模拟报错,属正常)。 * * 风险:定投只是摊平买入成本,不能避免下跌亏损;链上每笔都有 gas / 网络费,小额高频不划算; * 市价单有滑点(见 exchange.IO("slippage"))。 * * 用法:添加一个 DEX 交易所对象(专用钱包),钱包里放计价币和少量原生币付手续费。 */ // ---------------- 参数 ---------------- var SYMBOL = null // 交易对;null = 交易所对象配置的交易对 var TOTAL = 500 // 总预算(计价币) var PER_ORDER = 50 // 每份金额(计价币) var INTERVAL_HOURS = 24 // 每份间隔(小时) var NATIVE = true // Jupiter 上用原生定投(dca.create) var MIN_PRICE = null // 原生定投:价格低于它跳过这一轮(null = 不限) var MAX_PRICE = null // 原生定投:价格高于它跳过这一轮(null = 不限) var DIP_PCT = 5 // 自己买:价格低于均线这么多 % 时加倍 var DIP_MULT = 2 // 自己买:逢低的倍数 var MA_DAYS = 20 // 自己买:均线天数 var DRY_RUN = true // true 只模拟,不花钱 function fmt(x, d) { return x === null || x === undefined || isNaN(x) ? "-" : Number(x).toFixed(d === undefined ? 2 : d) } // ---------------- 做法 1:Jupiter 原生定投 ---------------- function nativeDca() { var key = "dca_native_" + SYMBOL // 已创建的定投 ID,防止重启后重复创建 var params = { symbol: SYMBOL, side: "buy", total: TOTAL, perOrder: PER_ORDER, interval: INTERVAL_HOURS * 3600 } if (MIN_PRICE) params.minPrice = MIN_PRICE if (MAX_PRICE) params.maxPrice = MAX_PRICE var active = exchange.IO("dca.list", { status: "active", symbol: SYMBOL }) if (!active) throw new Error("dca.list failed: " + GetLastError()) if (active.length === 0 && !_G(key)) { if (DRY_RUN) { var sim = exchange.IO("dca.simulate", params) if (!sim) Log("dca.simulate 出错:", GetLastError()) else Log("dca.simulate(" + sim.version + ")", sim.ok ? "通过" : "未通过:" + sim.reason, "计划:", JSON.stringify(sim.plan)) } else { var r = exchange.IO("dca.create", params) if (!r) throw new Error("dca.create failed: " + GetLastError()) Log("已创建定投", r.version, r.id, "每份", r.perOrder, "共", r.numberOfOrders, "份", "#00AA00") _G(key, r.id) } } while (true) { try { var rows = [] ;["active", "history"].forEach(function (st) { var list = exchange.IO("dca.list", { status: st, symbol: SYMBOL }) if (!list) throw new Error("dca.list failed: " + GetLastError()) list.forEach(function (d) { rows.push([d.id, d.version, d.state, fmt(d.perOrder, 4), (d.ordersFilled === null ? "?" : d.ordersFilled) + " / " + d.numberOfOrders, fmt(d.spent, 4), fmt(d.filledQty, 6), fmt(d.avgPrice, 4), d.nextFillAt ? _D(d.nextFillAt) : "-"]) }) }) LogStatus(_D(), SYMBOL, "Jupiter 原生定投", DRY_RUN ? "(DRY_RUN:只模拟了创建,下表是钱包里已有的定投)" : "", "\n`" + JSON.stringify({ type: "table", title: "定投", cols: ["ID", "版本", "状态", "每份", "已买份数", "已花费", "已买到", "均价", "下次"], rows: rows }) + "`") } catch (e) { Log("本轮出错:", e.message || e, "#FF0000") } Sleep(60000) } } // ---------------- 做法 2:策略自己定时买 ---------------- function buyOnce(D, amount, price) { var q = exchange.IO("route", SYMBOL, "buy", amount / price) // 只询价:买到这么多要花多少 if (DRY_RUN) { if (exchange.GetName() !== "Jupiter") { var sim = exchange.IO("simulate", SYMBOL, "buy", amount) // 市价买:数量为计价币 Log(sim ? "simulate 通过:" + sim.route : "simulate 未通过(钱包余额不够时正常):" + GetLastError()) } if (!q || !q.price) { Log("询价失败:", GetLastError()); return false } D.spent += amount; D.bought += amount / q.price Log("DRY_RUN 买入", fmt(amount, 2), "→", fmt(amount / q.price, 6), "@", fmt(q.price, 4)) return true } var id = exchange.CreateOrder(SYMBOL, "buy", -1, amount) if (!id) { Log("买入失败:", GetLastError(), "#FF0000"); return false } for (var i = 0; i < 60; i++) { var o = exchange.GetOrder(id) if (o && o.Status === ORDER_STATE_CLOSED) { D.spent += amount; D.bought += o.DealAmount Log("买入", fmt(amount, 2), "→", o.DealAmount, "@", fmt(o.AvgPrice, 4), "#00AA00") return true } if (o && o.Status === ORDER_STATE_CANCELED) { Log("买单未成交(撤销 / 链上失败)", id, "#FF0000"); return false } Sleep(2000) } Log("买单两分钟未确认,记为已花费,请稍后核对", id, "#FF0000") D.spent += amount return true } function loopDca() { var key = DRY_RUN ? "dca_loop_paper" : "dca_loop" var D = _G(key) if (!D || D.symbol !== SYMBOL) D = { symbol: SYMBOL, spent: 0, bought: 0, buys: 0, lastBuy: 0 } while (true) { try { var t = exchange.GetTicker(SYMBOL) if (!t) throw new Error("GetTicker failed: " + GetLastError()) var r = exchange.GetRecords(SYMBOL, PERIOD_D1) var ma = r && r.length >= MA_DAYS ? TA.MA(r, MA_DAYS)[r.length - 1] : null var dip = ma !== null && t.Sell < ma * (1 - DIP_PCT / 100) var due = Date.now() - D.lastBuy >= INTERVAL_HOURS * 3600000 if (due && D.spent < TOTAL - 1e-9) { var amount = Math.min(PER_ORDER * (dip ? DIP_MULT : 1), TOTAL - D.spent) Log("定投第", D.buys + 1, "份", fmt(amount, 2), dip ? "(低于均线 " + DIP_PCT + "%,×" + DIP_MULT + ")" : "") if (buyOnce(D, amount, t.Sell)) { D.buys++; D.lastBuy = Date.now() } _G(key, D) } var avg = D.bought > 0 ? D.spent / D.bought : null var value = D.bought * t.Buy var table = { type: "table", title: SYMBOL + " 定投" + (DRY_RUN ? "(DRY_RUN 纸面)" : ""), cols: ["项目", "值"], rows: [ ["已买份数", D.buys], ["已花费 / 总预算", fmt(D.spent, 2) + " / " + TOTAL], ["已买到", fmt(D.bought, 6)], ["持仓均价", fmt(avg, 4)], ["现价(买一)", fmt(t.Buy, 4)], [MA_DAYS + " 日均线", fmt(ma, 4) + (dip ? "(逢低)" : "")], ["市值 / 盈亏", fmt(value, 2) + " / " + fmt(value - D.spent, 2)], ["下次买入", D.spent >= TOTAL - 1e-9 ? "预算已用完" : _D(D.lastBuy + INTERVAL_HOURS * 3600000)]] } LogStatus(_D(), "\n`" + JSON.stringify(table) + "`") } catch (e) { Log("本轮出错:", e.message || e, "#FF0000") } Sleep(60000) } } function main() { SYMBOL = SYMBOL || exchange.GetCurrency() if (exchange.GetName() === "Jupiter" && NATIVE) nativeDca() else loopDca() }

参考