Mostra de format ceruta de user pe 19.09.2026 — de aprobat inainte sa aplicam formatul la celelalte 8 capitole. Specificatia e in STYLE.md, sectiunea FORMAT TUTORIAL. Structura: pasii 1-9 in ordinea in care faci lucrurile la ecran, fiecare terminat cu "Treci mai departe doar daca...". Cele trei perechi de TF (weekly-daily, monthly-weekly, daily-60min) una langa alta in pasul 2; perechea monthly-weekly e acoperita doar partial de material si scrie asta. Nivelurile 0/1/2 desfiintate ca structura. Niciun tabel de 2 coloane. Sursele mutate la final. 14 diagrame, de la 5: cele 5 vechi raman neatinse, 9 noi. Un agent per diagrama, fiecare cu audit + PNG privit. Toate 9 ies curate. 10 reguli din material care lipseau din versiunea veche, fiecare cu citat si fisier:linie — printre ele stopul se numara pe TF-ul deciziei (20_2025-08-25:9-15) si nu muti ordinul dupa pret (03_locul_crimei:296-311). Contradictie noua, a 7-a: pe weekly regula de pivoti spune Quarterly, dar MICI citeste pivotul monthly. Completarile proprii stau in blocuri "Completare proprie:", conform deciziei userului de azi. Cunoscut, nereparat: audit_diagrams da 9 fals-pozitive pe buy_doji_aplus si buy_doji_fereastra, care folosesc <g transform> (auditul nu-l intelege). A zecea, in buy_doji_sell (titlu peste eticheta "doji 99.20 / 100.10"), e reala si preexistenta. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ThN7ixZrowca8aTGrK1MUJ
215 lines
11 KiB
Python
215 lines
11 KiB
Python
#!/usr/bin/env python3
|
|
"""Buy Doji — aceeași regulă pe trei perechi de timeframe-uri.
|
|
|
|
Citește `gen/strategii/buy_doji_perechi_tf.spec.txt` și scrie
|
|
`gen/strategii/buy_doji_perechi_tf.svg`. Trei rânduri (weekly→daily,
|
|
monthly→weekly, daily→60 min); pe fiecare, stânga = TF mare (11 candele,
|
|
buy doji + confirmare), dreapta = TF mic (15 candele, pullback de 8 candele
|
|
la high-ul dojiului mare, doji acolo, confirmare, intrare).
|
|
|
|
Rândul A e seria din spec; B și C se CALCULEAZĂ din A prin
|
|
p' = round(100 + f*(p - 100), 2), cu f = 2.5 și 0.4. Cifrele din desen ies
|
|
din serii, iar assert-urile le compară cu valorile afirmate în spec.
|
|
Coordonatele sunt absolute (fără <g transform>), ca audit_diagrams.py să
|
|
vadă pozițiile reale ale textelor.
|
|
|
|
Rulare: python3 gen/strategii/buy_doji_perechi_tf.py
|
|
"""
|
|
from pathlib import Path
|
|
|
|
OUT = Path(__file__).with_suffix(".svg")
|
|
W = 720
|
|
|
|
# --- seria A din spec: (open, high, low, close) ----------------------------
|
|
BIG_A = [(122.0, 122.6, 119.4, 120.0), (120.0, 120.5, 117.0, 117.5), (117.5, 119.0, 117.1, 118.4),
|
|
(118.4, 118.8, 114.3, 114.8), (114.8, 115.3, 111.5, 112.0), (112.0, 113.4, 111.6, 112.9),
|
|
(112.9, 113.2, 109.1, 109.6), (109.6, 110.0, 106.3, 106.8), (106.8, 107.2, 103.6, 104.0),
|
|
(104.0, 105.2, 101.9, 103.8), # 9: doji
|
|
(103.8, 107.4, 103.5, 107.0)] # 10: confirmare
|
|
SMALL_A = [(107.0, 107.2, 106.4, 106.6), (106.6, 106.8, 106.0, 106.1), (106.1, 106.6, 105.95, 106.4),
|
|
(106.4, 106.5, 105.8, 105.9), (105.9, 106.0, 105.5, 105.6), (105.6, 105.95, 105.5, 105.8),
|
|
(105.8, 105.9, 105.4, 105.5), (105.5, 105.6, 105.25, 105.35),
|
|
(105.35, 105.75, 105.10, 105.40), # 8: doji la nivel
|
|
(105.40, 106.40, 105.30, 106.30), # 9: confirmare => intrare
|
|
(106.3, 107.0, 106.2, 106.9), (106.9, 107.5, 106.7, 107.4), (107.4, 107.55, 107.0, 107.2),
|
|
(107.2, 108.0, 107.1, 107.9), (107.9, 108.6, 107.8, 108.5)]
|
|
|
|
|
|
def scaled(bars, f):
|
|
return [tuple(round(100 + f * (p - 100), 2) for p in b) for b in bars]
|
|
|
|
|
|
# key, f, TF mare, TF mic, text sub rând, valori așteptate din spec
|
|
ROWS = [
|
|
("A", 1.0, "weekly", "daily",
|
|
"stop {p:.2f} % (pe weekly ar fi {pb:.2f} %) · 8 candele = 8 zile de bursă",
|
|
dict(dh=105.20, dl=101.90, conf=107.00, sdl=105.10, entry=106.30, sl=105.10, risk=1.20,
|
|
pct=1.13, tp=107.50, b_entry=107.00, b_sl=101.90, b_risk=5.10, b_pct=4.77)),
|
|
("B", 2.5, "monthly", "weekly",
|
|
"stop {p:.2f} % (pe monthly ar fi {pb:.2f} %) · 8 candele = 8 săptămâni",
|
|
dict(dh=113.00, dl=104.75, conf=117.50, sdl=112.75, entry=115.75, sl=112.75, risk=3.00,
|
|
pct=2.59, tp=118.75, b_entry=117.50, b_sl=104.75, b_risk=12.75, b_pct=10.85)),
|
|
("C", 0.4, "daily", "60 min",
|
|
"stop {p:.2f} % (pe daily ar fi {pb:.2f} %) · 8 candele ≈ 1,2 zile de bursă (7 bare/zi)",
|
|
dict(dh=102.08, dl=100.76, conf=102.80, sdl=102.04, entry=102.52, sl=102.04, risk=0.48,
|
|
pct=0.47, tp=103.00, b_entry=102.80, b_sl=100.76, b_risk=2.04, b_pct=1.98)),
|
|
]
|
|
|
|
|
|
def eq(a, b):
|
|
return abs(a - b) < 0.005
|
|
|
|
|
|
def check(key, big, small, exp):
|
|
"""Assert-urile obligatorii din spec, pe seriile calculate; întoarce nivelurile."""
|
|
for name, bars in (("mare", big), ("mic", small)):
|
|
for i, (o, h, l, c) in enumerate(bars):
|
|
assert h >= max(o, c) and l <= min(o, c), f"{key} {name}[{i}] h/l incoerent"
|
|
if i:
|
|
assert eq(o, bars[i - 1][3]), f"{key} {name}[{i}] open != close precedent"
|
|
assert len(big) == 11 and len(small) == 15
|
|
assert eq(small[0][0], big[10][3]), f"{key}: open mic[0] != close mare[10]"
|
|
for name, (o, h, l, c) in (("doji mare", big[9]), ("doji mic", small[8])):
|
|
assert abs(o - c) <= 0.10 * (h - l) + 1e-9, f"{key}: {name} nu e doji"
|
|
assert big[10][3] > big[9][1] and small[9][3] > small[8][1], f"{key}: fără confirmare"
|
|
sl = min(b[2] for b in small[6:10])
|
|
assert eq(sl, small[8][2]), f"{key}: SL nu e low-ul dojiului mic"
|
|
entry = small[9][3]
|
|
risk = round(entry - sl, 2)
|
|
tp = round(entry + risk, 2)
|
|
first = next(i for i in range(10, 15) if small[i][1] >= tp - 1e-9)
|
|
assert first == 11, f"{key}: TP1 atins la {first}, nu la 11"
|
|
pct = risk / entry * 100
|
|
b_entry, b_sl = big[10][3], big[9][2]
|
|
b_risk = round(b_entry - b_sl, 2)
|
|
b_pct = b_risk / b_entry * 100
|
|
got = dict(dh=big[9][1], dl=big[9][2], conf=big[10][3], sdl=small[8][2], entry=entry, sl=sl,
|
|
risk=risk, pct=pct, tp=tp, b_entry=b_entry, b_sl=b_sl, b_risk=b_risk, b_pct=b_pct)
|
|
for k, v in exp.items():
|
|
assert eq(round(got[k], 2), v), f"{key}: {k} = {got[k]:.4f}, spec {v}"
|
|
assert abs(small[8][2] - big[9][1]) / big[9][1] <= 0.005, f"{key}: doji mic nu e la nivel"
|
|
assert 4 <= b_pct / pct <= 5, f"{key}: raport stopuri {b_pct / pct:.2f} nu e 4-5x"
|
|
return got
|
|
|
|
|
|
# --- desen ------------------------------------------------------------------
|
|
GREEN, RED, GRAY, INK = "#188a3e", "#c00", "#888", "#333"
|
|
PLOT_H = 160 # înălțimea zonei de preț, egală pe toate graficele
|
|
BX0, BGAP = 40, 16 # TF mare: x candele
|
|
SX0, SGAP = 392, 15 # TF mic
|
|
BODY_W = 9
|
|
SL_X1, SL_X2 = 380, 614 # liniile de nivel pe graficul mic
|
|
COL_X = 620 # coloana cu etichetele de nivel ale graficului mic
|
|
|
|
out = []
|
|
|
|
|
|
def text(x, y, s, fill=INK, anchor="start", bold=False, size=None):
|
|
st = []
|
|
if bold:
|
|
st.append("font-weight:bold")
|
|
if size:
|
|
st.append(f"font-size:{size}px")
|
|
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>')
|
|
|
|
|
|
def hline(x1, x2, y, color, dash):
|
|
out.append(f'<line x1="{x1:.1f}" y1="{y:.1f}" x2="{x2:.1f}" y2="{y:.1f}" '
|
|
f'stroke="{color}" stroke-width="1.5" stroke-dasharray="{dash}"/>')
|
|
|
|
|
|
def candles(bars, x0, gap, yf):
|
|
for i, (o, h, l, c) in enumerate(bars):
|
|
cx = x0 + i * gap
|
|
color = GREEN if c >= o else RED
|
|
out.append(f'<line x1="{cx:.1f}" y1="{yf(h):.1f}" x2="{cx:.1f}" y2="{yf(l):.1f}" '
|
|
f'stroke="#1a1a1a" stroke-width="1.2"/>')
|
|
top = yf(max(o, c))
|
|
hh = max(yf(min(o, c)) - top, 1.6)
|
|
out.append(f'<rect x="{cx - BODY_W / 2:.1f}" y="{top:.1f}" width="{BODY_W}" '
|
|
f'height="{hh:.1f}" fill="{color}"/>')
|
|
|
|
|
|
out.append('<defs><marker id="pt-arr" 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="#555"/></marker></defs>')
|
|
text(20, 22, "Aceeași regulă pe trei perechi — exemplu construit (fără ticker)", bold=True, size=14)
|
|
|
|
y0 = 40
|
|
ROW_H = 290
|
|
for key, f, tf_big, tf_small, cap, exp in ROWS:
|
|
big, small = scaled(BIG_A, f), scaled(SMALL_A, f)
|
|
g = check(key, big, small, exp)
|
|
|
|
# titlu rând + subtitluri
|
|
text(20, y0 + 14, f"{key}. {tf_big} → {tf_small}", bold=True, size=13)
|
|
text(BX0 - 16, y0 + 32, f"{tf_big}: direcția", fill="#555")
|
|
text(SL_X1, y0 + 32, f"{tf_small}: intrarea", fill="#555")
|
|
top = y0 + 50
|
|
|
|
# scară proprie pe fiecare grafic, constantă în interiorul lui
|
|
bmax = max(b[1] for b in big); bmin = min(b[2] for b in big)
|
|
smax = max(b[1] for b in small); smin = min(b[2] for b in small)
|
|
bs, ss = PLOT_H / (bmax - bmin), PLOT_H / (smax - smin)
|
|
yb = lambda p, t=top, m=bmax, s=bs: t + (m - p) * s # noqa: E731
|
|
ys = lambda p, t=top, m=smax, s=ss: t + (m - p) * s # noqa: E731
|
|
|
|
# TF mare
|
|
hline(BX0 - 16, BX0 + 10 * BGAP + 12, yb(g["dh"]), GRAY, "3 3")
|
|
text(BX0 - 14, yb(g["dh"]) + 14, f"high doji {tf_big} {g['dh']:.2f}", fill="#666")
|
|
candles(big, BX0, BGAP, yb)
|
|
text(BX0 + 9 * BGAP, yb(g["dl"]) + 15, "doji", fill="#b06000", anchor="middle", bold=True)
|
|
text(BX0 + 10 * BGAP - 4, yb(big[10][2]) + 15, "confirmare: long permis",
|
|
fill=GREEN, bold=True)
|
|
|
|
# zoom: de la close-ul confirmării mari la open-ul primei bare mici (același preț)
|
|
x1, y1 = BX0 + 10 * BGAP + 8, yb(g["conf"])
|
|
x2, y2 = SX0 - 10, ys(g["conf"])
|
|
out.append(f'<line x1="{x1:.1f}" y1="{y1:.1f}" x2="{x2:.1f}" y2="{y2:.1f}" stroke="#555" '
|
|
f'stroke-width="1.3" stroke-dasharray="5 3" marker-end="url(#pt-arr)"/>')
|
|
text(300, y1 + 2, f"zoom pe {tf_small},", fill="#555")
|
|
text(300, y1 + 16, "după confirmare", fill="#555")
|
|
|
|
# TF mic: niveluri
|
|
y_dh, y_e, y_sl, y_tp = ys(g["dh"]), ys(g["entry"]), ys(g["sl"]), ys(g["tp"])
|
|
assert abs((y_sl - y_e) / (y_e - y_tp) - 1) < 1e-6, f"{key}: SL-intrare != intrare-TP1 în px"
|
|
hline(SL_X1, SL_X2, y_dh, GRAY, "3 3")
|
|
hline(SL_X1, SL_X2, y_e, GREEN, "6 3")
|
|
hline(SL_X1, SL_X2, y_sl, RED, "6 3")
|
|
hline(SL_X1, SL_X2, y_tp, GREEN, "2 3")
|
|
text(COL_X, y_tp + 4, f"TP1 1:1 {g['tp']:.2f}", fill=GREEN, size=11)
|
|
text(COL_X, y_e + 4, f"intrare {g['entry']:.2f}", fill=GREEN, size=11)
|
|
text(COL_X, y_dh - 5, f"high doji {g['dh']:.2f}", fill="#666", size=11)
|
|
text(COL_X, y_sl + 12, f"SL {g['sl']:.2f}", fill=RED, size=11)
|
|
text(COL_X, y_sl + 25, "= low 4 candele", fill=RED, size=11)
|
|
candles(small, SX0, SGAP, ys)
|
|
|
|
# paranteză peste pullback-ul de 8 candele (barele 0..7)
|
|
yb8 = y_tp - 12
|
|
xa, xb = SX0 - 5, SX0 + 7 * SGAP + 5
|
|
out.append(f'<path d="M{xa:.1f},{yb8 + 6:.1f} V{yb8:.1f} H{xb:.1f} V{yb8 + 6:.1f}" '
|
|
f'fill="none" stroke="#555" stroke-width="1"/>')
|
|
text((xa + xb) / 2, yb8 - 5, "pullback: 8 candele", fill="#555", anchor="middle")
|
|
text(SX0 + 8 * SGAP + 4, y_sl + 15, "doji la nivel", fill="#b06000", anchor="end", bold=True)
|
|
text(SX0 + 9 * SGAP - 8, y_e - 5, "intrare", fill=GREEN, anchor="end", bold=True)
|
|
|
|
# text sub rând
|
|
text(20, top + PLOT_H + 50, cap.format(p=g["pct"], pb=g["b_pct"]), fill=INK, bold=True)
|
|
print(f"{key}: doji mare {g['dh']:.2f}/{g['dl']:.2f} conf {g['conf']:.2f} | intrare {g['entry']:.2f} "
|
|
f"SL {g['sl']:.2f} risc {g['risk']:.2f} ({g['pct']:.2f} %) TP1 {g['tp']:.2f} | "
|
|
f"doar mare: risc {g['b_risk']:.2f} ({g['b_pct']:.2f} %)")
|
|
y0 += ROW_H
|
|
if key != "C":
|
|
out.append(f'<line x1="20" y1="{y0 - 14:.0f}" x2="{W - 20}" y2="{y0 - 14:.0f}" stroke="#ddd"/>')
|
|
|
|
text(20, y0 + 4, "Cifre construite, aceeași formă scalată pe trei perechi.", fill="#555")
|
|
text(20, y0 + 21, "Procentele reale depind de simbol (LABU daily: stop 9 %).", fill="#555")
|
|
H = y0 + 32
|
|
|
|
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"scris {OUT} ({W}x{H:.0f})")
|