buy_doji: graficul TF-ului mic devine lupa pe candelele TF-ului mare
Userul nu putea vedea ce bucata din weekly e reprezentata de daily. In perechi_tf si directie, graficul mic nu mai e o serie care vine dupa cel mare, ci descompunerea lui reala: o candela mare = k candele mici, cu open, close, max(high) si min(low) care coincid, verificat bara cu bara prin assert. k per pereche: 5 zile/saptamana, 4 saptamani/luna, 7 bare de 60 min/zi. Corespondenta se vede: caseta de lupa peste exact candelele marite, ghidaje catre colturile cadrului mic, si nivelul-cheie desenat ca aceeasi linie pe ambele panouri, cu acelasi numar de ambele parti. Interdictia "eticheta unui panou nu poate numi pretul celuilalt" capata o exceptie explicita pentru nivelurile comune, cu assert propriu ca numarul apare identic pe ambele panouri. Pullback-ul si momentul intrarii sunt acum etichetate pe desen, nu doar in text. Toate cifrele recalculate din serii. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ThN7ixZrowca8aTGrK1MUJ
This commit is contained in:
@@ -3,16 +3,27 @@
|
||||
|
||||
Citește `gen/strategii/buy_doji_directie.spec.txt` și scrie
|
||||
`gen/strategii/buy_doji_directie.svg`. Două panouri stivuite: sus weekly
|
||||
(10 bare, Buy Doji confirmat), jos daily (17 bare, uptrend, Sell Doji tehnic
|
||||
valid, short-ul lovește SL-ul). Coordonatele sunt absolute (fără
|
||||
<g transform>), ca audit_diagrams.py să vadă pozițiile reale ale textelor.
|
||||
(12 bare, Buy Doji confirmat), jos daily (15 bare = ultimele 3 bare weekly
|
||||
desfăcute zi cu zi, 5 zile pe săptămână).
|
||||
|
||||
Seria weekly 9..11 NU e scrisă de mână: e agregarea reală a celor 15 zile
|
||||
(open = open-ul primei zile, close = close-ul ultimei, high/low = extremele).
|
||||
Panoul weekly poartă un dreptunghi peste exact acele 3 bare, plus două
|
||||
ghidaje către colțurile cadrului daily, iar high-ul și low-ul doji-ului daily
|
||||
sunt desenate ca aceeași linie punctată, cu același număr, pe ambele panouri.
|
||||
|
||||
Coordonatele sunt absolute (fără <g transform>), ca audit_diagrams.py să vadă
|
||||
pozițiile reale ale textelor.
|
||||
|
||||
Rulare: python3 gen/strategii/buy_doji_directie.py
|
||||
"""
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
OUT = Path(__file__).with_suffix(".svg")
|
||||
W = 900
|
||||
GROUP = 5 # zile pe săptămână
|
||||
ZOOM = 3 # câte bare weekly sunt desfăcute jos
|
||||
|
||||
|
||||
def trend(start, closes, wick=0.15):
|
||||
@@ -23,75 +34,97 @@ def trend(start, closes, wick=0.15):
|
||||
return bars
|
||||
|
||||
|
||||
# --- seriile din spec: (open, high, low, close) -----------------------------
|
||||
def agg(days):
|
||||
"""Agregă GROUP zile într-o bară weekly."""
|
||||
return (days[0][0], max(b[1] for b in days), min(b[2] for b in days), days[-1][3])
|
||||
|
||||
|
||||
# --- seria daily: 15 bare = 3 săptămâni --------------------------------------
|
||||
DY = trend(81.30, [81.70, 82.20, 82.00, 82.60, 83.10, 83.50]) + [
|
||||
(83.50, 84.30, 83.40, 83.60), # 6: zi cu respingere sus (dă high-ul de referință)
|
||||
(83.60, 83.90, 83.05, 83.55), # 7: doji daily
|
||||
(83.55, 83.65, 82.60, 82.70), # 8: confirmare => Sell Doji, intrare short
|
||||
(82.70, 82.80, 82.00, 82.25), # 9: singura zi de scădere
|
||||
(82.25, 83.05, 82.15, 82.95),
|
||||
(82.95, 83.70, 82.85, 83.60),
|
||||
(83.60, 84.45, 83.50, 84.30), # 12: SL lovit
|
||||
(84.30, 84.95, 84.20, 84.80),
|
||||
(84.80, 85.45, 84.70, 85.30)]
|
||||
|
||||
# --- seria weekly: 9 bare de context + cele 3 agregate din daily -------------
|
||||
WK = trend(89.0, [88.0, 86.2, 84.9, 83.1, 81.6, 80.2, 78.9]) + [
|
||||
(78.90, 79.80, 77.60, 78.80), # 7: doji
|
||||
(78.80, 81.60, 78.60, 81.30), # 8: confirmare => Buy Doji weekly
|
||||
(81.30, 82.70, 81.10, 82.40)]
|
||||
DY = trend(81.30, [81.7, 82.2, 82.0, 82.6, 83.1, 83.5, 83.3, 83.9, 84.4]) + [
|
||||
(84.40, 84.80, 83.95, 84.35), # 9: doji
|
||||
(84.35, 84.45, 83.45, 83.60), # 10: confirmare => Sell Doji, intrare short
|
||||
(83.60, 83.70, 83.00, 83.20),
|
||||
(83.20, 84.00, 83.10, 83.90),
|
||||
(83.90, 84.70, 83.80, 84.60),
|
||||
(84.60, 85.25, 84.50, 85.10), # 14: SL lovit
|
||||
(85.10, 85.75, 85.00, 85.60),
|
||||
(85.60, 86.30, 85.50, 86.10)]
|
||||
(78.90, 79.80, 77.60, 78.80), # 7: doji weekly
|
||||
(78.80, 81.60, 78.60, 81.30)] # 8: confirmare => Buy Doji weekly
|
||||
WK += [agg(DY[k:k + GROUP]) for k in range(0, len(DY), GROUP)]
|
||||
Z0 = len(WK) - ZOOM # prima bară weekly mărită jos
|
||||
|
||||
|
||||
def eq(a, b):
|
||||
return abs(a - b) < 0.005
|
||||
|
||||
|
||||
# --- assert-urile din spec ---------------------------------------------------
|
||||
assert len(WK) == 10 and len(DY) == 17
|
||||
# --- assert-uri: coerență, agregare, poveste ---------------------------------
|
||||
assert len(DY) == ZOOM * GROUP and len(WK) == 9 + ZOOM
|
||||
for name, bars in (("weekly", WK), ("daily", DY)):
|
||||
for i, (o, h, l, c) in enumerate(bars):
|
||||
assert h >= max(o, c) and l <= min(o, c), f"{name}[{i}] h/l incoerent"
|
||||
if i:
|
||||
assert eq(o, bars[i - 1][3]), f"{name}[{i}] open != close precedent"
|
||||
for name, (o, h, l, c) in (("doji weekly", WK[7]), ("doji daily", DY[9])):
|
||||
assert eq(DY[0][0], WK[8][3]), "daily nu începe la close-ul confirmării weekly"
|
||||
# agregarea, verificată din nou pe valorile finale (nu pe cele care au construit-o)
|
||||
for k in range(ZOOM):
|
||||
days = DY[k * GROUP:(k + 1) * GROUP]
|
||||
o, h, l, c = WK[Z0 + k]
|
||||
assert eq(o, days[0][0]) and eq(c, days[-1][3]), f"weekly[{Z0+k}] o/c != zile"
|
||||
assert eq(h, max(b[1] for b in days)), f"weekly[{Z0+k}] high != max(high zile)"
|
||||
assert eq(l, min(b[2] for b in days)), f"weekly[{Z0+k}] low != min(low zile)"
|
||||
for name, (o, h, l, c) in (("doji weekly", WK[7]), ("doji daily", DY[7])):
|
||||
assert abs(o - c) <= 0.10 * (h - l) + 1e-9, f"{name} nu e doji"
|
||||
assert WK[8][3] > WK[7][1], "weekly: fără confirmare"
|
||||
assert DY[10][3] < DY[9][2], "daily: fără confirmare Sell Doji"
|
||||
ENTRY = DY[10][3]
|
||||
# stopul: 5 cenți peste high-ul ultimelor 4 candele, ca la pasul 5 (oglindit)
|
||||
SL = round(max(b[1] for b in DY[7:11]) + 0.05, 2)
|
||||
assert DY[8][3] < DY[7][2], "daily: fără confirmare Sell Doji"
|
||||
|
||||
DH, DL = DY[7][1], DY[7][2] # nivelurile comune: high/low doji daily
|
||||
ENTRY = DY[8][3]
|
||||
SL = round(max(b[1] for b in DY[5:9]) + 0.05, 2) # 5 cenți peste high-ul de 4 candele
|
||||
RISK = round(SL - ENTRY, 2)
|
||||
TP1 = round(ENTRY - RISK, 2)
|
||||
assert eq(ENTRY, 83.60) and eq(SL, 84.85) and eq(RISK, 1.25) and eq(TP1, 82.35)
|
||||
LOW_AFTER = min(b[2] for b in DY[11:])
|
||||
assert eq(LOW_AFTER, 83.00) and LOW_AFTER > TP1, "TP1 atins"
|
||||
HIT = next(i for i in range(11, 17) if DY[i][1] >= SL - 1e-9)
|
||||
assert HIT == 14, f"SL lovit la {HIT}"
|
||||
assert eq(DY[0][0], WK[8][3]), "daily nu începe la close-ul confirmării weekly"
|
||||
assert eq(ENTRY, 82.70) and eq(SL, 84.35) and eq(RISK, 1.65) and eq(TP1, 81.05)
|
||||
assert SL - DH > 0.30, "stopul e prea lipit de high-ul doji-ului (etichete suprapuse)"
|
||||
assert DL - ENTRY > 0.30, "intrarea e prea lipită de low-ul doji-ului"
|
||||
LOW_AFTER = min(b[2] for b in DY[9:])
|
||||
assert eq(LOW_AFTER, 82.00) and LOW_AFTER > TP1, "TP1 atins"
|
||||
HIT = next(i for i in range(9, len(DY)) if DY[i][1] >= SL - 1e-9)
|
||||
assert HIT == 12, f"SL lovit la {HIT}"
|
||||
assert min(b[2] for b in DY) < DH < max(b[1] for b in WK), "nivel comun în afara scării"
|
||||
|
||||
# --- desen -------------------------------------------------------------------
|
||||
GREEN, RED, ORANGE, GRAY, INK = "#188a3e", "#c00", "#d06a00", "#777", "#333"
|
||||
GREEN, RED, ORANGE, GRAY, INK, BLUE = "#188a3e", "#c00", "#d06a00", "#777", "#333", "#1a5fb4"
|
||||
BODY_W = 9
|
||||
out = []
|
||||
out, texts = [], []
|
||||
PANEL = "none"
|
||||
|
||||
|
||||
def text(x, y, s, fill=INK, anchor="start", bold=False, size=None):
|
||||
def text(x, y, s, fill=INK, anchor="start", bold=False, size=None, panel=None):
|
||||
st = (["font-weight:bold"] if bold else []) + ([f"font-size:{size}px"] if size else [])
|
||||
style = f' style="{";".join(st)}"' if st else ""
|
||||
a = f' text-anchor="{anchor}"' if anchor != "start" else ""
|
||||
out.append(f'<text x="{x:.1f}" y="{y:.1f}" fill="{fill}"{a}{style}>{s}</text>')
|
||||
texts.append((panel or PANEL, s))
|
||||
|
||||
|
||||
def line(x1, y1, x2, y2, color, width=1.5, dash=None, extra=""):
|
||||
def line(x1, y1, x2, y2, color, width=1.5, dash=None):
|
||||
d = f' stroke-dasharray="{dash}"' if dash else ""
|
||||
out.append(f'<line x1="{x1:.1f}" y1="{y1:.1f}" x2="{x2:.1f}" y2="{y2:.1f}" '
|
||||
f'stroke="{color}" stroke-width="{width}"{d}{extra}/>')
|
||||
f'stroke="{color}" stroke-width="{width}"{d}/>')
|
||||
|
||||
|
||||
def rect(x, y, w, h, fill, opacity, stroke="none"):
|
||||
def rect(x, y, w, h, fill, opacity, stroke="none", dash=None):
|
||||
d = f' stroke-dasharray="{dash}"' if dash else ""
|
||||
out.append(f'<rect x="{x:.1f}" y="{y:.1f}" width="{w:.1f}" height="{h:.1f}" fill="{fill}" '
|
||||
f'fill-opacity="{opacity}" stroke="{stroke}" rx="3"/>')
|
||||
f'fill-opacity="{opacity}" stroke="{stroke}" stroke-width="1.5"{d} rx="3"/>')
|
||||
|
||||
|
||||
def arrow(x1, y1, x2, y2, color):
|
||||
"""Săgeată de la (x1,y1) la vârful (x2,y2): linie + cap triunghi."""
|
||||
d = max(((x2 - x1) ** 2 + (y2 - y1) ** 2) ** 0.5, 1e-9)
|
||||
ux, uy = (x2 - x1) / d, (y2 - y1) / d
|
||||
bx, by = x2 - 9 * ux, y2 - 9 * uy
|
||||
@@ -114,72 +147,121 @@ text(20, 24, "NU: Sell Doji daily contra unui Buy Doji weekly — exemplu constr
|
||||
bold=True, size=14)
|
||||
|
||||
# ---- panoul de sus: weekly ----------------------------------------------------
|
||||
WX0, WGAP, WS, WT = 60, 16, 12.0, 66
|
||||
wmax = max(b[1] for b in WK)
|
||||
PANEL = "weekly"
|
||||
WX0, WGAP, WS, WT = 60, 16, 16, 70
|
||||
wmax, wmin = max(b[1] for b in WK), min(b[2] for b in WK)
|
||||
yw = lambda p: WT + (wmax - p) * WS # noqa: E731
|
||||
xw = lambda i: WX0 + i * WGAP # noqa: E731
|
||||
w_bottom = yw(wmin)
|
||||
text(20, 50, "weekly: direcția", fill="#555", bold=True)
|
||||
rect(xw(7) - 8, yw(WK[8][1]) - 4, WGAP + 16, yw(WK[7][2]) - yw(WK[8][1]) + 8,
|
||||
GREEN, 0.12, GREEN)
|
||||
rect(xw(7) - 8, yw(WK[8][1]) - 4, WGAP + 16, yw(WK[7][2]) - yw(WK[8][1]) + 8, GREEN, 0.12, GREEN)
|
||||
# dreptunghiul de corespondență: exact barele weekly desfăcute în panoul daily
|
||||
ZX1, ZX2, ZY1, ZY2 = xw(Z0) - 8, xw(len(WK) - 1) + 8, WT - 10, w_bottom + 8
|
||||
rect(ZX1, ZY1, ZX2 - ZX1, ZY2 - ZY1, BLUE, 0.07, BLUE, "5 4")
|
||||
text(ZX2 + 12, ZY1 + 14, f"ultimele {ZOOM} săptămâni,", fill=BLUE, size=11)
|
||||
text(ZX2 + 12, ZY1 + 30, "mărite jos", fill=BLUE, size=11)
|
||||
|
||||
# nivelurile comune, desenate identic pe ambele panouri
|
||||
COMMON = [(DH, f"{DH:.2f}", "high doji"), (DL, f"{DL:.2f}", "low doji")]
|
||||
candles(WK, WX0, WGAP, yw)
|
||||
text(xw(7), yw(WK[7][2]) + 16, "doji weekly", fill=ORANGE, anchor="middle", bold=True)
|
||||
# săgeata weekly: ce + de ce
|
||||
arrow(300, yw(WK[8][3]) - 6, xw(8) + 12, yw(WK[8][3]) - 6, GREEN)
|
||||
text(306, yw(WK[8][3]) - 10, f"weekly: Buy Doji confirmat (close {WK[8][3]:.2f}, "
|
||||
f"peste high-ul doji-ului {WK[7][1]:.2f})", fill=GREEN, bold=True)
|
||||
text(306, yw(WK[8][3]) + 8, "deci direcția permisă e LONG: pe daily cauți doar Buy Doji",
|
||||
fill=GREEN)
|
||||
w_bottom = yw(min(b[2] for b in WK))
|
||||
for k, (lvl, lab, _) in enumerate(COMMON):
|
||||
line(44, yw(lvl), ZX2 + 8, yw(lvl), BLUE, 1.2, "3 3")
|
||||
text(40, yw(lvl) + (-2 if k == 0 else 11), lab, fill=BLUE, anchor="end", size=10,
|
||||
panel="common")
|
||||
for k in range(ZOOM):
|
||||
text(xw(Z0 + k), ZY2 - 6, f"S{k + 1}", fill=BLUE, anchor="middle",
|
||||
size=10, bold=True, panel="common")
|
||||
arrow(470, yw(80.50), xw(8) + 11, yw(80.50), GREEN)
|
||||
text(476, yw(80.50) - 4, f"weekly: Buy Doji confirmat (close {WK[8][3]:.2f}, peste "
|
||||
f"high-ul doji-ului {WK[7][1]:.2f})", fill=GREEN, bold=True, size=11)
|
||||
text(476, yw(80.50) + 14, "deci direcția permisă e LONG: pe daily cauți doar Buy Doji",
|
||||
fill=GREEN, size=11)
|
||||
text(xw(7) + 5, ZY2 + 8, "doji weekly", fill=ORANGE, anchor="end", bold=True, size=10)
|
||||
|
||||
# ---- panoul de jos: daily -----------------------------------------------------
|
||||
PANEL = "daily"
|
||||
DX0, DGAP, DS = 60, 18, 40.0
|
||||
DT = w_bottom + 110
|
||||
dmax = max(b[1] for b in DY)
|
||||
FT = w_bottom + 92 # marginea de sus a cadrului daily
|
||||
DT = FT + 14
|
||||
dmax, dmin = max(b[1] for b in DY), min(b[2] for b in DY)
|
||||
yd = lambda p: DT + (dmax - p) * DS # noqa: E731
|
||||
xd = lambda i: DX0 + i * DGAP # noqa: E731
|
||||
line(20, w_bottom + 34, W - 20, w_bottom + 34, "#ddd", 1)
|
||||
text(20, w_bottom + 56, f"daily: zilele de după confirmarea weekly "
|
||||
f"(open {DY[0][0]:.2f} = close-ul ei)", fill="#555", bold=True)
|
||||
FX1, FX2 = xd(0) - 14, xd(len(DY) - 1) + 14
|
||||
FB = max(yd(dmin), yd(TP1)) + 14
|
||||
|
||||
LX1, LX2, COL = xd(9) - 10, 392, 400
|
||||
# ghidajele: colțurile de jos ale dreptunghiului -> colțurile de sus ale cadrului
|
||||
line(ZX1, ZY2, FX1, FT, BLUE, 1.2, "5 4")
|
||||
line(ZX2, ZY2, FX2, FT, BLUE, 1.2, "5 4")
|
||||
out.append(f'<rect x="{FX1:.1f}" y="{FT:.1f}" width="{FX2 - FX1:.1f}" height="{FB - FT:.1f}" '
|
||||
f'fill="none" stroke="{BLUE}" stroke-width="1.5" stroke-dasharray="5 4" rx="3"/>')
|
||||
text(FX2 + 34, ZY2 + 26, f"daily: aceleași {ZOOM} săptămâni, zi cu zi", fill=BLUE, bold=True)
|
||||
text(FX2 + 34, ZY2 + 44, f"({GROUP} zile = o bară weekly; open, close și extremele coincid)",
|
||||
fill="#555", size=11)
|
||||
|
||||
rect(xd(7) - 8, yd(DY[7][1]) - 3, DGAP + 16, yd(DY[8][2]) - yd(DY[7][1]) + 6, ORANGE, 0.15, ORANGE)
|
||||
for k, (lvl, lab, _) in enumerate(COMMON):
|
||||
line(44, yd(lvl), FX2 + 14, yd(lvl), BLUE, 1.2, "3 3")
|
||||
text(40, yd(lvl) + 4, lab, fill=BLUE, anchor="end", size=10, panel="common")
|
||||
|
||||
LX2, COL = 340, 348
|
||||
y_sl, y_e, y_tp = yd(SL), yd(ENTRY), yd(TP1)
|
||||
assert abs((y_sl - y_e) - (y_e - y_tp)) < 1e-6, "SL-intrare != intrare-TP1 în px"
|
||||
rect(xd(9) - 8, yd(DY[9][1]) - 3, DGAP + 16, yd(DY[10][2]) - yd(DY[9][1]) + 6, ORANGE, 0.15, ORANGE)
|
||||
line(LX1, y_sl, LX2, y_sl, RED, 1.5, "6 3")
|
||||
line(xd(10) - 8, y_e, LX2, y_e, ORANGE, 1.5, "6 3")
|
||||
line(xd(10) - 8, y_tp, LX2, y_tp, GRAY, 1.5, "2 3")
|
||||
text(COL, y_sl + 4, f"stop {SL:.2f} = 5 cenți peste high-ul de 4 candele", fill=RED, size=11)
|
||||
text(COL, y_e + 4, f"intrare short {ENTRY:.2f}", fill=ORANGE, size=11)
|
||||
text(COL, y_tp + 4, f"ținta {TP1:.2f} — neatinsă (a scăzut o zi, la {LOW_AFTER:.2f})",
|
||||
fill=GRAY, size=11)
|
||||
line(44, y_sl, LX2, y_sl, RED, 1.5, "6 3")
|
||||
line(xd(8) - 8, y_e, LX2, y_e, ORANGE, 1.5, "6 3")
|
||||
line(xd(8) - 8, y_tp, LX2, y_tp, GRAY, 1.5, "2 3")
|
||||
text(COL, y_sl + 4, f"stop {SL:.2f}", fill=RED, size=11, bold=True)
|
||||
text(COL, y_e + 4, f"intrare short {ENTRY:.2f}", fill=ORANGE, size=11, bold=True)
|
||||
text(COL, y_tp + 4, f"ținta {TP1:.2f} = 1R — neatinsă", fill=GRAY, size=11)
|
||||
candles(DY, DX0, DGAP, yd)
|
||||
for k in range(ZOOM):
|
||||
text((xd(k * GROUP) + xd(k * GROUP + GROUP - 1)) / 2, FB + 16, f"S{k + 1}",
|
||||
fill=BLUE, anchor="middle", size=10, bold=True, panel="common")
|
||||
text((xd(7) + xd(8)) / 2, yd(DY[7][1]) - 6, "doji daily", fill=ORANGE, anchor="middle",
|
||||
bold=True, size=10)
|
||||
|
||||
# SL lovit la bara 14: săgeată din dreapta sus
|
||||
cx14 = xd(HIT)
|
||||
out.append(f'<circle cx="{cx14:.1f}" cy="{y_sl:.1f}" r="7" fill="none" stroke="{RED}" '
|
||||
cx_hit = xd(HIT)
|
||||
out.append(f'<circle cx="{cx_hit:.1f}" cy="{y_sl:.1f}" r="7" fill="none" stroke="{RED}" '
|
||||
f'stroke-width="2"/>')
|
||||
y_hit = DT - 40
|
||||
arrow(560, y_hit + 6, cx14 + 9, y_sl - 9, RED)
|
||||
text(568, y_hit, f"aici te-a scos: stopul {SL:.2f}, lovit a {HIT - 10}-a zi", fill=RED, bold=True)
|
||||
text(568, y_hit + 18, "banii mari de pe weekly cumpără mai departe", fill=RED)
|
||||
arrow(512, y_sl - 26, cx_hit + 9, y_sl - 9, RED)
|
||||
text(520, y_sl - 30, f"aici te-a scos: stopul {SL:.2f} (5 cenți peste high-ul de 4 candele),",
|
||||
fill=RED, bold=True, size=11)
|
||||
text(520, y_sl - 12, f"lovit a {HIT - 8}-a zi — banii mari de pe weekly cumpără mai departe",
|
||||
fill=RED, size=11)
|
||||
|
||||
# intrarea short: săgeată de jos-dreapta spre close-ul confirmării
|
||||
y_bot = yd(min(b[2] for b in DY))
|
||||
x_e = xd(12) + 6
|
||||
arrow(x_e, y_tp + 30, xd(10) + 7, y_e + 4, ORANGE)
|
||||
text(x_e + 6, y_tp + 28, f"aici ai intrat SHORT, la {ENTRY:.2f}: Sell Doji corect ca formă",
|
||||
fill=ORANGE, bold=True)
|
||||
text(x_e + 6, y_tp + 46, f"(close sub low-ul doji-ului {DY[9][2]:.2f}) — dar contra weekly-ului",
|
||||
fill=ORANGE)
|
||||
arrow(512, y_e + 52, xd(8) + 8, y_e + 4, ORANGE)
|
||||
text(520, y_e + 48, f"aici ai intrat SHORT, la {ENTRY:.2f}: Sell Doji corect ca formă",
|
||||
fill=ORANGE, bold=True, size=11)
|
||||
text(520, y_e + 66, f"(close sub low-ul doji-ului {DL:.2f}) — dar contra weekly-ului",
|
||||
fill=ORANGE, size=11)
|
||||
text(520, y_e + 84, f"a scăzut o singură zi, până la {LOW_AFTER:.2f}", fill=ORANGE, size=11)
|
||||
|
||||
y0 = max(y_bot, y_tp + 46) + 34
|
||||
text(20, y0, "Seria e construită: un singur caz ilustrează regula, nu o dovedește.", fill="#555")
|
||||
H = y0 + 24
|
||||
PANEL = "none"
|
||||
y0 = FB + 42
|
||||
text(20, y0, "Liniile albastre punctate sunt aceleași niveluri pe ambele panouri: "
|
||||
"high-ul și low-ul doji-ului daily.", fill="#555", size=11)
|
||||
text(20, y0 + 18, "Seria e construită: un singur caz ilustrează regula, nu o dovedește.",
|
||||
fill="#555", size=11)
|
||||
H = y0 + 40
|
||||
|
||||
# --- niciun număr scris de mână: fiecare preț din etichete vine din serii ------
|
||||
WNUM = {f"{v:.2f}" for b in WK for v in b}
|
||||
DNUM = {f"{v:.2f}" for b in DY for v in b} | {f"{v:.2f}" for v in (SL, ENTRY, TP1, RISK)}
|
||||
CNUM = {f"{DH:.2f}", f"{DL:.2f}"}
|
||||
ALLOWED = {"weekly": WNUM, "daily": DNUM, "common": CNUM, "none": set()}
|
||||
for panel, s in texts:
|
||||
for n in re.findall(r"\d+\.\d\d", s):
|
||||
assert n in ALLOWED[panel], f"eticheta {panel} conține prețul străin {n}: {s!r}"
|
||||
# nivelurile comune apar cu exact același număr pe ambele panouri
|
||||
com = [s for p, s in texts if p == "common" and re.fullmatch(r"\d+\.\d\d", s)]
|
||||
for lab in (f"{DH:.2f}", f"{DL:.2f}"):
|
||||
assert com.count(lab) == 2, f"nivelul comun {lab} nu apare pe ambele panouri: {com}"
|
||||
|
||||
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
|
||||
OUT.write_text(svg, encoding="utf-8")
|
||||
print(f"intrare {ENTRY:.2f} SL {SL:.2f} risc {RISK:.2f} TP1 {TP1:.2f} | min după {LOW_AFTER:.2f} | "
|
||||
f"SL lovit la bara {HIT}")
|
||||
for k in range(ZOOM):
|
||||
print(f"S{k+1}: weekly {WK[Z0+k]} = zilele {k*GROUP}..{k*GROUP+GROUP-1}")
|
||||
print(f"intrare {ENTRY:.2f} SL {SL:.2f} risc {RISK:.2f} TP1 {TP1:.2f} | "
|
||||
f"doji high {DH:.2f} low {DL:.2f} | min după {LOW_AFTER:.2f} | SL lovit la bara {HIT}")
|
||||
print(f"scris {OUT} ({W}x{H:.0f})")
|
||||
|
||||
Reference in New Issue
Block a user