mirror of
https://github.com/discountry/ritmex-bot.git
synced 2026-09-10 16:58:08 +00:00
Enhance environment variable parsing and account snapshot validation
- Introduced `normalizeEnvValue` function to improve handling of environment variable values, including trimming, unquoting, and stripping inline comments. - Updated `resolveSymbolFromEnv` and parsing functions to utilize the new normalization logic. - Added `validateAccountSnapshotForSymbol` function to validate account snapshots, ensuring numeric fields are correctly formatted and flagging any issues. - Implemented tests for environment variable parsing and account snapshot validation to ensure robustness and correctness.
This commit is contained in:
@@ -13,4 +13,24 @@ export function computePositionPnl(
|
||||
: (position.entryPrice - (priceForPnl as number)) * absAmt;
|
||||
}
|
||||
|
||||
export function computeStopLossPnl(
|
||||
position: PositionSnapshot,
|
||||
bestBid?: number | null,
|
||||
bestAsk?: number | null
|
||||
): number | null {
|
||||
const absAmt = Math.abs(position.positionAmt);
|
||||
if (!Number.isFinite(absAmt) || absAmt <= 0) return 0;
|
||||
|
||||
// If entry price is missing, prefer the exchange-provided unrealized PnL.
|
||||
if (!Number.isFinite(position.entryPrice) || position.entryPrice <= 0) {
|
||||
return Number.isFinite(position.unrealizedProfit) ? position.unrealizedProfit : null;
|
||||
}
|
||||
|
||||
const priceForPnl = position.positionAmt > 0 ? bestBid : bestAsk;
|
||||
if (!Number.isFinite(priceForPnl as number) || (priceForPnl as number) <= 0) {
|
||||
return Number.isFinite(position.unrealizedProfit) ? position.unrealizedProfit : null;
|
||||
}
|
||||
|
||||
return computePositionPnl(position, bestBid, bestAsk);
|
||||
}
|
||||
|
||||
|
||||
@@ -47,6 +47,47 @@ export function getPosition(snapshot: AsterAccountSnapshot | null, symbol: strin
|
||||
};
|
||||
}
|
||||
|
||||
export function validateAccountSnapshotForSymbol(
|
||||
snapshot: AsterAccountSnapshot | null,
|
||||
symbol: string
|
||||
): { ok: true } | { ok: false; issues: string[] } {
|
||||
if (!snapshot) return { ok: true };
|
||||
const positions = snapshot.positions?.filter((p) => p.symbol === symbol) ?? [];
|
||||
if (positions.length === 0) return { ok: true };
|
||||
|
||||
const NON_ZERO_EPS = 1e-8;
|
||||
const issues: string[] = [];
|
||||
|
||||
for (const position of positions) {
|
||||
const amt = Number(position.positionAmt);
|
||||
if (!Number.isFinite(amt)) {
|
||||
issues.push("invalid_positionAmt");
|
||||
continue;
|
||||
}
|
||||
if (Math.abs(amt) <= NON_ZERO_EPS) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const entryPrice = Number(position.entryPrice);
|
||||
if (!Number.isFinite(entryPrice) || entryPrice <= 0) {
|
||||
issues.push("invalid_entryPrice");
|
||||
}
|
||||
|
||||
const unrealizedProfit = Number(position.unrealizedProfit);
|
||||
if (!Number.isFinite(unrealizedProfit)) {
|
||||
issues.push("invalid_unrealizedProfit");
|
||||
}
|
||||
|
||||
const rawMark = Number(position.markPrice);
|
||||
if (position.markPrice != null && position.markPrice !== "" && (!Number.isFinite(rawMark) || rawMark <= 0)) {
|
||||
issues.push("invalid_markPrice");
|
||||
}
|
||||
}
|
||||
|
||||
if (issues.length === 0) return { ok: true };
|
||||
return { ok: false, issues: Array.from(new Set(issues)) };
|
||||
}
|
||||
|
||||
export function getSMA(values: AsterKline[], length: number): number | null {
|
||||
if (!Array.isArray(values) || values.length < length) return null;
|
||||
const window = values.slice(-length);
|
||||
|
||||
Reference in New Issue
Block a user