"""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 ... 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 = {}
@dataclass
class Seg:
"""nivel valabil doar pe barele i0..i1"""
i0: int
i1: int
price: float
text: str
color: str = ORANGE
label_at: str = "out"
dy: float = -5.0
dash: str = ""
def spread(items, gap=14):
"""[(y, ...)] -> aceleași, cu y-urile împinse ca două etichete să nu se atingă"""
items = sorted(items, key=lambda t: t[0])
out = []
for it in items:
y = it[0] if not out else max(it[0], out[-1][0] + gap)
out.append((y,) + tuple(it[1:]))
return out
@dataclass
class PChart(Chart):
"""copiat din gen/az_pivoti.py (vezi acolo explicațiile)"""
segs: list = field(default_factory=list)
seps: list = field(default_factory=list)
pad_px: float = 24.0
lm: int = 104
pad_bot_px: float = 0.0
def render(self) -> str:
n = len(self.candles)
self.left = self.lm if any(s.label_at == "lm" for s in self.segs) else self.left
segs = list(self.segs) + [Seg(0, n - 1, lv.price, lv.text, lv.color, "out", dash="4 3")
for lv in self.levels]
saved_lv, saved_ma = list(self.levels), list(self.mas)
longest = max([len(s.text) for s in segs if s.label_at == "out"] + [len(m.text) for m in saved_ma] + [0])
self.width = max(self.width, int(self._cx(n - 1) + self.gap / 2 + 6 + longest * 6.2 + 8))
top_price = max([c.h for c in self.candles] + [s.price for s in segs])
self.levels = [Level(s.price, "", "none") for s in segs]
self.levels.append(Level(top_price + self.pad_px / self.scale, "", "none"))
if self.pad_bot_px:
low_price = min([c.l for c in self.candles] + [s.price for s in segs])
self.levels.append(Level(low_price - self.pad_bot_px / self.scale, "", "none"))
self.mas = [MA(m.period, m.color, m.dash) for m in saved_ma]
svg = self.render_base()
self.levels, self.mas = saved_lv, saved_ma
parts = svg.split("\n ")
extra = []
for k in self.seps:
x = self._cx(k) - self.gap / 2
extra.append(f'')
closes = [c.c for c in self.candles]
xr = self._cx(n - 1) + self.gap / 2 + 6
out_lab = [(self._y(s.price) + 4, s) for s in segs if s.label_at == "out"]
out_lab += [(self._y(sum(closes[-m.period:]) / m.period) + 4, Seg(0, 0, 0, m.text, m.color))
for m in saved_ma if m.text]
placed = {id(s): y for y, s in spread(out_lab)}
placed.update({id(s): y for y, s in spread([(self._y(s.price) + 4, s) for s in segs if s.label_at == "lm"])})
for s in segs:
x0, x1, y = self._cx(s.i0) - self.gap / 2, self._cx(s.i1) + self.gap / 2, self._y(s.price)
dash = f' stroke-dasharray="{s.dash}"' if s.dash else ""
extra.append(f'')
if s.label_at == "out":
extra.append(f'{s.text}')
elif s.label_at == "lm":
extra.append(f'{s.text}')
elif s.label_at == "left":
extra.append(f'{s.text}')
else:
extra.append(f'{s.text}')
for y, s in out_lab:
if s.i1 == 0 and s.price == 0:
extra.append(f'{s.text}')
k = 2 if self.title else 1
return "\n ".join(parts[:k] + extra + parts[k:])
def render_base(self):
svg = Chart.render(self)
svg = re.sub(r'\n ]*stroke="none"[^>]*/>\n ]*fill="none"[^>]*>', "", svg)
prices = [p for c in self.candles for p in (c.o, c.h, c.l, c.c)] + [lv.price for lv in self.levels]
self._body_bottom = self.top + (max(prices) - min(prices)) * self.scale
return svg
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, 55.5, # 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, 0.9)}, 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"
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".*?", re.S)
assert pat.search(md), f"marcatorul {name} lipsește din .md"
md = pat.sub(lambda _m: f"\n{svg}\n", md)
MD.write_text(md, encoding="utf-8")
print("scris", MD)