@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,199 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
const hexAddress = z
|
|
3
|
+
.string()
|
|
4
|
+
.regex(/^0x[a-fA-F0-9]{40}$/, "must be a 0x-prefixed 40-byte hex address");
|
|
5
|
+
const hexPrivateKey = z
|
|
6
|
+
.string()
|
|
7
|
+
.regex(/^0x[a-fA-F0-9]{64}$/, "must be a 0x-prefixed 32-byte hex private key");
|
|
8
|
+
const aptosAddress = z
|
|
9
|
+
.string()
|
|
10
|
+
.regex(/^0x[a-fA-F0-9]{1,64}$/, "must be a 0x-prefixed Aptos address");
|
|
11
|
+
const starknetFieldElement = z
|
|
12
|
+
.string()
|
|
13
|
+
.regex(/^0x[a-fA-F0-9]{1,64}$/, "must be a 0x-prefixed Starknet field element");
|
|
14
|
+
function isLoopbackHost(hostname) {
|
|
15
|
+
return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1";
|
|
16
|
+
}
|
|
17
|
+
function allowSecureProtocol(value, secureProtocol, localInsecureProtocol) {
|
|
18
|
+
const parsed = new URL(value);
|
|
19
|
+
if (parsed.protocol === secureProtocol) {
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
return parsed.protocol === localInsecureProtocol && isLoopbackHost(parsed.hostname);
|
|
23
|
+
}
|
|
24
|
+
const secureHttpUrl = z
|
|
25
|
+
.string()
|
|
26
|
+
.url()
|
|
27
|
+
.refine((value) => allowSecureProtocol(value, "https:", "http:"), {
|
|
28
|
+
message: "must use https:// (http:// allowed only for localhost)",
|
|
29
|
+
});
|
|
30
|
+
const secureWsUrl = z
|
|
31
|
+
.string()
|
|
32
|
+
.url()
|
|
33
|
+
.refine((value) => allowSecureProtocol(value, "wss:", "ws:"), {
|
|
34
|
+
message: "must use wss:// (ws:// allowed only for localhost)",
|
|
35
|
+
});
|
|
36
|
+
const EXCHANGE_URL_HOST_ALLOWLIST = {
|
|
37
|
+
DECIBEL_REST_URL: ["api.testnet.aptoslabs.com", "api.mainnet.aptoslabs.com"],
|
|
38
|
+
DECIBEL_WS_URL: ["api.testnet.aptoslabs.com", "api.mainnet.aptoslabs.com"],
|
|
39
|
+
DECIBEL_FULLNODE_URL: ["api.testnet.aptoslabs.com", "api.mainnet.aptoslabs.com"],
|
|
40
|
+
AEVO_REST_URL: ["api-testnet.aevo.xyz", "api.aevo.xyz"],
|
|
41
|
+
ORDERLY_REST_URL: ["testnet-api.orderly.org", "api-evm.orderly.org"],
|
|
42
|
+
PARADEX_REST_URL: ["api.testnet.paradex.trade", "api.prod.paradex.trade"],
|
|
43
|
+
};
|
|
44
|
+
function isTrustedExchangeHost(key, value) {
|
|
45
|
+
const hostname = new URL(value).hostname.toLowerCase();
|
|
46
|
+
if (isLoopbackHost(hostname)) {
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
const trustedHosts = EXCHANGE_URL_HOST_ALLOWLIST[key];
|
|
50
|
+
return trustedHosts.includes(hostname);
|
|
51
|
+
}
|
|
52
|
+
export const envSchema = z
|
|
53
|
+
.object({
|
|
54
|
+
// Hyperliquid credentials
|
|
55
|
+
HYPERLIQUID_PRIVATE_KEY: hexPrivateKey.optional(),
|
|
56
|
+
HYPERLIQUID_WALLET_ADDRESS: hexAddress.optional(),
|
|
57
|
+
HYPERLIQUID_NETWORK: z.enum(["testnet", "mainnet"]).optional(),
|
|
58
|
+
// Decibel credentials/settings
|
|
59
|
+
DECIBEL_API_WALLET_PRIVATE_KEY: hexPrivateKey.optional(),
|
|
60
|
+
DECIBEL_API_WALLET_ADDRESS: aptosAddress.optional(),
|
|
61
|
+
DECIBEL_API_BEARER_TOKEN: z.string().min(1).optional(),
|
|
62
|
+
DECIBEL_SUBACCOUNT_ADDRESS: aptosAddress.optional(),
|
|
63
|
+
DECIBEL_PACKAGE_ADDRESS: aptosAddress.optional(),
|
|
64
|
+
DECIBEL_REST_URL: secureHttpUrl.optional(),
|
|
65
|
+
DECIBEL_WS_URL: secureWsUrl.optional(),
|
|
66
|
+
DECIBEL_FULLNODE_URL: secureHttpUrl.optional(),
|
|
67
|
+
DECIBEL_NETWORK: z.enum(["testnet", "mainnet"]).optional(),
|
|
68
|
+
// Aevo credentials
|
|
69
|
+
AEVO_API_KEY: z.string().min(1).optional(),
|
|
70
|
+
AEVO_API_SECRET: z.string().min(1).optional(),
|
|
71
|
+
AEVO_SIGNING_KEY: hexPrivateKey.optional(),
|
|
72
|
+
AEVO_ACCOUNT_PRIVATE_KEY: hexPrivateKey.optional(),
|
|
73
|
+
AEVO_REST_URL: secureHttpUrl.optional(),
|
|
74
|
+
AEVO_NETWORK: z.enum(["testnet", "mainnet"]).optional(),
|
|
75
|
+
// Orderly credentials
|
|
76
|
+
ORDERLY_BROKER_ID: z.string().min(1).optional(),
|
|
77
|
+
ORDERLY_CHAIN_ID: z.string().min(1).optional(),
|
|
78
|
+
ORDERLY_ACCOUNT_ID: z.string().min(1).optional(),
|
|
79
|
+
ORDERLY_KEY: z.string().min(1).optional(),
|
|
80
|
+
ORDERLY_SECRET: z.string().min(1).optional(),
|
|
81
|
+
ORDERLY_TRADING_KEY: z.string().min(1).optional(),
|
|
82
|
+
ORDERLY_TRADING_SECRET: z.string().min(1).optional(),
|
|
83
|
+
ORDERLY_REST_URL: secureHttpUrl.optional(),
|
|
84
|
+
ORDERLY_NETWORK: z.enum(["testnet", "mainnet"]).optional(),
|
|
85
|
+
// Paradex credentials
|
|
86
|
+
PARADEX_API_BEARER_TOKEN: z.string().min(1).optional(),
|
|
87
|
+
PARADEX_ACCOUNT_ADDRESS: starknetFieldElement.optional(),
|
|
88
|
+
PARADEX_PRIVATE_KEY: starknetFieldElement.optional(),
|
|
89
|
+
PARADEX_ETHEREUM_ACCOUNT: hexAddress.optional(),
|
|
90
|
+
PARADEX_CHAIN_ID: z.string().min(1).optional(),
|
|
91
|
+
PARADEX_REST_URL: secureHttpUrl.optional(),
|
|
92
|
+
PARADEX_NETWORK: z.enum(["testnet", "mainnet"]).optional(),
|
|
93
|
+
// Risk policy
|
|
94
|
+
RISK_MAX_POSITION_SIZE_USD: z.coerce.number().positive().optional().default(1_000),
|
|
95
|
+
RISK_MAX_TOTAL_EXPOSURE_USD: z.coerce.number().positive().optional().default(3_000),
|
|
96
|
+
RISK_MAX_LEVERAGE: z.coerce.number().positive().optional().default(3),
|
|
97
|
+
RISK_MIN_SIGNAL_CONFIDENCE: z.coerce.number().min(0).max(1).optional().default(0.3),
|
|
98
|
+
RISK_MAX_DRAWDOWN_PCT: z.coerce.number().positive().max(100).optional().default(10),
|
|
99
|
+
RISK_DEFAULT_LEVERAGE: z.coerce.number().positive().optional().default(3),
|
|
100
|
+
// Execution safety
|
|
101
|
+
EXECUTION_STOP_LOSS_PCT: z.coerce.number().positive().optional().default(2),
|
|
102
|
+
EXECUTION_TAKE_PROFIT_PCT: z.coerce.number().positive().optional().default(4),
|
|
103
|
+
EXECUTION_SPREAD_OFFSET_PCT: z.coerce.number().min(0).max(100).optional().default(30),
|
|
104
|
+
// Operator heartbeat
|
|
105
|
+
OPERATOR_HEARTBEAT_INTERVAL_MS: z.coerce.number().int().positive().optional().default(60_000),
|
|
106
|
+
// Endpoint safety
|
|
107
|
+
PERPS_ALLOW_UNTRUSTED_ENDPOINTS: z.enum(["0", "1", "true", "false"]).optional(),
|
|
108
|
+
PERPS_BLOCKED_EXCHANGES: z.string().optional(),
|
|
109
|
+
PERPS_BLOCKED_MARKETS: z.string().optional(),
|
|
110
|
+
PERPS_BLOCKED_MARKET_PATTERNS: z.string().optional(),
|
|
111
|
+
PERPS_REPUTATION_BLOCKLIST_FILE: z.string().min(1).optional(),
|
|
112
|
+
})
|
|
113
|
+
.superRefine((env, ctx) => {
|
|
114
|
+
if (env.DECIBEL_API_WALLET_PRIVATE_KEY && !env.DECIBEL_API_WALLET_ADDRESS) {
|
|
115
|
+
ctx.addIssue({
|
|
116
|
+
code: z.ZodIssueCode.custom,
|
|
117
|
+
path: ["DECIBEL_API_WALLET_ADDRESS"],
|
|
118
|
+
message: "required when DECIBEL_API_WALLET_PRIVATE_KEY is set",
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
if (env.DECIBEL_API_BEARER_TOKEN && !env.DECIBEL_API_WALLET_ADDRESS) {
|
|
122
|
+
ctx.addIssue({
|
|
123
|
+
code: z.ZodIssueCode.custom,
|
|
124
|
+
path: ["DECIBEL_API_WALLET_ADDRESS"],
|
|
125
|
+
message: "required when DECIBEL_API_BEARER_TOKEN is set",
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
const aevoCredentialValues = [env.AEVO_API_KEY, env.AEVO_API_SECRET];
|
|
129
|
+
const aevoCredentialCount = aevoCredentialValues.filter(Boolean).length;
|
|
130
|
+
if (aevoCredentialCount === 1) {
|
|
131
|
+
if (!env.AEVO_API_KEY) {
|
|
132
|
+
ctx.addIssue({
|
|
133
|
+
code: z.ZodIssueCode.custom,
|
|
134
|
+
path: ["AEVO_API_KEY"],
|
|
135
|
+
message: "required when AEVO_API_SECRET is set",
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
if (!env.AEVO_API_SECRET) {
|
|
139
|
+
ctx.addIssue({
|
|
140
|
+
code: z.ZodIssueCode.custom,
|
|
141
|
+
path: ["AEVO_API_SECRET"],
|
|
142
|
+
message: "required when AEVO_API_KEY is set",
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
const orderlyCoreValues = [env.ORDERLY_ACCOUNT_ID, env.ORDERLY_KEY, env.ORDERLY_SECRET];
|
|
147
|
+
const orderlyCoreCount = orderlyCoreValues.filter(Boolean).length;
|
|
148
|
+
if (orderlyCoreCount > 0 && orderlyCoreCount < orderlyCoreValues.length) {
|
|
149
|
+
if (!env.ORDERLY_ACCOUNT_ID) {
|
|
150
|
+
ctx.addIssue({
|
|
151
|
+
code: z.ZodIssueCode.custom,
|
|
152
|
+
path: ["ORDERLY_ACCOUNT_ID"],
|
|
153
|
+
message: "required with ORDERLY_KEY and ORDERLY_SECRET",
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
if (!env.ORDERLY_KEY) {
|
|
157
|
+
ctx.addIssue({
|
|
158
|
+
code: z.ZodIssueCode.custom,
|
|
159
|
+
path: ["ORDERLY_KEY"],
|
|
160
|
+
message: "required with ORDERLY_ACCOUNT_ID and ORDERLY_SECRET",
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
if (!env.ORDERLY_SECRET) {
|
|
164
|
+
ctx.addIssue({
|
|
165
|
+
code: z.ZodIssueCode.custom,
|
|
166
|
+
path: ["ORDERLY_SECRET"],
|
|
167
|
+
message: "required with ORDERLY_ACCOUNT_ID and ORDERLY_KEY",
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
const allowUntrustedEndpoints = env.PERPS_ALLOW_UNTRUSTED_ENDPOINTS === "1" ||
|
|
172
|
+
env.PERPS_ALLOW_UNTRUSTED_ENDPOINTS === "true";
|
|
173
|
+
if (!allowUntrustedEndpoints) {
|
|
174
|
+
for (const key of Object.keys(EXCHANGE_URL_HOST_ALLOWLIST)) {
|
|
175
|
+
const value = env[key];
|
|
176
|
+
if (!value) {
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
if (!isTrustedExchangeHost(key, value)) {
|
|
180
|
+
ctx.addIssue({
|
|
181
|
+
code: z.ZodIssueCode.custom,
|
|
182
|
+
path: [key],
|
|
183
|
+
message: "host is not in the trusted exchange list (set PERPS_ALLOW_UNTRUSTED_ENDPOINTS=1 to override intentionally)",
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
export function parseEnv(env) {
|
|
190
|
+
const result = envSchema.safeParse(env);
|
|
191
|
+
if (!result.success) {
|
|
192
|
+
const lines = result.error.issues.map((issue) => {
|
|
193
|
+
const key = issue.path.length > 0 ? issue.path.join(".") : "env";
|
|
194
|
+
return `${key}: ${issue.message}`;
|
|
195
|
+
});
|
|
196
|
+
throw new Error(`Invalid environment configuration:\n${lines.join("\n")}`);
|
|
197
|
+
}
|
|
198
|
+
return result.data;
|
|
199
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { createCipheriv, createDecipheriv, randomBytes, } from "node:crypto";
|
|
2
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { dirname } from "node:path";
|
|
4
|
+
import { MASTER_KEY_PATH } from "./paths.js";
|
|
5
|
+
import { ensurePrivateDir, hardenPrivateFile, PRIVATE_FILE_MODE } from "./fs-security.js";
|
|
6
|
+
const ENCRYPTION_PREFIX = "enc:v1:";
|
|
7
|
+
const KEY_SIZE_BYTES = 32;
|
|
8
|
+
const IV_SIZE_BYTES = 12;
|
|
9
|
+
const AUTH_TAG_BYTES = 16;
|
|
10
|
+
let cachedKey = null;
|
|
11
|
+
function parseStoredKey(raw) {
|
|
12
|
+
const decoded = Buffer.from(raw.trim(), "base64");
|
|
13
|
+
if (decoded.length !== KEY_SIZE_BYTES) {
|
|
14
|
+
throw new Error("Invalid secret key length");
|
|
15
|
+
}
|
|
16
|
+
return decoded;
|
|
17
|
+
}
|
|
18
|
+
function getOrCreateMasterKey() {
|
|
19
|
+
if (cachedKey) {
|
|
20
|
+
return cachedKey;
|
|
21
|
+
}
|
|
22
|
+
if (existsSync(MASTER_KEY_PATH)) {
|
|
23
|
+
hardenPrivateFile(MASTER_KEY_PATH);
|
|
24
|
+
const raw = readFileSync(MASTER_KEY_PATH, "utf-8");
|
|
25
|
+
cachedKey = parseStoredKey(raw);
|
|
26
|
+
return cachedKey;
|
|
27
|
+
}
|
|
28
|
+
ensurePrivateDir(dirname(MASTER_KEY_PATH));
|
|
29
|
+
const key = randomBytes(KEY_SIZE_BYTES);
|
|
30
|
+
writeFileSync(MASTER_KEY_PATH, key.toString("base64"), { mode: PRIVATE_FILE_MODE });
|
|
31
|
+
hardenPrivateFile(MASTER_KEY_PATH);
|
|
32
|
+
cachedKey = key;
|
|
33
|
+
return key;
|
|
34
|
+
}
|
|
35
|
+
export function isEncryptedSecret(value) {
|
|
36
|
+
return value.startsWith(ENCRYPTION_PREFIX);
|
|
37
|
+
}
|
|
38
|
+
export function encryptSecret(plaintext) {
|
|
39
|
+
const key = getOrCreateMasterKey();
|
|
40
|
+
const iv = randomBytes(IV_SIZE_BYTES);
|
|
41
|
+
const cipher = createCipheriv("aes-256-gcm", key, iv);
|
|
42
|
+
const encrypted = Buffer.concat([cipher.update(plaintext, "utf-8"), cipher.final()]);
|
|
43
|
+
const authTag = cipher.getAuthTag();
|
|
44
|
+
const payload = Buffer.concat([iv, authTag, encrypted]).toString("base64");
|
|
45
|
+
return `${ENCRYPTION_PREFIX}${payload}`;
|
|
46
|
+
}
|
|
47
|
+
export function decryptSecret(ciphertext) {
|
|
48
|
+
if (!isEncryptedSecret(ciphertext)) {
|
|
49
|
+
return ciphertext;
|
|
50
|
+
}
|
|
51
|
+
const payload = Buffer.from(ciphertext.slice(ENCRYPTION_PREFIX.length), "base64");
|
|
52
|
+
if (payload.length <= IV_SIZE_BYTES + AUTH_TAG_BYTES) {
|
|
53
|
+
throw new Error("Stored secret payload is invalid");
|
|
54
|
+
}
|
|
55
|
+
const iv = payload.subarray(0, IV_SIZE_BYTES);
|
|
56
|
+
const authTag = payload.subarray(IV_SIZE_BYTES, IV_SIZE_BYTES + AUTH_TAG_BYTES);
|
|
57
|
+
const encrypted = payload.subarray(IV_SIZE_BYTES + AUTH_TAG_BYTES);
|
|
58
|
+
const key = getOrCreateMasterKey();
|
|
59
|
+
const decipher = createDecipheriv("aes-256-gcm", key, iv);
|
|
60
|
+
decipher.setAuthTag(authTag);
|
|
61
|
+
return Buffer.concat([decipher.update(encrypted), decipher.final()]).toString("utf-8");
|
|
62
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Global settings management
|
|
3
|
+
* Stores user preferences in SQLite
|
|
4
|
+
*/
|
|
5
|
+
export interface Settings {
|
|
6
|
+
defaultExchange: string;
|
|
7
|
+
testnetByDefault: boolean;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Get a setting value
|
|
11
|
+
*/
|
|
12
|
+
export declare function getSetting<K extends keyof Settings>(key: K): Settings[K];
|
|
13
|
+
/**
|
|
14
|
+
* Set a setting value
|
|
15
|
+
*/
|
|
16
|
+
export declare function setSetting<K extends keyof Settings>(key: K, value: Settings[K]): void;
|
|
17
|
+
/**
|
|
18
|
+
* Get all settings
|
|
19
|
+
*/
|
|
20
|
+
export declare function getAllSettings(): Settings;
|
|
21
|
+
/**
|
|
22
|
+
* Reset all settings to defaults
|
|
23
|
+
*/
|
|
24
|
+
export declare function resetSettings(): void;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Global settings management
|
|
3
|
+
* Stores user preferences in SQLite
|
|
4
|
+
*/
|
|
5
|
+
import { getDb } from "./db/index.js";
|
|
6
|
+
const DEFAULT_SETTINGS = {
|
|
7
|
+
defaultExchange: "hyperliquid",
|
|
8
|
+
testnetByDefault: false,
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Ensure settings table exists
|
|
12
|
+
*/
|
|
13
|
+
function ensureSettingsTable() {
|
|
14
|
+
const db = getDb();
|
|
15
|
+
db.exec(`
|
|
16
|
+
CREATE TABLE IF NOT EXISTS settings (
|
|
17
|
+
key TEXT PRIMARY KEY,
|
|
18
|
+
value TEXT NOT NULL,
|
|
19
|
+
updated_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now'))
|
|
20
|
+
)
|
|
21
|
+
`);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Get a setting value
|
|
25
|
+
*/
|
|
26
|
+
export function getSetting(key) {
|
|
27
|
+
ensureSettingsTable();
|
|
28
|
+
const db = getDb();
|
|
29
|
+
const row = db.prepare("SELECT value FROM settings WHERE key = ?").get(key);
|
|
30
|
+
if (!row) {
|
|
31
|
+
return DEFAULT_SETTINGS[key];
|
|
32
|
+
}
|
|
33
|
+
// Parse based on expected type
|
|
34
|
+
const defaultValue = DEFAULT_SETTINGS[key];
|
|
35
|
+
if (typeof defaultValue === "boolean") {
|
|
36
|
+
return (row.value === "true");
|
|
37
|
+
}
|
|
38
|
+
return row.value;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Set a setting value
|
|
42
|
+
*/
|
|
43
|
+
export function setSetting(key, value) {
|
|
44
|
+
ensureSettingsTable();
|
|
45
|
+
const db = getDb();
|
|
46
|
+
const stringValue = String(value);
|
|
47
|
+
db.prepare(`
|
|
48
|
+
INSERT INTO settings (key, value, updated_at)
|
|
49
|
+
VALUES (?, ?, strftime('%s', 'now'))
|
|
50
|
+
ON CONFLICT(key) DO UPDATE SET
|
|
51
|
+
value = excluded.value,
|
|
52
|
+
updated_at = excluded.updated_at
|
|
53
|
+
`).run(key, stringValue);
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Get all settings
|
|
57
|
+
*/
|
|
58
|
+
export function getAllSettings() {
|
|
59
|
+
ensureSettingsTable();
|
|
60
|
+
const db = getDb();
|
|
61
|
+
const rows = db.prepare("SELECT key, value FROM settings").all();
|
|
62
|
+
const settings = { ...DEFAULT_SETTINGS };
|
|
63
|
+
for (const row of rows) {
|
|
64
|
+
if (row.key in settings) {
|
|
65
|
+
const key = row.key;
|
|
66
|
+
const defaultValue = DEFAULT_SETTINGS[key];
|
|
67
|
+
if (typeof defaultValue === "boolean") {
|
|
68
|
+
;
|
|
69
|
+
settings[key] = row.value === "true";
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
;
|
|
73
|
+
settings[key] = row.value;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return settings;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Reset all settings to defaults
|
|
81
|
+
*/
|
|
82
|
+
export function resetSettings() {
|
|
83
|
+
ensureSettingsTable();
|
|
84
|
+
const db = getDb();
|
|
85
|
+
db.prepare("DELETE FROM settings").run();
|
|
86
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
export type SignalValue = "up" | "down";
|
|
2
|
+
export interface TradeSignal {
|
|
3
|
+
id: number;
|
|
4
|
+
traceId: string | null;
|
|
5
|
+
exchange: string;
|
|
6
|
+
market: string | null;
|
|
7
|
+
strategy: string | null;
|
|
8
|
+
signal: SignalValue;
|
|
9
|
+
reason: string | null;
|
|
10
|
+
pnlUsd: number | null;
|
|
11
|
+
metadata: Record<string, unknown> | null;
|
|
12
|
+
createdAt: number;
|
|
13
|
+
}
|
|
14
|
+
export interface AddSignalInput {
|
|
15
|
+
traceId?: string;
|
|
16
|
+
exchange: string;
|
|
17
|
+
market?: string;
|
|
18
|
+
strategy?: string;
|
|
19
|
+
signal: SignalValue;
|
|
20
|
+
reason?: string;
|
|
21
|
+
pnlUsd?: number;
|
|
22
|
+
metadata?: Record<string, unknown>;
|
|
23
|
+
}
|
|
24
|
+
export interface SignalQuery {
|
|
25
|
+
exchange?: string;
|
|
26
|
+
market?: string;
|
|
27
|
+
strategy?: string;
|
|
28
|
+
signal?: SignalValue;
|
|
29
|
+
minutes?: number;
|
|
30
|
+
limit?: number;
|
|
31
|
+
}
|
|
32
|
+
export interface SignalSummary {
|
|
33
|
+
total: number;
|
|
34
|
+
up: number;
|
|
35
|
+
down: number;
|
|
36
|
+
winRate: number;
|
|
37
|
+
totalPnlUsd: number;
|
|
38
|
+
avgPnlUsd: number;
|
|
39
|
+
byStrategy: Array<{
|
|
40
|
+
strategy: string;
|
|
41
|
+
total: number;
|
|
42
|
+
up: number;
|
|
43
|
+
down: number;
|
|
44
|
+
winRate: number;
|
|
45
|
+
totalPnlUsd: number;
|
|
46
|
+
}>;
|
|
47
|
+
byMarket: Array<{
|
|
48
|
+
market: string;
|
|
49
|
+
total: number;
|
|
50
|
+
up: number;
|
|
51
|
+
down: number;
|
|
52
|
+
winRate: number;
|
|
53
|
+
totalPnlUsd: number;
|
|
54
|
+
}>;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Add a trade signal to the database.
|
|
58
|
+
* Returns the inserted row id.
|
|
59
|
+
*/
|
|
60
|
+
export declare function addSignal(input: AddSignalInput): number;
|
|
61
|
+
/**
|
|
62
|
+
* Query trade signals with optional filters.
|
|
63
|
+
* Returns signals ordered by created_at DESC.
|
|
64
|
+
*/
|
|
65
|
+
export declare function querySignals(query?: SignalQuery): TradeSignal[];
|
|
66
|
+
/**
|
|
67
|
+
* Get aggregated signal summary statistics.
|
|
68
|
+
*/
|
|
69
|
+
export declare function getSignalSummary(options?: {
|
|
70
|
+
minutes?: number;
|
|
71
|
+
exchange?: string;
|
|
72
|
+
strategy?: string;
|
|
73
|
+
}): SignalSummary;
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { getDb } from "./db/index.js";
|
|
2
|
+
function rowToSignal(row) {
|
|
3
|
+
return {
|
|
4
|
+
id: row.id,
|
|
5
|
+
traceId: row.trace_id,
|
|
6
|
+
exchange: row.exchange,
|
|
7
|
+
market: row.market,
|
|
8
|
+
strategy: row.strategy,
|
|
9
|
+
signal: row.signal,
|
|
10
|
+
reason: row.reason,
|
|
11
|
+
pnlUsd: row.pnl_usd,
|
|
12
|
+
metadata: row.metadata ? JSON.parse(row.metadata) : null,
|
|
13
|
+
createdAt: row.created_at,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Add a trade signal to the database.
|
|
18
|
+
* Returns the inserted row id.
|
|
19
|
+
*/
|
|
20
|
+
export function addSignal(input) {
|
|
21
|
+
const db = getDb();
|
|
22
|
+
const result = db
|
|
23
|
+
.prepare(`INSERT INTO trade_signals (trace_id, exchange, market, strategy, signal, reason, pnl_usd, metadata)
|
|
24
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
25
|
+
.run(input.traceId ?? null, input.exchange, input.market ?? null, input.strategy ?? null, input.signal, input.reason ?? null, input.pnlUsd ?? null, input.metadata ? JSON.stringify(input.metadata) : null);
|
|
26
|
+
return Number(result.lastInsertRowid);
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Query trade signals with optional filters.
|
|
30
|
+
* Returns signals ordered by created_at DESC.
|
|
31
|
+
*/
|
|
32
|
+
export function querySignals(query) {
|
|
33
|
+
const db = getDb();
|
|
34
|
+
const conditions = [];
|
|
35
|
+
const params = [];
|
|
36
|
+
if (query?.exchange) {
|
|
37
|
+
conditions.push("exchange = ?");
|
|
38
|
+
params.push(query.exchange);
|
|
39
|
+
}
|
|
40
|
+
if (query?.market) {
|
|
41
|
+
conditions.push("market = ?");
|
|
42
|
+
params.push(query.market);
|
|
43
|
+
}
|
|
44
|
+
if (query?.strategy) {
|
|
45
|
+
conditions.push("strategy = ?");
|
|
46
|
+
params.push(query.strategy);
|
|
47
|
+
}
|
|
48
|
+
if (query?.signal) {
|
|
49
|
+
conditions.push("signal = ?");
|
|
50
|
+
params.push(query.signal);
|
|
51
|
+
}
|
|
52
|
+
if (query?.minutes) {
|
|
53
|
+
const cutoff = Date.now() - query.minutes * 60 * 1000;
|
|
54
|
+
conditions.push("created_at > ?");
|
|
55
|
+
params.push(cutoff);
|
|
56
|
+
}
|
|
57
|
+
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
58
|
+
const limit = query?.limit ?? 100;
|
|
59
|
+
const rows = db
|
|
60
|
+
.prepare(`SELECT * FROM trade_signals ${where} ORDER BY created_at DESC LIMIT ?`)
|
|
61
|
+
.all(...params, limit);
|
|
62
|
+
return rows.map(rowToSignal);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Get aggregated signal summary statistics.
|
|
66
|
+
*/
|
|
67
|
+
export function getSignalSummary(options) {
|
|
68
|
+
const db = getDb();
|
|
69
|
+
const minutes = options?.minutes ?? 1440;
|
|
70
|
+
const cutoff = Date.now() - minutes * 60 * 1000;
|
|
71
|
+
const overallConditions = ["created_at > ?"];
|
|
72
|
+
const overallParams = [cutoff];
|
|
73
|
+
if (options?.exchange) {
|
|
74
|
+
overallConditions.push("exchange = ?");
|
|
75
|
+
overallParams.push(options.exchange);
|
|
76
|
+
}
|
|
77
|
+
if (options?.strategy) {
|
|
78
|
+
overallConditions.push("strategy = ?");
|
|
79
|
+
overallParams.push(options.strategy);
|
|
80
|
+
}
|
|
81
|
+
const where = overallConditions.join(" AND ");
|
|
82
|
+
// Overall stats
|
|
83
|
+
const overall = db
|
|
84
|
+
.prepare(`SELECT COUNT(*) as total,
|
|
85
|
+
COALESCE(SUM(CASE WHEN signal = 'up' THEN 1 ELSE 0 END), 0) as up,
|
|
86
|
+
COALESCE(SUM(CASE WHEN signal = 'down' THEN 1 ELSE 0 END), 0) as down,
|
|
87
|
+
COALESCE(SUM(pnl_usd), 0) as total_pnl,
|
|
88
|
+
COALESCE(AVG(pnl_usd), 0) as avg_pnl
|
|
89
|
+
FROM trade_signals
|
|
90
|
+
WHERE ${where}`)
|
|
91
|
+
.get(...overallParams);
|
|
92
|
+
// By strategy
|
|
93
|
+
const byStrategyRows = db
|
|
94
|
+
.prepare(`SELECT strategy, COUNT(*) as total,
|
|
95
|
+
SUM(CASE WHEN signal = 'up' THEN 1 ELSE 0 END) as up,
|
|
96
|
+
SUM(CASE WHEN signal = 'down' THEN 1 ELSE 0 END) as down,
|
|
97
|
+
COALESCE(SUM(pnl_usd), 0) as total_pnl
|
|
98
|
+
FROM trade_signals
|
|
99
|
+
WHERE strategy IS NOT NULL AND ${where}
|
|
100
|
+
GROUP BY strategy`)
|
|
101
|
+
.all(...overallParams);
|
|
102
|
+
// By market
|
|
103
|
+
const byMarketRows = db
|
|
104
|
+
.prepare(`SELECT market, COUNT(*) as total,
|
|
105
|
+
SUM(CASE WHEN signal = 'up' THEN 1 ELSE 0 END) as up,
|
|
106
|
+
SUM(CASE WHEN signal = 'down' THEN 1 ELSE 0 END) as down,
|
|
107
|
+
COALESCE(SUM(pnl_usd), 0) as total_pnl
|
|
108
|
+
FROM trade_signals
|
|
109
|
+
WHERE market IS NOT NULL AND ${where}
|
|
110
|
+
GROUP BY market`)
|
|
111
|
+
.all(...overallParams);
|
|
112
|
+
return {
|
|
113
|
+
total: overall.total,
|
|
114
|
+
up: overall.up,
|
|
115
|
+
down: overall.down,
|
|
116
|
+
winRate: overall.total > 0 ? overall.up / overall.total : 0,
|
|
117
|
+
totalPnlUsd: overall.total_pnl,
|
|
118
|
+
avgPnlUsd: overall.avg_pnl,
|
|
119
|
+
byStrategy: byStrategyRows.map((r) => ({
|
|
120
|
+
strategy: r.strategy,
|
|
121
|
+
total: r.total,
|
|
122
|
+
up: r.up,
|
|
123
|
+
down: r.down,
|
|
124
|
+
winRate: r.total > 0 ? r.up / r.total : 0,
|
|
125
|
+
totalPnlUsd: r.total_pnl,
|
|
126
|
+
})),
|
|
127
|
+
byMarket: byMarketRows.map((r) => ({
|
|
128
|
+
market: r.market,
|
|
129
|
+
total: r.total,
|
|
130
|
+
up: r.up,
|
|
131
|
+
down: r.down,
|
|
132
|
+
winRate: r.total > 0 ? r.up / r.total : 0,
|
|
133
|
+
totalPnlUsd: r.total_pnl,
|
|
134
|
+
})),
|
|
135
|
+
};
|
|
136
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic JSON serialization with sorted keys.
|
|
3
|
+
* Used by both the execution journal (idempotency hashing) and
|
|
4
|
+
* the agent signature module — must remain identical in both contexts.
|
|
5
|
+
*/
|
|
6
|
+
export function stableStringify(value) {
|
|
7
|
+
if (value === null || typeof value !== "object") {
|
|
8
|
+
const primitive = JSON.stringify(value);
|
|
9
|
+
return primitive === undefined ? "null" : primitive;
|
|
10
|
+
}
|
|
11
|
+
if (Array.isArray(value)) {
|
|
12
|
+
return `[${value.map((item) => stableStringify(item)).join(",")}]`;
|
|
13
|
+
}
|
|
14
|
+
const obj = value;
|
|
15
|
+
const keys = Object.keys(obj).sort((a, b) => a.localeCompare(b));
|
|
16
|
+
return `{${keys.map((key) => `${JSON.stringify(key)}:${stableStringify(obj[key])}`).join(",")}}`;
|
|
17
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { PerpDEXAdapter, Order } from "../adapters/interface.js";
|
|
2
|
+
import type { Config } from "./config.js";
|
|
3
|
+
export interface PositionContext {
|
|
4
|
+
market: string;
|
|
5
|
+
side: "long" | "short";
|
|
6
|
+
size: number;
|
|
7
|
+
entryPrice: number;
|
|
8
|
+
markPrice: number;
|
|
9
|
+
notionalUsd: number;
|
|
10
|
+
unrealizedPnl: number;
|
|
11
|
+
realizedPnl: number;
|
|
12
|
+
leverage: number;
|
|
13
|
+
liquidationPrice: number | null;
|
|
14
|
+
marginType: string;
|
|
15
|
+
}
|
|
16
|
+
export interface StateContext {
|
|
17
|
+
exchange: string;
|
|
18
|
+
timestamp: number;
|
|
19
|
+
equity: number;
|
|
20
|
+
availableBalance: number;
|
|
21
|
+
marginUsed: number;
|
|
22
|
+
marginAvailable: number;
|
|
23
|
+
totalExposureUsd: number;
|
|
24
|
+
netUnrealizedPnl: number;
|
|
25
|
+
netRealizedPnl: number;
|
|
26
|
+
leverageUsed: number;
|
|
27
|
+
positionCount: number;
|
|
28
|
+
positions: PositionContext[];
|
|
29
|
+
openOrderCount: number;
|
|
30
|
+
openOrders: Order[];
|
|
31
|
+
halted: boolean;
|
|
32
|
+
haltReason: string;
|
|
33
|
+
drawdownFromPeakPct: number;
|
|
34
|
+
peakEquity: number;
|
|
35
|
+
riskLimits: {
|
|
36
|
+
maxPositionSizeUsd: number;
|
|
37
|
+
maxTotalExposureUsd: number;
|
|
38
|
+
maxLeverage: number;
|
|
39
|
+
minSignalConfidence: number;
|
|
40
|
+
maxDrawdownPct: number;
|
|
41
|
+
};
|
|
42
|
+
errors: string[];
|
|
43
|
+
}
|
|
44
|
+
export declare function buildStateContext(adapter: PerpDEXAdapter, exchangeId: string, config?: Config): Promise<StateContext>;
|