Files
atm-curs-dl/gen/az_earnings.py
Claude Agent 4383e500af 08 si 09: reparate 11 defecte de continut plus pasul vizual
Verificare independenta pe continut, aritmetica si, separat, pe 31 de
screenshot-uri citite. Niciun punct contestat.

08:
- TP1/TP2 era atribuit la Strategia 3, care are 1:1 / trail / lasata sa
  curga, fara TP2. Schema 1:1 / 2:1 / trail e insa a lui, din Q&A
  11.06.2025 - atribuire corectata cu ancora verificata, si scoasa din
  lista de completari, unde figura greșit ca adaugire a autorului. La Surse,
  Strategia 3 rămâne, cu mențiunea "TP la 1:1 si trail, fara TP2".
- "dupa TP1 stopul pozitiilor 2 si 3 trece la break-even" nu era in nicio
  sursa si contrazicea sursa: acolo a treia poate sta pe stopul initial,
  fiindca dupa 1R si 2R totalul ramane pe profit. Inlocuit cu calculul lui
  (-8 $ fata de +24 $). Agentul a prins si o fraza vecina falsa pe desen.
- gotcha de volatilitate era inversul regulii din 02.09 (stop pe break-even
  pana se lamureste). Corectat dupa sursa.
- "pretul e respins chiar la SMA-200" era fals pe seria desenata: o bara
  inchidea peste medie. Reparat DESENUL, nu textul - doua close-uri
  schimbate, cu assert ca nicio bara nu mai inchide peste SMA dupa
  atingere. Cifrele din tabel neschimbate.

09:
- "pullback-ul il atinge a doua zi dupa intrare" nu corespundea desenului:
  stopul e atins la a doua bara, nu in ziua urmatoare. Corectat in ambele
  locuri, cu assert pe bara.
- eticheta "stop" era scrisa peste linia "sus gap 66.70", deci pe imagine
  parea ca stopul a fost acolo, nu la 68.60. Mark se ancoreaza doar pe
  candele si nu incapea nicaieri curat, asa ca informatia a trecut in
  eticheta nivelului, cu assert pe bara de atingere.
- trei parafraze ale recoltei nu mai apar ca citate ale lui, iar condiția de
  la pre-earnings e atribuita explicit recoltei din 25.08, cu mențiunea ca
  nu e in citatul lui.
- rezerva lui la pattern-ul pre-earnings ("nu suta la suta") e adaugata si
  in enumerarea edge-urilor, singurul loc care o slabea.
- "-6%" apare acum la toate trei raportarile, cum spune textul.

Doua limite lasate cu bună știință, nu reparate: la transe liniile de 418 si
419 se vad ca una, iar la tinta de 1/2 linia de confluenta si SL-ul sunt la
0.10 $ - in ambele cazuri apropierea vine din date, si depărtarea ar cere
schimbarea cifrelor din text.

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

535 lines
30 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 and cds[CH + 1].l > sl_n >= cds[stop_bar].l, "textul: stop atins la a doua bară după intrare (67.70), nu la prima (68.90)"
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)}: atins la a doua bară după intrare", 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(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] - 2, f"{fn[0][0]:.0f}%", RED, "above", dy=-8),
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] - 2, f"{fn[2][0]:.0f}%", RED, "above", dy=-8),
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=-3),
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)