mirror of
https://github.com/discountry/ritmex-bot.git
synced 2026-09-11 09:18:08 +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[] = [],
|
assets: AsterAccountAsset[] = [],
|
||||||
options?: { marketSymbol?: string | null; marketId?: number | null }
|
options?: { marketSymbol?: string | null; marketId?: number | null }
|
||||||
): AsterAccountSnapshot {
|
): AsterAccountSnapshot {
|
||||||
const targetSymbol = options?.marketSymbol?.toUpperCase();
|
const targetSymbol = options?.marketSymbol ?? null;
|
||||||
const targetMarketId = options?.marketId;
|
const targetMarketId =
|
||||||
|
options?.marketId != null && Number.isFinite(Number(options.marketId))
|
||||||
|
? Number(options.marketId)
|
||||||
|
: null;
|
||||||
const filteredPositions = positions.filter((position) => {
|
const filteredPositions = positions.filter((position) => {
|
||||||
const marketMatches =
|
if (targetMarketId != null) {
|
||||||
targetMarketId == null ||
|
const positionMarketId = Number(position.market_id);
|
||||||
(Number.isFinite(Number(position.market_id)) && Number(position.market_id) === Number(targetMarketId));
|
if (Number.isFinite(positionMarketId)) {
|
||||||
const symbolMatches =
|
return positionMarketId === targetMarketId;
|
||||||
!targetSymbol ||
|
}
|
||||||
(typeof position.symbol === "string" && position.symbol.toUpperCase() === targetSymbol);
|
return targetSymbol ? symbolsMatch(position.symbol, targetSymbol) : false;
|
||||||
return marketMatches && symbolMatches;
|
}
|
||||||
|
if (targetSymbol) {
|
||||||
|
return symbolsMatch(position.symbol, targetSymbol);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
});
|
});
|
||||||
const transformedPositions = filteredPositions.map((position) => lighterPositionToAster(symbol, position));
|
const transformedPositions = filteredPositions.map((position) => lighterPositionToAster(symbol, position));
|
||||||
const aggregateUnrealized = transformedPositions.reduce((acc, pos) => acc + Number(pos.unrealizedProfit ?? 0), 0);
|
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,
|
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