Files
atm-curs-dl/gen/az_earnings.py
Claude Agent f647874a52 datoria tehnica: garda pe dash, si PChart intr-un singur loc
Garda: `Level(p, text, color, "left")` punea tacut "left" in `dash` in loc
de `label_at`, iar SVG-ul iesea fara linie punctata si fara nicio eroare -
trei agenti au pierdut timp pe asta pe 13.09. Acum `__post_init__` crapa
cu mesaj care spune ce argument voiai. Verificat pe cazurile valide si pe
cele doua gresite.

PChart: era copiat in cinci generatoare, iar copiile divergisera - toate
cinci aveau hash-uri diferite si patru pastrau `spread()` cel vechi.
Nucleul comun trece in gen/pchart.py; 385 de linii sterse, 11 adaugate.

az_executie.py isi pastreaza PChart-ul: varianta lui calculeaza mediile
dintr-un istoric dinainte de bara 0 si si le deseneaza singura. Unirea
celor doua comportamente ca sa scap de un fisier ar fi riscat cele 10
diagrame de acolo, pentru zero castig. Ia doar `spread` reparat.

Reparatia schimba 4 documente, deci PROGRESS gresea cand spunea ca nicio
diagrama comisa nu sufera de bug: 08 si 10 se misca cu 0,2 px, dar 07 si
09 aveau etichete la 6 px de linia lor. Verificat cu ochii pe 3 diagrame
randate - etichetele stau acum pe linia lor, cu liniuta de legatura unde
a trebuit mutata. audit_diagrams: 0 probleme.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ThN7ixZrowca8aTGrK1MUJ
2026-09-18 11:57:57 +00:00

447 lines
25 KiB
Python
Raw Permalink 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)
from pchart import PChart, Seg, spread # noqa: E402
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)