perp-cli 0.11.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -25
- package/dist/_stderr-filter.d.ts +0 -0
- package/dist/_stderr-filter.js +30 -0
- package/dist/agent-wallet/expiry.d.ts +15 -0
- package/dist/agent-wallet/expiry.js +26 -0
- package/dist/agent-wallet/lighter-keystore.d.ts +34 -0
- package/dist/agent-wallet/lighter-keystore.js +232 -0
- package/dist/agent-wallet/passphrase.d.ts +13 -0
- package/dist/agent-wallet/passphrase.js +54 -0
- package/dist/agent-wallet/signing-strategy.d.ts +67 -0
- package/dist/agent-wallet/signing-strategy.js +51 -0
- package/dist/agent-wallet/store.d.ts +37 -0
- package/dist/agent-wallet/store.js +172 -0
- package/dist/api/public/_http.d.ts +15 -0
- package/dist/api/public/_http.js +24 -0
- package/dist/api/public/hyperliquid.js +36 -24
- package/dist/api/public/lighter.d.ts +7 -1
- package/dist/api/public/lighter.js +85 -37
- package/dist/api/public/pacifica.js +47 -18
- package/dist/bridge-engine.js +102 -49
- package/dist/cli-spec.js +1 -2
- package/dist/commands/account.js +2 -500
- package/dist/commands/agent.d.ts +8 -2
- package/dist/commands/agent.js +1903 -366
- package/dist/commands/alerts.js +3 -1
- package/dist/commands/arb/index.js +37 -7
- package/dist/commands/arb-auto.js +44 -11
- package/dist/commands/arb.js +83 -34
- package/dist/commands/bot.d.ts +1 -1
- package/dist/commands/bot.js +18 -14
- package/dist/commands/bridge.d.ts +1 -1
- package/dist/commands/bridge.js +4 -4
- package/dist/commands/funds.d.ts +1 -1
- package/dist/commands/funds.js +18 -68
- package/dist/commands/jobs.d.ts +1 -1
- package/dist/commands/jobs.js +2 -2
- package/dist/commands/manage.d.ts +1 -1
- package/dist/commands/manage.js +12 -84
- package/dist/commands/plan.d.ts +1 -1
- package/dist/commands/plan.js +2 -2
- package/dist/commands/portfolio.d.ts +19 -3
- package/dist/commands/portfolio.js +747 -163
- package/dist/commands/rebalance.d.ts +1 -1
- package/dist/commands/rebalance.js +18 -12
- package/dist/commands/risk.js +2 -6
- package/dist/commands/settings.js +30 -53
- package/dist/commands/trade.js +16 -15
- package/dist/commands/wallet.d.ts +2 -1
- package/dist/commands/wallet.js +756 -28
- package/dist/errors.d.ts +64 -0
- package/dist/errors.js +82 -2
- package/dist/event-stream.js +12 -1
- package/dist/exchanges/aster-typed-data.d.ts +156 -0
- package/dist/exchanges/aster-typed-data.js +189 -0
- package/dist/exchanges/aster.d.ts +60 -11
- package/dist/exchanges/aster.js +278 -136
- package/dist/exchanges/hyperliquid.d.ts +14 -0
- package/dist/exchanges/hyperliquid.js +84 -34
- package/dist/exchanges/lighter-spot.js +14 -3
- package/dist/exchanges/lighter.d.ts +66 -0
- package/dist/exchanges/lighter.js +199 -27
- package/dist/exchanges/pacifica-typed-data.d.ts +75 -0
- package/dist/exchanges/pacifica-typed-data.js +74 -0
- package/dist/exchanges/pacifica.d.ts +29 -1
- package/dist/exchanges/pacifica.js +94 -6
- package/dist/funding/rates.js +59 -21
- package/dist/guardrail/perp-guardrail.js +9 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +255 -360
- package/dist/jobs.js +3 -1
- package/dist/mcp-server.js +85 -83
- package/dist/position-history.js +47 -4
- package/dist/settings.d.ts +67 -3
- package/dist/settings.js +1 -2
- package/dist/signer/ows-evm.d.ts +14 -0
- package/dist/signer/ows-evm.js +28 -9
- package/dist/smart-order.d.ts +8 -2
- package/dist/smart-order.js +46 -11
- package/dist/strategies/funding-arb.js +29 -8
- package/dist/trade-validator.js +30 -5
- package/dist/utils.d.ts +10 -0
- package/dist/utils.js +19 -0
- package/package.json +2 -2
- package/skills/perp-cli/SKILL.md +23 -14
- package/skills/perp-cli/references/agent-operations.md +6 -4
- package/skills/perp-cli/references/commands.md +55 -34
- package/skills/perp-cli/scripts/preflight.sh +4 -4
- package/skills/perp-cli/scripts/spot-perp-scan.sh +2 -2
- package/dist/__tests__/alert-logic.test.d.ts +0 -1
- package/dist/__tests__/alert-logic.test.js +0 -107
- package/dist/__tests__/arb-auto-3dex.test.d.ts +0 -1
- package/dist/__tests__/arb-auto-3dex.test.js +0 -397
- package/dist/__tests__/arb-history-stats.test.d.ts +0 -1
- package/dist/__tests__/arb-history-stats.test.js +0 -176
- package/dist/__tests__/arb-logic.test.d.ts +0 -1
- package/dist/__tests__/arb-logic.test.js +0 -84
- package/dist/__tests__/arb-manage.test.d.ts +0 -1
- package/dist/__tests__/arb-manage.test.js +0 -253
- package/dist/__tests__/arb-new-features.test.d.ts +0 -1
- package/dist/__tests__/arb-new-features.test.js +0 -457
- package/dist/__tests__/arb-sizing.test.d.ts +0 -1
- package/dist/__tests__/arb-sizing.test.js +0 -48
- package/dist/__tests__/arb-state.test.d.ts +0 -1
- package/dist/__tests__/arb-state.test.js +0 -284
- package/dist/__tests__/arb-userflow.test.d.ts +0 -1
- package/dist/__tests__/arb-userflow.test.js +0 -945
- package/dist/__tests__/arb-utils.test.d.ts +0 -1
- package/dist/__tests__/arb-utils.test.js +0 -264
- package/dist/__tests__/bot-conditions.test.d.ts +0 -1
- package/dist/__tests__/bot-conditions.test.js +0 -341
- package/dist/__tests__/client-id-tracker.test.d.ts +0 -1
- package/dist/__tests__/client-id-tracker.test.js +0 -137
- package/dist/__tests__/commands/new-atomic-commands.test.d.ts +0 -1
- package/dist/__tests__/commands/new-atomic-commands.test.js +0 -502
- package/dist/__tests__/commands/order-intent.test.d.ts +0 -1
- package/dist/__tests__/commands/order-intent.test.js +0 -600
- package/dist/__tests__/commands/trade-commands.test.d.ts +0 -1
- package/dist/__tests__/commands/trade-commands.test.js +0 -821
- package/dist/__tests__/config.test.d.ts +0 -1
- package/dist/__tests__/config.test.js +0 -86
- package/dist/__tests__/cross-chain-margin.test.d.ts +0 -1
- package/dist/__tests__/cross-chain-margin.test.js +0 -287
- package/dist/__tests__/dex-asset-map.test.d.ts +0 -1
- package/dist/__tests__/dex-asset-map.test.js +0 -191
- package/dist/__tests__/errors.test.d.ts +0 -1
- package/dist/__tests__/errors.test.js +0 -110
- package/dist/__tests__/event-stream.test.d.ts +0 -1
- package/dist/__tests__/event-stream.test.js +0 -276
- package/dist/__tests__/exchanges/interface.test.d.ts +0 -1
- package/dist/__tests__/exchanges/interface.test.js +0 -132
- package/dist/__tests__/exchanges/mock-adapter.d.ts +0 -69
- package/dist/__tests__/exchanges/mock-adapter.js +0 -137
- package/dist/__tests__/execution-log.test.d.ts +0 -1
- package/dist/__tests__/execution-log.test.js +0 -106
- package/dist/__tests__/funding-calc.test.d.ts +0 -1
- package/dist/__tests__/funding-calc.test.js +0 -71
- package/dist/__tests__/funding-history.test.d.ts +0 -1
- package/dist/__tests__/funding-history.test.js +0 -343
- package/dist/__tests__/funding-rates.test.d.ts +0 -1
- package/dist/__tests__/funding-rates.test.js +0 -342
- package/dist/__tests__/funding.test.d.ts +0 -1
- package/dist/__tests__/funding.test.js +0 -173
- package/dist/__tests__/gap-logic.test.d.ts +0 -1
- package/dist/__tests__/gap-logic.test.js +0 -43
- package/dist/__tests__/hip3-dex.test.d.ts +0 -1
- package/dist/__tests__/hip3-dex.test.js +0 -234
- package/dist/__tests__/integration/agent-features.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/agent-features.integration.test.js +0 -553
- package/dist/__tests__/integration/atomic-commands.integration.test.d.ts +0 -13
- package/dist/__tests__/integration/atomic-commands.integration.test.js +0 -246
- package/dist/__tests__/integration/bridge-simulation.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/bridge-simulation.integration.test.js +0 -453
- package/dist/__tests__/integration/bridge-strict.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/bridge-strict.integration.test.js +0 -812
- package/dist/__tests__/integration/bridge.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/bridge.integration.test.js +0 -309
- package/dist/__tests__/integration/cli-e2e.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/cli-e2e.integration.test.js +0 -202
- package/dist/__tests__/integration/dex-arb.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/dex-arb.integration.test.js +0 -116
- package/dist/__tests__/integration/envelope-consistency.integration.test.d.ts +0 -13
- package/dist/__tests__/integration/envelope-consistency.integration.test.js +0 -205
- package/dist/__tests__/integration/hip3-dex.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/hip3-dex.integration.test.js +0 -147
- package/dist/__tests__/integration/hyperliquid.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/hyperliquid.integration.test.js +0 -79
- package/dist/__tests__/integration/lighter.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/lighter.integration.test.js +0 -53
- package/dist/__tests__/integration/new-commands-e2e.integration.test.d.ts +0 -9
- package/dist/__tests__/integration/new-commands-e2e.integration.test.js +0 -236
- package/dist/__tests__/integration/order-verification.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/order-verification.integration.test.js +0 -321
- package/dist/__tests__/integration/pacifica.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/pacifica.integration.test.js +0 -75
- package/dist/__tests__/integration/response-shapes.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/response-shapes.integration.test.js +0 -278
- package/dist/__tests__/liquidity.test.d.ts +0 -1
- package/dist/__tests__/liquidity.test.js +0 -225
- package/dist/__tests__/plan-executor.test.d.ts +0 -1
- package/dist/__tests__/plan-executor.test.js +0 -314
- package/dist/__tests__/position-history.test.d.ts +0 -1
- package/dist/__tests__/position-history.test.js +0 -367
- package/dist/__tests__/retry.test.d.ts +0 -1
- package/dist/__tests__/retry.test.js +0 -310
- package/dist/__tests__/risk-assessment.test.d.ts +0 -1
- package/dist/__tests__/risk-assessment.test.js +0 -288
- package/dist/__tests__/security-adversarial.test.d.ts +0 -1
- package/dist/__tests__/security-adversarial.test.js +0 -574
- package/dist/__tests__/strategies.test.d.ts +0 -1
- package/dist/__tests__/strategies.test.js +0 -539
- package/dist/__tests__/trade-execution.test.d.ts +0 -1
- package/dist/__tests__/trade-execution.test.js +0 -129
- package/dist/__tests__/trade-validator.test.d.ts +0 -1
- package/dist/__tests__/trade-validator.test.js +0 -655
- package/dist/__tests__/utils.test.d.ts +0 -1
- package/dist/__tests__/utils.test.js +0 -76
- package/dist/arb/reconcile.d.ts +0 -28
- package/dist/arb/reconcile.js +0 -162
- package/dist/commands/alert.d.ts +0 -2
- package/dist/commands/alert.js +0 -421
- package/dist/commands/analytics.d.ts +0 -3
- package/dist/commands/analytics.js +0 -311
- package/dist/commands/arb-manage.d.ts +0 -5
- package/dist/commands/arb-manage.js +0 -5
- package/dist/commands/dashboard.d.ts +0 -3
- package/dist/commands/dashboard.js +0 -176
- package/dist/commands/deposit.d.ts +0 -4
- package/dist/commands/deposit.js +0 -573
- package/dist/commands/dex.d.ts +0 -3
- package/dist/commands/dex.js +0 -120
- package/dist/commands/env.d.ts +0 -2
- package/dist/commands/env.js +0 -136
- package/dist/commands/funding.d.ts +0 -3
- package/dist/commands/funding.js +0 -554
- package/dist/commands/gap.d.ts +0 -2
- package/dist/commands/gap.js +0 -305
- package/dist/commands/health.d.ts +0 -2
- package/dist/commands/health.js +0 -67
- package/dist/commands/multileg.d.ts +0 -16
- package/dist/commands/multileg.js +0 -174
- package/dist/commands/ows.d.ts +0 -2
- package/dist/commands/ows.js +0 -887
- package/dist/commands/pnl.d.ts +0 -12
- package/dist/commands/pnl.js +0 -243
- package/dist/commands/run.d.ts +0 -3
- package/dist/commands/run.js +0 -202
- package/dist/commands/stream.d.ts +0 -5
- package/dist/commands/stream.js +0 -200
- package/dist/commands/withdraw.d.ts +0 -3
- package/dist/commands/withdraw.js +0 -187
- package/dist/exchanges/edgex.d.ts +0 -62
- package/dist/exchanges/edgex.js +0 -552
- package/dist/exchanges/nado.d.ts +0 -52
- package/dist/exchanges/nado.js +0 -223
- package/dist/funding/index.d.ts +0 -3
- package/dist/funding/index.js +0 -3
- package/dist/retry.d.ts +0 -74
- package/dist/retry.js +0 -129
- package/dist/ws/market-feed.d.ts +0 -74
- package/dist/ws/market-feed.js +0 -227
package/dist/smart-order.js
CHANGED
|
@@ -16,7 +16,10 @@
|
|
|
16
16
|
* - Saves 5-50+ bps vs raw market orders on thin books
|
|
17
17
|
* - Predictable execution price
|
|
18
18
|
*
|
|
19
|
-
*
|
|
19
|
+
* SSOT rule #2: silent fallback is forbidden. The opt-in `fallback: true` flag
|
|
20
|
+
* is preserved for callers that explicitly want best-effort market substitution
|
|
21
|
+
* after IOC reject, but the DEFAULT is `false` — failure must surface as a
|
|
22
|
+
* thrown error, never a silent market sweep.
|
|
20
23
|
*/
|
|
21
24
|
/**
|
|
22
25
|
* Infer tick size from orderbook prices.
|
|
@@ -77,12 +80,38 @@ function hasOrderError(result) {
|
|
|
77
80
|
return true;
|
|
78
81
|
return false;
|
|
79
82
|
}
|
|
83
|
+
/**
|
|
84
|
+
* Best-effort extraction of the embedded reject reason for the thrown error
|
|
85
|
+
* message. Falls back to JSON.stringify so the caller always gets actionable
|
|
86
|
+
* context.
|
|
87
|
+
*/
|
|
88
|
+
function describeOrderError(result) {
|
|
89
|
+
if (!result || typeof result !== "object")
|
|
90
|
+
return String(result);
|
|
91
|
+
const r = result;
|
|
92
|
+
const resp = r.response;
|
|
93
|
+
const data = resp?.data;
|
|
94
|
+
const statuses = data?.statuses;
|
|
95
|
+
if (Array.isArray(statuses) && statuses.length > 0 && statuses[0].error) {
|
|
96
|
+
return String(statuses[0].error);
|
|
97
|
+
}
|
|
98
|
+
if (typeof r.error === "string")
|
|
99
|
+
return r.error;
|
|
100
|
+
if (typeof r.message === "string")
|
|
101
|
+
return r.message;
|
|
102
|
+
try {
|
|
103
|
+
return JSON.stringify(result);
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
return String(result);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
80
109
|
/**
|
|
81
110
|
* Execute a smart order at best bid/ask + 1 tick.
|
|
82
111
|
* Drop-in replacement for `adapter.marketOrder()`.
|
|
83
112
|
*/
|
|
84
113
|
export async function smartOrder(adapter, symbol, side, size, opts = {}) {
|
|
85
|
-
const { tickTolerance = 1, fallback =
|
|
114
|
+
const { tickTolerance = 1, fallback = false, reduceOnly = false } = opts;
|
|
86
115
|
// 1. Fetch orderbook
|
|
87
116
|
const book = await adapter.getOrderbook(symbol);
|
|
88
117
|
// 2. Validate book has liquidity on the relevant side
|
|
@@ -111,15 +140,21 @@ export async function smartOrder(adapter, symbol, side, size, opts = {}) {
|
|
|
111
140
|
});
|
|
112
141
|
// Check for exchange-level rejection inside a "success" response
|
|
113
142
|
// (e.g., Hyperliquid returns { status: "ok", response: { data: { statuses: [{ error: "..." }] } } })
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
143
|
+
// SSOT rule #2: when fallback is opt-out (default), an embedded reject
|
|
144
|
+
// must throw — otherwise the caller would silently receive a "limit_ioc"
|
|
145
|
+
// result for a rejected order and treat the failure as success.
|
|
146
|
+
if (hasOrderError(result)) {
|
|
147
|
+
if (fallback) {
|
|
148
|
+
const fallbackResult = await adapter.marketOrder(symbol, side, size);
|
|
149
|
+
return {
|
|
150
|
+
result: fallbackResult,
|
|
151
|
+
method: "market_fallback",
|
|
152
|
+
price: formattedPrice,
|
|
153
|
+
bestBookPrice: bestEntry[0],
|
|
154
|
+
tickSize: tick.toFixed(decimals),
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
throw new Error(`smartOrder: exchange rejected IOC limit (${describeOrderError(result)})`);
|
|
123
158
|
}
|
|
124
159
|
return {
|
|
125
160
|
result,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { symbolMatch } from "../utils.js";
|
|
1
|
+
import { symbolMatch, logSettledRejections } from "../utils.js";
|
|
2
2
|
import { updateJobState } from "../jobs.js";
|
|
3
3
|
import { fetchAllBalances, computeRebalancePlan, hasEnoughBalance } from "../rebalance.js";
|
|
4
4
|
import { checkArbLiquidity } from "../liquidity.js";
|
|
@@ -6,6 +6,9 @@ import { computeAnnualSpread } from "../funding.js";
|
|
|
6
6
|
import { fetchPacificaPrices, fetchHyperliquidMeta, fetchLighterOrderBookDetails, fetchLighterFundingRates, } from "../shared-api.js";
|
|
7
7
|
import { computeMatchedSize, reconcileArbFills } from "../arb-sizing.js";
|
|
8
8
|
async function fetchAllRates() {
|
|
9
|
+
// SSOT rule #2: each DEX is independent. allSettled keeps the comparison
|
|
10
|
+
// alive when one DEX errors out, while rejection reasons get logged to
|
|
11
|
+
// stderr (explicit failure, never silent).
|
|
9
12
|
const [pacRates, hlRates, ltRates] = await Promise.allSettled([
|
|
10
13
|
fetchPacificaPrices().then(assets => assets.map(p => ({
|
|
11
14
|
exchange: "pacifica", symbol: p.symbol, fundingRate: p.funding, markPrice: p.mark,
|
|
@@ -20,14 +23,32 @@ async function fetchAllRates() {
|
|
|
20
23
|
]);
|
|
21
24
|
const priceMap = new Map(details.map(d => [d.marketId, d.lastTradePrice]));
|
|
22
25
|
const symMap = new Map(details.map(d => [d.marketId, d.symbol]));
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
symbol
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
const out = [];
|
|
27
|
+
for (const fr of funding) {
|
|
28
|
+
const symbol = fr.symbol || symMap.get(fr.marketId) || "";
|
|
29
|
+
if (!symbol)
|
|
30
|
+
continue;
|
|
31
|
+
// Prefer fr.markPrice; fall back to orderBook last-trade as documented
|
|
32
|
+
// price source preference (NOT an error fallback).
|
|
33
|
+
const directMark = fr.markPrice;
|
|
34
|
+
const fallbackMark = priceMap.get(fr.marketId);
|
|
35
|
+
let markPrice;
|
|
36
|
+
if (directMark !== null && Number.isFinite(directMark) && directMark > 0) {
|
|
37
|
+
markPrice = directMark;
|
|
38
|
+
}
|
|
39
|
+
else if (fallbackMark !== undefined && Number.isFinite(fallbackMark) && fallbackMark > 0) {
|
|
40
|
+
markPrice = fallbackMark;
|
|
41
|
+
}
|
|
42
|
+
// SSOT rule #2: skip rows whose mark price cannot be resolved at all
|
|
43
|
+
// — never publish a 0 mark price downstream.
|
|
44
|
+
if (markPrice === undefined)
|
|
45
|
+
continue;
|
|
46
|
+
out.push({ exchange: "lighter", symbol, fundingRate: fr.rate, markPrice });
|
|
47
|
+
}
|
|
48
|
+
return out;
|
|
29
49
|
})(),
|
|
30
50
|
]);
|
|
51
|
+
logSettledRejections([pacRates, hlRates, ltRates], ["pacifica", "hyperliquid", "lighter"], "funding-arb");
|
|
31
52
|
return [
|
|
32
53
|
...(pacRates.status === "fulfilled" ? pacRates.value : []),
|
|
33
54
|
...(hlRates.status === "fulfilled" ? hlRates.value : []),
|
|
@@ -141,7 +162,7 @@ export async function runFundingArb(adapters, params, jobId, log = console.log)
|
|
|
141
162
|
for (const move of plan.moves) {
|
|
142
163
|
log(`[ARB] $${move.amount} ${move.from} → ${move.to}`);
|
|
143
164
|
}
|
|
144
|
-
log(`[ARB] Run 'perp rebalance execute' to rebalance.`);
|
|
165
|
+
log(`[ARB] Run 'perp funds rebalance execute' to rebalance.`);
|
|
145
166
|
}
|
|
146
167
|
}
|
|
147
168
|
}
|
package/dist/trade-validator.js
CHANGED
|
@@ -10,13 +10,26 @@ export async function validateTrade(adapter, params) {
|
|
|
10
10
|
const checks = [];
|
|
11
11
|
const warnings = [];
|
|
12
12
|
const sym = params.symbol.toUpperCase();
|
|
13
|
-
// Fetch market data, balance, positions, orderbook in parallel
|
|
13
|
+
// Fetch market data, balance, positions, orderbook in parallel.
|
|
14
|
+
// SSOT rule #2: errors must propagate, never silently substitute defaults.
|
|
15
|
+
// getMarkets retains a tracked-error wrapper so the failed-load case surfaces
|
|
16
|
+
// a structured symbol_valid check (not a generic exception).
|
|
17
|
+
let marketsError = null;
|
|
14
18
|
const [markets, balance, positions, orderbook] = await Promise.all([
|
|
15
|
-
adapter.getMarkets().catch(() =>
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
+
adapter.getMarkets().catch((e) => {
|
|
20
|
+
marketsError = e instanceof Error ? e.message : String(e);
|
|
21
|
+
return [];
|
|
22
|
+
}),
|
|
23
|
+
adapter.getBalance(),
|
|
24
|
+
adapter.getPositions(),
|
|
25
|
+
params.type !== "limit"
|
|
26
|
+
? adapter.getOrderbook(sym)
|
|
27
|
+
: Promise.resolve({ bids: [], asks: [] }),
|
|
19
28
|
]);
|
|
29
|
+
if (markets.length === 0 && marketsError) {
|
|
30
|
+
checks.push({ check: "symbol_valid", passed: false, message: `Failed to load markets from ${adapter.name}: ${marketsError}` });
|
|
31
|
+
return { valid: false, checks, warnings, timestamp: new Date().toISOString() };
|
|
32
|
+
}
|
|
20
33
|
// 1. Symbol validity (handle BTC vs BTC-PERP suffix variants)
|
|
21
34
|
const market = markets.find(m => {
|
|
22
35
|
const ms = m.symbol.toUpperCase();
|
|
@@ -30,9 +43,21 @@ export async function validateTrade(adapter, params) {
|
|
|
30
43
|
return { valid: false, checks, warnings, timestamp: new Date().toISOString() };
|
|
31
44
|
}
|
|
32
45
|
const markPrice = Number(market.markPrice);
|
|
46
|
+
if (params.type === "limit" && (params.price === undefined || !Number.isFinite(params.price))) {
|
|
47
|
+
throw new Error(`Limit order requires explicit price (received ${params.price}); refusing silent mark-price substitution.`);
|
|
48
|
+
}
|
|
49
|
+
if (markPrice <= 0 && params.price === undefined) {
|
|
50
|
+
throw new Error(`No mark price available for ${sym} on ${adapter.name} and no override price supplied; cannot validate trade.`);
|
|
51
|
+
}
|
|
33
52
|
const price = params.price ?? markPrice;
|
|
34
53
|
const notional = params.size * price;
|
|
54
|
+
if (params.leverage !== undefined && (!Number.isFinite(params.leverage) || params.leverage <= 0)) {
|
|
55
|
+
throw new Error(`Leverage must be a positive number (received ${params.leverage}).`);
|
|
56
|
+
}
|
|
35
57
|
const leverage = params.leverage ?? market.maxLeverage;
|
|
58
|
+
if (!Number.isFinite(leverage) || leverage <= 0) {
|
|
59
|
+
throw new Error(`No usable leverage for ${sym} (resolved=${leverage}); supply params.leverage explicitly.`);
|
|
60
|
+
}
|
|
36
61
|
const marginRequired = notional / leverage;
|
|
37
62
|
// 2. Balance check
|
|
38
63
|
const available = Number(balance.available);
|
package/dist/utils.d.ts
CHANGED
|
@@ -38,3 +38,13 @@ export declare function jsonError(code: string, message: string, meta?: Partial<
|
|
|
38
38
|
* In JSON mode, errors are returned as JSON instead of crashing.
|
|
39
39
|
*/
|
|
40
40
|
export declare function withJsonErrors<T>(isJson: boolean, fn: () => Promise<T>, exchange?: string): Promise<T | undefined>;
|
|
41
|
+
/**
|
|
42
|
+
* Log each rejected outcome from Promise.allSettled to stderr with a
|
|
43
|
+
* per-source label. SSOT rule #2 helper: turn a quiet rejection inside a
|
|
44
|
+
* multi-DEX comparison into an explicit one-line failure on stderr.
|
|
45
|
+
*
|
|
46
|
+
* @param settled Outcomes from Promise.allSettled.
|
|
47
|
+
* @param labels Source label per index (e.g. ["pacifica", "hyperliquid", ...]).
|
|
48
|
+
* @param prefix Bracketed prefix prepended to each line (e.g. "arb-auto").
|
|
49
|
+
*/
|
|
50
|
+
export declare function logSettledRejections(settled: PromiseSettledResult<unknown>[], labels: string[], prefix: string): void;
|
package/dist/utils.js
CHANGED
|
@@ -221,3 +221,22 @@ export async function withJsonErrors(isJson, fn, exchange) {
|
|
|
221
221
|
return undefined;
|
|
222
222
|
}
|
|
223
223
|
}
|
|
224
|
+
/**
|
|
225
|
+
* Log each rejected outcome from Promise.allSettled to stderr with a
|
|
226
|
+
* per-source label. SSOT rule #2 helper: turn a quiet rejection inside a
|
|
227
|
+
* multi-DEX comparison into an explicit one-line failure on stderr.
|
|
228
|
+
*
|
|
229
|
+
* @param settled Outcomes from Promise.allSettled.
|
|
230
|
+
* @param labels Source label per index (e.g. ["pacifica", "hyperliquid", ...]).
|
|
231
|
+
* @param prefix Bracketed prefix prepended to each line (e.g. "arb-auto").
|
|
232
|
+
*/
|
|
233
|
+
export function logSettledRejections(settled, labels, prefix) {
|
|
234
|
+
for (let i = 0; i < settled.length; i++) {
|
|
235
|
+
const r = settled[i];
|
|
236
|
+
if (r.status === "rejected") {
|
|
237
|
+
const reason = r.reason instanceof Error ? r.reason.message : String(r.reason);
|
|
238
|
+
// eslint-disable-next-line no-console
|
|
239
|
+
console.error(`[${prefix}] ${labels[i] ?? `index-${i}`} fetch failed: ${reason}`);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "perp-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "Multi-DEX Perpetual Futures CLI - Pacifica, Hyperliquid, Lighter, Aster",
|
|
5
5
|
"bin": {
|
|
6
6
|
"perp": "dist/index.js",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"dependencies": {
|
|
47
47
|
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
48
48
|
"@msgpack/msgpack": "^3.1.3",
|
|
49
|
-
"@open-wallet-standard/core": "1.2
|
|
49
|
+
"@open-wallet-standard/core": "1.3.2",
|
|
50
50
|
"@solana/web3.js": "^1.98.0",
|
|
51
51
|
"@types/react": "^19.2.14",
|
|
52
52
|
"bs58": "^6.0.0",
|
package/skills/perp-cli/SKILL.md
CHANGED
|
@@ -99,20 +99,30 @@ perp --json arb close ETH # sells spot + buys back perp
|
|
|
99
99
|
|
|
100
100
|
Spot exchanges: HL, LT. Pacifica and Aster are perp-only.
|
|
101
101
|
|
|
102
|
-
##
|
|
102
|
+
## Strategy Engine (19 bot algorithms + nested scripted plans)
|
|
103
103
|
|
|
104
104
|
```bash
|
|
105
|
-
perp
|
|
106
|
-
perp
|
|
107
|
-
perp
|
|
108
|
-
perp
|
|
109
|
-
perp
|
|
110
|
-
perp
|
|
111
|
-
perp
|
|
112
|
-
perp
|
|
113
|
-
perp
|
|
114
|
-
perp
|
|
115
|
-
perp
|
|
105
|
+
perp strategy list-strategies # list all 19 strategies
|
|
106
|
+
perp strategy run <strategy> [symbol] # run any registered strategy
|
|
107
|
+
perp strategy run funding-auto # multi-exchange funding arb
|
|
108
|
+
perp strategy apex [symbol] # APEX orchestrator (Radar+Pulse+Guard)
|
|
109
|
+
perp strategy reflect # performance analysis (win rate, fees, PnL)
|
|
110
|
+
perp strategy preset-list # list strategy presets
|
|
111
|
+
perp strategy preset <name> [symbol] # run from preset
|
|
112
|
+
perp strategy start <config.yaml> # start from YAML/JSON config
|
|
113
|
+
perp strategy quick-grid <symbol> # quick grid bot
|
|
114
|
+
perp strategy quick-dca <symbol> <side> <amount> <interval>
|
|
115
|
+
perp strategy quick-arb # quick funding arb
|
|
116
|
+
|
|
117
|
+
# Scripted plans (one-shot multi-step execution)
|
|
118
|
+
perp strategy plan example # show plan format
|
|
119
|
+
perp --json strategy plan validate <file> # validate before run
|
|
120
|
+
perp --json strategy plan execute <file> --dry-run # dry-run plan
|
|
121
|
+
|
|
122
|
+
# Background process supervisor (tmux)
|
|
123
|
+
perp background list # list running jobs
|
|
124
|
+
perp background stop <id> # stop a job
|
|
125
|
+
perp background logs <id> -f # follow logs
|
|
116
126
|
```
|
|
117
127
|
|
|
118
128
|
**Strategies:** grid, dca, funding-arb, funding-arb-v2, funding-auto, basis-arb, simple-mm, engine-mm, avellaneda-mm, regime-mm, grid-mm, liquidation-mm, momentum-breakout, mean-reversion, aggressive-taker, hedge-agent, rfq-agent, claude-agent, apex
|
|
@@ -136,7 +146,6 @@ perp --json -e <EX> trade cancel <SYM> # cancel all orders for
|
|
|
136
146
|
## Agent Tools
|
|
137
147
|
|
|
138
148
|
```bash
|
|
139
|
-
perp --json agent schema # discover all commands (don't guess)
|
|
140
149
|
perp --json -e <EX> trade check <SYM> <SIDE> <SIZE> # pre-validate trade
|
|
141
150
|
perp --json portfolio # balances + positions + risk level
|
|
142
151
|
perp --json --fields totalEquity,positions portfolio # filtered output
|
|
@@ -172,7 +181,7 @@ Response format: `{ "ok": true, "data": {...} }` or `{ "ok": false, "error": { "
|
|
|
172
181
|
| `SYMBOL_NOT_FOUND` | `market list` to verify symbol |
|
|
173
182
|
| `SIZE_TOO_SMALL` | `market info <SYM>` for min size |
|
|
174
183
|
| `DUPLICATE_ORDER` | already submitted — check positions |
|
|
175
|
-
| Lighter `invalid signature` | `
|
|
184
|
+
| Lighter `invalid signature` | `perp wallet agent approve lighter` to regenerate |
|
|
176
185
|
|
|
177
186
|
## Examples
|
|
178
187
|
|
|
@@ -81,12 +81,14 @@ perp --json arb scan --min 5
|
|
|
81
81
|
```
|
|
82
82
|
|
|
83
83
|
### Lighter API Key Setup
|
|
84
|
-
Lighter uses a separate API key for trading
|
|
85
|
-
When `LIGHTER_PRIVATE_KEY` is set (env var or `wallet set`), the CLI auto-generates and saves the API key on first use.
|
|
84
|
+
Lighter uses a separate API key for trading. There are two paths:
|
|
86
85
|
|
|
87
|
-
|
|
86
|
+
1. **Auto-setup (default)**: When `LIGHTER_PRIVATE_KEY` is set (env var or `wallet set`), the CLI auto-generates and saves the API key (slot 4) on first use.
|
|
87
|
+
2. **Managed agent (recommended for long-running agents)**: `perp wallet agent approve lighter --master <wallet> --api-key-index <slot>` registers a per-agent slot (4-254) with explicit expiry tracking + 3-tier signer routing.
|
|
88
|
+
|
|
89
|
+
If auto-setup fails (e.g. transient network error), retry manually:
|
|
88
90
|
```bash
|
|
89
|
-
perp --json
|
|
91
|
+
perp --json wallet agent approve lighter --master <wallet>
|
|
90
92
|
```
|
|
91
93
|
|
|
92
94
|
### Using the Same EVM Key for Multiple Exchanges
|
|
@@ -69,7 +69,11 @@ perp --json trade market <SYM> buy <SIZE> --split # split via market command f
|
|
|
69
69
|
perp --json trade market <SYM> buy <SIZE> --split --max-slippage 0.5
|
|
70
70
|
```
|
|
71
71
|
|
|
72
|
-
## Funds (Deposit, Withdraw, Transfer, Bridge)
|
|
72
|
+
## Funds (Deposit, Withdraw, Transfer, Bridge, Rebalance)
|
|
73
|
+
|
|
74
|
+
All fund movement lives under `perp funds`.
|
|
75
|
+
|
|
76
|
+
### Exchange in/out
|
|
73
77
|
```bash
|
|
74
78
|
perp --json funds deposit pacifica <AMOUNT>
|
|
75
79
|
perp --json funds deposit hyperliquid <AMOUNT>
|
|
@@ -79,19 +83,26 @@ perp --json funds deposit lighter info # all Lighter deposi
|
|
|
79
83
|
perp --json funds withdraw pacifica <AMOUNT>
|
|
80
84
|
perp --json funds withdraw hyperliquid <AMOUNT>
|
|
81
85
|
perp --json funds withdraw lighter <AMOUNT>
|
|
82
|
-
perp --json funds transfer <AMOUNT> <ADDRESS>
|
|
83
|
-
perp --json funds
|
|
84
|
-
|
|
85
|
-
|
|
86
|
+
perp --json funds transfer <AMOUNT> <ADDRESS> # HL internal transfer (instant)
|
|
87
|
+
perp --json funds info # combined routes & limits
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Cross-chain bridge (multi-provider router: cctp / relay / debridge — auto-cheapest)
|
|
91
|
+
```bash
|
|
92
|
+
perp --json funds bridge chains # supported chains
|
|
93
|
+
perp --json funds bridge quote --from <CHAIN> --to <CHAIN> --amount <AMT>
|
|
94
|
+
perp --json funds bridge send --from <CHAIN> --to <CHAIN> --amount <AMT> # auto-cheapest
|
|
95
|
+
perp --json funds bridge send --from <CHAIN> --to <CHAIN> --amount <AMT> --provider cctp
|
|
96
|
+
perp --json funds bridge exchange --from <EX> --to <EX> --amount <AMT> # between exchanges
|
|
97
|
+
perp --json funds bridge status <ORDER_ID>
|
|
86
98
|
```
|
|
87
99
|
|
|
88
|
-
|
|
100
|
+
### Inter-exchange rebalance (orchestrated withdraw → bridge → deposit)
|
|
89
101
|
```bash
|
|
90
|
-
perp --json
|
|
91
|
-
perp --json
|
|
92
|
-
perp --json
|
|
93
|
-
perp --json
|
|
94
|
-
perp --json bridge status <ORDER_ID>
|
|
102
|
+
perp --json funds rebalance check # balances across exchanges
|
|
103
|
+
perp --json funds rebalance plan # compute optimal moves
|
|
104
|
+
perp --json funds rebalance execute --auto-bridge # withdraw → bridge → deposit
|
|
105
|
+
perp --json funds rebalance execute --withdraw-only # only withdraw, manual rest
|
|
95
106
|
```
|
|
96
107
|
|
|
97
108
|
## Arbitrage
|
|
@@ -137,7 +148,6 @@ perp --json risk liquidation-distance # % distance from liquidation for AL
|
|
|
137
148
|
perp --json risk limits # view current risk limits
|
|
138
149
|
perp --json risk limits --min-liq-distance 30 --max-leverage 5 # set risk limits
|
|
139
150
|
perp --json risk check --notional 1000 --leverage 3 # pre-trade risk check
|
|
140
|
-
perp --json agent ping # exchange connectivity & latency
|
|
141
151
|
perp --json history summary # trading performance
|
|
142
152
|
perp --json history pnl # P&L breakdown by exchange
|
|
143
153
|
perp --json history funding # funding payment aggregation
|
|
@@ -148,24 +158,34 @@ perp --json history perf --period summary # performance summary stats
|
|
|
148
158
|
perp --json history list # execution audit trail
|
|
149
159
|
```
|
|
150
160
|
|
|
151
|
-
## Automated Strategies (
|
|
161
|
+
## Automated Strategies (strategy — 19 algorithms + nested scripted plans)
|
|
152
162
|
```bash
|
|
153
|
-
perp --json
|
|
154
|
-
perp --json
|
|
155
|
-
perp --json
|
|
156
|
-
perp --json
|
|
157
|
-
perp --json
|
|
158
|
-
perp --json
|
|
159
|
-
perp --json
|
|
160
|
-
perp --json
|
|
161
|
-
perp --json
|
|
162
|
-
perp --json
|
|
163
|
-
perp --json
|
|
164
|
-
perp --json
|
|
165
|
-
perp --json
|
|
166
|
-
perp --json
|
|
167
|
-
|
|
168
|
-
|
|
163
|
+
perp --json strategy list-strategies # list all 19 strategies
|
|
164
|
+
perp --json strategy run <STRATEGY> [SYMBOL] # run any registered strategy
|
|
165
|
+
perp --json strategy run funding-auto # multi-exchange funding arb (no symbol needed)
|
|
166
|
+
perp --json strategy apex [SYMBOL] # APEX autonomous orchestrator (Radar+Pulse+Guard)
|
|
167
|
+
perp --json strategy reflect # trading performance analysis (win rate, fees, PnL)
|
|
168
|
+
perp --json strategy preset-list # list strategy presets
|
|
169
|
+
perp --json strategy preset <NAME> [SYMBOL] # run from preset
|
|
170
|
+
perp --json strategy twap <SYMBOL> <SIDE> <SIZE> <DURATION> # TWAP execution
|
|
171
|
+
perp --json strategy grid <SYMBOL> --range <PCT> --grids <N> --size <USD>
|
|
172
|
+
perp --json strategy dca <SYMBOL> <SIDE> <AMOUNT> <INTERVAL>
|
|
173
|
+
perp --json strategy trailing-stop <SYMBOL> # trailing stop with callback %
|
|
174
|
+
perp --json strategy funding-arb # auto funding arb
|
|
175
|
+
perp --json strategy quick-grid <SYMBOL> # quick grid bot
|
|
176
|
+
perp --json strategy quick-arb # quick arb bot
|
|
177
|
+
|
|
178
|
+
# Scripted execution plans (one-shot multi-step)
|
|
179
|
+
perp --json strategy plan example # show plan format
|
|
180
|
+
perp --json strategy plan validate <FILE> # validate plan
|
|
181
|
+
perp --json strategy plan execute <FILE> --dry-run # dry-run plan
|
|
182
|
+
|
|
183
|
+
# Background process supervisor (tmux)
|
|
184
|
+
perp --json background list # list running jobs
|
|
185
|
+
perp --json background stop <ID> # stop a job
|
|
186
|
+
perp --json background logs <ID> # view job logs
|
|
187
|
+
perp --json background remove <ID> # remove a job entry
|
|
188
|
+
perp --json background clean # remove all stopped/done jobs
|
|
169
189
|
```
|
|
170
190
|
|
|
171
191
|
### Available Strategies
|
|
@@ -175,10 +195,11 @@ Arbitrage: `funding-arb`, `funding-auto`, `basis-arb`
|
|
|
175
195
|
Infrastructure: `hedge-agent`, `rfq-agent`, `claude-agent`
|
|
176
196
|
Classic: `grid`, `dca`, `twap`, `apex`
|
|
177
197
|
|
|
178
|
-
##
|
|
198
|
+
## Agent Wallet Management
|
|
179
199
|
```bash
|
|
180
|
-
perp
|
|
181
|
-
perp agent
|
|
182
|
-
perp agent
|
|
183
|
-
perp agent
|
|
200
|
+
perp wallet agent approve <exchange> [--master <name>] # Register an agent wallet
|
|
201
|
+
perp wallet agent list [exchange] # List registered agents
|
|
202
|
+
perp wallet agent revoke <exchange> <agentName> # Revoke an agent
|
|
203
|
+
perp wallet agent rotate <exchange> [agentName] # Revoke + re-approve same name
|
|
204
|
+
perp wallet agent verify [exchange] [agentName] # Verify via DEX query API
|
|
184
205
|
```
|
|
@@ -41,12 +41,12 @@ else
|
|
|
41
41
|
add_check "wallet" "fail" "No wallet configured — run: perp wallet set <exchange> <key>"
|
|
42
42
|
fi
|
|
43
43
|
|
|
44
|
-
# 3. Exchange connectivity
|
|
45
|
-
HEALTH_OUT=$($PERP --json
|
|
44
|
+
# 3. Exchange connectivity (proxy via unauthenticated market prices fetch)
|
|
45
|
+
HEALTH_OUT=$($PERP --json market prices 2>/dev/null || echo '{"ok":false}')
|
|
46
46
|
if echo "$HEALTH_OUT" | grep -q '"ok":true'; then
|
|
47
|
-
add_check "connectivity" "pass" "
|
|
47
|
+
add_check "connectivity" "pass" "Exchange reachable"
|
|
48
48
|
else
|
|
49
|
-
add_check "connectivity" "warn" "
|
|
49
|
+
add_check "connectivity" "warn" "Exchange unreachable"
|
|
50
50
|
fi
|
|
51
51
|
|
|
52
52
|
# 4. Portfolio & balances
|
|
@@ -38,8 +38,8 @@ fi
|
|
|
38
38
|
# 2. Run appropriate scan
|
|
39
39
|
SCAN_RESULT=$($PERP --json arb scan --mode "$MODE" --min "$MIN_SPREAD" 2>/dev/null || echo '{"ok":false}')
|
|
40
40
|
|
|
41
|
-
# 3. Get exchange health for context
|
|
42
|
-
HEALTH=$($PERP --json
|
|
41
|
+
# 3. Get exchange health for context (proxy via market prices fetch)
|
|
42
|
+
HEALTH=$($PERP --json market prices 2>/dev/null || echo '{"ok":false}')
|
|
43
43
|
|
|
44
44
|
if $JSON_MODE; then
|
|
45
45
|
cat <<EOF
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
/**
|
|
3
|
-
* Tests for the alert price merging logic (extracted from alert.ts).
|
|
4
|
-
* Validates the fix for the price map key inconsistency bug.
|
|
5
|
-
*/
|
|
6
|
-
function mergePrices(pacData, hlData) {
|
|
7
|
-
const map = new Map();
|
|
8
|
-
if (pacData) {
|
|
9
|
-
for (const p of pacData) {
|
|
10
|
-
map.set(`pac:${p.symbol}`, Number(p.mark));
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
if (hlData) {
|
|
14
|
-
for (const [symbol, price] of Object.entries(hlData)) {
|
|
15
|
-
map.set(`hl:${symbol}`, Number(price));
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
// Merge into bare symbol keys: prefer pac price, fallback to hl
|
|
19
|
-
const allSymbols = new Set();
|
|
20
|
-
for (const k of map.keys()) {
|
|
21
|
-
if (k.includes(":"))
|
|
22
|
-
allSymbols.add(k.split(":")[1]);
|
|
23
|
-
}
|
|
24
|
-
for (const sym of allSymbols) {
|
|
25
|
-
const pacPrice = map.get(`pac:${sym}`);
|
|
26
|
-
const hlPrice = map.get(`hl:${sym}`);
|
|
27
|
-
map.set(sym, pacPrice ?? hlPrice ?? 0);
|
|
28
|
-
}
|
|
29
|
-
return map;
|
|
30
|
-
}
|
|
31
|
-
describe("Alert price merging", () => {
|
|
32
|
-
it("prefers Pacifica price when both available", () => {
|
|
33
|
-
const map = mergePrices([{ symbol: "BTC", mark: "100000" }], { BTC: "99900" });
|
|
34
|
-
expect(map.get("BTC")).toBe(100000);
|
|
35
|
-
expect(map.get("pac:BTC")).toBe(100000);
|
|
36
|
-
expect(map.get("hl:BTC")).toBe(99900);
|
|
37
|
-
});
|
|
38
|
-
it("falls back to HL price when pac unavailable", () => {
|
|
39
|
-
const map = mergePrices(null, { ETH: "3500" });
|
|
40
|
-
expect(map.get("ETH")).toBe(3500);
|
|
41
|
-
expect(map.has("pac:ETH")).toBe(false);
|
|
42
|
-
expect(map.get("hl:ETH")).toBe(3500);
|
|
43
|
-
});
|
|
44
|
-
it("handles Pacifica-only symbols", () => {
|
|
45
|
-
const map = mergePrices([{ symbol: "SOL", mark: "150" }], {});
|
|
46
|
-
expect(map.get("SOL")).toBe(150);
|
|
47
|
-
});
|
|
48
|
-
it("handles empty data", () => {
|
|
49
|
-
const map = mergePrices(null, null);
|
|
50
|
-
expect(map.size).toBe(0);
|
|
51
|
-
});
|
|
52
|
-
it("handles multiple symbols from both sources", () => {
|
|
53
|
-
const map = mergePrices([
|
|
54
|
-
{ symbol: "BTC", mark: "100000" },
|
|
55
|
-
{ symbol: "ETH", mark: "3500" },
|
|
56
|
-
], {
|
|
57
|
-
BTC: "99900",
|
|
58
|
-
ETH: "3490",
|
|
59
|
-
SOL: "150",
|
|
60
|
-
});
|
|
61
|
-
// Pac preferred for BTC and ETH
|
|
62
|
-
expect(map.get("BTC")).toBe(100000);
|
|
63
|
-
expect(map.get("ETH")).toBe(3500);
|
|
64
|
-
// HL-only for SOL
|
|
65
|
-
expect(map.get("SOL")).toBe(150);
|
|
66
|
-
});
|
|
67
|
-
it("bare symbol key is always set (no orphaned prefixed keys)", () => {
|
|
68
|
-
const map = mergePrices([{ symbol: "DOGE", mark: "0.15" }], { DOGE: "0.14", ARB: "1.20" });
|
|
69
|
-
// Every symbol with a prefixed key should have a bare key
|
|
70
|
-
for (const k of map.keys()) {
|
|
71
|
-
if (k.includes(":")) {
|
|
72
|
-
const bare = k.split(":")[1];
|
|
73
|
-
expect(map.has(bare)).toBe(true);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
});
|
|
77
|
-
});
|
|
78
|
-
describe("Alert trigger conditions", () => {
|
|
79
|
-
it("fires price above alert", () => {
|
|
80
|
-
const currentPrice = 105000;
|
|
81
|
-
const threshold = 100000;
|
|
82
|
-
const condition = "above";
|
|
83
|
-
expect(condition === "above" && currentPrice >= threshold).toBe(true);
|
|
84
|
-
});
|
|
85
|
-
it("does NOT fire price above when below", () => {
|
|
86
|
-
const currentPrice = 95000;
|
|
87
|
-
const threshold = 100000;
|
|
88
|
-
const condition = "above";
|
|
89
|
-
expect(condition === "above" && currentPrice >= threshold).toBe(false);
|
|
90
|
-
});
|
|
91
|
-
it("fires price below alert", () => {
|
|
92
|
-
const currentPrice = 95000;
|
|
93
|
-
const threshold = 100000;
|
|
94
|
-
const condition = "below";
|
|
95
|
-
expect(condition === "below" && currentPrice <= threshold).toBe(true);
|
|
96
|
-
});
|
|
97
|
-
it("fires funding spread alert", () => {
|
|
98
|
-
const spread = 35; // annual %
|
|
99
|
-
const alertThreshold = 30;
|
|
100
|
-
expect(spread >= alertThreshold).toBe(true);
|
|
101
|
-
});
|
|
102
|
-
it("does NOT fire funding spread below threshold", () => {
|
|
103
|
-
const spread = 25;
|
|
104
|
-
const alertThreshold = 30;
|
|
105
|
-
expect(spread >= alertThreshold).toBe(false);
|
|
106
|
-
});
|
|
107
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|