perp-cli 0.6.4 → 0.7.2
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/README.md +7 -2
- package/dist/api/public/hyperliquid.d.ts +2 -2
- package/dist/api/public/hyperliquid.js +0 -10
- package/dist/api/public/lighter.d.ts +2 -1
- package/dist/api/public/pacifica.d.ts +2 -1
- package/dist/arb/index.d.ts +7 -4
- package/dist/arb/index.js +5 -4
- package/dist/arb/sizing.d.ts +1 -1
- package/dist/arb/state.d.ts +1 -1
- package/dist/arb/state.js +4 -3
- package/dist/arb/utils.d.ts +0 -10
- package/dist/arb/utils.js +0 -42
- package/dist/bot/apex/guard.d.ts +26 -0
- package/dist/bot/apex/guard.js +86 -0
- package/dist/bot/apex/index.d.ts +6 -0
- package/dist/bot/apex/index.js +4 -0
- package/dist/bot/apex/pulse.d.ts +22 -0
- package/dist/bot/apex/pulse.js +107 -0
- package/dist/bot/apex/radar.d.ts +17 -0
- package/dist/bot/apex/radar.js +150 -0
- package/dist/bot/conditions.d.ts +1 -1
- package/dist/bot/config.d.ts +6 -1
- package/dist/bot/config.js +2 -2
- package/dist/bot/engine.d.ts +23 -2
- package/dist/bot/engine.js +331 -351
- package/dist/bot/index.d.ts +16 -0
- package/dist/bot/index.js +10 -0
- package/dist/bot/indicators.d.ts +29 -0
- package/dist/bot/indicators.js +144 -0
- package/dist/bot/reflect.d.ts +34 -0
- package/dist/bot/reflect.js +155 -0
- package/dist/bot/strategies/aggressive-taker.d.ts +36 -0
- package/dist/bot/strategies/aggressive-taker.js +125 -0
- package/dist/bot/strategies/apex-strategy.d.ts +36 -0
- package/dist/bot/strategies/apex-strategy.js +185 -0
- package/dist/bot/strategies/avellaneda-mm.d.ts +27 -0
- package/dist/bot/strategies/avellaneda-mm.js +111 -0
- package/dist/bot/strategies/basis-arb.d.ts +27 -0
- package/dist/bot/strategies/basis-arb.js +97 -0
- package/dist/bot/strategies/claude-agent.d.ts +31 -0
- package/dist/bot/strategies/claude-agent.js +155 -0
- package/dist/bot/strategies/dca-strategy.d.ts +25 -0
- package/dist/bot/strategies/dca-strategy.js +59 -0
- package/dist/bot/strategies/engine-mm.d.ts +29 -0
- package/dist/bot/strategies/engine-mm.js +141 -0
- package/dist/bot/strategies/funding-arb-strategy.d.ts +32 -0
- package/dist/bot/strategies/funding-arb-strategy.js +168 -0
- package/dist/bot/strategies/funding-arb-v2.d.ts +32 -0
- package/dist/bot/strategies/funding-arb-v2.js +222 -0
- package/dist/bot/strategies/funding-auto.d.ts +1 -0
- package/dist/bot/strategies/funding-auto.js +572 -0
- package/dist/bot/strategies/grid-mm.d.ts +25 -0
- package/dist/bot/strategies/grid-mm.js +99 -0
- package/dist/bot/strategies/grid-strategy.d.ts +39 -0
- package/dist/bot/strategies/grid-strategy.js +176 -0
- package/dist/bot/strategies/hedge-agent.d.ts +25 -0
- package/dist/bot/strategies/hedge-agent.js +79 -0
- package/dist/bot/strategies/liquidation-mm.d.ts +25 -0
- package/dist/bot/strategies/liquidation-mm.js +108 -0
- package/dist/bot/strategies/mean-reversion.d.ts +35 -0
- package/dist/bot/strategies/mean-reversion.js +118 -0
- package/dist/bot/strategies/momentum-breakout.d.ts +36 -0
- package/dist/bot/strategies/momentum-breakout.js +159 -0
- package/dist/bot/strategies/regime-mm.d.ts +25 -0
- package/dist/bot/strategies/regime-mm.js +111 -0
- package/dist/bot/strategies/rfq-agent.d.ts +25 -0
- package/dist/bot/strategies/rfq-agent.js +71 -0
- package/dist/bot/strategies/simple-mm.d.ts +25 -0
- package/dist/bot/strategies/simple-mm.js +76 -0
- package/dist/bot/strategy-registry.d.ts +9 -0
- package/dist/bot/strategy-registry.js +14 -0
- package/dist/bot/strategy-types.d.ts +64 -0
- package/dist/bot/strategy-types.js +5 -0
- package/dist/bot/trade-journal.d.ts +27 -0
- package/dist/bot/trade-journal.js +53 -0
- package/dist/bot/tui/BotDashboard.d.ts +56 -0
- package/dist/bot/tui/BotDashboard.js +94 -0
- package/dist/bot/tui/index.d.ts +6 -0
- package/dist/bot/tui/index.js +8 -0
- package/dist/bridge-engine.d.ts +0 -1
- package/dist/bridge-engine.js +0 -1
- package/dist/cache.d.ts +0 -1
- package/dist/cache.js +0 -1
- package/dist/commands/account.d.ts +1 -1
- package/dist/commands/account.js +94 -91
- package/dist/commands/agent.d.ts +1 -1
- package/dist/commands/arb/index.d.ts +1 -1
- package/dist/commands/arb/index.js +1 -1
- package/dist/commands/arb-auto.d.ts +1 -1
- package/dist/commands/arb-auto.js +5 -3
- package/dist/commands/arb.d.ts +2 -1
- package/dist/commands/arb.js +2 -0
- package/dist/commands/bot.d.ts +1 -1
- package/dist/commands/bot.js +236 -15
- package/dist/commands/bridge.js +1 -1
- package/dist/commands/dashboard.d.ts +1 -1
- package/dist/commands/dashboard.js +1 -1
- package/dist/commands/funds.d.ts +1 -1
- package/dist/commands/funds.js +34 -21
- package/dist/commands/history.d.ts +1 -1
- package/dist/commands/history.js +10 -5
- package/dist/commands/manage.d.ts +2 -3
- package/dist/commands/manage.js +11 -18
- package/dist/commands/market.d.ts +1 -1
- package/dist/commands/market.js +26 -17
- package/dist/commands/plan.d.ts +1 -1
- package/dist/commands/rebalance.d.ts +1 -1
- package/dist/commands/rebalance.js +8 -9
- package/dist/commands/risk.d.ts +1 -1
- package/dist/commands/settings.d.ts +1 -1
- package/dist/commands/settings.js +3 -1
- package/dist/commands/trade.d.ts +1 -1
- package/dist/commands/trade.js +42 -55
- package/dist/commands/wallet.d.ts +0 -2
- package/dist/commands/wallet.js +0 -5
- package/dist/constants.d.ts +0 -3
- package/dist/constants.js +0 -4
- package/dist/cross-chain-margin.d.ts +1 -1
- package/dist/dashboard/index.d.ts +4 -0
- package/dist/dashboard/index.js +3 -0
- package/dist/dashboard/server.d.ts +1 -1
- package/dist/dashboard/server.js +1 -1
- package/dist/dashboard/ws-feeds.d.ts +1 -1
- package/dist/event-stream.d.ts +1 -1
- package/dist/exchanges/aster.d.ts +62 -0
- package/dist/exchanges/aster.js +420 -0
- package/dist/exchanges/capabilities.d.ts +74 -0
- package/dist/exchanges/capabilities.js +44 -0
- package/dist/exchanges/edgex.d.ts +62 -0
- package/dist/exchanges/edgex.js +552 -0
- package/dist/exchanges/hyperliquid-spot.d.ts +1 -1
- package/dist/exchanges/hyperliquid-spot.js +20 -12
- package/dist/exchanges/hyperliquid.d.ts +8 -2
- package/dist/exchanges/hyperliquid.js +21 -6
- package/dist/exchanges/index.d.ts +11 -0
- package/dist/exchanges/index.js +10 -0
- package/dist/exchanges/interface.d.ts +3 -0
- package/dist/exchanges/lighter-spot.d.ts +13 -0
- package/dist/exchanges/lighter-spot.js +98 -26
- package/dist/exchanges/lighter.d.ts +9 -3
- package/dist/exchanges/lighter.js +18 -8
- package/dist/exchanges/nado.d.ts +52 -0
- package/dist/exchanges/nado.js +223 -0
- package/dist/exchanges/pacifica.d.ts +7 -1
- package/dist/exchanges/pacifica.js +17 -2
- package/dist/exchanges/registry.d.ts +13 -0
- package/dist/exchanges/registry.js +37 -0
- package/dist/exchanges/spot-interface.d.ts +9 -0
- package/dist/funding/rates.js +1 -1
- package/dist/index.js +2 -6
- package/dist/jobs.js +1 -1
- package/dist/liquidity.d.ts +1 -2
- package/dist/liquidity.js +12 -5
- package/dist/mcp-server.js +4 -2
- package/dist/pacifica/constants.d.ts +0 -3
- package/dist/pacifica/constants.js +0 -4
- package/dist/pacifica/types/account.d.ts +0 -8
- package/dist/pacifica/types/lake.d.ts +0 -6
- package/dist/pacifica/types/position.d.ts +0 -12
- package/dist/pacifica/types/ws.d.ts +0 -24
- package/dist/pacifica/types/ws.js +1 -41
- package/dist/plan-executor.d.ts +1 -1
- package/dist/rebalance.d.ts +1 -7
- package/dist/rebalance.js +0 -6
- package/dist/risk.d.ts +1 -1
- package/dist/risk.js +3 -1
- package/dist/smart-order.d.ts +1 -1
- package/dist/strategies/dca.d.ts +1 -1
- package/dist/strategies/funding-arb.d.ts +1 -1
- package/dist/strategies/funding-arb.js +193 -181
- package/dist/strategies/grid.d.ts +1 -1
- package/dist/strategies/grid.js +1 -1
- package/dist/strategies/index.d.ts +12 -0
- package/dist/strategies/index.js +7 -0
- package/dist/strategies/split-order.d.ts +1 -1
- package/dist/strategies/trailing-stop.d.ts +1 -1
- package/dist/strategies/twap.d.ts +1 -1
- package/dist/trade-validator.d.ts +1 -1
- package/package.json +7 -1
- package/skills/perp-cli/SKILL.md +20 -0
- package/skills/perp-cli/references/commands.md +22 -8
package/dist/dashboard/server.js
CHANGED
|
@@ -318,7 +318,7 @@ export async function startDashboard(exchanges, opts = {}) {
|
|
|
318
318
|
}, { once: true });
|
|
319
319
|
}
|
|
320
320
|
return new Promise((resolve) => {
|
|
321
|
-
server.listen(port, async () => {
|
|
321
|
+
server.listen(port, "127.0.0.1", async () => {
|
|
322
322
|
// Start WS feeds (connects to exchange WS APIs)
|
|
323
323
|
await feedMgr.start();
|
|
324
324
|
// Start arb REST polling (30s cycle)
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Falls back to REST polling if WS connection fails.
|
|
6
6
|
* Arb/market data stays on REST (cross-exchange aggregation, no single WS covers it).
|
|
7
7
|
*/
|
|
8
|
-
import type { ExchangeBalance, ExchangePosition, ExchangeOrder } from "../exchanges/
|
|
8
|
+
import type { ExchangeBalance, ExchangePosition, ExchangeOrder } from "../exchanges/index.js";
|
|
9
9
|
import type { DashboardExchange } from "./server.js";
|
|
10
10
|
export interface WsFeedState {
|
|
11
11
|
balance: ExchangeBalance;
|
package/dist/event-stream.d.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Streams normalized account events as NDJSON (one JSON per line).
|
|
5
5
|
* Uses poll-based diffing for cross-exchange compatibility.
|
|
6
6
|
*/
|
|
7
|
-
import type { ExchangeAdapter } from "./exchanges/
|
|
7
|
+
import type { ExchangeAdapter } from "./exchanges/index.js";
|
|
8
8
|
export type EventType = "position_opened" | "position_closed" | "position_updated" | "order_placed" | "order_filled" | "order_cancelled" | "balance_update" | "liquidation_warning" | "margin_call" | "heartbeat" | "error";
|
|
9
9
|
export interface StreamEvent {
|
|
10
10
|
type: EventType;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Aster DEX adapter — Binance Futures API compatible REST wrapper.
|
|
3
|
+
* Docs: https://docs.asterdex.com/product/aster-perpetuals/api/api-documentation
|
|
4
|
+
* Base: https://fapi.asterdex.com
|
|
5
|
+
*/
|
|
6
|
+
import type { ExchangeAdapter, ExchangeMarketInfo, ExchangePosition, ExchangeOrder, ExchangeBalance, ExchangeTrade, ExchangeFundingPayment, ExchangeKline } from "./interface.js";
|
|
7
|
+
export declare class AsterAdapter implements ExchangeAdapter {
|
|
8
|
+
readonly name = "aster";
|
|
9
|
+
readonly chain = "bnb";
|
|
10
|
+
readonly aliases: readonly ["ast"];
|
|
11
|
+
private _apiKey;
|
|
12
|
+
private _apiSecret;
|
|
13
|
+
private _baseUrl;
|
|
14
|
+
private _testnet;
|
|
15
|
+
constructor(apiKey?: string, apiSecret?: string, testnet?: boolean);
|
|
16
|
+
get isReadOnly(): boolean;
|
|
17
|
+
/** CLI symbol → Aster API symbol (ETH → ETHUSDT) */
|
|
18
|
+
private _toApi;
|
|
19
|
+
/** Aster API symbol → CLI symbol (ETHUSDT → ETH) */
|
|
20
|
+
private _fromApi;
|
|
21
|
+
init(): Promise<void>;
|
|
22
|
+
getMarkets(): Promise<ExchangeMarketInfo[]>;
|
|
23
|
+
getOrderbook(symbol: string): Promise<{
|
|
24
|
+
bids: [string, string][];
|
|
25
|
+
asks: [string, string][];
|
|
26
|
+
}>;
|
|
27
|
+
getRecentTrades(symbol: string, limit?: number): Promise<ExchangeTrade[]>;
|
|
28
|
+
getFundingHistory(symbol: string, limit?: number): Promise<{
|
|
29
|
+
time: number;
|
|
30
|
+
rate: string;
|
|
31
|
+
price: string | null;
|
|
32
|
+
}[]>;
|
|
33
|
+
getKlines(symbol: string, interval: string, startTime: number, endTime: number): Promise<ExchangeKline[]>;
|
|
34
|
+
getBalance(): Promise<ExchangeBalance>;
|
|
35
|
+
getPositions(): Promise<ExchangePosition[]>;
|
|
36
|
+
getOpenOrders(): Promise<ExchangeOrder[]>;
|
|
37
|
+
getOrderHistory(limit?: number): Promise<ExchangeOrder[]>;
|
|
38
|
+
getTradeHistory(limit?: number): Promise<ExchangeTrade[]>;
|
|
39
|
+
getFundingPayments(limit?: number): Promise<ExchangeFundingPayment[]>;
|
|
40
|
+
marketOrder(symbol: string, side: "buy" | "sell", size: string): Promise<unknown>;
|
|
41
|
+
limitOrder(symbol: string, side: "buy" | "sell", price: string, size: string, opts?: {
|
|
42
|
+
reduceOnly?: boolean;
|
|
43
|
+
tif?: string;
|
|
44
|
+
}): Promise<unknown>;
|
|
45
|
+
editOrder(symbol: string, orderId: string, price: string, size: string): Promise<unknown>;
|
|
46
|
+
cancelOrder(symbol: string, orderId: string): Promise<unknown>;
|
|
47
|
+
cancelAllOrders(symbol?: string): Promise<unknown>;
|
|
48
|
+
setLeverage(symbol: string, leverage: number, marginMode?: "cross" | "isolated"): Promise<unknown>;
|
|
49
|
+
stopOrder(symbol: string, side: "buy" | "sell", size: string, triggerPrice: string, opts?: {
|
|
50
|
+
limitPrice?: string;
|
|
51
|
+
reduceOnly?: boolean;
|
|
52
|
+
}): Promise<unknown>;
|
|
53
|
+
withdraw(amount: string, _destination: string, _opts?: {
|
|
54
|
+
assetId?: number;
|
|
55
|
+
routeType?: number;
|
|
56
|
+
}): Promise<unknown>;
|
|
57
|
+
private _sign;
|
|
58
|
+
private _publicGet;
|
|
59
|
+
private _signedGet;
|
|
60
|
+
private _signedPost;
|
|
61
|
+
private _signedDelete;
|
|
62
|
+
}
|
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Aster DEX adapter — Binance Futures API compatible REST wrapper.
|
|
3
|
+
* Docs: https://docs.asterdex.com/product/aster-perpetuals/api/api-documentation
|
|
4
|
+
* Base: https://fapi.asterdex.com
|
|
5
|
+
*/
|
|
6
|
+
import { createHmac } from "node:crypto";
|
|
7
|
+
export class AsterAdapter {
|
|
8
|
+
name = "aster";
|
|
9
|
+
chain = "bnb";
|
|
10
|
+
aliases = ["ast"];
|
|
11
|
+
_apiKey;
|
|
12
|
+
_apiSecret;
|
|
13
|
+
_baseUrl;
|
|
14
|
+
_testnet;
|
|
15
|
+
constructor(apiKey, apiSecret, testnet = false) {
|
|
16
|
+
this._apiKey = apiKey || process.env.ASTER_API_KEY || "";
|
|
17
|
+
this._apiSecret = apiSecret || process.env.ASTER_API_SECRET || "";
|
|
18
|
+
this._testnet = testnet;
|
|
19
|
+
this._baseUrl = testnet
|
|
20
|
+
? (process.env.ASTER_TESTNET_URL || "https://testnet.asterdex.com")
|
|
21
|
+
: "https://fapi.asterdex.com";
|
|
22
|
+
}
|
|
23
|
+
get isReadOnly() {
|
|
24
|
+
return !this._apiKey || !this._apiSecret;
|
|
25
|
+
}
|
|
26
|
+
/** CLI symbol → Aster API symbol (ETH → ETHUSDT) */
|
|
27
|
+
_toApi(symbol) {
|
|
28
|
+
const s = symbol.toUpperCase().replace(/-PERP$/, "");
|
|
29
|
+
if (s.endsWith("USDT") || s.endsWith("BUSD"))
|
|
30
|
+
return s;
|
|
31
|
+
return `${s}USDT`;
|
|
32
|
+
}
|
|
33
|
+
/** Aster API symbol → CLI symbol (ETHUSDT → ETH) */
|
|
34
|
+
_fromApi(symbol) {
|
|
35
|
+
return symbol.replace(/USDT$/, "").replace(/BUSD$/, "");
|
|
36
|
+
}
|
|
37
|
+
async init() {
|
|
38
|
+
// Verify connectivity by fetching server time
|
|
39
|
+
await this._publicGet("/fapi/v1/time");
|
|
40
|
+
}
|
|
41
|
+
// ── Market Data ──
|
|
42
|
+
async getMarkets() {
|
|
43
|
+
const [info, tickers] = await Promise.all([
|
|
44
|
+
this._publicGet("/fapi/v1/exchangeInfo"),
|
|
45
|
+
this._publicGet("/fapi/v1/ticker/24hr"),
|
|
46
|
+
]);
|
|
47
|
+
const tickerMap = new Map();
|
|
48
|
+
for (const t of tickers ?? []) {
|
|
49
|
+
tickerMap.set(String(t.symbol), t);
|
|
50
|
+
}
|
|
51
|
+
// Also fetch premium index for mark/index/funding
|
|
52
|
+
let premiumMap = new Map();
|
|
53
|
+
try {
|
|
54
|
+
const premiums = await this._publicGet("/fapi/v1/premiumIndex");
|
|
55
|
+
premiumMap = new Map(premiums.map(p => [String(p.symbol), p]));
|
|
56
|
+
}
|
|
57
|
+
catch { /* non-critical */ }
|
|
58
|
+
const tradingSymbols = (info?.symbols ?? [])
|
|
59
|
+
.filter((s) => String(s.contractType) === "PERPETUAL" && String(s.status) === "TRADING");
|
|
60
|
+
// Fetch open interest per symbol in parallel (batched to avoid overwhelming the API).
|
|
61
|
+
// The /fapi/v1/ticker/24hr response does NOT include openInterest — need a separate endpoint.
|
|
62
|
+
const oiMap = new Map();
|
|
63
|
+
const OI_BATCH = 20;
|
|
64
|
+
for (let i = 0; i < tradingSymbols.length; i += OI_BATCH) {
|
|
65
|
+
const batch = tradingSymbols.slice(i, i + OI_BATCH);
|
|
66
|
+
const results = await Promise.allSettled(batch.map(async (s) => {
|
|
67
|
+
const sym = String(s.symbol);
|
|
68
|
+
const res = await this._publicGet("/fapi/v1/openInterest", { symbol: sym });
|
|
69
|
+
return { sym, oi: String(res?.openInterest ?? "0") };
|
|
70
|
+
}));
|
|
71
|
+
for (const r of results) {
|
|
72
|
+
if (r.status === "fulfilled")
|
|
73
|
+
oiMap.set(r.value.sym, r.value.oi);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return tradingSymbols
|
|
77
|
+
.map((s) => {
|
|
78
|
+
const sym = String(s.symbol);
|
|
79
|
+
const ticker = tickerMap.get(sym);
|
|
80
|
+
const premium = premiumMap.get(sym);
|
|
81
|
+
// Extract max leverage from leverage brackets if available
|
|
82
|
+
const maxLev = Number(s.maxLeverage ?? 50);
|
|
83
|
+
return {
|
|
84
|
+
symbol: this._fromApi(sym),
|
|
85
|
+
markPrice: String(premium?.markPrice ?? ticker?.lastPrice ?? "0"),
|
|
86
|
+
indexPrice: String(premium?.indexPrice ?? "0"),
|
|
87
|
+
fundingRate: String(premium?.lastFundingRate ?? "0"),
|
|
88
|
+
volume24h: String(ticker?.quoteVolume ?? ticker?.volume ?? "0"),
|
|
89
|
+
openInterest: oiMap.get(sym) ?? "0",
|
|
90
|
+
maxLeverage: maxLev,
|
|
91
|
+
};
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
async getOrderbook(symbol) {
|
|
95
|
+
const res = await this._publicGet("/fapi/v1/depth", { symbol: this._toApi(symbol), limit: "50" });
|
|
96
|
+
return {
|
|
97
|
+
bids: res?.bids ?? [],
|
|
98
|
+
asks: res?.asks ?? [],
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
async getRecentTrades(symbol, limit = 20) {
|
|
102
|
+
const trades = await this._publicGet("/fapi/v1/trades", {
|
|
103
|
+
symbol: this._toApi(symbol),
|
|
104
|
+
limit: String(limit),
|
|
105
|
+
});
|
|
106
|
+
return (trades ?? []).map((t) => ({
|
|
107
|
+
time: Number(t.time ?? 0),
|
|
108
|
+
symbol: this._fromApi(String(t.symbol ?? symbol)),
|
|
109
|
+
side: t.isBuyerMaker ? "sell" : "buy",
|
|
110
|
+
price: String(t.price ?? "0"),
|
|
111
|
+
size: String(t.qty ?? "0"),
|
|
112
|
+
fee: "0",
|
|
113
|
+
}));
|
|
114
|
+
}
|
|
115
|
+
async getFundingHistory(symbol, limit = 10) {
|
|
116
|
+
const data = await this._publicGet("/fapi/v1/fundingRate", {
|
|
117
|
+
symbol: this._toApi(symbol),
|
|
118
|
+
limit: String(limit),
|
|
119
|
+
});
|
|
120
|
+
return (data ?? []).map((d) => ({
|
|
121
|
+
time: Number(d.fundingTime ?? 0),
|
|
122
|
+
rate: String(d.fundingRate ?? "0"),
|
|
123
|
+
price: d.markPrice ? String(d.markPrice) : null,
|
|
124
|
+
}));
|
|
125
|
+
}
|
|
126
|
+
async getKlines(symbol, interval, startTime, endTime) {
|
|
127
|
+
const data = await this._publicGet("/fapi/v1/klines", {
|
|
128
|
+
symbol: this._toApi(symbol),
|
|
129
|
+
interval,
|
|
130
|
+
startTime: String(startTime),
|
|
131
|
+
endTime: String(endTime),
|
|
132
|
+
limit: "500",
|
|
133
|
+
});
|
|
134
|
+
return (data ?? []).map((k) => ({
|
|
135
|
+
time: Number(k[0]),
|
|
136
|
+
open: String(k[1]),
|
|
137
|
+
high: String(k[2]),
|
|
138
|
+
low: String(k[3]),
|
|
139
|
+
close: String(k[4]),
|
|
140
|
+
volume: String(k[5]),
|
|
141
|
+
trades: Number(k[8] ?? 0),
|
|
142
|
+
}));
|
|
143
|
+
}
|
|
144
|
+
// ── Account ──
|
|
145
|
+
async getBalance() {
|
|
146
|
+
const account = await this._signedGet("/fapi/v2/account");
|
|
147
|
+
const totalWallet = Number(account.totalWalletBalance ?? 0);
|
|
148
|
+
const unrealizedPnl = Number(account.totalUnrealizedProfit ?? 0);
|
|
149
|
+
const available = Number(account.availableBalance ?? 0);
|
|
150
|
+
const marginUsed = Number(account.totalInitialMargin ?? 0);
|
|
151
|
+
return {
|
|
152
|
+
equity: String(totalWallet + unrealizedPnl),
|
|
153
|
+
available: String(available),
|
|
154
|
+
marginUsed: String(marginUsed),
|
|
155
|
+
unrealizedPnl: String(unrealizedPnl),
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
async getPositions() {
|
|
159
|
+
const data = await this._signedGet("/fapi/v2/positionRisk");
|
|
160
|
+
return (data ?? [])
|
|
161
|
+
.filter((p) => Number(p.positionAmt ?? 0) !== 0)
|
|
162
|
+
.map((p) => {
|
|
163
|
+
const amt = Number(p.positionAmt ?? 0);
|
|
164
|
+
return {
|
|
165
|
+
symbol: this._fromApi(String(p.symbol ?? "")),
|
|
166
|
+
side: amt > 0 ? "long" : "short",
|
|
167
|
+
size: String(Math.abs(amt)),
|
|
168
|
+
entryPrice: String(p.entryPrice ?? "0"),
|
|
169
|
+
markPrice: String(p.markPrice ?? "0"),
|
|
170
|
+
liquidationPrice: String(p.liquidationPrice ?? "0"),
|
|
171
|
+
unrealizedPnl: String(p.unRealizedProfit ?? "0"),
|
|
172
|
+
leverage: Number(p.leverage ?? 1),
|
|
173
|
+
};
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
async getOpenOrders() {
|
|
177
|
+
const orders = await this._signedGet("/fapi/v1/openOrders");
|
|
178
|
+
return (orders ?? []).map((o) => ({
|
|
179
|
+
orderId: String(o.orderId ?? ""),
|
|
180
|
+
symbol: this._fromApi(String(o.symbol ?? "")),
|
|
181
|
+
side: String(o.side).toLowerCase(),
|
|
182
|
+
price: String(o.price ?? "0"),
|
|
183
|
+
size: String(o.origQty ?? "0"),
|
|
184
|
+
filled: String(o.executedQty ?? "0"),
|
|
185
|
+
status: String(o.status ?? ""),
|
|
186
|
+
type: String(o.type ?? ""),
|
|
187
|
+
}));
|
|
188
|
+
}
|
|
189
|
+
async getOrderHistory(limit = 30) {
|
|
190
|
+
// allOrders requires symbol — get from positions or recent trades
|
|
191
|
+
const positions = await this.getPositions();
|
|
192
|
+
const apiSymbols = new Set(positions.map(p => this._toApi(p.symbol)));
|
|
193
|
+
if (apiSymbols.size === 0) {
|
|
194
|
+
// Fallback: query the most common perp symbols when no open positions exist
|
|
195
|
+
for (const s of ["BTCUSDT", "ETHUSDT", "SOLUSDT", "BNBUSDT", "XRPUSDT"]) {
|
|
196
|
+
apiSymbols.add(s);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
const allOrders = [];
|
|
200
|
+
for (const sym of apiSymbols) {
|
|
201
|
+
try {
|
|
202
|
+
const orders = await this._signedGet("/fapi/v1/allOrders", {
|
|
203
|
+
symbol: sym,
|
|
204
|
+
limit: String(limit),
|
|
205
|
+
});
|
|
206
|
+
for (const o of orders ?? []) {
|
|
207
|
+
allOrders.push({
|
|
208
|
+
orderId: String(o.orderId ?? ""),
|
|
209
|
+
symbol: this._fromApi(String(o.symbol ?? "")),
|
|
210
|
+
side: String(o.side).toLowerCase(),
|
|
211
|
+
price: String(o.price ?? "0"),
|
|
212
|
+
size: String(o.origQty ?? "0"),
|
|
213
|
+
filled: String(o.executedQty ?? "0"),
|
|
214
|
+
status: String(o.status ?? ""),
|
|
215
|
+
type: String(o.type ?? ""),
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
catch { /* skip */ }
|
|
220
|
+
}
|
|
221
|
+
return allOrders.slice(0, limit);
|
|
222
|
+
}
|
|
223
|
+
async getTradeHistory(limit = 30) {
|
|
224
|
+
const positions = await this.getPositions();
|
|
225
|
+
const apiSymbols = new Set(positions.map(p => this._toApi(p.symbol)));
|
|
226
|
+
if (apiSymbols.size === 0)
|
|
227
|
+
apiSymbols.add("BTCUSDT");
|
|
228
|
+
const allTrades = [];
|
|
229
|
+
for (const sym of apiSymbols) {
|
|
230
|
+
try {
|
|
231
|
+
const trades = await this._signedGet("/fapi/v1/userTrades", {
|
|
232
|
+
symbol: sym,
|
|
233
|
+
limit: String(limit),
|
|
234
|
+
});
|
|
235
|
+
for (const t of trades ?? []) {
|
|
236
|
+
allTrades.push({
|
|
237
|
+
time: Number(t.time ?? 0),
|
|
238
|
+
symbol: this._fromApi(String(t.symbol ?? "")),
|
|
239
|
+
side: t.buyer ? "buy" : "sell",
|
|
240
|
+
price: String(t.price ?? "0"),
|
|
241
|
+
size: String(t.qty ?? "0"),
|
|
242
|
+
fee: String(t.commission ?? "0"),
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
catch { /* skip */ }
|
|
247
|
+
}
|
|
248
|
+
return allTrades.sort((a, b) => b.time - a.time).slice(0, limit);
|
|
249
|
+
}
|
|
250
|
+
async getFundingPayments(limit = 200) {
|
|
251
|
+
const data = await this._signedGet("/fapi/v1/income", {
|
|
252
|
+
incomeType: "FUNDING_FEE",
|
|
253
|
+
limit: String(limit),
|
|
254
|
+
});
|
|
255
|
+
return (data ?? []).map((f) => ({
|
|
256
|
+
time: Number(f.time ?? 0),
|
|
257
|
+
symbol: this._fromApi(String(f.symbol ?? "")),
|
|
258
|
+
payment: String(f.income ?? "0"),
|
|
259
|
+
}));
|
|
260
|
+
}
|
|
261
|
+
// ── Trading ──
|
|
262
|
+
async marketOrder(symbol, side, size) {
|
|
263
|
+
return this._signedPost("/fapi/v1/order", {
|
|
264
|
+
symbol: this._toApi(symbol),
|
|
265
|
+
side: side.toUpperCase(),
|
|
266
|
+
type: "MARKET",
|
|
267
|
+
quantity: size,
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
async limitOrder(symbol, side, price, size, opts) {
|
|
271
|
+
const params = {
|
|
272
|
+
symbol: this._toApi(symbol),
|
|
273
|
+
side: side.toUpperCase(),
|
|
274
|
+
type: "LIMIT",
|
|
275
|
+
price,
|
|
276
|
+
quantity: size,
|
|
277
|
+
timeInForce: opts?.tif?.toUpperCase() || "GTC",
|
|
278
|
+
};
|
|
279
|
+
if (opts?.reduceOnly)
|
|
280
|
+
params.reduceOnly = "true";
|
|
281
|
+
return this._signedPost("/fapi/v1/order", params);
|
|
282
|
+
}
|
|
283
|
+
async editOrder(symbol, orderId, price, size) {
|
|
284
|
+
// Aster has no atomic edit — cancel + replace
|
|
285
|
+
const openOrders = await this.getOpenOrders();
|
|
286
|
+
const existing = openOrders.find(o => o.orderId === orderId);
|
|
287
|
+
const side = existing?.side ?? "buy";
|
|
288
|
+
await this.cancelOrder(symbol, orderId);
|
|
289
|
+
return this.limitOrder(symbol, side, price, size);
|
|
290
|
+
}
|
|
291
|
+
async cancelOrder(symbol, orderId) {
|
|
292
|
+
return this._signedDelete("/fapi/v1/order", {
|
|
293
|
+
symbol: this._toApi(symbol),
|
|
294
|
+
orderId,
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
async cancelAllOrders(symbol) {
|
|
298
|
+
if (!symbol) {
|
|
299
|
+
// Cancel all for all symbols with open orders
|
|
300
|
+
const orders = await this.getOpenOrders();
|
|
301
|
+
const apiSymbols = new Set(orders.map(o => this._toApi(o.symbol)));
|
|
302
|
+
const results = [];
|
|
303
|
+
for (const sym of apiSymbols) {
|
|
304
|
+
results.push(await this._signedDelete("/fapi/v1/allOpenOrders", { symbol: sym }));
|
|
305
|
+
}
|
|
306
|
+
return results;
|
|
307
|
+
}
|
|
308
|
+
return this._signedDelete("/fapi/v1/allOpenOrders", { symbol: this._toApi(symbol) });
|
|
309
|
+
}
|
|
310
|
+
// ── Risk ──
|
|
311
|
+
async setLeverage(symbol, leverage, marginMode) {
|
|
312
|
+
// Set margin type first if specified
|
|
313
|
+
if (marginMode) {
|
|
314
|
+
try {
|
|
315
|
+
await this._signedPost("/fapi/v1/marginType", {
|
|
316
|
+
symbol: this._toApi(symbol),
|
|
317
|
+
marginType: marginMode === "cross" ? "CROSSED" : "ISOLATED",
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
catch {
|
|
321
|
+
// May fail if already set — non-critical
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
return this._signedPost("/fapi/v1/leverage", {
|
|
325
|
+
symbol: this._toApi(symbol),
|
|
326
|
+
leverage: String(leverage),
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
async stopOrder(symbol, side, size, triggerPrice, opts) {
|
|
330
|
+
const params = {
|
|
331
|
+
symbol: this._toApi(symbol),
|
|
332
|
+
side: side.toUpperCase(),
|
|
333
|
+
quantity: size,
|
|
334
|
+
stopPrice: triggerPrice,
|
|
335
|
+
type: opts?.limitPrice ? "STOP" : "STOP_MARKET",
|
|
336
|
+
};
|
|
337
|
+
if (opts?.limitPrice) {
|
|
338
|
+
params.price = opts.limitPrice;
|
|
339
|
+
params.timeInForce = "GTC";
|
|
340
|
+
}
|
|
341
|
+
if (opts?.reduceOnly)
|
|
342
|
+
params.reduceOnly = "true";
|
|
343
|
+
return this._signedPost("/fapi/v1/order", params);
|
|
344
|
+
}
|
|
345
|
+
// ── Withdraw (unified interface) ──
|
|
346
|
+
async withdraw(amount, _destination, _opts) {
|
|
347
|
+
// Aster withdrawal is via separate spot/wallet API, not futures API
|
|
348
|
+
throw new Error("Aster withdrawal requires the spot API (not available in futures mode). Use the Aster web UI to withdraw.");
|
|
349
|
+
}
|
|
350
|
+
// ── Internal HTTP helpers ──
|
|
351
|
+
_sign(params) {
|
|
352
|
+
params.timestamp = String(Date.now());
|
|
353
|
+
params.recvWindow = "5000";
|
|
354
|
+
const queryString = Object.entries(params)
|
|
355
|
+
.map(([k, v]) => `${k}=${encodeURIComponent(v)}`)
|
|
356
|
+
.join("&");
|
|
357
|
+
const signature = createHmac("sha256", this._apiSecret)
|
|
358
|
+
.update(queryString)
|
|
359
|
+
.digest("hex");
|
|
360
|
+
return `${queryString}&signature=${signature}`;
|
|
361
|
+
}
|
|
362
|
+
async _publicGet(path, params = {}) {
|
|
363
|
+
const qs = Object.entries(params)
|
|
364
|
+
.map(([k, v]) => `${k}=${encodeURIComponent(v)}`)
|
|
365
|
+
.join("&");
|
|
366
|
+
const url = `${this._baseUrl}${path}${qs ? `?${qs}` : ""}`;
|
|
367
|
+
const res = await fetch(url);
|
|
368
|
+
if (!res.ok) {
|
|
369
|
+
const text = await res.text();
|
|
370
|
+
throw new Error(`GET ${path} failed (${res.status}): ${text.slice(0, 200)}`);
|
|
371
|
+
}
|
|
372
|
+
return res.json();
|
|
373
|
+
}
|
|
374
|
+
async _signedGet(path, params = {}) {
|
|
375
|
+
if (this.isReadOnly)
|
|
376
|
+
throw new Error("No API key configured for Aster. Set ASTER_API_KEY and ASTER_API_SECRET in ~/.perp/.env");
|
|
377
|
+
const signed = this._sign({ ...params });
|
|
378
|
+
const url = `${this._baseUrl}${path}?${signed}`;
|
|
379
|
+
const res = await fetch(url, {
|
|
380
|
+
headers: { "X-MBX-APIKEY": this._apiKey },
|
|
381
|
+
});
|
|
382
|
+
if (!res.ok) {
|
|
383
|
+
const text = await res.text();
|
|
384
|
+
throw new Error(`GET ${path} failed (${res.status}): ${text.slice(0, 200)}`);
|
|
385
|
+
}
|
|
386
|
+
return res.json();
|
|
387
|
+
}
|
|
388
|
+
async _signedPost(path, params = {}) {
|
|
389
|
+
if (this.isReadOnly)
|
|
390
|
+
throw new Error("No API key configured for Aster. Set ASTER_API_KEY and ASTER_API_SECRET in ~/.perp/.env");
|
|
391
|
+
const signed = this._sign({ ...params });
|
|
392
|
+
const res = await fetch(`${this._baseUrl}${path}`, {
|
|
393
|
+
method: "POST",
|
|
394
|
+
headers: {
|
|
395
|
+
"X-MBX-APIKEY": this._apiKey,
|
|
396
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
397
|
+
},
|
|
398
|
+
body: signed,
|
|
399
|
+
});
|
|
400
|
+
if (!res.ok) {
|
|
401
|
+
const text = await res.text();
|
|
402
|
+
throw new Error(`POST ${path} failed (${res.status}): ${text.slice(0, 200)}`);
|
|
403
|
+
}
|
|
404
|
+
return res.json();
|
|
405
|
+
}
|
|
406
|
+
async _signedDelete(path, params = {}) {
|
|
407
|
+
if (this.isReadOnly)
|
|
408
|
+
throw new Error("No API key configured for Aster. Set ASTER_API_KEY and ASTER_API_SECRET in ~/.perp/.env");
|
|
409
|
+
const signed = this._sign({ ...params });
|
|
410
|
+
const res = await fetch(`${this._baseUrl}${path}?${signed}`, {
|
|
411
|
+
method: "DELETE",
|
|
412
|
+
headers: { "X-MBX-APIKEY": this._apiKey },
|
|
413
|
+
});
|
|
414
|
+
if (!res.ok) {
|
|
415
|
+
const text = await res.text();
|
|
416
|
+
throw new Error(`DELETE ${path} failed (${res.status}): ${text.slice(0, 200)}`);
|
|
417
|
+
}
|
|
418
|
+
return res.json();
|
|
419
|
+
}
|
|
420
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Capability sub-interfaces for exchange-specific features.
|
|
3
|
+
* Use type guard functions (isTwapCapable, hasPacificaSdk, etc.) instead of instanceof checks.
|
|
4
|
+
*/
|
|
5
|
+
import type { ExchangeAdapter } from "./interface.js";
|
|
6
|
+
export interface WithdrawCapable {
|
|
7
|
+
withdraw(amount: string, destination: string, opts?: {
|
|
8
|
+
assetId?: number;
|
|
9
|
+
routeType?: number;
|
|
10
|
+
}): Promise<unknown>;
|
|
11
|
+
}
|
|
12
|
+
export interface TwapCapable {
|
|
13
|
+
twapOrder(symbol: string, side: "buy" | "sell", size: string, duration: number, opts?: {
|
|
14
|
+
reduceOnly?: boolean;
|
|
15
|
+
}): Promise<unknown>;
|
|
16
|
+
twapCancel(symbol: string, twapId: number): Promise<unknown>;
|
|
17
|
+
}
|
|
18
|
+
export interface TpSlCapable {
|
|
19
|
+
setTpSl(symbol: string, side: "buy" | "sell", opts: {
|
|
20
|
+
tp?: string;
|
|
21
|
+
tpLimit?: string;
|
|
22
|
+
sl?: string;
|
|
23
|
+
size?: string;
|
|
24
|
+
}): Promise<unknown>;
|
|
25
|
+
}
|
|
26
|
+
export interface TriggerOrderCapable {
|
|
27
|
+
triggerOrder(symbol: string, side: "buy" | "sell", size: string, triggerPrice: string, type: string, opts?: Record<string, unknown>): Promise<unknown>;
|
|
28
|
+
}
|
|
29
|
+
export interface DexCapable {
|
|
30
|
+
readonly dex: string;
|
|
31
|
+
setDex(dex: string): void;
|
|
32
|
+
listDeployedDexes(): Promise<{
|
|
33
|
+
name: string;
|
|
34
|
+
deployer: string;
|
|
35
|
+
assets: string[];
|
|
36
|
+
}[]>;
|
|
37
|
+
}
|
|
38
|
+
export interface SubAccountCapable {
|
|
39
|
+
createSubAccount(name: string): Promise<unknown>;
|
|
40
|
+
subAccountTransfer(subAccountUser: string, isDeposit: boolean, amount: number): Promise<unknown>;
|
|
41
|
+
}
|
|
42
|
+
export interface PacificaSdkCapable {
|
|
43
|
+
readonly sdk: unknown;
|
|
44
|
+
readonly publicKey: string;
|
|
45
|
+
readonly signer: (msg: Uint8Array) => Promise<Uint8Array>;
|
|
46
|
+
}
|
|
47
|
+
export interface LighterAccountCapable {
|
|
48
|
+
readonly accountIndex: number;
|
|
49
|
+
readonly address: string;
|
|
50
|
+
setupApiKey(apiKeyIndex?: number): Promise<{
|
|
51
|
+
privateKey: string;
|
|
52
|
+
publicKey: string;
|
|
53
|
+
}>;
|
|
54
|
+
}
|
|
55
|
+
export interface EvmAddressCapable {
|
|
56
|
+
readonly address: string;
|
|
57
|
+
}
|
|
58
|
+
export interface QueryOrderCapable {
|
|
59
|
+
queryOrder(orderId: number): Promise<unknown>;
|
|
60
|
+
}
|
|
61
|
+
export interface UsdTransferCapable {
|
|
62
|
+
usdTransfer(amount: number, destination: string): Promise<unknown>;
|
|
63
|
+
}
|
|
64
|
+
export declare function isWithdrawCapable(adapter: ExchangeAdapter): adapter is ExchangeAdapter & WithdrawCapable;
|
|
65
|
+
export declare function isTwapCapable(adapter: ExchangeAdapter): adapter is ExchangeAdapter & TwapCapable;
|
|
66
|
+
export declare function isTpSlCapable(adapter: ExchangeAdapter): adapter is ExchangeAdapter & TpSlCapable;
|
|
67
|
+
export declare function isTriggerOrderCapable(adapter: ExchangeAdapter): adapter is ExchangeAdapter & TriggerOrderCapable;
|
|
68
|
+
export declare function isDexCapable(adapter: ExchangeAdapter): adapter is ExchangeAdapter & DexCapable;
|
|
69
|
+
export declare function isSubAccountCapable(adapter: ExchangeAdapter): adapter is ExchangeAdapter & SubAccountCapable;
|
|
70
|
+
export declare function hasPacificaSdk(adapter: ExchangeAdapter): adapter is ExchangeAdapter & PacificaSdkCapable;
|
|
71
|
+
export declare function hasLighterAccount(adapter: ExchangeAdapter): adapter is ExchangeAdapter & LighterAccountCapable;
|
|
72
|
+
export declare function hasEvmAddress(adapter: ExchangeAdapter): adapter is ExchangeAdapter & EvmAddressCapable;
|
|
73
|
+
export declare function isQueryOrderCapable(adapter: ExchangeAdapter): adapter is ExchangeAdapter & QueryOrderCapable;
|
|
74
|
+
export declare function isUsdTransferCapable(adapter: ExchangeAdapter): adapter is ExchangeAdapter & UsdTransferCapable;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Capability sub-interfaces for exchange-specific features.
|
|
3
|
+
* Use type guard functions (isTwapCapable, hasPacificaSdk, etc.) instead of instanceof checks.
|
|
4
|
+
*/
|
|
5
|
+
// ── Type Guards ──
|
|
6
|
+
export function isWithdrawCapable(adapter) {
|
|
7
|
+
return typeof adapter.withdraw === "function";
|
|
8
|
+
}
|
|
9
|
+
export function isTwapCapable(adapter) {
|
|
10
|
+
return typeof adapter.twapOrder === "function"
|
|
11
|
+
&& typeof adapter.twapCancel === "function";
|
|
12
|
+
}
|
|
13
|
+
export function isTpSlCapable(adapter) {
|
|
14
|
+
return typeof adapter.setTpSl === "function";
|
|
15
|
+
}
|
|
16
|
+
export function isTriggerOrderCapable(adapter) {
|
|
17
|
+
return typeof adapter.triggerOrder === "function";
|
|
18
|
+
}
|
|
19
|
+
export function isDexCapable(adapter) {
|
|
20
|
+
return typeof adapter.setDex === "function"
|
|
21
|
+
&& typeof adapter.listDeployedDexes === "function";
|
|
22
|
+
}
|
|
23
|
+
export function isSubAccountCapable(adapter) {
|
|
24
|
+
return typeof adapter.createSubAccount === "function";
|
|
25
|
+
}
|
|
26
|
+
export function hasPacificaSdk(adapter) {
|
|
27
|
+
return adapter.name === "pacifica"
|
|
28
|
+
&& adapter.sdk != null
|
|
29
|
+
&& typeof adapter.sdk === "object"
|
|
30
|
+
&& typeof adapter.publicKey === "string";
|
|
31
|
+
}
|
|
32
|
+
export function hasLighterAccount(adapter) {
|
|
33
|
+
return typeof adapter.accountIndex === "number"
|
|
34
|
+
&& typeof adapter.setupApiKey === "function";
|
|
35
|
+
}
|
|
36
|
+
export function hasEvmAddress(adapter) {
|
|
37
|
+
return typeof adapter.address === "string";
|
|
38
|
+
}
|
|
39
|
+
export function isQueryOrderCapable(adapter) {
|
|
40
|
+
return typeof adapter.queryOrder === "function";
|
|
41
|
+
}
|
|
42
|
+
export function isUsdTransferCapable(adapter) {
|
|
43
|
+
return typeof adapter.usdTransfer === "function";
|
|
44
|
+
}
|