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
435 lines
24 KiB
Python
435 lines
24 KiB
Python
"""Diagramele pentru summaries/08_ANALIZA_ZILNICA/07_context_de_piata.md.
|
|
|
|
Toate prețurile sunt construite (fără ticker). Rulează:
|
|
.venv/bin/python gen/az_context.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.
|
|
|
|
`PChart` e copiat din gen/az_pivoti.py: etichetele nivelurilor stau în marginea
|
|
din dreapta, după ultima candelă, împinse în jos când se ating.
|
|
McClellan, AAII, Fear & Greed, breadth și COT nu sunt preț: în .md sunt tabele.
|
|
"""
|
|
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 Chart, Level, Mark, MA, from_closes # noqa: E402
|
|
|
|
MD = Path("/workspace/atm-curs-dl/summaries/08_ANALIZA_ZILNICA/07_context_de_piata.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 = {}
|
|
|
|
|
|
from pchart import PChart, Seg, spread # noqa: E402
|
|
|
|
|
|
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 near(a, b, tol=0.011):
|
|
return abs(a - b) <= tol
|
|
|
|
|
|
def wicks(n, special, default=(0.3, 0.3)):
|
|
w = [default] * n
|
|
for i, v in special.items():
|
|
w[i] = v
|
|
return w
|
|
|
|
|
|
def true_range(cds):
|
|
out = [cds[0].h - cds[0].l]
|
|
for p, c in zip(cds, cds[1:]):
|
|
out.append(max(c.h, p.c) - min(c.l, p.c))
|
|
return out
|
|
|
|
|
|
V = {} # cifrele folosite în text, tipărite la final
|
|
|
|
# ================================================================ 1. VIX DA: reject în fostul suport + lower low
|
|
vix_head = [15.2, 14.8, 15.4, 15.0, 14.6, 15.1, # 0-5 calm
|
|
17.0, 19.5, 23.8, 27.5, 26.0, # 6-10 spike
|
|
24.1, 22.0, 20.2, 19.6, 20.4, 19.8, 20.6, 19.5, # 11-18 se sprijină pe 19.20
|
|
17.9, 16.9, 16.4] # 19-21 sparge suportul
|
|
vw_head = {8: (0.8, 0.3), 9: (0.9, 0.4), 13: (0.3, 1.0), 14: (0.3, 0.4), 15: (0.3, 0.4), 16: (0.3, 0.6),
|
|
17: (0.3, 0.6), 18: (0.3, 0.3)}
|
|
vix_da = vix_head + [17.4, 18.5, 17.6, 16.8, 16.2, 15.6, 15.9, 15.1, 14.7, 15.0, 14.5, 14.3]
|
|
SUP = 19.20
|
|
RJ = 23
|
|
cds = from_closes(vix_da, wicks=wicks(len(vix_da), {**vw_head, 23: (0.7, 0.2)}), start=15.0)
|
|
assert all(near(cds[i].l, SUP) for i in (13, 14, 15, 18)), [cds[i].l for i in range(13, 19)]
|
|
assert all(c.l >= SUP - 0.01 for c in cds[11:19]), "suportul 19.20 ține pe barele 11-18"
|
|
assert cds[19].c < SUP, "bara 19 închide sub suport"
|
|
assert all(c.h < SUP for c in cds[20:RJ]), "după bara de spargere nimic nu revine la 19.20 până la retest"
|
|
assert near(cds[RJ].h, SUP) and cds[RJ].c < SUP, "retestul atinge 19.20 din jos și închide sub"
|
|
assert cds[RJ + 1].c < cds[RJ].c, "bara de după retest confirmă rejectul"
|
|
LL0 = min(c.l for c in cds[19:22])
|
|
LL = next(i for i in range(RJ + 1, len(cds)) if cds[i].l < LL0)
|
|
assert all(c.h < SUP for c in cds[RJ + 1:])
|
|
peak_v = max(range(len(cds)), key=lambda i: cds[i].h)
|
|
V["vix"] = dict(sup=SUP, peak=cds[peak_v].h, rj_h=cds[RJ].h, rj_c=cds[RJ].c, ll0=LL0, ll=LL, ll_l=cds[LL].l,
|
|
end=cds[-1].c)
|
|
print("D1 VIX", {k: (f2(v) if isinstance(v, float) else v) for k, v in V["vix"].items()})
|
|
ch = PChart(candles=cds,
|
|
levels=[Level(SUP, f"fost suport = rezistență {SUP:.2f}", ORANGE)],
|
|
marks=[Mark(peak_v, f"spike {cds[peak_v].h:.1f}", RED, "above"),
|
|
Mark(16, "suport", ORANGE, "below", dy=4),
|
|
Mark(RJ, "reject", RED, "above", dy=-4),
|
|
Mark(LL, "lower low", GREEN, "below", dy=4)],
|
|
scale=12, title="Exemplu construit (fără ticker) — daily, VIX (citit invers)",
|
|
caption="VIX respins sub fostul suport și lower low: frica scade, favorabil long-urilor pe indice.",
|
|
**STYLE)
|
|
svgs["vix_da"] = ch.render()
|
|
|
|
# ================================================================ 2. indicele, aceeași perioadă: HL + long
|
|
idx = [55.0, 55.3, 54.9, 55.2, 55.5, 55.2,
|
|
54.2, 53.0, 51.2, 49.8, 50.3,
|
|
51.0, 51.9, 52.4, 52.7, 52.2, 52.6, 52.1, 52.6,
|
|
53.4, 54.0, 54.3,
|
|
53.8, 53.2, 54.3,
|
|
54.8, 55.3, 55.9, 55.6, 56.2, 56.8, 56.5, 57.1, 57.5]
|
|
assert len(idx) == len(vix_da), "aceeași perioadă, bară cu bară"
|
|
cds = from_closes(idx, wicks=wicks(len(idx), {8: (0.3, 0.8), 9: (0.3, 0.9)}), start=55.2)
|
|
HL, TRG = RJ, RJ + 1
|
|
bottom = min(range(len(cds)), key=lambda i: cds[i].l)
|
|
assert bottom == peak_v, "bottom-ul indicelui = spike-ul VIX"
|
|
assert cds[HL].l > max(c.l for c in cds[11:19]), "pullback-ul face higher low peste consolidarea de după bottom"
|
|
assert cds[HL].l == min(c.l for c in cds[20:]), "bara 23 e low-ul pullback-ului de după bara 19"
|
|
assert cds[TRG].c > cds[HL].h, "trigger: close peste high-ul barei de higher low"
|
|
entry = cds[TRG].c
|
|
sl = round(cds[HL].l - 0.10, 2)
|
|
risk = entry - sl
|
|
tp = round(entry + 2 * risk, 2)
|
|
hit = next(i for i in range(TRG + 1, len(cds)) if cds[i].h >= tp)
|
|
assert min(c.l for c in cds[TRG + 1:]) > sl
|
|
V["idx"] = dict(bottom=cds[bottom].l, hl=cds[HL].l, entry=entry, sl=sl, risk=risk, tp=tp, hit=hit - TRG)
|
|
print("D2 indice", {k: (f2(v) if isinstance(v, float) else v) for k, v in V["idx"].items()})
|
|
ch = PChart(candles=cds,
|
|
levels=[Level(tp, f"TP {f2(tp)} = 2R", BLUE),
|
|
Level(entry, f"intrare {f2(entry)}", GREEN),
|
|
Level(sl, f"SL {f2(sl)}", RED)],
|
|
marks=[Mark(bottom, "bottom (VIX în spike)", GRAY, "below", dy=4),
|
|
Mark(HL, "HL = reject pe VIX", GREEN, "below", dy=14),
|
|
Mark(hit, "TP", BLUE, "above")],
|
|
scale=24, title="Exemplu construit (fără ticker) — daily, indicele, aceeași perioadă",
|
|
caption="Bara 23: VIX respins la 19.20, indicele face higher low. Intrarea vine pe close-ul următor.",
|
|
**STYLE)
|
|
svg_ = ch.render()
|
|
assert abs(px_ratio(ch, sl, entry, tp) - 2.0) < 0.01, "2R nu e desenat la 2x risc"
|
|
svgs["vix_indice"] = svg_
|
|
|
|
# ================================================================ 3. VIX NU: higher low pe VIX
|
|
vix_nu = vix_head + [17.4, 18.5, 17.6, 17.2, 18.4, 19.8, 21.6, 23.4, 22.5, 24.6, 23.8, 25.2]
|
|
cds = from_closes(vix_nu, wicks=wicks(len(vix_nu), {**vw_head, 23: (0.7, 0.2), 25: (0.2, 0.3)}), start=15.0)
|
|
assert [c.c for c in cds[:24]] == [c.c for c in from_closes(vix_da, start=15.0)[:24]], "până la bara 23 identic cu DA"
|
|
HLV = 25
|
|
assert cds[HLV].l > LL0 and cds[HLV].l == min(c.l for c in cds[RJ:]), "VIX face higher low"
|
|
back = next(i for i in range(HLV, len(cds)) if cds[i].c > SUP)
|
|
V["vixnu"] = dict(hl=cds[HLV].l, back=back, back_c=cds[back].c, end=cds[-1].c)
|
|
print("D3 VIX NU", {k: (f2(v) if isinstance(v, float) else v) for k, v in V["vixnu"].items()})
|
|
ch = PChart(candles=cds,
|
|
levels=[Level(SUP, f"fost suport {SUP:.2f}", ORANGE),
|
|
Level(LL0, f"low anterior {f2(LL0)}", GRAY)],
|
|
marks=[Mark(RJ, "reject?", GRAY, "above", dy=-4),
|
|
Mark(HLV, "higher low", RED, "below", dy=4)],
|
|
scale=12, title="Exemplu construit (fără ticker) — daily, VIX (citit invers)",
|
|
caption="Același început, dar VIX face higher low și închide peste 19.20: risc de nouă cădere pe indice.",
|
|
**STYLE)
|
|
svgs["vix_nu"] = ch.render()
|
|
|
|
# ================================================================ 4. ATR în spike la locul crimei: bounce
|
|
cl4 = [50.0, 50.6, 51.1, 50.8, 51.6, 52.3, 52.0, 53.0, 53.8, 54.4, # 0-9 swing high la 9
|
|
53.9, 53.4, 53.8, # 10-12
|
|
54.8, 55.7, 55.4, 56.5, 57.4, 57.1, 58.3, 59.2, 58.9, 60.1, 61.2, 62.0, # 13-24 vârf
|
|
61.4, 61.7, 60.6, 59.8, # 25-28 început de corecție
|
|
58.0, 56.3, 56.1, # 29-31 bare mari, hammer la 31
|
|
56.6, # 32 trigger
|
|
57.2, 57.9, 57.6, 58.6, 59.3, 59.0, 60.0, 60.8] # 33-40
|
|
cds = from_closes(cl4, wicks=wicks(len(cl4), {29: (0.4, 0.6), 30: (0.4, 0.7), 31: (0.1, 1.5)}, default=(0.2, 0.2)), start=49.7)
|
|
LC = cds[9].h
|
|
HM, TG = 31, 32
|
|
PK = max(range(len(cds)), key=lambda i: cds[i].h)
|
|
tr = true_range(cds)
|
|
for c, t in zip(cds, tr):
|
|
c.vol = t
|
|
tr_norm = sum(tr[10:29]) / len(tr[10:29])
|
|
assert PK == 24
|
|
assert all(c.h < LC for c in cds[:9]) and cds[13].c > LC, "bara 9 e swing high-ul spart = locul crimei"
|
|
assert near(cds[HM].l, LC, 0.051) and cds[HM].c > LC, "hammer-ul atinge locul crimei și închide deasupra"
|
|
_hb = abs(cds[HM].c - cds[HM].o)
|
|
assert (min(cds[HM].o, cds[HM].c) - cds[HM].l) >= 3 * _hb and cds[HM].c >= cds[HM].l + 2 / 3 * (cds[HM].h - cds[HM].l), "hammer adevărat: codița de jos >= 3x corpul, close în treimea de sus"
|
|
assert min(tr[29:31]) >= 2 * tr_norm, ("barele 29-30: range de 2x normalul", tr[25:33], tr_norm)
|
|
assert cds[TG].c > cds[HM].h, "trigger: close peste high-ul hammer-ului"
|
|
entry = cds[TG].c
|
|
sl = round(cds[HM].l - 0.10, 2)
|
|
risk = entry - sl
|
|
tp1 = round((cds[PK].h + cds[HM].l) / 2, 2) # jumătatea distanței până la vârf
|
|
R1 = (tp1 - entry) / risk
|
|
tp2 = round(entry + 2 * risk, 2)
|
|
hit1 = next(i for i in range(TG + 1, len(cds)) if cds[i].h >= tp1)
|
|
hit2 = next(i for i in range(TG + 1, len(cds)) if cds[i].h >= tp2)
|
|
assert tp2 < cds[PK].h and min(c.l for c in cds[TG + 1:]) > sl
|
|
V["atr"] = dict(lc=LC, pk=cds[PK].h, hm_l=cds[HM].l, tr_norm=tr_norm, tr_spike=max(tr[29:32]),
|
|
spike_x=max(tr[29:32]) / tr_norm, entry=entry, sl=sl, risk=risk, tp1=tp1, R1=R1, tp2=tp2,
|
|
hit1=hit1 - TG, hit2=hit2 - TG)
|
|
print("D4 ATR", {k: (f2(v) if isinstance(v, float) else v) for k, v in V["atr"].items()})
|
|
ch = PChart(candles=cds,
|
|
levels=[Level(tp2, f"TP2 {f2(tp2)} = 2R", BLUE),
|
|
Level(tp1, f"TP1 {f2(tp1)} = 1/2 până la vârf ({R1:.1f}R)", BLUE),
|
|
Level(entry, f"intrare {f2(entry)}", GREEN),
|
|
Level(LC, f"locul crimei {f2(LC)}", ORANGE),
|
|
Level(sl, f"SL {f2(sl)}", RED)],
|
|
marks=[Mark(9, "swing high", ORANGE, "above", dy=-4),
|
|
Mark(PK, f"vârf {f2(cds[PK].h)}", GRAY, "above"),
|
|
Mark(30, "spike de range", RED, "below", dy=26),
|
|
Mark(TG, "trigger", GREEN, "above", dy=-4)],
|
|
mas=[MA(20, GRAY, dash="4 3", text="SMA-20")],
|
|
scale=18, vol_h=70, vol_label="true range pe bară (volatilitate)",
|
|
title="Exemplu construit (fără ticker) — daily, indicele",
|
|
caption="Range-ul barelor sare la peste 2x normalul exact când prețul atinge locul crimei.",
|
|
**STYLE)
|
|
svg_ = ch.render()
|
|
assert abs(px_ratio(ch, sl, entry, tp2) - 2.0) < 0.01
|
|
assert abs(px_ratio(ch, sl, entry, tp1) - R1) < 0.01
|
|
svgs["atr_spike"] = svg_
|
|
|
|
# ================================================================ 5. ATR în cădere liberă: nu e bottom
|
|
cl5 = [50.0, 50.7, 51.2, 50.9, 51.8, 52.5, 52.2, 53.2, 53.9, 54.5,
|
|
54.0, 55.0, 55.6, 56.3, 57.0,
|
|
56.6, 56.3, 55.9, 56.0, 55.6, 55.3, 55.4, 55.0, 54.7, 54.8, 54.5, 54.2, 54.3, 54.0, 53.8, 53.9,
|
|
53.6, 53.4, 53.5, 53.2, 53.0]
|
|
w5 = [(0.3, 0.3)] * 15 + [(0.12, 0.12)] * 21
|
|
cds = from_closes(cl5, wicks=w5, start=49.7)
|
|
LC5 = cds[9].h
|
|
tr = true_range(cds)
|
|
for c, t in zip(cds, tr):
|
|
c.vol = t
|
|
up_tr = sum(tr[1:15]) / 14
|
|
dn_tr = sum(tr[15:]) / len(tr[15:])
|
|
touch = next(i for i in range(15, len(cds)) if cds[i].l <= LC5)
|
|
assert all(c.h < LC5 for c in cds[:9]) and cds[11].c > LC5
|
|
assert max(tr[15:]) < up_tr, "fără nicio bară de spike în corecție"
|
|
assert cds[-1].c < LC5 - 1.5, "prețul trece prin locul crimei fără reacție"
|
|
V["atrnu"] = dict(lc=LC5, up_tr=up_tr, dn_tr=dn_tr, touch=touch, end=cds[-1].c)
|
|
print("D5 ATR NU", {k: (f2(v) if isinstance(v, float) else v) for k, v in V["atrnu"].items()})
|
|
ch = PChart(candles=cds,
|
|
levels=[Level(LC5, f"locul crimei {f2(LC5)}", ORANGE)],
|
|
marks=[Mark(9, "swing high", ORANGE, "above", dy=-4),
|
|
Mark(touch, "atins, fără spike", RED, "below", dy=4),
|
|
Mark(33, "lower lows", RED, "below", dy=4)],
|
|
mas=[MA(20, GRAY, dash="4 3", text="SMA-20")],
|
|
scale=26, vol_h=70, vol_label="true range pe bară (volatilitate)",
|
|
title="Exemplu construit (fără ticker) — daily, indicele",
|
|
caption="Range-ul se micșorează pe cădere: nimeni nu capitulează, deci încă nu e bottom.",
|
|
**STYLE)
|
|
svgs["atr_nu"] = ch.render()
|
|
|
|
# ================================================================ 6-7. top purtat de mega-cap-uri: indice vs RSP
|
|
ci = [50.0, 50.5, 50.2, 51.0, 51.6, 51.3, 52.1, 52.8, 52.5, 53.3, 53.9, 53.6, 54.4, 55.0, 54.7, 55.5, 56.0,
|
|
55.4, 54.8, 55.1, 54.5, 54.2,
|
|
54.9, 55.6, 56.1, 56.7, 56.8,
|
|
55.9,
|
|
55.4, 55.7, 54.9, 54.3, 54.6, 53.8, 53.2, 53.5, 52.6, 52.4, 52.8, 52.1]
|
|
cds = from_closes(ci, wicks=wicks(len(ci), {26: (0.6, 0.1)}), start=49.8)
|
|
P1, P2, ST, EN = 16, 26, 26, 27
|
|
assert cds[P1].h == max(c.h for c in cds[:22]) and cds[P2].h == max(c.h for c in cds)
|
|
assert cds[P2].h > cds[P1].h, "indicele: al doilea vârf mai sus"
|
|
assert abs(cds[ST].c - cds[ST].o) <= 0.15 and cds[ST].h - max(cds[ST].o, cds[ST].c) >= 0.5, "shooting star"
|
|
assert cds[25].c - cds[25].o >= 0.5 and cds[EN].o - cds[EN].c >= 0.8, "evening star: bull mare, stea, bear mare"
|
|
assert cds[EN].c < cds[ST].l, "trigger short: close sub low-ul stelei"
|
|
entry = cds[EN].c
|
|
sl = round(cds[P2].h + 0.10, 2)
|
|
risk = sl - entry
|
|
tp = round(entry - 2 * risk, 2)
|
|
hit = next(i for i in range(EN + 1, len(cds)) if cds[i].l <= tp)
|
|
assert max(c.h for c in cds[EN + 1:]) < sl
|
|
V["top"] = dict(p1=cds[P1].h, p2=cds[P2].h, star_l=cds[ST].l, entry=entry, sl=sl, risk=risk, tp=tp,
|
|
hit=hit - EN)
|
|
print("D6 top indice", {k: (f2(v) if isinstance(v, float) else v) for k, v in V["top"].items()})
|
|
ch = PChart(candles=cds,
|
|
levels=[Level(sl, f"SL {f2(sl)}", RED),
|
|
Level(cds[P1].h, f"vârf 1 {f2(cds[P1].h)}", GRAY),
|
|
Level(entry, f"intrare short {f2(entry)}", GREEN),
|
|
Level(tp, f"TP {f2(tp)} = 2R", BLUE)],
|
|
marks=[Mark(P1, "vârf 1", GRAY, "above", dy=-4),
|
|
Mark(ST, "vârf 2 mai sus", RED, "above", dy=-4),
|
|
Mark(EN, "evening star", RED, "below", dy=4),
|
|
Mark(hit, "TP", BLUE, "below", dy=4)],
|
|
scale=22, title="Exemplu construit (fără ticker) — daily, indicele (ponderat)",
|
|
caption="Indicele face vârf nou. Singur, arată a continuare. Verificarea e pe RSP, dedesubt.",
|
|
**STYLE)
|
|
svg_ = ch.render()
|
|
assert abs(1 / px_ratio(ch, tp, entry, sl) - 2.0) < 0.01, "2R nu e desenat la 2x risc"
|
|
svgs["top_indice"] = svg_
|
|
|
|
cr = [40.0, 40.3, 40.1, 40.6, 41.0, 40.8, 41.3, 41.7, 41.5, 42.0, 42.4, 42.2, 42.7, 43.1, 42.9, 43.4, 43.7,
|
|
43.1, 42.6, 42.8, 42.3, 42.0,
|
|
42.4, 42.8, 43.0, 43.3, 43.2,
|
|
42.8,
|
|
42.4, 42.6, 42.0, 41.6, 41.8, 41.2, 40.8, 41.0, 40.4, 40.2, 40.5, 40.0]
|
|
assert len(cr) == len(ci)
|
|
cds = from_closes(cr, wicks=wicks(len(cr), {26: (0.3, 0.1)}, default=(0.25, 0.25)), start=39.8)
|
|
r1 = cds[P1].h
|
|
r2 = max(c.h for c in cds[22:28])
|
|
i_r2 = max(range(22, 28), key=lambda i: cds[i].h)
|
|
assert r1 == max(c.h for c in cds) and r2 < r1, "RSP: al doilea vârf mai jos = nu confirmă"
|
|
V["rsp"] = dict(r1=r1, r2=r2, i_r2=i_r2, diff=(r2 - r1) / r1 * 100)
|
|
print("D7 RSP", {k: (f2(v) if isinstance(v, float) else v) for k, v in V["rsp"].items()})
|
|
ch = PChart(candles=cds,
|
|
levels=[Level(r1, f"vârf 1 {f2(r1)}", GRAY)],
|
|
marks=[Mark(P1, "vârf 1", GRAY, "above", dy=-4),
|
|
Mark(i_r2, f"vârf 2 mai jos {f2(r2)}", RED, "above", dy=-20)],
|
|
scale=40, title="Exemplu construit (fără ticker) — daily, varianta equal weight (tip RSP)",
|
|
caption="Aceleași zile. Compania medie nu mai face vârf nou: urcarea o duc câteva mega-cap-uri.",
|
|
**STYLE)
|
|
svgs["top_rsp"] = ch.render()
|
|
|
|
# ================================================================ 8-9. CPI: breakout din compresie DA / NU
|
|
head8 = [50.0, 50.5, 50.3, 51.0, 51.6, 51.4, 52.1, 52.7, 52.5, 53.2, 53.8, 53.6, 54.3, 54.9, # 0-13
|
|
54.8, 55.1, 54.6, 54.9, 55.2, 54.7, 55.0, 54.8, 55.1, 54.9, 55.0] # 14-24 compresie
|
|
hv8 = [44, 47, 42, 48, 50, 43, 49, 51, 44, 50, 52, 45, 50, 48,
|
|
38, 36, 35, 33, 32, 31, 30, 29, 28, 27, 26]
|
|
hw8 = [(0.3, 0.3)] * 14 + [(0.2, 0.2)] * 11
|
|
C0, CP = 14, 25
|
|
tail_da = [56.4, 56.9, 57.3, 57.0, 57.8, 58.4, 58.1, 58.9, 59.5, 59.2, 60.0, 60.5, 60.2, 60.9]
|
|
tv_da = [96, 62, 55, 44, 52, 54, 42, 50, 53, 41, 49, 51, 40, 47]
|
|
tw_da = [(0.2, 0.3), (0.2, 0.2)] + [(0.3, 0.3)] * 12
|
|
cds = from_closes(head8 + tail_da, wicks=hw8 + tw_da, vols=hv8 + tv_da, start=49.8)
|
|
RH = max(c.h for c in cds[C0:CP])
|
|
RL = min(c.l for c in cds[C0:CP])
|
|
vavg = sum(hv8[C0:CP]) / (CP - C0)
|
|
CF = CP + 1
|
|
assert near(RH, 55.40) and near(RL, 54.40), (RH, RL)
|
|
assert cds[CP].c > RH and cds[CP].vol >= 3 * vavg, "bara de CPI închide peste range cu volum de 3x"
|
|
assert cds[CF].l > RH and cds[CF].c > cds[CP].c, "a doua zi rămâne deasupra range-ului: confirmare"
|
|
entry = cds[CF].c
|
|
sl = round(RH - 0.20, 2)
|
|
risk = entry - sl
|
|
tp = round(entry + 2 * risk, 2)
|
|
hit = next(i for i in range(CF + 1, len(cds)) if cds[i].h >= tp)
|
|
assert min(c.l for c in cds[CF + 1:]) > sl
|
|
V["cpi"] = dict(rh=RH, rl=RL, cp_c=cds[CP].c, vol=cds[CP].vol, vavg=vavg, entry=entry, sl=sl, risk=risk,
|
|
tp=tp, hit=hit - CF)
|
|
print("D8 CPI DA", {k: (f2(v) if isinstance(v, float) else v) for k, v in V["cpi"].items()})
|
|
ch = PChart(candles=cds,
|
|
levels=[Level(tp, f"TP {f2(tp)} = 2R", BLUE),
|
|
Level(entry, f"intrare {f2(entry)}", GREEN),
|
|
Level(RH, f"range high {f2(RH)}", ORANGE),
|
|
Level(sl, f"SL {f2(sl)}", RED),
|
|
Level(RL, f"range low {f2(RL)}", ORANGE)],
|
|
marks=[Mark(19, "compresie înainte de CPI", GRAY, "below", dy=26),
|
|
Mark(CP, "CPI", PURPLE, "below", dy=4),
|
|
Mark(CF, "confirmare", GREEN, "above", dy=-4)],
|
|
scale=22, vol_h=70, title="Exemplu construit (fără ticker) — daily, indicele",
|
|
caption="Nu intri înainte de raport. Intri după ce ziua de după CPI rămâne deasupra range-ului.",
|
|
**STYLE)
|
|
svg_ = ch.render()
|
|
assert abs(px_ratio(ch, sl, entry, tp) - 2.0) < 0.01
|
|
svgs["cpi_da"] = svg_
|
|
|
|
tail_nu = [55.1, 54.2, 53.8, 54.1, 53.4, 52.9, 53.2, 52.5, 52.0, 52.3, 51.7, 51.2, 51.5, 50.9]
|
|
tv_nu = [92, 70, 58, 44, 60, 62, 45, 58, 60, 44, 55, 57, 42, 52]
|
|
tw_nu = [(1.4, 0.3)] + [(0.3, 0.3)] * 13
|
|
cds = from_closes(head8 + tail_nu, wicks=hw8 + tw_nu, vols=hv8 + tv_nu, start=49.8)
|
|
assert near(max(c.h for c in cds[C0:CP]), RH) and near(min(c.l for c in cds[C0:CP]), RL), "aceeași compresie"
|
|
assert cds[CP].h > RH + 0.8 and RL < cds[CP].c < RH, "CPI: spike peste range, close înapoi înăuntru"
|
|
BD = next(i for i in range(CP + 1, len(cds)) if cds[i].c < RL)
|
|
assert BD == CP + 1
|
|
V["cpinu"] = dict(cp_h=cds[CP].h, cp_c=cds[CP].c, bd_c=cds[BD].c, end=cds[-1].c)
|
|
print("D9 CPI NU", {k: (f2(v) if isinstance(v, float) else v) for k, v in V["cpinu"].items()})
|
|
ch = PChart(candles=cds,
|
|
levels=[Level(RH, f"range high {f2(RH)}", ORANGE),
|
|
Level(RL, f"range low {f2(RL)}", ORANGE)],
|
|
marks=[Mark(19, "compresie înainte de CPI", GRAY, "below", dy=26),
|
|
Mark(CP, f"CPI: spike {f2(cds[CP].h)}", PURPLE, "above", dy=-4),
|
|
Mark(BD, "close sub range", RED, "below", dy=40)],
|
|
scale=22, vol_h=70, title="Exemplu construit (fără ticker) — daily, indicele",
|
|
caption="Același range. Spike-ul de pe CPI se închide înapoi înăuntru: cine a cumpărat la 56 e prins.",
|
|
**STYLE)
|
|
svgs["cpi_nu"] = ch.render()
|
|
|
|
# ================================================================ 10. bond-urile: hammer în suportul quarterly
|
|
cl10 = [46.0, 45.6, 45.9, 45.2, 44.7, 45.0, 44.3, 43.8, 44.1, 43.4, 43.0, 43.3, # 0-11
|
|
43.9, 44.4, 44.1, 44.7, 44.3, 43.9, # 12-17
|
|
43.4, 42.9, 42.6, 42.5, # 18-21 (21 hammer)
|
|
43.0, 43.4, 43.2, 43.8, 44.3, 44.0, 44.6, 45.1, 44.8, 45.4, 45.9, 45.6] # 22-33
|
|
cds = from_closes(cl10, wicks=wicks(len(cl10), {21: (0.1, 0.7)}, default=(0.2, 0.2)), start=46.3)
|
|
L1 = min(range(0, 12), key=lambda i: cds[i].l)
|
|
H10 = 21
|
|
SQ = 41.80
|
|
assert near(cds[H10].l, SQ) and cds[H10].l == min(c.l for c in cds), "hammer-ul face low-ul seriei, în suportul quarterly"
|
|
assert cds[H10].l < cds[L1].l, "al doilea picior mai jos"
|
|
assert min(cds[H10].o, cds[H10].c) - cds[H10].l >= 2 * abs(cds[H10].o - cds[H10].c), "hammer: codiță >= 2x corpul"
|
|
TG10 = H10 + 1
|
|
assert cds[TG10].c > cds[H10].h
|
|
V["tlt"] = dict(l1=cds[L1].l, sq=SQ, hm_l=cds[H10].l, hm_c=cds[H10].c, tg=TG10, tg_c=cds[TG10].c)
|
|
print("D10 TLT", {k: (f2(v) if isinstance(v, float) else v) for k, v in V["tlt"].items()})
|
|
ch = PChart(candles=cds,
|
|
levels=[Level(SQ, f"suport quarterly {SQ:.2f}", ORANGE)],
|
|
marks=[Mark(3, "downtrend", GRAY, "above", dy=-4),
|
|
Mark(L1, "picior 1", GRAY, "below", dy=4),
|
|
Mark(H10, "hammer, picior 2 mai jos", GREEN, "below", dy=4)],
|
|
scale=26, pad_bot_px=10, title="Exemplu construit (fără ticker) — daily, ETF de bond-uri pe termen lung",
|
|
caption="Spike low sub picior 1, hammer în suportul quarterly: bond-urile își pot face bottom-ul.",
|
|
**STYLE)
|
|
svgs["tlt"] = ch.render()
|
|
|
|
# ================================================================ 11. acțiunea sensibilă la dobândă: ascending triangle
|
|
cl11 = [49.0, 49.6, 50.3, 50.1, 51.0, 51.8, 52.6, 53.5, # 0-7
|
|
54.8, 54.0, 53.1, 53.8, 54.4, 54.8, 54.2, 53.6, 54.2, 54.8, 54.4, 54.0, 54.5, 54.8, 54.5, 54.3, 54.6, # 8-24
|
|
55.7, # 25 breakout
|
|
56.2, 55.9, 56.6, 57.2, 56.9, 57.6, 58.2, 57.9, 58.6, 59.2, 58.9, 59.5, 60.0, 59.7] # 26-39
|
|
cds = from_closes(cl11, wicks=[(0.2, 0.2)] * len(cl11), start=48.7)
|
|
RT = 55.00
|
|
BO = 25
|
|
assert near(max(c.h for c in cds[8:BO]), RT), "vârful triunghiului e plat la 55.00"
|
|
assert sum(1 for c in cds[8:BO] if near(c.h, RT)) >= 4, "cel puțin 4 atingeri ale rezistenței"
|
|
sw = [min(cds[a:b], key=lambda c: c.l).l for a, b in ((9, 13), (14, 17), (18, 21), (22, 25))]
|
|
assert all(x < y for x, y in zip(sw, sw[1:])), f"low-urile nu urcă: {sw}"
|
|
assert cds[BO].c > RT and all(c.c <= RT for c in cds[8:BO])
|
|
assert BO > TG10, "breakout-ul acțiunii vine după trigger-ul de pe bond-uri"
|
|
entry = cds[BO].c
|
|
sl = round(sw[-1] - 0.10, 2)
|
|
risk = entry - sl
|
|
tp = round(entry + 2 * risk, 2)
|
|
hit = next(i for i in range(BO + 1, len(cds)) if cds[i].h >= tp)
|
|
assert min(c.l for c in cds[BO + 1:]) > sl
|
|
V["dhi"] = dict(rt=RT, lows=[f2(x) for x in sw], entry=entry, sl=sl, risk=risk, tp=tp, hit=hit - BO)
|
|
print("D11 actiune", {k: (f2(v) if isinstance(v, float) else v) for k, v in V["dhi"].items()})
|
|
lowbars = [min(range(a, b), key=lambda i: cds[i].l) for a, b in ((9, 13), (14, 17), (18, 21), (22, 25))]
|
|
ch = PChart(candles=cds,
|
|
levels=[Level(tp, f"TP {f2(tp)} = 2R", BLUE),
|
|
Level(entry, f"intrare {f2(entry)}", GREEN),
|
|
Level(RT, f"rezistență {RT:.2f}", ORANGE),
|
|
Level(sl, f"SL {f2(sl)}", RED)],
|
|
marks=[Mark(lowbars[0], "low", GRAY, "below", dy=4),
|
|
Mark(lowbars[1], "HL", GREEN, "below", dy=4),
|
|
Mark(lowbars[2], "HL", GREEN, "below", dy=4),
|
|
Mark(BO, "breakout", GREEN, "above", dy=-4),
|
|
Mark(hit, "TP", BLUE, "above")],
|
|
scale=20, title="Exemplu construit (fără ticker) — daily, acțiune sensibilă la dobândă",
|
|
caption="Aceleași zile ca bond-urile: ascending triangle, breakout la 3 zile după trigger-ul de pe bond-uri.",
|
|
**STYLE)
|
|
svg_ = ch.render()
|
|
assert abs(px_ratio(ch, sl, entry, tp) - 2.0) < 0.01
|
|
svgs["dhi"] = svg_
|
|
|
|
# ---------------------------------------------------------------- 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)
|