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
package/dist/commands/bot.js
CHANGED
|
@@ -1,14 +1,8 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
2
|
import { printJson, jsonOk } from "../utils.js";
|
|
3
|
-
import { loadBotConfig, quickGridConfig, quickDCAConfig } from "../bot/
|
|
4
|
-
import { runBot } from "../bot/engine.js";
|
|
5
|
-
import { PRESETS, getPreset, getPresetsByStrategy } from "../bot/presets.js";
|
|
3
|
+
import { loadBotConfig, parseStrategy, quickGridConfig, quickDCAConfig, runBot, PRESETS, getPreset, getPresetsByStrategy } from "../bot/index.js";
|
|
6
4
|
import { updateJobState } from "../jobs.js";
|
|
7
|
-
import { runTWAP } from "../strategies/
|
|
8
|
-
import { runFundingArb } from "../strategies/funding-arb.js";
|
|
9
|
-
import { runGrid } from "../strategies/grid.js";
|
|
10
|
-
import { runDCA } from "../strategies/dca.js";
|
|
11
|
-
import { runTrailingStop } from "../strategies/trailing-stop.js";
|
|
5
|
+
import { runTWAP, runFundingArb, runGrid, runDCA, runTrailingStop } from "../strategies/index.js";
|
|
12
6
|
export function registerBotCommands(program, getAdapter, getAdapterFor, isJson) {
|
|
13
7
|
const bot = program.command("bot").description("Automated trading bots with condition monitoring & risk management");
|
|
14
8
|
// ── bot start <config> ──
|
|
@@ -16,6 +10,7 @@ export function registerBotCommands(program, getAdapter, getAdapterFor, isJson)
|
|
|
16
10
|
.command("start <config>")
|
|
17
11
|
.description("Start a bot from a YAML/JSON config file")
|
|
18
12
|
.option("--background", "Run in background (tmux)")
|
|
13
|
+
.option("--headless", "Run without TUI dashboard")
|
|
19
14
|
.option("--job-id <id>", "Job ID (set by background runner)")
|
|
20
15
|
.action(async (configPath, opts) => {
|
|
21
16
|
const config = loadBotConfig(configPath);
|
|
@@ -35,8 +30,9 @@ export function registerBotCommands(program, getAdapter, getAdapterFor, isJson)
|
|
|
35
30
|
const adapter = config.strategy.type === "funding-arb"
|
|
36
31
|
? await getAdapterFor(config.exchange)
|
|
37
32
|
: await getAdapter();
|
|
33
|
+
const mode = resolveOutputMode(isJson, opts.headless);
|
|
38
34
|
const log = makeLog();
|
|
39
|
-
await runBot(adapter, config, opts.jobId, log);
|
|
35
|
+
await runBot(adapter, config, opts.jobId, log, undefined, mode);
|
|
40
36
|
});
|
|
41
37
|
// ── bot quick grid ──
|
|
42
38
|
bot
|
|
@@ -50,6 +46,7 @@ export function registerBotCommands(program, getAdapter, getAdapterFor, isJson)
|
|
|
50
46
|
.option("--max-drawdown <usd>", "Stop if drawdown exceeds ($)", "100")
|
|
51
47
|
.option("--max-runtime <sec>", "Max runtime in seconds", "0")
|
|
52
48
|
.option("--background", "Run in background (tmux)")
|
|
49
|
+
.option("--headless", "Run without TUI dashboard")
|
|
53
50
|
.option("--job-id <id>", "Job ID")
|
|
54
51
|
.action(async (symbol, opts) => {
|
|
55
52
|
const adapter = await getAdapter();
|
|
@@ -84,18 +81,20 @@ export function registerBotCommands(program, getAdapter, getAdapterFor, isJson)
|
|
|
84
81
|
printBotJobStarted(config.name, job.id);
|
|
85
82
|
return;
|
|
86
83
|
}
|
|
84
|
+
const mode = resolveOutputMode(isJson, opts.headless);
|
|
87
85
|
const log = makeLog();
|
|
88
|
-
await runBot(adapter, config, opts.jobId, log);
|
|
86
|
+
await runBot(adapter, config, opts.jobId, log, undefined, mode);
|
|
89
87
|
});
|
|
90
88
|
// ── bot quick dca ──
|
|
91
89
|
bot
|
|
92
90
|
.command("quick-dca <symbol> <side> <amount> <interval>")
|
|
93
|
-
.description("Quick-start a DCA bot")
|
|
91
|
+
.description("Quick-start a DCA bot (interval: seconds between orders)")
|
|
94
92
|
.option("--orders <n>", "Total orders (0 = unlimited)", "0")
|
|
95
93
|
.option("--price-limit <price>", "Don't buy above / sell below this price")
|
|
96
94
|
.option("--trigger-drop <pct>", "Only buy when price drops X% from recent high")
|
|
97
95
|
.option("--max-drawdown <usd>", "Stop if drawdown exceeds ($)", "100")
|
|
98
96
|
.option("--background", "Run in background (tmux)")
|
|
97
|
+
.option("--headless", "Run without TUI dashboard")
|
|
99
98
|
.option("--job-id <id>", "Job ID")
|
|
100
99
|
.action(async (symbol, side, amount, interval, opts) => {
|
|
101
100
|
const adapter = await getAdapter();
|
|
@@ -129,8 +128,9 @@ export function registerBotCommands(program, getAdapter, getAdapterFor, isJson)
|
|
|
129
128
|
printBotJobStarted(config.name, job.id);
|
|
130
129
|
return;
|
|
131
130
|
}
|
|
131
|
+
const mode = resolveOutputMode(isJson, opts.headless);
|
|
132
132
|
const log = makeLog();
|
|
133
|
-
await runBot(adapter, config, opts.jobId, log);
|
|
133
|
+
await runBot(adapter, config, opts.jobId, log, undefined, mode);
|
|
134
134
|
});
|
|
135
135
|
// ── bot quick arb ──
|
|
136
136
|
bot
|
|
@@ -144,6 +144,7 @@ export function registerBotCommands(program, getAdapter, getAdapterFor, isJson)
|
|
|
144
144
|
.option("--interval <sec>", "Check interval in seconds", "60")
|
|
145
145
|
.option("--max-drawdown <usd>", "Stop if drawdown exceeds ($)", "200")
|
|
146
146
|
.option("--background", "Run in background (tmux)")
|
|
147
|
+
.option("--headless", "Run without TUI dashboard")
|
|
147
148
|
.option("--job-id <id>", "Job ID")
|
|
148
149
|
.action(async (opts) => {
|
|
149
150
|
const exchangeNames = opts.exchanges.split(",").map(e => e.trim());
|
|
@@ -204,8 +205,9 @@ export function registerBotCommands(program, getAdapter, getAdapterFor, isJson)
|
|
|
204
205
|
printBotJobStarted(config.name, job.id);
|
|
205
206
|
return;
|
|
206
207
|
}
|
|
208
|
+
const mode = resolveOutputMode(isJson, opts.headless);
|
|
207
209
|
const log = makeLog();
|
|
208
|
-
await runBot(primaryAdapter, config, opts.jobId, log, adapters);
|
|
210
|
+
await runBot(primaryAdapter, config, opts.jobId, log, adapters, mode);
|
|
209
211
|
});
|
|
210
212
|
// ── bot preset list ──
|
|
211
213
|
bot
|
|
@@ -240,6 +242,7 @@ export function registerBotCommands(program, getAdapter, getAdapterFor, isJson)
|
|
|
240
242
|
.command("preset <name> [symbol]")
|
|
241
243
|
.description("Start a bot from a preset (use 'preset-list' to see options)")
|
|
242
244
|
.option("--background", "Run in background (tmux)")
|
|
245
|
+
.option("--headless", "Run without TUI dashboard")
|
|
243
246
|
.option("--job-id <id>", "Job ID")
|
|
244
247
|
.action(async (name, symbol, opts) => {
|
|
245
248
|
const preset = getPreset(name);
|
|
@@ -287,7 +290,8 @@ export function registerBotCommands(program, getAdapter, getAdapterFor, isJson)
|
|
|
287
290
|
console.log(chalk.cyan.bold(`\n Starting preset: ${chalk.white(preset.name)}`));
|
|
288
291
|
console.log(chalk.gray(` ${preset.description}\n`));
|
|
289
292
|
}
|
|
290
|
-
|
|
293
|
+
const mode = resolveOutputMode(isJson, opts.headless);
|
|
294
|
+
await runBot(primaryAdapter, config, opts.jobId, log, adapters, mode);
|
|
291
295
|
return;
|
|
292
296
|
}
|
|
293
297
|
// Grid / DCA presets
|
|
@@ -312,7 +316,217 @@ export function registerBotCommands(program, getAdapter, getAdapterFor, isJson)
|
|
|
312
316
|
console.log(chalk.cyan.bold(`\n Starting preset: ${chalk.white(preset.name)}`));
|
|
313
317
|
console.log(chalk.gray(` ${preset.description}\n`));
|
|
314
318
|
}
|
|
315
|
-
|
|
319
|
+
const mode = resolveOutputMode(isJson, opts.headless);
|
|
320
|
+
await runBot(adapter, config, opts.jobId, log, undefined, mode);
|
|
321
|
+
});
|
|
322
|
+
// ── bot list-strategies ──
|
|
323
|
+
bot
|
|
324
|
+
.command("list-strategies")
|
|
325
|
+
.description("List all available trading strategies")
|
|
326
|
+
.action(async () => {
|
|
327
|
+
await import("../bot/engine.js");
|
|
328
|
+
const { listStrategies } = await import("../bot/strategy-registry.js");
|
|
329
|
+
const strategies = listStrategies();
|
|
330
|
+
if (isJson())
|
|
331
|
+
return printJson(jsonOk({ strategies }));
|
|
332
|
+
console.log(chalk.cyan.bold("\n Available Strategies:\n"));
|
|
333
|
+
for (const s of strategies) {
|
|
334
|
+
console.log(` ${chalk.white(s)}`);
|
|
335
|
+
}
|
|
336
|
+
console.log();
|
|
337
|
+
});
|
|
338
|
+
// ── bot apex ──
|
|
339
|
+
bot
|
|
340
|
+
.command("apex [symbol]")
|
|
341
|
+
.description("Start APEX autonomous orchestrator")
|
|
342
|
+
.option("--preset <preset>", "Preset: default, conservative, aggressive", "default")
|
|
343
|
+
.option("--max-slots <n>", "Max concurrent positions", "3")
|
|
344
|
+
.option("--daily-limit <usd>", "Daily loss limit in USD", "250")
|
|
345
|
+
.option("--headless", "Run without TUI dashboard")
|
|
346
|
+
.option("--background", "Run in background (tmux)")
|
|
347
|
+
.option("--job-id <id>", "Job ID")
|
|
348
|
+
.action(async (symbol, opts) => {
|
|
349
|
+
const adapter = await getAdapter();
|
|
350
|
+
const sym = (symbol ?? "ETH").toUpperCase();
|
|
351
|
+
const config = {
|
|
352
|
+
name: `apex-${sym.toLowerCase()}-${Date.now().toString(36)}`,
|
|
353
|
+
exchange: adapter.name,
|
|
354
|
+
symbol: sym,
|
|
355
|
+
strategy: {
|
|
356
|
+
type: "apex",
|
|
357
|
+
preset: opts.preset,
|
|
358
|
+
max_slots: parseInt(opts.maxSlots),
|
|
359
|
+
},
|
|
360
|
+
entry_conditions: [{ type: "always", value: 0 }],
|
|
361
|
+
exit_conditions: [],
|
|
362
|
+
risk: {
|
|
363
|
+
max_position_usd: 1000,
|
|
364
|
+
max_daily_loss: parseFloat(opts.dailyLimit),
|
|
365
|
+
max_drawdown: parseFloat(opts.dailyLimit) * 2,
|
|
366
|
+
pause_after_loss_sec: 300,
|
|
367
|
+
max_open_bots: 1,
|
|
368
|
+
},
|
|
369
|
+
monitor_interval_sec: 10,
|
|
370
|
+
};
|
|
371
|
+
if (opts.background) {
|
|
372
|
+
const { startJob } = await import("../jobs.js");
|
|
373
|
+
const cliArgs = [
|
|
374
|
+
`-e`, adapter.name, `apex`, sym,
|
|
375
|
+
`--preset`, opts.preset, `--max-slots`, opts.maxSlots,
|
|
376
|
+
`--daily-limit`, opts.dailyLimit,
|
|
377
|
+
];
|
|
378
|
+
const job = startJob({
|
|
379
|
+
strategy: `bot:apex`,
|
|
380
|
+
exchange: adapter.name,
|
|
381
|
+
params: { symbol: sym, ...opts },
|
|
382
|
+
cliArgs,
|
|
383
|
+
});
|
|
384
|
+
if (isJson())
|
|
385
|
+
return printJson(jsonOk(job));
|
|
386
|
+
printBotJobStarted(config.name, job.id);
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
const mode = resolveOutputMode(isJson, opts.headless);
|
|
390
|
+
const log = makeLog();
|
|
391
|
+
await runBot(adapter, config, opts.jobId, log, undefined, mode);
|
|
392
|
+
});
|
|
393
|
+
// ── bot reflect ──
|
|
394
|
+
bot
|
|
395
|
+
.command("reflect")
|
|
396
|
+
.description("Analyze trading performance")
|
|
397
|
+
.option("--period <days>", "Analysis period in days", "7")
|
|
398
|
+
.action(async (opts) => {
|
|
399
|
+
const { readJournal } = await import("../bot/trade-journal.js");
|
|
400
|
+
const { analyzePerformance } = await import("../bot/reflect.js");
|
|
401
|
+
const periodDays = Number(opts.period);
|
|
402
|
+
const entries = readJournal({ from: Date.now() - periodDays * 86400000 });
|
|
403
|
+
const report = analyzePerformance(entries, periodDays);
|
|
404
|
+
if (isJson())
|
|
405
|
+
return printJson(jsonOk(report));
|
|
406
|
+
console.log(chalk.cyan.bold("\n Performance Report\n"));
|
|
407
|
+
console.log(` Period: ${chalk.white(`${report.period.days}d`)} (${new Date(report.period.from).toLocaleDateString()} – ${new Date(report.period.to).toLocaleDateString()})`);
|
|
408
|
+
console.log(` Total trades: ${chalk.white(report.totalTrades)}`);
|
|
409
|
+
if (report.totalTrades === 0) {
|
|
410
|
+
console.log(chalk.gray("\n No trades in the selected period.\n"));
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
const wrColor = report.winRate >= 0.5 ? chalk.green : chalk.red;
|
|
414
|
+
console.log(` Win rate: ${wrColor((report.winRate * 100).toFixed(1) + "%")}`);
|
|
415
|
+
console.log(` Avg win: ${chalk.green(`$${report.avgWin.toFixed(2)}`)}`);
|
|
416
|
+
console.log(` Avg loss: ${chalk.red(`$${report.avgLoss.toFixed(2)}`)}`);
|
|
417
|
+
const pfColor = report.profitFactor >= 1.0 ? chalk.green : chalk.red;
|
|
418
|
+
console.log(` Profit factor: ${pfColor(report.profitFactor === Infinity ? "∞" : report.profitFactor.toFixed(2))}`);
|
|
419
|
+
console.log(` Fee drag: ${chalk.yellow((report.feeDragRatio * 100).toFixed(1) + "%")}`);
|
|
420
|
+
console.log(` Direction: ${chalk.gray(`long ${(report.directionSplit.long * 100).toFixed(0)}% / short ${(report.directionSplit.short * 100).toFixed(0)}%`)}`);
|
|
421
|
+
if (report.bestStrategy)
|
|
422
|
+
console.log(` Best strategy: ${chalk.green(report.bestStrategy)}`);
|
|
423
|
+
if (report.worstStrategy && report.worstStrategy !== report.bestStrategy)
|
|
424
|
+
console.log(` Worst strategy:${chalk.red(report.worstStrategy)}`);
|
|
425
|
+
console.log(chalk.cyan.bold("\n Suggestions:\n"));
|
|
426
|
+
for (const s of report.suggestions) {
|
|
427
|
+
console.log(` ${chalk.white("•")} ${chalk.gray(s)}`);
|
|
428
|
+
}
|
|
429
|
+
console.log();
|
|
430
|
+
});
|
|
431
|
+
// ── bot run <strategy> <symbol> ──
|
|
432
|
+
bot
|
|
433
|
+
.command("run <strategy> [symbol]")
|
|
434
|
+
.description("Run a strategy (use 'perp bot list-strategies' to see all). Symbol optional for multi-symbol strategies.")
|
|
435
|
+
.option("--config <path>", "YAML/JSON config file")
|
|
436
|
+
.option("--headless", "Run without TUI dashboard")
|
|
437
|
+
.option("--param <key=value>", "Strategy parameter (repeatable)", (val, acc) => [...acc, val], [])
|
|
438
|
+
.action(async (strategyName, symbol, opts) => {
|
|
439
|
+
const sym = symbol?.toUpperCase() || "ALL";
|
|
440
|
+
// Strategies that require an explicit symbol
|
|
441
|
+
const symbolRequiredStrategies = [
|
|
442
|
+
"grid", "dca", "simple-mm", "engine-mm", "avellaneda-mm",
|
|
443
|
+
"momentum-breakout", "mean-reversion", "aggressive-taker",
|
|
444
|
+
"grid-mm", "liquidation-mm", "regime-mm",
|
|
445
|
+
];
|
|
446
|
+
if (sym === "ALL" && symbolRequiredStrategies.includes(strategyName)) {
|
|
447
|
+
console.error(chalk.red(`\n Error: The '${strategyName}' strategy requires a <symbol> argument.`));
|
|
448
|
+
console.error(chalk.gray(` Usage: perp bot run ${strategyName} <symbol>\n`));
|
|
449
|
+
process.exitCode = 1;
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
// Load or build config
|
|
453
|
+
let config;
|
|
454
|
+
if (opts.config) {
|
|
455
|
+
config = loadBotConfig(opts.config);
|
|
456
|
+
// Override strategy type and symbol from CLI args
|
|
457
|
+
config.strategy = { ...config.strategy, type: strategyName };
|
|
458
|
+
config.symbol = sym;
|
|
459
|
+
}
|
|
460
|
+
else {
|
|
461
|
+
// Parse --param key=value pairs
|
|
462
|
+
const params = { type: strategyName };
|
|
463
|
+
for (const kv of opts.param) {
|
|
464
|
+
const eq = kv.indexOf("=");
|
|
465
|
+
if (eq === -1)
|
|
466
|
+
continue;
|
|
467
|
+
const key = kv.slice(0, eq);
|
|
468
|
+
const raw = kv.slice(eq + 1);
|
|
469
|
+
// Try to coerce numbers and booleans
|
|
470
|
+
if (raw === "true")
|
|
471
|
+
params[key] = true;
|
|
472
|
+
else if (raw === "false")
|
|
473
|
+
params[key] = false;
|
|
474
|
+
else if (!isNaN(Number(raw)) && raw !== "")
|
|
475
|
+
params[key] = Number(raw);
|
|
476
|
+
else
|
|
477
|
+
params[key] = raw;
|
|
478
|
+
}
|
|
479
|
+
const adapter = await getAdapter();
|
|
480
|
+
const strategy = parseStrategy(strategyName, params);
|
|
481
|
+
config = {
|
|
482
|
+
name: `${strategyName}-${sym.toLowerCase()}-${Date.now().toString(36)}`,
|
|
483
|
+
exchange: adapter.name,
|
|
484
|
+
symbol: sym,
|
|
485
|
+
strategy,
|
|
486
|
+
entry_conditions: [{ type: "always", value: 0 }],
|
|
487
|
+
exit_conditions: [],
|
|
488
|
+
risk: {
|
|
489
|
+
max_position_usd: 1000,
|
|
490
|
+
max_daily_loss: 100,
|
|
491
|
+
max_drawdown: 200,
|
|
492
|
+
pause_after_loss_sec: 300,
|
|
493
|
+
max_open_bots: 5,
|
|
494
|
+
},
|
|
495
|
+
monitor_interval_sec: 10,
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
// Verify strategy is registered
|
|
499
|
+
await import("../bot/engine.js");
|
|
500
|
+
const { getStrategy, listStrategies: listStrats } = await import("../bot/strategy-registry.js");
|
|
501
|
+
if (!getStrategy(strategyName)) {
|
|
502
|
+
const available = listStrats();
|
|
503
|
+
console.error(chalk.red(`\n Unknown strategy: "${strategyName}"`));
|
|
504
|
+
console.error(chalk.gray(`\n Available strategies:`));
|
|
505
|
+
for (const s of available)
|
|
506
|
+
console.error(chalk.gray(` - ${s}`));
|
|
507
|
+
console.error();
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
const adapter = await getAdapter();
|
|
511
|
+
config.exchange = adapter.name;
|
|
512
|
+
const mode = resolveOutputMode(isJson, opts.headless);
|
|
513
|
+
const log = makeLog();
|
|
514
|
+
// Multi-exchange strategies need all adapters
|
|
515
|
+
let extraAdapters;
|
|
516
|
+
const multiExchangeStrategies = ["funding-auto", "funding-arb", "funding-arb-v2", "basis-arb", "hedge-agent"];
|
|
517
|
+
if (multiExchangeStrategies.includes(strategyName) && getAdapterFor) {
|
|
518
|
+
extraAdapters = new Map();
|
|
519
|
+
for (const ex of ["pacifica", "hyperliquid", "lighter"]) {
|
|
520
|
+
if (ex === adapter.name)
|
|
521
|
+
continue;
|
|
522
|
+
try {
|
|
523
|
+
const a = await getAdapterFor(ex);
|
|
524
|
+
extraAdapters.set(ex, a);
|
|
525
|
+
}
|
|
526
|
+
catch { /* skip unavailable exchanges */ }
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
await runBot(adapter, config, undefined, log, extraAdapters, mode);
|
|
316
530
|
});
|
|
317
531
|
// ── bot example ──
|
|
318
532
|
bot
|
|
@@ -341,6 +555,13 @@ function makeLog() {
|
|
|
341
555
|
console.log(`${chalk.gray(ts)} ${msg}`);
|
|
342
556
|
};
|
|
343
557
|
}
|
|
558
|
+
function resolveOutputMode(isJson, headless) {
|
|
559
|
+
if (isJson())
|
|
560
|
+
return "json";
|
|
561
|
+
if (headless)
|
|
562
|
+
return "headless";
|
|
563
|
+
return process.stdout.isTTY ? "tui" : "headless";
|
|
564
|
+
}
|
|
344
565
|
function printBotJobStarted(name, jobId) {
|
|
345
566
|
console.log(chalk.green(`\n Bot "${name}" started in background.`));
|
|
346
567
|
console.log(` Job ID: ${chalk.white.bold(jobId)}`);
|
package/dist/commands/bridge.js
CHANGED
|
@@ -3,7 +3,7 @@ import { formatUsd, makeTable, printJson, jsonOk } from "../utils.js";
|
|
|
3
3
|
import { logExecution } from "../execution-log.js";
|
|
4
4
|
import { executeBestBridge, getAllQuotes, checkDebridgeStatus, checkBridgeGasBalance, CHAIN_IDS, USDC_ADDRESSES, EXCHANGE_TO_CHAIN, } from "../bridge-engine.js";
|
|
5
5
|
export function registerBridgeCommands(program, isJson) {
|
|
6
|
-
const bridge = program.command("bridge").description("Cross-chain USDC bridge (deBridge DLN)");
|
|
6
|
+
const bridge = program.command("bridge").description("Cross-chain USDC bridge (deBridge DLN). See also: perp funds bridge (CCTP V2)");
|
|
7
7
|
// ── bridge chains ──
|
|
8
8
|
bridge
|
|
9
9
|
.command("chains")
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
|
-
import type { ExchangeAdapter } from "../exchanges/
|
|
2
|
+
import type { ExchangeAdapter } from "../exchanges/index.js";
|
|
3
3
|
export declare function registerDashboardCommands(program: Command, getAdapterForExchange: (exchange: string) => Promise<ExchangeAdapter>, isJson: () => boolean, getHLAdapterForDex?: (dex: string) => Promise<ExchangeAdapter>): void;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
|
-
import { startDashboard } from "../dashboard/
|
|
2
|
+
import { startDashboard } from "../dashboard/index.js";
|
|
3
3
|
const KNOWN_HIP3_DEXES = ["xyz", "flx", "hyna", "km", "cash", "vntl"];
|
|
4
4
|
const HL_INFO_URL = "https://api.hyperliquid.xyz/info";
|
|
5
5
|
/** Lightweight check: query HL info API directly for dex positions (no SDK/WebSocket needed) */
|
package/dist/commands/funds.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
|
-
import type { ExchangeAdapter } from "../exchanges/
|
|
2
|
+
import type { ExchangeAdapter } from "../exchanges/index.js";
|
|
3
3
|
import type { Network } from "../pacifica/index.js";
|
|
4
4
|
export declare function registerFundsCommands(program: Command, getAdapter: () => Promise<ExchangeAdapter>, isJson: () => boolean, getNetwork: () => Network): void;
|
package/dist/commands/funds.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
|
-
import { printJson, formatUsd, jsonOk } from "../utils.js";
|
|
3
|
-
import { PacificaAdapter } from "../exchanges/pacifica.js";
|
|
4
|
-
import { HyperliquidAdapter } from "../exchanges/hyperliquid.js";
|
|
2
|
+
import { printJson, formatUsd, jsonOk, jsonError } from "../utils.js";
|
|
5
3
|
import { logExecution } from "../execution-log.js";
|
|
4
|
+
import { hasPacificaSdk, hasEvmAddress, isWithdrawCapable, isUsdTransferCapable, hasLighterAccount } from "../exchanges/capabilities.js";
|
|
6
5
|
const DEFAULT_RELAYER = "http://localhost:3100";
|
|
7
6
|
function getRelayerUrl() {
|
|
8
7
|
return process.env.PERP_RELAYER_URL || DEFAULT_RELAYER;
|
|
@@ -38,14 +37,16 @@ export function registerFundsCommands(program, getAdapter, isJson, getNetwork) {
|
|
|
38
37
|
if (!isJson())
|
|
39
38
|
console.log(chalk.cyan(`\n Depositing $${formatUsd(amountNum)} via relayer (gasless)...\n`));
|
|
40
39
|
const adapter = await getAdapter();
|
|
41
|
-
if (
|
|
40
|
+
if (adapter.name !== "pacifica")
|
|
42
41
|
throw new Error("Requires --exchange pacifica");
|
|
42
|
+
const { PacificaAdapter } = await import("../exchanges/pacifica.js");
|
|
43
|
+
const pacAdapter = adapter;
|
|
43
44
|
try {
|
|
44
45
|
const buildRes = await fetch(`${getRelayerUrl()}/deposit/pacifica/build`, {
|
|
45
46
|
method: "POST",
|
|
46
47
|
headers: { "Content-Type": "application/json" },
|
|
47
48
|
body: JSON.stringify({
|
|
48
|
-
userPubkey:
|
|
49
|
+
userPubkey: pacAdapter.keypair.publicKey.toBase58(),
|
|
49
50
|
amount: amountNum,
|
|
50
51
|
testnet: getNetwork() === "testnet",
|
|
51
52
|
}),
|
|
@@ -61,7 +62,7 @@ export function registerFundsCommands(program, getAdapter, isJson, getNetwork) {
|
|
|
61
62
|
}
|
|
62
63
|
const { Transaction } = await import("@solana/web3.js");
|
|
63
64
|
const tx = Transaction.from(Buffer.from(transaction, "base64"));
|
|
64
|
-
tx.partialSign(
|
|
65
|
+
tx.partialSign(pacAdapter.keypair);
|
|
65
66
|
const signed = tx.serialize().toString("base64");
|
|
66
67
|
const submitRes = await fetch(`${getRelayerUrl()}/deposit/pacifica/submit`, {
|
|
67
68
|
method: "POST",
|
|
@@ -104,15 +105,17 @@ export function registerFundsCommands(program, getAdapter, isJson, getNetwork) {
|
|
|
104
105
|
console.log(chalk.gray(" Requires: SOL for gas (~0.005 SOL) + USDC on Solana\n"));
|
|
105
106
|
}
|
|
106
107
|
const adapter = await getAdapter();
|
|
107
|
-
if (
|
|
108
|
+
if (adapter.name !== "pacifica")
|
|
108
109
|
throw new Error("Requires --exchange pacifica");
|
|
110
|
+
const { PacificaAdapter: PacAdapterCls } = await import("../exchanges/pacifica.js");
|
|
111
|
+
const pacDirect = adapter;
|
|
109
112
|
try {
|
|
110
113
|
const { Connection, Transaction } = await import("@solana/web3.js");
|
|
111
114
|
const { buildDepositInstruction } = await import("../pacifica/deposit.js");
|
|
112
115
|
const network = getNetwork();
|
|
113
116
|
const rpcUrl = network === "testnet" ? "https://api.devnet.solana.com" : "https://api.mainnet-beta.solana.com";
|
|
114
117
|
const connection = new Connection(rpcUrl, "confirmed");
|
|
115
|
-
const solBalance = await connection.getBalance(
|
|
118
|
+
const solBalance = await connection.getBalance(pacDirect.keypair.publicKey);
|
|
116
119
|
if (solBalance < 5_000_000) {
|
|
117
120
|
console.error(chalk.red(" Insufficient SOL for gas. Need ~0.005 SOL."));
|
|
118
121
|
console.error(chalk.gray(" Tip: Use relayer for gasless deposits (start relayer server)\n"));
|
|
@@ -120,7 +123,7 @@ export function registerFundsCommands(program, getAdapter, isJson, getNetwork) {
|
|
|
120
123
|
}
|
|
121
124
|
const { PublicKey } = await import("@solana/web3.js");
|
|
122
125
|
const USDC_MINT = new PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v");
|
|
123
|
-
const tokenAccounts = await connection.getParsedTokenAccountsByOwner(
|
|
126
|
+
const tokenAccounts = await connection.getParsedTokenAccountsByOwner(pacDirect.keypair.publicKey, { mint: USDC_MINT });
|
|
124
127
|
const usdcBalance = tokenAccounts.value.length > 0
|
|
125
128
|
? tokenAccounts.value[0].account.data.parsed.info.tokenAmount.uiAmount ?? 0
|
|
126
129
|
: 0;
|
|
@@ -128,11 +131,11 @@ export function registerFundsCommands(program, getAdapter, isJson, getNetwork) {
|
|
|
128
131
|
console.error(chalk.red(` Insufficient USDC on Solana. Have: $${formatUsd(usdcBalance)}, need: $${formatUsd(amountNum)}`));
|
|
129
132
|
process.exit(1);
|
|
130
133
|
}
|
|
131
|
-
const ix = await buildDepositInstruction(
|
|
134
|
+
const ix = await buildDepositInstruction(pacDirect.keypair.publicKey, amountNum, network);
|
|
132
135
|
const tx = new Transaction().add(ix);
|
|
133
|
-
tx.feePayer =
|
|
136
|
+
tx.feePayer = pacDirect.keypair.publicKey;
|
|
134
137
|
tx.recentBlockhash = (await connection.getLatestBlockhash()).blockhash;
|
|
135
|
-
tx.sign(
|
|
138
|
+
tx.sign(pacDirect.keypair);
|
|
136
139
|
const sig = await connection.sendRawTransaction(tx.serialize());
|
|
137
140
|
await connection.confirmTransaction(sig, "confirmed");
|
|
138
141
|
logExecution({
|
|
@@ -173,7 +176,13 @@ export function registerFundsCommands(program, getAdapter, isJson, getNetwork) {
|
|
|
173
176
|
console.log(chalk.cyan(`\n Depositing $${formatUsd(amountNum)} via relayer (gasless)...\n`));
|
|
174
177
|
const { ethers } = await import("ethers");
|
|
175
178
|
const { loadPrivateKey } = await import("../config.js");
|
|
176
|
-
|
|
179
|
+
let pk;
|
|
180
|
+
try {
|
|
181
|
+
pk = await loadPrivateKey("hyperliquid");
|
|
182
|
+
}
|
|
183
|
+
catch (err) {
|
|
184
|
+
throw new Error(`Private key not configured for Hyperliquid. Run: perp init`);
|
|
185
|
+
}
|
|
177
186
|
const wallet = new ethers.Wallet(pk);
|
|
178
187
|
try {
|
|
179
188
|
const res = await fetch(`${getRelayerUrl()}/deposit/hyperliquid`, {
|
|
@@ -475,7 +484,7 @@ export function registerFundsCommands(program, getAdapter, isJson, getNetwork) {
|
|
|
475
484
|
if (isNaN(amountNum) || amountNum <= 0)
|
|
476
485
|
throw new Error("Invalid amount");
|
|
477
486
|
const adapter = await getAdapter();
|
|
478
|
-
if (!(adapter
|
|
487
|
+
if (!hasPacificaSdk(adapter) || !isWithdrawCapable(adapter))
|
|
479
488
|
throw new Error("Requires --exchange pacifica");
|
|
480
489
|
const dest = opts.to || adapter.publicKey;
|
|
481
490
|
if (!isJson()) {
|
|
@@ -483,7 +492,7 @@ export function registerFundsCommands(program, getAdapter, isJson, getNetwork) {
|
|
|
483
492
|
console.log(` Destination: ${dest}`);
|
|
484
493
|
}
|
|
485
494
|
try {
|
|
486
|
-
const result = await adapter.
|
|
495
|
+
const result = await adapter.withdraw(String(amountNum), dest);
|
|
487
496
|
logExecution({
|
|
488
497
|
type: "bridge", exchange: "pacifica", symbol: "USDC", side: "withdraw",
|
|
489
498
|
size: String(amountNum), status: "success", dryRun: false,
|
|
@@ -515,7 +524,7 @@ export function registerFundsCommands(program, getAdapter, isJson, getNetwork) {
|
|
|
515
524
|
if (isNaN(amountNum) || amountNum <= 0)
|
|
516
525
|
throw new Error("Invalid amount");
|
|
517
526
|
const adapter = await getAdapter();
|
|
518
|
-
if (!(adapter
|
|
527
|
+
if (adapter.name !== "hyperliquid" || !hasEvmAddress(adapter) || !isWithdrawCapable(adapter))
|
|
519
528
|
throw new Error("Requires --exchange hyperliquid");
|
|
520
529
|
const dest = opts.to || adapter.address;
|
|
521
530
|
if (!isJson()) {
|
|
@@ -562,8 +571,7 @@ export function registerFundsCommands(program, getAdapter, isJson, getNetwork) {
|
|
|
562
571
|
if (isNaN(amountNum) || amountNum <= 0)
|
|
563
572
|
throw new Error("Invalid amount");
|
|
564
573
|
const adapter = await getAdapter();
|
|
565
|
-
|
|
566
|
-
if (!(adapter instanceof LighterAdapter))
|
|
574
|
+
if (!hasLighterAccount(adapter) || !isWithdrawCapable(adapter))
|
|
567
575
|
throw new Error("Requires --exchange lighter");
|
|
568
576
|
if (!isJson()) {
|
|
569
577
|
console.log(chalk.cyan(`\n Withdrawing $${formatUsd(amountNum)} USDC from Lighter...\n`));
|
|
@@ -571,7 +579,7 @@ export function registerFundsCommands(program, getAdapter, isJson, getNetwork) {
|
|
|
571
579
|
console.log(` Address: ${adapter.address}`);
|
|
572
580
|
}
|
|
573
581
|
try {
|
|
574
|
-
const result = await adapter.withdraw(amountNum, parseInt(opts.assetId), parseInt(opts.route));
|
|
582
|
+
const result = await adapter.withdraw(String(amountNum), "", { assetId: parseInt(opts.assetId), routeType: parseInt(opts.route) });
|
|
575
583
|
logExecution({
|
|
576
584
|
type: "bridge", exchange: "lighter", symbol: "USDC", side: "withdraw",
|
|
577
585
|
size: String(amountNum), status: "success", dryRun: false,
|
|
@@ -604,7 +612,7 @@ export function registerFundsCommands(program, getAdapter, isJson, getNetwork) {
|
|
|
604
612
|
if (isNaN(amountNum) || amountNum <= 0)
|
|
605
613
|
throw new Error("Invalid amount");
|
|
606
614
|
const adapter = await getAdapter();
|
|
607
|
-
if (!(adapter
|
|
615
|
+
if (!isUsdTransferCapable(adapter))
|
|
608
616
|
throw new Error("Requires --exchange hyperliquid");
|
|
609
617
|
if (!isJson())
|
|
610
618
|
console.log(chalk.cyan(`\n Transferring $${formatUsd(amountNum)} USDC to ${destination}...\n`));
|
|
@@ -620,7 +628,7 @@ export function registerFundsCommands(program, getAdapter, isJson, getNetwork) {
|
|
|
620
628
|
// ═══════════════════════════════════════════════════════
|
|
621
629
|
funds
|
|
622
630
|
.command("bridge")
|
|
623
|
-
.description("Bridge USDC between chains via CCTP V2")
|
|
631
|
+
.description("Bridge USDC between chains via CCTP V2. See also: perp bridge (deBridge DLN)")
|
|
624
632
|
.requiredOption("--from <chain>", "Source chain (arbitrum, ethereum)")
|
|
625
633
|
.requiredOption("--to <chain>", "Destination chain (arbitrum, ethereum, solana)")
|
|
626
634
|
.requiredOption("--amount <amount>", "USDC amount")
|
|
@@ -654,8 +662,13 @@ export function registerFundsCommands(program, getAdapter, isJson, getNetwork) {
|
|
|
654
662
|
console.log(chalk.gray(` Check: perp funds bridge-status --hash ${result.messageHash}\n`));
|
|
655
663
|
}
|
|
656
664
|
else {
|
|
665
|
+
if (isJson()) {
|
|
666
|
+
printJson(jsonError("RELAYER_UNAVAILABLE", "CCTP bridge requires relayer server. Start: cd packages/relayer && pnpm start"));
|
|
667
|
+
process.exit(1);
|
|
668
|
+
}
|
|
657
669
|
console.error(chalk.red("\n CCTP bridge requires relayer server."));
|
|
658
670
|
console.error(chalk.gray(" Start: cd packages/relayer && pnpm start\n"));
|
|
671
|
+
process.exit(1);
|
|
659
672
|
}
|
|
660
673
|
});
|
|
661
674
|
funds
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
|
-
import type { ExchangeAdapter } from "../exchanges/
|
|
2
|
+
import type { ExchangeAdapter } from "../exchanges/index.js";
|
|
3
3
|
export declare function registerHistoryCommands(program: Command, isJson: () => boolean, getAdapterForExchange?: (exchange: string) => Promise<ExchangeAdapter>): void;
|
package/dist/commands/history.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
2
|
import { printJson, jsonOk, jsonError, makeTable, formatUsd, formatPnl, withJsonErrors } from "../utils.js";
|
|
3
3
|
import { readExecutionLog, getExecutionStats, pruneExecutionLog } from "../execution-log.js";
|
|
4
|
-
import { loadArbState } from "../arb/
|
|
4
|
+
import { loadArbState } from "../arb/index.js";
|
|
5
5
|
import { readPositionHistory, getPositionStats } from "../position-history.js";
|
|
6
6
|
import { saveEquitySnapshot, readEquityHistory, computePnlMetrics, computeDailyPnl, aggregateWeekly, } from "../equity-tracker.js";
|
|
7
7
|
const EXCHANGES = ["pacifica", "hyperliquid", "lighter"];
|
|
@@ -75,7 +75,11 @@ export function registerHistoryCommands(program, isJson, getAdapterForExchange)
|
|
|
75
75
|
: chalk.red;
|
|
76
76
|
const sideColor = r.side === "buy" || r.side === "long" ? chalk.green : chalk.red;
|
|
77
77
|
const time = new Date(r.timestamp).toLocaleString();
|
|
78
|
-
const
|
|
78
|
+
const metaPrice = r.meta?.price ?? r.meta?.markPrice ?? r.meta?.entryPrice;
|
|
79
|
+
const effectivePrice = r.price ?? (typeof metaPrice === "number" || typeof metaPrice === "string" ? String(metaPrice) : undefined);
|
|
80
|
+
const sizeNum = Number(r.size);
|
|
81
|
+
const priceNum = effectivePrice ? Number(effectivePrice) : 0;
|
|
82
|
+
const notional = r.notional ? `$${formatUsd(r.notional)}` : (priceNum > 0 && sizeNum > 0) ? `$${formatUsd(priceNum * sizeNum)}` : "-";
|
|
79
83
|
return [
|
|
80
84
|
chalk.gray(time),
|
|
81
85
|
chalk.white(r.exchange),
|
|
@@ -899,8 +903,8 @@ function registerPnlSubcommands(parent, getAdapterForExchange, isJson) {
|
|
|
899
903
|
process.on("SIGINT", () => controller.abort());
|
|
900
904
|
// Initial snapshot
|
|
901
905
|
const snaps = await takeSnapshot(getAdapterForExchange, exchanges);
|
|
902
|
-
|
|
903
|
-
console.log(`${chalk.gray(new Date().toLocaleTimeString())} Total equity: $${formatUsd(
|
|
906
|
+
let prevTotal = snaps.reduce((s, sn) => s + sn.equity, 0);
|
|
907
|
+
console.log(`${chalk.gray(new Date().toLocaleTimeString())} Total equity: $${formatUsd(prevTotal)}`);
|
|
904
908
|
while (!controller.signal.aborted) {
|
|
905
909
|
await new Promise((resolve) => {
|
|
906
910
|
const timer = setTimeout(resolve, intervalMs);
|
|
@@ -911,9 +915,10 @@ function registerPnlSubcommands(parent, getAdapterForExchange, isJson) {
|
|
|
911
915
|
try {
|
|
912
916
|
const s = await takeSnapshot(getAdapterForExchange, exchanges);
|
|
913
917
|
const t = s.reduce((sum, sn) => sum + sn.equity, 0);
|
|
914
|
-
const delta = t -
|
|
918
|
+
const delta = t - prevTotal;
|
|
915
919
|
console.log(`${chalk.gray(new Date().toLocaleTimeString())} ` +
|
|
916
920
|
`Total: $${formatUsd(t)} ${formatPnl(delta)}`);
|
|
921
|
+
prevTotal = t;
|
|
917
922
|
}
|
|
918
923
|
catch (err) {
|
|
919
924
|
console.error(chalk.red(`Snapshot error: ${err instanceof Error ? err.message : err}`));
|
|
@@ -1,4 +1,3 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
|
-
import type { ExchangeAdapter } from "../exchanges/
|
|
3
|
-
|
|
4
|
-
export declare function registerManageCommands(program: Command, getAdapter: () => Promise<ExchangeAdapter>, isJson: () => boolean, getPacificaAdapter: () => PacificaAdapter): void;
|
|
2
|
+
import type { ExchangeAdapter } from "../exchanges/index.js";
|
|
3
|
+
export declare function registerManageCommands(program: Command, getAdapter: () => Promise<ExchangeAdapter>, isJson: () => boolean, getPacificaAdapter: () => unknown): void;
|