@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,109 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname } from "node:path";
|
|
3
|
+
import { CREDENTIAL_VAULT_PATH } from "./paths.js";
|
|
4
|
+
import { encryptSecret } from "./secrets.js";
|
|
5
|
+
import { ensurePrivateDir, hardenPrivateFile, PRIVATE_FILE_MODE } from "./fs-security.js";
|
|
6
|
+
const VAULT_SCHEMA_VERSION = 1;
|
|
7
|
+
function createVault() {
|
|
8
|
+
return {
|
|
9
|
+
version: VAULT_SCHEMA_VERSION,
|
|
10
|
+
updatedAt: new Date().toISOString(),
|
|
11
|
+
entries: [],
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
function readVault() {
|
|
15
|
+
if (!existsSync(CREDENTIAL_VAULT_PATH)) {
|
|
16
|
+
return createVault();
|
|
17
|
+
}
|
|
18
|
+
try {
|
|
19
|
+
hardenPrivateFile(CREDENTIAL_VAULT_PATH);
|
|
20
|
+
const raw = readFileSync(CREDENTIAL_VAULT_PATH, "utf-8");
|
|
21
|
+
const parsed = JSON.parse(raw);
|
|
22
|
+
return {
|
|
23
|
+
version: VAULT_SCHEMA_VERSION,
|
|
24
|
+
updatedAt: typeof parsed.updatedAt === "string" ? parsed.updatedAt : new Date().toISOString(),
|
|
25
|
+
entries: Array.isArray(parsed.entries) ? parsed.entries : [],
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return createVault();
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function writeVault(vault) {
|
|
33
|
+
ensurePrivateDir(dirname(CREDENTIAL_VAULT_PATH));
|
|
34
|
+
writeFileSync(CREDENTIAL_VAULT_PATH, `${JSON.stringify(vault, null, 2)}\n`, {
|
|
35
|
+
mode: PRIVATE_FILE_MODE,
|
|
36
|
+
});
|
|
37
|
+
hardenPrivateFile(CREDENTIAL_VAULT_PATH);
|
|
38
|
+
}
|
|
39
|
+
function profileToSecretMap(profile) {
|
|
40
|
+
const secrets = {};
|
|
41
|
+
if (profile.credentials.hyperliquid?.privateKey) {
|
|
42
|
+
secrets.HYPERLIQUID_PRIVATE_KEY = profile.credentials.hyperliquid.privateKey;
|
|
43
|
+
}
|
|
44
|
+
if (profile.credentials.decibel?.apiWalletPrivateKey) {
|
|
45
|
+
secrets.DECIBEL_API_WALLET_PRIVATE_KEY = profile.credentials.decibel.apiWalletPrivateKey;
|
|
46
|
+
}
|
|
47
|
+
if (profile.credentials.decibel?.apiBearerToken) {
|
|
48
|
+
secrets.DECIBEL_API_BEARER_TOKEN = profile.credentials.decibel.apiBearerToken;
|
|
49
|
+
}
|
|
50
|
+
if (profile.credentials.aevo?.signingKey) {
|
|
51
|
+
secrets.AEVO_SIGNING_KEY = profile.credentials.aevo.signingKey;
|
|
52
|
+
}
|
|
53
|
+
if (profile.credentials.aevo?.apiKey) {
|
|
54
|
+
secrets.AEVO_API_KEY = profile.credentials.aevo.apiKey;
|
|
55
|
+
}
|
|
56
|
+
if (profile.credentials.aevo?.apiSecret) {
|
|
57
|
+
secrets.AEVO_API_SECRET = profile.credentials.aevo.apiSecret;
|
|
58
|
+
}
|
|
59
|
+
if (profile.credentials.orderly?.tradingSecret) {
|
|
60
|
+
secrets.ORDERLY_TRADING_SECRET = profile.credentials.orderly.tradingSecret;
|
|
61
|
+
}
|
|
62
|
+
if (profile.credentials.orderly?.key) {
|
|
63
|
+
secrets.ORDERLY_KEY = profile.credentials.orderly.key;
|
|
64
|
+
}
|
|
65
|
+
if (profile.credentials.orderly?.secret) {
|
|
66
|
+
secrets.ORDERLY_SECRET = profile.credentials.orderly.secret;
|
|
67
|
+
}
|
|
68
|
+
if (profile.credentials.paradex?.privateKey) {
|
|
69
|
+
secrets.PARADEX_PRIVATE_KEY = profile.credentials.paradex.privateKey;
|
|
70
|
+
}
|
|
71
|
+
if (profile.credentials.paradex?.apiBearerToken) {
|
|
72
|
+
secrets.PARADEX_API_BEARER_TOKEN = profile.credentials.paradex.apiBearerToken;
|
|
73
|
+
}
|
|
74
|
+
return secrets;
|
|
75
|
+
}
|
|
76
|
+
function buildId(profile) {
|
|
77
|
+
const day = profile.generatedAt.slice(0, 10).replace(/-/g, "");
|
|
78
|
+
const suffix = Math.random().toString(36).slice(2, 8);
|
|
79
|
+
return `onb-${day}-${suffix}`;
|
|
80
|
+
}
|
|
81
|
+
export function saveOnboardingProfileToVault(profile) {
|
|
82
|
+
const vault = readVault();
|
|
83
|
+
const id = buildId(profile);
|
|
84
|
+
const plainSecrets = profileToSecretMap(profile);
|
|
85
|
+
const encrypted = {};
|
|
86
|
+
for (const [key, value] of Object.entries(plainSecrets)) {
|
|
87
|
+
encrypted[key] = encryptSecret(value);
|
|
88
|
+
}
|
|
89
|
+
const entry = {
|
|
90
|
+
id,
|
|
91
|
+
createdAt: profile.generatedAt,
|
|
92
|
+
mode: profile.mode,
|
|
93
|
+
exchanges: [...profile.exchanges],
|
|
94
|
+
wallets: { ...profile.wallets },
|
|
95
|
+
encrypted,
|
|
96
|
+
};
|
|
97
|
+
vault.entries.unshift(entry);
|
|
98
|
+
vault.updatedAt = new Date().toISOString();
|
|
99
|
+
writeVault(vault);
|
|
100
|
+
return { id, path: CREDENTIAL_VAULT_PATH };
|
|
101
|
+
}
|
|
102
|
+
export function getCredentialVaultSummary() {
|
|
103
|
+
const vault = readVault();
|
|
104
|
+
return {
|
|
105
|
+
path: CREDENTIAL_VAULT_PATH,
|
|
106
|
+
entries: vault.entries.length,
|
|
107
|
+
updatedAt: vault.updatedAt,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import type { Address, Hex } from "viem";
|
|
2
|
+
/**
|
|
3
|
+
* Account types
|
|
4
|
+
*/
|
|
5
|
+
export type AccountType = "readonly" | "api_wallet";
|
|
6
|
+
export type AccountSource = "cli_import";
|
|
7
|
+
/**
|
|
8
|
+
* Account interface
|
|
9
|
+
*/
|
|
10
|
+
export interface Account {
|
|
11
|
+
id: number;
|
|
12
|
+
alias: string;
|
|
13
|
+
exchange: string;
|
|
14
|
+
userAddress: Address;
|
|
15
|
+
type: AccountType;
|
|
16
|
+
source: AccountSource;
|
|
17
|
+
apiWalletPrivateKey: Hex | null;
|
|
18
|
+
apiWalletPublicKey: Address | null;
|
|
19
|
+
isDefault: boolean;
|
|
20
|
+
createdAt: number;
|
|
21
|
+
updatedAt: number;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Input for creating an account
|
|
25
|
+
*/
|
|
26
|
+
export interface CreateAccountInput {
|
|
27
|
+
alias: string;
|
|
28
|
+
exchange: string;
|
|
29
|
+
userAddress: Address;
|
|
30
|
+
type: AccountType;
|
|
31
|
+
source?: AccountSource;
|
|
32
|
+
apiWalletPrivateKey?: Hex;
|
|
33
|
+
apiWalletPublicKey?: Address;
|
|
34
|
+
setAsDefault?: boolean;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Create a new account
|
|
38
|
+
*/
|
|
39
|
+
export declare function createAccount(input: CreateAccountInput): Account;
|
|
40
|
+
/**
|
|
41
|
+
* Get an account by ID
|
|
42
|
+
*/
|
|
43
|
+
export declare function getAccountById(id: number): Account | null;
|
|
44
|
+
/**
|
|
45
|
+
* Get an account by alias
|
|
46
|
+
*/
|
|
47
|
+
export declare function getAccountByAlias(alias: string, exchange?: string): Account | null;
|
|
48
|
+
/**
|
|
49
|
+
* Get an account by alias scoped to a specific exchange
|
|
50
|
+
*/
|
|
51
|
+
export declare function getAccountByAliasForExchange(alias: string, exchange: string): Account | null;
|
|
52
|
+
/**
|
|
53
|
+
* Get the default account
|
|
54
|
+
*/
|
|
55
|
+
export declare function getDefaultAccount(): Account | null;
|
|
56
|
+
/**
|
|
57
|
+
* Get the default account for a specific exchange
|
|
58
|
+
*/
|
|
59
|
+
export declare function getDefaultAccountForExchange(exchange: string): Account | null;
|
|
60
|
+
/**
|
|
61
|
+
* Get all accounts
|
|
62
|
+
*/
|
|
63
|
+
export declare function getAllAccounts(): Account[];
|
|
64
|
+
/**
|
|
65
|
+
* Set an account as default by alias
|
|
66
|
+
*/
|
|
67
|
+
export declare function setDefaultAccount(alias: string, exchange?: string): Account;
|
|
68
|
+
/**
|
|
69
|
+
* Delete an account by alias
|
|
70
|
+
*/
|
|
71
|
+
export declare function deleteAccount(alias: string, exchange?: string): boolean;
|
|
72
|
+
/**
|
|
73
|
+
* Check if an alias is already taken
|
|
74
|
+
*/
|
|
75
|
+
export declare function isAliasTaken(alias: string, exchange?: string): boolean;
|
|
76
|
+
/**
|
|
77
|
+
* Update an account's API wallet credentials
|
|
78
|
+
*/
|
|
79
|
+
export declare function updateAccountApiWallet(alias: string, apiWalletPrivateKey: Hex, apiWalletPublicKey: Address, exchange?: string): Account;
|
|
80
|
+
/**
|
|
81
|
+
* Get account count
|
|
82
|
+
*/
|
|
83
|
+
export declare function getAccountCount(exchange?: string): number;
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { getDb } from "./index.js";
|
|
2
|
+
import { decryptSecret, encryptSecret, isEncryptedSecret } from "../secrets.js";
|
|
3
|
+
function decodePrivateKey(row) {
|
|
4
|
+
if (!row.api_wallet_private_key) {
|
|
5
|
+
return null;
|
|
6
|
+
}
|
|
7
|
+
if (isEncryptedSecret(row.api_wallet_private_key)) {
|
|
8
|
+
return decryptSecret(row.api_wallet_private_key);
|
|
9
|
+
}
|
|
10
|
+
// Legacy plaintext rows are migrated in-place on first read.
|
|
11
|
+
const encrypted = encryptSecret(row.api_wallet_private_key);
|
|
12
|
+
getDb()
|
|
13
|
+
.prepare("UPDATE accounts SET api_wallet_private_key = ?, updated_at = strftime('%s', 'now') WHERE id = ?")
|
|
14
|
+
.run(encrypted, row.id);
|
|
15
|
+
return row.api_wallet_private_key;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Convert database row to Account object
|
|
19
|
+
*/
|
|
20
|
+
function rowToAccount(row) {
|
|
21
|
+
return {
|
|
22
|
+
id: row.id,
|
|
23
|
+
alias: row.alias,
|
|
24
|
+
exchange: row.exchange,
|
|
25
|
+
userAddress: row.user_address,
|
|
26
|
+
type: row.type,
|
|
27
|
+
source: row.source,
|
|
28
|
+
apiWalletPrivateKey: decodePrivateKey(row),
|
|
29
|
+
apiWalletPublicKey: row.api_wallet_public_key,
|
|
30
|
+
isDefault: row.is_default === 1,
|
|
31
|
+
createdAt: row.created_at,
|
|
32
|
+
updatedAt: row.updated_at,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Create a new account
|
|
37
|
+
*/
|
|
38
|
+
export function createAccount(input) {
|
|
39
|
+
const db = getDb();
|
|
40
|
+
if (isAliasTaken(input.alias, input.exchange)) {
|
|
41
|
+
throw new Error(`Alias "${input.alias}" is already in use for exchange "${input.exchange}". Choose a different alias.`);
|
|
42
|
+
}
|
|
43
|
+
// If this is the first account or setAsDefault is true, make it default
|
|
44
|
+
const accountCount = db.prepare("SELECT COUNT(*) as count FROM accounts WHERE exchange = ?").get(input.exchange);
|
|
45
|
+
const shouldBeDefault = accountCount.count === 0 || input.setAsDefault;
|
|
46
|
+
// If setting as default for this exchange, unset current default first
|
|
47
|
+
if (shouldBeDefault) {
|
|
48
|
+
db.prepare("UPDATE accounts SET is_default = 0 WHERE is_default = 1 AND exchange = ?").run(input.exchange);
|
|
49
|
+
}
|
|
50
|
+
const result = db.prepare(`
|
|
51
|
+
INSERT INTO accounts (
|
|
52
|
+
alias,
|
|
53
|
+
exchange,
|
|
54
|
+
user_address,
|
|
55
|
+
type,
|
|
56
|
+
source,
|
|
57
|
+
api_wallet_private_key,
|
|
58
|
+
api_wallet_public_key,
|
|
59
|
+
is_default
|
|
60
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
61
|
+
`).run(input.alias, input.exchange, input.userAddress, input.type, input.source || "cli_import", input.apiWalletPrivateKey ? encryptSecret(input.apiWalletPrivateKey) : null, input.apiWalletPublicKey || null, shouldBeDefault ? 1 : 0);
|
|
62
|
+
return getAccountById(Number(result.lastInsertRowid));
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Get an account by ID
|
|
66
|
+
*/
|
|
67
|
+
export function getAccountById(id) {
|
|
68
|
+
const db = getDb();
|
|
69
|
+
const row = db.prepare("SELECT * FROM accounts WHERE id = ?").get(id);
|
|
70
|
+
return row ? rowToAccount(row) : null;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Get an account by alias
|
|
74
|
+
*/
|
|
75
|
+
export function getAccountByAlias(alias, exchange) {
|
|
76
|
+
const db = getDb();
|
|
77
|
+
if (exchange) {
|
|
78
|
+
const row = db
|
|
79
|
+
.prepare("SELECT * FROM accounts WHERE alias = ? AND exchange = ?")
|
|
80
|
+
.get(alias, exchange);
|
|
81
|
+
return row ? rowToAccount(row) : null;
|
|
82
|
+
}
|
|
83
|
+
const rows = db.prepare("SELECT * FROM accounts WHERE alias = ? ORDER BY created_at ASC").all(alias);
|
|
84
|
+
if (rows.length === 0) {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
if (rows.length > 1) {
|
|
88
|
+
throw new Error(`Multiple accounts found with alias "${alias}". Re-run with --exchange to disambiguate.`);
|
|
89
|
+
}
|
|
90
|
+
return rowToAccount(rows[0]);
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Get an account by alias scoped to a specific exchange
|
|
94
|
+
*/
|
|
95
|
+
export function getAccountByAliasForExchange(alias, exchange) {
|
|
96
|
+
return getAccountByAlias(alias, exchange);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Get the default account
|
|
100
|
+
*/
|
|
101
|
+
export function getDefaultAccount() {
|
|
102
|
+
const db = getDb();
|
|
103
|
+
const row = db.prepare("SELECT * FROM accounts WHERE is_default = 1").get();
|
|
104
|
+
return row ? rowToAccount(row) : null;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Get the default account for a specific exchange
|
|
108
|
+
*/
|
|
109
|
+
export function getDefaultAccountForExchange(exchange) {
|
|
110
|
+
const db = getDb();
|
|
111
|
+
const row = db
|
|
112
|
+
.prepare("SELECT * FROM accounts WHERE is_default = 1 AND exchange = ?")
|
|
113
|
+
.get(exchange);
|
|
114
|
+
return row ? rowToAccount(row) : null;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Get all accounts
|
|
118
|
+
*/
|
|
119
|
+
export function getAllAccounts() {
|
|
120
|
+
const db = getDb();
|
|
121
|
+
const rows = db.prepare("SELECT * FROM accounts ORDER BY is_default DESC, created_at ASC").all();
|
|
122
|
+
return rows.map(rowToAccount);
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Set an account as default by alias
|
|
126
|
+
*/
|
|
127
|
+
export function setDefaultAccount(alias, exchange) {
|
|
128
|
+
const db = getDb();
|
|
129
|
+
// Check if account exists
|
|
130
|
+
const account = getAccountByAlias(alias, exchange);
|
|
131
|
+
if (!account) {
|
|
132
|
+
const exchangeLabel = exchange ? ` for exchange "${exchange}"` : "";
|
|
133
|
+
throw new Error(`Account with alias "${alias}" not found${exchangeLabel}`);
|
|
134
|
+
}
|
|
135
|
+
// Unset current default for this exchange only
|
|
136
|
+
db.prepare("UPDATE accounts SET is_default = 0 WHERE is_default = 1 AND exchange = ?").run(account.exchange);
|
|
137
|
+
// Set new default
|
|
138
|
+
db.prepare("UPDATE accounts SET is_default = 1, updated_at = strftime('%s', 'now') WHERE id = ?").run(account.id);
|
|
139
|
+
return getAccountById(account.id);
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Delete an account by alias
|
|
143
|
+
*/
|
|
144
|
+
export function deleteAccount(alias, exchange) {
|
|
145
|
+
const db = getDb();
|
|
146
|
+
const account = getAccountByAlias(alias, exchange);
|
|
147
|
+
if (!account) {
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
const wasDefault = account.isDefault;
|
|
151
|
+
db.prepare("DELETE FROM accounts WHERE id = ?").run(account.id);
|
|
152
|
+
// If deleted account was default, set the first remaining account as default
|
|
153
|
+
if (wasDefault) {
|
|
154
|
+
const firstAccount = db
|
|
155
|
+
.prepare("SELECT * FROM accounts WHERE exchange = ? ORDER BY created_at ASC LIMIT 1")
|
|
156
|
+
.get(account.exchange);
|
|
157
|
+
if (firstAccount) {
|
|
158
|
+
db.prepare("UPDATE accounts SET is_default = 1 WHERE id = ?").run(firstAccount.id);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return true;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Check if an alias is already taken
|
|
165
|
+
*/
|
|
166
|
+
export function isAliasTaken(alias, exchange) {
|
|
167
|
+
const db = getDb();
|
|
168
|
+
const row = exchange
|
|
169
|
+
? db.prepare("SELECT 1 FROM accounts WHERE alias = ? AND exchange = ?").get(alias, exchange)
|
|
170
|
+
: db.prepare("SELECT 1 FROM accounts WHERE alias = ?").get(alias);
|
|
171
|
+
return row !== undefined;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Update an account's API wallet credentials
|
|
175
|
+
*/
|
|
176
|
+
export function updateAccountApiWallet(alias, apiWalletPrivateKey, apiWalletPublicKey, exchange) {
|
|
177
|
+
const db = getDb();
|
|
178
|
+
const account = getAccountByAlias(alias, exchange);
|
|
179
|
+
if (!account) {
|
|
180
|
+
const exchangeLabel = exchange ? ` for exchange "${exchange}"` : "";
|
|
181
|
+
throw new Error(`Account with alias "${alias}" not found${exchangeLabel}`);
|
|
182
|
+
}
|
|
183
|
+
db.prepare(`
|
|
184
|
+
UPDATE accounts
|
|
185
|
+
SET
|
|
186
|
+
api_wallet_private_key = ?,
|
|
187
|
+
api_wallet_public_key = ?,
|
|
188
|
+
type = 'api_wallet',
|
|
189
|
+
updated_at = strftime('%s', 'now')
|
|
190
|
+
WHERE id = ?
|
|
191
|
+
`).run(encryptSecret(apiWalletPrivateKey), apiWalletPublicKey, account.id);
|
|
192
|
+
return getAccountById(account.id);
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Get account count
|
|
196
|
+
*/
|
|
197
|
+
export function getAccountCount(exchange) {
|
|
198
|
+
const db = getDb();
|
|
199
|
+
const result = exchange
|
|
200
|
+
? db.prepare("SELECT COUNT(*) as count FROM accounts WHERE exchange = ?").get(exchange)
|
|
201
|
+
: db.prepare("SELECT COUNT(*) as count FROM accounts").get();
|
|
202
|
+
return result.count;
|
|
203
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Funding History Database Operations
|
|
3
|
+
* Store and retrieve funding rate history for arbitrage analysis
|
|
4
|
+
*/
|
|
5
|
+
export interface FundingRecord {
|
|
6
|
+
id?: number;
|
|
7
|
+
exchange: string;
|
|
8
|
+
market: string;
|
|
9
|
+
rate: number;
|
|
10
|
+
annualized: number;
|
|
11
|
+
nextFunding?: number;
|
|
12
|
+
recordedAt: number;
|
|
13
|
+
}
|
|
14
|
+
export interface FundingSpread {
|
|
15
|
+
market: string;
|
|
16
|
+
highExchange: string;
|
|
17
|
+
highRate: number;
|
|
18
|
+
lowExchange: string;
|
|
19
|
+
lowRate: number;
|
|
20
|
+
spread: number;
|
|
21
|
+
annualizedSpread: number;
|
|
22
|
+
recordedAt: number;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Store a funding rate record
|
|
26
|
+
*/
|
|
27
|
+
export declare function storeFundingRate(record: Omit<FundingRecord, "id">): number;
|
|
28
|
+
/**
|
|
29
|
+
* Store multiple funding rate records
|
|
30
|
+
*/
|
|
31
|
+
export declare function storeFundingRates(records: Omit<FundingRecord, "id">[]): void;
|
|
32
|
+
/**
|
|
33
|
+
* Get funding history for a specific market across all exchanges
|
|
34
|
+
*/
|
|
35
|
+
export declare function getFundingHistory(market: string, options?: {
|
|
36
|
+
hours?: number;
|
|
37
|
+
exchanges?: string[];
|
|
38
|
+
}): FundingRecord[];
|
|
39
|
+
/**
|
|
40
|
+
* Get latest funding rate for each exchange for a market
|
|
41
|
+
*/
|
|
42
|
+
export declare function getLatestFundingRates(market: string): FundingRecord[];
|
|
43
|
+
/**
|
|
44
|
+
* Calculate average funding spreads over time
|
|
45
|
+
*/
|
|
46
|
+
export declare function getFundingSpreads(market: string, hours?: number): FundingSpread[];
|
|
47
|
+
/**
|
|
48
|
+
* Get average spread statistics
|
|
49
|
+
*/
|
|
50
|
+
export declare function getSpreadStats(market: string, hours?: number): {
|
|
51
|
+
avgSpread: number;
|
|
52
|
+
avgAnnualized: number;
|
|
53
|
+
minSpread: number;
|
|
54
|
+
maxSpread: number;
|
|
55
|
+
consistency: number;
|
|
56
|
+
samples: number;
|
|
57
|
+
bestPair: {
|
|
58
|
+
high: string;
|
|
59
|
+
low: string;
|
|
60
|
+
} | null;
|
|
61
|
+
};
|
|
62
|
+
/**
|
|
63
|
+
* Clean up old records (keep last N days)
|
|
64
|
+
*/
|
|
65
|
+
export declare function cleanupOldRecords(daysToKeep?: number): number;
|
|
66
|
+
/**
|
|
67
|
+
* Get record count
|
|
68
|
+
*/
|
|
69
|
+
export declare function getRecordCount(): number;
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Funding History Database Operations
|
|
3
|
+
* Store and retrieve funding rate history for arbitrage analysis
|
|
4
|
+
*/
|
|
5
|
+
import { getDb } from "./index.js";
|
|
6
|
+
/**
|
|
7
|
+
* Store a funding rate record
|
|
8
|
+
*/
|
|
9
|
+
export function storeFundingRate(record) {
|
|
10
|
+
const db = getDb();
|
|
11
|
+
const stmt = db.prepare(`
|
|
12
|
+
INSERT INTO funding_history (exchange, market, rate, annualized, next_funding, recorded_at)
|
|
13
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
14
|
+
`);
|
|
15
|
+
const result = stmt.run(record.exchange, record.market, record.rate, record.annualized, record.nextFunding || null, record.recordedAt);
|
|
16
|
+
return result.lastInsertRowid;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Store multiple funding rate records
|
|
20
|
+
*/
|
|
21
|
+
export function storeFundingRates(records) {
|
|
22
|
+
const db = getDb();
|
|
23
|
+
const stmt = db.prepare(`
|
|
24
|
+
INSERT INTO funding_history (exchange, market, rate, annualized, next_funding, recorded_at)
|
|
25
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
26
|
+
`);
|
|
27
|
+
const insertMany = db.transaction((items) => {
|
|
28
|
+
for (const r of items) {
|
|
29
|
+
stmt.run(r.exchange, r.market, r.rate, r.annualized, r.nextFunding || null, r.recordedAt);
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
insertMany(records);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Get funding history for a specific market across all exchanges
|
|
36
|
+
*/
|
|
37
|
+
export function getFundingHistory(market, options = {}) {
|
|
38
|
+
const db = getDb();
|
|
39
|
+
const hours = options.hours || 24;
|
|
40
|
+
const cutoff = Date.now() - hours * 60 * 60 * 1000;
|
|
41
|
+
let sql = `
|
|
42
|
+
SELECT id, exchange, market, rate, annualized, next_funding as nextFunding, recorded_at as recordedAt
|
|
43
|
+
FROM funding_history
|
|
44
|
+
WHERE market = ? AND recorded_at >= ?
|
|
45
|
+
`;
|
|
46
|
+
const params = [market, cutoff];
|
|
47
|
+
if (options.exchanges && options.exchanges.length > 0) {
|
|
48
|
+
sql += ` AND exchange IN (${options.exchanges.map(() => "?").join(",")})`;
|
|
49
|
+
params.push(...options.exchanges);
|
|
50
|
+
}
|
|
51
|
+
sql += " ORDER BY recorded_at DESC";
|
|
52
|
+
return db.prepare(sql).all(...params);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Get latest funding rate for each exchange for a market
|
|
56
|
+
*/
|
|
57
|
+
export function getLatestFundingRates(market) {
|
|
58
|
+
const db = getDb();
|
|
59
|
+
const sql = `
|
|
60
|
+
SELECT f.id, f.exchange, f.market, f.rate, f.annualized,
|
|
61
|
+
f.next_funding as nextFunding, f.recorded_at as recordedAt
|
|
62
|
+
FROM funding_history f
|
|
63
|
+
INNER JOIN (
|
|
64
|
+
SELECT exchange, MAX(recorded_at) as max_time
|
|
65
|
+
FROM funding_history
|
|
66
|
+
WHERE market = ?
|
|
67
|
+
GROUP BY exchange
|
|
68
|
+
) latest ON f.exchange = latest.exchange AND f.recorded_at = latest.max_time
|
|
69
|
+
WHERE f.market = ?
|
|
70
|
+
ORDER BY f.rate DESC
|
|
71
|
+
`;
|
|
72
|
+
return db.prepare(sql).all(market, market);
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Calculate average funding spreads over time
|
|
76
|
+
*/
|
|
77
|
+
export function getFundingSpreads(market, hours = 24) {
|
|
78
|
+
const db = getDb();
|
|
79
|
+
const cutoff = Date.now() - hours * 60 * 60 * 1000;
|
|
80
|
+
// Get all records grouped by time bucket (hourly)
|
|
81
|
+
const sql = `
|
|
82
|
+
SELECT
|
|
83
|
+
exchange,
|
|
84
|
+
market,
|
|
85
|
+
rate,
|
|
86
|
+
annualized,
|
|
87
|
+
recorded_at as recordedAt,
|
|
88
|
+
(recorded_at / 3600000) * 3600000 as bucket -- Round to hour
|
|
89
|
+
FROM funding_history
|
|
90
|
+
WHERE market = ? AND recorded_at >= ?
|
|
91
|
+
ORDER BY bucket, rate DESC
|
|
92
|
+
`;
|
|
93
|
+
const records = db.prepare(sql).all(market, cutoff);
|
|
94
|
+
// Group by bucket and calculate spreads
|
|
95
|
+
const buckets = new Map();
|
|
96
|
+
for (const r of records) {
|
|
97
|
+
const existing = buckets.get(r.bucket) || [];
|
|
98
|
+
existing.push(r);
|
|
99
|
+
buckets.set(r.bucket, existing);
|
|
100
|
+
}
|
|
101
|
+
const spreads = [];
|
|
102
|
+
for (const [bucket, rates] of buckets) {
|
|
103
|
+
if (rates.length < 2)
|
|
104
|
+
continue;
|
|
105
|
+
const sorted = [...rates].sort((a, b) => b.rate - a.rate);
|
|
106
|
+
const high = sorted[0];
|
|
107
|
+
const low = sorted[sorted.length - 1];
|
|
108
|
+
spreads.push({
|
|
109
|
+
market,
|
|
110
|
+
highExchange: high.exchange,
|
|
111
|
+
highRate: high.rate,
|
|
112
|
+
lowExchange: low.exchange,
|
|
113
|
+
lowRate: low.rate,
|
|
114
|
+
spread: high.rate - low.rate,
|
|
115
|
+
annualizedSpread: (high.rate - low.rate) * 24 * 365,
|
|
116
|
+
recordedAt: bucket,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
return spreads.sort((a, b) => b.recordedAt - a.recordedAt);
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Get average spread statistics
|
|
123
|
+
*/
|
|
124
|
+
export function getSpreadStats(market, hours = 24) {
|
|
125
|
+
const spreads = getFundingSpreads(market, hours);
|
|
126
|
+
if (spreads.length === 0) {
|
|
127
|
+
return {
|
|
128
|
+
avgSpread: 0,
|
|
129
|
+
avgAnnualized: 0,
|
|
130
|
+
minSpread: 0,
|
|
131
|
+
maxSpread: 0,
|
|
132
|
+
consistency: 0,
|
|
133
|
+
samples: 0,
|
|
134
|
+
bestPair: null,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
const avgSpread = spreads.reduce((sum, s) => sum + s.spread, 0) / spreads.length;
|
|
138
|
+
const avgAnnualized = spreads.reduce((sum, s) => sum + s.annualizedSpread, 0) / spreads.length;
|
|
139
|
+
const minSpread = Math.min(...spreads.map(s => s.spread));
|
|
140
|
+
const maxSpread = Math.max(...spreads.map(s => s.spread));
|
|
141
|
+
const positiveCount = spreads.filter(s => s.spread > 0.00001).length; // > 0.001%
|
|
142
|
+
// Find most common high/low pair
|
|
143
|
+
const pairCounts = new Map();
|
|
144
|
+
for (const s of spreads) {
|
|
145
|
+
const key = `${s.highExchange}|${s.lowExchange}`;
|
|
146
|
+
pairCounts.set(key, (pairCounts.get(key) || 0) + 1);
|
|
147
|
+
}
|
|
148
|
+
let bestPair = null;
|
|
149
|
+
let maxCount = 0;
|
|
150
|
+
for (const [key, count] of pairCounts) {
|
|
151
|
+
if (count > maxCount) {
|
|
152
|
+
maxCount = count;
|
|
153
|
+
const [high, low] = key.split("|");
|
|
154
|
+
bestPair = { high, low };
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return {
|
|
158
|
+
avgSpread,
|
|
159
|
+
avgAnnualized,
|
|
160
|
+
minSpread,
|
|
161
|
+
maxSpread,
|
|
162
|
+
consistency: (positiveCount / spreads.length) * 100,
|
|
163
|
+
samples: spreads.length,
|
|
164
|
+
bestPair,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Clean up old records (keep last N days)
|
|
169
|
+
*/
|
|
170
|
+
export function cleanupOldRecords(daysToKeep = 7) {
|
|
171
|
+
const db = getDb();
|
|
172
|
+
const cutoff = Date.now() - daysToKeep * 24 * 60 * 60 * 1000;
|
|
173
|
+
const result = db.prepare("DELETE FROM funding_history WHERE recorded_at < ?").run(cutoff);
|
|
174
|
+
return result.changes;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Get record count
|
|
178
|
+
*/
|
|
179
|
+
export function getRecordCount() {
|
|
180
|
+
const db = getDb();
|
|
181
|
+
const result = db.prepare("SELECT COUNT(*) as count FROM funding_history").get();
|
|
182
|
+
return result.count;
|
|
183
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import Database from "better-sqlite3";
|
|
2
|
+
/**
|
|
3
|
+
* Get or create the database connection
|
|
4
|
+
*/
|
|
5
|
+
export declare function getDb(): Database.Database;
|
|
6
|
+
/**
|
|
7
|
+
* Close the database connection
|
|
8
|
+
*/
|
|
9
|
+
export declare function closeDb(): void;
|
|
10
|
+
export * from "./accounts.js";
|
|
11
|
+
export * from "./funding-history.js";
|