@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,17 @@
|
|
|
1
|
+
export type Side = "buy" | "sell";
|
|
2
|
+
export type NormalizedSide = "long" | "short";
|
|
3
|
+
export declare function normalizeMarket(symbol: string): string;
|
|
4
|
+
export declare function normalizeSide(value: string): NormalizedSide;
|
|
5
|
+
export declare function validateSideWithAliases(value: string): Side;
|
|
6
|
+
export declare function getAssetIndex(publicClient: {
|
|
7
|
+
allPerpMetas: () => Promise<Array<{
|
|
8
|
+
universe: Array<{
|
|
9
|
+
name: string;
|
|
10
|
+
}>;
|
|
11
|
+
}>>;
|
|
12
|
+
spotMeta: () => Promise<{
|
|
13
|
+
universe: Array<{
|
|
14
|
+
name: string;
|
|
15
|
+
}>;
|
|
16
|
+
}>;
|
|
17
|
+
}, coin: string): Promise<number>;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
export function normalizeMarket(symbol) {
|
|
2
|
+
let market = symbol.toUpperCase();
|
|
3
|
+
if (!market.includes("-")) {
|
|
4
|
+
market = `${market}-PERP`;
|
|
5
|
+
}
|
|
6
|
+
return market;
|
|
7
|
+
}
|
|
8
|
+
export function normalizeSide(value) {
|
|
9
|
+
const side = value.trim().toLowerCase();
|
|
10
|
+
if (!["buy", "long", "sell", "short"].includes(side)) {
|
|
11
|
+
throw new Error('Side must be: buy, long, sell, or short');
|
|
12
|
+
}
|
|
13
|
+
return side === "buy" || side === "long" ? "long" : "short";
|
|
14
|
+
}
|
|
15
|
+
export function validateSideWithAliases(value) {
|
|
16
|
+
const lower = value.toLowerCase();
|
|
17
|
+
if (lower === "long" || lower === "buy") {
|
|
18
|
+
return "buy";
|
|
19
|
+
}
|
|
20
|
+
if (lower === "short" || lower === "sell") {
|
|
21
|
+
return "sell";
|
|
22
|
+
}
|
|
23
|
+
throw new Error('Side must be "buy", "sell", "long", or "short"');
|
|
24
|
+
}
|
|
25
|
+
export async function getAssetIndex(publicClient, coin) {
|
|
26
|
+
// Fetch all perp metas (includes main dex at index 0 and builder dexes at index 1+)
|
|
27
|
+
const allPerpMetas = await publicClient.allPerpMetas();
|
|
28
|
+
// Check all perp dexes (case-sensitive matching)
|
|
29
|
+
for (let dexIndex = 0; dexIndex < allPerpMetas.length; dexIndex++) {
|
|
30
|
+
const dex = allPerpMetas[dexIndex];
|
|
31
|
+
const marketIndex = dex.universe.findIndex((a) => a.name === coin);
|
|
32
|
+
if (marketIndex !== -1) {
|
|
33
|
+
if (dexIndex === 0) {
|
|
34
|
+
// Main perp: just the index
|
|
35
|
+
return marketIndex;
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
// Builder-deployed perp: 100000 + perp_dex_index * 10000 + index_in_meta
|
|
39
|
+
return 100000 + dexIndex * 10000 + marketIndex;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
// Then check spot markets (case-sensitive matching)
|
|
44
|
+
const spotMeta = await publicClient.spotMeta();
|
|
45
|
+
const spotIndex = spotMeta.universe.findIndex((a) => a.name === coin);
|
|
46
|
+
if (spotIndex !== -1) {
|
|
47
|
+
// Spot assets use 10000 + index per Hyperliquid API docs
|
|
48
|
+
return 10000 + spotIndex;
|
|
49
|
+
}
|
|
50
|
+
throw new Error(`Unknown coin: ${coin}`);
|
|
51
|
+
}
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Trigger order commands using the adapter interface.
|
|
3
|
+
*/
|
|
4
|
+
import { getContext, getOutputOptions, getSelectedExchange } from "../../cli/program.js";
|
|
5
|
+
import { output, outputError, outputSuccess } from "../../cli/output.js";
|
|
6
|
+
import { getExchangeAdapter } from "../../lib/exchange.js";
|
|
7
|
+
import { getExchangeCredentials } from "../../lib/config.js";
|
|
8
|
+
import { RiskPolicyMiddleware } from "../../lib/risk/policy-middleware.js";
|
|
9
|
+
import { runWithExecutionJournal } from "../../lib/execution/journal.js";
|
|
10
|
+
import { withJsonContract } from "../../lib/contracts.js";
|
|
11
|
+
import { inferExitCode } from "../../lib/exit-codes.js";
|
|
12
|
+
import { assertTradeAllowed } from "../../lib/trade-reputation.js";
|
|
13
|
+
import { normalizeMarket, normalizeSide } from "./shared.js";
|
|
14
|
+
function parsePositive(value, field) {
|
|
15
|
+
const parsed = Number.parseFloat(value);
|
|
16
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
17
|
+
throw new Error(`${field} must be a positive number`);
|
|
18
|
+
}
|
|
19
|
+
return parsed;
|
|
20
|
+
}
|
|
21
|
+
function assertCapability(adapter, orderType) {
|
|
22
|
+
if (orderType === "take_profit" && !adapter.info.features.takeProfitOrders) {
|
|
23
|
+
throw new Error(`${adapter.info.name} does not support take-profit orders`);
|
|
24
|
+
}
|
|
25
|
+
if ((orderType === "stop" || orderType === "stop_limit") && !adapter.info.features.stopOrders) {
|
|
26
|
+
throw new Error(`${adapter.info.name} does not support stop orders`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
async function runTriggerOrder(args) {
|
|
30
|
+
const { command, commandName, orderType, side, symbol, size, triggerPrice, price, allowPostOnly } = args;
|
|
31
|
+
const ctx = getContext(command);
|
|
32
|
+
const outputOpts = getOutputOptions(command);
|
|
33
|
+
const exchangeId = getSelectedExchange(command);
|
|
34
|
+
const opts = command.opts();
|
|
35
|
+
try {
|
|
36
|
+
const market = normalizeMarket(symbol);
|
|
37
|
+
const normalizedSide = normalizeSide(side);
|
|
38
|
+
const requestedSize = parsePositive(size, "Size");
|
|
39
|
+
const trigger = parsePositive(triggerPrice, "Trigger price");
|
|
40
|
+
const limitPrice = price !== undefined ? parsePositive(price, "Price") : undefined;
|
|
41
|
+
const confidence = opts.confidence ? Number.parseFloat(opts.confidence) : 1;
|
|
42
|
+
if (!Number.isFinite(confidence) || confidence < 0 || confidence > 1) {
|
|
43
|
+
throw new Error("Confidence must be a number between 0 and 1");
|
|
44
|
+
}
|
|
45
|
+
if (opts.postOnly && !allowPostOnly) {
|
|
46
|
+
throw new Error(`${commandName} does not support --post-only`);
|
|
47
|
+
}
|
|
48
|
+
const adapter = getExchangeAdapter();
|
|
49
|
+
const credentials = getExchangeCredentials(ctx.config, exchangeId, {
|
|
50
|
+
requireTrading: true,
|
|
51
|
+
});
|
|
52
|
+
await adapter.connect({
|
|
53
|
+
testnet: ctx.config.testnet,
|
|
54
|
+
rpcUrl: credentials.fullnodeUrl,
|
|
55
|
+
wsUrl: credentials.wsUrl,
|
|
56
|
+
credentials,
|
|
57
|
+
});
|
|
58
|
+
try {
|
|
59
|
+
assertCapability(adapter, orderType);
|
|
60
|
+
const referencePrice = limitPrice ?? trigger;
|
|
61
|
+
const riskPolicy = new RiskPolicyMiddleware(ctx.config, exchangeId);
|
|
62
|
+
const riskDecision = await riskPolicy.evaluateOrder(adapter, {
|
|
63
|
+
market,
|
|
64
|
+
side: normalizedSide,
|
|
65
|
+
requestedSizeBase: requestedSize,
|
|
66
|
+
referencePriceUsd: referencePrice,
|
|
67
|
+
confidence,
|
|
68
|
+
reason: `cli:order:${orderType}`,
|
|
69
|
+
});
|
|
70
|
+
if (!riskDecision.allowed || riskDecision.adjustedSizeBase <= 0) {
|
|
71
|
+
throw new Error(riskDecision.reason ?? "Order blocked by risk policy");
|
|
72
|
+
}
|
|
73
|
+
assertTradeAllowed({
|
|
74
|
+
exchangeId,
|
|
75
|
+
market,
|
|
76
|
+
command: `order.${commandName}`,
|
|
77
|
+
});
|
|
78
|
+
const finalSize = riskDecision.adjustedSizeBase;
|
|
79
|
+
const journaled = await runWithExecutionJournal({
|
|
80
|
+
idempotencyKey: opts.idempotencyKey,
|
|
81
|
+
metadata: {
|
|
82
|
+
command: `order.${commandName}`,
|
|
83
|
+
exchange: exchangeId,
|
|
84
|
+
testnet: ctx.config.testnet,
|
|
85
|
+
market,
|
|
86
|
+
side: normalizedSide,
|
|
87
|
+
orderType,
|
|
88
|
+
},
|
|
89
|
+
request: {
|
|
90
|
+
market,
|
|
91
|
+
side: normalizedSide,
|
|
92
|
+
orderType,
|
|
93
|
+
requestedSize: requestedSize.toFixed(8),
|
|
94
|
+
triggerPrice: trigger.toFixed(8),
|
|
95
|
+
price: limitPrice?.toFixed(8) ?? null,
|
|
96
|
+
reduceOnly: opts.reduceOnly ?? false,
|
|
97
|
+
postOnly: opts.postOnly ?? false,
|
|
98
|
+
confidence,
|
|
99
|
+
},
|
|
100
|
+
execute: async () => {
|
|
101
|
+
const order = await adapter.placeOrder({
|
|
102
|
+
market,
|
|
103
|
+
side: normalizedSide,
|
|
104
|
+
type: orderType,
|
|
105
|
+
size: finalSize.toFixed(8),
|
|
106
|
+
triggerPrice: trigger.toFixed(8),
|
|
107
|
+
price: limitPrice?.toFixed(8),
|
|
108
|
+
reduceOnly: opts.reduceOnly,
|
|
109
|
+
postOnly: opts.postOnly,
|
|
110
|
+
});
|
|
111
|
+
return {
|
|
112
|
+
order,
|
|
113
|
+
risk: {
|
|
114
|
+
requestedSize,
|
|
115
|
+
adjustedSize: finalSize,
|
|
116
|
+
adjustedNotionalUsd: riskDecision.adjustedSizeUsd,
|
|
117
|
+
referencePrice,
|
|
118
|
+
},
|
|
119
|
+
trigger: {
|
|
120
|
+
triggerPrice: trigger,
|
|
121
|
+
limitPrice: limitPrice ?? null,
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
},
|
|
125
|
+
});
|
|
126
|
+
const response = {
|
|
127
|
+
...journaled.result.order,
|
|
128
|
+
risk: journaled.result.risk,
|
|
129
|
+
trigger: journaled.result.trigger,
|
|
130
|
+
idempotency: {
|
|
131
|
+
key: journaled.idempotencyKey,
|
|
132
|
+
replayed: journaled.replayed,
|
|
133
|
+
autoGenerated: journaled.autoGeneratedKey,
|
|
134
|
+
journalId: journaled.journalId,
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
if (outputOpts.json) {
|
|
138
|
+
output(withJsonContract("order.execution.result", {
|
|
139
|
+
executionStatus: journaled.replayed ? "replayed" : "executed",
|
|
140
|
+
...response,
|
|
141
|
+
}), outputOpts);
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
outputSuccess(journaled.replayed
|
|
145
|
+
? "Order replayed from idempotency journal!"
|
|
146
|
+
: `${commandName} order placed!`);
|
|
147
|
+
console.log(` Order ID: ${response.id}`);
|
|
148
|
+
console.log(` Market: ${response.market}`);
|
|
149
|
+
console.log(` Side: ${response.side.toUpperCase()}`);
|
|
150
|
+
console.log(` Type: ${response.type}`);
|
|
151
|
+
console.log(` Size: ${response.size}`);
|
|
152
|
+
console.log(` Trigger: ${response.trigger.triggerPrice}`);
|
|
153
|
+
if (response.trigger.limitPrice !== null) {
|
|
154
|
+
console.log(` Limit: ${response.trigger.limitPrice}`);
|
|
155
|
+
}
|
|
156
|
+
console.log(` Status: ${response.status}`);
|
|
157
|
+
if (Math.abs(response.risk.adjustedSize - response.risk.requestedSize) > 1e-12) {
|
|
158
|
+
console.log(` Risk Size: adjusted from ${response.risk.requestedSize} to ${response.risk.adjustedSize.toFixed(8)}`);
|
|
159
|
+
}
|
|
160
|
+
console.log(` Idempotency: ${response.idempotency.key}` +
|
|
161
|
+
(response.idempotency.replayed ? " (replayed)" : ""));
|
|
162
|
+
console.log("");
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
finally {
|
|
166
|
+
await adapter.disconnect();
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
catch (err) {
|
|
170
|
+
const code = inferExitCode(err);
|
|
171
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
172
|
+
if (outputOpts.json) {
|
|
173
|
+
output(withJsonContract("order.execution.result", {
|
|
174
|
+
status: "error",
|
|
175
|
+
error: {
|
|
176
|
+
message,
|
|
177
|
+
exitCode: code,
|
|
178
|
+
},
|
|
179
|
+
}), outputOpts);
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
outputError(message);
|
|
183
|
+
}
|
|
184
|
+
process.exit(code);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
export function registerTriggerOrderSimpleCommands(order) {
|
|
188
|
+
order
|
|
189
|
+
.command("stop <side> <symbol> <size> <triggerPrice>")
|
|
190
|
+
.description("Place a stop-market order (side: buy/long or sell/short)")
|
|
191
|
+
.option("--reduce-only", "Reduce-only order")
|
|
192
|
+
.option("--confidence <0-1>", "Signal confidence for risk policy (default: 1.0)")
|
|
193
|
+
.option("--idempotency-key <key>", "Idempotency key for safe retries")
|
|
194
|
+
.action(async function (side, symbol, size, triggerPrice) {
|
|
195
|
+
await runTriggerOrder({
|
|
196
|
+
command: this,
|
|
197
|
+
commandName: "stop",
|
|
198
|
+
orderType: "stop",
|
|
199
|
+
side,
|
|
200
|
+
symbol,
|
|
201
|
+
size,
|
|
202
|
+
triggerPrice,
|
|
203
|
+
allowPostOnly: false,
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
order
|
|
207
|
+
.command("stop-limit <side> <symbol> <size> <triggerPrice> <price>")
|
|
208
|
+
.description("Place a stop-limit order (side: buy/long or sell/short)")
|
|
209
|
+
.option("--reduce-only", "Reduce-only order")
|
|
210
|
+
.option("--post-only", "Post-only order")
|
|
211
|
+
.option("--confidence <0-1>", "Signal confidence for risk policy (default: 1.0)")
|
|
212
|
+
.option("--idempotency-key <key>", "Idempotency key for safe retries")
|
|
213
|
+
.action(async function (side, symbol, size, triggerPrice, price) {
|
|
214
|
+
await runTriggerOrder({
|
|
215
|
+
command: this,
|
|
216
|
+
commandName: "stop-limit",
|
|
217
|
+
orderType: "stop_limit",
|
|
218
|
+
side,
|
|
219
|
+
symbol,
|
|
220
|
+
size,
|
|
221
|
+
triggerPrice,
|
|
222
|
+
price,
|
|
223
|
+
allowPostOnly: true,
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
order
|
|
227
|
+
.command("take-profit <side> <symbol> <size> <triggerPrice> [price]")
|
|
228
|
+
.description("Place a take-profit order (side: buy/long or sell/short)")
|
|
229
|
+
.option("--reduce-only", "Reduce-only order")
|
|
230
|
+
.option("--post-only", "Post-only order")
|
|
231
|
+
.option("--confidence <0-1>", "Signal confidence for risk policy (default: 1.0)")
|
|
232
|
+
.option("--idempotency-key <key>", "Idempotency key for safe retries")
|
|
233
|
+
.action(async function (side, symbol, size, triggerPrice, price) {
|
|
234
|
+
await runTriggerOrder({
|
|
235
|
+
command: this,
|
|
236
|
+
commandName: "take-profit",
|
|
237
|
+
orderType: "take_profit",
|
|
238
|
+
side,
|
|
239
|
+
symbol,
|
|
240
|
+
size,
|
|
241
|
+
triggerPrice,
|
|
242
|
+
price,
|
|
243
|
+
allowPostOnly: true,
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { registerSetCommand } from "./set.js";
|
|
2
|
+
import { registerStatusCommand } from "./status.js";
|
|
3
|
+
export function registerReferralCommands(program) {
|
|
4
|
+
const referral = program.command("referral").description("Referral management");
|
|
5
|
+
registerSetCommand(referral);
|
|
6
|
+
registerStatusCommand(referral);
|
|
7
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { getContext, getOutputOptions } from "../../cli/program.js";
|
|
2
|
+
import { output, outputError, outputSuccess } from "../../cli/output.js";
|
|
3
|
+
export function registerSetCommand(referral) {
|
|
4
|
+
referral
|
|
5
|
+
.command("set")
|
|
6
|
+
.description("Set referral code (link to a referrer)")
|
|
7
|
+
.argument("<code>", "Referral code")
|
|
8
|
+
.action(async function (code) {
|
|
9
|
+
const ctx = getContext(this);
|
|
10
|
+
const outputOpts = getOutputOptions(this);
|
|
11
|
+
try {
|
|
12
|
+
const client = ctx.getWalletClient();
|
|
13
|
+
const result = await client.setReferrer({ code });
|
|
14
|
+
if (outputOpts.json) {
|
|
15
|
+
output(result, outputOpts);
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
outputSuccess(`Referral code set: ${code}`);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
catch (err) {
|
|
22
|
+
outputError(err instanceof Error ? err.message : String(err));
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { getContext, getOutputOptions } from "../../cli/program.js";
|
|
2
|
+
import { output, outputError } from "../../cli/output.js";
|
|
3
|
+
export function registerStatusCommand(referral) {
|
|
4
|
+
referral
|
|
5
|
+
.command("status")
|
|
6
|
+
.description("Get referral status")
|
|
7
|
+
.action(async function () {
|
|
8
|
+
const ctx = getContext(this);
|
|
9
|
+
const outputOpts = getOutputOptions(this);
|
|
10
|
+
try {
|
|
11
|
+
const client = ctx.getPublicClient();
|
|
12
|
+
const user = ctx.getWalletAddress();
|
|
13
|
+
const result = await client.referral({ user });
|
|
14
|
+
if (outputOpts.json) {
|
|
15
|
+
output(result, outputOpts);
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
if (!result) {
|
|
19
|
+
console.log("No referral information found");
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
output(result, outputOpts);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
catch (err) {
|
|
27
|
+
outputError(err instanceof Error ? err.message : String(err));
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { getDb } from "../../lib/db/index.js";
|
|
3
|
+
import { AGENT_AUDIT_LOG_PATH } from "../../lib/paths.js";
|
|
4
|
+
import { queryRiskEvaluations } from "../../lib/risk/evaluation-log.js";
|
|
5
|
+
import { withJsonContract } from "../../lib/contracts.js";
|
|
6
|
+
import { output } from "../../cli/output.js";
|
|
7
|
+
import { getOutputOptions } from "../../cli/program.js";
|
|
8
|
+
function readAuditEventsForTrace(traceId) {
|
|
9
|
+
if (!existsSync(AGENT_AUDIT_LOG_PATH))
|
|
10
|
+
return [];
|
|
11
|
+
const lines = readFileSync(AGENT_AUDIT_LOG_PATH, "utf-8")
|
|
12
|
+
.split("\n")
|
|
13
|
+
.filter(Boolean);
|
|
14
|
+
return lines
|
|
15
|
+
.map((line) => {
|
|
16
|
+
try {
|
|
17
|
+
return JSON.parse(line);
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
})
|
|
23
|
+
.filter((event) => event?.traceId === traceId);
|
|
24
|
+
}
|
|
25
|
+
function formatCurrency(usd) {
|
|
26
|
+
return `$${usd.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
|
|
27
|
+
}
|
|
28
|
+
function formatHumanTimeline(traceId, events) {
|
|
29
|
+
const lines = [`Trace: ${traceId}`, ""];
|
|
30
|
+
for (let i = 0; i < events.length; i++) {
|
|
31
|
+
const event = events[i];
|
|
32
|
+
const ts = new Date(event.timestamp).toISOString();
|
|
33
|
+
const num = i + 1;
|
|
34
|
+
if (event.type === "risk_evaluation") {
|
|
35
|
+
const d = event.data;
|
|
36
|
+
lines.push(`${num}. [${ts}] Risk Evaluation`);
|
|
37
|
+
lines.push(` Exchange: ${d.exchange} | Market: ${d.market ?? "N/A"} | Side: ${d.side ?? "N/A"}`);
|
|
38
|
+
lines.push(` Requested: ${formatCurrency(d.requestedSizeUsd)} | Equity: ${formatCurrency(d.equity)} | Exposure: ${formatCurrency(d.exposureUsd)}`);
|
|
39
|
+
if (d.confidence !== undefined) {
|
|
40
|
+
lines.push(` Confidence: ${d.confidence}`);
|
|
41
|
+
}
|
|
42
|
+
const rulesEvaluated = d.rulesEvaluated;
|
|
43
|
+
const rulesFired = d.rulesFired;
|
|
44
|
+
lines.push(` Rules evaluated: ${rulesEvaluated.length > 0 ? rulesEvaluated.join(", ") : "(none)"}`);
|
|
45
|
+
lines.push(` Rules fired: ${rulesFired.length > 0 ? rulesFired.join(", ") : "(none)"}`);
|
|
46
|
+
const result = d.allowed ? "ALLOWED" : "DENIED";
|
|
47
|
+
const adjustedPart = d.adjustedSizeUsd !== undefined
|
|
48
|
+
? ` | Adjusted size: ${formatCurrency(d.adjustedSizeUsd)}`
|
|
49
|
+
: "";
|
|
50
|
+
lines.push(` Result: ${result}${adjustedPart}`);
|
|
51
|
+
}
|
|
52
|
+
else if (event.type === "journal_entry") {
|
|
53
|
+
const d = event.data;
|
|
54
|
+
lines.push(`${num}. [${ts}] Execution Journal`);
|
|
55
|
+
lines.push(` Key: ${d.idempotencyKey} | Command: ${d.command}`);
|
|
56
|
+
lines.push(` Exchange: ${d.exchange} | Market: ${d.market ?? "N/A"}`);
|
|
57
|
+
lines.push(` Status: ${d.status}`);
|
|
58
|
+
}
|
|
59
|
+
else if (event.type === "audit_event") {
|
|
60
|
+
const d = event.data;
|
|
61
|
+
lines.push(`${num}. [${ts}] Audit Event`);
|
|
62
|
+
lines.push(` Action: ${d.action ?? "unknown"} | Status: ${d.status ?? "unknown"}`);
|
|
63
|
+
lines.push(` Exchange: ${d.exchange ?? "N/A"} | Market: ${d.market ?? "N/A"}`);
|
|
64
|
+
}
|
|
65
|
+
if (i < events.length - 1) {
|
|
66
|
+
lines.push("");
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return lines.join("\n");
|
|
70
|
+
}
|
|
71
|
+
export function registerReplayCommands(program) {
|
|
72
|
+
program
|
|
73
|
+
.command("replay <trace-id>")
|
|
74
|
+
.description("Reconstruct the full execution timeline for a trace ID")
|
|
75
|
+
.action(async (traceId, _opts, command) => {
|
|
76
|
+
const { json } = getOutputOptions(command);
|
|
77
|
+
const events = [];
|
|
78
|
+
// 1. Query risk evaluation log
|
|
79
|
+
const riskEvals = queryRiskEvaluations({ traceId });
|
|
80
|
+
for (const r of riskEvals) {
|
|
81
|
+
events.push({
|
|
82
|
+
type: "risk_evaluation",
|
|
83
|
+
timestamp: r.createdAt,
|
|
84
|
+
data: {
|
|
85
|
+
exchange: r.exchange,
|
|
86
|
+
market: r.market,
|
|
87
|
+
side: r.side,
|
|
88
|
+
requestedSizeUsd: r.requestedSizeUsd,
|
|
89
|
+
equity: r.equity,
|
|
90
|
+
exposureUsd: r.exposureUsd,
|
|
91
|
+
confidence: r.confidence,
|
|
92
|
+
rulesEvaluated: r.rulesEvaluated,
|
|
93
|
+
rulesFired: r.rulesFired,
|
|
94
|
+
allowed: r.allowed,
|
|
95
|
+
adjustedSizeUsd: r.adjustedSizeUsd,
|
|
96
|
+
reason: r.reason,
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
// 2. Query execution journal
|
|
101
|
+
const db = getDb();
|
|
102
|
+
const journalEntries = db
|
|
103
|
+
.prepare("SELECT * FROM execution_journal WHERE trace_id = ? ORDER BY created_at")
|
|
104
|
+
.all(traceId);
|
|
105
|
+
for (const j of journalEntries) {
|
|
106
|
+
events.push({
|
|
107
|
+
type: "journal_entry",
|
|
108
|
+
timestamp: j.created_at,
|
|
109
|
+
data: {
|
|
110
|
+
idempotencyKey: j.idempotency_key,
|
|
111
|
+
command: j.command,
|
|
112
|
+
exchange: j.exchange,
|
|
113
|
+
market: j.market,
|
|
114
|
+
side: j.side,
|
|
115
|
+
orderType: j.order_type,
|
|
116
|
+
status: j.status,
|
|
117
|
+
errorMessage: j.error_message,
|
|
118
|
+
},
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
// 3. Scan audit log
|
|
122
|
+
const auditEvents = readAuditEventsForTrace(traceId);
|
|
123
|
+
for (const a of auditEvents) {
|
|
124
|
+
events.push({
|
|
125
|
+
type: "audit_event",
|
|
126
|
+
timestamp: a.timestamp,
|
|
127
|
+
data: {
|
|
128
|
+
action: a.action,
|
|
129
|
+
status: a.status,
|
|
130
|
+
exchange: a.exchange,
|
|
131
|
+
market: a.market,
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
// Sort chronologically
|
|
136
|
+
events.sort((a, b) => a.timestamp - b.timestamp);
|
|
137
|
+
if (events.length === 0) {
|
|
138
|
+
output(json
|
|
139
|
+
? withJsonContract("replay.timeline", { traceId, events: [] })
|
|
140
|
+
: `No events found for trace ID: ${traceId}`, { json });
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
if (json) {
|
|
144
|
+
output(withJsonContract("replay.timeline", { traceId, events }), {
|
|
145
|
+
json,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
output(formatHumanTimeline(traceId, events), { json });
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { getOutputOptions } from "../../cli/program.js";
|
|
2
|
+
import { output } from "../../cli/output.js";
|
|
3
|
+
import { withJsonContract } from "../../lib/contracts.js";
|
|
4
|
+
import { getDenialsByReason, getEvalWindows, detectAnomalies, } from "../../lib/risk/analytics.js";
|
|
5
|
+
export function registerRiskAnalyticsCommands(parent) {
|
|
6
|
+
parent
|
|
7
|
+
.command("patterns")
|
|
8
|
+
.description("Show denial patterns from risk evaluations")
|
|
9
|
+
.option("--minutes <n>", "Time window in minutes", Number.parseInt, 60)
|
|
10
|
+
.action(function () {
|
|
11
|
+
const outputOpts = getOutputOptions(this);
|
|
12
|
+
const opts = this.opts();
|
|
13
|
+
const denials = getDenialsByReason(opts.minutes);
|
|
14
|
+
const windows = getEvalWindows(5, opts.minutes);
|
|
15
|
+
if (outputOpts.json) {
|
|
16
|
+
output(withJsonContract("risk.patterns", { denials, windows }), outputOpts);
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
if (denials.length === 0) {
|
|
20
|
+
console.log(`\nNo denials in the last ${opts.minutes} minutes.\n`);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
console.log(`\nDenial patterns (last ${opts.minutes} min):\n`);
|
|
24
|
+
for (const d of denials) {
|
|
25
|
+
const lastSeen = new Date(d.lastSeen).toISOString();
|
|
26
|
+
console.log(` ${d.reason}`);
|
|
27
|
+
console.log(` Count: ${d.count}`);
|
|
28
|
+
console.log(` Markets: ${d.markets.join(", ") || "(none)"}`);
|
|
29
|
+
console.log(` Last seen: ${lastSeen}`);
|
|
30
|
+
console.log("");
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
parent
|
|
34
|
+
.command("anomalies")
|
|
35
|
+
.description("Detect anomalies in recent risk evaluations")
|
|
36
|
+
.option("--threshold <0-1>", "Denial rate threshold", Number.parseFloat, 0.5)
|
|
37
|
+
.option("--window <minutes>", "Window size in minutes", Number.parseInt, 5)
|
|
38
|
+
.action(function () {
|
|
39
|
+
const outputOpts = getOutputOptions(this);
|
|
40
|
+
const opts = this.opts();
|
|
41
|
+
const alerts = detectAnomalies({
|
|
42
|
+
denialRateThreshold: opts.threshold,
|
|
43
|
+
windowMinutes: opts.window,
|
|
44
|
+
});
|
|
45
|
+
if (outputOpts.json) {
|
|
46
|
+
output(withJsonContract("risk.anomalies", {
|
|
47
|
+
alerts,
|
|
48
|
+
window: opts.window,
|
|
49
|
+
}), outputOpts);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
if (alerts.length === 0) {
|
|
53
|
+
console.log(`\nNo anomalies detected in the last ${opts.window} minutes.\n`);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
console.log(`\nAnomalies detected:\n`);
|
|
57
|
+
for (const a of alerts) {
|
|
58
|
+
const severity = a.severity === "critical" ? "CRITICAL" : "WARNING";
|
|
59
|
+
console.log(` [${severity}] ${a.type}`);
|
|
60
|
+
console.log(` ${a.message}`);
|
|
61
|
+
console.log("");
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
}
|