strategii: primele 30 de diagrame, cu generatoarele lor

Cate un agent grafician per diagrama, fiecare cu generatorul si SVG-ul lui.
Graficele de pret trec prin svg_candles, cu assert-urile din specificatie
scrise in generator — verifica structura, nu doar ca ruleaza: ca al doilea
picior e mai jos, ca intrarea e close-ul barei de confirmare, ca 1:1 e exact.

Verificare prin sondaj pe buy_doji_ansamblu, cu screenshot citit: serie
continua de 32 de candele, panou de volum, MA(20), doji-ul si ieșirea pe
trail etichetate, cifrele identice cu specificatia.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ThN7ixZrowca8aTGrK1MUJ
This commit is contained in:
Claude Agent
2026-09-18 19:22:13 +00:00
parent af448dda69
commit 01599d2e9d
60 changed files with 9035 additions and 0 deletions

View File

@@ -0,0 +1,188 @@
#!/usr/bin/env python3
"""Buy Doji — fereastra de 3 candele: DA (valid) vs. NU (expirat).
Citește `gen/strategii/buy_doji_fereastra.spec.txt` și scrie
`gen/strategii/buy_doji_fereastra.svg`. Două panouri mici, unul sub altul,
cu aceeași serie până la doji: sus confirmarea vine la bara +2 (în fereastră),
jos niciuna din +1..+3 nu confirmă, iar a 4-a închide sub low-ul dojiului.
Toate cifrele din desen vin din specificație: doji h 50.10 / l 49.20,
closes-urile DA/NU, pragul 50.10, low-ul 49.20. Seriile sunt construite
(open = close-ul barei anterioare, mai puțin open-ul primei bare, start-ul),
ca nivelurile afirmate să iasă exact din serie.
Rulare: python3 gen/strategii/buy_doji_fereastra.py
"""
import re
import sys
from pathlib import Path
sys.path.insert(0, "/workspace/atm-curs-dl")
from svg_candles import Chart, Level, Mark, from_closes # noqa: E402
OUT = Path(__file__).with_suffix(".svg")
W = 460
VGAP = 34
SCALE = 45.0 # px / unitate, aceeași pe ambele panouri (comparație)
# --- seriile din specificație ----------------------------------------------
CLOSES_DA = [52.20, 51.40, 50.60, 49.65,
49.60, # doji, h 50.10 / l 49.20
49.90, # +1 — sub 50.10
50.40, # +2 — peste 50.10 => confirmă
50.80]
CLOSES_NU = [52.20, 51.40, 50.60, 49.65,
49.60, # doji, h 50.10 / l 49.20
49.95, # +1
50.05, # +2
49.85, # +3
49.15] # a 4-a — sub low-ul dojiului
assert len(CLOSES_DA) == 8 and len(CLOSES_NU) == 9
assert CLOSES_DA[:5] == CLOSES_NU[:5]
HIGH_DOJI, LOW_DOJI = 50.10, 49.20
# wick-uri: (up, down). Doji-ul (index 4) primește mecioni ca să iasă exact
# h=50.10 / l=49.20; restul au mecioni mici, egali.
wicks_da = [(0.15, 0.15)] * len(CLOSES_DA)
wicks_nu = [(0.15, 0.15)] * len(CLOSES_NU)
wicks_da[4] = wicks_nu[4] = (0.45, 0.40)
START = CLOSES_DA[0] + 0.15 # prima bară roșie, open > close
cds_da = from_closes(CLOSES_DA, wicks=wicks_da, start=START)
cds_nu = from_closes(CLOSES_NU, wicks=wicks_nu, start=START)
doji_da, doji_nu = cds_da[4], cds_nu[4]
entry = cds_da[6].c # intrare la close-ul confirmării
# --- assert-urile din specificație -----------------------------------------
# doji-ul iese exact la nivelurile afirmate
assert abs(doji_da.h - HIGH_DOJI) < 1e-9 and abs(doji_da.l - LOW_DOJI) < 1e-9, \
f"doji DA {doji_da.h}/{doji_da.l} != {HIGH_DOJI}/{LOW_DOJI}"
assert abs(doji_nu.h - HIGH_DOJI) < 1e-9 and abs(doji_nu.l - LOW_DOJI) < 1e-9, \
f"doji NU {doji_nu.h}/{doji_nu.l} != {HIGH_DOJI}/{LOW_DOJI}"
# 1. DA: primul close peste 50.10 DUPĂ doji e bara +2 (index 6), în fereastră;
# bara +1 nu confirmă
primul_da = next(i for i in range(5, len(cds_da)) if cds_da[i].c > HIGH_DOJI)
assert primul_da == 6, f"prima confirmare la index {primul_da}, nu 6 (+2)"
assert primul_da - 4 <= 3, "confirmarea nu e în fereastra de 3 candele"
assert abs(cds_da[5].c - 49.90) < 1e-9 and cds_da[5].c <= HIGH_DOJI, \
"bara +1 nu trebuie să confirme"
assert abs(cds_da[6].c - entry) < 1e-9 and abs(entry - 50.40) < 1e-9, \
"intrarea nu e close-ul de 50.40 al barei +2"
# 2. NU: niciun close din +1..+3 nu depășește 50.10; a 4-a închide sub 49.20
assert all(cds_nu[i].c <= HIGH_DOJI for i in (5, 6, 7)), \
"un close din +1..+3 ar confirma, fereastra n-ar fi expirat"
assert cds_nu[8].c < LOW_DOJI, f"close[+4]={cds_nu[8].c} nu e sub {LOW_DOJI}"
# 3. ambele doji: corp <= 10% din amplitudine
for d in (doji_da, doji_nu):
assert abs(d.o - d.c) <= 0.10 * (d.h - d.l), "bara 4 nu e doji"
# 4. aceeași serie până la doji (aceleași 5 bare)
ser_da = [(round(c.o, 9), round(c.h, 9), round(c.l, 9), round(c.c, 9)) for c in cds_da[:5]]
ser_nu = [(round(c.o, 9), round(c.h, 9), round(c.l, 9), round(c.c, 9)) for c in cds_nu[:5]]
assert ser_da == ser_nu, "panourile nu au aceeași serie până la doji"
# --- desen: două Chart-uri, lipite vertical într-un singur SVG -------------
def _panel(ch: Chart):
svg = ch.render()
m = re.search(r'viewBox="0 0 (\d+) ([\d.]+)"', svg)
body = svg[svg.index(">") + 1:svg.rindex("</svg>")]
return body, float(m.group(2))
# DA — confirmarea vine la +2
cds_da[5].note = "+1: close 49.90 — sub 50.10, nu confirmă"
cds_da[6].note = "+2: close 50.40 — peste 50.10, confirmă (intrare la close)"
ch_da = Chart(
candles=cds_da,
levels=[Level(HIGH_DOJI, "high doji 50.10", "#b06000", label_at="left"),
Level(LOW_DOJI, "low doji 49.20", "#c00", label_at="left")],
marks=[Mark(4, "doji", "#b06000", "above"),
Mark(6, f"confirmare +2 — intrare {entry:.2f}", "#188a3e", "above")],
scale=SCALE, gap=40, body_w=13, left=44, width=W,
title="DA — confirmare la +2, în fereastră",
)
# NU — nicio confirmare în 3 candele, apoi setup mort
cds_nu[5].note = "+1: 49.95; +2: 50.05; +3: 49.85 — toate sub 50.10"
cds_nu[8].note = "a 4-a: close 49.15 — sub low-ul dojiului 49.20, setup mort"
ch_nu = Chart(
candles=cds_nu,
levels=[Level(HIGH_DOJI, "high doji 50.10", "#b06000", label_at="left"),
Level(LOW_DOJI, "low doji 49.20", "#c00", label_at="left")],
marks=[Mark(4, "doji", "#b06000", "above"),
Mark(8, "a 4-a", "#c00", "above")],
scale=SCALE, gap=40, body_w=13, left=44, width=W,
title="NU — nicio confirmare în 3 candele",
)
body_da, h_da = _panel(ch_da)
body_nu, h_nu = _panel(ch_nu)
cap1 = "Serii construite: open = close-ul barei anterioare."
cap2 = "Doji: h 50.10 / l 49.20. Fereastra = maximum 3 candele după doji."
cap_top = h_da + VGAP + h_nu + 20
H = cap_top + 22
parts = [
f'<svg viewBox="0 0 {W} {H:.0f}" xmlns="http://www.w3.org/2000/svg" '
f'style="width:100%;height:auto;font:12px sans-serif">',
f'<g transform="translate(0,0)">{body_da}</g>',
f'<g transform="translate(0,{h_da + VGAP:.0f})">{body_nu}</g>',
f'<text x="24" y="{cap_top:.0f}" fill="#555">{cap1}</text>',
f'<text x="24" y="{cap_top + 17:.0f}" fill="#555">{cap2}</text>',
"</svg>",
]
svg = "\n ".join(parts)
assert "\n\n" not in svg, "linie goală în SVG"
assert "<" not in cap1 and "<" not in cap2
# --- verificarea citirii: etichete în panză, fără suprapuneri ---------------
TEXT = re.compile(r'<text x="([-\d.]+)" y="([-\d.]+)"([^>]*)>(.*?)</text>', re.S)
def _texts(body: str, y_off: float, tag: str):
out = []
for m in TEXT.finditer(body):
x, y = float(m.group(1)), float(m.group(2)) + y_off
attrs, s = m.group(3), m.group(4)
anchor = "start" # implicit în SVG
if 'text-anchor="end"' in attrs:
anchor = "end"
elif 'text-anchor="middle"' in attrs:
anchor = "middle"
bold = "bold" in attrs
w = len(s) * 12 * (0.62 if bold else 0.52)
left = x - w if anchor == "end" else (x - w / 2 if anchor == "middle" else x)
out.append((left, left + w, y, s, tag))
return out
labels = _texts(body_da, 0.0, "DA") + _texts(body_nu, h_da + VGAP, "NU")
for left, right, y, s, tag in labels:
assert left >= -2 and right <= W + 2, f"[{tag}] text iese din panză: {s!r}"
assert 0 <= y <= H, f"[{tag}] text în afara înălțimii: {s!r} (y={y:.0f})"
for i in range(len(labels)):
li, ri, yi, si, ti = labels[i]
for j in range(i + 1, len(labels)):
lj, rj, yj, sj, tj = labels[j]
if ti == tj and abs(yi - yj) < 12 and li < rj - 4 and lj < ri - 4:
raise AssertionError(f"[{ti}] texte suprapuse: {si!r} / {sj!r}")
print(f"DA: doji {doji_da.h:.2f}/{doji_da.l:.2f} +1 {cds_da[5].c:.2f} "
f"+2 {entry:.2f} (confirmă) bare {len(cds_da)}")
print(f"NU: +1 {cds_nu[5].c:.2f} +2 {cds_nu[6].c:.2f} +3 {cds_nu[7].c:.2f} "
f"a 4-a {cds_nu[8].c:.2f} (< {LOW_DOJI}) bare {len(cds_nu)}")
# 5. distanța în pixeli 50.10 -> 50.40 e vizibilă, cu scară comună
dist = ch_da._y(HIGH_DOJI) - ch_da._y(entry)
assert ch_da.scale == ch_nu.scale == SCALE, "scara nu e comună între panouri"
assert dist > 0 and dist > 10, f"distanța 50.10->50.40 = {dist:.1f}px, prea mică"
print(f"scris {OUT} ({W}x{H:.0f}); {len(labels)} etichete verificate; "
f"50.10->50.40 = {dist:.1f}px")
OUT.write_text(svg, encoding="utf-8")