@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,899 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Aevo Adapter
|
|
3
|
+
* Implements PerpDEXAdapter interface for Aevo DEX
|
|
4
|
+
*
|
|
5
|
+
* API Docs: https://docs.aevo.xyz/
|
|
6
|
+
* Base URL: https://api.aevo.xyz
|
|
7
|
+
*/
|
|
8
|
+
import { privateKeyToAccount } from "viem/accounts";
|
|
9
|
+
import { registerAdapter, } from "./interface.js";
|
|
10
|
+
import { fetchWithTimeout } from "../lib/fetch.js";
|
|
11
|
+
import { createLogger } from "../lib/logger.js";
|
|
12
|
+
import { FUNDING_INTERVAL_HOURS } from "../lib/constants.js";
|
|
13
|
+
import { isRecord, pickArray, pickObject, pickUnknown, pickString, pickNumber, pickBoolean, toTimestampMs, decimalToScaled, decimalPlaces } from "./utils.js";
|
|
14
|
+
const log = createLogger("aevo");
|
|
15
|
+
export class AevoAdapter {
|
|
16
|
+
info = {
|
|
17
|
+
id: "aevo",
|
|
18
|
+
name: "Aevo",
|
|
19
|
+
type: "dex",
|
|
20
|
+
chains: ["ethereum"],
|
|
21
|
+
features: {
|
|
22
|
+
spot: false,
|
|
23
|
+
perp: true,
|
|
24
|
+
margin: true,
|
|
25
|
+
crossMargin: true,
|
|
26
|
+
isolatedMargin: false,
|
|
27
|
+
stopOrders: true,
|
|
28
|
+
takeProfitOrders: true,
|
|
29
|
+
postOnly: true,
|
|
30
|
+
reduceOnly: true,
|
|
31
|
+
subaccounts: false,
|
|
32
|
+
modifyOrders: true,
|
|
33
|
+
batchOrders: true,
|
|
34
|
+
cancelAllAfter: false,
|
|
35
|
+
publicTrades: true,
|
|
36
|
+
fundingHistory: true,
|
|
37
|
+
orderHistory: true,
|
|
38
|
+
mmp: true,
|
|
39
|
+
twapOrders: false,
|
|
40
|
+
},
|
|
41
|
+
urls: {
|
|
42
|
+
app: "https://app.aevo.xyz",
|
|
43
|
+
api: "https://api.aevo.xyz",
|
|
44
|
+
docs: "https://docs.aevo.xyz",
|
|
45
|
+
testnet: "https://api-testnet.aevo.xyz",
|
|
46
|
+
},
|
|
47
|
+
implementation: {
|
|
48
|
+
marketData: "full",
|
|
49
|
+
authenticatedReads: "full",
|
|
50
|
+
orderLifecycle: "full",
|
|
51
|
+
orderCancellation: "full",
|
|
52
|
+
subscriptions: "full",
|
|
53
|
+
advancedTrading: "full",
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
baseUrl = "https://api.aevo.xyz";
|
|
57
|
+
config = null;
|
|
58
|
+
connected = false;
|
|
59
|
+
marketsCache = null;
|
|
60
|
+
subscriptionTimers = new Set();
|
|
61
|
+
async connect(config) {
|
|
62
|
+
this.config = config;
|
|
63
|
+
const configuredBase = config.credentials?.restUrl;
|
|
64
|
+
const network = config.credentials?.network;
|
|
65
|
+
if (configuredBase) {
|
|
66
|
+
this.baseUrl = configuredBase.replace(/\/$/, "");
|
|
67
|
+
}
|
|
68
|
+
else if (network === "testnet" || (network !== "mainnet" && config.testnet)) {
|
|
69
|
+
this.baseUrl = "https://api-testnet.aevo.xyz";
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
this.baseUrl = "https://api.aevo.xyz";
|
|
73
|
+
}
|
|
74
|
+
await this.fetchMarkets();
|
|
75
|
+
this.connected = true;
|
|
76
|
+
}
|
|
77
|
+
async disconnect() {
|
|
78
|
+
for (const timer of this.subscriptionTimers) {
|
|
79
|
+
clearInterval(timer);
|
|
80
|
+
}
|
|
81
|
+
this.subscriptionTimers.clear();
|
|
82
|
+
this.config = null;
|
|
83
|
+
this.marketsCache = null;
|
|
84
|
+
this.connected = false;
|
|
85
|
+
}
|
|
86
|
+
isConnected() {
|
|
87
|
+
return this.connected;
|
|
88
|
+
}
|
|
89
|
+
// --------------------------------------------------------------------------
|
|
90
|
+
// Private API helpers
|
|
91
|
+
// --------------------------------------------------------------------------
|
|
92
|
+
async fetch(endpoint, options) {
|
|
93
|
+
const url = `${this.baseUrl}${endpoint}`;
|
|
94
|
+
log.debug(`Fetching ${endpoint}`);
|
|
95
|
+
return fetchWithTimeout(url, options);
|
|
96
|
+
}
|
|
97
|
+
async fetchAuthed(endpoint, options) {
|
|
98
|
+
const headers = {
|
|
99
|
+
...this.getAuthHeaders(),
|
|
100
|
+
...(options?.headers ?? {}),
|
|
101
|
+
};
|
|
102
|
+
return this.fetch(endpoint, {
|
|
103
|
+
...options,
|
|
104
|
+
headers,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
getAuthHeaders() {
|
|
108
|
+
const apiKey = this.config?.credentials?.apiKey;
|
|
109
|
+
const apiSecret = this.config?.credentials?.apiSecret;
|
|
110
|
+
if (!apiKey || !apiSecret) {
|
|
111
|
+
throw new Error("Aevo authenticated endpoints require API key + secret");
|
|
112
|
+
}
|
|
113
|
+
return {
|
|
114
|
+
AEVO_KEY: apiKey,
|
|
115
|
+
AEVO_SECRET: apiSecret,
|
|
116
|
+
"AEVO-KEY": apiKey,
|
|
117
|
+
"AEVO-SECRET": apiSecret,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
async fetchMarkets() {
|
|
121
|
+
if (this.marketsCache)
|
|
122
|
+
return this.marketsCache;
|
|
123
|
+
const markets = await this.fetch("/markets");
|
|
124
|
+
this.marketsCache = markets.filter((m) => m.instrument_type === "PERPETUAL");
|
|
125
|
+
return this.marketsCache;
|
|
126
|
+
}
|
|
127
|
+
// --------------------------------------------------------------------------
|
|
128
|
+
// Public Market Data
|
|
129
|
+
// --------------------------------------------------------------------------
|
|
130
|
+
async getMarkets() {
|
|
131
|
+
const markets = await this.fetchMarkets();
|
|
132
|
+
return markets.map((m) => ({
|
|
133
|
+
symbol: m.instrument_name,
|
|
134
|
+
baseAsset: m.underlying_asset,
|
|
135
|
+
quoteAsset: m.quote_asset,
|
|
136
|
+
type: "perp",
|
|
137
|
+
maxLeverage: parseInt(m.max_leverage, 10) || 20,
|
|
138
|
+
minSize: m.amount_step,
|
|
139
|
+
tickSize: m.price_step,
|
|
140
|
+
fundingInterval: FUNDING_INTERVAL_HOURS,
|
|
141
|
+
isActive: m.is_active,
|
|
142
|
+
}));
|
|
143
|
+
}
|
|
144
|
+
async getMarket(symbol) {
|
|
145
|
+
const markets = await this.getMarkets();
|
|
146
|
+
const normalized = this.normalizeSymbol(symbol);
|
|
147
|
+
return markets.find((m) => m.symbol === normalized) ?? null;
|
|
148
|
+
}
|
|
149
|
+
async getTicker(market) {
|
|
150
|
+
const symbol = this.normalizeSymbol(market);
|
|
151
|
+
const markets = await this.fetchMarkets();
|
|
152
|
+
const m = markets.find((row) => row.instrument_name === symbol);
|
|
153
|
+
if (!m)
|
|
154
|
+
throw new Error(`Market ${market} not found`);
|
|
155
|
+
let fundingRate = "0";
|
|
156
|
+
try {
|
|
157
|
+
const funding = await this.fetch(`/funding?instrument_name=${symbol}`);
|
|
158
|
+
fundingRate = funding.funding_rate;
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
// Some symbols can temporarily omit funding.
|
|
162
|
+
}
|
|
163
|
+
// Fetch statistics for volume, OI, 24h change
|
|
164
|
+
let volume24h = "0";
|
|
165
|
+
let change24h = "0";
|
|
166
|
+
let openInterest = "0";
|
|
167
|
+
let high24h = "0";
|
|
168
|
+
let low24h = "0";
|
|
169
|
+
try {
|
|
170
|
+
const stats = await this.fetch(`/statistics?instrument_name=${encodeURIComponent(symbol)}`);
|
|
171
|
+
volume24h = stats.daily_volume ?? "0";
|
|
172
|
+
openInterest = stats.open_interest ?? "0";
|
|
173
|
+
if (stats.mark_daily_change) {
|
|
174
|
+
change24h = (parseFloat(stats.mark_daily_change) * 100).toFixed(2);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
catch {
|
|
178
|
+
// Statistics endpoint may not be available for all symbols.
|
|
179
|
+
}
|
|
180
|
+
// Use orderbook top-of-book for bid/ask
|
|
181
|
+
let bid = m.mark_price;
|
|
182
|
+
let ask = m.mark_price;
|
|
183
|
+
try {
|
|
184
|
+
const book = await this.fetch(`/orderbook?instrument_name=${symbol}`);
|
|
185
|
+
if (book.bids?.length > 0)
|
|
186
|
+
bid = book.bids[0][0];
|
|
187
|
+
if (book.asks?.length > 0)
|
|
188
|
+
ask = book.asks[0][0];
|
|
189
|
+
}
|
|
190
|
+
catch {
|
|
191
|
+
// Fall back to mark price if orderbook unavailable.
|
|
192
|
+
}
|
|
193
|
+
return {
|
|
194
|
+
market: symbol,
|
|
195
|
+
lastPrice: m.mark_price,
|
|
196
|
+
markPrice: m.mark_price,
|
|
197
|
+
indexPrice: m.index_price,
|
|
198
|
+
bid,
|
|
199
|
+
ask,
|
|
200
|
+
volume24h,
|
|
201
|
+
change24h,
|
|
202
|
+
high24h,
|
|
203
|
+
low24h,
|
|
204
|
+
openInterest,
|
|
205
|
+
fundingRate,
|
|
206
|
+
timestamp: Date.now(),
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
async getTickers() {
|
|
210
|
+
const markets = await this.fetchMarkets();
|
|
211
|
+
// Batch fetch statistics for all markets
|
|
212
|
+
const statsMap = new Map();
|
|
213
|
+
try {
|
|
214
|
+
const allStats = await this.fetch("/statistics");
|
|
215
|
+
if (Array.isArray(allStats)) {
|
|
216
|
+
for (const stat of allStats) {
|
|
217
|
+
if (stat.instrument_name) {
|
|
218
|
+
statsMap.set(stat.instrument_name, stat);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
catch {
|
|
224
|
+
// Statistics endpoint may return individual stats or fail.
|
|
225
|
+
}
|
|
226
|
+
return markets.map((m) => {
|
|
227
|
+
const stats = statsMap.get(m.instrument_name);
|
|
228
|
+
const change = stats?.mark_daily_change
|
|
229
|
+
? (parseFloat(stats.mark_daily_change) * 100).toFixed(2)
|
|
230
|
+
: "0";
|
|
231
|
+
return {
|
|
232
|
+
market: m.instrument_name,
|
|
233
|
+
lastPrice: m.mark_price,
|
|
234
|
+
markPrice: m.mark_price,
|
|
235
|
+
indexPrice: m.index_price,
|
|
236
|
+
bid: m.mark_price,
|
|
237
|
+
ask: m.mark_price,
|
|
238
|
+
volume24h: stats?.daily_volume ?? "0",
|
|
239
|
+
change24h: change,
|
|
240
|
+
high24h: "0",
|
|
241
|
+
low24h: "0",
|
|
242
|
+
openInterest: stats?.open_interest ?? "0",
|
|
243
|
+
fundingRate: stats?.funding_daily_avg ?? "0",
|
|
244
|
+
timestamp: Date.now(),
|
|
245
|
+
};
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
async getOrderBook(market, depth = 20) {
|
|
249
|
+
const symbol = this.normalizeSymbol(market);
|
|
250
|
+
const book = await this.fetch(`/orderbook?instrument_name=${symbol}`);
|
|
251
|
+
return {
|
|
252
|
+
market: symbol,
|
|
253
|
+
bids: book.bids.slice(0, depth).map(([price, size]) => ({ price, size })),
|
|
254
|
+
asks: book.asks.slice(0, depth).map(([price, size]) => ({ price, size })),
|
|
255
|
+
timestamp: toTimestampMs(book.last_updated, Date.now()),
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
async getFundingRate(market) {
|
|
259
|
+
const symbol = this.normalizeSymbol(market);
|
|
260
|
+
const funding = await this.fetch(`/funding?instrument_name=${symbol}`);
|
|
261
|
+
return {
|
|
262
|
+
market: symbol,
|
|
263
|
+
rate: funding.funding_rate,
|
|
264
|
+
nextFundingTime: toTimestampMs(funding.next_funding_time, Date.now() + 8 * 3600000),
|
|
265
|
+
timestamp: Date.now(),
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
async getFundingRates() {
|
|
269
|
+
const markets = await this.fetchMarkets();
|
|
270
|
+
const rates = [];
|
|
271
|
+
for (const m of markets) {
|
|
272
|
+
try {
|
|
273
|
+
const rate = await this.getFundingRate(m.instrument_name);
|
|
274
|
+
rates.push(rate);
|
|
275
|
+
}
|
|
276
|
+
catch {
|
|
277
|
+
// Best effort across all markets.
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
return rates;
|
|
281
|
+
}
|
|
282
|
+
// --------------------------------------------------------------------------
|
|
283
|
+
// Account Data (requires auth)
|
|
284
|
+
// --------------------------------------------------------------------------
|
|
285
|
+
async getPositions() {
|
|
286
|
+
this.ensureAuth();
|
|
287
|
+
const response = await this.fetchAuthed("/positions");
|
|
288
|
+
const rows = pickArray(response, ["positions", "data.positions", "data.rows", "results"]);
|
|
289
|
+
return rows
|
|
290
|
+
.map((row) => {
|
|
291
|
+
const market = this.normalizeSymbol(pickString(row, ["instrument_name", "market", "symbol"]) ?? "");
|
|
292
|
+
if (!market)
|
|
293
|
+
return null;
|
|
294
|
+
const signedSize = pickNumber(row, ["size", "amount", "position_size", "quantity"], 0);
|
|
295
|
+
const sideValue = pickString(row, ["side"])?.toLowerCase();
|
|
296
|
+
const side = sideValue === "sell" || sideValue === "short" || signedSize < 0 ? "short" : "long";
|
|
297
|
+
const absSize = Math.abs(signedSize);
|
|
298
|
+
if (absSize === 0)
|
|
299
|
+
return null;
|
|
300
|
+
const entryPrice = pickString(row, ["entry_price", "avg_entry_price", "average_entry_price"]) ?? "0";
|
|
301
|
+
const markPrice = pickString(row, ["mark_price", "last_price", "index_price"]) ?? entryPrice;
|
|
302
|
+
return {
|
|
303
|
+
market,
|
|
304
|
+
side,
|
|
305
|
+
size: absSize.toString(),
|
|
306
|
+
entryPrice,
|
|
307
|
+
markPrice,
|
|
308
|
+
liquidationPrice: pickString(row, ["liquidation_price", "liq_price", "estimated_liquidation_price"]) ??
|
|
309
|
+
null,
|
|
310
|
+
unrealizedPnl: pickString(row, ["unrealized_pnl", "upnl", "pnl"]) ?? "0",
|
|
311
|
+
realizedPnl: pickString(row, ["realized_pnl", "rpnl"]) ?? "0",
|
|
312
|
+
leverage: pickNumber(row, ["leverage"], 1),
|
|
313
|
+
marginType: "cross",
|
|
314
|
+
margin: pickString(row, ["margin", "margin_used", "initial_margin"]) ?? "0",
|
|
315
|
+
timestamp: toTimestampMs(pickUnknown(row, ["updated_at", "timestamp", "created_at"]), Date.now()),
|
|
316
|
+
};
|
|
317
|
+
})
|
|
318
|
+
.filter((row) => row !== null);
|
|
319
|
+
}
|
|
320
|
+
async getPosition(market) {
|
|
321
|
+
const positions = await this.getPositions();
|
|
322
|
+
const symbol = this.normalizeSymbol(market);
|
|
323
|
+
return positions.find((p) => p.market === symbol) ?? null;
|
|
324
|
+
}
|
|
325
|
+
async getOrders(market) {
|
|
326
|
+
this.ensureAuth();
|
|
327
|
+
const query = market ? `?instrument_name=${encodeURIComponent(this.normalizeSymbol(market))}` : "";
|
|
328
|
+
const response = await this.fetchAuthed(`/orders${query}`);
|
|
329
|
+
const rows = pickArray(response, ["orders", "data.orders", "data.rows", "results"]);
|
|
330
|
+
return rows
|
|
331
|
+
.map((row) => this.toOrder(row))
|
|
332
|
+
.filter((row) => row !== null);
|
|
333
|
+
}
|
|
334
|
+
async getOrder(orderId) {
|
|
335
|
+
this.ensureAuth();
|
|
336
|
+
try {
|
|
337
|
+
const response = await this.fetchAuthed(`/orders/${encodeURIComponent(orderId)}`);
|
|
338
|
+
const rows = pickArray(response, ["order", "data", "results"]);
|
|
339
|
+
if (rows.length > 0) {
|
|
340
|
+
return this.toOrder(rows[0]);
|
|
341
|
+
}
|
|
342
|
+
if (isRecord(response)) {
|
|
343
|
+
return this.toOrder(response);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
catch {
|
|
347
|
+
// Fall through to broader lookup below.
|
|
348
|
+
}
|
|
349
|
+
const orders = await this.getOrders();
|
|
350
|
+
return orders.find((o) => o.id === orderId) ?? null;
|
|
351
|
+
}
|
|
352
|
+
async getBalances() {
|
|
353
|
+
this.ensureAuth();
|
|
354
|
+
const response = await this.fetchAuthed("/account");
|
|
355
|
+
const balanceRows = pickArray(response, ["balances", "data.balances", "results"]);
|
|
356
|
+
if (balanceRows.length > 0) {
|
|
357
|
+
return balanceRows.map((row) => {
|
|
358
|
+
const asset = pickString(row, ["asset", "symbol", "token"]) ?? "USD";
|
|
359
|
+
const total = pickString(row, ["total", "balance", "equity"]) ?? "0";
|
|
360
|
+
const available = pickString(row, ["available", "available_balance", "free"]) ?? total;
|
|
361
|
+
const locked = pickString(row, ["locked", "margin_used", "in_orders"]) ?? "0";
|
|
362
|
+
const unrealizedPnl = pickString(row, ["unrealized_pnl", "upnl"]) ?? "0";
|
|
363
|
+
const marginUsed = pickString(row, ["margin_used", "initial_margin", "maintenance_margin"]) ?? locked;
|
|
364
|
+
return {
|
|
365
|
+
asset,
|
|
366
|
+
total,
|
|
367
|
+
available,
|
|
368
|
+
locked,
|
|
369
|
+
unrealizedPnl,
|
|
370
|
+
marginUsed,
|
|
371
|
+
};
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
const accountRow = isRecord(response)
|
|
375
|
+
? response
|
|
376
|
+
: pickObject(response, ["account", "data.account", "data", "results.0"]);
|
|
377
|
+
const total = pickString(accountRow, ["equity", "balance", "total_collateral", "account_value"]) ?? "0";
|
|
378
|
+
const available = pickString(accountRow, ["available_balance", "available", "free_collateral"]) ?? total;
|
|
379
|
+
const marginUsed = pickString(accountRow, ["margin_used", "used_margin", "initial_margin"]) ??
|
|
380
|
+
Math.max(0, parseFloat(total) - parseFloat(available)).toString();
|
|
381
|
+
return [
|
|
382
|
+
{
|
|
383
|
+
asset: "USD",
|
|
384
|
+
total,
|
|
385
|
+
available,
|
|
386
|
+
locked: marginUsed,
|
|
387
|
+
unrealizedPnl: pickString(accountRow, ["unrealized_pnl", "upnl"]) ?? "0",
|
|
388
|
+
marginUsed,
|
|
389
|
+
},
|
|
390
|
+
];
|
|
391
|
+
}
|
|
392
|
+
async getTrades(market, limit = 100) {
|
|
393
|
+
this.ensureAuth();
|
|
394
|
+
const query = new URLSearchParams();
|
|
395
|
+
if (market)
|
|
396
|
+
query.set("instrument_name", this.normalizeSymbol(market));
|
|
397
|
+
if (limit > 0)
|
|
398
|
+
query.set("limit", String(limit));
|
|
399
|
+
const suffix = query.size > 0 ? `?${query.toString()}` : "";
|
|
400
|
+
const response = await this.fetchAuthed(`/trade-history${suffix}`);
|
|
401
|
+
const rows = pickArray(response, ["trades", "data.trades", "data.rows", "results"]);
|
|
402
|
+
return rows
|
|
403
|
+
.map((row) => {
|
|
404
|
+
const marketSymbol = this.normalizeSymbol(pickString(row, ["instrument_name", "market", "symbol"]) ?? "");
|
|
405
|
+
if (!marketSymbol)
|
|
406
|
+
return null;
|
|
407
|
+
const sideRaw = pickString(row, ["side"])?.toLowerCase() ?? "buy";
|
|
408
|
+
const side = sideRaw === "sell" || sideRaw === "short" ? "short" : "long";
|
|
409
|
+
const orderId = pickString(row, ["order_id", "client_order_id"]);
|
|
410
|
+
return {
|
|
411
|
+
id: pickString(row, ["trade_id", "id", "fill_id", "execution_id"]) ??
|
|
412
|
+
`${marketSymbol}-${Date.now()}`,
|
|
413
|
+
market: marketSymbol,
|
|
414
|
+
side,
|
|
415
|
+
price: pickString(row, ["price", "execution_price", "avg_price"]) ?? "0",
|
|
416
|
+
size: pickString(row, ["size", "amount", "quantity", "filled_amount"]) ?? "0",
|
|
417
|
+
fee: pickString(row, ["fee", "fee_paid"]) ?? "0",
|
|
418
|
+
feeAsset: pickString(row, ["fee_asset", "fee_currency"]) ?? "USD",
|
|
419
|
+
timestamp: toTimestampMs(pickUnknown(row, ["created_at", "timestamp", "trade_time"]), Date.now()),
|
|
420
|
+
...(orderId ? { orderId } : {}),
|
|
421
|
+
};
|
|
422
|
+
})
|
|
423
|
+
.filter((row) => row !== null);
|
|
424
|
+
}
|
|
425
|
+
// --------------------------------------------------------------------------
|
|
426
|
+
// Trading
|
|
427
|
+
// --------------------------------------------------------------------------
|
|
428
|
+
async placeOrder(params) {
|
|
429
|
+
this.ensureAuth();
|
|
430
|
+
const apiPrivateKey = this.config?.credentials?.privateKey;
|
|
431
|
+
if (!apiPrivateKey) {
|
|
432
|
+
throw new Error("Aevo order placement requires signing key (AEVO_SIGNING_KEY)");
|
|
433
|
+
}
|
|
434
|
+
const symbol = this.normalizeSymbol(params.market);
|
|
435
|
+
const market = (await this.fetchMarkets()).find((row) => row.instrument_name === symbol);
|
|
436
|
+
if (!market) {
|
|
437
|
+
throw new Error(`Unknown Aevo market: ${params.market}`);
|
|
438
|
+
}
|
|
439
|
+
const isBuy = params.side === "long";
|
|
440
|
+
const instrument = BigInt(market.instrument_id);
|
|
441
|
+
const limitPrice = await this.resolveLimitPrice(params, symbol);
|
|
442
|
+
const amount = this.sizeToContracts(params.size, market.amount_step);
|
|
443
|
+
const limitPriceUnits = decimalToScaled(limitPrice, 6);
|
|
444
|
+
if (amount <= 0n) {
|
|
445
|
+
throw new Error(`Order size ${params.size} is below Aevo minimum step ${market.amount_step}`);
|
|
446
|
+
}
|
|
447
|
+
const account = privateKeyToAccount(apiPrivateKey);
|
|
448
|
+
const timestamp = BigInt(Math.floor(Date.now() / 1000));
|
|
449
|
+
const salt = BigInt(Date.now()) * 1000000n + BigInt(Math.floor(Math.random() * 1_000_000));
|
|
450
|
+
const signature = await account.signTypedData({
|
|
451
|
+
domain: this.config?.testnet
|
|
452
|
+
? {
|
|
453
|
+
name: "Aevo Testnet",
|
|
454
|
+
version: "1",
|
|
455
|
+
chainId: 11155111,
|
|
456
|
+
}
|
|
457
|
+
: {
|
|
458
|
+
name: "Aevo Mainnet",
|
|
459
|
+
version: "1",
|
|
460
|
+
chainId: 1,
|
|
461
|
+
},
|
|
462
|
+
types: {
|
|
463
|
+
Order: [
|
|
464
|
+
{ name: "maker", type: "address" },
|
|
465
|
+
{ name: "isBuy", type: "bool" },
|
|
466
|
+
{ name: "limitPrice", type: "uint256" },
|
|
467
|
+
{ name: "amount", type: "uint256" },
|
|
468
|
+
{ name: "salt", type: "uint256" },
|
|
469
|
+
{ name: "instrument", type: "uint256" },
|
|
470
|
+
{ name: "timestamp", type: "uint256" },
|
|
471
|
+
],
|
|
472
|
+
},
|
|
473
|
+
primaryType: "Order",
|
|
474
|
+
message: {
|
|
475
|
+
maker: account.address,
|
|
476
|
+
isBuy,
|
|
477
|
+
limitPrice: limitPriceUnits,
|
|
478
|
+
amount,
|
|
479
|
+
salt,
|
|
480
|
+
instrument,
|
|
481
|
+
timestamp,
|
|
482
|
+
},
|
|
483
|
+
});
|
|
484
|
+
const payload = {
|
|
485
|
+
instrument: instrument.toString(),
|
|
486
|
+
maker: account.address,
|
|
487
|
+
is_buy: isBuy,
|
|
488
|
+
amount: amount.toString(),
|
|
489
|
+
limit_price: limitPriceUnits.toString(),
|
|
490
|
+
salt: salt.toString(),
|
|
491
|
+
timestamp: timestamp.toString(),
|
|
492
|
+
signature,
|
|
493
|
+
post_only: params.postOnly ?? false,
|
|
494
|
+
reduce_only: params.reduceOnly ?? false,
|
|
495
|
+
};
|
|
496
|
+
const response = await this.fetchAuthed("/orders", {
|
|
497
|
+
method: "POST",
|
|
498
|
+
body: JSON.stringify(payload),
|
|
499
|
+
});
|
|
500
|
+
const orderCandidate = pickObject(response, ["order", "data", "results.0"]);
|
|
501
|
+
if (orderCandidate) {
|
|
502
|
+
const mapped = this.toOrder(orderCandidate);
|
|
503
|
+
if (mapped) {
|
|
504
|
+
return mapped;
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
return {
|
|
508
|
+
id: pickString(orderCandidate ?? {}, ["order_id", "id", "client_order_id"]) ??
|
|
509
|
+
salt.toString(),
|
|
510
|
+
market: symbol,
|
|
511
|
+
side: params.side,
|
|
512
|
+
type: params.type,
|
|
513
|
+
size: params.size,
|
|
514
|
+
price: limitPrice,
|
|
515
|
+
filled: "0",
|
|
516
|
+
remaining: params.size,
|
|
517
|
+
status: "open",
|
|
518
|
+
reduceOnly: params.reduceOnly ?? false,
|
|
519
|
+
postOnly: params.postOnly ?? false,
|
|
520
|
+
timestamp: Date.now(),
|
|
521
|
+
triggerPrice: params.triggerPrice,
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
async cancelOrder(params) {
|
|
525
|
+
this.ensureAuth();
|
|
526
|
+
const orderId = params.orderId ?? params.clientOrderId;
|
|
527
|
+
if (!orderId) {
|
|
528
|
+
throw new Error("cancelOrder requires orderId or clientOrderId");
|
|
529
|
+
}
|
|
530
|
+
await this.fetchAuthed(`/orders/${encodeURIComponent(orderId)}`, {
|
|
531
|
+
method: "DELETE",
|
|
532
|
+
});
|
|
533
|
+
return true;
|
|
534
|
+
}
|
|
535
|
+
async cancelAllOrders(market) {
|
|
536
|
+
this.ensureAuth();
|
|
537
|
+
const suffix = market
|
|
538
|
+
? `?instrument_name=${encodeURIComponent(this.normalizeSymbol(market))}`
|
|
539
|
+
: "";
|
|
540
|
+
const response = await this.fetchAuthed(`/orders-all${suffix}`, {
|
|
541
|
+
method: "DELETE",
|
|
542
|
+
});
|
|
543
|
+
const cancelled = pickNumber(response, ["cancelled", "cancelled_orders", "count"], NaN);
|
|
544
|
+
if (Number.isFinite(cancelled) && cancelled >= 0) {
|
|
545
|
+
return Math.floor(cancelled);
|
|
546
|
+
}
|
|
547
|
+
return market ? 1 : 0;
|
|
548
|
+
}
|
|
549
|
+
async setLeverage(market, leverage) {
|
|
550
|
+
this.ensureAuth();
|
|
551
|
+
const symbol = this.normalizeSymbol(market);
|
|
552
|
+
const payloads = [
|
|
553
|
+
{ instrument_name: symbol, leverage },
|
|
554
|
+
{ market: symbol, leverage },
|
|
555
|
+
{ instrument: symbol, leverage },
|
|
556
|
+
];
|
|
557
|
+
let lastError = null;
|
|
558
|
+
for (const payload of payloads) {
|
|
559
|
+
try {
|
|
560
|
+
await this.fetchAuthed("/account/update-margin", {
|
|
561
|
+
method: "POST",
|
|
562
|
+
body: JSON.stringify(payload),
|
|
563
|
+
});
|
|
564
|
+
return;
|
|
565
|
+
}
|
|
566
|
+
catch (err) {
|
|
567
|
+
lastError = err instanceof Error ? err : new Error(String(err));
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
throw lastError ?? new Error("Failed to update Aevo leverage");
|
|
571
|
+
}
|
|
572
|
+
async setMarginType(market, type) {
|
|
573
|
+
this.ensureAuth();
|
|
574
|
+
const symbol = this.normalizeSymbol(market);
|
|
575
|
+
const payloads = [
|
|
576
|
+
{ instrument_name: symbol, margin_type: type },
|
|
577
|
+
{ market: symbol, margin_type: type },
|
|
578
|
+
{ instrument: symbol, margin_type: type },
|
|
579
|
+
{ instrument_name: symbol, type },
|
|
580
|
+
];
|
|
581
|
+
let lastError = null;
|
|
582
|
+
for (const payload of payloads) {
|
|
583
|
+
try {
|
|
584
|
+
await this.fetchAuthed("/account/margin-type", {
|
|
585
|
+
method: "POST",
|
|
586
|
+
body: JSON.stringify(payload),
|
|
587
|
+
});
|
|
588
|
+
return;
|
|
589
|
+
}
|
|
590
|
+
catch (err) {
|
|
591
|
+
lastError = err instanceof Error ? err : new Error(String(err));
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
throw lastError ?? new Error("Failed to update Aevo margin type");
|
|
595
|
+
}
|
|
596
|
+
// --------------------------------------------------------------------------
|
|
597
|
+
// Advanced Trading
|
|
598
|
+
// --------------------------------------------------------------------------
|
|
599
|
+
async modifyOrder(params) {
|
|
600
|
+
this.ensureAuth();
|
|
601
|
+
const payload = {};
|
|
602
|
+
if (params.price)
|
|
603
|
+
payload.limit_price = params.price;
|
|
604
|
+
if (params.size)
|
|
605
|
+
payload.amount = params.size;
|
|
606
|
+
if (params.triggerPrice)
|
|
607
|
+
payload.trigger_price = params.triggerPrice;
|
|
608
|
+
const response = await this.fetchAuthed(`/orders/${encodeURIComponent(params.orderId)}`, {
|
|
609
|
+
method: "PUT",
|
|
610
|
+
body: JSON.stringify(payload),
|
|
611
|
+
});
|
|
612
|
+
const orderCandidate = pickObject(response, ["order", "data", "results.0"]);
|
|
613
|
+
if (orderCandidate) {
|
|
614
|
+
const mapped = this.toOrder(orderCandidate);
|
|
615
|
+
if (mapped)
|
|
616
|
+
return mapped;
|
|
617
|
+
}
|
|
618
|
+
// Fetch back the order for full details
|
|
619
|
+
const order = await this.getOrder(params.orderId);
|
|
620
|
+
if (order)
|
|
621
|
+
return order;
|
|
622
|
+
throw new Error(`Failed to retrieve modified order ${params.orderId}`);
|
|
623
|
+
}
|
|
624
|
+
async batchPlaceOrders(paramsList) {
|
|
625
|
+
// Sequential: Aevo batch endpoint requires identical signing per-order
|
|
626
|
+
const results = [];
|
|
627
|
+
for (const params of paramsList) {
|
|
628
|
+
results.push(await this.placeOrder(params));
|
|
629
|
+
}
|
|
630
|
+
return results;
|
|
631
|
+
}
|
|
632
|
+
async batchCancelOrders(paramsList) {
|
|
633
|
+
const results = [];
|
|
634
|
+
for (const params of paramsList) {
|
|
635
|
+
try {
|
|
636
|
+
results.push(await this.cancelOrder(params));
|
|
637
|
+
}
|
|
638
|
+
catch {
|
|
639
|
+
results.push(false);
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
return results;
|
|
643
|
+
}
|
|
644
|
+
async cancelAllAfter(_timeoutMs) {
|
|
645
|
+
throw new Error("Aevo does not support cancelAllAfter (dead man's switch)");
|
|
646
|
+
}
|
|
647
|
+
async getOrderHistory(market, limit = 100) {
|
|
648
|
+
this.ensureAuth();
|
|
649
|
+
const query = new URLSearchParams();
|
|
650
|
+
if (market)
|
|
651
|
+
query.set("instrument_name", this.normalizeSymbol(market));
|
|
652
|
+
if (limit > 0)
|
|
653
|
+
query.set("limit", String(limit));
|
|
654
|
+
const suffix = query.size > 0 ? `?${query.toString()}` : "";
|
|
655
|
+
const response = await this.fetchAuthed(`/order-history${suffix}`);
|
|
656
|
+
const rows = pickArray(response, ["orders", "data.orders", "data.rows", "results"]);
|
|
657
|
+
return rows
|
|
658
|
+
.map((row) => this.toOrder(row))
|
|
659
|
+
.filter((row) => row !== null);
|
|
660
|
+
}
|
|
661
|
+
async getFundingHistory(market, limit = 100) {
|
|
662
|
+
this.ensureAuth();
|
|
663
|
+
const query = new URLSearchParams();
|
|
664
|
+
if (market)
|
|
665
|
+
query.set("instrument_name", this.normalizeSymbol(market));
|
|
666
|
+
if (limit > 0)
|
|
667
|
+
query.set("limit", String(limit));
|
|
668
|
+
const suffix = query.size > 0 ? `?${query.toString()}` : "";
|
|
669
|
+
const response = await this.fetchAuthed(`/funding-history${suffix}`);
|
|
670
|
+
const rows = pickArray(response, ["funding_history", "data.rows", "results"]);
|
|
671
|
+
return rows.map((row) => ({
|
|
672
|
+
market: this.normalizeSymbol(pickString(row, ["instrument_name", "market"]) ?? ""),
|
|
673
|
+
amount: pickString(row, ["payment", "amount", "funding_payment"]) ?? "0",
|
|
674
|
+
rate: pickString(row, ["funding_rate", "rate"]) ?? "0",
|
|
675
|
+
timestamp: toTimestampMs(pickUnknown(row, ["created_at", "timestamp"]), Date.now()),
|
|
676
|
+
}));
|
|
677
|
+
}
|
|
678
|
+
async getPublicTrades(market, limit = 100) {
|
|
679
|
+
const symbol = this.normalizeSymbol(market);
|
|
680
|
+
const response = await this.fetch(`/trades?instrument_name=${encodeURIComponent(symbol)}&limit=${limit}`);
|
|
681
|
+
const rows = pickArray(response, ["trades", "data.trades", "results"]);
|
|
682
|
+
return rows.map((row) => ({
|
|
683
|
+
id: pickString(row, ["trade_id", "id"]) ?? `${Date.now()}`,
|
|
684
|
+
market: symbol,
|
|
685
|
+
side: (pickString(row, ["side"])?.toLowerCase() === "sell" ? "short" : "long"),
|
|
686
|
+
price: pickString(row, ["price"]) ?? "0",
|
|
687
|
+
size: pickString(row, ["size", "amount"]) ?? "0",
|
|
688
|
+
timestamp: toTimestampMs(pickUnknown(row, ["created_at", "timestamp"]), Date.now()),
|
|
689
|
+
}));
|
|
690
|
+
}
|
|
691
|
+
// --------------------------------------------------------------------------
|
|
692
|
+
// Market Maker Protection
|
|
693
|
+
// --------------------------------------------------------------------------
|
|
694
|
+
async setMMP(config) {
|
|
695
|
+
this.ensureAuth();
|
|
696
|
+
await this.fetchAuthed("/mmp", {
|
|
697
|
+
method: "POST",
|
|
698
|
+
body: JSON.stringify({
|
|
699
|
+
instrument_name: this.normalizeSymbol(config.market),
|
|
700
|
+
window_ms: config.windowMs,
|
|
701
|
+
frozen_ms: config.frozenMs,
|
|
702
|
+
quantity_limit: config.quantityLimit,
|
|
703
|
+
delta_limit: config.deltaLimit,
|
|
704
|
+
}),
|
|
705
|
+
});
|
|
706
|
+
}
|
|
707
|
+
async getMMP(market) {
|
|
708
|
+
this.ensureAuth();
|
|
709
|
+
const symbol = this.normalizeSymbol(market);
|
|
710
|
+
const response = await this.fetchAuthed(`/mmp?instrument_name=${encodeURIComponent(symbol)}`);
|
|
711
|
+
const data = pickObject(response, ["data", "result", "mmp"]) ?? {};
|
|
712
|
+
return {
|
|
713
|
+
market: symbol,
|
|
714
|
+
frozen: pickBoolean(data, ["frozen", "is_frozen"], false),
|
|
715
|
+
frozenUntil: pickNumber(data, ["frozen_until", "frozen_end_time"], 0) || null,
|
|
716
|
+
config: {
|
|
717
|
+
market: symbol,
|
|
718
|
+
windowMs: pickNumber(data, ["window_ms", "interval"], 0),
|
|
719
|
+
frozenMs: pickNumber(data, ["frozen_ms", "freeze_duration"], 0),
|
|
720
|
+
quantityLimit: pickString(data, ["quantity_limit", "qty_limit"]) ?? "0",
|
|
721
|
+
deltaLimit: pickString(data, ["delta_limit"]) ?? "0",
|
|
722
|
+
},
|
|
723
|
+
};
|
|
724
|
+
}
|
|
725
|
+
async resetMMP(market) {
|
|
726
|
+
this.ensureAuth();
|
|
727
|
+
const symbol = this.normalizeSymbol(market);
|
|
728
|
+
await this.fetchAuthed("/reset-mmp", {
|
|
729
|
+
method: "POST",
|
|
730
|
+
body: JSON.stringify({ instrument_name: symbol }),
|
|
731
|
+
});
|
|
732
|
+
}
|
|
733
|
+
// --------------------------------------------------------------------------
|
|
734
|
+
// TWAP Orders
|
|
735
|
+
// --------------------------------------------------------------------------
|
|
736
|
+
async placeTWAP(_params) {
|
|
737
|
+
throw new Error("Aevo TWAP orders are not supported through this adapter");
|
|
738
|
+
}
|
|
739
|
+
async cancelTWAP(_twapId) {
|
|
740
|
+
throw new Error("Aevo TWAP orders are not supported through this adapter");
|
|
741
|
+
}
|
|
742
|
+
async getTWAPStatus(_twapId) {
|
|
743
|
+
throw new Error("Aevo TWAP orders are not supported through this adapter");
|
|
744
|
+
}
|
|
745
|
+
// --------------------------------------------------------------------------
|
|
746
|
+
// Margin Management
|
|
747
|
+
// --------------------------------------------------------------------------
|
|
748
|
+
async updateIsolatedMargin(_market, _amount) {
|
|
749
|
+
throw new Error("Aevo does not support isolated margin adjustment");
|
|
750
|
+
}
|
|
751
|
+
// --------------------------------------------------------------------------
|
|
752
|
+
// Subscriptions (polling)
|
|
753
|
+
// --------------------------------------------------------------------------
|
|
754
|
+
subscribe(callbacks) {
|
|
755
|
+
const timer = setInterval(async () => {
|
|
756
|
+
try {
|
|
757
|
+
if (callbacks.onPositions) {
|
|
758
|
+
callbacks.onPositions(await this.getPositions());
|
|
759
|
+
}
|
|
760
|
+
if (callbacks.onOrders) {
|
|
761
|
+
callbacks.onOrders(await this.getOrders());
|
|
762
|
+
}
|
|
763
|
+
if (callbacks.onBalances) {
|
|
764
|
+
callbacks.onBalances(await this.getBalances());
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
catch (err) {
|
|
768
|
+
callbacks.onError?.(err instanceof Error ? err : new Error(String(err)));
|
|
769
|
+
}
|
|
770
|
+
}, 3000);
|
|
771
|
+
this.subscriptionTimers.add(timer);
|
|
772
|
+
return () => {
|
|
773
|
+
clearInterval(timer);
|
|
774
|
+
this.subscriptionTimers.delete(timer);
|
|
775
|
+
};
|
|
776
|
+
}
|
|
777
|
+
subscribeOrderBook(market, callback) {
|
|
778
|
+
const symbol = this.normalizeSymbol(market);
|
|
779
|
+
const timer = setInterval(async () => {
|
|
780
|
+
try {
|
|
781
|
+
callback(await this.getOrderBook(symbol));
|
|
782
|
+
}
|
|
783
|
+
catch {
|
|
784
|
+
// Polling callback intentionally ignores transient read errors.
|
|
785
|
+
}
|
|
786
|
+
}, 1500);
|
|
787
|
+
this.subscriptionTimers.add(timer);
|
|
788
|
+
return () => {
|
|
789
|
+
clearInterval(timer);
|
|
790
|
+
this.subscriptionTimers.delete(timer);
|
|
791
|
+
};
|
|
792
|
+
}
|
|
793
|
+
subscribeTicker(market, callback) {
|
|
794
|
+
const symbol = this.normalizeSymbol(market);
|
|
795
|
+
const timer = setInterval(async () => {
|
|
796
|
+
try {
|
|
797
|
+
callback(await this.getTicker(symbol));
|
|
798
|
+
}
|
|
799
|
+
catch {
|
|
800
|
+
// Polling callback intentionally ignores transient read errors.
|
|
801
|
+
}
|
|
802
|
+
}, 1200);
|
|
803
|
+
this.subscriptionTimers.add(timer);
|
|
804
|
+
return () => {
|
|
805
|
+
clearInterval(timer);
|
|
806
|
+
this.subscriptionTimers.delete(timer);
|
|
807
|
+
};
|
|
808
|
+
}
|
|
809
|
+
// --------------------------------------------------------------------------
|
|
810
|
+
// Private helpers
|
|
811
|
+
// --------------------------------------------------------------------------
|
|
812
|
+
normalizeSymbol(symbol) {
|
|
813
|
+
const upper = symbol.toUpperCase().trim();
|
|
814
|
+
if (!upper)
|
|
815
|
+
return "";
|
|
816
|
+
if (upper.endsWith("-PERP"))
|
|
817
|
+
return upper;
|
|
818
|
+
return `${upper}-PERP`;
|
|
819
|
+
}
|
|
820
|
+
ensureAuth() {
|
|
821
|
+
if (!this.config?.credentials?.apiKey || !this.config?.credentials?.apiSecret) {
|
|
822
|
+
throw new Error("Aevo authenticated endpoints require API key + secret");
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
async resolveLimitPrice(params, market) {
|
|
826
|
+
if (params.type === "market") {
|
|
827
|
+
const ticker = await this.getTicker(market);
|
|
828
|
+
const mark = parseFloat(ticker.markPrice || ticker.lastPrice);
|
|
829
|
+
if (!Number.isFinite(mark) || mark <= 0) {
|
|
830
|
+
throw new Error(`Unable to resolve Aevo market price for ${market}`);
|
|
831
|
+
}
|
|
832
|
+
const padded = params.side === "long" ? mark * 1.01 : mark * 0.99;
|
|
833
|
+
return padded.toFixed(8);
|
|
834
|
+
}
|
|
835
|
+
if (!params.price) {
|
|
836
|
+
throw new Error("Limit/triggered orders require price");
|
|
837
|
+
}
|
|
838
|
+
return params.price;
|
|
839
|
+
}
|
|
840
|
+
toOrder(row) {
|
|
841
|
+
const market = this.normalizeSymbol(pickString(row, ["instrument_name", "market", "symbol"]) ?? "");
|
|
842
|
+
if (!market)
|
|
843
|
+
return null;
|
|
844
|
+
const sideRaw = pickString(row, ["side", "direction"])?.toLowerCase() ?? "buy";
|
|
845
|
+
const side = sideRaw === "sell" || sideRaw === "short" ? "short" : "long";
|
|
846
|
+
const size = pickString(row, ["amount", "size", "quantity", "orig_qty"]) ?? "0";
|
|
847
|
+
const filled = pickString(row, ["filled_amount", "filled", "executed_quantity"]) ?? "0";
|
|
848
|
+
const remaining = pickString(row, ["remaining_amount", "remaining", "leaves_qty"]) ??
|
|
849
|
+
Math.max(0, parseFloat(size) - parseFloat(filled)).toString();
|
|
850
|
+
const statusRaw = pickString(row, ["status", "order_status"])?.toLowerCase() ?? "open";
|
|
851
|
+
const status = statusRaw.includes("cancel")
|
|
852
|
+
? "cancelled"
|
|
853
|
+
: statusRaw.includes("fill")
|
|
854
|
+
? parseFloat(remaining) > 0
|
|
855
|
+
? "partial"
|
|
856
|
+
: "filled"
|
|
857
|
+
: statusRaw.includes("expire")
|
|
858
|
+
? "expired"
|
|
859
|
+
: "open";
|
|
860
|
+
const typeRaw = pickString(row, ["order_type", "type"])?.toLowerCase() ?? "limit";
|
|
861
|
+
const type = typeRaw.includes("market")
|
|
862
|
+
? "market"
|
|
863
|
+
: typeRaw.includes("stop") && typeRaw.includes("limit")
|
|
864
|
+
? "stop_limit"
|
|
865
|
+
: typeRaw.includes("stop")
|
|
866
|
+
? "stop"
|
|
867
|
+
: typeRaw.includes("take")
|
|
868
|
+
? "take_profit"
|
|
869
|
+
: "limit";
|
|
870
|
+
return {
|
|
871
|
+
id: pickString(row, ["order_id", "id", "client_order_id", "uuid"]) ??
|
|
872
|
+
`${market}-${Date.now()}`,
|
|
873
|
+
market,
|
|
874
|
+
side,
|
|
875
|
+
type,
|
|
876
|
+
size,
|
|
877
|
+
price: pickString(row, ["limit_price", "price", "avg_price"]),
|
|
878
|
+
filled,
|
|
879
|
+
remaining,
|
|
880
|
+
status,
|
|
881
|
+
reduceOnly: pickBoolean(row, ["reduce_only", "reduceOnly"], false),
|
|
882
|
+
postOnly: pickBoolean(row, ["post_only", "postOnly"], false),
|
|
883
|
+
timestamp: toTimestampMs(pickUnknown(row, ["created_at", "timestamp", "updated_at"]), Date.now()),
|
|
884
|
+
triggerPrice: pickString(row, ["trigger_price"]) ?? undefined,
|
|
885
|
+
};
|
|
886
|
+
}
|
|
887
|
+
sizeToContracts(size, amountStep) {
|
|
888
|
+
const stepDecimals = decimalPlaces(amountStep);
|
|
889
|
+
const sizeScaled = decimalToScaled(size, stepDecimals);
|
|
890
|
+
const stepScaled = decimalToScaled(amountStep, stepDecimals);
|
|
891
|
+
if (stepScaled <= 0n) {
|
|
892
|
+
throw new Error(`Invalid Aevo amount_step ${amountStep}`);
|
|
893
|
+
}
|
|
894
|
+
return sizeScaled / stepScaled;
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
// Register the adapter
|
|
898
|
+
registerAdapter("aevo", () => new AevoAdapter());
|
|
899
|
+
export default AevoAdapter;
|