strategii: cele trei diagrame ale anexei A3

Un agent per diagramă, din `.spec.txt`-urile scrise la sesiunea trecută.
`audit_diagrams` 0 pe toate trei; fiecare randată și privită ca PNG.

Două retușuri făcute de sesiunea principală, peste ce au raportat agenții:
- `intrarea_retest_ratat` folosea alte fitiluri la candelele 12 și 14 decât
  `intrarea_doua_variante`, deși cele două împart aceeași serie 0-15.
  Aliniate după `doua_variante`, care e scris direct din spec.
- `intrarea_cazul_lui` avea un `Mark` „suport weekly" sub linia de SL, care
  dubla eticheta nivelului și părea să eticheteze SL-ul. Scos.

Abaterile de la spec pe care le-au raportat agenții și le-am acceptat:
vârful din `cazul_lui` cade pe bara 32, nu 31 (în lista de close-uri din
spec acolo e 105.20), iar bara 33 deschide chiar la 105.20, deci cerința
„toate celelalte high-uri sub 105.20" nu se poate îndeplini strict.

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-19 17:29:50 +00:00
parent 9348b1ec1b
commit edc270417d
9 changed files with 1798 additions and 4 deletions

View File

@@ -0,0 +1,155 @@
"""Diagrama din `gen/strategii/intrarea_cazul_lui.spec.txt` (TIP: PRET).
Trade-ul userului din A3 §2.1, reconstruit din descriere: downtrend pe 15 min
până la suportul weekly 100.00, buy momentum pe o verde mare, intrare la
close 102.10, imediat o roșie mare care acoperă 83% din corpul verzii, apoi
trendul. Ieșirea userului la 102.60, înainte de TP1; vârful 105.20 după.
Pe același grafic, nivelul 1/2 = 101.20 unde s-ar fi umplut varianta B.
Toate cifrele sunt CONSTRUITE (fără ticker); mediile SMA 5 / SMA 10 sunt
ilustrative — lecția 10 nu dă lungimile reale.
Rulează:
python3 gen/strategii/intrarea_cazul_lui.py
Scrie lângă el `intrarea_cazul_lui.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 / "intrarea_cazul_lui.svg"
GRAY, RED, GREEN, BLUE, ORANGE = "#777", "#c00", "#188a3e", "#1a5fb4", "#b06000"
MA5_C, MA10_C = "#8a3ea8", "#2a8a8a"
SUPORT, SL, ENTRY, HALF, TP1 = 100.00, 99.90, 102.10, 101.20, 104.30
CLOSES = [
103.80, 103.50, 103.60, 103.20, 102.90, 103.00, 102.60, 102.30, 101.90, 101.40, 101.00, # 0-10
100.70, 100.45, 100.30, # 11-13 la suport
102.10, # 14 buy momentum
100.60, # 15 roșie mare
101.10, 101.60, 101.90, # 16-18
102.60, # 19 ieșirea userului
102.90, 102.70, 103.20, 103.60, 103.40, 103.90, 104.20, 104.10, 104.60,
104.90, 104.70, 105.00, 105.20, 105.00, # 20-33
]
assert len(CLOSES) == 34, len(CLOSES)
# (sus, jos); excepțiile fixează exact low-urile/high-urile din specificație
WICKS = [(0.12, 0.10)] * 34
WICKS[10] = (0.10, 0.20) # low 100.80
WICKS[11] = (0.10, 0.30) # low 100.40
WICKS[12] = (0.10, 0.55) # low 99.90, wick sub 100.00
WICKS[13] = (0.10, 0.20) # low 100.10
WICKS[14] = (0.15, 0.10) # h 102.25, l 100.20
WICKS[15] = (0.10, 0.10) # h 102.20, l 100.50
# spec zice „candela 31 (close 105.20)", dar în lista lui 105.20 e la indexul 32;
# vârful e pus pe bara 32, unde e de fapt close-ul 105.20.
WICKS[31] = (0.10, 0.10) # h 105.10 < 105.20
WICKS[32] = (0.00, 0.10) # h 105.20 = maximul
WICKS[33] = (0.00, 0.10) # open 105.20 → high nu poate fi sub 105.20
cds = from_closes(CLOSES, wicks=WICKS, start=104.00)
R = lambda x: round(x, 2) # noqa: E731
# ── assert-urile din specificație ──────────────────────────────────────────
# 1. open == close anterior
for a, b in zip(cds, cds[1:]):
assert a.c == b.o, (a.c, b.o)
g, r = cds[14], cds[15]
assert (R(g.o), R(g.c), R(g.h), R(g.l)) == (100.30, 102.10, 102.25, 100.20), g
assert (R(r.o), R(r.c), R(r.h), R(r.l)) == (102.10, 100.60, 102.20, 100.50), r
assert (R(cds[10].l), R(cds[11].l), R(cds[12].l), R(cds[13].l)) == (100.80, 100.40, 99.90, 100.10)
# 2. SL = low 4 candele; nici verdea, nici roșia nu îl ating
assert R(min(c.l for c in cds[10:14])) == SL
assert g.l > SL and r.l > SL
# 3. culori
assert g.c > g.o and r.c < r.o
# 4. roșia acoperă 83% din corpul verzii
acop = (r.o - r.c) / (g.c - g.o)
assert abs(acop - 0.83) <= 0.01, acop
# 5. 1/2 din corp, atins de roșie
half = (g.o + g.c) / 2
assert R(half) == HALF and r.l <= half, (half, r.l)
# 6. R-urile
risc = ENTRY - SL
assert R(risc) == 2.20 and R(ENTRY + risc) == TP1
flot = (r.c - ENTRY) / risc
iesire = (cds[19].c - ENTRY) / risc
varf = (105.20 - ENTRY) / risc
assert (R(flot), R(iesire), R(varf)) == (-0.68, 0.23, 1.41), (flot, iesire, varf)
# 7. mediile mobile
sma = lambda n, i: sum(CLOSES[i + 1 - n:i + 1]) / n # noqa: E731
assert g.c > sma(5, 14) and g.c > sma(10, 14)
assert r.c < sma(5, 15) and r.c < sma(10, 15)
assert (R(sma(5, 15)), round(sma(10, 15), 3)) == (100.83, 101.335)
# 8. ieșirea înainte de TP1; vârful
assert cds[19].c < TP1 and all(c.h < TP1 for c in cds[15:20])
assert R(max(c.h for c in cds)) == 105.20 and R(cds[32].h) == 105.20 == cds[32].c
assert all(c.h < 105.20 - 1e-9 for c in cds[:32])
# varianta B (doar în legendă)
assert R(g.c - (g.c - g.o) / 3) == 101.50
assert R(HALF - SL) == 1.30 and R(HALF + (HALF - SL)) == 102.50
levels = [
Level(TP1, "TP1 104.30 (1:1)", BLUE, label_at="left"),
Level(ENTRY, "intrare la close 102.10", GREEN),
Level(HALF, "1/2 = 101.20 (B s-ar fi umplut aici)", ORANGE, dash="2 3"),
Level(SUPORT, "suport weekly 100.00", GRAY, label_at="left"),
Level(SL, "", RED), # eticheta SL sub linie, mai jos (0.10 sub suport)
Level(99.50, "", "none", dash=""), # rezervă loc sub SL pentru etichete
]
marks = [
Mark(14, "▲ buy momentum", GREEN, "above", dy=-20), # peste SMA 10
Mark(32, "vârf 105.20 = +1.41R", GRAY, "above"),
]
ch = Chart(
candles=cds, levels=levels, marks=marks,
mas=[MA(5, MA5_C), MA(10, MA10_C, dash="5 3")],
scale=60, gap=16, body_w=10, left=44, width=640,
title="Exemplu construit (fără ticker) — 15 min, suport weekly",
)
svg = ch.render()
# barele 15 și 19 au candele lipite în stânga/dreapta: un Mark centrat le-ar
# tăia, așa că eticheta lor stă în golul de lângă candelă (roșia: ▲ chiar sub
# low-urile barelor 16+, peste suport; ieșirea: deasupra barelor 14-19,
# terminată în bara 19, cu linie de legătură).
y19 = ch._y(103.30)
extra = [
f'<text x="{ch._cx(15) - 5:.0f}" y="{ch._y(100.20):.1f}" fill="{RED}" '
f'style="font-weight:bold">▲ roșie mare: −0.68R flotant</text>',
f'<text x="{ch._cx(19) + 4:.0f}" y="{y19:.1f}" fill="{ORANGE}" text-anchor="end" '
f'style="font-weight:bold">ieși aici: 102.60 = +0.23R</text>',
f'<line x1="{ch._cx(19):.1f}" y1="{y19 + 4:.1f}" x2="{ch._cx(19):.1f}" '
f'y2="{ch._y(cds[19].h) - 3:.1f}" stroke="{ORANGE}" stroke-width="1"/>',
f'<text x="30" y="{ch._y(SL) + 15:.1f}" fill="{RED}">SL 99.90 (low 4 candele)</text>',
f'<text x="24" y="{ch.top + 12:.1f}" fill="#555">'
f'<tspan fill="{MA5_C}">── SMA 5</tspan> / <tspan fill="{MA10_C}">- - SMA 10</tspan> ilustrative</text>',
]
legend = [
"Exemplu construit, fără ticker. Prețurile, mediile mobile și ieșirea sunt",
"ilustrative; secvența (verde mare → roșie mare → trend) e cea descrisă de user.",
"Varianta B (ordin la 1/2 = 101.20, risc 1.30, TP1 102.50) e aritmetică pe",
"aceleași cifre, nu un trade din curs. 1/3 din corp = 101.50.",
"Close-ul roșiei (100.60): sub ambele MA (SMA 5 = 100.83, SMA 10 = 101.34).",
]
H0 = float(re.findall(r'viewBox="0 0 \d+ ([0-9.]+)"', svg)[0])
extra += [f'<text x="24" y="{H0 + 10 + 17 * i:.1f}" fill="#555">{t}</text>'
for i, t in enumerate(legend)]
new_h = H0 + 10 + 17 * (len(legend) - 1) + 12
svg = re.sub(r'(viewBox="0 0 \d+ )[0-9.]+(")',
lambda m: m.group(1) + f"{new_h:.0f}" + m.group(2), svg, count=1)
svg = svg.replace("\n</svg>", "\n " + "\n ".join(extra) + "\n</svg>")
svg = re.sub(r"\n <text [^>]*></text>", "", svg) # etichete goale (SL, rezervă)
svg = re.sub(r"\n <line [^>]*stroke=\"none\"[^>]*/>", "", svg)
assert "\n\n" not in svg
OUT.write_text(svg, encoding="utf-8")
print(f"scris {OUT.name}: acoperire {acop:.3f}, flotant {flot:+.2f}R, "
f"ieșire {iesire:+.2f}R, vârf {varf:+.2f}R, SMA5/10@15 = "
f"{sma(5, 15):.2f}/{sma(10, 15):.3f}")