feat: Add live readiness gate with deadman switch enforcement and enhance order validation logging with client order ID.
parent
8d6e3f5cb3
commit
bfc06b716c
@ -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
|
||||
@ -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…
Reference in new issue