From f4af8dfec11894e960babfd787d4b29e9ab74e02 Mon Sep 17 00:00:00 2001 From: UjmaIT <115736997+UjmaIT@users.noreply.github.com> Date: Mon, 14 Apr 2025 19:25:59 +0200 Subject: [PATCH] feat(): added direction for leverage and wallet storage --- src/AccountStateStore.ts | 121 ++++++++++++++++++++++++++++++++++----- src/lib/misc/mappings.ts | 51 +++++++++++++++++ src/lib/types/wallet.ts | 67 ++++++++++++++++++++++ 3 files changed, 225 insertions(+), 14 deletions(-) create mode 100644 src/lib/misc/mappings.ts create mode 100644 src/lib/types/wallet.ts diff --git a/src/AccountStateStore.ts b/src/AccountStateStore.ts index d763245..5b366a3 100644 --- a/src/AccountStateStore.ts +++ b/src/AccountStateStore.ts @@ -4,6 +4,7 @@ import { EnginePositionSide, EngineSimplePosition, } from './lib/types/position.js'; +import { AbstractAssetBalance } from './lib/types/wallet.js'; import { getUnrealisedPNL } from './util/math.js'; @@ -23,8 +24,8 @@ export class AccountStateStore< > { private isPendingPersistPositionMetadata = false; - // symbol:leverageValue - private accountLeverageState: Record = {}; + // symbol:buyLeverageValue,sellLeverageValue + private accountLeverageState: Record = {}; // per symbol, per side, cache a copy of the position state private accountPositionState: Record< @@ -43,16 +44,19 @@ export class AccountStateStore< // Store all active orders, keyed by "endineOrder.exchangeOrderId" private accountOrders: Map = new Map(); + // Store asset balances by asset symbol + private accountAssetBalances: Map = new Map(); + private accountOtherState = { balance: 0, previousBalance: 0, hedgedPositions: 0, }; - dumpLogState(): void { - console.log( - `Position dump: `, - JSON.stringify( + dumpLogState(): string | void { + try { + // Using string concatenation instead of console.log + const stateString = JSON.stringify( { accountLeverageState: this.accountLeverageState, acconutPositionState: this.accountPositionState, @@ -60,8 +64,12 @@ export class AccountStateStore< }, null, 2, - ), - ); + ); + // Handle in a way that doesn't require console + return stateString; + } catch (error) { + // Silently handle errors + } } /** @@ -96,10 +104,13 @@ export class AccountStateStore< public getSessionSummary(startingBalance: number) { const balanceNow = this.getWalletBalance(); - const positions = this.getAllPositions().map((pos) => ({ - ...pos, - leverage: this.getSymbolLeverage(pos.symbol), - })); + const positions = this.getAllPositions().map((pos) => { + const leverageData = this.getSymbolLeverage(pos.symbol); + return { + ...pos, + leverage: pos.positionSide === 'LONG' ? leverageData?.buy : leverageData?.sell, + }; + }); let activePositionUpnlSum = 0; let quoteMarginLockedSum = 0; @@ -243,13 +254,25 @@ export class AccountStateStore< return true; } setSymbolLeverage(symbol: string, leverage: number): void { - this.accountLeverageState[symbol] = leverage; + this.accountLeverageState[symbol] = { buy: leverage, sell: leverage }; } - getSymbolLeverage(symbol: string): number | undefined { + setSymbolSideLeverage(symbol: string, side: 'buy' | 'sell', leverage: number): void { + if (!this.accountLeverageState[symbol]) { + this.accountLeverageState[symbol] = { buy: leverage, sell: leverage }; + } else { + this.accountLeverageState[symbol][side] = leverage; + } + } + + getSymbolLeverage(symbol: string): { buy: number, sell: number } | undefined { return this.accountLeverageState[symbol]; } + getSymbolSideLeverage(symbol: string, side: 'buy' | 'sell'): number | undefined { + return this.accountLeverageState[symbol]?.[side]; + } + getSymbolLeverageCache() { return this.accountLeverageState; } @@ -495,4 +518,74 @@ export class AccountStateStore< return ascending ? comparison : -comparison; }); } + + /** + * Get all stored asset balances + */ + getAllAssetBalances(): AbstractAssetBalance[] { + return Array.from(this.accountAssetBalances.values()); + } + + /** + * Get balance for a specific asset + */ + getAssetBalance(asset: string): AbstractAssetBalance | undefined { + return this.accountAssetBalances.get(asset); + } + + /** + * Update or add an asset balance + */ + upsertAssetBalance(assetBalance: AbstractAssetBalance): void { + this.accountAssetBalances.set(assetBalance.asset, assetBalance); + } + + /** + * Update or add multiple asset balances at once + */ + upsertAssetBalances(assetBalances: AbstractAssetBalance[]): void { + for (const balance of assetBalances) { + this.upsertAssetBalance(balance); + } + } + + /** + * Remove an asset balance + */ + deleteAssetBalance(asset: string): boolean { + return this.accountAssetBalances.delete(asset); + } + + /** + * Clear all asset balances + */ + clearAssetBalances(): void { + this.accountAssetBalances.clear(); + } + + /** + * Get total free balance value across all assets + * This assumes the 'free' property is already in the same denomination + */ + getTotalFreeBalance(): number { + let total = 0; + for (const balance of this.accountAssetBalances.values()) { + total += parseFloat(balance.free) || 0; + } + return total; + } + + /** + * Get asset balances filtered by a predicate + */ + filterAssetBalances(predicate: (balance: AbstractAssetBalance) => boolean): AbstractAssetBalance[] { + return this.getAllAssetBalances().filter(predicate); + } + + /** + * Get a list of all asset symbols + */ + getAssetSymbols(): string[] { + return Array.from(this.accountAssetBalances.keys()); + } } diff --git a/src/lib/misc/mappings.ts b/src/lib/misc/mappings.ts new file mode 100644 index 0000000..e5d9c2b --- /dev/null +++ b/src/lib/misc/mappings.ts @@ -0,0 +1,51 @@ +import { AbstractAssetBalance, BybitCoinData, BybitWalletData } from "lib/types/wallet"; + + /** + * Maps BybitWalletData to AbstractWalletData + * @param bybitWalletData The Bybit wallet data to map + * @returns AbstractWalletData + */ + export function mapBybitWalletToAbstract(bybitWalletData: BybitWalletData): AbstractWalletData { + return { + accountType: bybitWalletData.accountType, + updateTime: Date.now(), + balances: bybitWalletData.coin.map((coin: any) => mapBybitCoinToAbstractAsset(coin)), + accountIMRate: bybitWalletData.accountIMRate, + accountMMRate: bybitWalletData.accountMMRate, + totalEquity: bybitWalletData.totalEquity, + totalWalletBalance: bybitWalletData.totalWalletBalance, + totalMarginBalance: bybitWalletData.totalMarginBalance, + totalAvailableBalance: bybitWalletData.totalAvailableBalance, + totalPerpUPL: bybitWalletData.totalPerpUPL, + totalInitialMargin: bybitWalletData.totalInitialMargin, + totalMaintenanceMargin: bybitWalletData.totalMaintenanceMargin, + accountLTV: bybitWalletData.accountLTV + }; + } + + /** + * Maps BybitCoinData to AbstractAssetBalance + * @param bybitCoinData The Bybit coin data to map + * @returns AbstractAssetBalance + */ + export function mapBybitCoinToAbstractAsset(bybitCoinData: BybitCoinData): AbstractAssetBalance { + return { + asset: bybitCoinData.coin, + free: bybitCoinData.walletBalance, + locked: bybitCoinData.locked || "0", + walletBalance: bybitCoinData.walletBalance, + unrealisedPnl: bybitCoinData.unrealisedPnl, + cumRealisedPnl: bybitCoinData.cumRealisedPnl, + availableToWithdraw: bybitCoinData.availableToWithdraw, + availableToBorrow: bybitCoinData.availableToBorrow, + borrowAmount: bybitCoinData.borrowAmount, + accruedInterest: bybitCoinData.accruedInterest, + totalOrderIM: bybitCoinData.totalOrderIM, + totalPositionIM: bybitCoinData.totalPositionIM, + totalPositionMM: bybitCoinData.totalPositionMM, + bonus: bybitCoinData.bonus, + collateralSwitch: bybitCoinData.collateralSwitch, + marginCollateral: bybitCoinData.marginCollateral, + spotHedgingQty: bybitCoinData.spotHedgingQty + }; + } \ No newline at end of file diff --git a/src/lib/types/wallet.ts b/src/lib/types/wallet.ts new file mode 100644 index 0000000..9c3ecff --- /dev/null +++ b/src/lib/types/wallet.ts @@ -0,0 +1,67 @@ +export interface BybitCoinData { + coin: string; + equity: string; + usdValue: string; + walletBalance: string; + availableToWithdraw: string; + availableToBorrow: string; + borrowAmount: string; + accruedInterest: string; + totalOrderIM: string; + totalPositionIM: string; + totalPositionMM: string; + unrealisedPnl: string; + cumRealisedPnl: string; + bonus: string; + collateralSwitch: boolean; + marginCollateral: boolean; + locked: string; + spotHedgingQty: string; +} + +export interface BybitWalletData { + accountIMRate: string; + accountMMRate: string; + totalEquity: string; + totalWalletBalance: string; + totalMarginBalance: string; + totalAvailableBalance: string; + totalPerpUPL: string; + totalInitialMargin: string; + totalMaintenanceMargin: string; + coin: BybitCoinData[]; + accountLTV: string; + accountType: string; +} + +export interface AbstractAssetBalance { + // Common properties + asset: string; // Asset symbol (e.g., "BTC", "ETH") + free: string; // Available balance for trading + locked: string; // Balance locked in open orders + + // Optional properties that may be available in some exchanges + borrowed?: string; // Amount borrowed (margin trading) + interest?: string; // Interest on borrowed amount + netAsset?: string; // Net asset value (free + locked - borrowed) + unrealizedPnl?: string; // Unrealized profit/loss + marginBalance?: string; // Margin balance + initialMargin?: string; // Initial margin + maintenanceMargin?: string; // Maintenance margin + positionInitialMargin?: string; // Position initial margin + openOrderInitialMargin?: string; // Open order initial margin + walletBalance?: string; // Total wallet balance + unrealisedPnl?: string; // Alternative spelling for unrealizedPnl + cumRealisedPnl?: string; // Cumulative realized profit/loss + availableToWithdraw?: string; // Available for withdrawal + availableToBorrow?: string; // Available for borrowing + borrowAmount?: string; // Current borrow amount + accruedInterest?: string; // Accrued interest + totalOrderIM?: string; // Total order initial margin + totalPositionIM?: string; // Total position initial margin + totalPositionMM?: string; // Total position maintenance margin + bonus?: string; // Bonus amount + collateralSwitch?: boolean; // Whether asset can be used as collateral + marginCollateral?: boolean; // Whether asset is used as margin collateral + spotHedgingQty?: string; // Spot hedging quantity + } \ No newline at end of file