"""Portfolio state helpers (positions.json, reconcile with exchange).""" from ..runtime import get_runtime_paths from .file_utils import load_json_locked, save_json_locked from .trade_common import bj_now_iso PATHS = get_runtime_paths() POSITIONS_FILE = PATHS.positions_file POSITIONS_LOCK = PATHS.positions_lock def load_positions() -> list: return load_json_locked(POSITIONS_FILE, POSITIONS_LOCK, {"positions": []}).get("positions", []) def save_positions(positions: list): save_json_locked(POSITIONS_FILE, POSITIONS_LOCK, {"positions": positions}) def upsert_position(positions: list, position: dict): sym = position["symbol"] for i, existing in enumerate(positions): if existing.get("symbol") == sym: positions[i] = position return positions positions.append(position) return positions def reconcile_positions_with_exchange(ex, positions: list): from .exchange_service import fetch_balances balances = fetch_balances(ex) existing_by_symbol = {p.get("symbol"): p for p in positions} reconciled = [] for asset, qty in balances.items(): if asset == "USDT": continue if qty <= 0: continue sym = f"{asset}USDT" old = existing_by_symbol.get(sym, {}) reconciled.append( { "account_id": old.get("account_id", "binance-main"), "symbol": sym, "base_asset": asset, "quote_asset": "USDT", "market_type": "spot", "quantity": qty, "avg_cost": old.get("avg_cost"), "opened_at": old.get("opened_at", bj_now_iso()), "updated_at": bj_now_iso(), "note": old.get("note", "Reconciled from Binance balances"), } ) save_positions(reconciled) return reconciled, balances