feat: Add live readiness gate with deadman switch enforcement and enhance order validation logging with client order ID.

pull/12760/head
vijay sharma 3 weeks ago
parent 8d6e3f5cb3
commit bfc06b716c

@ -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:

@ -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}")

@ -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)

@ -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).

@ -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

@ -61,6 +61,7 @@ ALL_GATES=(
"p37_scheduler_templates"
"p38_soak_stability"
"p39_ops_hardening"
"p40_live_readiness"
)
# Parse flags

@ -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" <<EOF
{
"max_open_trades": 1,
"stake_currency": "INR",
"stake_amount": 1000,
"fiat_display_currency": "INR",
"dry_run": false,
"timeframe": "1m",
"entry_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1
},
"exit_pricing": {
"price_side": "same",
"use_order_book": true
},
"exchange": {
"name": "icicibreeze",
"key": "mock_key",
"secret": "mock_secret",
"pair_whitelist": ["RELIANCE/INR"],
"pair_blacklist": []
},
"icicibreeze": {
"app_key": "mock_app_key",
"s_key": "mock_s_key",
"session_token": "mock_token",
"live_trading": {
"enabled": true
}
},
"risk_guard": {
"enabled": true,
"max_trades_per_day": 100
}
}
EOF
# Strategy for Immediate Buy
cat > "${RUN_DIR}/strategy.py" <<EOF
from freqtrade.strategy import IStrategy
from pandas import DataFrame
class TestStrategy(IStrategy):
MIN_ROI = {"0": 100.0}
STOPLOSS = -0.99
TIMEFRAME = "1m"
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> 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
Loading…
Cancel
Save