refactor: rename Aster-prefixed universal types to clean names

- AsterOrder → Order
- AsterAccountSnapshot → AccountSnapshot
- AsterAccountPosition → AccountPosition
- AsterAccountAsset → AccountAsset
- AsterDepthLevel → DepthLevel
- AsterDepth → Depth
- AsterTicker → Ticker
- AsterKline → Kline

These types are the platform-agnostic contract used by all 8 exchanges,
not Aster-specific. Renamed across 63 files.
This commit is contained in:
discountry
2026-04-06 18:11:57 +08:00
parent 230c0d5e18
commit fda6bcad1d
62 changed files with 727 additions and 727 deletions
+5 -5
View File
@@ -15,7 +15,7 @@ import {
routeTrailingStopOrder, routeTrailingStopOrder,
} from "../exchanges/order-router"; } from "../exchanges/order-router";
import { buildAdapterFromEnv } from "../exchanges/resolve-from-env"; import { buildAdapterFromEnv } from "../exchanges/resolve-from-env";
import type { AsterDepth, AsterKline, AsterOrder, AsterTicker } from "../exchanges/types"; import type { Depth, Kline, Order, Ticker } from "../exchanges/types";
import { startStrategy } from "./strategy-runner"; import { startStrategy } from "./strategy-runner";
import type { import type {
CommandErrorPayload, CommandErrorPayload,
@@ -266,7 +266,7 @@ async function handleMarketTicker(
buildAdapterFromEnvFn: typeof buildAdapterFromEnv buildAdapterFromEnvFn: typeof buildAdapterFromEnv
): Promise<unknown> { ): Promise<unknown> {
const { adapter, exchange, symbol } = createAdapterContext(command, buildAdapterFromEnvFn); const { adapter, exchange, symbol } = createAdapterContext(command, buildAdapterFromEnvFn);
const ticker = await waitForFirst<AsterTicker>( const ticker = await waitForFirst<Ticker>(
(cb) => adapter.watchTicker(symbol, cb), (cb) => adapter.watchTicker(symbol, cb),
command.timeoutMs, command.timeoutMs,
"market ticker" "market ticker"
@@ -279,7 +279,7 @@ async function handleMarketDepth(
buildAdapterFromEnvFn: typeof buildAdapterFromEnv buildAdapterFromEnvFn: typeof buildAdapterFromEnv
): Promise<unknown> { ): Promise<unknown> {
const { adapter, exchange, symbol } = createAdapterContext(command, buildAdapterFromEnvFn); const { adapter, exchange, symbol } = createAdapterContext(command, buildAdapterFromEnvFn);
const depth = await waitForFirst<AsterDepth>( const depth = await waitForFirst<Depth>(
(cb) => adapter.watchDepth(symbol, cb), (cb) => adapter.watchDepth(symbol, cb),
command.timeoutMs, command.timeoutMs,
"market depth" "market depth"
@@ -300,7 +300,7 @@ async function handleMarketKline(
buildAdapterFromEnvFn: typeof buildAdapterFromEnv buildAdapterFromEnvFn: typeof buildAdapterFromEnv
): Promise<unknown> { ): Promise<unknown> {
const { adapter, exchange, symbol } = createAdapterContext(command, buildAdapterFromEnvFn); const { adapter, exchange, symbol } = createAdapterContext(command, buildAdapterFromEnvFn);
const klines = await waitForFirst<AsterKline[]>( const klines = await waitForFirst<Kline[]>(
(cb) => adapter.watchKlines(symbol, command.interval, cb), (cb) => adapter.watchKlines(symbol, command.interval, cb),
command.timeoutMs, command.timeoutMs,
"market kline" "market kline"
@@ -379,7 +379,7 @@ async function handleOrderCreate(
timeInForce: payload.timeInForce, timeInForce: payload.timeInForce,
}; };
let order: AsterOrder; let order: Order;
switch (payload.type) { switch (payload.type) {
case "limit": case "limit":
order = await routeLimitOrder({ order = await routeLimitOrder({
+4 -4
View File
@@ -1,4 +1,4 @@
import type { AsterOrder } from "../../exchanges/types"; import type { Order } from "../../exchanges/types";
export interface OrderTarget { export interface OrderTarget {
side: "BUY" | "SELL"; side: "BUY" | "SELL";
@@ -8,11 +8,11 @@ export interface OrderTarget {
} }
export function makeOrderPlan( export function makeOrderPlan(
openOrders: AsterOrder[], openOrders: Order[],
targets: OrderTarget[] targets: OrderTarget[]
): { toCancel: AsterOrder[]; toPlace: OrderTarget[] } { ): { toCancel: Order[]; toPlace: OrderTarget[] } {
const unmatched = new Set(targets.map((_, idx) => idx)); const unmatched = new Set(targets.map((_, idx) => idx));
const toCancel: AsterOrder[] = []; const toCancel: Order[] = [];
for (const order of openOrders) { for (const order of openOrders) {
const orderPrice = String(order.price); const orderPrice = String(order.price);
+2 -2
View File
@@ -1,11 +1,11 @@
import type { ExchangeAdapter } from "../../exchanges/adapter"; import type { ExchangeAdapter } from "../../exchanges/adapter";
import type { AsterOrder } from "../../exchanges/types"; import type { Order } from "../../exchanges/types";
import { isUnknownOrderError } from "../../utils/errors"; import { isUnknownOrderError } from "../../utils/errors";
export async function safeCancelOrder( export async function safeCancelOrder(
exchange: ExchangeAdapter, exchange: ExchangeAdapter,
symbol: string, symbol: string,
order: AsterOrder, order: Order,
onResolved: (orderId: number | string) => void, onResolved: (orderId: number | string) => void,
onUnknown: () => void, onUnknown: () => void,
onError: (err: unknown) => void onError: (err: unknown) => void
+11 -11
View File
@@ -1,5 +1,5 @@
import type { ExchangeAdapter } from "../exchanges/adapter"; import type { ExchangeAdapter } from "../exchanges/adapter";
import type { AsterOrder } from "../exchanges/types"; import type { Order } from "../exchanges/types";
import { import {
routeCloseOrder, routeCloseOrder,
routeLimitOrder, routeLimitOrder,
@@ -88,7 +88,7 @@ export function unlockOperating(
export async function deduplicateOrders( export async function deduplicateOrders(
adapter: ExchangeAdapter, adapter: ExchangeAdapter,
symbol: string, symbol: string,
openOrders: AsterOrder[], openOrders: Order[],
locks: OrderLockMap, locks: OrderLockMap,
timers: OrderTimerMap, timers: OrderTimerMap,
pendings: OrderPendingMap, pendings: OrderPendingMap,
@@ -139,7 +139,7 @@ type PlaceOrderOptions = {
export async function placeOrder( export async function placeOrder(
adapter: ExchangeAdapter, adapter: ExchangeAdapter,
symbol: string, symbol: string,
openOrders: AsterOrder[], openOrders: Order[],
locks: OrderLockMap, locks: OrderLockMap,
timers: OrderTimerMap, timers: OrderTimerMap,
pendings: OrderPendingMap, pendings: OrderPendingMap,
@@ -150,7 +150,7 @@ export async function placeOrder(
reduceOnly = false, reduceOnly = false,
guard?: OrderGuardOptions, guard?: OrderGuardOptions,
opts?: PlaceOrderOptions opts?: PlaceOrderOptions
): Promise<AsterOrder | undefined> { ): Promise<Order | undefined> {
const type = "LIMIT"; const type = "LIMIT";
if (isOperating(locks, type)) return; if (isOperating(locks, type)) return;
const priceNum = Number(price); const priceNum = Number(price);
@@ -197,7 +197,7 @@ export async function placeOrder(
export async function placeMarketOrder( export async function placeMarketOrder(
adapter: ExchangeAdapter, adapter: ExchangeAdapter,
symbol: string, symbol: string,
openOrders: AsterOrder[], openOrders: Order[],
locks: OrderLockMap, locks: OrderLockMap,
timers: OrderTimerMap, timers: OrderTimerMap,
pendings: OrderPendingMap, pendings: OrderPendingMap,
@@ -207,7 +207,7 @@ export async function placeMarketOrder(
reduceOnly = false, reduceOnly = false,
guard?: OrderGuardOptions, guard?: OrderGuardOptions,
opts?: { qtyStep: number } opts?: { qtyStep: number }
): Promise<AsterOrder | undefined> { ): Promise<Order | undefined> {
const type = "MARKET"; const type = "MARKET";
if (isOperating(locks, type)) return; if (isOperating(locks, type)) return;
if (!enforceMarkPriceGuard(side, guard?.expectedPrice ?? null, guard, log, "市价单")) return; if (!enforceMarkPriceGuard(side, guard?.expectedPrice ?? null, guard, log, "市价单")) return;
@@ -247,7 +247,7 @@ export async function placeMarketOrder(
export async function placeStopLossOrder( export async function placeStopLossOrder(
adapter: ExchangeAdapter, adapter: ExchangeAdapter,
symbol: string, symbol: string,
openOrders: AsterOrder[], openOrders: Order[],
locks: OrderLockMap, locks: OrderLockMap,
timers: OrderTimerMap, timers: OrderTimerMap,
pendings: OrderPendingMap, pendings: OrderPendingMap,
@@ -258,7 +258,7 @@ export async function placeStopLossOrder(
log: LogHandler, log: LogHandler,
guard?: OrderGuardOptions, guard?: OrderGuardOptions,
opts?: { priceTick: number; qtyStep: number } opts?: { priceTick: number; qtyStep: number }
): Promise<AsterOrder | undefined> { ): Promise<Order | undefined> {
const type = "STOP_MARKET"; const type = "STOP_MARKET";
if (isOperating(locks, type)) return; if (isOperating(locks, type)) return;
if (!enforceMarkPriceGuard(side, stopPrice, guard, log, "止损单")) return; if (!enforceMarkPriceGuard(side, stopPrice, guard, log, "止损单")) return;
@@ -314,7 +314,7 @@ export async function placeStopLossOrder(
export async function placeTrailingStopOrder( export async function placeTrailingStopOrder(
adapter: ExchangeAdapter, adapter: ExchangeAdapter,
symbol: string, symbol: string,
openOrders: AsterOrder[], openOrders: Order[],
locks: OrderLockMap, locks: OrderLockMap,
timers: OrderTimerMap, timers: OrderTimerMap,
pendings: OrderPendingMap, pendings: OrderPendingMap,
@@ -325,7 +325,7 @@ export async function placeTrailingStopOrder(
log: LogHandler, log: LogHandler,
guard?: OrderGuardOptions, guard?: OrderGuardOptions,
opts?: { priceTick: number; qtyStep: number } opts?: { priceTick: number; qtyStep: number }
): Promise<AsterOrder | undefined> { ): Promise<Order | undefined> {
const type = "TRAILING_STOP_MARKET"; const type = "TRAILING_STOP_MARKET";
if (isOperating(locks, type)) return; if (isOperating(locks, type)) return;
if (!adapter.supportsTrailingStops()) { if (!adapter.supportsTrailingStops()) {
@@ -375,7 +375,7 @@ export async function placeTrailingStopOrder(
export async function marketClose( export async function marketClose(
adapter: ExchangeAdapter, adapter: ExchangeAdapter,
symbol: string, symbol: string,
openOrders: AsterOrder[], openOrders: Order[],
locks: OrderLockMap, locks: OrderLockMap,
timers: OrderTimerMap, timers: OrderTimerMap,
pendings: OrderPendingMap, pendings: OrderPendingMap,
+13 -13
View File
@@ -1,30 +1,30 @@
import type { import type {
AsterAccountSnapshot, AccountSnapshot,
AsterOrder, Order,
AsterDepth, Depth,
AsterTicker, Ticker,
AsterKline, Kline,
CreateOrderParams, CreateOrderParams,
} from "./types"; } from "./types";
export interface AccountListener { export interface AccountListener {
(snapshot: AsterAccountSnapshot): void; (snapshot: AccountSnapshot): void;
} }
export interface OrderListener { export interface OrderListener {
(orders: AsterOrder[]): void; (orders: Order[]): void;
} }
export interface DepthListener { export interface DepthListener {
(depth: AsterDepth): void; (depth: Depth): void;
} }
export interface TickerListener { export interface TickerListener {
(ticker: AsterTicker): void; (ticker: Ticker): void;
} }
export interface KlineListener { export interface KlineListener {
(klines: AsterKline[]): void; (klines: Kline[]): void;
} }
export interface FundingRateSnapshot { export interface FundingRateSnapshot {
@@ -72,7 +72,7 @@ export interface ExchangeAdapter {
watchTicker(symbol: string, cb: TickerListener): void; watchTicker(symbol: string, cb: TickerListener): void;
watchKlines(symbol: string, interval: string, cb: KlineListener): void; watchKlines(symbol: string, interval: string, cb: KlineListener): void;
watchFundingRate?(symbol: string, cb: FundingRateListener): void; watchFundingRate?(symbol: string, cb: FundingRateListener): void;
createOrder(params: CreateOrderParams): Promise<AsterOrder>; createOrder(params: CreateOrderParams): Promise<Order>;
cancelOrder(params: { symbol: string; orderId: number | string }): Promise<void>; cancelOrder(params: { symbol: string; orderId: number | string }): Promise<void>;
cancelOrders(params: { symbol: string; orderIdList: Array<number | string> }): Promise<void>; cancelOrders(params: { symbol: string; orderIdList: Array<number | string> }): Promise<void>;
cancelAllOrders(params: { symbol: string }): Promise<void>; cancelAllOrders(params: { symbol: string }): Promise<void>;
@@ -82,8 +82,8 @@ export interface ExchangeAdapter {
offConnectionEvent?(listener: ConnectionEventListener): void; offConnectionEvent?(listener: ConnectionEventListener): void;
onRestHealthEvent?(listener: RestHealthListener): void; onRestHealthEvent?(listener: RestHealthListener): void;
offRestHealthEvent?(listener: RestHealthListener): void; offRestHealthEvent?(listener: RestHealthListener): void;
queryOpenOrders?(): Promise<AsterOrder[]>; queryOpenOrders?(): Promise<Order[]>;
queryAccountSnapshot?(): Promise<AsterAccountSnapshot | null>; queryAccountSnapshot?(): Promise<AccountSnapshot | null>;
changeMarginMode?(params: { symbol: string; marginMode: "isolated" | "cross" }): Promise<void>; changeMarginMode?(params: { symbol: string; marginMode: "isolated" | "cross" }): Promise<void>;
forceCancelAllOrders?(): Promise<boolean>; forceCancelAllOrders?(): Promise<boolean>;
} }
+5 -5
View File
@@ -7,7 +7,7 @@ import type {
OrderListener, OrderListener,
TickerListener, TickerListener,
} from "./adapter"; } from "./adapter";
import type { AsterOrder, CreateOrderParams, AsterDepth, AsterTicker, AsterKline } from "./types"; import type { Order, CreateOrderParams, Depth, Ticker, Kline } from "./types";
import { extractMessage } from "../utils/errors"; import { extractMessage } from "../utils/errors";
import { AsterGateway } from "./aster/client"; import { AsterGateway } from "./aster/client";
@@ -111,26 +111,26 @@ export class AsterExchangeAdapter implements ExchangeAdapter {
watchDepth(symbol: string, cb: DepthListener): void { watchDepth(symbol: string, cb: DepthListener): void {
void this.ensureInitialized("watchDepth"); void this.ensureInitialized("watchDepth");
this.gateway.onDepth(symbol, this.safeInvoke("watchDepth", (depth: AsterDepth) => { this.gateway.onDepth(symbol, this.safeInvoke("watchDepth", (depth: Depth) => {
cb(depth); cb(depth);
})); }));
} }
watchTicker(symbol: string, cb: TickerListener): void { watchTicker(symbol: string, cb: TickerListener): void {
void this.ensureInitialized("watchTicker"); void this.ensureInitialized("watchTicker");
this.gateway.onTicker(symbol, this.safeInvoke("watchTicker", (ticker: AsterTicker) => { this.gateway.onTicker(symbol, this.safeInvoke("watchTicker", (ticker: Ticker) => {
cb(ticker); cb(ticker);
})); }));
} }
watchKlines(symbol: string, interval: string, cb: KlineListener): void { watchKlines(symbol: string, interval: string, cb: KlineListener): void {
void this.ensureInitialized("watchKlines"); void this.ensureInitialized("watchKlines");
this.gateway.onKlines(symbol, interval, this.safeInvoke("watchKlines", (klines: AsterKline[]) => { this.gateway.onKlines(symbol, interval, this.safeInvoke("watchKlines", (klines: Kline[]) => {
cb(klines); cb(klines);
})); }));
} }
async createOrder(params: CreateOrderParams): Promise<AsterOrder> { async createOrder(params: CreateOrderParams): Promise<Order> {
await this.ensureInitialized("createOrder"); await this.ensureInitialized("createOrder");
return this.gateway.createOrder(params); return this.gateway.createOrder(params);
} }
+56 -56
View File
@@ -1,11 +1,11 @@
import crypto from "crypto"; import crypto from "crypto";
import { setInterval, clearInterval, setTimeout, clearTimeout } from "timers"; import { setInterval, clearInterval, setTimeout, clearTimeout } from "timers";
import type { import type {
AsterAccountPosition, AccountPosition,
AsterAccountSnapshot, AccountSnapshot,
AsterDepth, Depth,
AsterKline, Kline,
AsterOrder, Order,
AsterSpotAccount, AsterSpotAccount,
AsterSpotAggTrade, AsterSpotAggTrade,
AsterSpotBookTicker, AsterSpotBookTicker,
@@ -18,7 +18,7 @@ import type {
AsterSpotTicker24h, AsterSpotTicker24h,
AsterSpotTrade, AsterSpotTrade,
AsterSpotUserTrade, AsterSpotUserTrade,
AsterTicker, Ticker,
AsterFuturesExchangeInfo, AsterFuturesExchangeInfo,
AsterFuturesSymbolInfo, AsterFuturesSymbolInfo,
CancelSpotOrderParams, CancelSpotOrderParams,
@@ -244,7 +244,7 @@ export class AsterSpotRestClient {
}; };
} }
async createOrder(params: CreateSpotOrderParams): Promise<AsterOrder> { async createOrder(params: CreateSpotOrderParams): Promise<Order> {
const response = await this.request<any>({ const response = await this.request<any>({
path: "/api/v1/order", path: "/api/v1/order",
method: "POST", method: "POST",
@@ -255,7 +255,7 @@ export class AsterSpotRestClient {
return toOrderFromRest(response); return toOrderFromRest(response);
} }
async cancelOrder(params: CancelSpotOrderParams): Promise<AsterOrder> { async cancelOrder(params: CancelSpotOrderParams): Promise<Order> {
const response = await this.request<any>({ const response = await this.request<any>({
path: "/api/v1/order", path: "/api/v1/order",
method: "DELETE", method: "DELETE",
@@ -270,7 +270,7 @@ export class AsterSpotRestClient {
return toOrderFromRest(response); return toOrderFromRest(response);
} }
async getOrder(params: QuerySpotOrderParams): Promise<AsterOrder> { async getOrder(params: QuerySpotOrderParams): Promise<Order> {
const response = await this.request<any>({ const response = await this.request<any>({
path: "/api/v1/order", path: "/api/v1/order",
method: "GET", method: "GET",
@@ -285,7 +285,7 @@ export class AsterSpotRestClient {
return toOrderFromRest(response); return toOrderFromRest(response);
} }
async getOpenOrders(params: SpotOpenOrdersParams = {}): Promise<AsterOrder[]> { async getOpenOrders(params: SpotOpenOrdersParams = {}): Promise<Order[]> {
const response = await this.request<any[]>({ const response = await this.request<any[]>({
path: "/api/v1/openOrders", path: "/api/v1/openOrders",
method: "GET", method: "GET",
@@ -319,7 +319,7 @@ export class AsterSpotRestClient {
}); });
} }
async getAllOrders(params: SpotAllOrdersParams): Promise<AsterOrder[]> { async getAllOrders(params: SpotAllOrdersParams): Promise<Order[]> {
const response = await this.request<any[]>({ const response = await this.request<any[]>({
path: "/api/v1/allOrders", path: "/api/v1/allOrders",
method: "GET", method: "GET",
@@ -549,7 +549,7 @@ export class AsterSpotRestClient {
} }
} }
function toDepth(streamSymbol: string, data: any): AsterDepth { function toDepth(streamSymbol: string, data: any): Depth {
return { return {
eventType: data.e, eventType: data.e,
eventTime: data.E, eventTime: data.E,
@@ -561,7 +561,7 @@ function toDepth(streamSymbol: string, data: any): AsterDepth {
}; };
} }
function toTicker(data: any): AsterTicker { function toTicker(data: any): Ticker {
return { return {
eventType: data.e, eventType: data.e,
eventTime: data.E, eventTime: data.E,
@@ -584,7 +584,7 @@ function toTicker(data: any): AsterTicker {
}; };
} }
function toKline(data: any): AsterKline { function toKline(data: any): Kline {
return { return {
eventType: data.e, eventType: data.e,
eventTime: data.E, eventTime: data.E,
@@ -607,7 +607,7 @@ function toKline(data: any): AsterKline {
}; };
} }
function fromRestKline(entry: any[], interval: string, symbol: string): AsterKline { function fromRestKline(entry: any[], interval: string, symbol: string): Kline {
return { return {
eventType: undefined, eventType: undefined,
eventTime: undefined, eventTime: undefined,
@@ -628,7 +628,7 @@ function fromRestKline(entry: any[], interval: string, symbol: string): AsterKli
}; };
} }
function toOrderFromRest(raw: any): AsterOrder { function toOrderFromRest(raw: any): Order {
return { return {
avgPrice: raw.avgPrice ?? "0", avgPrice: raw.avgPrice ?? "0",
clientOrderId: raw.clientOrderId ?? "", clientOrderId: raw.clientOrderId ?? "",
@@ -656,7 +656,7 @@ function toOrderFromRest(raw: any): AsterOrder {
}; };
} }
function toOrderFromEvent(event: any): AsterOrder { function toOrderFromEvent(event: any): Order {
return { return {
avgPrice: event.ap ?? "0", avgPrice: event.ap ?? "0",
clientOrderId: event.c ?? "", clientOrderId: event.c ?? "",
@@ -684,7 +684,7 @@ function toOrderFromEvent(event: any): AsterOrder {
}; };
} }
function toPositionFromRisk(raw: any): AsterAccountPosition { function toPositionFromRisk(raw: any): AccountPosition {
const positionSide = String(raw.positionSide ?? raw.ps ?? "BOTH").toUpperCase() as PositionSide; const positionSide = String(raw.positionSide ?? raw.ps ?? "BOTH").toUpperCase() as PositionSide;
return { return {
symbol: raw.symbol ?? raw.s ?? "", symbol: raw.symbol ?? raw.s ?? "",
@@ -708,16 +708,16 @@ function toPositionFromRisk(raw: any): AsterAccountPosition {
}; };
} }
function deepCloneAccount(snapshot: AsterAccountSnapshot | null): AsterAccountSnapshot | null { function deepCloneAccount(snapshot: AccountSnapshot | null): AccountSnapshot | null {
return snapshot ? JSON.parse(JSON.stringify(snapshot)) : null; return snapshot ? JSON.parse(JSON.stringify(snapshot)) : null;
} }
function sumUnrealizedProfit(positions: AsterAccountPosition[]): string { function sumUnrealizedProfit(positions: AccountPosition[]): string {
const total = positions.reduce((acc, position) => acc + Number(position.unrealizedProfit ?? 0), 0); const total = positions.reduce((acc, position) => acc + Number(position.unrealizedProfit ?? 0), 0);
return total.toFixed(8); return total.toFixed(8);
} }
function clonePositions(positions: AsterAccountPosition[]): AsterAccountPosition[] { function clonePositions(positions: AccountPosition[]): AccountPosition[] {
return positions.map((position) => ({ return positions.map((position) => ({
...position, ...position,
updateTime: position.updateTime ?? Date.now(), updateTime: position.updateTime ?? Date.now(),
@@ -763,18 +763,18 @@ export class AsterRestClient {
this.apiSecret = requireEnv(options.apiSecret ?? process.env.ASTER_API_SECRET, "ASTER_API_SECRET"); this.apiSecret = requireEnv(options.apiSecret ?? process.env.ASTER_API_SECRET, "ASTER_API_SECRET");
} }
async getAccount(): Promise<AsterAccountSnapshot> { async getAccount(): Promise<AccountSnapshot> {
return this.signedRequest<AsterAccountSnapshot>({ path: "/fapi/v2/account", method: "GET", params: {} }); return this.signedRequest<AccountSnapshot>({ path: "/fapi/v2/account", method: "GET", params: {} });
} }
async getOpenOrders(symbol?: string): Promise<AsterOrder[]> { async getOpenOrders(symbol?: string): Promise<Order[]> {
const params: Record<string, unknown> = {}; const params: Record<string, unknown> = {};
if (symbol) params.symbol = symbol; if (symbol) params.symbol = symbol;
const raw = await this.signedRequest<any[]>({ path: "/fapi/v1/openOrders", method: "GET", params }); const raw = await this.signedRequest<any[]>({ path: "/fapi/v1/openOrders", method: "GET", params });
return raw.map(toOrderFromRest); return raw.map(toOrderFromRest);
} }
async getPositions(symbol?: string): Promise<AsterAccountPosition[]> { async getPositions(symbol?: string): Promise<AccountPosition[]> {
const params: Record<string, unknown> = {}; const params: Record<string, unknown> = {};
if (symbol) params.symbol = symbol.toUpperCase(); if (symbol) params.symbol = symbol.toUpperCase();
const raw = await this.signedRequest<any[]>({ path: "/fapi/v2/positionRisk", method: "GET", params }); const raw = await this.signedRequest<any[]>({ path: "/fapi/v2/positionRisk", method: "GET", params });
@@ -800,7 +800,7 @@ export class AsterRestClient {
} }
} }
async createOrder(params: CreateOrderParams): Promise<AsterOrder> { async createOrder(params: CreateOrderParams): Promise<Order> {
// Sanitize and normalize params for Aster futures API. Paradex-specific flags // Sanitize and normalize params for Aster futures API. Paradex-specific flags
// like reduceOnly/closePosition on STOP/TRAILING should not leak here. // like reduceOnly/closePosition on STOP/TRAILING should not leak here.
const payload: Record<string, unknown> = {}; const payload: Record<string, unknown> = {};
@@ -830,12 +830,12 @@ export class AsterRestClient {
return toOrderFromRest(response); return toOrderFromRest(response);
} }
async cancelOrder(params: { symbol: string; orderId?: number; origClientOrderId?: string }): Promise<AsterOrder> { async cancelOrder(params: { symbol: string; orderId?: number; origClientOrderId?: string }): Promise<Order> {
const response = await this.signedRequest<any>({ path: "/fapi/v1/order", method: "DELETE", params }); const response = await this.signedRequest<any>({ path: "/fapi/v1/order", method: "DELETE", params });
return toOrderFromRest(response); return toOrderFromRest(response);
} }
async cancelOrders(params: { symbol: string; orderIdList?: Array<number | string>; origClientOrderIdList?: string[] }): Promise<AsterOrder[]> { async cancelOrders(params: { symbol: string; orderIdList?: Array<number | string>; origClientOrderIdList?: string[] }): Promise<Order[]> {
const payload: Record<string, unknown> = { symbol: params.symbol }; const payload: Record<string, unknown> = { symbol: params.symbol };
if (params.orderIdList?.length) { if (params.orderIdList?.length) {
payload.orderIdList = `[${params.orderIdList payload.orderIdList = `[${params.orderIdList
@@ -853,7 +853,7 @@ export class AsterRestClient {
await this.signedRequest({ path: "/fapi/v1/allOpenOrders", method: "DELETE", params }); await this.signedRequest({ path: "/fapi/v1/allOpenOrders", method: "DELETE", params });
} }
async getKlines(symbol: string, interval: string, limit = DEFAULT_KLINE_LIMIT): Promise<AsterKline[]> { async getKlines(symbol: string, interval: string, limit = DEFAULT_KLINE_LIMIT): Promise<Kline[]> {
const upper = symbol.toUpperCase(); const upper = symbol.toUpperCase();
const url = `${FUTURES_REST_BASE}/fapi/v1/continuousKlines?pair=${upper}&contractType=PERPETUAL&interval=${encodeURIComponent(interval)}&limit=${limit}`; const url = `${FUTURES_REST_BASE}/fapi/v1/continuousKlines?pair=${upper}&contractType=PERPETUAL&interval=${encodeURIComponent(interval)}&limit=${limit}`;
let response: Response; let response: Response;
@@ -949,9 +949,9 @@ export class AsterRestClient {
} }
type DepthHandler = (depth: AsterDepth) => void; type DepthHandler = (depth: Depth) => void;
type TickerHandler = (ticker: AsterTicker) => void; type TickerHandler = (ticker: Ticker) => void;
type KlineHandler = (kline: AsterKline) => void; type KlineHandler = (kline: Kline) => void;
type StreamKind = "depth" | "ticker" | "kline"; type StreamKind = "depth" | "ticker" | "kline";
@@ -1244,7 +1244,7 @@ export class AsterUserStream {
} }
} }
function updateAccountSnapshot(snapshot: AsterAccountSnapshot | null, event: { eventTime: number; payload: AccountUpdatePayload }): AsterAccountSnapshot | null { function updateAccountSnapshot(snapshot: AccountSnapshot | null, event: { eventTime: number; payload: AccountUpdatePayload }): AccountSnapshot | null {
if (!snapshot) return snapshot; if (!snapshot) return snapshot;
const next = deepCloneAccount(snapshot); const next = deepCloneAccount(snapshot);
if (!next) return snapshot; if (!next) return snapshot;
@@ -1294,7 +1294,7 @@ function updateAccountSnapshot(snapshot: AsterAccountSnapshot | null, event: { e
return next; return next;
} }
function mergeOrderSnapshot(map: Map<string, AsterOrder>, order: AsterOrder): void { function mergeOrderSnapshot(map: Map<string, Order>, order: Order): void {
const rawId = order.orderId; const rawId = order.orderId;
if (rawId === undefined || rawId === null) return; if (rawId === undefined || rawId === null) return;
const key = String(rawId); const key = String(rawId);
@@ -1311,18 +1311,18 @@ export class AsterGateway {
private readonly publicStreams: AsterPublicStreams; private readonly publicStreams: AsterPublicStreams;
private readonly userStream: AsterUserStream; private readonly userStream: AsterUserStream;
private accountSnapshot: AsterAccountSnapshot | null = null; private accountSnapshot: AccountSnapshot | null = null;
private readonly openOrders = new Map<string, AsterOrder>(); private readonly openOrders = new Map<string, Order>();
private positionSyncTimer: ReturnType<typeof setInterval> | null = null; private positionSyncTimer: ReturnType<typeof setInterval> | null = null;
private positionSyncInFlight = false; private positionSyncInFlight = false;
private readonly accountEvent = new SimpleEvent<AsterAccountSnapshot>(); private readonly accountEvent = new SimpleEvent<AccountSnapshot>();
private readonly ordersEvent = new SimpleEvent<AsterOrder[]>(); private readonly ordersEvent = new SimpleEvent<Order[]>();
private readonly depthEvents = new Map<string, SimpleEvent<AsterDepth>>(); private readonly depthEvents = new Map<string, SimpleEvent<Depth>>();
private readonly tickerEvents = new Map<string, SimpleEvent<AsterTicker>>(); private readonly tickerEvents = new Map<string, SimpleEvent<Ticker>>();
private readonly klineEvents = new Map<string, SimpleEvent<AsterKline[]>>(); private readonly klineEvents = new Map<string, SimpleEvent<Kline[]>>();
private readonly klineStores = new Map<string, AsterKline[]>(); private readonly klineStores = new Map<string, Kline[]>();
private readonly klineRefreshTimers = new Map<string, ReturnType<typeof setInterval>>(); private readonly klineRefreshTimers = new Map<string, ReturnType<typeof setInterval>>();
private readonly klineInitialFetches = new Map<string, Promise<void>>(); private readonly klineInitialFetches = new Map<string, Promise<void>>();
private initialized = false; private initialized = false;
@@ -1381,21 +1381,21 @@ export class AsterGateway {
return this.initializing; return this.initializing;
} }
onAccount(listener: (snapshot: AsterAccountSnapshot) => void): void { onAccount(listener: (snapshot: AccountSnapshot) => void): void {
this.accountEvent.add(listener); this.accountEvent.add(listener);
if (this.accountSnapshot) listener(this.accountSnapshot); if (this.accountSnapshot) listener(this.accountSnapshot);
} }
onOrders(listener: (orders: AsterOrder[]) => void): void { onOrders(listener: (orders: Order[]) => void): void {
this.ordersEvent.add(listener); this.ordersEvent.add(listener);
listener(Array.from(this.openOrders.values())); listener(Array.from(this.openOrders.values()));
} }
onDepth(symbol: string, listener: (depth: AsterDepth) => void): void { onDepth(symbol: string, listener: (depth: Depth) => void): void {
const upper = symbol.toUpperCase(); const upper = symbol.toUpperCase();
let event = this.depthEvents.get(upper); let event = this.depthEvents.get(upper);
if (!event) { if (!event) {
event = new SimpleEvent<AsterDepth>(); event = new SimpleEvent<Depth>();
this.depthEvents.set(upper, event); this.depthEvents.set(upper, event);
this.publicStreams.subscribeDepth(upper, (depth) => { this.publicStreams.subscribeDepth(upper, (depth) => {
event?.emit(depth); event?.emit(depth);
@@ -1404,11 +1404,11 @@ export class AsterGateway {
event.add(listener); event.add(listener);
} }
onTicker(symbol: string, listener: (ticker: AsterTicker) => void): void { onTicker(symbol: string, listener: (ticker: Ticker) => void): void {
const upper = symbol.toUpperCase(); const upper = symbol.toUpperCase();
let event = this.tickerEvents.get(upper); let event = this.tickerEvents.get(upper);
if (!event) { if (!event) {
event = new SimpleEvent<AsterTicker>(); event = new SimpleEvent<Ticker>();
this.tickerEvents.set(upper, event); this.tickerEvents.set(upper, event);
this.publicStreams.subscribeTicker(upper, (ticker) => { this.publicStreams.subscribeTicker(upper, (ticker) => {
event?.emit(ticker); event?.emit(ticker);
@@ -1417,12 +1417,12 @@ export class AsterGateway {
event.add(listener); event.add(listener);
} }
onKlines(symbol: string, interval: string, listener: (klines: AsterKline[]) => void): void { onKlines(symbol: string, interval: string, listener: (klines: Kline[]) => void): void {
const upper = symbol.toUpperCase(); const upper = symbol.toUpperCase();
const key = `${upper}:${interval}`; const key = `${upper}:${interval}`;
let event = this.klineEvents.get(key); let event = this.klineEvents.get(key);
if (!event) { if (!event) {
event = new SimpleEvent<AsterKline[]>(); event = new SimpleEvent<Kline[]>();
this.klineEvents.set(key, event); this.klineEvents.set(key, event);
this.publicStreams.subscribeKline(symbol, interval, (kline) => { this.publicStreams.subscribeKline(symbol, interval, (kline) => {
const storeKey = `${upper}:${interval}`; const storeKey = `${upper}:${interval}`;
@@ -1509,7 +1509,7 @@ export class AsterGateway {
console.error("[AsterGateway] 刷新持仓失败", positionError); console.error("[AsterGateway] 刷新持仓失败", positionError);
} }
const normalizedPositions = clonePositions(positions); const normalizedPositions = clonePositions(positions);
const snapshot: AsterAccountSnapshot = { const snapshot: AccountSnapshot = {
...account, ...account,
positions: normalizedPositions, positions: normalizedPositions,
totalUnrealizedProfit: sumUnrealizedProfit(normalizedPositions), totalUnrealizedProfit: sumUnrealizedProfit(normalizedPositions),
@@ -1547,7 +1547,7 @@ export class AsterGateway {
if (!Array.isArray(positions)) return; if (!Array.isArray(positions)) return;
const normalizedPositions = clonePositions(positions); const normalizedPositions = clonePositions(positions);
if (!this.accountSnapshot) { if (!this.accountSnapshot) {
const snapshot: AsterAccountSnapshot = { const snapshot: AccountSnapshot = {
canTrade: true, canTrade: true,
canDeposit: true, canDeposit: true,
canWithdraw: true, canWithdraw: true,
@@ -1561,7 +1561,7 @@ export class AsterGateway {
this.accountEvent.emit(snapshot); this.accountEvent.emit(snapshot);
return; return;
} }
const nextSnapshot: AsterAccountSnapshot = { const nextSnapshot: AccountSnapshot = {
...this.accountSnapshot, ...this.accountSnapshot,
positions: normalizedPositions, positions: normalizedPositions,
totalUnrealizedProfit: sumUnrealizedProfit(normalizedPositions), totalUnrealizedProfit: sumUnrealizedProfit(normalizedPositions),
@@ -1576,15 +1576,15 @@ export class AsterGateway {
} }
} }
getAccountSnapshot(): AsterAccountSnapshot | null { getAccountSnapshot(): AccountSnapshot | null {
return this.accountSnapshot; return this.accountSnapshot;
} }
getOpenOrdersSnapshot(): AsterOrder[] { getOpenOrdersSnapshot(): Order[] {
return Array.from(this.openOrders.values()); return Array.from(this.openOrders.values());
} }
async createOrder(params: CreateOrderParams): Promise<AsterOrder> { async createOrder(params: CreateOrderParams): Promise<Order> {
const normalized = await this.normalizeOrderParams(params); const normalized = await this.normalizeOrderParams(params);
const order = await this.rest.createOrder(normalized); const order = await this.rest.createOrder(normalized);
mergeOrderSnapshot(this.openOrders, order); mergeOrderSnapshot(this.openOrders, order);
+6 -6
View File
@@ -1,4 +1,4 @@
import type { AsterOrder, CreateOrderParams } from "../types"; import type { Order, CreateOrderParams } from "../types";
import type { import type {
BaseOrderIntent, BaseOrderIntent,
ClosePositionIntent, ClosePositionIntent,
@@ -25,7 +25,7 @@ function applyCommonFields(params: CreateOrderParams, intent: BaseOrderIntent):
return params; return params;
} }
export async function createLimitOrder(intent: LimitOrderIntent): Promise<AsterOrder> { export async function createLimitOrder(intent: LimitOrderIntent): Promise<Order> {
const params: CreateOrderParams = applyCommonFields( const params: CreateOrderParams = applyCommonFields(
{ {
symbol: intent.symbol, symbol: intent.symbol,
@@ -40,7 +40,7 @@ export async function createLimitOrder(intent: LimitOrderIntent): Promise<AsterO
return intent.adapter.createOrder(params); return intent.adapter.createOrder(params);
} }
export async function createMarketOrder(intent: MarketOrderIntent): Promise<AsterOrder> { export async function createMarketOrder(intent: MarketOrderIntent): Promise<Order> {
const params: CreateOrderParams = applyCommonFields( const params: CreateOrderParams = applyCommonFields(
{ {
symbol: intent.symbol, symbol: intent.symbol,
@@ -53,7 +53,7 @@ export async function createMarketOrder(intent: MarketOrderIntent): Promise<Aste
return intent.adapter.createOrder(params); return intent.adapter.createOrder(params);
} }
export async function createStopOrder(intent: StopOrderIntent): Promise<AsterOrder> { export async function createStopOrder(intent: StopOrderIntent): Promise<Order> {
const params: CreateOrderParams = applyCommonFields( const params: CreateOrderParams = applyCommonFields(
{ {
symbol: intent.symbol, symbol: intent.symbol,
@@ -69,7 +69,7 @@ export async function createStopOrder(intent: StopOrderIntent): Promise<AsterOrd
return intent.adapter.createOrder(params); return intent.adapter.createOrder(params);
} }
export async function createTrailingStopOrder(intent: TrailingStopOrderIntent): Promise<AsterOrder> { export async function createTrailingStopOrder(intent: TrailingStopOrderIntent): Promise<Order> {
const params: CreateOrderParams = applyCommonFields( const params: CreateOrderParams = applyCommonFields(
{ {
symbol: intent.symbol, symbol: intent.symbol,
@@ -85,7 +85,7 @@ export async function createTrailingStopOrder(intent: TrailingStopOrderIntent):
return intent.adapter.createOrder(params); return intent.adapter.createOrder(params);
} }
export async function createClosePositionOrder(intent: ClosePositionIntent): Promise<AsterOrder> { export async function createClosePositionOrder(intent: ClosePositionIntent): Promise<Order> {
const params: CreateOrderParams = applyCommonFields( const params: CreateOrderParams = applyCommonFields(
{ {
symbol: intent.symbol, symbol: intent.symbol,
+2 -2
View File
@@ -6,7 +6,7 @@ import type {
OrderListener, OrderListener,
TickerListener, TickerListener,
} from "../adapter"; } from "../adapter";
import type { AsterOrder, CreateOrderParams } from "../types"; import type { Order, CreateOrderParams } from "../types";
import { extractMessage } from "../../utils/errors"; import { extractMessage } from "../../utils/errors";
import { BackpackGateway, type BackpackGatewayOptions } from "./gateway"; import { BackpackGateway, type BackpackGatewayOptions } from "./gateway";
@@ -85,7 +85,7 @@ export class BackpackExchangeAdapter implements ExchangeAdapter {
this.gateway.watchKlines(interval, this.safeInvoke("watchKlines", cb)); this.gateway.watchKlines(interval, this.safeInvoke("watchKlines", cb));
} }
async createOrder(params: CreateOrderParams): Promise<AsterOrder> { async createOrder(params: CreateOrderParams): Promise<Order> {
await this.ensureInitialized("createOrder"); await this.ensureInitialized("createOrder");
return this.gateway.createOrder(params); return this.gateway.createOrder(params);
} }
+27 -27
View File
@@ -8,12 +8,12 @@ import NodeWebSocket from "ws";
import { sign, utils as edUtils, hashes as edHashes } from "@noble/ed25519"; import { sign, utils as edUtils, hashes as edHashes } from "@noble/ed25519";
import { sha512 } from "@noble/hashes/sha512"; import { sha512 } from "@noble/hashes/sha512";
import type { import type {
AsterAccountSnapshot, AccountSnapshot,
AsterAccountPosition, AccountPosition,
AsterOrder, Order,
AsterDepth, Depth,
AsterTicker, Ticker,
AsterKline, Kline,
CreateOrderParams, CreateOrderParams,
OrderType, OrderType,
} from "../types"; } from "../types";
@@ -92,8 +92,8 @@ export class BackpackGateway {
private tickerPollTimer: ReturnType<typeof setInterval> | null = null; private tickerPollTimer: ReturnType<typeof setInterval> | null = null;
private readonly klinePollTimers = new Map<string, ReturnType<typeof setInterval>>(); private readonly klinePollTimers = new Map<string, ReturnType<typeof setInterval>>();
private readonly localOrders = new Map<string, AsterOrder>(); private readonly localOrders = new Map<string, Order>();
private lastBalanceSnapshot: AsterAccountSnapshot | null = null; private lastBalanceSnapshot: AccountSnapshot | null = null;
private marketId = ""; private marketId = "";
private ws: WebSocket | null = null; private ws: WebSocket | null = null;
@@ -244,7 +244,7 @@ export class BackpackGateway {
this.exchange.fetchOpenOrders(this.marketSymbol), this.exchange.fetchOpenOrders(this.marketSymbol),
this.exchange.fetchOrders(this.marketSymbol, undefined, 200, {}), this.exchange.fetchOrders(this.marketSymbol, undefined, 200, {}),
]); ]);
const active = new Map<string, AsterOrder>(); const active = new Map<string, Order>();
for (const entry of [...openOrders, ...allOrders]) { for (const entry of [...openOrders, ...allOrders]) {
const status = this.normalizeStatus(entry.status ?? (entry.info?.status as string)); const status = this.normalizeStatus(entry.status ?? (entry.info?.status as string));
if (this.isTerminalStatus(status)) continue; if (this.isTerminalStatus(status)) continue;
@@ -315,7 +315,7 @@ export class BackpackGateway {
// ---- Order actions ----------------------------------------------------- // ---- Order actions -----------------------------------------------------
async createOrder(params: CreateOrderParams): Promise<AsterOrder> { async createOrder(params: CreateOrderParams): Promise<Order> {
await this.ensureInitialized(); await this.ensureInitialized();
const symbol = this.marketSymbol; const symbol = this.marketSymbol;
const normalizedType = this.normalizeOrderType(params.type); const normalizedType = this.normalizeOrderType(params.type);
@@ -399,11 +399,11 @@ export class BackpackGateway {
// ---- Mapping helpers --------------------------------------------------- // ---- Mapping helpers ---------------------------------------------------
private mapBalanceToAccountSnapshot(balance: Balances): AsterAccountSnapshot { private mapBalanceToAccountSnapshot(balance: Balances): AccountSnapshot {
return this.mapBalanceToAccountSnapshotWithPositions(balance, []); return this.mapBalanceToAccountSnapshotWithPositions(balance, []);
} }
private async fetchAccountSnapshot(): Promise<AsterAccountSnapshot> { private async fetchAccountSnapshot(): Promise<AccountSnapshot> {
await this.ensureInitialized(); await this.ensureInitialized();
const [balance, positions] = await Promise.all([ const [balance, positions] = await Promise.all([
this.exchange.fetchBalance(), this.exchange.fetchBalance(),
@@ -417,7 +417,7 @@ export class BackpackGateway {
return this.mapBalanceToAccountSnapshotWithPositions(balance, positions ?? []); return this.mapBalanceToAccountSnapshotWithPositions(balance, positions ?? []);
} }
private mapBalanceToAccountSnapshotWithPositions(balance: Balances, rawPositions: any[]): AsterAccountSnapshot { private mapBalanceToAccountSnapshotWithPositions(balance: Balances, rawPositions: any[]): AccountSnapshot {
const now = Date.now(); const now = Date.now();
const assets = this.normalizeAssets(balance, now); const assets = this.normalizeAssets(balance, now);
const positions = this.normalizePositions(rawPositions, now); const positions = this.normalizePositions(rawPositions, now);
@@ -425,7 +425,7 @@ export class BackpackGateway {
const totalUnrealized = this.sumStrings(positions.map((position) => position.unrealizedProfit ?? "0")); const totalUnrealized = this.sumStrings(positions.map((position) => position.unrealizedProfit ?? "0"));
const availableBalance = this.sumStrings(assets.map((asset) => asset.availableBalance)); const availableBalance = this.sumStrings(assets.map((asset) => asset.availableBalance));
const snapshot: AsterAccountSnapshot = { const snapshot: AccountSnapshot = {
canTrade: true, canTrade: true,
canDeposit: true, canDeposit: true,
canWithdraw: true, canWithdraw: true,
@@ -447,9 +447,9 @@ export class BackpackGateway {
return snapshot; return snapshot;
} }
private normalizeAssets(balance: Balances, now: number): AsterAccountSnapshot["assets"] { private normalizeAssets(balance: Balances, now: number): AccountSnapshot["assets"] {
const metaKeys = new Set(["free", "used", "total", "info", "timestamp", "datetime", "debt"]); const metaKeys = new Set(["free", "used", "total", "info", "timestamp", "datetime", "debt"]);
const assets: AsterAccountSnapshot["assets"] = []; const assets: AccountSnapshot["assets"] = [];
for (const [currency, value] of Object.entries(balance)) { for (const [currency, value] of Object.entries(balance)) {
if (metaKeys.has(currency)) continue; if (metaKeys.has(currency)) continue;
if (!value || typeof value !== "object") continue; if (!value || typeof value !== "object") continue;
@@ -460,9 +460,9 @@ export class BackpackGateway {
return assets; return assets;
} }
private normalizePositions(rawPositions: any[], now: number): AsterAccountSnapshot["positions"] { private normalizePositions(rawPositions: any[], now: number): AccountSnapshot["positions"] {
if (!Array.isArray(rawPositions)) return []; if (!Array.isArray(rawPositions)) return [];
const positions: AsterAccountSnapshot["positions"] = []; const positions: AccountSnapshot["positions"] = [];
for (const raw of rawPositions) { for (const raw of rawPositions) {
const info = raw?.info ?? raw ?? {}; const info = raw?.info ?? raw ?? {};
const quantity = this.toNumber(raw?.contracts ?? info.netExposureQuantity ?? info.netQuantity); const quantity = this.toNumber(raw?.contracts ?? info.netExposureQuantity ?? info.netQuantity);
@@ -493,7 +493,7 @@ export class BackpackGateway {
return positions; return positions;
} }
private mapOrderBookToDepth(orderbook: CcxtOrderBook): AsterDepth { private mapOrderBookToDepth(orderbook: CcxtOrderBook): Depth {
return { return {
lastUpdateId: orderbook.nonce || Date.now(), lastUpdateId: orderbook.nonce || Date.now(),
bids: (orderbook.bids ?? []) bids: (orderbook.bids ?? [])
@@ -506,7 +506,7 @@ export class BackpackGateway {
}; };
} }
private mapTickerToAsterTicker(ticker: CcxtTicker): AsterTicker { private mapTickerToAsterTicker(ticker: CcxtTicker): Ticker {
return { return {
symbol: ticker.symbol, symbol: ticker.symbol,
lastPrice: ticker.last?.toString() ?? "0", lastPrice: ticker.last?.toString() ?? "0",
@@ -522,7 +522,7 @@ export class BackpackGateway {
private mapOHLCVToKline( private mapOHLCVToKline(
candle: [number, number, number, number, number, number], candle: [number, number, number, number, number, number],
interval: string interval: string
): AsterKline { ): Kline {
const [openTime, open, high, low, close, volume] = candle; const [openTime, open, high, low, close, volume] = candle;
return { return {
openTime, openTime,
@@ -536,7 +536,7 @@ export class BackpackGateway {
}; };
} }
private mapRestOrder(order: CcxtOrder): AsterOrder { private mapRestOrder(order: CcxtOrder): Order {
const info = (order.info ?? {}) as Record<string, unknown>; const info = (order.info ?? {}) as Record<string, unknown>;
const side = (order.side ?? "buy").toUpperCase() as "BUY" | "SELL"; const side = (order.side ?? "buy").toUpperCase() as "BUY" | "SELL";
let type = this.normalizeOrderType(order.type ?? (info.o as string)) as OrderType; let type = this.normalizeOrderType(order.type ?? (info.o as string)) as OrderType;
@@ -579,7 +579,7 @@ export class BackpackGateway {
}; };
} }
private mapWsOrder(data: Record<string, unknown>): AsterOrder { private mapWsOrder(data: Record<string, unknown>): Order {
const sideRaw = String(data.S ?? "").toUpperCase(); const sideRaw = String(data.S ?? "").toUpperCase();
const side: "BUY" | "SELL" = sideRaw === "BID" ? "BUY" : "SELL"; const side: "BUY" | "SELL" = sideRaw === "BID" ? "BUY" : "SELL";
const triggerPresent = data.P != null || data.B != null; const triggerPresent = data.P != null || data.B != null;
@@ -626,7 +626,7 @@ export class BackpackGateway {
} }
} }
private mapWsPosition(data: Record<string, unknown>): AsterAccountPosition | null { private mapWsPosition(data: Record<string, unknown>): AccountPosition | null {
const quantityRaw = data.q ?? data.Q; const quantityRaw = data.q ?? data.Q;
const qty = Number(this.toStringAmount(quantityRaw)); const qty = Number(this.toStringAmount(quantityRaw));
if (!Number.isFinite(qty)) return null; if (!Number.isFinite(qty)) return null;
@@ -651,8 +651,8 @@ export class BackpackGateway {
}; };
} }
private mergeWsPosition(position: AsterAccountPosition): void { private mergeWsPosition(position: AccountPosition): void {
const snapshot: AsterAccountSnapshot = this.lastBalanceSnapshot const snapshot: AccountSnapshot = this.lastBalanceSnapshot
? { ? {
...this.lastBalanceSnapshot, ...this.lastBalanceSnapshot,
positions: this.lastBalanceSnapshot.positions ? [...this.lastBalanceSnapshot.positions] : [], positions: this.lastBalanceSnapshot.positions ? [...this.lastBalanceSnapshot.positions] : [],
@@ -692,7 +692,7 @@ export class BackpackGateway {
this.emitAccount(snapshot); this.emitAccount(snapshot);
} }
private emitAccount(snapshot: AsterAccountSnapshot): void { private emitAccount(snapshot: AccountSnapshot): void {
for (const listener of this.accountListeners) { for (const listener of this.accountListeners) {
try { try {
listener(snapshot); listener(snapshot);
+6 -6
View File
@@ -1,4 +1,4 @@
import type { AsterOrder, CreateOrderParams } from "../types"; import type { Order, CreateOrderParams } from "../types";
import type { import type {
BaseOrderIntent, BaseOrderIntent,
ClosePositionIntent, ClosePositionIntent,
@@ -22,7 +22,7 @@ function applyCommonFields(params: CreateOrderParams, intent: BaseOrderIntent):
return params; return params;
} }
export async function createLimitOrder(intent: LimitOrderIntent): Promise<AsterOrder> { export async function createLimitOrder(intent: LimitOrderIntent): Promise<Order> {
const params: CreateOrderParams = applyCommonFields( const params: CreateOrderParams = applyCommonFields(
{ {
symbol: intent.symbol, symbol: intent.symbol,
@@ -37,7 +37,7 @@ export async function createLimitOrder(intent: LimitOrderIntent): Promise<AsterO
return intent.adapter.createOrder(params); return intent.adapter.createOrder(params);
} }
export async function createMarketOrder(intent: MarketOrderIntent): Promise<AsterOrder> { export async function createMarketOrder(intent: MarketOrderIntent): Promise<Order> {
const params: CreateOrderParams = applyCommonFields( const params: CreateOrderParams = applyCommonFields(
{ {
symbol: intent.symbol, symbol: intent.symbol,
@@ -50,7 +50,7 @@ export async function createMarketOrder(intent: MarketOrderIntent): Promise<Aste
return intent.adapter.createOrder(params); return intent.adapter.createOrder(params);
} }
export async function createStopOrder(intent: StopOrderIntent): Promise<AsterOrder> { export async function createStopOrder(intent: StopOrderIntent): Promise<Order> {
const params: CreateOrderParams = applyCommonFields( const params: CreateOrderParams = applyCommonFields(
{ {
symbol: intent.symbol, symbol: intent.symbol,
@@ -65,11 +65,11 @@ export async function createStopOrder(intent: StopOrderIntent): Promise<AsterOrd
return intent.adapter.createOrder(params); return intent.adapter.createOrder(params);
} }
export async function createTrailingStopOrder(_intent: TrailingStopOrderIntent): Promise<AsterOrder> { export async function createTrailingStopOrder(_intent: TrailingStopOrderIntent): Promise<Order> {
throw new Error("Backpack exchange does not support trailing stop orders"); throw new Error("Backpack exchange does not support trailing stop orders");
} }
export async function createClosePositionOrder(intent: ClosePositionIntent): Promise<AsterOrder> { export async function createClosePositionOrder(intent: ClosePositionIntent): Promise<Order> {
const params: CreateOrderParams = applyCommonFields( const params: CreateOrderParams = applyCommonFields(
{ {
symbol: intent.symbol, symbol: intent.symbol,
+3 -3
View File
@@ -9,7 +9,7 @@ import type {
OrderListener, OrderListener,
TickerListener, TickerListener,
} from "../adapter"; } from "../adapter";
import type { AsterOrder, CreateOrderParams } from "../types"; import type { Order, CreateOrderParams } from "../types";
import { extractMessage } from "../../utils/errors"; import { extractMessage } from "../../utils/errors";
import { BinanceGateway, type BinanceGatewayOptions } from "./gateway"; import { BinanceGateway, type BinanceGatewayOptions } from "./gateway";
@@ -121,7 +121,7 @@ export class BinanceExchangeAdapter implements ExchangeAdapter {
.catch((error) => this.handleInitError("watchFundingRate", error)); .catch((error) => this.handleInitError("watchFundingRate", error));
} }
async createOrder(params: CreateOrderParams): Promise<AsterOrder> { async createOrder(params: CreateOrderParams): Promise<Order> {
await this.ensureInitialized("createOrder"); await this.ensureInitialized("createOrder");
return this.gateway.createOrder(params); return this.gateway.createOrder(params);
} }
@@ -146,7 +146,7 @@ export class BinanceExchangeAdapter implements ExchangeAdapter {
return this.gateway.getPrecision(this.symbol); return this.gateway.getPrecision(this.symbol);
} }
async queryOpenOrders(): Promise<AsterOrder[]> { async queryOpenOrders(): Promise<Order[]> {
await this.ensureInitialized("queryOpenOrders"); await this.ensureInitialized("queryOpenOrders");
return this.gateway.queryOpenOrders(); return this.gateway.queryOpenOrders();
} }
+34 -34
View File
@@ -6,13 +6,13 @@ import axios from "axios";
import { createHash } from "crypto"; import { createHash } from "crypto";
import NodeWebSocket from "ws"; import NodeWebSocket from "ws";
import type { import type {
AsterAccountAsset, AccountAsset,
AsterAccountPosition, AccountPosition,
AsterAccountSnapshot, AccountSnapshot,
AsterDepth, Depth,
AsterKline, Kline,
AsterOrder, Order,
AsterTicker, Ticker,
CreateOrderParams, CreateOrderParams,
OrderType, OrderType,
PositionSide, PositionSide,
@@ -225,9 +225,9 @@ export class BinanceGateway {
private readonly accountListeners = new Set<AccountListener>(); private readonly accountListeners = new Set<AccountListener>();
private readonly orderListeners = new Set<OrderListener>(); private readonly orderListeners = new Set<OrderListener>();
private readonly depthSubs = new Map<string, PublicSubscription<AsterDepth>>(); private readonly depthSubs = new Map<string, PublicSubscription<Depth>>();
private readonly tickerSubs = new Map<string, PublicSubscription<AsterTicker>>(); private readonly tickerSubs = new Map<string, PublicSubscription<Ticker>>();
private readonly klineSubs = new Map<string, PublicSubscription<AsterKline[]>>(); private readonly klineSubs = new Map<string, PublicSubscription<Kline[]>>();
private readonly fundingSubs = new Map<string, PublicSubscription<{ symbol: string; fundingRate: number; updateTime: number }>>(); private readonly fundingSubs = new Map<string, PublicSubscription<{ symbol: string; fundingRate: number; updateTime: number }>>();
private accountPollTimer: ReturnType<typeof setInterval> | null = null; private accountPollTimer: ReturnType<typeof setInterval> | null = null;
@@ -254,15 +254,15 @@ export class BinanceGateway {
}, },
}; };
private readonly localOrders = new Map<string, AsterOrder>(); private readonly localOrders = new Map<string, Order>();
private readonly spotBalances = new Map<string, { free: number; locked: number }>(); private readonly spotBalances = new Map<string, { free: number; locked: number }>();
private readonly perpBalances = new Map<string, { wallet: number; available: number }>(); private readonly perpBalances = new Map<string, { wallet: number; available: number }>();
private readonly perpPositions = new Map<string, AsterAccountPosition>(); private readonly perpPositions = new Map<string, AccountPosition>();
private readonly lastMarkPriceBySymbol = new Map<string, number>(); private readonly lastMarkPriceBySymbol = new Map<string, number>();
private lastSpotSnapshot: AsterAccountSnapshot | null = null; private lastSpotSnapshot: AccountSnapshot | null = null;
private lastPerpSnapshot: AsterAccountSnapshot | null = null; private lastPerpSnapshot: AccountSnapshot | null = null;
constructor(options: BinanceGatewayOptions) { constructor(options: BinanceGatewayOptions) {
this.apiKey = options.apiKey; this.apiKey = options.apiKey;
@@ -465,7 +465,7 @@ export class BinanceGateway {
}); });
} }
async createOrder(params: CreateOrderParams): Promise<AsterOrder> { async createOrder(params: CreateOrderParams): Promise<Order> {
await this.ensureInitialized(params.symbol); await this.ensureInitialized(params.symbol);
const market = this.resolveMarket(params.symbol); const market = this.resolveMarket(params.symbol);
const exchange = this.getExchange(market.kind); const exchange = this.getExchange(market.kind);
@@ -601,10 +601,10 @@ export class BinanceGateway {
}; };
} }
async queryOpenOrders(): Promise<AsterOrder[]> { async queryOpenOrders(): Promise<Order[]> {
await this.ensureInitialized(this.defaultSymbol); await this.ensureInitialized(this.defaultSymbol);
const kinds = this.getPrivateKinds(); const kinds = this.getPrivateKinds();
const result: AsterOrder[] = []; const result: Order[] = [];
for (const kind of kinds) { for (const kind of kinds) {
const exchange = this.getExchange(kind); const exchange = this.getExchange(kind);
const openOrders = (await exchange.fetchOpenOrders()) as CcxtOrder[]; const openOrders = (await exchange.fetchOpenOrders()) as CcxtOrder[];
@@ -619,7 +619,7 @@ export class BinanceGateway {
return result; return result;
} }
async queryAccountSnapshot(): Promise<AsterAccountSnapshot | null> { async queryAccountSnapshot(): Promise<AccountSnapshot | null> {
await this.ensureInitialized(this.defaultSymbol); await this.ensureInitialized(this.defaultSymbol);
const kinds = this.getPrivateKinds(); const kinds = this.getPrivateKinds();
for (const kind of kinds) { for (const kind of kinds) {
@@ -910,7 +910,7 @@ export class BinanceGateway {
this.lastPerpSnapshot = this.buildPerpSnapshot(); this.lastPerpSnapshot = this.buildPerpSnapshot();
} }
private mapSpotExecutionReport(payload: any): AsterOrder | null { private mapSpotExecutionReport(payload: any): Order | null {
const order = payload; const order = payload;
const symbolRaw = String(order?.s ?? "").toUpperCase(); const symbolRaw = String(order?.s ?? "").toUpperCase();
if (!symbolRaw) return null; if (!symbolRaw) return null;
@@ -940,7 +940,7 @@ export class BinanceGateway {
}; };
} }
private mapPerpOrderTradeUpdate(payload: any): AsterOrder | null { private mapPerpOrderTradeUpdate(payload: any): Order | null {
const order = payload?.o; const order = payload?.o;
if (!order) return null; if (!order) return null;
const symbolRaw = String(order?.s ?? "").toUpperCase(); const symbolRaw = String(order?.s ?? "").toUpperCase();
@@ -1111,7 +1111,7 @@ export class BinanceGateway {
(ws as any).onerror = (event: any) => onError(event?.error ?? event); (ws as any).onerror = (event: any) => onError(event?.error ?? event);
} }
private mapDepthPayload(payload: unknown, market: BinanceMarketRef): AsterDepth | null { private mapDepthPayload(payload: unknown, market: BinanceMarketRef): Depth | null {
const data = payload as any; const data = payload as any;
const bids = Array.isArray(data?.b) ? data.b : []; const bids = Array.isArray(data?.b) ? data.b : [];
const asks = Array.isArray(data?.a) ? data.a : []; const asks = Array.isArray(data?.a) ? data.a : [];
@@ -1129,7 +1129,7 @@ export class BinanceGateway {
}; };
} }
private mapTickerPayload(payload: unknown, market: BinanceMarketRef): AsterTicker | null { private mapTickerPayload(payload: unknown, market: BinanceMarketRef): Ticker | null {
const data = payload as any; const data = payload as any;
const lastPrice = String(data?.c ?? ""); const lastPrice = String(data?.c ?? "");
if (!lastPrice) return null; if (!lastPrice) return null;
@@ -1156,7 +1156,7 @@ export class BinanceGateway {
}; };
} }
private mapKlinePayload(payload: unknown, interval: string): AsterKline[] | null { private mapKlinePayload(payload: unknown, interval: string): Kline[] | null {
const data = payload as any; const data = payload as any;
const kline = data?.k; const kline = data?.k;
if (!kline) return null; if (!kline) return null;
@@ -1288,7 +1288,7 @@ export class BinanceGateway {
} }
private async attachPerpPositions(): Promise<void> { private async attachPerpPositions(): Promise<void> {
const next = new Map<string, AsterAccountPosition>(); const next = new Map<string, AccountPosition>();
try { try {
const raw = (await this.perpExchange.fetchPositions()) as any[]; const raw = (await this.perpExchange.fetchPositions()) as any[];
for (const row of raw ?? []) { for (const row of raw ?? []) {
@@ -1325,7 +1325,7 @@ export class BinanceGateway {
private async fetchAndUpdateOrders(kind: MarketKind): Promise<void> { private async fetchAndUpdateOrders(kind: MarketKind): Promise<void> {
const exchange = this.getExchange(kind); const exchange = this.getExchange(kind);
const openOrders = (await exchange.fetchOpenOrders()) as CcxtOrder[]; const openOrders = (await exchange.fetchOpenOrders()) as CcxtOrder[];
const remote = new Map<string, AsterOrder>(); const remote = new Map<string, Order>();
for (const order of openOrders) { for (const order of openOrders) {
const market = this.resolveMarketByCcxtSymbol(kind, String(order?.symbol ?? "")); const market = this.resolveMarketByCcxtSymbol(kind, String(order?.symbol ?? ""));
const mapped = this.mapCcxtOrder(order, kind, market?.id ?? String(order?.symbol ?? "")); const mapped = this.mapCcxtOrder(order, kind, market?.id ?? String(order?.symbol ?? ""));
@@ -1367,7 +1367,7 @@ export class BinanceGateway {
} }
} }
private upsertOrder(order: AsterOrder, kind: MarketKind, marketId: string): void { private upsertOrder(order: Order, kind: MarketKind, marketId: string): void {
const id = String(order.orderId); const id = String(order.orderId);
const symbol = this.resolveDisplaySymbol(kind, marketId); const symbol = this.resolveDisplaySymbol(kind, marketId);
const normalized = { ...order, symbol }; const normalized = { ...order, symbol };
@@ -1402,7 +1402,7 @@ export class BinanceGateway {
if (changed) this.emitOrders(); if (changed) this.emitOrders();
} }
private isOrderActive(order: AsterOrder): boolean { private isOrderActive(order: Order): boolean {
const status = String(order.status ?? "").toUpperCase(); const status = String(order.status ?? "").toUpperCase();
if (!status) return true; if (!status) return true;
if (status === "FILLED" || status === "CANCELED" || status === "CANCELLED" || status === "REJECTED" || status === "EXPIRED") { if (status === "FILLED" || status === "CANCELED" || status === "CANCELLED" || status === "REJECTED" || status === "EXPIRED") {
@@ -1412,7 +1412,7 @@ export class BinanceGateway {
return true; return true;
} }
private buildCombinedAccountSnapshot(): AsterAccountSnapshot | null { private buildCombinedAccountSnapshot(): AccountSnapshot | null {
const kinds = this.getPrivateKinds(); const kinds = this.getPrivateKinds();
if (kinds.length === 0) return null; if (kinds.length === 0) return null;
if (kinds.length === 1) { if (kinds.length === 1) {
@@ -1421,7 +1421,7 @@ export class BinanceGateway {
const spot = this.buildSpotSnapshot(); const spot = this.buildSpotSnapshot();
const perp = this.buildPerpSnapshot(); const perp = this.buildPerpSnapshot();
const perpAssetsTagged: AsterAccountAsset[] = perp.assets.map((asset) => ({ const perpAssetsTagged: AccountAsset[] = perp.assets.map((asset) => ({
...asset, ...asset,
asset: `${asset.asset}0`, asset: `${asset.asset}0`,
})); }));
@@ -1441,8 +1441,8 @@ export class BinanceGateway {
}; };
} }
private buildSpotSnapshot(): AsterAccountSnapshot { private buildSpotSnapshot(): AccountSnapshot {
const assets: AsterAccountAsset[] = []; const assets: AccountAsset[] = [];
let totalWallet = 0; let totalWallet = 0;
const now = Date.now(); const now = Date.now();
for (const [asset, balance] of this.spotBalances.entries()) { for (const [asset, balance] of this.spotBalances.entries()) {
@@ -1471,9 +1471,9 @@ export class BinanceGateway {
}; };
} }
private buildPerpSnapshot(): AsterAccountSnapshot { private buildPerpSnapshot(): AccountSnapshot {
const now = Date.now(); const now = Date.now();
const assets: AsterAccountAsset[] = []; const assets: AccountAsset[] = [];
let totalWallet = 0; let totalWallet = 0;
for (const [asset, balance] of this.perpBalances.entries()) { for (const [asset, balance] of this.perpBalances.entries()) {
totalWallet += balance.wallet; totalWallet += balance.wallet;
@@ -1506,7 +1506,7 @@ export class BinanceGateway {
}; };
} }
private mapCcxtOrder(order: CcxtOrder, kind: MarketKind, marketId: string): AsterOrder { private mapCcxtOrder(order: CcxtOrder, kind: MarketKind, marketId: string): Order {
const symbol = this.resolveDisplaySymbol(kind, marketId); const symbol = this.resolveDisplaySymbol(kind, marketId);
const side = String(order.side ?? "buy").toUpperCase() === "SELL" ? "SELL" : "BUY"; const side = String(order.side ?? "buy").toUpperCase() === "SELL" ? "SELL" : "BUY";
return { return {
+6 -6
View File
@@ -1,4 +1,4 @@
import type { AsterOrder, CreateOrderParams } from "../types"; import type { Order, CreateOrderParams } from "../types";
import type { import type {
BaseOrderIntent, BaseOrderIntent,
ClosePositionIntent, ClosePositionIntent,
@@ -25,7 +25,7 @@ function applyCommonFields(params: CreateOrderParams, intent: BaseOrderIntent):
return params; return params;
} }
export async function createLimitOrder(intent: LimitOrderIntent): Promise<AsterOrder> { export async function createLimitOrder(intent: LimitOrderIntent): Promise<Order> {
const params: CreateOrderParams = applyCommonFields( const params: CreateOrderParams = applyCommonFields(
{ {
symbol: intent.symbol, symbol: intent.symbol,
@@ -40,7 +40,7 @@ export async function createLimitOrder(intent: LimitOrderIntent): Promise<AsterO
return intent.adapter.createOrder(params); return intent.adapter.createOrder(params);
} }
export async function createMarketOrder(intent: MarketOrderIntent): Promise<AsterOrder> { export async function createMarketOrder(intent: MarketOrderIntent): Promise<Order> {
const params: CreateOrderParams = applyCommonFields( const params: CreateOrderParams = applyCommonFields(
{ {
symbol: intent.symbol, symbol: intent.symbol,
@@ -53,7 +53,7 @@ export async function createMarketOrder(intent: MarketOrderIntent): Promise<Aste
return intent.adapter.createOrder(params); return intent.adapter.createOrder(params);
} }
export async function createStopOrder(intent: StopOrderIntent): Promise<AsterOrder> { export async function createStopOrder(intent: StopOrderIntent): Promise<Order> {
const params: CreateOrderParams = applyCommonFields( const params: CreateOrderParams = applyCommonFields(
{ {
symbol: intent.symbol, symbol: intent.symbol,
@@ -69,7 +69,7 @@ export async function createStopOrder(intent: StopOrderIntent): Promise<AsterOrd
return intent.adapter.createOrder(params); return intent.adapter.createOrder(params);
} }
export async function createTrailingStopOrder(intent: TrailingStopOrderIntent): Promise<AsterOrder> { export async function createTrailingStopOrder(intent: TrailingStopOrderIntent): Promise<Order> {
const params: CreateOrderParams = applyCommonFields( const params: CreateOrderParams = applyCommonFields(
{ {
symbol: intent.symbol, symbol: intent.symbol,
@@ -85,7 +85,7 @@ export async function createTrailingStopOrder(intent: TrailingStopOrderIntent):
return intent.adapter.createOrder(params); return intent.adapter.createOrder(params);
} }
export async function createClosePositionOrder(intent: ClosePositionIntent): Promise<AsterOrder> { export async function createClosePositionOrder(intent: ClosePositionIntent): Promise<Order> {
const params: CreateOrderParams = applyCommonFields( const params: CreateOrderParams = applyCommonFields(
{ {
symbol: intent.symbol, symbol: intent.symbol,
+6 -6
View File
@@ -11,8 +11,8 @@ import type {
TickerListener, TickerListener,
} from "./adapter"; } from "./adapter";
import type { import type {
AsterAccountSnapshot, AccountSnapshot,
AsterOrder, Order,
CreateOrderParams, CreateOrderParams,
} from "./types"; } from "./types";
@@ -66,7 +66,7 @@ export class DryRunExchangeAdapter implements ExchangeAdapter {
this.inner.watchFundingRate(symbol, cb); this.inner.watchFundingRate(symbol, cb);
} }
async createOrder(params: CreateOrderParams): Promise<AsterOrder> { async createOrder(params: CreateOrderParams): Promise<Order> {
this.record("createOrder", params); this.record("createOrder", params);
return createSyntheticOrder(params, ++this.syntheticCounter); return createSyntheticOrder(params, ++this.syntheticCounter);
} }
@@ -104,12 +104,12 @@ export class DryRunExchangeAdapter implements ExchangeAdapter {
this.inner.offRestHealthEvent?.(listener); this.inner.offRestHealthEvent?.(listener);
} }
async queryOpenOrders(): Promise<AsterOrder[]> { async queryOpenOrders(): Promise<Order[]> {
if (!this.inner.queryOpenOrders) return []; if (!this.inner.queryOpenOrders) return [];
return this.inner.queryOpenOrders(); return this.inner.queryOpenOrders();
} }
async queryAccountSnapshot(): Promise<AsterAccountSnapshot | null> { async queryAccountSnapshot(): Promise<AccountSnapshot | null> {
if (!this.inner.queryAccountSnapshot) return null; if (!this.inner.queryAccountSnapshot) return null;
return this.inner.queryAccountSnapshot(); return this.inner.queryAccountSnapshot();
} }
@@ -128,7 +128,7 @@ export class DryRunExchangeAdapter implements ExchangeAdapter {
} }
} }
function createSyntheticOrder(params: CreateOrderParams, counter: number): AsterOrder { function createSyntheticOrder(params: CreateOrderParams, counter: number): Order {
const now = Date.now(); const now = Date.now();
const orderId = `dry-run-${now}-${counter}`; const orderId = `dry-run-${now}-${counter}`;
return { return {
+2 -2
View File
@@ -9,7 +9,7 @@ import type {
OrderListener, OrderListener,
TickerListener, TickerListener,
} from "../adapter"; } from "../adapter";
import type { AsterOrder, CreateOrderParams } from "../types"; import type { Order, CreateOrderParams } from "../types";
import { extractMessage } from "../../utils/errors"; import { extractMessage } from "../../utils/errors";
import { import {
GrvtGateway, GrvtGateway,
@@ -123,7 +123,7 @@ export class GrvtExchangeAdapter implements ExchangeAdapter {
this.gateway.onKlines(this.safeInvoke("watchKlines", cb)); this.gateway.onKlines(this.safeInvoke("watchKlines", cb));
} }
async createOrder(params: CreateOrderParams): Promise<AsterOrder> { async createOrder(params: CreateOrderParams): Promise<Order> {
await this.ensureInitialized("createOrder"); await this.ensureInitialized("createOrder");
return this.gateway.createOrder(params); return this.gateway.createOrder(params);
} }
+49 -49
View File
@@ -23,12 +23,12 @@ import type {
IOrder, IOrder,
} from "@grvt/client/interfaces"; } from "@grvt/client/interfaces";
import type { import type {
AsterAccountSnapshot, AccountSnapshot,
AsterAccountPosition, AccountPosition,
AsterDepth, Depth,
AsterKline, Kline,
AsterOrder, Order,
AsterTicker, Ticker,
CreateOrderParams, CreateOrderParams,
OrderSide, OrderSide,
GrvtSignedOrder, GrvtSignedOrder,
@@ -172,7 +172,7 @@ interface HostsConfig {
interface KlineCache { interface KlineCache {
interval: string; interval: string;
values: AsterKline[]; values: Kline[];
} }
interface InstrumentInfo { interface InstrumentInfo {
@@ -215,12 +215,12 @@ export class GrvtGateway {
private instrumentInfo: InstrumentInfo | null = null; private instrumentInfo: InstrumentInfo | null = null;
private sessionPromise: Promise<void> | null = null; private sessionPromise: Promise<void> | null = null;
private accountSnapshot: AsterAccountSnapshot | null = null; private accountSnapshot: AccountSnapshot | null = null;
private openOrders: AsterOrder[] = []; private openOrders: Order[] = [];
private positions: AsterAccountPosition[] = []; private positions: AccountPosition[] = [];
private lastPositionsUpdateAt: number = 0; private lastPositionsUpdateAt: number = 0;
private depthSnapshot: AsterDepth | null = null; private depthSnapshot: Depth | null = null;
private tickerSnapshot: AsterTicker | null = null; private tickerSnapshot: Ticker | null = null;
private klineCache: KlineCache | null = null; private klineCache: KlineCache | null = null;
// WebSocket state // WebSocket state
@@ -228,11 +228,11 @@ export class GrvtGateway {
private wsConnected = false; private wsConnected = false;
private positionsRefreshTimer: ReturnType<typeof setTimeout> | null = null; private positionsRefreshTimer: ReturnType<typeof setTimeout> | null = null;
private accountListeners = new Set<(snapshot: AsterAccountSnapshot) => void>(); private accountListeners = new Set<(snapshot: AccountSnapshot) => void>();
private ordersListeners = new Set<(orders: AsterOrder[]) => void>(); private ordersListeners = new Set<(orders: Order[]) => void>();
private depthListeners = new Set<(depth: AsterDepth) => void>(); private depthListeners = new Set<(depth: Depth) => void>();
private tickerListeners = new Set<(ticker: AsterTicker) => void>(); private tickerListeners = new Set<(ticker: Ticker) => void>();
private klineListeners = new Set<(klines: AsterKline[]) => void>(); private klineListeners = new Set<(klines: Kline[]) => void>();
private accountTimer: ReturnType<typeof setInterval> | null = null; private accountTimer: ReturnType<typeof setInterval> | null = null;
private ordersTimer: ReturnType<typeof setInterval> | null = null; private ordersTimer: ReturnType<typeof setInterval> | null = null;
@@ -316,7 +316,7 @@ export class GrvtGateway {
this.initialized = true; this.initialized = true;
} }
onAccount(listener: (snapshot: AsterAccountSnapshot) => void): () => void { onAccount(listener: (snapshot: AccountSnapshot) => void): () => void {
this.accountListeners.add(listener); this.accountListeners.add(listener);
if (this.accountSnapshot) listener(cloneAccount(this.accountSnapshot)); if (this.accountSnapshot) listener(cloneAccount(this.accountSnapshot));
return () => { return () => {
@@ -324,7 +324,7 @@ export class GrvtGateway {
}; };
} }
onOrders(listener: (orders: AsterOrder[]) => void): () => void { onOrders(listener: (orders: Order[]) => void): () => void {
this.ordersListeners.add(listener); this.ordersListeners.add(listener);
if (this.openOrders.length) listener(cloneOrders(this.openOrders)); if (this.openOrders.length) listener(cloneOrders(this.openOrders));
return () => { return () => {
@@ -332,7 +332,7 @@ export class GrvtGateway {
}; };
} }
onDepth(listener: (depth: AsterDepth) => void): () => void { onDepth(listener: (depth: Depth) => void): () => void {
this.depthListeners.add(listener); this.depthListeners.add(listener);
if (this.depthSnapshot) listener(cloneDepth(this.depthSnapshot)); if (this.depthSnapshot) listener(cloneDepth(this.depthSnapshot));
return () => { return () => {
@@ -340,7 +340,7 @@ export class GrvtGateway {
}; };
} }
onTicker(listener: (ticker: AsterTicker) => void): () => void { onTicker(listener: (ticker: Ticker) => void): () => void {
this.tickerListeners.add(listener); this.tickerListeners.add(listener);
if (this.tickerSnapshot) listener(cloneTicker(this.tickerSnapshot)); if (this.tickerSnapshot) listener(cloneTicker(this.tickerSnapshot));
return () => { return () => {
@@ -348,7 +348,7 @@ export class GrvtGateway {
}; };
} }
onKlines(listener: (klines: AsterKline[]) => void): () => void { onKlines(listener: (klines: Kline[]) => void): () => void {
this.klineListeners.add(listener); this.klineListeners.add(listener);
if (this.klineCache) listener(cloneKlines(this.klineCache.values)); if (this.klineCache) listener(cloneKlines(this.klineCache.values));
return () => { return () => {
@@ -356,19 +356,19 @@ export class GrvtGateway {
}; };
} }
getAccountSnapshot(): AsterAccountSnapshot | null { getAccountSnapshot(): AccountSnapshot | null {
return this.accountSnapshot ? cloneAccount(this.accountSnapshot) : null; return this.accountSnapshot ? cloneAccount(this.accountSnapshot) : null;
} }
getOpenOrders(): AsterOrder[] { getOpenOrders(): Order[] {
return cloneOrders(this.openOrders); return cloneOrders(this.openOrders);
} }
getPositions(): AsterAccountPosition[] { getPositions(): AccountPosition[] {
return this.positions.map((position) => ({ ...position })); return this.positions.map((position) => ({ ...position }));
} }
async createOrder(params: CreateOrderParams): Promise<AsterOrder> { async createOrder(params: CreateOrderParams): Promise<Order> {
if (params.type === "TRAILING_STOP_MARKET") { if (params.type === "TRAILING_STOP_MARKET") {
throw new Error(TRAILING_NOT_SUPPORTED_ERROR); throw new Error(TRAILING_NOT_SUPPORTED_ERROR);
} }
@@ -660,7 +660,7 @@ export class GrvtGateway {
this.emitKlines(klines); this.emitKlines(klines);
} }
private mergeOrder(order: AsterOrder): void { private mergeOrder(order: Order): void {
const index = this.openOrders.findIndex((item) => String(item.orderId) === String(order.orderId)); const index = this.openOrders.findIndex((item) => String(item.orderId) === String(order.orderId));
if (index >= 0) { if (index >= 0) {
this.openOrders[index] = order; this.openOrders[index] = order;
@@ -678,7 +678,7 @@ export class GrvtGateway {
} }
} }
private emitAccount(snapshot: AsterAccountSnapshot): void { private emitAccount(snapshot: AccountSnapshot): void {
const cloned = cloneAccount(snapshot); const cloned = cloneAccount(snapshot);
this.accountListeners.forEach((listener) => { this.accountListeners.forEach((listener) => {
try { try {
@@ -689,7 +689,7 @@ export class GrvtGateway {
}); });
} }
private emitOrders(orders: AsterOrder[]): void { private emitOrders(orders: Order[]): void {
const cloned = cloneOrders(orders); const cloned = cloneOrders(orders);
this.ordersListeners.forEach((listener) => { this.ordersListeners.forEach((listener) => {
try { try {
@@ -700,7 +700,7 @@ export class GrvtGateway {
}); });
} }
private emitDepth(depth: AsterDepth): void { private emitDepth(depth: Depth): void {
const cloned = cloneDepth(depth); const cloned = cloneDepth(depth);
this.depthListeners.forEach((listener) => { this.depthListeners.forEach((listener) => {
try { try {
@@ -711,7 +711,7 @@ export class GrvtGateway {
}); });
} }
private emitTicker(ticker: AsterTicker): void { private emitTicker(ticker: Ticker): void {
const cloned = cloneTicker(ticker); const cloned = cloneTicker(ticker);
this.tickerListeners.forEach((listener) => { this.tickerListeners.forEach((listener) => {
try { try {
@@ -722,7 +722,7 @@ export class GrvtGateway {
}); });
} }
private emitKlines(klines: AsterKline[]): void { private emitKlines(klines: Kline[]): void {
const cloned = cloneKlines(klines); const cloned = cloneKlines(klines);
this.klineListeners.forEach((listener) => { this.klineListeners.forEach((listener) => {
try { try {
@@ -974,15 +974,15 @@ function normalizeHex(value: string): string {
return `0x${trimmed.toLowerCase()}`; return `0x${trimmed.toLowerCase()}`;
} }
function cloneAccount(snapshot: AsterAccountSnapshot): AsterAccountSnapshot { function cloneAccount(snapshot: AccountSnapshot): AccountSnapshot {
return JSON.parse(JSON.stringify(snapshot)); return JSON.parse(JSON.stringify(snapshot));
} }
function cloneOrders(orders: AsterOrder[]): AsterOrder[] { function cloneOrders(orders: Order[]): Order[] {
return orders.map((order) => ({ ...order })); return orders.map((order) => ({ ...order }));
} }
function cloneDepth(depth: AsterDepth): AsterDepth { function cloneDepth(depth: Depth): Depth {
return { return {
...depth, ...depth,
bids: depth.bids.map((level) => [...level] as [string, string]), bids: depth.bids.map((level) => [...level] as [string, string]),
@@ -990,11 +990,11 @@ function cloneDepth(depth: AsterDepth): AsterDepth {
}; };
} }
function cloneTicker(ticker: AsterTicker): AsterTicker { function cloneTicker(ticker: Ticker): Ticker {
return { ...ticker }; return { ...ticker };
} }
function cloneKlines(klines: AsterKline[]): AsterKline[] { function cloneKlines(klines: Kline[]): Kline[] {
return klines.map((kline) => ({ ...kline })); return klines.map((kline) => ({ ...kline }));
} }
@@ -1023,11 +1023,11 @@ function scaleDecimal(value: string | number | undefined, decimals: number): big
return scaleDecimal(fixed.toFixed(decimals), decimals); return scaleDecimal(fixed.toFixed(decimals), decimals);
} }
function sumUnrealized(positions: AsterAccountPosition[]): number { function sumUnrealized(positions: AccountPosition[]): number {
return positions.reduce((total, position) => total + Number(position.unrealizedProfit ?? 0), 0); return positions.reduce((total, position) => total + Number(position.unrealizedProfit ?? 0), 0);
} }
function getNewestPositionEventTime(positions: AsterAccountPosition[]): number { function getNewestPositionEventTime(positions: AccountPosition[]): number {
if (!positions.length) return Date.now(); if (!positions.length) return Date.now();
return positions.reduce((max, p) => Math.max(max, Number(p.updateTime) || 0), 0); return positions.reduce((max, p) => Math.max(max, Number(p.updateTime) || 0), 0);
} }
@@ -1036,7 +1036,7 @@ function mapAccountSnapshot(
response: IApiSubAccountSummaryResponse, response: IApiSubAccountSummaryResponse,
symbol: string, symbol: string,
instrument: string instrument: string
): AsterAccountSnapshot { ): AccountSnapshot {
const result = response.result; const result = response.result;
if (!result) { if (!result) {
return emptyAccount(symbol); return emptyAccount(symbol);
@@ -1068,7 +1068,7 @@ function mapPositions(
response: IApiPositionsResponse, response: IApiPositionsResponse,
symbol: string, symbol: string,
instrument: string instrument: string
): AsterAccountPosition[] { ): AccountPosition[] {
return (response.result ?? []) return (response.result ?? [])
.filter((entry) => !entry.instrument || entry.instrument === instrument) .filter((entry) => !entry.instrument || entry.instrument === instrument)
.map((entry) => ({ .map((entry) => ({
@@ -1084,11 +1084,11 @@ function mapPositions(
})); }));
} }
function mapOpenOrders(response: IApiOpenOrdersResponse, symbol: string): AsterOrder[] { function mapOpenOrders(response: IApiOpenOrdersResponse, symbol: string): Order[] {
return (response.result ?? []).map((order) => mapOrder(order, symbol)); return (response.result ?? []).map((order) => mapOrder(order, symbol));
} }
function mapOrder(order: IOrder, symbol: string): AsterOrder { function mapOrder(order: IOrder, symbol: string): Order {
const leg = order.legs?.[0]; const leg = order.legs?.[0];
const state = order.state; const state = order.state;
const metadata = order.metadata; const metadata = order.metadata;
@@ -1126,7 +1126,7 @@ function mapOrder(order: IOrder, symbol: string): AsterOrder {
}; };
} }
function mapDepth(response: IApiOrderbookLevelsResponse, symbol: string): AsterDepth | null { function mapDepth(response: IApiOrderbookLevelsResponse, symbol: string): Depth | null {
const result = response.result; const result = response.result;
if (!result) return null; if (!result) return null;
const toLevel = (entries: Array<{ price?: string; size?: string }> | undefined) => const toLevel = (entries: Array<{ price?: string; size?: string }> | undefined) =>
@@ -1140,7 +1140,7 @@ function mapDepth(response: IApiOrderbookLevelsResponse, symbol: string): AsterD
}; };
} }
function mapTicker(response: IApiTickerResponse, symbol: string): AsterTicker | null { function mapTicker(response: IApiTickerResponse, symbol: string): Ticker | null {
const result = response.result; const result = response.result;
if (!result) return null; if (!result) return null;
const buyVolume = Number(result.buy_volume_24h_b ?? 0); const buyVolume = Number(result.buy_volume_24h_b ?? 0);
@@ -1161,7 +1161,7 @@ function mapTicker(response: IApiTickerResponse, symbol: string): AsterTicker |
}; };
} }
function mapKlines(response: IApiCandlestickResponse, _symbol: string): AsterKline[] { function mapKlines(response: IApiCandlestickResponse, _symbol: string): Kline[] {
return (response.result ?? []).reverse().map((entry) => ({ return (response.result ?? []).reverse().map((entry) => ({
openTime: nsToMs(entry.open_time ?? Date.now() * ONE_SECOND_IN_NANOSECONDS), openTime: nsToMs(entry.open_time ?? Date.now() * ONE_SECOND_IN_NANOSECONDS),
closeTime: nsToMs(entry.close_time ?? Date.now() * ONE_SECOND_IN_NANOSECONDS), closeTime: nsToMs(entry.close_time ?? Date.now() * ONE_SECOND_IN_NANOSECONDS),
@@ -1174,7 +1174,7 @@ function mapKlines(response: IApiCandlestickResponse, _symbol: string): AsterKli
})); }));
} }
function mapCreateOrderResponse(response: IApiCreateOrderResponse, symbol: string): AsterOrder { function mapCreateOrderResponse(response: IApiCreateOrderResponse, symbol: string): Order {
const order = response.result ?? (response as unknown as { order?: IOrder }).order; const order = response.result ?? (response as unknown as { order?: IOrder }).order;
if (!order) { if (!order) {
return { return {
@@ -1551,7 +1551,7 @@ function padPrivateKey(value: string): string {
return hex; return hex;
} }
function emptyAccount(symbol: string): AsterAccountSnapshot { function emptyAccount(symbol: string): AccountSnapshot {
return { return {
canTrade: true, canTrade: true,
canDeposit: true, canDeposit: true,
@@ -1568,5 +1568,5 @@ function emptyAccount(symbol: string): AsterAccountSnapshot {
updateTime: Date.now(), updateTime: Date.now(),
}, },
], ],
} as AsterAccountSnapshot; } as AccountSnapshot;
} }
+6 -6
View File
@@ -1,4 +1,4 @@
import type { AsterOrder, CreateOrderParams } from "../types"; import type { Order, CreateOrderParams } from "../types";
import type { import type {
BaseOrderIntent, BaseOrderIntent,
ClosePositionIntent, ClosePositionIntent,
@@ -25,7 +25,7 @@ function applyCommonFields(params: CreateOrderParams, intent: BaseOrderIntent):
return params; return params;
} }
export async function createLimitOrder(intent: LimitOrderIntent): Promise<AsterOrder> { export async function createLimitOrder(intent: LimitOrderIntent): Promise<Order> {
const params: CreateOrderParams = applyCommonFields( const params: CreateOrderParams = applyCommonFields(
{ {
symbol: intent.symbol, symbol: intent.symbol,
@@ -40,7 +40,7 @@ export async function createLimitOrder(intent: LimitOrderIntent): Promise<AsterO
return intent.adapter.createOrder(params); return intent.adapter.createOrder(params);
} }
export async function createMarketOrder(intent: MarketOrderIntent): Promise<AsterOrder> { export async function createMarketOrder(intent: MarketOrderIntent): Promise<Order> {
const params: CreateOrderParams = applyCommonFields( const params: CreateOrderParams = applyCommonFields(
{ {
symbol: intent.symbol, symbol: intent.symbol,
@@ -53,7 +53,7 @@ export async function createMarketOrder(intent: MarketOrderIntent): Promise<Aste
return intent.adapter.createOrder(params); return intent.adapter.createOrder(params);
} }
export async function createStopOrder(intent: StopOrderIntent): Promise<AsterOrder> { export async function createStopOrder(intent: StopOrderIntent): Promise<Order> {
const params: CreateOrderParams = applyCommonFields( const params: CreateOrderParams = applyCommonFields(
{ {
symbol: intent.symbol, symbol: intent.symbol,
@@ -71,11 +71,11 @@ export async function createStopOrder(intent: StopOrderIntent): Promise<AsterOrd
return intent.adapter.createOrder(params); return intent.adapter.createOrder(params);
} }
export async function createTrailingStopOrder(_intent: TrailingStopOrderIntent): Promise<AsterOrder> { export async function createTrailingStopOrder(_intent: TrailingStopOrderIntent): Promise<Order> {
throw new Error("GRVT exchange does not support trailing stop orders"); throw new Error("GRVT exchange does not support trailing stop orders");
} }
export async function createClosePositionOrder(intent: ClosePositionIntent): Promise<AsterOrder> { export async function createClosePositionOrder(intent: ClosePositionIntent): Promise<Order> {
const params: CreateOrderParams = applyCommonFields( const params: CreateOrderParams = applyCommonFields(
{ {
symbol: intent.symbol, symbol: intent.symbol,
+2 -2
View File
@@ -7,7 +7,7 @@ import type {
OrderListener, OrderListener,
TickerListener, TickerListener,
} from "../adapter"; } from "../adapter";
import type { AsterOrder, CreateOrderParams } from "../types"; import type { Order, CreateOrderParams } from "../types";
import { extractMessage } from "../../utils/errors"; import { extractMessage } from "../../utils/errors";
import { LighterGateway, type LighterGatewayOptions } from "./gateway"; import { LighterGateway, type LighterGatewayOptions } from "./gateway";
@@ -102,7 +102,7 @@ export class LighterExchangeAdapter implements ExchangeAdapter {
this.gateway.watchKlines(interval, handler); this.gateway.watchKlines(interval, handler);
} }
async createOrder(params: CreateOrderParams): Promise<AsterOrder> { async createOrder(params: CreateOrderParams): Promise<Order> {
await this.ensureInitialized("createOrder"); await this.ensureInitialized("createOrder");
return this.gateway.createOrder(params); return this.gateway.createOrder(params);
} }
+18 -18
View File
@@ -8,12 +8,12 @@ import type {
TickerListener, TickerListener,
} from "../adapter"; } from "../adapter";
import type { import type {
AsterAccountAsset, AccountAsset,
AsterAccountSnapshot, AccountSnapshot,
AsterDepth, Depth,
AsterKline, Kline,
AsterOrder, Order,
AsterTicker, Ticker,
CreateOrderParams, CreateOrderParams,
} from "../types"; } from "../types";
import type { OrderSide, OrderType } from "../types"; import type { OrderSide, OrderType } from "../types";
@@ -189,12 +189,12 @@ export class LighterGateway {
private accountPollInFlight = false; private accountPollInFlight = false;
private ordersResyncTimer: ReturnType<typeof setInterval> | null = null; private ordersResyncTimer: ReturnType<typeof setInterval> | null = null;
private ordersResyncInFlight = false; private ordersResyncInFlight = false;
private readonly klineCache = new Map<string, AsterKline[]>(); private readonly klineCache = new Map<string, Kline[]>();
private readonly accountEvent = createEvent<AsterAccountSnapshot>(); private readonly accountEvent = createEvent<AccountSnapshot>();
private readonly ordersEvent = createEvent<AsterOrder[]>(); private readonly ordersEvent = createEvent<Order[]>();
private readonly depthEvent = createEvent<AsterDepth>(); private readonly depthEvent = createEvent<Depth>();
private readonly tickerEvent = createEvent<AsterTicker>(); private readonly tickerEvent = createEvent<Ticker>();
private readonly klinesEvent = createEvent<AsterKline[]>(); private readonly klinesEvent = createEvent<Kline[]>();
private readonly auth = { token: null as string | null, expiresAt: 0 }; private readonly auth = { token: null as string | null, expiresAt: 0 };
private readonly l1Address: string | null; private readonly l1Address: string | null;
private loggedCreateOrderPayload = false; private loggedCreateOrderPayload = false;
@@ -359,8 +359,8 @@ export class LighterGateway {
this.klinesEvent.add(handler); this.klinesEvent.add(handler);
} }
async createOrder(params: CreateOrderParams): Promise<AsterOrder> { async createOrder(params: CreateOrderParams): Promise<Order> {
const run = async (): Promise<AsterOrder> => { const run = async (): Promise<Order> => {
await this.ensureInitialized(); await this.ensureInitialized();
const conversion = this.mapCreateOrderParams(params); const conversion = this.mapCreateOrderParams(params);
const { baseAmountScaledString, priceScaledString, triggerPriceScaledString, ...signParams } = conversion; const { baseAmountScaledString, priceScaledString, triggerPriceScaledString, ...signParams } = conversion;
@@ -1710,7 +1710,7 @@ export class LighterGateway {
const bestAsk = getBestPrice(this.orderBook.asks, "ask"); const bestAsk = getBestPrice(this.orderBook.asks, "ask");
if (bestBid == null && bestAsk == null) return; if (bestBid == null && bestAsk == null) return;
const last = bestBid != null && bestAsk != null ? (bestBid + bestAsk) / 2 : (bestBid ?? bestAsk ?? 0); const last = bestBid != null && bestAsk != null ? (bestBid + bestAsk) / 2 : (bestBid ?? bestAsk ?? 0);
const ticker: AsterTicker = { const ticker: Ticker = {
symbol: this.displaySymbol, symbol: this.displaySymbol,
eventType: "lighterSyntheticTicker", eventType: "lighterSyntheticTicker",
eventTime: Date.now(), eventTime: Date.now(),
@@ -1736,10 +1736,10 @@ export class LighterGateway {
this.tickerEvent.emit(ticker); this.tickerEvent.emit(ticker);
} }
private buildAccountAssets(): AsterAccountAsset[] { private buildAccountAssets(): AccountAsset[] {
if (!this.assets.size) return []; if (!this.assets.size) return [];
const now = Date.now(); const now = Date.now();
const list: AsterAccountAsset[] = []; const list: AccountAsset[] = [];
for (const asset of this.assets.values()) { for (const asset of this.assets.values()) {
const balanceNum = parseNumber(asset.balance); const balanceNum = parseNumber(asset.balance);
const lockedNum = parseNumber(asset.locked_balance ?? 0); const lockedNum = parseNumber(asset.locked_balance ?? 0);
@@ -2039,7 +2039,7 @@ function mergeLevels(existing: LighterOrderBookLevel[], updates: LighterOrderBoo
return Array.from(map.entries()).map(([price, size]) => ({ price, size } as LighterOrderBookLevel)); return Array.from(map.entries()).map(([price, size]) => ({ price, size } as LighterOrderBookLevel));
} }
function cloneKlines(klines: AsterKline[]): AsterKline[] { function cloneKlines(klines: Kline[]): Kline[] {
return klines.map((kline) => ({ ...kline })); return klines.map((kline) => ({ ...kline }));
} }
+21 -21
View File
@@ -1,12 +1,12 @@
import type { import type {
AsterAccountAsset, AccountAsset,
AsterAccountPosition, AccountPosition,
AsterAccountSnapshot, AccountSnapshot,
AsterDepth, Depth,
AsterDepthLevel, DepthLevel,
AsterKline, Kline,
AsterOrder, Order,
AsterTicker, Ticker,
OrderSide, OrderSide,
OrderType, OrderType,
} from "../types"; } from "../types";
@@ -23,8 +23,8 @@ import { coerceBooleanFlag, normalizeBooleanFlag } from "./flags";
import { normalizeOrderIdentity } from "./order-identity"; import { normalizeOrderIdentity } from "./order-identity";
import { normalizeOrderStatus } from "./status"; import { normalizeOrderStatus } from "./status";
export function toDepth(symbol: string, snapshot: LighterOrderBookSnapshot): AsterDepth { export function toDepth(symbol: string, snapshot: LighterOrderBookSnapshot): Depth {
const toLevels = (levels: LighterOrderBookLevel[]): AsterDepthLevel[] => const toLevels = (levels: LighterOrderBookLevel[]): DepthLevel[] =>
levels.map((level) => [level.price, level.size]); levels.map((level) => [level.price, level.size]);
return { return {
symbol, symbol,
@@ -36,7 +36,7 @@ export function toDepth(symbol: string, snapshot: LighterOrderBookSnapshot): Ast
}; };
} }
export function toTicker(symbol: string, stats: LighterMarketStats): AsterTicker { export function toTicker(symbol: string, stats: LighterMarketStats): Ticker {
return { return {
symbol, symbol,
eventType: "lighterTicker", eventType: "lighterTicker",
@@ -50,10 +50,10 @@ export function toTicker(symbol: string, stats: LighterMarketStats): AsterTicker
priceChange: stats.daily_price_change != null ? String(stats.daily_price_change) : undefined, priceChange: stats.daily_price_change != null ? String(stats.daily_price_change) : undefined,
markPrice: stats.mid_price ?? stats.mark_price ?? stats.index_price, markPrice: stats.mid_price ?? stats.mark_price ?? stats.index_price,
weightedAvgPrice: undefined, weightedAvgPrice: undefined,
} as AsterTicker; } as Ticker;
} }
export function toKlines(symbol: string, interval: string, klines: LighterKline[]): AsterKline[] { export function toKlines(symbol: string, interval: string, klines: LighterKline[]): Kline[] {
return klines.map((entry) => ({ return klines.map((entry) => ({
symbol, symbol,
eventType: "lighterKline", eventType: "lighterKline",
@@ -72,11 +72,11 @@ export function toKlines(symbol: string, interval: string, klines: LighterKline[
})); }));
} }
export function toOrders(symbol: string, orders: LighterOrder[]): AsterOrder[] { export function toOrders(symbol: string, orders: LighterOrder[]): Order[] {
return orders.map((order) => lighterOrderToAster(symbol, order)); return orders.map((order) => lighterOrderToAster(symbol, order));
} }
export function lighterOrderToAster(symbol: string, order: LighterOrder): AsterOrder { export function lighterOrderToAster(symbol: string, order: LighterOrder): Order {
const booleanIsAsk = normalizeBooleanFlag(order.is_ask); const booleanIsAsk = normalizeBooleanFlag(order.is_ask);
const normalizedSide = order.side?.toLowerCase(); const normalizedSide = order.side?.toLowerCase();
const side: OrderSide = const side: OrderSide =
@@ -162,7 +162,7 @@ export function toAccountSnapshot(
symbol: string, symbol: string,
details: LighterAccountDetails, details: LighterAccountDetails,
positions: LighterPosition[] = [], positions: LighterPosition[] = [],
assets: AsterAccountAsset[] = [], assets: AccountAsset[] = [],
options?: { options?: {
marketSymbol?: string | null; marketSymbol?: string | null;
marketId?: number | null; marketId?: number | null;
@@ -172,7 +172,7 @@ export function toAccountSnapshot(
baseAssetId?: number | null; baseAssetId?: number | null;
quoteAssetId?: number | null; quoteAssetId?: number | null;
} }
): AsterAccountSnapshot { ): AccountSnapshot {
const targetSymbol = options?.marketSymbol ?? null; const targetSymbol = options?.marketSymbol ?? null;
const targetMarketId = const targetMarketId =
options?.marketId != null && Number.isFinite(Number(options.marketId)) options?.marketId != null && Number.isFinite(Number(options.marketId))
@@ -228,7 +228,7 @@ export function toAccountSnapshot(
}; };
} }
function defaultAsset(details: LighterAccountDetails, quoteAsset: string): AsterAccountAsset[] { function defaultAsset(details: LighterAccountDetails, quoteAsset: string): AccountAsset[] {
return [ return [
{ {
asset: quoteAsset || "USDC", asset: quoteAsset || "USDC",
@@ -239,7 +239,7 @@ function defaultAsset(details: LighterAccountDetails, quoteAsset: string): Aster
]; ];
} }
function computeTotalWalletBalance(assets: AsterAccountAsset[], details: LighterAccountDetails): string { function computeTotalWalletBalance(assets: AccountAsset[], details: LighterAccountDetails): string {
const sum = assets.reduce((acc, asset) => acc + Number(asset.walletBalance ?? 0), 0); const sum = assets.reduce((acc, asset) => acc + Number(asset.walletBalance ?? 0), 0);
if (Number.isFinite(sum) && sum > 0) { if (Number.isFinite(sum) && sum > 0) {
return sum.toString(); return sum.toString();
@@ -247,7 +247,7 @@ function computeTotalWalletBalance(assets: AsterAccountAsset[], details: Lighter
return details.total_asset_value ?? details.collateral ?? "0"; return details.total_asset_value ?? details.collateral ?? "0";
} }
function findAsset(assets: AsterAccountAsset[], target: string | undefined | null): AsterAccountAsset | undefined { function findAsset(assets: AccountAsset[], target: string | undefined | null): AccountAsset | undefined {
if (!target) return undefined; if (!target) return undefined;
const normalizedTarget = target.toUpperCase().split(/[-:/]/)[0]; const normalizedTarget = target.toUpperCase().split(/[-:/]/)[0];
return assets.find((asset) => asset.asset.toUpperCase().split(/[-:/]/)[0] === normalizedTarget); return assets.find((asset) => asset.asset.toUpperCase().split(/[-:/]/)[0] === normalizedTarget);
@@ -261,7 +261,7 @@ function normalizeMarketType(value: string | null | undefined): "perp" | "spot"
return undefined; return undefined;
} }
function lighterPositionToAster(symbol: string, position: LighterPosition): AsterAccountPosition { function lighterPositionToAster(symbol: string, position: LighterPosition): AccountPosition {
const sign = position.sign ?? 0; const sign = position.sign ?? 0;
const positionSide = sign > 0 ? "LONG" : sign < 0 ? "SHORT" : "BOTH"; const positionSide = sign > 0 ? "LONG" : sign < 0 ? "SHORT" : "BOTH";
const magnitude = Number(position.position ?? 0); const magnitude = Number(position.position ?? 0);
+6 -6
View File
@@ -1,4 +1,4 @@
import type { AsterOrder, CreateOrderParams } from "../types"; import type { Order, CreateOrderParams } from "../types";
import type { import type {
BaseOrderIntent, BaseOrderIntent,
ClosePositionIntent, ClosePositionIntent,
@@ -25,7 +25,7 @@ function applyCommonFields(params: CreateOrderParams, intent: BaseOrderIntent):
return params; return params;
} }
export async function createLimitOrder(intent: LimitOrderIntent): Promise<AsterOrder> { export async function createLimitOrder(intent: LimitOrderIntent): Promise<Order> {
const params: CreateOrderParams = applyCommonFields( const params: CreateOrderParams = applyCommonFields(
{ {
symbol: intent.symbol, symbol: intent.symbol,
@@ -40,7 +40,7 @@ export async function createLimitOrder(intent: LimitOrderIntent): Promise<AsterO
return intent.adapter.createOrder(params); return intent.adapter.createOrder(params);
} }
export async function createMarketOrder(intent: MarketOrderIntent): Promise<AsterOrder> { export async function createMarketOrder(intent: MarketOrderIntent): Promise<Order> {
const params: CreateOrderParams = applyCommonFields( const params: CreateOrderParams = applyCommonFields(
{ {
symbol: intent.symbol, symbol: intent.symbol,
@@ -54,7 +54,7 @@ export async function createMarketOrder(intent: MarketOrderIntent): Promise<Aste
return intent.adapter.createOrder(params); return intent.adapter.createOrder(params);
} }
export async function createStopOrder(intent: StopOrderIntent): Promise<AsterOrder> { export async function createStopOrder(intent: StopOrderIntent): Promise<Order> {
const params: CreateOrderParams = applyCommonFields( const params: CreateOrderParams = applyCommonFields(
{ {
symbol: intent.symbol, symbol: intent.symbol,
@@ -71,11 +71,11 @@ export async function createStopOrder(intent: StopOrderIntent): Promise<AsterOrd
return intent.adapter.createOrder(params); return intent.adapter.createOrder(params);
} }
export async function createTrailingStopOrder(_intent: TrailingStopOrderIntent): Promise<AsterOrder> { export async function createTrailingStopOrder(_intent: TrailingStopOrderIntent): Promise<Order> {
throw new Error("Lighter exchange does not support trailing stop orders"); throw new Error("Lighter exchange does not support trailing stop orders");
} }
export async function createClosePositionOrder(intent: ClosePositionIntent): Promise<AsterOrder> { export async function createClosePositionOrder(intent: ClosePositionIntent): Promise<Order> {
const params: CreateOrderParams = applyCommonFields( const params: CreateOrderParams = applyCommonFields(
{ {
symbol: intent.symbol, symbol: intent.symbol,
+2 -2
View File
@@ -9,7 +9,7 @@ import type {
OrderListener, OrderListener,
TickerListener, TickerListener,
} from "../adapter"; } from "../adapter";
import type { AsterOrder, CreateOrderParams } from "../types"; import type { Order, CreateOrderParams } from "../types";
import { extractMessage } from "../../utils/errors"; import { extractMessage } from "../../utils/errors";
import { NadoGateway, type NadoGatewayOptions } from "./gateway"; import { NadoGateway, type NadoGatewayOptions } from "./gateway";
import type { ChainEnv } from "@nadohq/shared"; import type { ChainEnv } from "@nadohq/shared";
@@ -115,7 +115,7 @@ export class NadoExchangeAdapter implements ExchangeAdapter {
this.gateway.onFundingRate(symbol, this.safeInvoke("watchFundingRate", cb)); this.gateway.onFundingRate(symbol, this.safeInvoke("watchFundingRate", cb));
} }
async createOrder(params: CreateOrderParams): Promise<AsterOrder> { async createOrder(params: CreateOrderParams): Promise<Order> {
await this.ensureInitialized("createOrder"); await this.ensureInitialized("createOrder");
return this.gateway.createOrder(params); return this.gateway.createOrder(params);
} }
+22 -22
View File
@@ -23,13 +23,13 @@ import type {
TickerListener, TickerListener,
} from "../adapter"; } from "../adapter";
import type { import type {
AsterAccountAsset, AccountAsset,
AsterAccountPosition, AccountPosition,
AsterAccountSnapshot, AccountSnapshot,
AsterDepth, Depth,
AsterKline, Kline,
AsterOrder, Order,
AsterTicker, Ticker,
CreateOrderParams, CreateOrderParams,
TimeInForce, TimeInForce,
} from "../types"; } from "../types";
@@ -331,7 +331,7 @@ export class NadoGateway {
private readonly openOrdersByDigest = new Map<string, LocalOrder>(); private readonly openOrdersByDigest = new Map<string, LocalOrder>();
private readonly triggerOrdersByDigest = new Map<string, TriggerOrder>(); private readonly triggerOrdersByDigest = new Map<string, TriggerOrder>();
private accountSnapshot: AsterAccountSnapshot | null = null; private accountSnapshot: AccountSnapshot | null = null;
private lastAccountSyncAt = 0; private lastAccountSyncAt = 0;
private gatewayWs: NodeWebSocket | null = null; private gatewayWs: NodeWebSocket | null = null;
@@ -356,7 +356,7 @@ export class NadoGateway {
private accountPollTimer: ReturnType<typeof setInterval> | null = null; private accountPollTimer: ReturnType<typeof setInterval> | null = null;
private ordersPollTimer: ReturnType<typeof setInterval> | null = null; private ordersPollTimer: ReturnType<typeof setInterval> | null = null;
private triggerOrdersPollTimer: ReturnType<typeof setInterval> | null = null; private triggerOrdersPollTimer: ReturnType<typeof setInterval> | null = null;
private klinesState = new Map<string, { productId: number; periodSec: number; klines: AsterKline[] }>(); private klinesState = new Map<string, { productId: number; periodSec: number; klines: Kline[] }>();
private subscriptionRequestId = 1; private subscriptionRequestId = 1;
private subscriptionAuthComplete = false; private subscriptionAuthComplete = false;
@@ -567,7 +567,7 @@ export class NadoGateway {
}; };
} }
async createOrder(params: CreateOrderParams): Promise<AsterOrder> { async createOrder(params: CreateOrderParams): Promise<Order> {
await this.ensureInitialized(params.symbol); await this.ensureInitialized(params.symbol);
const meta = this.getSymbolMetaOrThrow(params.symbol); const meta = this.getSymbolMetaOrThrow(params.symbol);
if (params.type === "STOP_MARKET") { if (params.type === "STOP_MARKET") {
@@ -1139,7 +1139,7 @@ export class NadoGateway {
return Number.isFinite(asNumber) && asNumber > 0 ? Math.floor(asNumber) : 60; return Number.isFinite(asNumber) && asNumber > 0 ? Math.floor(asNumber) : 60;
} }
private async fetchCandlesticks(productId: number, periodSec: number, limit: number): Promise<AsterKline[]> { private async fetchCandlesticks(productId: number, periodSec: number, limit: number): Promise<Kline[]> {
try { try {
const result = await this.indexer.getCandlesticks({ productId, period: periodSec, limit }); const result = await this.indexer.getCandlesticks({ productId, period: periodSec, limit });
const reversed = Array.from(result).reverse(); const reversed = Array.from(result).reverse();
@@ -1262,8 +1262,8 @@ export class NadoGateway {
} }
} }
private buildAsterOrdersSnapshot(): AsterOrder[] { private buildAsterOrdersSnapshot(): Order[] {
const orders: AsterOrder[] = []; const orders: Order[] = [];
for (const order of this.openOrdersByDigest.values()) { for (const order of this.openOrdersByDigest.values()) {
const meta = this.symbolMetaByProductId.get(order.productId); const meta = this.symbolMetaByProductId.get(order.productId);
if (!meta) continue; if (!meta) continue;
@@ -1322,7 +1322,7 @@ export class NadoGateway {
return orders; return orders;
} }
private buildDepthSnapshot(productId: number, symbol: string): AsterDepth | null { private buildDepthSnapshot(productId: number, symbol: string): Depth | null {
const bbo = this.bestBidOfferByProductId.get(productId); const bbo = this.bestBidOfferByProductId.get(productId);
if (!bbo) return null; if (!bbo) return null;
const bids: [string, string][] = [[fromX18(bbo.bidX18).toFixed(), fromX18(bbo.bidQtyX18).toFixed()]]; const bids: [string, string][] = [[fromX18(bbo.bidX18).toFixed(), fromX18(bbo.bidQtyX18).toFixed()]];
@@ -1336,7 +1336,7 @@ export class NadoGateway {
}; };
} }
private buildTickerSnapshot(productId: number, symbol: string): AsterTicker | null { private buildTickerSnapshot(productId: number, symbol: string): Ticker | null {
const bbo = this.bestBidOfferByProductId.get(productId); const bbo = this.bestBidOfferByProductId.get(productId);
if (!bbo) return null; if (!bbo) return null;
const trade = this.lastTradePriceByProductId.get(productId); const trade = this.lastTradePriceByProductId.get(productId);
@@ -1360,10 +1360,10 @@ export class NadoGateway {
}; };
} }
private mapSubaccountInfoToAsterSnapshot(data: NonNullable<NadoSubaccountInfoResponse["data"]>): AsterAccountSnapshot { private mapSubaccountInfoToAsterSnapshot(data: NonNullable<NadoSubaccountInfoResponse["data"]>): AccountSnapshot {
const now = nowMs(); const now = nowMs();
const assets: AsterAccountAsset[] = []; const assets: AccountAsset[] = [];
const positions: AsterAccountPosition[] = []; const positions: AccountPosition[] = [];
const spotBalanceByProductId = new Map<number, string>(); const spotBalanceByProductId = new Map<number, string>();
for (const entry of data.spot_balances ?? []) { for (const entry of data.spot_balances ?? []) {
@@ -1771,7 +1771,7 @@ export class NadoGateway {
if (state.periodSec !== event.granularity) continue; if (state.periodSec !== event.granularity) continue;
const openTime = event.timestamp * 1000; const openTime = event.timestamp * 1000;
const closeTime = openTime + state.periodSec * 1000 - 1; const closeTime = openTime + state.periodSec * 1000 - 1;
const next: AsterKline = { const next: Kline = {
openTime, openTime,
closeTime, closeTime,
open: fromX18(event.open_x18).toFixed(), open: fromX18(event.open_x18).toFixed(),
@@ -1795,7 +1795,7 @@ export class NadoGateway {
} }
} }
private async createLimitOrder(meta: SymbolMeta, params: CreateOrderParams): Promise<AsterOrder> { private async createLimitOrder(meta: SymbolMeta, params: CreateOrderParams): Promise<Order> {
if (!this.chainId) throw new Error("Nado not initialized (chainId missing)"); if (!this.chainId) throw new Error("Nado not initialized (chainId missing)");
const side = params.side; const side = params.side;
const qty = params.quantity ?? 0; const qty = params.quantity ?? 0;
@@ -1905,7 +1905,7 @@ export class NadoGateway {
}; };
} }
private async createMarketOrder(meta: SymbolMeta, params: CreateOrderParams): Promise<AsterOrder> { private async createMarketOrder(meta: SymbolMeta, params: CreateOrderParams): Promise<Order> {
if (!this.chainId) throw new Error("Nado not initialized (chainId missing)"); if (!this.chainId) throw new Error("Nado not initialized (chainId missing)");
const side = params.side; const side = params.side;
const qty = params.quantity ?? 0; const qty = params.quantity ?? 0;
@@ -2017,7 +2017,7 @@ export class NadoGateway {
}; };
} }
private async createStopOrder(meta: SymbolMeta, params: CreateOrderParams): Promise<AsterOrder> { private async createStopOrder(meta: SymbolMeta, params: CreateOrderParams): Promise<Order> {
if (!this.chainId || !this.endpointAddr) throw new Error("Nado not initialized (contracts missing)"); if (!this.chainId || !this.endpointAddr) throw new Error("Nado not initialized (contracts missing)");
const side = params.side; const side = params.side;
const qty = params.quantity ?? 0; const qty = params.quantity ?? 0;
+6 -6
View File
@@ -1,4 +1,4 @@
import type { AsterOrder, CreateOrderParams } from "../types"; import type { Order, CreateOrderParams } from "../types";
import type { import type {
BaseOrderIntent, BaseOrderIntent,
ClosePositionIntent, ClosePositionIntent,
@@ -25,7 +25,7 @@ function applyCommonFields(params: CreateOrderParams, intent: BaseOrderIntent):
return params; return params;
} }
export async function createLimitOrder(intent: LimitOrderIntent): Promise<AsterOrder> { export async function createLimitOrder(intent: LimitOrderIntent): Promise<Order> {
const params: CreateOrderParams = applyCommonFields( const params: CreateOrderParams = applyCommonFields(
{ {
symbol: intent.symbol, symbol: intent.symbol,
@@ -40,7 +40,7 @@ export async function createLimitOrder(intent: LimitOrderIntent): Promise<AsterO
return intent.adapter.createOrder(params); return intent.adapter.createOrder(params);
} }
export async function createMarketOrder(intent: MarketOrderIntent): Promise<AsterOrder> { export async function createMarketOrder(intent: MarketOrderIntent): Promise<Order> {
const params: CreateOrderParams = applyCommonFields( const params: CreateOrderParams = applyCommonFields(
{ {
symbol: intent.symbol, symbol: intent.symbol,
@@ -54,7 +54,7 @@ export async function createMarketOrder(intent: MarketOrderIntent): Promise<Aste
return intent.adapter.createOrder(params); return intent.adapter.createOrder(params);
} }
export async function createStopOrder(intent: StopOrderIntent): Promise<AsterOrder> { export async function createStopOrder(intent: StopOrderIntent): Promise<Order> {
const params: CreateOrderParams = applyCommonFields( const params: CreateOrderParams = applyCommonFields(
{ {
symbol: intent.symbol, symbol: intent.symbol,
@@ -71,11 +71,11 @@ export async function createStopOrder(intent: StopOrderIntent): Promise<AsterOrd
return intent.adapter.createOrder(params); return intent.adapter.createOrder(params);
} }
export async function createTrailingStopOrder(_intent: TrailingStopOrderIntent): Promise<AsterOrder> { export async function createTrailingStopOrder(_intent: TrailingStopOrderIntent): Promise<Order> {
throw new Error("Nado exchange does not support trailing stop orders"); throw new Error("Nado exchange does not support trailing stop orders");
} }
export async function createClosePositionOrder(intent: ClosePositionIntent): Promise<AsterOrder> { export async function createClosePositionOrder(intent: ClosePositionIntent): Promise<Order> {
const params: CreateOrderParams = applyCommonFields( const params: CreateOrderParams = applyCommonFields(
{ {
symbol: intent.symbol, symbol: intent.symbol,
+11 -11
View File
@@ -1,5 +1,5 @@
import type { ExchangeAdapter } from "./adapter"; import type { ExchangeAdapter } from "./adapter";
import type { AsterOrder } from "./types"; import type { Order } from "./types";
import { SUPPORTED_EXCHANGE_IDS, type SupportedExchangeId } from "./create-adapter"; import { SUPPORTED_EXCHANGE_IDS, type SupportedExchangeId } from "./create-adapter";
import type { import type {
BaseOrderIntent, BaseOrderIntent,
@@ -21,11 +21,11 @@ import * as binanceOrders from "./binance/order";
type ExchangeKey = SupportedExchangeId; type ExchangeKey = SupportedExchangeId;
interface ExchangeOrderHandlers { interface ExchangeOrderHandlers {
limit(intent: LimitOrderIntent): Promise<AsterOrder>; limit(intent: LimitOrderIntent): Promise<Order>;
market(intent: MarketOrderIntent): Promise<AsterOrder>; market(intent: MarketOrderIntent): Promise<Order>;
stop(intent: StopOrderIntent): Promise<AsterOrder>; stop(intent: StopOrderIntent): Promise<Order>;
trailingStop?: (intent: TrailingStopOrderIntent) => Promise<AsterOrder>; trailingStop?: (intent: TrailingStopOrderIntent) => Promise<Order>;
close(intent: ClosePositionIntent): Promise<AsterOrder>; close(intent: ClosePositionIntent): Promise<Order>;
} }
const handlerMap: Record<ExchangeKey, ExchangeOrderHandlers> = { const handlerMap: Record<ExchangeKey, ExchangeOrderHandlers> = {
@@ -117,19 +117,19 @@ function getHandlers(intent: BaseOrderIntent): ExchangeOrderHandlers {
return handlers; return handlers;
} }
export function routeLimitOrder(intent: LimitOrderIntent): Promise<AsterOrder> { export function routeLimitOrder(intent: LimitOrderIntent): Promise<Order> {
return getHandlers(intent).limit(intent); return getHandlers(intent).limit(intent);
} }
export function routeMarketOrder(intent: MarketOrderIntent): Promise<AsterOrder> { export function routeMarketOrder(intent: MarketOrderIntent): Promise<Order> {
return getHandlers(intent).market(intent); return getHandlers(intent).market(intent);
} }
export function routeStopOrder(intent: StopOrderIntent): Promise<AsterOrder> { export function routeStopOrder(intent: StopOrderIntent): Promise<Order> {
return getHandlers(intent).stop(intent); return getHandlers(intent).stop(intent);
} }
export function routeTrailingStopOrder(intent: TrailingStopOrderIntent): Promise<AsterOrder> { export function routeTrailingStopOrder(intent: TrailingStopOrderIntent): Promise<Order> {
const handlers = getHandlers(intent); const handlers = getHandlers(intent);
if (!handlers.trailingStop) { if (!handlers.trailingStop) {
throw new Error("Trailing stop orders are not supported by the current exchange"); throw new Error("Trailing stop orders are not supported by the current exchange");
@@ -137,6 +137,6 @@ export function routeTrailingStopOrder(intent: TrailingStopOrderIntent): Promise
return handlers.trailingStop(intent); return handlers.trailingStop(intent);
} }
export function routeCloseOrder(intent: ClosePositionIntent): Promise<AsterOrder> { export function routeCloseOrder(intent: ClosePositionIntent): Promise<Order> {
return getHandlers(intent).close(intent); return getHandlers(intent).close(intent);
} }
+2 -2
View File
@@ -7,7 +7,7 @@ import type {
OrderListener, OrderListener,
TickerListener, TickerListener,
} from "../adapter"; } from "../adapter";
import type { AsterOrder, CreateOrderParams } from "../types"; import type { Order, CreateOrderParams } from "../types";
import { extractMessage } from "../../utils/errors"; import { extractMessage } from "../../utils/errors";
import { ParadexGateway, type ParadexGatewayOptions } from "./gateway"; import { ParadexGateway, type ParadexGatewayOptions } from "./gateway";
@@ -85,7 +85,7 @@ export class ParadexExchangeAdapter implements ExchangeAdapter {
this.gateway.watchKlines(interval, this.safeInvoke("watchKlines", cb)); this.gateway.watchKlines(interval, this.safeInvoke("watchKlines", cb));
} }
async createOrder(params: CreateOrderParams): Promise<AsterOrder> { async createOrder(params: CreateOrderParams): Promise<Order> {
await this.ensureInitialized("createOrder"); await this.ensureInitialized("createOrder");
return this.gateway.createOrder(params); return this.gateway.createOrder(params);
} }
+20 -20
View File
@@ -7,12 +7,12 @@ import ccxt, {
} from "ccxt"; } from "ccxt";
import { createRequire } from "module"; import { createRequire } from "module";
import type { import type {
AsterAccountAsset, AccountAsset,
AsterAccountSnapshot, AccountSnapshot,
AsterDepth, Depth,
AsterKline, Kline,
AsterOrder, Order,
AsterTicker, Ticker,
CreateOrderParams, CreateOrderParams,
OrderType, OrderType,
} from "../types"; } from "../types";
@@ -91,8 +91,8 @@ export class ParadexGateway {
private depthListeners = new Set<DepthListener>(); private depthListeners = new Set<DepthListener>();
private tickerListeners = new Set<TickerListener>(); private tickerListeners = new Set<TickerListener>();
private klineListeners = new Map<string, Set<KlineListener>>(); private klineListeners = new Map<string, Set<KlineListener>>();
private readonly localOrders = new Map<string, AsterOrder>(); private readonly localOrders = new Map<string, Order>();
private lastBalanceSnapshot: AsterAccountSnapshot | null = null; private lastBalanceSnapshot: AccountSnapshot | null = null;
private accountPollTimer: NodeJS.Timeout | null = null; private accountPollTimer: NodeJS.Timeout | null = null;
private orderPollTimer: NodeJS.Timeout | null = null; private orderPollTimer: NodeJS.Timeout | null = null;
@@ -546,7 +546,7 @@ export class ParadexGateway {
this.klinePollTimers.set(interval, setInterval(() => void poll(), this.pollIntervals.klines)); this.klinePollTimers.set(interval, setInterval(() => void poll(), this.pollIntervals.klines));
} }
async createOrder(params: CreateOrderParams): Promise<AsterOrder> { async createOrder(params: CreateOrderParams): Promise<Order> {
await this.ensureInitialized(params.symbol); await this.ensureInitialized(params.symbol);
const symbol = this.marketSymbol; const symbol = this.marketSymbol;
const type = this.mapOrderTypeToCcxt(params.type); const type = this.mapOrderTypeToCcxt(params.type);
@@ -678,7 +678,7 @@ export class ParadexGateway {
} }
} }
private mapBalanceToAccountSnapshot(balance: Balances): AsterAccountSnapshot { private mapBalanceToAccountSnapshot(balance: Balances): AccountSnapshot {
const now = Date.now(); const now = Date.now();
const rawPositions = (() => { const rawPositions = (() => {
@@ -711,7 +711,7 @@ export class ParadexGateway {
...Object.keys(total), ...Object.keys(total),
]); ]);
const assets: AsterAccountAsset[] = Array.from(assetKeys).map((asset) => ({ const assets: AccountAsset[] = Array.from(assetKeys).map((asset) => ({
asset, asset,
walletBalance: String(total[asset] ?? 0), walletBalance: String(total[asset] ?? 0),
availableBalance: String(free[asset] ?? 0), availableBalance: String(free[asset] ?? 0),
@@ -737,7 +737,7 @@ export class ParadexGateway {
}; };
} }
private mapBalanceToAccountSnapshotFromPositions(rawPositions: any[]): AsterAccountSnapshot { private mapBalanceToAccountSnapshotFromPositions(rawPositions: any[]): AccountSnapshot {
const now = Date.now(); const now = Date.now();
const positions = this.normalizePositions(rawPositions, now); const positions = this.normalizePositions(rawPositions, now);
this.logger("positions", JSON.stringify({ raw: rawPositions, mapped: positions })); this.logger("positions", JSON.stringify({ raw: rawPositions, mapped: positions }));
@@ -761,7 +761,7 @@ export class ParadexGateway {
}; };
} }
private normalizePositions(rawPositions: any[], now: number): AsterAccountSnapshot["positions"] { private normalizePositions(rawPositions: any[], now: number): AccountSnapshot["positions"] {
return rawPositions return rawPositions
.filter((pos) => pos) .filter((pos) => pos)
.map((pos: any) => { .map((pos: any) => {
@@ -807,7 +807,7 @@ export class ParadexGateway {
}); });
} }
private mapOrderToAsterOrder(order: CcxtOrder): AsterOrder { private mapOrderToAsterOrder(order: CcxtOrder): Order {
const side = (order.side ?? "buy").toUpperCase() as "BUY" | "SELL"; const side = (order.side ?? "buy").toUpperCase() as "BUY" | "SELL";
const mappedType = this.mapCcxtOrderTypeToAster(order.type); const mappedType = this.mapCcxtOrderTypeToAster(order.type);
return { return {
@@ -830,7 +830,7 @@ export class ParadexGateway {
}; };
} }
private mapOrderBookToDepth(orderbook: CcxtOrderBook): AsterDepth { private mapOrderBookToDepth(orderbook: CcxtOrderBook): Depth {
return { return {
lastUpdateId: orderbook.nonce || Date.now(), lastUpdateId: orderbook.nonce || Date.now(),
bids: (orderbook.bids || []) bids: (orderbook.bids || [])
@@ -843,7 +843,7 @@ export class ParadexGateway {
}; };
} }
private mapTickerToAsterTicker(ticker: CcxtTicker): AsterTicker { private mapTickerToAsterTicker(ticker: CcxtTicker): Ticker {
return { return {
symbol: ticker.symbol, symbol: ticker.symbol,
lastPrice: ticker.last?.toString() || "0", lastPrice: ticker.last?.toString() || "0",
@@ -856,7 +856,7 @@ export class ParadexGateway {
}; };
} }
private mapOHLCVToKline(candle: CcxtOhlcv, interval: string): AsterKline { private mapOHLCVToKline(candle: CcxtOhlcv, interval: string): Kline {
const [timestampRaw, openRaw, highRaw, lowRaw, closeRaw, volumeRaw] = candle; const [timestampRaw, openRaw, highRaw, lowRaw, closeRaw, volumeRaw] = candle;
const timestamp = typeof timestampRaw === "number" && Number.isFinite(timestampRaw) const timestamp = typeof timestampRaw === "number" && Number.isFinite(timestampRaw)
? timestampRaw ? timestampRaw
@@ -926,7 +926,7 @@ export class ParadexGateway {
return base[interval] ?? 60 * 1000; return base[interval] ?? 60 * 1000;
} }
private upsertLocalOrder(order: AsterOrder): void { private upsertLocalOrder(order: Order): void {
const key = String(order.orderId); const key = String(order.orderId);
if (this.isOrderClosed(order)) { if (this.isOrderClosed(order)) {
this.localOrders.delete(key); this.localOrders.delete(key);
@@ -944,7 +944,7 @@ export class ParadexGateway {
} }
private updateOrdersFromRemote(open: CcxtOrder[], _closed: CcxtOrder[]): void { private updateOrdersFromRemote(open: CcxtOrder[], _closed: CcxtOrder[]): void {
const nextOpen = new Map<string, AsterOrder>(); const nextOpen = new Map<string, Order>();
for (const order of open) { for (const order of open) {
const mapped = this.mapOrderToAsterOrder(order); const mapped = this.mapOrderToAsterOrder(order);
@@ -973,7 +973,7 @@ export class ParadexGateway {
} }
} }
private isOrderClosed(order: AsterOrder): boolean { private isOrderClosed(order: Order): boolean {
const status = (order.status ?? "").toUpperCase(); const status = (order.status ?? "").toUpperCase();
if ( if (
status.includes("CLOSE") || status.includes("CLOSE") ||
+6 -6
View File
@@ -1,4 +1,4 @@
import type { AsterOrder, CreateOrderParams } from "../types"; import type { Order, CreateOrderParams } from "../types";
import type { import type {
BaseOrderIntent, BaseOrderIntent,
ClosePositionIntent, ClosePositionIntent,
@@ -25,7 +25,7 @@ function applyCommonFields(params: CreateOrderParams, intent: BaseOrderIntent):
return params; return params;
} }
export async function createLimitOrder(intent: LimitOrderIntent): Promise<AsterOrder> { export async function createLimitOrder(intent: LimitOrderIntent): Promise<Order> {
const params: CreateOrderParams = applyCommonFields( const params: CreateOrderParams = applyCommonFields(
{ {
symbol: intent.symbol, symbol: intent.symbol,
@@ -40,7 +40,7 @@ export async function createLimitOrder(intent: LimitOrderIntent): Promise<AsterO
return intent.adapter.createOrder(params); return intent.adapter.createOrder(params);
} }
export async function createMarketOrder(intent: MarketOrderIntent): Promise<AsterOrder> { export async function createMarketOrder(intent: MarketOrderIntent): Promise<Order> {
const params: CreateOrderParams = applyCommonFields( const params: CreateOrderParams = applyCommonFields(
{ {
symbol: intent.symbol, symbol: intent.symbol,
@@ -54,7 +54,7 @@ export async function createMarketOrder(intent: MarketOrderIntent): Promise<Aste
return intent.adapter.createOrder(params); return intent.adapter.createOrder(params);
} }
export async function createStopOrder(intent: StopOrderIntent): Promise<AsterOrder> { export async function createStopOrder(intent: StopOrderIntent): Promise<Order> {
const params: CreateOrderParams = applyCommonFields( const params: CreateOrderParams = applyCommonFields(
{ {
symbol: intent.symbol, symbol: intent.symbol,
@@ -72,11 +72,11 @@ export async function createStopOrder(intent: StopOrderIntent): Promise<AsterOrd
return intent.adapter.createOrder(params); return intent.adapter.createOrder(params);
} }
export async function createTrailingStopOrder(_intent: TrailingStopOrderIntent): Promise<AsterOrder> { export async function createTrailingStopOrder(_intent: TrailingStopOrderIntent): Promise<Order> {
throw new Error("Paradex exchange does not support trailing stop orders"); throw new Error("Paradex exchange does not support trailing stop orders");
} }
export async function createClosePositionOrder(intent: ClosePositionIntent): Promise<AsterOrder> { export async function createClosePositionOrder(intent: ClosePositionIntent): Promise<Order> {
const params: CreateOrderParams = applyCommonFields( const params: CreateOrderParams = applyCommonFields(
{ {
symbol: intent.symbol, symbol: intent.symbol,
+3 -3
View File
@@ -10,7 +10,7 @@ import type {
RestHealthListener, RestHealthListener,
TickerListener, TickerListener,
} from "../adapter"; } from "../adapter";
import type { AsterOrder, CreateOrderParams } from "../types"; import type { Order, CreateOrderParams } from "../types";
import { extractMessage } from "../../utils/errors"; import { extractMessage } from "../../utils/errors";
import { StandxGateway, type StandxGatewayOptions, type ConnectionEventListener, type ConnectionEventType } from "./gateway"; import { StandxGateway, type StandxGatewayOptions, type ConnectionEventListener, type ConnectionEventType } from "./gateway";
@@ -88,7 +88,7 @@ export class StandxExchangeAdapter implements ExchangeAdapter {
this.gateway.onFundingRate(symbol, this.safeInvoke("watchFundingRate", cb)); this.gateway.onFundingRate(symbol, this.safeInvoke("watchFundingRate", cb));
} }
async createOrder(params: CreateOrderParams): Promise<AsterOrder> { async createOrder(params: CreateOrderParams): Promise<Order> {
await this.ensureInitialized("createOrder"); await this.ensureInitialized("createOrder");
return this.gateway.createOrder(params); return this.gateway.createOrder(params);
} }
@@ -151,7 +151,7 @@ export class StandxExchangeAdapter implements ExchangeAdapter {
* HTTP API * HTTP API
* *
*/ */
async queryOpenOrders(): Promise<AsterOrder[]> { async queryOpenOrders(): Promise<Order[]> {
await this.ensureInitialized("queryOpenOrders"); await this.ensureInitialized("queryOpenOrders");
return this.gateway.queryOpenOrders(this.symbol); return this.gateway.queryOpenOrders(this.symbol);
} }
+33 -33
View File
@@ -14,13 +14,13 @@ import type {
TickerListener, TickerListener,
} from "../adapter"; } from "../adapter";
import type { import type {
AsterAccountAsset, AccountAsset,
AsterAccountPosition, AccountPosition,
AsterAccountSnapshot, AccountSnapshot,
AsterDepth, Depth,
AsterKline, Kline,
AsterOrder, Order,
AsterTicker, Ticker,
CreateOrderParams, CreateOrderParams,
OrderSide, OrderSide,
OrderType, OrderType,
@@ -77,7 +77,7 @@ type FundingState = {
}; };
type VirtualStop = { type VirtualStop = {
order: AsterOrder; order: Order;
stopPrice: number; stopPrice: number;
side: OrderSide; side: OrderSide;
symbol: string; symbol: string;
@@ -279,7 +279,7 @@ function resolutionFromInterval(interval: string): { resolution: string; seconds
return { resolution: "1", seconds: 60 }; return { resolution: "1", seconds: 60 };
} }
function mergeOrderSnapshot(map: Map<string, AsterOrder>, order: AsterOrder): void { function mergeOrderSnapshot(map: Map<string, Order>, order: Order): void {
const key = String(order.orderId); const key = String(order.orderId);
const existing = map.get(key); const existing = map.get(key);
if (!existing) { if (!existing) {
@@ -418,12 +418,12 @@ export class StandxGateway {
private readonly fundingListeners = new Map<string, Set<FundingRateListener>>(); private readonly fundingListeners = new Map<string, Set<FundingRateListener>>();
private readonly connectionListeners = new Set<ConnectionEventListener>(); private readonly connectionListeners = new Set<ConnectionEventListener>();
private readonly openOrders = new Map<string, AsterOrder>(); private readonly openOrders = new Map<string, Order>();
private readonly positions = new Map<string, AsterAccountPosition>(); private readonly positions = new Map<string, AccountPosition>();
private readonly balances = new Map<string, AsterAccountAsset>(); private readonly balances = new Map<string, AccountAsset>();
private readonly virtualStops = new Map<string, VirtualStop>(); private readonly virtualStops = new Map<string, VirtualStop>();
private accountSnapshot: AsterAccountSnapshot | null = null; private accountSnapshot: AccountSnapshot | null = null;
private readonly restHealthListeners = new Set<RestHealthListener>(); private readonly restHealthListeners = new Set<RestHealthListener>();
private restConsecutiveErrors = 0; private restConsecutiveErrors = 0;
private restUnhealthy = false; private restUnhealthy = false;
@@ -582,14 +582,14 @@ export class StandxGateway {
* HTTP API * HTTP API
* *
*/ */
async queryOpenOrders(symbol: string): Promise<AsterOrder[]> { async queryOpenOrders(symbol: string): Promise<Order[]> {
const normalized = normalizeSymbol(symbol); const normalized = normalizeSymbol(symbol);
const ordersPayload = await this.requestJson<unknown>("/api/query_open_orders", { const ordersPayload = await this.requestJson<unknown>("/api/query_open_orders", {
method: "GET", method: "GET",
params: { symbol: normalized }, params: { symbol: normalized },
}); });
const orders = extractOrders(ordersPayload); const orders = extractOrders(ordersPayload);
const result: AsterOrder[] = []; const result: Order[] = [];
for (const raw of orders) { for (const raw of orders) {
const order = this.mapOrder(raw); const order = this.mapOrder(raw);
result.push(order); result.push(order);
@@ -618,7 +618,7 @@ export class StandxGateway {
} }
} }
async createOrder(params: CreateOrderParams): Promise<AsterOrder> { async createOrder(params: CreateOrderParams): Promise<Order> {
const normalizedSymbol = normalizeSymbol(params.symbol); const normalizedSymbol = normalizeSymbol(params.symbol);
if (params.type === "STOP_MARKET") { if (params.type === "STOP_MARKET") {
return this.createVirtualStopOrder(normalizedSymbol, params); return this.createVirtualStopOrder(normalizedSymbol, params);
@@ -785,7 +785,7 @@ export class StandxGateway {
}; };
} }
private async createVirtualStopOrder(symbol: string, params: CreateOrderParams): Promise<AsterOrder> { private async createVirtualStopOrder(symbol: string, params: CreateOrderParams): Promise<Order> {
const stopPrice = Number(params.stopPrice); const stopPrice = Number(params.stopPrice);
if (!Number.isFinite(stopPrice)) { if (!Number.isFinite(stopPrice)) {
throw new Error("STOP_MARKET requires stopPrice for StandX"); throw new Error("STOP_MARKET requires stopPrice for StandX");
@@ -796,7 +796,7 @@ export class StandxGateway {
} }
const now = Date.now(); const now = Date.now();
const clientOrderId = crypto.randomUUID(); const clientOrderId = crypto.randomUUID();
const order: AsterOrder = { const order: Order = {
orderId: clientOrderId, orderId: clientOrderId,
clientOrderId, clientOrderId,
symbol, symbol,
@@ -825,7 +825,7 @@ export class StandxGateway {
return order; return order;
} }
private async submitOrder(symbol: string, params: CreateOrderParams): Promise<AsterOrder> { private async submitOrder(symbol: string, params: CreateOrderParams): Promise<Order> {
const orderType = params.type === "MARKET" ? "market" : "limit"; const orderType = params.type === "MARKET" ? "market" : "limit";
const clientOrderId = crypto.randomUUID(); const clientOrderId = crypto.randomUUID();
const qty = toDecimalString(params.quantity); const qty = toDecimalString(params.quantity);
@@ -870,7 +870,7 @@ export class StandxGateway {
throw new Error(response.message ?? "StandX order rejected"); throw new Error(response.message ?? "StandX order rejected");
} }
const now = Date.now(); const now = Date.now();
const order: AsterOrder = { const order: Order = {
orderId: clientOrderId, orderId: clientOrderId,
clientOrderId, clientOrderId,
symbol, symbol,
@@ -1038,7 +1038,7 @@ export class StandxGateway {
} }
this.logDebug("ws depth stats", detail); this.logDebug("ws depth stats", detail);
} }
const depth: AsterDepth = { const depth: Depth = {
lastUpdateId: Number(message.seq ?? Date.now()), lastUpdateId: Number(message.seq ?? Date.now()),
bids: finalBids, bids: finalBids,
asks: finalAsks, asks: finalAsks,
@@ -1355,7 +1355,7 @@ export class StandxGateway {
console.log(`[StandxGateway] ws raw`, output); console.log(`[StandxGateway] ws raw`, output);
} }
private emitDepth(symbol: string, depth: AsterDepth): void { private emitDepth(symbol: string, depth: Depth): void {
const listeners = this.depthListeners.get(normalizeSymbol(symbol)); const listeners = this.depthListeners.get(normalizeSymbol(symbol));
if (!listeners) return; if (!listeners) return;
for (const listener of listeners) { for (const listener of listeners) {
@@ -1367,7 +1367,7 @@ export class StandxGateway {
} }
} }
private emitTicker(symbol: string, ticker: AsterTicker): void { private emitTicker(symbol: string, ticker: Ticker): void {
const listeners = this.tickerListeners.get(normalizeSymbol(symbol)); const listeners = this.tickerListeners.get(normalizeSymbol(symbol));
if (!listeners) return; if (!listeners) return;
const price = Number(ticker.lastPrice); const price = Number(ticker.lastPrice);
@@ -1406,7 +1406,7 @@ export class StandxGateway {
(sum, position) => sum + Number(position.unrealizedProfit ?? 0), (sum, position) => sum + Number(position.unrealizedProfit ?? 0),
0 0
); );
const snapshot: AsterAccountSnapshot = { const snapshot: AccountSnapshot = {
canTrade: true, canTrade: true,
canDeposit: true, canDeposit: true,
canWithdraw: true, canWithdraw: true,
@@ -1427,7 +1427,7 @@ export class StandxGateway {
} }
} }
private async refreshAccountSnapshot(): Promise<AsterAccountSnapshot | null> { private async refreshAccountSnapshot(): Promise<AccountSnapshot | null> {
try { try {
const [balance, positions] = await Promise.all([ const [balance, positions] = await Promise.all([
this.requestJson<StandxBalanceSnapshot>("/api/query_balance", { method: "GET" }), this.requestJson<StandxBalanceSnapshot>("/api/query_balance", { method: "GET" }),
@@ -1444,7 +1444,7 @@ export class StandxGateway {
} }
if (balance) { if (balance) {
const token = "DUSD"; const token = "DUSD";
const asset: AsterAccountAsset = { const asset: AccountAsset = {
asset: token, asset: token,
walletBalance: String(balance.balance ?? "0"), walletBalance: String(balance.balance ?? "0"),
availableBalance: String(balance.cross_available ?? balance.balance ?? "0"), availableBalance: String(balance.cross_available ?? balance.balance ?? "0"),
@@ -1461,7 +1461,7 @@ export class StandxGateway {
} }
} }
async queryAccountSnapshot(): Promise<AsterAccountSnapshot | null> { async queryAccountSnapshot(): Promise<AccountSnapshot | null> {
return await this.refreshAccountSnapshot(); return await this.refreshAccountSnapshot();
} }
@@ -1515,7 +1515,7 @@ export class StandxGateway {
if (!data?.symbol) return; if (!data?.symbol) return;
const bids = normalizeDepthLevels((data.bids ?? []).map(([price, qty]) => [String(price), String(qty)]), "bid"); const bids = normalizeDepthLevels((data.bids ?? []).map(([price, qty]) => [String(price), String(qty)]), "bid");
const asks = normalizeDepthLevels((data.asks ?? []).map(([price, qty]) => [String(price), String(qty)]), "ask"); const asks = normalizeDepthLevels((data.asks ?? []).map(([price, qty]) => [String(price), String(qty)]), "ask");
const depth: AsterDepth = { const depth: Depth = {
lastUpdateId: Date.now(), lastUpdateId: Date.now(),
bids, bids,
asks, asks,
@@ -1554,7 +1554,7 @@ export class StandxGateway {
}, },
}); });
if (!response || response.s !== "ok" || !Array.isArray(response.t)) return; if (!response || response.s !== "ok" || !Array.isArray(response.t)) return;
const klines: AsterKline[] = response.t.map((openTime, index) => { const klines: Kline[] = response.t.map((openTime, index) => {
const o = response.o?.[index]; const o = response.o?.[index];
const h = response.h?.[index]; const h = response.h?.[index];
const l = response.l?.[index]; const l = response.l?.[index];
@@ -1647,7 +1647,7 @@ export class StandxGateway {
} }
} }
private mapOrder(data: StandxOrder): AsterOrder { private mapOrder(data: StandxOrder): Order {
const clientOrderId = data.cl_ord_id ? String(data.cl_ord_id) : data.id != null ? String(data.id) : ""; const clientOrderId = data.cl_ord_id ? String(data.cl_ord_id) : data.id != null ? String(data.id) : "";
const orderId = clientOrderId || (data.id != null ? String(data.id) : "") || crypto.randomUUID(); const orderId = clientOrderId || (data.id != null ? String(data.id) : "") || crypto.randomUUID();
const normalizedClientId = clientOrderId || orderId; const normalizedClientId = clientOrderId || orderId;
@@ -1675,7 +1675,7 @@ export class StandxGateway {
}; };
} }
private mapPosition(data: StandxPosition): AsterAccountPosition { private mapPosition(data: StandxPosition): AccountPosition {
return { return {
symbol: data.symbol, symbol: data.symbol,
positionAmt: String(data.qty ?? "0"), positionAmt: String(data.qty ?? "0"),
@@ -1690,7 +1690,7 @@ export class StandxGateway {
}; };
} }
private mapBalance(data: StandxBalance): AsterAccountAsset { private mapBalance(data: StandxBalance): AccountAsset {
const walletBalance = data.total ?? data.free ?? "0"; const walletBalance = data.total ?? data.free ?? "0";
const availableBalance = data.free ?? walletBalance; const availableBalance = data.free ?? walletBalance;
return { return {
@@ -1701,7 +1701,7 @@ export class StandxGateway {
}; };
} }
private mapTicker(data: StandxPrice): AsterTicker { private mapTicker(data: StandxPrice): Ticker {
const spread = data.spread ?? [data.spread_bid ?? "0", data.spread_ask ?? "0"]; const spread = data.spread ?? [data.spread_bid ?? "0", data.spread_ask ?? "0"];
const lastPrice = data.last_price ?? data.mark_price ?? data.index_price ?? data.mid_price ?? "0"; const lastPrice = data.last_price ?? data.mark_price ?? data.index_price ?? data.mid_price ?? "0";
return { return {
+6 -6
View File
@@ -1,4 +1,4 @@
import type { AsterOrder, CreateOrderParams } from "../types"; import type { Order, CreateOrderParams } from "../types";
import type { import type {
BaseOrderIntent, BaseOrderIntent,
ClosePositionIntent, ClosePositionIntent,
@@ -25,7 +25,7 @@ function applyCommonFields(params: CreateOrderParams, intent: BaseOrderIntent):
return params; return params;
} }
export async function createLimitOrder(intent: LimitOrderIntent): Promise<AsterOrder> { export async function createLimitOrder(intent: LimitOrderIntent): Promise<Order> {
const params: CreateOrderParams = applyCommonFields( const params: CreateOrderParams = applyCommonFields(
{ {
symbol: intent.symbol, symbol: intent.symbol,
@@ -42,7 +42,7 @@ export async function createLimitOrder(intent: LimitOrderIntent): Promise<AsterO
return intent.adapter.createOrder(params); return intent.adapter.createOrder(params);
} }
export async function createMarketOrder(intent: MarketOrderIntent): Promise<AsterOrder> { export async function createMarketOrder(intent: MarketOrderIntent): Promise<Order> {
const params: CreateOrderParams = applyCommonFields( const params: CreateOrderParams = applyCommonFields(
{ {
symbol: intent.symbol, symbol: intent.symbol,
@@ -56,7 +56,7 @@ export async function createMarketOrder(intent: MarketOrderIntent): Promise<Aste
return intent.adapter.createOrder(params); return intent.adapter.createOrder(params);
} }
export async function createStopOrder(intent: StopOrderIntent): Promise<AsterOrder> { export async function createStopOrder(intent: StopOrderIntent): Promise<Order> {
const params: CreateOrderParams = applyCommonFields( const params: CreateOrderParams = applyCommonFields(
{ {
symbol: intent.symbol, symbol: intent.symbol,
@@ -73,11 +73,11 @@ export async function createStopOrder(intent: StopOrderIntent): Promise<AsterOrd
return intent.adapter.createOrder(params); return intent.adapter.createOrder(params);
} }
export async function createTrailingStopOrder(_intent: TrailingStopOrderIntent): Promise<AsterOrder> { export async function createTrailingStopOrder(_intent: TrailingStopOrderIntent): Promise<Order> {
throw new Error("StandX exchange does not support trailing stop orders"); throw new Error("StandX exchange does not support trailing stop orders");
} }
export async function createClosePositionOrder(intent: ClosePositionIntent): Promise<AsterOrder> { export async function createClosePositionOrder(intent: ClosePositionIntent): Promise<Order> {
const params: CreateOrderParams = applyCommonFields( const params: CreateOrderParams = applyCommonFields(
{ {
symbol: intent.symbol, symbol: intent.symbol,
+14 -14
View File
@@ -30,7 +30,7 @@ export interface CreateOrderParams {
tpPrice?: number; // 止盈价格 tpPrice?: number; // 止盈价格
} }
export interface AsterAccountPosition { export interface AccountPosition {
symbol: string; symbol: string;
positionAmt: string; positionAmt: string;
entryPrice: string; entryPrice: string;
@@ -254,7 +254,7 @@ export interface GrvtSignedOrder extends GrvtUnsignedOrder {
signature: GrvtSignature; signature: GrvtSignature;
} }
export interface AsterAccountAsset { export interface AccountAsset {
asset: string; asset: string;
walletBalance: string; walletBalance: string;
availableBalance: string; availableBalance: string;
@@ -272,7 +272,7 @@ export interface AsterAccountAsset {
marginAvailable?: boolean; marginAvailable?: boolean;
} }
export interface AsterAccountSnapshot { export interface AccountSnapshot {
canTrade: boolean; canTrade: boolean;
canDeposit: boolean; canDeposit: boolean;
canWithdraw: boolean; canWithdraw: boolean;
@@ -288,8 +288,8 @@ export interface AsterAccountSnapshot {
totalCrossUnPnl?: string; totalCrossUnPnl?: string;
availableBalance?: string; availableBalance?: string;
maxWithdrawAmount?: string; maxWithdrawAmount?: string;
positions: AsterAccountPosition[]; positions: AccountPosition[];
assets: AsterAccountAsset[]; assets: AccountAsset[];
marketType?: "perp" | "spot"; marketType?: "perp" | "spot";
baseAsset?: string; baseAsset?: string;
quoteAsset?: string; quoteAsset?: string;
@@ -297,22 +297,22 @@ export interface AsterAccountSnapshot {
quoteAssetId?: number; quoteAssetId?: number;
} }
export interface AsterDepthLevel extends Array<string> { export interface DepthLevel extends Array<string> {
0: string; // price 0: string; // price
1: string; // quantity 1: string; // quantity
} }
export interface AsterDepth { export interface Depth {
lastUpdateId: number; lastUpdateId: number;
bids: AsterDepthLevel[]; bids: DepthLevel[];
asks: AsterDepthLevel[]; asks: DepthLevel[];
eventTime?: number; eventTime?: number;
eventType?: string; eventType?: string;
tradeTime?: number; tradeTime?: number;
symbol?: string; symbol?: string;
} }
export interface AsterTicker { export interface Ticker {
symbol: string; symbol: string;
lastPrice: string; lastPrice: string;
openPrice: string; openPrice: string;
@@ -409,8 +409,8 @@ export interface AsterSpotDepth {
lastUpdateId: number; lastUpdateId: number;
E?: number; E?: number;
T?: number; T?: number;
bids: AsterDepthLevel[]; bids: DepthLevel[];
asks: AsterDepthLevel[]; asks: DepthLevel[];
} }
export interface AsterSpotTrade { export interface AsterSpotTrade {
@@ -583,7 +583,7 @@ export interface AsterSpotUserTrade {
buyer: boolean; buyer: boolean;
} }
export interface AsterKline { export interface Kline {
eventType?: string; eventType?: string;
eventTime?: number; eventTime?: number;
symbol?: string; symbol?: string;
@@ -604,7 +604,7 @@ export interface AsterKline {
isClosed?: boolean; isClosed?: boolean;
} }
export interface AsterOrder { export interface Order {
orderId: number | string; orderId: number | string;
clientOrderId: string; clientOrderId: string;
symbol: string; symbol: string;
+7 -7
View File
@@ -1,6 +1,6 @@
import type { BasisArbConfig } from "../config"; import type { BasisArbConfig } from "../config";
import type { ExchangeAdapter, FundingRateSnapshot } from "../exchanges/adapter"; import type { ExchangeAdapter, FundingRateSnapshot } from "../exchanges/adapter";
import type { AsterAccountSnapshot, AsterDepth, AsterSpotBookTicker } from "../exchanges/types"; import type { AccountSnapshot, Depth, AsterSpotBookTicker } from "../exchanges/types";
import { AsterSpotRestClient, AsterRestClient } from "../exchanges/aster/client"; import { AsterSpotRestClient, AsterRestClient } from "../exchanges/aster/client";
import { createTradeLog, type TradeLogEntry } from "../logging/trade-log"; import { createTradeLog, type TradeLogEntry } from "../logging/trade-log";
import { StrategyEventEmitter } from "./common/event-emitter"; import { StrategyEventEmitter } from "./common/event-emitter";
@@ -155,7 +155,7 @@ export class BasisArbEngine {
private bootstrap(): void { private bootstrap(): void {
const log: LogHandler = (type, detail) => this.tradeLog.push(type, detail); const log: LogHandler = (type, detail) => this.tradeLog.push(type, detail);
safeSubscribe<AsterDepth>( safeSubscribe<Depth>(
this.exchange.watchDepth.bind(this.exchange, this.config.futuresSymbol), this.exchange.watchDepth.bind(this.exchange, this.config.futuresSymbol),
(depth) => { (depth) => {
this.applyFuturesDepth(depth); this.applyFuturesDepth(depth);
@@ -168,7 +168,7 @@ export class BasisArbEngine {
); );
if (this.exchange.id === "nado" || this.exchange.id === "standx" || this.exchange.id === "binance") { if (this.exchange.id === "nado" || this.exchange.id === "standx" || this.exchange.id === "binance") {
safeSubscribe<AsterDepth>( safeSubscribe<Depth>(
this.exchange.watchDepth.bind(this.exchange, this.config.spotSymbol), this.exchange.watchDepth.bind(this.exchange, this.config.spotSymbol),
(depth) => { (depth) => {
this.applySpotDepth(depth); this.applySpotDepth(depth);
@@ -196,7 +196,7 @@ export class BasisArbEngine {
); );
} }
safeSubscribe<AsterAccountSnapshot>( safeSubscribe<AccountSnapshot>(
this.exchange.watchAccount.bind(this.exchange), this.exchange.watchAccount.bind(this.exchange),
(snapshot) => { (snapshot) => {
this.applyAccountSnapshot(snapshot); this.applyAccountSnapshot(snapshot);
@@ -210,7 +210,7 @@ export class BasisArbEngine {
} }
} }
private applyFuturesDepth(depth: AsterDepth): void { private applyFuturesDepth(depth: Depth): void {
if (!depth?.bids?.length || !depth?.asks?.length) { if (!depth?.bids?.length || !depth?.asks?.length) {
return; return;
} }
@@ -360,7 +360,7 @@ export class BasisArbEngine {
this.emitUpdate(); this.emitUpdate();
} }
private applySpotDepth(depth: AsterDepth): void { private applySpotDepth(depth: Depth): void {
if (!depth?.bids?.length || !depth?.asks?.length) { if (!depth?.bids?.length || !depth?.asks?.length) {
return; return;
} }
@@ -395,7 +395,7 @@ export class BasisArbEngine {
this.emitUpdate(); this.emitUpdate();
} }
private applyAccountSnapshot(snapshot: AsterAccountSnapshot): void { private applyAccountSnapshot(snapshot: AccountSnapshot): void {
const assets = Array.isArray(snapshot.assets) ? snapshot.assets : []; const assets = Array.isArray(snapshot.assets) ? snapshot.assets : [];
const spotBalances: SpotBalanceStateEntry[] = []; const spotBalances: SpotBalanceStateEntry[] = [];
+10 -10
View File
@@ -1,5 +1,5 @@
import NodeWebSocket from "ws"; import NodeWebSocket from "ws";
import type { AsterDepthLevel } from "../../exchanges/types"; import type { DepthLevel } from "../../exchanges/types";
import type { DepthImbalance } from "../../utils/depth"; import type { DepthImbalance } from "../../utils/depth";
const WebSocketCtor: typeof globalThis.WebSocket = const WebSocketCtor: typeof globalThis.WebSocket =
@@ -56,14 +56,14 @@ export type BinanceConnectionListener = (state: BinanceConnectionState) => void;
interface DepthUpdateEvent { interface DepthUpdateEvent {
U: number; U: number;
u: number; u: number;
bids: AsterDepthLevel[]; bids: DepthLevel[];
asks: AsterDepthLevel[]; asks: DepthLevel[];
} }
interface DepthSnapshotResponse { interface DepthSnapshotResponse {
lastUpdateId: number; lastUpdateId: number;
bids: AsterDepthLevel[]; bids: DepthLevel[];
asks: AsterDepthLevel[]; asks: DepthLevel[];
} }
export class BinanceDepthTracker { export class BinanceDepthTracker {
@@ -540,7 +540,7 @@ export class BinanceDepthTracker {
return true; return true;
} }
private applyLevels(book: Map<string, number>, levels: AsterDepthLevel[]): void { private applyLevels(book: Map<string, number>, levels: DepthLevel[]): void {
for (const level of levels) { for (const level of levels) {
const priceRaw = level?.[0]; const priceRaw = level?.[0];
const qtyRaw = level?.[1]; const qtyRaw = level?.[1];
@@ -657,8 +657,8 @@ export class BinanceDepthTracker {
throw new Error("invalid lastUpdateId"); throw new Error("invalid lastUpdateId");
} }
const bids = Array.isArray(json.bids) ? (json.bids as AsterDepthLevel[]) : []; const bids = Array.isArray(json.bids) ? (json.bids as DepthLevel[]) : [];
const asks = Array.isArray(json.asks) ? (json.asks as AsterDepthLevel[]) : []; const asks = Array.isArray(json.asks) ? (json.asks as DepthLevel[]) : [];
this.lastRestSyncAt = Date.now(); this.lastRestSyncAt = Date.now();
this.restConsecutiveFailures = 0; this.restConsecutiveFailures = 0;
this.restLastError = null; this.restLastError = null;
@@ -697,8 +697,8 @@ export class BinanceDepthTracker {
const bidsRaw = Array.isArray(payload.b) ? payload.b : []; const bidsRaw = Array.isArray(payload.b) ? payload.b : [];
const asksRaw = Array.isArray(payload.a) ? payload.a : []; const asksRaw = Array.isArray(payload.a) ? payload.a : [];
const bids = bidsRaw.filter((level): level is AsterDepthLevel => Array.isArray(level)) as AsterDepthLevel[]; const bids = bidsRaw.filter((level): level is DepthLevel => Array.isArray(level)) as DepthLevel[];
const asks = asksRaw.filter((level): level is AsterDepthLevel => Array.isArray(level)) as AsterDepthLevel[]; const asks = asksRaw.filter((level): level is DepthLevel => Array.isArray(level)) as DepthLevel[];
return { U, u, bids, asks }; return { U, u, bids, asks };
} catch { } catch {
+13 -13
View File
@@ -1,6 +1,6 @@
import type { GridConfig, GridDirection } from "../config"; import type { GridConfig, GridDirection } from "../config";
import type { ExchangeAdapter } from "../exchanges/adapter"; import type { ExchangeAdapter } from "../exchanges/adapter";
import type { AsterAccountSnapshot, AsterDepth, AsterOrder, AsterTicker } from "../exchanges/types"; import type { AccountSnapshot, Depth, Order, Ticker } from "../exchanges/types";
import { createTradeLog, type TradeLogEntry } from "../logging/trade-log"; import { createTradeLog, type TradeLogEntry } from "../logging/trade-log";
import { decimalsOf } from "../utils/math"; import { decimalsOf } from "../utils/math";
import { extractMessage } from "../utils/errors"; import { extractMessage } from "../utils/errors";
@@ -51,7 +51,7 @@ export interface GridEngineSnapshot {
midPrice: number | null; midPrice: number | null;
gridLines: GridLineSnapshot[]; gridLines: GridLineSnapshot[];
desiredOrders: DesiredGridOrder[]; desiredOrders: DesiredGridOrder[];
openOrders: AsterOrder[]; openOrders: Order[];
position: PositionSnapshot; position: PositionSnapshot;
running: boolean; running: boolean;
stopReason: string | null; stopReason: string | null;
@@ -115,10 +115,10 @@ export class GridEngine {
private lastAbsPositionAmt = 0; private lastAbsPositionAmt = 0;
private immediateCloseToPlace: Array<{ sourceLevel: number; targetLevel: number; side: "BUY" | "SELL"; price: string }> = []; private immediateCloseToPlace: Array<{ sourceLevel: number; targetLevel: number; side: "BUY" | "SELL"; price: string }> = [];
private accountSnapshot: AsterAccountSnapshot | null = null; private accountSnapshot: AccountSnapshot | null = null;
private depthSnapshot: AsterDepth | null = null; private depthSnapshot: Depth | null = null;
private tickerSnapshot: AsterTicker | null = null; private tickerSnapshot: Ticker | null = null;
private openOrders: AsterOrder[] = []; private openOrders: Order[] = [];
private position: PositionSnapshot = { positionAmt: 0, entryPrice: 0, unrealizedProfit: 0, markPrice: null }; private position: PositionSnapshot = { positionAmt: 0, entryPrice: 0, unrealizedProfit: 0, markPrice: null };
private desiredOrders: DesiredGridOrder[] = []; private desiredOrders: DesiredGridOrder[] = [];
@@ -277,7 +277,7 @@ export class GridEngine {
private bootstrap(): void { private bootstrap(): void {
const log: LogHandler = (type, detail) => this.tradeLog.push(type, detail); const log: LogHandler = (type, detail) => this.tradeLog.push(type, detail);
safeSubscribe<AsterAccountSnapshot>( safeSubscribe<AccountSnapshot>(
this.exchange.watchAccount.bind(this.exchange), this.exchange.watchAccount.bind(this.exchange),
(snapshot) => { (snapshot) => {
this.accountSnapshot = snapshot; this.accountSnapshot = snapshot;
@@ -301,7 +301,7 @@ export class GridEngine {
} }
); );
safeSubscribe<AsterOrder[]>( safeSubscribe<Order[]>(
this.exchange.watchOrders.bind(this.exchange), this.exchange.watchOrders.bind(this.exchange),
(orders) => { (orders) => {
this.openOrders = Array.isArray(orders) this.openOrders = Array.isArray(orders)
@@ -327,7 +327,7 @@ export class GridEngine {
} }
); );
safeSubscribe<AsterDepth>( safeSubscribe<Depth>(
this.exchange.watchDepth.bind(this.exchange, this.config.symbol), this.exchange.watchDepth.bind(this.exchange, this.config.symbol),
(depth) => { (depth) => {
this.depthSnapshot = depth; this.depthSnapshot = depth;
@@ -345,7 +345,7 @@ export class GridEngine {
} }
); );
safeSubscribe<AsterTicker>( safeSubscribe<Ticker>(
this.exchange.watchTicker.bind(this.exchange, this.config.symbol), this.exchange.watchTicker.bind(this.exchange, this.config.symbol),
(ticker) => { (ticker) => {
this.tickerSnapshot = ticker; this.tickerSnapshot = ticker;
@@ -366,7 +366,7 @@ export class GridEngine {
); );
} }
private synchronizeLocks(orders: AsterOrder[] | null | undefined): void { private synchronizeLocks(orders: Order[] | null | undefined): void {
const list = Array.isArray(orders) ? orders : []; const list = Array.isArray(orders) ? orders : [];
const FINAL = new Set(["FILLED", "CANCELED", "CANCELLED", "REJECTED", "EXPIRED"]); const FINAL = new Set(["FILLED", "CANCELED", "CANCELLED", "REJECTED", "EXPIRED"]);
Object.keys(this.pendings).forEach((type) => { Object.keys(this.pendings).forEach((type) => {
@@ -619,7 +619,7 @@ export class GridEngine {
const activeOrders = this.openOrders.filter((o) => this.isActiveLimitOrder(o)); const activeOrders = this.openOrders.filter((o) => this.isActiveLimitOrder(o));
// Build lookup for all recent orders by id (including non-active) to read final statuses // Build lookup for all recent orders by id (including non-active) to read final statuses
const allOrdersById = new Map<string, AsterOrder>(); const allOrdersById = new Map<string, Order>();
for (const o of this.openOrders) { for (const o of this.openOrders) {
if (o.symbol !== this.config.symbol) continue; if (o.symbol !== this.config.symbol) continue;
allOrdersById.set(String(o.orderId), o); allOrdersById.set(String(o.orderId), o);
@@ -1187,7 +1187,7 @@ export class GridEngine {
return `${side}:${price}:${intent}`; return `${side}:${price}:${intent}`;
} }
private isActiveLimitOrder(o: AsterOrder): boolean { private isActiveLimitOrder(o: Order): boolean {
if (o.symbol !== this.config.symbol) return false; if (o.symbol !== this.config.symbol) return false;
if (o.type !== "LIMIT") return false; if (o.type !== "LIMIT") return false;
const s = String(o.status || "").toUpperCase(); const s = String(o.status || "").toUpperCase();
+17 -17
View File
@@ -1,6 +1,6 @@
import type { TradingConfig } from "../config"; import type { TradingConfig } from "../config";
import type { ExchangeAdapter } from "../exchanges/adapter"; import type { ExchangeAdapter } from "../exchanges/adapter";
import type { AsterAccountSnapshot, AsterOrder, AsterTicker } from "../exchanges/types"; import type { AccountSnapshot, Order, Ticker } from "../exchanges/types";
import { import {
calcStopLossPrice, calcStopLossPrice,
calcTrailingActivationPrice, calcTrailingActivationPrice,
@@ -30,11 +30,11 @@ export interface GuardianEngineSnapshot {
unrealized: number; unrealized: number;
targetStopPrice: number | null; targetStopPrice: number | null;
trailingActivationPrice: number | null; trailingActivationPrice: number | null;
stopOrder: AsterOrder | null; stopOrder: Order | null;
trailingOrder: AsterOrder | null; trailingOrder: Order | null;
requiresStop: boolean; requiresStop: boolean;
tradeLog: TradeLogEntry[]; tradeLog: TradeLogEntry[];
openOrders: AsterOrder[]; openOrders: Order[];
lastUpdated: number | null; lastUpdated: number | null;
guardStatus: "idle" | "protecting" | "pending"; guardStatus: "idle" | "protecting" | "pending";
} }
@@ -43,9 +43,9 @@ type GuardianEngineEvent = "update";
type GuardianEngineListener = (snapshot: GuardianEngineSnapshot) => void; type GuardianEngineListener = (snapshot: GuardianEngineSnapshot) => void;
export class GuardianEngine { export class GuardianEngine {
private accountSnapshot: AsterAccountSnapshot | null = null; private accountSnapshot: AccountSnapshot | null = null;
private openOrders: AsterOrder[] = []; private openOrders: Order[] = [];
private tickerSnapshot: AsterTicker | null = null; private tickerSnapshot: Ticker | null = null;
private readonly locks: OrderLockMap = {}; private readonly locks: OrderLockMap = {};
private readonly timers: OrderTimerMap = {}; private readonly timers: OrderTimerMap = {};
@@ -101,7 +101,7 @@ export class GuardianEngine {
private bootstrap(): void { private bootstrap(): void {
const log: LogHandler = (type, detail) => this.tradeLog.push(type, detail); const log: LogHandler = (type, detail) => this.tradeLog.push(type, detail);
safeSubscribe<AsterAccountSnapshot>( safeSubscribe<AccountSnapshot>(
this.exchange.watchAccount.bind(this.exchange), this.exchange.watchAccount.bind(this.exchange),
(snapshot) => { (snapshot) => {
this.accountSnapshot = snapshot; this.accountSnapshot = snapshot;
@@ -114,7 +114,7 @@ export class GuardianEngine {
} }
); );
safeSubscribe<AsterOrder[]>( safeSubscribe<Order[]>(
this.exchange.watchOrders.bind(this.exchange), this.exchange.watchOrders.bind(this.exchange),
(orders) => { (orders) => {
this.synchronizeLocks(orders); this.synchronizeLocks(orders);
@@ -141,7 +141,7 @@ export class GuardianEngine {
} }
); );
safeSubscribe<AsterTicker>( safeSubscribe<Ticker>(
this.exchange.watchTicker.bind(this.exchange, this.config.symbol), this.exchange.watchTicker.bind(this.exchange, this.config.symbol),
(ticker) => { (ticker) => {
this.tickerSnapshot = ticker; this.tickerSnapshot = ticker;
@@ -155,7 +155,7 @@ export class GuardianEngine {
); );
} }
private synchronizeLocks(orders: AsterOrder[] | null | undefined): void { private synchronizeLocks(orders: Order[] | null | undefined): void {
const list = Array.isArray(orders) ? orders : []; const list = Array.isArray(orders) ? orders : [];
Object.keys(this.pending).forEach((type) => { Object.keys(this.pending).forEach((type) => {
const pendingId = this.pending[type]; const pendingId = this.pending[type];
@@ -261,8 +261,8 @@ export class GuardianEngine {
price: number; price: number;
stopPrice: number; stopPrice: number;
activationPrice: number; activationPrice: number;
currentStop?: AsterOrder; currentStop?: Order;
currentTrailing?: AsterOrder; currentTrailing?: Order;
}): Promise<void> { }): Promise<void> {
const { position, direction, stopSide, price, stopPrice, activationPrice, currentStop, currentTrailing } = params; const { position, direction, stopSide, price, stopPrice, activationPrice, currentStop, currentTrailing } = params;
@@ -416,7 +416,7 @@ export class GuardianEngine {
private async tryReplaceStop( private async tryReplaceStop(
side: "BUY" | "SELL", side: "BUY" | "SELL",
currentOrder: AsterOrder, currentOrder: Order,
nextStopPrice: number, nextStopPrice: number,
lastPrice: number lastPrice: number
): Promise<void> { ): Promise<void> {
@@ -559,7 +559,7 @@ export class GuardianEngine {
} }
} }
private isProtectiveOrder(order: AsterOrder): boolean { private isProtectiveOrder(order: Order): boolean {
if (order.symbol !== this.config.symbol) { if (order.symbol !== this.config.symbol) {
return false; return false;
} }
@@ -571,14 +571,14 @@ export class GuardianEngine {
return type === "STOP_MARKET" || hasStopPrice; return type === "STOP_MARKET" || hasStopPrice;
} }
private findStopOrder(side: "BUY" | "SELL"): AsterOrder | undefined { private findStopOrder(side: "BUY" | "SELL"): Order | undefined {
return this.openOrders.find((order) => { return this.openOrders.find((order) => {
const hasStopPrice = Number.isFinite(Number(order.stopPrice)) && Number(order.stopPrice) > 0; const hasStopPrice = Number.isFinite(Number(order.stopPrice)) && Number(order.stopPrice) > 0;
return order.side === side && (order.type === "STOP_MARKET" || hasStopPrice); return order.side === side && (order.type === "STOP_MARKET" || hasStopPrice);
}); });
} }
private findTrailingOrder(side: "BUY" | "SELL"): AsterOrder | undefined { private findTrailingOrder(side: "BUY" | "SELL"): Order | undefined {
return this.openOrders.find((order) => order.type === "TRAILING_STOP_MARKET" && order.side === side); return this.openOrders.find((order) => order.type === "TRAILING_STOP_MARKET" && order.side === side);
} }
+20 -20
View File
@@ -1,11 +1,11 @@
import type { LiquidityMakerConfig } from "../config"; import type { LiquidityMakerConfig } from "../config";
import type { ExchangeAdapter } from "../exchanges/adapter"; import type { ExchangeAdapter } from "../exchanges/adapter";
import type { import type {
AsterAccountSnapshot, AccountSnapshot,
AsterDepth, Depth,
AsterKline, Kline,
AsterOrder, Order,
AsterTicker, Ticker,
} from "../exchanges/types"; } from "../exchanges/types";
import { formatPriceToString } from "../utils/math"; import { formatPriceToString } from "../utils/math";
import { createTradeLog } from "../logging/trade-log"; import { createTradeLog } from "../logging/trade-log";
@@ -65,12 +65,12 @@ type MakerListener = (snapshot: LiquidityMakerEngineSnapshot) => void;
const EPS = 1e-5; const EPS = 1e-5;
export class LiquidityMakerEngine { export class LiquidityMakerEngine {
private accountSnapshot: AsterAccountSnapshot | null = null; private accountSnapshot: AccountSnapshot | null = null;
private depthSnapshot: AsterDepth | null = null; private depthSnapshot: Depth | null = null;
private tickerSnapshot: AsterTicker | null = null; private tickerSnapshot: Ticker | null = null;
private lastKline: AsterKline | null = null; private lastKline: Kline | null = null;
private liveCandle: { startMs: number; open: number; close: number } | null = null; private liveCandle: { startMs: number; open: number; close: number } | null = null;
private openOrders: AsterOrder[] = []; private openOrders: Order[] = [];
private readonly locks: OrderLockMap = {}; private readonly locks: OrderLockMap = {};
private readonly timers: OrderTimerMap = {}; private readonly timers: OrderTimerMap = {};
@@ -180,7 +180,7 @@ export class LiquidityMakerEngine {
private bootstrap(): void { private bootstrap(): void {
const log: LogHandler = (type, detail) => this.tradeLog.push(type, detail); const log: LogHandler = (type, detail) => this.tradeLog.push(type, detail);
safeSubscribe<AsterAccountSnapshot>( safeSubscribe<AccountSnapshot>(
this.exchange.watchAccount.bind(this.exchange), this.exchange.watchAccount.bind(this.exchange),
(snapshot) => { (snapshot) => {
this.accountSnapshot = snapshot; this.accountSnapshot = snapshot;
@@ -224,7 +224,7 @@ export class LiquidityMakerEngine {
} }
); );
safeSubscribe<AsterOrder[]>( safeSubscribe<Order[]>(
this.exchange.watchOrders.bind(this.exchange), this.exchange.watchOrders.bind(this.exchange),
(orders) => { (orders) => {
this.syncLocksWithOrders(orders); this.syncLocksWithOrders(orders);
@@ -232,7 +232,7 @@ export class LiquidityMakerEngine {
// 检测成交:对比上一轮的订单ID // 检测成交:对比上一轮的订单ID
const currentIds = new Set<string>(); const currentIds = new Set<string>();
const activeOrders: AsterOrder[] = []; const activeOrders: Order[] = [];
if (Array.isArray(orders)) { if (Array.isArray(orders)) {
for (const order of orders) { for (const order of orders) {
@@ -268,7 +268,7 @@ export class LiquidityMakerEngine {
} }
); );
safeSubscribe<AsterDepth>( safeSubscribe<Depth>(
this.exchange.watchDepth.bind(this.exchange, this.config.symbol), this.exchange.watchDepth.bind(this.exchange, this.config.symbol),
(depth) => { (depth) => {
this.depthSnapshot = depth; this.depthSnapshot = depth;
@@ -282,7 +282,7 @@ export class LiquidityMakerEngine {
} }
); );
safeSubscribe<AsterTicker>( safeSubscribe<Ticker>(
this.exchange.watchTicker.bind(this.exchange, this.config.symbol), this.exchange.watchTicker.bind(this.exchange, this.config.symbol),
(ticker) => { (ticker) => {
this.tickerSnapshot = ticker; this.tickerSnapshot = ticker;
@@ -296,7 +296,7 @@ export class LiquidityMakerEngine {
} }
); );
safeSubscribe<AsterKline[]>( safeSubscribe<Kline[]>(
this.exchange.watchKlines.bind(this.exchange, this.config.symbol, "1m"), this.exchange.watchKlines.bind(this.exchange, this.config.symbol, "1m"),
(klines) => { (klines) => {
if (!Array.isArray(klines) || !klines.length) return; if (!Array.isArray(klines) || !klines.length) return;
@@ -318,7 +318,7 @@ export class LiquidityMakerEngine {
} }
/** 检测成交订单 */ /** 检测成交订单 */
private detectFills(orders: AsterOrder[] | null | undefined): void { private detectFills(orders: Order[] | null | undefined): void {
if (!Array.isArray(orders)) return; if (!Array.isArray(orders)) return;
// 查找已成交或部分成交的订单 // 查找已成交或部分成交的订单
@@ -357,7 +357,7 @@ export class LiquidityMakerEngine {
} }
} }
private syncLocksWithOrders(orders: AsterOrder[] | null | undefined): void { private syncLocksWithOrders(orders: Order[] | null | undefined): void {
const list = Array.isArray(orders) ? orders : []; const list = Array.isArray(orders) ? orders : [];
Object.keys(this.pending).forEach((type) => { Object.keys(this.pending).forEach((type) => {
const pendingId = this.pending[type]; const pendingId = this.pending[type];
@@ -759,7 +759,7 @@ export class LiquidityMakerEngine {
* depthImbalanceRatio * depthImbalanceRatio
* 簿 * 簿
*/ */
private evaluateDepth(depth: AsterDepth): { private evaluateDepth(depth: Depth): {
buySum: number; buySum: number;
sellSum: number; sellSum: number;
skipBuySide: boolean; skipBuySide: boolean;
@@ -1186,7 +1186,7 @@ export class LiquidityMakerEngine {
return position; return position;
} }
private getSpotBalances(snapshot: AsterAccountSnapshot | null = this.accountSnapshot): { baseAvailable: number; quoteAvailable: number; baseWallet: number } | null { private getSpotBalances(snapshot: AccountSnapshot | null = this.accountSnapshot): { baseAvailable: number; quoteAvailable: number; baseWallet: number } | null {
const assets = snapshot?.assets ?? []; const assets = snapshot?.assets ?? [];
if (!assets.length) return null; if (!assets.length) return null;
const parsed = parseSymbolParts(this.config.symbol); const parsed = parseSymbolParts(this.config.symbol);
+14 -14
View File
@@ -1,10 +1,10 @@
import type { MakerConfig } from "../config"; import type { MakerConfig } from "../config";
import type { ExchangeAdapter } from "../exchanges/adapter"; import type { ExchangeAdapter } from "../exchanges/adapter";
import type { import type {
AsterAccountSnapshot, AccountSnapshot,
AsterDepth, Depth,
AsterOrder, Order,
AsterTicker, Ticker,
} from "../exchanges/types"; } from "../exchanges/types";
import { formatPriceToString } from "../utils/math"; import { formatPriceToString } from "../utils/math";
import { createTradeLog, type TradeLogEntry } from "../logging/trade-log"; import { createTradeLog, type TradeLogEntry } from "../logging/trade-log";
@@ -47,7 +47,7 @@ export interface MakerEngineSnapshot {
pnl: number; pnl: number;
accountUnrealized: number; accountUnrealized: number;
sessionVolume: number; sessionVolume: number;
openOrders: AsterOrder[]; openOrders: Order[];
desiredOrders: DesiredOrder[]; desiredOrders: DesiredOrder[];
tradeLog: TradeLogEntry[]; tradeLog: TradeLogEntry[];
lastUpdated: number | null; lastUpdated: number | null;
@@ -66,10 +66,10 @@ const EPS = 1e-5;
const INSUFFICIENT_BALANCE_COOLDOWN_MS = 15_000; const INSUFFICIENT_BALANCE_COOLDOWN_MS = 15_000;
export class MakerEngine { export class MakerEngine {
private accountSnapshot: AsterAccountSnapshot | null = null; private accountSnapshot: AccountSnapshot | null = null;
private depthSnapshot: AsterDepth | null = null; private depthSnapshot: Depth | null = null;
private tickerSnapshot: AsterTicker | null = null; private tickerSnapshot: Ticker | null = null;
private openOrders: AsterOrder[] = []; private openOrders: Order[] = [];
private readonly locks: OrderLockMap = {}; private readonly locks: OrderLockMap = {};
private readonly timers: OrderTimerMap = {}; private readonly timers: OrderTimerMap = {};
@@ -154,7 +154,7 @@ export class MakerEngine {
private bootstrap(): void { private bootstrap(): void {
const log: LogHandler = (type, detail) => this.tradeLog.push(type, detail); const log: LogHandler = (type, detail) => this.tradeLog.push(type, detail);
safeSubscribe<AsterAccountSnapshot>( safeSubscribe<AccountSnapshot>(
this.exchange.watchAccount.bind(this.exchange), this.exchange.watchAccount.bind(this.exchange),
(snapshot) => { (snapshot) => {
this.accountSnapshot = snapshot; this.accountSnapshot = snapshot;
@@ -178,7 +178,7 @@ export class MakerEngine {
} }
); );
safeSubscribe<AsterOrder[]>( safeSubscribe<Order[]>(
this.exchange.watchOrders.bind(this.exchange), this.exchange.watchOrders.bind(this.exchange),
(orders) => { (orders) => {
this.syncLocksWithOrders(orders); this.syncLocksWithOrders(orders);
@@ -211,7 +211,7 @@ export class MakerEngine {
} }
); );
safeSubscribe<AsterDepth>( safeSubscribe<Depth>(
this.exchange.watchDepth.bind(this.exchange, this.config.symbol), this.exchange.watchDepth.bind(this.exchange, this.config.symbol),
(depth) => { (depth) => {
this.depthSnapshot = depth; this.depthSnapshot = depth;
@@ -229,7 +229,7 @@ export class MakerEngine {
} }
); );
safeSubscribe<AsterTicker>( safeSubscribe<Ticker>(
this.exchange.watchTicker.bind(this.exchange, this.config.symbol), this.exchange.watchTicker.bind(this.exchange, this.config.symbol),
(ticker) => { (ticker) => {
this.tickerSnapshot = ticker; this.tickerSnapshot = ticker;
@@ -250,7 +250,7 @@ export class MakerEngine {
// Maker strategy does not require realtime klines. // Maker strategy does not require realtime klines.
} }
private syncLocksWithOrders(orders: AsterOrder[] | null | undefined): void { private syncLocksWithOrders(orders: Order[] | null | undefined): void {
const list = Array.isArray(orders) ? orders : []; const list = Array.isArray(orders) ? orders : [];
Object.keys(this.pending).forEach((type) => { Object.keys(this.pending).forEach((type) => {
const pendingId = this.pending[type]; const pendingId = this.pending[type];
+20 -20
View File
@@ -1,10 +1,10 @@
import type { MakerPointsConfig } from "../config"; import type { MakerPointsConfig } from "../config";
import type { ExchangeAdapter } from "../exchanges/adapter"; import type { ExchangeAdapter } from "../exchanges/adapter";
import type { import type {
AsterAccountSnapshot, AccountSnapshot,
AsterDepth, Depth,
AsterOrder, Order,
AsterTicker, Ticker,
} from "../exchanges/types"; } from "../exchanges/types";
import { formatPriceToString } from "../utils/math"; import { formatPriceToString } from "../utils/math";
import { createTradeLog, type TradeLogEntry } from "../logging/trade-log"; import { createTradeLog, type TradeLogEntry } from "../logging/trade-log";
@@ -59,7 +59,7 @@ export interface MakerPointsSnapshot {
pnl: number; pnl: number;
accountUnrealized: number; accountUnrealized: number;
sessionVolume: number; sessionVolume: number;
openOrders: AsterOrder[]; openOrders: Order[];
desiredOrders: DesiredOrder[]; desiredOrders: DesiredOrder[];
tradeLog: TradeLogEntry[]; tradeLog: TradeLogEntry[];
lastUpdated: number | null; lastUpdated: number | null;
@@ -102,10 +102,10 @@ const STANDX_MARGIN_MODE_MAX_ATTEMPTS = 10;
const ACCOUNT_STALE_REST_PROBE_MIN_INTERVAL_MS = 5_000; const ACCOUNT_STALE_REST_PROBE_MIN_INTERVAL_MS = 5_000;
export class MakerPointsEngine { export class MakerPointsEngine {
private accountSnapshot: AsterAccountSnapshot | null = null; private accountSnapshot: AccountSnapshot | null = null;
private depthSnapshot: AsterDepth | null = null; private depthSnapshot: Depth | null = null;
private tickerSnapshot: AsterTicker | null = null; private tickerSnapshot: Ticker | null = null;
private openOrders: AsterOrder[] = []; private openOrders: Order[] = [];
private readonly locks: OrderLockMap = {}; private readonly locks: OrderLockMap = {};
private readonly timers: OrderTimerMap = {}; private readonly timers: OrderTimerMap = {};
@@ -298,7 +298,7 @@ export class MakerPointsEngine {
const log: LogHandler = (type, detail) => this.tradeLog.push(type, detail); const log: LogHandler = (type, detail) => this.tradeLog.push(type, detail);
this.setupRestHealthProtection(); this.setupRestHealthProtection();
safeSubscribe<AsterAccountSnapshot>( safeSubscribe<AccountSnapshot>(
this.exchange.watchAccount.bind(this.exchange), this.exchange.watchAccount.bind(this.exchange),
(snapshot) => { (snapshot) => {
this.applyAccountSnapshot(snapshot); this.applyAccountSnapshot(snapshot);
@@ -310,7 +310,7 @@ export class MakerPointsEngine {
} }
); );
safeSubscribe<AsterOrder[]>( safeSubscribe<Order[]>(
this.exchange.watchOrders.bind(this.exchange), this.exchange.watchOrders.bind(this.exchange),
(orders) => { (orders) => {
this.syncLocksWithOrders(orders); this.syncLocksWithOrders(orders);
@@ -339,7 +339,7 @@ export class MakerPointsEngine {
} }
); );
safeSubscribe<AsterDepth>( safeSubscribe<Depth>(
this.exchange.watchDepth.bind(this.exchange, this.config.symbol), this.exchange.watchDepth.bind(this.exchange, this.config.symbol),
(depth) => { (depth) => {
this.depthSnapshot = depth; this.depthSnapshot = depth;
@@ -358,7 +358,7 @@ export class MakerPointsEngine {
} }
); );
safeSubscribe<AsterTicker>( safeSubscribe<Ticker>(
this.exchange.watchTicker.bind(this.exchange, this.config.symbol), this.exchange.watchTicker.bind(this.exchange, this.config.symbol),
(ticker) => { (ticker) => {
this.tickerSnapshot = ticker; this.tickerSnapshot = ticker;
@@ -376,7 +376,7 @@ export class MakerPointsEngine {
this.setupConnectionProtection(); this.setupConnectionProtection();
} }
private applyAccountSnapshot(snapshot: AsterAccountSnapshot): void { private applyAccountSnapshot(snapshot: AccountSnapshot): void {
this.accountSnapshot = snapshot; this.accountSnapshot = snapshot;
// StandX: WS 推送使用本地接收时间戳;REST 快照使用响应里的 time 字段映射到 snapshot.updateTime // StandX: WS 推送使用本地接收时间戳;REST 快照使用响应里的 time 字段映射到 snapshot.updateTime
this.lastStandxAccountTime = this.lastStandxAccountTime =
@@ -545,7 +545,7 @@ export class MakerPointsEngine {
} }
} }
private syncLocksWithOrders(orders: AsterOrder[] | null | undefined): void { private syncLocksWithOrders(orders: Order[] | null | undefined): void {
const list = Array.isArray(orders) ? orders : []; const list = Array.isArray(orders) ? orders : [];
Object.keys(this.pending).forEach((type) => { Object.keys(this.pending).forEach((type) => {
const pendingId = this.pending[type]; const pendingId = this.pending[type];
@@ -746,7 +746,7 @@ export class MakerPointsEngine {
ask1: number; ask1: number;
skipBuy: boolean; skipBuy: boolean;
skipSell: boolean; skipSell: boolean;
depth: AsterDepth | null; depth: Depth | null;
}): DesiredOrder[] { }): DesiredOrder[] {
const { bid1, ask1, skipBuy, skipSell, depth } = params; const { bid1, ask1, skipBuy, skipSell, depth } = params;
@@ -837,7 +837,7 @@ export class MakerPointsEngine {
* *
*/ */
private checkDepthStatusChanged( private checkDepthStatusChanged(
depth: AsterDepth | null, depth: Depth | null,
bid1: number, bid1: number,
ask1: number ask1: number
): boolean { ): boolean {
@@ -879,7 +879,7 @@ export class MakerPointsEngine {
/** /**
* *
*/ */
private shouldTriggerImmediateDepthProtection(depth: AsterDepth | null): boolean { private shouldTriggerImmediateDepthProtection(depth: Depth | null): boolean {
if (!depth) return false; if (!depth) return false;
if (this.defenseMode || this.reconnectResetPending || this.stopLossProcessing) return false; if (this.defenseMode || this.reconnectResetPending || this.stopLossProcessing) return false;
@@ -917,7 +917,7 @@ export class MakerPointsEngine {
/** /**
* minRepriceBps * minRepriceBps
*/ */
private shouldTriggerImmediateReprice(depth: AsterDepth | null): boolean { private shouldTriggerImmediateReprice(depth: Depth | null): boolean {
if (!depth) return false; if (!depth) return false;
if (this.defenseMode || this.reconnectResetPending || this.stopLossProcessing) return false; if (this.defenseMode || this.reconnectResetPending || this.stopLossProcessing) return false;
@@ -1993,7 +1993,7 @@ export class MakerPointsEngine {
void poll(); void poll();
} }
private getStandxMarginMode(snapshot: AsterAccountSnapshot | null): string | null { private getStandxMarginMode(snapshot: AccountSnapshot | null): string | null {
if (this.exchange.id !== "standx") return null; if (this.exchange.id !== "standx") return null;
const positions = snapshot?.positions ?? []; const positions = snapshot?.positions ?? [];
const match = positions.find((pos) => pos.symbol === this.config.symbol); const match = positions.find((pos) => pos.symbol === this.config.symbol);
+18 -18
View File
@@ -1,11 +1,11 @@
import type { MakerConfig } from "../config"; import type { MakerConfig } from "../config";
import type { ExchangeAdapter } from "../exchanges/adapter"; import type { ExchangeAdapter } from "../exchanges/adapter";
import type { import type {
AsterAccountSnapshot, AccountSnapshot,
AsterDepth, Depth,
AsterKline, Kline,
AsterOrder, Order,
AsterTicker, Ticker,
} from "../exchanges/types"; } from "../exchanges/types";
import { formatPriceToString } from "../utils/math"; import { formatPriceToString } from "../utils/math";
import { createTradeLog } from "../logging/trade-log"; import { createTradeLog } from "../logging/trade-log";
@@ -56,12 +56,12 @@ type MakerListener = (snapshot: OffsetMakerEngineSnapshot) => void;
const EPS = 1e-5; const EPS = 1e-5;
export class OffsetMakerEngine { export class OffsetMakerEngine {
private accountSnapshot: AsterAccountSnapshot | null = null; private accountSnapshot: AccountSnapshot | null = null;
private depthSnapshot: AsterDepth | null = null; private depthSnapshot: Depth | null = null;
private tickerSnapshot: AsterTicker | null = null; private tickerSnapshot: Ticker | null = null;
private lastKline: AsterKline | null = null; private lastKline: Kline | null = null;
private liveCandle: { startMs: number; open: number; close: number } | null = null; private liveCandle: { startMs: number; open: number; close: number } | null = null;
private openOrders: AsterOrder[] = []; private openOrders: Order[] = [];
private readonly locks: OrderLockMap = {}; private readonly locks: OrderLockMap = {};
private readonly timers: OrderTimerMap = {}; private readonly timers: OrderTimerMap = {};
@@ -163,7 +163,7 @@ export class OffsetMakerEngine {
private bootstrap(): void { private bootstrap(): void {
const log: LogHandler = (type, detail) => this.tradeLog.push(type, detail); const log: LogHandler = (type, detail) => this.tradeLog.push(type, detail);
safeSubscribe<AsterAccountSnapshot>( safeSubscribe<AccountSnapshot>(
this.exchange.watchAccount.bind(this.exchange), this.exchange.watchAccount.bind(this.exchange),
(snapshot) => { (snapshot) => {
this.accountSnapshot = snapshot; this.accountSnapshot = snapshot;
@@ -207,7 +207,7 @@ export class OffsetMakerEngine {
} }
); );
safeSubscribe<AsterOrder[]>( safeSubscribe<Order[]>(
this.exchange.watchOrders.bind(this.exchange), this.exchange.watchOrders.bind(this.exchange),
(orders) => { (orders) => {
this.syncLocksWithOrders(orders); this.syncLocksWithOrders(orders);
@@ -236,7 +236,7 @@ export class OffsetMakerEngine {
} }
); );
safeSubscribe<AsterDepth>( safeSubscribe<Depth>(
this.exchange.watchDepth.bind(this.exchange, this.config.symbol), this.exchange.watchDepth.bind(this.exchange, this.config.symbol),
(depth) => { (depth) => {
this.depthSnapshot = depth; this.depthSnapshot = depth;
@@ -250,7 +250,7 @@ export class OffsetMakerEngine {
} }
); );
safeSubscribe<AsterTicker>( safeSubscribe<Ticker>(
this.exchange.watchTicker.bind(this.exchange, this.config.symbol), this.exchange.watchTicker.bind(this.exchange, this.config.symbol),
(ticker) => { (ticker) => {
this.tickerSnapshot = ticker; this.tickerSnapshot = ticker;
@@ -264,7 +264,7 @@ export class OffsetMakerEngine {
} }
); );
safeSubscribe<AsterKline[]>( safeSubscribe<Kline[]>(
this.exchange.watchKlines.bind(this.exchange, this.config.symbol, "1m"), this.exchange.watchKlines.bind(this.exchange, this.config.symbol, "1m"),
(klines) => { (klines) => {
if (!Array.isArray(klines) || !klines.length) return; if (!Array.isArray(klines) || !klines.length) return;
@@ -284,7 +284,7 @@ export class OffsetMakerEngine {
); );
} }
private syncLocksWithOrders(orders: AsterOrder[] | null | undefined): void { private syncLocksWithOrders(orders: Order[] | null | undefined): void {
const list = Array.isArray(orders) ? orders : []; const list = Array.isArray(orders) ? orders : [];
Object.keys(this.pending).forEach((type) => { Object.keys(this.pending).forEach((type) => {
const pendingId = this.pending[type]; const pendingId = this.pending[type];
@@ -600,7 +600,7 @@ export class OffsetMakerEngine {
} }
} }
private evaluateDepth(depth: AsterDepth): { private evaluateDepth(depth: Depth): {
buySum: number; buySum: number;
sellSum: number; sellSum: number;
skipBuySide: boolean; skipBuySide: boolean;
@@ -1038,7 +1038,7 @@ export class OffsetMakerEngine {
return position; return position;
} }
private getSpotBalances(snapshot: AsterAccountSnapshot | null = this.accountSnapshot): { baseAvailable: number; quoteAvailable: number; baseWallet: number } | null { private getSpotBalances(snapshot: AccountSnapshot | null = this.accountSnapshot): { baseAvailable: number; quoteAvailable: number; baseWallet: number } | null {
const assets = snapshot?.assets ?? []; const assets = snapshot?.assets ?? [];
if (!assets.length) return null; if (!assets.length) return null;
const parsed = parseSymbolParts(this.config.symbol); const parsed = parseSymbolParts(this.config.symbol);
+13 -13
View File
@@ -1,5 +1,5 @@
import type { ExchangeAdapter } from "../exchanges/adapter"; import type { ExchangeAdapter } from "../exchanges/adapter";
import type { AsterAccountSnapshot, AsterDepth, AsterOrder, AsterTicker } from "../exchanges/types"; import type { AccountSnapshot, Depth, Order, Ticker } from "../exchanges/types";
import { createTradeLog, type TradeLogEntry } from "../logging/trade-log"; import { createTradeLog, type TradeLogEntry } from "../logging/trade-log";
import { marketClose, placeMarketOrder, placeStopLossOrder, unlockOperating } from "../core/order-coordinator"; import { marketClose, placeMarketOrder, placeStopLossOrder, unlockOperating } from "../core/order-coordinator";
import type { OrderLockMap, OrderPendingMap, OrderTimerMap } from "../core/order-coordinator"; import type { OrderLockMap, OrderPendingMap, OrderTimerMap } from "../core/order-coordinator";
@@ -55,9 +55,9 @@ export interface SwingEngineSnapshot {
stopLossTarget: number | null; stopLossTarget: number | null;
stopLossKillSwitch: boolean; stopLossKillSwitch: boolean;
openOrders: AsterOrder[]; openOrders: Order[];
depth: AsterDepth | null; depth: Depth | null;
ticker: AsterTicker | null; ticker: Ticker | null;
tradeLog: TradeLogEntry[]; tradeLog: TradeLogEntry[];
lastUpdated: number | null; lastUpdated: number | null;
@@ -70,10 +70,10 @@ type SwingListener = (snapshot: SwingEngineSnapshot) => void;
const EPS = 1e-5; const EPS = 1e-5;
export class SwingEngine { export class SwingEngine {
private accountSnapshot: AsterAccountSnapshot | null = null; private accountSnapshot: AccountSnapshot | null = null;
private openOrders: AsterOrder[] = []; private openOrders: Order[] = [];
private depthSnapshot: AsterDepth | null = null; private depthSnapshot: Depth | null = null;
private tickerSnapshot: AsterTicker | null = null; private tickerSnapshot: Ticker | null = null;
private readonly locks: OrderLockMap = {}; private readonly locks: OrderLockMap = {};
private readonly timers: OrderTimerMap = {}; private readonly timers: OrderTimerMap = {};
@@ -163,7 +163,7 @@ export class SwingEngine {
private bootstrap(): void { private bootstrap(): void {
const log: LogHandler = (type, detail) => this.tradeLog.push(type, detail); const log: LogHandler = (type, detail) => this.tradeLog.push(type, detail);
safeSubscribe<AsterAccountSnapshot>( safeSubscribe<AccountSnapshot>(
this.exchange.watchAccount.bind(this.exchange), this.exchange.watchAccount.bind(this.exchange),
(snapshot) => { (snapshot) => {
this.accountSnapshot = snapshot; this.accountSnapshot = snapshot;
@@ -192,7 +192,7 @@ export class SwingEngine {
} }
); );
safeSubscribe<AsterOrder[]>( safeSubscribe<Order[]>(
this.exchange.watchOrders.bind(this.exchange), this.exchange.watchOrders.bind(this.exchange),
(orders) => { (orders) => {
this.synchronizeLocks(orders); this.synchronizeLocks(orders);
@@ -214,7 +214,7 @@ export class SwingEngine {
} }
); );
safeSubscribe<AsterDepth>( safeSubscribe<Depth>(
this.exchange.watchDepth.bind(this.exchange, this.config.symbol), this.exchange.watchDepth.bind(this.exchange, this.config.symbol),
(depth) => { (depth) => {
this.depthSnapshot = depth; this.depthSnapshot = depth;
@@ -227,7 +227,7 @@ export class SwingEngine {
} }
); );
safeSubscribe<AsterTicker>( safeSubscribe<Ticker>(
this.exchange.watchTicker.bind(this.exchange, this.config.symbol), this.exchange.watchTicker.bind(this.exchange, this.config.symbol),
(ticker) => { (ticker) => {
this.tickerSnapshot = ticker; this.tickerSnapshot = ticker;
@@ -241,7 +241,7 @@ export class SwingEngine {
); );
} }
private synchronizeLocks(orders: AsterOrder[] | null | undefined): void { private synchronizeLocks(orders: Order[] | null | undefined): void {
const list = Array.isArray(orders) ? orders : []; const list = Array.isArray(orders) ? orders : [];
Object.keys(this.pending).forEach((type) => { Object.keys(this.pending).forEach((type) => {
const pendingId = this.pending[type]; const pendingId = this.pending[type];
+20 -20
View File
@@ -2,11 +2,11 @@ import crypto from "crypto";
import type { TradingConfig } from "../config"; import type { TradingConfig } from "../config";
import type { ExchangeAdapter } from "../exchanges/adapter"; import type { ExchangeAdapter } from "../exchanges/adapter";
import type { import type {
AsterAccountSnapshot, AccountSnapshot,
AsterOrder, Order,
AsterTicker, Ticker,
AsterDepth, Depth,
AsterKline, Kline,
} from "../exchanges/types"; } from "../exchanges/types";
import { import {
calcStopLossPrice, calcStopLossPrice,
@@ -51,9 +51,9 @@ export interface TrendEngineSnapshot {
totalTrades: number; totalTrades: number;
sessionVolume: number; sessionVolume: number;
tradeLog: TradeLogEntry[]; tradeLog: TradeLogEntry[];
openOrders: AsterOrder[]; openOrders: Order[];
depth: AsterDepth | null; depth: Depth | null;
ticker: AsterTicker | null; ticker: Ticker | null;
lastUpdated: number | null; lastUpdated: number | null;
lastOpenSignal: OpenOrderPlan; lastOpenSignal: OpenOrderPlan;
} }
@@ -68,11 +68,11 @@ type TrendEngineEvent = "update";
type TrendEngineListener = (snapshot: TrendEngineSnapshot) => void; type TrendEngineListener = (snapshot: TrendEngineSnapshot) => void;
export class TrendEngine { export class TrendEngine {
private accountSnapshot: AsterAccountSnapshot | null = null; private accountSnapshot: AccountSnapshot | null = null;
private openOrders: AsterOrder[] = []; private openOrders: Order[] = [];
private depthSnapshot: AsterDepth | null = null; private depthSnapshot: Depth | null = null;
private tickerSnapshot: AsterTicker | null = null; private tickerSnapshot: Ticker | null = null;
private klineSnapshot: AsterKline[] = []; private klineSnapshot: Kline[] = [];
private readonly locks: OrderLockMap = {}; private readonly locks: OrderLockMap = {};
private readonly timers: OrderTimerMap = {}; private readonly timers: OrderTimerMap = {};
@@ -164,7 +164,7 @@ export class TrendEngine {
private bootstrap(): void { private bootstrap(): void {
const log: LogHandler = (type, detail) => this.tradeLog.push(type, detail); const log: LogHandler = (type, detail) => this.tradeLog.push(type, detail);
safeSubscribe<AsterAccountSnapshot>( safeSubscribe<AccountSnapshot>(
this.exchange.watchAccount.bind(this.exchange), this.exchange.watchAccount.bind(this.exchange),
(snapshot) => { (snapshot) => {
this.accountSnapshot = snapshot; this.accountSnapshot = snapshot;
@@ -181,7 +181,7 @@ export class TrendEngine {
} }
); );
safeSubscribe<AsterOrder[]>( safeSubscribe<Order[]>(
this.exchange.watchOrders.bind(this.exchange), this.exchange.watchOrders.bind(this.exchange),
(orders) => { (orders) => {
this.synchronizeLocks(orders); this.synchronizeLocks(orders);
@@ -215,7 +215,7 @@ export class TrendEngine {
} }
); );
safeSubscribe<AsterDepth>( safeSubscribe<Depth>(
this.exchange.watchDepth.bind(this.exchange, this.config.symbol), this.exchange.watchDepth.bind(this.exchange, this.config.symbol),
(depth) => { (depth) => {
this.depthSnapshot = depth; this.depthSnapshot = depth;
@@ -228,7 +228,7 @@ export class TrendEngine {
} }
); );
safeSubscribe<AsterTicker>( safeSubscribe<Ticker>(
this.exchange.watchTicker.bind(this.exchange, this.config.symbol), this.exchange.watchTicker.bind(this.exchange, this.config.symbol),
(ticker) => { (ticker) => {
this.tickerSnapshot = ticker; this.tickerSnapshot = ticker;
@@ -241,7 +241,7 @@ export class TrendEngine {
} }
); );
safeSubscribe<AsterKline[]>( safeSubscribe<Kline[]>(
this.exchange.watchKlines.bind(this.exchange, this.config.symbol, this.config.klineInterval), this.exchange.watchKlines.bind(this.exchange, this.config.symbol, this.config.klineInterval),
(klines) => { (klines) => {
this.klineSnapshot = Array.isArray(klines) ? klines : []; this.klineSnapshot = Array.isArray(klines) ? klines : [];
@@ -258,7 +258,7 @@ export class TrendEngine {
); );
} }
private synchronizeLocks(orders: AsterOrder[] | null | undefined): void { private synchronizeLocks(orders: Order[] | null | undefined): void {
const list = Array.isArray(orders) ? orders : []; const list = Array.isArray(orders) ? orders : [];
Object.keys(this.pending).forEach((type) => { Object.keys(this.pending).forEach((type) => {
const pendingId = this.pending[type]; const pendingId = this.pending[type];
@@ -834,7 +834,7 @@ export class TrendEngine {
private async tryReplaceStop( private async tryReplaceStop(
side: "BUY" | "SELL", side: "BUY" | "SELL",
currentOrder: AsterOrder, currentOrder: Order,
nextStopPrice: number, nextStopPrice: number,
lastPrice: number lastPrice: number
): Promise<void> { ): Promise<void> {
+2 -2
View File
@@ -1,9 +1,9 @@
import type { AsterDepth } from "../exchanges/types"; import type { Depth } from "../exchanges/types";
export type DepthImbalance = "balanced" | "buy_dominant" | "sell_dominant"; export type DepthImbalance = "balanced" | "buy_dominant" | "sell_dominant";
export function computeDepthStats( export function computeDepthStats(
depth: AsterDepth, depth: Depth,
levels = 10, levels = 10,
ratio = 3 ratio = 3
): { ): {
+5 -5
View File
@@ -1,6 +1,6 @@
import type { AsterDepth, AsterTicker } from "../exchanges/types"; import type { Depth, Ticker } from "../exchanges/types";
export function getTopPrices(depth?: AsterDepth | null): { topBid: number | null; topAsk: number | null } { export function getTopPrices(depth?: Depth | null): { topBid: number | null; topAsk: number | null } {
const bid = Number(depth?.bids?.[0]?.[0]); const bid = Number(depth?.bids?.[0]?.[0]);
const ask = Number(depth?.asks?.[0]?.[0]); const ask = Number(depth?.asks?.[0]?.[0]);
return { return {
@@ -16,7 +16,7 @@ export function getTopPrices(depth?: AsterDepth | null): { topBid: number | null
* @returns 退 * @returns 退
*/ */
export function getPricesAtLevel( export function getPricesAtLevel(
depth?: AsterDepth | null, depth?: Depth | null,
level: number = 1 level: number = 1
): { bidAtLevel: number | null; askAtLevel: number | null } { ): { bidAtLevel: number | null; askAtLevel: number | null } {
const index = Math.max(0, level - 1); const index = Math.max(0, level - 1);
@@ -49,7 +49,7 @@ export function getPricesAtLevel(
return { bidAtLevel, askAtLevel }; return { bidAtLevel, askAtLevel };
} }
export function getMidOrLast(depth?: AsterDepth | null, ticker?: AsterTicker | null): number | null { export function getMidOrLast(depth?: Depth | null, ticker?: Ticker | null): number | null {
const { topBid, topAsk } = getTopPrices(depth); const { topBid, topAsk } = getTopPrices(depth);
if (topBid != null && topAsk != null) return (topBid + topAsk) / 2; if (topBid != null && topAsk != null) return (topBid + topAsk) / 2;
const last = Number(ticker?.lastPrice); const last = Number(ticker?.lastPrice);
@@ -64,7 +64,7 @@ export function getMidOrLast(depth?: AsterDepth | null, ticker?: AsterTicker | n
* @returns () * @returns ()
*/ */
export function getDepthBetweenPrices( export function getDepthBetweenPrices(
depth: AsterDepth | null | undefined, depth: Depth | null | undefined,
side: "BUY" | "SELL", side: "BUY" | "SELL",
targetPrice: number targetPrice: number
): number { ): number {
+6 -6
View File
@@ -1,4 +1,4 @@
import type { AsterAccountAsset, AsterAccountSnapshot, AsterKline } from "../exchanges/types"; import type { AccountAsset, AccountSnapshot, Kline } from "../exchanges/types";
export interface PositionSnapshot { export interface PositionSnapshot {
positionAmt: number; positionAmt: number;
@@ -7,7 +7,7 @@ export interface PositionSnapshot {
markPrice: number | null; markPrice: number | null;
} }
export function getPosition(snapshot: AsterAccountSnapshot | null, symbol: string): PositionSnapshot { export function getPosition(snapshot: AccountSnapshot | null, symbol: string): PositionSnapshot {
if (!snapshot) { if (!snapshot) {
return { positionAmt: 0, entryPrice: 0, unrealizedProfit: 0, markPrice: null }; return { positionAmt: 0, entryPrice: 0, unrealizedProfit: 0, markPrice: null };
} }
@@ -48,7 +48,7 @@ export function getPosition(snapshot: AsterAccountSnapshot | null, symbol: strin
} }
export function validateAccountSnapshotForSymbol( export function validateAccountSnapshotForSymbol(
snapshot: AsterAccountSnapshot | null, snapshot: AccountSnapshot | null,
symbol: string symbol: string
): { ok: true } | { ok: false; issues: string[] } { ): { ok: true } | { ok: false; issues: string[] } {
if (!snapshot) return { ok: true }; if (!snapshot) return { ok: true };
@@ -88,7 +88,7 @@ export function validateAccountSnapshotForSymbol(
return { ok: false, issues: Array.from(new Set(issues)) }; return { ok: false, issues: Array.from(new Set(issues)) };
} }
export function getSMA(values: AsterKline[], length: number): number | null { export function getSMA(values: Kline[], length: number): number | null {
if (!Array.isArray(values) || values.length < length) return null; if (!Array.isArray(values) || values.length < length) return null;
const window = values.slice(-length); const window = values.slice(-length);
const closes = window.map((kline) => Number(kline.close)); const closes = window.map((kline) => Number(kline.close));
@@ -118,7 +118,7 @@ export function calcTrailingActivationPrice(entryPrice: number, qty: number, sid
} }
export function computeBollingerBandwidth( export function computeBollingerBandwidth(
values: AsterKline[], values: Kline[],
length: number, length: number,
stdMultiplier: number stdMultiplier: number
): number | null { ): number | null {
@@ -190,7 +190,7 @@ function normalizeBaseSymbol(symbol: string | undefined): string | undefined {
return parseSymbolParts(symbol).base; return parseSymbolParts(symbol).base;
} }
function selectAsset(assets: AsterAccountAsset[], baseSymbol: string, baseAssetId?: number): AsterAccountAsset | undefined { function selectAsset(assets: AccountAsset[], baseSymbol: string, baseAssetId?: number): AccountAsset | undefined {
const normalized = baseSymbol.toUpperCase(); const normalized = baseSymbol.toUpperCase();
const targetId = Number(baseAssetId); const targetId = Number(baseAssetId);
return assets.find((asset) => { return assets.find((asset) => {
+2 -2
View File
@@ -1,8 +1,8 @@
import { describe, expect, it } from "vitest"; import { describe, expect, it } from "vitest";
import type { AsterAccountSnapshot } from "../src/exchanges/types"; import type { AccountSnapshot } from "../src/exchanges/types";
import { validateAccountSnapshotForSymbol } from "../src/utils/strategy"; import { validateAccountSnapshotForSymbol } from "../src/utils/strategy";
function baseSnapshot(positions: AsterAccountSnapshot["positions"]): AsterAccountSnapshot { function baseSnapshot(positions: AccountSnapshot["positions"]): AccountSnapshot {
return { return {
canTrade: true, canTrade: true,
canDeposit: true, canDeposit: true,
+13 -13
View File
@@ -1,47 +1,47 @@
import { describe, expect, it, vi } from "vitest"; import { describe, expect, it, vi } from "vitest";
import type { ExchangeAdapter } from "../src/exchanges/adapter"; import type { ExchangeAdapter } from "../src/exchanges/adapter";
import type { import type {
AsterAccountSnapshot, AccountSnapshot,
AsterDepth, Depth,
AsterKline, Kline,
AsterOrder, Order,
AsterTicker, Ticker,
} from "../src/exchanges/types"; } from "../src/exchanges/types";
import { BasisArbEngine } from "../src/strategy/basis-arb-engine"; import { BasisArbEngine } from "../src/strategy/basis-arb-engine";
class StubAdapter implements ExchangeAdapter { class StubAdapter implements ExchangeAdapter {
id = "aster"; id = "aster";
private depthHandler: ((depth: AsterDepth) => void) | null = null; private depthHandler: ((depth: Depth) => void) | null = null;
supportsTrailingStops(): boolean { supportsTrailingStops(): boolean {
return false; return false;
} }
watchAccount(_cb: (snapshot: AsterAccountSnapshot) => void): void { watchAccount(_cb: (snapshot: AccountSnapshot) => void): void {
// not required for this test // not required for this test
} }
watchOrders(_cb: (orders: AsterOrder[]) => void): void { watchOrders(_cb: (orders: Order[]) => void): void {
// not required for this test // not required for this test
} }
watchDepth(_symbol: string, cb: (depth: AsterDepth) => void): void { watchDepth(_symbol: string, cb: (depth: Depth) => void): void {
this.depthHandler = cb; this.depthHandler = cb;
} }
emitDepth(depth: AsterDepth): void { emitDepth(depth: Depth): void {
this.depthHandler?.(depth); this.depthHandler?.(depth);
} }
watchTicker(_symbol: string, _cb: (ticker: AsterTicker) => void): void { watchTicker(_symbol: string, _cb: (ticker: Ticker) => void): void {
// not required for this test // not required for this test
} }
watchKlines(_symbol: string, _interval: string, _cb: (klines: AsterKline[]) => void): void { watchKlines(_symbol: string, _interval: string, _cb: (klines: Kline[]) => void): void {
// not required for this test // not required for this test
} }
createOrder(): Promise<AsterOrder> { createOrder(): Promise<Order> {
throw new Error("not implemented"); throw new Error("not implemented");
} }
@@ -1,6 +1,6 @@
import { afterEach, describe, expect, it, vi } from "vitest"; import { afterEach, describe, expect, it, vi } from "vitest";
import type { ExchangeAdapter } from "../src/exchanges/adapter"; import type { ExchangeAdapter } from "../src/exchanges/adapter";
import type { AsterAccountSnapshot, AsterDepth, AsterKline, AsterOrder, AsterTicker } from "../src/exchanges/types"; import type { AccountSnapshot, Depth, Kline, Order, Ticker } from "../src/exchanges/types";
import { MakerPointsEngine } from "../src/strategy/maker-points-engine"; import { MakerPointsEngine } from "../src/strategy/maker-points-engine";
const ORIGINAL_FETCH = globalThis.fetch; const ORIGINAL_FETCH = globalThis.fetch;
@@ -12,13 +12,13 @@ class StubAdapter implements ExchangeAdapter {
return false; return false;
} }
watchAccount(_cb: (snapshot: AsterAccountSnapshot) => void): void {} watchAccount(_cb: (snapshot: AccountSnapshot) => void): void {}
watchOrders(_cb: (orders: AsterOrder[]) => void): void {} watchOrders(_cb: (orders: Order[]) => void): void {}
watchDepth(_symbol: string, _cb: (depth: AsterDepth) => void): void {} watchDepth(_symbol: string, _cb: (depth: Depth) => void): void {}
watchTicker(_symbol: string, _cb: (ticker: AsterTicker) => void): void {} watchTicker(_symbol: string, _cb: (ticker: Ticker) => void): void {}
watchKlines(_symbol: string, _interval: string, _cb: (klines: AsterKline[]) => void): void {} watchKlines(_symbol: string, _interval: string, _cb: (klines: Kline[]) => void): void {}
async createOrder(): Promise<AsterOrder> { async createOrder(): Promise<Order> {
throw new Error("not implemented"); throw new Error("not implemented");
} }
@@ -26,7 +26,7 @@ class StubAdapter implements ExchangeAdapter {
async cancelOrders(): Promise<void> {} async cancelOrders(): Promise<void> {}
async cancelAllOrders(): Promise<void> {} async cancelAllOrders(): Promise<void> {}
async queryAccountSnapshot(): Promise<AsterAccountSnapshot | null> { async queryAccountSnapshot(): Promise<AccountSnapshot | null> {
return { return {
canTrade: true, canTrade: true,
canDeposit: true, canDeposit: true,
+12 -12
View File
@@ -3,11 +3,11 @@ import { executeCliCommand } from "../src/cli/command-executor";
import type { ParsedCliCommand } from "../src/cli/command-types"; import type { ParsedCliCommand } from "../src/cli/command-types";
import type { ExchangeAdapter } from "../src/exchanges/adapter"; import type { ExchangeAdapter } from "../src/exchanges/adapter";
import type { import type {
AsterAccountSnapshot, AccountSnapshot,
AsterDepth, Depth,
AsterKline, Kline,
AsterOrder, Order,
AsterTicker, Ticker,
CreateOrderParams, CreateOrderParams,
} from "../src/exchanges/types"; } from "../src/exchanges/types";
@@ -22,7 +22,7 @@ class FakeAdapter implements ExchangeAdapter {
return true; return true;
} }
watchAccount(cb: (snapshot: AsterAccountSnapshot) => void): void { watchAccount(cb: (snapshot: AccountSnapshot) => void): void {
cb({ cb({
canTrade: true, canTrade: true,
canDeposit: true, canDeposit: true,
@@ -35,11 +35,11 @@ class FakeAdapter implements ExchangeAdapter {
}); });
} }
watchOrders(cb: (orders: AsterOrder[]) => void): void { watchOrders(cb: (orders: Order[]) => void): void {
cb([]); cb([]);
} }
watchDepth(_symbol: string, cb: (depth: AsterDepth) => void): void { watchDepth(_symbol: string, cb: (depth: Depth) => void): void {
cb({ cb({
lastUpdateId: 1, lastUpdateId: 1,
bids: [["100", "1"]], bids: [["100", "1"]],
@@ -47,7 +47,7 @@ class FakeAdapter implements ExchangeAdapter {
}); });
} }
watchTicker(symbol: string, cb: (ticker: AsterTicker) => void): void { watchTicker(symbol: string, cb: (ticker: Ticker) => void): void {
cb({ cb({
symbol, symbol,
lastPrice: "100", lastPrice: "100",
@@ -57,10 +57,10 @@ class FakeAdapter implements ExchangeAdapter {
volume: "10", volume: "10",
quoteVolume: "1000", quoteVolume: "1000",
eventTime: Date.now(), eventTime: Date.now(),
} as AsterTicker); } as Ticker);
} }
watchKlines(_symbol: string, _interval: string, cb: (klines: AsterKline[]) => void): void { watchKlines(_symbol: string, _interval: string, cb: (klines: Kline[]) => void): void {
cb([ cb([
{ {
openTime: 1, openTime: 1,
@@ -75,7 +75,7 @@ class FakeAdapter implements ExchangeAdapter {
]); ]);
} }
async createOrder(params: CreateOrderParams): Promise<AsterOrder> { async createOrder(params: CreateOrderParams): Promise<Order> {
this.createOrderCalls += 1; this.createOrderCalls += 1;
return { return {
orderId: "1", orderId: "1",
+11 -11
View File
@@ -2,11 +2,11 @@ import { describe, expect, it } from "vitest";
import { DryRunExchangeAdapter } from "../src/exchanges/dry-run-adapter"; import { DryRunExchangeAdapter } from "../src/exchanges/dry-run-adapter";
import type { ExchangeAdapter } from "../src/exchanges/adapter"; import type { ExchangeAdapter } from "../src/exchanges/adapter";
import type { import type {
AsterAccountSnapshot, AccountSnapshot,
AsterDepth, Depth,
AsterKline, Kline,
AsterOrder, Order,
AsterTicker, Ticker,
CreateOrderParams, CreateOrderParams,
} from "../src/exchanges/types"; } from "../src/exchanges/types";
@@ -18,13 +18,13 @@ class BaseAdapter implements ExchangeAdapter {
return true; return true;
} }
watchAccount(_cb: (snapshot: AsterAccountSnapshot) => void): void {} watchAccount(_cb: (snapshot: AccountSnapshot) => void): void {}
watchOrders(_cb: (orders: AsterOrder[]) => void): void {} watchOrders(_cb: (orders: Order[]) => void): void {}
watchDepth(_symbol: string, _cb: (depth: AsterDepth) => void): void {} watchDepth(_symbol: string, _cb: (depth: Depth) => void): void {}
watchTicker(_symbol: string, _cb: (ticker: AsterTicker) => void): void {} watchTicker(_symbol: string, _cb: (ticker: Ticker) => void): void {}
watchKlines(_symbol: string, _interval: string, _cb: (klines: AsterKline[]) => void): void {} watchKlines(_symbol: string, _interval: string, _cb: (klines: Kline[]) => void): void {}
async createOrder(_params: CreateOrderParams): Promise<AsterOrder> { async createOrder(_params: CreateOrderParams): Promise<Order> {
this.createCalls += 1; this.createCalls += 1;
throw new Error("should not be called in dry-run"); throw new Error("should not be called in dry-run");
} }
+11 -11
View File
@@ -18,11 +18,11 @@ import {
import { buildAdapterFromEnv } from "../src/exchanges/resolve-from-env"; import { buildAdapterFromEnv } from "../src/exchanges/resolve-from-env";
import type { ExchangeAdapter } from "../src/exchanges/adapter"; import type { ExchangeAdapter } from "../src/exchanges/adapter";
import type { import type {
AsterAccountSnapshot, AccountSnapshot,
AsterDepth, Depth,
AsterKline, Kline,
AsterOrder, Order,
AsterTicker, Ticker,
CreateOrderParams, CreateOrderParams,
} from "../src/exchanges/types"; } from "../src/exchanges/types";
@@ -78,17 +78,17 @@ class RecorderAdapter implements ExchangeAdapter {
return false; return false;
} }
watchAccount(_cb: (snapshot: AsterAccountSnapshot) => void): void {} watchAccount(_cb: (snapshot: AccountSnapshot) => void): void {}
watchOrders(_cb: (orders: AsterOrder[]) => void): void {} watchOrders(_cb: (orders: Order[]) => void): void {}
watchDepth(_symbol: string, _cb: (depth: AsterDepth) => void): void {} watchDepth(_symbol: string, _cb: (depth: Depth) => void): void {}
watchTicker(_symbol: string, _cb: (ticker: AsterTicker) => void): void {} watchTicker(_symbol: string, _cb: (ticker: Ticker) => void): void {}
watchKlines(_symbol: string, _interval: string, _cb: (klines: AsterKline[]) => void): void {} watchKlines(_symbol: string, _interval: string, _cb: (klines: Kline[]) => void): void {}
async createOrder(params: CreateOrderParams): Promise<AsterOrder> { async createOrder(params: CreateOrderParams): Promise<Order> {
this.lastCreateOrderParams = params; this.lastCreateOrderParams = params;
return { return {
orderId: 1, orderId: 1,
+22 -22
View File
@@ -1,10 +1,10 @@
import { describe, expect, it } from "vitest"; import { describe, expect, it } from "vitest";
import type { ExchangeAdapter } from "../src/exchanges/adapter"; import type { ExchangeAdapter } from "../src/exchanges/adapter";
import type { import type {
AsterAccountSnapshot, AccountSnapshot,
AsterDepth, Depth,
AsterOrder, Order,
AsterTicker, Ticker,
CreateOrderParams, CreateOrderParams,
} from "../src/exchanges/types"; } from "../src/exchanges/types";
import type { GridConfig } from "../src/config"; import type { GridConfig } from "../src/config";
@@ -13,11 +13,11 @@ import { GridEngine } from "../src/strategy/grid-engine";
class StubAdapter implements ExchangeAdapter { class StubAdapter implements ExchangeAdapter {
id = "aster"; id = "aster";
private accountHandler: ((snapshot: AsterAccountSnapshot) => void) | null = null; private accountHandler: ((snapshot: AccountSnapshot) => void) | null = null;
private orderHandler: ((orders: AsterOrder[]) => void) | null = null; private orderHandler: ((orders: Order[]) => void) | null = null;
private depthHandler: ((depth: AsterDepth) => void) | null = null; private depthHandler: ((depth: Depth) => void) | null = null;
private tickerHandler: ((ticker: AsterTicker) => void) | null = null; private tickerHandler: ((ticker: Ticker) => void) | null = null;
private currentOrders: AsterOrder[] = []; private currentOrders: Order[] = [];
public createdOrders: CreateOrderParams[] = []; public createdOrders: CreateOrderParams[] = [];
public marketOrders: CreateOrderParams[] = []; public marketOrders: CreateOrderParams[] = [];
@@ -28,19 +28,19 @@ class StubAdapter implements ExchangeAdapter {
return false; return false;
} }
watchAccount(cb: (snapshot: AsterAccountSnapshot) => void): void { watchAccount(cb: (snapshot: AccountSnapshot) => void): void {
this.accountHandler = cb; this.accountHandler = cb;
} }
watchOrders(cb: (orders: AsterOrder[]) => void): void { watchOrders(cb: (orders: Order[]) => void): void {
this.orderHandler = cb; this.orderHandler = cb;
} }
watchDepth(_symbol: string, cb: (depth: AsterDepth) => void): void { watchDepth(_symbol: string, cb: (depth: Depth) => void): void {
this.depthHandler = cb; this.depthHandler = cb;
} }
watchTicker(_symbol: string, cb: (ticker: AsterTicker) => void): void { watchTicker(_symbol: string, cb: (ticker: Ticker) => void): void {
this.tickerHandler = cb; this.tickerHandler = cb;
} }
@@ -48,24 +48,24 @@ class StubAdapter implements ExchangeAdapter {
// not used in tests // not used in tests
} }
emitAccount(snapshot: AsterAccountSnapshot): void { emitAccount(snapshot: AccountSnapshot): void {
this.accountHandler?.(snapshot); this.accountHandler?.(snapshot);
} }
emitOrders(orders: AsterOrder[]): void { emitOrders(orders: Order[]): void {
this.orderHandler?.(orders); this.orderHandler?.(orders);
} }
emitDepth(depth: AsterDepth): void { emitDepth(depth: Depth): void {
this.depthHandler?.(depth); this.depthHandler?.(depth);
} }
emitTicker(ticker: AsterTicker): void { emitTicker(ticker: Ticker): void {
this.tickerHandler?.(ticker); this.tickerHandler?.(ticker);
} }
async createOrder(params: CreateOrderParams): Promise<AsterOrder> { async createOrder(params: CreateOrderParams): Promise<Order> {
const order: AsterOrder = { const order: Order = {
orderId: `${Date.now()}-${Math.random()}`, orderId: `${Date.now()}-${Math.random()}`,
clientOrderId: "test", clientOrderId: "test",
symbol: params.symbol, symbol: params.symbol,
@@ -107,7 +107,7 @@ class StubAdapter implements ExchangeAdapter {
} }
} }
function createAccountSnapshot(symbol: string, positionAmt: number): AsterAccountSnapshot { function createAccountSnapshot(symbol: string, positionAmt: number): AccountSnapshot {
return { return {
canTrade: true, canTrade: true,
canDeposit: true, canDeposit: true,
@@ -126,7 +126,7 @@ function createAccountSnapshot(symbol: string, positionAmt: number): AsterAccoun
}, },
], ],
assets: [], assets: [],
} as unknown as AsterAccountSnapshot; } as unknown as AccountSnapshot;
} }
describe("GridEngine", () => { describe("GridEngine", () => {
@@ -296,7 +296,7 @@ describe("GridEngine", () => {
adapter.emitAccount(createAccountSnapshot(baseConfig.symbol, baseConfig.orderSize * 2)); adapter.emitAccount(createAccountSnapshot(baseConfig.symbol, baseConfig.orderSize * 2));
const reduceOrder: AsterOrder = { const reduceOrder: Order = {
orderId: "existing-reduce", orderId: "existing-reduce",
clientOrderId: "existing-reduce", clientOrderId: "existing-reduce",
symbol: baseConfig.symbol, symbol: baseConfig.symbol,
@@ -1,6 +1,6 @@
import { describe, expect, it } from "vitest"; import { describe, expect, it } from "vitest";
import type { ExchangeAdapter } from "../src/exchanges/adapter"; import type { ExchangeAdapter } from "../src/exchanges/adapter";
import type { AsterAccountSnapshot, AsterDepth, AsterKline, AsterOrder, AsterTicker } from "../src/exchanges/types"; import type { AccountSnapshot, Depth, Kline, Order, Ticker } from "../src/exchanges/types";
import { t } from "../src/i18n"; import { t } from "../src/i18n";
import { MakerPointsEngine } from "../src/strategy/maker-points-engine"; import { MakerPointsEngine } from "../src/strategy/maker-points-engine";
@@ -11,13 +11,13 @@ class StubAdapter implements ExchangeAdapter {
return false; return false;
} }
watchAccount(_cb: (snapshot: AsterAccountSnapshot) => void): void {} watchAccount(_cb: (snapshot: AccountSnapshot) => void): void {}
watchOrders(_cb: (orders: AsterOrder[]) => void): void {} watchOrders(_cb: (orders: Order[]) => void): void {}
watchDepth(_symbol: string, _cb: (depth: AsterDepth) => void): void {} watchDepth(_symbol: string, _cb: (depth: Depth) => void): void {}
watchTicker(_symbol: string, _cb: (ticker: AsterTicker) => void): void {} watchTicker(_symbol: string, _cb: (ticker: Ticker) => void): void {}
watchKlines(_symbol: string, _interval: string, _cb: (klines: AsterKline[]) => void): void {} watchKlines(_symbol: string, _interval: string, _cb: (klines: Kline[]) => void): void {}
async createOrder(): Promise<AsterOrder> { async createOrder(): Promise<Order> {
throw new Error("not implemented"); throw new Error("not implemented");
} }
@@ -1,23 +1,23 @@
import { afterEach, describe, expect, it, vi } from "vitest"; import { afterEach, describe, expect, it, vi } from "vitest";
import type { ExchangeAdapter } from "../src/exchanges/adapter"; import type { ExchangeAdapter } from "../src/exchanges/adapter";
import type { AsterAccountSnapshot, AsterDepth, AsterKline, AsterOrder, AsterTicker } from "../src/exchanges/types"; import type { AccountSnapshot, Depth, Kline, Order, Ticker } from "../src/exchanges/types";
import { MakerPointsEngine } from "../src/strategy/maker-points-engine"; import { MakerPointsEngine } from "../src/strategy/maker-points-engine";
class StubAdapter implements ExchangeAdapter { class StubAdapter implements ExchangeAdapter {
id = "standx"; id = "standx";
accountSnapshot: AsterAccountSnapshot | null = null; accountSnapshot: AccountSnapshot | null = null;
supportsTrailingStops(): boolean { supportsTrailingStops(): boolean {
return false; return false;
} }
watchAccount(_cb: (snapshot: AsterAccountSnapshot) => void): void {} watchAccount(_cb: (snapshot: AccountSnapshot) => void): void {}
watchOrders(_cb: (orders: AsterOrder[]) => void): void {} watchOrders(_cb: (orders: Order[]) => void): void {}
watchDepth(_symbol: string, _cb: (depth: AsterDepth) => void): void {} watchDepth(_symbol: string, _cb: (depth: Depth) => void): void {}
watchTicker(_symbol: string, _cb: (ticker: AsterTicker) => void): void {} watchTicker(_symbol: string, _cb: (ticker: Ticker) => void): void {}
watchKlines(_symbol: string, _interval: string, _cb: (klines: AsterKline[]) => void): void {} watchKlines(_symbol: string, _interval: string, _cb: (klines: Kline[]) => void): void {}
async createOrder(): Promise<AsterOrder> { async createOrder(): Promise<Order> {
throw new Error("not implemented"); throw new Error("not implemented");
} }
@@ -25,7 +25,7 @@ class StubAdapter implements ExchangeAdapter {
async cancelOrders(): Promise<void> {} async cancelOrders(): Promise<void> {}
async cancelAllOrders(): Promise<void> {} async cancelAllOrders(): Promise<void> {}
async queryAccountSnapshot(): Promise<AsterAccountSnapshot | null> { async queryAccountSnapshot(): Promise<AccountSnapshot | null> {
return this.accountSnapshot; return this.accountSnapshot;
} }
} }
+11 -11
View File
@@ -1,25 +1,25 @@
import { afterEach, describe, expect, it, vi } from "vitest"; import { afterEach, describe, expect, it, vi } from "vitest";
import type { ExchangeAdapter } from "../src/exchanges/adapter"; import type { ExchangeAdapter } from "../src/exchanges/adapter";
import type { AsterAccountSnapshot, AsterDepth, AsterKline, AsterOrder, AsterTicker } from "../src/exchanges/types"; import type { AccountSnapshot, Depth, Kline, Order, Ticker } from "../src/exchanges/types";
import { MakerPointsEngine } from "../src/strategy/maker-points-engine"; import { MakerPointsEngine } from "../src/strategy/maker-points-engine";
class StubAdapter implements ExchangeAdapter { class StubAdapter implements ExchangeAdapter {
id = "standx"; id = "standx";
cancelAllCount = 0; cancelAllCount = 0;
openOrders: AsterOrder[] | Error = []; openOrders: Order[] | Error = [];
accountSnapshot: AsterAccountSnapshot | null = null; accountSnapshot: AccountSnapshot | null = null;
supportsTrailingStops(): boolean { supportsTrailingStops(): boolean {
return false; return false;
} }
watchAccount(_cb: (snapshot: AsterAccountSnapshot) => void): void {} watchAccount(_cb: (snapshot: AccountSnapshot) => void): void {}
watchOrders(_cb: (orders: AsterOrder[]) => void): void {} watchOrders(_cb: (orders: Order[]) => void): void {}
watchDepth(_symbol: string, _cb: (depth: AsterDepth) => void): void {} watchDepth(_symbol: string, _cb: (depth: Depth) => void): void {}
watchTicker(_symbol: string, _cb: (ticker: AsterTicker) => void): void {} watchTicker(_symbol: string, _cb: (ticker: Ticker) => void): void {}
watchKlines(_symbol: string, _interval: string, _cb: (klines: AsterKline[]) => void): void {} watchKlines(_symbol: string, _interval: string, _cb: (klines: Kline[]) => void): void {}
async createOrder(): Promise<AsterOrder> { async createOrder(): Promise<Order> {
throw new Error("not implemented"); throw new Error("not implemented");
} }
@@ -31,12 +31,12 @@ class StubAdapter implements ExchangeAdapter {
this.openOrders = []; this.openOrders = [];
} }
async queryOpenOrders(): Promise<AsterOrder[]> { async queryOpenOrders(): Promise<Order[]> {
if (this.openOrders instanceof Error) throw this.openOrders; if (this.openOrders instanceof Error) throw this.openOrders;
return this.openOrders; return this.openOrders;
} }
async queryAccountSnapshot(): Promise<AsterAccountSnapshot | null> { async queryAccountSnapshot(): Promise<AccountSnapshot | null> {
return this.accountSnapshot; return this.accountSnapshot;
} }
} }
@@ -1,33 +1,33 @@
import { afterEach, describe, expect, it, vi } from "vitest"; import { afterEach, describe, expect, it, vi } from "vitest";
import type { ExchangeAdapter } from "../src/exchanges/adapter"; import type { ExchangeAdapter } from "../src/exchanges/adapter";
import type { AsterAccountSnapshot, AsterDepth, AsterKline, AsterOrder, AsterTicker } from "../src/exchanges/types"; import type { AccountSnapshot, Depth, Kline, Order, Ticker } from "../src/exchanges/types";
import { MakerPointsEngine } from "../src/strategy/maker-points-engine"; import { MakerPointsEngine } from "../src/strategy/maker-points-engine";
class StubAdapter implements ExchangeAdapter { class StubAdapter implements ExchangeAdapter {
id = "standx"; id = "standx";
private depthListeners: Array<(depth: AsterDepth) => void> = []; private depthListeners: Array<(depth: Depth) => void> = [];
supportsTrailingStops(): boolean { supportsTrailingStops(): boolean {
return false; return false;
} }
watchAccount(_cb: (snapshot: AsterAccountSnapshot) => void): void {} watchAccount(_cb: (snapshot: AccountSnapshot) => void): void {}
watchOrders(_cb: (orders: AsterOrder[]) => void): void {} watchOrders(_cb: (orders: Order[]) => void): void {}
watchTicker(_symbol: string, _cb: (ticker: AsterTicker) => void): void {} watchTicker(_symbol: string, _cb: (ticker: Ticker) => void): void {}
watchKlines(_symbol: string, _interval: string, _cb: (klines: AsterKline[]) => void): void {} watchKlines(_symbol: string, _interval: string, _cb: (klines: Kline[]) => void): void {}
watchDepth(_symbol: string, cb: (depth: AsterDepth) => void): void { watchDepth(_symbol: string, cb: (depth: Depth) => void): void {
this.depthListeners.push(cb); this.depthListeners.push(cb);
} }
emitDepth(depth: AsterDepth): void { emitDepth(depth: Depth): void {
for (const listener of this.depthListeners) { for (const listener of this.depthListeners) {
listener(depth); listener(depth);
} }
} }
async createOrder(): Promise<AsterOrder> { async createOrder(): Promise<Order> {
throw new Error("not implemented"); throw new Error("not implemented");
} }
@@ -35,7 +35,7 @@ class StubAdapter implements ExchangeAdapter {
async cancelOrders(): Promise<void> {} async cancelOrders(): Promise<void> {}
async cancelAllOrders(): Promise<void> {} async cancelAllOrders(): Promise<void> {}
async queryAccountSnapshot(): Promise<AsterAccountSnapshot | null> { async queryAccountSnapshot(): Promise<AccountSnapshot | null> {
return null; return null;
} }
} }
+10 -10
View File
@@ -1,33 +1,33 @@
import { afterEach, describe, expect, it, vi } from "vitest"; import { afterEach, describe, expect, it, vi } from "vitest";
import type { ExchangeAdapter } from "../src/exchanges/adapter"; import type { ExchangeAdapter } from "../src/exchanges/adapter";
import type { AsterAccountSnapshot, AsterDepth, AsterKline, AsterOrder, AsterTicker } from "../src/exchanges/types"; import type { AccountSnapshot, Depth, Kline, Order, Ticker } from "../src/exchanges/types";
import { MakerPointsEngine } from "../src/strategy/maker-points-engine"; import { MakerPointsEngine } from "../src/strategy/maker-points-engine";
class StubAdapter implements ExchangeAdapter { class StubAdapter implements ExchangeAdapter {
id = "standx"; id = "standx";
private depthListeners: Array<(depth: AsterDepth) => void> = []; private depthListeners: Array<(depth: Depth) => void> = [];
supportsTrailingStops(): boolean { supportsTrailingStops(): boolean {
return false; return false;
} }
watchAccount(_cb: (snapshot: AsterAccountSnapshot) => void): void {} watchAccount(_cb: (snapshot: AccountSnapshot) => void): void {}
watchOrders(_cb: (orders: AsterOrder[]) => void): void {} watchOrders(_cb: (orders: Order[]) => void): void {}
watchTicker(_symbol: string, _cb: (ticker: AsterTicker) => void): void {} watchTicker(_symbol: string, _cb: (ticker: Ticker) => void): void {}
watchKlines(_symbol: string, _interval: string, _cb: (klines: AsterKline[]) => void): void {} watchKlines(_symbol: string, _interval: string, _cb: (klines: Kline[]) => void): void {}
watchDepth(_symbol: string, cb: (depth: AsterDepth) => void): void { watchDepth(_symbol: string, cb: (depth: Depth) => void): void {
this.depthListeners.push(cb); this.depthListeners.push(cb);
} }
emitDepth(depth: AsterDepth): void { emitDepth(depth: Depth): void {
for (const listener of this.depthListeners) { for (const listener of this.depthListeners) {
listener(depth); listener(depth);
} }
} }
async createOrder(): Promise<AsterOrder> { async createOrder(): Promise<Order> {
throw new Error("not implemented"); throw new Error("not implemented");
} }
@@ -35,7 +35,7 @@ class StubAdapter implements ExchangeAdapter {
async cancelOrders(): Promise<void> {} async cancelOrders(): Promise<void> {}
async cancelAllOrders(): Promise<void> {} async cancelAllOrders(): Promise<void> {}
async queryAccountSnapshot(): Promise<AsterAccountSnapshot | null> { async queryAccountSnapshot(): Promise<AccountSnapshot | null> {
return null; return null;
} }
} }
+12 -12
View File
@@ -1,6 +1,6 @@
import { afterEach, describe, expect, it, vi } from "vitest"; import { afterEach, describe, expect, it, vi } from "vitest";
import type { ExchangeAdapter } from "../src/exchanges/adapter"; import type { ExchangeAdapter } from "../src/exchanges/adapter";
import type { AsterAccountSnapshot, AsterDepth, AsterKline, AsterOrder, AsterTicker } from "../src/exchanges/types"; import type { AccountSnapshot, Depth, Kline, Order, Ticker } from "../src/exchanges/types";
import { MakerPointsEngine } from "../src/strategy/maker-points-engine"; import { MakerPointsEngine } from "../src/strategy/maker-points-engine";
class StandxStubAdapter implements ExchangeAdapter { class StandxStubAdapter implements ExchangeAdapter {
@@ -12,20 +12,20 @@ class StandxStubAdapter implements ExchangeAdapter {
return false; return false;
} }
watchAccount(_cb: (snapshot: AsterAccountSnapshot) => void): void {} watchAccount(_cb: (snapshot: AccountSnapshot) => void): void {}
watchOrders(_cb: (orders: AsterOrder[]) => void): void {} watchOrders(_cb: (orders: Order[]) => void): void {}
watchDepth(_symbol: string, _cb: (depth: AsterDepth) => void): void {} watchDepth(_symbol: string, _cb: (depth: Depth) => void): void {}
watchTicker(_symbol: string, _cb: (ticker: AsterTicker) => void): void {} watchTicker(_symbol: string, _cb: (ticker: Ticker) => void): void {}
watchKlines(_symbol: string, _interval: string, _cb: (klines: AsterKline[]) => void): void {} watchKlines(_symbol: string, _interval: string, _cb: (klines: Kline[]) => void): void {}
async createOrder(): Promise<AsterOrder> { async createOrder(): Promise<Order> {
throw new Error("not implemented"); throw new Error("not implemented");
} }
async cancelOrder(): Promise<void> {} async cancelOrder(): Promise<void> {}
async cancelOrders(): Promise<void> {} async cancelOrders(): Promise<void> {}
async cancelAllOrders(): Promise<void> {} async cancelAllOrders(): Promise<void> {}
async queryAccountSnapshot(): Promise<AsterAccountSnapshot | null> { async queryAccountSnapshot(): Promise<AccountSnapshot | null> {
return { return {
canTrade: true, canTrade: true,
canDeposit: true, canDeposit: true,
@@ -98,7 +98,7 @@ describe("MakerPointsEngine StandX isolated margin guard", () => {
asks: [["101", "1"]], asks: [["101", "1"]],
eventTime: Date.now(), eventTime: Date.now(),
symbol: "BTC-USD", symbol: "BTC-USD",
} as AsterDepth; } as Depth;
(engine as any).tickerSnapshot = { (engine as any).tickerSnapshot = {
symbol: "BTC-USD", symbol: "BTC-USD",
lastPrice: "100", lastPrice: "100",
@@ -108,7 +108,7 @@ describe("MakerPointsEngine StandX isolated margin guard", () => {
volume: "0", volume: "0",
quoteVolume: "0", quoteVolume: "0",
eventTime: Date.now(), eventTime: Date.now(),
} as AsterTicker; } as Ticker;
const syncSpy = vi.fn().mockResolvedValue(undefined); const syncSpy = vi.fn().mockResolvedValue(undefined);
(engine as any).syncOrders = syncSpy; (engine as any).syncOrders = syncSpy;
@@ -162,7 +162,7 @@ describe("MakerPointsEngine StandX isolated margin guard", () => {
asks: [["101", "1"]], asks: [["101", "1"]],
eventTime: Date.now(), eventTime: Date.now(),
symbol: "BTC-USD", symbol: "BTC-USD",
} as AsterDepth; } as Depth;
(engine as any).tickerSnapshot = { (engine as any).tickerSnapshot = {
symbol: "BTC-USD", symbol: "BTC-USD",
lastPrice: "100", lastPrice: "100",
@@ -172,7 +172,7 @@ describe("MakerPointsEngine StandX isolated margin guard", () => {
volume: "0", volume: "0",
quoteVolume: "0", quoteVolume: "0",
eventTime: Date.now(), eventTime: Date.now(),
} as AsterTicker; } as Ticker;
const syncSpy = vi.fn().mockResolvedValue(undefined); const syncSpy = vi.fn().mockResolvedValue(undefined);
(engine as any).syncOrders = syncSpy; (engine as any).syncOrders = syncSpy;
+3 -3
View File
@@ -1,6 +1,6 @@
import { describe, expect, it, vi, beforeEach, afterAll } from "vitest"; import { describe, expect, it, vi, beforeEach, afterAll } from "vitest";
import type { ExchangeAdapter } from "../src/exchanges/adapter"; import type { ExchangeAdapter } from "../src/exchanges/adapter";
import type { AsterOrder } from "../src/exchanges/types"; import type { Order } from "../src/exchanges/types";
import type { OrderLockMap, OrderTimerMap, OrderPendingMap } from "../src/core/order-coordinator"; import type { OrderLockMap, OrderTimerMap, OrderPendingMap } from "../src/core/order-coordinator";
import { import {
deduplicateOrders, deduplicateOrders,
@@ -15,7 +15,7 @@ import {
const originalTradeExchange = process.env.TRADE_EXCHANGE; const originalTradeExchange = process.env.TRADE_EXCHANGE;
const originalExchange = process.env.EXCHANGE; const originalExchange = process.env.EXCHANGE;
const baseOrder: AsterOrder = { const baseOrder: Order = {
orderId: 1, orderId: 1,
clientOrderId: "client", clientOrderId: "client",
symbol: "BTCUSDT", symbol: "BTCUSDT",
@@ -66,7 +66,7 @@ describe("order-coordinator", () => {
const timers: OrderTimerMap = {}; const timers: OrderTimerMap = {};
const pending: OrderPendingMap = {}; const pending: OrderPendingMap = {};
const log = vi.fn(); const log = vi.fn();
const openOrders: AsterOrder[] = [ const openOrders: Order[] = [
{ ...baseOrder, orderId: 1 }, { ...baseOrder, orderId: 1 },
{ ...baseOrder, orderId: 2 }, { ...baseOrder, orderId: 2 },
]; ];
+3 -3
View File
@@ -1,10 +1,10 @@
import { describe, expect, it } from "vitest"; import { describe, expect, it } from "vitest";
import { getDepthBetweenPrices } from "../src/utils/price"; import { getDepthBetweenPrices } from "../src/utils/price";
import type { AsterDepth } from "../src/exchanges/types"; import type { Depth } from "../src/exchanges/types";
describe("getDepthBetweenPrices boundary", () => { describe("getDepthBetweenPrices boundary", () => {
it("SELL side excludes quantity exactly at target price", () => { it("SELL side excludes quantity exactly at target price", () => {
const depth: AsterDepth = { const depth: Depth = {
lastUpdateId: 1, lastUpdateId: 1,
bids: [], bids: [],
asks: [ asks: [
@@ -20,7 +20,7 @@ describe("getDepthBetweenPrices boundary", () => {
}); });
it("BUY side excludes quantity exactly at target price", () => { it("BUY side excludes quantity exactly at target price", () => {
const depth: AsterDepth = { const depth: Depth = {
lastUpdateId: 1, lastUpdateId: 1,
bids: [ bids: [
["69355", "1"], ["69355", "1"],
+3 -3
View File
@@ -1,8 +1,8 @@
import { describe, expect, it } from "vitest"; import { describe, expect, it } from "vitest";
import { computeBollingerBandwidth, getPosition, getSMA } from "../src/utils/strategy"; import { computeBollingerBandwidth, getPosition, getSMA } from "../src/utils/strategy";
import type { AsterAccountSnapshot, AsterKline } from "../src/exchanges/types"; import type { AccountSnapshot, Kline } from "../src/exchanges/types";
const mockSnapshot = (positions: Array<{ symbol: string; amt: number; entry: number; pnl: number }> = []): AsterAccountSnapshot => ({ const mockSnapshot = (positions: Array<{ symbol: string; amt: number; entry: number; pnl: number }> = []): AccountSnapshot => ({
canTrade: true, canTrade: true,
canDeposit: true, canDeposit: true,
canWithdraw: true, canWithdraw: true,
@@ -20,7 +20,7 @@ const mockSnapshot = (positions: Array<{ symbol: string; amt: number; entry: num
assets: [], assets: [],
}); });
const mockKlines = (values: number[]): AsterKline[] => const mockKlines = (values: number[]): Kline[] =>
values.map((value, index) => ({ values.map((value, index) => ({
openTime: index, openTime: index,
open: String(value), open: String(value),