"""Diagramele pentru summaries/08_ANALIZA_ZILNICA/09_earnings_si_optiuni.md. Toate prețurile sunt construite (fără ticker). Rulează: .venv/bin/python gen/az_earnings.py Înlocuiește în .md fiecare bloc ... cu output-ul generatorului. Ca să schimbi o cifră, o schimbi aici și rulezi din nou. Excepția față de restul seriilor: bara de după raportare NU deschide la close-ul precedentei — gap-ul de earnings e chiar subiectul. `series(..., opens={i: open})` pune open-ul explicit doar pe barele de gap; mărimea fiecărui gap afirmată în text are `assert`. `PChart` / `Seg` / `spread` sunt copiate din gen/az_pivoti.py (etichetele nivelurilor în marginea dreaptă, împinse automat să nu se atingă). """ 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 Candle, Chart, Level, Mark, MA # noqa: E402 MD = Path("/workspace/atm-curs-dl/summaries/08_ANALIZA_ZILNICA/09_earnings_si_optiuni.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 = {} FACTS = {} # ---------------------------------------------------------------- layout (din az_pivoti.py) @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): 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 # ---------------------------------------------------------------- utilitare def series(closes, start, wicks=None, opens=None, vols=None): """ca from_closes, dar cu open explicit pe barele de gap (opens={i: open})""" wicks, opens = wicks or {}, opens or {} out, o = [], start for i, c in enumerate(closes): o = opens.get(i, o) wu, wd = wicks.get(i, (0.3, 0.3)) out.append(Candle(o, max(o, c) + wu, min(o, c) - wd, c, vol=(vols[i] if vols else None))) o = c return out 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 pct(a, b): return (b - a) / a * 100 T_D = "Exemplu construit (fără ticker) — daily" T_W = "Exemplu construit (fără ticker) — weekly" # ================================================================ 1-2. gamble trade: același setup înainte vs după raportare cl = [58.0, 57.2, 57.6, 56.5, 55.6, 56.0, 54.9, 54.0, 54.4, 53.2, 52.3, 52.7, 51.5, 50.4, # 0-13 downtrend 49.8, # 14 picior 1 50.9, 51.6, 51.2, 50.5, # 15-18 50.7, # 19 hammer, picior 2 51.8, # 20 buy hammer (trigger) 52.2, 52.0, # 21-22 (22 = raport after close) 46.4, # 23 gap down 46.9, 46.1, 45.5, 46.2, 46.8, 46.3, 45.9, # 24-30 45.95, # 31 doji (higher low) 46.9, # 32 buy doji 47.4, 48.1, 47.8, 48.6, 49.3, 49.0, 49.8, 50.5, 50.2, 51.0, 51.8] # 33-43 wk = {19: (0.1, 0.95), 20: (0.2, 0.2), 23: (0.3, 0.3), 26: (0.3, 0.3), 31: (0.4, 0.45), 32: (0.2, 0.2)} cds = series(cl, 58.6, wicks=wk, opens={23: 47.0}) HM, TG, E, G = 19, 20, 22, 23 assert cds[HM].c > cds[HM].o and (cds[HM].o - cds[HM].l) >= 3 * (cds[HM].c - cds[HM].o), "bara 19 nu e hammer" assert abs(cds[14].l - cds[HM].l) <= 0.1, "double bottom: cele două picioare la același nivel" assert cds[TG].c > cds[HM].h, "trigger: close peste high-ul hammer-ului" entry = cds[TG].c sl = round(min(c.l for c in cds[14:TG + 1]) - 0.10, 2) risk = entry - sl tp = round(entry + 2 * risk, 2) assert cds[G].h < cds[E].l, "gap real: high-ul barei de după raport sub low-ul barei de raport" gap_pct = pct(cds[E].c, cds[G].o) assert -10 < gap_pct < -9, gap_pct fill = cds[G].o # stop-ul se execută la open, nu la SL loss_R = (entry - fill) / risk assert fill < sl and abs(loss_R - 2.0) < 0.01, loss_R assert max(c.h for c in cds[TG:E + 1]) < tp, "TP-ul nu e atins înainte de raport" # fracțiunea: poziția la care pierderea în gap-ul maxim istoric (12%) rămâne 1R HIST = 12.0 frac = risk / (entry - cds[E].c * (1 - HIST / 100)) assert abs(frac - 0.40) < 0.005 and abs((entry - cds[E].c * 0.88) / risk - 2.52) < 0.01, "textul: 40% din poziție, 2.5R la −12%" FACTS["g"] = dict(entry=entry, sl=sl, risk=risk, tp=tp, gap=gap_pct, fill=fill, loss_R=loss_R, e_close=cds[E].c, e_low=cds[E].l, g_high=cds[G].h, frac=frac) print(f"D1 DB lows {f2(cds[14].l)}/{f2(cds[HM].l)} entry {f2(entry)} SL {f2(sl)} risc {f2(risk)} TP {f2(tp)}; " f"raport close {f2(cds[E].c)} gap open {f2(fill)} ({gap_pct:.1f}%) -> {loss_R:.2f}R; fracție la -{HIST}%: {frac:.2f}") ch = PChart(candles=cds[:31], levels=[Level(tp, f"TP {f2(tp)} = 2R (neatins)", BLUE), Level(entry, f"intrare {f2(entry)}", GREEN), Level(sl, f"SL {f2(sl)}", RED), Level(fill, f"executat la open {f2(fill)} = −{loss_R:.1f}R", PURPLE)], marks=[Mark(6, "downtrend", GRAY, "above", dy=-6), Mark(14, "picior 1", GRAY, "below", dy=2), Mark(HM, "hammer", GRAY, "below", dy=2), Mark(TG, "buy hammer", GREEN, "above", dy=-4), Mark(E, "raport", ORANGE, "above", dy=-22), Mark(G, f"gap {gap_pct:.1f}%", RED, "below", dy=4)], scale=24, title=T_D, caption="Setup bun, dar raportul vine peste 2 zile. Gap-ul sare peste stop: pierzi 2R, nu 1R.", **STYLE) svg_ = ch.render() assert abs(px_ratio(ch, sl, entry, tp) - 2.0) < 0.01 svgs["gamble_nu"] = svg_ # 2. după raport: setup nou, gap-ul de deasupra devine țintă DJ, BD = 31, 32 hl = min(c.l for c in cds[28:BD + 1]) low_post = min(c.l for c in cds[G:28]) assert abs(cds[31].l - hl) < 1e-9 and hl > low_post, "higher low: minimul de după 28 peste minimul de după gap" assert abs(cds[DJ].o - cds[DJ].c) <= 0.1 and cds[BD].c > cds[DJ].h, "buy doji" entry2 = cds[BD].c sl2 = round(hl - 0.10, 2) risk2 = entry2 - sl2 tp2 = round(cds[E].l, 2) # umplerea gap-ului: low-ul barei de raport R2 = (tp2 - entry2) / risk2 hit2 = next(i for i in range(BD + 1, len(cds)) if cds[i].h >= tp2) assert min(c.l for c in cds[BD + 1:]) > sl2 assert R2 >= 3, R2 FACTS["g2"] = dict(entry=entry2, sl=sl2, risk=risk2, tp=tp2, R=R2, hl=hl, low=low_post, bars=hit2 - BD, half=(cds[E].l + cds[G].h) / 2) print(f"D2 low după gap {f2(low_post)} HL {f2(hl)} entry {f2(entry2)} SL {f2(sl2)} risc {f2(risk2)} " f"TP gap {f2(tp2)} = {R2:.2f}R atins la {hit2} (+{hit2 - BD})") ch = PChart(candles=cds, levels=[Level(tp2, f"TP {f2(tp2)} = gap umplut = {R2:.1f}R", BLUE), Level(entry2, f"intrare {f2(entry2)}", GREEN), Level(sl2, f"SL {f2(sl2)}", RED)], marks=[Mark(E, "raport", ORANGE, "above", dy=-4), Mark(G, "gap", RED, "above", dy=-4), Mark(26, "low", GRAY, "below", dy=2), Mark(31, "higher low", GREEN, "below", dy=14), Mark(BD, "buy doji", GREEN, "above", dy=-4), Mark(hit2, "TP", BLUE, "above")], scale=24, title=T_D, caption="Același simbol. Aștepți raportul, apoi joci setup-ul format după el, cu stop care nu mai sare.", **STYLE) svg_ = ch.render() assert abs(px_ratio(ch, sl2, entry2, tp2) - R2) < 0.01 svgs["gamble_da"] = svg_ # ================================================================ 3-4. gap-up de earnings: pullback în gap cl = [59.0, 59.4, 59.1, 59.8, 60.3, 60.0, 60.6, 61.1, 60.7, 61.2, 61.6, 61.2, 60.9, 61.5, 61.9, 61.6, 62.1, 61.8, # 0-17 62.0, # 18 raport after close 68.2, # 19 gap up 69.0, 69.8, 69.3, 70.4, # 20-23 continuare 69.2, 68.0, 67.1, 66.0, 65.2, # 24-28 pullback 65.6, # 29 hammer în gap 66.9, # 30 trigger 67.5, 68.4, 69.1, 68.7, 69.9, 70.8, 70.5, 71.6, 72.3, 72.0, 72.9] # 31-41 wk = {19: (0.3, 0.3), 23: (0.3, 0.3), 28: (0.3, 0.3), 29: (0.1, 0.9), 30: (0.2, 0.2)} vol = [40, 38, 42, 44, 41, 39, 43, 45, 40, 42, 44, 39, 37, 41, 43, 40, 45, 42, 48, 150, 95, 88, 70, 76, 52, 47, 41, 38, 34, 44, 72, 55, 58, 50, 46, 57, 60, 48, 55, 52, 45, 50] cds = series(cl, 58.7, wicks=wk, opens={19: 67.0}, vols=vol) E, G, HM, TG = 18, 19, 29, 30 gap_bot, gap_top = cds[E].h, cds[G].l half = (gap_bot + gap_top) / 2 gap_pct = pct(cds[E].c, cds[G].o) assert gap_top > gap_bot and 8 < gap_pct < 8.2, gap_pct assert vol[G] == max(vol), "gap-ul are cel mai mare volum" assert all(vol[i] > vol[i + 1] for i in range(24, 28)), "pullback cu volum în scădere" assert gap_bot < cds[HM].l < half, "hammer-ul intră sub jumătatea gap-ului, dar nu îl umple" assert cds[TG].c > cds[HM].h entry = cds[TG].c sl = round(cds[HM].l - 0.20, 2) risk = entry - sl tp = round(entry + 2 * risk, 2) peak = max(c.h for c in cds[G:HM]) hit = next(i for i in range(TG + 1, len(cds)) if cds[i].h >= tp) assert min(c.l for c in cds[TG + 1:]) > sl and tp > peak FACTS["gp"] = dict(gap=gap_pct, bot=gap_bot, top=gap_top, half=half, entry=entry, sl=sl, risk=risk, tp=tp, peak=peak, bars=hit - TG, hm=cds[HM].l, pre=cds[E].c, open=cds[G].o) print(f"D3 raport close {f2(cds[E].c)} gap open {f2(cds[G].o)} (+{gap_pct:.2f}%) zona {f2(gap_bot)}-{f2(gap_top)} " f"1/2 {f2(half)}; vârf {f2(peak)}; hammer low {f2(cds[HM].l)}; entry {f2(entry)} SL {f2(sl)} risc {f2(risk)} " f"TP {f2(tp)} atins +{hit - TG}") ch = PChart(candles=cds, levels=[Level(tp, f"TP {f2(tp)} = 2R", BLUE), Level(entry, f"intrare {f2(entry)}", GREEN), Level(gap_top, f"sus gap {f2(gap_top)}", ORANGE), Level(half, f"1/2 gap {f2(half)}", ORANGE, dash="2 3"), Level(sl, f"SL {f2(sl)}", RED), Level(gap_bot, f"jos gap {f2(gap_bot)}", ORANGE)], marks=[Mark(E, "raport", ORANGE, "above", dy=-4), Mark(G, f"gap +{gap_pct:.1f}%", GREEN, "above", dy=-34), Mark(HM, "hammer în gap", GREEN, "below", dy=2), Mark(hit, "TP", BLUE, "above")], scale=18, vol_h=70, title=T_D, caption="Nu cumperi gap-ul. Aștepți ca prețul să revină în el și să arate că e apărat.", **STYLE) svg_ = ch.render() assert abs(px_ratio(ch, sl, entry, tp) - 2.0) < 0.01 svgs["gap_pullback_da"] = svg_ # 4. NU: cumpărat după ce „s-a dus" CH = 23 entry_n = cds[CH].c sl_n = round(min(c.l for c in cds[CH - 2:CH + 1]) - 0.10, 2) risk_n = entry_n - sl_n stop_bar = next(i for i in range(CH + 1, len(cds)) if cds[i].l <= sl_n) assert stop_bar - CH == 2, "textul: stop atins a doua zi după intrare" dist_gap = pct(gap_top, entry_n) FACTS["gpn"] = dict(entry=entry_n, sl=sl_n, risk=risk_n, dist=dist_gap, stop=stop_bar - CH) print(f"D4 chase entry {f2(entry_n)} ({dist_gap:.1f}% peste sus gap) SL {f2(sl_n)} risc {f2(risk_n)} stop la +{stop_bar - CH}") ch = PChart(candles=cds[:34], levels=[Level(entry_n, f"intrare {f2(entry_n)} (după ce s-a dus)", PURPLE), Level(sl_n, f"SL {f2(sl_n)}", RED), Level(gap_top, f"sus gap {f2(gap_top)}", ORANGE), Level(half, f"1/2 gap {f2(half)}", ORANGE, dash="2 3"), Level(gap_bot, f"jos gap {f2(gap_bot)}", ORANGE)], marks=[Mark(G, f"gap +{gap_pct:.1f}%", GREEN, "below", dy=2), Mark(CH, "cumpărat aici", PURPLE, "above", dy=-4), Mark(stop_bar, "stop", RED, "below", dy=2), Mark(HM, "intrarea bună vine aici", GREEN, "below", dy=2)], scale=18, vol_h=70, title=T_D, caption="Același gap. Cine cumpără după 4 zile de urcare e scos de pullback-ul normal spre gap.", **STYLE) svgs["gap_pullback_nu"] = ch.render() # ================================================================ 5-6. cădere pe rezultate bune cl = [50.0, 50.8, 51.5, 51.1, 52.0, 52.8, 52.4, 53.3, 54.0, 53.6, # 0-9 uptrend 52.9, 52.2, 51.7, # 10-12 pullback, swing low 52.4, 53.2, 54.1, 54.8, 55.3, # 13-17 run-up pre-raport 56.2, # 18 gap up, închide roșu 54.8, 54.1, 53.4, 52.6, 52.0, # 19-23 cădere 51.9, # 24 retest swing low 52.5, 52.9, 52.3, # 25-27 52.1, # 28 picior 2 53.4, # 29 trigger peste neckline 53.9, 54.5, 54.2, 55.0, 55.6, 55.3, 56.1, 56.6, 56.3, 57.0, 57.4, 57.9] # 30-39 wk = {12: (0.3, 0.4), 18: (0.4, 0.3), 24: (0.2, 0.5), 26: (0.25, 0.3), 28: (0.2, 0.7), 29: (0.2, 0.2)} vol = [40, 44, 46, 38, 45, 47, 39, 46, 48, 37, 42, 44, 45, 43, 48, 52, 55, 58, 160, 120, 90, 80, 75, 70, 85, 55, 50, 45, 60, 80, 62, 58, 50, 60, 57, 48, 55, 52, 47, 50, 54, 51] cds = series(cl, 49.6, wicks=wk, opens={18: 58.0}, vols=vol) SWL, PR, G = 12, 17, 18 gap_pct = pct(cds[PR].c, cds[G].o) assert cds[G].l > cds[PR].h and 4.8 < gap_pct < 5.0, gap_pct assert cds[G].c < cds[G].o, "bara de raport închide sub open: reversal în aceeași zi" assert vol[G] == max(vol) # NU: cumperi la open-ul gap-ului entry_n = cds[G].o sl_n = round(cds[PR].h - 0.10, 2) # sub high-ul de dinainte de raport: gap-ul umplut = idee moartă risk_n = entry_n - sl_n stop_bar = next(i for i in range(G + 1, len(cds)) if cds[i].l <= sl_n) assert stop_bar == G + 1 fall = pct(cds[G].h, min(c.l for c in cds[G:28])) print(f"D5 pre close {f2(cds[PR].c)} high {f2(cds[PR].h)}; gap open {f2(entry_n)} (+{gap_pct:.2f}%) high {f2(cds[G].h)} " f"close {f2(cds[G].c)}; SL {f2(sl_n)} risc {f2(risk_n)} stop la +1; cădere {fall:.1f}%") swl = cds[SWL].l ch = PChart(candles=cds[:27], levels=[Level(entry_n, f"cumpărat la open {f2(entry_n)}", PURPLE), Level(sl_n, f"SL {f2(sl_n)} (gap umplut)", RED), Level(swl, f"swing low {f2(swl)}", GRAY)], marks=[Mark(SWL, "swing low", GRAY, "below", dy=2), Mark(G, "rezultate bune", GREEN, "above", dy=-4), Mark(G + 1, "stop", RED, "below", dy=2), Mark(24, "retest", GRAY, "below", dy=2)], scale=24, vol_h=70, title=T_D, caption=f"Gap +{gap_pct:.1f}% pe rezultate bune, închis roșu în aceeași zi. A doua zi gap-ul e umplut.", **STYLE) svgs["rezultate_bune_nu"] = ch.render() # 6. DA: retestul swing low-ului + double bottom P2, TG = 28, 29 neck = cds[26].h assert abs(cds[24].l - swl) <= 0.15 and abs(cds[P2].l - cds[24].l) <= 0.15, "retest swing low + picior 2 la același nivel" assert all(c.c <= neck for c in cds[24:TG]) and cds[TG].c > neck, "trigger: prima închidere peste neckline" entry = cds[TG].c sl = round(min(cds[24].l, cds[P2].l, swl) - 0.15, 2) risk = entry - sl tp = round(entry + 2 * risk, 2) hit = next(i for i in range(TG + 1, len(cds)) if cds[i].h >= tp) assert min(c.l for c in cds[TG + 1:]) > sl and tp < cds[G].o FACTS["rb"] = dict(gap=gap_pct, open=entry_n, sl_n=sl_n, risk_n=risk_n, pre_h=cds[PR].h, g_close=cds[G].c, g_high=cds[G].h, swl=swl, l24=cds[24].l, l28=cds[P2].l, neck=neck, entry=entry, sl=sl, risk=risk, tp=tp, bars=hit - TG, fall=fall) print(f"D6 swing low {f2(swl)} retest {f2(cds[24].l)} picior 2 {f2(cds[P2].l)} neck {f2(neck)} entry {f2(entry)} " f"SL {f2(sl)} risc {f2(risk)} TP {f2(tp)} atins +{hit - TG}") ch = PChart(candles=cds, levels=[Level(tp, f"TP {f2(tp)} = 2R", BLUE), Level(entry, f"intrare {f2(entry)}", GREEN), Level(neck, f"neckline {f2(neck)}", ORANGE), Level(sl, f"SL {f2(sl)}", RED)], marks=[Mark(SWL, "swing low", GRAY, "below", dy=2), Mark(G, "rezultate bune", GREEN, "above", dy=-4), Mark(24, "picior 1", GRAY, "below", dy=2), Mark(P2, "picior 2", GRAY, "below", dy=2), Mark(TG, "trigger", GREEN, "above", dy=-4), Mark(hit, "TP", BLUE, "above")], scale=24, vol_h=70, title=T_D, caption="Nu cumperi căderea. Aștepți retestul swing low-ului și un bottom confirmat.", **STYLE) svg_ = ch.render() assert abs(px_ratio(ch, sl, entry, tp) - 2.0) < 0.01 svgs["rezultate_bune_da"] = svg_ # ================================================================ 7-8. pattern-ul pre-earnings (weekly, 3 trimestre) def quarter(base, up, drop, react): """13 săptămâni: 9 de urcare, 3 de cădere pre-raport, raport; `react` = close după raport""" rise = [round(base + up * (k + 1) / 9 + (0.4 if k % 3 == 1 else 0), 2) for k in range(9)] top = rise[-1] pre = [round(top * (1 - drop * (k + 1) / 3 / 100), 2) for k in range(3)] return rise + pre, react def pre_series(reacts): closes, opens, E = [], {}, [] base = 50.0 for q, (up, drop, gp) in enumerate(reacts): body, _ = quarter(base, up, drop, gp) closes += body E.append(len(closes) - 1) if gp is None: break o = round(closes[-1] * (1 + gp / 100), 2) opens[len(closes)] = o closes.append(round(o + (0.6 if gp > 0 else -0.6), 2)) base = closes[-1] return series(closes, 49.6, opens=opens), E reacts = [(4.0, 6.0, 9.0), (3.0, 6.0, 10.0), (3.0, 6.0, None)] cds, E = pre_series(reacts) assert len(cds) == 38, len(cds) facts = [] for q, e in enumerate(E): drop = pct(cds[e - 3].c, cds[e].c) assert -6.5 < drop < -5.5, drop if q < 2: g = pct(cds[e].c, cds[e + 1].o) assert cds[e + 1].l > cds[e].h and g > 8, g facts.append((drop, g)) else: facts.append((drop, None)) FACTS["pre"] = facts print("D7", [(f"{d:.1f}%", g and f"{g:.1f}%") for d, g in facts]) ch = PChart(candles=cds, seps=[E[0] + 1, E[1] + 1], marks=[Mark(E[0] - 2, f"{facts[0][0]:.0f}%", RED, "above", dy=-8), Mark(E[0] + 1, f"E1 +{facts[0][1]:.0f}%", GREEN, "above", dy=-4), Mark(E[1] - 2, f"{facts[1][0]:.0f}%", RED, "above", dy=-8), Mark(E[1] + 1, f"E2 +{facts[1][1]:.0f}%", GREEN, "above", dy=-4), Mark(E[2] - 2, f"{facts[2][0]:.0f}%", RED, "above", dy=-8), Mark(E[2], "E3 ?", ORANGE, "below", dy=4)], scale=9, title=T_W, caption="Istoric: cădere înainte de raport, apoi gap up — de două ori. A treia oară nu e garantat.", **STYLE) svgs["pre_da"] = ch.render() reacts_n = [(4.0, 6.0, 9.0), (3.0, 6.0, -8.0), (6.0, 6.0, None)] cds, E = pre_series(reacts_n) fn = [] for q, e in enumerate(E): drop = pct(cds[e - 3].c, cds[e].c) assert -6.5 < drop < -5.5, drop g = pct(cds[e].c, cds[e + 1].o) if q < 2 else None fn.append((drop, g)) assert fn[1][1] < -7.5 and cds[E[1] + 1].h < cds[E[1]].l FACTS["pre_n"] = fn print("D8", [(f"{d:.1f}%", g and f"{g:.1f}%") for d, g in fn]) ch = PChart(candles=cds, seps=[E[0] + 1, E[1] + 1], marks=[Mark(E[0] + 1, f"E1 +{fn[0][1]:.0f}%", GREEN, "above", dy=-4), Mark(E[1] - 2, f"{fn[1][0]:.0f}%", RED, "above", dy=-8), Mark(E[1] + 1, f"E2 {fn[1][1]:.0f}%", RED, "below", dy=4), Mark(E[2], "E3 ?", ORANGE, "below", dy=4)], scale=9, title=T_W, caption="Aceeași cădere pre-raport, dar la E2 a urmat gap down. Istoric amestecat: fără edge.", **STYLE) svgs["pre_nu"] = ch.render() # ================================================================ 9. butterfly: bias din call/put, volatilitate mare cl = [50.2, 49.8, 50.5, 50.1, 49.6, 50.3, 50.9, 50.4, 49.9, 50.6, 51.0, 50.5, 50.0, 49.7, 50.3, 50.8, 50.4, 49.9, 50.2, 50.7, 50.3, 49.8, 50.1, 50.6, 50.2, 49.9, 50.0, # 0-26 range 61.5, # 27 gap după raport 62.3, 61.8, 62.6, 62.0, 62.9, 62.4] # 28-33 cds = series(cl, 50.0, opens={27: 60.0}) E, G = 26, 27 K1, K2, K3 = 55.0, 60.0, 65.0 # aripa jos, corpul, aripa sus (completarea mea) S = cds[G].c move = pct(cds[E].c, S) assert 22.5 < move < 23.5, move fly = max(0.0, (K2 - K1) - abs(S - K2)) call = max(0.0, S - K1) assert K1 < S < K3 and abs(fly - 3.5) < 1e-9 and abs(call - 6.5) < 1e-9 assert max(c.h for c in cds[:G]) < K1 - 3 FACTS["fly"] = dict(pre=cds[E].c, S=S, move=move, K1=K1, K2=K2, K3=K3, fly=fly, call=call, open=cds[G].o) print(f"D9 pre {f2(cds[E].c)} close după raport {f2(S)} (+{move:.1f}%); butterfly {K1}/{K2}/{K3} valorează {fly} din {K2 - K1}; call {K1} = {call}") ch = PChart(candles=cds, levels=[Level(K3, f"aripa sus {K3:.2f}", PURPLE), Level(K2, f"corp butterfly {K2:.2f}", BLUE), Level(K1, f"aripa jos {K1:.2f}", PURPLE)], marks=[Mark(13, "range înainte de raport", GRAY, "above", dy=-8), Mark(E, "call/put 3-4x", ORANGE, "below", dy=4), Mark(G, f"+{move:.0f}%", GREEN, "below", dy=4)], scale=12, pad_bot_px=20, title=T_D, caption=f"Strike-urile sunt prețuri, deci se desenează. Payoff-ul nu: la {f2(S)} butterfly-ul valorează {fly:.2f} din 5.00.", **STYLE) svgs["butterfly"] = ch.render() # ================================================================ 10. condor: fără direcție, mișcare mai mică decât cea implicită cl = [50.4, 50.9, 50.5, 49.8, 50.2, 50.8, 51.3, 50.7, 50.1, 49.6, 50.0, 50.6, 51.1, 50.6, 50.0, 49.5, 49.9, 50.5, 51.0, 50.4, 49.9, 50.3, 50.8, 50.3, 49.8, 50.1, 50.0, # 0-26 range 51.5, # 27 după raport 51.2, 50.8, 51.3, 50.9, 51.4, 51.1] # 28-33 cds = series(cl, 50.0, opens={27: 51.8}) E, G = 26, 27 EM = 7.0 # mișcarea implicită din prețul opțiunilor, % (completarea mea) P_BUY, P_SELL, C_SELL, C_BUY = 44.0, 46.0, 54.0, 56.0 move = pct(cds[E].c, cds[G].o) assert 3.5 < move < 3.7, move lo, hi = cds[E].c * (1 - EM / 100), cds[E].c * (1 + EM / 100) assert P_SELL < lo < hi < C_SELL, "strike-urile vândute stau în afara mișcării implicite" assert all(P_SELL < c.l and c.h < C_SELL for c in cds[G:]), "după raport prețul rămâne între strike-urile vândute" FACTS["condor"] = dict(pre=cds[E].c, open=cds[G].o, move=move, EM=EM, lo=lo, hi=hi, k=(P_BUY, P_SELL, C_SELL, C_BUY), hi_after=max(c.h for c in cds[G:])) print(f"D10 pre {f2(cds[E].c)} open după raport {f2(cds[G].o)} (+{move:.1f}%); mișcare implicită ±{EM}% = {f2(lo)}-{f2(hi)}") ch = PChart(candles=cds, levels=[Level(C_BUY, f"call cumpărat {C_BUY:.2f}", PURPLE), Level(C_SELL, f"call vândut {C_SELL:.2f}", BLUE), Level(hi, f"+{EM:.0f}% implicit {f2(hi)}", GRAY, dash="2 3"), Level(lo, f"−{EM:.0f}% implicit {f2(lo)}", GRAY, dash="2 3"), Level(P_SELL, f"put vândut {P_SELL:.2f}", BLUE), Level(P_BUY, f"put cumpărat {P_BUY:.2f}", PURPLE)], marks=[Mark(13, "range, fără bias", GRAY, "above", dy=-8), Mark(E, "raport", ORANGE, "above", dy=-20), Mark(G, f"+{move:.1f}%", GREEN, "below", dy=10)], scale=14, title=T_D, caption=f"Piața plătea o mișcare de ±{EM:.0f}%; a venit +{move:.1f}%. Prețul rămâne între strike-urile vândute.", **STYLE) svgs["condor"] = ch.render() # ---------------------------------------------------------------- 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)