m2d: diagramele refacute; ansamblul primeste corespondenta de TF

Ansamblul arata acum 15 min si 3 min ca descompunere reala (k=5, cu
assert), cu caseta punctata, ghidaje si nivelul comun legat prin
conector — sablonul din buy_doji_perechi_tf.

Noi, pentru pasii care nu aveau diagrama: directie (15 min ramane in Buy
vs. se inchide Sell), trigger (bara inchisa peste capat vs. atinsa doar
cu fitilul, plus al doilea semnal marcat "nu mai intri"), stop (cele 4
bare si alternativa 2xATR, cu cifrele SPY din curs), iesire (TP1 plus
bull trail, long si short, oglinda calculata nu desenata din ochi).
Secventa era din polilinii desenate de mana — rescrisa pe serie.

Stopul e fix pe extrema celor 4 bare, fara marja: asa spune cursul aici,
spre deosebire de 01_buy_doji. Diferenta trebuie spusa in text.

Textul capitolului e neatins — agentul a atins pragul de context.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ThN7ixZrowca8aTGrK1MUJ
This commit is contained in:
Claude Agent
2026-09-20 12:45:14 +00:00
parent 3143e35ed9
commit 0ed607134d
13 changed files with 1610 additions and 527 deletions

View File

@@ -1,136 +1,390 @@
"""Diagrama de ansamblu M2D (strategii/03_m2d.md, marcajul `ansamblu`).
#!/usr/bin/env python3
"""M2D — tot trade-ul, pe perechea 15 min → 3 min, cu lupă.
„Setup M2D LONG complet pe TF mic (3 min), cu TF mare (15 min) în Buy":
urcare -> pullback (M = Sell Momentum) -> trigger (A = primul Buy) -> intrare
-> SL de 4 candele -> TP1 la 1:1 -> continuare -> vârf -> ieșire pe bull trail.
Pentru `summaries/17_STRATEGII/03_m2d.md`, marcajul `<!-- DIAGRAMA: ansamblu -->`.
Toate close-urile, wick-urile, volumele și nivelurile vin din
`gen/strategii/m2d_ansamblu.spec.txt`. Seria e construită (exemplu generic,
fără ticker), la scara 1.00 $ risc / 2.2R randament — nu e un trade real.
Tiparul e cel din `buy_doji_perechi_tf.py`: panoul din dreapta NU e o serie
care vine după cea din stânga — e **descompunerea reală** a ultimelor 4
candele de 15 minute. O candelă de 15 min = k = 5 candele de 3 min, cu
open, close, max(high) și min(low) care coincid, verificat bară cu bară.
Rulează:
python3 gen/strategii/m2d_ansamblu.py
Scrie lângă el `m2d_ansamblu.svg`.
Sursa de adevăr e seria fină (`FINE`, 3 min). Candelele de 15 min din zona
mărită se CALCULEAZĂ din ea prin agregare; barele de dinainte (`BASE`) sunt
date separat și se leagă de lupă prin close == open.
Toate cifrele din desen ies din serii. Regula de stop din tot capitolul:
stopul stă FIX pe extrema ultimelor 4 candele de pe TF mic, fără marjă.
Rulare: python3 gen/strategii/m2d_ansamblu.py
"""
import sys
from pathlib import Path
sys.path.insert(0, "/workspace/atm-curs-dl")
from svg_candles import Chart, Level, Mark, MA, from_closes # noqa: E402
OUT = Path(__file__).with_suffix(".svg")
W = 720
K = 5 # 5 candele de 3 min într-una de 15 min
NZ = 4 # câte candele de 15 min sunt mărite
STOP_BARS = 4 # stopul = extrema ultimelor 4 bare de pe TF mic
PULLBACK_N = 9 # barele 0..8 = pullback-ul (M)
TRIG = 9 # bara de trigger (A) pe TF mic
OUT = Path(__file__).resolve().parent / "m2d_ansamblu.svg"
GRAY, RED, GREEN, BLUE, ORANGE = "#777", "#c00", "#188a3e", "#1a5fb4", "#b06000"
# ---------------------------------------------------------------- seria (32 bare)
CLOSES = [
96.8, 97.2, 97.7, 98.1, 98.6, 99.1, 99.5, 100.0, # 0-7 urcare
99.8, 99.4, 99.0, 99.2, 98.9, 99.15, 99.3, # 8-14 pullback (M)
99.8, 100.2, # 15-16 trigger (A)
100.7, 101.2, 101.6, 102.0, 102.4, 102.7, 103.0, # 17-23 continuare
103.3, 103.1, 102.8, 102.7, 102.9, 102.5, 102.2, 102.0, # 24-31 vârf + violare
]
assert len(CLOSES) == 32, len(CLOSES)
# fitile (sus, jos); jos mai mari pe 10-12, ca minimul de 4 candele (11:15) să
# fie clar sub intrare, la exact 98.80 (candela 12).
WICKS = (
[(0.15, 0.10)] * 8 # 0-7
+ [(0.10, 0.15), (0.10, 0.20), (0.10, 0.15), # 8-10
(0.10, 0.15), (0.10, 0.10), (0.10, 0.05), (0.10, 0.10)] # 11-14
+ [(0.15, 0.10), (0.15, 0.10)] # 15-16
+ [(0.15, 0.10)] * 7 # 17-23
+ [(0.20, 0.10), (0.10, 0.15), (0.10, 0.15), (0.12, 0.15), # 24-27
(0.10, 0.15), (0.10, 0.15), (0.10, 0.15), (0.10, 0.20)] # 28-31
)
assert len(WICKS) == 32, len(WICKS)
VOLS = (
[62, 66, 60, 68, 64, 70, 66, 74] # 0-7 trend
+ [44, 40, 38, 36, 34, 33, 35] # 8-14 pullback mic
+ [82, 88] # 15-16 trigger
+ [72, 70, 74, 68, 72, 66, 70] # 17-23 trend
+ [76, 64, 60, 58, 62, 56, 54, 66] # 24-31 vârf + ieșire
)
assert len(VOLS) == 32, len(VOLS)
cds = from_closes(CLOSES, wicks=WICKS, vols=VOLS, start=96.4)
# ---------------------------------------------------------------- assert-urile din spec
entry = cds[15].c # 1. intrarea = close-ul trigger-ului
SL = min(c.l for c in cds[11:15]) # 2. minimul ultimelor 4 dinainte
TP1 = entry + (entry - SL) # 4. TP1 exact 1:1
assert entry == round(cds[15].c, 2), (entry, cds[15].c)
assert SL == min(c.l for c in cds[11:15]), SL
assert SL < entry, (SL, entry)
assert abs(TP1 - (entry + (entry - SL))) < 1e-6
for a, b in zip(cds, cds[1:]): # 5. serie continuă, fără goluri
assert a.c == b.o, (a.c, b.o)
assert cds[15].c > cds[14].c # 6. trigger în sus după pullback
assert any(c.c < c.o for c in cds[8:15]) # 7. pullback = Sell
assert any(c.c > c.o for c in cds[0:8]) # context în sus
exit_price = cds[31].c
assert exit_price > entry # 8. ieșirea e peste intrare
# bull trail = low-ul ultimei candele care a închis peste high-ul precedentei
qual = [i for i in range(1, 31) if cds[i].c > cds[i - 1].h]
assert qual and qual[-1] == 24, qual
bull = cds[24].l
assert cds[31].c < bull, (cds[31].c, bull) # finala închide sub linie = ieșire aici
r_mult = (exit_price - entry) / (entry - SL) # 9. R-ul afișat
assert round(r_mult, 1) == 2.2, r_mult
# ---------------------------------------------------------------- niveluri, etichete, marks
levels = [
Level(entry, f"intrare {entry:.2f}", GREEN, label_at="left"),
Level(SL, f"SL {SL:.2f} (4 candele)", RED, label_at="right"),
Level(TP1, f"TP1 {TP1:.2f} = 1:1", BLUE, label_at="right"),
]
assert [round(lv.price, 2) for lv in levels] == [99.80, 98.80, 100.80]
exit_text = f"ieșire {exit_price:.2f} pe bull trail ({r_mult:.1f}R)"
assert f"{r_mult:.1f}R" in exit_text
marks = [
Mark(0, "TF mare (15 min) în Buy", BLUE, "above", dy=-227),
Mark(11, "M: Sell Momentum (pullback)", RED, "below"),
Mark(15, "A: primul Buy = trigger", GREEN, "above", dy=-122),
# „vârf" stă sub candela 24: deasupra ei, orice text de la y-ul vârfului se
# calcă pe titlul din stânga-sus (ambele pornesc din `top`).
Mark(24, "vârf 103.30", GRAY, "below", dy=25),
Mark(31, "close sub bull trail = ieși", RED, "below", dy=55),
Mark(31, exit_text, RED, "below", dy=73),
# --- seria fină (3 min), 20 de bare: (open, high, low, close) --------------
FINE = [
(100.60, 100.70, 100.40, 100.45), (100.45, 100.50, 100.25, 100.30),
(100.30, 100.35, 100.15, 100.20), (100.20, 100.25, 100.05, 100.10),
(100.10, 100.15, 100.00, 100.05), (100.05, 100.10, 99.95, 100.00),
(100.00, 100.05, 99.92, 99.95), (99.95, 100.00, 99.90, 99.98),
(99.98, 100.08, 99.94, 100.02), # 8: capătul pullback-ului
(100.02, 100.45, 100.00, 100.40), # 9: A = trigger, intri
(100.40, 100.60, 100.35, 100.55), (100.55, 100.75, 100.50, 100.70),
(100.70, 101.00, 100.65, 100.95), # 12: TP1 atins
(100.95, 101.10, 100.88, 101.05), (101.05, 101.15, 100.95, 101.00),
(101.00, 101.40, 100.95, 101.35), (101.35, 101.55, 101.25, 101.45),
(101.45, 101.50, 101.20, 101.25),
(101.25, 101.30, 101.05, 101.10), # 18: close sub trail = ieși
(101.10, 101.15, 100.90, 100.95),
]
caption = (f"Serie construită (32 bare, 3 min). R = ({exit_price:.2f} − {entry:.2f}) / "
f"{entry - SL:.2f} = {r_mult:.1f}R.")
assert f"{r_mult:.1f}R" in caption
# --- barele de 15 min dinaintea lupei; ultima poartă semnalul Buy Momentum --
BASE = [
(98.00, 98.35, 97.85, 98.25), (98.25, 98.70, 98.15, 98.60),
(98.60, 99.10, 98.50, 99.00), (99.00, 99.45, 98.90, 99.30),
(99.30, 99.95, 99.20, 99.85),
(99.85, 100.75, 99.80, 100.60), # 5: Buy Momentum pe 15 min
]
SIG_B = 5 # candela de 15 min cu semnalul
ch = Chart(
candles=cds, levels=levels, marks=marks,
mas=[MA(20, ORANGE, dash="4 3", text="SMA-20")],
scale=52.0, gap=16, body_w=9, left=44, width=720, vol_h=70,
vol_label="volum",
title="Simbol construit — 3 min (TF mare 15 min în Buy)",
caption=caption,
)
svg = ch.render()
# bull trail: nu e un `Level` (spec-ul cere exact trei niveluri), deci se
# injectează ca linie separată, la aceeași scară (`ch._y`).
yb = ch._y(bull)
bull_layer = (
f'<line x1="24" y1="{yb:.1f}" x2="{ch.width - 14}" y2="{yb:.1f}" '
f'stroke="{ORANGE}" stroke-width="1.5" stroke-dasharray="2 3"/>'
f'<text x="{ch.width - 28}" y="{yb - 6:.1f}" fill="{ORANGE}" '
f'text-anchor="end">bull trail {bull:.2f}</text>'
)
i = svg.index(">") + 1
svg = svg[:i] + "\n " + bull_layer + svg[i:]
def aggregate(fine, k, n):
out_ = []
for j in range(n):
part = fine[j * k:(j + 1) * k]
out_.append((part[0][0], max(b[1] for b in part),
min(b[2] for b in part), part[-1][3]))
return out_
assert svg.count("\n\n") == 0, "linie goală în SVG rupe blocul din markdown"
OUT.write_text(svg)
print(f"scris {OUT.name}: {len(cds)} candele, intrare {entry:.2f}, SL {SL:.2f}, "
f"TP1 {TP1:.2f}, ieșire {exit_price:.2f} = {r_mult:.1f}R, bull trail {bull:.2f}")
def eq(a, b):
return abs(a - b) < 0.005
ZOOM = aggregate(FINE, K, NZ)
BIG = BASE + ZOOM
# ---------------------------------------------------------------- verificări
for name, bars in (("15 min", BIG), ("3 min", FINE)):
for i, (o, h, l, c) in enumerate(bars):
assert h >= max(o, c) and l <= min(o, c), f"{name}[{i}]: high/low incoerent"
if i:
assert eq(o, bars[i - 1][3]), f"{name}[{i}]: open != close precedent"
assert len(FINE) == K * NZ, len(FINE)
# (1) unde cad granițele candelelor de 15 min peste poveste — exact ce spune
# textul capitolului, și poate fi fals
span = len({i // K for i in range(PULLBACK_N)})
assert span == 2, f"pullback-ul se întinde peste {span} candele de 15 min, nu 2"
trig_big = TRIG // K
assert trig_big == 1, f"trigger-ul cade în candela mare {trig_big}"
assert eq(FINE[0][0], BASE[SIG_B][3]), "open-ul primei bare fine != close-ul semnalului"
# (2) pullback-ul (M): coboară, dar NU rupe low-ul candelei de 15 min din care
# a pornit mișcarea — definiția ancorată din mini-glosar
PULL = FINE[:PULLBACK_N]
ref_low = BASE[SIG_B][2]
pull_low = min(b[2] for b in PULL)
assert PULL[0][3] > PULL[-1][3], "pullback-ul nu coboară"
assert pull_low > ref_low, f"pullback-ul rupe low-ul de referință ({pull_low} <= {ref_low})"
# (3) trigger-ul (A). Săgeata vine dintr-un indicator proprietar, deci forma
# ei pe desen e construită de mine: bara de trigger e PRIMA din pullback
# care se închide peste high-urile ultimelor LOOK bare. Verific că e
# într-adevăr prima — altfel desenul ar contrazice regula „primul semnal".
LOOK = 3
def rupe(i):
return i >= LOOK and FINE[i][3] > max(b[1] for b in FINE[i - LOOK:i])
assert rupe(TRIG), "bara de trigger nu rupe high-urile barelor dinainte"
assert not any(rupe(i) for i in range(LOOK, TRIG)), \
"o bară dinaintea trigger-ului rupea deja — nu mai e primul semnal"
# (4) trade-ul
ENTRY = FINE[TRIG][3]
SL = min(b[2] for b in FINE[TRIG - STOP_BARS + 1:TRIG + 1])
RISK = round(ENTRY - SL, 2)
TP1 = round(ENTRY + RISK, 2)
assert eq(SL, pull_low), "stopul nu e minimul pullback-ului"
assert eq(SL, min(b[2] for b in FINE)), "stopul nu e minimul zonei mărite"
TP_BAR = next(i for i in range(TRIG + 1, len(FINE)) if FINE[i][1] >= TP1 - 1e-9)
assert TP_BAR == 12, TP_BAR
# (5) bull trail: low-ul ultimei bare care s-a închis peste high-ul precedentei;
# ieșirea = prima bară care se ÎNCHIDE sub linie
trail, EXIT_BAR = None, None
for i in range(1, len(FINE)):
if trail is not None and FINE[i][3] < trail:
EXIT_BAR = i
break
if i > TRIG and FINE[i][3] > FINE[i - 1][1]:
trail = FINE[i][2]
TRAIL = trail
EXIT_BAR = EXIT_BAR if EXIT_BAR is not None else len(FINE) - 1
EXIT = FINE[EXIT_BAR][3]
assert (TRAIL, EXIT_BAR) == (101.25, 18), (TRAIL, EXIT_BAR)
assert EXIT > ENTRY, "ieșirea pe trail e sub intrare"
R = (EXIT - ENTRY) / RISK
assert round(R, 1) == 1.4, R
# wick-uri sub linie nu scot: bara 17 coboară sub trail, dar se închide deasupra
assert FINE[17][2] < TRAIL <= FINE[17][3], "bara 17 nu mai ilustrează wick sub trail"
LEVEL = BASE[SIG_B][3] # nivelul comun celor două panouri
assert eq(LEVEL, FINE[0][0])
# ---------------------------------------------------------------- desen
GREEN, RED, INK, BLUE, GRAY = "#188a3e", "#c00", "#333", "#1a5fb4", "#888"
LINK, CYAN = "#aaa", "#0891b2"
PLOT_H = 168
BX0, BGAP, BW = 104, 15, 9
SX0, SGAP, SW = 372, 11, 7
FRAME_L = SX0 - 8
COL_X = 600
MIDX = FRAME_L - 20
PAD = 0.06
out, LABELS = [], []
def text(x, y, s, fill=INK, anchor="start", bold=False, size=12):
st = [f"font-size:{size}px"] + (["font-weight:bold"] if bold else [])
a = f' text-anchor="{anchor}"' if anchor != "start" else ""
w = len(s) * (5.7 if size >= 12 else size * 0.5)
x0 = x if anchor == "start" else (x - w / 2 if anchor == "middle" else x - w)
assert 4 <= x0 and x0 + w <= W - 4, f"text iese din viewBox: {s!r} ({x0:.0f}..{x0 + w:.0f})"
LABELS.append((x0, x0 + w, y, s))
out.append(f'<text x="{x:.1f}" y="{y:.1f}" fill="{fill}"{a} '
f'style="{";".join(st)}">{s}</text>')
def hline(x1, x2, y, color, dash, width=1.5):
out.append(f'<line x1="{x1:.1f}" y1="{y:.1f}" x2="{x2:.1f}" y2="{y:.1f}" '
f'stroke="{color}" stroke-width="{width}" stroke-dasharray="{dash}"/>')
def line(x1, y1, x2, y2, color, dash="", width=1.2, marker=""):
d = f' stroke-dasharray="{dash}"' if dash else ""
m = f' marker-end="url(#{marker})"' if marker else ""
out.append(f'<line x1="{x1:.1f}" y1="{y1:.1f}" x2="{x2:.1f}" y2="{y2:.1f}" '
f'stroke="{color}" stroke-width="{width}"{d}{m}/>')
def rect(x0_, y0_, x1, y1, color, fill_op=0.10, dash=""):
d = f' stroke-dasharray="{dash}"' if dash else ""
out.append(f'<rect x="{x0_:.1f}" y="{y0_:.1f}" width="{x1 - x0_:.1f}" '
f'height="{y1 - y0_:.1f}" fill="{color}" fill-opacity="{fill_op}" '
f'stroke="{color}" stroke-width="1.2"{d}/>')
def candles(bars, x0, gap, yf, bw):
for i, (o, h, l, c) in enumerate(bars):
cx = x0 + i * gap
color = GREEN if c >= o else RED
line(cx, yf(h), cx, yf(l), "#1a1a1a", width=1.2)
top_ = yf(max(o, c))
hh = max(yf(min(o, c)) - top_, 1.6)
out.append(f'<rect x="{cx - bw / 2:.1f}" y="{top_:.1f}" width="{bw}" '
f'height="{hh:.1f}" fill="{color}"/>')
def stack(items, x_line):
y_prev = None
for y, lines, fill in sorted(items, key=lambda t: t[0]):
y_lab = y if y_prev is None or y >= y_prev + 16 else y_prev + 16
if y_lab - y > 2:
out.append(f'<path d="M{x_line + 2:.1f},{y - 4:.1f} H{x_line + 7:.1f} '
f'L{COL_X - 4:.1f},{y_lab - 4:.1f}" fill="none" stroke="{fill}" '
f'stroke-width="0.8" stroke-opacity="0.8"/>')
else:
line(x_line + 2, y - 4, COL_X - 4, y_lab - 4, fill, width=0.8)
for j, s in enumerate(lines):
text(COL_X, y_lab + j * 14, s, fill=fill, size=10)
y_prev = y_lab + (len(lines) - 1) * 14
def seg_dist(p, a, b):
(px, py), (ax, ay), (bx, by) = p, a, b
dx, dy = bx - ax, by - ay
t = 0.0 if dx == dy == 0 else max(0.0, min(1.0, ((px - ax) * dx + (py - ay) * dy)
/ (dx * dx + dy * dy)))
return ((px - ax - t * dx) ** 2 + (py - ay - t * dy) ** 2) ** 0.5
out.append('<defs><marker id="pt" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" '
'markerHeight="7" orient="auto"><path d="M0,0 L10,5 L0,10 z" fill="#c00"/></marker>'
'<marker id="pt-o" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" '
'markerHeight="7" orient="auto"><path d="M0,0 L10,5 L0,10 z" fill="#b06000"/></marker>'
'</defs>')
text(20, 24, "M2D long, tot trade-ul: 15 min dă direcția, 3 min dă intrarea",
bold=True, size=14)
text(20, 42, "Graficul din dreapta nu e altă bucată de piață: e aceeași, mărită. "
"O candelă de 15 min = 5 candele de 3 min.", fill="#555", size=11)
top = 80
text(20, top - 12, "15 min: direcția", fill="#555", size=11)
text(FRAME_L, top - 12, f"3 min: descompunerea ultimelor {NZ} candele de 15 min",
fill="#555", size=11)
zlo_, zhi_ = SL, max(b[1] for b in FINE)
pad = PAD * (zhi_ - zlo_)
lo, hi = zlo_ - pad, zhi_ + pad
bmax = max(b[1] for b in BIG)
bmin = min(min(b[2] for b in BIG), lo)
bs, ss = PLOT_H / (bmax - bmin), PLOT_H / (hi - lo)
yb = lambda p: top + (bmax - p) * bs # noqa: E731
ys = lambda p: top + (hi - p) * ss # noqa: E731
# ---- panoul de 15 min ------------------------------------------------------
sx_a, sx_b = BX0 + SIG_B * BGAP - 8, BX0 + SIG_B * BGAP + 8
rect(sx_a, yb(BASE[SIG_B][1]) - 5, sx_b, yb(BASE[SIG_B][2]) + 5, CYAN)
line(BX0 + SIG_B * BGAP, yb(BASE[SIG_B][2]) + 6, BX0 + SIG_B * BGAP, top + PLOT_H + 12,
CYAN, width=1)
text(BX0 + SIG_B * BGAP - 30, top + PLOT_H + 26, "Buy Momentum pe 15 min:",
fill=CYAN, anchor="middle", bold=True, size=11)
text(BX0 + SIG_B * BGAP - 30, top + PLOT_H + 39, "de aici ai voie doar long",
fill=CYAN, anchor="middle", size=11)
# caseta de lupă peste exact candelele de 15 min care se sparg în dreapta
lx0, lx1 = BX0 + len(BASE) * BGAP - 7, BX0 + (len(BIG) - 1) * BGAP + 8
ly0, ly1 = yb(hi), yb(lo)
rect(lx0, ly0, lx1, ly1, BLUE, fill_op=0.06, dash="4 3")
# minimul de referință al pullback-ului: trăiește pe TF-ul mare (pe panoul mic
# e sub scară), deci se marchează aici și se trimite acolo din etichetă
hline(BX0 - 14, lx1, yb(ref_low), "#555", "2 3", width=1.2)
text(24, yb(ref_low) + 12, "minimul din care a pornit", fill="#555", size=10)
text(24, yb(ref_low) + 26, f"mișcarea: {ref_low:.2f}", fill="#555", size=10)
hline(BX0 - 14, lx1, yb(LEVEL), LINK, "3 3", width=1.0)
candles(BIG, BX0, BGAP, yb, BW)
# ---- ghidajele de lupă -----------------------------------------------------
guides = [((lx1, ly0), (FRAME_L, top)), ((lx1, ly1), (FRAME_L, top + PLOT_H))]
for a_, b_ in guides:
line(a_[0], a_[1], b_[0], b_[1], BLUE, dash="5 3", width=1.1)
# ---- nivelul comun: conector între panouri, numărul scris o singură dată ---
ycb, ycs = yb(LEVEL), ys(LEVEL)
def clearance(mx):
conn = [((lx1, ycb), (mx, ycb)), ((mx, ycb), (mx, ycs)), ((mx, ycs), (FRAME_L, ycs))]
pts = [(sx + (ex - sx) * t / 24, sy + (ey - sy) * t / 24)
for (sx, sy), (ex, ey) in conn for t in range(25)]
return min(seg_dist(q, ga, gb) for q in pts if q[0] >= lx1 + 10 for ga, gb in guides)
mid = max(range(int(lx1) + 14, int(FRAME_L) - 13), key=clearance)
dmin = clearance(mid)
assert dmin > 10, f"conectorul trece la {dmin:.1f} px de un ghidaj"
out.append(f'<path d="M{lx1:.1f},{ycb:.1f} H{mid:.1f} V{ycs:.1f} H{FRAME_L:.1f}" '
f'fill="none" stroke="{LINK}" stroke-width="1.0" stroke-dasharray="3 3"/>')
tx = min(mid + 5, FRAME_L - 60)
text(tx, (ycb + ycs) / 2 + 4, f"{LEVEL:.2f}", fill="#777", size=10)
text(tx, (ycb + ycs) / 2 + 17, "același preț", fill="#999", size=10)
# ---- panoul de 3 min -------------------------------------------------------
y_e, y_sl, y_tp, y_tr = ys(ENTRY), ys(SL), ys(TP1), ys(TRAIL)
assert abs((y_sl - y_e) / (y_e - y_tp) - 1) < 1e-6, "SL-intrare != intrare-TP1 în px"
assert top + PLOT_H - y_sl > 6, "linia de stop e lipită de marginea cadrului"
frame_r = SX0 + (len(FINE) - 1) * SGAP + 8
rect(FRAME_L, top, frame_r, top + PLOT_H, BLUE, fill_op=0.03)
# zona de evidențiere peste pullback + trigger: acolo se decide intrarea
zx0 = SX0 - 7
zx1 = SX0 + TRIG * SGAP + 7
rect(zx0, ys(max(b[1] for b in FINE[:TRIG + 1])) - 5, zx1, ys(SL) + 5, GREEN)
hline(FRAME_L, frame_r, ycs, LINK, "3 3", width=1.0)
hline(FRAME_L, frame_r, y_tp, GREEN, "2 3")
hline(FRAME_L, frame_r, y_e, GREEN, "6 3")
hline(FRAME_L, frame_r, y_sl, RED, "6 3")
hline(FRAME_L, frame_r, y_tr, "#b06000", "2 2", width=1.2)
candles(FINE, SX0, SGAP, ys, SW)
stack([(y_tp + 4, [f"TP1 {TP1:.2f} = 1:1", "iese prima tranșă"], GREEN),
(y_tr + 4, [f"bull trail {TRAIL:.2f}", "urcă, nu coboară"], "#b06000"),
(y_e + 4, [f"intrare {ENTRY:.2f}", "close-ul trigger-ului"], GREEN),
(y_sl + 4, [f"stop {SL:.2f}", f"minimul a {STOP_BARS} bare"], RED)],
frame_r)
# paranteza pullback-ului, sub cadru
pxa, pxb = SX0 - 5, SX0 + (PULLBACK_N - 1) * SGAP + 5
out.append(f'<path d="M{pxa:.1f},{top + PLOT_H + 2:.1f} V{top + PLOT_H + 8:.1f} '
f'H{pxb:.1f} V{top + PLOT_H + 2:.1f}" fill="none" stroke="{RED}" stroke-width="1"/>')
text((pxa + pxb) / 2, top + PLOT_H + 22, f"M: pullback, {PULLBACK_N} bare",
fill=RED, anchor="middle", bold=True, size=11)
text((pxa + pxb) / 2, top + PLOT_H + 36, "fără să rupă minimul",
fill=RED, anchor="middle", size=11)
# ieșirea: săgeată în sus spre bara 18, cu explicația sub cadru, la dreapta
exx = SX0 + EXIT_BAR * SGAP
line(exx, top + PLOT_H + 14, exx, ys(FINE[EXIT_BAR][2]) + 5, "#b06000",
dash="3 3", width=1.1, marker="pt-o")
text(frame_r, top + PLOT_H + 22, "close sub trail",
fill="#b06000", anchor="end", bold=True, size=11)
text(frame_r, top + PLOT_H + 36, f"ieșire {EXIT:.2f} = {R:.1f}R",
fill="#b06000", anchor="end", size=11)
# trigger-ul: săgeată în sus spre bara 9, cu explicația dedesubt
tgx = SX0 + TRIG * SGAP
line(tgx, top + PLOT_H + 50, tgx, ys(FINE[TRIG][2]) + 5, GREEN, width=1.6, marker="pt")
text(pxa, top + PLOT_H + 58, "A: primul Buy după pullback — aici intri,",
fill=GREEN, bold=True, size=11)
text(pxa, top + PLOT_H + 72, "pentru că 15 min e încă în Buy.", fill=GREEN, size=11)
text(pxa, top + PLOT_H + 86, "Wick-urile nu contează: se citește close-ul.",
fill="#555", size=11)
LAB_N = len(LABELS) # etichetele de pe panouri; ce urmează e subsol
H = top + PLOT_H + 108
text(20, H - 6, f"Serie construită, fără ticker. Risc = {ENTRY:.2f} − {SL:.2f} = {RISK:.2f}; "
f"TP1 la aceeași distanță în sus; ieșirea pe trail la {R:.1f}R.",
fill="#555", size=11)
H += 12
# ---- prețul fiecărui panou rămâne pe panoul lui ---------------------------
COMUNE = {round(LEVEL, 2)}
MARE = {"ref_low": ref_low}
MIC = {k_: v for k_, v in (("entry", ENTRY), ("sl", SL), ("tp", TP1), ("trail", TRAIL))
if round(v, 2) not in COMUNE}
for x0_, _x1, _y, s_ in LABELS[:LAB_N]:
rau = MARE if x0_ >= MIDX else MIC
for kk, v in rau.items():
assert f"{v:.2f}" not in s_, \
f"eticheta {s_!r} de pe panoul {'mic' if x0_ >= MIDX else 'mare'} " \
f"conține prețul {kk}={v:.2f} al celuilalt panou"
cx_ = [x0_ for x0_, _x1, _y, s_ in LABELS[:LAB_N] if f"{LEVEL:.2f}" in s_]
assert len(cx_) == 1 and lx1 < cx_[0] < FRAME_L, \
f"{LEVEL:.2f} nu e scris exact o dată, în cotul conectorului ({cx_})"
for nume, v in (("intrarea", ENTRY), ("stopul", SL), ("TP1", TP1), ("trail-ul", TRAIL)):
assert any(f"{v:.2f}" in s_ and x0_ >= MIDX for x0_, _x1, _y, s_ in LABELS[:LAB_N]), \
f"{nume} nu apare pe panoul mic"
for a in range(len(LABELS)):
assert 0 < LABELS[a][2] < H, f"etichetă în afara înălțimii: {LABELS[a][3]!r}"
for b in range(a + 1, len(LABELS)):
xa0, xa1, ya_, sa = LABELS[a]
xb0, xb1, yb_, sb = LABELS[b]
assert not (abs(ya_ - yb_) < 13 and xa0 < xb1 - 4 and xb0 < xa1 - 4), \
f"etichete suprapuse: {sa!r} / {sb!r}"
svg = (f'<svg viewBox="0 0 {W} {H:.0f}" xmlns="http://www.w3.org/2000/svg" '
f'style="width:100%;height:auto;font:12px sans-serif">\n ' + "\n ".join(out) + "\n</svg>")
assert "\n\n" not in svg
assert ".md:" not in svg, "trimiteri la surse în SVG"
OUT.write_text(svg, encoding="utf-8")
print(f"scris {OUT} ({W}x{H:.0f}) | 15 min: {len(BIG)} candele, lupă pe ultimele {NZ} "
f"= {len(FINE)} bare de 3 min | nivel comun {LEVEL:.2f} | "
f"intrare {ENTRY:.2f} SL {SL:.2f} risc {RISK:.2f} TP1 {TP1:.2f} la bara {TP_BAR} | "
f"trail {TRAIL:.2f}, ieșire bara {EXIT_BAR} la {EXIT:.2f} = {R:.2f}R | "
f"pullback peste {span} candele de 15 min, trigger în candela mare {trig_big}")

View File

@@ -1,117 +1,111 @@
<svg viewBox="0 0 720 568" xmlns="http://www.w3.org/2000/svg" style="width:100%;height:auto;font:12px sans-serif">
<line x1="24" y1="87.2" x2="706" y2="87.2" stroke="#b06000" stroke-width="1.5" stroke-dasharray="2 3"/><text x="692" y="81.2" fill="#b06000" text-anchor="end">bull trail 102.90</text>
<text x="24" y="48" fill="#333" style="font-weight:bold">Simbol construit — 3 min (TF mare 15 min în Buy)</text>
<line x1="24" y1="248.4" x2="706" y2="248.4" stroke="#188a3e" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="30" y="242.4" fill="#188a3e">intrare 99.80</text>
<line x1="24" y1="300.4" x2="706" y2="300.4" stroke="#c00" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="692" y="294.4" fill="#c00" text-anchor="end">SL 98.80 (4 candele)</text>
<line x1="24" y1="196.4" x2="706" y2="196.4" stroke="#1a5fb4" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="692" y="190.4" fill="#1a5fb4" text-anchor="end">TP1 100.80 = 1:1</text>
<polyline points="348.0,276.3 364.0,262.8 380.0,249.3 396.0,236.3 412.0,223.6 428.0,211.3 444.0,200.9 460.0,192.4 476.0,185.3 492.0,177.3 508.0,169.2 524.0,160.9 540.0,153.6" fill="none" stroke="#b06000" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="546" y="148" fill="#b06000">SMA-20</text>
<line x1="44.0" y1="396.6" x2="44.0" y2="430.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="39.5" y="404.4" width="9" height="20.8" fill="#188a3e"/>
<line x1="60.0" y1="375.8" x2="60.0" y2="409.6" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="55.5" y="383.6" width="9" height="20.8" fill="#188a3e"/>
<line x1="76.0" y1="349.8" x2="76.0" y2="388.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="71.5" y="357.6" width="9" height="26.0" fill="#188a3e"/>
<line x1="92.0" y1="329.0" x2="92.0" y2="362.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="87.5" y="336.8" width="9" height="20.8" fill="#188a3e"/>
<line x1="108.0" y1="303.0" x2="108.0" y2="342.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="103.5" y="310.8" width="9" height="26.0" fill="#188a3e"/>
<line x1="124.0" y1="277.0" x2="124.0" y2="316.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="119.5" y="284.8" width="9" height="26.0" fill="#188a3e"/>
<line x1="140.0" y1="256.2" x2="140.0" y2="290.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="135.5" y="264.0" width="9" height="20.8" fill="#188a3e"/>
<line x1="156.0" y1="230.2" x2="156.0" y2="269.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="151.5" y="238.0" width="9" height="26.0" fill="#188a3e"/>
<line x1="172.0" y1="232.8" x2="172.0" y2="256.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="167.5" y="238.0" width="9" height="10.4" fill="#c00"/>
<line x1="188.0" y1="243.2" x2="188.0" y2="279.6" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="183.5" y="248.4" width="9" height="20.8" fill="#c00"/>
<line x1="204.0" y1="264.0" x2="204.0" y2="297.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="199.5" y="269.2" width="9" height="20.8" fill="#c00"/>
<line x1="220.0" y1="274.4" x2="220.0" y2="297.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="215.5" y="279.6" width="9" height="10.4" fill="#188a3e"/>
<line x1="236.0" y1="274.4" x2="236.0" y2="300.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="231.5" y="279.6" width="9" height="15.6" fill="#c00"/>
<line x1="252.0" y1="277.0" x2="252.0" y2="297.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="247.5" y="282.2" width="9" height="13.0" fill="#188a3e"/>
<line x1="268.0" y1="269.2" x2="268.0" y2="287.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="263.5" y="274.4" width="9" height="7.8" fill="#188a3e"/>
<line x1="284.0" y1="240.6" x2="284.0" y2="279.6" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="279.5" y="248.4" width="9" height="26.0" fill="#188a3e"/>
<line x1="300.0" y1="219.8" x2="300.0" y2="253.6" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="295.5" y="227.6" width="9" height="20.8" fill="#188a3e"/>
<line x1="316.0" y1="193.8" x2="316.0" y2="232.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="311.5" y="201.6" width="9" height="26.0" fill="#188a3e"/>
<line x1="332.0" y1="167.8" x2="332.0" y2="206.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="327.5" y="175.6" width="9" height="26.0" fill="#188a3e"/>
<line x1="348.0" y1="147.0" x2="348.0" y2="180.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="343.5" y="154.8" width="9" height="20.8" fill="#188a3e"/>
<line x1="364.0" y1="126.2" x2="364.0" y2="160.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="359.5" y="134.0" width="9" height="20.8" fill="#188a3e"/>
<line x1="380.0" y1="105.4" x2="380.0" y2="139.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="375.5" y="113.2" width="9" height="20.8" fill="#188a3e"/>
<line x1="396.0" y1="89.8" x2="396.0" y2="118.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="391.5" y="97.6" width="9" height="15.6" fill="#188a3e"/>
<line x1="412.0" y1="74.2" x2="412.0" y2="102.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="407.5" y="82.0" width="9" height="15.6" fill="#188a3e"/>
<line x1="428.0" y1="56.0" x2="428.0" y2="87.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="423.5" y="66.4" width="9" height="15.6" fill="#188a3e"/>
<line x1="444.0" y1="61.2" x2="444.0" y2="84.6" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="439.5" y="66.4" width="9" height="10.4" fill="#c00"/>
<line x1="460.0" y1="71.6" x2="460.0" y2="100.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="455.5" y="76.8" width="9" height="15.6" fill="#c00"/>
<line x1="476.0" y1="86.2" x2="476.0" y2="105.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="471.5" y="92.4" width="9" height="5.2" fill="#c00"/>
<line x1="492.0" y1="82.0" x2="492.0" y2="105.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="487.5" y="87.2" width="9" height="10.4" fill="#188a3e"/>
<line x1="508.0" y1="82.0" x2="508.0" y2="115.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="503.5" y="87.2" width="9" height="20.8" fill="#c00"/>
<line x1="524.0" y1="102.8" x2="524.0" y2="131.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="519.5" y="108.0" width="9" height="15.6" fill="#c00"/>
<line x1="540.0" y1="118.4" x2="540.0" y2="144.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="535.5" y="123.6" width="9" height="10.4" fill="#c00"/>
<line x1="24" y1="526.4" x2="706" y2="526.4" stroke="#888"/>
<rect x="39.5" y="477.1" width="9" height="49.3" fill="#188a3e" opacity="0.55"/>
<rect x="55.5" y="473.9" width="9" height="52.5" fill="#188a3e" opacity="0.55"/>
<rect x="71.5" y="478.7" width="9" height="47.7" fill="#188a3e" opacity="0.55"/>
<rect x="87.5" y="472.3" width="9" height="54.1" fill="#188a3e" opacity="0.55"/>
<rect x="103.5" y="475.5" width="9" height="50.9" fill="#188a3e" opacity="0.55"/>
<rect x="119.5" y="470.7" width="9" height="55.7" fill="#188a3e" opacity="0.55"/>
<rect x="135.5" y="473.9" width="9" height="52.5" fill="#188a3e" opacity="0.55"/>
<rect x="151.5" y="467.5" width="9" height="58.9" fill="#188a3e" opacity="0.55"/>
<rect x="167.5" y="491.4" width="9" height="35.0" fill="#c00" opacity="0.55"/>
<rect x="183.5" y="494.6" width="9" height="31.8" fill="#c00" opacity="0.55"/>
<rect x="199.5" y="496.2" width="9" height="30.2" fill="#c00" opacity="0.55"/>
<rect x="215.5" y="497.8" width="9" height="28.6" fill="#188a3e" opacity="0.55"/>
<rect x="231.5" y="499.4" width="9" height="27.0" fill="#c00" opacity="0.55"/>
<rect x="247.5" y="500.1" width="9" height="26.2" fill="#188a3e" opacity="0.55"/>
<rect x="263.5" y="498.6" width="9" height="27.8" fill="#188a3e" opacity="0.55"/>
<rect x="279.5" y="461.2" width="9" height="65.2" fill="#188a3e" opacity="0.55"/>
<rect x="295.5" y="456.4" width="9" height="70.0" fill="#188a3e" opacity="0.55"/>
<rect x="311.5" y="469.1" width="9" height="57.3" fill="#188a3e" opacity="0.55"/>
<rect x="327.5" y="470.7" width="9" height="55.7" fill="#188a3e" opacity="0.55"/>
<rect x="343.5" y="467.5" width="9" height="58.9" fill="#188a3e" opacity="0.55"/>
<rect x="359.5" y="472.3" width="9" height="54.1" fill="#188a3e" opacity="0.55"/>
<rect x="375.5" y="469.1" width="9" height="57.3" fill="#188a3e" opacity="0.55"/>
<rect x="391.5" y="473.9" width="9" height="52.5" fill="#188a3e" opacity="0.55"/>
<rect x="407.5" y="470.7" width="9" height="55.7" fill="#188a3e" opacity="0.55"/>
<rect x="423.5" y="465.9" width="9" height="60.5" fill="#188a3e" opacity="0.55"/>
<rect x="439.5" y="475.5" width="9" height="50.9" fill="#c00" opacity="0.55"/>
<rect x="455.5" y="478.7" width="9" height="47.7" fill="#c00" opacity="0.55"/>
<rect x="471.5" y="480.3" width="9" height="46.1" fill="#c00" opacity="0.55"/>
<rect x="487.5" y="477.1" width="9" height="49.3" fill="#188a3e" opacity="0.55"/>
<rect x="503.5" y="481.9" width="9" height="44.5" fill="#c00" opacity="0.55"/>
<rect x="519.5" y="483.4" width="9" height="43.0" fill="#c00" opacity="0.55"/>
<rect x="535.5" y="473.9" width="9" height="52.5" fill="#c00" opacity="0.55"/>
<text x="24" y="450.4" fill="#555">volum</text>
<text x="32.0" y="159.6" fill="#1a5fb4" text-anchor="start" style="font-weight:bold">TF mare (15 min) în Buy</text>
<text x="220.0" y="317.8" fill="#c00" text-anchor="middle" style="font-weight:bold">M: Sell Momentum (pullback)</text>
<text x="284.0" y="108.6" fill="#188a3e" text-anchor="middle" style="font-weight:bold">A: primul Buy = trigger</text>
<text x="428.0" y="132.2" fill="#777" text-anchor="middle" style="font-weight:bold">vârf 103.30</text>
<text x="540.0" y="219.4" fill="#c00" text-anchor="middle" style="font-weight:bold">close sub bull trail = ieși</text>
<text x="540.0" y="237.4" fill="#c00" text-anchor="middle" style="font-weight:bold">ieșire 102.00 pe bull trail (2.2R)</text>
<text x="24" y="556" fill="#555">Serie construită (32 bare, 3 min). R = (102.00 − 99.80) / 1.00 = 2.2R.</text>
<svg viewBox="0 0 720 368" xmlns="http://www.w3.org/2000/svg" style="width:100%;height:auto;font:12px sans-serif">
<defs><marker id="pt" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto"><path d="M0,0 L10,5 L0,10 z" fill="#c00"/></marker><marker id="pt-o" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto"><path d="M0,0 L10,5 L0,10 z" fill="#b06000"/></marker></defs>
<text x="20.0" y="24.0" fill="#333" style="font-size:14px;font-weight:bold">M2D long, tot trade-ul: 15 min dă direcția, 3 min dă intrarea</text>
<text x="20.0" y="42.0" fill="#555" style="font-size:11px">Graficul din dreapta nu e altă bucată de piață: e aceeași, mărită. O candelă de 15 min = 5 candele de 3 min.</text>
<text x="20.0" y="68.0" fill="#555" style="font-size:11px">15 min: direcția</text>
<text x="364.0" y="68.0" fill="#555" style="font-size:11px">3 min: descompunerea ultimelor 4 candele de 15 min</text>
<rect x="171.0" y="111.3" width="16.0" height="53.1" fill="#0891b2" fill-opacity="0.1" stroke="#0891b2" stroke-width="1.2"/>
<line x1="179.0" y1="165.5" x2="179.0" y2="260.0" stroke="#0891b2" stroke-width="1"/>
<text x="149.0" y="274.0" fill="#0891b2" text-anchor="middle" style="font-size:11px;font-weight:bold">Buy Momentum pe 15 min:</text>
<text x="149.0" y="287.0" fill="#0891b2" text-anchor="middle" style="font-size:11px">de aici ai voie doar long</text>
<rect x="187.0" y="75.5" width="60.0" height="83.9" fill="#1a5fb4" fill-opacity="0.06" stroke="#1a5fb4" stroke-width="1.2" stroke-dasharray="4 3"/>
<line x1="90.0" y1="159.5" x2="247.0" y2="159.5" stroke="#555" stroke-width="1.2" stroke-dasharray="2 3"/>
<text x="24.0" y="171.5" fill="#555" style="font-size:10px">minimul din care a pornit</text>
<text x="24.0" y="185.5" fill="#555" style="font-size:10px">mișcarea: 99.80</text>
<line x1="90.0" y1="123.1" x2="247.0" y2="123.1" stroke="#aaa" stroke-width="1.0" stroke-dasharray="3 3"/>
<line x1="104.0" y1="225.3" x2="104.0" y2="248.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="99.5" y="229.8" width="9" height="11.4" fill="#188a3e"/>
<line x1="119.0" y1="209.4" x2="119.0" y2="234.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="114.5" y="213.9" width="9" height="15.9" fill="#188a3e"/>
<line x1="134.0" y1="191.2" x2="134.0" y2="218.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="129.5" y="195.8" width="9" height="18.2" fill="#188a3e"/>
<line x1="149.0" y1="175.4" x2="149.0" y2="200.3" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="144.5" y="182.2" width="9" height="13.6" fill="#188a3e"/>
<line x1="164.0" y1="152.6" x2="164.0" y2="186.7" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="159.5" y="157.2" width="9" height="25.0" fill="#188a3e"/>
<line x1="179.0" y1="116.3" x2="179.0" y2="159.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="174.5" y="123.1" width="9" height="34.1" fill="#188a3e"/>
<line x1="194.0" y1="118.6" x2="194.0" y2="150.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="189.5" y="123.1" width="9" height="25.0" fill="#c00"/>
<line x1="209.0" y1="129.9" x2="209.0" y2="154.9" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="204.5" y="132.2" width="9" height="15.9" fill="#188a3e"/>
<line x1="224.0" y1="98.2" x2="224.0" y2="134.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="219.5" y="105.0" width="9" height="27.2" fill="#188a3e"/>
<line x1="239.0" y1="80.0" x2="239.0" y2="109.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="234.5" y="105.0" width="9" height="2.3" fill="#c00"/>
<line x1="247.0" y1="75.5" x2="364.0" y2="80.0" stroke="#1a5fb4" stroke-width="1.1" stroke-dasharray="5 3"/>
<line x1="247.0" y1="159.4" x2="364.0" y2="248.0" stroke="#1a5fb4" stroke-width="1.1" stroke-dasharray="5 3"/>
<path d="M247.0,123.1 H350.0 V175.4 H364.0" fill="none" stroke="#aaa" stroke-width="1.0" stroke-dasharray="3 3"/>
<text x="304.0" y="153.2" fill="#777" style="font-size:10px">100.60</text>
<text x="304.0" y="166.2" fill="#999" style="font-size:10px">același preț</text>
<rect x="364.0" y="80.0" width="225.0" height="168.0" fill="#1a5fb4" fill-opacity="0.03" stroke="#1a5fb4" stroke-width="1.2"/>
<rect x="365.0" y="161.3" width="113.0" height="82.7" fill="#188a3e" fill-opacity="0.1" stroke="#188a3e" stroke-width="1.2"/>
<line x1="364.0" y1="175.4" x2="589.0" y2="175.4" stroke="#aaa" stroke-width="1.0" stroke-dasharray="3 3"/>
<line x1="364.0" y1="148.1" x2="589.0" y2="148.1" stroke="#188a3e" stroke-width="1.5" stroke-dasharray="2 3"/>
<line x1="364.0" y1="193.5" x2="589.0" y2="193.5" stroke="#188a3e" stroke-width="1.5" stroke-dasharray="6 3"/>
<line x1="364.0" y1="239.0" x2="589.0" y2="239.0" stroke="#c00" stroke-width="1.5" stroke-dasharray="6 3"/>
<line x1="364.0" y1="116.3" x2="589.0" y2="116.3" stroke="#b06000" stroke-width="1.2" stroke-dasharray="2 2"/>
<line x1="372.0" y1="166.3" x2="372.0" y2="193.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="368.5" y="175.4" width="7" height="13.6" fill="#c00"/>
<line x1="383.0" y1="184.5" x2="383.0" y2="207.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="379.5" y="189.0" width="7" height="13.6" fill="#c00"/>
<line x1="394.0" y1="198.1" x2="394.0" y2="216.3" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="390.5" y="202.6" width="7" height="9.1" fill="#c00"/>
<line x1="405.0" y1="207.2" x2="405.0" y2="225.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="401.5" y="211.7" width="7" height="9.1" fill="#c00"/>
<line x1="416.0" y1="216.3" x2="416.0" y2="229.9" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="412.5" y="220.8" width="7" height="4.5" fill="#c00"/>
<line x1="427.0" y1="220.8" x2="427.0" y2="234.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="423.5" y="225.4" width="7" height="4.5" fill="#c00"/>
<line x1="438.0" y1="225.4" x2="438.0" y2="237.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="434.5" y="229.9" width="7" height="4.5" fill="#c00"/>
<line x1="449.0" y1="229.9" x2="449.0" y2="239.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="445.5" y="231.7" width="7" height="2.7" fill="#188a3e"/>
<line x1="460.0" y1="222.6" x2="460.0" y2="235.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="456.5" y="228.1" width="7" height="3.6" fill="#188a3e"/>
<line x1="471.0" y1="189.0" x2="471.0" y2="229.9" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="467.5" y="193.5" width="7" height="34.5" fill="#188a3e"/>
<line x1="482.0" y1="175.4" x2="482.0" y2="198.1" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="478.5" y="179.9" width="7" height="13.6" fill="#188a3e"/>
<line x1="493.0" y1="161.7" x2="493.0" y2="184.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="489.5" y="166.3" width="7" height="13.6" fill="#188a3e"/>
<line x1="504.0" y1="139.0" x2="504.0" y2="170.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="500.5" y="143.5" width="7" height="22.7" fill="#188a3e"/>
<line x1="515.0" y1="129.9" x2="515.0" y2="149.9" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="511.5" y="134.5" width="7" height="9.1" fill="#188a3e"/>
<line x1="526.0" y1="125.4" x2="526.0" y2="143.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="522.5" y="134.5" width="7" height="4.5" fill="#c00"/>
<line x1="537.0" y1="102.6" x2="537.0" y2="143.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="533.5" y="107.2" width="7" height="31.8" fill="#188a3e"/>
<line x1="548.0" y1="89.0" x2="548.0" y2="116.3" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="544.5" y="98.1" width="7" height="9.1" fill="#188a3e"/>
<line x1="559.0" y1="93.5" x2="559.0" y2="120.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="555.5" y="98.1" width="7" height="18.2" fill="#c00"/>
<line x1="570.0" y1="111.7" x2="570.0" y2="134.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="566.5" y="116.3" width="7" height="13.6" fill="#c00"/>
<line x1="581.0" y1="125.4" x2="581.0" y2="148.1" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="577.5" y="129.9" width="7" height="13.6" fill="#c00"/>
<line x1="591.0" y1="116.3" x2="596.0" y2="116.3" stroke="#b06000" stroke-width="0.8"/>
<text x="600.0" y="120.3" fill="#b06000" style="font-size:10px">bull trail 101.25</text>
<text x="600.0" y="134.3" fill="#b06000" style="font-size:10px">urcă, nu coboară</text>
<line x1="591.0" y1="148.1" x2="596.0" y2="148.1" stroke="#188a3e" stroke-width="0.8"/>
<text x="600.0" y="152.1" fill="#188a3e" style="font-size:10px">TP1 100.90 = 1:1</text>
<text x="600.0" y="166.1" fill="#188a3e" style="font-size:10px">iese prima tranșă</text>
<line x1="591.0" y1="193.5" x2="596.0" y2="193.5" stroke="#188a3e" stroke-width="0.8"/>
<text x="600.0" y="197.5" fill="#188a3e" style="font-size:10px">intrare 100.40</text>
<text x="600.0" y="211.5" fill="#188a3e" style="font-size:10px">close-ul trigger-ului</text>
<line x1="591.0" y1="239.0" x2="596.0" y2="239.0" stroke="#c00" stroke-width="0.8"/>
<text x="600.0" y="243.0" fill="#c00" style="font-size:10px">stop 99.90</text>
<text x="600.0" y="257.0" fill="#c00" style="font-size:10px">minimul a 4 bare</text>
<path d="M367.0,250.0 V256.0 H465.0 V250.0" fill="none" stroke="#c00" stroke-width="1"/>
<text x="416.0" y="270.0" fill="#c00" text-anchor="middle" style="font-size:11px;font-weight:bold">M: pullback, 9 bare</text>
<text x="416.0" y="284.0" fill="#c00" text-anchor="middle" style="font-size:11px">fără să rupă minimul</text>
<line x1="570.0" y1="262.0" x2="570.0" y2="139.5" stroke="#b06000" stroke-width="1.1" stroke-dasharray="3 3" marker-end="url(#pt-o)"/>
<text x="589.0" y="270.0" fill="#b06000" text-anchor="end" style="font-size:11px;font-weight:bold">close sub trail</text>
<text x="589.0" y="284.0" fill="#b06000" text-anchor="end" style="font-size:11px">ieșire 101.10 = 1.4R</text>
<line x1="471.0" y1="298.0" x2="471.0" y2="234.9" stroke="#188a3e" stroke-width="1.6" marker-end="url(#pt)"/>
<text x="367.0" y="306.0" fill="#188a3e" style="font-size:11px;font-weight:bold">A: primul Buy după pullback — aici intri,</text>
<text x="367.0" y="320.0" fill="#188a3e" style="font-size:11px">pentru că 15 min e încă în Buy.</text>
<text x="367.0" y="334.0" fill="#555" style="font-size:11px">Wick-urile nu contează: se citește close-ul.</text>
<text x="20.0" y="350.0" fill="#555" style="font-size:11px">Serie construită, fără ticker. Risc = 100.40 − 99.90 = 0.50; TP1 la aceeași distanță în sus; ieșirea pe trail la 1.4R.</text>
</svg>

Before

Width:  |  Height:  |  Size: 9.7 KiB

After

Width:  |  Height:  |  Size: 10 KiB

View File

@@ -0,0 +1,114 @@
#!/usr/bin/env python3
"""M2D, pasul 1 — direcția o dă TF-ul mare: DA (rămâne în Buy) vs. NU (se închide Sell).
Pentru `summaries/17_STRATEGII/03_m2d.md`, marcajul `<!-- DIAGRAMA: directie -->`.
Ambele panouri sunt graficul de 15 minute al aceluiași exemplu construit ca în
diagrama de ansamblu — primele 6 candele sunt identice, deci cifrele din
capitol nu se bat cap în cap de la o diagramă la alta.
Sus: pullback-ul de pe 3 min lasă pe 15 min o candelă roșie, dar indicatorul
nu dă Sell și candela nu se închide sub nivelul semnalului — setup-ul long
trăiește. Jos: candela de 15 min se ÎNCHIDE Sell — setup-ul long e anulat.
Rulare: python3 gen/strategii/m2d_directie.py
"""
import sys
from pathlib import Path
sys.path.insert(0, "/workspace/atm-curs-dl")
sys.path.insert(0, str(Path(__file__).resolve().parent))
from svg_candles import Arrow, Candle, Chart, Mark # noqa: E402
import m2d_duo as duo # noqa: E402
OUT = Path(__file__).with_suffix(".svg")
W, SCALE, GAP, LEFT = 680, 46.0, 46, 74
GREEN, RED, BLUE, CYAN, GREY = "#188a3e", "#c00", "#1a5fb4", "#0891b2", "#555"
# --- aceleași candele de 15 min ca în diagrama de ansamblu -----------------
BASE = [(98.00, 98.35, 97.85, 98.25), (98.25, 98.70, 98.15, 98.60),
(98.60, 99.10, 98.50, 99.00), (99.00, 99.45, 98.90, 99.30),
(99.30, 99.95, 99.20, 99.85),
(99.85, 100.75, 99.80, 100.60)] # 5: Buy Momentum pe 15 min
DA = BASE + [(100.60, 100.70, 100.00, 100.05), # 6: roșie, dar nu Sell
(100.05, 100.45, 99.90, 100.40), # 7: aici cade trigger-ul de pe 3 min
(100.40, 101.15, 100.35, 101.00)]
NU = BASE + [(100.60, 100.70, 100.00, 100.05),
(100.05, 100.10, 99.20, 99.30), # 7: se ÎNCHIDE Sell — setup anulat
(99.30, 99.40, 98.60, 98.75)]
SIG = 5
# ---------------------------------------------------------------- verificări
for nume, bars in (("DA", DA), ("NU", NU)):
for i, (o, h, l, c) in enumerate(bars):
assert h >= max(o, c) and l <= min(o, c), f"{nume}[{i}]: high/low incoerent"
if i:
assert abs(o - bars[i - 1][3]) < 1e-9, f"{nume}[{i}]: open != close precedent"
assert DA[:SIG + 2] == NU[:SIG + 2], "panourile nu pornesc din aceeași serie"
SEMNAL = BASE[SIG][3] # 100.60 — același nivel ca în diagrama de ansamblu
REF_LOW = BASE[SIG][2] # 99.80 — minimul din care a pornit mișcarea
assert BASE[SIG][3] > BASE[SIG][0], "candela de semnal nu e verde"
# DA: nicio candelă de după semnal nu se închide sub minimul de referință
assert all(b[3] > REF_LOW for b in DA[SIG + 1:]), "DA: o candelă închide sub referință"
assert DA[6][3] < DA[6][0], "DA: candela 6 ar trebui să fie roșie (pullback pe 3 min)"
assert DA[6][3] > REF_LOW, "DA: candela roșie rupe deja referința"
# NU: exact una rupe — a șaptea, care se închide sub minimul de referință
rupe = [i for i, b in enumerate(NU) if i > SIG and b[3] < REF_LOW]
assert rupe == [7, 8], rupe
assert NU[7][3] < NU[7][0] and NU[7][3] < REF_LOW, "NU: candela 7 nu se închide Sell"
def chart(bars, marks, arrows, extra_h=0.0):
return Chart(candles=[Candle(*b) for b in bars], marks=marks, arrows=arrows,
scale=SCALE, gap=GAP, body_w=15, left=LEFT, width=W, top=64,
label_gap=34 + extra_h)
TITLU_DA = "DA — 15 min e încă în Buy când vine trigger-ul de pe 3 min"
TITLU_NU = "NU — 15 min s-a închis Sell: setup-ul long e anulat"
ch_da = chart(
DA,
[],
[Arrow(SIG, "Buy Momentum pe 15 min:\nde aici ai voie doar long",
where="below", color=CYAN, dx=-60, dy=54),
Arrow(6, "candelă roșie pe 15 min, dar close-ul\nrămâne peste 99.80: pullback, nu întoarcere",
where="below", color=GREY, dx=-30, dy=26),
Arrow(7, "aici cade trigger-ul de pe 3 min",
where="above", color=GREEN, dx=-24, dy=18)])
ch_nu = chart(
NU,
[],
[Arrow(SIG, "Buy Momentum pe 15 min:\nde aici ai voie doar long",
where="below", color=CYAN, dx=-60, dy=54),
Arrow(7, "close 99.30, sub 99.80: direcția mare\ns-a răsucit — nu mai intri long",
where="below", color=RED, dx=-40, dy=20),
Arrow(8, "de aici cauți short, nu long", where="below", color=RED, dx=-120, dy=82)])
body_da, h_da = duo.panel(ch_da, W)
body_nu, h_nu = duo.panel(ch_nu, W)
extra_da = (duo.titlu(TITLU_DA, color=GREEN)
+ duo.zone(ch_da, SIG, 7, "zona în care ai voie long", GREEN, pad=20, lift=26)
+ duo.level(ch_da, SEMNAL, ["close-ul candelei de semnal 100.60"], CYAN, W,
below=False, at_left=False)
+ duo.level(ch_da, REF_LOW, ["minimul din care a pornit mișcarea 99.80"],
GREY, W, below=True, dash="2 3"))
extra_nu = (duo.titlu(TITLU_NU, color=RED)
+ duo.zone(ch_nu, 7, 8, "setup anulat", RED, pad=20)
+ duo.level(ch_nu, SEMNAL, ["close-ul candelei de semnal 100.60"], CYAN, W,
below=False, at_left=False)
+ duo.level(ch_nu, REF_LOW, ["minimul din care a pornit mișcarea 99.80"],
GREY, W, below=True, dash="2 3"))
svg, H, n = duo.build(
[("DA", body_da, h_da, extra_da), ("NU", body_nu, h_nu, extra_nu)],
["Același grafic de 15 minute ca în diagrama de ansamblu; serie construită, fără ticker.",
"Pe 15 min se citește DIRECȚIA, nu intrarea. Intrarea vine de pe 3 min (pasul 3)."],
W)
duo.fara_preturi_straine(svg, [SEMNAL, REF_LOW, NU[7][3]])
OUT.write_text(svg, encoding="utf-8")
print(f"scris {OUT} ({W}x{H:.0f}), {n} etichete | semnal {SEMNAL:.2f}, referință {REF_LOW:.2f} | "
f"DA: ultima închidere {DA[-1][3]:.2f} | NU: candela 7 închide {NU[7][3]:.2f}")

View File

@@ -0,0 +1,66 @@
<svg viewBox="0 0 680 681" xmlns="http://www.w3.org/2000/svg" style="width:100%;height:auto;font:12px sans-serif">
<g transform="translate(0,0)"><text x="24" y="22" fill="#188a3e" style="font-weight:bold">DA — 15 min e încă în Buy când vine trigger-ul de pe 3 min</text><rect x="284.0" y="48.4" width="132.0" height="85.7" fill="#188a3e" fill-opacity="0.12" stroke="#188a3e" stroke-width="1"/><text x="290.0" y="41.4" fill="#188a3e" style="font-weight:bold">zona în care ai voie long</text><line x1="24" y1="89.3" x2="666" y2="89.3" stroke="#0891b2" stroke-width="1.5" stroke-dasharray="4 3"/><text x="656" y="83.3" fill="#0891b2" text-anchor="end" style="font-weight:bold">close-ul candelei de semnal 100.60</text><line x1="24" y1="126.1" x2="666" y2="126.1" stroke="#555" stroke-width="1.5" stroke-dasharray="2 3"/><text x="30" y="147.1" fill="#555" text-anchor="start" style="font-weight:bold">minimul din care a pornit mișcarea 99.80</text>
<line x1="74.0" y1="192.8" x2="74.0" y2="215.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="66.5" y="197.4" width="15" height="11.5" fill="#188a3e"/>
<line x1="120.0" y1="176.7" x2="120.0" y2="202.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="112.5" y="181.3" width="15" height="16.1" fill="#188a3e"/>
<line x1="166.0" y1="158.3" x2="166.0" y2="185.9" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="158.5" y="162.9" width="15" height="18.4" fill="#188a3e"/>
<line x1="212.0" y1="142.2" x2="212.0" y2="167.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="204.5" y="149.1" width="15" height="13.8" fill="#188a3e"/>
<line x1="258.0" y1="119.2" x2="258.0" y2="153.7" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="250.5" y="123.8" width="15" height="25.3" fill="#188a3e"/>
<line x1="304.0" y1="82.4" x2="304.0" y2="126.1" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="296.5" y="89.3" width="15" height="34.5" fill="#188a3e"/>
<line x1="350.0" y1="84.7" x2="350.0" y2="116.9" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="342.5" y="89.3" width="15" height="25.3" fill="#c00"/>
<line x1="396.0" y1="96.2" x2="396.0" y2="121.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="388.5" y="98.5" width="15" height="16.1" fill="#188a3e"/>
<line x1="442.0" y1="64.0" x2="442.0" y2="100.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="434.5" y="70.9" width="15" height="27.6" fill="#188a3e"/>
<line x1="244.0" y1="247.9" x2="300.4" y2="137.2" stroke="#0891b2" stroke-width="1.8"/>
<polygon points="304.0,130.1 303.9,139.0 296.8,135.4" fill="#0891b2"/>
<text x="244.0" y="261.9" fill="#0891b2" text-anchor="middle" style="font-weight:bold">Buy Momentum pe 15 min:</text>
<text x="244.0" y="275.9" fill="#0891b2" text-anchor="middle" style="font-weight:bold">de aici ai voie doar long</text>
<line x1="320.0" y1="201.5" x2="347.2" y2="128.4" stroke="#555" stroke-width="1.8"/>
<polygon points="350.0,120.9 351.0,129.8 343.5,127.0" fill="#555"/>
<text x="320.0" y="215.5" fill="#555" text-anchor="middle" style="font-weight:bold">candelă roșie pe 15 min, dar close-ul</text>
<text x="320.0" y="229.5" fill="#555" text-anchor="middle" style="font-weight:bold">rămâne peste 99.80: pullback, nu întoarcere</text>
<line x1="372.0" y1="74.0" x2="389.6" y2="87.4" stroke="#188a3e" stroke-width="1.8"/>
<polygon points="396.0,92.2 387.2,90.6 392.0,84.2" fill="#188a3e"/>
<text x="372.0" y="69.0" fill="#188a3e" text-anchor="middle" style="font-weight:bold">aici cade trigger-ul de pe 3 min</text>
</g>
<g transform="translate(0,311)"><text x="24" y="22" fill="#c00" style="font-weight:bold">NU — 15 min s-a închis Sell: setup-ul long e anulat</text><rect x="376.0" y="85.9" width="86.0" height="85.0" fill="#c00" fill-opacity="0.12" stroke="#c00" stroke-width="1"/><text x="382.0" y="78.9" fill="#c00" style="font-weight:bold">setup anulat</text><line x1="24" y1="70.9" x2="666" y2="70.9" stroke="#0891b2" stroke-width="1.5" stroke-dasharray="4 3"/><text x="656" y="64.9" fill="#0891b2" text-anchor="end" style="font-weight:bold">close-ul candelei de semnal 100.60</text><line x1="24" y1="107.7" x2="666" y2="107.7" stroke="#555" stroke-width="1.5" stroke-dasharray="2 3"/><text x="30" y="128.7" fill="#555" text-anchor="start" style="font-weight:bold">minimul din care a pornit mișcarea 99.80</text>
<line x1="74.0" y1="174.4" x2="74.0" y2="197.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="66.5" y="179.0" width="15" height="11.5" fill="#188a3e"/>
<line x1="120.0" y1="158.3" x2="120.0" y2="183.6" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="112.5" y="162.9" width="15" height="16.1" fill="#188a3e"/>
<line x1="166.0" y1="139.9" x2="166.0" y2="167.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="158.5" y="144.5" width="15" height="18.4" fill="#188a3e"/>
<line x1="212.0" y1="123.8" x2="212.0" y2="149.1" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="204.5" y="130.7" width="15" height="13.8" fill="#188a3e"/>
<line x1="258.0" y1="100.8" x2="258.0" y2="135.3" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="250.5" y="105.4" width="15" height="25.3" fill="#188a3e"/>
<line x1="304.0" y1="64.0" x2="304.0" y2="107.7" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="296.5" y="70.9" width="15" height="34.5" fill="#188a3e"/>
<line x1="350.0" y1="66.3" x2="350.0" y2="98.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="342.5" y="70.9" width="15" height="25.3" fill="#c00"/>
<line x1="396.0" y1="93.9" x2="396.0" y2="135.3" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="388.5" y="96.2" width="15" height="34.5" fill="#c00"/>
<line x1="442.0" y1="126.1" x2="442.0" y2="162.9" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="434.5" y="130.7" width="15" height="25.3" fill="#c00"/>
<line x1="244.0" y1="229.5" x2="300.4" y2="118.8" stroke="#0891b2" stroke-width="1.8"/>
<polygon points="304.0,111.7 303.9,120.6 296.8,117.0" fill="#0891b2"/>
<text x="244.0" y="243.5" fill="#0891b2" text-anchor="middle" style="font-weight:bold">Buy Momentum pe 15 min:</text>
<text x="244.0" y="257.5" fill="#0891b2" text-anchor="middle" style="font-weight:bold">de aici ai voie doar long</text>
<line x1="356.0" y1="190.9" x2="391.1" y2="145.6" stroke="#c00" stroke-width="1.8"/>
<polygon points="396.0,139.3 394.3,148.1 387.9,143.2" fill="#c00"/>
<text x="356.0" y="204.9" fill="#c00" text-anchor="middle" style="font-weight:bold">close 99.30, sub 99.80: direcția mare</text>
<text x="356.0" y="218.9" fill="#c00" text-anchor="middle" style="font-weight:bold">s-a răsucit — nu mai intri long</text>
<line x1="322.0" y1="274.9" x2="436.1" y2="172.3" stroke="#c00" stroke-width="1.8"/>
<polygon points="442.0,166.9 438.7,175.2 433.4,169.3" fill="#c00"/>
<text x="322.0" y="288.9" fill="#c00" text-anchor="middle" style="font-weight:bold">de aici cauți short, nu long</text>
</g>
<text x="24" y="641" fill="#555">Același grafic de 15 minute ca în diagrama de ansamblu; serie construită, fără ticker.</text>
<text x="24" y="658" fill="#555">Pe 15 min se citește DIRECȚIA, nu intrarea. Intrarea vine de pe 3 min (pasul 3).</text>
</svg>

After

Width:  |  Height:  |  Size: 6.9 KiB

124
gen/strategii/m2d_duo.py Normal file
View File

@@ -0,0 +1,124 @@
"""Ajutor comun pentru diagramele capitolului M2D cu două panouri (DA / NU).
Tiparul e cel din `buy_doji_fereastra.py`: două `Chart`-uri desenate cu
`_render()` și puse unul sub altul într-un singur SVG, plus verificarea că
nicio etichetă nu iese din pânză și că nicio pereche de etichete de pe
ACELAȘI panou nu se suprapune. E scos aici ca să nu fie copiat în patru
generatoare; nu are alt rol.
Nu se rulează singur: îl importă `m2d_directie.py`, `m2d_secventa.py`,
`m2d_trigger.py`, `m2d_stop.py`, `m2d_iesire.py`.
"""
import re
TEXT = re.compile(r'<text x="([-\d.]+)" y="([-\d.]+)"([^>]*)>(.*?)</text>', re.S)
GREY = "#555"
def panel(ch, width):
"""Corpul SVG al unui Chart, fără <svg>, plus înălțimea lui.
`_render()`, nu `render()`: al doilea restaurează top/left după desen,
iar liniile desenate de mână ar cădea deplasate față de corp.
"""
svg = ch._render()
m = re.search(r'viewBox="0 0 ([\d.]+) ([\d.]+)"', svg)
assert m, "viewBox lipsă"
assert float(m.group(1)) == width, f"textul unei săgeți iese din {width}px"
return svg[svg.index(">") + 1:svg.rindex("</svg>")], float(m.group(2))
def level(ch, price, lines, color, width, below=False, at_left=True, dash="4 3"):
"""Linie punctată pe toată lățimea + etichetă, desenată de mână.
Eticheta din `Level` stă lipită de linie și cade peste candelele care o
traversează; aici o pun în marginea liberă a panoului.
"""
y = ch._y(price)
out = [f'<line x1="24" y1="{y:.1f}" x2="{width - 14}" y2="{y:.1f}" stroke="{color}" '
f'stroke-width="1.5" stroke-dasharray="{dash}"/>']
for k, s in enumerate(lines):
ty = (y + 21 + 14 * k) if below else (y - 6 - 14 * (len(lines) - 1 - k))
tx, anchor = (30, "start") if at_left else (width - 24, "end")
out.append(f'<text x="{tx}" y="{ty:.1f}" fill="{color}" text-anchor="{anchor}" '
f'style="font-weight:bold">{s}</text>')
return out
def zone(ch, i0, i1, label, color, lift=0.0, pad=16):
"""Dreptunghi de evidențiere peste candelele i0..i1, cu eticheta deasupra."""
cds = ch.candles[i0:i1 + 1]
x0, x1 = ch._cx(i0) - pad, ch._cx(i1) + pad
y0 = ch._y(max(c.h for c in cds)) - 8 - lift
y1 = ch._y(min(c.l for c in cds)) + 8
out = [f'<rect x="{x0:.1f}" y="{y0:.1f}" width="{x1 - x0:.1f}" height="{y1 - y0:.1f}" '
f'fill="{color}" fill-opacity="0.12" stroke="{color}" stroke-width="1"/>']
if label:
out.append(f'<text x="{x0 + 6:.1f}" y="{y0 - 7:.1f}" fill="{color}" '
f'style="font-weight:bold">{label}</text>')
return out
def titlu(s, y=22, color="#333", x=24):
"""Titlul panoului, desenat de mână.
`Chart(title=...)` îl pune la `top - 8`, adică la 7 px de eticheta unei
zone desenate la `y(pmax) - 15` — se calcă mereu, indiferent de `top`.
"""
return [f'<text x="{x}" y="{y}" fill="{color}" style="font-weight:bold">{s}</text>']
def _texts(body, y_off, tag):
out = []
for m in TEXT.finditer(body):
x, y = float(m.group(1)), float(m.group(2)) + y_off
attrs, s = m.group(3), m.group(4)
anchor = ("end" if 'text-anchor="end"' in attrs
else "middle" if 'text-anchor="middle"' in attrs else "start")
w = len(s) * 12 * (0.62 if "bold" in attrs else 0.52)
left = x - w if anchor == "end" else (x - w / 2 if anchor == "middle" else x)
out.append((left, left + w, y, s, tag))
return out
def build(panels, captions, width, vgap=0):
"""`panels` = listă de (eticheta panoului, corp SVG, înălțime, extra).
Întoarce SVG-ul întreg, după ce verifică etichetele.
"""
parts = [f'<svg viewBox="0 0 {width} {{H}}" xmlns="http://www.w3.org/2000/svg" '
f'style="width:100%;height:auto;font:12px sans-serif">']
labels, off = [], 0.0
for tag, body, h, extra in panels:
parts.append(f'<g transform="translate(0,{off:.0f})">{"".join(extra)}{body}</g>')
labels += _texts(body + "".join(extra), off, tag)
off += h + vgap
cap_top = off + 6
for i, c in enumerate(captions):
assert "<" not in c
parts.append(f'<text x="24" y="{cap_top + 17 * i:.0f}" fill="{GREY}">{c}</text>')
labels.append((24, 24 + len(c) * 6.3, cap_top + 17 * i, c, "cap"))
H = cap_top + 17 * len(captions) + 6
parts.append("</svg>")
svg = "\n ".join(parts).replace("{H}", f"{H:.0f}")
assert "\n\n" not in svg, "linie goală în SVG"
assert ".md:" not in svg, "trimitere fișier:linie în SVG"
for left, right, y, s, tag in labels:
assert left >= -2 and right <= width + 2, f"[{tag}] text iese din pânză: {s!r}"
assert 0 <= y <= H, f"[{tag}] text în afara înălțimii: {s!r} (y={y:.0f})"
for i in range(len(labels)):
li, ri, yi, si, ti = labels[i]
for j in range(i + 1, len(labels)):
lj, rj, yj, sj, tj = labels[j]
if ti == tj and abs(yi - yj) < 12 and li < rj - 4 and lj < ri - 4:
raise AssertionError(f"[{ti}] texte suprapuse: {si!r} / {sj!r}")
return svg, H, len(labels)
def fara_preturi_straine(svg, permise):
"""Nicio etichetă nu numește un `\\d+\\.\\d\\d` care nu e un nivel desenat."""
gasite = {float(x) for x in re.findall(r"\d+\.\d\d", " ".join(
m.group(4) for m in TEXT.finditer(svg)))}
strain = {g for g in gasite if not any(abs(g - p) < 0.005 for p in permise)}
assert not strain, f"prețuri în etichete care nu sunt niveluri desenate: {sorted(strain)}"

169
gen/strategii/m2d_iesire.py Normal file
View File

@@ -0,0 +1,169 @@
#!/usr/bin/env python3
"""M2D, pasul 5 — ieșirea: TP1 la 1:1 și restul pe trail, long și short.
Pentru `summaries/17_STRATEGII/03_m2d.md`, marcajul `<!-- DIAGRAMA: iesire -->`.
Sus, LONG: barele de după trigger din seria de 3 minute a diagramei de
ansamblu. Prima tranșă iese la TP1 = 1:1. Restul merge pe bull trail: linia
sare pe low-ul fiecărei bare care se ÎNCHIDE peste high-ul precedentei și nu
coboară niciodată. Ieșirea vine la prima bară care se închide sub linie —
fitilul de dedesubt al barei dinainte nu scoate pe nimeni.
Jos, SHORT: exact aceeași serie oglindită în jurul lui 100.00 (p' = 200 − p),
deci bear trail-ul e construit de același cod, doar cu semnele întoarse.
Oglinda e calculată, nu desenată din ochi.
Rulare: python3 gen/strategii/m2d_iesire.py
"""
import sys
from pathlib import Path
sys.path.insert(0, "/workspace/atm-curs-dl")
sys.path.insert(0, str(Path(__file__).resolve().parent))
from svg_candles import Arrow, Candle, Chart # noqa: E402
import m2d_duo as duo # noqa: E402
OUT = Path(__file__).with_suffix(".svg")
W, SCALE, GAP, LEFT = 680, 150.0, 40, 66
GREEN, RED, ORANGE, GREY = "#188a3e", "#c00", "#b06000", "#555"
OGLINDA = 200.0 # axa de oglindire: p' = OGLINDA − p
LONG = [(99.98, 100.08, 99.94, 100.02),
(100.02, 100.45, 100.00, 100.40), # 1: trigger, intrare
(100.40, 100.60, 100.35, 100.55), (100.55, 100.75, 100.50, 100.70),
(100.70, 101.00, 100.65, 100.95), # 4: TP1 atins
(100.95, 101.10, 100.88, 101.05), (101.05, 101.15, 100.95, 101.00),
(101.00, 101.40, 100.95, 101.35), (101.35, 101.55, 101.25, 101.45),
(101.45, 101.50, 101.20, 101.25), # 9: fitil sub linie, close peste
(101.25, 101.30, 101.05, 101.10), # 10: close sub linie = ieși
(101.10, 101.15, 100.90, 100.95)]
TRIG, SL_LONG = 1, 99.90 # stopul vine din pasul 4
SHORT = [(OGLINDA - o, OGLINDA - l, OGLINDA - h, OGLINDA - c) for o, h, l, c in LONG]
SL_SHORT = OGLINDA - SL_LONG
def trepte(bars, trig, sus):
"""Linia de trail, bară cu bară, de la trigger încolo.
sus=True: bull trail — sare pe low-ul barei care se ÎNCHIDE peste high-ul
precedentei. sus=False: bear trail — pe high-ul barei care se închide sub
low-ul precedentei. `None` cât timp linia nu există încă.
"""
linie, out, iesire = None, [], None
for i, (o, h, l, c) in enumerate(bars):
if i > trig and linie is not None and ((c < linie) if sus else (c > linie)):
iesire = i
out.append(linie)
break
if i > trig:
p = bars[i - 1]
if (c > p[1]) if sus else (c < p[2]):
linie = l if sus else h
out.append(linie)
return out + [linie] * (len(bars) - len(out)), iesire
# ---------------------------------------------------------------- verificări
for nume, bars in (("LONG", LONG), ("SHORT", SHORT)):
for i, (o, h, l, c) in enumerate(bars):
assert h >= max(o, c) and l <= min(o, c), f"{nume}[{i}]: high/low incoerent"
if i:
assert abs(o - bars[i - 1][3]) < 1e-9, f"{nume}[{i}]: open != close precedent"
E_L, E_S = LONG[TRIG][3], SHORT[TRIG][3]
R_L, R_S = round(E_L - SL_LONG, 2), round(SL_SHORT - E_S, 2)
TP_L, TP_S = round(E_L + R_L, 2), round(E_S - R_S, 2)
assert (E_L, R_L, TP_L) == (100.40, 0.50, 100.90), (E_L, R_L, TP_L)
assert (E_S, R_S, TP_S) == (99.60, 0.50, 99.10), (E_S, R_S, TP_S)
assert abs((OGLINDA - E_L) - E_S) < 1e-9 and abs((OGLINDA - TP_L) - TP_S) < 1e-9, \
"oglinda nu e exactă"
TP_BAR_L = next(i for i in range(TRIG + 1, len(LONG)) if LONG[i][1] >= TP_L - 1e-9)
TP_BAR_S = next(i for i in range(TRIG + 1, len(SHORT)) if SHORT[i][2] <= TP_S + 1e-9)
assert TP_BAR_L == TP_BAR_S == 4, (TP_BAR_L, TP_BAR_S)
LIN_L, EX_L = trepte(LONG, TRIG, True)
LIN_S, EX_S = trepte(SHORT, TRIG, False)
assert EX_L == EX_S == 10, (EX_L, EX_S)
TRAIL_L, TRAIL_S = LIN_L[EX_L], LIN_S[EX_S]
assert (TRAIL_L, TRAIL_S) == (101.25, 98.75), (TRAIL_L, TRAIL_S)
assert abs((OGLINDA - TRAIL_L) - TRAIL_S) < 1e-9, "trail-ul oglindit nu se potrivește"
# linia urcă (long) / coboară (short) și nu se întoarce niciodată
pas_l = [v for v in LIN_L[:EX_L + 1] if v is not None]
pas_s = [v for v in LIN_S[:EX_S + 1] if v is not None]
assert all(b >= a for a, b in zip(pas_l, pas_l[1:])), "bull trail-ul coboară"
assert all(b <= a for a, b in zip(pas_s, pas_s[1:])), "bear trail-ul urcă"
# bara 9: fitilul trece de linie, close-ul nu — deci nu scoate
assert LONG[9][2] < TRAIL_L <= LONG[9][3], "bara 9 nu mai ilustrează fitilul sub linie"
assert SHORT[9][1] > TRAIL_S >= SHORT[9][3], "oglinda barei 9 nu ține"
EXIT_L, EXIT_S = LONG[EX_L][3], SHORT[EX_S][3]
RR_L = round((EXIT_L - E_L) / R_L, 1)
RR_S = round((E_S - EXIT_S) / R_S, 1)
assert RR_L == RR_S == 1.4, (RR_L, RR_S)
def chart(bars, arrows, top=70):
return Chart(candles=[Candle(*b) for b in bars], arrows=arrows, scale=SCALE,
gap=GAP, body_w=14, left=LEFT, width=W, top=top)
def scara(ch, linie, pana_la, color):
"""Linia de trail desenată ca scară: un palier pe fiecare bară."""
seg, prev = [], None
for i, v in enumerate(linie[:pana_la + 1]):
if v is None:
continue
y, x0, x1 = ch._y(v), ch._cx(i) - GAP / 2, ch._cx(i) + GAP / 2
seg.append(f'<line x1="{x0:.1f}" y1="{y:.1f}" x2="{x1:.1f}" y2="{y:.1f}" '
f'stroke="{color}" stroke-width="2"/>')
if prev is not None and abs(prev - v) > 1e-9:
seg.append(f'<line x1="{x0:.1f}" y1="{ch._y(prev):.1f}" x2="{x0:.1f}" '
f'y2="{y:.1f}" stroke="{color}" stroke-width="1.2" '
f'stroke-dasharray="2 2"/>')
prev = v
return seg
ch_l = chart(LONG, [
Arrow(TP_BAR_L, "prima tranșă iese aici, la TP1",
where="above", color=GREEN, dx=-60, dy=-8),
Arrow(9, "fitilul trece sub linie, close-ul nu:\nnu ieși pe fitil",
where="below", color=GREY, dx=-30, dy=86),
Arrow(EX_L, "prima bară ÎNCHISĂ sub linie:\niese restul poziției, 1.4R",
where="below", color=ORANGE, dx=-20, dy=18)])
ch_s = chart(SHORT, [
Arrow(TP_BAR_S, "prima tranșă iese aici, la TP1",
where="below", color=GREEN, dx=-60, dy=20),
Arrow(EX_S, "prima bară ÎNCHISĂ peste linie:\niese restul poziției, 1.4R",
where="above", color=ORANGE, dx=-20, dy=-8)])
body_l, h_l = duo.panel(ch_l, W)
body_s, h_s = duo.panel(ch_s, W)
extra_l = (duo.titlu("LONG — bull trail: linia urcă, nu coboară", color=GREEN)
+ scara(ch_l, LIN_L, EX_L, ORANGE)
+ duo.level(ch_l, TP_L, ["TP1 100.90 = 1:1"], GREEN, W, at_left=False)
+ duo.level(ch_l, E_L, ["intrare 100.40"], GREEN, W, below=True, at_left=False)
+ duo.level(ch_l, SL_LONG, ["stop 99.90 (pasul 4)"], RED, W, below=True))
extra_s = (duo.titlu("SHORT — bear trail: exact aceeași serie, oglindită", color=RED)
+ scara(ch_s, LIN_S, EX_S, ORANGE)
+ duo.level(ch_s, SL_SHORT, ["stop 100.10 (pasul 4)"], RED, W, at_left=False)
+ duo.level(ch_s, E_S, ["intrare 99.60"], GREEN, W, below=False)
+ duo.level(ch_s, TP_S, ["TP1 99.10 = 1:1"], GREEN, W, below=True,
at_left=False))
svg, H, n = duo.build(
[("LONG", body_l, h_l, extra_l), ("SHORT", body_s, h_s, extra_s)],
["Serii construite, aceleași cifre ca în restul capitolului.",
"Scara portocalie e linia de trail, palier cu palier.",
"Panoul de jos e panoul de sus oglindit în jurul lui 100.00, calculat, "
"nu desenat din ochi."],
W)
duo.fara_preturi_straine(svg, [E_L, TP_L, SL_LONG, TRAIL_L, EXIT_L,
E_S, TP_S, SL_SHORT, TRAIL_S, EXIT_S, OGLINDA, 100.00])
OUT.write_text(svg, encoding="utf-8")
print(f"scris {OUT} ({W}x{H:.0f}), {n} etichete | LONG: intrare {E_L:.2f} SL {SL_LONG:.2f} "
f"TP1 {TP_L:.2f} la bara {TP_BAR_L}, trail {TRAIL_L:.2f}, ieșire bara {EX_L} la "
f"{EXIT_L:.2f} = {RR_L}R | SHORT (oglindit): intrare {E_S:.2f} SL {SL_SHORT:.2f} "
f"TP1 {TP_S:.2f}, trail {TRAIL_S:.2f}, ieșire {EXIT_S:.2f} = {RR_S}R")

View File

@@ -0,0 +1,75 @@
<svg viewBox="0 0 680 737" xmlns="http://www.w3.org/2000/svg" style="width:100%;height:auto;font:12px sans-serif">
<g transform="translate(0,0)"><text x="24" y="22" fill="#188a3e" style="font-weight:bold">LONG — bull trail: linia urcă, nu coboară</text><line x1="126.0" y1="250.0" x2="166.0" y2="250.0" stroke="#b06000" stroke-width="2"/><line x1="166.0" y1="227.5" x2="206.0" y2="227.5" stroke="#b06000" stroke-width="2"/><line x1="166.0" y1="250.0" x2="166.0" y2="227.5" stroke="#b06000" stroke-width="1.2" stroke-dasharray="2 2"/><line x1="206.0" y1="205.0" x2="246.0" y2="205.0" stroke="#b06000" stroke-width="2"/><line x1="206.0" y1="227.5" x2="206.0" y2="205.0" stroke="#b06000" stroke-width="1.2" stroke-dasharray="2 2"/><line x1="246.0" y1="170.5" x2="286.0" y2="170.5" stroke="#b06000" stroke-width="2"/><line x1="246.0" y1="205.0" x2="246.0" y2="170.5" stroke="#b06000" stroke-width="1.2" stroke-dasharray="2 2"/><line x1="286.0" y1="170.5" x2="326.0" y2="170.5" stroke="#b06000" stroke-width="2"/><line x1="326.0" y1="160.0" x2="366.0" y2="160.0" stroke="#b06000" stroke-width="2"/><line x1="326.0" y1="170.5" x2="326.0" y2="160.0" stroke="#b06000" stroke-width="1.2" stroke-dasharray="2 2"/><line x1="366.0" y1="115.0" x2="406.0" y2="115.0" stroke="#b06000" stroke-width="2"/><line x1="366.0" y1="160.0" x2="366.0" y2="115.0" stroke="#b06000" stroke-width="1.2" stroke-dasharray="2 2"/><line x1="406.0" y1="115.0" x2="446.0" y2="115.0" stroke="#b06000" stroke-width="2"/><line x1="446.0" y1="115.0" x2="486.0" y2="115.0" stroke="#b06000" stroke-width="2"/><line x1="24" y1="167.5" x2="666" y2="167.5" stroke="#188a3e" stroke-width="1.5" stroke-dasharray="4 3"/><text x="656" y="161.5" fill="#188a3e" text-anchor="end" style="font-weight:bold">TP1 100.90 = 1:1</text><line x1="24" y1="242.5" x2="666" y2="242.5" stroke="#188a3e" stroke-width="1.5" stroke-dasharray="4 3"/><text x="656" y="263.5" fill="#188a3e" text-anchor="end" style="font-weight:bold">intrare 100.40</text><line x1="24" y1="317.5" x2="666" y2="317.5" stroke="#c00" stroke-width="1.5" stroke-dasharray="4 3"/><text x="30" y="338.5" fill="#c00" text-anchor="start" style="font-weight:bold">stop 99.90 (pasul 4)</text>
<line x1="66.0" y1="290.5" x2="66.0" y2="311.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="59.0" y="299.5" width="14" height="6.0" fill="#188a3e"/>
<line x1="106.0" y1="235.0" x2="106.0" y2="302.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="99.0" y="242.5" width="14" height="57.0" fill="#188a3e"/>
<line x1="146.0" y1="212.5" x2="146.0" y2="250.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="139.0" y="220.0" width="14" height="22.5" fill="#188a3e"/>
<line x1="186.0" y1="190.0" x2="186.0" y2="227.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="179.0" y="197.5" width="14" height="22.5" fill="#188a3e"/>
<line x1="226.0" y1="152.5" x2="226.0" y2="205.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="219.0" y="160.0" width="14" height="37.5" fill="#188a3e"/>
<line x1="266.0" y1="137.5" x2="266.0" y2="170.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="259.0" y="145.0" width="14" height="15.0" fill="#188a3e"/>
<line x1="306.0" y1="130.0" x2="306.0" y2="160.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="299.0" y="145.0" width="14" height="7.5" fill="#c00"/>
<line x1="346.0" y1="92.5" x2="346.0" y2="160.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="339.0" y="100.0" width="14" height="52.5" fill="#188a3e"/>
<line x1="386.0" y1="70.0" x2="386.0" y2="115.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="379.0" y="85.0" width="14" height="15.0" fill="#188a3e"/>
<line x1="426.0" y1="77.5" x2="426.0" y2="122.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="419.0" y="85.0" width="14" height="30.0" fill="#c00"/>
<line x1="466.0" y1="107.5" x2="466.0" y2="145.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="459.0" y="115.0" width="14" height="22.5" fill="#c00"/>
<line x1="506.0" y1="130.0" x2="506.0" y2="167.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="499.0" y="137.5" width="14" height="22.5" fill="#c00"/>
<line x1="166.0" y1="114.5" x2="219.0" y2="144.6" stroke="#188a3e" stroke-width="1.8"/>
<polygon points="226.0,148.5 217.1,148.0 221.0,141.1" fill="#188a3e"/>
<text x="166.0" y="109.5" fill="#188a3e" text-anchor="middle" style="font-weight:bold">prima tranșă iese aici, la TP1</text>
<line x1="396.0" y1="261.5" x2="424.3" y2="134.3" stroke="#555" stroke-width="1.8"/>
<polygon points="426.0,126.5 428.2,135.2 420.4,133.4" fill="#555"/>
<text x="396.0" y="275.5" fill="#555" text-anchor="middle" style="font-weight:bold">fitilul trece sub linie, close-ul nu:</text>
<text x="396.0" y="289.5" fill="#555" text-anchor="middle" style="font-weight:bold">nu ieși pe fitil</text>
<line x1="446.0" y1="193.5" x2="462.7" y2="156.3" stroke="#b06000" stroke-width="1.8"/>
<polygon points="466.0,149.0 466.4,157.9 459.1,154.7" fill="#b06000"/>
<text x="446.0" y="207.5" fill="#b06000" text-anchor="middle" style="font-weight:bold">prima bară ÎNCHISĂ sub linie:</text>
<text x="446.0" y="221.5" fill="#b06000" text-anchor="middle" style="font-weight:bold">iese restul poziției, 1.4R</text>
</g>
<g transform="translate(0,337)"><text x="24" y="22" fill="#c00" style="font-weight:bold">SHORT — bear trail: exact aceeași serie, oglindită</text><line x1="126.0" y1="131.5" x2="166.0" y2="131.5" stroke="#b06000" stroke-width="2"/><line x1="166.0" y1="154.0" x2="206.0" y2="154.0" stroke="#b06000" stroke-width="2"/><line x1="166.0" y1="131.5" x2="166.0" y2="154.0" stroke="#b06000" stroke-width="1.2" stroke-dasharray="2 2"/><line x1="206.0" y1="176.5" x2="246.0" y2="176.5" stroke="#b06000" stroke-width="2"/><line x1="206.0" y1="154.0" x2="206.0" y2="176.5" stroke="#b06000" stroke-width="1.2" stroke-dasharray="2 2"/><line x1="246.0" y1="211.0" x2="286.0" y2="211.0" stroke="#b06000" stroke-width="2"/><line x1="246.0" y1="176.5" x2="246.0" y2="211.0" stroke="#b06000" stroke-width="1.2" stroke-dasharray="2 2"/><line x1="286.0" y1="211.0" x2="326.0" y2="211.0" stroke="#b06000" stroke-width="2"/><line x1="326.0" y1="221.5" x2="366.0" y2="221.5" stroke="#b06000" stroke-width="2"/><line x1="326.0" y1="211.0" x2="326.0" y2="221.5" stroke="#b06000" stroke-width="1.2" stroke-dasharray="2 2"/><line x1="366.0" y1="266.5" x2="406.0" y2="266.5" stroke="#b06000" stroke-width="2"/><line x1="366.0" y1="221.5" x2="366.0" y2="266.5" stroke="#b06000" stroke-width="1.2" stroke-dasharray="2 2"/><line x1="406.0" y1="266.5" x2="446.0" y2="266.5" stroke="#b06000" stroke-width="2"/><line x1="446.0" y1="266.5" x2="486.0" y2="266.5" stroke="#b06000" stroke-width="2"/><line x1="24" y1="64.0" x2="666" y2="64.0" stroke="#c00" stroke-width="1.5" stroke-dasharray="4 3"/><text x="656" y="58.0" fill="#c00" text-anchor="end" style="font-weight:bold">stop 100.10 (pasul 4)</text><line x1="24" y1="139.0" x2="666" y2="139.0" stroke="#188a3e" stroke-width="1.5" stroke-dasharray="4 3"/><text x="30" y="133.0" fill="#188a3e" text-anchor="start" style="font-weight:bold">intrare 99.60</text><line x1="24" y1="214.0" x2="666" y2="214.0" stroke="#188a3e" stroke-width="1.5" stroke-dasharray="4 3"/><text x="656" y="235.0" fill="#188a3e" text-anchor="end" style="font-weight:bold">TP1 99.10 = 1:1</text>
<line x1="66.0" y1="70.0" x2="66.0" y2="91.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="59.0" y="76.0" width="14" height="6.0" fill="#c00"/>
<line x1="106.0" y1="79.0" x2="106.0" y2="146.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="99.0" y="82.0" width="14" height="57.0" fill="#c00"/>
<line x1="146.0" y1="131.5" x2="146.0" y2="169.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="139.0" y="139.0" width="14" height="22.5" fill="#c00"/>
<line x1="186.0" y1="154.0" x2="186.0" y2="191.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="179.0" y="161.5" width="14" height="22.5" fill="#c00"/>
<line x1="226.0" y1="176.5" x2="226.0" y2="229.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="219.0" y="184.0" width="14" height="37.5" fill="#c00"/>
<line x1="266.0" y1="211.0" x2="266.0" y2="244.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="259.0" y="221.5" width="14" height="15.0" fill="#c00"/>
<line x1="306.0" y1="221.5" x2="306.0" y2="251.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="299.0" y="229.0" width="14" height="7.5" fill="#188a3e"/>
<line x1="346.0" y1="221.5" x2="346.0" y2="289.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="339.0" y="229.0" width="14" height="52.5" fill="#c00"/>
<line x1="386.0" y1="266.5" x2="386.0" y2="311.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="379.0" y="281.5" width="14" height="15.0" fill="#c00"/>
<line x1="426.0" y1="259.0" x2="426.0" y2="304.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="419.0" y="266.5" width="14" height="30.0" fill="#188a3e"/>
<line x1="466.0" y1="236.5" x2="466.0" y2="274.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="459.0" y="244.0" width="14" height="22.5" fill="#188a3e"/>
<line x1="506.0" y1="214.0" x2="506.0" y2="251.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="499.0" y="221.5" width="14" height="22.5" fill="#188a3e"/>
<line x1="166.0" y1="279.0" x2="219.7" y2="237.9" stroke="#188a3e" stroke-width="1.8"/>
<polygon points="226.0,233.0 222.1,241.0 217.2,234.7" fill="#188a3e"/>
<text x="166.0" y="293.0" fill="#188a3e" text-anchor="middle" style="font-weight:bold">prima tranșă iese aici, la TP1</text>
<line x1="446.0" y1="198.0" x2="462.0" y2="225.6" stroke="#b06000" stroke-width="1.8"/>
<polygon points="466.0,232.5 458.5,227.6 465.4,223.6" fill="#b06000"/>
<text x="446.0" y="179.0" fill="#b06000" text-anchor="middle" style="font-weight:bold">prima bară ÎNCHISĂ peste linie:</text>
<text x="446.0" y="193.0" fill="#b06000" text-anchor="middle" style="font-weight:bold">iese restul poziției, 1.4R</text>
</g>
<text x="24" y="680" fill="#555">Serii construite, aceleași cifre ca în restul capitolului.</text>
<text x="24" y="697" fill="#555">Scara portocalie e linia de trail, palier cu palier.</text>
<text x="24" y="714" fill="#555">Panoul de jos e panoul de sus oglindit în jurul lui 100.00, calculat, nu desenat din ochi.</text>
</svg>

After

Width:  |  Height:  |  Size: 10 KiB

View File

@@ -1,268 +1,125 @@
#!/usr/bin/env python3
"""Secvența M → A a strategiei M2D — schemă conceptuală pe două panouri.
"""M2D, pasul 2 — pullback-ul (M) pe TF mic: DA (există) vs. NU (lipsește).
Pentru `strategii/03_m2d.md`, marcajul `<!-- DIAGRAMA: secventa -->`.
Nu e grafic de preț (spec: „schemă pe două panouri, nu candele de trade”):
SVG scris de mână, doar stdlib — nu se folosește svg_candles / pchart.
Pentru `summaries/17_STRATEGII/03_m2d.md`, marcajul `<!-- DIAGRAMA: secventa -->`.
Ce arată (toate etichetele vin din spec; nicio cifră de preț nu se inventează):
- panoul de sus, LONG: pe TF mare (15 min) polilinia urcă și „rămâne în Buy pe
tot parcursul”; pe TF mic (3 min) urcă → pullback (M: Sell Momentum) → urcă
peste vârful anterior (A: Buy Momentum — TRIGGER, intri long);
- panoul de jos, SHORT: oglindit — TF mare coboară („rămâne în Sell”), TF mic
coboară → pullback în sus (M: Buy Momentum) → coboară sub minimul anterior
(A: Sell Momentum — TRIGGER, intri short);
- două linii verticale punctate leagă momentul M și momentul A de rândul de sus
(verificarea că TF mare rămâne în aceeași direcție);
- textul de subsol: „Secvența contează, nu starea...”.
Panoul de sus e chiar graficul de 3 minute din diagrama de ansamblu, primele
13 bare — aceleași cifre în tot capitolul. Panoul de jos pornește din același
preț, dar urcă fără să se oprească: nu există pullback, deci nu există setup
M2D, oricât de frumoasă ar fi mișcarea.
Rulare: python3 gen/strategii/m2d_secventa.py -> gen/strategii/m2d_secventa.svg
Definiția pe care o susține desenul: pullback = revenire scurtă contra
direcției mari care NU rupe minimul din care a pornit mișcarea. Minimul acela
e low-ul candelei de 15 min care a dat semnalul (99.80) și e desenat pe
ambele panouri.
Rulare: python3 gen/strategii/m2d_secventa.py
"""
import sys
from pathlib import Path
from xml.sax.saxutils import escape
sys.path.insert(0, "/workspace/atm-curs-dl")
sys.path.insert(0, str(Path(__file__).resolve().parent))
from svg_candles import Arrow, Candle, Chart # noqa: E402
import m2d_duo as duo # noqa: E402
OUT = Path(__file__).with_suffix(".svg")
W, SCALE, GAP, LEFT = 680, 108.0, 32, 66
GREEN, RED, BLUE, CYAN, GREY = "#188a3e", "#c00", "#1a5fb4", "#0891b2", "#555"
W, H = 700, 780
BLACK, GRAY, RED, GREEN, BLUE, CYAN, WHITE = (
"#000000", "#777777", "#c00", "#188a3e", "#1a5fb4", "#00b8c4", "#ffffff")
PANEL_BG, PANEL_EDGE = "#fafafa", "#ddd"
REF_LOW = 99.80 # low-ul candelei de 15 min de semnal (vezi diagrama „directie")
SEMNAL = 100.60 # close-ul ei = open-ul primei bare de 3 min
# ---------------------------------------------------------------- geometrie
# Fiecare polilinie: listă de (x, y). y mic = sus.
# Index: 0 = start, 1 = vârf/trough anterior, 2 = M, 3 = A, 4 = continuare.
LONG_TOP = [(40, 162), (150, 148), (250, 138), (430, 128), (660, 118)]
LONG_BOT = [(40, 255), (180, 225), (250, 282), (430, 210), (660, 198)]
SHORT_TOP = [(40, 430), (150, 444), (250, 454), (430, 464), (660, 474)]
SHORT_BOT = [(40, 527), (180, 562), (250, 500), (430, 574), (660, 588)]
# --- DA: primele 13 bare din seria de 3 min a diagramei de ansamblu --------
DA = [(100.60, 100.70, 100.40, 100.45), (100.45, 100.50, 100.25, 100.30),
(100.30, 100.35, 100.15, 100.20), (100.20, 100.25, 100.05, 100.10),
(100.10, 100.15, 100.00, 100.05), (100.05, 100.10, 99.95, 100.00),
(100.00, 100.05, 99.92, 99.95), (99.95, 100.00, 99.90, 99.98),
(99.98, 100.08, 99.94, 100.02), # 8: capătul pullback-ului
(100.02, 100.45, 100.00, 100.40), # 9: primul Buy = trigger
(100.40, 100.60, 100.35, 100.55), (100.55, 100.75, 100.50, 100.70),
(100.70, 101.00, 100.65, 100.95)]
PULL_END = 8
L_M, L_A, S_M, S_A = 2, 3, 2, 3 # indicii momentelor M și A
# --- NU: aceeași plecare, dar urcare continuă, fără pullback --------------
NU = [(100.60, 100.80, 100.55, 100.75), (100.75, 100.95, 100.70, 100.90),
(100.90, 101.10, 100.85, 101.05), (101.05, 101.20, 101.00, 101.15),
(101.15, 101.35, 101.10, 101.30), (101.30, 101.45, 101.25, 101.40),
(101.40, 101.60, 101.35, 101.55), (101.55, 101.75, 101.50, 101.70),
(101.70, 101.90, 101.65, 101.85), (101.85, 102.00, 101.80, 101.95),
(101.95, 102.15, 101.90, 102.10), (102.10, 102.25, 102.05, 102.20),
(102.20, 102.40, 102.15, 102.35)]
PANELS = {
"LONG": (14, 92, 672, 250),
"SHORT": (14, 362, 672, 270),
}
TEXTS = [] # (x, y, text, size, weight, anchor, color) pentru verificare
# ---------------------------------------------------------------- verificări
for nume, bars in (("DA", DA), ("NU", NU)):
for i, (o, h, l, c) in enumerate(bars):
assert h >= max(o, c) and l <= min(o, c), f"{nume}[{i}]: high/low incoerent"
if i:
assert abs(o - bars[i - 1][3]) < 1e-9, f"{nume}[{i}]: open != close precedent"
assert abs(bars[0][0] - SEMNAL) < 1e-9, f"{nume}: nu pornește din close-ul semnalului"
def txt(x, y, s, size=13, weight="400", fill=BLACK, anchor="middle"):
TEXTS.append((x, y, s, size, weight, anchor, fill))
return (f'<text x="{x:.1f}" y="{y:.1f}" font-size="{size}" '
f'font-weight="{weight}" fill="{fill}" text-anchor="{anchor}">'
f'{escape(s)}</text>')
def run_in_jos(bars):
"""Cel mai lung șir de close-uri strict descrescătoare."""
best = cur = 1
for a, b in zip(bars, bars[1:]):
cur = cur + 1 if b[3] < a[3] else 1
best = max(best, cur)
return best
def line(x1, y1, x2, y2, stroke=GRAY, width=1.0, dash=None):
d = f' stroke-dasharray="{dash}"' if dash else ""
return (f'<line x1="{x1:.1f}" y1="{y1:.1f}" x2="{x2:.1f}" y2="{y2:.1f}" '
f'stroke="{stroke}" stroke-width="{width}"{d}/>')
assert run_in_jos(DA) >= 3, "DA nu are pullback (niciun șir de 3 close-uri în jos)"
assert run_in_jos(NU) < 3, "NU are totuși un pullback"
PULL_LOW = min(b[2] for b in DA[:PULL_END + 1])
assert abs(PULL_LOW - 99.90) < 1e-9, PULL_LOW
assert PULL_LOW > REF_LOW, "pullback-ul rupe minimul din care a pornit mișcarea"
assert DA[0][3] > DA[PULL_END - 1][3], "pullback-ul nu coboară"
assert DA[9][3] > max(b[1] for b in DA[6:9]), "bara 9 nu rupe în sus capătul pullback-ului"
assert min(b[3] for b in NU) > SEMNAL, "NU: o bară se închide sub prețul de plecare"
def poly(pts, stroke, width=2.0):
p = " ".join(f"{x:.1f},{y:.1f}" for x, y in pts)
return f'<polyline points="{p}" fill="none" stroke="{stroke}" stroke-width="{width}"/>'
def chart(bars, arrows):
return Chart(candles=[Candle(*b) for b in bars], arrows=arrows,
scale=SCALE, gap=GAP, body_w=11, left=LEFT, width=W, top=64)
def dot(x, y, fill, r=5.0):
return f'<circle cx="{x:.1f}" cy="{y:.1f}" r="{r:.1f}" fill="{fill}"/>'
ch_da = chart(DA, [
Arrow(PULL_END - 1, "M: pullback-ul obligatoriu — prețul merge\n"
"contra direcției mari, dar se oprește la 99.90",
where="below", color=RED, dx=-24, dy=46),
Arrow(9, "prima bară care urcă peste capătul pullback-ului:\n"
"aici începe pasul 3 (trigger-ul)",
where="above", color=GREEN, dx=-40, dy=6)])
ch_nu = chart(NU, [
Arrow(6, "urcare continuă, fără nicio pauză în jos:\n"
"asta e breakout, nu M2D — nu ai unde pune stopul",
where="below", color=RED, dx=-70, dy=40)])
body_da, h_da = duo.panel(ch_da, W)
body_nu, h_nu = duo.panel(ch_nu, W)
def build():
p = [
f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 {W} {H}" '
f'width="{W}" height="{H}" role="img" aria-label="Secventa M apoi A a '
f'strategiei M2D, pe doua time frame-uri, pentru long si short: time '
f'frame-ul mare ramane in directie cat timp time frame-ul mic face '
f'pullback (M) si apoi da trigger-ul in directia mare (A)">',
'<g font-family="system-ui, sans-serif">',
f'<rect x="0" y="0" width="{W}" height="{H}" fill="{WHITE}"/>',
txt(W / 2, 30, "Secvența M → A: nu e o stare, e o secvență",
19, "700", BLACK),
txt(W / 2, 52, "M2D — același moment, două time frame-uri: "
"TF mare dă direcția, TF mic dă trigger-ul",
13, "400", GRAY),
txt(W / 2, 72, "sus = LONG; jos = SHORT — aceleași momente, în oglindă",
13, "400", GRAY),
]
extra_da = (duo.titlu("DA — pullback pe 3 min, care nu rupe minimul de referință", color=GREEN)
+ duo.zone(ch_da, 0, PULL_END, "M: pullback", RED, pad=12, lift=26)
+ duo.level(ch_da, REF_LOW,
["minimul din care a pornit mișcarea 99.80 — sub el nu mai e pullback"],
GREY, W, below=True, dash="2 3")
+ duo.level(ch_da, PULL_LOW, ["cel mai jos punct al pullback-ului 99.90"],
RED, W, below=False, at_left=False))
extra_nu = (duo.titlu("NU — niciun pullback: nu ai setup, oricât de frumoasă e urcarea",
color=RED)
+ duo.level(ch_nu, SEMNAL,
["close-ul candelei de 15 min de semnal 100.60"], CYAN, W,
below=True, at_left=False))
# ------------------------------------------------------ panouri
for name, (px, py, pw, ph) in PANELS.items():
p.append(f'<rect x="{px}" y="{py}" width="{pw}" height="{ph}" rx="8" '
f'fill="{PANEL_BG}" stroke="{PANEL_EDGE}" stroke-width="1.2"/>')
# ------------------------------------------------------ panoul LONG
p.append(txt(30, 116, "LONG", 15, "700", GREEN, "start"))
p.append(txt(30, 140, "TF mare (ex. 15 min)", 13, "400", GRAY, "start"))
p.append(txt(660, 114, "rămâne în Buy pe tot parcursul", 13, "400",
GREEN, "end"))
for x, y_top, y_bot in ((LONG_TOP[L_M][0], LONG_TOP[L_M][1], LONG_BOT[L_M][1]),
(LONG_TOP[L_A][0], LONG_TOP[L_A][1], LONG_BOT[L_A][1])):
p.append(line(x, y_top, x, y_bot, "#999999", 1.2, "4 4"))
p.append(poly(LONG_TOP, GREEN))
p.append(dot(*LONG_TOP[1], CYAN))
p.append(txt(185, 126, "Buy Momentum", 13, "400", CYAN, "start"))
p.append(txt(30, 202, "TF mic (ex. 3 min)", 13, "400", GRAY, "start"))
p.append(poly(LONG_BOT, "#333333"))
p.append(dot(*LONG_BOT[L_M], RED))
p.append(txt(262, 296, "M: Sell Momentum (pullback)", 13, "400",
RED, "start"))
p.append(dot(*LONG_BOT[L_A], GREEN, r=6))
p.append(txt(660, 240, "A: Buy Momentum — TRIGGER, intri long",
13, "400", GREEN, "end"))
# ------------------------------------------------------ panoul SHORT
p.append(txt(30, 386, "SHORT", 15, "700", RED, "start"))
p.append(txt(30, 410, "TF mare (ex. 15 min)", 13, "400", GRAY, "start"))
p.append(txt(660, 494, "rămâne în Sell pe tot parcursul", 13, "400",
RED, "end"))
for x, y_top, y_bot in ((SHORT_TOP[S_M][0], SHORT_TOP[S_M][1], SHORT_BOT[S_M][1]),
(SHORT_TOP[S_A][0], SHORT_TOP[S_A][1], SHORT_BOT[S_A][1])):
p.append(line(x, y_top, x, y_bot, "#999999", 1.2, "4 4"))
p.append(poly(SHORT_TOP, RED))
p.append(dot(*SHORT_TOP[1], RED))
p.append(txt(168, 432, "Sell Momentum", 13, "400", RED, "start"))
p.append(txt(30, 470, "TF mic (ex. 3 min)", 13, "400", GRAY, "start"))
p.append(poly(SHORT_BOT, "#333333"))
p.append(dot(*SHORT_BOT[S_M], CYAN))
p.append(txt(238, 494, "M: Buy Momentum (pullback)", 13, "400",
CYAN, "end"))
p.append(dot(*SHORT_BOT[S_A], RED, r=6))
p.append(txt(660, 610, "A: Sell Momentum — TRIGGER, intri short",
13, "400", RED, "end"))
# ------------------------------------------------------ subsol
p.append(f'<rect x="40" y="648" width="620" height="116" rx="8" '
f'fill="#ffffff" stroke="{GRAY}" stroke-width="1.2"/>')
p.append(txt(W / 2, 676,
"Secvența contează, nu starea: mare în Buy → mic în Sell → "
"mic în Buy", 13, "400", BLACK))
p.append(txt(W / 2, 696,
"(cu mare încă în Buy). Un Buy direct pe TF mic, fără Sell "
"între ele,", 13, "400", BLACK))
p.append(txt(W / 2, 716, "NU e trigger (vezi 1.3).", 13, "400", BLACK))
p.append(txt(W / 2, 746,
"Sursă: 14_strategia_1_m2d.md:30-38, 172-180; "
"09_QA/08_2026-01-20.md:65-67", 13, "400", GRAY))
p.append('</g></svg>')
return "\n".join(p) + "\n"
# ---------------------------------------------------------------- verificare
def est_w(s, size, weight):
factor = 0.62 if weight == "700" else 0.56
return len(s) * size * factor
def verify(svg):
# 1. geometria: panourile nu se suprapun, sunt în panză
long_ = PANELS["LONG"]
short_ = PANELS["SHORT"]
assert long_[1] + long_[3] < short_[1], "panourile LONG/SHORT se suprapun"
assert short_[1] + short_[3] < 648, "panoul SHORT intră în subsol"
for _, (px, py, pw, ph) in PANELS.items():
assert px >= 0 and py >= 0 and px + pw <= W and py + ph <= H
# 2. secvența LONG: TF mare rămâne în Buy (urcă monoton: y strict descrescător)
ys = [y for _, y in LONG_TOP]
assert all(ys[i] > ys[i + 1] for i in range(len(ys) - 1)), \
"LONG: TF mare nu urcă monoton (nu rămâne în Buy)"
# M = pullback (minim local), A = reluare peste vârful anterior
assert LONG_BOT[L_M][1] > LONG_BOT[L_M - 1][1] and \
LONG_BOT[L_M][1] > LONG_BOT[L_M + 1][1], "LONG: M nu e un minim local"
assert LONG_BOT[L_A][1] < LONG_BOT[1][1], \
"LONG: A nu urcă peste vârful anterior (pullback-ul)"
assert LONG_TOP[L_M][0] < LONG_TOP[L_A][0], "LONG: M trebuie înainte de A"
assert LONG_TOP[L_M][0] == LONG_BOT[L_M][0] and \
LONG_TOP[L_A][0] == LONG_BOT[L_A][0], \
"LONG: liniile verticale nu leagă exact M și A de rândul de sus"
# 3. secvența SHORT: oglindit
ys = [y for _, y in SHORT_TOP]
assert all(ys[i] < ys[i + 1] for i in range(len(ys) - 1)), \
"SHORT: TF mare nu coboară monoton (nu rămâne în Sell)"
assert SHORT_BOT[S_M][1] < SHORT_BOT[S_M - 1][1] and \
SHORT_BOT[S_M][1] < SHORT_BOT[S_M + 1][1], "SHORT: M nu e un maxim local"
assert SHORT_BOT[S_A][1] > SHORT_BOT[1][1], \
"SHORT: A nu coboară sub minimul anterior"
assert SHORT_TOP[S_M][0] < SHORT_TOP[S_A][0], "SHORT: M trebuie înainte de A"
assert SHORT_TOP[S_M][0] == SHORT_BOT[S_M][0] and \
SHORT_TOP[S_A][0] == SHORT_BOT[S_A][0], \
"SHORT: liniile verticale nu leagă exact M și A de rândul de sus"
# 4. verticalele chiar traversează de la rândul de sus până la moment
assert LONG_TOP[2][1] < LONG_BOT[L_M][1]
assert LONG_TOP[3][1] < LONG_BOT[L_A][1]
assert SHORT_TOP[2][1] < SHORT_BOT[S_M][1]
assert SHORT_TOP[3][1] < SHORT_BOT[S_A][1]
# 5. toate etichetele în panză
for x, y, s, size, weight, anchor, _ in TEXTS:
ew = est_w(s, size, weight)
left = x - ew if anchor == "end" else (x - ew / 2 if anchor == "middle" else x)
right = left + ew
assert -2 <= left and right <= W + 2, f"text în afara panzei: {s!r}"
assert 0 <= y <= H, f"text în afara înălțimii: {s!r} (y={y})"
# 6. etichete care nu se suprapun
for i in range(len(TEXTS)):
xi, yi, si, szi, wi, ai, _ = TEXTS[i]
we = est_w(si, szi, wi)
li = xi - we if ai == "end" else (xi - we / 2 if ai == "middle" else xi)
ri = li + we
for j in range(i + 1, len(TEXTS)):
xj, yj, sj, szj, wj, aj, _ = TEXTS[j]
wej = est_w(sj, szj, wj)
lj = xj - wej if aj == "end" else (xj - wej / 2 if aj == "middle" else xj)
rj = lj + wej
if abs(yi - yj) < 13 and li < rj - 4 and lj < ri - 4:
raise AssertionError(f"texte suprapuse: {si!r} / {sj!r}")
# 7. niciun text nu se tastează peste o polilinie (coliziune text-linie)
segments = [(LONG_TOP, PANELS["LONG"]), (LONG_BOT, PANELS["LONG"]),
(SHORT_TOP, PANELS["SHORT"]), (SHORT_BOT, PANELS["SHORT"])]
for x, y, s, size, weight, anchor, _ in TEXTS:
ew = est_w(s, size, weight)
left = x - ew if anchor == "end" else (x - ew / 2 if anchor == "middle" else x)
right = left + ew
top, bot = y - size + 1, y
for pts, _ in segments:
for (x1, y1), (x2, y2) in zip(pts, pts[1:]):
xa, xb = min(x1, x2), max(x1, x2)
if xb < left - 2 or xa > right + 2:
continue
for xc in (max(xa, left), min(xb, right)):
t = 0 if x2 == x1 else (xc - x1) / (x2 - x1)
yc = y1 + t * (y2 - y1)
if top - 1 <= yc <= bot + 1:
raise AssertionError(
f"text peste polilinie: {s!r} (y={y}, linia y={yc:.0f})")
# 8. etichetele obligatorii din spec sunt prezente
for cheie in ("LONG", "SHORT",
"TF mare (ex. 15 min)", "TF mic (ex. 3 min)",
"rămâne în Buy pe tot parcursul",
"rămâne în Sell pe tot parcursul",
"Buy Momentum", "Sell Momentum",
"M: Sell Momentum (pullback)",
"A: Buy Momentum — TRIGGER, intri long",
"M: Buy Momentum (pullback)",
"A: Sell Momentum — TRIGGER, intri short",
"Secvența contează, nu starea",
"NU e trigger (vezi 1.3)"):
assert cheie in svg, f"lipsește din desen: {cheie!r}"
# 9. fără cifre de preț inventate (spec: diagramă fără niveluri)
for interzis in ("$", "%", "€", "intrare ", "SL ", "TP"):
assert interzis not in svg, f"nu ar trebui să apară preț: {interzis!r}"
return True
SVG = build()
assert verify(SVG), "verificare eșuată"
OUT.write_text(SVG, encoding="utf-8")
print(f"scris {OUT} ({W}x{H}), verificare OK: {len(TEXTS)} etichete, "
f"{len(LONG_TOP) + len(LONG_BOT) + len(SHORT_TOP) + len(SHORT_BOT)} puncte")
svg, H, n = duo.build(
[("DA", body_da, h_da, extra_da), ("NU", body_nu, h_nu, extra_nu)],
["Ambele panouri: 3 minute, serie construită, aceleași cifre ca în diagrama de ansamblu.",
'Pullback nu înseamnă „nu mai face minim nou": înseamnă că nu rupe minimul '
'din care a pornit mișcarea.'],
W)
duo.fara_preturi_straine(svg, [REF_LOW, PULL_LOW, SEMNAL])
OUT.write_text(svg, encoding="utf-8")
print(f"scris {OUT} ({W}x{H:.0f}), {n} etichete | DA: șir în jos de {run_in_jos(DA)} bare, "
f"minim {PULL_LOW:.2f} peste referința {REF_LOW:.2f} | NU: șir în jos de "
f"{run_in_jos(NU)} bare")

View File

@@ -1,42 +1,72 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 700 780" width="700" height="780" role="img" aria-label="Secventa M apoi A a strategiei M2D, pe doua time frame-uri, pentru long si short: time frame-ul mare ramane in directie cat timp time frame-ul mic face pullback (M) si apoi da trigger-ul in directia mare (A)">
<g font-family="system-ui, sans-serif">
<rect x="0" y="0" width="700" height="780" fill="#ffffff"/>
<text x="350.0" y="30.0" font-size="19" font-weight="700" fill="#000000" text-anchor="middle">Secvența M → A: nu e o stare, e o secvență</text>
<text x="350.0" y="52.0" font-size="13" font-weight="400" fill="#777777" text-anchor="middle">M2D — același moment, două time frame-uri: TF mare dă direcția, TF mic dă trigger-ul</text>
<text x="350.0" y="72.0" font-size="13" font-weight="400" fill="#777777" text-anchor="middle">sus = LONG; jos = SHORT — aceleași momente, în oglindă</text>
<rect x="14" y="92" width="672" height="250" rx="8" fill="#fafafa" stroke="#ddd" stroke-width="1.2"/>
<rect x="14" y="362" width="672" height="270" rx="8" fill="#fafafa" stroke="#ddd" stroke-width="1.2"/>
<text x="30.0" y="116.0" font-size="15" font-weight="700" fill="#188a3e" text-anchor="start">LONG</text>
<text x="30.0" y="140.0" font-size="13" font-weight="400" fill="#777777" text-anchor="start">TF mare (ex. 15 min)</text>
<text x="660.0" y="114.0" font-size="13" font-weight="400" fill="#188a3e" text-anchor="end">rămâne în Buy pe tot parcursul</text>
<line x1="250.0" y1="138.0" x2="250.0" y2="282.0" stroke="#999999" stroke-width="1.2" stroke-dasharray="4 4"/>
<line x1="430.0" y1="128.0" x2="430.0" y2="210.0" stroke="#999999" stroke-width="1.2" stroke-dasharray="4 4"/>
<polyline points="40.0,162.0 150.0,148.0 250.0,138.0 430.0,128.0 660.0,118.0" fill="none" stroke="#188a3e" stroke-width="2.0"/>
<circle cx="150.0" cy="148.0" r="5.0" fill="#00b8c4"/>
<text x="185.0" y="126.0" font-size="13" font-weight="400" fill="#00b8c4" text-anchor="start">Buy Momentum</text>
<text x="30.0" y="202.0" font-size="13" font-weight="400" fill="#777777" text-anchor="start">TF mic (ex. 3 min)</text>
<polyline points="40.0,255.0 180.0,225.0 250.0,282.0 430.0,210.0 660.0,198.0" fill="none" stroke="#333333" stroke-width="2.0"/>
<circle cx="250.0" cy="282.0" r="5.0" fill="#c00"/>
<text x="262.0" y="296.0" font-size="13" font-weight="400" fill="#c00" text-anchor="start">M: Sell Momentum (pullback)</text>
<circle cx="430.0" cy="210.0" r="6.0" fill="#188a3e"/>
<text x="660.0" y="240.0" font-size="13" font-weight="400" fill="#188a3e" text-anchor="end">A: Buy Momentum — TRIGGER, intri long</text>
<text x="30.0" y="386.0" font-size="15" font-weight="700" fill="#c00" text-anchor="start">SHORT</text>
<text x="30.0" y="410.0" font-size="13" font-weight="400" fill="#777777" text-anchor="start">TF mare (ex. 15 min)</text>
<text x="660.0" y="494.0" font-size="13" font-weight="400" fill="#c00" text-anchor="end">rămâne în Sell pe tot parcursul</text>
<line x1="250.0" y1="454.0" x2="250.0" y2="500.0" stroke="#999999" stroke-width="1.2" stroke-dasharray="4 4"/>
<line x1="430.0" y1="464.0" x2="430.0" y2="574.0" stroke="#999999" stroke-width="1.2" stroke-dasharray="4 4"/>
<polyline points="40.0,430.0 150.0,444.0 250.0,454.0 430.0,464.0 660.0,474.0" fill="none" stroke="#c00" stroke-width="2.0"/>
<circle cx="150.0" cy="444.0" r="5.0" fill="#c00"/>
<text x="168.0" y="432.0" font-size="13" font-weight="400" fill="#c00" text-anchor="start">Sell Momentum</text>
<text x="30.0" y="470.0" font-size="13" font-weight="400" fill="#777777" text-anchor="start">TF mic (ex. 3 min)</text>
<polyline points="40.0,527.0 180.0,562.0 250.0,500.0 430.0,574.0 660.0,588.0" fill="none" stroke="#333333" stroke-width="2.0"/>
<circle cx="250.0" cy="500.0" r="5.0" fill="#00b8c4"/>
<text x="238.0" y="494.0" font-size="13" font-weight="400" fill="#00b8c4" text-anchor="end">M: Buy Momentum (pullback)</text>
<circle cx="430.0" cy="574.0" r="6.0" fill="#c00"/>
<text x="660.0" y="610.0" font-size="13" font-weight="400" fill="#c00" text-anchor="end">A: Sell Momentum — TRIGGER, intri short</text>
<rect x="40" y="648" width="620" height="116" rx="8" fill="#ffffff" stroke="#777777" stroke-width="1.2"/>
<text x="350.0" y="676.0" font-size="13" font-weight="400" fill="#000000" text-anchor="middle">Secvența contează, nu starea: mare în Buy → mic în Sell → mic în Buy</text>
<text x="350.0" y="696.0" font-size="13" font-weight="400" fill="#000000" text-anchor="middle">(cu mare încă în Buy). Un Buy direct pe TF mic, fără Sell între ele,</text>
<text x="350.0" y="716.0" font-size="13" font-weight="400" fill="#000000" text-anchor="middle">NU e trigger (vezi 1.3).</text>
<text x="350.0" y="746.0" font-size="13" font-weight="400" fill="#777777" text-anchor="middle">Sursă: 14_strategia_1_m2d.md:30-38, 172-180; 09_QA/08_2026-01-20.md:65-67</text>
</g></svg>
<svg viewBox="0 0 680 743" xmlns="http://www.w3.org/2000/svg" style="width:100%;height:auto;font:12px sans-serif">
<g transform="translate(0,0)"><text x="24" y="22" fill="#188a3e" style="font-weight:bold">DA — pullback pe 3 min, care nu rupe minimul de referință</text><rect x="54.0" y="62.4" width="280.0" height="128.4" fill="#c00" fill-opacity="0.12" stroke="#c00" stroke-width="1"/><text x="60.0" y="55.4" fill="#c00" style="font-weight:bold">M: pullback</text><line x1="24" y1="193.6" x2="666" y2="193.6" stroke="#555" stroke-width="1.5" stroke-dasharray="2 3"/><text x="30" y="214.6" fill="#555" text-anchor="start" style="font-weight:bold">minimul din care a pornit mișcarea 99.80 — sub el nu mai e pullback</text><line x1="24" y1="182.8" x2="666" y2="182.8" stroke="#c00" stroke-width="1.5" stroke-dasharray="4 3"/><text x="656" y="176.8" fill="#c00" text-anchor="end" style="font-weight:bold">cel mai jos punct al pullback-ului 99.90</text>
<line x1="66.0" y1="96.4" x2="66.0" y2="128.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="60.5" y="107.2" width="11" height="16.2" fill="#c00"/>
<line x1="98.0" y1="118.0" x2="98.0" y2="145.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="92.5" y="123.4" width="11" height="16.2" fill="#c00"/>
<line x1="130.0" y1="134.2" x2="130.0" y2="155.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="124.5" y="139.6" width="11" height="10.8" fill="#c00"/>
<line x1="162.0" y1="145.0" x2="162.0" y2="166.6" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="156.5" y="150.4" width="11" height="10.8" fill="#c00"/>
<line x1="194.0" y1="155.8" x2="194.0" y2="172.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="188.5" y="161.2" width="11" height="5.4" fill="#c00"/>
<line x1="226.0" y1="161.2" x2="226.0" y2="177.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="220.5" y="166.6" width="11" height="5.4" fill="#c00"/>
<line x1="258.0" y1="166.6" x2="258.0" y2="180.6" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="252.5" y="172.0" width="11" height="5.4" fill="#c00"/>
<line x1="290.0" y1="172.0" x2="290.0" y2="182.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="284.5" y="174.2" width="11" height="3.2" fill="#188a3e"/>
<line x1="322.0" y1="163.4" x2="322.0" y2="178.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="316.5" y="169.8" width="11" height="4.3" fill="#188a3e"/>
<line x1="354.0" y1="123.4" x2="354.0" y2="172.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="348.5" y="128.8" width="11" height="41.0" fill="#188a3e"/>
<line x1="386.0" y1="107.2" x2="386.0" y2="134.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="380.5" y="112.6" width="11" height="16.2" fill="#188a3e"/>
<line x1="418.0" y1="91.0" x2="418.0" y2="118.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="412.5" y="96.4" width="11" height="16.2" fill="#188a3e"/>
<line x1="450.0" y1="64.0" x2="450.0" y2="101.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="444.5" y="69.4" width="11" height="27.0" fill="#188a3e"/>
<line x1="266.0" y1="258.8" x2="287.5" y2="194.4" stroke="#c00" stroke-width="1.8"/>
<polygon points="290.0,186.8 291.3,195.7 283.7,193.1" fill="#c00"/>
<text x="266.0" y="272.8" fill="#c00" text-anchor="middle" style="font-weight:bold">M: pullback-ul obligatoriu — prețul merge</text>
<text x="266.0" y="286.8" fill="#c00" text-anchor="middle" style="font-weight:bold">contra direcției mari, dar se oprește la 99.90</text>
<line x1="314.0" y1="62.0" x2="349.4" y2="112.8" stroke="#188a3e" stroke-width="1.8"/>
<polygon points="354.0,119.4 346.1,115.1 352.7,110.5" fill="#188a3e"/>
<text x="314.0" y="43.0" fill="#188a3e" text-anchor="middle" style="font-weight:bold">prima bară care urcă peste capătul pullback-ului:</text>
<text x="314.0" y="57.0" fill="#188a3e" text-anchor="middle" style="font-weight:bold">aici începe pasul 3 (trigger-ul)</text>
</g>
<g transform="translate(0,322)"><text x="24" y="22" fill="#c00" style="font-weight:bold">NU — niciun pullback: nu ai setup, oricât de frumoasă e urcarea</text><line x1="24" y1="258.4" x2="666" y2="258.4" stroke="#0891b2" stroke-width="1.5" stroke-dasharray="4 3"/><text x="656" y="279.4" fill="#0891b2" text-anchor="end" style="font-weight:bold">close-ul candelei de 15 min de semnal 100.60</text>
<line x1="66.0" y1="236.8" x2="66.0" y2="263.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="60.5" y="242.2" width="11" height="16.2" fill="#188a3e"/>
<line x1="98.0" y1="220.6" x2="98.0" y2="247.6" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="92.5" y="226.0" width="11" height="16.2" fill="#188a3e"/>
<line x1="130.0" y1="204.4" x2="130.0" y2="231.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="124.5" y="209.8" width="11" height="16.2" fill="#188a3e"/>
<line x1="162.0" y1="193.6" x2="162.0" y2="215.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="156.5" y="199.0" width="11" height="10.8" fill="#188a3e"/>
<line x1="194.0" y1="177.4" x2="194.0" y2="204.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="188.5" y="182.8" width="11" height="16.2" fill="#188a3e"/>
<line x1="226.0" y1="166.6" x2="226.0" y2="188.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="220.5" y="172.0" width="11" height="10.8" fill="#188a3e"/>
<line x1="258.0" y1="150.4" x2="258.0" y2="177.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="252.5" y="155.8" width="11" height="16.2" fill="#188a3e"/>
<line x1="290.0" y1="134.2" x2="290.0" y2="161.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="284.5" y="139.6" width="11" height="16.2" fill="#188a3e"/>
<line x1="322.0" y1="118.0" x2="322.0" y2="145.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="316.5" y="123.4" width="11" height="16.2" fill="#188a3e"/>
<line x1="354.0" y1="107.2" x2="354.0" y2="128.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="348.5" y="112.6" width="11" height="10.8" fill="#188a3e"/>
<line x1="386.0" y1="91.0" x2="386.0" y2="118.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="380.5" y="96.4" width="11" height="16.2" fill="#188a3e"/>
<line x1="418.0" y1="80.2" x2="418.0" y2="101.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="412.5" y="85.6" width="11" height="10.8" fill="#188a3e"/>
<line x1="450.0" y1="64.0" x2="450.0" y2="91.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="444.5" y="69.4" width="11" height="16.2" fill="#188a3e"/>
<line x1="188.0" y1="311.8" x2="254.2" y2="188.4" stroke="#c00" stroke-width="1.8"/>
<polygon points="258.0,181.4 257.7,190.3 250.7,186.6" fill="#c00"/>
<text x="188.0" y="325.8" fill="#c00" text-anchor="middle" style="font-weight:bold">urcare continuă, fără nicio pauză în jos:</text>
<text x="188.0" y="339.8" fill="#c00" text-anchor="middle" style="font-weight:bold">asta e breakout, nu M2D — nu ai unde pune stopul</text>
</g>
<text x="24" y="703" fill="#555">Ambele panouri: 3 minute, serie construită, aceleași cifre ca în diagrama de ansamblu.</text>
<text x="24" y="720" fill="#555">Pullback nu înseamnă „nu mai face minim nou": înseamnă că nu rupe minimul din care a pornit mișcarea.</text>
</svg>

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 6.9 KiB

136
gen/strategii/m2d_stop.py Normal file
View File

@@ -0,0 +1,136 @@
#!/usr/bin/env python3
"""M2D, pasul 4 — stopul: extrema ultimelor 4 bare, și alternativa 2 × ATR(10).
Pentru `summaries/17_STRATEGII/03_m2d.md`, marcajul `<!-- DIAGRAMA: stop -->`.
Sus: exemplul construit din tot capitolul (3 min). Numeri 4 bare înapoi de la
bara de trigger, iei cel mai jos low și acolo stă stopul — fix pe el, fără
marjă; asta e regula folosită în TOATE diagramele capitolului.
Jos: exemplul SPY din curs, singurul loc din material unde apar cifre
absolute pentru stop. Reale din curs: close-ul de trigger 517.78, ATR(10)
2.88, stopul 2×ATR 512.02 și low-ul metodei pe 4 candele 512.87. Restul
seriei e construit ca să scoată exact acel low.
Rulare: python3 gen/strategii/m2d_stop.py
"""
import sys
from pathlib import Path
sys.path.insert(0, "/workspace/atm-curs-dl")
sys.path.insert(0, str(Path(__file__).resolve().parent))
from svg_candles import Arrow, Candle, Chart # noqa: E402
import m2d_duo as duo # noqa: E402
OUT = Path(__file__).with_suffix(".svg")
W = 680
GREEN, RED, BLUE, ORANGE, GREY = "#188a3e", "#c00", "#1a5fb4", "#b06000", "#555"
STOP_BARS = 4
# --- panoul 1: exemplul construit, 3 min (aceleași bare ca în ansamblu) ----
MIC = [(100.60, 100.70, 100.40, 100.45), (100.45, 100.50, 100.25, 100.30),
(100.30, 100.35, 100.15, 100.20), (100.20, 100.25, 100.05, 100.10),
(100.10, 100.15, 100.00, 100.05), (100.05, 100.10, 99.95, 100.00),
(100.00, 100.05, 99.92, 99.95), (99.95, 100.00, 99.90, 99.98),
(99.98, 100.08, 99.94, 100.02),
(100.02, 100.45, 100.00, 100.40), # 9: trigger
(100.40, 100.60, 100.35, 100.55), (100.55, 100.75, 100.50, 100.70)]
TRIG_M = 9
# --- panoul 2: SPY, 15 min. Reale: 517.78, 2.88, 512.02, 512.87 -----------
SPY_CLOSE_TRIG = 517.78
SPY_ATR = 2.88
SPY_ATR_STOP = 512.02
SPY_LOW4 = 512.87
SPY = [(521.40, 521.80, 520.10, 520.50), (520.50, 520.90, 519.20, 519.60),
(519.60, 519.80, 518.10, 518.40), (518.40, 518.70, 516.90, 517.20),
(517.20, 517.40, 515.70, 516.10), (516.10, 516.40, 514.90, 515.30),
(515.30, 515.60, 513.40, 514.60),
(514.60, 514.90, 513.40, 514.00), # 7
(514.00, 514.30, SPY_LOW4, 513.50), # 8: cel mai jos low din 4
(513.50, 514.20, 513.20, 513.90), # 9
(513.90, 518.10, 513.80, SPY_CLOSE_TRIG)] # 10: trigger
TRIG_S = 10
# ---------------------------------------------------------------- verificări
for nume, bars in (("mic", MIC), ("SPY", SPY)):
for i, (o, h, l, c) in enumerate(bars):
assert h >= max(o, c) and l <= min(o, c), f"{nume}[{i}]: high/low incoerent"
if i:
assert abs(o - bars[i - 1][3]) < 1e-9, f"{nume}[{i}]: open != close precedent"
ENTRY_M = MIC[TRIG_M][3]
SL_M = min(b[2] for b in MIC[TRIG_M - STOP_BARS + 1:TRIG_M + 1])
RISK_M = round(ENTRY_M - SL_M, 2)
assert (ENTRY_M, SL_M, RISK_M) == (100.40, 99.90, 0.50), (ENTRY_M, SL_M, RISK_M)
# stopul nu e „undeva pe-aproape": e minimul celor 4 bare, și e minimul întregii serii
assert abs(SL_M - min(b[2] for b in MIC)) < 1e-9, "stopul nu e minimul seriei mici"
# a cincea bară înapoi are un low mai mare — deci numărul 4 chiar contează
assert MIC[TRIG_M - STOP_BARS][2] > SL_M, "a 5-a bară strică demonstrația celor 4"
PCT_M = RISK_M / ENTRY_M * 100
SL_S = min(b[2] for b in SPY[TRIG_S - STOP_BARS + 1:TRIG_S + 1])
assert abs(SL_S - SPY_LOW4) < 1e-9, f"low-ul pe 4 bare iese {SL_S}, nu {SPY_LOW4}"
assert abs(SPY[TRIG_S][3] - SPY_CLOSE_TRIG) < 1e-9
assert abs((SPY_CLOSE_TRIG - 2 * SPY_ATR) - SPY_ATR_STOP) < 1e-9, \
"2×ATR nu dă stopul din curs"
MARJA = round(SPY_LOW4 - SPY_ATR_STOP, 2)
assert MARJA == 0.85, MARJA
RISK_S4 = round(SPY_CLOSE_TRIG - SPY_LOW4, 2)
RISK_SA = round(SPY_CLOSE_TRIG - SPY_ATR_STOP, 2)
assert (RISK_S4, RISK_SA) == (4.91, 5.76), (RISK_S4, RISK_SA)
assert SPY_ATR_STOP < SPY_LOW4, "ATR ar trebui să dea un stop mai larg"
def chart(bars, scale, gap, left, body_w, arrows, top=64):
return Chart(candles=[Candle(*b) for b in bars], arrows=arrows, scale=scale,
gap=gap, body_w=body_w, left=left, width=W, top=top)
ch_m = chart(MIC, 150.0, 34, 62, 13, [
Arrow(TRIG_M, "bara de trigger: de la ea numeri înapoi",
where="above", color=GREEN, dx=-30, dy=-6),
Arrow(TRIG_M - STOP_BARS + 2, "cel mai jos low dintre cele 4 bare: 99.90.\n"
"Stopul stă FIX acolo, fără marjă",
where="below", color=RED, dx=-40, dy=40)])
ch_s = chart(SPY, 17.0, 34, 62, 13, [
Arrow(TRIG_S, "close-ul barei de trigger 517.78",
where="above", color=GREEN, dx=-60, dy=-4),
Arrow(8, "2 × ATR(10) = 5.76 sub close:\n"
"stop 512.02, cu 0.85 sub low-ul celor 4 bare",
where="below", color=ORANGE, dx=-20, dy=52)])
body_m, h_m = duo.panel(ch_m, W)
body_s, h_s = duo.panel(ch_s, W)
extra_m = (duo.titlu("Metoda de bază — cele 4 bare", color=RED)
+ duo.zone(ch_m, TRIG_M - STOP_BARS + 1, TRIG_M,
f"{STOP_BARS} bare, numărate de la trigger înapoi", RED,
pad=13, lift=30)
+ duo.level(ch_m, ENTRY_M, ["intrare 100.40"], GREEN, W, at_left=False)
+ duo.level(ch_m, SL_M, ["stop 99.90 — risc 0.50 pe acțiune (0,50 %)"],
RED, W, below=True))
extra_s = (duo.titlu("Alternativa din curs — 2 × ATR(10), pe SPY", color=ORANGE)
+ duo.zone(ch_s, TRIG_S - STOP_BARS + 1, TRIG_S, "", RED, pad=13)
+ duo.level(ch_s, SPY_CLOSE_TRIG, ["intrare 517.78"], GREEN, W, at_left=False)
+ duo.level(ch_s, SPY_LOW4, ["stop pe 4 bare 512.87 — risc 4.91"],
RED, W, below=False)
+ duo.level(ch_s, SPY_ATR_STOP, ["stop 2×ATR 512.02 — risc 5.76"],
ORANGE, W, below=True, dash="2 3"))
svg, H, n = duo.build(
[("mic", body_m, h_m, extra_m), ("SPY", body_s, h_s, extra_s)],
["Sus: serie construită, aceleași cifre ca în restul capitolului.",
"Jos: din curs sunt reale doar 517.78, ATR(10) = 2.88, 512.02 și 512.87; "
"restul barelor sunt construite.",
"ATR-ul dă un stop mai larg, deci un risc mai mare pe acțiune — și, la același",
"risc în dolari, mai puține acțiuni."],
W)
duo.fara_preturi_straine(svg, [ENTRY_M, SL_M, RISK_M, SPY_CLOSE_TRIG, SPY_LOW4,
SPY_ATR_STOP, SPY_ATR, RISK_S4, RISK_SA, MARJA,
2 * SPY_ATR, PCT_M])
OUT.write_text(svg, encoding="utf-8")
print(f"scris {OUT} ({W}x{H:.0f}), {n} etichete | mic: intrare {ENTRY_M:.2f} stop {SL_M:.2f} "
f"risc {RISK_M:.2f} ({PCT_M:.2f} %) | SPY: intrare {SPY_CLOSE_TRIG:.2f}, 4 bare "
f"{SPY_LOW4:.2f} (risc {RISK_S4:.2f}), 2×ATR {SPY_ATR_STOP:.2f} (risc {RISK_SA:.2f}), "
f"diferență {MARJA:.2f}")

View File

@@ -0,0 +1,70 @@
<svg viewBox="0 0 680 765" xmlns="http://www.w3.org/2000/svg" style="width:100%;height:auto;font:12px sans-serif">
<g transform="translate(0,0)"><text x="24" y="22" fill="#c00" style="font-weight:bold">Metoda de bază — cele 4 bare</text><rect x="253.0" y="71.0" width="128.0" height="128.5" fill="#c00" fill-opacity="0.12" stroke="#c00" stroke-width="1"/><text x="259.0" y="64.0" fill="#c00" style="font-weight:bold">4 bare, numărate de la trigger înapoi</text><line x1="24" y1="116.5" x2="666" y2="116.5" stroke="#188a3e" stroke-width="1.5" stroke-dasharray="4 3"/><text x="656" y="110.5" fill="#188a3e" text-anchor="end" style="font-weight:bold">intrare 100.40</text><line x1="24" y1="191.5" x2="666" y2="191.5" stroke="#c00" stroke-width="1.5" stroke-dasharray="4 3"/><text x="30" y="212.5" fill="#c00" text-anchor="start" style="font-weight:bold">stop 99.90 — risc 0.50 pe acțiune (0,50 %)</text>
<line x1="62.0" y1="71.5" x2="62.0" y2="116.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="55.5" y="86.5" width="13" height="22.5" fill="#c00"/>
<line x1="96.0" y1="101.5" x2="96.0" y2="139.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="89.5" y="109.0" width="13" height="22.5" fill="#c00"/>
<line x1="130.0" y1="124.0" x2="130.0" y2="154.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="123.5" y="131.5" width="13" height="15.0" fill="#c00"/>
<line x1="164.0" y1="139.0" x2="164.0" y2="169.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="157.5" y="146.5" width="13" height="15.0" fill="#c00"/>
<line x1="198.0" y1="154.0" x2="198.0" y2="176.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="191.5" y="161.5" width="13" height="7.5" fill="#c00"/>
<line x1="232.0" y1="161.5" x2="232.0" y2="184.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="225.5" y="169.0" width="13" height="7.5" fill="#c00"/>
<line x1="266.0" y1="169.0" x2="266.0" y2="188.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="259.5" y="176.5" width="13" height="7.5" fill="#c00"/>
<line x1="300.0" y1="176.5" x2="300.0" y2="191.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="293.5" y="179.5" width="13" height="4.5" fill="#188a3e"/>
<line x1="334.0" y1="164.5" x2="334.0" y2="185.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="327.5" y="173.5" width="13" height="6.0" fill="#188a3e"/>
<line x1="368.0" y1="109.0" x2="368.0" y2="176.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="361.5" y="116.5" width="13" height="57.0" fill="#188a3e"/>
<line x1="402.0" y1="86.5" x2="402.0" y2="124.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="395.5" y="94.0" width="13" height="22.5" fill="#188a3e"/>
<line x1="436.0" y1="64.0" x2="436.0" y2="101.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="429.5" y="71.5" width="13" height="22.5" fill="#188a3e"/>
<line x1="338.0" y1="50.0" x2="364.2" y2="98.0" stroke="#188a3e" stroke-width="1.8"/>
<polygon points="368.0,105.0 360.7,99.9 367.7,96.1" fill="#188a3e"/>
<text x="338.0" y="45.0" fill="#188a3e" text-anchor="middle" style="font-weight:bold">bara de trigger: de la ea numeri înapoi</text>
<line x1="260.0" y1="261.5" x2="295.9" y2="202.3" stroke="#c00" stroke-width="1.8"/>
<polygon points="300.0,195.5 299.3,204.4 292.4,200.3" fill="#c00"/>
<text x="260.0" y="275.5" fill="#c00" text-anchor="middle" style="font-weight:bold">cel mai jos low dintre cele 4 bare: 99.90.</text>
<text x="260.0" y="289.5" fill="#c00" text-anchor="middle" style="font-weight:bold">Stopul stă FIX acolo, fără marjă</text>
</g>
<g transform="translate(0,324)"><text x="24" y="22" fill="#b06000" style="font-weight:bold">Alternativa din curs — 2 × ATR(10), pe SPY</text><rect x="287.0" y="118.9" width="128.0" height="104.9" fill="#c00" fill-opacity="0.12" stroke="#c00" stroke-width="1"/><line x1="24" y1="132.3" x2="666" y2="132.3" stroke="#188a3e" stroke-width="1.5" stroke-dasharray="4 3"/><text x="656" y="126.3" fill="#188a3e" text-anchor="end" style="font-weight:bold">intrare 517.78</text><line x1="24" y1="215.8" x2="666" y2="215.8" stroke="#c00" stroke-width="1.5" stroke-dasharray="4 3"/><text x="30" y="209.8" fill="#c00" text-anchor="start" style="font-weight:bold">stop pe 4 bare 512.87 — risc 4.91</text><line x1="24" y1="230.3" x2="666" y2="230.3" stroke="#b06000" stroke-width="1.5" stroke-dasharray="2 3"/><text x="30" y="251.3" fill="#b06000" text-anchor="start" style="font-weight:bold">stop 2×ATR 512.02 — risc 5.76</text>
<line x1="62.0" y1="64.0" x2="62.0" y2="92.9" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="55.5" y="70.8" width="13" height="15.3" fill="#c00"/>
<line x1="96.0" y1="79.3" x2="96.0" y2="108.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="89.5" y="86.1" width="13" height="15.3" fill="#c00"/>
<line x1="130.0" y1="98.0" x2="130.0" y2="126.9" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="123.5" y="101.4" width="13" height="20.4" fill="#c00"/>
<line x1="164.0" y1="116.7" x2="164.0" y2="147.3" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="157.5" y="121.8" width="13" height="20.4" fill="#c00"/>
<line x1="198.0" y1="138.8" x2="198.0" y2="167.7" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="191.5" y="142.2" width="13" height="18.7" fill="#c00"/>
<line x1="232.0" y1="155.8" x2="232.0" y2="181.3" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="225.5" y="160.9" width="13" height="13.6" fill="#c00"/>
<line x1="266.0" y1="169.4" x2="266.0" y2="206.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="259.5" y="174.5" width="13" height="11.9" fill="#c00"/>
<line x1="300.0" y1="181.3" x2="300.0" y2="206.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="293.5" y="186.4" width="13" height="10.2" fill="#c00"/>
<line x1="334.0" y1="191.5" x2="334.0" y2="215.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="327.5" y="196.6" width="13" height="8.5" fill="#c00"/>
<line x1="368.0" y1="193.2" x2="368.0" y2="210.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="361.5" y="198.3" width="13" height="6.8" fill="#188a3e"/>
<line x1="402.0" y1="126.9" x2="402.0" y2="200.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="395.5" y="132.3" width="13" height="66.0" fill="#188a3e"/>
<line x1="342.0" y1="92.9" x2="394.8" y2="119.3" stroke="#188a3e" stroke-width="1.8"/>
<polygon points="402.0,122.9 393.1,122.9 396.6,115.7" fill="#188a3e"/>
<text x="342.0" y="87.9" fill="#188a3e" text-anchor="middle" style="font-weight:bold">close-ul barei de trigger 517.78</text>
<line x1="314.0" y1="297.8" x2="332.0" y2="227.6" stroke="#b06000" stroke-width="1.8"/>
<polygon points="334.0,219.8 335.9,228.6 328.1,226.6" fill="#b06000"/>
<text x="314.0" y="311.8" fill="#b06000" text-anchor="middle" style="font-weight:bold">2 × ATR(10) = 5.76 sub close:</text>
<text x="314.0" y="325.8" fill="#b06000" text-anchor="middle" style="font-weight:bold">stop 512.02, cu 0.85 sub low-ul celor 4 bare</text>
</g>
<text x="24" y="691" fill="#555">Sus: serie construită, aceleași cifre ca în restul capitolului.</text>
<text x="24" y="708" fill="#555">Jos: din curs sunt reale doar 517.78, ATR(10) = 2.88, 512.02 și 512.87; restul barelor sunt construite.</text>
<text x="24" y="725" fill="#555">ATR-ul dă un stop mai larg, deci un risc mai mare pe acțiune — și, la același</text>
<text x="24" y="742" fill="#555">risc în dolari, mai puține acțiuni.</text>
</svg>

After

Width:  |  Height:  |  Size: 7.2 KiB

View File

@@ -0,0 +1,118 @@
#!/usr/bin/env python3
"""M2D, pasul 3 — trigger-ul (A): DA (bară închisă peste) vs. NU (doar atins).
Pentru `summaries/17_STRATEGII/03_m2d.md`, marcajul `<!-- DIAGRAMA: trigger -->`.
Aceeași serie de 3 minute ca în diagrama de ansamblu până la bara 8; de acolo
încolo, două continuări. Sus: bara 9 se ÎNCHIDE peste capătul pullback-ului,
deci e trigger, și intri la close. Jos: bara 9 atinge același nivel cu
fitilul, dar se închide sub el — nu e trigger; pe barele următoare pullback-ul
rupe minimul de referință și setup-ul moare.
Indicatorul care dă săgeata de Buy Momentum e proprietar cursului. Pe desen,
săgeata e pusă acolo unde prețul se închide peste capătul pullback-ului —
construcția mea, ca regula să fie verificabilă cu ochiul pe un grafic.
Rulare: python3 gen/strategii/m2d_trigger.py
"""
import sys
from pathlib import Path
sys.path.insert(0, "/workspace/atm-curs-dl")
sys.path.insert(0, str(Path(__file__).resolve().parent))
from svg_candles import Arrow, Candle, Chart # noqa: E402
import m2d_duo as duo # noqa: E402
OUT = Path(__file__).with_suffix(".svg")
W, SCALE, GAP, LEFT = 680, 104.0, 30, 56
GREEN, RED, GREY = "#188a3e", "#c00", "#555"
REF_LOW = 99.80 # minimul din care a pornit mișcarea (vezi pașii 1 și 2)
COMUN = [(100.60, 100.70, 100.40, 100.45), (100.45, 100.50, 100.25, 100.30),
(100.30, 100.35, 100.15, 100.20), (100.20, 100.25, 100.05, 100.10),
(100.10, 100.15, 100.00, 100.05), (100.05, 100.10, 99.95, 100.00),
(100.00, 100.05, 99.92, 99.95), (99.95, 100.00, 99.90, 99.98),
(99.98, 100.08, 99.94, 100.02)]
TRIG = 9
DA = COMUN + [(100.02, 100.45, 100.00, 100.40), # 9: se ÎNCHIDE peste capăt
(100.40, 100.60, 100.35, 100.55), (100.55, 100.75, 100.50, 100.70),
(100.70, 101.00, 100.65, 100.95)]
NU = COMUN + [(100.02, 100.45, 99.98, 100.00), # 9: atinge, dar se închide sub
(100.00, 100.05, 99.82, 99.85),
(99.85, 99.90, 99.55, 99.60), # 11: rupe minimul de referință
(99.60, 99.70, 99.45, 99.50)]
# ---------------------------------------------------------------- verificări
for nume, bars in (("DA", DA), ("NU", NU)):
for i, (o, h, l, c) in enumerate(bars):
assert h >= max(o, c) and l <= min(o, c), f"{nume}[{i}]: high/low incoerent"
if i:
assert abs(o - bars[i - 1][3]) < 1e-9, f"{nume}[{i}]: open != close precedent"
assert DA[:TRIG] == NU[:TRIG] == COMUN, "panourile nu pornesc din aceeași serie"
CAPAT = max(b[1] for b in COMUN[6:]) # capătul pullback-ului = 100.08
assert abs(CAPAT - 100.08) < 1e-9, CAPAT
# nicio bară dinaintea trigger-ului nu se închidea deja peste capăt
assert all(b[3] <= CAPAT for b in COMUN[6:]), "o bară dinainte închidea deja peste capăt"
# DA: bara 9 se închide peste capăt — e trigger, și e PRIMA care face asta
assert DA[TRIG][3] > CAPAT, "DA: bara de trigger nu se închide peste capăt"
assert all(b[3] <= CAPAT for b in DA[6:TRIG]), "DA: trigger-ul nu e primul de după minim"
ENTRY = DA[TRIG][3]
assert abs(ENTRY - 100.40) < 1e-9, ENTRY
# al doilea semnal, pe care NU mai intri
AL_DOILEA = next(i for i in range(TRIG + 1, len(DA))
if DA[i][3] > max(b[1] for b in DA[i - 3:i]))
assert AL_DOILEA == 10, AL_DOILEA
# NU: bara 9 ATINGE capătul cu fitilul, dar se închide sub el
assert NU[TRIG][1] > CAPAT, "NU: bara 9 nici nu atinge capătul"
assert NU[TRIG][3] < CAPAT, "NU: bara 9 se închide totuși peste capăt"
assert all(b[3] <= CAPAT for b in NU[6:]), "NU: o bară se închide peste capăt"
RUPT = next(i for i, b in enumerate(NU) if b[3] < REF_LOW)
assert RUPT == 11, RUPT
def chart(bars, arrows):
return Chart(candles=[Candle(*b) for b in bars], arrows=arrows,
scale=SCALE, gap=GAP, body_w=12, left=LEFT, width=W, top=64)
ch_da = chart(DA, [
Arrow(TRIG, "s-a ÎNCHIS peste 100.08: ăsta e trigger-ul.\n"
"Intri la close, adică la 100.40",
where="below", color=GREEN, dx=-40, dy=54),
Arrow(AL_DOILEA, "al doilea semnal — aici NU mai intri:\n"
"statistica cursului e măsurată pe primul",
where="above", color=RED, dx=40, dy=-4)])
ch_nu = chart(NU, [
Arrow(TRIG, "fitilul a trecut peste 100.08, close-ul nu:\n"
"un puseu de câteva secunde nu e trigger",
where="above", color=RED, dx=-90, dy=4),
Arrow(RUPT, "pullback-ul rupe 99.80:\nsetup mort, cauți altul",
where="below", color=RED, dx=-30, dy=24)])
body_da, h_da = duo.panel(ch_da, W)
body_nu, h_nu = duo.panel(ch_nu, W)
nivel_capat = ["capătul pullback-ului 100.08", "peste el trebuie ÎNCHIS, nu doar atins"]
extra_da = (duo.titlu("DA — bara se închide peste capăt", color=GREEN)
+ duo.zone(ch_da, TRIG - 2, TRIG, "aici se decide intrarea", GREEN, pad=13, lift=30)
+ duo.level(ch_da, CAPAT, nivel_capat, GREY, W, below=True, at_left=False))
extra_nu = (duo.titlu("NU — atins cu fitilul, închis dedesubt: nu e trigger", color=RED)
+ duo.level(ch_nu, CAPAT, nivel_capat, GREY, W, below=False, at_left=False)
+ duo.level(ch_nu, REF_LOW, ["minimul din care a pornit mișcarea 99.80"],
RED, W, below=True, dash="2 3"))
svg, H, n = duo.build(
[("DA", body_da, h_da, extra_da), ("NU", body_nu, h_nu, extra_nu)],
["Primele 9 bare sunt identice pe ambele panouri; se schimbă doar ce urmează.",
"Trigger-ul se citește pe bară ÎNCHISĂ, nu pe fitil — la fel se citește și "
"ieșirea pe trail (pasul 5)."],
W)
duo.fara_preturi_straine(svg, [CAPAT, ENTRY, REF_LOW])
OUT.write_text(svg, encoding="utf-8")
print(f"scris {OUT} ({W}x{H:.0f}), {n} etichete | capăt {CAPAT:.2f} | "
f"DA: trigger la bara {TRIG}, intrare {ENTRY:.2f}, al doilea semnal la {AL_DOILEA} | "
f"NU: bara {TRIG} high {NU[TRIG][1]:.2f} close {NU[TRIG][3]:.2f}, rupe la bara {RUPT}")

View File

@@ -0,0 +1,76 @@
<svg viewBox="0 0 680 662" xmlns="http://www.w3.org/2000/svg" style="width:100%;height:auto;font:12px sans-serif">
<g transform="translate(0,0)"><text x="24" y="22" fill="#188a3e" style="font-weight:bold">DA — bara se închide peste capăt</text><rect x="253.0" y="83.2" width="86.0" height="103.2" fill="#188a3e" fill-opacity="0.12" stroke="#188a3e" stroke-width="1"/><text x="259.0" y="76.2" fill="#188a3e" style="font-weight:bold">aici se decide intrarea</text><line x1="24" y1="159.7" x2="666" y2="159.7" stroke="#555" stroke-width="1.5" stroke-dasharray="4 3"/><text x="656" y="180.7" fill="#555" text-anchor="end" style="font-weight:bold">capătul pullback-ului 100.08</text><text x="656" y="194.7" fill="#555" text-anchor="end" style="font-weight:bold">peste el trebuie ÎNCHIS, nu doar atins</text>
<line x1="56.0" y1="95.2" x2="56.0" y2="126.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="50.0" y="105.6" width="12" height="15.6" fill="#c00"/>
<line x1="86.0" y1="116.0" x2="86.0" y2="142.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="80.0" y="121.2" width="12" height="15.6" fill="#c00"/>
<line x1="116.0" y1="131.6" x2="116.0" y2="152.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="110.0" y="136.8" width="12" height="10.4" fill="#c00"/>
<line x1="146.0" y1="142.0" x2="146.0" y2="162.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="140.0" y="147.2" width="12" height="10.4" fill="#c00"/>
<line x1="176.0" y1="152.4" x2="176.0" y2="168.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="170.0" y="157.6" width="12" height="5.2" fill="#c00"/>
<line x1="206.0" y1="157.6" x2="206.0" y2="173.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="200.0" y="162.8" width="12" height="5.2" fill="#c00"/>
<line x1="236.0" y1="162.8" x2="236.0" y2="176.3" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="230.0" y="168.0" width="12" height="5.2" fill="#c00"/>
<line x1="266.0" y1="168.0" x2="266.0" y2="178.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="260.0" y="170.1" width="12" height="3.1" fill="#188a3e"/>
<line x1="296.0" y1="159.7" x2="296.0" y2="174.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="290.0" y="165.9" width="12" height="4.2" fill="#188a3e"/>
<line x1="326.0" y1="121.2" x2="326.0" y2="168.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="320.0" y="126.4" width="12" height="39.5" fill="#188a3e"/>
<line x1="356.0" y1="105.6" x2="356.0" y2="131.6" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="350.0" y="110.8" width="12" height="15.6" fill="#188a3e"/>
<line x1="386.0" y1="90.0" x2="386.0" y2="116.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="380.0" y="95.2" width="12" height="15.6" fill="#188a3e"/>
<line x1="416.0" y1="64.0" x2="416.0" y2="100.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="410.0" y="69.2" width="12" height="26.0" fill="#188a3e"/>
<line x1="286.0" y1="252.0" x2="322.4" y2="179.2" stroke="#188a3e" stroke-width="1.8"/>
<polygon points="326.0,172.0 326.0,180.9 318.8,177.4" fill="#188a3e"/>
<text x="286.0" y="266.0" fill="#188a3e" text-anchor="middle" style="font-weight:bold">s-a ÎNCHIS peste 100.08: ăsta e trigger-ul.</text>
<text x="286.0" y="280.0" fill="#188a3e" text-anchor="middle" style="font-weight:bold">Intri la close, adică la 100.40</text>
<line x1="396.0" y1="52.0" x2="361.0" y2="95.4" stroke="#c00" stroke-width="1.8"/>
<polygon points="356.0,101.6 357.9,92.9 364.1,97.9" fill="#c00"/>
<text x="396.0" y="33.0" fill="#c00" text-anchor="middle" style="font-weight:bold">al doilea semnal — aici NU mai intri:</text>
<text x="396.0" y="47.0" fill="#c00" text-anchor="middle" style="font-weight:bold">statistica cursului e măsurată pe primul</text>
</g>
<g transform="translate(0,315)"><text x="24" y="22" fill="#c00" style="font-weight:bold">NU — atins cu fitilul, închis dedesubt: nu e trigger</text><line x1="24" y1="128.5" x2="666" y2="128.5" stroke="#555" stroke-width="1.5" stroke-dasharray="4 3"/><text x="656" y="108.5" fill="#555" text-anchor="end" style="font-weight:bold">capătul pullback-ului 100.08</text><text x="656" y="122.5" fill="#555" text-anchor="end" style="font-weight:bold">peste el trebuie ÎNCHIS, nu doar atins</text><line x1="24" y1="157.6" x2="666" y2="157.6" stroke="#c00" stroke-width="1.5" stroke-dasharray="2 3"/><text x="30" y="178.6" fill="#c00" text-anchor="start" style="font-weight:bold">minimul din care a pornit mișcarea 99.80</text>
<line x1="56.0" y1="64.0" x2="56.0" y2="95.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="50.0" y="74.4" width="12" height="15.6" fill="#c00"/>
<line x1="86.0" y1="84.8" x2="86.0" y2="110.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="80.0" y="90.0" width="12" height="15.6" fill="#c00"/>
<line x1="116.0" y1="100.4" x2="116.0" y2="121.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="110.0" y="105.6" width="12" height="10.4" fill="#c00"/>
<line x1="146.0" y1="110.8" x2="146.0" y2="131.6" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="140.0" y="116.0" width="12" height="10.4" fill="#c00"/>
<line x1="176.0" y1="121.2" x2="176.0" y2="136.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="170.0" y="126.4" width="12" height="5.2" fill="#c00"/>
<line x1="206.0" y1="126.4" x2="206.0" y2="142.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="200.0" y="131.6" width="12" height="5.2" fill="#c00"/>
<line x1="236.0" y1="131.6" x2="236.0" y2="145.1" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="230.0" y="136.8" width="12" height="5.2" fill="#c00"/>
<line x1="266.0" y1="136.8" x2="266.0" y2="147.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="260.0" y="138.9" width="12" height="3.1" fill="#188a3e"/>
<line x1="296.0" y1="128.5" x2="296.0" y2="143.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="290.0" y="134.7" width="12" height="4.2" fill="#188a3e"/>
<line x1="326.0" y1="90.0" x2="326.0" y2="138.9" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="320.0" y="134.7" width="12" height="2.1" fill="#c00"/>
<line x1="356.0" y1="131.6" x2="356.0" y2="155.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="350.0" y="136.8" width="12" height="15.6" fill="#c00"/>
<line x1="386.0" y1="147.2" x2="386.0" y2="183.6" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="380.0" y="152.4" width="12" height="26.0" fill="#c00"/>
<line x1="416.0" y1="168.0" x2="416.0" y2="194.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="410.0" y="178.4" width="12" height="10.4" fill="#c00"/>
<line x1="236.0" y1="64.0" x2="318.2" y2="84.1" stroke="#c00" stroke-width="1.8"/>
<polygon points="326.0,86.0 317.3,88.0 319.2,80.2" fill="#c00"/>
<text x="236.0" y="45.0" fill="#c00" text-anchor="middle" style="font-weight:bold">fitilul a trecut peste 100.08, close-ul nu:</text>
<text x="236.0" y="59.0" fill="#c00" text-anchor="middle" style="font-weight:bold">un puseu de câteva secunde nu e trigger</text>
<line x1="356.0" y1="237.6" x2="381.9" y2="194.5" stroke="#c00" stroke-width="1.8"/>
<polygon points="386.0,187.6 385.3,196.5 378.5,192.4" fill="#c00"/>
<text x="356.0" y="251.6" fill="#c00" text-anchor="middle" style="font-weight:bold">pullback-ul rupe 99.80:</text>
<text x="356.0" y="265.6" fill="#c00" text-anchor="middle" style="font-weight:bold">setup mort, cauți altul</text>
</g>
<text x="24" y="622" fill="#555">Primele 9 bare sunt identice pe ambele panouri; se schimbă doar ce urmează.</text>
<text x="24" y="639" fill="#555">Trigger-ul se citește pe bară ÎNCHISĂ, nu pe fitil — la fel se citește și ieșirea pe trail (pasul 5).</text>
</svg>

After

Width:  |  Height:  |  Size: 7.4 KiB