# Settlement Price Fix Report

**Date:** 2026-05-31  
**Files changed:** `app/Services/TradeService.php`, `resources/js/lib/assets.js`

---

## Root Causes Found

### Bug 1 — DB Cache Overrides Live Price (PRIMARY BUG)
`getCurrentPrice()` checked `$asset->latestPrice` (an Eloquent DB relationship) before fetching live data. If the DB had a seeded/stale price equal to the entry price, every trade settled as a **tie → cancelled** even when the user won.

**Fix:** `processExpiredTrades()` now calls `fetchClosePrice()` directly, bypassing the DB cache entirely. `getCurrentPrice()` was also updated to always use live data.

### Bug 2 — No Logging in Settlement
`processExpiredTrades()` had no visibility into what prices were being used, making it impossible to diagnose wrong results.

**Fix:** Added `\Log::info('Settling trade', [...])` before `closeTrade()`, logging uuid, asset, direction, entry_price, close_price, and expected `won` outcome.

### Bug 3 — Commodities Always Returned 0
Gold (XAUUSD) and Silver (XAGUSD) are 6-char symbols. The old code treated them as Forex and sent them to Frankfurter (e.g. XAU/USD). Frankfurter does **not** support metals — it only supports fiat currencies. Result: price = 0 → tie → refund.

**Fix:** Added a commodity map that routes XAUUSD → XAUUSDT and XAGUSD → XAGUSDT on Binance, checked *before* the 6-char forex branch.

### Bug 4 — Frontend: Commodities Returned 0 Entry Price
`fetchEntryPrice()` in `assets.js` had no handler for Commodities, so Gold/Silver trades were placed with `entry_price = 0` in the DB. Even with a correct close price, `bccomp(close, 0, 8)` would always be positive → wrong result for DOWN trades.

**Fix:** Added Commodities branch in `fetchEntryPrice()` using `COMMODITY_BINANCE_MAP` (XAUUSD → XAUUSDT, XAGUSD → XAGUSDT).

---

## Changes Made

### `app/Services/TradeService.php`

**`processExpiredTrades()`**
- Gets `$symbol` from `$trade->asset_symbol ?? $trade->asset?->symbol`
- Calls `$this->fetchClosePrice($symbol)` directly (no DB cache)
- Pre-computes expected `$won` outcome for logging
- Logs settlement details before calling `closeTrade()`
- Falls back to entry_price (→ cancelled/refund) only when `fetchClosePrice` returns 0

**`getCurrentPrice()`**
- Removed DB cache lookup (`$asset->latestPrice`)
- Now calls `fetchClosePrice()` and returns null when price = 0

**`fetchLivePrice()` → renamed `fetchClosePrice()`**
- Return type changed from `?string` to `float` (0 on failure)
- Added commodity branch (XAUUSD/XAGUSD → Binance) before forex branch
- Crypto branch: only tries Binance when symbol already ends with `USDT` (no more EURUSDUSDT attempts)
- Each branch returns early on failure (no bleed-through to wrong source)
- Added `\Log::info/warning/error` at every branch for full observability

### `resources/js/lib/assets.js`

**`fetchEntryPrice()`**
- Added `COMMODITY_BINANCE_MAP` constant
- Added Commodities branch: XAUUSD/XAGUSD fetched from Binance XAUUSDT/XAGUSDT
- Wrapped entire function in `try/catch` with `console.error` on failure
- Forex `data.rates[to]` explicitly cast with `parseFloat()` (was implicit)

---

## Settlement Logic (unchanged, verified correct)

`Trade::determineResult()` uses `bccomp(exitPrice, entryPrice, 8)`:
- UP trade: exitPrice > entryPrice → won ✓
- DOWN trade: exitPrice < entryPrice → won ✓  
- Tie (exit == entry): → cancelled → stake refunded ✓

This logic is correct. The bugs were all in price *fetching*, not in the comparison.

---

## Verification Steps

1. Place a BTCUSDT UP trade. Check log after expiry:
   ```
   Settling trade  uuid=...  asset=BTCUSDT  entry_price=105000.xx  close_price=105050.xx  won=true
   ```

2. Place a EURUSD DOWN trade. Check log:
   ```
   Frankfurter price for EUR/USD: 1.16xxx
   Settling trade  ...  close_price=1.16xxx  won=true/false
   ```

3. Place a XAUUSD trade. Check log:
   ```
   Binance commodity price for XAUUSD: 3280.xx
   Settling trade  ...  close_price=3280.xx
   ```

4. `entry_price` in DB must never be 0. If it is, the frontend `fetchEntryPrice` failed — check browser console for errors.
