Files
atm-curs-dl/gen/strategii/buy_doji_scaner.py
Claude Agent 9c1936e73a 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
2026-09-19 18:27:13 +00:00

139 lines
6.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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")