@raintree-technology/perps 0.1.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/CHANGELOG.md +33 -0
- package/LICENSE +21 -0
- package/README.md +175 -0
- package/dist/adapters/aevo.d.ts +64 -0
- package/dist/adapters/aevo.js +899 -0
- package/dist/adapters/certification.d.ts +33 -0
- package/dist/adapters/certification.js +99 -0
- package/dist/adapters/decibel/order-manager.d.ts +45 -0
- package/dist/adapters/decibel/order-manager.js +140 -0
- package/dist/adapters/decibel/rest-client.d.ts +176 -0
- package/dist/adapters/decibel/rest-client.js +155 -0
- package/dist/adapters/decibel/ws-feed.d.ts +28 -0
- package/dist/adapters/decibel/ws-feed.js +166 -0
- package/dist/adapters/decibel.d.ts +108 -0
- package/dist/adapters/decibel.js +1377 -0
- package/dist/adapters/hyperliquid.d.ts +63 -0
- package/dist/adapters/hyperliquid.js +797 -0
- package/dist/adapters/index.d.ts +11 -0
- package/dist/adapters/index.js +12 -0
- package/dist/adapters/interface.d.ts +310 -0
- package/dist/adapters/interface.js +15 -0
- package/dist/adapters/orderly.d.ts +70 -0
- package/dist/adapters/orderly.js +936 -0
- package/dist/adapters/paradex.d.ts +69 -0
- package/dist/adapters/paradex.js +862 -0
- package/dist/adapters/utils.d.ts +17 -0
- package/dist/adapters/utils.js +122 -0
- package/dist/cli/command-metadata.d.ts +2 -0
- package/dist/cli/command-metadata.js +44 -0
- package/dist/cli/context.d.ts +14 -0
- package/dist/cli/context.js +59 -0
- package/dist/cli/experience.d.ts +48 -0
- package/dist/cli/experience.js +243 -0
- package/dist/cli/ink/app/AppShell.d.ts +12 -0
- package/dist/cli/ink/app/AppShell.js +32 -0
- package/dist/cli/ink/app/MetricStrip.d.ts +6 -0
- package/dist/cli/ink/app/MetricStrip.js +14 -0
- package/dist/cli/ink/app/Panel.d.ts +9 -0
- package/dist/cli/ink/app/Panel.js +7 -0
- package/dist/cli/ink/app/ascii.d.ts +2 -0
- package/dist/cli/ink/app/ascii.js +46 -0
- package/dist/cli/ink/app/index.d.ts +5 -0
- package/dist/cli/ink/app/index.js +4 -0
- package/dist/cli/ink/app/types.d.ts +15 -0
- package/dist/cli/ink/app/types.js +1 -0
- package/dist/cli/ink/components/PnL.d.ts +12 -0
- package/dist/cli/ink/components/PnL.js +23 -0
- package/dist/cli/ink/components/Spinner.d.ts +13 -0
- package/dist/cli/ink/components/Spinner.js +13 -0
- package/dist/cli/ink/components/Table.d.ts +14 -0
- package/dist/cli/ink/components/Table.js +42 -0
- package/dist/cli/ink/components/WatchHeader.d.ts +10 -0
- package/dist/cli/ink/components/WatchHeader.js +18 -0
- package/dist/cli/ink/components/index.d.ts +4 -0
- package/dist/cli/ink/components/index.js +4 -0
- package/dist/cli/ink/index.d.ts +4 -0
- package/dist/cli/ink/index.js +4 -0
- package/dist/cli/ink/render.d.ts +12 -0
- package/dist/cli/ink/render.js +21 -0
- package/dist/cli/ink/theme.d.ts +29 -0
- package/dist/cli/ink/theme.js +40 -0
- package/dist/cli/network-defaults.d.ts +10 -0
- package/dist/cli/network-defaults.js +35 -0
- package/dist/cli/output.d.ts +11 -0
- package/dist/cli/output.js +115 -0
- package/dist/cli/program.d.ts +18 -0
- package/dist/cli/program.js +164 -0
- package/dist/cli/watch.d.ts +19 -0
- package/dist/cli/watch.js +35 -0
- package/dist/client/index.d.ts +55 -0
- package/dist/client/index.js +157 -0
- package/dist/commands/account/add.d.ts +2 -0
- package/dist/commands/account/add.js +510 -0
- package/dist/commands/account/balances-simple.d.ts +5 -0
- package/dist/commands/account/balances-simple.js +63 -0
- package/dist/commands/account/index.d.ts +2 -0
- package/dist/commands/account/index.js +17 -0
- package/dist/commands/account/ls.d.ts +2 -0
- package/dist/commands/account/ls.js +95 -0
- package/dist/commands/account/positions-simple.d.ts +5 -0
- package/dist/commands/account/positions-simple.js +77 -0
- package/dist/commands/account/remove.d.ts +2 -0
- package/dist/commands/account/remove.js +47 -0
- package/dist/commands/account/set-default.d.ts +2 -0
- package/dist/commands/account/set-default.js +47 -0
- package/dist/commands/agent/index.d.ts +2 -0
- package/dist/commands/agent/index.js +126 -0
- package/dist/commands/arb/alert.d.ts +6 -0
- package/dist/commands/arb/alert.js +88 -0
- package/dist/commands/arb/basis-execute.d.ts +6 -0
- package/dist/commands/arb/basis-execute.js +332 -0
- package/dist/commands/arb/basis.d.ts +6 -0
- package/dist/commands/arb/basis.js +181 -0
- package/dist/commands/arb/compare.d.ts +6 -0
- package/dist/commands/arb/compare.js +216 -0
- package/dist/commands/arb/execute.d.ts +6 -0
- package/dist/commands/arb/execute.js +467 -0
- package/dist/commands/arb/funding.d.ts +6 -0
- package/dist/commands/arb/funding.js +201 -0
- package/dist/commands/arb/history.d.ts +6 -0
- package/dist/commands/arb/history.js +153 -0
- package/dist/commands/arb/index.d.ts +6 -0
- package/dist/commands/arb/index.js +29 -0
- package/dist/commands/arb/positions.d.ts +6 -0
- package/dist/commands/arb/positions.js +158 -0
- package/dist/commands/arb/spread.d.ts +6 -0
- package/dist/commands/arb/spread.js +253 -0
- package/dist/commands/arb/track.d.ts +6 -0
- package/dist/commands/arb/track.js +259 -0
- package/dist/commands/asset/book-simple.d.ts +5 -0
- package/dist/commands/asset/book-simple.js +77 -0
- package/dist/commands/asset/index.d.ts +2 -0
- package/dist/commands/asset/index.js +5 -0
- package/dist/commands/completion.d.ts +2 -0
- package/dist/commands/completion.js +161 -0
- package/dist/commands/config/index.d.ts +5 -0
- package/dist/commands/config/index.js +109 -0
- package/dist/commands/data/index.d.ts +31 -0
- package/dist/commands/data/index.js +1466 -0
- package/dist/commands/doctor.d.ts +2 -0
- package/dist/commands/doctor.js +201 -0
- package/dist/commands/exchange/index.d.ts +2 -0
- package/dist/commands/exchange/index.js +107 -0
- package/dist/commands/index.d.ts +2 -0
- package/dist/commands/index.js +48 -0
- package/dist/commands/markets/index.d.ts +2 -0
- package/dist/commands/markets/index.js +5 -0
- package/dist/commands/markets/ls-simple.d.ts +7 -0
- package/dist/commands/markets/ls-simple.js +277 -0
- package/dist/commands/operator/index.d.ts +2 -0
- package/dist/commands/operator/index.js +146 -0
- package/dist/commands/order/cancel-simple.d.ts +5 -0
- package/dist/commands/order/cancel-simple.js +104 -0
- package/dist/commands/order/index.d.ts +2 -0
- package/dist/commands/order/index.js +13 -0
- package/dist/commands/order/limit-simple.d.ts +5 -0
- package/dist/commands/order/limit-simple.js +195 -0
- package/dist/commands/order/market-simple.d.ts +5 -0
- package/dist/commands/order/market-simple.js +190 -0
- package/dist/commands/order/shared.d.ts +17 -0
- package/dist/commands/order/shared.js +51 -0
- package/dist/commands/order/trigger-simple.d.ts +5 -0
- package/dist/commands/order/trigger-simple.js +246 -0
- package/dist/commands/referral/index.d.ts +2 -0
- package/dist/commands/referral/index.js +7 -0
- package/dist/commands/referral/set.d.ts +2 -0
- package/dist/commands/referral/set.js +26 -0
- package/dist/commands/referral/status.d.ts +2 -0
- package/dist/commands/referral/status.js +31 -0
- package/dist/commands/replay/index.d.ts +2 -0
- package/dist/commands/replay/index.js +152 -0
- package/dist/commands/risk/analytics.d.ts +2 -0
- package/dist/commands/risk/analytics.js +64 -0
- package/dist/commands/risk/audit.d.ts +2 -0
- package/dist/commands/risk/audit.js +52 -0
- package/dist/commands/risk/index.d.ts +2 -0
- package/dist/commands/risk/index.js +9 -0
- package/dist/commands/risk/rules.d.ts +2 -0
- package/dist/commands/risk/rules.js +102 -0
- package/dist/commands/server.d.ts +2 -0
- package/dist/commands/server.js +208 -0
- package/dist/commands/setup/index.d.ts +2 -0
- package/dist/commands/setup/index.js +478 -0
- package/dist/commands/signal/index.d.ts +2 -0
- package/dist/commands/signal/index.js +129 -0
- package/dist/commands/state/index.d.ts +2 -0
- package/dist/commands/state/index.js +5 -0
- package/dist/commands/state/show.d.ts +2 -0
- package/dist/commands/state/show.js +105 -0
- package/dist/commands/strategy/index.d.ts +4 -0
- package/dist/commands/strategy/index.js +73 -0
- package/dist/commands/traces/index.d.ts +2 -0
- package/dist/commands/traces/index.js +76 -0
- package/dist/commands/ui/demo.d.ts +9 -0
- package/dist/commands/ui/demo.js +195 -0
- package/dist/commands/ui/index.d.ts +2 -0
- package/dist/commands/ui/index.js +7 -0
- package/dist/commands/ui/terminal.d.ts +2 -0
- package/dist/commands/ui/terminal.js +255 -0
- package/dist/commands/upgrade.d.ts +2 -0
- package/dist/commands/upgrade.js +98 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +4 -0
- package/dist/lib/agent/audit.d.ts +12 -0
- package/dist/lib/agent/audit.js +13 -0
- package/dist/lib/agent/gateway.d.ts +13 -0
- package/dist/lib/agent/gateway.js +598 -0
- package/dist/lib/agent/metrics.d.ts +33 -0
- package/dist/lib/agent/metrics.js +175 -0
- package/dist/lib/agent/signature.d.ts +8 -0
- package/dist/lib/agent/signature.js +28 -0
- package/dist/lib/agent/tools.d.ts +28 -0
- package/dist/lib/agent/tools.js +453 -0
- package/dist/lib/agent/x402.d.ts +23 -0
- package/dist/lib/agent/x402.js +62 -0
- package/dist/lib/api-wallet.d.ts +69 -0
- package/dist/lib/api-wallet.js +101 -0
- package/dist/lib/balance-watcher.d.ts +25 -0
- package/dist/lib/balance-watcher.js +83 -0
- package/dist/lib/book-watcher.d.ts +25 -0
- package/dist/lib/book-watcher.js +48 -0
- package/dist/lib/config.d.ts +88 -0
- package/dist/lib/config.js +427 -0
- package/dist/lib/constants.d.ts +50 -0
- package/dist/lib/constants.js +84 -0
- package/dist/lib/contracts.d.ts +7 -0
- package/dist/lib/contracts.js +8 -0
- package/dist/lib/credential-vault.d.ts +22 -0
- package/dist/lib/credential-vault.js +109 -0
- package/dist/lib/db/accounts.d.ts +83 -0
- package/dist/lib/db/accounts.js +203 -0
- package/dist/lib/db/funding-history.d.ts +69 -0
- package/dist/lib/db/funding-history.js +183 -0
- package/dist/lib/db/index.d.ts +11 -0
- package/dist/lib/db/index.js +272 -0
- package/dist/lib/events/bus.d.ts +10 -0
- package/dist/lib/events/bus.js +17 -0
- package/dist/lib/events/types.d.ts +51 -0
- package/dist/lib/events/types.js +1 -0
- package/dist/lib/exchange.d.ts +30 -0
- package/dist/lib/exchange.js +84 -0
- package/dist/lib/execution/journal.d.ts +25 -0
- package/dist/lib/execution/journal.js +158 -0
- package/dist/lib/execution/safety.d.ts +34 -0
- package/dist/lib/execution/safety.js +197 -0
- package/dist/lib/exit-codes.d.ts +18 -0
- package/dist/lib/exit-codes.js +60 -0
- package/dist/lib/fetch.d.ts +18 -0
- package/dist/lib/fetch.js +66 -0
- package/dist/lib/fs-security.d.ts +10 -0
- package/dist/lib/fs-security.js +26 -0
- package/dist/lib/funding-tracker.d.ts +40 -0
- package/dist/lib/funding-tracker.js +118 -0
- package/dist/lib/logger.d.ts +27 -0
- package/dist/lib/logger.js +82 -0
- package/dist/lib/network-model.d.ts +13 -0
- package/dist/lib/network-model.js +30 -0
- package/dist/lib/onboarding.d.ts +133 -0
- package/dist/lib/onboarding.js +1459 -0
- package/dist/lib/operator-state.d.ts +25 -0
- package/dist/lib/operator-state.js +82 -0
- package/dist/lib/orders-watcher.d.ts +24 -0
- package/dist/lib/orders-watcher.js +74 -0
- package/dist/lib/paths.d.ts +20 -0
- package/dist/lib/paths.js +23 -0
- package/dist/lib/portfolio-watcher.d.ts +33 -0
- package/dist/lib/portfolio-watcher.js +95 -0
- package/dist/lib/position-watcher.d.ts +16 -0
- package/dist/lib/position-watcher.js +44 -0
- package/dist/lib/price-watcher.d.ts +15 -0
- package/dist/lib/price-watcher.js +84 -0
- package/dist/lib/prompts.d.ts +32 -0
- package/dist/lib/prompts.js +105 -0
- package/dist/lib/rate-limit.d.ts +32 -0
- package/dist/lib/rate-limit.js +88 -0
- package/dist/lib/risk/analytics.d.ts +39 -0
- package/dist/lib/risk/analytics.js +98 -0
- package/dist/lib/risk/drawdown.d.ts +18 -0
- package/dist/lib/risk/drawdown.js +49 -0
- package/dist/lib/risk/evaluation-log.d.ts +29 -0
- package/dist/lib/risk/evaluation-log.js +61 -0
- package/dist/lib/risk/index.d.ts +4 -0
- package/dist/lib/risk/index.js +4 -0
- package/dist/lib/risk/limits.d.ts +23 -0
- package/dist/lib/risk/limits.js +27 -0
- package/dist/lib/risk/manager.d.ts +32 -0
- package/dist/lib/risk/manager.js +85 -0
- package/dist/lib/risk/policy-middleware.d.ts +33 -0
- package/dist/lib/risk/policy-middleware.js +267 -0
- package/dist/lib/risk/position-sizer.d.ts +9 -0
- package/dist/lib/risk/position-sizer.js +14 -0
- package/dist/lib/risk/rules-store.d.ts +16 -0
- package/dist/lib/risk/rules-store.js +47 -0
- package/dist/lib/schema.d.ts +254 -0
- package/dist/lib/schema.js +199 -0
- package/dist/lib/secrets.d.ts +3 -0
- package/dist/lib/secrets.js +62 -0
- package/dist/lib/settings.d.ts +24 -0
- package/dist/lib/settings.js +86 -0
- package/dist/lib/signals.d.ts +73 -0
- package/dist/lib/signals.js +136 -0
- package/dist/lib/stable-stringify.d.ts +6 -0
- package/dist/lib/stable-stringify.js +17 -0
- package/dist/lib/state-context.d.ts +44 -0
- package/dist/lib/state-context.js +133 -0
- package/dist/lib/strategy/basis-trade.d.ts +2 -0
- package/dist/lib/strategy/basis-trade.js +24 -0
- package/dist/lib/strategy/funding-arb.d.ts +2 -0
- package/dist/lib/strategy/funding-arb.js +23 -0
- package/dist/lib/strategy/interface.d.ts +23 -0
- package/dist/lib/strategy/interface.js +1 -0
- package/dist/lib/strategy/registry.d.ts +4 -0
- package/dist/lib/strategy/registry.js +10 -0
- package/dist/lib/telemetry.d.ts +25 -0
- package/dist/lib/telemetry.js +101 -0
- package/dist/lib/trace-queries.d.ts +20 -0
- package/dist/lib/trace-queries.js +133 -0
- package/dist/lib/trace.d.ts +1 -0
- package/dist/lib/trace.js +4 -0
- package/dist/lib/trade-reputation.d.ts +6 -0
- package/dist/lib/trade-reputation.js +99 -0
- package/dist/lib/ui-tokens.d.ts +21 -0
- package/dist/lib/ui-tokens.js +26 -0
- package/dist/lib/validate.d.ts +39 -0
- package/dist/lib/validate.js +108 -0
- package/dist/lib/validation.d.ts +9 -0
- package/dist/lib/validation.js +64 -0
- package/dist/server/cache.d.ts +38 -0
- package/dist/server/cache.js +56 -0
- package/dist/server/index.d.ts +2 -0
- package/dist/server/index.js +89 -0
- package/dist/server/ipc.d.ts +18 -0
- package/dist/server/ipc.js +159 -0
- package/dist/server/subscriptions.d.ts +18 -0
- package/dist/server/subscriptions.js +114 -0
- package/package.json +124 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { loadConfig } from "./config.js";
|
|
2
|
+
import { readOperatorState } from "./operator-state.js";
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
// Helpers
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
function resolveEquityFromBalances(balances) {
|
|
7
|
+
if (balances.length === 0) {
|
|
8
|
+
return { equity: 0, available: 0, marginUsed: 0 };
|
|
9
|
+
}
|
|
10
|
+
const stableAssets = ["USD", "USDC", "USDT"];
|
|
11
|
+
const preferred = balances.find((b) => stableAssets.includes(b.asset.toUpperCase()));
|
|
12
|
+
const candidate = preferred ?? balances[0];
|
|
13
|
+
const equity = Number.parseFloat(candidate.total);
|
|
14
|
+
const available = Number.parseFloat(candidate.available);
|
|
15
|
+
const marginUsed = Number.parseFloat(candidate.marginUsed);
|
|
16
|
+
return {
|
|
17
|
+
equity: Number.isFinite(equity) ? equity : 0,
|
|
18
|
+
available: Number.isFinite(available) ? available : 0,
|
|
19
|
+
marginUsed: Number.isFinite(marginUsed) ? marginUsed : 0,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
function toPositionContext(p) {
|
|
23
|
+
const size = Number.parseFloat(p.size);
|
|
24
|
+
const markPrice = Number.parseFloat(p.markPrice);
|
|
25
|
+
const liqPrice = p.liquidationPrice
|
|
26
|
+
? Number.parseFloat(p.liquidationPrice)
|
|
27
|
+
: null;
|
|
28
|
+
return {
|
|
29
|
+
market: p.market,
|
|
30
|
+
side: p.side,
|
|
31
|
+
size,
|
|
32
|
+
entryPrice: Number.parseFloat(p.entryPrice),
|
|
33
|
+
markPrice,
|
|
34
|
+
notionalUsd: Math.abs(size) * markPrice,
|
|
35
|
+
unrealizedPnl: Number.parseFloat(p.unrealizedPnl),
|
|
36
|
+
realizedPnl: Number.parseFloat(p.realizedPnl),
|
|
37
|
+
leverage: p.leverage,
|
|
38
|
+
liquidationPrice: liqPrice,
|
|
39
|
+
marginType: p.marginType,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function round2(n) {
|
|
43
|
+
return Math.round(n * 100) / 100;
|
|
44
|
+
}
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
// Builder
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
export async function buildStateContext(adapter, exchangeId, config) {
|
|
49
|
+
const errors = [];
|
|
50
|
+
// Fetch in parallel with allSettled so partial failures don't kill everything
|
|
51
|
+
const [positionsResult, balancesResult, ordersResult] = await Promise.allSettled([
|
|
52
|
+
adapter.getPositions(),
|
|
53
|
+
adapter.getBalances(),
|
|
54
|
+
adapter.getOrders(),
|
|
55
|
+
]);
|
|
56
|
+
let rawPositions = [];
|
|
57
|
+
if (positionsResult.status === "fulfilled") {
|
|
58
|
+
rawPositions = positionsResult.value;
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
errors.push(`Failed to fetch positions: ${positionsResult.reason}`);
|
|
62
|
+
}
|
|
63
|
+
let rawBalances = [];
|
|
64
|
+
if (balancesResult.status === "fulfilled") {
|
|
65
|
+
rawBalances = balancesResult.value;
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
errors.push(`Failed to fetch balances: ${balancesResult.reason}`);
|
|
69
|
+
}
|
|
70
|
+
let rawOrders = [];
|
|
71
|
+
if (ordersResult.status === "fulfilled") {
|
|
72
|
+
rawOrders = ordersResult.value;
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
errors.push(`Failed to fetch orders: ${ordersResult.reason}`);
|
|
76
|
+
}
|
|
77
|
+
// Resolve equity from balances
|
|
78
|
+
const { equity, available, marginUsed } = resolveEquityFromBalances(rawBalances);
|
|
79
|
+
// Map positions
|
|
80
|
+
const positions = rawPositions.map(toPositionContext);
|
|
81
|
+
// Compute aggregates
|
|
82
|
+
const totalExposureUsd = positions.reduce((sum, p) => sum + p.notionalUsd, 0);
|
|
83
|
+
const netUnrealizedPnl = positions.reduce((sum, p) => sum + p.unrealizedPnl, 0);
|
|
84
|
+
const netRealizedPnl = positions.reduce((sum, p) => sum + p.realizedPnl, 0);
|
|
85
|
+
const leverageUsed = equity > 0 ? round2(totalExposureUsd / equity) : 0;
|
|
86
|
+
// Filter open orders
|
|
87
|
+
const openOrders = rawOrders.filter((o) => o.status === "open");
|
|
88
|
+
// Read operator state for halted/drawdown info
|
|
89
|
+
const operatorState = readOperatorState();
|
|
90
|
+
const killSwitchHalted = operatorState.killSwitch.enabled;
|
|
91
|
+
const heartbeatHalted = operatorState.heartbeat?.halted ?? false;
|
|
92
|
+
const halted = killSwitchHalted || heartbeatHalted;
|
|
93
|
+
let haltReason = "";
|
|
94
|
+
if (killSwitchHalted) {
|
|
95
|
+
haltReason = operatorState.killSwitch.reason ?? "kill switch enabled";
|
|
96
|
+
}
|
|
97
|
+
else if (heartbeatHalted) {
|
|
98
|
+
haltReason = operatorState.heartbeat?.haltReason ?? "halted";
|
|
99
|
+
}
|
|
100
|
+
const drawdownFromPeakPct = operatorState.heartbeat?.drawdownPct ?? 0;
|
|
101
|
+
const peakEquity = operatorState.heartbeat?.peakEquity ?? equity;
|
|
102
|
+
// Risk limits from config
|
|
103
|
+
const resolvedConfig = config ?? loadConfig(false);
|
|
104
|
+
const risk = resolvedConfig.risk;
|
|
105
|
+
return {
|
|
106
|
+
exchange: exchangeId,
|
|
107
|
+
timestamp: Date.now(),
|
|
108
|
+
equity,
|
|
109
|
+
availableBalance: available,
|
|
110
|
+
marginUsed,
|
|
111
|
+
marginAvailable: available,
|
|
112
|
+
totalExposureUsd,
|
|
113
|
+
netUnrealizedPnl,
|
|
114
|
+
netRealizedPnl,
|
|
115
|
+
leverageUsed,
|
|
116
|
+
positionCount: positions.length,
|
|
117
|
+
positions,
|
|
118
|
+
openOrderCount: openOrders.length,
|
|
119
|
+
openOrders,
|
|
120
|
+
halted,
|
|
121
|
+
haltReason,
|
|
122
|
+
drawdownFromPeakPct,
|
|
123
|
+
peakEquity,
|
|
124
|
+
riskLimits: {
|
|
125
|
+
maxPositionSizeUsd: risk.maxPositionSizeUsd,
|
|
126
|
+
maxTotalExposureUsd: risk.maxTotalExposureUsd,
|
|
127
|
+
maxLeverage: risk.maxLeverage,
|
|
128
|
+
minSignalConfidence: risk.minSignalConfidence,
|
|
129
|
+
maxDrawdownPct: risk.maxDrawdownPct,
|
|
130
|
+
},
|
|
131
|
+
errors,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { registerStrategy } from "./registry.js";
|
|
2
|
+
export const basisTradeStrategy = {
|
|
3
|
+
id: "basis-trade",
|
|
4
|
+
name: "Basis Trade (Cash-and-Carry)",
|
|
5
|
+
description: "Single-exchange basis convergence trade. When the perp trades at a premium to the index, short the perp to profit as the basis converges. When at a discount, long the perp.",
|
|
6
|
+
legs: 1,
|
|
7
|
+
shape: "directional",
|
|
8
|
+
exchanges: "single",
|
|
9
|
+
entrySignal: "Absolute basis spread (perp vs index) exceeds minimum threshold",
|
|
10
|
+
exitSignal: "Basis converges toward zero, manual close, or TP/SL hit",
|
|
11
|
+
parameters: [
|
|
12
|
+
{ name: "minBasis", type: "number", default: 0.1, description: "Minimum basis spread (%) to enter" },
|
|
13
|
+
{ name: "size", type: "number", description: "Position size in USD" },
|
|
14
|
+
{ name: "confidence", type: "number", default: 0.5, description: "Signal confidence (0-1)" },
|
|
15
|
+
{ name: "takeProfitPct", type: "number", description: "Take-profit basis convergence threshold (%)" },
|
|
16
|
+
{ name: "stopLossPct", type: "number", description: "Stop-loss basis divergence threshold (%)" },
|
|
17
|
+
],
|
|
18
|
+
riskProfile: {
|
|
19
|
+
maxPositionSizeUsd: 3000,
|
|
20
|
+
maxLeverage: 3,
|
|
21
|
+
maxDrawdownPct: 5,
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
registerStrategy(basisTradeStrategy);
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { registerStrategy } from "./registry.js";
|
|
2
|
+
export const fundingArbStrategy = {
|
|
3
|
+
id: "funding-arb",
|
|
4
|
+
name: "Funding Rate Arbitrage",
|
|
5
|
+
description: "Delta-neutral cross-exchange funding rate capture. Short on the exchange paying the highest rate, long on the exchange paying the lowest. Profits from the funding rate differential.",
|
|
6
|
+
legs: 2,
|
|
7
|
+
shape: "delta-neutral",
|
|
8
|
+
exchanges: "cross",
|
|
9
|
+
entrySignal: "Funding rate spread between exchanges exceeds minimum threshold",
|
|
10
|
+
exitSignal: "Funding rate spread compresses or reverses, manual close, or TP/SL hit",
|
|
11
|
+
parameters: [
|
|
12
|
+
{ name: "minSpread", type: "number", default: 0.05, description: "Minimum annualized funding spread (%) to enter" },
|
|
13
|
+
{ name: "size", type: "number", description: "Position size in USD per leg" },
|
|
14
|
+
{ name: "confidence", type: "number", default: 0.5, description: "Signal confidence (0-1)" },
|
|
15
|
+
{ name: "takeProfitPct", type: "number", default: 4, description: "Take-profit percentage" },
|
|
16
|
+
{ name: "stopLossPct", type: "number", default: 2, description: "Stop-loss percentage" },
|
|
17
|
+
],
|
|
18
|
+
riskProfile: {
|
|
19
|
+
maxPositionSizeUsd: 5000,
|
|
20
|
+
maxLeverage: 3,
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
registerStrategy(fundingArbStrategy);
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export interface StrategyParameter {
|
|
2
|
+
name: string;
|
|
3
|
+
type: "number" | "string" | "boolean";
|
|
4
|
+
default?: unknown;
|
|
5
|
+
description: string;
|
|
6
|
+
}
|
|
7
|
+
export interface StrategyRiskProfile {
|
|
8
|
+
maxPositionSizeUsd?: number;
|
|
9
|
+
maxLeverage?: number;
|
|
10
|
+
maxDrawdownPct?: number;
|
|
11
|
+
}
|
|
12
|
+
export interface StrategyDefinition {
|
|
13
|
+
id: string;
|
|
14
|
+
name: string;
|
|
15
|
+
description: string;
|
|
16
|
+
legs: number;
|
|
17
|
+
shape: "delta-neutral" | "directional" | "market-neutral";
|
|
18
|
+
parameters: StrategyParameter[];
|
|
19
|
+
riskProfile: StrategyRiskProfile;
|
|
20
|
+
exchanges: "single" | "cross";
|
|
21
|
+
entrySignal: string;
|
|
22
|
+
exitSignal: string;
|
|
23
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { StrategyDefinition } from "./interface.js";
|
|
2
|
+
export declare function registerStrategy(definition: StrategyDefinition): void;
|
|
3
|
+
export declare function listStrategies(): StrategyDefinition[];
|
|
4
|
+
export declare function getStrategy(id: string): StrategyDefinition | undefined;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
const strategies = new Map();
|
|
2
|
+
export function registerStrategy(definition) {
|
|
3
|
+
strategies.set(definition.id, definition);
|
|
4
|
+
}
|
|
5
|
+
export function listStrategies() {
|
|
6
|
+
return [...strategies.values()];
|
|
7
|
+
}
|
|
8
|
+
export function getStrategy(id) {
|
|
9
|
+
return strategies.get(id);
|
|
10
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export interface TelemetryCounter {
|
|
2
|
+
total: number;
|
|
3
|
+
success: number;
|
|
4
|
+
failed: number;
|
|
5
|
+
skipped: number;
|
|
6
|
+
manual: number;
|
|
7
|
+
lastUpdatedAt: number;
|
|
8
|
+
}
|
|
9
|
+
export interface TelemetryStore {
|
|
10
|
+
version: number;
|
|
11
|
+
updatedAt: number;
|
|
12
|
+
counters: Record<string, TelemetryCounter>;
|
|
13
|
+
}
|
|
14
|
+
export declare function recordTelemetryMetric(args: {
|
|
15
|
+
metric: string;
|
|
16
|
+
status: "success" | "failed" | "skipped" | "manual";
|
|
17
|
+
}): void;
|
|
18
|
+
export declare function getTelemetrySnapshot(): TelemetryStore;
|
|
19
|
+
export declare function summarizeFailureRates(store: TelemetryStore): Array<{
|
|
20
|
+
metric: string;
|
|
21
|
+
total: number;
|
|
22
|
+
failureRate: number;
|
|
23
|
+
successRate: number;
|
|
24
|
+
}>;
|
|
25
|
+
export declare function resetTelemetry(): void;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname } from "node:path";
|
|
3
|
+
import { TELEMETRY_PATH } from "./paths.js";
|
|
4
|
+
import { ensurePrivateDir, hardenPrivateFile, PRIVATE_FILE_MODE } from "./fs-security.js";
|
|
5
|
+
const TELEMETRY_SCHEMA_VERSION = 1;
|
|
6
|
+
function telemetryEnabled() {
|
|
7
|
+
if (process.env.PERPS_DISABLE_TELEMETRY === "1") {
|
|
8
|
+
return false;
|
|
9
|
+
}
|
|
10
|
+
if (process.env.VITEST) {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
return true;
|
|
14
|
+
}
|
|
15
|
+
function createCounter() {
|
|
16
|
+
return {
|
|
17
|
+
total: 0,
|
|
18
|
+
success: 0,
|
|
19
|
+
failed: 0,
|
|
20
|
+
skipped: 0,
|
|
21
|
+
manual: 0,
|
|
22
|
+
lastUpdatedAt: 0,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
function createStore() {
|
|
26
|
+
return {
|
|
27
|
+
version: TELEMETRY_SCHEMA_VERSION,
|
|
28
|
+
updatedAt: Date.now(),
|
|
29
|
+
counters: {},
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
function readStore() {
|
|
33
|
+
if (!existsSync(TELEMETRY_PATH)) {
|
|
34
|
+
return createStore();
|
|
35
|
+
}
|
|
36
|
+
try {
|
|
37
|
+
const raw = readFileSync(TELEMETRY_PATH, "utf-8");
|
|
38
|
+
const parsed = JSON.parse(raw);
|
|
39
|
+
return {
|
|
40
|
+
version: TELEMETRY_SCHEMA_VERSION,
|
|
41
|
+
updatedAt: typeof parsed.updatedAt === "number" ? parsed.updatedAt : Date.now(),
|
|
42
|
+
counters: parsed.counters ?? {},
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return createStore();
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function writeStore(store) {
|
|
50
|
+
ensurePrivateDir(dirname(TELEMETRY_PATH));
|
|
51
|
+
writeFileSync(TELEMETRY_PATH, `${JSON.stringify(store, null, 2)}\n`, {
|
|
52
|
+
mode: PRIVATE_FILE_MODE,
|
|
53
|
+
});
|
|
54
|
+
hardenPrivateFile(TELEMETRY_PATH);
|
|
55
|
+
}
|
|
56
|
+
function upsertCounter(store, key) {
|
|
57
|
+
const existing = store.counters[key] ?? createCounter();
|
|
58
|
+
store.counters[key] = existing;
|
|
59
|
+
return existing;
|
|
60
|
+
}
|
|
61
|
+
export function recordTelemetryMetric(args) {
|
|
62
|
+
if (!telemetryEnabled()) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
const store = readStore();
|
|
66
|
+
const counter = upsertCounter(store, args.metric);
|
|
67
|
+
counter.total += 1;
|
|
68
|
+
if (args.status === "success")
|
|
69
|
+
counter.success += 1;
|
|
70
|
+
if (args.status === "failed")
|
|
71
|
+
counter.failed += 1;
|
|
72
|
+
if (args.status === "skipped")
|
|
73
|
+
counter.skipped += 1;
|
|
74
|
+
if (args.status === "manual")
|
|
75
|
+
counter.manual += 1;
|
|
76
|
+
counter.lastUpdatedAt = Date.now();
|
|
77
|
+
store.updatedAt = counter.lastUpdatedAt;
|
|
78
|
+
writeStore(store);
|
|
79
|
+
}
|
|
80
|
+
export function getTelemetrySnapshot() {
|
|
81
|
+
return readStore();
|
|
82
|
+
}
|
|
83
|
+
export function summarizeFailureRates(store) {
|
|
84
|
+
const rows = [];
|
|
85
|
+
for (const [metric, counter] of Object.entries(store.counters)) {
|
|
86
|
+
const total = Math.max(0, counter.total);
|
|
87
|
+
const failureRate = total > 0 ? counter.failed / total : 0;
|
|
88
|
+
const successRate = total > 0 ? counter.success / total : 0;
|
|
89
|
+
rows.push({
|
|
90
|
+
metric,
|
|
91
|
+
total,
|
|
92
|
+
failureRate,
|
|
93
|
+
successRate,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
rows.sort((a, b) => b.failureRate - a.failureRate || b.total - a.total);
|
|
97
|
+
return rows;
|
|
98
|
+
}
|
|
99
|
+
export function resetTelemetry() {
|
|
100
|
+
writeStore(createStore());
|
|
101
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export interface TraceSummary {
|
|
2
|
+
traceId: string;
|
|
3
|
+
firstSeen: number;
|
|
4
|
+
lastSeen: number;
|
|
5
|
+
exchange: string;
|
|
6
|
+
markets: string[];
|
|
7
|
+
journalEntries: number;
|
|
8
|
+
riskEvaluations: number;
|
|
9
|
+
riskDenials: number;
|
|
10
|
+
journalStatus: "succeeded" | "failed" | "pending" | "mixed" | null;
|
|
11
|
+
durationMs: number;
|
|
12
|
+
}
|
|
13
|
+
export interface TraceListOptions {
|
|
14
|
+
minutes?: number;
|
|
15
|
+
exchange?: string;
|
|
16
|
+
market?: string;
|
|
17
|
+
limit?: number;
|
|
18
|
+
status?: "succeeded" | "failed" | "denied";
|
|
19
|
+
}
|
|
20
|
+
export declare function listTraces(options?: TraceListOptions): TraceSummary[];
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { getDb } from "./db/index.js";
|
|
2
|
+
function deriveJournalStatus(statuses, journalEntries) {
|
|
3
|
+
if (journalEntries === 0)
|
|
4
|
+
return null;
|
|
5
|
+
if (statuses.size === 1) {
|
|
6
|
+
const value = [...statuses][0];
|
|
7
|
+
if (value === "succeeded" || value === "failed" || value === "pending") {
|
|
8
|
+
return value;
|
|
9
|
+
}
|
|
10
|
+
return "mixed";
|
|
11
|
+
}
|
|
12
|
+
return "mixed";
|
|
13
|
+
}
|
|
14
|
+
export function listTraces(options) {
|
|
15
|
+
const db = getDb();
|
|
16
|
+
const minutes = options?.minutes ?? 60;
|
|
17
|
+
const limit = options?.limit ?? 50;
|
|
18
|
+
const cutoff = Date.now() - minutes * 60 * 1000;
|
|
19
|
+
const traceMap = new Map();
|
|
20
|
+
// 1. Query risk_evaluation_log grouped by trace_id
|
|
21
|
+
const riskRows = db
|
|
22
|
+
.prepare(`SELECT trace_id,
|
|
23
|
+
MIN(created_at) as first_seen,
|
|
24
|
+
MAX(created_at) as last_seen,
|
|
25
|
+
exchange,
|
|
26
|
+
GROUP_CONCAT(DISTINCT market) as markets,
|
|
27
|
+
COUNT(*) as eval_count,
|
|
28
|
+
SUM(CASE WHEN allowed = 0 THEN 1 ELSE 0 END) as denial_count
|
|
29
|
+
FROM risk_evaluation_log
|
|
30
|
+
WHERE trace_id IS NOT NULL AND created_at > ?
|
|
31
|
+
GROUP BY trace_id`)
|
|
32
|
+
.all(cutoff);
|
|
33
|
+
for (const row of riskRows) {
|
|
34
|
+
const markets = new Set(row.markets ? row.markets.split(",").filter(Boolean) : []);
|
|
35
|
+
traceMap.set(row.trace_id, {
|
|
36
|
+
traceId: row.trace_id,
|
|
37
|
+
firstSeen: row.first_seen,
|
|
38
|
+
lastSeen: row.last_seen,
|
|
39
|
+
exchange: row.exchange,
|
|
40
|
+
markets,
|
|
41
|
+
journalEntries: 0,
|
|
42
|
+
riskEvaluations: row.eval_count,
|
|
43
|
+
riskDenials: row.denial_count,
|
|
44
|
+
statuses: new Set(),
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
// 2. Query execution_journal grouped by trace_id
|
|
48
|
+
const journalRows = db
|
|
49
|
+
.prepare(`SELECT trace_id,
|
|
50
|
+
MIN(created_at) as first_seen,
|
|
51
|
+
MAX(created_at) as last_seen,
|
|
52
|
+
exchange,
|
|
53
|
+
GROUP_CONCAT(DISTINCT market) as markets,
|
|
54
|
+
COUNT(*) as entry_count,
|
|
55
|
+
GROUP_CONCAT(DISTINCT status) as statuses
|
|
56
|
+
FROM execution_journal
|
|
57
|
+
WHERE trace_id IS NOT NULL AND created_at > ?
|
|
58
|
+
GROUP BY trace_id`)
|
|
59
|
+
.all(cutoff);
|
|
60
|
+
for (const row of journalRows) {
|
|
61
|
+
const existing = traceMap.get(row.trace_id);
|
|
62
|
+
const journalMarkets = row.markets
|
|
63
|
+
? row.markets.split(",").filter(Boolean)
|
|
64
|
+
: [];
|
|
65
|
+
const journalStatuses = row.statuses
|
|
66
|
+
? row.statuses.split(",").filter(Boolean)
|
|
67
|
+
: [];
|
|
68
|
+
if (existing) {
|
|
69
|
+
// Merge
|
|
70
|
+
existing.firstSeen = Math.min(existing.firstSeen, row.first_seen);
|
|
71
|
+
existing.lastSeen = Math.max(existing.lastSeen, row.last_seen);
|
|
72
|
+
for (const m of journalMarkets)
|
|
73
|
+
existing.markets.add(m);
|
|
74
|
+
existing.journalEntries = row.entry_count;
|
|
75
|
+
for (const s of journalStatuses)
|
|
76
|
+
existing.statuses.add(s);
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
const markets = new Set(journalMarkets);
|
|
80
|
+
const statuses = new Set(journalStatuses);
|
|
81
|
+
traceMap.set(row.trace_id, {
|
|
82
|
+
traceId: row.trace_id,
|
|
83
|
+
firstSeen: row.first_seen,
|
|
84
|
+
lastSeen: row.last_seen,
|
|
85
|
+
exchange: row.exchange,
|
|
86
|
+
markets,
|
|
87
|
+
journalEntries: row.entry_count,
|
|
88
|
+
riskEvaluations: 0,
|
|
89
|
+
riskDenials: 0,
|
|
90
|
+
statuses,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
// 3. Convert to TraceSummary, apply filters, sort, limit
|
|
95
|
+
let results = [];
|
|
96
|
+
for (const acc of traceMap.values()) {
|
|
97
|
+
const journalStatus = deriveJournalStatus(acc.statuses, acc.journalEntries);
|
|
98
|
+
results.push({
|
|
99
|
+
traceId: acc.traceId,
|
|
100
|
+
firstSeen: acc.firstSeen,
|
|
101
|
+
lastSeen: acc.lastSeen,
|
|
102
|
+
exchange: acc.exchange,
|
|
103
|
+
markets: [...acc.markets],
|
|
104
|
+
journalEntries: acc.journalEntries,
|
|
105
|
+
riskEvaluations: acc.riskEvaluations,
|
|
106
|
+
riskDenials: acc.riskDenials,
|
|
107
|
+
journalStatus,
|
|
108
|
+
durationMs: acc.lastSeen - acc.firstSeen,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
// Apply optional filters
|
|
112
|
+
if (options?.exchange) {
|
|
113
|
+
const ex = options.exchange;
|
|
114
|
+
results = results.filter((t) => t.exchange === ex);
|
|
115
|
+
}
|
|
116
|
+
if (options?.market) {
|
|
117
|
+
const mkt = options.market;
|
|
118
|
+
results = results.filter((t) => t.markets.includes(mkt));
|
|
119
|
+
}
|
|
120
|
+
if (options?.status) {
|
|
121
|
+
const st = options.status;
|
|
122
|
+
if (st === "denied") {
|
|
123
|
+
results = results.filter((t) => t.riskDenials > 0);
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
results = results.filter((t) => t.journalStatus === st);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
// Sort by lastSeen DESC
|
|
130
|
+
results.sort((a, b) => b.lastSeen - a.lastSeen);
|
|
131
|
+
// Limit
|
|
132
|
+
return results.slice(0, limit);
|
|
133
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function generateTraceId(): string;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
function parseCsvList(raw) {
|
|
3
|
+
if (!raw) {
|
|
4
|
+
return [];
|
|
5
|
+
}
|
|
6
|
+
return raw
|
|
7
|
+
.split(",")
|
|
8
|
+
.map((value) => value.trim())
|
|
9
|
+
.filter((value) => value.length > 0);
|
|
10
|
+
}
|
|
11
|
+
function asStringArray(value) {
|
|
12
|
+
if (!Array.isArray(value)) {
|
|
13
|
+
return [];
|
|
14
|
+
}
|
|
15
|
+
return value.filter((item) => typeof item === "string");
|
|
16
|
+
}
|
|
17
|
+
function compilePatterns(values, source) {
|
|
18
|
+
const patterns = [];
|
|
19
|
+
for (const value of values) {
|
|
20
|
+
try {
|
|
21
|
+
patterns.push(new RegExp(value, "i"));
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
throw new Error(`Invalid market pattern in ${source}: ${value}`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return patterns;
|
|
28
|
+
}
|
|
29
|
+
function normalizeExchange(value) {
|
|
30
|
+
return value.trim().toLowerCase();
|
|
31
|
+
}
|
|
32
|
+
function normalizeMarket(value) {
|
|
33
|
+
return value.trim().toUpperCase();
|
|
34
|
+
}
|
|
35
|
+
function loadBlocklistFromEnv() {
|
|
36
|
+
const exchanges = parseCsvList(process.env.PERPS_BLOCKED_EXCHANGES).map(normalizeExchange);
|
|
37
|
+
const markets = parseCsvList(process.env.PERPS_BLOCKED_MARKETS).map(normalizeMarket);
|
|
38
|
+
const rawPatterns = parseCsvList(process.env.PERPS_BLOCKED_MARKET_PATTERNS);
|
|
39
|
+
const marketPatterns = compilePatterns(rawPatterns, "PERPS_BLOCKED_MARKET_PATTERNS");
|
|
40
|
+
return {
|
|
41
|
+
exchanges,
|
|
42
|
+
markets,
|
|
43
|
+
marketPatterns,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
function loadBlocklistFromFile(filePath) {
|
|
47
|
+
if (!existsSync(filePath)) {
|
|
48
|
+
throw new Error(`Reputation blocklist file not found: ${filePath}`);
|
|
49
|
+
}
|
|
50
|
+
let parsed;
|
|
51
|
+
try {
|
|
52
|
+
parsed = JSON.parse(readFileSync(filePath, "utf-8"));
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
throw new Error(`Unable to parse reputation blocklist file: ${filePath}`);
|
|
56
|
+
}
|
|
57
|
+
const exchanges = asStringArray(parsed.exchanges).map(normalizeExchange);
|
|
58
|
+
const markets = asStringArray(parsed.markets).map(normalizeMarket);
|
|
59
|
+
const rawPatterns = asStringArray(parsed.marketPatterns);
|
|
60
|
+
const marketPatterns = compilePatterns(rawPatterns, `blocklist file (${filePath})`);
|
|
61
|
+
return {
|
|
62
|
+
exchanges,
|
|
63
|
+
markets,
|
|
64
|
+
marketPatterns,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
function dedupe(values) {
|
|
68
|
+
return [...new Set(values)];
|
|
69
|
+
}
|
|
70
|
+
function loadEffectiveBlocklist() {
|
|
71
|
+
const envBlocklist = loadBlocklistFromEnv();
|
|
72
|
+
const filePath = process.env.PERPS_REPUTATION_BLOCKLIST_FILE?.trim();
|
|
73
|
+
if (!filePath) {
|
|
74
|
+
return envBlocklist;
|
|
75
|
+
}
|
|
76
|
+
const fileBlocklist = loadBlocklistFromFile(filePath);
|
|
77
|
+
return {
|
|
78
|
+
exchanges: dedupe([...envBlocklist.exchanges, ...fileBlocklist.exchanges]),
|
|
79
|
+
markets: dedupe([...envBlocklist.markets, ...fileBlocklist.markets]),
|
|
80
|
+
marketPatterns: [...envBlocklist.marketPatterns, ...fileBlocklist.marketPatterns],
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
export function assertTradeAllowed(input) {
|
|
84
|
+
const exchange = normalizeExchange(input.exchangeId);
|
|
85
|
+
const market = normalizeMarket(input.market);
|
|
86
|
+
const blocklist = loadEffectiveBlocklist();
|
|
87
|
+
const contextPrefix = input.command ? `${input.command}: ` : "";
|
|
88
|
+
if (blocklist.exchanges.includes(exchange)) {
|
|
89
|
+
throw new Error(`${contextPrefix}trade blocked by reputation policy: exchange "${exchange}" is blocked`);
|
|
90
|
+
}
|
|
91
|
+
if (blocklist.markets.includes(market)) {
|
|
92
|
+
throw new Error(`${contextPrefix}trade blocked by reputation policy: market "${market}" is blocked`);
|
|
93
|
+
}
|
|
94
|
+
for (const pattern of blocklist.marketPatterns) {
|
|
95
|
+
if (pattern.test(market)) {
|
|
96
|
+
throw new Error(`${contextPrefix}trade blocked by reputation policy: market "${market}" matched blocked pattern ${pattern.source}`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UI tokens for lib/ modules
|
|
3
|
+
*
|
|
4
|
+
* Re-exports highlighter and inquirerTheme so that lib/ code
|
|
5
|
+
* never reaches into cli/ directly.
|
|
6
|
+
*/
|
|
7
|
+
export { highlighter } from "../cli/experience.js";
|
|
8
|
+
export declare const inquirerTheme: {
|
|
9
|
+
prefix: {
|
|
10
|
+
idle: string;
|
|
11
|
+
done: string;
|
|
12
|
+
};
|
|
13
|
+
style: {
|
|
14
|
+
answer: (text: string) => string;
|
|
15
|
+
message: (text: string) => string;
|
|
16
|
+
error: (text: string) => string;
|
|
17
|
+
highlight: (text: string) => string;
|
|
18
|
+
description: (text: string) => string;
|
|
19
|
+
help: (text: string) => string;
|
|
20
|
+
};
|
|
21
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UI tokens for lib/ modules
|
|
3
|
+
*
|
|
4
|
+
* Re-exports highlighter and inquirerTheme so that lib/ code
|
|
5
|
+
* never reaches into cli/ directly.
|
|
6
|
+
*/
|
|
7
|
+
export { highlighter } from "../cli/experience.js";
|
|
8
|
+
/**
|
|
9
|
+
* Theme configuration for @inquirer/prompts
|
|
10
|
+
* Uses highlighter so NO_COLOR is respected in prompts.
|
|
11
|
+
*/
|
|
12
|
+
import { highlighter } from "../cli/experience.js";
|
|
13
|
+
export const inquirerTheme = {
|
|
14
|
+
prefix: {
|
|
15
|
+
idle: highlighter.info("?"),
|
|
16
|
+
done: highlighter.success("✔"),
|
|
17
|
+
},
|
|
18
|
+
style: {
|
|
19
|
+
answer: highlighter.info,
|
|
20
|
+
message: highlighter.bold,
|
|
21
|
+
error: highlighter.error,
|
|
22
|
+
highlight: highlighter.info,
|
|
23
|
+
description: highlighter.dimWhite,
|
|
24
|
+
help: highlighter.dimWhite,
|
|
25
|
+
},
|
|
26
|
+
};
|