bitsentry 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- bitsentry-0.1.0/.gitignore +11 -0
- bitsentry-0.1.0/AGENTS.md +43 -0
- bitsentry-0.1.0/Makefile +24 -0
- bitsentry-0.1.0/PKG-INFO +69 -0
- bitsentry-0.1.0/README.md +43 -0
- bitsentry-0.1.0/bitsentry/__init__.py +18 -0
- bitsentry-0.1.0/bitsentry/api/__init__.py +0 -0
- bitsentry-0.1.0/bitsentry/api/server.py +273 -0
- bitsentry-0.1.0/bitsentry/audit_engine.py +329 -0
- bitsentry-0.1.0/bitsentry/bgc_client.py +153 -0
- bitsentry-0.1.0/bitsentry/cli.py +10 -0
- bitsentry-0.1.0/bitsentry/mcp/__init__.py +3 -0
- bitsentry-0.1.0/bitsentry/mcp/server.py +169 -0
- bitsentry-0.1.0/bitsentry/position_monitor.py +223 -0
- bitsentry-0.1.0/bitsentry/reporter.py +227 -0
- bitsentry-0.1.0/bitsentry/risk_guardian.py +256 -0
- bitsentry-0.1.0/bitsentry/scheduler.py +81 -0
- bitsentry-0.1.0/bitsentry/strategy_evaluator.py +269 -0
- bitsentry-0.1.0/config/risk_rules.yaml +11 -0
- bitsentry-0.1.0/dashboard/index.html +623 -0
- bitsentry-0.1.0/pyproject.toml +43 -0
- bitsentry-0.1.0/run.py +62 -0
- bitsentry-0.1.0/tests/__init__.py +0 -0
- bitsentry-0.1.0/validation/audit_report.html +81 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# BitSentry — AGENTS.md
|
|
2
|
+
|
|
3
|
+
## What We Are Building
|
|
4
|
+
BitSentry is a pip-installable Python library (`pip install bitsentry`) that is the safety, audit, and intelligence layer for Bitget trading agents and traders. Built for the Bitget AI Base Camp Hackathon S1 — Track 2: Trading Infra.
|
|
5
|
+
|
|
6
|
+
## Core Mission
|
|
7
|
+
Every Bitget trading agent is a black box. When it loses money, nobody knows why. BitSentry fixes that by wrapping every trade decision with risk enforcement, audit logging, and performance intelligence.
|
|
8
|
+
|
|
9
|
+
## 5 Layers We Are Building
|
|
10
|
+
1. BGCClient — wrapper around bgc CLI for all Bitget data calls (DONE)
|
|
11
|
+
2. AuditEngine — SQLite logger + SHA-256 integrity verification (NEXT)
|
|
12
|
+
3. RiskGuardian — 5-layer risk middleware that approves or blocks trades
|
|
13
|
+
4. PositionMonitor — live position safety ratings (green/yellow/red)
|
|
14
|
+
5. StrategyEvaluator — rolling win rate tracker with PERFORMING/DEGRADING/DEAD verdict
|
|
15
|
+
|
|
16
|
+
## Plus
|
|
17
|
+
- FastAPI REST server exposing all layers as endpoints
|
|
18
|
+
- React dashboard for human traders
|
|
19
|
+
- MCP server for AI agent integration
|
|
20
|
+
- Makefile with: make install, make test, make validate, make verify
|
|
21
|
+
|
|
22
|
+
## Credentials (Demo Account)
|
|
23
|
+
BITGET_DEMO_API_KEY=bg_a3a670745f27aaee1609acfe408215aa
|
|
24
|
+
BITGET_DEMO_SECRET_KEY=912bf1e07cf01cc21ad485954d202b0e72fcab89877131129fa793d427fb50e9
|
|
25
|
+
BITGET_DEMO_PASSPHRASE=BitgetDemoKey
|
|
26
|
+
|
|
27
|
+
## Build Rules
|
|
28
|
+
- Always read existing files before editing them
|
|
29
|
+
- Never guess bgc tool names — run bgc --help first
|
|
30
|
+
- Test every file immediately after writing it
|
|
31
|
+
- One component at a time, confirm it works before moving on
|
|
32
|
+
- Demo mode: always use BGCClient(demo=True) in tests
|
|
33
|
+
- All bgc calls go through BGCClient, never call bgc directly in other files
|
|
34
|
+
|
|
35
|
+
## Current Status
|
|
36
|
+
- bitsentry/bgc_client.py — COMPLETE AND TESTED
|
|
37
|
+
- bitsentry/audit_engine.py — IN PROGRESS
|
|
38
|
+
- bitsentry/risk_guardian.py — TODO
|
|
39
|
+
- bitsentry/position_monitor.py — TODO
|
|
40
|
+
- bitsentry/strategy_evaluator.py — TODO
|
|
41
|
+
|
|
42
|
+
## Stack
|
|
43
|
+
Python 3.11+, FastAPI, SQLite, bgc CLI, React (dashboard), MCP protocol
|
bitsentry-0.1.0/Makefile
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
PY = /opt/miniconda3/bin/python3.13
|
|
2
|
+
|
|
3
|
+
.PHONY: install run test validate verify lint clean
|
|
4
|
+
|
|
5
|
+
install:
|
|
6
|
+
$(PY) -m pip install -e .
|
|
7
|
+
|
|
8
|
+
run:
|
|
9
|
+
$(PY) run.py
|
|
10
|
+
|
|
11
|
+
test:
|
|
12
|
+
$(PY) -m pytest tests/
|
|
13
|
+
|
|
14
|
+
validate:
|
|
15
|
+
$(PY) -c "from bitsentry.audit_engine import AuditEngine; e=AuditEngine(); r=e.generate_audit_report(); print(r); e.export_html_report(); print('HTML report: validation/audit_report.html')"
|
|
16
|
+
|
|
17
|
+
verify:
|
|
18
|
+
$(PY) -c "from bitsentry.audit_engine import AuditEngine; e=AuditEngine(); r=e.generate_audit_report(); v=e.verify_integrity(r['integrity_hash']); print('Integrity verified:', v)"
|
|
19
|
+
|
|
20
|
+
lint:
|
|
21
|
+
$(PY) -m ruff check bitsentry/
|
|
22
|
+
|
|
23
|
+
clean:
|
|
24
|
+
rm -rf dist/ build/ *.egg-info __pycache__
|
bitsentry-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: bitsentry
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Safety, audit, and intelligence layer for Bitget trading agents and traders
|
|
5
|
+
Project-URL: Homepage, https://github.com/Benita2001/BitSentry
|
|
6
|
+
Project-URL: Repository, https://github.com/Benita2001/BitSentry
|
|
7
|
+
Author-email: Benita Chidera <0xbeni123@gmail.com>
|
|
8
|
+
License: MIT
|
|
9
|
+
Keywords: agent,audit,bitget,crypto,mcp,risk-management,trading
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Topic :: Office/Business :: Financial :: Investment
|
|
15
|
+
Requires-Python: >=3.10
|
|
16
|
+
Requires-Dist: fastapi>=0.104.0
|
|
17
|
+
Requires-Dist: httpx>=0.25.0
|
|
18
|
+
Requires-Dist: mcp>=1.0.0
|
|
19
|
+
Requires-Dist: pydantic>=2.0.0
|
|
20
|
+
Requires-Dist: python-dotenv>=1.0.0
|
|
21
|
+
Requires-Dist: pyyaml>=6.0.0
|
|
22
|
+
Requires-Dist: requests>=2.31.0
|
|
23
|
+
Requires-Dist: rich>=13.0.0
|
|
24
|
+
Requires-Dist: uvicorn>=0.24.0
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
|
|
27
|
+
# BitSentry
|
|
28
|
+
|
|
29
|
+
Safety, audit, and intelligence layer for Bitget trading agents and traders.
|
|
30
|
+
|
|
31
|
+
## Structure
|
|
32
|
+
|
|
33
|
+
- `bitsentry/bgc_client.py` — Bitget API client
|
|
34
|
+
- `bitsentry/audit_engine.py` — Trade audit and logging
|
|
35
|
+
- `bitsentry/risk_guardian.py` — Pre-trade risk enforcement
|
|
36
|
+
- `bitsentry/position_monitor.py` — Real-time position tracking
|
|
37
|
+
- `bitsentry/strategy_evaluator.py` — Strategy scoring and auditing
|
|
38
|
+
- `bitsentry/api/server.py` — FastAPI REST server (11 endpoints)
|
|
39
|
+
- `config/risk_rules.yaml` — Configurable risk parameters
|
|
40
|
+
- `run.py` — Server launcher with startup banner
|
|
41
|
+
|
|
42
|
+
## Setup
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
make install # pip install -e .
|
|
46
|
+
make run # start server on http://127.0.0.1:8000
|
|
47
|
+
make validate # generate audit report + HTML
|
|
48
|
+
make verify # verify SHA-256 integrity hash
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## API Endpoints
|
|
52
|
+
|
|
53
|
+
| Method | Path | Description |
|
|
54
|
+
|--------|------|-------------|
|
|
55
|
+
| GET | `/health` | Server health + mode |
|
|
56
|
+
| GET | `/positions` | Open positions with safety ratings |
|
|
57
|
+
| GET | `/positions/summary` | GREEN/YELLOW/RED counts |
|
|
58
|
+
| GET | `/positions/safe-to-trade` | Pre-trade exposure check |
|
|
59
|
+
| POST | `/risk/check` | 5-layer risk middleware |
|
|
60
|
+
| GET | `/strategy/leaderboard` | Strategies ranked by profit factor |
|
|
61
|
+
| GET | `/strategy/{tag}` | Strategy health verdict |
|
|
62
|
+
| POST | `/strategy/record` | Record a trade result |
|
|
63
|
+
| GET | `/audit/report` | Full audit + SHA-256 hash |
|
|
64
|
+
| GET | `/audit/verify` | Verify integrity hash |
|
|
65
|
+
| GET | `/docs` | Swagger UI |
|
|
66
|
+
|
|
67
|
+
## Stack
|
|
68
|
+
|
|
69
|
+
Python 3.10+, FastAPI, SQLite, bgc CLI, Pydantic v2
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# BitSentry
|
|
2
|
+
|
|
3
|
+
Safety, audit, and intelligence layer for Bitget trading agents and traders.
|
|
4
|
+
|
|
5
|
+
## Structure
|
|
6
|
+
|
|
7
|
+
- `bitsentry/bgc_client.py` — Bitget API client
|
|
8
|
+
- `bitsentry/audit_engine.py` — Trade audit and logging
|
|
9
|
+
- `bitsentry/risk_guardian.py` — Pre-trade risk enforcement
|
|
10
|
+
- `bitsentry/position_monitor.py` — Real-time position tracking
|
|
11
|
+
- `bitsentry/strategy_evaluator.py` — Strategy scoring and auditing
|
|
12
|
+
- `bitsentry/api/server.py` — FastAPI REST server (11 endpoints)
|
|
13
|
+
- `config/risk_rules.yaml` — Configurable risk parameters
|
|
14
|
+
- `run.py` — Server launcher with startup banner
|
|
15
|
+
|
|
16
|
+
## Setup
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
make install # pip install -e .
|
|
20
|
+
make run # start server on http://127.0.0.1:8000
|
|
21
|
+
make validate # generate audit report + HTML
|
|
22
|
+
make verify # verify SHA-256 integrity hash
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## API Endpoints
|
|
26
|
+
|
|
27
|
+
| Method | Path | Description |
|
|
28
|
+
|--------|------|-------------|
|
|
29
|
+
| GET | `/health` | Server health + mode |
|
|
30
|
+
| GET | `/positions` | Open positions with safety ratings |
|
|
31
|
+
| GET | `/positions/summary` | GREEN/YELLOW/RED counts |
|
|
32
|
+
| GET | `/positions/safe-to-trade` | Pre-trade exposure check |
|
|
33
|
+
| POST | `/risk/check` | 5-layer risk middleware |
|
|
34
|
+
| GET | `/strategy/leaderboard` | Strategies ranked by profit factor |
|
|
35
|
+
| GET | `/strategy/{tag}` | Strategy health verdict |
|
|
36
|
+
| POST | `/strategy/record` | Record a trade result |
|
|
37
|
+
| GET | `/audit/report` | Full audit + SHA-256 hash |
|
|
38
|
+
| GET | `/audit/verify` | Verify integrity hash |
|
|
39
|
+
| GET | `/docs` | Swagger UI |
|
|
40
|
+
|
|
41
|
+
## Stack
|
|
42
|
+
|
|
43
|
+
Python 3.10+, FastAPI, SQLite, bgc CLI, Pydantic v2
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# bitsentry — Safety, audit, and intelligence layer for Bitget trading agents
|
|
2
|
+
__version__ = "0.1.0"
|
|
3
|
+
|
|
4
|
+
from bitsentry.bgc_client import BGCClient
|
|
5
|
+
from bitsentry.audit_engine import AuditEngine
|
|
6
|
+
from bitsentry.risk_guardian import RiskGuardian, RiskCheckResult
|
|
7
|
+
from bitsentry.position_monitor import PositionMonitor, PositionSnapshot
|
|
8
|
+
from bitsentry.strategy_evaluator import StrategyEvaluator, StrategyHealth
|
|
9
|
+
from bitsentry.reporter import ReportGenerator
|
|
10
|
+
from bitsentry.scheduler import Scheduler
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"BGCClient", "AuditEngine",
|
|
14
|
+
"RiskGuardian", "RiskCheckResult",
|
|
15
|
+
"PositionMonitor", "PositionSnapshot",
|
|
16
|
+
"StrategyEvaluator", "StrategyHealth",
|
|
17
|
+
"ReportGenerator", "Scheduler",
|
|
18
|
+
]
|
|
File without changes
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import dataclasses
|
|
4
|
+
import os
|
|
5
|
+
from contextlib import asynccontextmanager
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from fastapi import FastAPI, HTTPException, Query
|
|
9
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
10
|
+
from fastapi.responses import FileResponse
|
|
11
|
+
from fastapi.staticfiles import StaticFiles
|
|
12
|
+
from pydantic import BaseModel
|
|
13
|
+
|
|
14
|
+
from bitsentry.audit_engine import AuditEngine
|
|
15
|
+
from bitsentry.bgc_client import BGCClient, BGCError
|
|
16
|
+
from bitsentry.position_monitor import PositionMonitor
|
|
17
|
+
from bitsentry.reporter import ReportGenerator
|
|
18
|
+
from bitsentry.risk_guardian import RiskGuardian
|
|
19
|
+
from bitsentry.strategy_evaluator import StrategyEvaluator
|
|
20
|
+
|
|
21
|
+
# ── Global component references ───────────────────────────────────────────────
|
|
22
|
+
# Populated during lifespan startup; all routes read from here.
|
|
23
|
+
|
|
24
|
+
_state: dict[str, Any] = {}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _asdict(obj: Any) -> Any:
|
|
28
|
+
"""Recursively convert dataclasses to dicts for JSON serialization."""
|
|
29
|
+
if dataclasses.is_dataclass(obj) and not isinstance(obj, type):
|
|
30
|
+
return {k: _asdict(v) for k, v in dataclasses.asdict(obj).items()}
|
|
31
|
+
if isinstance(obj, list):
|
|
32
|
+
return [_asdict(i) for i in obj]
|
|
33
|
+
return obj
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
# ── Startup / shutdown ────────────────────────────────────────────────────────
|
|
37
|
+
|
|
38
|
+
@asynccontextmanager
|
|
39
|
+
async def lifespan(application: FastAPI):
|
|
40
|
+
demo = os.environ.get("BITGET_DEMO", "true").lower() != "false"
|
|
41
|
+
try:
|
|
42
|
+
client = BGCClient(demo=demo)
|
|
43
|
+
except BGCError as exc:
|
|
44
|
+
print(f"[bitsentry] WARNING: BGCClient failed to initialize: {exc}")
|
|
45
|
+
client = None
|
|
46
|
+
|
|
47
|
+
audit = AuditEngine()
|
|
48
|
+
guardian = RiskGuardian(audit_engine=audit)
|
|
49
|
+
monitor = PositionMonitor(bgc_client=client, audit_engine=audit) if client else None
|
|
50
|
+
evaluator = StrategyEvaluator(audit_engine=audit)
|
|
51
|
+
|
|
52
|
+
reporter = ReportGenerator(
|
|
53
|
+
audit_engine=audit,
|
|
54
|
+
strategy_evaluator=evaluator,
|
|
55
|
+
position_monitor=monitor,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
_state.update({
|
|
59
|
+
"client": client,
|
|
60
|
+
"audit": audit,
|
|
61
|
+
"guardian": guardian,
|
|
62
|
+
"monitor": monitor,
|
|
63
|
+
"evaluator": evaluator,
|
|
64
|
+
"reporter": reporter,
|
|
65
|
+
"demo": demo,
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
print("[bitsentry] API server ready.")
|
|
69
|
+
yield
|
|
70
|
+
_state.clear()
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
# ── App ───────────────────────────────────────────────────────────────────────
|
|
74
|
+
|
|
75
|
+
app = FastAPI(
|
|
76
|
+
title="BitSentry API",
|
|
77
|
+
description="Safety, audit, and intelligence layer for Bitget trading agents",
|
|
78
|
+
version="0.1.0",
|
|
79
|
+
lifespan=lifespan,
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
app.add_middleware(
|
|
83
|
+
CORSMiddleware,
|
|
84
|
+
allow_origins=["*"],
|
|
85
|
+
allow_methods=["*"],
|
|
86
|
+
allow_headers=["*"],
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
# Serve dashboard static files at /dashboard/*
|
|
90
|
+
_dashboard_path = os.path.join(os.path.dirname(__file__), "../../dashboard")
|
|
91
|
+
if os.path.exists(_dashboard_path):
|
|
92
|
+
app.mount("/dashboard", StaticFiles(directory=_dashboard_path, html=True), name="dashboard")
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
# ── Request / response models ─────────────────────────────────────────────────
|
|
96
|
+
|
|
97
|
+
class RiskCheckRequest(BaseModel):
|
|
98
|
+
symbol: str
|
|
99
|
+
side: str
|
|
100
|
+
size_usdt: float
|
|
101
|
+
leverage: float
|
|
102
|
+
account_balance_usdt: float
|
|
103
|
+
daily_pnl_usdt: float
|
|
104
|
+
consecutive_losses: int
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class RecordTradeRequest(BaseModel):
|
|
108
|
+
strategy_tag: str
|
|
109
|
+
symbol: str
|
|
110
|
+
side: str
|
|
111
|
+
entry_price: float
|
|
112
|
+
exit_price: float
|
|
113
|
+
size_usdt: float
|
|
114
|
+
market_condition: str = ""
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
# ── Routes ────────────────────────────────────────────────────────────────────
|
|
118
|
+
|
|
119
|
+
@app.get("/ui")
|
|
120
|
+
async def serve_dashboard():
|
|
121
|
+
dashboard_file = os.path.join(os.path.dirname(__file__), "../../dashboard/index.html")
|
|
122
|
+
return FileResponse(dashboard_file)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
@app.get("/")
|
|
126
|
+
def root():
|
|
127
|
+
return {
|
|
128
|
+
"name": "BitSentry",
|
|
129
|
+
"version": "0.1.0",
|
|
130
|
+
"docs": "/docs",
|
|
131
|
+
"status": "running",
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
@app.get("/health")
|
|
136
|
+
def health():
|
|
137
|
+
return {
|
|
138
|
+
"status": "ok",
|
|
139
|
+
"version": "0.1.0",
|
|
140
|
+
"demo_mode": _state.get("demo", True),
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
# ── Positions ─────────────────────────────────────────────────────────────────
|
|
145
|
+
|
|
146
|
+
@app.get("/positions")
|
|
147
|
+
def get_positions():
|
|
148
|
+
monitor: PositionMonitor | None = _state.get("monitor")
|
|
149
|
+
if not monitor:
|
|
150
|
+
raise HTTPException(503, "PositionMonitor unavailable — BGCClient failed to initialize")
|
|
151
|
+
return _asdict(monitor.get_positions())
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
@app.get("/positions/summary")
|
|
155
|
+
def get_positions_summary():
|
|
156
|
+
monitor: PositionMonitor | None = _state.get("monitor")
|
|
157
|
+
if not monitor:
|
|
158
|
+
raise HTTPException(503, "PositionMonitor unavailable — BGCClient failed to initialize")
|
|
159
|
+
return monitor.get_account_summary()
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
@app.get("/positions/safe-to-trade")
|
|
163
|
+
def safe_to_trade(
|
|
164
|
+
symbol: str = Query(..., description="Trading symbol, e.g. BTCUSDT"),
|
|
165
|
+
direction: str = Query(..., description="long or short"),
|
|
166
|
+
):
|
|
167
|
+
monitor: PositionMonitor | None = _state.get("monitor")
|
|
168
|
+
if not monitor:
|
|
169
|
+
raise HTTPException(503, "PositionMonitor unavailable — BGCClient failed to initialize")
|
|
170
|
+
return monitor.get_safe_to_trade(symbol=symbol, direction=direction)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
# ── Risk ──────────────────────────────────────────────────────────────────────
|
|
174
|
+
|
|
175
|
+
@app.post("/risk/check")
|
|
176
|
+
def risk_check(body: RiskCheckRequest):
|
|
177
|
+
guardian: RiskGuardian = _state["guardian"]
|
|
178
|
+
result = guardian.check(
|
|
179
|
+
symbol=body.symbol,
|
|
180
|
+
side=body.side,
|
|
181
|
+
size_usdt=body.size_usdt,
|
|
182
|
+
leverage=body.leverage,
|
|
183
|
+
account_balance_usdt=body.account_balance_usdt,
|
|
184
|
+
daily_pnl_usdt=body.daily_pnl_usdt,
|
|
185
|
+
consecutive_losses=body.consecutive_losses,
|
|
186
|
+
)
|
|
187
|
+
return _asdict(result)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
# ── Strategy — leaderboard must be declared before /{strategy_tag} ────────────
|
|
191
|
+
|
|
192
|
+
@app.get("/strategy/leaderboard")
|
|
193
|
+
def strategy_leaderboard():
|
|
194
|
+
evaluator: StrategyEvaluator = _state["evaluator"]
|
|
195
|
+
return evaluator.get_leaderboard()
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
@app.get("/strategy/{strategy_tag}")
|
|
199
|
+
def strategy_health(strategy_tag: str):
|
|
200
|
+
evaluator: StrategyEvaluator = _state["evaluator"]
|
|
201
|
+
health = evaluator.evaluate(strategy_tag)
|
|
202
|
+
return _asdict(health)
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
@app.post("/strategy/record")
|
|
206
|
+
def record_trade(body: RecordTradeRequest):
|
|
207
|
+
evaluator: StrategyEvaluator = _state["evaluator"]
|
|
208
|
+
trade_id = evaluator.record_trade_result(
|
|
209
|
+
strategy_tag=body.strategy_tag,
|
|
210
|
+
symbol=body.symbol,
|
|
211
|
+
side=body.side,
|
|
212
|
+
entry_price=body.entry_price,
|
|
213
|
+
exit_price=body.exit_price,
|
|
214
|
+
size_usdt=body.size_usdt,
|
|
215
|
+
market_condition=body.market_condition,
|
|
216
|
+
)
|
|
217
|
+
return {"recorded": True, "trade_id": trade_id}
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
# ── Audit ─────────────────────────────────────────────────────────────────────
|
|
221
|
+
|
|
222
|
+
@app.get("/audit/report")
|
|
223
|
+
def audit_report():
|
|
224
|
+
audit: AuditEngine = _state["audit"]
|
|
225
|
+
return audit.generate_audit_report()
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
@app.get("/audit/verify")
|
|
229
|
+
def audit_verify():
|
|
230
|
+
audit: AuditEngine = _state["audit"]
|
|
231
|
+
report = audit.generate_audit_report()
|
|
232
|
+
integrity_hash = report["integrity_hash"]
|
|
233
|
+
verified = audit.verify_integrity(integrity_hash)
|
|
234
|
+
return {"verified": verified, "hash": integrity_hash}
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
# ── Reports ───────────────────────────────────────────────────────────────────
|
|
238
|
+
|
|
239
|
+
class SendReportRequest(BaseModel):
|
|
240
|
+
type: str # "daily" | "weekly" | "monthly"
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
@app.get("/report/daily")
|
|
244
|
+
def report_daily():
|
|
245
|
+
reporter: ReportGenerator = _state["reporter"]
|
|
246
|
+
return {"report": reporter.generate_daily_report()}
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
@app.get("/report/weekly")
|
|
250
|
+
def report_weekly():
|
|
251
|
+
reporter: ReportGenerator = _state["reporter"]
|
|
252
|
+
return {"report": reporter.generate_weekly_report()}
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
@app.get("/report/monthly")
|
|
256
|
+
def report_monthly():
|
|
257
|
+
reporter: ReportGenerator = _state["reporter"]
|
|
258
|
+
return {"report": reporter.generate_monthly_report()}
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
@app.post("/report/send")
|
|
262
|
+
def report_send(body: SendReportRequest):
|
|
263
|
+
reporter: ReportGenerator = _state["reporter"]
|
|
264
|
+
t = body.type.lower()
|
|
265
|
+
if t == "daily":
|
|
266
|
+
sent = reporter.send_daily_report()
|
|
267
|
+
elif t == "weekly":
|
|
268
|
+
sent = reporter.send_weekly_report()
|
|
269
|
+
elif t == "monthly":
|
|
270
|
+
sent = reporter.send_monthly_report()
|
|
271
|
+
else:
|
|
272
|
+
raise HTTPException(400, f"Unknown report type '{body.type}'. Use daily, weekly, or monthly.")
|
|
273
|
+
return {"sent": sent, "type": t}
|