Files
atm-curs-dl/gen/az_earnings.py
Claude Agent 994ded8ffa Analiza Zilnica 09: earnings si optiuni (10 diagrame)
Al saptelea document de strategie. Primul scris dintr-o singura trecere,
fara agent de reparatii: promptul a primit de la start clasa PChart din
az_pivoti.py pentru plasarea etichetelor, in loc sa o redescopere la final.

Acoperite: earnings = gamble trade, cu perechea DA/NU pe aceeasi serie -
setup format inainte de raport, cu gap de -9.6% peste stop si executie la
open (-2.0R), fata de acelasi setup asteptat pana dupa raport (3.1R).
Gap-ul de earnings ca tinta de pullback (DA/NU), caderea pe rezultate bune
(DA/NU), raportul call/put 3-4x ca bias, pattern-ul pre-earnings pe trei
trimestre.

Rezerva lui la pattern-ul pre-earnings ("nu suta la suta") e pastrata in
text, in caption si in tabel, iar varianta care functioneaza e marcata ca
ilustrare a conditiei lui, nu ca semnal.

Payoff-ul optiunilor e tabel, nu diagrama, si documentul spune de ce:
payoff-ul are pretul pe orizontala si profitul pe verticala, fara timp -
alt grafic decat unul de pret. Pe graficul de pret apar doar strike-urile,
pentru ca sunt preturi.

Reparat la citirea screenshot-urilor, pe langa suprapuneri: "higher low"
era ancorat pe bara 30, dar minimul e pe bara 31; mutat, cu assert pe bara.
Si o linie de TP 57.90 care se suprapunea cu open-ul de 58.00.

Marcat ca adaugire proprie: tot exemplul de condor (nu are exemplu in
transcrieri), strike-urile butterfly-ului, si tabelul cu fractiunea de
pozitie la un gamble trade.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MtSTyTmt6AbCL9j5ajEDm1
2026-09-14 06:08:23 +00:00

534 lines
29 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

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

"""Diagramele pentru summaries/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 <!--SVG:nume--> ... <!--/SVG--> 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'<line x1="{x:.1f}" y1="{self.top - 4:.1f}" x2="{x:.1f}" '
f'y2="{self._body_bottom:.1f}" stroke="#bbb" stroke-dasharray="2 3"/>')
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'<line x1="{x0:.1f}" y1="{y:.1f}" x2="{x1:.1f}" y2="{y:.1f}" '
f'stroke="{s.color}" stroke-width="{1.5 if s.dash else 2}"{dash}/>')
if s.label_at == "out":
extra.append(f'<text x="{xr:.1f}" y="{placed[id(s)]:.1f}" fill="{s.color}">{s.text}</text>')
elif s.label_at == "lm":
extra.append(f'<text x="{x0 - 6:.1f}" y="{placed[id(s)]:.1f}" fill="{s.color}" '
f'text-anchor="end">{s.text}</text>')
elif s.label_at == "left":
extra.append(f'<text x="{x0 + 3:.1f}" y="{y + s.dy:.1f}" fill="{s.color}">{s.text}</text>')
else:
extra.append(f'<text x="{x1 - 3:.1f}" y="{y + s.dy:.1f}" fill="{s.color}" '
f'text-anchor="end">{s.text}</text>')
for y, s in out_lab:
if s.i1 == 0 and s.price == 0:
extra.append(f'<text x="{xr:.1f}" y="{placed[id(s)]:.1f}" fill="{s.color}">{s.text}</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 <line [^>]*stroke="none"[^>]*/>\n <text [^>]*fill="none"[^>]*></text>', "", 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"<!--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)