diff --git a/adapters/ccxt_shim/breeze_ccxt.py b/adapters/ccxt_shim/breeze_ccxt.py index 1acd2961e..304e841f9 100644 --- a/adapters/ccxt_shim/breeze_ccxt.py +++ b/adapters/ccxt_shim/breeze_ccxt.py @@ -630,6 +630,7 @@ class BreezeCCXT(ccxt.Exchange): } raise OperationalException("fetch_balance not supported in real mode yet.") + def create_order( self, symbol, order_type, side, amount, price=None, params: dict | None = None ): @@ -721,7 +722,9 @@ class BreezeCCXT(ccxt.Exchange): # P35.5 T4: Pass reduceOnly to router reduce_only = params.get("reduceOnly", False) if params else False - self.order_router.validate_entry(symbol, side, amount, position_check, reduce_only) + self.order_router.validate_entry( + symbol, side, amount, position_check, reduce_only, client_order_id + ) # P18: Paper Forward Mode if self.paper_mode: diff --git a/adapters/ccxt_shim/order_router.py b/adapters/ccxt_shim/order_router.py index 3fbd3e07b..48370bb82 100644 --- a/adapters/ccxt_shim/order_router.py +++ b/adapters/ccxt_shim/order_router.py @@ -85,10 +85,10 @@ class OrderRouter: return # If logic reaches here, it's a SELL - + # P35.5 T4: Allow explicit reduceOnly to bypass position check if reduce_only: - return + return if position_check_callback is None: # Fail safe: Block if we can't check positions @@ -268,6 +268,7 @@ class OrderRouter: amount: float, position_check_callback: Any | None = None, reduce_only: bool = False, + client_order_id: str | None = None, ) -> None: """ Primary validation entry point. @@ -279,4 +280,5 @@ class OrderRouter: # 2. Buyer Only self.assert_buyer_only(symbol, side, position_check_callback, reduce_only) - logger.info(f"OrderRouter: Validated {side} {amount} {symbol} (Lot: {lot_size})") + cid_log = f" [CID: {client_order_id}]" if client_order_id else "" + logger.info(f"OrderRouter: Validated {side} {amount} {symbol} (Lot: {lot_size}){cid_log}") diff --git a/docs/OPS_RUNBOOK.md b/docs/OPS_RUNBOOK.md index bbec500bf..b1587e37e 100644 --- a/docs/OPS_RUNBOOK.md +++ b/docs/OPS_RUNBOOK.md @@ -2,7 +2,42 @@ This runbook describes how to operate the Freqtrade bot with the ICICI Breeze adapter in production. -## 1. Prerequisites +## 1. Safety & Emergency Procedures + +### 1.1 Deadman Switch (Live Mode) + +Live trading requires a fresh "deadman" file every 10 minutes. +**To Renew:** + +```bash +touch user_data/secrets/deadman_live.ok +``` + +**To Stop (Emergency):** + +```bash +rm user_data/secrets/deadman_live.ok +``` + +(Trading will halt on next order attempt). + +### 1.2 Resetting Risk Halts + +If `RiskGuard` blocks entries (Max Loss/Consecutive Losses): + +1. Stop Freqtrade. +2. Delete `user_data/generated/runtime/live_halt.json`. +3. Restart. + +### 1.3 Readiness Failures + +If `LiveReadiness` fails: + +- Check Disk Space. +- Check Session Token. +- Ensure Deadman is fresh. + +## 2. Prerequisites - **Python 3.10+** - **Systemd** (for Linux service management) diff --git a/docs/PHASE_P40.md b/docs/PHASE_P40.md new file mode 100644 index 000000000..f0016dcfa --- /dev/null +++ b/docs/PHASE_P40.md @@ -0,0 +1,35 @@ +# Phase P40: Codebase Hardening & Audit Grade Documentation + +## 1. Objective + +Achieve "Audit Grade" status for live trading readiness, focusing on: + +- **Fail-Closed Readiness Checks** (Deadman Switch). +- **Strict Idempotency** (Client Order IDs). +- **Persistent Risk Halts** (Capital Protection). +- **Operational Hardening** (No Secrets, No Open Ports). + +## 2. Changes Implemented + +### 2.1 Codebase + +- **Readiness:** `LiveReadiness` module ensures environment health before every live order. +- **Idempotency:** `OrderIdempotency` module persists request hashes to prevent duplicate submissions. +- **Risk:** `RiskGuard` now persists halt states to disk, surviving process restarts. +- **Gates:** P39 (Ops Hygiene), P21 (Secrets), P40 (Live Readiness). + +### 2.2 Documentation + +- **Audit Review:** `docs/third_party_review_outside_freqtrade.md` provides a comprehensive system overview for external auditors. +- **Runbook:** Updated `docs/OPS_RUNBOOK.md` with Safe Mode procedures. + +## 3. Verification + +- **Gate P40:** Validates proper fail-closed behavior for Deadman switch. +- **Soak Logs:** `health.json` metrics track system stability. +- **Acceptance Suite:** All gates automated via `scripts/accept_all.sh`. + +## 4. Next Steps + +- External Audit Review. +- Production Deployment (Phase P41). diff --git a/docs/third_party_review_outside_freqtrade.md b/docs/third_party_review_outside_freqtrade.md new file mode 100644 index 000000000..a93197728 --- /dev/null +++ b/docs/third_party_review_outside_freqtrade.md @@ -0,0 +1,96 @@ +# Third Party Review: High Frequency Trading System (ICICI Breeze Adapter) + +**Revision:** 1.0.0 +**Date:** 2026-01-31 +**Scope:** `adapters/ccxt_shim`, `freqtrade` integration, Security & Risk Modules. + +## 1. System Overview + +This system integrates Freqtrade with ICICI Breeze API via a custom CCXT-compatible Shim ("BreezeCCXT"). +It implements Strict Risk Gates (P15), Clean Architecture (P35), and Fail-Closed Security (P40). + +### Architecture (Ports & Adapters) + +```mermaid +graph TD + User[Trader / Ops] -->|SSH / API| FT[Freqtrade Core] + FT -->|CCXT Interface| Shim[BreezeCCXT Shim] + + subgraph Adapters [adapters/ccxt_shim] + Shim --> Router[OrderRouter (P16)] + Shim --> Risk[RiskGuard (P15/P40)] + Shim --> Ready[LiveReadiness (P40)] + Shim --> Idem[Idempotency (P40)] + Shim --> Circuit[CircuitBreaker] + end + + subgraph Infrastructure + Risk -->|Persist| Disk[user_data/generated] + Idem -->|Persist| Disk + Ready -->|Check| Secrets[Deadman Switch] + end + + Shim -->|HTTP| Breeze[ICICI Breeze API] +``` + +## 2. Security Posture + +### 2.1 Secrets Hygiene (Gate P21) + +- **Policy:** Zero secrets in code/logs. +- **Implementation:** `p21_secrets_hygiene.sh` scans all artifacts post-run. +- **Evidence:** Logs are sanitized. API keys injected via Env/File only. + +### 2.2 Network Security (Gate P20) + +- **Policy:** Zero open ports on public interfaces. +- **Implementation:** `p20_no_open_ports_pos.sh` calls `p20_scan_port_exposure.py`. +- **Status:** PASS (Localhost binding forced). + +## 3. Operational Hardening + +### 3.1 Live Readiness (Gate P40) + +- **Mechanism:** `LiveReadiness.check()` enforces Fail-Closed. +- **Checks:** + 1. Deadman Switch (`user_data/secrets/deadman_live.ok`) presence & freshness (<10m). + 2. Disk Space (>2GB). + 3. Session Token validity. +- **Failure Mode:** `OperationalException` (Trade Blocked). + +### 3.2 Order Idempotency (Gate P40) + +- **Mechanism:** `OrderIdempotency` class with persistence. +- **Logic:** Request -> Hash(Fields) -> Cache Check -> Block if Exists. +- **Persistence:** `runtime/order_id_cache.json` (survives restarts). + +### 3.3 Capital Risk Guard (Gate P15) + +- **Mechanism:** `RiskGuard` enforces: + - Max Loss Per Day. + - Max Consecutive Losses. + - Max Open Positions. +- **Persistence:** `runtime/live_halt.json`. A halt triggers a persistent block until manual reset or next day. + +## 4. Operational Invariants + +| Invariant | Implementation | Failure Handling | +|-----------|----------------|------------------| +| **No Duplicate Orders** | `OrderIdempotency.is_duplicate` | Raise `DUPLICATE_SUPPRESSED` | +| **No Unattended Runs** | `check_deadman` | Raise `DEADMAN_FAIL` | +| **No Infinite Loss** | `RiskGuard.record_loss` | Persistent Halt (Entry Block) | +| **No Broken Code** | `P39 Hygiene Gate` | CI Failure on FIXME/TODO | + +## 5. Verification Evidence + +- **Soak Testing:** P38 gate runs 90s soak, verifying `health.json` metrics. +- **Audit Logs:** All runs archive artifacts to `generated/accept_runs/$RUN_ID`. +- **Gate Suite:** `scripts/accept_all.sh` runs 40+ checks daily. + +## 6. Runbook Reference + +See `docs/ops/RUNBOOK.md` for: + +- resetting risk halts +- renewing deadman switch +- rotating API keys diff --git a/scripts/accept_all.sh b/scripts/accept_all.sh index e892faf3a..c3635d73f 100755 --- a/scripts/accept_all.sh +++ b/scripts/accept_all.sh @@ -61,6 +61,7 @@ ALL_GATES=( "p37_scheduler_templates" "p38_soak_stability" "p39_ops_hardening" + "p40_live_readiness" ) # Parse flags diff --git a/scripts/gates/p40_live_readiness.sh b/scripts/gates/p40_live_readiness.sh new file mode 100755 index 000000000..449410825 --- /dev/null +++ b/scripts/gates/p40_live_readiness.sh @@ -0,0 +1,138 @@ +#!/bin/bash +# P40: Live Readiness & Deadman +# Verifies that live trading requires Deadman switch and Readiness. + +set -euo pipefail + +GATE_ID="p40_live_readiness" +source scripts/gates/common.sh "$GATE_ID" "$@" + +# 1. Setup Config +echo ">>> Gate P40: Setup..." + +# Ensure secrets dir exists +mkdir -p user_data/secrets + +# Mock Config with Live Trading Enabled +cat > "${RUN_DIR}/config_p40.json" < "${RUN_DIR}/strategy.py" < DataFrame: + return dataframe + + def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + dataframe.loc[:, 'enter_long'] = 1 + return dataframe + + def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + dataframe.loc[:, 'exit_long'] = 0 + return dataframe +EOF + +if [ "$GATE_MODE" == "pos" ]; then + echo ">>> Gate P40: Positive (Deadman Active)..." + + # Enable Deadman + touch user_data/secrets/deadman_live.ok + touch -m user_data/secrets/deadman_live.ok + + export FT_ENABLE_LIVE_ORDERS=1 + export BREEZE_MOCK=1 + + # Run + timeout 15s "$FREQTRADE" trade \ + --config "${RUN_DIR}/config_p40.json" \ + --strategy TestStrategy \ + --strategy-path "${RUN_DIR}" \ + --user-data-dir user_data \ + > "${RUN_DIR}/p40_pos.log" 2>&1 || true + + if grep -q "Deadman Switch Failed" "${RUN_DIR}/p40_pos.log"; then + echo "[FAIL] Deadman blocked valid orders." + cat "${RUN_DIR}/p40_pos.log" | tail -n 20 + finish_gate 1 + else + echo "[OK] Deadman check passed." + # Verify attempt + if grep -q "LIVE ORDER: Placing" "${RUN_DIR}/p40_pos.log" || grep -q "Mock mode" "${RUN_DIR}/p40_pos.log"; then + echo "[OK] Order placement logic reached." + fi + finish_gate 0 + fi + +elif [ "$GATE_MODE" == "neg" ]; then + echo ">>> Gate P40: Negative (Missing Deadman)..." + + # Remove Deadman + rm -f user_data/secrets/deadman_live.ok + + export FT_ENABLE_LIVE_ORDERS=1 + export BREEZE_MOCK=1 + + # Run + timeout 15s "$FREQTRADE" trade \ + --config "${RUN_DIR}/config_p40.json" \ + --strategy TestStrategy \ + --strategy-path "${RUN_DIR}" \ + --user-data-dir user_data \ + > "${RUN_DIR}/p40_neg.log" 2>&1 || true + + if grep -q "Deadman Switch Failed" "${RUN_DIR}/p40_neg.log"; then + echo "[OK] Deadman blocked live orders." + finish_gate 0 + else + echo "[FAIL] Deadman check failed to block or log missing." + cat "${RUN_DIR}/p40_neg.log" | tail -n 20 + finish_gate 1 + fi + +else + echo "ERROR: Unknown valid mode $GATE_MODE" + finish_gate 1 +fi