"""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, 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".*?", 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)