Al cincilea document de strategie, pe modelul aprobat al lui 01. Acoperite: pivot monthly / quarterly / anual, proiectie bull cu calculul din H/L/C-ul perioadei anterioare, proiectie bear cu short pe reject, pivoti divergenti = trading range (DA/NU), "inside" = compresie (DA/NU), trei variante de reject pe pivot, scarita HH/HL pe pivoti (DA/NU). Pivotii "in trepte" (nivel valabil doar pe luna sau trimestrul lui) se desenau greu cu Chart, asa ca generatorul are o subclasa PChart care foloseste _y/_cx din svg_candles - nicio coordonata scrisa de mana, si svg_candles.py ramane neatins. Etichetele perioadei curente merg in marginea dreapta, ale perioadei trecute in stanga, iar doua etichete apropiate se imping automat una sub alta. Reparat la citirea screenshot-urilor: pe D4 lipsea linia R1 B 53.10, desi textul vorbeste de close peste ea si de retest pe ea. Plus sase marcaje care stateau peste linii de pret. Audit: 0, nicio cifra din text schimbata, toate assert-urile trec. Neconfirmate, marcate ca atare in document: "pivot + 2" (10.09) - poate fi +2% pe zi sau zona pivot-R2, nu e desenat ca regula; "proiectie neutra" (03.09) nu e definita in sesiuni, e legata de "bull neutru" din Modul 2 ca adaugire proprie. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MtSTyTmt6AbCL9j5ajEDm1
581 lines
34 KiB
Python
581 lines
34 KiB
Python
"""Diagramele pentru summaries/08_ANALIZA_ZILNICA/04_pivoti_si_proiectii.md.
|
||
|
||
Toate prețurile sunt construite (fără ticker). Rulează:
|
||
.venv/bin/python gen/az_pivoti.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.
|
||
|
||
Pivoții sunt „în trepte": fiecare perioadă (lună / trimestru) are nivelurile
|
||
ei, calculate din H/L/C ale perioadei anterioare. `PChart` desenează aceste
|
||
segmente cu aceeași scară ca a candelelor (`_y`, `_cx` din svg_candles) —
|
||
nu există coordonate scrise de mână.
|
||
"""
|
||
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/04_pivoti_si_proiectii.md")
|
||
GRAY, RED, GREEN, BLUE, ORANGE, PURPLE = "#777", "#c00", "#188a3e", "#1a5fb4", "#b06000", "#6a3d9a"
|
||
STYLE = dict(gap=15, body_w=9, left=44, width=830)
|
||
svgs = {}
|
||
|
||
|
||
@dataclass
|
||
class Seg:
|
||
"""nivel valabil doar pe barele i0..i1 (o lună, un trimestru)"""
|
||
i0: int
|
||
i1: int
|
||
price: float
|
||
text: str
|
||
color: str = ORANGE
|
||
label_at: str = "out" # "out" marginea dreaptă | "lm" marginea stângă | "left"/"right" pe linie
|
||
dy: float = -5.0 # pentru "left"/"right": eticheta deasupra liniei (negativ) sau dedesubt
|
||
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):
|
||
"""Chart cu niveluri pe perioade. Nivelurile (`levels`) devin segmente pe toată seria;
|
||
etichetele stau în margini (dreapta / stânga), niciodată peste candele."""
|
||
segs: list = field(default_factory=list)
|
||
seps: list = field(default_factory=list) # indecși unde începe o perioadă nouă
|
||
pad_px: float = 24.0 # spațiu între cel mai sus preț și titlu
|
||
lm: int = 104 # marginea stângă pentru etichetele "lm"
|
||
pad_bot_px: float = 0.0 # spațiu sub cel mai jos preț, pentru etichetele de sub candele
|
||
|
||
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)
|
||
# lățimea: cât să încapă cea mai lungă etichetă din marginea dreaptă
|
||
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])
|
||
# prețurile segmentelor intră în scară ca niveluri invizibile
|
||
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] # eticheta MA o punem noi, în margine
|
||
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: # eticheta de MA
|
||
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)
|
||
# nivelurile invizibile lasă linii/etichete goale: le scoatem
|
||
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 pivots(bars):
|
||
"""floor pivots din H/L/C ale perioadei încheiate"""
|
||
H, L, C = max(c.h for c in bars), min(c.l for c in bars), bars[-1].c
|
||
P = (H + L + C) / 3
|
||
return dict(H=H, L=L, C=C, P=P, R1=2 * P - L, S1=2 * P - H, R2=P + (H - L), S2=P - (H - L))
|
||
|
||
|
||
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}"
|
||
|
||
|
||
# ================================================================ 1. calcul + proiecție bull (daily, pivot monthly)
|
||
A0, B0, N = 0, 21, 42
|
||
PA = 48.00 # pivotul lunii A (calculat din luna dinaintea ei, nu e pe desen)
|
||
a_cl = [47.0, 46.8, 47.4, 47.1, 48.0, 48.5, 48.2, 49.1, 49.7, 49.3, 50.2, 50.8, 50.4, 51.3, 51.8, 51.5, 51.9, 51.3,
|
||
51.7, 51.1, 51.4]
|
||
b_cl = [51.0, 50.6, 50.4, 51.1, 51.8, 52.5, 52.2, 53.0, 53.5, 53.0, 52.7, 53.3, 54.0, 54.6, 54.3, 55.0, 55.5, 54.9,
|
||
54.5, 54.9, 55.1]
|
||
w = [(0.3, 0.3)] * 21 + [(0.3, 0.3), (0.2, 0.3), (0.3, 0.35), (0.3, 0.3)] + [(0.3, 0.3)] * 17
|
||
cds = from_closes(a_cl + b_cl, wicks=w, start=47.3)
|
||
pv = pivots(cds[A0:B0])
|
||
b = cds[B0:]
|
||
assert pv["P"] > PA, "proiecția trebuie să fie bull: pivotul lunii B peste pivotul lunii A"
|
||
assert max(c.h for c in b) >= pv["R2"], "în proiecția bull exemplul trebuie să atingă R2"
|
||
hit_r1 = B0 + next(i for i, c in enumerate(b) if c.h >= pv["R1"])
|
||
hit_r2 = B0 + next(i for i, c in enumerate(b) if c.h >= pv["R2"])
|
||
low_b = min(c.l for c in b)
|
||
i_low_b = B0 + min(range(len(b)), key=lambda i: b[i].l)
|
||
assert abs(low_b - pv["P"]) <= 0.15 and low_b > pv["S1"], "pullback-ul lunii B trebuie să se oprească la pivot, peste S1"
|
||
P1 = pv
|
||
assert hit_r1 - B0 + 1 == 9 and hit_r2 - B0 + 1 == 17, "textul: R1 în ziua 9, R2 în ziua 17"
|
||
print(f"D1 luna A H {f2(pv['H'])} L {f2(pv['L'])} C {f2(pv['C'])} -> P {f2(pv['P'])} R1 {f2(pv['R1'])} "
|
||
f"R2 {f2(pv['R2'])} S1 {f2(pv['S1'])}; low B {f2(low_b)} la {i_low_b}; R1 la {hit_r1}, R2 la {hit_r2}")
|
||
ch = PChart(candles=cds,
|
||
segs=[Seg(A0, B0 - 1, PA, f"pivot A {PA:.2f}", ORANGE, "lm"),
|
||
Seg(B0, N - 1, pv["R2"], f"R2 {f2(pv['R2'])}", BLUE),
|
||
Seg(B0, N - 1, pv["R1"], f"R1 {f2(pv['R1'])}", BLUE),
|
||
Seg(B0, N - 1, pv["P"], f"pivot B {f2(pv['P'])}", ORANGE),
|
||
Seg(B0, N - 1, pv["S1"], f"S1 {f2(pv['S1'])}", RED)],
|
||
seps=[B0],
|
||
marks=[Mark(i_low_b, "ține pivotul", ORANGE, "below", dy=4),
|
||
Mark(hit_r1, "R1", BLUE, "above"),
|
||
Mark(hit_r2, "R2", BLUE, "above")],
|
||
scale=20, title="Exemplu construit (fără ticker) — daily, pivot monthly",
|
||
caption=f"Pivotul B = (H {f2(pv['H'])} + L {f2(pv['L'])} + C {f2(pv['C'])}) / 3 = {f2(pv['P'])} > {PA:.2f}: proiecție bull.",
|
||
**STYLE)
|
||
svgs["bull"] = ch.render()
|
||
|
||
# ================================================================ 2. proiecție bear + reject sub pivot (short)
|
||
PA2 = 52.00
|
||
a_cl = [53.0, 53.2, 52.6, 52.9, 52.0, 51.5, 51.8, 50.9, 50.3, 50.7, 49.8, 49.2, 49.6, 48.7, 48.2, 48.5, 48.1, 48.7,
|
||
48.3, 48.9, 48.6]
|
||
b_cl = [49.0, 49.4, 49.7, 49.0, 48.8, 48.3, 48.6, 47.9, 47.3, 47.6, 46.9, 46.3, 46.7, 46.0, 45.5, 45.8, 45.0, 44.6,
|
||
44.9, 44.5, 44.8]
|
||
w = [(0.3, 0.3)] * 21 + [(0.3, 0.3), (0.3, 0.3), (0.6, 0.2), (0.2, 0.3)] + [(0.3, 0.3)] * 17
|
||
cds = from_closes(a_cl + b_cl, wicks=w, start=52.7)
|
||
pv = pivots(cds[A0:B0])
|
||
b = cds[B0:]
|
||
RJ, TR = B0 + 2, B0 + 3
|
||
assert pv["P"] < PA2, "proiecția trebuie să fie bear"
|
||
assert cds[RJ].h > pv["P"] and cds[RJ].c < pv["P"], "bara de reject: codiță peste pivot, închidere sub"
|
||
assert all(c.h < pv["P"] for c in cds[B0:RJ]), "înainte de reject prețul e sub pivot"
|
||
assert cds[TR].c < cds[RJ].l, "trigger-ul short: închidere sub low-ul barei de reject"
|
||
entry = cds[TR].c
|
||
sl = round(cds[RJ].h + 0.10, 2)
|
||
risk = sl - entry
|
||
tp = round(entry - 2 * risk, 2)
|
||
hit = next(i for i in range(TR + 1, N) if cds[i].l <= tp)
|
||
assert min(c.l for c in b) <= pv["S2"], "în proiecția bear exemplul atinge S2"
|
||
assert max(c.h for c in cds[TR + 1:]) < pv["P"], "după reject prețul nu mai revine peste pivot"
|
||
hit_s1 = next(i for i in range(B0, N) if cds[i].l <= pv["S1"])
|
||
hit_s2 = next(i for i in range(B0, N) if cds[i].l <= pv["S2"])
|
||
assert hit - B0 + 1 == 12, "textul: TP în a 12-a zi"
|
||
print(f"D2 P {f2(pv['P'])} R1 {f2(pv['R1'])} S1 {f2(pv['S1'])} S2 {f2(pv['S2'])}; reject high {f2(cds[RJ].h)} "
|
||
f"close {f2(cds[RJ].c)}; entry {f2(entry)} SL {f2(sl)} risc {f2(risk)} TP {f2(tp)} atins la {hit}; S1 {hit_s1} S2 {hit_s2}")
|
||
ch = PChart(candles=cds,
|
||
segs=[Seg(A0, B0 - 1, PA2, f"pivot A {PA2:.2f}", ORANGE, "lm"),
|
||
Seg(B0, N - 1, pv["R1"], f"R1 {f2(pv['R1'])}", BLUE),
|
||
Seg(B0, N - 1, pv["P"], f"pivot B {f2(pv['P'])}", ORANGE),
|
||
Seg(B0, N - 1, pv["S1"], f"S1 {f2(pv['S1'])}", RED),
|
||
Seg(B0, N - 1, pv["S2"], f"S2 {f2(pv['S2'])}", RED)],
|
||
seps=[B0],
|
||
levels=[Level(sl, f"SL {f2(sl)}", RED, label_at="left"),
|
||
Level(entry, f"intrare short {f2(entry)}", GREEN, label_at="left"),
|
||
Level(tp, f"TP {f2(tp)} = 2R", BLUE, label_at="left")],
|
||
marks=[Mark(RJ, "reject", RED, "above", dy=-4)],
|
||
scale=20, title="Exemplu construit (fără ticker) — daily, pivot monthly",
|
||
caption=f"Pivotul B {f2(pv['P'])} < pivotul A {PA2:.2f}: proiecție bear. Rejectul sub pivot dă short-ul.",
|
||
**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["bear"] = svg_
|
||
BEAR = dict(pv=pv, entry=entry, sl=sl, tp=tp, risk=risk, rj_h=cds[RJ].h, rj_c=cds[RJ].c, PA=PA2)
|
||
|
||
# ================================================================ 3-4. pivoți divergenți = trading range
|
||
PAd, R1Ad, S1Ad = 50.00, 54.00, 46.00 # nivelurile lunii A (din luna dinaintea ei)
|
||
a_cl = [51.6, 52.3, 52.9, 53.2, 52.6, 52.0, 51.3, 50.6, 49.9, 49.2, 48.7, 48.4, 48.9, 49.6, 50.3, 50.9, 51.4, 50.8,
|
||
50.2, 50.6, 50.2]
|
||
aw = [(0.3, 0.3)] * 21
|
||
# 3. NU: luna B rămâne între S1 și R1
|
||
b_nu = [50.8, 51.5, 52.2, 52.6, 52.1, 51.4, 50.6, 49.8, 49.0, 48.5, 48.9, 49.7, 50.5, 51.3, 52.0, 52.5, 52.0, 51.2,
|
||
50.5, 49.8, 50.3]
|
||
cds = from_closes(a_cl + b_nu, wicks=aw + [(0.3, 0.3)] * 21, start=51.0)
|
||
pv = pivots(cds[A0:B0])
|
||
b = cds[B0:]
|
||
assert pv["P"] > PAd and pv["S1"] > S1Ad and pv["R1"] < R1Ad, "pivoții nu sunt divergenți"
|
||
assert max(c.h for c in b) < pv["R1"] and min(c.l for c in b) > pv["S1"], "luna B trebuie să rămână în range"
|
||
top_b = B0 + max(range(21), key=lambda i: b[i].h)
|
||
bot_b = B0 + min(range(21), key=lambda i: b[i].l)
|
||
DIV = pv
|
||
print(f"D3 A-levels P {PAd} R1 {R1Ad} S1 {S1Ad}; B: P {f2(pv['P'])} R1 {f2(pv['R1'])} S1 {f2(pv['S1'])} R2 {f2(pv['R2'])}; "
|
||
f"B high {f2(max(c.h for c in b))} low {f2(min(c.l for c in b))}")
|
||
ch = PChart(candles=cds,
|
||
segs=[Seg(A0, B0 - 1, R1Ad, f"R1 A {R1Ad:.2f}", BLUE, "lm"),
|
||
Seg(A0, B0 - 1, PAd, f"pivot A {PAd:.2f}", ORANGE, "lm"),
|
||
Seg(A0, B0 - 1, S1Ad, f"S1 A {S1Ad:.2f}", RED, "lm"),
|
||
Seg(B0, N - 1, pv["R1"], f"R1 B {f2(pv['R1'])} (mai jos)", BLUE),
|
||
Seg(B0, N - 1, pv["P"], f"pivot B {f2(pv['P'])} (mai sus)", ORANGE),
|
||
Seg(B0, N - 1, pv["S1"], f"S1 B {f2(pv['S1'])} (mai sus)", RED)],
|
||
seps=[B0],
|
||
marks=[Mark(top_b, "nu atinge R1", GRAY, "above", dy=-12),
|
||
Mark(bot_b, "nu atinge S1", GRAY, "below", dy=14)],
|
||
scale=24, title="Exemplu construit (fără ticker) — daily, pivot monthly",
|
||
caption="Suport și pivot mai sus, rezistență mai jos: luna B e un trading range.", **STYLE)
|
||
svgs["divergenti_nu"] = ch.render()
|
||
|
||
# 4. DA: luna B atinge R1 și continuă
|
||
b_da = [50.8, 51.4, 52.0, 52.6, 53.4, 53.9, 53.6, 53.4, 53.9, 54.3, 54.9, 54.6, 55.2, 55.7, 55.4, 56.0, 56.5, 56.2,
|
||
56.6, 56.3, 56.8]
|
||
bw = [(0.3, 0.3)] * 7 + [(0.2, 0.3), (0.3, 0.2)] + [(0.3, 0.3)] * 12
|
||
cds = from_closes(a_cl + b_da, wicks=aw + bw, start=51.0)
|
||
pv = pivots(cds[A0:B0])
|
||
BO, RT, CF = B0 + 4, B0 + 7, B0 + 8
|
||
assert abs(pv["P"] - DIV["P"]) < 1e-9, "luna A trebuie să fie aceeași ca la diagrama NU"
|
||
assert all(c.c <= pv["R1"] for c in cds[B0:BO]) and cds[BO].c > pv["R1"], "bara 25 e prima închidere peste R1 B"
|
||
assert abs(cds[RT].l - pv["R1"]) <= 0.12 and cds[RT].c > pv["R1"], "retestul trebuie să atingă R1 B și să închidă peste"
|
||
assert cds[CF].c > cds[RT].h, "candela din dreapta confirmă retestul"
|
||
entry = cds[CF].c
|
||
sl = round(cds[RT].l - 0.20, 2)
|
||
risk = entry - sl
|
||
tp = round(pv["R2"], 2)
|
||
R = (tp - entry) / risk
|
||
hit = next(i for i in range(CF + 1, N) if cds[i].h >= tp)
|
||
hit_r1a = next(i for i in range(CF, N) if cds[i].h >= R1Ad)
|
||
assert R >= 2, R
|
||
print(f"D4 breakout {f2(cds[BO].c)} retest low {f2(cds[RT].l)} entry {f2(entry)} SL {f2(sl)} risc {f2(risk)} "
|
||
f"TP R2 {f2(tp)} = {R:.2f}R atins la {hit}; R1 A atins la {hit_r1a}")
|
||
DIVT = dict(entry=entry, sl=sl, tp=tp, R=R, bo=cds[BO].c, rt=cds[RT].l, risk=risk)
|
||
ch = PChart(candles=cds,
|
||
segs=[Seg(A0, B0 - 1, R1Ad, f"R1 A {R1Ad:.2f}", BLUE, "lm"),
|
||
Seg(A0, B0 - 1, PAd, f"pivot A {PAd:.2f}", ORANGE, "lm"),
|
||
Seg(A0, B0 - 1, S1Ad, f"S1 A {S1Ad:.2f}", RED, "lm"),
|
||
Seg(B0, N - 1, pv["R1"], f"R1 B {f2(pv['R1'])}", BLUE),
|
||
Seg(B0, N - 1, pv["P"], f"pivot B {f2(pv['P'])}", ORANGE),
|
||
Seg(B0, N - 1, pv["S1"], f"S1 B {f2(pv['S1'])}", RED)],
|
||
seps=[B0],
|
||
levels=[Level(sl, f"SL {f2(sl)}", RED, label_at="left"),
|
||
Level(entry, f"intrare {f2(entry)}", GREEN, label_at="left"),
|
||
Level(tp, f"TP = R2 B {f2(tp)} = {R:.1f}R", BLUE, label_at="left")],
|
||
marks=[Mark(BO, "close peste R1 B", GREEN, "above", dy=-18),
|
||
Mark(RT, "retest", ORANGE, "below", dy=4)],
|
||
scale=24, title="Exemplu construit (fără ticker) — daily, pivot monthly",
|
||
caption=f"Aceeași lună A. De data asta luna B atinge R1 ({f2(pv['R1'])}) și merge spre R2.", **STYLE)
|
||
svg_ = ch.render()
|
||
assert abs(px_ratio(ch, sl, entry, tp) - R) < 0.01
|
||
svgs["divergenti_da"] = svg_
|
||
|
||
# ================================================================ 5-6. proiecție „inside" = compresie
|
||
PAi, R1Ai, S1Ai = 50.00, 55.00, 45.00
|
||
a_cl = [50.6, 51.1, 51.5, 51.0, 50.4, 49.9, 49.6, 50.1, 50.7, 51.2, 51.4, 50.9, 50.3, 49.8, 50.2, 50.8, 51.2, 50.7,
|
||
50.3, 50.6, 50.9]
|
||
aw = [(0.25, 0.25)] * 21
|
||
av = [52, 48, 50, 46, 44, 47, 45, 42, 44, 43, 40, 42, 41, 38, 40, 39, 37, 38, 36, 37, 35]
|
||
# 5. NU: luna B stă în compresie
|
||
b_nu = [50.5, 51.0, 51.5, 51.2, 50.6, 50.2, 50.5, 50.9, 51.3, 51.0, 50.6, 50.4, 50.7, 51.1, 50.9, 50.6, 50.5, 50.8,
|
||
51.0, 50.8, 50.7]
|
||
bv = [34, 33, 35, 31, 30, 32, 29, 28, 30, 27, 26, 27, 25, 26, 24, 25, 23, 24, 22, 23, 22]
|
||
cds = from_closes(a_cl + b_nu, wicks=aw + [(0.2, 0.2)] * 21, vols=av + bv, start=50.2)
|
||
pv = pivots(cds[A0:B0])
|
||
b = cds[B0:]
|
||
wa, wb = R1Ai - S1Ai, pv["R1"] - pv["S1"]
|
||
assert abs(wb - (pv["H"] - pv["L"])) < 1e-9, "R1 - S1 = H - L al lunii anterioare"
|
||
assert wb / wa < 0.35, "proiecția B nu e inside față de A"
|
||
assert max(c.h for c in b) <= pv["R1"] and min(c.l for c in b) >= pv["S1"], "luna B trebuie să rămână între S1 și R1"
|
||
INS = pv
|
||
print(f"D5 A: H {f2(pv['H'])} L {f2(pv['L'])} C {f2(pv['C'])} -> P {f2(pv['P'])} R1 {f2(pv['R1'])} S1 {f2(pv['S1'])}; "
|
||
f"lățime A {wa:.2f} B {wb:.2f} ({wb / wa:.0%})")
|
||
ch = PChart(candles=cds,
|
||
segs=[Seg(A0, B0 - 1, R1Ai, f"R1 A {R1Ai:.2f}", BLUE, "lm"),
|
||
Seg(A0, B0 - 1, PAi, f"pivot A {PAi:.2f}", ORANGE, "lm"),
|
||
Seg(A0, B0 - 1, S1Ai, f"S1 A {S1Ai:.2f}", RED, "lm"),
|
||
Seg(B0, N - 1, pv["R1"], f"R1 B {f2(pv['R1'])}", BLUE),
|
||
Seg(B0, N - 1, pv["S1"], f"S1 B {f2(pv['S1'])}", RED)],
|
||
seps=[B0],
|
||
marks=[Mark(10, "luna A: range îngust", GRAY, "above", dy=-24),
|
||
Mark(B0 + 10, "luna B: compresie", GRAY, "below", dy=30)],
|
||
scale=22, vol_h=70, title="Exemplu construit (fără ticker) — daily, pivot monthly",
|
||
caption=f"R1 − S1 = H − L al lunii trecute: {wb:.2f} față de {wa:.2f}. Proiecție inside, volum în scădere.",
|
||
**STYLE)
|
||
svgs["inside_nu"] = ch.render()
|
||
|
||
# 6. DA: din compresie, breakout cu volum până la rezistența lunii trecute
|
||
b_da = [50.5, 51.0, 51.5, 51.1, 50.6, 50.2, 50.7, 51.3, 51.4, 51.3, 51.6, 52.3, 53.0, 53.5, 53.2, 53.9, 54.4, 54.1,
|
||
54.7, 55.2, 55.0]
|
||
bw = [(0.2, 0.2)] * 7 + [(0.15, 0.15)] * 4 + [(0.1, 0.2)] + [(0.3, 0.3)] * 9
|
||
bv = [34, 33, 35, 31, 30, 32, 29, 28, 27, 26, 30, 92, 70, 64, 48, 60, 58, 44, 55, 57, 45]
|
||
cds = from_closes(a_cl + b_da, wicks=aw + bw, vols=av + bv, start=50.2)
|
||
pv = pivots(cds[A0:B0])
|
||
BK = B0 + 11
|
||
assert abs(pv["P"] - INS["P"]) < 1e-9
|
||
assert all(c.c <= pv["R1"] for c in cds[B0:BK]) and cds[BK].c > pv["R1"], "bara 32 e prima închidere peste R1 B"
|
||
assert bv[11] >= 2.5 * sum(bv[1:11]) / 10, "breakout-ul trebuie să aibă volum clar peste media compresiei"
|
||
entry = cds[BK].c
|
||
hl = min(c.l for c in cds[BK - 3:BK])
|
||
sl = round(hl - 0.10, 2)
|
||
risk = entry - sl
|
||
tp = R1Ai
|
||
R = (tp - entry) / risk
|
||
hit = next(i for i in range(BK + 1, N) if cds[i].h >= tp)
|
||
assert R >= 2, R
|
||
|
||
assert min(c.l for c in cds[BK + 1:]) > sl
|
||
assert hit - B0 + 1 == 19 and BK - B0 + 1 == 12, "textul: breakout ziua 12, TP ziua 19"
|
||
assert all(abs(cds[i].l - 51.15) < 0.01 for i in range(BK - 3, BK)), "textul: ultimele trei low-uri la 51.15"
|
||
print(f"D6 breakout close {f2(entry)} vol {bv[11]} vs medie {sum(bv[1:11]) / 10:.0f}; HL {f2(hl)} SL {f2(sl)} "
|
||
f"risc {f2(risk)} TP {f2(tp)} = {R:.2f}R atins la {hit}")
|
||
INST = dict(entry=entry, sl=sl, tp=tp, R=R, risk=risk, vol=bv[11], vavg=sum(bv[1:11]) / 10)
|
||
ch = PChart(candles=cds,
|
||
segs=[Seg(A0, B0 - 1, PAi, f"pivot A {PAi:.2f}", ORANGE, "lm"),
|
||
Seg(A0, B0 - 1, S1Ai, f"S1 A {S1Ai:.2f}", RED, "lm"),
|
||
Seg(B0, N - 1, pv["R1"], f"R1 B {f2(pv['R1'])}", PURPLE),
|
||
Seg(B0, N - 1, pv["S1"], f"S1 B {f2(pv['S1'])}", RED)],
|
||
seps=[B0],
|
||
levels=[Level(sl, f"SL {f2(sl)}", RED, label_at="left"),
|
||
Level(entry, f"intrare {f2(entry)}", GREEN, label_at="left"),
|
||
Level(tp, f"TP = R1 lunii trecute {f2(tp)} = {R:.1f}R", BLUE, label_at="left")],
|
||
marks=[Mark(BK, "breakout cu volum", GREEN, "above", dy=-30)],
|
||
scale=22, vol_h=70, title="Exemplu construit (fără ticker) — daily, pivot monthly",
|
||
caption="Aceeași compresie; volumul de 3x pe breakout o scoate până la rezistența lunii trecute.",
|
||
**STYLE)
|
||
svg_ = ch.render()
|
||
assert abs(px_ratio(ch, sl, entry, tp) - R) < 0.01
|
||
svgs["inside_da"] = svg_
|
||
|
||
# ================================================================ 7. reject deasupra pivotului (suport) — long
|
||
PIV = 50.00
|
||
cl7 = [55.8, 55.2, 55.5, 54.7, 54.1, 54.4, 53.6, 53.0, 53.2, 52.4, 51.8, 51.1, # 0-11 pullback spre pivot
|
||
50.5, 50.3, 50.7, 50.4, 50.6, # 12-16 rejecturi pe pivot
|
||
50.65, # 17 doji
|
||
51.6, # 18 buy doji
|
||
52.3, 52.0, 52.8, 53.5, 53.2, 54.0, 54.6, 54.3, 55.0, 55.6, 55.3, 56.0] # 19-30
|
||
w7 = [(0.3, 0.3)] * 12 + [(0.2, 0.7), (0.2, 0.6), (0.2, 0.8), (0.2, 0.5), (0.2, 0.6), (0.5, 0.5), (0.2, 0.2)] \
|
||
+ [(0.3, 0.3)] * 12
|
||
cds = from_closes(cl7, wicks=w7, start=56.2)
|
||
DJ, BD = 17, 18
|
||
rej = range(12, 17)
|
||
assert all(cds[i].l < PIV and cds[i].c > PIV for i in rej), "fiecare reject: codiță sub pivot, închidere peste"
|
||
assert abs(cds[DJ].o - cds[DJ].c) <= 0.1 and cds[BD].c > cds[DJ].h, "buy doji după doji"
|
||
entry = cds[BD].c
|
||
sl = round(min(cds[i].l for i in range(12, BD + 1)) - 0.10, 2)
|
||
risk = entry - sl
|
||
tp = round(entry + 2 * risk, 2)
|
||
hit = next(i for i in range(BD + 1, len(cds)) if cds[i].h >= tp)
|
||
assert min(c.l for c in cds[BD + 1:]) > PIV
|
||
assert hit - BD == 12, "textul: TP după 12 săptămâni"
|
||
print(f"D7 rejecturi lows {[f2(cds[i].l) for i in rej]}; entry {f2(entry)} SL {f2(sl)} risc {f2(risk)} TP {f2(tp)} atins la {hit}")
|
||
REJL = dict(entry=entry, sl=sl, tp=tp, risk=risk, low=min(cds[i].l for i in rej))
|
||
ch = PChart(candles=cds,
|
||
levels=[Level(PIV, f"pivot quarterly {PIV:.2f}", ORANGE, label_at="right"),
|
||
Level(sl, f"SL {f2(sl)}", RED, label_at="left"),
|
||
Level(entry, f"intrare {f2(entry)}", GREEN, label_at="left"),
|
||
Level(tp, f"TP {f2(tp)} = 2R", BLUE, label_at="left")],
|
||
marks=[Mark(4, "pullback", GRAY, "below", dy=8),
|
||
Mark(14, "5 rejecturi deasupra pivotului", ORANGE, "below", dy=18),
|
||
Mark(BD, "buy doji", GREEN, "above", dy=-4),
|
||
Mark(hit, "TP", BLUE, "above")],
|
||
scale=22, pad_bot_px=14, title="Exemplu construit (fără ticker) — weekly, pivot quarterly",
|
||
caption="Codițele coboară sub pivot, close-urile rămân deasupra: pivotul e apărat.", **STYLE)
|
||
svg_ = ch.render()
|
||
assert abs(px_ratio(ch, sl, entry, tp) - 2.0) < 0.01
|
||
svgs["reject_deasupra"] = svg_
|
||
|
||
# ================================================================ 8. reject sub pivot -> cumperi mai jos, în suportul quarterly
|
||
PM, PQ = 52.00, 48.00 # pivot monthly, pivot quarterly
|
||
cl8 = [49.4, 49.9, 50.6, 51.2, 51.7, # 0-4 urcare spre pivotul monthly
|
||
51.5, 51.8, 51.4, 51.6, # 5-8 rejecturi sub pivot
|
||
50.9, 50.3, 50.6, 49.8, 49.2, 49.5, 48.4, # 9-15 cădere
|
||
48.5, 48.55, # 16 hammer, 17 doji în pivotul quarterly
|
||
49.1, # 18 buy doji
|
||
49.6, 49.4, 50.2, 50.8, 50.5, 51.3, 51.9, 51.6, 52.3, 52.6, 52.4, 53.0] # 19-30
|
||
w8 = [(0.3, 0.3)] * 5 + [(0.6, 0.2), (0.5, 0.2), (0.7, 0.2), (0.5, 0.2)] + [(0.3, 0.3)] * 7 \
|
||
+ [(0.1, 0.4), (0.35, 0.3), (0.2, 0.2)] + [(0.3, 0.3)] * 12
|
||
v8 = [40, 42, 45, 44, 41, 38, 36, 40, 35, 50, 55, 42, 58, 60, 44, 62, 70, 48, 66, 52, 40, 55, 58, 42, 54, 57, 40, 52, 50, 41, 49]
|
||
cds = from_closes(cl8, wicks=w8, vols=v8, start=49.0)
|
||
HM, DJ, BD = 16, 17, 18
|
||
assert all(cds[i].h > PM and cds[i].c < PM for i in range(5, 9)), "rejecturi: codiță peste pivotul monthly, close sub"
|
||
assert abs(cds[HM].l - PQ) <= 0.1 and cds[HM].c > PQ, "hammer-ul atinge pivotul quarterly și închide peste"
|
||
assert abs(cds[DJ].o - cds[DJ].c) <= 0.1 and cds[BD].c > cds[DJ].h
|
||
entry = cds[BD].c
|
||
sl = round(min(c.l for c in cds[HM:BD + 1]) - 0.20, 2)
|
||
risk = entry - sl
|
||
tp1 = PM
|
||
R1m = (tp1 - entry) / risk
|
||
tp2 = round(entry + 3 * risk, 2)
|
||
hit1 = next(i for i in range(BD + 1, len(cds)) if cds[i].h >= tp1)
|
||
hit2 = next(i for i in range(BD + 1, len(cds)) if cds[i].h >= tp2)
|
||
assert R1m >= 2, R1m
|
||
assert min(c.l for c in cds[BD + 1:]) > sl
|
||
assert v8[HM] > max(v8[9:HM]), "textul: hammer cu cel mai mare volum de la cădere încoace"
|
||
print(f"D8 rejecturi highs {[f2(cds[i].h) for i in range(5, 9)]}; hammer low {f2(cds[HM].l)}; entry {f2(entry)} SL {f2(sl)} "
|
||
f"risc {f2(risk)} TP1 {f2(tp1)} = {R1m:.2f}R la {hit1}; TP2 {f2(tp2)} = 3R la {hit2}")
|
||
REJS = dict(entry=entry, sl=sl, tp1=tp1, R1=R1m, tp2=tp2, risk=risk, hm=cds[HM].l)
|
||
ch = PChart(candles=cds,
|
||
levels=[Level(tp2, f"TP2 {f2(tp2)} = 3R", BLUE, label_at="left"),
|
||
Level(PM, f"pivot monthly {PM:.2f} = TP1 ({R1m:.1f}R)", ORANGE, label_at="left"),
|
||
Level(entry, f"intrare {f2(entry)}", GREEN, label_at="right"),
|
||
Level(PQ, f"pivot quarterly {PQ:.2f}", PURPLE, label_at="right"),
|
||
Level(sl, f"SL {f2(sl)}", RED, label_at="right")],
|
||
marks=[Mark(6, "reject sub pivot: NU", RED, "above", dy=-16),
|
||
Mark(HM, "hammer", PURPLE, "below", dy=8),
|
||
Mark(BD, "buy doji", GREEN, "above", dy=-6)],
|
||
scale=26, pad_bot_px=12, vol_h=70, title="Exemplu construit (fără ticker) — daily",
|
||
caption="Nu cumperi rejectul de sub pivotul monthly. Cumperi mai jos, în pivotul quarterly.", **STYLE)
|
||
svg_ = ch.render()
|
||
assert abs(px_ratio(ch, sl, entry, tp2) - 3.0) < 0.01
|
||
svgs["reject_sub"] = svg_
|
||
|
||
# ================================================================ 9. respins de 5 ori sub pivot, apoi închidere deasupra cu doji
|
||
PQ9 = 50.00
|
||
cl9 = [47.2, 46.6, 46.9, 46.2, 45.6, 45.9, 45.3, # 0-6 downtrend
|
||
45.8, 46.6, 47.4, 48.3, 49.1, # 7-11 revenire spre pivot
|
||
49.6, 49.3, 49.7, 49.4, 49.8, 49.5, # 12-17 rejecturi sub pivot (5 cu codiță peste)
|
||
50.35, 50.40, # 18 close peste, 19 doji peste
|
||
51.2, # 20 buy doji
|
||
51.0, 51.7, 52.3, 52.0, 52.8, 53.4, 53.1, 53.9, 54.4, 54.1, 54.8] # 21-31
|
||
w9 = [(0.3, 0.3)] * 12 + [(0.6, 0.2), (0.3, 0.2), (0.6, 0.2), (0.45, 0.2), (0.6, 0.2), (0.3, 0.2)] \
|
||
+ [(0.2, 0.3), (0.35, 0.3), (0.2, 0.2)] + [(0.3, 0.3)] * 11
|
||
cds = from_closes(cl9, wicks=w9, start=47.6)
|
||
rj = [i for i in range(12, 18) if cds[i].h > PQ9 and cds[i].c < PQ9]
|
||
assert len(rj) == 5, rj
|
||
assert all(c.c < PQ9 for c in cds[:18]), "până la bara 18 niciun close peste pivot"
|
||
X, DJ, BD = 18, 19, 20
|
||
assert cds[X].c > PQ9 and cds[DJ].c > PQ9 and abs(cds[DJ].o - cds[DJ].c) <= 0.1 and cds[BD].c > cds[DJ].h
|
||
entry = cds[BD].c
|
||
sl = round(min(cds[DJ].l, PQ9) - 0.25, 2) # sub doji și sub pivot
|
||
risk = entry - sl
|
||
tp = round(entry + 2 * risk, 2)
|
||
hit = next(i for i in range(BD + 1, len(cds)) if cds[i].h >= tp)
|
||
assert min(c.l for c in cds[BD + 1:]) > PQ9
|
||
assert hit - BD == 8, "textul: TP după 8 săptămâni"
|
||
print(f"D9 rejecturi la {rj}; close peste {f2(cds[X].c)}, doji {f2(cds[DJ].c)}; entry {f2(entry)} SL {f2(sl)} "
|
||
f"risc {f2(risk)} TP {f2(tp)} atins la {hit}")
|
||
BRK = dict(entry=entry, sl=sl, tp=tp, risk=risk)
|
||
ch = PChart(candles=cds,
|
||
levels=[Level(PQ9, f"pivot quarterly {PQ9:.2f}", ORANGE, label_at="left"),
|
||
Level(sl, f"SL {f2(sl)}", RED, label_at="right"),
|
||
Level(entry, f"intrare {f2(entry)}", GREEN, label_at="right"),
|
||
Level(tp, f"TP {f2(tp)} = 2R", BLUE, label_at="left")],
|
||
marks=[Mark(3, "downtrend", GRAY, "above", dy=-10),
|
||
Mark(14, "5 rejecturi sub pivot", RED, "above", dy=-40),
|
||
Mark(DJ, "doji peste", "#333", "below", dy=4),
|
||
Mark(BD, "buy doji", GREEN, "above", dy=-4)],
|
||
scale=24, title="Exemplu construit (fără ticker) — weekly, pivot quarterly",
|
||
caption="Cinci respingeri, apoi două close-uri peste pivot și buy doji: acum pivotul e suport.", **STYLE)
|
||
svg_ = ch.render()
|
||
assert abs(px_ratio(ch, sl, entry, tp) - 2.0) < 0.01
|
||
svgs["reject_apoi_peste"] = svg_
|
||
|
||
# ================================================================ 10-11. scărița pe pivoți (weekly, pivot quarterly)
|
||
Q = 13
|
||
PQ1 = 44.00
|
||
q1 = [45.6, 45.2, 45.8, 46.5, 47.2, 48.0, 48.6, 48.2, 47.6, 47.9, 48.8, 49.4, 49.0]
|
||
q2 = [48.7, 48.4, 48.9, 49.6, 50.3, 51.0, 51.5, 51.0, 50.4, 50.1, 50.6, 51.3, 51.1]
|
||
q3_da = [50.8, 50.7, 51.1, 51.7, 52.3, 51.9, 52.6, 53.3, 53.0, 53.8, 54.4, 54.1, 54.9]
|
||
q3_nu = [50.8, 50.7, 51.1, 51.4, 51.2, 50.6, 50.1, 49.6, 49.9, 49.2, 48.7, 49.0, 48.4]
|
||
wq = [(0.3, 0.3)] * 39
|
||
wq[14] = (0.3, 0.3) # low-ul trimestrului 2
|
||
wq[22] = (0.3, 0.4) # higher low în trimestrul 2
|
||
wq[27] = (0.3, 0.3) # higher low în trimestrul 3
|
||
|
||
|
||
def stairs(q3):
|
||
cds = from_closes(q1 + q2 + q3, wicks=wq, start=46.0)
|
||
p2 = pivots(cds[0:Q])
|
||
p3 = pivots(cds[Q:2 * Q])
|
||
return cds, p2, p3
|
||
|
||
|
||
cds, p2, p3 = stairs(q3_da)
|
||
lows = [min(range(0, Q), key=lambda i: cds[i].l),
|
||
min(range(Q, 18), key=lambda i: cds[i].l),
|
||
min(range(20, 2 * Q), key=lambda i: cds[i].l),
|
||
min(range(2 * Q, 30), key=lambda i: cds[i].l)]
|
||
hh = [max(range(0, Q), key=lambda i: cds[i].h), max(range(Q, 2 * Q), key=lambda i: cds[i].h)]
|
||
assert PQ1 < p2["P"] < p3["P"], "pivoții quarterly nu urcă în trepte"
|
||
assert all(cds[lows[k]].l < cds[lows[k + 1]].l for k in range(3)), "swing low-urile nu urcă"
|
||
assert cds[lows[1]].l > p2["P"] and cds[lows[2]].l > p2["P"] and cds[lows[3]].l > p3["P"] - 0.05, "HL sub pivotul trimestrului"
|
||
assert cds[lows[3]].l < p3["P"] + 0.5
|
||
brk = next(i for i in range(2 * Q, 39) if cds[i].c > cds[hh[1]].h)
|
||
assert cds[hh[1]].h > cds[hh[0]].h
|
||
print(f"D10 pivoti Q1 {PQ1} Q2 {f2(p2['P'])} Q3 {f2(p3['P'])}; lows {[f2(cds[i].l) for i in lows]} la {lows}; "
|
||
f"high Q1 {f2(cds[hh[0]].h)} Q2 {f2(cds[hh[1]].h)}; breakout la {brk} close {f2(cds[brk].c)}")
|
||
STAIR = dict(p2=p2, p3=p3, lows=[cds[i].l for i in lows], hq1=cds[hh[0]].h, hq2=cds[hh[1]].h, brk=cds[brk].c)
|
||
ch = PChart(candles=cds,
|
||
segs=[Seg(0, Q - 1, PQ1, f"pivot Q1 {PQ1:.2f}", ORANGE, "lm"),
|
||
Seg(Q, 2 * Q - 1, p2["P"], f"pivot Q2 {f2(p2['P'])}", ORANGE, "left", 14),
|
||
Seg(2 * Q, 38, p3["P"], f"pivot Q3 {f2(p3['P'])}", ORANGE)],
|
||
seps=[Q, 2 * Q],
|
||
marks=[Mark(lows[0], "low", RED, "below", dy=8),
|
||
Mark(lows[1], "HL", GREEN, "below", dy=22),
|
||
Mark(lows[2], "HL", GREEN, "below", dy=30),
|
||
Mark(lows[3], "HL", GREEN, "below", dy=14),
|
||
Mark(hh[1], "vârf Q2", BLUE, "above"),
|
||
Mark(brk, "breakout", GREEN, "above", dy=-4)],
|
||
mas=[MA(20, GRAY, dash="4 3", text="SMA-20")],
|
||
scale=18, title="Exemplu construit (fără ticker) — weekly, pivot quarterly",
|
||
caption="Treaptă cu treaptă: pivotul fiecărui trimestru mai sus, fiecare higher low deasupra lui.", **STYLE)
|
||
svgs["scarita_da"] = ch.render()
|
||
|
||
cds, p2n, p3n = stairs(q3_nu)
|
||
assert abs(p3n["P"] - p3["P"]) < 1e-9, "Q1 și Q2 identice cu diagrama DA"
|
||
assert max(c.h for c in cds[2 * Q:]) < STAIR["hq2"], "breakout-ul peste vârful Q2 nu vine"
|
||
hl2 = STAIR["lows"][2]
|
||
ll = next(i for i in range(2 * Q, 39) if cds[i].c < hl2)
|
||
top3 = max(range(2 * Q, 39), key=lambda i: cds[i].h)
|
||
assert cds[ll].c < p3n["P"]
|
||
under = next(i for i in range(2 * Q, 39) if cds[i].c < p3n["P"])
|
||
print(f"D11 high Q3 {f2(max(c.h for c in cds[2 * Q:]))} < vârf Q2 {f2(STAIR['hq2'])}; close sub pivot Q3 la {under} "
|
||
f"({f2(cds[under].c)}); close sub HL {f2(hl2)} la {ll} ({f2(cds[ll].c)})")
|
||
STAIRN = dict(h3=max(c.h for c in cds[2 * Q:]), under=cds[under].c, ll=cds[ll].c, hl2=hl2)
|
||
ch = PChart(candles=cds,
|
||
segs=[Seg(0, Q - 1, PQ1, f"pivot Q1 {PQ1:.2f}", ORANGE, "lm"),
|
||
Seg(Q, 2 * Q - 1, p2n["P"], f"pivot Q2 {f2(p2n['P'])}", ORANGE, "left", 14),
|
||
Seg(2 * Q, 38, p3n["P"], f"pivot Q3 {f2(p3n['P'])}", ORANGE)],
|
||
seps=[Q, 2 * Q],
|
||
levels=[Level(STAIR["hq2"], f"vârf Q2 {f2(STAIR['hq2'])}", BLUE, label_at="left"),
|
||
Level(hl2, f"HL din Q2 {f2(hl2)}", RED, label_at="left")],
|
||
marks=[Mark(top3, "fără breakout", GRAY, "above", dy=-4),
|
||
Mark(ll, "lower low", RED, "below", dy=4)],
|
||
mas=[MA(20, GRAY, dash="4 3", text="SMA-20")],
|
||
scale=18, title="Exemplu construit (fără ticker) — weekly, pivot quarterly",
|
||
caption="Pivotul Q3 a urcat, dar prețul nu face vârf nou și închide sub HL-ul din Q2: scărița s-a rupt.",
|
||
**STYLE)
|
||
svgs["scarita_nu"] = 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)
|