# CV6 AI Trading OS — Production Audit Prompt

You are a Principal Quant Software Engineer, Trading Systems Architect, and Production Risk Engineer.
Your job is a **deep production audit** of the CV6 AI Trading OS codebase.

**DO NOT add new features.**
**DO NOT rewrite architecture unless absolutely necessary.**
Fix only real bugs, missing paths, and safety gaps.

---

## Codebase Root
`D:\CV6_AI_Trading_OS\`

Key dirs: `app/autonomous/`, `app/risk/`, `app/risk_guard/`, `app/execution/`, `app/broker/`

---

## AUDIT TARGET 1 — INTRADAY AUTO SQUARE-OFF AT 3:10 PM

**Required behaviour:**
NSE MIS (intraday) positions MUST be closed by **15:10 IST** (before broker auto-SQ at 15:20).

**Files to read:**
- `app/autonomous/position_monitor.py` — `_is_eod()`, `monitor_pool()`
- `app/autonomous/autonomous_config.py` — `trading_end`
- `app/autonomous/autonomous_engine.py` — where `monitor_pool()` is called
- `app/risk_guard/risk_guard.py` — `_check_market_hours()`

**Known bugs to verify and fix:**

1. `autonomous_config.py` default `trading_end = "15:15"` — should be `"15:10"` for MIS safety.

2. `position_monitor._is_eod()` checks `(end - now).total_seconds() <= 300` (5 min before EOD).
   - With `trading_end="15:15"` → triggers at 15:10. ✓ Correct time but wrong config default.
   - With `trading_end="15:10"` → triggers at 15:05. Better. Verify this is acceptable.
   - **Fix:** Set default `trading_end = "15:10"` and confirm `_is_eod()` window covers NSE MIS.

3. `risk_guard._check_market_hours()` uses **HARDCODED** `market_close = "15:25"` — completely ignores `config.trading_end`. This means:
   - Risk guard allows new entries up to 15:25 even while position_monitor is squaring off at 15:10.
   - **Fix:** `market_close` should read from `autonomous_config.trading_end` (or a shared config), not a hardcoded string.

4. `position_monitor` closes positions only when `pool.style in {"INTRADAY", "SCALPING"}`.
   - Verify that ALL intraday broker orders (product=MIS) map to pools with these styles.
   - If any MIS order goes into a SWING or LONG_TERM pool, it will NOT be auto-closed. Check `capital_pool.py` and `broker_allocator.py`.

---

## AUDIT TARGET 2 — COMMODITY (MCX) AUTO CLOSE AT 11:00 PM

**Required behaviour:**
MCX commodity futures positions MUST be closed by **23:00 IST** (before MCX session end at 23:30).

**Files to read:**
- `app/autonomous/position_monitor.py`
- `app/autonomous/autonomous_engine.py` — look at `_phase_monitor()`
- `app/autonomous/autonomous_config.py`
- `app/execution/order_manager.py`

**Known bugs to fix:**

1. **No MCX close time exists anywhere.** `trading_end` is a single value for ALL exchanges.
   - The position monitor passes ONE `trading_end` to ALL pools regardless of exchange.
   - MCX positions have no EOD trigger at 23:00.
   - **Fix:** Add `commodity_trading_end: str = "23:00"` to `AutonomousConfig`.
   - In `position_monitor.monitor_pool()`, add a parameter `exchange: str = "NSE"` OR check position's exchange field.
   - If `pos.exchange == "MCX"` → use `commodity_trading_end` for EOD check.

2. **`AutonomousPosition` in `capital_pool.py`** — check if it stores `exchange` field.
   - If not, add `exchange: str = "NSE"` to `AutonomousPosition`.
   - Ensure order execution sets `pos.exchange` from the order dict.

3. **Risk guard `_check_market_hours()`** blocks ALL orders after 15:25.
   - This incorrectly blocks VALID MCX evening session trades (MCX opens 09:00 - 23:30).
   - **Fix:** If `exchange == "MCX"`, skip NSE market hour check OR use MCX hours (09:00-23:30).
   - The check must be exchange-aware, not NSE-only.

4. **New order entries after 15:10 for MCX** — autonomous scanner should still allow MCX entries in the evening session. Check if `_phase_scan()` or `_phase_rank()` incorrectly blocks based on NSE close time.

---

## AUDIT TARGET 3 — DUPLICATE ORDER PREVENTION

**Required behaviour:**
The same symbol must NEVER receive two buy orders in the same direction within one trading session from the autonomous engine.

**Files to read:**
- `app/autonomous/autonomous_engine.py` — `_phase_rank()`, `_phase_execute()`
- `app/execution/order_manager.py`
- `app/execution/execution_router.py`
- `app/autonomous/capital_pool.py` — `all_positions()`

**Known bugs to fix:**

1. **Race condition between rank and execute:**
   `_phase_rank()` filters `traded_syms = {p.symbol for p in pool_manager.all_positions()}`.
   But between `_phase_rank()` and `_phase_execute()`, there is NO lock.
   If two engine cycles run close together (async), both can pass the symbol filter and both place an order.
   - **Fix:** Add an in-memory `_pending_symbols: set` to the engine.
   - In `_phase_rank()`: after filtering, add the top symbol to `_pending_symbols`.
   - In `_phase_execute()`: check `_pending_symbols` before executing. Remove after execution (success or fail).

2. **Pending (open/queued) orders not tracked:**
   When an order is placed but not yet filled (status = PENDING/OPEN), `all_positions()` may not include it (positions are open trades, not pending orders).
   - Next AI cycle sees the symbol as "not traded" → places ANOTHER order.
   - **Fix:** Track placed order IDs in `autonomous_engine._placed_order_ids: Dict[str, float]` (symbol → timestamp).
   - Before placing any order for a symbol, check if a pending order was placed for it in the last N minutes (e.g., 15 minutes).
   - On order success, add to `_placed_order_ids`. Clear stale entries on each cycle.

3. **Max duplicate via UI + Autonomous:**
   If the user manually places an order from ExecutionPage AND the autonomous engine independently selects the same symbol, duplicate positions occur.
   - **Fix:** `_phase_rank()` should also check the BROKER's live order book (call `broker.orders()` cached) and filter symbols that already have an open/pending order.
   - Add this as a soft-check (log warning, skip symbol) not a hard crash.

4. **`ai_cache` deduplication is for AI calls only** — it does NOT prevent duplicate order execution. Don't confuse the two.

---

## AUDIT TARGET 4 — RISK SYSTEM GAPS

**Files to read:**
- `app/risk_guard/risk_guard.py`
- `app/risk/risk_manager.py`, `risk_engine.py`, `risk_validator.py`
- `app/autonomous/autonomous_engine.py` — where is `risk_guard.evaluate()` called?
- `app/autonomous/autonomous_config.py`

**Known bugs to fix:**

1. **DUAL risk config — out of sync:**
   - `RiskConfig` in `risk_guard.py`: `daily_loss_limit=5000`, `weekly_loss_limit=15000`
   - `AutonomousConfig` in `autonomous_config.py`: `max_daily_loss=2000`, `max_weekly_loss=8000`
   - These are DIFFERENT values. Which one does the autonomous engine actually use?
   - **Fix:** Consolidate to ONE source. Either `AutonomousConfig` drives `RiskConfig`, or vice versa. Verify the `risk_guard.update_config()` is called when autonomous config changes.

2. **`LossTracker.record()` — is it called after each trade?**
   - Verify in `autonomous_engine._phase_monitor()` or `_phase_execute()` that after a position closes, `risk_guard.loss_tracker.record(pnl)` is called.
   - If NOT called, loss limits are never enforced during live trading.

3. **`risk_guard.evaluate()` — is it called BEFORE each order?**
   - Find where `risk_guard.evaluate()` or `risk_guard.check_*()` is called in the execution path.
   - It MUST be called in `_phase_execute()` before `broker.place_order()`.
   - If it is called, verify `trading_mode` is passed correctly (PAPER vs REAL).

4. **`max_open_positions` not enforced per-pool:**
   - `autonomous_config.max_open_positions = 10` is a global limit.
   - But capital pools have individual position tracking.
   - Verify `_phase_execute()` checks this limit before placing.

5. **Broker allocation uses disconnected brokers:**
   - `broker_allocation: {SWING: "AliceBlue", FNO: "Upstox", LONG_TERM: "Zerodha"}`
   - None of these are connected. Orders for SWING/FNO/LONG_TERM will silently fail or use wrong broker.
   - **Fix:** Add a check in `broker_allocator.py` — if the allocated broker is not connected, fall back to the connected broker (AngelOne or MStock) and log a warning.

---

## AUDIT TARGET 5 — OTHER CRITICAL PATHS

1. **Angel One IP whitelist (AG7002):**
   - Logs show `49.37.162.59 is not a registered IP` for every AngelOne order.
   - AngelOne orders will ALL fail until this IP is whitelisted at https://smartapi.angelbroking.com.
   - **Fix in code:** In `angel_one.py` `place_order()`, detect `AG7002` error code and raise a clear exception: `"AngelOne order blocked: IP not whitelisted. Register 49.37.162.59 at smartapi.angelbroking.com"` — do NOT silently log and continue.

2. **Auto-connect on server restart:**
   - Startup connects AngelOne (stored in `broker_mgr`) and MStock (NOW stored via `broker_mgr.register('mstock', instance)` — recently fixed).
   - Verify `broker_mgr.register()` is called AFTER `mstock.connect()` succeeds.
   - Verify `get_broker('mstock')` returns the registered authenticated instance, not a new unauthenticated one.

3. **`position_monitor` closes position in memory but does NOT place the exit order with broker:**
   - `pool.close_position(pos_id, exit_price, reason)` updates internal state only.
   - Is a SELL order actually sent to the broker? Check `autonomous_engine._phase_monitor()` after `actions = position_monitor.monitor_pool(...)`.
   - If no broker exit order is placed, positions in CV6 show "closed" but REAL broker positions remain open — **critical capital risk**.

4. **`_check_market_hours` returns False outside hours but autonomous engine ignores PAPER mode:**
   - Verify the trading_mode ("PAPER" vs "REAL") is correctly passed to `risk_guard.evaluate()`.
   - In PAPER mode, market hour checks should be bypassed.

---

## DELIVERABLE FORMAT

For each bug found:
```
FILE: <relative path>
LINE: <line number>
BUG: <one-line description>
SEVERITY: CRITICAL | HIGH | MEDIUM
FIX: <exact code change>
```

Fix all CRITICAL bugs directly. For HIGH bugs, show the fix. For MEDIUM, document only.
Do NOT add features. Do NOT restructure. Surgical fixes only.
