doji_forma nu mai foloseste 49.20 ca low al dojiului — era fix pretul stopului, in prima diagrama pe care o vede un incepator. Bara e pe 50.10/49.25, cu procentele recalculate. LABU: stopul e etichetat "pe cel mai jos dintre cele trei minime", nu "sub minime"; zonele arata doua teste ale pivotului, cu "revenire peste pivot 88.90" intre ele; numarul barei a iesit din desen, nota spune "a 6-a bara dupa intrare", derivat din serie. perechi_tf nu mai pune pretul TF-ului mare pe panoul TF-ului mic. aplus nu mai numeste un nivel care nu e desenat. directie nu mai zice "saptamanile" pe un panou daily. sell acopera toata cresterea, 14 bare. transe: cele trei sageti nu mai converg intr-un ghem — fiecare are linia ei cu pretul la capat; linia de intrare e desenata, ca parantezele de risc sa plece de undeva. locatie arata 49.25, cifra din care iese 0,51 %. stop si iesire: linii si sageti care taiau texte, mutate. Jumatate din probleme erau aceeasi greseala: un pret scris de mana intr-o eticheta, ramas in urma cand se schimba seria. Sunt acum aserttiuni care o fac imposibila; PROGRESS.md spune care merita generalizata. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ThN7ixZrowca8aTGrK1MUJ
156 lines
7.5 KiB
Python
156 lines
7.5 KiB
Python
"""Buy Doji — ieșirea: prima treime la țintă, a doua pe trailing stop, limita de 8 candele.
|
|
|
|
Citește `gen/strategii/buy_doji_iesire.spec.txt`. Barele vin din
|
|
`buy_doji_ansamblu.serie()` — același trade ca în restul capitolului
|
|
(doji 50.10/49.25, intrare 50.40, stop 49.20, ținta 1 51.60).
|
|
|
|
A — prima treime iese la ținta 51.60; apoi două candele cu gap urcă linia
|
|
de trailing stop (51.95, apoi 52.60); a doua treime iese la prima
|
|
închidere sub linie, 52.30 (+1.58R).
|
|
B — același setup, dar prețul stă 8 candele între stop și țintă: ieși la
|
|
close-ul celei de-a 8-a, 50.70 (+0.25R).
|
|
|
|
Barele de după intrare din panoul B 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 Chart, Level, Zone, Arrow, from_closes # noqa: E402
|
|
from buy_doji_ansamblu import serie, CONF, ENTRY, SL, TP1, R, trail # noqa: E402
|
|
|
|
OUT = Path(__file__).with_suffix(".svg")
|
|
S = 10 # prima bară arătată
|
|
GREEN, RED, ORANGE, BLUE, GREY = "#188a3e", "#c00", "#b06000", "#1a5fb4", "#555"
|
|
W, VGAP = 760, 14
|
|
|
|
full = serie()
|
|
steps, iesire = trail(full) # [(21, 51.95), (27, 52.60)], 28
|
|
a = full[S:iesire + 1]
|
|
cf = CONF - S
|
|
EXIT_A = a[iesire - S].c
|
|
R_A = round((EXIT_A - ENTRY) / R, 2)
|
|
tp_i = next(i for i in range(CONF + 1, len(full)) if full[i].h >= TP1)
|
|
|
|
# --- aserțiunile panoului A -------------------------------------------------------
|
|
assert steps == [(21, 51.95), (27, 52.60)] and iesire == 28, (steps, iesire)
|
|
for i, t in steps: # gap-urile sunt reale
|
|
assert full[i].l > full[i - 1].h, f"bara {i} nu deschide cu gap"
|
|
assert all(full[i].c >= steps[0][1] for i in range(steps[0][0], iesire)), \
|
|
"ar fi ieșit mai devreme: un close sub linia de trail"
|
|
assert EXIT_A == 52.30 and R_A == 1.58 and EXIT_A < steps[-1][1]
|
|
assert tp_i == 19 and full[tp_i].h >= TP1 > full[tp_i - 1].h, tp_i
|
|
|
|
# --- panoul B: 8 candele între stop și țintă --------------------------------------
|
|
b = full[S:CONF + 1]
|
|
b += from_closes([50.60, 50.30, 50.90, 50.50, 51.10, 50.80, 51.20, 50.70],
|
|
wick=0.15, start=ENTRY)
|
|
opt = b[cf + 1:]
|
|
EXIT_B = opt[-1].c
|
|
R_B = round((EXIT_B - ENTRY) / R, 2)
|
|
assert len(opt) == 8, len(opt)
|
|
for i in range(1, len(b)):
|
|
assert abs(b[i].o - b[i - 1].c) < 1e-9, f"B: open[{i}] != close[{i-1}]"
|
|
assert all(c.h < TP1 and c.l > SL for c in opt), "B: o bară atinge ținta sau stopul"
|
|
assert (round(max(c.h for c in opt), 2), round(min(c.l for c in opt), 2)) == (51.35, 50.15)
|
|
assert EXIT_B == 50.70 and R_B == 0.25
|
|
print(f"iesire A: treapta 1 {steps[0][1]:.2f}, treapta 2 {steps[1][1]:.2f}, "
|
|
f"ieșire {EXIT_A:.2f} = +{R_A}R (ținta 1 {TP1:.2f} la bara {tp_i}); "
|
|
f"B: ieșire {EXIT_B:.2f} = +{R_B}R")
|
|
|
|
common: dict = dict(scale=40.0, gap=18, body_w=9, left=48, width=W)
|
|
|
|
ch_a = Chart(
|
|
candles=a,
|
|
levels=[Level(TP1, "ținta 1: 51.60 (= 1R)", GREEN, "2 3"),
|
|
Level(ENTRY, "intrarea: 50.40", GREY, "4 3"),
|
|
Level(SL, "stopul: 49.20", RED, "4 3")],
|
|
zones=[Zone(cf, tp_i - S, "", GREEN), Zone(tp_i - S + 1, iesire - S, "", BLUE)],
|
|
arrows=[
|
|
Arrow(tp_i - S, "ținta 1 atinsă: vinzi prima treime\nai luat exact cât riscai (1R)",
|
|
where="above", price=TP1, color=GREEN, dx=10, dy=-26),
|
|
Arrow(steps[0][0] - S, "gap: low-ul de azi e peste high-ul de ieri\nlinia de trail urcă la 51.95",
|
|
where="below", price=steps[0][1], color=ORANGE, dx=130, dy=40),
|
|
Arrow(steps[1][0] - S, "încă un gap: linia urcă la 52.60\nurcă doar, nu coboară niciodată",
|
|
where="above", price=steps[1][1], color=ORANGE, dx=150),
|
|
# dx mare: cu dx=110 tija săgeții trecea peste textul portocaliu al gap-ului
|
|
Arrow(iesire - S, "prima închidere sub linie: ieși\ncu a doua treime, la 52.30 (+1.58R)",
|
|
where="below", price=EXIT_A, color=RED, dx=250, dy=96),
|
|
],
|
|
title="A — prima treime la țintă, a doua pe trailing stop",
|
|
**common,
|
|
)
|
|
|
|
ch_b = Chart(
|
|
candles=b,
|
|
levels=[Level(TP1, "ținta 1: 51.60 — neatinsă", GREEN, "2 3"),
|
|
Level(ENTRY, "intrarea: 50.40", GREY, "4 3"),
|
|
Level(SL, "stopul: 49.20 — neatins", RED, "4 3")],
|
|
zones=[Zone(cf + 1, len(b) - 1, "", GREY, p_top=TP1, p_bot=SL)],
|
|
arrows=[
|
|
Arrow(len(b) - 1, "a 8-a candelă: ieși oricum, la close 50.70 (+0.25R)\nbanii blocați aici lipsesc din alt trade",
|
|
where="below", price=EXIT_B, color=GREY, dx=120, dy=40),
|
|
],
|
|
title="B — limita de 8 candele: nici ținta, nici stopul",
|
|
**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`/`left` intern și le restaurează: tot ce desenăm noi
|
|
# după randare trebuie deplasat cu aceiași pixeli
|
|
off_y = float(re.search(r'<line x1="24" y1="([\d.]+)"', svg).group(1)) - ch._y(ch.levels[0].price)
|
|
off_x = float(re.search(r'<line x1="([\d.]+)"[^>]*stroke="#1a1a1a"', svg).group(1)) - ch._cx(0)
|
|
return svg[svg.index(">") + 1:svg.rindex("</svg>")].strip(), h, off_x, off_y
|
|
|
|
|
|
body_a, h_a, ax, ay = panou(ch_a)
|
|
Y, X = (lambda p: ch_a._y(p) + ay), (lambda i: ch_a._cx(i) + ax)
|
|
(i1, t1), (i2, t2) = [(i - S, t) for i, t in steps]
|
|
extra_a = [
|
|
# linia de trail, în trepte
|
|
f'<polyline points="{X(i1) - 7:.1f},{Y(t1):.1f} {X(i2) - 7:.1f},{Y(t1):.1f} '
|
|
f'{X(i2) - 7:.1f},{Y(t2):.1f} {X(iesire - S) + 7:.1f},{Y(t2):.1f}" '
|
|
f'fill="none" stroke="{ORANGE}" stroke-width="2"/>',
|
|
f'<text x="{X(iesire - S) + 14:.1f}" y="{Y(t2) + 4:.1f}" fill="{ORANGE}" style="font-weight:bold">'
|
|
f'linia de trailing stop</text>',
|
|
# etichetele zonelor, lângă ele (Zone le-ar ridica deasupra candelelor vecine)
|
|
f'<text x="{X(cf) - 6:.1f}" y="{Y(SL) + 60:.1f}" fill="{GREEN}" style="font-weight:bold">'
|
|
f'prima treime: de la intrare la ținta 1:1</text>',
|
|
f'<text x="{X(tp_i - S + 1) - 6:.1f}" y="{Y(SL) + 78:.1f}" fill="{BLUE}" style="font-weight:bold">'
|
|
f'a doua treime: merge pe trailing stop</text>',
|
|
]
|
|
extra_a.append(f'<text x="24" y="{Y(SL) + 100:.1f}" fill="{GREY}">A treia treime o lași mai '
|
|
f'departe, cu stopul mutat la intrare (break-even).</text>')
|
|
h_a = max(h_a, Y(SL) + 112)
|
|
|
|
body_b, h_b, bx, by = panou(ch_b, h_a + VGAP)
|
|
y_num = ch_b._y(min(c.l for c in opt)) + by + 18 # un singur rând, lipit de bare
|
|
extra_b = [
|
|
f'<text x="{ch_b._cx(cf + 1) + bx - 6:.1f}" y="{ch_b._y(TP1) + by - 8:.1f}" fill="{GREY}" '
|
|
f'style="font-weight:bold">8 candele în care nu se întâmplă nimic</text>',
|
|
]
|
|
for k, c in enumerate(opt, 1):
|
|
col = GREEN if c.c >= c.o else RED
|
|
extra_b.append(f'<text x="{ch_b._cx(cf + k) + bx:.1f}" y="{y_num:.1f}" fill="{col}" '
|
|
f'text-anchor="middle" style="font-weight:bold">{k}</text>')
|
|
assert y_num - (ch_b._y(min(c.l for c in opt)) + by) <= 24, "numerele stau prea departe de bare"
|
|
H = h_b + 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_a, *extra_a,
|
|
f'<line x1="24" y1="{h_a + VGAP / 2:.0f}" x2="{W - 14}" y2="{h_a + VGAP / 2:.0f}" stroke="#ddd"/>',
|
|
body_b, *extra_b,
|
|
]) + "\n</svg>"
|
|
assert "\n\n" not in svg, "linie goală în SVG"
|
|
for tx, ty in re.findall(r'<text x="([-\d.]+)" y="([-\d.]+)"', svg):
|
|
assert 0 <= float(tx) <= W and 0 <= float(ty) <= H, f"text în afara pânzei la ({tx}, {ty})"
|
|
OUT.write_text(svg, encoding="utf-8")
|
|
print(f"scris {OUT} ({W}x{H:.0f})")
|