buy_doji: capitolul rescris ca tutorial in 9 pasi, cu 9 diagrame noi

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
This commit is contained in:
Claude Agent
2026-09-19 18:27:13 +00:00
parent a8be65532a
commit 9c1936e73a
32 changed files with 6343 additions and 1143 deletions

View File

@@ -0,0 +1,138 @@
#!/usr/bin/env python3
"""Scanerul Buy Doji în TradeStation — ordinea filtrelor și capcanele.
Schemă conceptuală (NU grafic de preț): pâlnie de cutii tot mai înguste, de
sus în jos; în dreapta fiecărei cutii, notele și capcanele ei (roșu).
Pentru `strategii/01_buy_doji.md`, Pasul 1. Spec: `buy_doji_scaner.spec.txt`.
Fără cifre de „câte simboluri rămân" — materialul nu le dă.
Rulare: .venv/bin/python gen/strategii/buy_doji_scaner.py
"""
import html
import textwrap
from pathlib import Path
OUT = Path(__file__).resolve().parent / "buy_doji_scaner.svg"
W = 640
BLACK, GRAY, RED, BLUE, GREEN = "#000000", "#666666", "#c00", "#1a5fb4", "#188a3e"
GREEN_BG, BOX_BG = "#eef7f0", "#eef3fb"
BOX_CX = 165 # axa pâlniei (coloana stângă)
NOTE_X = 340 # începutul coloanei de note
NOTE_CHARS = 44 # wrap pentru note, la 12px
BOX_FS, NOTE_FS, LH = 13, 12, 16
WIDTHS = [300, 280, 260, 240, 220, 200]
# (linii în cutie, [(tip, text)]); tip: "n" = notă gri, "c" = capcană
BOXES = [
(["Univers:", "All Stocks (~13.000)", "S&P 500 · Russell 3000"],
[("n", "mai mare = găsește mai mult, durează mai mult")]),
(["Trigger: jbtgrshdATM", "BDATM = 0", "(Sell Doji: SDATM = 0)"],
[("c", "Interval = TF-ul scanerului, setat manual"),
("c", "0 = ultima candelă închisă; 1 = cu o zi în urmă")]),
(["Nivel: RSV1ATM", "% of PP ≤ 0.5 (60 min)", "% of PP ≤ 2 (daily)"],
[("n", "Pivot Interval: 2 = weekly (pe 60 min), 3 = monthly (pe daily)"),
("c", "RS/SR = 1 la filtrare; cu 100, pragul se scrie ×100")]),
(["Lichiditate: Volume Colors", "Average Volume ≥ 500.000",
"Average Length = 10"],
[("n", "volum daily, indiferent de TF-ul scanerului")]),
(["Opțional:", "Since Bear ≥ 8", "Bull Reversal = 1", "RVOL doar Display"],
[("c", "Bear Reversal e selectat implicit — comută pe Bull"),
("c", "în zile extreme, pragurile rigide golesc lista")]),
(["Lista de candidați", "→ o verifici pe grafic", "(pașii 2-6).",
"Scanerul nu e undă verde."], []),
]
FOOT = ("Delay: scanerul vede candela închisă — daily rulat la 12:00 "
"arată triggerul de ieri.")
def est_w(s, fs, bold=False):
return len(s) * fs * (0.60 if bold else 0.55)
def txt(x, y, s, fs, fill=BLACK, bold=False, anchor="start"):
return (f'<text x="{x:g}" y="{y:g}" font-family="system-ui, sans-serif" '
f'font-size="{fs}" font-weight="{700 if bold else 400}" fill="{fill}" '
f'text-anchor="{anchor}">{html.escape(s, quote=False)}</text>')
svg, bboxes, rects = [], [], [] # bboxes: (x0, x1, ytop, ybot, text)
def put(x, y, s, fs, fill=BLACK, bold=False, anchor="start"):
w = est_w(s, fs, bold)
x0 = x - w / 2 if anchor == "middle" else x
bboxes.append((x0, x0 + w, y - fs * 0.8, y + fs * 0.25, s))
svg.append(txt(x, y, s, fs, fill, bold, anchor))
return x0, x0 + w
put(W / 2, 30, "Scanerul Buy Doji în TradeStation", 18, bold=True, anchor="middle")
put(W / 2, 52, "ordinea filtrelor și capcanele", 13, GRAY, anchor="middle")
y = 76
for i, (lines, notes) in enumerate(BOXES):
last = i == len(BOXES) - 1
note_lines = []
for kind, s in notes:
pre = "capcană: " if kind == "c" else ""
wrapped = textwrap.wrap(pre + s, NOTE_CHARS)
note_lines.append([(kind, ln) for j, ln in enumerate(wrapped)])
n_note = sum(len(n) for n in note_lines) + max(0, len(note_lines) - 1) * 0.4
h = max(len(lines), n_note) * LH + 20
w = WIDTHS[i]
x = BOX_CX - w / 2
rects.append((x, y, w, h))
stroke, fill = (GREEN, GREEN_BG) if last else (BLUE, BOX_BG)
svg.append(f'<rect x="{x:g}" y="{y:g}" width="{w}" height="{h:g}" rx="6" '
f'fill="{fill}" stroke="{stroke}" stroke-width="{2 if last else 1.5}"/>')
ty = y + (h - len(lines) * LH) / 2 + 12
for j, ln in enumerate(lines):
x0, x1 = put(BOX_CX, ty + j * LH, ln, BOX_FS, GREEN if last else BLACK,
bold=(j == 0), anchor="middle")
assert x0 >= x + 6 and x1 <= x + w - 6, f"textul nu încape în cutia {i}: {ln!r}"
if note_lines:
assert x + w + 24 <= NOTE_X, f"cutia {i} prea aproape de note"
svg.append(f'<line x1="{x + w + 4:g}" y1="{y + h / 2:g}" x2="{NOTE_X - 8}" '
f'y2="{y + h / 2:g}" stroke="#bbbbbb" stroke-dasharray="3 3"/>')
ny = y + (h - n_note * LH) / 2 + 12
for group in note_lines:
for kind, ln in group:
put(NOTE_X, ny, ln, NOTE_FS, RED if kind == "c" else GRAY)
ny += LH
ny += 0.4 * LH
y += h
if not last:
svg.append(f'<path d="M {BOX_CX - 8} {y + 4:g} L {BOX_CX + 8} {y + 4:g} '
f'L {BOX_CX} {y + 14:g} Z" fill="{BLUE}"/>')
y += 18
y += 30
for j, ln in enumerate(textwrap.wrap(FOOT, 88)):
put(20, y + j * LH, ln, 12.5, BLACK)
H = int(y + LH + 20)
# ------------------------------------------------------------------ verificare
assert all(a > b for a, b in zip(WIDTHS, WIDTHS[1:])), "lățimile nu scad strict"
assert all(r1[2] > r2[2] for r1, r2 in zip(rects, rects[1:]))
for x0, x1, t, b, s in bboxes:
assert 0 <= x0 and x1 <= W and 0 <= t and b <= H, f"text în afara viewBox: {s!r}"
for a in range(len(bboxes)):
for b in range(a + 1, len(bboxes)):
p, q = bboxes[a], bboxes[b]
assert not (p[0] < q[1] and q[0] < p[1] and p[2] < q[3] and q[2] < p[3]), \
f"texte suprapuse: {p[4]!r} / {q[4]!r}"
for x0, x1, t, b, s in bboxes: # notele nu intră în cutii
if x0 >= NOTE_X - 1:
assert all(x0 > rx + rw for rx, _, rw, _ in rects), f"notă peste cutie: {s!r}"
out = "\n".join([
f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 {W} {H}" width="{W}" '
f'height="{H}" role="img" aria-label="Scanerul Buy Doji în TradeStation — '
f'ordinea filtrelor și capcanele">',
f'<rect x="0" y="0" width="{W}" height="{H}" fill="#ffffff"/>',
*svg, "</svg>"]) + "\n"
assert "\n\n" not in out and not any(not ln.strip() for ln in out.splitlines()), "linie goală"
OUT.write_text(out, encoding="utf-8")
print(f"scris {OUT} ({W}x{H}), verificare OK")