Files
atm-curs-dl/gen/strategii/intrarea_doua_variante.py
Claude Agent edc270417d 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
2026-09-19 17:29:50 +00:00

156 lines
8.1 KiB
Python

"""Diagrama din `gen/strategii/intrarea_doua_variante.spec.txt` (TIP: PRET).
Același setup cu cele două intrări pe același grafic: A la close-ul candelei
de semnal, B la retestul de 1/3 și 1/2 din corpul ei. Un singur stop, comun
(sub structură), și trei paranteze de risc în dreapta, la aceeași scară cu
prețul — diferența de risc vine din intrare, nu din stop.
Seria e CONSTRUITĂ (fără ticker), 32 de candele de 15 min, fără volum.
Etichetele de sub candele (suport atins / atinge 1/3 / atinge 1/2) sunt
desenate de mână sub linia de SL, cu linii de legătură: ca Mark-uri „below"
ar cădea peste liniile de 1/2 și 1/3 și peste wick-ul candelei vecine.
Rulează: .venv/bin/python gen/strategii/intrarea_doua_variante.py
-> gen/strategii/intrarea_doua_variante.svg
"""
import math
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__).resolve().parent / "intrarea_doua_variante.svg"
GRI, ROSU, VERDE, PORT, ALB = "#777", "#c00", "#188a3e", "#b06000", "#1a5fb4"
closes = [103.60, 103.35, 103.45, 103.10, 102.80, 102.90, 102.50, 102.20, 101.85, 101.95, 101.50, 101.10, # 0-11
100.60, 100.40, 100.35, # 12-14 suport
102.15, # 15 semnal
101.70, 101.40, 101.60, # 16-18 retest
102.00, 102.40, 102.30, 102.80, 103.20, 103.00, 103.50, 103.90, 104.20, 104.10, 104.50, 104.80, 105.00]
wicks = [(0.15, 0.15)] * 12 \
+ [(0.10, 0.15), (0.10, 0.50), (0.10, 0.15)] \
+ [(0.15, 0.10)] \
+ [(0.10, 0.20), (0.10, 0.20), (0.10, 0.10)] \
+ [(0.10, 0.03)] + [(0.15, 0.12)] * 12
assert len(closes) == len(wicks) == 32
cds = from_closes(closes, wicks=wicks, start=103.80)
# ── assert-urile din spec ───────────────────────────────────────────────────
S = cds[15]
for a, b in zip(cds, cds[1:]): # 1
assert abs(a.c - b.o) < 1e-9
assert abs(S.o - 100.35) < 1e-9 and abs(S.c - 102.15) < 1e-9 # 2
assert abs(S.h - 102.30) < 1e-9 and abs(S.l - 100.25) < 1e-9
SL = min(c.l for c in cds[11:15]) # 3
assert abs(SL - 99.90) < 1e-9 and abs(cds[13].l - SL) < 1e-9 and S.l > SL
assert all(c.l > SL for i, c in enumerate(cds[11:15], 11) if i != 13)
half = (S.o + S.c) / 2 # 4
third = S.c - (S.c - S.o) / 3
assert abs(half - 101.25) < 1e-9 and abs(third - 101.55) < 1e-9
assert min(c.l for c in cds[16:19]) <= half and cds[16].l <= third # 5
assert cds[16].l > half # 16 atinge doar 1/3, 17 atinge 1/2
assert all(c.l > third for c in cds[19:]) # după retest nu mai revine la 1/3
RISK_PER_TRADE = 50.0
intrari = {"A": S.c, "1/3": third, "1/2": half}
riscuri = {k: e - SL for k, e in intrari.items()} # 6
for k, r in zip(intrari, (2.25, 1.65, 1.35)):
assert abs(riscuri[k] - r) < 1e-6, (k, riscuri[k])
actiuni = {k: math.floor(RISK_PER_TRADE / r + 1e-9) for k, r in riscuri.items()}
assert list(actiuni.values()) == [22, 30, 37], actiuni
tp = {k: e + (e - SL) for k, e in intrari.items()} # 7
for k, v in zip(intrari, (104.40, 103.20, 102.60)):
assert abs(tp[k] - v) < 1e-9, (k, tp[k])
assert max(c.h for c in cds) >= tp["A"] # 9
# ── desenul ─────────────────────────────────────────────────────────────────
W = 880
levels = [
Level(100.00, "suport 100.00", GRI),
Level(SL, "", ROSU), # eticheta SL e scrisă sub linie, mai jos
Level(S.c, f"A: intrare la close {S.c:.2f}", VERDE),
Level(third, f"B: 1/3 = {third:.2f}", PORT),
Level(half, f"B: 1/2 = {half:.2f}", PORT),
Level(tp["A"], f"TP1 A {tp['A']:.2f}", ALB),
Level(tp["1/2"], f"TP1 B (1/2) {tp['1/2']:.2f}", ALB, dash="2 3"),
Level(tp["1/3"], f"TP1 B (1/3) {tp['1/3']:.2f}", ALB, dash="2 3"),
]
ch = Chart(candles=cds, levels=levels,
marks=[Mark(15, "semnal: candela verde mare", VERDE, "above", dy=-14)],
scale=60, gap=15, body_w=9, left=44, width=W,
title="Exemplu construit (fără ticker) — 15 min")
svg = ch.render()
svg = re.sub(r'\s*<text [^>]*></text>', "", svg) # eticheta goală a SL-ului
y, cx = ch._y, ch._cx
ySL = y(SL)
extra = []
def txt(x, yy, s, fill, anchor="start", bold=False):
st = ' style="font-weight:bold"' if bold else ""
extra.append(f'<text x="{x:.1f}" y="{yy:.1f}" fill="{fill}" text-anchor="{anchor}"{st}>{s}</text>')
def ln(x1, y1, x2, y2, stroke, w=1.2, dash=""):
d = f' stroke-dasharray="{dash}"' if dash else ""
extra.append(f'<line x1="{x1:.1f}" y1="{y1:.1f}" x2="{x2:.1f}" y2="{y2:.1f}" stroke="{stroke}" stroke-width="{w}"{d}/>')
# eticheta SL, sub linia lui, în dreapta
txt(W - 28, ySL + 16, f"SL {SL:.2f} = low-ul ultimelor 4 candele (comun A și B)", ROSU, "end")
# etichetele de sub candele, sub linia de SL, cu linii de legătură spre wick
R1, R2 = ySL + 16, ySL + 31
txt(cx(13), R1, "suport atins", GRI, "middle", True)
ln(cx(16), y(cds[16].l) + 2, cx(16), R2 - 11, PORT, 1, "2 2")
txt(cx(16) + 4, R2, "atinge 1/3", PORT, "end", True)
ln(cx(17), y(cds[17].l) + 2, cx(17), R1 - 11, PORT, 1, "2 2")
txt(cx(17) - 4, R1, "atinge 1/2", PORT, "start", True)
# parantezele de risc: intrare → SL, la aceeași scară cu prețul
BX = {"A": 560, "1/3": 605, "1/2": 650}
col = {"A": VERDE, "1/3": PORT, "1/2": PORT}
eticheta = {"A": "A", "1/3": "B 1/3", "1/2": "B 1/2"}
px = {}
for k, e in intrari.items():
x, y0, y1 = BX[k], y(e), ySL
px[k] = y1 - y0
ln(x, y0, x, y1, col[k], 2.2)
ln(x - 6, y0, x + 6, y0, col[k], 2.2)
ln(x - 6, y1, x + 6, y1, ROSU, 2.2)
txt(x, y0 - 6, eticheta[k], col[k], "middle", True)
txt(x + 6, (y0 + y1) / 2 + 4, f"{riscuri[k]:.2f}", col[k], "start")
# 8. scara: raportul pixelilor == raportul riscurilor, toleranță 1%
for k in intrari:
assert abs(px[k] / px["A"] - riscuri[k] / riscuri["A"]) < 0.01 * riscuri[k] / riscuri["A"], k
# blocul cu riscurile, sub SL, sub paranteze
bloc = [(f"risc A {riscuri['A']:.2f} → {actiuni['A']} acțiuni la 50 $", VERDE),
(f"risc B 1/3 {riscuri['1/3']:.2f} → {actiuni['1/3']} acțiuni", PORT),
(f"risc B 1/2 {riscuri['1/2']:.2f} → {actiuni['1/2']} acțiuni", PORT)]
for i, (s, c) in enumerate(bloc):
txt(530, ySL + 34 + 15 * i, s, c)
ylegenda = ySL + 34 + 15 * len(bloc) + 12
txt(24, ylegenda, "Exemplu construit, fără ticker. Cifrele nu vin din curs. "
"Risc permis 50 $ (0,5% din 10.000 $).", "#555")
H = ylegenda + 12
svg = re.sub(r'viewBox="0 0 \d+ \d+"', f'viewBox="0 0 {W} {H:.0f}"', svg)
svg = svg.replace("\n</svg>", "\n " + "\n ".join(extra) + "\n</svg>")
assert "\n\n" not in svg, "linie goală în SVG"
# ── verificare: etichete în pânză, fără suprapuneri între ele ─────────────
cutii = []
for xs, ys, attrs, s in re.findall(r'<text x="([\d.-]+)" y="([\d.-]+)"([^>]*)>([^<]*)</text>', svg):
x, yy = float(xs), float(ys)
w = len(s) * (7.0 if "bold" in attrs else 6.3)
x0 = x - w if 'anchor="end"' in attrs else (x - w / 2 if 'anchor="middle"' in attrs else x)
assert -2 <= x0 and x0 + w <= W + 2 and 0 <= yy <= H, f"în afara pânzei: {s!r}"
cutii.append((x0, x0 + w, yy, s))
for i, a in enumerate(cutii):
for b in cutii[i + 1:]:
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 + "\n", encoding="utf-8")
print(f"SL {SL:.2f} | 1/3 {third:.2f} | 1/2 {half:.2f} | riscuri "
+ ", ".join(f"{k} {r:.2f}→{actiuni[k]}" for k, r in riscuri.items())
+ " | px " + ", ".join(f"{v:.1f}" for v in px.values()))