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

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

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

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

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

531 lines
30 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Diagramele pentru summaries/08_ANALIZA_ZILNICA/08_executie_si_risc.md.
Toate prețurile sunt construite (fără ticker). Rulează:
.venv/bin/python gen/az_executie.py
Înlocuiește în .md fiecare bloc <!--SVG:nume--> ... <!--/SVG--> cu output-ul
generatorului (inclusiv tabelul eșantionului, ca totalul să vină din calcul).
`PChart` e copiat din gen/az_pivoti.py: etichetele nivelurilor stau în marginea
dreaptă și se împing singure în jos când sunt apropiate. Adăugat aici: `hist`,
close-uri dinaintea primei bare desenate, ca SMA-200 să fie calculată pe 200 de
bare reale, nu pe cele 45 vizibile.
"""
import math
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/08_executie_si_risc.md")
GRAY, RED, GREEN, BLUE, ORANGE, PURPLE = "#777", "#c00", "#188a3e", "#1a5fb4", "#b06000", "#6a3d9a"
STYLE = dict(gap=15, body_w=9, left=44, width=620)
RISC = 25.0
svgs = {}
# `Seg` si `spread` vin din pchart.py. `PChart` NU: varianta de aici calculeaza
# mediile dintr-un istoric de close-uri dinainte de bara 0 (`hist`), ca SMA-20 sa
# fie deja incalzita la prima bara desenata, si si le deseneaza singura. Clasa
# comuna nu are nevoie de asta, iar unirea lor ar risca cele 10 diagrame de aici.
from pchart import Seg, spread # noqa: E402
@dataclass
class PChart(Chart):
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
hist: list = field(default_factory=list) # close-uri înainte de bara 0, doar pentru medii
def ma_vals(self, period):
full = self.hist + [c.c for c in self.candles]
h = len(self.hist)
return [sum(full[h + i + 1 - period:h + i + 1]) / period if h + i + 1 >= period else None
for i in range(len(self.candles))]
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))
ma_prices = [v for m in saved_ma for v in self.ma_vals(m.period) if v is not None]
top_price = max([c.h for c in self.candles] + [s.price for s in segs] + ma_prices)
self.levels = [Level(s.price, "", "none") for s in segs] + [Level(p, "", "none") for p in ma_prices]
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 = [] # mediile le desenăm noi, cu `hist`
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"/>')
for m in saved_ma:
pts = [f"{self._cx(i):.1f},{self._y(v):.1f}" for i, v in enumerate(self.ma_vals(m.period)) if v is not None]
dash = f' stroke-dasharray="{m.dash}"' if m.dash else ""
extra.append(f'<polyline points="{" ".join(pts)}" fill="none" stroke="{m.color}" stroke-width="1.5"{dash}/>')
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" and s.text]
ma_lab = [(self._y(self.ma_vals(m.period)[-1]) + 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 + ma_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 not s.text:
continue
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 _, s in ma_lab:
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 f2(x):
return f"{x:.2f}"
def rpx(ch, entry, sl, tp):
"""câți R e `tp`, măsurat în pixeli pe desen"""
return (ch._y(entry) - ch._y(tp)) / (ch._y(sl) - ch._y(entry))
def first(cds, a, cond):
return next(i for i in range(a, len(cds)) if cond(cds[i]))
def shares(risk_per_share):
return math.floor(RISC / risk_per_share + 1e-9)
# ================================================================ 1. risc fix 25 $ la 1:4 — long
cl1 = [101.55, 101.40, 101.48, 101.25, 101.05, 101.12, 100.90, 100.72, 100.80, 100.62, 100.50, 100.46, 100.52,
100.47, 100.60, # 14 trigger
100.70, 100.62, 100.78, 100.90, 100.84, 101.00, 101.12, 101.05, 101.22, 101.35, 101.28, 101.45, 101.58,
101.66, 101.55, 101.60, 101.48, 101.52, 101.40]
cds = from_closes(cl1, wick=0.06, start=101.60)
T = 14
entry = cds[T].c
sl = round(entry - 0.25, 2)
tp = round(entry + 4 * (entry - sl), 2)
n1 = shares(entry - sl)
assert sl < min(c.l for c in cds[T - 3:T + 1]), "stopul trebuie să stea sub low-urile bazei"
assert n1 == 100 and abs(n1 * (entry - sl) - 25) < 1e-6 and abs(n1 * (tp - entry) - 100) < 1e-6
hit = first(cds, T + 1, lambda c: c.h >= tp)
assert min(c.l for c in cds[T + 1:hit + 1]) > sl, "stopul nu trebuie atins înainte de țintă"
top0 = max(range(T), key=lambda i: cds[i].h)
assert abs(cds[top0].h - tp) < 0.15, "ținta 4R trebuie să cadă lângă vârful de unde a plecat pullback-ul"
print(f"D1 entry {f2(entry)} SL {f2(sl)} TP {f2(tp)} {n1} acțiuni; vârf anterior {f2(cds[top0].h)}; TP atins la {hit}")
L1 = dict(entry=entry, sl=sl, tp=tp, n=n1, hit=hit - T, top=cds[top0].h)
ch = PChart(candles=cds,
segs=[Seg(T, len(cds) - 1, tp, f"TP {f2(tp)} = 4R = +100 $", BLUE, dash="4 3"),
Seg(T, len(cds) - 1, entry, f"intrare {f2(entry)} ({n1} acțiuni)", GREEN),
Seg(T, len(cds) - 1, sl, f"SL {f2(sl)} = 1R = −25 $", RED)],
marks=[Mark(3, "pullback", GRAY, "below", dy=30),
Mark(T, "trigger", GREEN, "below", dy=4),
Mark(hit, "4R", BLUE, "above", dy=-2)],
scale=130, title="Exemplu construit (fără ticker) — 5 min",
caption=f"Risc 0.25 $/acțiune × {n1} acțiuni = 25 $. Ținta e de 4 ori mai departe decât stopul.", **STYLE)
svg_ = ch.render()
assert abs(rpx(ch, entry, sl, tp) - 4.0) < 0.01, "4R nu e desenat la 4x stopul"
svgs["risc_long"] = svg_
# ================================================================ 2. aceeași aritmetică pe short
cl2 = [180.60 - c for c in cl1]
cds = from_closes(cl2, wick=0.06, start=180.60 - 101.60)
entry = cds[T].c
sl = round(entry + 0.25, 2)
tp = round(entry - 4 * (sl - entry), 2)
n2 = shares(sl - entry)
assert sl > max(c.h for c in cds[T - 3:T + 1]) and n2 == 100
hit = first(cds, T + 1, lambda c: c.l <= tp)
assert max(c.h for c in cds[T + 1:hit + 1]) < sl
print(f"D2 short entry {f2(entry)} SL {f2(sl)} TP {f2(tp)} atins la {hit}")
S2 = dict(entry=entry, sl=sl, tp=tp)
ch = PChart(candles=cds,
segs=[Seg(T, len(cds) - 1, sl, f"SL {f2(sl)} = 1R = −25 $", RED),
Seg(T, len(cds) - 1, entry, f"intrare short {f2(entry)} ({n2} acțiuni)", GREEN),
Seg(T, len(cds) - 1, tp, f"TP {f2(tp)} = 4R = +100 $", BLUE, dash="4 3")],
marks=[Mark(3, "raliu", GRAY, "above", dy=-30),
Mark(T, "trigger", RED, "above", dy=-4),
Mark(hit, "4R", BLUE, "below", dy=2)],
scale=130, title="Exemplu construit (fără ticker) — 5 min",
caption="Short: stopul deasupra, ținta dedesubt, tot 0.25 $ contra 1.00 $.", **STYLE)
svg_ = ch.render()
assert abs(rpx(ch, entry, sl, tp) - 4.0) < 0.01
svgs["risc_short"] = svg_
# ================================================================ 3-4. break-even: DA și NU
base = [25.90, 25.78, 25.82, 25.66, 25.55, 25.60, 25.46, 25.35, 25.40, 25.28, 25.24, 25.26, 25.22, 25.28,
25.40, # 14 trigger
25.48, 25.58, 25.70, 25.76, 25.68] # 17: +1R -> stop la break-even
be_da = [25.60, 25.55, 25.66, 25.80, 25.74, 25.92, 26.05, 25.98, 26.15, 26.28, 26.22, 26.36, 26.45, 26.38, 26.42]
be_nu = [25.58, 25.43, 25.52, 25.66, 25.80, 25.74, 25.92, 26.05, 25.98, 26.15, 26.28, 26.22, 26.36, 26.45, 26.38]
def be_chart(tail, name, nu):
cds = from_closes(base + tail, wick=0.04, start=26.00)
entry = cds[T].c
sl = round(entry - 0.25, 2)
r1 = round(entry + 0.25, 2)
tp = round(entry + 1.00, 2)
assert sl < min(c.l for c in cds[T - 3:T + 1])
mv = first(cds, T + 1, lambda c: c.h >= r1)
assert mv == 17, mv
hit = first(cds, mv + 1, lambda c: c.h >= tp)
out = next((i for i in range(mv + 1, hit) if cds[i].l <= entry), None)
assert min(c.l for c in cds[T + 1:]) > sl, "stopul inițial nu e atins niciodată"
assert (out is not None) == nu, "NU: scos la break-even înainte de țintă; DA: nu"
n = len(cds)
end = out if nu else hit
segs = [Seg(T, n - 1, tp, f"TP {f2(tp)} = 4R", BLUE, dash="4 3"),
Seg(T, mv, sl, f"SL inițial {f2(sl)} = −1R", RED),
Seg(mv, end, entry, f"SL mutat la intrare {f2(entry)} = 0", ORANGE),
Seg(T, mv, entry, "", GREEN),
Seg(mv, n - 1, r1, f"+1R {f2(r1)}: aici muți stopul", GRAY, dash="2 3")]
marks = [Mark(T, "intrare", GREEN, "below", dy=4), Mark(mv, "+1R", GRAY, "above", dy=-2)]
if nu:
marks += [Mark(out, "scos la 0", ORANGE, "below", dy=4), Mark(hit, "4R, fără tine", BLUE, "above", dy=-6)]
cap = "Codița coboară până la intrare (25.39): ieși pe zero, apoi prețul merge la țintă."
else:
marks += [Mark(hit, "4R", BLUE, "above", dy=-2)]
cap = "Pullback-ul se oprește deasupra intrării: stopul la zero nu se atinge, ținta da."
ch = PChart(candles=cds, segs=segs, marks=marks, scale=150,
title="Exemplu construit (fără ticker) — 5 min", caption=cap, **STYLE)
svg_ = ch.render()
assert abs(rpx(ch, entry, sl, tp) - 4.0) < 0.01 and abs(rpx(ch, entry, sl, r1) - 1.0) < 0.01
svgs[name] = svg_
low_pb = min(c.l for c in cds[mv + 1:hit])
print(f"D{'4' if nu else '3'} entry {f2(entry)} SL {f2(sl)} +1R {f2(r1)} la {mv} TP {f2(tp)} la {hit}; "
f"low pullback {f2(low_pb)}; scos la {out}")
return dict(entry=entry, sl=sl, r1=r1, tp=tp, low=low_pb)
BE_DA = be_chart(be_da, "be_da", False)
BE_NU = be_chart(be_nu, "be_nu", True)
# ================================================================ 5. trei poziții: TP1 / TP2 / trail
cl5 = [41.80, 41.50, 41.60, 41.10, 40.70, 40.85, 40.40, 40.10, 40.20, 39.80, 39.75, 39.70,
40.00, # 12 buy doji
40.20, 40.10, 40.35, 40.55, 40.45, 40.70, 40.90, 40.80, 41.05, 41.30, 41.20, 41.55, 41.85, 41.70,
42.05, 42.30, 42.10, 41.90, 41.80, 41.30, 41.10, 40.90, 41.00]
cds = from_closes(cl5, wick=0.10, start=42.00)
T = 12
entry = cds[T].c
sl = round(entry - 0.50, 2)
tp1, tp2 = round(entry + 0.50, 2), round(entry + 1.00, 2)
n5 = shares(entry - sl) // 3 * 3
per = n5 // 3
assert sl < min(c.l for c in cds[T - 3:T + 1]) and n5 == 48
h1 = first(cds, T + 1, lambda c: c.h >= tp1)
h2 = first(cds, h1 + 1, lambda c: c.h >= tp2)
assert min(c.l for c in cds[T + 1:]) > sl, "stopul inițial nu e atins"
# trail: linia = low-ul ultimei candele care a închis peste high-ul precedentei; urcă doar; ieși la close sub ea
trail, steps, ex = None, [], None
for i in range(h1 + 1, len(cds)):
if trail is not None and cds[i].c < trail:
ex = i
break
if cds[i].c > cds[i - 1].h and (trail is None or cds[i].l > trail):
trail = cds[i].l
steps.append((i, trail))
assert ex is not None and ex > h2
x_price = cds[ex].c
pnl = [per * (tp1 - entry), per * (tp2 - entry), per * (x_price - entry)]
R_tot = sum(pnl) / (n5 * (entry - sl))
print(f"D5 entry {f2(entry)} SL {f2(sl)} {n5}={per}x3; TP1 {f2(tp1)} la {h1}, TP2 {f2(tp2)} la {h2}; trail "
f"{[(i, f2(p)) for i, p in steps]}; ieșire {ex} la {f2(x_price)}; P&L {[f2(p) for p in pnl]} = {f2(sum(pnl))} "
f"({R_tot:.2f}R pe riscul de {f2(n5 * (entry - sl))})")
TP3 = dict(entry=entry, sl=sl, tp1=tp1, tp2=tp2, n=n5, per=per, x=x_price, pnl=pnl, R=R_tot, steps=steps,
risk=n5 * (entry - sl), h1=h1 - T, h2=h2 - T, ex=ex - T)
segs = [Seg(T, steps[0][0] - 1, sl, f"SL inițial {f2(sl)} (toate 3)", RED),
Seg(T, len(cds) - 1, entry, f"intrare {f2(entry)}", GREEN),
Seg(T, h1, tp1, f"TP1 {f2(tp1)} = 1R: poziția 1", BLUE, dash="4 3"),
Seg(T, h2, tp2, f"TP2 {f2(tp2)} = 2R: poziția 2", PURPLE, dash="4 3")]
for k, (i, p) in enumerate(steps):
i1 = steps[k + 1][0] - 1 if k + 1 < len(steps) else ex
segs.append(Seg(i, i1, p, f"trail final {f2(p)}: poziția 3" if k + 1 == len(steps) else "", ORANGE))
ch = PChart(candles=cds, segs=segs,
marks=[Mark(T, "buy doji", GREEN, "below", dy=4),
Mark(h1, "TP1", BLUE, "above", dy=-2),
Mark(h2, "TP2", PURPLE, "above", dy=-2),
Mark(ex, "ieșire: close sub trail", ORANGE, "above", dy=-40)],
scale=90, title="Exemplu construit (fără ticker) — daily",
caption=f"{n5} acțiuni = 3 × {per}. Fiecare poziție iese altfel; stopul comun e 0.50 $ sub intrare.", **STYLE)
svg_ = ch.render()
assert abs(rpx(ch, entry, sl, tp1) - 1.0) < 0.01 and abs(rpx(ch, entry, sl, tp2) - 2.0) < 0.01
assert abs(rpx(ch, entry, sl, steps[-1][1]) - (steps[-1][1] - entry) / (entry - sl)) < 0.01
svgs["trei_pozitii"] = svg_
# ================================================================ 6-7. ținta realistă 1/2 vs ținta lacomă
cl6 = [63.20, 64.00, 64.90, 65.80, 65.30, 64.60, 64.90, 64.00, 63.20, 63.50, 62.60, 61.80, 62.10, 61.20, 60.50,
60.35, 60.40,
60.70, # 17 trigger
60.90, 61.50, 61.30, 62.00, 62.60, 62.30, 63.00, 63.50, 63.90, 64.20, 63.70, 63.00, 62.40, 61.60, 61.00,
60.30, 59.80, 59.50, 59.90]
w6 = [(0.2, 0.2)] * 15 + [(0.15, 0.35), (0.25, 0.25)] + [(0.2, 0.2)] * 20
w6[3] = (0.2, 0.2)
cds = from_closes(cl6, wicks=w6, start=62.60)
T, SUP = 17, 60.00
entry = cds[T].c
top = max(range(T), key=lambda i: cds[i].h)
hi = cds[top].h
sl = round(min(c.l for c in cds[T - 3:T + 1]) - 0.10, 2)
risk = entry - sl
half = round(entry + (hi - entry) / 2, 2)
assert abs(min(c.l for c in cds[T - 3:T + 1]) - SUP) < 0.01, "low-ul bazei stă exact pe confluență"
hh = first(cds, T + 1, lambda c: c.h >= half)
peak = max(c.h for c in cds[T + 1:])
stop_i = first(cds, T + 1, lambda c: c.l <= sl)
assert hh < stop_i and peak < hi, "1/2 se atinge înainte de stop; vârful nu se atinge deloc"
Rh, Rg = (half - entry) / risk, (hi - entry) / risk
print(f"D6 vârf {f2(hi)} entry {f2(entry)} SL {f2(sl)} risc {f2(risk)} 1/2 {f2(half)} ({Rh:.2f}R) la {hh}; "
f"max după {f2(peak)}; stop la {stop_i}; lacom {Rg:.2f}R")
HALF = dict(hi=hi, entry=entry, sl=sl, risk=risk, half=half, Rh=Rh, Rg=Rg, peak=peak, hh=hh - T,
covered=(peak - entry) / (hi - entry))
n = len(cds)
common = [Seg(0, n - 1, SUP, "", PURPLE, dash="2 3"),
Seg(T, n - 1, entry, f"intrare {f2(entry)}", GREEN),
Seg(T, n - 1, sl, f"SL {f2(sl)} = −1R, sub confluența S2 + monthly {SUP:.2f} (mov)", RED)]
ch = PChart(candles=cds,
segs=common + [Seg(top, n - 1, hi, f"vârf anterior {f2(hi)}", GRAY, dash="4 3"),
Seg(T, n - 1, half, f"TP = 1/2 din distanță {f2(half)} = {Rh:.1f}R", BLUE, dash="4 3")],
marks=[Mark(top, "vârf", GRAY, "above", dy=-2), Mark(T, "intrare", GREEN, "below", dy=6),
Mark(hh, "TP atins", BLUE, "above", dy=-2)],
scale=26, title="Exemplu construit (fără ticker) — daily",
caption="DA: ținta e jumătate din drumul până la vârf. Atinsă în 8 zile, înainte de întoarcere.", **STYLE)
svg_ = ch.render()
assert abs(rpx(ch, entry, sl, half) - Rh) < 0.01
svgs["jumatate_da"] = svg_
ch = PChart(candles=cds,
segs=common + [Seg(top, n - 1, hi, f"TP lacom = vârful {f2(hi)} = {Rg:.1f}R", RED, dash="4 3")],
marks=[Mark(top, "vârf", GRAY, "above", dy=-2), Mark(T, "intrare", GREEN, "below", dy=6),
Mark(first(cds, T + 1, lambda c: c.h >= peak), "se întoarce", GRAY, "above", dy=-2),
Mark(stop_i, "stop: −1R", RED, "below", dy=4)],
scale=26, title="Exemplu construit (fără ticker) — daily",
caption="NU: aceeași serie, ținta la vârf. Prețul acoperă 70% din drum, se întoarce și ia stopul.", **STYLE)
svg_ = ch.render()
assert abs(rpx(ch, entry, sl, hi) - Rg) < 0.01
assert abs(HALF["covered"] - 0.70) < 0.02, HALF["covered"]
svgs["lacom_nu"] = svg_
# ================================================================ 8. reintrare după stop, cu același risc
cl8 = [30.60, 30.48, 30.52, 30.36, 30.22, 30.26, 30.10,
30.20, # 7 intrarea 1
30.14, 30.05, 29.92, 29.80, 29.70, 29.74,
29.84, # 14 intrarea 2
29.78, 29.70, 29.58, 29.46, 29.40, 29.44,
29.56, # 21 intrarea 3
29.62, 29.55, 29.72, 29.86, 29.80, 29.98, 30.12, 30.06, 30.24, 30.38, 30.32, 30.50, 30.60, 30.52, 30.58]
cds = from_closes(cl8, wick=0.03, start=30.70)
E = [(7, 0.25), (14, 0.20), (21, 0.25)]
trades = []
for k, (i, r) in enumerate(E):
e = cds[i].c
s = round(e - r, 2)
tp = round(e + 4 * r, 2)
ns = shares(r)
assert s < min(c.l for c in cds[i - 2:i + 1]), f"stopul intrării {k + 1} nu e sub structură"
st = next((j for j in range(i + 1, len(cds)) if cds[j].l <= s), 10 ** 6)
th = next((j for j in range(i + 1, len(cds)) if cds[j].h >= tp), 10 ** 6)
trades.append(dict(i=i, e=e, s=s, tp=tp, n=ns, stop=st if st < th else None, hit=th if th < st else None))
assert abs(ns * r - RISC) < 1e-6, "fiecare intrare riscă exact 25 $"
assert trades[0]["stop"] and trades[1]["stop"] and trades[2]["hit"], trades
assert trades[0]["stop"] < trades[1]["i"] and trades[1]["stop"] < trades[2]["i"]
net = -RISC - RISC + trades[2]["n"] * (trades[2]["tp"] - trades[2]["e"])
assert abs(net - 50) < 1e-6
print("D8", [(t["i"], f2(t["e"]), f2(t["s"]), t["n"], t["stop"], t["hit"]) for t in trades], "net", net)
REIN = dict(trades=trades, net=net)
t1, t2, t3 = trades
n = len(cds)
ch = PChart(candles=cds,
segs=[Seg(t3["i"], n - 1, t3["tp"], f"#3 TP {f2(t3['tp'])} = 4R = +100 $", BLUE, dash="4 3"),
Seg(t1["i"], t1["stop"], t1["e"], f"#1 intrare {f2(t1['e'])} ({t1['n']} acț.)", GREEN, "left"),
Seg(t1["i"], t1["stop"], t1["s"], f"#1 SL {f2(t1['s'])}: −25 $", RED, "lm"),
Seg(t2["i"], t2["stop"], t2["e"], f"#2 intrare {f2(t2['e'])} ({t2['n']} acț.)", GREEN, "left"),
Seg(t2["i"], t2["stop"], t2["s"], f"#2 SL {f2(t2['s'])}: −25 $", RED, "lm"),
Seg(t3["i"], t3["hit"], t3["e"], f"#3 intrare {f2(t3['e'])} ({t3['n']} acț.)", GREEN),
Seg(t3["i"], t3["hit"], t3["s"], f"#3 SL {f2(t3['s'])}", RED)],
marks=[Mark(t1["stop"], "stop", RED, "below", dy=4),
Mark(t2["stop"], "stop", RED, "below", dy=4),
Mark(t3["i"], "#3", GREEN, "above", dy=-4), Mark(t3["hit"], "4R", BLUE, "above", dy=-2)],
scale=170, title="Exemplu construit (fără ticker) — 5 min",
caption="Două stopuri de câte 25 $, a treia intrare cu același risc: −25 −25 +100 = +50 $.", **STYLE)
svg_ = ch.render()
for t in trades:
assert abs(rpx(ch, t["e"], t["s"], t["tp"]) - 4.0) < 0.01
svgs["reintrare"] = svg_
# ================================================================ 9. tranșe de 1/4 la acțiuni scumpe
cl9 = [450, 462, 468, 460, 448, 452, 438, 426, 430, 414, 400, 394, 398, 410, 422, 430, 436, 428, 420, 412,
406, # 20 higher low
418, # 21 confirmare = tranșa 1
426, 432, 428, 425, 420, 416, 424, 432, 438, 435, 444, 452, 448, 458, 466, 471, 465, 468]
cds = from_closes(cl9, wick=2.0, start=445)
HL, CF, SH = 20, 21, 23
prev_low = min(c.l for c in cds[:HL])
assert cds[HL].l > prev_low and cds[CF].c > cds[HL].h, "higher low confirmat de candela din dreapta"
top = max(range(HL), key=lambda i: cds[i].h)
sh = cds[SH].h
move = sh - cds[HL].l
lv = [(CF, cds[CF].c, "confirmare")]
for frac, name in [(1 / 3, "1/3"), (1 / 2, "1/2"), (2 / 3, "2/3")]:
p = round(sh - frac * move, 2)
i = first(cds, SH + 1, lambda c, p=p: c.l <= p)
lv.append((i, p, name))
assert [x[0] for x in lv] == [21, 25, 26, 27], lv
sl9 = cds[HL].l - 5
per9 = 2
avg = sum(p for _, p, _ in lv) / 4
risk_tr = sum(per9 * (p - sl9) for _, p, _ in lv)
risk_chase = 4 * per9 * (sh - sl9)
tgt = cds[top].h
hit = first(cds, 28, lambda c: c.h >= tgt)
assert min(c.l for c in cds[CF:]) > sl9 and hit > lv[-1][0]
prof_tr = 4 * per9 * (tgt - avg)
prof_ch = 4 * per9 * (tgt - sh)
print(f"D9 low anterior {prev_low} HL {cds[HL].l} vârf {sh} mișcare {move}; tranșe {lv}; SL {sl9} medie {avg} "
f"risc {risk_tr} vs {risk_chase}; țintă {tgt} la {hit}; profit {prof_tr} vs {prof_ch}")
TR = dict(lv=lv, sl=sl9, avg=avg, risk=risk_tr, chase=risk_chase, tgt=tgt, hl=cds[HL].l, sh=sh, move=move,
prev_low=prev_low, per=per9, prof=prof_tr, prof_ch=prof_ch)
n = len(cds)
ch = PChart(candles=cds,
segs=[Seg(top, n - 1, tgt, f"țintă: vârful anterior {tgt:.0f}", BLUE, dash="4 3"),
Seg(SH, n - 1, sh, f"vârful după HL {sh:.0f} (aici NU cumperi tot)", GRAY, dash="2 3")]
+ [Seg(lv[0][0], n - 1, lv[0][1], f"tranșele 1 și 3: câte {per9} acț. la {lv[0][1]:.0f} / {lv[2][1]:.0f}", GREEN),
Seg(lv[1][0], n - 1, lv[1][1], f"tranșa 2: {per9} acț. la {lv[1][1]:.0f} (1/3)", GREEN),
Seg(lv[2][0], n - 1, lv[2][1], "", GREEN),
Seg(lv[3][0], n - 1, lv[3][1], f"tranșa 4: {per9} acț. la {lv[3][1]:.0f} (2/3)", GREEN)]
+ [Seg(CF, n - 1, sl9, f"SL comun {sl9:.0f} (sub HL {cds[HL].l:.0f})", RED)],
marks=[Mark(11, "low", GRAY, "below", dy=4), Mark(HL, "HL", GREEN, "below", dy=4),
Mark(SH, "vârf", GRAY, "above", dy=-2), Mark(hit, "țintă", BLUE, "above", dy=-2)],
scale=3.4, title="Exemplu construit (fără ticker) — weekly, acțiune de ~420 $",
caption=f"4 tranșe × {per9} acțiuni, un singur stop. Preț mediu {avg:.2f}, risc total {risk_tr:.0f} $.",
**STYLE)
svg_ = ch.render()
assert abs(rpx(ch, avg, sl9, tgt) - (tgt - avg) / (avg - sl9)) < 0.01
svgs["transe"] = svg_
# ================================================================ 10. ținte în trepte, cu SMA-200 desenată
hist10 = [55.1 + 4.3 * k / 199 for k in range(200)]
cl10 = [60.40, 60.10, 60.50, 60.20,
55.20, # 4 marubozu de cădere
55.80, 55.90, 55.40, 55.00, 54.30, 54.70, 53.60, 52.80, 53.10, 52.00, 51.20, 51.50, 50.40, 49.60, 49.90,
49.10, 48.60, 48.90,
48.85, # 23 doji
49.60, # 24 buy doji
50.20, 49.90, 50.70, 51.30, 51.00, 51.80, 52.60, 52.20, 53.00, 53.80, 53.40, 54.30, 55.00, 54.60, 55.50,
56.20, 55.90, 56.70, 56.40, 55.90]
w10 = [(0.2, 0.2)] * 4 + [(0.05, 0.05)] + [(0.2, 0.2)] * 18 + [(0.4, 0.4)] + [(0.2, 0.2)] * 21
cds = from_closes(cl10, wicks=w10, start=60.30)
MAR, DJ, T = 4, 23, 24
R1, R2 = 51.00, 52.50 # pivotul lunii: date, calculul e în documentul 04
assert cds[MAR].o - cds[MAR].c >= 4.5, "marubozu mare"
assert abs(cds[DJ].o - cds[DJ].c) <= 0.1 and cds[T].c > cds[DJ].h, "buy doji"
entry = cds[T].c
sl = round(min(c.l for c in cds[T - 3:T + 1]) - 0.10, 2)
risk = entry - sl
ph_i = max(range(MAR + 1, T), key=lambda i: cds[i].h)
ph = cds[ph_i].h
half_m = round((cds[MAR].o + cds[MAR].c) / 2, 2)
sup = round(min(c.l for c in cds[:MAR]), 2)
tmp = PChart(candles=cds, hist=hist10)
sma = tmp.ma_vals(200)
hits = {}
for name, p in [("R1", R1), ("R2", R2), ("vârf", ph)]:
hits[name] = first(cds, T + 1, lambda c, p=p: c.h >= p)
hits["SMA-200"] = next(i for i in range(T + 1, len(cds)) if cds[i].h >= sma[i])
sma_hit = sma[hits["SMA-200"]]
assert R1 < R2 < ph < sma_hit < half_m < sup, (R1, R2, ph, sma_hit, half_m, sup)
assert hits["R1"] < hits["R2"] < hits["vârf"] < hits["SMA-200"]
assert max(c.h for c in cds[T + 1:]) < half_m and min(c.l for c in cds[T + 1:]) > sl
assert all(cds[i].c < sma[i] for i in range(hits["SMA-200"], len(cds))), "după atingere, nicio bară nu închide peste SMA-200"
rs = {k: (p - entry) / risk for k, p in [("R1", R1), ("R2", R2), ("vârf", ph), ("SMA-200", sma_hit),
("1/2 marubozu", half_m), ("fost suport", sup)]}
print(f"D10 entry {f2(entry)} SL {f2(sl)} risc {f2(risk)}; R1 {R1} R2 {R2} vârf {f2(ph)} SMA-200 la atingere "
f"{f2(sma_hit)} (ultima {f2(sma[-1])}) 1/2 mar {f2(half_m)} suport {f2(sup)}; hits {hits}; "
+ ", ".join(f"{k} {v:.2f}R" for k, v in rs.items()))
TREP = dict(entry=entry, sl=sl, risk=risk, ph=ph, sma=sma_hit, half=half_m, sup=sup, rs=rs,
hits={k: v - T for k, v in hits.items()}, mar_o=cds[MAR].o, mar_c=cds[MAR].c)
n = len(cds)
ch = PChart(candles=cds, hist=hist10,
segs=[Seg(0, n - 1, sup, f"6. fost suport {f2(sup)} = {rs['fost suport']:.1f}R", PURPLE, dash="4 3"),
Seg(MAR, n - 1, half_m, f"5. 1/2 marubozu {f2(half_m)} = {rs['1/2 marubozu']:.1f}R", ORANGE, dash="4 3"),
Seg(hits["SMA-200"], n - 1, sma_hit, f"4. SMA-200 (linia gri) atinsă la {f2(sma_hit)} = {rs['SMA-200']:.1f}R", GRAY, dash="2 3"),
Seg(ph_i, n - 1, ph, f"3. vârf anterior {f2(ph)} = {rs['vârf']:.1f}R", BLUE, dash="4 3"),
Seg(T, n - 1, R2, f"2. R2 {f2(R2)} = {rs['R2']:.1f}R", BLUE, dash="4 3"),
Seg(T, n - 1, R1, f"1. R1 {f2(R1)} = {rs['R1']:.1f}R", BLUE, dash="4 3"),
Seg(T, n - 1, entry, f"intrare {f2(entry)}", GREEN),
Seg(T, n - 1, sl, f"SL {f2(sl)} = −1R", RED)],
mas=[MA(200, GRAY)],
marks=[Mark(MAR, "marubozu", RED, "below", dy=4), Mark(T, "buy doji", GREEN, "below", dy=6),
Mark(hits["R1"], "1", BLUE, "above", dy=-2), Mark(hits["R2"], "2", BLUE, "above", dy=-2),
Mark(hits["vârf"], "3", BLUE, "above", dy=-2), Mark(hits["SMA-200"], "4", GRAY, "above", dy=-12)],
scale=24, title="Exemplu construit (fără ticker) — daily",
caption="Treptele 1-4 se ating în ordine; la SMA-200, sub 1/2 marubozu, prețul e respins.", **STYLE)
svg_ = ch.render()
for p, r in [(R1, rs["R1"]), (R2, rs["R2"]), (ph, rs["vârf"]), (sma_hit, rs["SMA-200"]), (half_m, rs["1/2 marubozu"]),
(sup, rs["fost suport"])]:
assert abs(rpx(ch, entry, sl, p) - r) < 0.01
svgs["trepte"] = svg_
# ================================================================ 11. eșantionul de 25 de tranzacții la 1:4
seq = "SSSSCSSSCSSSSCSSSCSSCSSSC" # S = stop (−25 $), C = țintă 4R (+100 $)
assert len(seq) == 25
rows, eq = [], 0.0
for k, ch_ in enumerate(seq, 1):
eq += 100 if ch_ == "C" else -RISC
rows.append((k, ch_, eq))
wins = seq.count("C")
total = wins * 100 - (25 - wins) * RISC
assert abs(total - eq) < 1e-9 and wins == 6 and total == 125
worst = min(r[2] for r in rows)
day1 = rows[4][2]
be_rate = 1 / (1 + 4)
print(f"D11 {wins}/25 câștigătoare, total {total}, minim {worst}, după 5 tranzacții {day1}; prag {be_rate:.0%}")
tbl = ["| # | rezultat | cumulat |", "|---|---|---|"]
for k, ch_, e in rows:
tbl.append(f"| {k} | {'țintă +100 $' if ch_ == 'C' else 'stop −25 $'} | {e:+.0f} $ |")
svgs["esantion_tabel"] = "\n".join(tbl)
SAMPLE = dict(wins=wins, total=total, worst=worst, day1=day1)
# ---------------------------------------------------------------- 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)