perp-cli 0.3.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/LICENSE +21 -0
- package/README.md +293 -0
- package/dist/__tests__/alert-logic.test.d.ts +1 -0
- package/dist/__tests__/alert-logic.test.js +107 -0
- package/dist/__tests__/arb-auto-3dex.test.d.ts +1 -0
- package/dist/__tests__/arb-auto-3dex.test.js +397 -0
- package/dist/__tests__/arb-history-stats.test.d.ts +1 -0
- package/dist/__tests__/arb-history-stats.test.js +176 -0
- package/dist/__tests__/arb-logic.test.d.ts +1 -0
- package/dist/__tests__/arb-logic.test.js +84 -0
- package/dist/__tests__/arb-manage.test.d.ts +1 -0
- package/dist/__tests__/arb-manage.test.js +253 -0
- package/dist/__tests__/arb-new-features.test.d.ts +1 -0
- package/dist/__tests__/arb-new-features.test.js +457 -0
- package/dist/__tests__/arb-sizing.test.d.ts +1 -0
- package/dist/__tests__/arb-sizing.test.js +48 -0
- package/dist/__tests__/arb-state.test.d.ts +1 -0
- package/dist/__tests__/arb-state.test.js +284 -0
- package/dist/__tests__/arb-userflow.test.d.ts +1 -0
- package/dist/__tests__/arb-userflow.test.js +945 -0
- package/dist/__tests__/arb-utils.test.d.ts +1 -0
- package/dist/__tests__/arb-utils.test.js +264 -0
- package/dist/__tests__/bot-conditions.test.d.ts +1 -0
- package/dist/__tests__/bot-conditions.test.js +341 -0
- package/dist/__tests__/client-id-tracker.test.d.ts +1 -0
- package/dist/__tests__/client-id-tracker.test.js +137 -0
- package/dist/__tests__/commands/new-atomic-commands.test.d.ts +1 -0
- package/dist/__tests__/commands/new-atomic-commands.test.js +502 -0
- package/dist/__tests__/commands/order-intent.test.d.ts +1 -0
- package/dist/__tests__/commands/order-intent.test.js +600 -0
- package/dist/__tests__/commands/trade-commands.test.d.ts +1 -0
- package/dist/__tests__/commands/trade-commands.test.js +821 -0
- package/dist/__tests__/config.test.d.ts +1 -0
- package/dist/__tests__/config.test.js +86 -0
- package/dist/__tests__/cross-chain-margin.test.d.ts +1 -0
- package/dist/__tests__/cross-chain-margin.test.js +287 -0
- package/dist/__tests__/dex-asset-map.test.d.ts +1 -0
- package/dist/__tests__/dex-asset-map.test.js +191 -0
- package/dist/__tests__/errors.test.d.ts +1 -0
- package/dist/__tests__/errors.test.js +110 -0
- package/dist/__tests__/event-stream.test.d.ts +1 -0
- package/dist/__tests__/event-stream.test.js +276 -0
- package/dist/__tests__/exchanges/interface.test.d.ts +1 -0
- package/dist/__tests__/exchanges/interface.test.js +132 -0
- package/dist/__tests__/exchanges/mock-adapter.d.ts +69 -0
- package/dist/__tests__/exchanges/mock-adapter.js +137 -0
- package/dist/__tests__/execution-log.test.d.ts +1 -0
- package/dist/__tests__/execution-log.test.js +106 -0
- package/dist/__tests__/funding-calc.test.d.ts +1 -0
- package/dist/__tests__/funding-calc.test.js +71 -0
- package/dist/__tests__/funding-history.test.d.ts +1 -0
- package/dist/__tests__/funding-history.test.js +343 -0
- package/dist/__tests__/funding-rates.test.d.ts +1 -0
- package/dist/__tests__/funding-rates.test.js +342 -0
- package/dist/__tests__/funding.test.d.ts +1 -0
- package/dist/__tests__/funding.test.js +173 -0
- package/dist/__tests__/gap-logic.test.d.ts +1 -0
- package/dist/__tests__/gap-logic.test.js +43 -0
- package/dist/__tests__/hip3-dex.test.d.ts +1 -0
- package/dist/__tests__/hip3-dex.test.js +234 -0
- package/dist/__tests__/integration/agent-features.integration.test.d.ts +1 -0
- package/dist/__tests__/integration/agent-features.integration.test.js +553 -0
- package/dist/__tests__/integration/atomic-commands.integration.test.d.ts +13 -0
- package/dist/__tests__/integration/atomic-commands.integration.test.js +246 -0
- package/dist/__tests__/integration/bridge-simulation.integration.test.d.ts +1 -0
- package/dist/__tests__/integration/bridge-simulation.integration.test.js +453 -0
- package/dist/__tests__/integration/bridge-strict.integration.test.d.ts +1 -0
- package/dist/__tests__/integration/bridge-strict.integration.test.js +812 -0
- package/dist/__tests__/integration/bridge.integration.test.d.ts +1 -0
- package/dist/__tests__/integration/bridge.integration.test.js +309 -0
- package/dist/__tests__/integration/cli-e2e.integration.test.d.ts +1 -0
- package/dist/__tests__/integration/cli-e2e.integration.test.js +202 -0
- package/dist/__tests__/integration/dex-arb.integration.test.d.ts +1 -0
- package/dist/__tests__/integration/dex-arb.integration.test.js +116 -0
- package/dist/__tests__/integration/envelope-consistency.integration.test.d.ts +13 -0
- package/dist/__tests__/integration/envelope-consistency.integration.test.js +205 -0
- package/dist/__tests__/integration/hip3-dex.integration.test.d.ts +1 -0
- package/dist/__tests__/integration/hip3-dex.integration.test.js +147 -0
- package/dist/__tests__/integration/hyperliquid.integration.test.d.ts +1 -0
- package/dist/__tests__/integration/hyperliquid.integration.test.js +79 -0
- package/dist/__tests__/integration/lighter.integration.test.d.ts +1 -0
- package/dist/__tests__/integration/lighter.integration.test.js +53 -0
- package/dist/__tests__/integration/new-commands-e2e.integration.test.d.ts +9 -0
- package/dist/__tests__/integration/new-commands-e2e.integration.test.js +236 -0
- package/dist/__tests__/integration/order-verification.integration.test.d.ts +1 -0
- package/dist/__tests__/integration/order-verification.integration.test.js +321 -0
- package/dist/__tests__/integration/pacifica.integration.test.d.ts +1 -0
- package/dist/__tests__/integration/pacifica.integration.test.js +75 -0
- package/dist/__tests__/integration/response-shapes.integration.test.d.ts +1 -0
- package/dist/__tests__/integration/response-shapes.integration.test.js +278 -0
- package/dist/__tests__/liquidity.test.d.ts +1 -0
- package/dist/__tests__/liquidity.test.js +225 -0
- package/dist/__tests__/plan-executor.test.d.ts +1 -0
- package/dist/__tests__/plan-executor.test.js +314 -0
- package/dist/__tests__/position-history.test.d.ts +1 -0
- package/dist/__tests__/position-history.test.js +367 -0
- package/dist/__tests__/retry.test.d.ts +1 -0
- package/dist/__tests__/retry.test.js +310 -0
- package/dist/__tests__/risk-assessment.test.d.ts +1 -0
- package/dist/__tests__/risk-assessment.test.js +145 -0
- package/dist/__tests__/security-adversarial.test.d.ts +1 -0
- package/dist/__tests__/security-adversarial.test.js +574 -0
- package/dist/__tests__/strategies.test.d.ts +1 -0
- package/dist/__tests__/strategies.test.js +539 -0
- package/dist/__tests__/trade-execution.test.d.ts +1 -0
- package/dist/__tests__/trade-execution.test.js +129 -0
- package/dist/__tests__/trade-validator.test.d.ts +1 -0
- package/dist/__tests__/trade-validator.test.js +655 -0
- package/dist/__tests__/utils.test.d.ts +1 -0
- package/dist/__tests__/utils.test.js +76 -0
- package/dist/api/public/hyperliquid.d.ts +18 -0
- package/dist/api/public/hyperliquid.js +82 -0
- package/dist/api/public/index.d.ts +8 -0
- package/dist/api/public/index.js +8 -0
- package/dist/api/public/lighter.d.ts +24 -0
- package/dist/api/public/lighter.js +100 -0
- package/dist/api/public/pacifica.d.ts +17 -0
- package/dist/api/public/pacifica.js +54 -0
- package/dist/api/public/urls.d.ts +12 -0
- package/dist/api/public/urls.js +33 -0
- package/dist/arb/history-stats.d.ts +44 -0
- package/dist/arb/history-stats.js +135 -0
- package/dist/arb/index.d.ts +4 -0
- package/dist/arb/index.js +4 -0
- package/dist/arb/sizing.d.ts +23 -0
- package/dist/arb/sizing.js +96 -0
- package/dist/arb/state.d.ts +51 -0
- package/dist/arb/state.js +112 -0
- package/dist/arb/utils.d.ts +81 -0
- package/dist/arb/utils.js +267 -0
- package/dist/arb-history-stats.d.ts +5 -0
- package/dist/arb-history-stats.js +5 -0
- package/dist/arb-sizing.d.ts +5 -0
- package/dist/arb-sizing.js +5 -0
- package/dist/arb-state.d.ts +5 -0
- package/dist/arb-state.js +5 -0
- package/dist/arb-utils.d.ts +5 -0
- package/dist/arb-utils.js +5 -0
- package/dist/bot/conditions.d.ts +32 -0
- package/dist/bot/conditions.js +141 -0
- package/dist/bot/config.d.ts +76 -0
- package/dist/bot/config.js +160 -0
- package/dist/bot/engine.d.ts +8 -0
- package/dist/bot/engine.js +519 -0
- package/dist/bot/presets.d.ts +11 -0
- package/dist/bot/presets.js +296 -0
- package/dist/bridge-engine.d.ts +133 -0
- package/dist/bridge-engine.js +1487 -0
- package/dist/cache.d.ts +25 -0
- package/dist/cache.js +99 -0
- package/dist/cli-spec.d.ts +50 -0
- package/dist/cli-spec.js +75 -0
- package/dist/client-id-tracker.d.ts +25 -0
- package/dist/client-id-tracker.js +76 -0
- package/dist/commands/account.d.ts +3 -0
- package/dist/commands/account.js +425 -0
- package/dist/commands/agent.d.ts +3 -0
- package/dist/commands/agent.js +386 -0
- package/dist/commands/alert.d.ts +2 -0
- package/dist/commands/alert.js +421 -0
- package/dist/commands/analytics.d.ts +3 -0
- package/dist/commands/analytics.js +311 -0
- package/dist/commands/arb/index.d.ts +3 -0
- package/dist/commands/arb/index.js +921 -0
- package/dist/commands/arb-auto.d.ts +54 -0
- package/dist/commands/arb-auto.js +1328 -0
- package/dist/commands/arb-manage.d.ts +5 -0
- package/dist/commands/arb-manage.js +5 -0
- package/dist/commands/arb.d.ts +2 -0
- package/dist/commands/arb.js +347 -0
- package/dist/commands/backtest.d.ts +2 -0
- package/dist/commands/backtest.js +327 -0
- package/dist/commands/bot.d.ts +3 -0
- package/dist/commands/bot.js +412 -0
- package/dist/commands/bridge.d.ts +2 -0
- package/dist/commands/bridge.js +396 -0
- package/dist/commands/dashboard.d.ts +3 -0
- package/dist/commands/dashboard.js +176 -0
- package/dist/commands/deposit.d.ts +4 -0
- package/dist/commands/deposit.js +573 -0
- package/dist/commands/dex.d.ts +3 -0
- package/dist/commands/dex.js +114 -0
- package/dist/commands/env.d.ts +2 -0
- package/dist/commands/env.js +136 -0
- package/dist/commands/funding.d.ts +2 -0
- package/dist/commands/funding.js +347 -0
- package/dist/commands/gap.d.ts +2 -0
- package/dist/commands/gap.js +305 -0
- package/dist/commands/health.d.ts +2 -0
- package/dist/commands/health.js +67 -0
- package/dist/commands/history.d.ts +2 -0
- package/dist/commands/history.js +235 -0
- package/dist/commands/init.d.ts +15 -0
- package/dist/commands/init.js +266 -0
- package/dist/commands/jobs.d.ts +2 -0
- package/dist/commands/jobs.js +133 -0
- package/dist/commands/manage.d.ts +4 -0
- package/dist/commands/manage.js +309 -0
- package/dist/commands/market.d.ts +3 -0
- package/dist/commands/market.js +225 -0
- package/dist/commands/plan.d.ts +3 -0
- package/dist/commands/plan.js +95 -0
- package/dist/commands/portfolio.d.ts +3 -0
- package/dist/commands/portfolio.js +169 -0
- package/dist/commands/rebalance.d.ts +3 -0
- package/dist/commands/rebalance.js +293 -0
- package/dist/commands/risk.d.ts +3 -0
- package/dist/commands/risk.js +169 -0
- package/dist/commands/run.d.ts +3 -0
- package/dist/commands/run.js +202 -0
- package/dist/commands/settings.d.ts +2 -0
- package/dist/commands/settings.js +102 -0
- package/dist/commands/stream.d.ts +5 -0
- package/dist/commands/stream.js +123 -0
- package/dist/commands/trade.d.ts +3 -0
- package/dist/commands/trade.js +1273 -0
- package/dist/commands/wallet.d.ts +14 -0
- package/dist/commands/wallet.js +602 -0
- package/dist/commands/withdraw.d.ts +3 -0
- package/dist/commands/withdraw.js +187 -0
- package/dist/config.d.ts +5 -0
- package/dist/config.js +68 -0
- package/dist/cross-chain-margin.d.ts +46 -0
- package/dist/cross-chain-margin.js +107 -0
- package/dist/dashboard/server.d.ts +80 -0
- package/dist/dashboard/server.js +340 -0
- package/dist/dashboard/ui.d.ts +4 -0
- package/dist/dashboard/ui.js +538 -0
- package/dist/dashboard/ws-feeds.d.ts +29 -0
- package/dist/dashboard/ws-feeds.js +660 -0
- package/dist/dex-asset-map.d.ts +80 -0
- package/dist/dex-asset-map.js +201 -0
- package/dist/errors.d.ts +109 -0
- package/dist/errors.js +84 -0
- package/dist/event-stream.d.ts +25 -0
- package/dist/event-stream.js +168 -0
- package/dist/exchanges/hyperliquid.d.ts +212 -0
- package/dist/exchanges/hyperliquid.js +931 -0
- package/dist/exchanges/interface.d.ts +95 -0
- package/dist/exchanges/interface.js +5 -0
- package/dist/exchanges/lighter.d.ts +159 -0
- package/dist/exchanges/lighter.js +793 -0
- package/dist/exchanges/pacifica.d.ts +51 -0
- package/dist/exchanges/pacifica.js +248 -0
- package/dist/execution-log.d.ts +36 -0
- package/dist/execution-log.js +102 -0
- package/dist/funding/history.d.ts +63 -0
- package/dist/funding/history.js +266 -0
- package/dist/funding/index.d.ts +3 -0
- package/dist/funding/index.js +3 -0
- package/dist/funding/normalize.d.ts +39 -0
- package/dist/funding/normalize.js +66 -0
- package/dist/funding/rates.d.ts +45 -0
- package/dist/funding/rates.js +172 -0
- package/dist/funding-history.d.ts +5 -0
- package/dist/funding-history.js +5 -0
- package/dist/funding-rates.d.ts +5 -0
- package/dist/funding-rates.js +5 -0
- package/dist/funding.d.ts +5 -0
- package/dist/funding.js +5 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +458 -0
- package/dist/jobs.d.ts +37 -0
- package/dist/jobs.js +152 -0
- package/dist/liquidity.d.ts +34 -0
- package/dist/liquidity.js +100 -0
- package/dist/mcp-server.d.ts +9 -0
- package/dist/mcp-server.js +1206 -0
- package/dist/pacifica/client.d.ts +111 -0
- package/dist/pacifica/client.js +310 -0
- package/dist/pacifica/constants.d.ts +27 -0
- package/dist/pacifica/constants.js +47 -0
- package/dist/pacifica/deposit.d.ts +14 -0
- package/dist/pacifica/deposit.js +78 -0
- package/dist/pacifica/index.d.ts +6 -0
- package/dist/pacifica/index.js +11 -0
- package/dist/pacifica/signing.d.ts +49 -0
- package/dist/pacifica/signing.js +97 -0
- package/dist/pacifica/types/account.d.ts +42 -0
- package/dist/pacifica/types/account.js +1 -0
- package/dist/pacifica/types/index.d.ts +6 -0
- package/dist/pacifica/types/index.js +6 -0
- package/dist/pacifica/types/lake.d.ts +18 -0
- package/dist/pacifica/types/lake.js +1 -0
- package/dist/pacifica/types/market.d.ts +64 -0
- package/dist/pacifica/types/market.js +1 -0
- package/dist/pacifica/types/order.d.ts +92 -0
- package/dist/pacifica/types/order.js +1 -0
- package/dist/pacifica/types/position.d.ts +25 -0
- package/dist/pacifica/types/position.js +1 -0
- package/dist/pacifica/types/ws.d.ts +34 -0
- package/dist/pacifica/types/ws.js +41 -0
- package/dist/pacifica/ws-client.d.ts +42 -0
- package/dist/pacifica/ws-client.js +180 -0
- package/dist/plan-executor.d.ts +48 -0
- package/dist/plan-executor.js +280 -0
- package/dist/position-history.d.ts +68 -0
- package/dist/position-history.js +222 -0
- package/dist/rebalance.d.ts +64 -0
- package/dist/rebalance.js +142 -0
- package/dist/retry.d.ts +74 -0
- package/dist/retry.js +129 -0
- package/dist/risk.d.ts +48 -0
- package/dist/risk.js +156 -0
- package/dist/settings.d.ts +19 -0
- package/dist/settings.js +45 -0
- package/dist/shared-api.d.ts +5 -0
- package/dist/shared-api.js +5 -0
- package/dist/strategies/dca.d.ts +25 -0
- package/dist/strategies/dca.js +114 -0
- package/dist/strategies/funding-arb.d.ts +15 -0
- package/dist/strategies/funding-arb.js +281 -0
- package/dist/strategies/grid.d.ts +34 -0
- package/dist/strategies/grid.js +185 -0
- package/dist/strategies/trailing-stop.d.ts +17 -0
- package/dist/strategies/trailing-stop.js +121 -0
- package/dist/strategies/twap.d.ts +20 -0
- package/dist/strategies/twap.js +78 -0
- package/dist/trade-validator.d.ts +39 -0
- package/dist/trade-validator.js +154 -0
- package/dist/utils.d.ts +38 -0
- package/dist/utils.js +110 -0
- package/package.json +63 -0
- package/skills/perp-cli/SKILL.md +149 -0
- package/skills/perp-cli/references/commands.md +143 -0
|
@@ -0,0 +1,519 @@
|
|
|
1
|
+
import { getMarketSnapshot, evaluateAllConditions } from "./conditions.js";
|
|
2
|
+
import { updateJobState } from "../jobs.js";
|
|
3
|
+
import { checkArbLiquidity } from "../liquidity.js";
|
|
4
|
+
import { computeMatchedSize, reconcileArbFills } from "../arb-sizing.js";
|
|
5
|
+
import chalk from "chalk";
|
|
6
|
+
export async function runBot(adapter, config, jobId, log = defaultLog, extraAdapters) {
|
|
7
|
+
const state = {
|
|
8
|
+
phase: "monitoring",
|
|
9
|
+
startTime: Date.now(),
|
|
10
|
+
equity: 0,
|
|
11
|
+
peakEquity: 0,
|
|
12
|
+
dailyPnl: 0,
|
|
13
|
+
fills: 0,
|
|
14
|
+
totalPnl: 0,
|
|
15
|
+
rebalanceCount: 0,
|
|
16
|
+
lastRebalance: 0,
|
|
17
|
+
strategyActive: false,
|
|
18
|
+
gridOrders: new Map(),
|
|
19
|
+
gridUpper: 0,
|
|
20
|
+
gridLower: 0,
|
|
21
|
+
dcaOrdersPlaced: 0,
|
|
22
|
+
dcaLastOrder: 0,
|
|
23
|
+
arbRunning: false,
|
|
24
|
+
arbPositions: 0,
|
|
25
|
+
};
|
|
26
|
+
let running = true;
|
|
27
|
+
const shutdown = () => { running = false; state.phase = "stopped"; };
|
|
28
|
+
process.on("SIGINT", shutdown);
|
|
29
|
+
process.on("SIGTERM", shutdown);
|
|
30
|
+
// Header
|
|
31
|
+
log(chalk.cyan.bold(`\n ╔═══════════════════════════════════════╗`));
|
|
32
|
+
log(chalk.cyan.bold(` ║ ${config.name.padEnd(37)}║`));
|
|
33
|
+
log(chalk.cyan.bold(` ╚═══════════════════════════════════════╝\n`));
|
|
34
|
+
log(` Exchange: ${chalk.white(config.exchange)}`);
|
|
35
|
+
log(` Symbol: ${chalk.white(config.symbol)}`);
|
|
36
|
+
log(` Strategy: ${chalk.white(config.strategy.type)}`);
|
|
37
|
+
log(` Risk: max drawdown $${config.risk.max_drawdown} | max daily loss $${config.risk.max_daily_loss}`);
|
|
38
|
+
log(` Entry: ${config.entry_conditions.map(c => c.type).join(" + ")}`);
|
|
39
|
+
log(` Exit: ${config.exit_conditions.map(c => c.type).join(" | ") || "manual"}`);
|
|
40
|
+
log("");
|
|
41
|
+
// Get initial equity
|
|
42
|
+
try {
|
|
43
|
+
const bal = await adapter.getBalance();
|
|
44
|
+
state.equity = parseFloat(bal.equity);
|
|
45
|
+
state.peakEquity = state.equity;
|
|
46
|
+
log(` Starting equity: $${state.equity.toFixed(2)}`);
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
log(chalk.yellow(` Could not fetch initial balance`));
|
|
50
|
+
}
|
|
51
|
+
log(chalk.gray(`\n Monitoring conditions... (Ctrl+C to stop)\n`));
|
|
52
|
+
try {
|
|
53
|
+
while (running) {
|
|
54
|
+
const loopStart = Date.now();
|
|
55
|
+
try {
|
|
56
|
+
// Get market data
|
|
57
|
+
const snapshot = await getMarketSnapshot(adapter, config.symbol);
|
|
58
|
+
// Update equity
|
|
59
|
+
try {
|
|
60
|
+
const bal = await adapter.getBalance();
|
|
61
|
+
state.equity = parseFloat(bal.equity);
|
|
62
|
+
if (state.equity > state.peakEquity)
|
|
63
|
+
state.peakEquity = state.equity;
|
|
64
|
+
state.dailyPnl = state.equity - state.peakEquity; // simplified
|
|
65
|
+
}
|
|
66
|
+
catch { /* non-critical */ }
|
|
67
|
+
const context = {
|
|
68
|
+
equity: state.equity,
|
|
69
|
+
startTime: state.startTime,
|
|
70
|
+
peakEquity: state.peakEquity,
|
|
71
|
+
dailyPnl: state.dailyPnl,
|
|
72
|
+
};
|
|
73
|
+
// ── Phase: Monitoring (waiting for entry conditions) ──
|
|
74
|
+
if (state.phase === "monitoring") {
|
|
75
|
+
const shouldEnter = evaluateAllConditions(config.entry_conditions, snapshot, context, "all");
|
|
76
|
+
if (shouldEnter) {
|
|
77
|
+
log(chalk.green(` ✓ Entry conditions met @ $${snapshot.price.toFixed(2)}`));
|
|
78
|
+
state.phase = "entering";
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
logStatus(log, state, snapshot, "waiting");
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
// ── Phase: Entering (start strategy) ──
|
|
85
|
+
if (state.phase === "entering") {
|
|
86
|
+
try {
|
|
87
|
+
await startStrategy(adapter, config, state, snapshot, log, extraAdapters);
|
|
88
|
+
state.phase = "running";
|
|
89
|
+
state.strategyActive = true;
|
|
90
|
+
log(chalk.green(` ▶ Strategy started`));
|
|
91
|
+
}
|
|
92
|
+
catch (err) {
|
|
93
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
94
|
+
log(chalk.red(` ✗ Strategy start failed: ${msg}`));
|
|
95
|
+
state.phase = "monitoring"; // retry
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
// ── Phase: Running (manage strategy + check exit) ──
|
|
99
|
+
if (state.phase === "running") {
|
|
100
|
+
// Check exit conditions (any = stop)
|
|
101
|
+
const shouldExit = config.exit_conditions.length > 0
|
|
102
|
+
&& evaluateAllConditions(config.exit_conditions, snapshot, context, "any");
|
|
103
|
+
// Check risk limits
|
|
104
|
+
const drawdown = state.peakEquity - state.equity;
|
|
105
|
+
const riskBreached = drawdown > config.risk.max_drawdown;
|
|
106
|
+
if (shouldExit || riskBreached) {
|
|
107
|
+
const reason = riskBreached
|
|
108
|
+
? `drawdown $${drawdown.toFixed(2)} > limit $${config.risk.max_drawdown}`
|
|
109
|
+
: "exit condition met";
|
|
110
|
+
log(chalk.yellow(` ⚠ Exiting: ${reason}`));
|
|
111
|
+
state.phase = "exiting";
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
// Manage running strategy
|
|
115
|
+
await manageStrategy(adapter, config, state, snapshot, log, extraAdapters);
|
|
116
|
+
logStatus(log, state, snapshot, "running");
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
// ── Phase: Exiting (close everything) ──
|
|
120
|
+
if (state.phase === "exiting") {
|
|
121
|
+
await stopStrategy(adapter, config, state, log);
|
|
122
|
+
state.strategyActive = false;
|
|
123
|
+
if (config.risk.pause_after_loss_sec > 0 && state.dailyPnl < 0) {
|
|
124
|
+
log(chalk.yellow(` ⏸ Pausing ${config.risk.pause_after_loss_sec}s after loss`));
|
|
125
|
+
state.phase = "paused";
|
|
126
|
+
await sleep(config.risk.pause_after_loss_sec * 1000);
|
|
127
|
+
state.phase = "monitoring";
|
|
128
|
+
log(chalk.gray(` Resuming monitoring...`));
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
// If exit due to risk breach, stop completely
|
|
132
|
+
log(chalk.red(` ■ Bot stopped.`));
|
|
133
|
+
break;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
// Update job state
|
|
137
|
+
if (jobId) {
|
|
138
|
+
updateJobState(jobId, {
|
|
139
|
+
result: {
|
|
140
|
+
phase: state.phase,
|
|
141
|
+
equity: state.equity,
|
|
142
|
+
peakEquity: state.peakEquity,
|
|
143
|
+
fills: state.fills,
|
|
144
|
+
totalPnl: state.totalPnl,
|
|
145
|
+
rebalances: state.rebalanceCount,
|
|
146
|
+
runtime: Math.floor((Date.now() - state.startTime) / 1000),
|
|
147
|
+
},
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
catch (err) {
|
|
152
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
153
|
+
log(chalk.red(` ✗ Loop error: ${msg}`));
|
|
154
|
+
}
|
|
155
|
+
// Wait for next interval
|
|
156
|
+
const elapsed = Date.now() - loopStart;
|
|
157
|
+
const waitMs = Math.max(0, config.monitor_interval_sec * 1000 - elapsed);
|
|
158
|
+
if (running && waitMs > 0)
|
|
159
|
+
await sleep(waitMs);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
finally {
|
|
163
|
+
process.removeListener("SIGINT", shutdown);
|
|
164
|
+
process.removeListener("SIGTERM", shutdown);
|
|
165
|
+
// Cleanup: cancel any remaining orders
|
|
166
|
+
if (state.strategyActive) {
|
|
167
|
+
log(chalk.gray(` Cleaning up orders...`));
|
|
168
|
+
try {
|
|
169
|
+
await adapter.cancelAllOrders(config.symbol);
|
|
170
|
+
}
|
|
171
|
+
catch { /* best effort */ }
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
const runtime = Math.floor((Date.now() - state.startTime) / 1000);
|
|
175
|
+
log(chalk.cyan.bold(`\n Bot "${config.name}" finished`));
|
|
176
|
+
log(` Runtime: ${formatDuration(runtime)} | Fills: ${state.fills} | PnL: $${state.totalPnl.toFixed(2)}`);
|
|
177
|
+
log(` Equity: $${state.equity.toFixed(2)} (peak: $${state.peakEquity.toFixed(2)})\n`);
|
|
178
|
+
if (jobId) {
|
|
179
|
+
updateJobState(jobId, { status: "done" });
|
|
180
|
+
}
|
|
181
|
+
return { fills: state.fills, totalPnl: state.totalPnl, runtime };
|
|
182
|
+
}
|
|
183
|
+
// ── Strategy lifecycle ──
|
|
184
|
+
async function startStrategy(adapter, config, state, snapshot, log, extraAdapters) {
|
|
185
|
+
const strat = config.strategy;
|
|
186
|
+
if (strat.type === "grid") {
|
|
187
|
+
await startGrid(adapter, config.symbol, strat, state, snapshot, log);
|
|
188
|
+
}
|
|
189
|
+
else if (strat.type === "dca") {
|
|
190
|
+
state.dcaOrdersPlaced = 0;
|
|
191
|
+
state.dcaLastOrder = 0;
|
|
192
|
+
log(` [DCA] Ready: ${strat.amount} ${config.symbol} every ${strat.interval_sec}s`);
|
|
193
|
+
}
|
|
194
|
+
else if (strat.type === "funding-arb") {
|
|
195
|
+
state.arbRunning = true;
|
|
196
|
+
state.arbPositions = 0;
|
|
197
|
+
log(` [ARB] Funding arb ready | spread >= ${strat.min_spread}% | size $${strat.size_usd}`);
|
|
198
|
+
log(` [ARB] Exchanges: ${strat.exchanges.join(", ")}`);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
async function manageStrategy(adapter, config, state, snapshot, log, extraAdapters) {
|
|
202
|
+
const strat = config.strategy;
|
|
203
|
+
if (strat.type === "grid") {
|
|
204
|
+
await manageGrid(adapter, config.symbol, strat, state, snapshot, log);
|
|
205
|
+
}
|
|
206
|
+
else if (strat.type === "dca") {
|
|
207
|
+
await manageDCA(adapter, config.symbol, strat, state, snapshot, log);
|
|
208
|
+
}
|
|
209
|
+
else if (strat.type === "funding-arb") {
|
|
210
|
+
await manageFundingArb(adapter, config.symbol, strat, state, snapshot, log, extraAdapters);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
async function stopStrategy(adapter, config, state, log) {
|
|
214
|
+
log(` Cancelling all orders...`);
|
|
215
|
+
try {
|
|
216
|
+
await adapter.cancelAllOrders(config.symbol);
|
|
217
|
+
}
|
|
218
|
+
catch { /* best effort */ }
|
|
219
|
+
state.gridOrders.clear();
|
|
220
|
+
}
|
|
221
|
+
// ── Grid strategy ──
|
|
222
|
+
async function startGrid(adapter, symbol, params, state, snapshot, log) {
|
|
223
|
+
// Determine price range
|
|
224
|
+
if (params.range_mode === "auto") {
|
|
225
|
+
const pct = params.range_pct ?? 3;
|
|
226
|
+
state.gridUpper = snapshot.price * (1 + pct / 100);
|
|
227
|
+
state.gridLower = snapshot.price * (1 - pct / 100);
|
|
228
|
+
log(` [GRID] Auto range: $${state.gridLower.toFixed(2)} - $${state.gridUpper.toFixed(2)} (±${pct}%)`);
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
if (!params.upper || !params.lower)
|
|
232
|
+
throw new Error("Fixed grid requires upper and lower");
|
|
233
|
+
state.gridUpper = params.upper;
|
|
234
|
+
state.gridLower = params.lower;
|
|
235
|
+
}
|
|
236
|
+
// Set leverage
|
|
237
|
+
if (params.leverage) {
|
|
238
|
+
try {
|
|
239
|
+
await adapter.setLeverage(symbol, params.leverage);
|
|
240
|
+
log(` [GRID] Leverage: ${params.leverage}x`);
|
|
241
|
+
}
|
|
242
|
+
catch { /* non-critical */ }
|
|
243
|
+
}
|
|
244
|
+
// Place grid orders
|
|
245
|
+
await placeGridOrders(adapter, symbol, params, state, snapshot.price, log);
|
|
246
|
+
}
|
|
247
|
+
async function placeGridOrders(adapter, symbol, params, state, currentPrice, log) {
|
|
248
|
+
const step = (state.gridUpper - state.gridLower) / (params.grids - 1);
|
|
249
|
+
const sizePerGrid = params.size / params.grids;
|
|
250
|
+
let placed = 0;
|
|
251
|
+
// Cancel existing orders first
|
|
252
|
+
if (state.gridOrders.size > 0) {
|
|
253
|
+
try {
|
|
254
|
+
await adapter.cancelAllOrders(symbol);
|
|
255
|
+
}
|
|
256
|
+
catch { /* */ }
|
|
257
|
+
state.gridOrders.clear();
|
|
258
|
+
}
|
|
259
|
+
for (let i = 0; i < params.grids; i++) {
|
|
260
|
+
const price = state.gridLower + step * i;
|
|
261
|
+
// Determine side based on current price
|
|
262
|
+
let side;
|
|
263
|
+
if (params.side === "long")
|
|
264
|
+
side = "buy";
|
|
265
|
+
else if (params.side === "short")
|
|
266
|
+
side = "sell";
|
|
267
|
+
else
|
|
268
|
+
side = price < currentPrice ? "buy" : "sell";
|
|
269
|
+
try {
|
|
270
|
+
const result = await adapter.limitOrder(symbol, side, price.toFixed(2), String(sizePerGrid));
|
|
271
|
+
const orderId = String(result?.orderId ?? result?.oid ?? result?.id ?? "");
|
|
272
|
+
state.gridOrders.set(i, orderId);
|
|
273
|
+
placed++;
|
|
274
|
+
}
|
|
275
|
+
catch {
|
|
276
|
+
// skip failed grid line
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
log(` [GRID] Placed ${placed}/${params.grids} orders (step: $${step.toFixed(2)}, size: ${sizePerGrid.toFixed(6)})`);
|
|
280
|
+
}
|
|
281
|
+
async function manageGrid(adapter, symbol, params, state, snapshot, log) {
|
|
282
|
+
const step = (state.gridUpper - state.gridLower) / (params.grids - 1);
|
|
283
|
+
const sizePerGrid = params.size / params.grids;
|
|
284
|
+
// Check for fills
|
|
285
|
+
try {
|
|
286
|
+
const openOrders = await adapter.getOpenOrders();
|
|
287
|
+
const openIds = new Set(openOrders.filter(o => o.symbol.toUpperCase() === symbol.toUpperCase()).map(o => o.orderId));
|
|
288
|
+
let newFills = 0;
|
|
289
|
+
for (const [idx, orderId] of state.gridOrders.entries()) {
|
|
290
|
+
if (!openIds.has(orderId)) {
|
|
291
|
+
// Order filled — place opposite order
|
|
292
|
+
newFills++;
|
|
293
|
+
state.fills++;
|
|
294
|
+
state.totalPnl += step * sizePerGrid;
|
|
295
|
+
// Determine new side and price
|
|
296
|
+
const oldPrice = state.gridLower + step * idx;
|
|
297
|
+
const wasBuy = oldPrice < snapshot.price;
|
|
298
|
+
const newSide = wasBuy ? "sell" : "buy";
|
|
299
|
+
const newPrice = wasBuy ? oldPrice + step : oldPrice - step;
|
|
300
|
+
if (newPrice >= state.gridLower && newPrice <= state.gridUpper) {
|
|
301
|
+
try {
|
|
302
|
+
const result = await adapter.limitOrder(symbol, newSide, newPrice.toFixed(2), String(sizePerGrid));
|
|
303
|
+
const newOrderId = String(result?.orderId ?? result?.oid ?? result?.id ?? "");
|
|
304
|
+
state.gridOrders.set(idx, newOrderId);
|
|
305
|
+
}
|
|
306
|
+
catch { /* skip */ }
|
|
307
|
+
}
|
|
308
|
+
else {
|
|
309
|
+
state.gridOrders.delete(idx);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
if (newFills > 0) {
|
|
314
|
+
log(chalk.green(` [GRID] ${newFills} fill(s) | Total: ${state.fills} | Est. PnL: $${state.totalPnl.toFixed(2)}`));
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
catch { /* retry next loop */ }
|
|
318
|
+
// Auto-rebalance if price exits range
|
|
319
|
+
if (params.rebalance) {
|
|
320
|
+
const outOfRange = snapshot.price > state.gridUpper || snapshot.price < state.gridLower;
|
|
321
|
+
const cooldownOk = Date.now() - state.lastRebalance > params.rebalance_cooldown * 1000;
|
|
322
|
+
if (outOfRange && cooldownOk) {
|
|
323
|
+
const pct = params.range_pct ?? 3;
|
|
324
|
+
state.gridUpper = snapshot.price * (1 + pct / 100);
|
|
325
|
+
state.gridLower = snapshot.price * (1 - pct / 100);
|
|
326
|
+
state.rebalanceCount++;
|
|
327
|
+
state.lastRebalance = Date.now();
|
|
328
|
+
log(chalk.yellow(` [GRID] Rebalance #${state.rebalanceCount}: price $${snapshot.price.toFixed(2)} outside range → new $${state.gridLower.toFixed(2)} - $${state.gridUpper.toFixed(2)}`));
|
|
329
|
+
await placeGridOrders(adapter, symbol, params, state, snapshot.price, log);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
// ── DCA strategy ──
|
|
334
|
+
async function manageDCA(adapter, symbol, params, state, snapshot, log) {
|
|
335
|
+
// Check if it's time for next order
|
|
336
|
+
const timeSinceLast = (Date.now() - state.dcaLastOrder) / 1000;
|
|
337
|
+
if (state.dcaLastOrder > 0 && timeSinceLast < params.interval_sec)
|
|
338
|
+
return;
|
|
339
|
+
// Check order limit
|
|
340
|
+
if (params.total_orders > 0 && state.dcaOrdersPlaced >= params.total_orders)
|
|
341
|
+
return;
|
|
342
|
+
// Check price limit
|
|
343
|
+
if (params.price_limit) {
|
|
344
|
+
// For DCA, we assume buying — skip if price above limit
|
|
345
|
+
if (snapshot.price > params.price_limit) {
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
// Place market order
|
|
350
|
+
try {
|
|
351
|
+
const side = "buy"; // DCA is typically buying
|
|
352
|
+
await adapter.marketOrder(symbol, side, String(params.amount));
|
|
353
|
+
state.dcaOrdersPlaced++;
|
|
354
|
+
state.dcaLastOrder = Date.now();
|
|
355
|
+
state.fills++;
|
|
356
|
+
const progress = params.total_orders > 0 ? ` (${state.dcaOrdersPlaced}/${params.total_orders})` : "";
|
|
357
|
+
log(chalk.green(` [DCA] Order #${state.dcaOrdersPlaced}${progress}: buy ${params.amount} ${symbol} @ $${snapshot.price.toFixed(2)}`));
|
|
358
|
+
}
|
|
359
|
+
catch (err) {
|
|
360
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
361
|
+
log(chalk.red(` [DCA] Order failed: ${msg}`));
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
async function fetchRatesFromAdapter(adapter, exchangeName) {
|
|
365
|
+
try {
|
|
366
|
+
const markets = await adapter.getMarkets();
|
|
367
|
+
return markets.map(m => ({
|
|
368
|
+
symbol: m.symbol,
|
|
369
|
+
rate: parseFloat(m.fundingRate),
|
|
370
|
+
price: parseFloat(m.markPrice),
|
|
371
|
+
}));
|
|
372
|
+
}
|
|
373
|
+
catch {
|
|
374
|
+
return [];
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
async function manageFundingArb(adapter, symbol, params, state, _snapshot, log, extraAdapters) {
|
|
378
|
+
// Build adapter map from primary + extras
|
|
379
|
+
const adapters = new Map();
|
|
380
|
+
adapters.set(adapter.name.toLowerCase(), adapter);
|
|
381
|
+
if (extraAdapters) {
|
|
382
|
+
for (const [name, a] of extraAdapters)
|
|
383
|
+
adapters.set(name, a);
|
|
384
|
+
}
|
|
385
|
+
if (adapters.size < 2) {
|
|
386
|
+
log(chalk.yellow(` [ARB] Need 2+ exchanges, have ${adapters.size}. Skipping.`));
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
// Fetch rates from all exchanges
|
|
390
|
+
const ratesByExchange = new Map();
|
|
391
|
+
for (const [name, a] of adapters) {
|
|
392
|
+
const rates = await fetchRatesFromAdapter(a, name);
|
|
393
|
+
const map = new Map();
|
|
394
|
+
for (const r of rates)
|
|
395
|
+
map.set(r.symbol.toUpperCase(), { rate: r.rate, price: r.price });
|
|
396
|
+
ratesByExchange.set(name, map);
|
|
397
|
+
}
|
|
398
|
+
// Find opportunities: largest spread between any two exchanges
|
|
399
|
+
const exchangeNames = [...ratesByExchange.keys()];
|
|
400
|
+
const opportunities = [];
|
|
401
|
+
// Collect all symbols
|
|
402
|
+
const allSymbols = new Set();
|
|
403
|
+
for (const [, map] of ratesByExchange) {
|
|
404
|
+
for (const sym of map.keys())
|
|
405
|
+
allSymbols.add(sym);
|
|
406
|
+
}
|
|
407
|
+
for (const sym of allSymbols) {
|
|
408
|
+
let minRate = Infinity, maxRate = -Infinity;
|
|
409
|
+
let minExchange = "", maxExchange = "";
|
|
410
|
+
for (const exName of exchangeNames) {
|
|
411
|
+
const rate = ratesByExchange.get(exName)?.get(sym)?.rate;
|
|
412
|
+
if (rate === undefined)
|
|
413
|
+
continue;
|
|
414
|
+
if (rate < minRate) {
|
|
415
|
+
minRate = rate;
|
|
416
|
+
minExchange = exName;
|
|
417
|
+
}
|
|
418
|
+
if (rate > maxRate) {
|
|
419
|
+
maxRate = rate;
|
|
420
|
+
maxExchange = exName;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
if (minExchange && maxExchange && minExchange !== maxExchange) {
|
|
424
|
+
const { computeAnnualSpread: cas } = await import("../funding.js");
|
|
425
|
+
const spread = cas(maxRate, maxExchange, minRate, minExchange);
|
|
426
|
+
if (spread >= params.min_spread) {
|
|
427
|
+
opportunities.push({
|
|
428
|
+
symbol: sym,
|
|
429
|
+
longExchange: minExchange, // long where rate is low (we receive funding)
|
|
430
|
+
shortExchange: maxExchange, // short where rate is high (we pay less or receive)
|
|
431
|
+
longRate: minRate,
|
|
432
|
+
shortRate: maxRate,
|
|
433
|
+
spread,
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
// Sort by spread descending
|
|
439
|
+
opportunities.sort((a, b) => b.spread - a.spread);
|
|
440
|
+
if (opportunities.length > 0) {
|
|
441
|
+
const top = opportunities.slice(0, 3);
|
|
442
|
+
for (const opp of top) {
|
|
443
|
+
log(` [ARB] ${opp.symbol}: ${opp.spread.toFixed(1)}% spread (long ${opp.longExchange} ${(opp.longRate * 100).toFixed(4)}% / short ${opp.shortExchange} ${(opp.shortRate * 100).toFixed(4)}%)`);
|
|
444
|
+
}
|
|
445
|
+
// Auto-execute if under position limit
|
|
446
|
+
if (state.arbPositions < params.max_positions && opportunities.length > 0) {
|
|
447
|
+
const best = opportunities[0];
|
|
448
|
+
const longAdapter = adapters.get(best.longExchange);
|
|
449
|
+
const shortAdapter = adapters.get(best.shortExchange);
|
|
450
|
+
if (longAdapter && shortAdapter) {
|
|
451
|
+
// Calculate size from USD
|
|
452
|
+
const price = ratesByExchange.get(best.longExchange)?.get(best.symbol)?.price ?? 0;
|
|
453
|
+
if (price > 0) {
|
|
454
|
+
// Liquidity check & size adjustment
|
|
455
|
+
const liq = await checkArbLiquidity(longAdapter, shortAdapter, best.symbol, params.size_usd, 0.5, (msg) => log(chalk.yellow(` ${msg}`)));
|
|
456
|
+
if (!liq.viable)
|
|
457
|
+
return;
|
|
458
|
+
// Compute matched size for both legs
|
|
459
|
+
const matched = computeMatchedSize(liq.adjustedSizeUsd, price, best.longExchange, best.shortExchange);
|
|
460
|
+
if (!matched) {
|
|
461
|
+
log(chalk.yellow(` [ARB] Skip ${best.symbol}: can't compute matched size (min notional or precision issue)`));
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
try {
|
|
465
|
+
log(chalk.cyan(` [ARB] Opening: ${matched.size} ${best.symbol} on both legs ($${matched.notional.toFixed(0)}/leg, slippage ~${liq.longSlippage.toFixed(2)}%/${liq.shortSlippage.toFixed(2)}%)`));
|
|
466
|
+
await Promise.all([
|
|
467
|
+
longAdapter.marketOrder(best.symbol, "buy", matched.size),
|
|
468
|
+
shortAdapter.marketOrder(best.symbol, "sell", matched.size),
|
|
469
|
+
]);
|
|
470
|
+
// Verify fills match, correct if needed
|
|
471
|
+
try {
|
|
472
|
+
const recon = await reconcileArbFills(longAdapter, shortAdapter, best.symbol, (msg) => log(chalk.yellow(` ${msg}`)));
|
|
473
|
+
if (!recon.matched) {
|
|
474
|
+
log(chalk.red(` [ARB] WARNING: fills not matched after correction attempt`));
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
catch { /* non-critical */ }
|
|
478
|
+
state.arbPositions++;
|
|
479
|
+
state.fills += 2;
|
|
480
|
+
log(chalk.green(` [ARB] Position opened! (${state.arbPositions}/${params.max_positions})`));
|
|
481
|
+
}
|
|
482
|
+
catch (err) {
|
|
483
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
484
|
+
log(chalk.red(` [ARB] Execution failed: ${msg}`));
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
else {
|
|
491
|
+
log(chalk.gray(` [ARB] No opportunities >= ${params.min_spread}% spread`));
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
// ── Utils ──
|
|
495
|
+
function logStatus(log, state, snapshot, phase) {
|
|
496
|
+
const runtime = formatDuration(Math.floor((Date.now() - state.startTime) / 1000));
|
|
497
|
+
const vol = snapshot.volatility24h.toFixed(1);
|
|
498
|
+
const fr = (snapshot.fundingRate * 100).toFixed(4);
|
|
499
|
+
const eq = state.equity.toFixed(2);
|
|
500
|
+
const dd = (state.peakEquity - state.equity).toFixed(2);
|
|
501
|
+
log(chalk.gray(` [${phase}] $${snapshot.price.toFixed(2)} | vol: ${vol}% | fr: ${fr}% | ` +
|
|
502
|
+
`eq: $${eq} | dd: $${dd} | fills: ${state.fills} | ${runtime}`));
|
|
503
|
+
}
|
|
504
|
+
function defaultLog(msg) {
|
|
505
|
+
const ts = new Date().toLocaleTimeString();
|
|
506
|
+
console.log(`${chalk.gray(ts)} ${msg}`);
|
|
507
|
+
}
|
|
508
|
+
function formatDuration(sec) {
|
|
509
|
+
if (sec < 60)
|
|
510
|
+
return `${sec}s`;
|
|
511
|
+
const m = Math.floor(sec / 60);
|
|
512
|
+
if (m < 60)
|
|
513
|
+
return `${m}m${sec % 60}s`;
|
|
514
|
+
const h = Math.floor(m / 60);
|
|
515
|
+
return `${h}h${m % 60}m`;
|
|
516
|
+
}
|
|
517
|
+
function sleep(ms) {
|
|
518
|
+
return new Promise(r => setTimeout(r, ms));
|
|
519
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { BotConfig } from "./config.js";
|
|
2
|
+
export interface Preset {
|
|
3
|
+
name: string;
|
|
4
|
+
description: string;
|
|
5
|
+
strategy: "grid" | "dca" | "funding-arb";
|
|
6
|
+
risk: "low" | "medium" | "high";
|
|
7
|
+
buildConfig: (exchange: string, symbol: string) => BotConfig;
|
|
8
|
+
}
|
|
9
|
+
export declare const PRESETS: Preset[];
|
|
10
|
+
export declare function getPreset(name: string): Preset | undefined;
|
|
11
|
+
export declare function getPresetsByStrategy(strategy: string): Preset[];
|