perp-cli 0.6.4 → 0.7.1
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 +5 -1
- package/dist/bot/config.js +1 -1
- package/dist/bot/engine.d.ts +23 -2
- package/dist/bot/engine.js +296 -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 +49 -0
- package/dist/bot/tui/BotDashboard.js +93 -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 +1 -1
- package/dist/commands/arb.d.ts +1 -1
- package/dist/commands/bot.d.ts +1 -1
- package/dist/commands/bot.js +198 -14
- 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 +33 -20
- package/dist/commands/history.d.ts +1 -1
- package/dist/commands/history.js +5 -4
- 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 +7 -7
- 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 +39 -52
- 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 +58 -0
- package/dist/exchanges/aster.js +409 -0
- package/dist/exchanges/capabilities.d.ts +74 -0
- package/dist/exchanges/capabilities.js +44 -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/account.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import { PacificaAdapter } from "../exchanges/pacifica.js";
|
|
2
|
-
import { HyperliquidAdapter } from "../exchanges/hyperliquid.js";
|
|
3
1
|
import { makeTable, formatUsd, formatPnl, printJson, jsonOk, jsonError, symbolMatch, withJsonErrors } from "../utils.js";
|
|
4
2
|
import chalk from "chalk";
|
|
5
3
|
import { assessRisk } from "../risk.js";
|
|
4
|
+
import { hasPacificaSdk, isDexCapable } from "../exchanges/capabilities.js";
|
|
6
5
|
const EXCHANGES = ["pacifica", "hyperliquid", "lighter"];
|
|
7
6
|
async function fetchExchangeSnapshot(name, getAdapter) {
|
|
8
7
|
try {
|
|
@@ -96,7 +95,7 @@ function buildSummary(snapshots) {
|
|
|
96
95
|
};
|
|
97
96
|
}
|
|
98
97
|
function pac(adapter) {
|
|
99
|
-
if (!(adapter
|
|
98
|
+
if (!hasPacificaSdk(adapter))
|
|
100
99
|
throw new Error("Market settings are only available on Pacifica.");
|
|
101
100
|
return adapter;
|
|
102
101
|
}
|
|
@@ -112,6 +111,7 @@ export function registerAccountCommands(program, getAdapter, isJson, getAdapterF
|
|
|
112
111
|
try {
|
|
113
112
|
if (name === "hyperliquid") {
|
|
114
113
|
const { HyperliquidSpotAdapter } = await import("../exchanges/hyperliquid-spot.js");
|
|
114
|
+
const { HyperliquidAdapter } = await import("../exchanges/hyperliquid.js");
|
|
115
115
|
const hlSpot = new HyperliquidSpotAdapter(adapter);
|
|
116
116
|
await hlSpot.init();
|
|
117
117
|
const raw = await hlSpot.getSpotBalances();
|
|
@@ -149,7 +149,7 @@ export function registerAccountCommands(program, getAdapter, isJson, getAdapterF
|
|
|
149
149
|
catch { /* spot not available */ }
|
|
150
150
|
// HL unified account: perp equity already includes spot USDC balance.
|
|
151
151
|
// Detect by checking if adapter is HL without a dex override.
|
|
152
|
-
const isUnified = name === "hyperliquid" && !adapter.dex;
|
|
152
|
+
const isUnified = name === "hyperliquid" && (!isDexCapable(adapter) || !adapter.dex);
|
|
153
153
|
const cutoff24h = Date.now() - 24 * 60 * 60 * 1000;
|
|
154
154
|
const recent = fundingPayments.filter(f => f.time >= cutoff24h);
|
|
155
155
|
const funding24h = recent.reduce((sum, f) => sum + Number(f.payment), 0);
|
|
@@ -368,7 +368,7 @@ export function registerAccountCommands(program, getAdapter, isJson, getAdapterF
|
|
|
368
368
|
]);
|
|
369
369
|
}
|
|
370
370
|
// --hip3: fetch HIP-3 dex positions for HL
|
|
371
|
-
if (opts.hip3 && ex === "hyperliquid" && adapter
|
|
371
|
+
if (opts.hip3 && ex === "hyperliquid" && isDexCapable(adapter)) {
|
|
372
372
|
const hip3 = await fetchHip3Data(adapter, a => a.getPositions());
|
|
373
373
|
for (const { dex, items } of hip3) {
|
|
374
374
|
for (const p of items) {
|
|
@@ -397,7 +397,7 @@ export function registerAccountCommands(program, getAdapter, isJson, getAdapterF
|
|
|
397
397
|
try {
|
|
398
398
|
const adapter = await getAdapterForExchange(ex);
|
|
399
399
|
grouped[ex] = await adapter.getPositions();
|
|
400
|
-
if (opts.hip3 && ex === "hyperliquid" && adapter
|
|
400
|
+
if (opts.hip3 && ex === "hyperliquid" && isDexCapable(adapter)) {
|
|
401
401
|
const hip3 = await fetchHip3Data(adapter, a => a.getPositions());
|
|
402
402
|
for (const { dex, items } of hip3)
|
|
403
403
|
grouped[`hip3:${dex}`] = items;
|
|
@@ -423,7 +423,7 @@ export function registerAccountCommands(program, getAdapter, isJson, getAdapterF
|
|
|
423
423
|
const positions = await adapter.getPositions();
|
|
424
424
|
// --hip3: append HIP-3 dex positions
|
|
425
425
|
const hip3Positions = [];
|
|
426
|
-
if (opts.hip3 && adapter
|
|
426
|
+
if (opts.hip3 && isDexCapable(adapter)) {
|
|
427
427
|
hip3Positions.push(...await fetchHip3Data(adapter, a => a.getPositions()));
|
|
428
428
|
}
|
|
429
429
|
if (isJson()) {
|
|
@@ -494,7 +494,7 @@ export function registerAccountCommands(program, getAdapter, isJson, getAdapterF
|
|
|
494
494
|
]);
|
|
495
495
|
}
|
|
496
496
|
// --hip3: fetch HIP-3 dex orders for HL
|
|
497
|
-
if (opts.hip3 && ex === "hyperliquid" && adapter
|
|
497
|
+
if (opts.hip3 && ex === "hyperliquid" && isDexCapable(adapter)) {
|
|
498
498
|
const hip3 = await fetchHip3Data(adapter, a => a.getOpenOrders());
|
|
499
499
|
for (const { dex, items } of hip3) {
|
|
500
500
|
for (const o of items) {
|
|
@@ -523,7 +523,7 @@ export function registerAccountCommands(program, getAdapter, isJson, getAdapterF
|
|
|
523
523
|
try {
|
|
524
524
|
const adapter = await getAdapterForExchange(ex);
|
|
525
525
|
grouped[ex] = await adapter.getOpenOrders();
|
|
526
|
-
if (opts.hip3 && ex === "hyperliquid" && adapter
|
|
526
|
+
if (opts.hip3 && ex === "hyperliquid" && isDexCapable(adapter)) {
|
|
527
527
|
const hip3 = await fetchHip3Data(adapter, a => a.getOpenOrders());
|
|
528
528
|
for (const { dex, items } of hip3)
|
|
529
529
|
grouped[`hip3:${dex}`] = items;
|
|
@@ -549,7 +549,7 @@ export function registerAccountCommands(program, getAdapter, isJson, getAdapterF
|
|
|
549
549
|
const orders = await adapter.getOpenOrders();
|
|
550
550
|
// --hip3: append HIP-3 dex orders
|
|
551
551
|
const hip3Orders = [];
|
|
552
|
-
if (opts.hip3 && adapter
|
|
552
|
+
if (opts.hip3 && isDexCapable(adapter)) {
|
|
553
553
|
hip3Orders.push(...await fetchHip3Data(adapter, a => a.getOpenOrders()));
|
|
554
554
|
}
|
|
555
555
|
if (isJson()) {
|
|
@@ -623,8 +623,9 @@ export function registerAccountCommands(program, getAdapter, isJson, getAdapterF
|
|
|
623
623
|
.action(async () => {
|
|
624
624
|
const adapter = await getAdapter();
|
|
625
625
|
// Pacifica: use dedicated SDK endpoint
|
|
626
|
-
if (adapter
|
|
627
|
-
const
|
|
626
|
+
if (hasPacificaSdk(adapter)) {
|
|
627
|
+
const sdk = adapter.sdk;
|
|
628
|
+
const settings = await sdk.getAccountSettings(adapter.publicKey);
|
|
628
629
|
if (isJson())
|
|
629
630
|
return printJson(jsonOk(settings));
|
|
630
631
|
if (!Array.isArray(settings) || settings.length === 0) {
|
|
@@ -902,9 +903,19 @@ export function registerAccountCommands(program, getAdapter, isJson, getAdapterF
|
|
|
902
903
|
.command("twap-orders")
|
|
903
904
|
.description("Active TWAP orders")
|
|
904
905
|
.action(async () => {
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
906
|
+
let p;
|
|
907
|
+
try {
|
|
908
|
+
const adapter = await getAdapter();
|
|
909
|
+
p = pac(adapter);
|
|
910
|
+
}
|
|
911
|
+
catch (err) {
|
|
912
|
+
if (isJson())
|
|
913
|
+
return printJson(jsonError("EXCHANGE_ERROR", err instanceof Error ? err.message : String(err)));
|
|
914
|
+
console.error(chalk.red(`\n ${err instanceof Error ? err.message : String(err)}\n`));
|
|
915
|
+
return;
|
|
916
|
+
}
|
|
917
|
+
const sdk = p.sdk;
|
|
918
|
+
const orders = await sdk.getTWAPOrders(p.publicKey);
|
|
908
919
|
if (isJson())
|
|
909
920
|
return printJson(jsonOk(orders));
|
|
910
921
|
if (!orders || orders.length === 0) {
|
|
@@ -932,7 +943,73 @@ export function registerAccountCommands(program, getAdapter, isJson, getAdapterF
|
|
|
932
943
|
const exchanges = opts.exchange
|
|
933
944
|
? opts.exchange.split(",").map(e => e.trim())
|
|
934
945
|
: [...EXCHANGES];
|
|
935
|
-
|
|
946
|
+
// Fetch snapshots + funding + spot ALL in parallel
|
|
947
|
+
const fundingByExSym = new Map();
|
|
948
|
+
const spotRows = [];
|
|
949
|
+
let totalSpotValue = 0;
|
|
950
|
+
const [snapshots] = await Promise.all([
|
|
951
|
+
// Phase 1: balance + positions + orders
|
|
952
|
+
Promise.all(exchanges.map(ex => fetchExchangeSnapshot(ex, getAdapterForExchange))),
|
|
953
|
+
// Phase 2: funding payments (parallel with Phase 1)
|
|
954
|
+
Promise.allSettled(exchanges.map(async (exName) => {
|
|
955
|
+
try {
|
|
956
|
+
const adapter = await getAdapterForExchange(exName);
|
|
957
|
+
const payments = await adapter.getFundingPayments(200);
|
|
958
|
+
for (const p of payments) {
|
|
959
|
+
const sym = p.symbol.replace("-PERP", "").toUpperCase();
|
|
960
|
+
const key = `${exName}:${sym}`;
|
|
961
|
+
fundingByExSym.set(key, (fundingByExSym.get(key) ?? 0) + Number(p.payment));
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
catch { /* skip */ }
|
|
965
|
+
})),
|
|
966
|
+
// Phase 3: spot balances (parallel with Phase 1+2)
|
|
967
|
+
Promise.allSettled(exchanges.map(async (exName) => {
|
|
968
|
+
try {
|
|
969
|
+
const adapter = await getAdapterForExchange(exName);
|
|
970
|
+
if (exName === "hyperliquid") {
|
|
971
|
+
const { HyperliquidSpotAdapter } = await import("../exchanges/hyperliquid-spot.js");
|
|
972
|
+
const { HyperliquidAdapter } = await import("../exchanges/hyperliquid.js");
|
|
973
|
+
const hlSpot = new HyperliquidSpotAdapter(adapter);
|
|
974
|
+
await hlSpot.init();
|
|
975
|
+
const bals = await hlSpot.getSpotBalances();
|
|
976
|
+
const markets = await hlSpot.getSpotMarkets();
|
|
977
|
+
const priceMap = new Map(markets.map(m => [m.baseToken.toUpperCase(), Number(m.markPrice)]));
|
|
978
|
+
for (const b of bals) {
|
|
979
|
+
const base = b.token.replace(/-SPOT$/i, "").toUpperCase();
|
|
980
|
+
if (base === "USDC" || Number(b.total) <= 0)
|
|
981
|
+
continue;
|
|
982
|
+
const price = priceMap.get(base) ?? 0;
|
|
983
|
+
const value = price * Number(b.total);
|
|
984
|
+
if (value < 1)
|
|
985
|
+
continue;
|
|
986
|
+
totalSpotValue += value;
|
|
987
|
+
spotRows.push([chalk.white.bold(base), chalk.gray("hyperliquid"), b.total, `$${formatUsd(price)}`, `$${formatUsd(value)}`]);
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
else if (exName === "lighter") {
|
|
991
|
+
const { LighterAdapter } = await import("../exchanges/lighter.js");
|
|
992
|
+
const { LighterSpotAdapter } = await import("../exchanges/lighter-spot.js");
|
|
993
|
+
const ltSpot = new LighterSpotAdapter(adapter);
|
|
994
|
+
await ltSpot.init();
|
|
995
|
+
const bals = await ltSpot.getSpotBalances();
|
|
996
|
+
const markets = await ltSpot.getSpotMarkets();
|
|
997
|
+
const priceMap = new Map(markets.map(m => [m.baseToken.toUpperCase(), Number(m.markPrice)]));
|
|
998
|
+
for (const b of bals) {
|
|
999
|
+
if (b.token === "USDC" || b.token === "USDC_SPOT" || Number(b.total) <= 0)
|
|
1000
|
+
continue;
|
|
1001
|
+
const price = priceMap.get(b.token.toUpperCase()) ?? 0;
|
|
1002
|
+
const value = price * Number(b.total);
|
|
1003
|
+
if (value < 1)
|
|
1004
|
+
continue;
|
|
1005
|
+
totalSpotValue += value;
|
|
1006
|
+
spotRows.push([chalk.white.bold(b.token), chalk.gray("lighter"), b.total, `$${formatUsd(price)}`, `$${formatUsd(value)}`]);
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
catch { /* spot not available */ }
|
|
1011
|
+
})),
|
|
1012
|
+
]);
|
|
936
1013
|
const summary = buildSummary(snapshots);
|
|
937
1014
|
if (isJson()) {
|
|
938
1015
|
return printJson(jsonOk(summary));
|
|
@@ -955,7 +1032,6 @@ export function registerAccountCommands(program, getAdapter, isJson, getAdapterF
|
|
|
955
1032
|
chalk.green("connected"),
|
|
956
1033
|
];
|
|
957
1034
|
});
|
|
958
|
-
// Totals row
|
|
959
1035
|
balRows.push([
|
|
960
1036
|
chalk.cyan.bold("TOTAL"),
|
|
961
1037
|
chalk.cyan.bold(`$${formatUsd(summary.totalEquity)}`),
|
|
@@ -965,21 +1041,6 @@ export function registerAccountCommands(program, getAdapter, isJson, getAdapterF
|
|
|
965
1041
|
"",
|
|
966
1042
|
]);
|
|
967
1043
|
console.log(makeTable(["Exchange", "Equity", "Available", "Margin Used", "uPnL", "Status"], balRows));
|
|
968
|
-
// ── Positions + Funding ──
|
|
969
|
-
// Fetch funding per exchange in parallel
|
|
970
|
-
const fundingByExSym = new Map();
|
|
971
|
-
await Promise.allSettled(exchanges.map(async (exName) => {
|
|
972
|
-
try {
|
|
973
|
-
const adapter = await getAdapterForExchange(exName);
|
|
974
|
-
const payments = await adapter.getFundingPayments(200);
|
|
975
|
-
for (const p of payments) {
|
|
976
|
-
const sym = p.symbol.replace("-PERP", "").toUpperCase();
|
|
977
|
-
const key = `${exName}:${sym}`;
|
|
978
|
-
fundingByExSym.set(key, (fundingByExSym.get(key) ?? 0) + Number(p.payment));
|
|
979
|
-
}
|
|
980
|
-
}
|
|
981
|
-
catch { /* skip */ }
|
|
982
|
-
}));
|
|
983
1044
|
if (summary.positions.length > 0) {
|
|
984
1045
|
console.log(chalk.white.bold("\n Open Positions"));
|
|
985
1046
|
const posRows = summary.positions.map(p => {
|
|
@@ -1005,65 +1066,7 @@ export function registerAccountCommands(program, getAdapter, isJson, getAdapterF
|
|
|
1005
1066
|
else {
|
|
1006
1067
|
console.log(chalk.gray("\n No open positions.\n"));
|
|
1007
1068
|
}
|
|
1008
|
-
// ── Spot Holdings ──
|
|
1009
|
-
const spotRows = [];
|
|
1010
|
-
let totalSpotValue = 0;
|
|
1011
|
-
for (const exName of exchanges) {
|
|
1012
|
-
try {
|
|
1013
|
-
const adapter = await getAdapterForExchange(exName);
|
|
1014
|
-
if (exName === "hyperliquid") {
|
|
1015
|
-
const { HyperliquidSpotAdapter } = await import("../exchanges/hyperliquid-spot.js");
|
|
1016
|
-
const hlSpot = new HyperliquidSpotAdapter(adapter);
|
|
1017
|
-
await hlSpot.init();
|
|
1018
|
-
const bals = await hlSpot.getSpotBalances();
|
|
1019
|
-
const markets = await hlSpot.getSpotMarkets();
|
|
1020
|
-
const priceMap = new Map(markets.map(m => [m.baseToken.toUpperCase(), Number(m.markPrice)]));
|
|
1021
|
-
for (const b of bals) {
|
|
1022
|
-
const base = b.token.replace(/-SPOT$/i, "").toUpperCase();
|
|
1023
|
-
if (base === "USDC" || Number(b.total) <= 0)
|
|
1024
|
-
continue;
|
|
1025
|
-
const price = priceMap.get(base) ?? 0;
|
|
1026
|
-
const value = price * Number(b.total);
|
|
1027
|
-
if (value < 1)
|
|
1028
|
-
continue; // skip dust
|
|
1029
|
-
totalSpotValue += value;
|
|
1030
|
-
spotRows.push([
|
|
1031
|
-
chalk.white.bold(base),
|
|
1032
|
-
chalk.gray("hyperliquid"),
|
|
1033
|
-
b.total,
|
|
1034
|
-
`$${formatUsd(price)}`,
|
|
1035
|
-
`$${formatUsd(value)}`,
|
|
1036
|
-
]);
|
|
1037
|
-
}
|
|
1038
|
-
}
|
|
1039
|
-
else if (exName === "lighter") {
|
|
1040
|
-
const { LighterAdapter } = await import("../exchanges/lighter.js");
|
|
1041
|
-
const { LighterSpotAdapter } = await import("../exchanges/lighter-spot.js");
|
|
1042
|
-
const ltSpot = new LighterSpotAdapter(adapter);
|
|
1043
|
-
await ltSpot.init();
|
|
1044
|
-
const bals = await ltSpot.getSpotBalances();
|
|
1045
|
-
const markets = await ltSpot.getSpotMarkets();
|
|
1046
|
-
const priceMap = new Map(markets.map(m => [m.baseToken.toUpperCase(), Number(m.markPrice)]));
|
|
1047
|
-
for (const b of bals) {
|
|
1048
|
-
if (b.token === "USDC" || b.token === "USDC_SPOT" || Number(b.total) <= 0)
|
|
1049
|
-
continue;
|
|
1050
|
-
const price = priceMap.get(b.token.toUpperCase()) ?? 0;
|
|
1051
|
-
const value = price * Number(b.total);
|
|
1052
|
-
if (value < 1)
|
|
1053
|
-
continue;
|
|
1054
|
-
totalSpotValue += value;
|
|
1055
|
-
spotRows.push([
|
|
1056
|
-
chalk.white.bold(b.token),
|
|
1057
|
-
chalk.gray("lighter"),
|
|
1058
|
-
b.total,
|
|
1059
|
-
`$${formatUsd(price)}`,
|
|
1060
|
-
`$${formatUsd(value)}`,
|
|
1061
|
-
]);
|
|
1062
|
-
}
|
|
1063
|
-
}
|
|
1064
|
-
}
|
|
1065
|
-
catch { /* spot not available */ }
|
|
1066
|
-
}
|
|
1069
|
+
// ── Spot Holdings (already fetched in parallel above) ──
|
|
1067
1070
|
if (spotRows.length > 0) {
|
|
1068
1071
|
console.log(chalk.white.bold("\n Spot Holdings"));
|
|
1069
1072
|
console.log(makeTable(["Token", "Exchange", "Amount", "Price", "Value"], spotRows));
|
package/dist/commands/agent.d.ts
CHANGED
|
@@ -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 registerAgentCommands(program: Command, getAdapter: () => Promise<ExchangeAdapter>, isJson: () => boolean): void;
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
|
-
import type
|
|
2
|
+
import { type ExchangeAdapter } from "../../exchanges/index.js";
|
|
3
3
|
export declare function registerArbManageCommands(program: Command, getAdapterForExchange: (exchange: string) => Promise<ExchangeAdapter>, isJson: () => boolean): void;
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
2
|
import { makeTable, formatUsd, formatPnl, printJson, jsonOk } from "../../utils.js";
|
|
3
|
+
import { SPOT_PERP_TOKEN_MAP } from "../../exchanges/index.js";
|
|
3
4
|
import { readExecutionLog, logExecution } from "../../execution-log.js";
|
|
4
5
|
import { toHourlyRate, computeAnnualSpread } from "../../funding.js";
|
|
5
6
|
import { fetchPacificaPrices, fetchHyperliquidMeta, fetchLighterOrderBookDetails, fetchLighterFundingRates, } from "../../shared-api.js";
|
|
6
7
|
import { computeBasisRisk } from "../../arb-utils.js";
|
|
7
8
|
import { smartOrder } from "../../smart-order.js";
|
|
8
9
|
import { removePosition as persistRemovePosition, getPositions as getArbStatePositions, loadArbState } from "../../arb-state.js";
|
|
9
|
-
import { SPOT_PERP_TOKEN_MAP } from "../../exchanges/spot-interface.js";
|
|
10
10
|
import { fetchAllBalances, computeRebalancePlan } from "../../rebalance.js";
|
|
11
11
|
import { EXCHANGE_TO_CHAIN, getBestQuote } from "../../bridge-engine.js";
|
|
12
12
|
import { computeEnhancedStats } from "../../arb-history-stats.js";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
2
|
import { formatUsd, printJson, jsonOk, makeTable } from "../utils.js";
|
|
3
3
|
import { computeExecutableSize } from "../liquidity.js";
|
|
4
|
-
import { computeMatchedSize, computeSpotPerpMatchedSize } from "../arb/
|
|
4
|
+
import { computeMatchedSize, computeSpotPerpMatchedSize } from "../arb/index.js";
|
|
5
5
|
import { computeAnnualSpread, toHourlyRate } from "../funding.js";
|
|
6
6
|
import { fetchPacificaPricesRaw, parsePacificaRaw, fetchHyperliquidMetaRaw, parseHyperliquidMetaRaw, fetchLighterOrderBookDetailsRaw, fetchLighterFundingRatesRaw, parseLighterRaw, } from "../shared-api.js";
|
|
7
7
|
import { scanDexArb } from "../dex-asset-map.js";
|
package/dist/commands/arb.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
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 registerArbCommands(program: Command, isJson: () => boolean, getAdapterForExchange?: (exchange: string) => Promise<ExchangeAdapter>): void;
|
|
4
4
|
export interface PriceSnapshot {
|
|
5
5
|
symbol: string;
|
package/dist/commands/bot.d.ts
CHANGED
|
@@ -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 registerBotCommands(program: Command, getAdapter: () => Promise<ExchangeAdapter>, getAdapterFor: (exchange: string) => Promise<ExchangeAdapter>, isJson: () => boolean): void;
|
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, 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,8 +81,9 @@ 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
|
|
@@ -96,6 +94,7 @@ export function registerBotCommands(program, getAdapter, getAdapterFor, isJson)
|
|
|
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,180 @@ 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
|
+
.action(async (symbol, opts) => {
|
|
347
|
+
const adapter = await getAdapter();
|
|
348
|
+
const sym = (symbol ?? "ETH").toUpperCase();
|
|
349
|
+
const config = {
|
|
350
|
+
name: `apex-${sym.toLowerCase()}-${Date.now().toString(36)}`,
|
|
351
|
+
exchange: adapter.name,
|
|
352
|
+
symbol: sym,
|
|
353
|
+
strategy: {
|
|
354
|
+
type: "apex",
|
|
355
|
+
preset: opts.preset,
|
|
356
|
+
max_slots: parseInt(opts.maxSlots),
|
|
357
|
+
},
|
|
358
|
+
entry_conditions: [{ type: "always", value: 0 }],
|
|
359
|
+
exit_conditions: [],
|
|
360
|
+
risk: {
|
|
361
|
+
max_position_usd: 1000,
|
|
362
|
+
max_daily_loss: parseFloat(opts.dailyLimit),
|
|
363
|
+
max_drawdown: parseFloat(opts.dailyLimit) * 2,
|
|
364
|
+
pause_after_loss_sec: 300,
|
|
365
|
+
max_open_bots: 1,
|
|
366
|
+
},
|
|
367
|
+
monitor_interval_sec: 10,
|
|
368
|
+
};
|
|
369
|
+
const mode = resolveOutputMode(isJson, opts.headless);
|
|
370
|
+
const log = makeLog();
|
|
371
|
+
await runBot(adapter, config, undefined, log, undefined, mode);
|
|
372
|
+
});
|
|
373
|
+
// ── bot reflect ──
|
|
374
|
+
bot
|
|
375
|
+
.command("reflect")
|
|
376
|
+
.description("Analyze trading performance")
|
|
377
|
+
.option("--period <days>", "Analysis period in days", "7")
|
|
378
|
+
.action(async (opts) => {
|
|
379
|
+
const { readJournal } = await import("../bot/trade-journal.js");
|
|
380
|
+
const { analyzePerformance } = await import("../bot/reflect.js");
|
|
381
|
+
const periodDays = Number(opts.period);
|
|
382
|
+
const entries = readJournal({ from: Date.now() - periodDays * 86400000 });
|
|
383
|
+
const report = analyzePerformance(entries, periodDays);
|
|
384
|
+
if (isJson())
|
|
385
|
+
return printJson(jsonOk(report));
|
|
386
|
+
console.log(chalk.cyan.bold("\n Performance Report\n"));
|
|
387
|
+
console.log(` Period: ${chalk.white(`${report.period.days}d`)} (${new Date(report.period.from).toLocaleDateString()} – ${new Date(report.period.to).toLocaleDateString()})`);
|
|
388
|
+
console.log(` Total trades: ${chalk.white(report.totalTrades)}`);
|
|
389
|
+
if (report.totalTrades === 0) {
|
|
390
|
+
console.log(chalk.gray("\n No trades in the selected period.\n"));
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
const wrColor = report.winRate >= 0.5 ? chalk.green : chalk.red;
|
|
394
|
+
console.log(` Win rate: ${wrColor((report.winRate * 100).toFixed(1) + "%")}`);
|
|
395
|
+
console.log(` Avg win: ${chalk.green(`$${report.avgWin.toFixed(2)}`)}`);
|
|
396
|
+
console.log(` Avg loss: ${chalk.red(`$${report.avgLoss.toFixed(2)}`)}`);
|
|
397
|
+
const pfColor = report.profitFactor >= 1.0 ? chalk.green : chalk.red;
|
|
398
|
+
console.log(` Profit factor: ${pfColor(report.profitFactor === Infinity ? "∞" : report.profitFactor.toFixed(2))}`);
|
|
399
|
+
console.log(` Fee drag: ${chalk.yellow((report.feeDragRatio * 100).toFixed(1) + "%")}`);
|
|
400
|
+
console.log(` Direction: ${chalk.gray(`long ${(report.directionSplit.long * 100).toFixed(0)}% / short ${(report.directionSplit.short * 100).toFixed(0)}%`)}`);
|
|
401
|
+
if (report.bestStrategy)
|
|
402
|
+
console.log(` Best strategy: ${chalk.green(report.bestStrategy)}`);
|
|
403
|
+
if (report.worstStrategy && report.worstStrategy !== report.bestStrategy)
|
|
404
|
+
console.log(` Worst strategy:${chalk.red(report.worstStrategy)}`);
|
|
405
|
+
console.log(chalk.cyan.bold("\n Suggestions:\n"));
|
|
406
|
+
for (const s of report.suggestions) {
|
|
407
|
+
console.log(` ${chalk.white("•")} ${chalk.gray(s)}`);
|
|
408
|
+
}
|
|
409
|
+
console.log();
|
|
410
|
+
});
|
|
411
|
+
// ── bot run <strategy> <symbol> ──
|
|
412
|
+
bot
|
|
413
|
+
.command("run <strategy> [symbol]")
|
|
414
|
+
.description("Run any registered strategy (symbol optional for multi-symbol strategies like funding-auto)")
|
|
415
|
+
.option("--config <path>", "YAML/JSON config file")
|
|
416
|
+
.option("--headless", "Run without TUI dashboard")
|
|
417
|
+
.option("--param <key=value>", "Strategy parameter (repeatable)", (val, acc) => [...acc, val], [])
|
|
418
|
+
.action(async (strategyName, symbol, opts) => {
|
|
419
|
+
const sym = symbol?.toUpperCase() || "ALL";
|
|
420
|
+
// Load or build config
|
|
421
|
+
let config;
|
|
422
|
+
if (opts.config) {
|
|
423
|
+
config = loadBotConfig(opts.config);
|
|
424
|
+
// Override strategy type and symbol from CLI args
|
|
425
|
+
config.strategy = { ...config.strategy, type: strategyName };
|
|
426
|
+
config.symbol = sym;
|
|
427
|
+
}
|
|
428
|
+
else {
|
|
429
|
+
// Parse --param key=value pairs
|
|
430
|
+
const params = { type: strategyName };
|
|
431
|
+
for (const kv of opts.param) {
|
|
432
|
+
const eq = kv.indexOf("=");
|
|
433
|
+
if (eq === -1)
|
|
434
|
+
continue;
|
|
435
|
+
const key = kv.slice(0, eq);
|
|
436
|
+
const raw = kv.slice(eq + 1);
|
|
437
|
+
// Try to coerce numbers and booleans
|
|
438
|
+
if (raw === "true")
|
|
439
|
+
params[key] = true;
|
|
440
|
+
else if (raw === "false")
|
|
441
|
+
params[key] = false;
|
|
442
|
+
else if (!isNaN(Number(raw)) && raw !== "")
|
|
443
|
+
params[key] = Number(raw);
|
|
444
|
+
else
|
|
445
|
+
params[key] = raw;
|
|
446
|
+
}
|
|
447
|
+
const adapter = await getAdapter();
|
|
448
|
+
config = {
|
|
449
|
+
name: `${strategyName}-${sym.toLowerCase()}-${Date.now().toString(36)}`,
|
|
450
|
+
exchange: adapter.name,
|
|
451
|
+
symbol: sym,
|
|
452
|
+
strategy: params,
|
|
453
|
+
entry_conditions: [{ type: "always", value: 0 }],
|
|
454
|
+
exit_conditions: [],
|
|
455
|
+
risk: {
|
|
456
|
+
max_position_usd: 1000,
|
|
457
|
+
max_daily_loss: 100,
|
|
458
|
+
max_drawdown: 200,
|
|
459
|
+
pause_after_loss_sec: 300,
|
|
460
|
+
max_open_bots: 5,
|
|
461
|
+
},
|
|
462
|
+
monitor_interval_sec: 10,
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
// Verify strategy is registered
|
|
466
|
+
await import("../bot/engine.js");
|
|
467
|
+
const { getStrategy } = await import("../bot/strategy-registry.js");
|
|
468
|
+
if (!getStrategy(strategyName)) {
|
|
469
|
+
console.error(chalk.red(`\n Unknown strategy: "${strategyName}"`));
|
|
470
|
+
console.error(chalk.gray(` Run 'perp bot list-strategies' to see available strategies.\n`));
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
const adapter = await getAdapter();
|
|
474
|
+
config.exchange = adapter.name;
|
|
475
|
+
const mode = resolveOutputMode(isJson, opts.headless);
|
|
476
|
+
const log = makeLog();
|
|
477
|
+
// Multi-exchange strategies need all adapters
|
|
478
|
+
let extraAdapters;
|
|
479
|
+
const multiExchangeStrategies = ["funding-auto", "funding-arb", "funding-arb-v2", "basis-arb", "hedge-agent"];
|
|
480
|
+
if (multiExchangeStrategies.includes(strategyName) && getAdapterFor) {
|
|
481
|
+
extraAdapters = new Map();
|
|
482
|
+
for (const ex of ["pacifica", "hyperliquid", "lighter"]) {
|
|
483
|
+
if (ex === adapter.name)
|
|
484
|
+
continue;
|
|
485
|
+
try {
|
|
486
|
+
const a = await getAdapterFor(ex);
|
|
487
|
+
extraAdapters.set(ex, a);
|
|
488
|
+
}
|
|
489
|
+
catch { /* skip unavailable exchanges */ }
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
await runBot(adapter, config, undefined, log, extraAdapters, mode);
|
|
316
493
|
});
|
|
317
494
|
// ── bot example ──
|
|
318
495
|
bot
|
|
@@ -341,6 +518,13 @@ function makeLog() {
|
|
|
341
518
|
console.log(`${chalk.gray(ts)} ${msg}`);
|
|
342
519
|
};
|
|
343
520
|
}
|
|
521
|
+
function resolveOutputMode(isJson, headless) {
|
|
522
|
+
if (isJson())
|
|
523
|
+
return "json";
|
|
524
|
+
if (headless)
|
|
525
|
+
return "headless";
|
|
526
|
+
return process.stdout.isTTY ? "tui" : "headless";
|
|
527
|
+
}
|
|
344
528
|
function printBotJobStarted(name, jobId) {
|
|
345
529
|
console.log(chalk.green(`\n Bot "${name}" started in background.`));
|
|
346
530
|
console.log(` Job ID: ${chalk.white.bold(jobId)}`);
|