Files
atm-curs-dl/gen/az_context.py
Claude Agent 890f944aeb 07 context: reparate 9 defecte gasite de verificare independenta
Un agent care nu a scris documentul l-a verificat fata de harvest/ si
transcrieri. Toate cele 9 puncte confirmate in surse inainte de reparatie,
niciunul contestat.

Conținut, in ordinea gravitatii:
- regula "nu intri inainte de raport" era extinsa nedeclarat la NFP si Fed.
  Restransa la CPI si declarata ca adaugire; la NFP si minuta Fed scrie acum
  doar ce spune sursa (volatilitate mare, atentie la ora). Alte patru
  extinderi nedeclarate marcate pe loc si trecute in completari.
- rândul COT spunea "tinta se poate largi", care nu apare in sursa.
  Rescris dupa Q&A 18.08.2025: pe valute comercianții trec peste zero
  INAINTE de un bottom important, apoi revin net short, semnalul e rar si
  nu se ia singur.
- rândul CPI atribuia sursei lucruri pe care ea nu le spune. Rescris dupa
  Q&A 11.06.2025: in ziua CPI corelatiile clasice n-au tinut, FedWatch
  arata ~100% menținere.
- afirmatie falsa despre documentul 01: transele de acolo cumpara
  retesturile de 1/3 si 2/3 din ultima miscare, nu retestul de 1/2.
- o parafraza a recoltei era in ghilimele ca vorba lui. Pus citatul real:
  "ar putea macar jumatate din distanta asta sa o acopere".
- doua durate nesursate si inconsecvente intre ele ("inca doua saptamani"
  fata de "inca o saptamana"), scoase amandoua.

Desen: bara numita "hammer" in diagrama de spike ATR era roșie, cu codita
de 1.1x corpul. Acum are close 56.10 cu low tot pe 54.60, codita de 7.5x
corpul si close la 83% din range, cu assert in generator (codita >= 3x
corpul, close in treimea de sus). True range-ul barei e neschimbat, deci
nicio cifra din text nu s-a modificat.

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

523 lines
29 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 = {}
@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):
"""copiat din gen/az_pivoti.py (vezi acolo explicațiile)"""
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
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)