Files
atm-curs-dl/gen/strategii/buy_doji_stop.py
Claude Agent 8b7b5f6b44 buy_doji: refacut ca tutorial pe 3 niveluri, cu diagrame adnotate
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
2026-09-19 21:09:31 +00:00

147 lines
7.4 KiB
Python

"""Buy Doji — unde pui stopul: 4 candele înapoi, sub structură.
Citește `gen/strategii/buy_doji_stop.spec.txt`. Barele vin din
`buy_doji_ansamblu.serie()` — e același trade ca în toate celelalte
diagrame ale capitolului (doji 50.10/49.25, intrare 50.40, stop 49.20).
Panoul de sus: cum numeri cele 4 candele, de la confirmare înapoi.
Panoul de jos: același trade, cu un stop pus „la ochi" (49.70, sub candela
de confirmare) față de stopul din regulă (49.20). Al doilea picior al
fundului coboară la 49.50: mătură stopul la ochi, nu-l atinge pe cel din
regulă. Barele de după intrare sunt CONSTRUITE.
"""
import re
import sys
from pathlib import Path
sys.path.insert(0, "/workspace/atm-curs-dl")
sys.path.insert(0, str(Path(__file__).parent))
from svg_candles import Candle, Chart, Level, Zone, Arrow # noqa: E402
from buy_doji_ansamblu import serie, DOJI, CONF, DOJI_L, ENTRY, SL, TP1, R # noqa: E402
OUT = Path(__file__).with_suffix(".svg")
S = 6 # prima bară arătată din serie
OCHI = 49.70 # stopul „la ochi", sub candela de confirmare
GREEN, RED, ORANGE, BLUE, GREY = "#188a3e", "#c00", "#b06000", "#1a5fb4", "#555"
W, VGAP = 720, 14
base = serie()[S:CONF + 1] # până la confirmare, inclusiv
after = [Candle(*b) for b in [
(50.40, 50.50, 49.85, 49.95), # +1: corecție
(49.95, 50.05, 49.50, 49.60), # +2: al doilea picior, low 49.50 — mătură 49.70
(49.60, 50.35, 49.55, 50.25),
(50.25, 50.90, 50.15, 50.80),
(50.80, 51.35, 50.70, 51.25),
(51.25, 51.80, 51.15, 51.70), # +6: atinge ținta 51.60
]]
jos = base + after
d, cf = DOJI - S, CONF - S # indici locali: doji, confirmare
# --- aserțiuni -----------------------------------------------------------------
for i, c in enumerate(jos):
assert c.l <= min(c.o, c.c) and c.h >= max(c.o, c.c), f"bara {i}: h/l nu cuprind o/c"
if i:
assert abs(c.o - jos[i - 1].c) < 1e-9, f"open[{i}] != close[{i-1}]"
# 1. cel mai jos low e al doji-ului, și cu 4 și cu 5 candele numărate
assert min(c.l for c in jos[cf - 3:cf + 1]) == min(c.l for c in jos[cf - 4:cf + 1]) == DOJI_L
assert jos[d].l == DOJI_L and round(DOJI_L - 0.05, 2) == SL
# 2. al doilea picior: mătură stopul la ochi, nu-l atinge pe cel din regulă
dip = [c.l for c in after]
assert min(dip) == 49.50 and SL < 49.50 < OCHI, dip
assert next(k for k, c in enumerate(after) if c.l <= OCHI) == 1, "stopul la ochi nu e lovit de al doilea picior"
assert all(c.l > SL for c in after), "stopul din regulă ar fi fost lovit"
# 3. al doilea picior e higher low față de low-ul doji-ului
assert min(dip) > DOJI_L
# 4. ținta e atinsă abia după corecție, la a 6-a bară de după intrare
assert next(k for k, c in enumerate(after, 1) if c.h >= TP1) == 6
# 5. stopul la ochi e mai aproape de intrare decât cel din regulă, și peste low-ul doji-ului
assert round(ENTRY - OCHI, 2) == 0.70 < R and DOJI_L < OCHI < jos[cf].l # sub candela de confirmare, peste low-ul doji-ului
assert round(ENTRY - SL, 2) == R
print(f"stop: low4 {DOJI_L:.2f} -> SL {SL:.2f}; la ochi {OCHI:.2f} lovit de low {min(dip):.2f}; "
f"țintă {TP1:.2f} la a 6-a bară")
common: dict = dict(scale=36.0, gap=20, body_w=10, left=48, width=W)
# --- panoul de sus: numărătoarea celor 4 candele --------------------------------
sus = Chart(
candles=base,
levels=[Level(SL, "stopul: 49.20", RED, "4 3", "left")],
zones=[Zone(cf - 3, cf, "numeri 4 candele, începând cu confirmarea", BLUE)],
arrows=[
Arrow(d, "cel mai jos low din cele 4: 49.25\n(e al doji-ului)",
where="below", price=DOJI_L, color=GREY, dx=250, dy=34),
Arrow(cf - 1, "stopul: 49.20, cu 5 cenți mai jos\nsub el, ideea că vânzătorii\nau obosit e moartă",
where="below", price=SL, color=RED, dx=250, dy=76),
Arrow(cf, "confirmarea: close 50.40\nde aici numeri înapoi",
where="above", color=GREEN, dx=250),
],
title="Pasul 5 — stopul: numeri 4 candele înapoi de la confirmare",
**common,
)
# --- panoul de jos: stop la ochi vs stop din regulă -----------------------------
gata = Chart(
candles=jos,
levels=[Level(TP1, "ținta 1: 51.60", GREEN, "2 2", "left"),
Level(ENTRY, "intrarea: 50.40", GREY, "4 3", "left"),
Level(OCHI, "stop pus la ochi: 49.70", ORANGE, "4 3", "left"),
Level(SL, "stopul din regulă: 49.20", RED, "4 3", "left")],
# eticheta zonei o punem manual: Zone o ridică deasupra candelelor din
# dreptul textului, adică departe de banda pe care o marchează
zones=[Zone(cf + 1, cf + 2, "", RED, p_top=OCHI, p_bot=SL)],
arrows=[
Arrow(cf + 2, "stopul pus la ochi (49.70) e măturat\nera prea aproape de intrare",
where="below", price=OCHI, color=ORANGE, dx=230, dy=6),
Arrow(cf + 2, "al doilea picior al fundului: 49.50\nfundurile fac des asta",
where="below", price=49.50, color=BLUE, dx=230, dy=56),
Arrow(cf + 1, "stopul din regulă (49.20) ține\nal doilea picior rămâne peste el",
where="below", price=SL, color=RED, dx=230, dy=106),
Arrow(len(jos) - 1, "prețul ajunge la țintă\ndar numai cu stopul corect\nmai erai în trade",
where="above", price=TP1, color=GREEN, dx=150),
],
title="Același trade: stop „la ochi” (sub candela de confirmare) vs stopul din regulă",
caption="Barele de după intrare și stopul la ochi sunt construite, pentru contrast.",
**common,
)
def panou(ch, sus_de=0.0):
ch.top += sus_de
svg = ch.render()
h = float(re.search(r'viewBox="0 0 [\d.]+ ([\d.]+)"', svg).group(1))
# render() mută `top` intern și îl restaurează; offsetul se citește din
# prima linie de Level, altfel tot ce desenăm pe urmă iese mai sus cu ~22px
off = float(re.search(r'<line x1="24" y1="([\d.]+)"', svg).group(1)) - ch._y(ch.levels[0].price)
return svg[svg.index(">") + 1:svg.rindex("</svg>")].strip(), h, off
body_sus, h_sus, off_sus = panou(sus)
# numerele 4 3 2 1 sub barele numărate, plus (5) — arată că iese la fel
yb = sus._y(SL) + off_sus + 22
nums = []
for k, n in zip(range(cf - 3, cf + 1), "4321"):
nums.append(f'<text x="{sus._cx(k):.1f}" y="{yb:.1f}" fill="{BLUE}" text-anchor="middle" '
f'style="font-weight:bold">{n}</text>')
nums.append(f'<text x="{sus._cx(cf - 4):.1f}" y="{yb:.1f}" fill="#999" text-anchor="middle">(5)</text>')
nums.append(f'<text x="24" y="{yb + 20:.1f}" fill="#555">'
f'cu 4 sau cu 5 candele iese la fel: în scădere, low-ul doji-ului e oricum cel mai jos</text>')
h_sus = max(h_sus, yb + 32)
body_jos, h_jos, off_jos = panou(gata, h_sus + VGAP)
zx = gata._cx(cf + 3) + 10
banda = (f'<text x="{zx:.1f}" y="{gata._y(OCHI) + off_jos - 6:.1f}" fill="{RED}" '
f'style="font-weight:bold">pe aici trece al doilea picior</text>')
H = h_jos + 8
svg = "\n ".join([
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">',
body_sus, *nums,
f'<line x1="24" y1="{h_sus + VGAP / 2:.0f}" x2="{W - 14}" y2="{h_sus + VGAP / 2:.0f}" stroke="#ddd"/>',
body_jos, banda,
]) + "\n</svg>"
assert "\n\n" not in svg and "<svg" in svg, "linie goală în SVG"
for x, y in re.findall(r'<text x="([-\d.]+)" y="([-\d.]+)"', svg):
assert 0 <= float(x) <= W and 0 <= float(y) <= H, f"text în afara pânzei la ({x}, {y})"
OUT.write_text(svg, encoding="utf-8")
print(f"scris {OUT} ({W}x{H:.0f})")