Files
atm-curs-dl/gen/az_executie.py
Claude Agent 303e9a4c91 Analiza Zilnica 08: executie si risc (10 diagrame + tabelul esantionului)
Al noualea document de strategie. Aici aritmetica e miezul, deci fiecare
raport R e pe assert masurat in pixeli, cu toleranta 0.01, si fiecare suma
din text e recalculata in generator.

Acoperite: riscul fix de 25 $ la 1:4 pe long si pe short, reintrarea dupa
stop cu acelasi risc (trei intrari, -25 -25 +100 = +50 $), break-even la
+1R cu perechea DA/NU pe aceleasi candele (pe NU ieși pe zero si 11 bare
mai tarziu prețul e la ținta), trei poziții cu TP1/TP2/trail (2.27R),
țintele in trepte R1 → R2 → varf anterior → SMA-200 → 1/2 marubozu → fost
suport, fiecare cu R-ul lui, ținta realista de 1/2 (3.3R, atinsa) fata de
ținta lacoma la varf (6.6R, ratata dupa 70% din drum), tranșele de 1/4 la
simboluri scumpe (2.6R fata de 1.0R cumparand tot la varf).

SMA-200 e calculata din 200 de close-uri reale, nu desenata aproximativ -
PChart a primit `hist` pentru asta.

Doua bug-uri prinse de agent la verificare: in text scria "5 cistigatoare"
la 20 de tranzactii, dar calculul da 4; si etichetele de trail goale
intrau in plasarea automata si impingeau etichetele TP1/TP2 cu 13px sub
liniile lor.

Marcat ca adaugire proprie: break-even la exact +1R, TP1 = 1R / TP2 = 2R,
a patra transa la 1/2, si regula de trail - cea din Strategia 3 cere un gol
intre bare, imposibil pe o serie continua, deci e inlocuita cu low-ul
ultimei candele care a inchis peste high-ul precedentei.

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

545 lines
30 KiB
Python
Raw 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 = {}
@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" # "out" marginea dreaptă | "lm" marginea stângă | "left"/"right" pe linie
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):
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=-2)]
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:h1 + 1]) > sl and min(c.l for c in cds[h1 + 1:]) > entry, "BE 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, h1, sl, f"SL {f2(sl)} (toate 3)", RED),
Seg(T, len(cds) - 1, entry, f"intrare {f2(entry)}; după TP1 = stop la zero", 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, f"confluență S2 + suport monthly {SUP:.2f}", PURPLE, dash="2 3"),
Seg(T, n - 1, entry, f"intrare {f2(entry)}", GREEN),
Seg(T, n - 1, sl, f"SL {f2(sl)} = −1R", 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),
Seg(t1["i"], t1["stop"], t1["s"], f"#1 SL {f2(t1['s'])}: −25 $", RED),
Seg(t2["i"], t2["stop"], t2["e"], f"#2 intrare {f2(t2['e'])} ({t2['n']} acț.)", GREEN),
Seg(t2["i"], t2["stop"], t2["s"], f"#2 SL {f2(t2['s'])}: −25 $", RED),
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["i"], "#1", GREEN, "above", dy=-2), Mark(t1["stop"], "stop", RED, "below", dy=4),
Mark(t2["i"], "#2", GREEN, "above", dy=-2), 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(i, n - 1, p, f"tranșa {k + 1}: {per9} acț. la {p:.0f} ({nm})", GREEN)
for k, (i, p, nm) in enumerate(lv)]
+ [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, 57.00, 56.30]
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 cds[-1].c < sma[-1], "respins sub 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)