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:
@@ -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}")
|
||||
|
||||
Reference in New Issue
Block a user