mirror of
https://github.com/discountry/ritmex-bot.git
synced 2026-09-09 08:18:07 +00:00
feat: enhance toAccountSnapshot function with improved market ID and symbol matching logic, and add corresponding unit tests
This commit is contained in:
@@ -144,16 +144,23 @@ export function toAccountSnapshot(
|
||||
assets: AsterAccountAsset[] = [],
|
||||
options?: { marketSymbol?: string | null; marketId?: number | null }
|
||||
): AsterAccountSnapshot {
|
||||
const targetSymbol = options?.marketSymbol?.toUpperCase();
|
||||
const targetMarketId = options?.marketId;
|
||||
const targetSymbol = options?.marketSymbol ?? null;
|
||||
const targetMarketId =
|
||||
options?.marketId != null && Number.isFinite(Number(options.marketId))
|
||||
? Number(options.marketId)
|
||||
: null;
|
||||
const filteredPositions = positions.filter((position) => {
|
||||
const marketMatches =
|
||||
targetMarketId == null ||
|
||||
(Number.isFinite(Number(position.market_id)) && Number(position.market_id) === Number(targetMarketId));
|
||||
const symbolMatches =
|
||||
!targetSymbol ||
|
||||
(typeof position.symbol === "string" && position.symbol.toUpperCase() === targetSymbol);
|
||||
return marketMatches && symbolMatches;
|
||||
if (targetMarketId != null) {
|
||||
const positionMarketId = Number(position.market_id);
|
||||
if (Number.isFinite(positionMarketId)) {
|
||||
return positionMarketId === targetMarketId;
|
||||
}
|
||||
return targetSymbol ? symbolsMatch(position.symbol, targetSymbol) : false;
|
||||
}
|
||||
if (targetSymbol) {
|
||||
return symbolsMatch(position.symbol, targetSymbol);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
const transformedPositions = filteredPositions.map((position) => lighterPositionToAster(symbol, position));
|
||||
const aggregateUnrealized = transformedPositions.reduce((acc, pos) => acc + Number(pos.unrealizedProfit ?? 0), 0);
|
||||
@@ -201,3 +208,23 @@ function lighterPositionToAster(symbol: string, position: LighterPosition): Aste
|
||||
markPrice: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
function symbolsMatch(source: string | null | undefined, target: string | null | undefined): boolean {
|
||||
if (!source || !target) return false;
|
||||
const sourceForms = normalizeSymbolForms(source);
|
||||
const targetForms = normalizeSymbolForms(target);
|
||||
if (!sourceForms.length || !targetForms.length) return false;
|
||||
return sourceForms.some((value) => targetForms.includes(value));
|
||||
}
|
||||
|
||||
function normalizeSymbolForms(value: string): string[] {
|
||||
const upper = value.toUpperCase();
|
||||
const sanitized = upper.replace(/[^A-Z0-9]/g, "");
|
||||
const parts = upper.split(/[-:/]/).filter(Boolean);
|
||||
const base = parts.length ? parts[0] : "";
|
||||
const forms = new Set<string>();
|
||||
if (upper) forms.add(upper);
|
||||
if (sanitized) forms.add(sanitized);
|
||||
if (base) forms.add(base);
|
||||
return Array.from(forms);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { toAccountSnapshot } from "../../src/exchanges/lighter/mappers";
|
||||
import type { LighterAccountDetails, LighterPosition } from "../../src/exchanges/lighter/types";
|
||||
|
||||
const baseDetails: LighterAccountDetails = {
|
||||
account_index: 1,
|
||||
collateral: "1000",
|
||||
};
|
||||
|
||||
function createPosition(overrides: Partial<LighterPosition> = {}): LighterPosition {
|
||||
return {
|
||||
market_id: 101,
|
||||
symbol: "BTC/USDC:USDC",
|
||||
sign: 1,
|
||||
position: "0.5",
|
||||
avg_entry_price: "100",
|
||||
position_value: "50",
|
||||
unrealized_pnl: "0",
|
||||
realized_pnl: "0",
|
||||
...overrides,
|
||||
} as LighterPosition;
|
||||
}
|
||||
|
||||
describe("toAccountSnapshot", () => {
|
||||
it("includes positions that match the configured market id regardless of reported symbol format", () => {
|
||||
const snapshot = toAccountSnapshot(
|
||||
"BTC",
|
||||
baseDetails,
|
||||
[
|
||||
createPosition({ market_id: 101, symbol: "BTC/USDC:USDC" }),
|
||||
createPosition({ market_id: 202, symbol: "ETH/USDC:USDC", sign: -1 }),
|
||||
],
|
||||
[],
|
||||
{ marketSymbol: "BTC", marketId: 101 }
|
||||
);
|
||||
|
||||
expect(snapshot.positions).toHaveLength(1);
|
||||
expect(snapshot.positions[0]).toMatchObject({ symbol: "BTC", positionAmt: "0.5" });
|
||||
});
|
||||
|
||||
it("falls back to fuzzy symbol matching when market id is unavailable", () => {
|
||||
const snapshot = toAccountSnapshot(
|
||||
"BTC",
|
||||
baseDetails,
|
||||
[
|
||||
createPosition({ market_id: Number.NaN as number, symbol: "BTC/USDC:USDC" }),
|
||||
createPosition({ market_id: Number.NaN as number, symbol: "ETH/USDC:USDC" }),
|
||||
],
|
||||
[],
|
||||
{ marketSymbol: "BTC" }
|
||||
);
|
||||
|
||||
expect(snapshot.positions).toHaveLength(1);
|
||||
expect(snapshot.positions[0]).toMatchObject({ symbol: "BTC", positionAmt: "0.5" });
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user