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
447 lines
25 KiB
Python
447 lines
25 KiB
Python
"""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)
|