perp-cli 0.7.1 → 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/dist/bot/config.d.ts +1 -0
- package/dist/bot/config.js +1 -1
- package/dist/bot/engine.js +41 -6
- package/dist/bot/index.d.ts +1 -1
- package/dist/bot/index.js +1 -1
- 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/commands/arb-auto.js +4 -2
- package/dist/commands/arb.d.ts +1 -0
- package/dist/commands/arb.js +2 -0
- package/dist/commands/bot.js +44 -7
- package/dist/commands/bridge.js +1 -1
- package/dist/commands/funds.js +1 -1
- package/dist/commands/history.js +5 -1
- package/dist/commands/market.js +19 -10
- package/dist/commands/trade.js +3 -3
- 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/package.json +1 -1
package/dist/bot/config.d.ts
CHANGED
|
@@ -56,6 +56,7 @@ export interface BotConfig {
|
|
|
56
56
|
monitor_interval_sec: number;
|
|
57
57
|
}
|
|
58
58
|
export declare function loadBotConfig(pathOrInline: string): BotConfig;
|
|
59
|
+
export declare function parseStrategy(type: string, raw: Record<string, unknown>): StrategyParams;
|
|
59
60
|
export declare function quickGridConfig(opts: {
|
|
60
61
|
exchange: string;
|
|
61
62
|
symbol: string;
|
package/dist/bot/config.js
CHANGED
|
@@ -53,7 +53,7 @@ function parseBotConfig(raw) {
|
|
|
53
53
|
};
|
|
54
54
|
return { name, exchange, symbol, strategy, entry_conditions: entryConds, exit_conditions: exitConds, risk, monitor_interval_sec: monitorInterval };
|
|
55
55
|
}
|
|
56
|
-
function parseStrategy(type, raw) {
|
|
56
|
+
export function parseStrategy(type, raw) {
|
|
57
57
|
switch (type) {
|
|
58
58
|
case "grid":
|
|
59
59
|
return {
|
package/dist/bot/engine.js
CHANGED
|
@@ -247,9 +247,12 @@ mode = "headless") {
|
|
|
247
247
|
}
|
|
248
248
|
}
|
|
249
249
|
// ── Emit state update for TUI / JSON ──
|
|
250
|
-
const positions = await
|
|
251
|
-
|
|
252
|
-
|
|
250
|
+
const [positions, openOrders, exchangeBalances] = await Promise.all([
|
|
251
|
+
extraAdapters ? fetchAllPositions(adapter, config.symbol, extraAdapters) : fetchPositions(adapter, config.symbol, adapter.name),
|
|
252
|
+
fetchOpenOrders(adapter, config.symbol),
|
|
253
|
+
extraAdapters ? fetchExchangeBalances(adapter, extraAdapters) : Promise.resolve(undefined),
|
|
254
|
+
]);
|
|
255
|
+
const tuiState = buildTuiState(state, config, snapshot, positions, openOrders, strategyCtx, exchangeBalances);
|
|
253
256
|
if (mode === "tui") {
|
|
254
257
|
tuiEmitter.emitState(tuiState);
|
|
255
258
|
}
|
|
@@ -390,11 +393,12 @@ function sleep(ms) {
|
|
|
390
393
|
return new Promise(r => setTimeout(r, ms));
|
|
391
394
|
}
|
|
392
395
|
// ── TUI helpers ──
|
|
393
|
-
async function fetchPositions(adapter, symbol) {
|
|
396
|
+
async function fetchPositions(adapter, symbol, exchangeName) {
|
|
394
397
|
try {
|
|
395
398
|
const positions = await adapter.getPositions();
|
|
399
|
+
const isAll = symbol.toUpperCase() === "ALL";
|
|
396
400
|
return positions
|
|
397
|
-
.filter(p => p.symbol.toUpperCase().includes(symbol.toUpperCase()) && parseFloat(p.size) !== 0)
|
|
401
|
+
.filter(p => (isAll || p.symbol.toUpperCase().includes(symbol.toUpperCase())) && parseFloat(p.size) !== 0)
|
|
398
402
|
.map(p => ({
|
|
399
403
|
symbol: p.symbol,
|
|
400
404
|
side: p.side,
|
|
@@ -402,12 +406,42 @@ async function fetchPositions(adapter, symbol) {
|
|
|
402
406
|
entryPrice: p.entryPrice,
|
|
403
407
|
markPrice: p.markPrice,
|
|
404
408
|
unrealizedPnl: p.unrealizedPnl,
|
|
409
|
+
exchange: exchangeName ?? adapter.name,
|
|
405
410
|
}));
|
|
406
411
|
}
|
|
407
412
|
catch {
|
|
408
413
|
return [];
|
|
409
414
|
}
|
|
410
415
|
}
|
|
416
|
+
async function fetchExchangeBalances(primaryAdapter, extraAdapters) {
|
|
417
|
+
const adapters = [[primaryAdapter.name, primaryAdapter]];
|
|
418
|
+
if (extraAdapters) {
|
|
419
|
+
for (const [name, a] of extraAdapters)
|
|
420
|
+
adapters.push([name, a]);
|
|
421
|
+
}
|
|
422
|
+
const results = [];
|
|
423
|
+
await Promise.all(adapters.map(async ([name, a]) => {
|
|
424
|
+
try {
|
|
425
|
+
const bal = await a.getBalance();
|
|
426
|
+
results.push({ exchange: name, equity: bal.equity, available: bal.available });
|
|
427
|
+
}
|
|
428
|
+
catch { /* skip */ }
|
|
429
|
+
}));
|
|
430
|
+
return results;
|
|
431
|
+
}
|
|
432
|
+
async function fetchAllPositions(primaryAdapter, symbol, extraAdapters) {
|
|
433
|
+
const adapters = [[primaryAdapter.name, primaryAdapter]];
|
|
434
|
+
if (extraAdapters) {
|
|
435
|
+
for (const [name, a] of extraAdapters)
|
|
436
|
+
adapters.push([name, a]);
|
|
437
|
+
}
|
|
438
|
+
const all = [];
|
|
439
|
+
await Promise.all(adapters.map(async ([name, a]) => {
|
|
440
|
+
const pos = await fetchPositions(a, symbol, name);
|
|
441
|
+
all.push(pos);
|
|
442
|
+
}));
|
|
443
|
+
return all.flat();
|
|
444
|
+
}
|
|
411
445
|
async function fetchOpenOrders(adapter, symbol) {
|
|
412
446
|
try {
|
|
413
447
|
const orders = await adapter.getOpenOrders();
|
|
@@ -425,7 +459,7 @@ async function fetchOpenOrders(adapter, symbol) {
|
|
|
425
459
|
return [];
|
|
426
460
|
}
|
|
427
461
|
}
|
|
428
|
-
function buildTuiState(state, config, snapshot, positions, openOrders, strategyCtx) {
|
|
462
|
+
function buildTuiState(state, config, snapshot, positions, openOrders, strategyCtx, exchangeBalances) {
|
|
429
463
|
// Extract serializable strategy state from the Map
|
|
430
464
|
const strategyState = {};
|
|
431
465
|
for (const [k, v] of strategyCtx.state) {
|
|
@@ -460,5 +494,6 @@ function buildTuiState(state, config, snapshot, positions, openOrders, strategyC
|
|
|
460
494
|
openOrders,
|
|
461
495
|
strategyState,
|
|
462
496
|
tick: strategyCtx.tick,
|
|
497
|
+
exchangeBalances,
|
|
463
498
|
};
|
|
464
499
|
}
|
package/dist/bot/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { loadBotConfig, quickGridConfig, quickDCAConfig, } from "./config.js";
|
|
1
|
+
export { loadBotConfig, parseStrategy, quickGridConfig, quickDCAConfig, } from "./config.js";
|
|
2
2
|
export type { ConditionType, Condition, RiskConfig, GridStrategyParams, DCAStrategyParams, FundingArbStrategyParams, GenericStrategyParams, StrategyParams, BotConfig, } from "./config.js";
|
|
3
3
|
export { runBot } from "./engine.js";
|
|
4
4
|
export type { BotLog, BotOutputMode } from "./engine.js";
|
package/dist/bot/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// bot/ barrel — public API
|
|
2
|
-
export { loadBotConfig, quickGridConfig, quickDCAConfig, } from "./config.js";
|
|
2
|
+
export { loadBotConfig, parseStrategy, quickGridConfig, quickDCAConfig, } from "./config.js";
|
|
3
3
|
export { runBot } from "./engine.js";
|
|
4
4
|
export { PRESETS, getPreset, getPresetsByStrategy } from "./presets.js";
|
|
5
5
|
export { evaluateCondition, evaluateAllConditions, getMarketSnapshot, calculateRSI } from "./conditions.js";
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
export interface ExchangeBalance {
|
|
2
|
+
exchange: string;
|
|
3
|
+
equity: string;
|
|
4
|
+
available: string;
|
|
5
|
+
}
|
|
1
6
|
export interface BotTuiState {
|
|
2
7
|
phase: string;
|
|
3
8
|
equity: number;
|
|
@@ -18,6 +23,7 @@ export interface BotTuiState {
|
|
|
18
23
|
openOrders: OpenOrder[];
|
|
19
24
|
strategyState: Record<string, unknown>;
|
|
20
25
|
tick: number;
|
|
26
|
+
exchangeBalances?: ExchangeBalance[];
|
|
21
27
|
}
|
|
22
28
|
export interface Position {
|
|
23
29
|
symbol: string;
|
|
@@ -26,6 +32,7 @@ export interface Position {
|
|
|
26
32
|
entryPrice: string;
|
|
27
33
|
markPrice: string;
|
|
28
34
|
unrealizedPnl: string;
|
|
35
|
+
exchange?: string;
|
|
29
36
|
}
|
|
30
37
|
export interface OpenOrder {
|
|
31
38
|
orderId: string;
|
|
@@ -68,9 +68,10 @@ export function BotDashboard({ initialState, onQuit, onPause, subscribe }) {
|
|
|
68
68
|
const drawdownPct = state.peakEquity > 0 ? (drawdown / state.peakEquity) * 100 : 0;
|
|
69
69
|
const bidOrders = state.openOrders.filter((o) => o.side.toLowerCase() === "buy");
|
|
70
70
|
const askOrders = state.openOrders.filter((o) => o.side.toLowerCase() === "sell");
|
|
71
|
-
|
|
71
|
+
const hasMultiExchange = (state.exchangeBalances?.length ?? 0) > 1;
|
|
72
|
+
return (_jsxs(Box, { flexDirection: "column", paddingX: 1, children: [_jsxs(Box, { flexDirection: "row", justifyContent: "space-between", marginBottom: 1, children: [_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { bold: true, color: "cyan", children: [state.strategy, " | ", state.symbol, " | ", state.exchange] }), _jsxs(Text, { color: "gray", children: ["Runtime: ", formatDuration(state.runtime), " | Tick: ", state.tick] })] }), _jsx(Box, { children: _jsx(Text, { bold: true, color: phaseColor(state.phase), children: phaseIndicator(state.phase) }) })] }), _jsxs(Box, { borderStyle: "single", borderColor: "gray", paddingX: 1, flexDirection: "row", justifyContent: "space-between", children: [_jsxs(Text, { children: ["Price: ", _jsxs(Text, { bold: true, color: "white", children: ["$", state.price.toFixed(2)] })] }), _jsxs(Text, { color: "gray", children: ["Vol24h: ", _jsxs(Text, { color: "white", children: [state.volatility24h.toFixed(1), "%"] })] }), _jsxs(Text, { color: "gray", children: ["FR: ", _jsxs(Text, { color: state.fundingRate >= 0 ? "green" : "red", children: [(state.fundingRate * 100).toFixed(4), "%"] })] }), _jsxs(Text, { color: "gray", children: ["OI: ", _jsx(Text, { color: "white", children: state.openInterest })] })] }), _jsxs(Box, { marginTop: 1, flexDirection: "row", justifyContent: "space-between", children: [_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: "cyan", bold: true, children: "Account" }), _jsxs(Text, { children: ["Equity: ", _jsxs(Text, { bold: true, color: "white", children: ["$", state.equity.toFixed(2)] }), _jsxs(Text, { color: "gray", children: [" (peak: $", state.peakEquity.toFixed(2), ")"] })] }), _jsxs(Text, { children: ["Daily P&L: ", _jsxs(Text, { bold: true, color: pnlColor(state.dailyPnl), children: ["$", state.dailyPnl.toFixed(2)] })] })] }), _jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { children: " " }), _jsxs(Text, { children: ["Drawdown: ", _jsxs(Text, { bold: true, color: drawdown > 0 ? "red" : "green", children: ["$", drawdown.toFixed(2), " (", drawdownPct.toFixed(1), "%)"] })] }), _jsxs(Text, { children: ["Fills: ", _jsx(Text, { bold: true, color: "white", children: state.fills }), _jsx(Text, { color: "gray", children: " | Total P&L: " }), _jsxs(Text, { bold: true, color: pnlColor(state.totalPnl), children: ["$", state.totalPnl.toFixed(2)] })] })] })] }), state.exchangeBalances && state.exchangeBalances.length > 1 && (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { color: "cyan", bold: true, children: "Exchange Balances" }), _jsxs(Box, { flexDirection: "row", children: [_jsx(Text, { color: "gray", children: pad("Exchange", 16) }), _jsx(Text, { color: "gray", children: pad("Equity", 14) }), _jsx(Text, { color: "gray", children: "Available" })] }), state.exchangeBalances.map((eb, i) => (_jsxs(Box, { flexDirection: "row", children: [_jsx(Text, { color: "white", children: pad(eb.exchange, 16) }), _jsx(Text, { color: "white", children: pad(`$${parseFloat(eb.equity).toFixed(2)}`, 14) }), _jsxs(Text, { color: "gray", children: ["$", parseFloat(eb.available).toFixed(2)] })] }, i)))] })), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { color: "cyan", bold: true, children: "Positions" }), state.positions.length === 0 ? (_jsx(Text, { color: "gray", children: " No open positions" })) : (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { flexDirection: "row", children: [hasMultiExchange && _jsx(Text, { color: "gray", children: pad("Exchange", 14) }), _jsx(Text, { color: "gray", children: pad("Symbol", 10) }), _jsx(Text, { color: "gray", children: pad("Side", 6) }), _jsx(Text, { color: "gray", children: pad("Size", 12) }), _jsx(Text, { color: "gray", children: pad("Entry", 12) }), _jsx(Text, { color: "gray", children: pad("Mark", 12) }), _jsx(Text, { color: "gray", children: "uPnL" })] }), state.positions.map((p, i) => {
|
|
72
73
|
const upnl = parseFloat(p.unrealizedPnl);
|
|
73
|
-
return (_jsxs(Box, { flexDirection: "row", children: [_jsx(Text, { color: "white", children: pad(p.symbol, 10) }), _jsx(Text, { color: p.side.toLowerCase() === "long" ? "green" : "red", children: pad(p.side, 6) }), _jsx(Text, { color: "white", children: pad(p.size, 12) }), _jsx(Text, { color: "white", children: pad(p.entryPrice, 12) }), _jsx(Text, { color: "white", children: pad(p.markPrice, 12) }), _jsxs(Text, { color: pnlColor(upnl), children: ["$", upnl.toFixed(2)] })] }, i));
|
|
74
|
+
return (_jsxs(Box, { flexDirection: "row", children: [hasMultiExchange && _jsx(Text, { color: "cyan", children: pad(p.exchange ?? "—", 14) }), _jsx(Text, { color: "white", children: pad(p.symbol, 10) }), _jsx(Text, { color: p.side.toLowerCase() === "long" ? "green" : "red", children: pad(p.side, 6) }), _jsx(Text, { color: "white", children: pad(p.size, 12) }), _jsx(Text, { color: "white", children: pad(p.entryPrice, 12) }), _jsx(Text, { color: "white", children: pad(p.markPrice, 12) }), _jsxs(Text, { color: pnlColor(upnl), children: ["$", upnl.toFixed(2)] })] }, i));
|
|
74
75
|
})] }))] }), _jsxs(Box, { marginTop: 1, flexDirection: "row", justifyContent: "space-between", children: [_jsxs(Box, { flexDirection: "column", width: "50%", children: [_jsxs(Text, { color: "green", bold: true, children: ["Bids (", bidOrders.length, ")"] }), bidOrders.length === 0 ? (_jsx(Text, { color: "gray", children: " --" })) : (bidOrders.slice(0, 8).map((o, i) => (_jsxs(Text, { color: "green", children: [pad(o.price, 12), " ", pad(o.size, 10), " ", o.type] }, i))))] }), _jsxs(Box, { flexDirection: "column", width: "50%", children: [_jsxs(Text, { color: "red", bold: true, children: ["Asks (", askOrders.length, ")"] }), askOrders.length === 0 ? (_jsx(Text, { color: "gray", children: " --" })) : (askOrders.slice(0, 8).map((o, i) => (_jsxs(Text, { color: "red", children: [pad(o.price, 12), " ", pad(o.size, 10), " ", o.type] }, i))))] })] }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { color: "cyan", bold: true, children: "Strategy State" }), _jsxs(Box, { flexDirection: "row", flexWrap: "wrap", children: [Object.entries(state.strategyState).slice(0, 12).map(([k, v]) => (_jsxs(Box, { marginRight: 2, children: [_jsxs(Text, { color: "gray", children: [k, ": "] }), _jsx(Text, { color: "white", children: String(v) })] }, k))), Object.keys(state.strategyState).length === 0 && (_jsx(Text, { color: "gray", children: " --" }))] })] }), _jsxs(Box, { marginTop: 1, flexDirection: "column", borderStyle: "single", borderColor: "gray", paddingX: 1, height: MAX_LOG_LINES + 2, children: [_jsx(Text, { color: "cyan", bold: true, children: "Log" }), logs.length === 0 ? (_jsx(Text, { color: "gray", children: " Waiting for events..." })) : (logs.map((entry, i) => (_jsxs(Text, { children: [_jsxs(Text, { color: "gray", children: [entry.time, " "] }), _jsx(Text, { children: colorizeLog(entry.message) })] }, i))))] }), _jsx(Box, { marginTop: 1, flexDirection: "row", justifyContent: "center", children: _jsxs(Text, { color: "gray", children: ["[", _jsx(Text, { color: "white", bold: true, children: "Q" }), "]uit [", _jsx(Text, { color: "white", bold: true, children: "P" }), "]", paused ? "resume" : "ause"] }) })] }));
|
|
75
76
|
}
|
|
76
77
|
// ── Utility ──
|
package/dist/bot/tui/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { BotDashboard } from "./BotDashboard.js";
|
|
2
|
-
export type { BotTuiState, Position, OpenOrder, LogEntry, StateListener, LogListener, DashboardProps } from "./BotDashboard.js";
|
|
2
|
+
export type { BotTuiState, ExchangeBalance, Position, OpenOrder, LogEntry, StateListener, LogListener, DashboardProps } from "./BotDashboard.js";
|
|
3
3
|
import type { DashboardProps } from "./BotDashboard.js";
|
|
4
4
|
export declare function startDashboard(props: DashboardProps): {
|
|
5
5
|
unmount: () => void;
|
|
@@ -1144,7 +1144,7 @@ export function registerArbAutoCommands(program, getAdapterForExchange, isJson,
|
|
|
1144
1144
|
if (opts.compare)
|
|
1145
1145
|
return handleCompare(isJson, opts.compare);
|
|
1146
1146
|
if (opts.rates)
|
|
1147
|
-
return handleRates(isJson, { symbol: opts.symbol, symbols: opts.symbols, all: opts.all, minSpread: opts.min });
|
|
1147
|
+
return handleRates(isJson, { symbol: opts.symbol, symbols: opts.symbols, all: opts.all, minSpread: opts.min, top: opts.top });
|
|
1148
1148
|
if (opts.basis)
|
|
1149
1149
|
return handleBasisScan(isJson, { minBasis: opts.min, symbol: typeof opts.basis === "string" ? opts.basis : opts.symbol });
|
|
1150
1150
|
if (opts.hip3)
|
|
@@ -1226,7 +1226,9 @@ export function registerArbAutoCommands(program, getAdapterForExchange, isJson,
|
|
|
1226
1226
|
if (!isJson())
|
|
1227
1227
|
console.log(chalk.cyan(" Scanning perp-perp funding rate spreads...\n"));
|
|
1228
1228
|
const spreads = await fetchFundingSpreads();
|
|
1229
|
-
|
|
1229
|
+
let filtered = spreads.filter(s => Math.abs(s.spread) >= minSpread);
|
|
1230
|
+
if (opts.top)
|
|
1231
|
+
filtered = filtered.slice(0, parseInt(opts.top));
|
|
1230
1232
|
if (isJson()) {
|
|
1231
1233
|
const perpPerp = filtered.map(s => {
|
|
1232
1234
|
const grossSpread = Math.abs(s.spread);
|
package/dist/commands/arb.d.ts
CHANGED
package/dist/commands/arb.js
CHANGED
|
@@ -278,6 +278,8 @@ export async function handleRates(isJson, opts) {
|
|
|
278
278
|
saveFundingSnapshot(allRates);
|
|
279
279
|
}
|
|
280
280
|
catch { /* non-critical */ }
|
|
281
|
+
if (opts.top)
|
|
282
|
+
snapshot.symbols = snapshot.symbols.slice(0, parseInt(opts.top));
|
|
281
283
|
if (isJson()) {
|
|
282
284
|
// Enrich with 24h rate history for each symbol
|
|
283
285
|
const now = new Date();
|
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")
|
|
@@ -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();
|
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")
|
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
|
@@ -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/market.js
CHANGED
|
@@ -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
|
]);
|
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();
|
|
@@ -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<{
|