feat: enhance toAccountSnapshot function with improved market ID and symbol matching logic, and add corresponding unit tests

This commit is contained in:
discountry
2025-11-08 17:14:01 +08:00
parent 274e2f3d75
commit e94cf1bda2
2 changed files with 92 additions and 9 deletions
+36 -9
View File
@@ -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);
}
+56
View File
@@ -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" });
});
});