Files
ForexBot2/docs/architecture_diagram.py
T
Harwinder Sekhon 354778683e Remove Oanda auto-execution daemon/executor (not auto-trading)
Deletes oanda_daemon.py, oanda_executor.py, their 28 tests and the daemon-only
config keys (oanda.lot_size/quality_gate/max_hold_bars). The Oanda data
provider and credentials stay — it is the live candle feed. 402 tests.

Claude-Session: https://claude.ai/code/session_01FC4s6VCURFte4dub4xKxYL
2026-09-03 21:00:21 -04:00

191 lines
10 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""ForexBot2 architecture diagram → docs/architecture.png.
Regenerate after architecture changes: venv/bin/python docs/architecture_diagram.py
(matplotlib only — no graphviz on the dev box)."""
import os
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
from matplotlib.patches import FancyBboxPatch, FancyArrowPatch
W, H = 100.0, 100.0
fig, ax = plt.subplots(figsize=(16, 11.5))
ax.set_xlim(0, W); ax.set_ylim(0, H); ax.axis("off")
INK = "#263238"; MUT = "#607D8B"; ARR = "#455A64"; HAUL = "#7B8FA1"
def box(x, y, w, h, title, lines, fc, ec, title_fs=8.2, body_fs=6.6, lw=1.3):
ax.add_patch(FancyBboxPatch((x, y), w, h,
boxstyle="round,pad=0.35,rounding_size=0.9",
facecolor=fc, edgecolor=ec, linewidth=lw, zorder=2))
cy = y + h - 1.9
ax.text(x + w / 2, cy, title, ha="center", va="center", fontsize=title_fs,
fontweight="bold", color=INK, zorder=3)
cy -= 2.3
for ln in lines:
ax.text(x + w / 2, cy, ln, ha="center", va="center", fontsize=body_fs,
color=INK, zorder=3)
cy -= 1.85
def band(x, y, w, h, label, color):
ax.add_patch(FancyBboxPatch((x, y), w, h,
boxstyle="round,pad=0.3,rounding_size=1.2",
facecolor="none", edgecolor=color, linewidth=1.0,
linestyle=(0, (4, 3)), zorder=1))
ax.text(x + 0.9, y + h - 1.4, label, ha="left", va="center",
fontsize=7.2, fontweight="bold", color=color, zorder=3)
def arrow(p1, p2, color=ARR, lw=1.5, style="-|>", conn="arc3,rad=0.0",
label=None, lx=0, ly=0, ls="solid", fs=6.4, rot=0):
ax.add_patch(FancyArrowPatch(p1, p2, arrowstyle=style, mutation_scale=11,
color=color, linewidth=lw, linestyle=ls,
connectionstyle=conn, zorder=4))
if label:
mx, my = (p1[0] + p2[0]) / 2 + lx, (p1[1] + p2[1]) / 2 + ly
ax.text(mx, my, label, fontsize=fs, color=MUT, ha="center",
va="center", zorder=5, rotation=rot,
bbox=dict(boxstyle="round,pad=0.15", fc="white", ec="none", alpha=0.9))
# ── title ────────────────────────────────────────────────────────────
ax.text(2, 98.4, "ForexBot2 — XAU/USD Signal-Intelligence Platform",
fontsize=15, fontweight="bold", color=INK, ha="left")
ax.text(2, 96.0, "one pipeline, four delivery surfaces · closed bars only · live == backtest parity",
fontsize=8.5, color=MUT, ha="left")
EXT_FC, EXT_EC = "#ECEFF1", "#90A4AE"
DAT_FC, DAT_EC = "#E3F2FD", "#5C9BD1"
NWS_FC, NWS_EC = "#E0F2F1", "#3D9C8F"
COR_FC, COR_EC = "#FFFDF5", "#C9A227"
REP_FC, REP_EC = "#FFE9A8", "#B8860B"
DEL_FC, DEL_EC = "#E8F5E9", "#5FAF63"
BTS_FC, BTS_EC = "#EDE7F6", "#8E6BC6"
# ── external services (top band) ─────────────────────────────────────
band(1, 85.0, 98, 9.4, "EXTERNAL SERVICES", "#78909C")
box(2.5, 86.2, 15.5, 6.4, "Oanda v20 API",
["candles (live feed)", "practice orders"], EXT_FC, EXT_EC)
box(19.5, 86.2, 15.5, 6.4, "Twelve Data · Tiingo",
["registered fallbacks", "(Tiingo: tick stream)"], EXT_FC, EXT_EC)
box(36.5, 86.2, 15.5, 6.4, "Forex Factory",
["economic calendar", "MyFXBook RSS fallback"], EXT_FC, EXT_EC)
box(53.5, 86.2, 12.5, 6.4, "News RSS ×4",
["gold / macro", "headlines"], EXT_FC, EXT_EC)
box(67.5, 86.2, 13.5, 6.4, "DeepSeek API",
["V4-Flash LLM", "thinking: disabled"], EXT_FC, EXT_EC)
box(82.5, 86.2, 15, 6.4, "Telegram API",
["owner bot +", "subscriber channel"], EXT_FC, EXT_EC)
# ── data & context layer ─────────────────────────────────────────────
box(2.5, 69.8, 33, 11.0, "providers/ (BaseDataProvider)",
["single-flight fetch · TTL+boundary cache",
"forming-candle DROP · validate/clean",
"paginated fetch_range · optional tick stream",
"active feed knob: data_providers.active = oanda"],
DAT_FC, DAT_EC)
box(38.5, 69.8, 33, 11.0, "news_engine.py + llm_client.py",
["calendar events + keyword sentiment (gold-inverse)",
"DeepSeek enrichment: batch headline scores,",
"event gold-direction, market blurb — display-only,",
"keyword fallback, daily request cap"],
NWS_FC, NWS_EC)
box(74.5, 69.8, 23, 11.0, "app_config.py",
["credentials & deployment:",
"env → config.json → refuse",
"(no hardcoded fallbacks)",
".env loaded at import"],
"#FAFAFA", "#9E9E9E")
# ── signal core container ────────────────────────────────────────────
ax.add_patch(FancyBboxPatch((2.5, 40.5), 95, 26.5,
boxstyle="round,pad=0.35,rounding_size=1.2",
facecolor=COR_FC, edgecolor=COR_EC, linewidth=1.6, zorder=1))
ax.text(50, 65.2, "SIGNAL CORE — runs identically in live scans and backtests (closed bars only)",
ha="center", fontsize=8.4, fontweight="bold", color="#8a6d1a", zorder=3)
box(4.5, 51.8, 28, 12.0, "engine.py — TechnicalEngine",
["9 scored indicators + ATR context:",
"EMA · RSI · MACD · BB · ADX · ROC ·",
"Keltner · divergence · candlestick",
"regime → adaptive weights → composite"],
"white", COR_EC)
box(4.5, 42.0, 28, 8.4, "pair_signals/xauusd.py — factory",
["ALWAYS used: config.json overlay +",
"validated per-TF deltas (hour blocks, trail…)"],
"white", COR_EC)
box(35.5, 42.0, 60, 20.7, "signal_aggregator.py — analyze_pair → analyze_from_dataframes",
["multi-TF agreement (M5→[M15,H1] · M15→[M5,H1] · H1→[M15,H4] · H4→[H1,D1])",
"→ news context (warn-only by default) → diminishing-decay penalties",
"→ exhaustion hard block → quality grade A+…F",
"→ ordered post-grade gate chain (session · XAU SELL vol/hour · regime · M5 noise)",
"→ setup generators: XAU ATR (A+/A) · generic swing · XAU_PB · XAU_CP",
"→ entry · SL · TP1/TP2 · trailing plan"],
"white", COR_EC, body_fs=6.9)
# ── SignalReport hub ─────────────────────────────────────────────────
box(31, 33.2, 38, 5.2, "SignalReport",
["signal (−2…+2) · grade · trade setup · reasons · news summary"],
REP_FC, REP_EC, title_fs=8.8)
# ── delivery surfaces ────────────────────────────────────────────────
band(1, 13.2, 98, 17.2, "DELIVERY SURFACES", "#4C8F50")
box(2.5, 15.0, 12, 12.6, "main.py",
["CLI report", "--detail", "--json"], DEL_FC, DEL_EC)
box(42.5, 15.0, 31, 12.6, "dashboard_publisher.py → Apache",
["independent daemon · scans at candle closes",
"atomic publish to /var/www/trade:",
"dashboard.html · snapshot.json · news.json",
"+ news_summary.php — on-demand AI",
"gold-impact summary (whitelist · cache · cap)"],
DEL_FC, DEL_EC)
box(76, 15.0, 21.5, 12.6, "telegram_bot.py",
["candle-boundary auto-scans", "quality threshold + dedup",
"alerts + LLM context line", "WS level-breach monitor"],
DEL_FC, DEL_EC)
# ── backtest / QA ────────────────────────────────────────────────────
box(2.5, 3.2, 71.8, 8.0, "backtest/gpu_backtest.py — bar-by-bar replay through the SAME analyze_from_dataframes",
["realism (results-critical): next-open entries · raw-path resolution · spread charged · SL-first ties · close-only trail",
"CuPy GPU / NumPy fallback · parquet cache · fed by providers.fetch_range (news off)"],
BTS_FC, BTS_EC)
box(77.5, 3.2, 20, 8.0, "test_all.py",
["359 tests · 22 modules", "no network (mocked)"], "#FAFAFA", "#9E9E9E")
# ── arrows: short structural hops (solid dark) ───────────────────────
arrow((10, 86.2), (14, 81.2), label="OHLCV", lx=-3.4, ly=0.2) # oanda → providers
arrow((27, 86.2), (24, 81.2), ls=(0, (3, 2)), lw=1.1) # fallbacks → providers
arrow((44, 86.2), (48, 81.2)) # FF → news
arrow((59.5, 86.2), (56, 81.2)) # RSS → news
arrow((72, 86.2), (62, 81.2), label="LLM", lx=3.4, ly=0.6) # deepseek → news
arrow((19, 69.8), (19, 64.2), label="DataFrames (UTC, closed bars)", lx=0.2, ly=0.9)
arrow((33.5, 57.5), (35.5, 55.5)) # engine → aggregator
arrow((32.5, 46.0), (35.5, 48.0)) # factory → aggregator
arrow((55, 69.8), (55, 63.1), label="warn-only context", lx=0, ly=1.0)
arrow((86, 69.8), (86, 67.3), ls=(0, (3, 2)), lw=1.1) # config → core
arrow((50, 40.5), (50, 38.8)) # core → report
arrow((36, 34.5), (8.5, 27.95), conn="arc3,rad=0.12") # report → cli
arrow((60, 33.2), (58, 27.95)) # report → dashboard
arrow((66, 34.5), (86.5, 27.95), conn="arc3,rad=-0.12") # report → telegram
# ── long-haul edges (thin dashed, routed through gutters) ────────────
# telegram bot ↔ Telegram API — right-margin arc, clear of app_config
arrow((95, 86.2), (95, 27.95), conn="arc3,rad=0.15", color=HAUL,
lw=1.2, ls=(0, (4, 2)))
ax.text(99.2, 57, "alerts / commands", fontsize=6.4, color=MUT,
rotation=90, ha="center", va="center")
# browser click → news_summary.php → DeepSeek
arrow((66, 27.95), (73, 86.2), conn="arc3,rad=-0.18", color=HAUL,
lw=1.2, ls=(0, (4, 2)),
label="on-demand AI summary\n(browser click → PHP)", lx=2.5, ly=11.6)
# providers.fetch_range → backtester — far-left margin channel
arrow((1.5, 69.8), (1.5, 11.0), color=BTS_EC, lw=1.1, ls=(0, (3, 2)),
label="fetch_range\n(history)", lx=3.6, ly=-1.2)
# backtester replays the signal core — right gutter between boxes
arrow((74.0, 11.6), (74.9, 40.3), color=HAUL, lw=1.3)
ax.text(81.8, 38.9, "replays the exact signal code", fontsize=6.4,
color=MUT, ha="center", va="center", zorder=5,
bbox=dict(boxstyle="round,pad=0.15", fc="white", ec="none", alpha=0.9))
out = os.path.join(os.path.dirname(os.path.abspath(__file__)), "architecture.png")
fig.savefig(out, dpi=200, bbox_inches="tight", facecolor="white")
print(f"saved {out}")