Files
atm-curs-dl/gen/strategia_8_dhrt.py
Claude Agent c198cd0a7b MODUL 5: 24 de reparatii pe cele doua strategii, dupa verificare
Opt erau blocante, adica un cititor ar fi executat gresit pe cont:

- 56% era legat de o intrare la 133 care da de fapt 35,3%; cifra se propaga in
  dimensionarea pozitiei. Acum spune ca 56% e rostit separat si ca raportul nu
  se verifica.
- "2/3 taie jumatate din risc" era fals (29,3% vs 35,3%).
- definitia lui buy doji se contrazicea cu regula de 4 luni din acelasi
  document.
- diagrama de trail arata stopul deasupra pretului: liniile traversau tot
  graficul, deci in stanga stateau peste candele. Acum fiecare porneste din
  swing low-ul care o declanseaza, cu cele patru swing low-uri marcate.
- linia gatului era data ca "media celor doua varfuri", ceea ce nu reproduce
  63.00 pe propriul desen; e din interiorul gatului.
- singura regula mecanica de "nu intra" nu era desenata cu mecanismul ei:
  panoul de volum avea bare egale de la o baza, in loc de histograma cu semn.
- "SL minim 54.17" se citea invers decat intelege textul: e pretul cel mai
  sus permis, nu stopul cel mai stramt.
- lectura "TP2 vine inaintea lui TP1" era prezentata ca geometrie in doua
  diagrame, desi fraza-sursa e neinteligibila si sursa sprijina la fel de bine
  varianta in care se recalculeaza si TP si SL. Acum e marcata ca lectura
  chiar acolo unde e folosita, cu varianta alternativa alaturi.

svg_candles are trei adaugiri optionale (Level.i0, Level.label_dy,
Chart.vol_zero) cerute de reparatiile de mai sus; selftest-ul trece si
diagramele existente nu se schimba daca nu sunt regenerate.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ThN7ixZrowca8aTGrK1MUJ
2026-10-02 02:35:51 +00:00

586 lines
34 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Diagramele pentru summaries/06_MODUL_5/05_strategia_8_dhrt.md.
Toate graficele cu candele / intrare / stop / ținte se construiesc cu
svg_candles (o singură scară px/unitate pe desen), fiecare cifră din etichete
e verificată prin assert, apoi se substituie marcatorii <!--SVG:nume--> din .md.
Regula de cifre: pe etichete intră DOAR prețuri rostite în transcriere (51, la
XLF) sau prețuri din exemplul construit de mine, declarat ca atare în titlu și
în caption. Valorile derivate din geometria cursului (SL la 1/3, TP1 = R,
TP2 = proiecția gâtului) apar doar pe desenele construite, unde seria e a mea.
Cele două schițe fără candele (`hs_pe_volum`, `cadrane`) sunt scrise de mână.
Rulează: .venv/bin/python gen/strategia_8_dhrt.py
"""
import os
import re
import sys
sys.path.insert(0, "/workspace/atm-curs-dl")
from svg_candles import Chart, Level, Mark, MA, Zone, from_closes # noqa: E402
MD = ("/workspace/atm-curs-dl/summaries/06_MODUL_5/"
"05_strategia_8_dhrt.md")
SVG = {}
# ----------------------------------------------------------------- unelte mici
def vols(n, base=44, span=12, **over):
v = [float(base + ((i * 7) % span) - span // 2) for i in range(n)]
for k, x in over.items():
v[int(k[1:])] = float(x)
return v
def floor_lows(cds, a, b, price):
"""Scurtează fitilele de jos ale barelor a..b până la `price`.
Nu atinge corpurile: dacă un open sau close ar fi sub nivel, crapă. Lungimea
fitilului e oricum un parametru liber al desenului; aici o fixez ca mai
multe bare să se oprească exact pe nivelul pe care îl afirmă eticheta.
"""
for c in cds[a:b + 1]:
assert min(c.o, c.c) >= price, f"corpul barei trece sub {price}"
if c.l < price:
c.l = price
return price
def lo(cds, a, b):
return round(min(c.l for c in cds[a:b + 1]), 2)
def hi(cds, a, b):
return round(max(c.h for c in cds[a:b + 1]), 2)
def deco(ch, name, distante=(), intregi=()):
"""Randează, verifică etichetele, salvează.
`distante` = numere cu două zecimale permise în etichete fără să fie
niveluri desenate (R-uri, înălțimi de pattern). `intregi` = întregi
rostiți în transcriere, permiși în etichete.
"""
svg = ch.render()
assert "\n\n" not in svg, f"{name}: linie goală în svg"
assert len(ch.caption) <= 118, f"{name}: caption de {len(ch.caption)} caractere"
desenate = ({f"{lv.price:.2f}" for lv in ch.levels}
| {f"{z.p_top:.2f}" for z in ch.zones if z.p_top is not None}
| {f"{z.p_bot:.2f}" for z in ch.zones if z.p_bot is not None}
| {f"{d:.2f}" for d in distante})
permise_int = ({str(i) for i in intregi}
| {f"{lv.price:.0f}" for lv in ch.levels})
for t in re.findall(r"<text\b[^>]*>(.*?)</text>", svg, re.S):
plain = re.sub(r"<[^>]+>", "", t)
for m in re.finditer(r"\d+\.\d\d", plain):
assert m.group(0) in desenate, \
f"{name}: eticheta numește {m.group(0)}, care nu e un nivel desenat"
for m in re.finditer(
r"(?<![\d.\-/])(\d{2,3})(?![\d.])"
r"(?!\s*(?:candele|bare|săptămâni|luni|perioade|%))", plain):
assert m.group(1) in permise_int, \
f"{name}: eticheta numește întregul {m.group(1)}, nepermis"
sus = min(ch._y(c.h) for c in ch.candles)
jos = max(ch._y(c.l) for c in ch.candles)
for lv in ch.levels:
assert ch._y(lv.price) > sus - 0.06 * (jos - sus), \
f"{name}: nivelul {lv.price} e lipit de marginea de sus"
assert ch._y(lv.price) < jos + 0.06 * (jos - sus), \
f"{name}: nivelul {lv.price} e lipit de marginea de jos"
SVG[name] = svg
print(f" {name}: {len(ch.candles)} bare, {len(ch.levels)} niveluri")
CONSTRUIT = ("Exemplu construit (fără ticker), la scară. Geometria e a cursului; "
"prețurile și R-urile sunt ale mele.")
# ===================================================== seria principală (HS răsturnat)
# O singură serie construită, folosită de harta, pasul 3 (stopul) și pasul 4
# (țintele), ca să nu se schimbe cifrele de la o secțiune la alta.
CL = (
[71.00, 69.00, 67.00, 65.00, 63.00, 61.00] # 0-5 downtrend anterior
+ [59.50, 58.80, 60.50, 62.00] # 6-9 umărul stâng
+ [62.80, 62.00, 63.00] # 10-12 gâtul, stânga (high 63.00)
+ [61.00, 58.00, 56.00, 54.00, 53.00, 52.30] # 13-18 căderea în cap
+ [54.00, 56.50, 58.50, 60.50, 62.00, 63.00] # 19-24 urcarea din cap
+ [62.30, 61.00, 59.30, 58.80, 59.60, 59.00] # 25-30 umărul drept
+ [59.10] # 31 doji
+ [65.00] # 32 breakout = intrare
+ [63.90, 64.60, 66.50, 69.00, 71.00] # 33-37 retest, apoi urcă
+ [73.00, 71.80, 74.00, 76.10, 77.50]) # 38-42 TP2, TP1, trail
WK = (
[(0.40, 0.40)] * 6
+ [(0.30, 0.30), (0.30, 0.30), (0.30, 0.30), (0.30, 0.30)]
+ [(0.20, 0.30), (0.30, 0.30), (0.00, 0.30)] # 12: high-ul gâtului stâng
+ [(0.30, 0.30)] * 5 + [(0.30, 0.30)]
+ [(0.30, 0.30)] * 5 + [(0.00, 0.30)] # 24: high-ul gâtului drept
+ [(0.30, 0.30), (0.30, 0.30), (0.30, 0.30), (0.30, 0.30),
(0.30, 0.30), (0.40, 0.30)]
+ [(0.90, 0.50)] # 31: fitile de doji
+ [(0.20, 0.20)] # 32: candela de breakout
+ [(0.40, 0.40)] * 5
+ [(0.40, 0.40)] * 5)
VL = vols(43, 46, 12, i27=56, i28=60, i29=64, i30=68, i31=74, i32=112,
i33=62, i34=66, i35=78, i36=84, i37=80, i39=86, i40=92)
CDS = from_closes(CL, wicks=WK, vols=VL, start=73.00)
CAP = lo(CDS, 13, 18) # low-ul capului
UMD = lo(CDS, 25, 31) # nivelul umărului drept
UMS = lo(CDS, 6, 9) # nivelul umărului stâng
H = round(UMD - CAP, 2) # înălțimea cap -> umăr drept
SL = round(CAP + H / 3, 2) # 1/3 de la cap: marginea de sus a treimii
E = CDS[32].c # intrarea, la close-ul candelei de breakout
R = round(E - SL, 2)
TP1 = round(E + R, 2)
# „măsoară din interiorul gâtului pe aici, iau un average" (`05_7_...txt:1304`):
# nivelul se ia din interiorul gâtului, NU de la cele mai de sus vârfuri. Pe
# desen vârfurile sunt mai sus (barele 13 și 25 ating 63.30), de aceea eticheta
# nu are voie să spună „media celor două vârfuri".
GAT = round((hi(CDS, 12, 12) + hi(CDS, 24, 24)) / 2, 2)
H_HS = round(GAT - CAP, 2)
TP2 = round(GAT + H_HS, 2)
assert (CAP, UMD, UMS) == (52.00, 58.50, 58.50), (CAP, UMD, UMS)
assert H == 6.50, H
assert SL == 54.17 and CAP < SL < UMD, (CAP, SL, UMD)
assert abs(CDS[31].c - CDS[31].o) <= 0.15 * (CDS[31].h - CDS[31].l), \
"candela 31 nu e doji"
assert CDS[32].c > hi(CDS, 31, 31) and CDS[32].c > GAT, \
"candela de breakout trebuie să închidă peste doji ȘI peste gât"
assert lo(CDS, 33, 37) > SL, "retestul nu trebuie să atingă stopul"
assert hi(CDS, 38, 42) >= TP1 > TP2, \
"aici TP1 (=R) iese peste TP2 (proiecția gâtului) — exact cazul din lecție"
assert lo(CDS, 13, 18) < lo(CDS, 6, 9) and lo(CDS, 13, 18) < lo(CDS, 25, 31), \
"capul trebuie să fie mai jos decât ambii umeri"
assert VL[32] > max(VL[25:32]), "candela de breakout nu are volumul cel mai mare"
# regresia de prins: eticheta gâtului spune „din interiorul gâtului, nu de la
# vârfuri", deci nivelul trebuie să stea strict sub cel mai de sus vârf al zonei
assert GAT < max(hi(CDS, 10, 13), hi(CDS, 24, 25)), \
f"gâtul {GAT} nu e sub vârfurile zonei: eticheta ar minți"
print(f"construit: cap {CAP} umeri {UMS}/{UMD} H {H} SL {SL} intrare {E}"
f" R {R} TP1 {TP1} gât {GAT} (h {H_HS}) TP2 {TP2}")
# ------------------------------------------------------------------- 1. harta
deco(Chart(
candles=CDS,
levels=[Level(SL, f"SL {SL:.2f}", "#c00", label_at="left"),
Level(E, f"intrare {E:.2f}", "#188a3e"),
Level(GAT, f"linia gâtului {GAT:.2f}", "#6a3d9a"),
Level(TP2, f"TP2 {TP2:.2f} = proiecția gâtului", "#1a5fb4",
label_at="left"),
Level(TP1, f"TP1 {TP1:.2f} = distanța stopului", "#1a5fb4")],
marks=[Mark(7, "umărul stâng", "#6a3d9a", "below", dy=14),
Mark(17, "capul", "#6a3d9a", "below", dy=16),
Mark(31, "doji", "#b06000", "below", dy=8),
Mark(32, "buy doji = trigger", "#188a3e", dy=-40),
Mark(34, "pullback la nivelul de breakout", "#b06000", "below", dy=22),
Mark(41, "apoi trail", "#1a5fb4", dy=-8)],
zones=[Zone(25, 31, "umărul drept", "#188a3e")],
mas=[MA(20, "#b06000", dash="4 3")],
scale=11.0, gap=15, body_w=9, left=58, width=700, top=46, vol_h=70,
vol_label="volum", title="DHRT, tot trade-ul — HS răsturnat (exemplu construit)",
caption=CONSTRUIT), "harta", distante=(R, H, H_HS))
# ===================================== 2. pasul 1, DA — capul în suport, umărul pe pivot
S_CL = (
[48.00, 46.50, 45.00, 43.50, 42.00] # 0-4 downtrend
+ [40.60, 40.10, 41.50, 43.00] # 5-8 umărul stâng
+ [43.60, 42.80, 43.50] # 9-11 gâtul
+ [41.50, 39.00, 37.50, 36.30] # 12-15 căderea în cap
+ [37.80, 39.50, 41.00, 42.60, 43.40] # 16-20 urcarea
+ [42.40, 41.20, 40.30, 40.10, 40.80, 40.40] # 21-26 umărul drept
+ [40.50, 44.20, 45.60, 47.00, 48.50]) # 27-31 doji, breakout, urcă
S_WK = ([(0.40, 0.40)] * 5
+ [(0.30, 0.30), (0.30, 0.10), (0.30, 0.10), (0.30, 0.30)]
+ [(0.30, 0.30), (0.30, 0.30), (0.50, 0.30)]
+ [(0.30, 0.30), (0.30, 0.30), (0.30, 0.30), (0.30, 0.30)]
+ [(0.30, 0.30)] * 4 + [(0.40, 0.30)]
+ [(0.30, 0.30), (0.30, 0.30), (0.30, 0.30), (0.30, 0.10),
(0.30, 0.10), (0.30, 0.30)]
+ [(0.70, 0.40)] + [(0.20, 0.20)] + [(0.40, 0.40)] * 3)
S_VL = vols(32, 44, 12, i23=54, i24=58, i25=62, i26=66, i27=70, i28=104, i29=84)
SC = from_closes(S_CL, wicks=S_WK, vols=S_VL, start=49.00)
SUP, PIV = lo(SC, 12, 15), lo(SC, 21, 27)
assert SUP == 36.00 and PIV == 40.00, (SUP, PIV)
assert PIV > SUP, "umărul drept trebuie să stea peste suportul capului"
assert abs(lo(SC, 5, 8) - PIV) <= 0.20, "umerii trebuie să fie aproximativ simetrici"
assert abs(SC[27].c - SC[27].o) <= 0.15 * (SC[27].h - SC[27].l), "bara 27 nu e doji"
assert SC[28].c > hi(SC, 27, 27) and SC[28].c > hi(SC, 9, 11), \
"breakout-ul trebuie să treacă peste doji și peste gât"
deco(Chart(
candles=SC,
levels=[Level(SUP, f"suport monthly {SUP:.2f}", "#b06000", label_at="left"),
Level(PIV, f"pivot monthly {PIV:.2f}", "#6a3d9a", label_at="left")],
marks=[Mark(6, "umărul stâng", "#6a3d9a", dy=-22),
Mark(14, "capul, în suport", "#b06000", "below", dy=16),
Mark(27, "doji", "#b06000", "below", dy=28),
Mark(28, "buy doji = trigger", "#188a3e", dy=-44)],
zones=[Zone(21, 27, "umărul drept, pullback pe pivot", "#188a3e")],
mas=[MA(20, "#b06000", dash="4 3", text="SMA-20")],
scale=14.0, gap=17, body_w=9, left=58, width=700, top=46, vol_h=70,
vol_label="volum", title="Pasul 1, DA — capul în suport, umărul drept pe pivot",
caption="Exemplu construit (fără ticker), la scară. Volumul crește pe "
"ultimele bare ale umărului și explodează pe breakout."), "pas1_da")
# ======================================== 3. pasul 1, NU — triple bottom la al 4-lea test
N_CL = (
[38.00, 36.50, 35.00, 33.60, 32.40] # 0-4 downtrend
+ [31.10, 32.80, 34.00] # 5-7 piciorul 1
+ [33.00, 31.90, 31.20, 32.60, 33.80] # 8-12 piciorul 2
+ [32.90, 31.80, 31.10, 32.20, 33.20] # 13-17 piciorul 3
+ [32.40, 31.60, 31.00, 31.40, 30.20, 28.60, 27.00]) # 18-24 al 4-lea: rupe
N_WK = ([(0.40, 0.40)] * 5
+ [(0.30, 0.10), (0.30, 0.30), (0.30, 0.30)]
+ [(0.30, 0.30), (0.30, 0.30), (0.30, 0.10), (0.30, 0.30), (0.30, 0.30)]
+ [(0.30, 0.30), (0.30, 0.30), (0.30, 0.10), (0.30, 0.30), (0.30, 0.30)]
+ [(0.30, 0.30), (0.30, 0.30), (0.30, 0.20), (0.30, 0.30),
(0.30, 0.30), (0.30, 0.30), (0.30, 0.30)])
N_VL = vols(25, 54, 14, i5=74, i10=58, i15=46, i20=36, i21=40, i22=62, i23=70,
i24=76)
NC = from_closes(N_CL, wicks=N_WK, vols=N_VL, start=39.00)
BAZA = floor_lows(NC, 5, 17, 31.00)
assert lo(NC, 5, 17) == 31.00, lo(NC, 5, 17)
_pic = [min(c.c for c in NC[a:b + 1]) for a, b in ((5, 7), (8, 12), (13, 17))]
assert max(_pic) - min(_pic) <= 0.30, \
f"cele trei picioare trebuie să închidă aproximativ la același nivel: {_pic}"
assert lo(NC, 18, 24) < BAZA, "al 4-lea test trebuie să rupă baza"
assert N_VL[5] > N_VL[10] > N_VL[15] > N_VL[20], \
"volumul trebuie să scadă de la un picior la altul"
deco(Chart(
candles=NC,
levels=[Level(BAZA, f"baza testată de 3 ori: {BAZA:.2f}", "#c00")],
marks=[Mark(5, "1", "#6a3d9a", "below", dy=14),
Mark(10, "2", "#6a3d9a", "below", dy=14),
Mark(15, "3", "#6a3d9a", "below", dy=14),
Mark(20, "al 4-lea test: nu mai ține", "#c00", "below", dy=96),
Mark(8, "volumul scade la fiecare picior", "#c00", dy=-8)],
zones=[Zone(18, 24, "breakdown, nu breakout", "#c00")],
mas=[MA(20, "#b06000", dash="4 3", text="SMA-20")],
scale=20.0, gap=19, body_w=9, left=58, width=700, top=46, vol_h=70,
label_gap=60,
vol_label="volum", title="Pasul 1, NU — triple bottom la al 4-lea test",
caption="Exemplu construit (fără ticker), la scară. Cu cât baza e retestată "
"mai des și volumul scade, cresc șansele să cadă."),
"pas1_nu")
# ========================================= 4. pasul 2, DA — trigger-ul din umărul drept
T_CL = ([55.00, 56.50, 58.00, 59.50, 61.00, 62.20, 63.00] # 0-6 urcarea din cap
+ [62.40, 61.30, 60.20, 59.40, 59.90, 59.20, 59.70,
59.10, 59.80, 59.30] # 7-16 consolidarea
+ [59.40] # 17 doji
+ [65.00] # 18 buy doji/breakout
+ [64.20, 65.80, 67.50, 69.00]) # 19-22 continuă
T_WK = ([(0.40, 0.40)] * 7
+ [(0.30, 0.30)] * 10
+ [(0.90, 0.70)] # 17 doji
+ [(0.20, 0.20)] # 18 breakout
+ [(0.40, 0.40)] * 4)
T_VL = vols(23, 44, 10, i4=78, i8=46, i9=42, i10=40, i11=44, i12=48, i13=52,
i14=58, i15=64, i16=70, i17=76, i18=118, i19=70, i20=84, i21=90)
TC = from_closes(T_CL, wicks=T_WK, vols=T_VL, start=54.00)
CONS_HI = hi(TC, 7, 17)
assert abs(TC[17].c - TC[17].o) <= 0.15 * (TC[17].h - TC[17].l), "bara 17 nu e doji"
assert TC[18].c > CONS_HI, "breakout-ul trebuie să închidă peste vârful consolidării"
assert T_VL[18] > T_VL[4], "volumul de breakout trebuie să treacă peste swing high-ul lui"
assert T_VL[10] < T_VL[13] < T_VL[16] < T_VL[17], "volumul nu e în acumulare"
deco(Chart(
candles=TC,
levels=[Level(CONS_HI, f"vârful consolidării {CONS_HI:.2f}", "#6a3d9a",
label_at="left")],
marks=[Mark(17, "doji: setup-ul", "#b06000", "below", dy=26),
Mark(18, "buy doji = trigger", "#188a3e", dy=-14),
Mark(11, "↓ volum în acumulare: bare tot mai mari", "#188a3e",
"below", dy=26),
Mark(4, "↓ swing high de volum", "#1a5fb4", "below", dy=70)],
zones=[Zone(7, 17, "umărul drept: 11 bare de consolidare", "#188a3e")],
mas=[MA(20, "#b06000", dash="4 3", text="SMA-20")],
scale=16.0, gap=26, body_w=11, left=58, width=700, top=46, vol_h=80,
vol_label="volum", title="Pasul 2, DA — trigger valid din umărul drept",
caption="Exemplu construit (fără ticker). Volumul de breakout trece peste "
"swing high-ul anterior de volum."), "pas2_da")
# ========================================== 5. pasul 2, NU — volumul rupe în jos
X_CL = ([55.00, 56.50, 58.00, 59.50, 61.00, 62.20, 63.00]
+ [62.40, 61.30, 60.20, 59.40, 59.90, 59.20, 59.70,
59.10, 59.80, 59.30]
+ [59.40] # 17 același doji
+ [64.40] # 18 breakout, dar fără volum
+ [62.00, 60.00, 57.50, 55.00]) # 19-22 breakdown
X_WK = T_WK
# Regula de „nu intra" din sursă e despre o histogramă CU SEMN care cade sub
# swing low-ul ei („histograma asta cade sub swing low-ul de aici […] iar prețul
# încă nu e acolo"), nu despre bare brute pornite de la o bază: pe bare
# nesemnate „minime tot mai joase" nu înseamnă nimic vizual. De aceea panoul de
# jos al acestei diagrame e desenat în jurul unui zero (`vol_zero`), cu trei
# minime tot mai joase, iar bara candelei de breakout fals e mică și sub zero.
ZERO = 50.0
X_VL = [60.0, 64.0, 68.0, 74.0, 80.0, 72.0, 66.0, # 0-6 urcarea
56.0, 50.0, 42.0, # 7-9 minimul 1
48.0, 46.0, 38.0, # 10-12 minimul 2
44.0, 42.0, 34.0, 40.0, # 13-16 minimul 3
36.0, # 17 doji
44.0, # 18 breakout fals
30.0, 24.0, 20.0, 16.0] # 19-22 breakdown
XC = from_closes(X_CL, wicks=X_WK, vols=X_VL, start=54.00)
assert abs(XC[17].c - XC[17].o) <= 0.15 * (XC[17].h - XC[17].l), "bara 17 nu e doji"
assert XC[18].c > hi(XC, 7, 17), "breakout-ul de preț există și aici"
assert X_VL[18] < X_VL[4], "volumul de breakout NU trebuie să treacă swing high-ul"
assert X_VL[18] < ZERO, \
"pe candela de breakout histograma trebuie să fie deja sub zero"
_SW = (9, 12, 15)
for _i in _SW:
assert X_VL[_i - 1] > X_VL[_i] < X_VL[_i + 1], \
f"bara {_i} nu e un minim local al histogramei"
assert X_VL[_i] < ZERO, f"minimul de la bara {_i} trebuie să fie sub zero"
assert X_VL[_SW[0]] > X_VL[_SW[1]] > X_VL[_SW[2]], \
f"minimele histogramei nu coboară: {[X_VL[i] for i in _SW]}"
assert XC[22].c < lo(XC, 7, 17), "după breakout-ul fals prețul rupe consolidarea"
deco(Chart(
candles=XC,
levels=[Level(lo(XC, 7, 17), f"baza {lo(XC, 7, 17):.2f}", "#c00",
label_at="left")],
marks=[Mark(17, "doji", "#b06000", "below", dy=26),
Mark(18, "închide peste consolidare, dar volumul e deja sub zero",
"#c00", dy=-10),
Mark(11, "histograma rupe sub propriul swing low, de trei ori",
"#c00", "below", dy=46),
Mark(22, "breakdown", "#c00", "below", dy=14)],
zones=[Zone(7, 17, "aceeași consolidare de 11 bare", "#c00")],
mas=[MA(20, "#b06000", dash="4 3", text="SMA-20")],
scale=14.0, gap=26, body_w=11, left=58, width=700, top=46, vol_h=80,
vol_zero=ZERO,
vol_label="histogramă de volum, cu semn (stil on balance volume)",
title="Pasul 2, NU — breakout de preț fără breakout de volum",
caption="Exemplu construit. Prețul e identic cu al diagramei de deasupra; "
"panoul de jos e histograma cu semn, nu volumul brut."), "pas2_nu")
# ================================================== 6. pasul 3 — stopul, treimea de jos
deco(Chart(
candles=CDS[6:36],
levels=[Level(UMD, f"umăr drept {UMD:.2f}", "#6a3d9a"),
Level(CAP, f"cap {CAP:.2f} — baza lui H", "#6a3d9a",
label_at="left"),
Level(SL, f"SL cel mai sus permis: {SL:.2f} = cap + H/3", "#c00"),
Level(E, f"intrare {E:.2f}; distanța până la SL e R", "#188a3e",
label_at="left")],
marks=[Mark(12, "capul", "#6a3d9a", "below", dy=16),
Mark(22, "banda roșie = treimea de jos din H", "#c00",
"below", dy=104)],
zones=[Zone(8, 27, "", "#c00", p_top=SL, p_bot=CAP)],
scale=13.0, gap=20, body_w=10, left=58, width=700, top=46,
title="Pasul 3, stopul — H de la cap la umărul drept, apoi o treime",
caption=CONSTRUIT), "pas3_stop", distante=(H, R))
# ========================================================= 7. pasul 4 — cele două ținte
deco(Chart(
candles=CDS[18:],
levels=[Level(CAP, f"capul {CAP:.2f} = baza înălțimii HS", "#6a3d9a"),
Level(SL, f"SL {SL:.2f}", "#c00", label_at="left"),
Level(E, f"intrare {E:.2f}", "#188a3e", label_at="left"),
Level(GAT, f"linia gâtului {GAT:.2f}, din interiorul gâtului "
f"(nu de la vârfuri)", "#6a3d9a", label_at="left"),
Level(TP2, f"TP2 {TP2:.2f} = gât + înălțimea HS "
f"({GAT:.2f} − {CAP:.2f} = {H_HS:.2f})", "#1a5fb4",
label_at="left"),
Level(TP1, f"TP1 {TP1:.2f} = intrare + R", "#1a5fb4",
label_at="left")],
marks=[Mark(14, "buy doji", "#188a3e", dy=-10)],
mas=[MA(20, "#b06000", dash="4 3", text="SMA-20")],
scale=12.0, gap=24, body_w=10, left=58, width=700, top=46,
title="Pasul 4, țintele — TP1 = R, TP2 = proiecția gâtului",
caption="Exemplu construit (fără ticker), la scară. Aici TP1 iese PESTE TP2; "
"ordinea lor nu e fixă."), "pas4_tinte", distante=(R, H_HS))
# ================================================ 8. retestul „locului crimei", DA
Y_CL = ([50.00, 51.20, 52.40, 53.00, 52.20, 53.40, 52.60, 53.20,
52.40, 53.00] # 0-9 consolidarea
+ [56.00] # 10 breakout
+ [54.60, 53.00, 52.70] # 11-13 retest în consolidare
+ [52.80] # 14 doji
+ [57.20, 58.80, 60.50, 62.00]) # 15-18 pleacă
Y_WK = ([(0.30, 0.30)] * 10 + [(0.20, 0.20)]
+ [(0.30, 0.30), (0.30, 0.30), (0.30, 0.30)]
+ [(0.60, 0.50)] + [(0.30, 0.30)] * 4)
# barele de retest trebuie să fie mai mici decât ale consolidării: legenda
# spune „volum mic pe retest, mare pe repornire"
Y_VL = vols(19, 44, 10, i10=96, i11=30, i12=26, i13=24, i14=28, i15=88, i16=82)
YC = from_closes(Y_CL, wicks=Y_WK, vols=Y_VL, start=49.00)
C_HI, C_LO = hi(YC, 0, 9), lo(YC, 0, 9)
JUM = round((C_HI + C_LO) / 2, 2)
assert YC[10].c > C_HI, "bara 10 trebuie să facă breakout peste consolidare"
assert JUM < lo(YC, 11, 14) < C_HI, \
"retestul trebuie să intre în consolidare, dar să se oprească peste jumătate"
assert YC[18].c > YC[10].c, "după retest prețul trebuie să plece mai sus"
assert max(Y_VL[11:15]) < min(Y_VL[0:10]), \
"barele de retest trebuie să fie sub toate barele consolidării"
assert Y_VL[15] > max(Y_VL[0:15]) * 0.9, "repornirea trebuie să vină cu volum"
# dreptunghiul consolidării e ancorat exact de cele două cifre din etichete,
# fără cei 8 px de padding impliciți: altfel capacul nu cade pe 53.70
assert JUM == round((C_HI + C_LO) / 2, 2), "jumătatea nu e a intervalului declarat"
deco(Chart(
candles=YC,
levels=[Level(C_HI, f"vârful consolidării {C_HI:.2f} = locul crimei", "#6a3d9a"),
Level(JUM, f"jumătatea consolidării {JUM:.2f} "
f"= ({C_HI:.2f} + {C_LO:.2f}) / 2", "#b06000"),
Level(C_LO, f"baza consolidării {C_LO:.2f}", "#6a3d9a")],
marks=[Mark(10, "breakout", "#188a3e", dy=-10),
Mark(13, "retest, se oprește peste jumătate", "#188a3e", "below", dy=20),
Mark(14, "doji aici: a doua intrare", "#188a3e", "below", dy=38)],
zones=[Zone(0, 9, "consolidarea", "#6a3d9a", p_top=C_HI, p_bot=C_LO)],
scale=22.0, gap=30, body_w=12, left=58, width=700, top=46, vol_h=70,
vol_label="volum", title="Retestul, DA — se oprește în jumătatea de sus",
caption="Exemplu construit (fără ticker). Volum mic pe retest, mare pe "
"repornire: nu vin vânzători noi."), "retest_da")
# ================================================ 9. retestul „locului crimei", NU
Z_CL = ([50.00, 51.20, 52.40, 53.00, 52.20, 53.40, 52.60, 53.20,
52.40, 53.00]
+ [56.00]
+ [54.80, 53.20, 51.80, 50.40] # 11-14 trece tot jos
+ [49.00, 47.60, 46.00, 44.50]) # 15-18 breakdown
Z_WK = ([(0.30, 0.30)] * 10 + [(0.20, 0.20)] + [(0.30, 0.30)] * 8)
Z_VL = vols(19, 44, 10, i10=96, i11=58, i12=64, i13=72, i14=80, i15=88, i16=94)
ZC = from_closes(Z_CL, wicks=Z_WK, vols=Z_VL, start=49.00)
assert hi(ZC, 0, 9) == C_HI and lo(ZC, 0, 9) == C_LO, \
"consolidarea trebuie să fie identică cu cea din diagrama DA"
assert ZC[10].c > C_HI, "și aici există breakout"
assert ZC[14].c < JUM and ZC[18].c < C_LO, "retestul trece sub bază: breakdown"
assert Z_VL[13] > Z_VL[11], "volumul crește pe coborâre — vin vânzători"
deco(Chart(
candles=ZC,
levels=[Level(C_HI, f"vârful consolidării {C_HI:.2f}", "#6a3d9a"),
Level(JUM, f"jumătatea consolidării {JUM:.2f}", "#b06000"),
Level(C_LO, f"baza {C_LO:.2f}", "#c00")],
marks=[Mark(10, "același breakout", "#188a3e", dy=-10),
Mark(13, "retestul trece sub jumătate", "#c00", "below", dy=52),
Mark(17, "volum în creștere pe coborâre", "#c00", "below", dy=16)],
zones=[Zone(0, 9, "aceeași consolidare", "#6a3d9a", p_top=C_HI, p_bot=C_LO)],
scale=15.0, gap=30, body_w=12, left=58, width=700, top=46, vol_h=70,
vol_label="volum", title="Retestul, NU — trece sub jumătate și rupe baza",
caption="Exemplu construit (fără ticker). Același breakout; diferă cât de "
"adânc cade retestul și ce face volumul."),
"retest_nu")
# ================================================= 10. varianta short — ascending wedge
W_CL = ([52.10, 52.50, 52.90] # 0-2 uptrend anterior
+ [53.40, 52.80, 52.10] # 3-5 vârf 1, picior 1
+ [52.80, 53.40, 53.90, 53.20, 52.70] # 6-10 vârf 2, picior 2
+ [53.20, 53.70, 54.10, 53.60, 53.20] # 11-15 vârf 3, picior 3
+ [53.60, 53.95] # 16-17 ultimul test
+ [53.90] # 18 sell doji
+ [53.10, 52.50, 52.00, 51.50, 51.00, 50.90, 51.20]) # 19-25 cade spre 51
W_WK = ([(0.25, 0.25)] * 18 + [(0.55, 0.50)] + [(0.25, 0.25)] * 7)
W_VL = vols(26, 48, 10, i3=62, i8=50, i10=40, i12=36, i13=34, i15=30, i16=28,
i17=26, i18=30, i19=78, i20=86, i21=92, i22=96, i23=88)
WC = from_closes(W_CL, wicks=W_WK, vols=W_VL, start=51.80)
TINTA = 51.00
L1, L2, L3 = lo(WC, 4, 6), lo(WC, 9, 11), lo(WC, 14, 16)
H1, H2, H3 = hi(WC, 2, 4), hi(WC, 7, 9), hi(WC, 12, 14)
assert L1 < L2 < L3, f"minimele wedge-ului trebuie să urce: {L1} {L2} {L3}"
assert H1 < H2 < H3, f"maximele trebuie să urce și ele: {H1} {H2} {H3}"
assert (H1 - L1) > (H2 - L2) > (H3 - L3), \
f"wedge înseamnă convergență: {H1-L1:.2f} {H2-L2:.2f} {H3-L3:.2f}"
assert abs(WC[18].c - WC[18].o) <= 0.20 * (WC[18].h - WC[18].l), "bara 18 nu e doji"
assert WC[19].c < lo(WC, 17, 18), "sell doji-ul trebuie să rupă în jos"
assert lo(WC, 21, 25) <= TINTA, "seria trebuie să atingă ținta rostită, 51"
assert W_VL[17] == min(W_VL), "volumul trebuie să se stingă în vârful wedge-ului"
assert max(W_VL[19:23]) > max(W_VL[:19]), "volumul trebuie să explodeze pe coborâre"
print(f" wedge: picioare {L1}/{L2}/{L3} vârfuri {H1}/{H2}/{H3} "
f"deschideri {H1-L1:.2f}/{H2-L2:.2f}/{H3-L3:.2f}")
deco(Chart(
candles=WC,
levels=[Level(TINTA, "ținta rostită în lecție: 51", "#1a5fb4",
label_at="left")],
marks=[Mark(16, "volumul se stinge", "#c00", "below", dy=30),
Mark(18, "sell doji = trigger de short", "#c00", dy=-10),
Mark(23, "ținta atinsă", "#1a5fb4", "below", dy=16),
Mark(10, "minimele urcă mai repede decât maximele", "#c00",
"below", dy=40)],
zones=[Zone(2, 18, "", "#c00")],
mas=[MA(20, "#b06000", dash="4 3", text="SMA-20")],
scale=65.0, gap=23, body_w=10, left=58, width=700, top=46, vol_h=70,
vol_label="volum", title="Varianta short — XLF, ascending wedge",
caption="Al lui: ținta de 51 și patternul pe XLF. Barele sunt ale mele; "
"wedge-ul se îngustează, nu urcă paralel."),
"short_wedge", intregi=(51,))
# ============================================ 11. HS răsturnat și pe volum (schiță)
SVG["hs_pe_volum"] = """<svg viewBox="0 0 700 410" xmlns="http://www.w3.org/2000/svg" style="width:100%;max-width:700px;height:auto;font:12px sans-serif">
<text x="20" y="22" fill="#333" style="font-weight:bold">Secretul: același HS răsturnat, de două ori — pe preț și pe volum</text>
<text x="20" y="44" fill="#555">Sus prețul, jos linia de trend a volumului. Când ambele desenează aceeași formă, setup-ul e mai tare.</text>
<rect x="20" y="60" width="660" height="150" fill="#fbfbfb" stroke="#ddd"/>
<text x="30" y="78" fill="#6a3d9a" style="font-weight:bold">PREȚ</text>
<polyline points="40,80 90,120 140,150 190,118 240,135 290,180 340,195 390,160 440,145 490,152 540,140 590,96 650,74" fill="none" stroke="#1a1a1a" stroke-width="2"/>
<line x1="150" y1="131" x2="610" y2="131" stroke="#6a3d9a" stroke-width="1.4" stroke-dasharray="4 3"/>
<text x="614" y="127" fill="#6a3d9a">linia gâtului</text>
<circle cx="140" cy="150" r="4" fill="#6a3d9a"/>
<text x="112" y="172" fill="#6a3d9a">umăr stâng</text>
<circle cx="340" cy="195" r="4" fill="#6a3d9a"/>
<text x="318" y="212" fill="#6a3d9a">cap</text>
<circle cx="490" cy="152" r="4" fill="#6a3d9a"/>
<text x="464" y="174" fill="#6a3d9a">umăr drept</text>
<text x="556" y="90" fill="#188a3e" style="font-weight:bold">breakout</text>
<rect x="20" y="226" width="660" height="150" fill="#fbfbfb" stroke="#ddd"/>
<text x="30" y="244" fill="#1a5fb4" style="font-weight:bold">VOLUM (linia de trend)</text>
<polyline points="40,268 90,292 140,322 190,296 240,308 290,336 340,352 390,322 440,292 490,298 540,264 590,248 650,236" fill="none" stroke="#1a5fb4" stroke-width="2"/>
<line x1="140" y1="322" x2="490" y2="322" stroke="#1a5fb4" stroke-width="1" stroke-dasharray="3 3"/>
<circle cx="140" cy="322" r="4" fill="#1a5fb4"/>
<text x="112" y="342" fill="#1a5fb4">umăr stâng</text>
<circle cx="340" cy="352" r="4" fill="#1a5fb4"/>
<text x="318" y="370" fill="#1a5fb4">cap</text>
<circle cx="490" cy="298" r="4" fill="#1a5fb4"/>
<text x="430" y="288" fill="#1a5fb4">umăr drept, mai sus decât cel stâng</text>
<text x="20" y="398" fill="#555">Umărul drept de pe volum mai sus decât cel stâng: cumpărătorii revin cu mai multă forță decât prima dată. Asta cauți.</text>
</svg>"""
# ================================================ 12. cadranele de filtru (schiță)
SVG["cadrane"] = """<svg viewBox="0 0 700 330" xmlns="http://www.w3.org/2000/svg" style="width:100%;max-width:700px;height:auto;font:12px sans-serif">
<text x="20" y="22" fill="#333" style="font-weight:bold">„Cadran long" — cele două cadrane în care ai voie să iei trigger-ul</text>
<rect x="60" y="44" width="150" height="100" fill="#eef4ff" stroke="#1a5fb4"/>
<text x="70" y="68" fill="#1a5fb4" style="font-weight:bold">improving</text>
<text x="70" y="88" fill="#333">se îmbunătățește,</text>
<text x="70" y="106" fill="#333">încă sub piață</text>
<text x="70" y="130" fill="#188a3e" style="font-weight:bold">DA</text>
<rect x="210" y="44" width="150" height="100" fill="#e9f7ee" stroke="#188a3e"/>
<text x="220" y="68" fill="#188a3e" style="font-weight:bold">outperforming</text>
<text x="220" y="88" fill="#333">bate piața</text>
<text x="220" y="130" fill="#188a3e" style="font-weight:bold">DA</text>
<rect x="60" y="144" width="150" height="100" fill="#fdeeee" stroke="#c00"/>
<text x="70" y="168" fill="#c00" style="font-weight:bold">lagging</text>
<text x="70" y="188" fill="#333">rămâne în urmă</text>
<text x="70" y="230" fill="#c00" style="font-weight:bold">NU</text>
<rect x="210" y="144" width="150" height="100" fill="#fdeeee" stroke="#c00"/>
<text x="220" y="168" fill="#c00" style="font-weight:bold">weakening</text>
<text x="220" y="188" fill="#333">se deteriorează</text>
<text x="220" y="230" fill="#c00" style="font-weight:bold">NU</text>
<text x="60" y="266" fill="#555">slab ←</text>
<text x="330" y="266" fill="#555">→ puternic</text>
<text x="400" y="62" fill="#333" style="font-weight:bold">Ordinea de importanță, după autor</text>
<text x="400" y="86" fill="#555">1. volumul — oricare dintre cele două</text>
<text x="400" y="106" fill="#555">2. relative strength în cadran long</text>
<text x="400" y="126" fill="#555">3. power în cadran long</text>
<text x="400" y="156" fill="#333" style="font-weight:bold">Toate trei aliniate = Trinity</text>
<text x="400" y="178" fill="#555">Nu e obligatoriu: „nu poți să le ai</text>
<text x="400" y="196" fill="#555">chiar pe toate". Volumul singur e</text>
<text x="400" y="214" fill="#555">suficient ca filtru minim.</text>
<text x="20" y="296" fill="#555">Relative strength se citește față de SPY, deci are sens doar pe equities (acțiuni, ETF-uri de sector).</text>
<text x="20" y="316" fill="#555">Pe commodities schimbi reperul pe o clasă similară: petrol față de gaze, aur față de argint, grâu față de porumb.</text>
</svg>"""
# ---------------------------------------------------------------- substituire
if os.path.exists(MD):
with open(MD, encoding="utf-8") as f:
md = f.read()
lipsa = [n for n in SVG if f"<!--SVG:{n}-->" not in md]
assert not lipsa, f"marcatori lipsă în .md: {lipsa}"
for n, svg in SVG.items():
md = re.sub(rf"<!--SVG:{n}-->(\s*<svg\b[\s\S]*?</svg>)?",
lambda _m: f"<!--SVG:{n}-->\n{svg}", md, count=1)
with open(MD, "w", encoding="utf-8") as f:
f.write(md)
print(f"\n{len(SVG)} diagrame substituite in {MD}")
else:
print(f"\n{len(SVG)} diagrame generate; {MD} nu exista inca")