buy_doji: refacut ca tutorial pe 3 niveluri, cu diagrame adnotate

Userul a respins mostra de format tutorial: prea lunga, prea multe
trimiteri in text, termeni tehnici nedefiniti, prea multe cifre fara
desen. Cerintele lui sunt acum contract in STYLE.md (FORMAT TUTORIAL v2).

Textul: 8430 -> 4190 de cuvinte. Nivel 1 (din avion, cu mini-glosar) ->
nivel 2 (6 pasi mecanici, o diagrama fiecare) -> oglinda short -> nivel 3
(rafinari). Trimiterile fisier:linie ies din corp si se aduna la final,
cu legenda de prescurtari. Doua tabele in tot documentul.

Toate cele 14 diagrame refacute: zone dreptunghiulare de evidentiere si
sageti care spun si de ce, nu doar ce. svg_candles primeste Zone si
Arrow; render() nu mai muta obiectul la iesire.

Trei corecturi de continut:
- un singur trade prin toata procedura (doji 50.10/49.25, intrare 50.40,
  stop 49.20, 1R 1.20, TP1 51.60); seria e acum o functie comuna,
  importata de stop/transe/iesire
- short: stopul urca la 100.15, ca sa fie deasupra maximului cum cere
  regula oglindita; cifra cursului ramane in text ca observatie
- LABU: trei minime dar doua atingeri ale pivotului 86.00, ceea ce
  impaca "triple bottom" cu "al doilea test", ambele din transcriere

Reparat si bug-ul vechi din buy_doji_sell.svg: titlul peste eticheta
dojiului.

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 21:09:31 +00:00
parent aff03870e8
commit 8b7b5f6b44
50 changed files with 7011 additions and 6921 deletions

View File

@@ -2,16 +2,19 @@
"""LABU — Buy Doji la pivotul monthly (Nivel 2.5).
Citește `gen/strategii/buy_doji_labu.spec.txt` și scrie
`gen/strategii/buy_doji_labu.svg`: 49 de candele daily, downtrend → triple
bottom → doji la pivotul monthly 86.00 → confirmare (close 89.50 peste
high-ul dojiului) → bull reversal → higher high, cu SL 81.45 (−9 %) și
`gen/strategii/buy_doji_labu.svg`: 57 de candele daily, downtrend → triple
bottom (trei minime aproape egale, dar pivotul lunar atins doar de două ori)
→ doji la pivotul monthly 86.00 → confirmare (close 89.50 peste high-ul
dojiului) → bull reversal → higher high, cu SL 81.45 (−9 %) și
TP 116.35 (+30 %).
Toate cifrele din desen vin din specificație: pivot 86.00, intrare 89.50,
SL 81.45, TP 116.35, high/low-ul dojiului (86.30 / 84.20) și low-urile
triple bottom-ului (82.40 / 81.45 / 81.45). Procentele 9 % și 30 % sunt ale
cursului (reale); nivelurile de preț sunt construite de sumarizator —
legenda o spune explicit.
legenda o spune explicit. Capitolul spune și „triple bottom", și „doji la al
doilea test al pivotului": desenul le satisface pe amândouă — trei minime în
zona 81-83, dintre care doar primele două vin după o urcare peste 86.00.
Rulare: python3 gen/strategii/buy_doji_labu.py
"""
@@ -20,7 +23,7 @@ import sys
from pathlib import Path
sys.path.insert(0, "/workspace/atm-curs-dl")
from svg_candles import Chart, Level, MA, Mark, from_closes # noqa: E402
from svg_candles import Arrow, Chart, Level, MA, Mark, Zone, from_closes # noqa: E402
OUT = Path(__file__).with_suffix(".svg")
@@ -29,50 +32,88 @@ PIVOT = 86.00 # pivot monthly (construit)
ENTRY = 89.50 # intrare = close-ul confirmării (construit)
SL = 81.45 # SL = −9 % față de intrare (construit; procentul e real)
TP = 116.35 # TP = +30 % față de intrare (construit; procentul e real)
DOJI_H, DOJI_L = 86.30, 84.20
DOJI_H, DOJI_L = 86.80, 84.90
I_DOJI, I_CONF = 33, 34
B1, B2, B3 = 16, 25, 31 # cele trei picioare ale triplului fund
# (B1, B2 = testele pivotului; B3 rămâne sub el)
I_RETEST = 37 # revenirea care testează intrarea și ține
FLAG = (42, 45) # consolidarea scurtă (bull flag)
I_TP = 55 # bara pe care prețul atinge 116.35
# --- seria de close-uri din specificație -----------------------------------
# --- seria de close-uri -----------------------------------------------------
# Textul capitolului e sursa: „triple bottom" (trei minime aproape egale) și
# „doji la al doilea test al pivotului". Nu se bat cap în cap: sunt trei
# minime de preț, dar doar două dintre ele vin după o urcare peste 86.00 —
# al treilea e o alunecare care nu mai atinge pivotul de sus în jos.
# Restul: „+30 % într-o lună" (21 de bare daily de la confirmare) și etapele
# „retest → bull flag → breakout". Prețurile sunt construite în jurul
# nivelurilor deja folosite în capitol (86.00 / 89.50 / 81.45 / 97.55 / 116.35).
closes = [
# downtrend 0-14
108.0, 106.5, 105.0, 103.0, 101.5, 100.0, 98.0, 96.5, 95.0, 93.5,
92.5, 91.0, 89.5, 87.5, 85.5,
# triple bottom 15 / 23 / 31
82.80, 84.5, 86.5, 88.0, 88.50,
87.0, 85.0, 83.0, 81.50,
83.5, 85.5, 87.0, 87.80, 87.0, 85.0,
83.0, 81.60, 85.00,
84.90, # 33 = doji (o 85.00, c 84.90, h 86.30, l 84.20)
89.50, # 34 = confirmare, close > 86.30 => intrare
# bull reversal / trend up 35-48
91.0, 93.0, 92.0, 95.0, 97.0, 96.0, 99.0, 102.0, 101.0, 105.0, 108.0,
112.0, 116.35, 118.0,
# 15-16: primul test al pivotului (bottom 1, low 82.40)
83.5, 82.80,
# 17-20: revenire peste pivot (88.90 high) — de aici vine al doilea test
84.5, 86.5, 88.0, 88.5,
# 21-24: coboară înapoi prin pivot: al doilea test al lui 86.00
87.0, 85.5, 84.0, 82.8,
# 25: bottom 2 (low 82.00), la capătul celui de-al doilea test
82.30,
# 26-29: un pull-up scurt care NU mai ajunge la pivot (high max 85.80)
83.5, 84.8, 85.4, 84.2,
# 30: alunecă iar, fără să fi atins pivotul între timp
83.0,
# 31: bottom 3 (low 81.45 = stopul) — al treilea minim, nu un test nou
81.90,
# 32: bara de întoarcere, închide peste pivot
86.10,
# 33 = doji: o 86.10, c 86.00, h 86.80, l 84.90 — corpul chiar pe pivot
86.00,
# 34 = confirmare: close 89.50 peste high-ul dojiului => intrare
89.50,
# 35-36: pornirea
92.0, 94.0,
# 37: retest — cade la 89.60, pe intrare, și ține
91.0,
# 38-41: reia urcarea și trece de ținta 1:1 (97.55)
93.5, 95.5, 98.5, 100.0,
# 42-45: bull flag — patru candele care se odihnesc
99.2, 99.8, 98.9, 99.5,
# 46-54: breakout și continuare
102.5, 104.0, 103.0, 106.0, 108.5, 107.5, 110.5, 112.5, 114.0,
# 55: +30 % față de intrare; 56: încă un higher high
116.35, 117.5,
]
assert len(closes) == 49, f"seria are {len(closes)} bare, spec cere 49"
assert len(closes) == 57, f"seria are {len(closes)} bare, aștept 57"
# wick-uri: (up, down). Restul (0.4, 0.5); ajustate ca low-urile/high-urile
# cerute de asserts să iasă exact (bar 15/23/31 = triple bottom; bar 16/24
# nu au voie să coboare sub piciorul din stânga; bar 33 = doji).
# wick-uri: (sus, jos) față de corp. Restul (0.4, 0.5); ajustate ca low-urile
# cerute de assert-uri să iasă exact.
wicks = [(0.4, 0.5)] * len(closes)
wicks[15] = (0.4, 0.40) # low 82.40
wicks[16] = (0.4, 0.20) # low 82.60 > 82.40 (piciorul 1 rămâne minim local)
wicks[23] = (0.4, 0.05) # low 81.45
wicks[24] = (0.4, 0.00) # low 81.50 > 81.45 (piciorul 2 rămâne minim local)
wicks[31] = (0.4, 0.15) # low 81.45
wicks[32] = (0.4, 0.10) # low 81.50 (nu sub 81.45)
wicks[33] = (1.30, 0.70) # doji: high 86.30, low 84.20
wicks[B1] = (0.4, 0.40) # bottom 1: low 82.40
wicks[17] = (0.4, 0.00) # low 82.80 > 82.40 (piciorul 1 rămâne minim local)
wicks[B2] = (0.4, 0.30) # bottom 2: low 82.00
wicks[26] = (0.4, 0.00) # low 82.30 > 82.00 (piciorul 2 rămâne minim local)
wicks[B3] = (0.4, 0.45) # bottom 3: low 81.45 = stopul
wicks[32] = (0.4, 0.00) # low 81.90 (nu coboară sub 81.45)
wicks[I_DOJI] = (0.70, 1.10) # doji: high 86.80, low 84.90
wicks[35] = (0.4, 0.00) # prima bară după intrare nu se mai duce sub 89.50
wicks[I_RETEST] = (0.4, 1.40) # retest: low 89.60, peste intrarea de 89.50
# volume construite: vânzare peste medie la triple bottom și la confirmare.
# Nu sunt numere din curs (nu sunt etichetate), doar panoul de volum cerut.
# volume construite: vânzare peste medie la cele două picioare, volum mare pe
# confirmare, volum mic pe flag. Nu sunt numere din curs.
vols = [
55, 52, 48, 50, 45, 47, 44, 46, 42, 44,
40, 42, 38, 41, 58,
62, 50, 46, 44, 43,
45, 44, 47, 66, 55,
50, 47, 45, 44, 42,
43, 45, 60, 72, 84,
78, 70, 64, 60, 58,
54, 52, 56, 60, 66,
74, 82, 90, 98,
62, 68, 50, 46, 44,
43, 45, 44, 47, 46,
45, 47, 52, 45, 44,
48, 70, 62, 58, 88,
76, 70, 58, 62, 66,
72, 70, 44, 40, 38,
42, 74, 70, 64, 68,
72, 66, 74, 78, 82,
90, 96,
]
assert len(vols) == len(closes), f"volume {len(vols)} != bare {len(closes)}"
@@ -80,80 +121,134 @@ cds = from_closes(closes, wicks=wicks, vols=vols, start=110.0)
assert abs(cds[0].o - 110.0) < 1e-9 and cds[0].c < cds[0].o, "prima bară nu e roșie"
# nota explicativă a dojiului (bloc stânga-jos, nu se suprapune cu candelele)
cds[33].note = ("bara 33 = doji: o 85.00, c 84.90, h 86.30, l 84.20 — "
"corp mic exact pe pivotul monthly 86.00 (indecizie la suport)")
cds[I_DOJI].note = ("doji = corp mic (o 86.10, c 86.00): cumpărătorii și vânzătorii sunt la "
"egalitate, exact pe pivotul lunar 86.00")
TP1 = round(ENTRY + (ENTRY - SL), 2) # ținta 1:1: cât ai riscat
assert abs(TP1 - 97.55) < 1e-9, f"ținta 1:1 = {TP1}, capitolul spune 97.55"
R = (TP - ENTRY) / (ENTRY - SL)
assert 3.2 <= R <= 3.4, f"{R:.2f}R, capitolul spune „cam 3,3R”"
I_TP1 = next(i for i in range(35, len(cds)) if cds[i].h >= TP1) # prima bară peste 1:1
cds[I_TP1].note = (f"ținta 1:1 ({TP1:.2f}) e atinsă pe bara {I_TP1}: acolo vinzi o treime, "
"restul merge mai departe — de asta nu vinzi tot la 1:1")
# --- assert-urile din specificație (toate, ca assert reale) ----------------
# 1. pivot 86.00 e straddled de doji
assert cds[33].l <= PIVOT <= cds[33].h, "doji-ul nu straddlează 86.00"
assert cds[I_DOJI].l <= PIVOT <= cds[I_DOJI].h, "doji-ul nu straddlează 86.00"
# corpul dojiului stă pe pivot, nu la un dolar sub el (eticheta spune „exact pe")
assert min(cds[I_DOJI].o, cds[I_DOJI].c) <= PIVOT <= max(cds[I_DOJI].o, cds[I_DOJI].c) + 0.15, \
f"corpul dojiului ({cds[I_DOJI].o}/{cds[I_DOJI].c}) nu e pe pivotul {PIVOT}"
# 2. confirmarea: close[34] == 89.50 == intrare și > high[33]
assert abs(cds[34].c - ENTRY) < 1e-9, f"close[34]={cds[34].c} != intrare 89.50"
assert cds[34].c > cds[33].h, "confirmarea nu închide peste high-ul dojiului"
# 2. confirmarea: close[34] == 89.50 == intrare și > high-ul dojiului
assert abs(cds[I_CONF].c - ENTRY) < 1e-9, f"close[{I_CONF}]={cds[I_CONF].c} != intrare 89.50"
assert cds[I_CONF].c > cds[I_DOJI].h, "confirmarea nu închide peste high-ul dojiului"
# 3. SL = min(low[30:35]) (regula: extrema ultimelor 4 candele de la confirmare)
min_30_35 = min(c.l for c in cds[30:35])
assert abs(min_30_35 - SL) < 1e-9, f"min low[30:35]={min_30_35} != SL {SL}"
# 3. SL = extrema ultimelor 4 candele de la confirmare
min_win = min(c.l for c in cds[I_CONF - 4:I_CONF + 1])
assert abs(min_win - SL) < 1e-9, f"min low[{I_CONF - 4}:{I_CONF + 1}]={min_win} != SL {SL}"
# 4./5. procentele REALE ale cursului
assert round((ENTRY - SL) / ENTRY * 100, 1) == 9.0, "stopul nu iese −9 %"
assert round((TP - ENTRY) / ENTRY * 100, 1) == 30.0, "TP nu iese +30 %"
# 6. triple bottom: exact trei minime locale sub 86.00, în zona 81-83,
# al doilea și al treilea fără low nou semnificativ față de primul
# 6. triple bottom, dar pivotul atins de două ori — aserțiunea care ține
# laolaltă cele două formulări din capitol („trei minime aproape egale" și
# „doji la al doilea test al pivotului 86.00").
lows = [c.l for c in cds]
bottom_idx = [i for i in range(1, len(cds) - 1)
if lows[i] < lows[i - 1] and lows[i] < lows[i + 1]
and lows[i] < PIVOT and 81.0 <= lows[i] <= 83.0]
assert bottom_idx == [15, 23, 31], f"triple bottom la {bottom_idx}, nu [15, 23, 31]"
b1, b2, b3 = lows[15], lows[23], lows[31]
tol = 0.02 * b1 # „semnificativ" = > 2 % sub primul low
assert b2 >= b1 - tol and b3 >= b1 - tol, \
f"al doilea/treilea picior coboară semnificativ sub primul: {b1}/{b2}/{b3}"
assert abs(b1 - 82.40) < 1e-9 and abs(b2 - SL) < 1e-9 and abs(b3 - SL) < 1e-9, \
f"low-urile triple bottom: {b1}/{b2}/{b3}"
assert bottom_idx == [B1, B2, B3], f"minimele fundului: {bottom_idx}, aștept [{B1}, {B2}, {B3}]"
assert len(bottom_idx) == 3, f"{len(bottom_idx)} minime locale în zona fundului, aștept 3"
b1, b2, b3 = (lows[i] for i in bottom_idx)
# „aproape egale": nici unul nu e cu mai mult de 2 % sub primul
assert max(b1, b2, b3) - min(b1, b2, b3) <= 0.02 * b1, \
f"minimele nu sunt aproape egale: {b1}/{b2}/{b3}"
assert abs(b1 - 82.40) < 1e-9 and abs(b2 - 82.00) < 1e-9 and abs(b3 - SL) < 1e-9, \
f"low-urile triplului fund: {b1}/{b2}/{b3}"
# un minim e „atingere a pivotului" doar dacă prețul a fost peste 86.00 după
# minimul dinainte: coboară prin pivot ca să ajungă acolo. Al treilea nu e.
tests = []
for k, i in enumerate(bottom_idx):
prev = bottom_idx[k - 1] + 1 if k else 0
if max(c.h for c in cds[prev:i]) >= PIVOT:
tests.append(i)
assert tests == [B1, B2], f"atingeri ale pivotului la {tests}, aștept [{B1}, {B2}]"
assert len(tests) == 2, f"{len(tests)} atingeri ale pivotului 86.00, aștept 2"
# între primele două picioare prețul urcă clar peste pivot (al doilea test)
assert max(c.h for c in cds[B1 + 1:B2]) > PIVOT + 1.5, "nu e revenire peste pivot înainte de B2"
# între al doilea și al treilea NU mai ajunge la pivot: al treilea nu e test
assert max(c.h for c in cds[B2 + 1:B3]) < PIVOT, \
f"pull-up-ul dintre B2 și B3 atinge pivotul ({max(c.h for c in cds[B2 + 1:B3]):.2f})"
# 7. TP atins: există i > 34 cu high[i] >= 116.35
assert any(c.h >= TP for c in cds[35:]), "TP 116.35 nu e atins după intrare"
# 7. TP atins la ~o lună de bursă de la confirmare (capitolul: „într-o lună")
assert abs(cds[I_TP].c - TP) < 1e-9, f"close[{I_TP}]={cds[I_TP].c} != {TP}"
assert max(c.h for c in cds[I_CONF + 1:I_TP]) < TP, "TP atins mai devreme de bara 55"
bars_to_tp = I_TP - I_CONF
assert 20 <= bars_to_tp <= 22, f"{bars_to_tp} bare până la +30 %, aștept ~21 (o lună)"
# 8. doji: corp <= 10 % din amplitudine
assert abs(cds[33].o - cds[33].c) <= 0.10 * (cds[33].h - cds[33].l), "bara 33 nu e doji"
assert abs(cds[33].h - DOJI_H) < 1e-9 and abs(cds[33].l - DOJI_L) < 1e-9, \
"doji h/l nu ies 86.30/84.20"
assert abs(cds[I_DOJI].o - cds[I_DOJI].c) <= 0.10 * (cds[I_DOJI].h - cds[I_DOJI].l), \
"bara doji nu e doji"
assert abs(cds[I_DOJI].h - DOJI_H) < 1e-9 and abs(cds[I_DOJI].l - DOJI_L) < 1e-9, \
"doji h/l nu ies 86.80/84.90"
# în plus: confirmarea vine cu volum peste medie (cerut de spec)
# 9. retestul: revine până pe intrare și ține (nu o rupe)
assert ENTRY <= cds[I_RETEST].l <= ENTRY + 0.5, \
f"retestul nu atinge intrarea: low {cds[I_RETEST].l}"
assert min(c.l for c in cds[I_CONF + 1:]) >= ENTRY, "prețul cade sub intrare după confirmare"
# 10. bull flag: 4 candele strânse, sub high-ul barei dinainte, urmate de breakout
f0, f1 = FLAG
flag_h = max(c.h for c in cds[f0:f1 + 1])
flag_l = min(c.l for c in cds[f0:f1 + 1])
assert flag_h - flag_l <= 2.5, f"flag prea larg: {flag_h - flag_l:.2f}"
assert flag_h <= cds[f0 - 1].h, "flagul face high nou — nu mai e consolidare"
assert cds[f1 + 1].c > flag_h, "nu există breakout după flag"
# în plus: confirmarea vine cu volum peste medie, flagul cu volum sub medie
mean_vol = sum(vols) / len(vols)
assert vols[34] > mean_vol, f"volumul confirmării {vols[34]} <= media {mean_vol:.0f}"
assert vols[I_CONF] > mean_vol, f"volumul confirmării {vols[I_CONF]} <= media {mean_vol:.0f}"
assert max(vols[f0:f1 + 1]) < mean_vol, "flagul nu vine pe volum scăzut"
entry = cds[34].c
w = 820 # lățime mărită: 49 bare × gap 15 + etichetele din dreapta
# (spec sugera 760, dar ultima candelă ajunge la x=764)
w = 990 # 57 bare × gap 15 + loc pentru etichetele din dreapta
ch = Chart(
candles=cds,
levels=[Level(PIVOT, "pivot monthly 86.00 (construit)", "#777",
dash="4 3", label_at="left"),
Level(SL, "SL 81.45 (construit) = −9 % (procent real)", "#c00",
label_at="left"),
Level(ENTRY, "intrare 89.50 (construit)", "#188a3e"),
Level(TP, "TP 116.35 (construit) = +30 % (procent real)", "#1a5fb4")],
marks=[Mark(15, f"bottom 1 {lows[15]:.2f}", "#c00", "below"),
Mark(23, f"bottom 2 {lows[23]:.2f}", "#c00", "below"),
Mark(31, f"bottom 3 {lows[31]:.2f}", "#c00", "below"),
Mark(33, "doji", "#b06000", "above"),
Mark(34, "confirmare (intrare)", "#188a3e", "above", dy=-14),
Mark(48, "higher high — TP atins", "#188a3e", "above")],
levels=[Level(PIVOT, "pivot lunar 86.00", "#777", dash="4 3", label_at="left"),
Level(SL, "stop 81.45 = 9 % sub intrare (procent real)", "#c00", label_at="left"),
Level(ENTRY, "intrare 89.50", "#188a3e"),
Level(TP1, f"ținta 1:1 {TP1:.2f}", "#777", dash="2 3", label_at="left"),
Level(TP, "116.35 = +30 % față de intrare (procent real)", "#1a5fb4", label_at="left")],
zones=[Zone(0, B3, "1. setup: scădere lungă, apoi trei minime aproape egale — pivotul 86.00 atins de două ori", "#777"),
Zone(I_DOJI, I_CONF, "2. doji + confirmare", "#b06000"),
# p_top sub 116.35: altfel eticheta zonei 3 se așază peste linia țintei
Zone(I_CONF + 1, I_TP - 1, "3. trendul pornește", "#188a3e", p_top=113.0),
Zone(I_TP, len(cds) - 1, "4. ținta", "#1a5fb4")],
arrows=[Arrow(I_CONF, "aici intri, la close 89.50:\nabia acum știi că au câștigat cumpărătorii",
ENTRY, where="above", color="#188a3e", dx=-150),
Arrow(B3, "al treilea minim: 81.45,\nfără o nouă atingere a pivotului\naici pui stopul, sub minime",
SL, where="below", color="#c00", dx=-40),
Arrow(I_RETEST, "retest: cade înapoi pe intrare\nși ține — trendul e sănătos",
cds[I_RETEST].l, where="below", color="#188a3e", dx=110, dy=56),
Arrow(FLAG[0] + 1, "aici se odihnește, nu se strică:\nbull flag, pe volum mic",
cds[FLAG[0] + 1].h, where="above", color="#188a3e", dx=-20),
Arrow(I_TP, f"a ajuns la 116.35: +30 % în 21 de zile de bursă,\ncam {R:.1f}R din cât ai riscat".replace("3.3R", "3,3R"),
TP, where="left", color="#1a5fb4", dx=-30, dy=-40)],
marks=[Mark(B1, f"test 1 · {lows[B1]:.2f}", "#c00", "below"),
Mark(B2, f"test 2 · {lows[B2]:.2f}", "#c00", "below")],
mas=[MA(20, "#b06000", dash="4 3", text="SMA-20")],
scale=8.0, gap=15, body_w=9, left=44, width=w, vol_h=70,
vol_label="volum", title="LABU — daily",
caption=("Procentele (stop 9 %, +30 %) sunt ale cursului; nivelurile de "
"preț sunt construite de sumarizator (18_2025-09-23.md:713)."),
caption=("Procentele (stop 9 %, +30 %) sunt ale cursului; "
"nivelurile de preț sunt puse de sumarizator."),
)
svg = ch.render()
assert "\n\n" not in svg and "<svg" in svg, "SVG invalid (linie goală?)"
# --- citirea SVG-ului: etichete în panză, fără suprapuneri -----------------
TEXT = re.compile(r'<text x="([-\d.]+)" y="([-\d.]+)"([^>]*)>(.*?)</text>', re.S)
H = float(re.search(r'viewBox="0 0 (\d+) ([\d.]+)"', svg).group(2))
W_SVG, H = (float(x) for x in re.search(r'viewBox="0 0 (\d+) ([\d.]+)"', svg).groups())
labels = []
for m in TEXT.finditer(svg):
x, y, attrs, s = float(m.group(1)), float(m.group(2)), m.group(3), m.group(4)
@@ -165,7 +260,7 @@ for m in TEXT.finditer(svg):
labels.append((left, left + wpx, y, s))
for left, right, y, s in labels:
assert left >= -2 and right <= w + 2, f"text iese din panză: {s!r} [{left:.0f},{right:.0f}]"
assert left >= -2 and right <= W_SVG + 2, f"text iese din panză: {s!r} [{left:.0f},{right:.0f}]"
assert 0 <= y <= H, f"text în afara înălțimii: {s!r} (y={y:.0f})"
for i in range(len(labels)):
@@ -177,14 +272,16 @@ for i in range(len(labels)):
# candelele sunt o serie continuă, lipită
xs = [ch._cx(i) for i in range(len(cds))]
assert xs[-1] + ch.body_w / 2 < w, f"ultima candelă iese din panză (x={xs[-1]})"
assert ".md:" not in svg and "Surse" not in svg, "trimiteri la surse în SVG"
assert xs[-1] + ch.body_w / 2 < W_SVG, f"ultima candelă iese din panză (x={xs[-1]})"
assert all(xs[i + 1] - xs[i] == ch.gap for i in range(len(xs) - 1)), "serie cu goluri"
for a, b in zip(cds, cds[1:]):
assert abs(a.c - b.o) < 1e-9, "open != close-ul barei anterioare"
print(f"LABU: {len(cds)} bare, doji {cds[33].l:.2f}-{cds[33].h:.2f} peste pivot "
f"{PIVOT:.2f}, intrare {entry:.2f}, SL {SL:.2f} (−9 %), TP {TP:.2f} (+30 %)")
print(f"triple bottom: {b1:.2f} / {b2:.2f} / {b3:.2f} "
print(f"triple bottom: {b1:.2f} / {b2:.2f} / {b3:.2f}; pivotul 86.00 atins de "
f"{len(tests)} ori (barele {tests}); +30 % după {bars_to_tp} bare "
f"volum confirmare {vols[34]} > medie {mean_vol:.0f}")
print(f"scris {OUT} ({w}x{H:.0f}); {len(labels)} etichete verificate, fără suprapuneri")
print(f"scris {OUT} ({W_SVG:.0f}x{H:.0f}); {len(labels)} etichete verificate, fără suprapuneri")
OUT.write_text(svg, encoding="utf-8")