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
531 lines
30 KiB
Python
531 lines
30 KiB
Python
"""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)
|