perp-cli 0.7.1 → 0.7.3
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/dist/arb/sizing.d.ts +1 -0
- package/dist/arb/sizing.js +25 -6
- package/dist/arb/utils.js +1 -0
- package/dist/bot/conditions.js +1 -2
- package/dist/bot/config.d.ts +3 -1
- package/dist/bot/config.js +4 -2
- package/dist/bot/engine.js +63 -18
- package/dist/bot/index.d.ts +1 -1
- package/dist/bot/index.js +1 -1
- package/dist/bot/presets.js +1 -0
- package/dist/bot/strategies/dca-strategy.js +8 -4
- package/dist/bot/strategies/funding-arb-strategy.d.ts +1 -1
- package/dist/bot/strategies/funding-arb-strategy.js +126 -14
- package/dist/bot/strategies/grid-strategy.js +56 -51
- package/dist/bot/tui/BotDashboard.d.ts +7 -0
- package/dist/bot/tui/BotDashboard.js +3 -2
- package/dist/bot/tui/index.d.ts +1 -1
- package/dist/cli-spec.js +1 -1
- package/dist/commands/account.js +1 -1
- package/dist/commands/arb/index.js +12 -4
- package/dist/commands/arb-auto.d.ts +2 -0
- package/dist/commands/arb-auto.js +41 -18
- package/dist/commands/arb.d.ts +1 -0
- package/dist/commands/arb.js +26 -5
- package/dist/commands/bot.js +47 -10
- package/dist/commands/bridge.js +1 -1
- package/dist/commands/dashboard.js +1 -1
- package/dist/commands/funds.js +1 -1
- package/dist/commands/history.js +6 -2
- package/dist/commands/init.d.ts +2 -2
- package/dist/commands/init.js +5 -0
- package/dist/commands/market.js +20 -11
- package/dist/commands/rebalance.js +3 -3
- package/dist/commands/risk.js +1 -1
- package/dist/commands/settings.js +1 -1
- package/dist/commands/trade.js +3 -3
- package/dist/commands/wallet.js +2 -2
- package/dist/config.d.ts +1 -1
- package/dist/config.js +1 -0
- package/dist/constants.js +1 -0
- package/dist/exchanges/aster.d.ts +4 -0
- package/dist/exchanges/aster.js +39 -28
- package/dist/exchanges/edgex.d.ts +62 -0
- package/dist/exchanges/edgex.js +552 -0
- package/dist/exchanges/index.d.ts +1 -0
- package/dist/exchanges/index.js +1 -0
- package/dist/exchanges/registry.js +5 -0
- package/dist/funding/normalize.js +1 -0
- package/dist/funding/rates.d.ts +1 -1
- package/dist/funding/rates.js +36 -3
- package/dist/index.js +19 -6
- package/dist/strategies/funding-arb.js +28 -5
- package/package.json +1 -1
package/dist/commands/bot.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
2
|
import { printJson, jsonOk } from "../utils.js";
|
|
3
|
-
import { loadBotConfig, quickGridConfig, quickDCAConfig, runBot, PRESETS, getPreset, getPresetsByStrategy } from "../bot/index.js";
|
|
3
|
+
import { loadBotConfig, parseStrategy, quickGridConfig, quickDCAConfig, runBot, PRESETS, getPreset, getPresetsByStrategy } from "../bot/index.js";
|
|
4
4
|
import { updateJobState } from "../jobs.js";
|
|
5
5
|
import { runTWAP, runFundingArb, runGrid, runDCA, runTrailingStop } from "../strategies/index.js";
|
|
6
6
|
export function registerBotCommands(program, getAdapter, getAdapterFor, isJson) {
|
|
@@ -88,7 +88,7 @@ export function registerBotCommands(program, getAdapter, getAdapterFor, isJson)
|
|
|
88
88
|
// ── bot quick dca ──
|
|
89
89
|
bot
|
|
90
90
|
.command("quick-dca <symbol> <side> <amount> <interval>")
|
|
91
|
-
.description("Quick-start a DCA bot")
|
|
91
|
+
.description("Quick-start a DCA bot (interval: seconds between orders)")
|
|
92
92
|
.option("--orders <n>", "Total orders (0 = unlimited)", "0")
|
|
93
93
|
.option("--price-limit <price>", "Don't buy above / sell below this price")
|
|
94
94
|
.option("--trigger-drop <pct>", "Only buy when price drops X% from recent high")
|
|
@@ -140,7 +140,7 @@ export function registerBotCommands(program, getAdapter, getAdapterFor, isJson)
|
|
|
140
140
|
.option("--close-spread <pct>", "Close when spread drops below (%)", "5")
|
|
141
141
|
.option("--size <usd>", "Position size per leg ($)", "50")
|
|
142
142
|
.option("--max-positions <n>", "Max simultaneous positions", "3")
|
|
143
|
-
.option("--exchanges <list>", "Comma-separated exchanges", "pacifica,hyperliquid,lighter")
|
|
143
|
+
.option("--exchanges <list>", "Comma-separated exchanges", "pacifica,hyperliquid,lighter,aster")
|
|
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)")
|
|
@@ -343,6 +343,8 @@ export function registerBotCommands(program, getAdapter, getAdapterFor, isJson)
|
|
|
343
343
|
.option("--max-slots <n>", "Max concurrent positions", "3")
|
|
344
344
|
.option("--daily-limit <usd>", "Daily loss limit in USD", "250")
|
|
345
345
|
.option("--headless", "Run without TUI dashboard")
|
|
346
|
+
.option("--background", "Run in background (tmux)")
|
|
347
|
+
.option("--job-id <id>", "Job ID")
|
|
346
348
|
.action(async (symbol, opts) => {
|
|
347
349
|
const adapter = await getAdapter();
|
|
348
350
|
const sym = (symbol ?? "ETH").toUpperCase();
|
|
@@ -366,9 +368,27 @@ export function registerBotCommands(program, getAdapter, getAdapterFor, isJson)
|
|
|
366
368
|
},
|
|
367
369
|
monitor_interval_sec: 10,
|
|
368
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
|
+
}
|
|
369
389
|
const mode = resolveOutputMode(isJson, opts.headless);
|
|
370
390
|
const log = makeLog();
|
|
371
|
-
await runBot(adapter, config,
|
|
391
|
+
await runBot(adapter, config, opts.jobId, log, undefined, mode);
|
|
372
392
|
});
|
|
373
393
|
// ── bot reflect ──
|
|
374
394
|
bot
|
|
@@ -411,12 +431,24 @@ export function registerBotCommands(program, getAdapter, getAdapterFor, isJson)
|
|
|
411
431
|
// ── bot run <strategy> <symbol> ──
|
|
412
432
|
bot
|
|
413
433
|
.command("run <strategy> [symbol]")
|
|
414
|
-
.description("Run
|
|
434
|
+
.description("Run a strategy (use 'perp bot list-strategies' to see all). Symbol optional for multi-symbol strategies.")
|
|
415
435
|
.option("--config <path>", "YAML/JSON config file")
|
|
416
436
|
.option("--headless", "Run without TUI dashboard")
|
|
417
437
|
.option("--param <key=value>", "Strategy parameter (repeatable)", (val, acc) => [...acc, val], [])
|
|
418
438
|
.action(async (strategyName, symbol, opts) => {
|
|
419
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
|
+
}
|
|
420
452
|
// Load or build config
|
|
421
453
|
let config;
|
|
422
454
|
if (opts.config) {
|
|
@@ -445,11 +477,12 @@ export function registerBotCommands(program, getAdapter, getAdapterFor, isJson)
|
|
|
445
477
|
params[key] = raw;
|
|
446
478
|
}
|
|
447
479
|
const adapter = await getAdapter();
|
|
480
|
+
const strategy = parseStrategy(strategyName, params);
|
|
448
481
|
config = {
|
|
449
482
|
name: `${strategyName}-${sym.toLowerCase()}-${Date.now().toString(36)}`,
|
|
450
483
|
exchange: adapter.name,
|
|
451
484
|
symbol: sym,
|
|
452
|
-
strategy
|
|
485
|
+
strategy,
|
|
453
486
|
entry_conditions: [{ type: "always", value: 0 }],
|
|
454
487
|
exit_conditions: [],
|
|
455
488
|
risk: {
|
|
@@ -464,10 +497,14 @@ export function registerBotCommands(program, getAdapter, getAdapterFor, isJson)
|
|
|
464
497
|
}
|
|
465
498
|
// Verify strategy is registered
|
|
466
499
|
await import("../bot/engine.js");
|
|
467
|
-
const { getStrategy } = await import("../bot/strategy-registry.js");
|
|
500
|
+
const { getStrategy, listStrategies: listStrats } = await import("../bot/strategy-registry.js");
|
|
468
501
|
if (!getStrategy(strategyName)) {
|
|
502
|
+
const available = listStrats();
|
|
469
503
|
console.error(chalk.red(`\n Unknown strategy: "${strategyName}"`));
|
|
470
|
-
console.error(chalk.gray(
|
|
504
|
+
console.error(chalk.gray(`\n Available strategies:`));
|
|
505
|
+
for (const s of available)
|
|
506
|
+
console.error(chalk.gray(` - ${s}`));
|
|
507
|
+
console.error();
|
|
471
508
|
return;
|
|
472
509
|
}
|
|
473
510
|
const adapter = await getAdapter();
|
|
@@ -479,7 +516,7 @@ export function registerBotCommands(program, getAdapter, getAdapterFor, isJson)
|
|
|
479
516
|
const multiExchangeStrategies = ["funding-auto", "funding-arb", "funding-arb-v2", "basis-arb", "hedge-agent"];
|
|
480
517
|
if (multiExchangeStrategies.includes(strategyName) && getAdapterFor) {
|
|
481
518
|
extraAdapters = new Map();
|
|
482
|
-
for (const ex of ["pacifica", "hyperliquid", "lighter"]) {
|
|
519
|
+
for (const ex of ["pacifica", "hyperliquid", "lighter", "aster"]) {
|
|
483
520
|
if (ex === adapter.name)
|
|
484
521
|
continue;
|
|
485
522
|
try {
|
|
@@ -647,7 +684,7 @@ function registerRunSubcommands(parent, getAdapter, getAdapterFor, isJson) {
|
|
|
647
684
|
.option("--interval <sec>", "Check interval in seconds", "60")
|
|
648
685
|
.option("--auto-execute", "Actually place trades (default: monitor only)")
|
|
649
686
|
.option("--max-positions <n>", "Max simultaneous positions", "3")
|
|
650
|
-
.option("--exchanges <list>", "Comma-separated exchanges to use", "pacifica,hyperliquid,lighter")
|
|
687
|
+
.option("--exchanges <list>", "Comma-separated exchanges to use", "pacifica,hyperliquid,lighter,aster")
|
|
651
688
|
.option("--auto-rebalance", "Trigger rebalance alerts when exchange balance is low")
|
|
652
689
|
.option("--rebalance-threshold <usd>", "Rebalance when available < this ($)", "100")
|
|
653
690
|
.option("--max-drawdown <usd>", "Close all positions if total uPnL exceeds this loss ($)")
|
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")
|
|
@@ -50,7 +50,7 @@ export function registerDashboardCommands(program, getAdapterForExchange, isJson
|
|
|
50
50
|
.description("Launch live web dashboard for real-time portfolio monitoring")
|
|
51
51
|
.option("-p, --port <port>", "Port to serve dashboard on (auto-finds free port)", "3456")
|
|
52
52
|
.option("--interval <ms>", "Poll interval in milliseconds", "5000")
|
|
53
|
-
.option("--exchanges <list>", "Comma-separated exchanges to monitor", "pacifica,hyperliquid,lighter")
|
|
53
|
+
.option("--exchanges <list>", "Comma-separated exchanges to monitor", "pacifica,hyperliquid,lighter,aster")
|
|
54
54
|
.option("--dex <list>", "Comma-separated HIP-3 dexes to monitor (e.g. xyz,flx,hyna)")
|
|
55
55
|
.option("--no-auto-dex", "Disable auto-detection of HIP-3 dexes with active positions")
|
|
56
56
|
.action(async (opts) => {
|
package/dist/commands/funds.js
CHANGED
|
@@ -628,7 +628,7 @@ export function registerFundsCommands(program, getAdapter, isJson, getNetwork) {
|
|
|
628
628
|
// ═══════════════════════════════════════════════════════
|
|
629
629
|
funds
|
|
630
630
|
.command("bridge")
|
|
631
|
-
.description("Bridge USDC between chains via CCTP V2")
|
|
631
|
+
.description("Bridge USDC between chains via CCTP V2. See also: perp bridge (deBridge DLN)")
|
|
632
632
|
.requiredOption("--from <chain>", "Source chain (arbitrum, ethereum)")
|
|
633
633
|
.requiredOption("--to <chain>", "Destination chain (arbitrum, ethereum, solana)")
|
|
634
634
|
.requiredOption("--amount <amount>", "USDC amount")
|
package/dist/commands/history.js
CHANGED
|
@@ -4,7 +4,7 @@ import { readExecutionLog, getExecutionStats, pruneExecutionLog } from "../execu
|
|
|
4
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
|
-
const EXCHANGES = ["pacifica", "hyperliquid", "lighter"];
|
|
7
|
+
const EXCHANGES = ["pacifica", "hyperliquid", "lighter", "aster"];
|
|
8
8
|
function parseSince(since) {
|
|
9
9
|
if (!since)
|
|
10
10
|
return undefined;
|
|
@@ -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),
|
package/dist/commands/init.d.ts
CHANGED
|
@@ -5,10 +5,10 @@ declare function loadEnvFile(): Record<string, string>;
|
|
|
5
5
|
declare function setEnvVar(key: string, value: string): void;
|
|
6
6
|
export declare const EXCHANGE_ENV_MAP: Record<string, {
|
|
7
7
|
envKey: string;
|
|
8
|
-
chain: "solana" | "evm";
|
|
8
|
+
chain: "solana" | "evm" | "apikey";
|
|
9
9
|
label: string;
|
|
10
10
|
}>;
|
|
11
|
-
export declare function validateKey(chain: "solana" | "evm", key: string): Promise<{
|
|
11
|
+
export declare function validateKey(chain: "solana" | "evm" | "apikey", key: string): Promise<{
|
|
12
12
|
valid: boolean;
|
|
13
13
|
address: string;
|
|
14
14
|
}>;
|
package/dist/commands/init.js
CHANGED
|
@@ -70,6 +70,7 @@ export const EXCHANGE_ENV_MAP = {
|
|
|
70
70
|
pacifica: { envKey: "PACIFICA_PRIVATE_KEY", chain: "solana", label: "Pacifica (Solana)" },
|
|
71
71
|
hyperliquid: { envKey: "HL_PRIVATE_KEY", chain: "evm", label: "Hyperliquid (EVM)" },
|
|
72
72
|
lighter: { envKey: "LIGHTER_PRIVATE_KEY", chain: "evm", label: "Lighter (EVM)" },
|
|
73
|
+
aster: { envKey: "ASTER_API_KEY", chain: "apikey", label: "Aster (BNB Chain)" },
|
|
73
74
|
};
|
|
74
75
|
// ── Validate keys ─────────────────────────────────────────────
|
|
75
76
|
export async function validateKey(chain, key) {
|
|
@@ -91,6 +92,10 @@ export async function validateKey(chain, key) {
|
|
|
91
92
|
return { valid: false, address: "" };
|
|
92
93
|
}
|
|
93
94
|
}
|
|
95
|
+
else if (chain === "apikey") {
|
|
96
|
+
// API key validation: just check it's a non-empty hex-like string
|
|
97
|
+
return { valid: key.length >= 16, address: key.slice(0, 8) + "..." + key.slice(-4) };
|
|
98
|
+
}
|
|
94
99
|
else {
|
|
95
100
|
try {
|
|
96
101
|
const { ethers } = await import("ethers");
|
package/dist/commands/market.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { makeTable, formatUsd, formatPercent, printJson, jsonOk, jsonError, withJsonErrors } from "../utils.js";
|
|
2
2
|
import chalk from "chalk";
|
|
3
3
|
import { hasPacificaSdk, isDexCapable } from "../exchanges/capabilities.js";
|
|
4
|
-
const EXCHANGES = ["pacifica", "hyperliquid", "lighter"];
|
|
4
|
+
const EXCHANGES = ["pacifica", "hyperliquid", "lighter", "aster"];
|
|
5
5
|
export function registerMarketCommands(program, getAdapter, isJson, getAdapterForExchange) {
|
|
6
6
|
const market = program.command("market").description("Market data commands");
|
|
7
7
|
market
|
|
@@ -163,7 +163,7 @@ export function registerMarketCommands(program, getAdapter, isJson, getAdapterFo
|
|
|
163
163
|
if (isJson())
|
|
164
164
|
return printJson(jsonError("SYMBOL_NOT_FOUND", `Market ${sym} not found`));
|
|
165
165
|
console.log(chalk.red(`\n Market "${sym}" not found.\n`));
|
|
166
|
-
return;
|
|
166
|
+
return process.exit(1);
|
|
167
167
|
}
|
|
168
168
|
if (isJson())
|
|
169
169
|
return printJson(jsonOk(m));
|
|
@@ -228,9 +228,10 @@ export function registerMarketCommands(program, getAdapter, isJson, getAdapterFo
|
|
|
228
228
|
market
|
|
229
229
|
.command("trades <symbol>")
|
|
230
230
|
.description("Recent trades for a symbol")
|
|
231
|
-
.
|
|
231
|
+
.option("--limit <n>", "Number of recent trades to show", "20")
|
|
232
|
+
.action(async (symbol, opts) => {
|
|
232
233
|
const adapter = await getAdapter();
|
|
233
|
-
const trades = await adapter.getRecentTrades(symbol.toUpperCase(),
|
|
234
|
+
const trades = await adapter.getRecentTrades(symbol.toUpperCase(), parseInt(opts.limit));
|
|
234
235
|
if (isJson())
|
|
235
236
|
return printJson(jsonOk(trades));
|
|
236
237
|
if (trades.length === 0) {
|
|
@@ -258,12 +259,20 @@ export function registerMarketCommands(program, getAdapter, isJson, getAdapterFo
|
|
|
258
259
|
console.log(chalk.gray("\n No funding history.\n"));
|
|
259
260
|
return;
|
|
260
261
|
}
|
|
261
|
-
const
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
262
|
+
const hasOracle = history.some((h) => h.price != null && h.price !== "0" && h.price !== "");
|
|
263
|
+
const rows = history.map((h) => {
|
|
264
|
+
const base = [
|
|
265
|
+
new Date(h.time).toLocaleString(),
|
|
266
|
+
formatPercent(h.rate),
|
|
267
|
+
];
|
|
268
|
+
if (hasOracle)
|
|
269
|
+
base.push(h.price != null ? `$${formatUsd(h.price)}` : chalk.gray("n/a"));
|
|
270
|
+
return base;
|
|
271
|
+
});
|
|
272
|
+
const headers = hasOracle ? ["Time", "Funding Rate", "Oracle"] : ["Time", "Funding Rate"];
|
|
273
|
+
console.log(makeTable(headers, rows));
|
|
274
|
+
if (!hasOracle)
|
|
275
|
+
console.log(chalk.gray(" Note: Oracle price not available for this exchange.\n"));
|
|
267
276
|
});
|
|
268
277
|
market
|
|
269
278
|
.command("mid <symbol>")
|
|
@@ -367,7 +376,7 @@ export function registerMarketCommands(program, getAdapter, isJson, getAdapterFo
|
|
|
367
376
|
console.log(chalk.cyan.bold("\n HIP-3 Deployed Perp DEXes\n"));
|
|
368
377
|
const rows = dexes.map(d => [
|
|
369
378
|
chalk.white.bold(d.name),
|
|
370
|
-
chalk.gray(d.deployer.slice(0,
|
|
379
|
+
chalk.gray(d.deployer ? d.deployer.slice(0, 6) + "..." + d.deployer.slice(-4) : "-"),
|
|
371
380
|
String(d.assets.length),
|
|
372
381
|
d.assets.slice(0, 5).join(", ") + (d.assets.length > 5 ? ` +${d.assets.length - 5}` : ""),
|
|
373
382
|
]);
|
|
@@ -9,7 +9,7 @@ export function registerRebalanceCommands(program, getAdapterForExchange, isJson
|
|
|
9
9
|
rebalance
|
|
10
10
|
.command("check")
|
|
11
11
|
.description("Show balances across all exchanges")
|
|
12
|
-
.option("--exchanges <list>", "Comma-separated exchanges", "pacifica,hyperliquid,lighter")
|
|
12
|
+
.option("--exchanges <list>", "Comma-separated exchanges", "pacifica,hyperliquid,lighter,aster")
|
|
13
13
|
.action(async (opts) => {
|
|
14
14
|
const exchangeNames = opts.exchanges.split(",").map((e) => e.trim());
|
|
15
15
|
const adapters = new Map();
|
|
@@ -55,7 +55,7 @@ export function registerRebalanceCommands(program, getAdapterForExchange, isJson
|
|
|
55
55
|
rebalance
|
|
56
56
|
.command("plan")
|
|
57
57
|
.description("Calculate optimal rebalancing moves")
|
|
58
|
-
.option("--exchanges <list>", "Comma-separated exchanges", "pacifica,hyperliquid,lighter")
|
|
58
|
+
.option("--exchanges <list>", "Comma-separated exchanges", "pacifica,hyperliquid,lighter,aster")
|
|
59
59
|
.option("--min-move <usd>", "Minimum move amount ($)", "50")
|
|
60
60
|
.option("--reserve <usd>", "Keep at least this much per exchange ($)", "20")
|
|
61
61
|
.action(async (opts) => {
|
|
@@ -115,7 +115,7 @@ export function registerRebalanceCommands(program, getAdapterForExchange, isJson
|
|
|
115
115
|
rebalance
|
|
116
116
|
.command("execute")
|
|
117
117
|
.description("Execute rebalancing (withdraw → bridge → deposit)")
|
|
118
|
-
.option("--exchanges <list>", "Comma-separated exchanges", "pacifica,hyperliquid,lighter")
|
|
118
|
+
.option("--exchanges <list>", "Comma-separated exchanges", "pacifica,hyperliquid,lighter,aster")
|
|
119
119
|
.option("--min-move <usd>", "Minimum move amount ($)", "50")
|
|
120
120
|
.option("--reserve <usd>", "Keep at least this much per exchange ($)", "20")
|
|
121
121
|
.option("--dry-run", "Show what would happen without executing")
|
package/dist/commands/risk.js
CHANGED
|
@@ -47,7 +47,7 @@ export async function runHealthCheck(isJson) {
|
|
|
47
47
|
const overall = allOk ? chalk.green("ALL HEALTHY") : chalk.red("ISSUES DETECTED");
|
|
48
48
|
console.log(`\n Overall: ${overall}\n`);
|
|
49
49
|
}
|
|
50
|
-
const EXCHANGES = ["pacifica", "hyperliquid", "lighter"];
|
|
50
|
+
const EXCHANGES = ["pacifica", "hyperliquid", "lighter", "aster"];
|
|
51
51
|
export function registerRiskCommands(program, getAdapterForExchange, isJson) {
|
|
52
52
|
const risk = program.command("risk").description("Risk management and guardrails");
|
|
53
53
|
// ── risk status ──
|
|
@@ -15,7 +15,7 @@ function resolveEnvKey(nameOrKey) {
|
|
|
15
15
|
if (info)
|
|
16
16
|
return { envKey: info.envKey, chain: info.chain };
|
|
17
17
|
// Common aliases
|
|
18
|
-
const aliases = { hl: "hyperliquid", pac: "pacifica", lt: "lighter" };
|
|
18
|
+
const aliases = { hl: "hyperliquid", pac: "pacifica", lt: "lighter", ast: "aster" };
|
|
19
19
|
const aliased = aliases[nameOrKey.toLowerCase()];
|
|
20
20
|
if (aliased)
|
|
21
21
|
return resolveEnvKey(aliased);
|
package/dist/commands/trade.js
CHANGED
|
@@ -244,7 +244,7 @@ export function registerTradeCommands(program, getAdapter, isJson, isDryRun = ()
|
|
|
244
244
|
trade
|
|
245
245
|
.command("limit <symbol> <side> <price> <size>")
|
|
246
246
|
.description("Place a limit order")
|
|
247
|
-
.option("--tif <tif>", "Time in force: GTC, IOC, ALO, TOB", "GTC")
|
|
247
|
+
.option("--tif <tif>", "Time in force: GTC, IOC, ALO (HL only), TOB (HL only)", "GTC")
|
|
248
248
|
.option("--reduce-only", "Reduce only order")
|
|
249
249
|
.option("--client-id <id>", "Client order ID for idempotent tracking")
|
|
250
250
|
.option("--auto-id", "Auto-generate a client order ID")
|
|
@@ -494,7 +494,7 @@ export function registerTradeCommands(program, getAdapter, isJson, isDryRun = ()
|
|
|
494
494
|
// === TP/SL — Pacifica + Hyperliquid ===
|
|
495
495
|
trade
|
|
496
496
|
.command("tpsl <symbol> <side>")
|
|
497
|
-
.description("Set take-profit / stop-loss on a position")
|
|
497
|
+
.description("Set take-profit / stop-loss on a position (side: the side to CLOSE — buy to close short, sell to close long)")
|
|
498
498
|
.option("--tp <price>", "Take profit trigger price")
|
|
499
499
|
.option("--tp-limit <price>", "Take profit limit price")
|
|
500
500
|
.option("--sl <price>", "Stop loss trigger price")
|
|
@@ -558,7 +558,7 @@ export function registerTradeCommands(program, getAdapter, isJson, isDryRun = ()
|
|
|
558
558
|
trade
|
|
559
559
|
.command("scale-tp <symbol>")
|
|
560
560
|
.description("Place multiple take-profit limit orders at different price levels (분할익절)")
|
|
561
|
-
.requiredOption("--levels <levels>", "Comma-separated price:percent pairs (e.g., 72000:25,75000:25,80000:50)")
|
|
561
|
+
.requiredOption("--levels <levels>", "[required] Comma-separated price:percent pairs (e.g., 72000:25,75000:25,80000:50)")
|
|
562
562
|
.option("--size <size>", "Override total position size (default: uses current position)")
|
|
563
563
|
.action(async (symbol, opts) => {
|
|
564
564
|
const sym = symbol.toUpperCase();
|
package/dist/commands/wallet.js
CHANGED
|
@@ -363,10 +363,10 @@ export function registerWalletCommands(program, isJson) {
|
|
|
363
363
|
if (!info) {
|
|
364
364
|
if (isJson()) {
|
|
365
365
|
const { jsonError } = await import("../utils.js");
|
|
366
|
-
return printJson(jsonError("INVALID_PARAMS", `Unknown exchange: ${exchange}. Use: pacifica, hyperliquid, lighter (or hl, pac, lt)`));
|
|
366
|
+
return printJson(jsonError("INVALID_PARAMS", `Unknown exchange: ${exchange}. Use: pacifica, hyperliquid, lighter, aster (or hl, pac, lt, ast)`));
|
|
367
367
|
}
|
|
368
368
|
console.error(chalk.red(`\n Unknown exchange: ${exchange}`));
|
|
369
|
-
console.error(chalk.gray(` Use: pacifica, hyperliquid, lighter (or hl, pac, lt)\n`));
|
|
369
|
+
console.error(chalk.gray(` Use: pacifica, hyperliquid, lighter, aster (or hl, pac, lt, ast)\n`));
|
|
370
370
|
process.exit(1);
|
|
371
371
|
}
|
|
372
372
|
const { valid, address } = await validateKey(info.chain, key);
|
package/dist/config.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Keypair } from "@solana/web3.js";
|
|
2
|
-
export type Exchange = "pacifica" | "hyperliquid" | "lighter";
|
|
2
|
+
export type Exchange = "pacifica" | "hyperliquid" | "lighter" | "aster";
|
|
3
3
|
/**
|
|
4
4
|
* Try to load a private key without throwing if none is found.
|
|
5
5
|
* Returns null if no key is configured.
|
package/dist/config.js
CHANGED
|
@@ -32,6 +32,7 @@ export async function loadPrivateKey(exchange, pkOverride, walletName) {
|
|
|
32
32
|
pacifica: ["PACIFICA_PRIVATE_KEY", "pk"],
|
|
33
33
|
hyperliquid: ["HYPERLIQUID_PRIVATE_KEY", "HL_PRIVATE_KEY"],
|
|
34
34
|
lighter: ["LIGHTER_PRIVATE_KEY"],
|
|
35
|
+
aster: ["ASTER_API_KEY"],
|
|
35
36
|
};
|
|
36
37
|
for (const envVar of envMap[exchange]) {
|
|
37
38
|
if (process.env[envVar])
|
package/dist/constants.js
CHANGED
|
@@ -14,6 +14,10 @@ export declare class AsterAdapter implements ExchangeAdapter {
|
|
|
14
14
|
private _testnet;
|
|
15
15
|
constructor(apiKey?: string, apiSecret?: string, testnet?: boolean);
|
|
16
16
|
get isReadOnly(): boolean;
|
|
17
|
+
/** CLI symbol → Aster API symbol (ETH → ETHUSDT) */
|
|
18
|
+
private _toApi;
|
|
19
|
+
/** Aster API symbol → CLI symbol (ETHUSDT → ETH) */
|
|
20
|
+
private _fromApi;
|
|
17
21
|
init(): Promise<void>;
|
|
18
22
|
getMarkets(): Promise<ExchangeMarketInfo[]>;
|
|
19
23
|
getOrderbook(symbol: string): Promise<{
|
package/dist/exchanges/aster.js
CHANGED
|
@@ -23,6 +23,17 @@ export class AsterAdapter {
|
|
|
23
23
|
get isReadOnly() {
|
|
24
24
|
return !this._apiKey || !this._apiSecret;
|
|
25
25
|
}
|
|
26
|
+
/** CLI symbol → Aster API symbol (ETH → ETHUSDT) */
|
|
27
|
+
_toApi(symbol) {
|
|
28
|
+
const s = symbol.toUpperCase().replace(/-PERP$/, "");
|
|
29
|
+
if (s.endsWith("USDT") || s.endsWith("BUSD"))
|
|
30
|
+
return s;
|
|
31
|
+
return `${s}USDT`;
|
|
32
|
+
}
|
|
33
|
+
/** Aster API symbol → CLI symbol (ETHUSDT → ETH) */
|
|
34
|
+
_fromApi(symbol) {
|
|
35
|
+
return symbol.replace(/USDT$/, "").replace(/BUSD$/, "");
|
|
36
|
+
}
|
|
26
37
|
async init() {
|
|
27
38
|
// Verify connectivity by fetching server time
|
|
28
39
|
await this._publicGet("/fapi/v1/time");
|
|
@@ -70,7 +81,7 @@ export class AsterAdapter {
|
|
|
70
81
|
// Extract max leverage from leverage brackets if available
|
|
71
82
|
const maxLev = Number(s.maxLeverage ?? 50);
|
|
72
83
|
return {
|
|
73
|
-
symbol: sym,
|
|
84
|
+
symbol: this._fromApi(sym),
|
|
74
85
|
markPrice: String(premium?.markPrice ?? ticker?.lastPrice ?? "0"),
|
|
75
86
|
indexPrice: String(premium?.indexPrice ?? "0"),
|
|
76
87
|
fundingRate: String(premium?.lastFundingRate ?? "0"),
|
|
@@ -81,7 +92,7 @@ export class AsterAdapter {
|
|
|
81
92
|
});
|
|
82
93
|
}
|
|
83
94
|
async getOrderbook(symbol) {
|
|
84
|
-
const res = await this._publicGet("/fapi/v1/depth", { symbol:
|
|
95
|
+
const res = await this._publicGet("/fapi/v1/depth", { symbol: this._toApi(symbol), limit: "50" });
|
|
85
96
|
return {
|
|
86
97
|
bids: res?.bids ?? [],
|
|
87
98
|
asks: res?.asks ?? [],
|
|
@@ -89,12 +100,12 @@ export class AsterAdapter {
|
|
|
89
100
|
}
|
|
90
101
|
async getRecentTrades(symbol, limit = 20) {
|
|
91
102
|
const trades = await this._publicGet("/fapi/v1/trades", {
|
|
92
|
-
symbol:
|
|
103
|
+
symbol: this._toApi(symbol),
|
|
93
104
|
limit: String(limit),
|
|
94
105
|
});
|
|
95
106
|
return (trades ?? []).map((t) => ({
|
|
96
107
|
time: Number(t.time ?? 0),
|
|
97
|
-
symbol:
|
|
108
|
+
symbol: this._fromApi(String(t.symbol ?? symbol)),
|
|
98
109
|
side: t.isBuyerMaker ? "sell" : "buy",
|
|
99
110
|
price: String(t.price ?? "0"),
|
|
100
111
|
size: String(t.qty ?? "0"),
|
|
@@ -103,7 +114,7 @@ export class AsterAdapter {
|
|
|
103
114
|
}
|
|
104
115
|
async getFundingHistory(symbol, limit = 10) {
|
|
105
116
|
const data = await this._publicGet("/fapi/v1/fundingRate", {
|
|
106
|
-
symbol:
|
|
117
|
+
symbol: this._toApi(symbol),
|
|
107
118
|
limit: String(limit),
|
|
108
119
|
});
|
|
109
120
|
return (data ?? []).map((d) => ({
|
|
@@ -114,7 +125,7 @@ export class AsterAdapter {
|
|
|
114
125
|
}
|
|
115
126
|
async getKlines(symbol, interval, startTime, endTime) {
|
|
116
127
|
const data = await this._publicGet("/fapi/v1/klines", {
|
|
117
|
-
symbol:
|
|
128
|
+
symbol: this._toApi(symbol),
|
|
118
129
|
interval,
|
|
119
130
|
startTime: String(startTime),
|
|
120
131
|
endTime: String(endTime),
|
|
@@ -151,7 +162,7 @@ export class AsterAdapter {
|
|
|
151
162
|
.map((p) => {
|
|
152
163
|
const amt = Number(p.positionAmt ?? 0);
|
|
153
164
|
return {
|
|
154
|
-
symbol: String(p.symbol ?? ""),
|
|
165
|
+
symbol: this._fromApi(String(p.symbol ?? "")),
|
|
155
166
|
side: amt > 0 ? "long" : "short",
|
|
156
167
|
size: String(Math.abs(amt)),
|
|
157
168
|
entryPrice: String(p.entryPrice ?? "0"),
|
|
@@ -166,7 +177,7 @@ export class AsterAdapter {
|
|
|
166
177
|
const orders = await this._signedGet("/fapi/v1/openOrders");
|
|
167
178
|
return (orders ?? []).map((o) => ({
|
|
168
179
|
orderId: String(o.orderId ?? ""),
|
|
169
|
-
symbol: String(o.symbol ?? ""),
|
|
180
|
+
symbol: this._fromApi(String(o.symbol ?? "")),
|
|
170
181
|
side: String(o.side).toLowerCase(),
|
|
171
182
|
price: String(o.price ?? "0"),
|
|
172
183
|
size: String(o.origQty ?? "0"),
|
|
@@ -178,15 +189,15 @@ export class AsterAdapter {
|
|
|
178
189
|
async getOrderHistory(limit = 30) {
|
|
179
190
|
// allOrders requires symbol — get from positions or recent trades
|
|
180
191
|
const positions = await this.getPositions();
|
|
181
|
-
const
|
|
182
|
-
if (
|
|
192
|
+
const apiSymbols = new Set(positions.map(p => this._toApi(p.symbol)));
|
|
193
|
+
if (apiSymbols.size === 0) {
|
|
183
194
|
// Fallback: query the most common perp symbols when no open positions exist
|
|
184
195
|
for (const s of ["BTCUSDT", "ETHUSDT", "SOLUSDT", "BNBUSDT", "XRPUSDT"]) {
|
|
185
|
-
|
|
196
|
+
apiSymbols.add(s);
|
|
186
197
|
}
|
|
187
198
|
}
|
|
188
199
|
const allOrders = [];
|
|
189
|
-
for (const sym of
|
|
200
|
+
for (const sym of apiSymbols) {
|
|
190
201
|
try {
|
|
191
202
|
const orders = await this._signedGet("/fapi/v1/allOrders", {
|
|
192
203
|
symbol: sym,
|
|
@@ -195,7 +206,7 @@ export class AsterAdapter {
|
|
|
195
206
|
for (const o of orders ?? []) {
|
|
196
207
|
allOrders.push({
|
|
197
208
|
orderId: String(o.orderId ?? ""),
|
|
198
|
-
symbol: String(o.symbol ?? ""),
|
|
209
|
+
symbol: this._fromApi(String(o.symbol ?? "")),
|
|
199
210
|
side: String(o.side).toLowerCase(),
|
|
200
211
|
price: String(o.price ?? "0"),
|
|
201
212
|
size: String(o.origQty ?? "0"),
|
|
@@ -211,11 +222,11 @@ export class AsterAdapter {
|
|
|
211
222
|
}
|
|
212
223
|
async getTradeHistory(limit = 30) {
|
|
213
224
|
const positions = await this.getPositions();
|
|
214
|
-
const
|
|
215
|
-
if (
|
|
216
|
-
|
|
225
|
+
const apiSymbols = new Set(positions.map(p => this._toApi(p.symbol)));
|
|
226
|
+
if (apiSymbols.size === 0)
|
|
227
|
+
apiSymbols.add("BTCUSDT");
|
|
217
228
|
const allTrades = [];
|
|
218
|
-
for (const sym of
|
|
229
|
+
for (const sym of apiSymbols) {
|
|
219
230
|
try {
|
|
220
231
|
const trades = await this._signedGet("/fapi/v1/userTrades", {
|
|
221
232
|
symbol: sym,
|
|
@@ -224,7 +235,7 @@ export class AsterAdapter {
|
|
|
224
235
|
for (const t of trades ?? []) {
|
|
225
236
|
allTrades.push({
|
|
226
237
|
time: Number(t.time ?? 0),
|
|
227
|
-
symbol: String(t.symbol ?? ""),
|
|
238
|
+
symbol: this._fromApi(String(t.symbol ?? "")),
|
|
228
239
|
side: t.buyer ? "buy" : "sell",
|
|
229
240
|
price: String(t.price ?? "0"),
|
|
230
241
|
size: String(t.qty ?? "0"),
|
|
@@ -243,14 +254,14 @@ export class AsterAdapter {
|
|
|
243
254
|
});
|
|
244
255
|
return (data ?? []).map((f) => ({
|
|
245
256
|
time: Number(f.time ?? 0),
|
|
246
|
-
symbol: String(f.symbol ?? ""),
|
|
257
|
+
symbol: this._fromApi(String(f.symbol ?? "")),
|
|
247
258
|
payment: String(f.income ?? "0"),
|
|
248
259
|
}));
|
|
249
260
|
}
|
|
250
261
|
// ── Trading ──
|
|
251
262
|
async marketOrder(symbol, side, size) {
|
|
252
263
|
return this._signedPost("/fapi/v1/order", {
|
|
253
|
-
symbol:
|
|
264
|
+
symbol: this._toApi(symbol),
|
|
254
265
|
side: side.toUpperCase(),
|
|
255
266
|
type: "MARKET",
|
|
256
267
|
quantity: size,
|
|
@@ -258,7 +269,7 @@ export class AsterAdapter {
|
|
|
258
269
|
}
|
|
259
270
|
async limitOrder(symbol, side, price, size, opts) {
|
|
260
271
|
const params = {
|
|
261
|
-
symbol:
|
|
272
|
+
symbol: this._toApi(symbol),
|
|
262
273
|
side: side.toUpperCase(),
|
|
263
274
|
type: "LIMIT",
|
|
264
275
|
price,
|
|
@@ -279,7 +290,7 @@ export class AsterAdapter {
|
|
|
279
290
|
}
|
|
280
291
|
async cancelOrder(symbol, orderId) {
|
|
281
292
|
return this._signedDelete("/fapi/v1/order", {
|
|
282
|
-
symbol:
|
|
293
|
+
symbol: this._toApi(symbol),
|
|
283
294
|
orderId,
|
|
284
295
|
});
|
|
285
296
|
}
|
|
@@ -287,14 +298,14 @@ export class AsterAdapter {
|
|
|
287
298
|
if (!symbol) {
|
|
288
299
|
// Cancel all for all symbols with open orders
|
|
289
300
|
const orders = await this.getOpenOrders();
|
|
290
|
-
const
|
|
301
|
+
const apiSymbols = new Set(orders.map(o => this._toApi(o.symbol)));
|
|
291
302
|
const results = [];
|
|
292
|
-
for (const sym of
|
|
303
|
+
for (const sym of apiSymbols) {
|
|
293
304
|
results.push(await this._signedDelete("/fapi/v1/allOpenOrders", { symbol: sym }));
|
|
294
305
|
}
|
|
295
306
|
return results;
|
|
296
307
|
}
|
|
297
|
-
return this._signedDelete("/fapi/v1/allOpenOrders", { symbol:
|
|
308
|
+
return this._signedDelete("/fapi/v1/allOpenOrders", { symbol: this._toApi(symbol) });
|
|
298
309
|
}
|
|
299
310
|
// ── Risk ──
|
|
300
311
|
async setLeverage(symbol, leverage, marginMode) {
|
|
@@ -302,7 +313,7 @@ export class AsterAdapter {
|
|
|
302
313
|
if (marginMode) {
|
|
303
314
|
try {
|
|
304
315
|
await this._signedPost("/fapi/v1/marginType", {
|
|
305
|
-
symbol:
|
|
316
|
+
symbol: this._toApi(symbol),
|
|
306
317
|
marginType: marginMode === "cross" ? "CROSSED" : "ISOLATED",
|
|
307
318
|
});
|
|
308
319
|
}
|
|
@@ -311,13 +322,13 @@ export class AsterAdapter {
|
|
|
311
322
|
}
|
|
312
323
|
}
|
|
313
324
|
return this._signedPost("/fapi/v1/leverage", {
|
|
314
|
-
symbol:
|
|
325
|
+
symbol: this._toApi(symbol),
|
|
315
326
|
leverage: String(leverage),
|
|
316
327
|
});
|
|
317
328
|
}
|
|
318
329
|
async stopOrder(symbol, side, size, triggerPrice, opts) {
|
|
319
330
|
const params = {
|
|
320
|
-
symbol:
|
|
331
|
+
symbol: this._toApi(symbol),
|
|
321
332
|
side: side.toUpperCase(),
|
|
322
333
|
quantity: size,
|
|
323
334
|
stopPrice: triggerPrice,
|