Files
atm-curs-dl/gen/az_context.py
Claude Agent f647874a52 datoria tehnica: garda pe dash, si PChart intr-un singur loc
Garda: `Level(p, text, color, "left")` punea tacut "left" in `dash` in loc
de `label_at`, iar SVG-ul iesea fara linie punctata si fara nicio eroare -
trei agenti au pierdut timp pe asta pe 13.09. Acum `__post_init__` crapa
cu mesaj care spune ce argument voiai. Verificat pe cazurile valide si pe
cele doua gresite.

PChart: era copiat in cinci generatoare, iar copiile divergisera - toate
cinci aveau hash-uri diferite si patru pastrau `spread()` cel vechi.
Nucleul comun trece in gen/pchart.py; 385 de linii sterse, 11 adaugate.

az_executie.py isi pastreaza PChart-ul: varianta lui calculeaza mediile
dintr-un istoric dinainte de bara 0 si si le deseneaza singura. Unirea
celor doua comportamente ca sa scap de un fisier ar fi riscat cele 10
diagrame de acolo, pentru zero castig. Ia doar `spread` reparat.

Reparatia schimba 4 documente, deci PROGRESS gresea cand spunea ca nicio
diagrama comisa nu sufera de bug: 08 si 10 se misca cu 0,2 px, dar 07 si
09 aveau etichete la 6 px de linia lor. Verificat cu ochii pe 3 diagrame
randate - etichetele stau acum pe linia lor, cu liniuta de legatura unde
a trebuit mutata. audit_diagrams: 0 probleme.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ThN7ixZrowca8aTGrK1MUJ
2026-09-18 11:57:57 +00:00

435 lines
24 KiB
Python

"""Diagramele pentru summaries/08_ANALIZA_ZILNICA/07_context_de_piata.md.
Toate prețurile sunt construite (fără ticker). Rulează:
.venv/bin/python gen/az_context.py
Înlocuiește în .md fiecare bloc <!--SVG:nume--> ... <!--/SVG--> cu output-ul
generatorului. Ca să schimbi o cifră, o schimbi aici și rulezi din nou.
`PChart` e copiat din gen/az_pivoti.py: etichetele nivelurilor stau în marginea
din dreapta, după ultima candelă, împinse în jos când se ating.
McClellan, AAII, Fear & Greed, breadth și COT nu sunt preț: în .md sunt tabele.
"""
import re
import sys
from dataclasses import dataclass, field
from pathlib import Path
sys.path.insert(0, "/workspace/atm-curs-dl")
from svg_candles import Chart, Level, Mark, MA, from_closes # noqa: E402
MD = Path("/workspace/atm-curs-dl/summaries/08_ANALIZA_ZILNICA/07_context_de_piata.md")
GRAY, RED, GREEN, BLUE, ORANGE, PURPLE = "#777", "#c00", "#188a3e", "#1a5fb4", "#b06000", "#6a3d9a"
STYLE = dict(gap=15, body_w=9, left=44, width=620)
svgs = {}
from pchart import PChart, Seg, spread # noqa: E402
def px_ratio(ch, a, b, c):
"""raportul (c-b)/(b-a) măsurat în pixeli, după render"""
return (ch._y(b) - ch._y(c)) / (ch._y(a) - ch._y(b))
def f2(x):
return f"{x:.2f}"
def near(a, b, tol=0.011):
return abs(a - b) <= tol
def wicks(n, special, default=(0.3, 0.3)):
w = [default] * n
for i, v in special.items():
w[i] = v
return w
def true_range(cds):
out = [cds[0].h - cds[0].l]
for p, c in zip(cds, cds[1:]):
out.append(max(c.h, p.c) - min(c.l, p.c))
return out
V = {} # cifrele folosite în text, tipărite la final
# ================================================================ 1. VIX DA: reject în fostul suport + lower low
vix_head = [15.2, 14.8, 15.4, 15.0, 14.6, 15.1, # 0-5 calm
17.0, 19.5, 23.8, 27.5, 26.0, # 6-10 spike
24.1, 22.0, 20.2, 19.6, 20.4, 19.8, 20.6, 19.5, # 11-18 se sprijină pe 19.20
17.9, 16.9, 16.4] # 19-21 sparge suportul
vw_head = {8: (0.8, 0.3), 9: (0.9, 0.4), 13: (0.3, 1.0), 14: (0.3, 0.4), 15: (0.3, 0.4), 16: (0.3, 0.6),
17: (0.3, 0.6), 18: (0.3, 0.3)}
vix_da = vix_head + [17.4, 18.5, 17.6, 16.8, 16.2, 15.6, 15.9, 15.1, 14.7, 15.0, 14.5, 14.3]
SUP = 19.20
RJ = 23
cds = from_closes(vix_da, wicks=wicks(len(vix_da), {**vw_head, 23: (0.7, 0.2)}), start=15.0)
assert all(near(cds[i].l, SUP) for i in (13, 14, 15, 18)), [cds[i].l for i in range(13, 19)]
assert all(c.l >= SUP - 0.01 for c in cds[11:19]), "suportul 19.20 ține pe barele 11-18"
assert cds[19].c < SUP, "bara 19 închide sub suport"
assert all(c.h < SUP for c in cds[20:RJ]), "după bara de spargere nimic nu revine la 19.20 până la retest"
assert near(cds[RJ].h, SUP) and cds[RJ].c < SUP, "retestul atinge 19.20 din jos și închide sub"
assert cds[RJ + 1].c < cds[RJ].c, "bara de după retest confirmă rejectul"
LL0 = min(c.l for c in cds[19:22])
LL = next(i for i in range(RJ + 1, len(cds)) if cds[i].l < LL0)
assert all(c.h < SUP for c in cds[RJ + 1:])
peak_v = max(range(len(cds)), key=lambda i: cds[i].h)
V["vix"] = dict(sup=SUP, peak=cds[peak_v].h, rj_h=cds[RJ].h, rj_c=cds[RJ].c, ll0=LL0, ll=LL, ll_l=cds[LL].l,
end=cds[-1].c)
print("D1 VIX", {k: (f2(v) if isinstance(v, float) else v) for k, v in V["vix"].items()})
ch = PChart(candles=cds,
levels=[Level(SUP, f"fost suport = rezistență {SUP:.2f}", ORANGE)],
marks=[Mark(peak_v, f"spike {cds[peak_v].h:.1f}", RED, "above"),
Mark(16, "suport", ORANGE, "below", dy=4),
Mark(RJ, "reject", RED, "above", dy=-4),
Mark(LL, "lower low", GREEN, "below", dy=4)],
scale=12, title="Exemplu construit (fără ticker) — daily, VIX (citit invers)",
caption="VIX respins sub fostul suport și lower low: frica scade, favorabil long-urilor pe indice.",
**STYLE)
svgs["vix_da"] = ch.render()
# ================================================================ 2. indicele, aceeași perioadă: HL + long
idx = [55.0, 55.3, 54.9, 55.2, 55.5, 55.2,
54.2, 53.0, 51.2, 49.8, 50.3,
51.0, 51.9, 52.4, 52.7, 52.2, 52.6, 52.1, 52.6,
53.4, 54.0, 54.3,
53.8, 53.2, 54.3,
54.8, 55.3, 55.9, 55.6, 56.2, 56.8, 56.5, 57.1, 57.5]
assert len(idx) == len(vix_da), "aceeași perioadă, bară cu bară"
cds = from_closes(idx, wicks=wicks(len(idx), {8: (0.3, 0.8), 9: (0.3, 0.9)}), start=55.2)
HL, TRG = RJ, RJ + 1
bottom = min(range(len(cds)), key=lambda i: cds[i].l)
assert bottom == peak_v, "bottom-ul indicelui = spike-ul VIX"
assert cds[HL].l > max(c.l for c in cds[11:19]), "pullback-ul face higher low peste consolidarea de după bottom"
assert cds[HL].l == min(c.l for c in cds[20:]), "bara 23 e low-ul pullback-ului de după bara 19"
assert cds[TRG].c > cds[HL].h, "trigger: close peste high-ul barei de higher low"
entry = cds[TRG].c
sl = round(cds[HL].l - 0.10, 2)
risk = entry - sl
tp = round(entry + 2 * risk, 2)
hit = next(i for i in range(TRG + 1, len(cds)) if cds[i].h >= tp)
assert min(c.l for c in cds[TRG + 1:]) > sl
V["idx"] = dict(bottom=cds[bottom].l, hl=cds[HL].l, entry=entry, sl=sl, risk=risk, tp=tp, hit=hit - TRG)
print("D2 indice", {k: (f2(v) if isinstance(v, float) else v) for k, v in V["idx"].items()})
ch = PChart(candles=cds,
levels=[Level(tp, f"TP {f2(tp)} = 2R", BLUE),
Level(entry, f"intrare {f2(entry)}", GREEN),
Level(sl, f"SL {f2(sl)}", RED)],
marks=[Mark(bottom, "bottom (VIX în spike)", GRAY, "below", dy=4),
Mark(HL, "HL = reject pe VIX", GREEN, "below", dy=14),
Mark(hit, "TP", BLUE, "above")],
scale=24, title="Exemplu construit (fără ticker) — daily, indicele, aceeași perioadă",
caption="Bara 23: VIX respins la 19.20, indicele face higher low. Intrarea vine pe close-ul următor.",
**STYLE)
svg_ = ch.render()
assert abs(px_ratio(ch, sl, entry, tp) - 2.0) < 0.01, "2R nu e desenat la 2x risc"
svgs["vix_indice"] = svg_
# ================================================================ 3. VIX NU: higher low pe VIX
vix_nu = vix_head + [17.4, 18.5, 17.6, 17.2, 18.4, 19.8, 21.6, 23.4, 22.5, 24.6, 23.8, 25.2]
cds = from_closes(vix_nu, wicks=wicks(len(vix_nu), {**vw_head, 23: (0.7, 0.2), 25: (0.2, 0.3)}), start=15.0)
assert [c.c for c in cds[:24]] == [c.c for c in from_closes(vix_da, start=15.0)[:24]], "până la bara 23 identic cu DA"
HLV = 25
assert cds[HLV].l > LL0 and cds[HLV].l == min(c.l for c in cds[RJ:]), "VIX face higher low"
back = next(i for i in range(HLV, len(cds)) if cds[i].c > SUP)
V["vixnu"] = dict(hl=cds[HLV].l, back=back, back_c=cds[back].c, end=cds[-1].c)
print("D3 VIX NU", {k: (f2(v) if isinstance(v, float) else v) for k, v in V["vixnu"].items()})
ch = PChart(candles=cds,
levels=[Level(SUP, f"fost suport {SUP:.2f}", ORANGE),
Level(LL0, f"low anterior {f2(LL0)}", GRAY)],
marks=[Mark(RJ, "reject?", GRAY, "above", dy=-4),
Mark(HLV, "higher low", RED, "below", dy=4)],
scale=12, title="Exemplu construit (fără ticker) — daily, VIX (citit invers)",
caption="Același început, dar VIX face higher low și închide peste 19.20: risc de nouă cădere pe indice.",
**STYLE)
svgs["vix_nu"] = ch.render()
# ================================================================ 4. ATR în spike la locul crimei: bounce
cl4 = [50.0, 50.6, 51.1, 50.8, 51.6, 52.3, 52.0, 53.0, 53.8, 54.4, # 0-9 swing high la 9
53.9, 53.4, 53.8, # 10-12
54.8, 55.7, 55.4, 56.5, 57.4, 57.1, 58.3, 59.2, 58.9, 60.1, 61.2, 62.0, # 13-24 vârf
61.4, 61.7, 60.6, 59.8, # 25-28 început de corecție
58.0, 56.3, 56.1, # 29-31 bare mari, hammer la 31
56.6, # 32 trigger
57.2, 57.9, 57.6, 58.6, 59.3, 59.0, 60.0, 60.8] # 33-40
cds = from_closes(cl4, wicks=wicks(len(cl4), {29: (0.4, 0.6), 30: (0.4, 0.7), 31: (0.1, 1.5)}, default=(0.2, 0.2)), start=49.7)
LC = cds[9].h
HM, TG = 31, 32
PK = max(range(len(cds)), key=lambda i: cds[i].h)
tr = true_range(cds)
for c, t in zip(cds, tr):
c.vol = t
tr_norm = sum(tr[10:29]) / len(tr[10:29])
assert PK == 24
assert all(c.h < LC for c in cds[:9]) and cds[13].c > LC, "bara 9 e swing high-ul spart = locul crimei"
assert near(cds[HM].l, LC, 0.051) and cds[HM].c > LC, "hammer-ul atinge locul crimei și închide deasupra"
_hb = abs(cds[HM].c - cds[HM].o)
assert (min(cds[HM].o, cds[HM].c) - cds[HM].l) >= 3 * _hb and cds[HM].c >= cds[HM].l + 2 / 3 * (cds[HM].h - cds[HM].l), "hammer adevărat: codița de jos >= 3x corpul, close în treimea de sus"
assert min(tr[29:31]) >= 2 * tr_norm, ("barele 29-30: range de 2x normalul", tr[25:33], tr_norm)
assert cds[TG].c > cds[HM].h, "trigger: close peste high-ul hammer-ului"
entry = cds[TG].c
sl = round(cds[HM].l - 0.10, 2)
risk = entry - sl
tp1 = round((cds[PK].h + cds[HM].l) / 2, 2) # jumătatea distanței până la vârf
R1 = (tp1 - entry) / risk
tp2 = round(entry + 2 * risk, 2)
hit1 = next(i for i in range(TG + 1, len(cds)) if cds[i].h >= tp1)
hit2 = next(i for i in range(TG + 1, len(cds)) if cds[i].h >= tp2)
assert tp2 < cds[PK].h and min(c.l for c in cds[TG + 1:]) > sl
V["atr"] = dict(lc=LC, pk=cds[PK].h, hm_l=cds[HM].l, tr_norm=tr_norm, tr_spike=max(tr[29:32]),
spike_x=max(tr[29:32]) / tr_norm, entry=entry, sl=sl, risk=risk, tp1=tp1, R1=R1, tp2=tp2,
hit1=hit1 - TG, hit2=hit2 - TG)
print("D4 ATR", {k: (f2(v) if isinstance(v, float) else v) for k, v in V["atr"].items()})
ch = PChart(candles=cds,
levels=[Level(tp2, f"TP2 {f2(tp2)} = 2R", BLUE),
Level(tp1, f"TP1 {f2(tp1)} = 1/2 până la vârf ({R1:.1f}R)", BLUE),
Level(entry, f"intrare {f2(entry)}", GREEN),
Level(LC, f"locul crimei {f2(LC)}", ORANGE),
Level(sl, f"SL {f2(sl)}", RED)],
marks=[Mark(9, "swing high", ORANGE, "above", dy=-4),
Mark(PK, f"vârf {f2(cds[PK].h)}", GRAY, "above"),
Mark(30, "spike de range", RED, "below", dy=26),
Mark(TG, "trigger", GREEN, "above", dy=-4)],
mas=[MA(20, GRAY, dash="4 3", text="SMA-20")],
scale=18, vol_h=70, vol_label="true range pe bară (volatilitate)",
title="Exemplu construit (fără ticker) — daily, indicele",
caption="Range-ul barelor sare la peste 2x normalul exact când prețul atinge locul crimei.",
**STYLE)
svg_ = ch.render()
assert abs(px_ratio(ch, sl, entry, tp2) - 2.0) < 0.01
assert abs(px_ratio(ch, sl, entry, tp1) - R1) < 0.01
svgs["atr_spike"] = svg_
# ================================================================ 5. ATR în cădere liberă: nu e bottom
cl5 = [50.0, 50.7, 51.2, 50.9, 51.8, 52.5, 52.2, 53.2, 53.9, 54.5,
54.0, 55.0, 55.6, 56.3, 57.0,
56.6, 56.3, 55.9, 56.0, 55.6, 55.3, 55.4, 55.0, 54.7, 54.8, 54.5, 54.2, 54.3, 54.0, 53.8, 53.9,
53.6, 53.4, 53.5, 53.2, 53.0]
w5 = [(0.3, 0.3)] * 15 + [(0.12, 0.12)] * 21
cds = from_closes(cl5, wicks=w5, start=49.7)
LC5 = cds[9].h
tr = true_range(cds)
for c, t in zip(cds, tr):
c.vol = t
up_tr = sum(tr[1:15]) / 14
dn_tr = sum(tr[15:]) / len(tr[15:])
touch = next(i for i in range(15, len(cds)) if cds[i].l <= LC5)
assert all(c.h < LC5 for c in cds[:9]) and cds[11].c > LC5
assert max(tr[15:]) < up_tr, "fără nicio bară de spike în corecție"
assert cds[-1].c < LC5 - 1.5, "prețul trece prin locul crimei fără reacție"
V["atrnu"] = dict(lc=LC5, up_tr=up_tr, dn_tr=dn_tr, touch=touch, end=cds[-1].c)
print("D5 ATR NU", {k: (f2(v) if isinstance(v, float) else v) for k, v in V["atrnu"].items()})
ch = PChart(candles=cds,
levels=[Level(LC5, f"locul crimei {f2(LC5)}", ORANGE)],
marks=[Mark(9, "swing high", ORANGE, "above", dy=-4),
Mark(touch, "atins, fără spike", RED, "below", dy=4),
Mark(33, "lower lows", RED, "below", dy=4)],
mas=[MA(20, GRAY, dash="4 3", text="SMA-20")],
scale=26, vol_h=70, vol_label="true range pe bară (volatilitate)",
title="Exemplu construit (fără ticker) — daily, indicele",
caption="Range-ul se micșorează pe cădere: nimeni nu capitulează, deci încă nu e bottom.",
**STYLE)
svgs["atr_nu"] = ch.render()
# ================================================================ 6-7. top purtat de mega-cap-uri: indice vs RSP
ci = [50.0, 50.5, 50.2, 51.0, 51.6, 51.3, 52.1, 52.8, 52.5, 53.3, 53.9, 53.6, 54.4, 55.0, 54.7, 55.5, 56.0,
55.4, 54.8, 55.1, 54.5, 54.2,
54.9, 55.6, 56.1, 56.7, 56.8,
55.9,
55.4, 55.7, 54.9, 54.3, 54.6, 53.8, 53.2, 53.5, 52.6, 52.4, 52.8, 52.1]
cds = from_closes(ci, wicks=wicks(len(ci), {26: (0.6, 0.1)}), start=49.8)
P1, P2, ST, EN = 16, 26, 26, 27
assert cds[P1].h == max(c.h for c in cds[:22]) and cds[P2].h == max(c.h for c in cds)
assert cds[P2].h > cds[P1].h, "indicele: al doilea vârf mai sus"
assert abs(cds[ST].c - cds[ST].o) <= 0.15 and cds[ST].h - max(cds[ST].o, cds[ST].c) >= 0.5, "shooting star"
assert cds[25].c - cds[25].o >= 0.5 and cds[EN].o - cds[EN].c >= 0.8, "evening star: bull mare, stea, bear mare"
assert cds[EN].c < cds[ST].l, "trigger short: close sub low-ul stelei"
entry = cds[EN].c
sl = round(cds[P2].h + 0.10, 2)
risk = sl - entry
tp = round(entry - 2 * risk, 2)
hit = next(i for i in range(EN + 1, len(cds)) if cds[i].l <= tp)
assert max(c.h for c in cds[EN + 1:]) < sl
V["top"] = dict(p1=cds[P1].h, p2=cds[P2].h, star_l=cds[ST].l, entry=entry, sl=sl, risk=risk, tp=tp,
hit=hit - EN)
print("D6 top indice", {k: (f2(v) if isinstance(v, float) else v) for k, v in V["top"].items()})
ch = PChart(candles=cds,
levels=[Level(sl, f"SL {f2(sl)}", RED),
Level(cds[P1].h, f"vârf 1 {f2(cds[P1].h)}", GRAY),
Level(entry, f"intrare short {f2(entry)}", GREEN),
Level(tp, f"TP {f2(tp)} = 2R", BLUE)],
marks=[Mark(P1, "vârf 1", GRAY, "above", dy=-4),
Mark(ST, "vârf 2 mai sus", RED, "above", dy=-4),
Mark(EN, "evening star", RED, "below", dy=4),
Mark(hit, "TP", BLUE, "below", dy=4)],
scale=22, title="Exemplu construit (fără ticker) — daily, indicele (ponderat)",
caption="Indicele face vârf nou. Singur, arată a continuare. Verificarea e pe RSP, dedesubt.",
**STYLE)
svg_ = ch.render()
assert abs(1 / px_ratio(ch, tp, entry, sl) - 2.0) < 0.01, "2R nu e desenat la 2x risc"
svgs["top_indice"] = svg_
cr = [40.0, 40.3, 40.1, 40.6, 41.0, 40.8, 41.3, 41.7, 41.5, 42.0, 42.4, 42.2, 42.7, 43.1, 42.9, 43.4, 43.7,
43.1, 42.6, 42.8, 42.3, 42.0,
42.4, 42.8, 43.0, 43.3, 43.2,
42.8,
42.4, 42.6, 42.0, 41.6, 41.8, 41.2, 40.8, 41.0, 40.4, 40.2, 40.5, 40.0]
assert len(cr) == len(ci)
cds = from_closes(cr, wicks=wicks(len(cr), {26: (0.3, 0.1)}, default=(0.25, 0.25)), start=39.8)
r1 = cds[P1].h
r2 = max(c.h for c in cds[22:28])
i_r2 = max(range(22, 28), key=lambda i: cds[i].h)
assert r1 == max(c.h for c in cds) and r2 < r1, "RSP: al doilea vârf mai jos = nu confirmă"
V["rsp"] = dict(r1=r1, r2=r2, i_r2=i_r2, diff=(r2 - r1) / r1 * 100)
print("D7 RSP", {k: (f2(v) if isinstance(v, float) else v) for k, v in V["rsp"].items()})
ch = PChart(candles=cds,
levels=[Level(r1, f"vârf 1 {f2(r1)}", GRAY)],
marks=[Mark(P1, "vârf 1", GRAY, "above", dy=-4),
Mark(i_r2, f"vârf 2 mai jos {f2(r2)}", RED, "above", dy=-20)],
scale=40, title="Exemplu construit (fără ticker) — daily, varianta equal weight (tip RSP)",
caption="Aceleași zile. Compania medie nu mai face vârf nou: urcarea o duc câteva mega-cap-uri.",
**STYLE)
svgs["top_rsp"] = ch.render()
# ================================================================ 8-9. CPI: breakout din compresie DA / NU
head8 = [50.0, 50.5, 50.3, 51.0, 51.6, 51.4, 52.1, 52.7, 52.5, 53.2, 53.8, 53.6, 54.3, 54.9, # 0-13
54.8, 55.1, 54.6, 54.9, 55.2, 54.7, 55.0, 54.8, 55.1, 54.9, 55.0] # 14-24 compresie
hv8 = [44, 47, 42, 48, 50, 43, 49, 51, 44, 50, 52, 45, 50, 48,
38, 36, 35, 33, 32, 31, 30, 29, 28, 27, 26]
hw8 = [(0.3, 0.3)] * 14 + [(0.2, 0.2)] * 11
C0, CP = 14, 25
tail_da = [56.4, 56.9, 57.3, 57.0, 57.8, 58.4, 58.1, 58.9, 59.5, 59.2, 60.0, 60.5, 60.2, 60.9]
tv_da = [96, 62, 55, 44, 52, 54, 42, 50, 53, 41, 49, 51, 40, 47]
tw_da = [(0.2, 0.3), (0.2, 0.2)] + [(0.3, 0.3)] * 12
cds = from_closes(head8 + tail_da, wicks=hw8 + tw_da, vols=hv8 + tv_da, start=49.8)
RH = max(c.h for c in cds[C0:CP])
RL = min(c.l for c in cds[C0:CP])
vavg = sum(hv8[C0:CP]) / (CP - C0)
CF = CP + 1
assert near(RH, 55.40) and near(RL, 54.40), (RH, RL)
assert cds[CP].c > RH and cds[CP].vol >= 3 * vavg, "bara de CPI închide peste range cu volum de 3x"
assert cds[CF].l > RH and cds[CF].c > cds[CP].c, "a doua zi rămâne deasupra range-ului: confirmare"
entry = cds[CF].c
sl = round(RH - 0.20, 2)
risk = entry - sl
tp = round(entry + 2 * risk, 2)
hit = next(i for i in range(CF + 1, len(cds)) if cds[i].h >= tp)
assert min(c.l for c in cds[CF + 1:]) > sl
V["cpi"] = dict(rh=RH, rl=RL, cp_c=cds[CP].c, vol=cds[CP].vol, vavg=vavg, entry=entry, sl=sl, risk=risk,
tp=tp, hit=hit - CF)
print("D8 CPI DA", {k: (f2(v) if isinstance(v, float) else v) for k, v in V["cpi"].items()})
ch = PChart(candles=cds,
levels=[Level(tp, f"TP {f2(tp)} = 2R", BLUE),
Level(entry, f"intrare {f2(entry)}", GREEN),
Level(RH, f"range high {f2(RH)}", ORANGE),
Level(sl, f"SL {f2(sl)}", RED),
Level(RL, f"range low {f2(RL)}", ORANGE)],
marks=[Mark(19, "compresie înainte de CPI", GRAY, "below", dy=26),
Mark(CP, "CPI", PURPLE, "below", dy=4),
Mark(CF, "confirmare", GREEN, "above", dy=-4)],
scale=22, vol_h=70, title="Exemplu construit (fără ticker) — daily, indicele",
caption="Nu intri înainte de raport. Intri după ce ziua de după CPI rămâne deasupra range-ului.",
**STYLE)
svg_ = ch.render()
assert abs(px_ratio(ch, sl, entry, tp) - 2.0) < 0.01
svgs["cpi_da"] = svg_
tail_nu = [55.1, 54.2, 53.8, 54.1, 53.4, 52.9, 53.2, 52.5, 52.0, 52.3, 51.7, 51.2, 51.5, 50.9]
tv_nu = [92, 70, 58, 44, 60, 62, 45, 58, 60, 44, 55, 57, 42, 52]
tw_nu = [(1.4, 0.3)] + [(0.3, 0.3)] * 13
cds = from_closes(head8 + tail_nu, wicks=hw8 + tw_nu, vols=hv8 + tv_nu, start=49.8)
assert near(max(c.h for c in cds[C0:CP]), RH) and near(min(c.l for c in cds[C0:CP]), RL), "aceeași compresie"
assert cds[CP].h > RH + 0.8 and RL < cds[CP].c < RH, "CPI: spike peste range, close înapoi înăuntru"
BD = next(i for i in range(CP + 1, len(cds)) if cds[i].c < RL)
assert BD == CP + 1
V["cpinu"] = dict(cp_h=cds[CP].h, cp_c=cds[CP].c, bd_c=cds[BD].c, end=cds[-1].c)
print("D9 CPI NU", {k: (f2(v) if isinstance(v, float) else v) for k, v in V["cpinu"].items()})
ch = PChart(candles=cds,
levels=[Level(RH, f"range high {f2(RH)}", ORANGE),
Level(RL, f"range low {f2(RL)}", ORANGE)],
marks=[Mark(19, "compresie înainte de CPI", GRAY, "below", dy=26),
Mark(CP, f"CPI: spike {f2(cds[CP].h)}", PURPLE, "above", dy=-4),
Mark(BD, "close sub range", RED, "below", dy=40)],
scale=22, vol_h=70, title="Exemplu construit (fără ticker) — daily, indicele",
caption="Același range. Spike-ul de pe CPI se închide înapoi înăuntru: cine a cumpărat la 56 e prins.",
**STYLE)
svgs["cpi_nu"] = ch.render()
# ================================================================ 10. bond-urile: hammer în suportul quarterly
cl10 = [46.0, 45.6, 45.9, 45.2, 44.7, 45.0, 44.3, 43.8, 44.1, 43.4, 43.0, 43.3, # 0-11
43.9, 44.4, 44.1, 44.7, 44.3, 43.9, # 12-17
43.4, 42.9, 42.6, 42.5, # 18-21 (21 hammer)
43.0, 43.4, 43.2, 43.8, 44.3, 44.0, 44.6, 45.1, 44.8, 45.4, 45.9, 45.6] # 22-33
cds = from_closes(cl10, wicks=wicks(len(cl10), {21: (0.1, 0.7)}, default=(0.2, 0.2)), start=46.3)
L1 = min(range(0, 12), key=lambda i: cds[i].l)
H10 = 21
SQ = 41.80
assert near(cds[H10].l, SQ) and cds[H10].l == min(c.l for c in cds), "hammer-ul face low-ul seriei, în suportul quarterly"
assert cds[H10].l < cds[L1].l, "al doilea picior mai jos"
assert min(cds[H10].o, cds[H10].c) - cds[H10].l >= 2 * abs(cds[H10].o - cds[H10].c), "hammer: codiță >= 2x corpul"
TG10 = H10 + 1
assert cds[TG10].c > cds[H10].h
V["tlt"] = dict(l1=cds[L1].l, sq=SQ, hm_l=cds[H10].l, hm_c=cds[H10].c, tg=TG10, tg_c=cds[TG10].c)
print("D10 TLT", {k: (f2(v) if isinstance(v, float) else v) for k, v in V["tlt"].items()})
ch = PChart(candles=cds,
levels=[Level(SQ, f"suport quarterly {SQ:.2f}", ORANGE)],
marks=[Mark(3, "downtrend", GRAY, "above", dy=-4),
Mark(L1, "picior 1", GRAY, "below", dy=4),
Mark(H10, "hammer, picior 2 mai jos", GREEN, "below", dy=4)],
scale=26, pad_bot_px=10, title="Exemplu construit (fără ticker) — daily, ETF de bond-uri pe termen lung",
caption="Spike low sub picior 1, hammer în suportul quarterly: bond-urile își pot face bottom-ul.",
**STYLE)
svgs["tlt"] = ch.render()
# ================================================================ 11. acțiunea sensibilă la dobândă: ascending triangle
cl11 = [49.0, 49.6, 50.3, 50.1, 51.0, 51.8, 52.6, 53.5, # 0-7
54.8, 54.0, 53.1, 53.8, 54.4, 54.8, 54.2, 53.6, 54.2, 54.8, 54.4, 54.0, 54.5, 54.8, 54.5, 54.3, 54.6, # 8-24
55.7, # 25 breakout
56.2, 55.9, 56.6, 57.2, 56.9, 57.6, 58.2, 57.9, 58.6, 59.2, 58.9, 59.5, 60.0, 59.7] # 26-39
cds = from_closes(cl11, wicks=[(0.2, 0.2)] * len(cl11), start=48.7)
RT = 55.00
BO = 25
assert near(max(c.h for c in cds[8:BO]), RT), "vârful triunghiului e plat la 55.00"
assert sum(1 for c in cds[8:BO] if near(c.h, RT)) >= 4, "cel puțin 4 atingeri ale rezistenței"
sw = [min(cds[a:b], key=lambda c: c.l).l for a, b in ((9, 13), (14, 17), (18, 21), (22, 25))]
assert all(x < y for x, y in zip(sw, sw[1:])), f"low-urile nu urcă: {sw}"
assert cds[BO].c > RT and all(c.c <= RT for c in cds[8:BO])
assert BO > TG10, "breakout-ul acțiunii vine după trigger-ul de pe bond-uri"
entry = cds[BO].c
sl = round(sw[-1] - 0.10, 2)
risk = entry - sl
tp = round(entry + 2 * risk, 2)
hit = next(i for i in range(BO + 1, len(cds)) if cds[i].h >= tp)
assert min(c.l for c in cds[BO + 1:]) > sl
V["dhi"] = dict(rt=RT, lows=[f2(x) for x in sw], entry=entry, sl=sl, risk=risk, tp=tp, hit=hit - BO)
print("D11 actiune", {k: (f2(v) if isinstance(v, float) else v) for k, v in V["dhi"].items()})
lowbars = [min(range(a, b), key=lambda i: cds[i].l) for a, b in ((9, 13), (14, 17), (18, 21), (22, 25))]
ch = PChart(candles=cds,
levels=[Level(tp, f"TP {f2(tp)} = 2R", BLUE),
Level(entry, f"intrare {f2(entry)}", GREEN),
Level(RT, f"rezistență {RT:.2f}", ORANGE),
Level(sl, f"SL {f2(sl)}", RED)],
marks=[Mark(lowbars[0], "low", GRAY, "below", dy=4),
Mark(lowbars[1], "HL", GREEN, "below", dy=4),
Mark(lowbars[2], "HL", GREEN, "below", dy=4),
Mark(BO, "breakout", GREEN, "above", dy=-4),
Mark(hit, "TP", BLUE, "above")],
scale=20, title="Exemplu construit (fără ticker) — daily, acțiune sensibilă la dobândă",
caption="Aceleași zile ca bond-urile: ascending triangle, breakout la 3 zile după trigger-ul de pe bond-uri.",
**STYLE)
svg_ = ch.render()
assert abs(px_ratio(ch, sl, entry, tp) - 2.0) < 0.01
svgs["dhi"] = svg_
# ---------------------------------------------------------------- substituție în .md
if MD.exists():
md = MD.read_text(encoding="utf-8")
for name, svg in svgs.items():
pat = re.compile(rf"<!--SVG:{name}-->.*?<!--/SVG-->", re.S)
assert pat.search(md), f"marcatorul {name} lipsește din .md"
md = pat.sub(lambda _m: f"<!--SVG:{name}-->\n{svg}\n<!--/SVG-->", md)
MD.write_text(md, encoding="utf-8")
print("scris", MD)