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:
222
gen/strategii/mdsrt_exemplu.py
Normal file
222
gen/strategii/mdsrt_exemplu.py
Normal file
@@ -0,0 +1,222 @@
|
||||
"""Diagrama din `gen/strategii/mdsrt_exemplu.spec.txt` (TIP: PRET).
|
||||
|
||||
Exemplul real din Q&A 15.07.2025 (`09_QA/24_2025-07-15.md`): SPY pe 5 min,
|
||||
double bottom cu al doilea picior mai jos (MDSRT pe 5 min, suportul = pivotul
|
||||
daily). Downtrend → picior 1 la 622.40 → revenire → picior 2 la 621.40 (rupe
|
||||
pivotul 622.00) → Buy Hammer care închide la 622.60, înapoi peste pivot. După
|
||||
trigger, un plan construit de mine (marcat) duce trade-ul până la TP1.
|
||||
|
||||
Reale din curs: nivelurile 622.40 / 622.00 / 621.40 / 622.60 și cele 8 bare
|
||||
dintre picioare. Construcția mea: entry 622.60, SL 621.30, TP1 623.90 (1:1) —
|
||||
materialul nu le dă (transcrierea planului se întrerupe). Volumele sunt
|
||||
relative, alese de generator.
|
||||
|
||||
Rulează:
|
||||
python3 gen/strategii/mdsrt_exemplu.py
|
||||
Scrie lângă el `mdsrt_exemplu.svg`.
|
||||
"""
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, "/workspace/atm-curs-dl")
|
||||
from svg_candles import Chart, Level, Mark, MA, from_closes # noqa: E402
|
||||
|
||||
OUT = Path(__file__).resolve().parent / "mdsrt_exemplu.svg"
|
||||
|
||||
GRAY, RED, GREEN, BLUE, ORANGE = "#777", "#c00", "#188a3e", "#1a5fb4", "#b06000"
|
||||
|
||||
# ── nivelurile reale din curs + planul construit (marcat) ──────────────────
|
||||
PIVOT = 622.00 # pivot daily, suportul
|
||||
LEG1 = 622.40 # swing low picior 1
|
||||
LEG2 = 621.40 # picior 2 (mai jos, rupe pivotul)
|
||||
ENTRY = 622.60 # close-ul Buy Hammer (construit)
|
||||
SL = 621.30 # 0.10 sub low-ul piciorului 2 (construit)
|
||||
TP1 = 623.90 # entry + risc = 1:1 (construit)
|
||||
|
||||
# ── seria: 30 de close-uri, exact ca în specificație ───────────────────────
|
||||
CLOSES = [
|
||||
627.00, 626.40, 626.70, 626.00, 625.40, 624.80, 624.40, 623.80, # 0-7
|
||||
623.20, 622.80, # 8-9
|
||||
622.40, # 10 = picior 1 (swing low 622.40)
|
||||
622.70, 623.10, 623.50, 623.20, 622.90, 622.60, 622.30, # 11-17
|
||||
622.60, # 18 = picior 2: low 621.40, Buy Hammer care închide 622.60
|
||||
623.00, 623.40, 623.90, # 19-21, 21 = TP1 1:1 (construit)
|
||||
624.20, 624.00, 624.40, 624.80, 625.20, 625.60, # 22-27
|
||||
625.30, 625.70, # 28-29
|
||||
]
|
||||
assert len(CLOSES) == 30, len(CLOSES)
|
||||
|
||||
# wick-uri (sus, jos): alternanță naturală; excepțiile sunt piciorul 1 și
|
||||
# hammer-ul de la piciorul 2 (cerute exact de specificație).
|
||||
WICKS = [
|
||||
(0.10, 0.10), (0.12, 0.14), (0.15, 0.12), (0.18, 0.15), (0.20, 0.16),
|
||||
(0.18, 0.14), (0.16, 0.13), (0.15, 0.12), (0.14, 0.12), (0.12, 0.10),
|
||||
(0.15, 0.05), # 10 = picior 1: low 622.40 - 0.05 = 622.35
|
||||
(0.12, 0.12), (0.15, 0.14), (0.18, 0.15), (0.16, 0.13), (0.14, 0.12),
|
||||
(0.12, 0.10), (0.10, 0.12),
|
||||
(0.10, 0.90), # 18 = hammer: high 622.70, low 621.40
|
||||
(0.12, 0.12), (0.14, 0.12), (0.15, 0.13),
|
||||
(0.16, 0.15), (0.14, 0.12), (0.15, 0.13), (0.16, 0.14), (0.17, 0.14),
|
||||
(0.18, 0.15), (0.14, 0.12), (0.15, 0.13),
|
||||
]
|
||||
assert len(WICKS) == 30, len(WICKS)
|
||||
|
||||
# volum relativ (ales de generator; materialul nu dă volumul pentru exemplu):
|
||||
# scade pe downtrend, spike de absorbție pe hammer, higher low vs piciorul 1.
|
||||
VOLS = [
|
||||
58, 56, 54, 52, 50, 49, 48, 47, 46, 45, # 0-9
|
||||
44, # 10 = picior 1
|
||||
43, 42, 41, 40, 39, 38, 39, # 11-17
|
||||
100, # 18 = hammer, maximul seriei
|
||||
62, 58, 54, # 19-21
|
||||
52, 50, 49, 48, 47, 46, 45, 44, # 22-29
|
||||
]
|
||||
assert len(VOLS) == 30, len(VOLS)
|
||||
|
||||
cds = from_closes(CLOSES, wicks=WICKS, vols=VOLS, start=627.00)
|
||||
|
||||
# ── assert-urile din specificație ──────────────────────────────────────────
|
||||
# 1. picior 1: low 622.35, close 622.40
|
||||
assert abs(cds[10].l - 622.35) < 1e-9, cds[10].l
|
||||
assert cds[10].c == 622.40, cds[10].c
|
||||
|
||||
# 2. picior 2 mai jos (621.40 < 622.35) și rupe pivotul daily 622.00
|
||||
assert abs(cds[18].l - LEG2) < 1e-9, cds[18].l
|
||||
assert cds[18].l < cds[10].l, (cds[18].l, cds[10].l)
|
||||
assert cds[18].l < PIVOT, cds[18].l
|
||||
|
||||
# 3. trigger-ul închide înapoi peste pivot
|
||||
assert cds[18].c == 622.60, cds[18].c
|
||||
assert cds[18].c > PIVOT, cds[18].c
|
||||
|
||||
# 4. 8 candele între picioare (peste minimul de 5)
|
||||
assert 18 - 10 == 8
|
||||
|
||||
# 5. hammer: coadă lungă în jos (0.90) > corp (0.30)
|
||||
corp = abs(cds[18].o - cds[18].c)
|
||||
coada = min(cds[18].o, cds[18].c) - cds[18].l
|
||||
assert abs(corp - 0.30) < 1e-9, corp
|
||||
assert abs(coada - 0.90) < 1e-9, coada
|
||||
assert coada > corp, (coada, corp)
|
||||
|
||||
# 6. entry = close-ul trigger-ului (construit), SL 0.10 sub low-ul piciorului 2
|
||||
assert cds[18].c == ENTRY
|
||||
assert abs(cds[18].l - SL - 0.10) < 1e-9, (cds[18].l, SL)
|
||||
risk = ENTRY - SL
|
||||
assert abs(risk - 1.30) < 1e-9, risk
|
||||
|
||||
# 7. TP1 simetric 1:1
|
||||
assert abs((ENTRY - SL) - (TP1 - ENTRY)) < 1e-9
|
||||
|
||||
# 8. după trigger, primul close care atinge 623.90 e bara 21 (nu mai devreme).
|
||||
# Seria pornește la 627.00, deci „atinge" se citește de jos în sus, din zona
|
||||
# picioarelor: barele 10-20 rămân sub TP1, bara 21 îl atinge.
|
||||
assert all(c.c < TP1 for c in cds[10:21]), [c.c for c in cds[10:21]]
|
||||
assert max(c.h for c in cds[19:21]) < TP1, [c.h for c in cds[19:21]]
|
||||
assert cds[21].c == TP1, cds[21].c
|
||||
|
||||
# 9. divergență: lower low pe preț, higher low pe volum
|
||||
assert cds[18].l < cds[10].l, (cds[18].l, cds[10].l)
|
||||
assert VOLS[18] > VOLS[10], (VOLS[18], VOLS[10])
|
||||
assert VOLS[18] == max(VOLS)
|
||||
|
||||
# 10. fiecare open == close-ul barei anterioare
|
||||
for a, b in zip(cds, cds[1:]):
|
||||
assert a.c == b.o, (a.c, b.o)
|
||||
|
||||
# ── geometria, ca să așez marks-urile fără suprapunere de text ─────────────
|
||||
SCALE, GAP, LEFT, WIDTH, BODY_W, VOL_H = 62, 15, 44, 640, 9, 70
|
||||
|
||||
|
||||
def cx(i: int) -> float:
|
||||
return LEFT + i * GAP
|
||||
|
||||
|
||||
levels = [
|
||||
Level(PIVOT, "pivot daily 622.00", ORANGE, label_at="left"),
|
||||
Level(SL, "SL 621.30 (construit)", RED, label_at="left"),
|
||||
Level(ENTRY, "intrare 622.60 (construit)", GREEN, label_at="left"),
|
||||
Level(TP1, "TP1 623.90 = 1:1 (construit)", GREEN, label_at="left"),
|
||||
Level(LEG1, "picior 1 = 622.40", BLUE, label_at="right"),
|
||||
]
|
||||
|
||||
# dy-uri calculate manual ca etichetele „below" să nu calce linia pivotului
|
||||
# (bara 10) și ca „hammer" să nu calce eticheta intrării (bara 18).
|
||||
marks = [
|
||||
Mark(10, "picior 1 = 622.40", BLUE, "below", dy=15),
|
||||
Mark(18, "picior 2 = 621.40 (mai jos) · Buy Hammer", RED, "below"),
|
||||
Mark(18, "hammer închide 622.60, înapoi peste pivot", GREEN, "above", dy=-20),
|
||||
]
|
||||
|
||||
ch = Chart(
|
||||
candles=cds, levels=levels, marks=marks,
|
||||
mas=[MA(20, ORANGE, dash="4 3", text="SMA-20")],
|
||||
scale=SCALE, gap=GAP, body_w=BODY_W, left=LEFT, width=WIDTH, vol_h=VOL_H,
|
||||
vol_label="volum",
|
||||
title="SPY — 5 min (Q&A 15.07.2025; intrare/SL/TP construite)",
|
||||
caption="",
|
||||
)
|
||||
svg = ch.render()
|
||||
|
||||
# ── legenda de onestitate (assert 11): ce e real, ce e construit ───────────
|
||||
legend = [
|
||||
"Legendă: nivelurile 622.40 / 622.00 / 621.40 / 622.60 și cele 8 candele dintre",
|
||||
"picioare sunt reale, din curs. Intrarea 622.60, SL-ul 621.30 și TP-ul 623.90",
|
||||
"sunt construcția mea — materialul nu le dă.",
|
||||
]
|
||||
m = re.search(r'viewBox="0 0 (\d+) ([0-9.]+)"', svg)
|
||||
W, H0 = int(m.group(1)), float(m.group(2))
|
||||
legend_svg = [f'<text x="24" y="{H0 + 16 + 17 * i:.1f}" fill="#555">{t}</text>'
|
||||
for i, t in enumerate(legend)]
|
||||
new_h = H0 + 16 + 17 * (len(legend) - 1) + 14
|
||||
svg = re.sub(r'(viewBox="0 0 \d+ )[0-9.]+(")',
|
||||
lambda mm: mm.group(1) + f"{new_h:.0f}" + mm.group(2), svg, count=1)
|
||||
svg = svg.replace("\n</svg>", "\n " + "\n ".join(legend_svg) + "\n</svg>")
|
||||
|
||||
assert svg.count("\n\n") == 0, "linie goală în SVG rupe blocul din markdown"
|
||||
assert "sunt reale, din curs" in svg and "construcția mea" in svg, \
|
||||
"legenda nu spune explicit ce e real și ce e construit"
|
||||
|
||||
# toate etichetele cerute de specificație sunt prezente
|
||||
for eticheta in ("pivot daily 622.00", "picior 1 = 622.40",
|
||||
"picior 2 = 621.40 (mai jos) · Buy Hammer",
|
||||
"hammer închide 622.60, înapoi peste pivot",
|
||||
"intrare 622.60 (construit)", "SL 621.30 (construit)",
|
||||
"TP1 623.90 = 1:1 (construit)"):
|
||||
assert eticheta in svg, f"lipsește eticheta: {eticheta!r}"
|
||||
assert "SPY — 5 min" in svg, "lipsește titlul"
|
||||
|
||||
# 1:1 desenat la 1:1 (STYLE.md: raportul din preț = raportul din pixeli)
|
||||
ys = {p: ch._y(p) for p in (SL, ENTRY, TP1)}
|
||||
assert abs((ys[SL] - ys[ENTRY]) - (ys[ENTRY] - ys[TP1])) < 1e-6, ys
|
||||
|
||||
# ── verificare: fiecare etichetă e în pânză și nu se suprapune alta ────────
|
||||
m = re.search(r'viewBox="0 0 (\d+) ([0-9.]+)"', svg)
|
||||
W, H = int(m.group(1)), float(m.group(2))
|
||||
texte = re.findall(r'<text x="([\d.]+)" y="([\d.]+)"([^>]*)>([^<]*)</text>', svg)
|
||||
cutii = []
|
||||
for xs, ys, attrs, continut in texte:
|
||||
x, yv = float(xs), float(ys)
|
||||
w = len(continut) * (7.44 if "bold" in attrs else 6.72)
|
||||
if 'text-anchor="end"' in attrs:
|
||||
x0 = x - w
|
||||
elif 'text-anchor="middle"' in attrs:
|
||||
x0 = x - w / 2
|
||||
else:
|
||||
x0 = x
|
||||
assert -2 <= x0 and x0 + w <= W + 2, f"etichetă în afara pânzei: {continut!r}"
|
||||
assert 0 <= yv <= H, f"etichetă în afara înălțimii: {continut!r}"
|
||||
cutii.append((x0, x0 + w, yv, continut))
|
||||
for i in range(len(cutii)):
|
||||
for j in range(i + 1, len(cutii)):
|
||||
a, b = cutii[i], cutii[j]
|
||||
if abs(a[2] - b[2]) < 13 and a[0] < b[1] - 2 and b[0] < a[1] - 2:
|
||||
raise AssertionError(f"texte suprapuse: {a[3]!r} / {b[3]!r}")
|
||||
|
||||
OUT.write_text(svg, encoding="utf-8")
|
||||
print(f"scris {OUT.name}: {len(cds)} candele | picior 1 {cds[10].l:.2f} (close "
|
||||
f"{cds[10].c:.2f}) | picior 2 {cds[18].l:.2f} (mai jos) | hammer închide "
|
||||
f"{cds[18].c:.2f} | intrare {ENTRY:.2f} | SL {SL:.2f} (risc {risk:.2f}) | "
|
||||
f"TP1 {TP1:.2f} = {(TP1 - ENTRY) / risk:.2f}:1 la bara 21 | "
|
||||
f"vol[18]={VOLS[18]} > vol[10]={VOLS[10]}")
|
||||
Reference in New Issue
Block a user