Userul a respins mostra de format tutorial: prea lunga, prea multe trimiteri in text, termeni tehnici nedefiniti, prea multe cifre fara desen. Cerintele lui sunt acum contract in STYLE.md (FORMAT TUTORIAL v2). Textul: 8430 -> 4190 de cuvinte. Nivel 1 (din avion, cu mini-glosar) -> nivel 2 (6 pasi mecanici, o diagrama fiecare) -> oglinda short -> nivel 3 (rafinari). Trimiterile fisier:linie ies din corp si se aduna la final, cu legenda de prescurtari. Doua tabele in tot documentul. Toate cele 14 diagrame refacute: zone dreptunghiulare de evidentiere si sageti care spun si de ce, nu doar ce. svg_candles primeste Zone si Arrow; render() nu mai muta obiectul la iesire. Trei corecturi de continut: - un singur trade prin toata procedura (doji 50.10/49.25, intrare 50.40, stop 49.20, 1R 1.20, TP1 51.60); seria e acum o functie comuna, importata de stop/transe/iesire - short: stopul urca la 100.15, ca sa fie deasupra maximului cum cere regula oglindita; cifra cursului ramane in text ca observatie - LABU: trei minime dar doua atingeri ale pivotului 86.00, ceea ce impaca "triple bottom" cu "al doilea test", ambele din transcriere Reparat si bug-ul vechi din buy_doji_sell.svg: titlul peste eticheta dojiului. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ThN7ixZrowca8aTGrK1MUJ
186 lines
8.0 KiB
Python
186 lines
8.0 KiB
Python
#!/usr/bin/env python3
|
|
"""Buy Doji — NU: Sell Doji daily contra unui Buy Doji weekly.
|
|
|
|
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.
|
|
|
|
Rulare: python3 gen/strategii/buy_doji_directie.py
|
|
"""
|
|
from pathlib import Path
|
|
|
|
OUT = Path(__file__).with_suffix(".svg")
|
|
W = 900
|
|
|
|
|
|
def trend(start, closes, wick=0.15):
|
|
bars, o = [], start
|
|
for c in closes:
|
|
bars.append((o, round(max(o, c) + wick, 2), round(min(o, c) - wick, 2), c))
|
|
o = c
|
|
return bars
|
|
|
|
|
|
# --- seriile din spec: (open, high, low, close) -----------------------------
|
|
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)]
|
|
|
|
|
|
def eq(a, b):
|
|
return abs(a - b) < 0.005
|
|
|
|
|
|
# --- assert-urile din spec ---------------------------------------------------
|
|
assert len(WK) == 10 and len(DY) == 17
|
|
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 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)
|
|
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"
|
|
|
|
# --- desen -------------------------------------------------------------------
|
|
GREEN, RED, ORANGE, GRAY, INK = "#188a3e", "#c00", "#d06a00", "#777", "#333"
|
|
BODY_W = 9
|
|
out = []
|
|
|
|
|
|
def text(x, y, s, fill=INK, anchor="start", bold=False, size=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>')
|
|
|
|
|
|
def line(x1, y1, x2, y2, color, width=1.5, dash=None, extra=""):
|
|
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}/>')
|
|
|
|
|
|
def rect(x, y, w, h, fill, opacity, stroke="none"):
|
|
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"/>')
|
|
|
|
|
|
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
|
|
line(x1, y1, bx, by, color, 1.8)
|
|
out.append(f'<polygon points="{x2:.1f},{y2:.1f} {bx - 4.5 * uy:.1f},{by + 4.5 * ux:.1f} '
|
|
f'{bx + 4.5 * uy:.1f},{by - 4.5 * ux:.1f}" fill="{color}"/>')
|
|
|
|
|
|
def candles(bars, x0, gap, yf):
|
|
for i, (o, h, l, c) in enumerate(bars):
|
|
cx = x0 + i * gap
|
|
line(cx, yf(h), cx, yf(l), "#1a1a1a", 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="{GREEN if c >= o else RED}"/>')
|
|
|
|
|
|
text(20, 24, "NU: Sell Doji daily contra unui Buy Doji weekly — exemplu construit",
|
|
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)
|
|
yw = lambda p: WT + (wmax - p) * WS # noqa: E731
|
|
xw = lambda i: WX0 + i * WGAP # noqa: E731
|
|
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)
|
|
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, "weekly: Buy Doji confirmat (close 81.30, peste high-ul doji-ului 79.80)",
|
|
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))
|
|
|
|
# ---- panoul de jos: daily -----------------------------------------------------
|
|
DX0, DGAP, DS = 60, 18, 40.0
|
|
DT = w_bottom + 110
|
|
dmax = max(b[1] 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, "daily: săptămânile de după confirmare (open 81.30 = close-ul ei)",
|
|
fill="#555", bold=True)
|
|
|
|
LX1, LX2, COL = xd(9) - 10, 392, 400
|
|
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, "stop 84.85 = 5 cenți peste high-ul de 4 candele", fill=RED, size=11)
|
|
text(COL, y_e + 4, "intrare short 83.60", fill=ORANGE, size=11)
|
|
text(COL, y_tp + 4, "ținta 82.35 — neatinsă (a scăzut o zi, la 83.00)",
|
|
fill=GRAY, size=11)
|
|
candles(DY, DX0, DGAP, yd)
|
|
|
|
# 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}" '
|
|
f'stroke-width="2"/>')
|
|
y_hit = DT - 40
|
|
arrow(560, y_hit + 6, cx14 + 9, y_sl - 9, RED)
|
|
text(568, y_hit, "aici te-a scos: stopul 84.85, lovit a patra zi", fill=RED, bold=True)
|
|
text(568, y_hit + 18, "banii mari de pe weekly cumpără mai departe", fill=RED)
|
|
|
|
# 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, "aici ai intrat SHORT, la 83.60: Sell Doji corect ca formă",
|
|
fill=ORANGE, bold=True)
|
|
text(x_e + 6, y_tp + 46, "(close sub low-ul doji-ului 83.95) — dar contra weekly-ului",
|
|
fill=ORANGE)
|
|
|
|
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
|
|
|
|
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}")
|
|
print(f"scris {OUT} ({W}x{H:.0f})")
|