perp-cli 0.6.4 → 0.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -2
- package/dist/api/public/hyperliquid.d.ts +2 -2
- package/dist/api/public/hyperliquid.js +0 -10
- package/dist/api/public/lighter.d.ts +2 -1
- package/dist/api/public/pacifica.d.ts +2 -1
- package/dist/arb/index.d.ts +7 -4
- package/dist/arb/index.js +5 -4
- package/dist/arb/sizing.d.ts +1 -1
- package/dist/arb/state.d.ts +1 -1
- package/dist/arb/state.js +4 -3
- package/dist/arb/utils.d.ts +0 -10
- package/dist/arb/utils.js +0 -42
- package/dist/bot/apex/guard.d.ts +26 -0
- package/dist/bot/apex/guard.js +86 -0
- package/dist/bot/apex/index.d.ts +6 -0
- package/dist/bot/apex/index.js +4 -0
- package/dist/bot/apex/pulse.d.ts +22 -0
- package/dist/bot/apex/pulse.js +107 -0
- package/dist/bot/apex/radar.d.ts +17 -0
- package/dist/bot/apex/radar.js +150 -0
- package/dist/bot/conditions.d.ts +1 -1
- package/dist/bot/config.d.ts +6 -1
- package/dist/bot/config.js +2 -2
- package/dist/bot/engine.d.ts +23 -2
- package/dist/bot/engine.js +331 -351
- package/dist/bot/index.d.ts +16 -0
- package/dist/bot/index.js +10 -0
- package/dist/bot/indicators.d.ts +29 -0
- package/dist/bot/indicators.js +144 -0
- package/dist/bot/reflect.d.ts +34 -0
- package/dist/bot/reflect.js +155 -0
- package/dist/bot/strategies/aggressive-taker.d.ts +36 -0
- package/dist/bot/strategies/aggressive-taker.js +125 -0
- package/dist/bot/strategies/apex-strategy.d.ts +36 -0
- package/dist/bot/strategies/apex-strategy.js +185 -0
- package/dist/bot/strategies/avellaneda-mm.d.ts +27 -0
- package/dist/bot/strategies/avellaneda-mm.js +111 -0
- package/dist/bot/strategies/basis-arb.d.ts +27 -0
- package/dist/bot/strategies/basis-arb.js +97 -0
- package/dist/bot/strategies/claude-agent.d.ts +31 -0
- package/dist/bot/strategies/claude-agent.js +155 -0
- package/dist/bot/strategies/dca-strategy.d.ts +25 -0
- package/dist/bot/strategies/dca-strategy.js +59 -0
- package/dist/bot/strategies/engine-mm.d.ts +29 -0
- package/dist/bot/strategies/engine-mm.js +141 -0
- package/dist/bot/strategies/funding-arb-strategy.d.ts +32 -0
- package/dist/bot/strategies/funding-arb-strategy.js +168 -0
- package/dist/bot/strategies/funding-arb-v2.d.ts +32 -0
- package/dist/bot/strategies/funding-arb-v2.js +222 -0
- package/dist/bot/strategies/funding-auto.d.ts +1 -0
- package/dist/bot/strategies/funding-auto.js +572 -0
- package/dist/bot/strategies/grid-mm.d.ts +25 -0
- package/dist/bot/strategies/grid-mm.js +99 -0
- package/dist/bot/strategies/grid-strategy.d.ts +39 -0
- package/dist/bot/strategies/grid-strategy.js +176 -0
- package/dist/bot/strategies/hedge-agent.d.ts +25 -0
- package/dist/bot/strategies/hedge-agent.js +79 -0
- package/dist/bot/strategies/liquidation-mm.d.ts +25 -0
- package/dist/bot/strategies/liquidation-mm.js +108 -0
- package/dist/bot/strategies/mean-reversion.d.ts +35 -0
- package/dist/bot/strategies/mean-reversion.js +118 -0
- package/dist/bot/strategies/momentum-breakout.d.ts +36 -0
- package/dist/bot/strategies/momentum-breakout.js +159 -0
- package/dist/bot/strategies/regime-mm.d.ts +25 -0
- package/dist/bot/strategies/regime-mm.js +111 -0
- package/dist/bot/strategies/rfq-agent.d.ts +25 -0
- package/dist/bot/strategies/rfq-agent.js +71 -0
- package/dist/bot/strategies/simple-mm.d.ts +25 -0
- package/dist/bot/strategies/simple-mm.js +76 -0
- package/dist/bot/strategy-registry.d.ts +9 -0
- package/dist/bot/strategy-registry.js +14 -0
- package/dist/bot/strategy-types.d.ts +64 -0
- package/dist/bot/strategy-types.js +5 -0
- package/dist/bot/trade-journal.d.ts +27 -0
- package/dist/bot/trade-journal.js +53 -0
- package/dist/bot/tui/BotDashboard.d.ts +56 -0
- package/dist/bot/tui/BotDashboard.js +94 -0
- package/dist/bot/tui/index.d.ts +6 -0
- package/dist/bot/tui/index.js +8 -0
- package/dist/bridge-engine.d.ts +0 -1
- package/dist/bridge-engine.js +0 -1
- package/dist/cache.d.ts +0 -1
- package/dist/cache.js +0 -1
- package/dist/commands/account.d.ts +1 -1
- package/dist/commands/account.js +94 -91
- package/dist/commands/agent.d.ts +1 -1
- package/dist/commands/arb/index.d.ts +1 -1
- package/dist/commands/arb/index.js +1 -1
- package/dist/commands/arb-auto.d.ts +1 -1
- package/dist/commands/arb-auto.js +5 -3
- package/dist/commands/arb.d.ts +2 -1
- package/dist/commands/arb.js +2 -0
- package/dist/commands/bot.d.ts +1 -1
- package/dist/commands/bot.js +236 -15
- package/dist/commands/bridge.js +1 -1
- package/dist/commands/dashboard.d.ts +1 -1
- package/dist/commands/dashboard.js +1 -1
- package/dist/commands/funds.d.ts +1 -1
- package/dist/commands/funds.js +34 -21
- package/dist/commands/history.d.ts +1 -1
- package/dist/commands/history.js +10 -5
- package/dist/commands/manage.d.ts +2 -3
- package/dist/commands/manage.js +11 -18
- package/dist/commands/market.d.ts +1 -1
- package/dist/commands/market.js +26 -17
- package/dist/commands/plan.d.ts +1 -1
- package/dist/commands/rebalance.d.ts +1 -1
- package/dist/commands/rebalance.js +8 -9
- package/dist/commands/risk.d.ts +1 -1
- package/dist/commands/settings.d.ts +1 -1
- package/dist/commands/settings.js +3 -1
- package/dist/commands/trade.d.ts +1 -1
- package/dist/commands/trade.js +42 -55
- package/dist/commands/wallet.d.ts +0 -2
- package/dist/commands/wallet.js +0 -5
- package/dist/constants.d.ts +0 -3
- package/dist/constants.js +0 -4
- package/dist/cross-chain-margin.d.ts +1 -1
- package/dist/dashboard/index.d.ts +4 -0
- package/dist/dashboard/index.js +3 -0
- package/dist/dashboard/server.d.ts +1 -1
- package/dist/dashboard/server.js +1 -1
- package/dist/dashboard/ws-feeds.d.ts +1 -1
- package/dist/event-stream.d.ts +1 -1
- package/dist/exchanges/aster.d.ts +62 -0
- package/dist/exchanges/aster.js +420 -0
- package/dist/exchanges/capabilities.d.ts +74 -0
- package/dist/exchanges/capabilities.js +44 -0
- package/dist/exchanges/edgex.d.ts +62 -0
- package/dist/exchanges/edgex.js +552 -0
- package/dist/exchanges/hyperliquid-spot.d.ts +1 -1
- package/dist/exchanges/hyperliquid-spot.js +20 -12
- package/dist/exchanges/hyperliquid.d.ts +8 -2
- package/dist/exchanges/hyperliquid.js +21 -6
- package/dist/exchanges/index.d.ts +11 -0
- package/dist/exchanges/index.js +10 -0
- package/dist/exchanges/interface.d.ts +3 -0
- package/dist/exchanges/lighter-spot.d.ts +13 -0
- package/dist/exchanges/lighter-spot.js +98 -26
- package/dist/exchanges/lighter.d.ts +9 -3
- package/dist/exchanges/lighter.js +18 -8
- package/dist/exchanges/nado.d.ts +52 -0
- package/dist/exchanges/nado.js +223 -0
- package/dist/exchanges/pacifica.d.ts +7 -1
- package/dist/exchanges/pacifica.js +17 -2
- package/dist/exchanges/registry.d.ts +13 -0
- package/dist/exchanges/registry.js +37 -0
- package/dist/exchanges/spot-interface.d.ts +9 -0
- package/dist/funding/rates.js +1 -1
- package/dist/index.js +2 -6
- package/dist/jobs.js +1 -1
- package/dist/liquidity.d.ts +1 -2
- package/dist/liquidity.js +12 -5
- package/dist/mcp-server.js +4 -2
- package/dist/pacifica/constants.d.ts +0 -3
- package/dist/pacifica/constants.js +0 -4
- package/dist/pacifica/types/account.d.ts +0 -8
- package/dist/pacifica/types/lake.d.ts +0 -6
- package/dist/pacifica/types/position.d.ts +0 -12
- package/dist/pacifica/types/ws.d.ts +0 -24
- package/dist/pacifica/types/ws.js +1 -41
- package/dist/plan-executor.d.ts +1 -1
- package/dist/rebalance.d.ts +1 -7
- package/dist/rebalance.js +0 -6
- package/dist/risk.d.ts +1 -1
- package/dist/risk.js +3 -1
- package/dist/smart-order.d.ts +1 -1
- package/dist/strategies/dca.d.ts +1 -1
- package/dist/strategies/funding-arb.d.ts +1 -1
- package/dist/strategies/funding-arb.js +193 -181
- package/dist/strategies/grid.d.ts +1 -1
- package/dist/strategies/grid.js +1 -1
- package/dist/strategies/index.d.ts +12 -0
- package/dist/strategies/index.js +7 -0
- package/dist/strategies/split-order.d.ts +1 -1
- package/dist/strategies/trailing-stop.d.ts +1 -1
- package/dist/strategies/twap.d.ts +1 -1
- package/dist/trade-validator.d.ts +1 -1
- package/package.json +7 -1
- package/skills/perp-cli/SKILL.md +20 -0
- package/skills/perp-cli/references/commands.md +22 -8
|
@@ -0,0 +1,572 @@
|
|
|
1
|
+
import { registerStrategy } from "../strategy-registry.js";
|
|
2
|
+
import { toHourlyRate, computeAnnualSpread } from "../../funding/normalize.js";
|
|
3
|
+
/**
|
|
4
|
+
* Minimum notional per exchange (USD):
|
|
5
|
+
* - HL: ~$2 (0.001 ETH)
|
|
6
|
+
* - PAC: $10
|
|
7
|
+
* - LT: ~$21 (0.01 ETH)
|
|
8
|
+
* Use $12 as safe minimum across all exchanges.
|
|
9
|
+
*/
|
|
10
|
+
const MIN_NOTIONAL_USD = 12;
|
|
11
|
+
/** Size precision per asset price range */
|
|
12
|
+
function formatSizeForPrice(rawSize, price) {
|
|
13
|
+
// Higher price → fewer decimals (BTC: 4, ETH: 3, SOL: 1, small coins: 0)
|
|
14
|
+
if (price > 10000)
|
|
15
|
+
return rawSize.toFixed(4); // BTC
|
|
16
|
+
if (price > 1000)
|
|
17
|
+
return rawSize.toFixed(3); // ETH
|
|
18
|
+
if (price > 10)
|
|
19
|
+
return rawSize.toFixed(2); // SOL, AVAX
|
|
20
|
+
if (price > 1)
|
|
21
|
+
return rawSize.toFixed(1); // DOGE, etc
|
|
22
|
+
return rawSize.toFixed(0); // sub-$1 tokens
|
|
23
|
+
}
|
|
24
|
+
/** Normalize exchange symbol to base asset: "ETH-PERP" → "ETH", "ETHUSDT" → "ETH" */
|
|
25
|
+
function normalizeSymbol(raw) {
|
|
26
|
+
return raw.toUpperCase().replace(/-PERP$/, "").replace(/USDT$/, "").replace(/USD$/, "").replace(/-USD$/, "");
|
|
27
|
+
}
|
|
28
|
+
// ── Cost helpers ──
|
|
29
|
+
function calculateCosts(notional, feeA, feeB, slippageA, slippageB) {
|
|
30
|
+
const entryCost = (feeA + feeB + slippageA + slippageB) * notional;
|
|
31
|
+
const totalCost = entryCost * 2; // entry + exit
|
|
32
|
+
return { entryCost, totalCost };
|
|
33
|
+
}
|
|
34
|
+
function calculateBreakEven(totalCost, hourlyIncome) {
|
|
35
|
+
if (hourlyIncome <= 0)
|
|
36
|
+
return Infinity;
|
|
37
|
+
return totalCost / hourlyIncome;
|
|
38
|
+
}
|
|
39
|
+
// ── Strategy ──
|
|
40
|
+
class FundingAutoStrategy {
|
|
41
|
+
name = "funding-auto";
|
|
42
|
+
_config = {};
|
|
43
|
+
// Config accessors
|
|
44
|
+
get spotPerpRatio() { return Number(this._config.spotPerpRatio ?? 0.7); }
|
|
45
|
+
get perpPerpRatio() { return Number(this._config.perpPerpRatio ?? 0.3); }
|
|
46
|
+
get perpPerpMinSpread() { return Number(this._config.perpPerpMinSpread ?? 20); }
|
|
47
|
+
get spotPerpMinSpread() { return Number(this._config.spotPerpMinSpread ?? 5); }
|
|
48
|
+
get perpPerpCloseSpread() { return Number(this._config.perpPerpCloseSpread ?? 5); }
|
|
49
|
+
get maxBreakEvenHours() { return Number(this._config.maxBreakEvenHours ?? 48); }
|
|
50
|
+
get maxPositions() { return Number(this._config.maxPositions ?? 5); }
|
|
51
|
+
get maxCapitalPct() { return Number(this._config.maxCapitalPct ?? 20); }
|
|
52
|
+
get defaultFeeRate() { return Number(this._config.defaultFeeRate ?? 0.0005); }
|
|
53
|
+
get defaultSlippage() { return Number(this._config.defaultSlippage ?? 0.001); }
|
|
54
|
+
get scanIntervalTicks() { return Number(this._config.scanIntervalTicks ?? 1); }
|
|
55
|
+
/** Position size: min of sizeUsd (if set) and maxCapitalPct% of total equity */
|
|
56
|
+
_cachedEquity = 0;
|
|
57
|
+
/** Symbols managed by this strategy instance (persists across ticks, prevents adopting unrelated positions) */
|
|
58
|
+
_managedSymbols = new Set();
|
|
59
|
+
getSizeUsd() {
|
|
60
|
+
const fixedSize = Number(this._config.sizeUsd ?? 0);
|
|
61
|
+
const capitalLimit = this._cachedEquity * (this.maxCapitalPct / 100);
|
|
62
|
+
if (fixedSize > 0 && capitalLimit > 0)
|
|
63
|
+
return Math.min(fixedSize, capitalLimit);
|
|
64
|
+
if (capitalLimit > 0)
|
|
65
|
+
return capitalLimit;
|
|
66
|
+
return fixedSize > 0 ? fixedSize : 100; // fallback
|
|
67
|
+
}
|
|
68
|
+
describe() {
|
|
69
|
+
return {
|
|
70
|
+
description: "Automated funding rate trading - perp-perp + spot-perp dual mode",
|
|
71
|
+
params: [
|
|
72
|
+
{ name: "spotPerpRatio", type: "number", required: false, default: 0.7, description: "Capital ratio for spot-perp mode" },
|
|
73
|
+
{ name: "perpPerpRatio", type: "number", required: false, default: 0.3, description: "Capital ratio for perp-perp mode" },
|
|
74
|
+
{ name: "perpPerpMinSpread", type: "number", required: false, default: 20, description: "Min annualized spread % for perp-perp entry" },
|
|
75
|
+
{ name: "spotPerpMinSpread", type: "number", required: false, default: 5, description: "Min annualized spread % for spot-perp entry" },
|
|
76
|
+
{ name: "perpPerpCloseSpread", type: "number", required: false, default: 0, description: "Close perp-perp when spread reverses below this % (0 = only on reversal)" },
|
|
77
|
+
{ name: "maxBreakEvenHours", type: "number", required: false, default: 48, description: "Max break-even hours to enter" },
|
|
78
|
+
{ name: "maxPositions", type: "number", required: false, default: 5, description: "Max concurrent positions" },
|
|
79
|
+
{ name: "maxCapitalPct", type: "number", required: false, default: 20, description: "Max % of total capital per position (e.g., 20 = 20%)" },
|
|
80
|
+
{ name: "defaultFeeRate", type: "number", required: false, default: 0.0005, description: "Default taker fee rate (0.05%)" },
|
|
81
|
+
{ name: "defaultSlippage", type: "number", required: false, default: 0.001, description: "Default slippage estimate (0.1%)" },
|
|
82
|
+
{ name: "scanIntervalTicks", type: "number", required: false, default: 1, description: "Scan every N ticks" },
|
|
83
|
+
{ name: "sizeUsd", type: "number", required: false, default: 0, description: "Fixed position size in USD (0 = use maxCapitalPct instead)" },
|
|
84
|
+
],
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
async init(ctx, _snapshot) {
|
|
88
|
+
this._config = ctx.config;
|
|
89
|
+
ctx.state.set("lastScan", 0);
|
|
90
|
+
ctx.state.set("priceCache", new Map());
|
|
91
|
+
ctx.log(`[funding-auto] Starting with ${this.spotPerpRatio}/${this.perpPerpRatio} spot-perp/perp-perp split`);
|
|
92
|
+
}
|
|
93
|
+
async onTick(ctx, _snapshot) {
|
|
94
|
+
// Cache total equity across ALL exchanges for capital-based sizing
|
|
95
|
+
try {
|
|
96
|
+
const adapters = this.getAdapters(ctx);
|
|
97
|
+
let totalEquity = 0;
|
|
98
|
+
for (const [, adapter] of adapters) {
|
|
99
|
+
try {
|
|
100
|
+
const bal = await adapter.getBalance();
|
|
101
|
+
totalEquity += Number(bal.equity) || 0;
|
|
102
|
+
}
|
|
103
|
+
catch { /* skip */ }
|
|
104
|
+
}
|
|
105
|
+
this._cachedEquity = totalEquity > 0 ? totalEquity : this._cachedEquity;
|
|
106
|
+
}
|
|
107
|
+
catch { /* keep previous cached value */ }
|
|
108
|
+
// Get all adapters
|
|
109
|
+
const adapters = this.getAdapters(ctx);
|
|
110
|
+
if (adapters.size < 2) {
|
|
111
|
+
ctx.log(`[funding-auto] Need 2+ exchanges, have ${adapters.size}. Skipping.`);
|
|
112
|
+
return [];
|
|
113
|
+
}
|
|
114
|
+
// ── Discover existing arb pairs from live exchange positions ──
|
|
115
|
+
const positions = await this.discoverArbPairs(adapters);
|
|
116
|
+
// Fetch funding rates from all exchanges (price data cached for position sizing)
|
|
117
|
+
const ratesByExchange = new Map();
|
|
118
|
+
const priceCache = new Map(); // "exchange:symbol" -> price
|
|
119
|
+
for (const [name, adapter] of adapters) {
|
|
120
|
+
try {
|
|
121
|
+
const markets = await adapter.getMarkets();
|
|
122
|
+
const map = new Map();
|
|
123
|
+
for (const m of markets) {
|
|
124
|
+
const raw = m.symbol.toUpperCase();
|
|
125
|
+
const sym = normalizeSymbol(raw);
|
|
126
|
+
const price = parseFloat(m.markPrice);
|
|
127
|
+
map.set(sym, {
|
|
128
|
+
rate: parseFloat(m.fundingRate),
|
|
129
|
+
price,
|
|
130
|
+
exchange: name,
|
|
131
|
+
});
|
|
132
|
+
if (price > 0) {
|
|
133
|
+
priceCache.set(`${name}:${sym}`, price);
|
|
134
|
+
priceCache.set(`${name}:${raw}`, price); // keep raw for order execution
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
ratesByExchange.set(name, map);
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
// Skip unavailable exchange
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
// Store priceCache for use by openPosition/closePosition
|
|
144
|
+
ctx.state.set("priceCache", priceCache);
|
|
145
|
+
// Calculate notional for discovered positions (using current price)
|
|
146
|
+
for (const pos of positions) {
|
|
147
|
+
const price = priceCache.get(`${pos.longExchange}:${pos.symbol}`)
|
|
148
|
+
|| priceCache.get(`${pos.shortExchange}:${pos.symbol}`) || 0;
|
|
149
|
+
if (price > 0) {
|
|
150
|
+
pos.notional = Math.max(pos.longSize, pos.shortSize) * price;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
// ── Phase 1: Monitor existing positions for exit ──
|
|
154
|
+
const surviving = [];
|
|
155
|
+
for (const pos of positions) {
|
|
156
|
+
if (pos.mode === "perp-perp") {
|
|
157
|
+
// Check if spread has converged below close threshold
|
|
158
|
+
const longRates = ratesByExchange.get(pos.longExchange);
|
|
159
|
+
const shortRates = ratesByExchange.get(pos.shortExchange);
|
|
160
|
+
if (!longRates || !shortRates) {
|
|
161
|
+
surviving.push(pos);
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
const longInfo = longRates.get(pos.symbol);
|
|
165
|
+
const shortInfo = shortRates.get(pos.symbol);
|
|
166
|
+
if (!longInfo || !shortInfo) {
|
|
167
|
+
surviving.push(pos);
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
const currentSpread = computeAnnualSpread(shortInfo.rate, pos.shortExchange, longInfo.rate, pos.longExchange);
|
|
171
|
+
// Calculate real P&L from exchange history
|
|
172
|
+
const [longPnl, shortPnl] = await Promise.all([
|
|
173
|
+
this.calculateRealPnl(adapters.get(pos.longExchange), pos.symbol),
|
|
174
|
+
this.calculateRealPnl(adapters.get(pos.shortExchange), pos.symbol),
|
|
175
|
+
]);
|
|
176
|
+
const totalFees = longPnl.fees + shortPnl.fees;
|
|
177
|
+
const totalFunding = longPnl.funding + shortPnl.funding;
|
|
178
|
+
pos.accumulatedFunding = totalFunding;
|
|
179
|
+
pos.totalCost = totalFees;
|
|
180
|
+
ctx.log(`[funding-auto] ${pos.symbol} spread ${currentSpread.toFixed(1)}% | ` +
|
|
181
|
+
`fees $${totalFees.toFixed(4)} | funding $${totalFunding.toFixed(4)} | ` +
|
|
182
|
+
`net $${(totalFunding - totalFees).toFixed(4)} (${pos.longExchange}↔${pos.shortExchange})`);
|
|
183
|
+
// Only close when spread REVERSES (≤ 0%) — never close a profitable position
|
|
184
|
+
// A spread of 5% or even 2% still earns funding. Closing costs more than keeping.
|
|
185
|
+
if (currentSpread <= 0) {
|
|
186
|
+
ctx.log(`[funding-auto] Closing perp-perp ${pos.symbol}: spread REVERSED to ${currentSpread.toFixed(1)}%`);
|
|
187
|
+
await this.closePosition(ctx, pos, adapters);
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
surviving.push(pos);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
else if (pos.mode === "spot-perp") {
|
|
194
|
+
// Spot-perp: exit if perp funding flips negative (we are short perp)
|
|
195
|
+
const shortRates = ratesByExchange.get(pos.shortExchange);
|
|
196
|
+
if (!shortRates) {
|
|
197
|
+
surviving.push(pos);
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
const shortInfo = shortRates.get(pos.symbol);
|
|
201
|
+
if (!shortInfo) {
|
|
202
|
+
surviving.push(pos);
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
const hourlyRate = toHourlyRate(shortInfo.rate, pos.shortExchange);
|
|
206
|
+
const annualized = Math.abs(hourlyRate) * 8760 * 100;
|
|
207
|
+
const pnl = await this.calculateRealPnl(adapters.get(pos.shortExchange), pos.symbol);
|
|
208
|
+
pos.accumulatedFunding = pnl.funding;
|
|
209
|
+
pos.totalCost = pnl.fees;
|
|
210
|
+
ctx.log(`[funding-auto] ${pos.symbol} rate ${annualized.toFixed(1)}% | ` +
|
|
211
|
+
`fees $${pnl.fees.toFixed(4)} | funding $${pnl.funding.toFixed(4)} | ` +
|
|
212
|
+
`net $${(pnl.funding - pnl.fees).toFixed(4)} (${pos.shortExchange})`);
|
|
213
|
+
// Exit if funding flipped (rate went negative — shorts would pay instead of receive)
|
|
214
|
+
if (shortInfo.rate < 0) {
|
|
215
|
+
ctx.log(`[funding-auto] Closing spot-perp ${pos.symbol}: funding flipped negative (${(shortInfo.rate * 100).toFixed(4)}%)`);
|
|
216
|
+
await this.closePosition(ctx, pos, adapters);
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
if (annualized < this.spotPerpMinSpread) {
|
|
220
|
+
ctx.log(`[funding-auto] Spot-perp ${pos.symbol} annualized rate dropped to ${annualized.toFixed(1)}% — monitoring`);
|
|
221
|
+
}
|
|
222
|
+
surviving.push(pos);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
// ── Phase 2: Scan for new opportunities ──
|
|
227
|
+
if (ctx.tick % this.scanIntervalTicks !== 0)
|
|
228
|
+
return surviving.length > 0 ? [{ type: "noop" }] : [];
|
|
229
|
+
if (surviving.length >= this.maxPositions)
|
|
230
|
+
return [{ type: "noop" }];
|
|
231
|
+
ctx.state.set("lastScan", Date.now());
|
|
232
|
+
ctx.log(`[funding-auto] Scanning ${ratesByExchange.size} exchanges, equity: $${this._cachedEquity.toFixed(2)}, size: $${this.getSizeUsd().toFixed(2)}/pos, active: ${surviving.length}`);
|
|
233
|
+
const activeSymbols = new Set(surviving.map(p => `${p.symbol}:${p.mode}`));
|
|
234
|
+
// Also block symbols we already tried to enter (prevents re-entry on failed/tiny orders)
|
|
235
|
+
const attempted = ctx.state.get("attemptedSymbols") ?? new Set();
|
|
236
|
+
for (const sym of attempted)
|
|
237
|
+
activeSymbols.add(sym);
|
|
238
|
+
const opportunities = [];
|
|
239
|
+
// Collect all symbols across exchanges
|
|
240
|
+
const allSymbols = new Set();
|
|
241
|
+
for (const [, map] of ratesByExchange) {
|
|
242
|
+
for (const sym of map.keys())
|
|
243
|
+
allSymbols.add(sym);
|
|
244
|
+
}
|
|
245
|
+
const exchangeNames = [...ratesByExchange.keys()];
|
|
246
|
+
// ── Perp-Perp opportunities ──
|
|
247
|
+
for (const sym of allSymbols) {
|
|
248
|
+
if (activeSymbols.has(`${sym}:perp-perp`))
|
|
249
|
+
continue;
|
|
250
|
+
let minRate = Infinity, maxRate = -Infinity;
|
|
251
|
+
let minExchange = "", maxExchange = "";
|
|
252
|
+
for (const exName of exchangeNames) {
|
|
253
|
+
const info = ratesByExchange.get(exName)?.get(sym);
|
|
254
|
+
if (!info)
|
|
255
|
+
continue;
|
|
256
|
+
const hourly = toHourlyRate(info.rate, exName);
|
|
257
|
+
if (hourly < minRate) {
|
|
258
|
+
minRate = hourly;
|
|
259
|
+
minExchange = exName;
|
|
260
|
+
}
|
|
261
|
+
if (hourly > maxRate) {
|
|
262
|
+
maxRate = hourly;
|
|
263
|
+
maxExchange = exName;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
if (!minExchange || !maxExchange || minExchange === maxExchange)
|
|
267
|
+
continue;
|
|
268
|
+
// Need raw rates for computeAnnualSpread
|
|
269
|
+
const longInfo = ratesByExchange.get(minExchange).get(sym);
|
|
270
|
+
const shortInfo = ratesByExchange.get(maxExchange).get(sym);
|
|
271
|
+
const annualSpread = computeAnnualSpread(shortInfo.rate, maxExchange, longInfo.rate, minExchange);
|
|
272
|
+
if (annualSpread < this.perpPerpMinSpread)
|
|
273
|
+
continue;
|
|
274
|
+
// Calculate cost and break-even
|
|
275
|
+
const notional = this.getSizeUsd();
|
|
276
|
+
const hourlyIncome = Math.abs(maxRate - minRate) * notional;
|
|
277
|
+
const { totalCost } = calculateCosts(notional, this.defaultFeeRate, this.defaultFeeRate, this.defaultSlippage, this.defaultSlippage);
|
|
278
|
+
const breakEvenHours = calculateBreakEven(totalCost, hourlyIncome);
|
|
279
|
+
if (breakEvenHours >= this.maxBreakEvenHours)
|
|
280
|
+
continue;
|
|
281
|
+
if (hourlyIncome <= 0)
|
|
282
|
+
continue;
|
|
283
|
+
opportunities.push({
|
|
284
|
+
symbol: sym,
|
|
285
|
+
longExchange: minExchange,
|
|
286
|
+
shortExchange: maxExchange,
|
|
287
|
+
longRate: longInfo.rate,
|
|
288
|
+
shortRate: shortInfo.rate,
|
|
289
|
+
annualSpread,
|
|
290
|
+
hourlyIncome,
|
|
291
|
+
breakEvenHours,
|
|
292
|
+
mode: "perp-perp",
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
// ── Spot-Perp opportunities ──
|
|
296
|
+
// DISABLED: spot-perp requires SpotAdapter to buy spot (not yet integrated).
|
|
297
|
+
// Opening perp short without spot buy creates naked short = directional risk.
|
|
298
|
+
// TODO: Enable when SpotAdapter is wired into the strategy context.
|
|
299
|
+
// Sort by break-even hours ascending (best opportunities first)
|
|
300
|
+
opportunities.sort((a, b) => a.breakEvenHours - b.breakEvenHours);
|
|
301
|
+
if (opportunities.length > 0) {
|
|
302
|
+
const top = opportunities.slice(0, 3);
|
|
303
|
+
for (const o of top) {
|
|
304
|
+
ctx.log(`[funding-auto] ${o.mode} ${o.symbol}: spread ${o.annualSpread.toFixed(1)}%, BE ${o.breakEvenHours.toFixed(0)}h, income $${(o.hourlyIncome * 24).toFixed(4)}/day (${o.longExchange}↔${o.shortExchange})`);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
else {
|
|
308
|
+
ctx.log(`[funding-auto] No opportunities found (perp-perp min ${this.perpPerpMinSpread}%, spot-perp min ${this.spotPerpMinSpread}%, BE max ${this.maxBreakEvenHours}h)`);
|
|
309
|
+
}
|
|
310
|
+
// ── Phase 3: Enter new positions ──
|
|
311
|
+
const slotsAvailable = this.maxPositions - surviving.length;
|
|
312
|
+
// Allocate slots by ratio
|
|
313
|
+
const perpPerpSlots = Math.max(1, Math.floor(slotsAvailable * this.perpPerpRatio));
|
|
314
|
+
const spotPerpSlots = Math.max(1, slotsAvailable - perpPerpSlots);
|
|
315
|
+
let perpPerpOpened = 0;
|
|
316
|
+
let spotPerpOpened = 0;
|
|
317
|
+
let currentCount = surviving.length;
|
|
318
|
+
for (const opp of opportunities) {
|
|
319
|
+
if (currentCount >= this.maxPositions)
|
|
320
|
+
break;
|
|
321
|
+
if (opp.mode === "perp-perp" && perpPerpOpened >= perpPerpSlots)
|
|
322
|
+
continue;
|
|
323
|
+
if (opp.mode === "spot-perp" && spotPerpOpened >= spotPerpSlots)
|
|
324
|
+
continue;
|
|
325
|
+
// Track attempted symbols to prevent re-entry on same tick cycle
|
|
326
|
+
attempted.add(`${opp.symbol}:${opp.mode}`);
|
|
327
|
+
ctx.state.set("attemptedSymbols", attempted);
|
|
328
|
+
const success = await this.openPosition(ctx, opp, adapters);
|
|
329
|
+
if (success) {
|
|
330
|
+
currentCount++;
|
|
331
|
+
this._managedSymbols.add(`${opp.symbol}:${opp.mode}`);
|
|
332
|
+
if (opp.mode === "perp-perp")
|
|
333
|
+
perpPerpOpened++;
|
|
334
|
+
else
|
|
335
|
+
spotPerpOpened++;
|
|
336
|
+
ctx.log(`[funding-auto] Opened ${opp.mode} ${opp.symbol}: spread ${opp.annualSpread.toFixed(1)}% | ` +
|
|
337
|
+
`BE ${opp.breakEvenHours.toFixed(1)}h | income $${opp.hourlyIncome.toFixed(4)}/h | ` +
|
|
338
|
+
`long ${opp.longExchange}, short ${opp.shortExchange} (${currentCount}/${this.maxPositions})`);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
return [{ type: "noop" }];
|
|
342
|
+
}
|
|
343
|
+
async onStop(ctx) {
|
|
344
|
+
const adapters = this.getAdapters(ctx);
|
|
345
|
+
const positions = await this.discoverArbPairs(adapters);
|
|
346
|
+
if (positions.length === 0) {
|
|
347
|
+
ctx.log("[funding-auto] No positions to close.");
|
|
348
|
+
return [];
|
|
349
|
+
}
|
|
350
|
+
ctx.log(`[funding-auto] Stopping - closing ${positions.length} positions`);
|
|
351
|
+
for (const pos of positions) {
|
|
352
|
+
try {
|
|
353
|
+
await this.closePosition(ctx, pos, adapters);
|
|
354
|
+
}
|
|
355
|
+
catch (err) {
|
|
356
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
357
|
+
ctx.log(`[funding-auto] Failed to close ${pos.symbol}: ${msg}`);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
return [{ type: "cancel_all" }];
|
|
361
|
+
}
|
|
362
|
+
// ── Helpers ──
|
|
363
|
+
getAdapters(ctx) {
|
|
364
|
+
const adapters = new Map();
|
|
365
|
+
adapters.set(ctx.adapter.name.toLowerCase(), ctx.adapter);
|
|
366
|
+
const extra = ctx.state.get("extraAdapters");
|
|
367
|
+
if (extra) {
|
|
368
|
+
for (const [name, a] of extra)
|
|
369
|
+
adapters.set(name, a);
|
|
370
|
+
}
|
|
371
|
+
return adapters;
|
|
372
|
+
}
|
|
373
|
+
/** Resolve cached price for a symbol on a given exchange */
|
|
374
|
+
getCachedPrice(ctx, exchange, symbol) {
|
|
375
|
+
const cache = ctx.state.get("priceCache");
|
|
376
|
+
return cache?.get(`${exchange}:${symbol}`) ?? 0;
|
|
377
|
+
}
|
|
378
|
+
/** Format size with exchange-appropriate precision based on asset price */
|
|
379
|
+
formatSize(rawSize, _symbol) {
|
|
380
|
+
// Use price to determine precision
|
|
381
|
+
const price = rawSize > 0 ? this.getSizeUsd() / rawSize : 0;
|
|
382
|
+
return formatSizeForPrice(rawSize, price);
|
|
383
|
+
}
|
|
384
|
+
/** Discover existing arb pairs from live exchange positions */
|
|
385
|
+
async discoverArbPairs(adapters) {
|
|
386
|
+
const allPositions = new Map();
|
|
387
|
+
for (const [name, adapter] of adapters) {
|
|
388
|
+
try {
|
|
389
|
+
const exchangePositions = await adapter.getPositions();
|
|
390
|
+
for (const p of exchangePositions) {
|
|
391
|
+
const sym = normalizeSymbol(p.symbol);
|
|
392
|
+
if (!allPositions.has(sym))
|
|
393
|
+
allPositions.set(sym, []);
|
|
394
|
+
allPositions.get(sym).push({
|
|
395
|
+
exchange: name,
|
|
396
|
+
side: p.side,
|
|
397
|
+
size: Math.abs(Number(p.size)),
|
|
398
|
+
symbol: p.symbol, // raw for order execution
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
catch { /* skip unavailable exchange */ }
|
|
403
|
+
}
|
|
404
|
+
// Find arb pairs: same symbol, opposite sides, different exchanges = perp-perp
|
|
405
|
+
const pairs = [];
|
|
406
|
+
const matchedShorts = new Set(); // track shorts already matched to perp-perp
|
|
407
|
+
for (const [sym, positionList] of allPositions) {
|
|
408
|
+
const longs = positionList.filter(p => p.side === "long");
|
|
409
|
+
const shorts = positionList.filter(p => p.side === "short");
|
|
410
|
+
for (const long of longs) {
|
|
411
|
+
for (const short of shorts) {
|
|
412
|
+
if (long.exchange !== short.exchange) {
|
|
413
|
+
pairs.push({
|
|
414
|
+
id: `${sym}-${long.exchange}-${short.exchange}`,
|
|
415
|
+
symbol: sym,
|
|
416
|
+
mode: "perp-perp",
|
|
417
|
+
longExchange: long.exchange,
|
|
418
|
+
shortExchange: short.exchange,
|
|
419
|
+
longSize: long.size,
|
|
420
|
+
shortSize: short.size,
|
|
421
|
+
longSymbol: long.symbol,
|
|
422
|
+
shortSymbol: short.symbol,
|
|
423
|
+
entrySpread: 0,
|
|
424
|
+
entryTime: 0,
|
|
425
|
+
notional: 0,
|
|
426
|
+
totalCost: 0,
|
|
427
|
+
accumulatedFunding: 0,
|
|
428
|
+
});
|
|
429
|
+
matchedShorts.add(`${sym}:${short.exchange}`);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
// Unmatched shorts = spot-perp ONLY if funding-auto previously entered them
|
|
434
|
+
// (prevents adopting unrelated positions like manual PURR short)
|
|
435
|
+
for (const short of shorts) {
|
|
436
|
+
if (!matchedShorts.has(`${sym}:${short.exchange}`) && this._managedSymbols.has(`${sym}:spot-perp`)) {
|
|
437
|
+
pairs.push({
|
|
438
|
+
id: `${sym}-spot-${short.exchange}`,
|
|
439
|
+
symbol: sym,
|
|
440
|
+
mode: "spot-perp",
|
|
441
|
+
longExchange: short.exchange,
|
|
442
|
+
shortExchange: short.exchange,
|
|
443
|
+
longSize: 0,
|
|
444
|
+
shortSize: short.size,
|
|
445
|
+
longSymbol: "",
|
|
446
|
+
shortSymbol: short.symbol,
|
|
447
|
+
entrySpread: 0,
|
|
448
|
+
entryTime: 0,
|
|
449
|
+
notional: 0,
|
|
450
|
+
totalCost: 0,
|
|
451
|
+
accumulatedFunding: 0,
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
return pairs;
|
|
457
|
+
}
|
|
458
|
+
/** Calculate actual P&L from exchange trade/funding history */
|
|
459
|
+
async calculateRealPnl(adapter, symbol) {
|
|
460
|
+
try {
|
|
461
|
+
const [trades, funding] = await Promise.all([
|
|
462
|
+
adapter.getTradeHistory(50),
|
|
463
|
+
adapter.getFundingPayments(50),
|
|
464
|
+
]);
|
|
465
|
+
const symTrades = trades.filter(t => normalizeSymbol(t.symbol) === symbol);
|
|
466
|
+
const symFunding = funding.filter(f => normalizeSymbol(f.symbol) === symbol);
|
|
467
|
+
const totalFees = symTrades.reduce((s, t) => s + Math.abs(Number(t.fee)), 0);
|
|
468
|
+
const totalFunding = symFunding.reduce((s, f) => s + Number(f.payment), 0);
|
|
469
|
+
return { fees: totalFees, funding: totalFunding };
|
|
470
|
+
}
|
|
471
|
+
catch {
|
|
472
|
+
return { fees: 0, funding: 0 };
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
async openPosition(ctx, opp, adapters) {
|
|
476
|
+
if (opp.mode === "perp-perp") {
|
|
477
|
+
const longAdapter = adapters.get(opp.longExchange);
|
|
478
|
+
const shortAdapter = adapters.get(opp.shortExchange);
|
|
479
|
+
if (!longAdapter || !shortAdapter)
|
|
480
|
+
return false;
|
|
481
|
+
const price = this.getCachedPrice(ctx, opp.longExchange, opp.symbol)
|
|
482
|
+
|| this.getCachedPrice(ctx, opp.shortExchange, opp.symbol);
|
|
483
|
+
if (price <= 0)
|
|
484
|
+
return false;
|
|
485
|
+
const notionalUsd = this.getSizeUsd();
|
|
486
|
+
const size = this.formatSize(notionalUsd / price, opp.symbol);
|
|
487
|
+
// Minimum notional check — skip if below exchange minimums
|
|
488
|
+
if (notionalUsd < MIN_NOTIONAL_USD) {
|
|
489
|
+
ctx.log(`[funding-auto] Skip ${opp.symbol}: notional $${notionalUsd.toFixed(0)} < min $${MIN_NOTIONAL_USD}`);
|
|
490
|
+
return false;
|
|
491
|
+
}
|
|
492
|
+
try {
|
|
493
|
+
await Promise.all([
|
|
494
|
+
longAdapter.marketOrder(opp.symbol, "buy", size),
|
|
495
|
+
shortAdapter.marketOrder(opp.symbol, "sell", size),
|
|
496
|
+
]);
|
|
497
|
+
return true;
|
|
498
|
+
}
|
|
499
|
+
catch (err) {
|
|
500
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
501
|
+
ctx.log(`[funding-auto] Perp-perp entry failed ${opp.symbol}: ${msg}`);
|
|
502
|
+
return false;
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
else {
|
|
506
|
+
// spot-perp: short perp (spot buy would need spot adapter — use perp adapter for now)
|
|
507
|
+
const shortAdapter = adapters.get(opp.shortExchange);
|
|
508
|
+
if (!shortAdapter)
|
|
509
|
+
return false;
|
|
510
|
+
const price = this.getCachedPrice(ctx, opp.shortExchange, opp.symbol);
|
|
511
|
+
if (price <= 0)
|
|
512
|
+
return false;
|
|
513
|
+
const notionalUsd = this.getSizeUsd();
|
|
514
|
+
const size = this.formatSize(notionalUsd / price, opp.symbol);
|
|
515
|
+
if (notionalUsd < MIN_NOTIONAL_USD) {
|
|
516
|
+
ctx.log(`[funding-auto] Skip ${opp.symbol}: notional $${notionalUsd.toFixed(0)} < min $${MIN_NOTIONAL_USD}`);
|
|
517
|
+
return false;
|
|
518
|
+
}
|
|
519
|
+
try {
|
|
520
|
+
// Short perp leg — spot buy is out-of-scope for perp-only adapters
|
|
521
|
+
await shortAdapter.marketOrder(opp.symbol, "sell", size);
|
|
522
|
+
ctx.log(`[funding-auto] Spot-perp ${opp.symbol}: perp short opened (spot leg requires manual hedge or spot adapter)`);
|
|
523
|
+
return true;
|
|
524
|
+
}
|
|
525
|
+
catch (err) {
|
|
526
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
527
|
+
ctx.log(`[funding-auto] Spot-perp entry failed ${opp.symbol}: ${msg}`);
|
|
528
|
+
return false;
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
async closePosition(ctx, pos, adapters) {
|
|
533
|
+
if (pos.mode === "perp-perp") {
|
|
534
|
+
const longAdapter = adapters.get(pos.longExchange);
|
|
535
|
+
const shortAdapter = adapters.get(pos.shortExchange);
|
|
536
|
+
if (longAdapter && shortAdapter) {
|
|
537
|
+
// Use actual sizes from exchange positions, with raw exchange symbols
|
|
538
|
+
const longSize = formatSizeForPrice(pos.longSize, pos.longSize > 0 && pos.notional > 0 ? pos.notional / pos.longSize : 0);
|
|
539
|
+
const shortSize = formatSizeForPrice(pos.shortSize, pos.shortSize > 0 && pos.notional > 0 ? pos.notional / pos.shortSize : 0);
|
|
540
|
+
try {
|
|
541
|
+
await Promise.all([
|
|
542
|
+
longAdapter.marketOrder(pos.longSymbol, "sell", longSize),
|
|
543
|
+
shortAdapter.marketOrder(pos.shortSymbol, "buy", shortSize),
|
|
544
|
+
]);
|
|
545
|
+
const netPnl = pos.accumulatedFunding - pos.totalCost;
|
|
546
|
+
ctx.log(`[funding-auto] Closed perp-perp ${pos.symbol} | funding $${pos.accumulatedFunding.toFixed(4)} - fees $${pos.totalCost.toFixed(4)} = net $${netPnl.toFixed(4)}`);
|
|
547
|
+
}
|
|
548
|
+
catch (err) {
|
|
549
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
550
|
+
ctx.log(`[funding-auto] Close failed ${pos.symbol}: ${msg}`);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
else {
|
|
555
|
+
// spot-perp: close perp short
|
|
556
|
+
const shortAdapter = adapters.get(pos.shortExchange);
|
|
557
|
+
if (!shortAdapter)
|
|
558
|
+
return;
|
|
559
|
+
const shortSize = formatSizeForPrice(pos.shortSize, pos.shortSize > 0 && pos.notional > 0 ? pos.notional / pos.shortSize : 0);
|
|
560
|
+
try {
|
|
561
|
+
await shortAdapter.marketOrder(pos.shortSymbol, "buy", shortSize);
|
|
562
|
+
const netPnl = pos.accumulatedFunding - pos.totalCost;
|
|
563
|
+
ctx.log(`[funding-auto] Closed spot-perp ${pos.symbol} | funding $${pos.accumulatedFunding.toFixed(4)} - fees $${pos.totalCost.toFixed(4)} = net $${netPnl.toFixed(4)}`);
|
|
564
|
+
}
|
|
565
|
+
catch (err) {
|
|
566
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
567
|
+
ctx.log(`[funding-auto] Close failed ${pos.symbol}: ${msg}`);
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
registerStrategy("funding-auto", (_config) => new FundingAutoStrategy());
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Strategy, StrategyContext, StrategyAction, EnrichedSnapshot } from "../strategy-types.js";
|
|
2
|
+
export declare class GridMmStrategy implements Strategy {
|
|
3
|
+
readonly name = "grid-mm";
|
|
4
|
+
describe(): {
|
|
5
|
+
description: string;
|
|
6
|
+
params: ({
|
|
7
|
+
name: string;
|
|
8
|
+
type: "number";
|
|
9
|
+
required: boolean;
|
|
10
|
+
description: string;
|
|
11
|
+
default?: undefined;
|
|
12
|
+
} | {
|
|
13
|
+
name: string;
|
|
14
|
+
type: "number";
|
|
15
|
+
required: boolean;
|
|
16
|
+
default: number;
|
|
17
|
+
description: string;
|
|
18
|
+
})[];
|
|
19
|
+
};
|
|
20
|
+
private get params();
|
|
21
|
+
private _config;
|
|
22
|
+
init(ctx: StrategyContext, snapshot: EnrichedSnapshot): Promise<void>;
|
|
23
|
+
onTick(ctx: StrategyContext, snapshot: EnrichedSnapshot): Promise<StrategyAction[]>;
|
|
24
|
+
onStop(_ctx: StrategyContext): Promise<StrategyAction[]>;
|
|
25
|
+
}
|