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
This commit is contained in:
Claude Agent
2026-09-14 06:08:23 +00:00
parent 08d2b8ef7a
commit 994ded8ffa
3 changed files with 3554 additions and 0 deletions

533
gen/az_earnings.py Normal file
View File

@@ -0,0 +1,533 @@
"""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)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff