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
This commit is contained in:
Claude Agent
2026-09-19 21:09:31 +00:00
parent aff03870e8
commit 8b7b5f6b44
50 changed files with 7011 additions and 6921 deletions

View File

@@ -1,130 +1,146 @@
"""Buy Doji — unde pui stopul: 4 candele înapoi, sub structură (exemplu construit).
"""Buy Doji — unde pui stopul: 4 candele înapoi, sub structură.
Citește specificația `gen/strategii/buy_doji_stop.spec.txt`. Două panouri
stivuite, aceeași serie 0-14 ca buy_doji_transe (downtrend, doji 12, +1 13,
confirmare 14). Sus: numărătoarea celor 4 candele (1 = confirmarea). Jos:
același trade, stop „la ochi" 52.90 față de stopul din regulă 51.55.
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; sys.path.insert(0, "/workspace/atm-curs-dl")
from svg_candles import Candle, Chart, Level, Mark
import sys
from pathlib import Path
OUT = "/workspace/atm-curs-dl/gen/strategii/buy_doji_stop.svg"
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
# --- seria: barele 0-14 exact ca în buy_doji_transe.spec.txt -----------------
closes = [60.40, 59.60, 59.90, 58.80, 58.10, 58.40, 57.20, 56.30, 56.60,
55.30, 54.20, 52.40]
bars, o = [], 61.00
for c in closes: # fitile implicite 0.15 peste/sub corp
bars.append((o, max(o, c) + 0.15, min(o, c) - 0.15, c))
o = c
bars += [(52.40, 53.00, 51.60, 52.30), # 12 doji
(52.30, 52.85, 52.10, 52.60), # 13 +1
(52.60, 53.85, 52.45, 53.70)] # 14 confirmare
after = [(53.70, 53.75, 52.70, 52.90), # 15 lovește stopul la ochi
(52.90, 53.05, 52.00, 52.20), # 16 al doilea picior, higher low
(52.20, 53.40, 52.10, 53.30),
(53.30, 54.30, 53.20, 54.20),
(54.20, 55.10, 54.10, 55.00),
(55.00, 56.00, 54.90, 55.95)] # 20 TP1
full = bars + after
o_, h_, l_, c_ = (list(x) for x in zip(*full))
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
ENTRY, SL, OCHI, TP1, LOW4 = 53.70, 51.55, 52.90, 55.85, 51.60
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
# --- assert-urile din spec ---------------------------------------------------
for i in range(1, len(full)): # 1
assert abs(o_[i] - c_[i - 1]) < 1e-9, f"open[{i}] != close[{i-1}]"
for i in range(len(full)):
assert l_[i] <= min(o_[i], c_[i]) and h_[i] >= max(o_[i], c_[i]), f"bara {i}"
assert min(l_[11:15]) == min(l_[10:15]) == LOW4 # 2
assert l_.index(LOW4) == 12, "cel mai jos low nu e dojiul"
assert round(LOW4 - 0.05, 2) == SL
assert min(l_[15:21]) == 52.00 and SL < 52.00 < OCHI # 3
assert next(i for i in range(15, 21) if l_[i] <= OCHI) == 15 # 4
assert all(l_[i] > SL for i in range(15, 21))
assert next(i for i in range(15, 21) if h_[i] >= TP1) == 20 # 5
assert 52.00 > LOW4 # 6
assert round(ENTRY - OCHI, 2) == 0.80 and c_[14] == ENTRY
assert round(ENTRY + (ENTRY - SL), 2) == TP1
# --- 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ă")
GREY, RED, ORANGE, GREEN, BLUE = "#555", "#c00", "#b06000", "#188a3e", "#1a5fb4"
W = 640
common: dict = dict(scale=34.0, gap=18, body_w=10, left=44, width=W)
common: dict = dict(scale=36.0, gap=20, body_w=10, left=48, width=W)
def mk(n):
return [Candle(*b) for b in full[:n]]
# --- panoul de sus: numărătoarea ---------------------------------------------
top = Chart(
candles=mk(15),
levels=[Level(LOW4, "cel mai jos low: 51.60 (doji)", GREY, "2 2", "left"),
Level(SL, "SL 51.55", RED, "4 3")],
# fără Mark pe doji: la gap 18 eticheta calcă pe bara 11; îl numește
# paranteza de dedesubt și cercul de pe low
marks=[Mark(14, "confirmare 53.70", GREEN, "above")],
title="Unde pui stopul: 4 candele înapoi, sub structură — exemplu construit",
# --- 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,
)
svg_top = top.render()
# numerele 4 3 2 1 + (5) sub bare, bandă de evidențiere, paranteză
yb = top._y(SL) + 18 # rândul cu numere, sub linia de SL
x = top._cx
extra_top = [
f'<rect x="{x(11) - 8:.1f}" y="{top._y(max(h_[11:15])) - 4:.1f}" '
f'width="{x(14) - x(11) + 16:.1f}" height="{yb + 6 - top._y(max(h_[11:15])):.1f}" '
f'fill="{BLUE}" opacity="0.08"/>',
f'<circle cx="{x(12):.1f}" cy="{top._y(LOW4):.1f}" r="4" fill="none" '
f'stroke="{RED}" stroke-width="1.5"/>',
]
for i, n in zip(range(11, 15), "4321"):
extra_top.append(f'<text x="{x(i):.1f}" y="{yb:.1f}" fill="{BLUE}" '
f'text-anchor="middle" style="font-weight:bold">{n}</text>')
extra_top.append(f'<text x="{x(10):.1f}" y="{yb:.1f}" fill="#999" '
f'text-anchor="middle">(5)</text>')
yp = yb + 8
extra_top.append(f'<polyline points="{x(11) - 5:.1f},{yp:.1f} {x(11) - 5:.1f},{yp + 5:.1f} '
f'{x(14) + 5:.1f},{yp + 5:.1f} {x(14) + 5:.1f},{yp:.1f}" '
f'fill="none" stroke="{BLUE}" stroke-width="1.2"/>')
extra_top.append(f'<text x="{x(14) + 14:.1f}" y="{yp + 9:.1f}" fill="{BLUE}">'
f'ultimele 4 candele (3 = doji, 1 = confirmarea)</text>')
extra_top.append(f'<text x="24" y="{yp + 28:.1f}" fill="#555">'
f'Cu 4 sau cu 5 candele iese la fel: în downtrend low-ul dojiului '
f'e oricum cel mai jos. SL = 51.60 − 0.05.</text>')
h_top = yp + 40
# --- panoul de jos: același trade, două stopuri -------------------------------
bot = Chart(
candles=mk(21),
levels=[Level(TP1, "TP1 55.85", GREEN, "2 2"),
Level(ENTRY, "intrare 53.70", GREY, "4 3"),
Level(OCHI, "stop la ochi 52.90 — lovit", ORANGE, "4 3"),
Level(SL, "SL din regulă 51.55 — ținut", RED, "4 3")],
marks=[Mark(16, "al doilea picior: 52.00, higher low", BLUE, "below", dy=10),
Mark(20, "TP1", GREEN, "above")],
title="Același trade: stop la ochi (0.80 sub intrare) vs stop din regulă",
caption="Barele 15-20 și stopul la ochi sunt construite, pentru contrast.",
top=h_top + 10 + 34, # panoul de jos începe sub cel de sus, fără <g transform>
# --- 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,
)
svg_bot = bot.render()
extra_bot = [
f'<circle cx="{bot._cx(15):.1f}" cy="{bot._y(OCHI):.1f}" r="5" fill="none" '
f'stroke="{ORANGE}" stroke-width="2"/>',
]
# header-ul (viewBox) e al panoului de jos: el conține și offset-ul celui de sus
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([
svg_bot.split(">", 1)[0] + ">",
svg_top.split(">", 1)[1].rsplit("</svg>", 1)[0].strip(), *extra_top,
f'<line x1="24" y1="{h_top + 2:.0f}" x2="{W - 14}" y2="{h_top + 2:.0f}" stroke="#ccc"/>',
svg_bot.split(">", 1)[1].rsplit("</svg>", 1)[0].strip(), *extra_bot,
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>"
H = float(re.search(r'viewBox="0 0 \d+ ([\d.]+)"', svg).group(1))
assert "<svg" in svg and "\n\n" not in svg, "linie goală în SVG"
with open(OUT, "w") as f:
f.write(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})")