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:
@@ -1,103 +1,166 @@
|
||||
"""Buy Doji — diagrama de ansamblu (daily, exemplu construit, fără ticker).
|
||||
|
||||
Citește specificația `gen/strategii/buy_doji_ansamblu.spec.txt`. Seria de
|
||||
32 de candele e construită din lista de close-uri dată, cu open-ul fiecărei
|
||||
bare = close-ul precedentului; wick-urile sunt fixate ca nivelurile
|
||||
(doji 50.10/49.20, intrare 50.40, SL 49.20, TP1 51.60, trail 52.60, ieșire
|
||||
sub trail la bara 28) să iasă exact din serie, nu doar din etichetă.
|
||||
Citește specificația `gen/strategii/buy_doji_ansamblu.spec.txt`.
|
||||
|
||||
Seria de aici e SURSA UNICĂ a trade-ului din capitol: buy_doji_stop,
|
||||
buy_doji_transe și buy_doji_iesire importă `serie()` și pornesc din
|
||||
aceleași bare (doji 50.10/49.25, confirmare 50.40, SL 49.20, TP1 51.60),
|
||||
ca începătorul să urmărească un singur trade prin toată procedura.
|
||||
|
||||
Barele 0-19: close-uri, open = close-ul precedentului. Barele 20-28 sunt
|
||||
date explicit: 21 și 27 deschid cu GAP (low peste high-ul precedentei),
|
||||
fiindcă regula de trail din curs se sprijină exact pe barele astea.
|
||||
"""
|
||||
import sys; sys.path.insert(0, "/workspace/atm-curs-dl")
|
||||
from svg_candles import Chart, Level, Mark, MA, from_closes
|
||||
from svg_candles import Candle, Chart, Level, MA, Zone, Arrow, from_closes
|
||||
|
||||
# --- seria: 32 de close-uri, exact ca în specificație -----------------------
|
||||
closes = [
|
||||
60.20, 59.20, 59.60, 58.40, 57.30, 57.70, 56.50, 55.40, 55.80, 54.70, # 0-9
|
||||
53.50, 52.30, 51.00, 49.65, # 10-13 downtrend
|
||||
49.60, # 14 doji
|
||||
49.90, # 15 +1 (nu confirmă)
|
||||
50.40, # 16 +2 confirmare => intrare
|
||||
50.90, 51.20, 51.50, 51.80, 52.10, 52.40, 52.20, 52.70, 52.95, 53.20, # 17-26
|
||||
52.80, 52.30, 52.00, 51.80, 51.90, # 27-31
|
||||
]
|
||||
assert len(closes) == 32
|
||||
OUT = "/workspace/atm-curs-dl/gen/strategii/buy_doji_ansamblu.svg"
|
||||
|
||||
# wick-uri: (up, down) implicite (0.25, 0.30) + excepțiile din specificație
|
||||
wicks = [(0.25, 0.30)] * 32
|
||||
wicks[14] = (0.45, 0.40) # doji: high 50.10, low 49.20
|
||||
wicks[15] = (0.15, 0.15) # high 50.05, low 49.45
|
||||
wicks[16] = (0.15, 0.15) # high 50.55, low 49.75
|
||||
for k in range(24, 28): # barele 24-27
|
||||
wicks[k] = (0.25, 0.20)
|
||||
DOJI, WAIT, CONF = 14, 15, 16 # indicii barelor-cheie
|
||||
DOJI_H, DOJI_L = 50.10, 49.25
|
||||
ENTRY, SL, TP1 = 50.40, 49.20, 51.60
|
||||
R = 1.20
|
||||
|
||||
# volum relativ ales de generator (materialul nu dă volum pe exemplu);
|
||||
# peste medie pe doji (14) și pe confirmare (16), apoi normal
|
||||
vols = [42, 44, 40, 46, 43, 41, 45, 42, 44, 47,
|
||||
43, 45, 48, 52, 78, 46, 88, 55,
|
||||
50, 48, 52, 47, 45, 49, 46, 44, 48, 51,
|
||||
54, 47, 45, 46]
|
||||
assert len(vols) == 32
|
||||
media_vol = sum(vols) / len(vols)
|
||||
assert vols[14] > media_vol and vols[16] > media_vol, "volum peste medie pe doji/confirmare"
|
||||
|
||||
cds = from_closes(closes, wicks=wicks, vols=vols, start=60.20)
|
||||
def serie() -> list[Candle]:
|
||||
"""Cele 32 de bare ale trade-ului, cu verificările de bază."""
|
||||
closes = [
|
||||
60.20, 59.20, 59.60, 58.40, 57.30, 57.70, 56.50, 55.40, 55.80, 54.70, # 0-9
|
||||
53.50, 52.30, 51.00, 49.65, # 10-13 downtrend
|
||||
49.60, # 14 doji
|
||||
49.90, # 15 +1 (nu confirmă)
|
||||
50.40, # 16 confirmare => intrare
|
||||
50.90, 51.20, 51.50, # 17-19 (19 atinge TP1)
|
||||
]
|
||||
wicks = [(0.25, 0.30)] * len(closes)
|
||||
wicks[14] = (0.45, 0.35) # doji: high 50.10, low 49.25
|
||||
wicks[15] = (0.15, 0.15) # high 50.05, low 49.45
|
||||
wicks[16] = (0.15, 0.15) # high 50.55, low 49.75
|
||||
cds = from_closes(closes, wicks=wicks, start=60.20)
|
||||
cds += [Candle(*b) for b in [
|
||||
(51.50, 51.85, 51.40, 51.75), # 20
|
||||
(52.00, 52.30, 51.95, 52.20), # 21 GAP: low 51.95 > high[20] 51.85 → trail 51.95
|
||||
(52.20, 52.45, 52.05, 52.35), # 22
|
||||
(52.35, 52.40, 52.00, 52.10), # 23 retragere, close peste trail: rămâi
|
||||
(52.10, 52.50, 52.05, 52.40), # 24
|
||||
(52.40, 52.55, 52.25, 52.50), # 25
|
||||
(52.50, 52.55, 52.35, 52.45), # 26
|
||||
(52.70, 53.10, 52.60, 52.95), # 27 GAP: low 52.60 > high[26] 52.55 → trail 52.60
|
||||
(52.95, 53.00, 52.20, 52.30), # 28 close 52.30 < 52.60: ieși
|
||||
]]
|
||||
cds += from_closes([52.00, 51.80, 51.90], wick=0.25, start=52.30) # 29-31
|
||||
assert len(cds) == 32
|
||||
for i, c in enumerate(cds):
|
||||
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 and i not in (21, 27):
|
||||
assert abs(c.o - cds[i - 1].c) < 1e-9, f"open[{i}] != close[{i-1}]"
|
||||
d = cds[DOJI]
|
||||
assert (round(d.h, 2), round(d.l, 2)) == (DOJI_H, DOJI_L), (d.h, d.l)
|
||||
assert abs(d.o - d.c) <= 0.10 * (d.h - d.l), "bara 14 nu e doji"
|
||||
assert cds[WAIT].c <= DOJI_H < cds[CONF].c, "fereastra: 15 nu confirmă, 16 da"
|
||||
assert abs(cds[CONF].c - ENTRY) < 1e-9
|
||||
assert abs(min(c.l for c in cds[CONF - 3:CONF + 1]) - DOJI_L) < 1e-9, "low-ul doji nu e cel mai jos din 4"
|
||||
assert round(DOJI_L - 0.05, 2) == SL and round(ENTRY - SL, 2) == R and round(ENTRY + R, 2) == TP1
|
||||
return cds
|
||||
|
||||
# adnotările pe bara +1 și pe confirmare, ca linii sub grafic (fără suprapunere)
|
||||
cds[15].color = "#777" # bara de așteptare: neutră, nu confirmă
|
||||
cds[15].note = "bara 15: close 49.90 sub high-ul dojiului 50.10 — NU confirmă"
|
||||
cds[16].note = "bara 16: close 50.40 peste 50.10 — confirmare (intrare)"
|
||||
|
||||
doji, wait, conf = cds[14], cds[15], cds[16]
|
||||
entry, sl, tp1, trail = 50.40, 49.20, 51.60, 52.60
|
||||
def trail(cds, start=CONF + 1):
|
||||
"""Regula de trail: bara cu low > high-ul precedentei urcă linia la low-ul ei;
|
||||
ieși la primul close sub linie. Întoarce (treptele, bara de ieșire)."""
|
||||
t, steps = None, []
|
||||
for i in range(start, len(cds)):
|
||||
if t is not None and cds[i].c < t:
|
||||
return steps, i
|
||||
if cds[i].l > cds[i - 1].h:
|
||||
t = cds[i].l
|
||||
steps.append((i, round(t, 2)))
|
||||
return steps, None
|
||||
|
||||
# --- cele 9 assert-uri cerute de specificație ------------------------------
|
||||
# 1. SL-ul e exact low-ul dojiului, cel mai jos din fereastra de 4 candele
|
||||
assert abs(min(cd.l for cd in cds[13:17]) - 49.20) < 1e-9, "min low[13:17] != 49.20"
|
||||
# 2. intrarea e close-ul barei 16
|
||||
assert abs(conf.c - entry) < 1e-9, f"close[16]={conf.c} != 50.40"
|
||||
# 3. confirmarea închide peste high-ul dojiului
|
||||
assert abs(doji.h - 50.10) < 1e-9 and conf.c > doji.h, "close[16] nu e peste high[14]"
|
||||
# 4. bara +1 NU confirmă
|
||||
assert wait.c <= doji.h, "bara 15 ar confirma, fereastra n-ar avea sens"
|
||||
# 5. 1:1 exact
|
||||
assert abs(abs(entry - sl) - abs(tp1 - entry)) < 1e-9 and abs((entry - sl) - 1.20) < 1e-9, \
|
||||
"risc != recompensă (nu e 1:1 exact)"
|
||||
# 6. primul high >= TP1 după intrare e bara 19
|
||||
primul_tp = next(i for i in range(17, len(cds)) if cds[i].h >= tp1)
|
||||
assert primul_tp == 19, f"TP1 51.60 atins întâi la bara {primul_tp}, nu 19"
|
||||
# 7. trailing 52.60 = low[27]; ieșire la bara 28 sub el; ieșirea e pe profit
|
||||
assert abs(cds[27].l - trail) < 1e-9, f"low[27]={cds[27].l} != 52.60"
|
||||
assert abs(cds[28].c - 52.30) < 1e-9 and cds[28].c < trail, "close[28] nu e sub trail"
|
||||
assert trail > entry, "trail sub intrare, ieșirea n-ar fi pe profit"
|
||||
# 8. doji: corp <= 10% din amplitudine
|
||||
assert abs(doji.o - doji.c) <= 0.10 * (doji.h - doji.l), "bara 14 nu e doji"
|
||||
# 9. downtrend vizibil
|
||||
assert cds[13].c < cds[0].c - 10, "downtrend prea mic"
|
||||
|
||||
print(f"doji {doji.h:.2f}/{doji.l:.2f} intrare {conf.c:.2f} "
|
||||
f"SL {sl:.2f} TP1 {tp1:.2f} trail {trail:.2f} ieșire bara 28 @ {cds[28].c:.2f}")
|
||||
if __name__ == "__main__":
|
||||
cds = serie()
|
||||
vols = [42, 44, 40, 46, 43, 41, 45, 42, 44, 47,
|
||||
43, 45, 48, 52, 78, 46, 88, 55,
|
||||
50, 48, 52, 47, 45, 49, 46, 44, 48, 51,
|
||||
54, 47, 45, 46]
|
||||
for c, v in zip(cds, vols):
|
||||
c.vol = v
|
||||
media_vol = sum(vols) / len(vols)
|
||||
assert vols[DOJI] > media_vol and vols[CONF] > media_vol, "volum peste medie pe doji/confirmare"
|
||||
|
||||
# --- desenul ----------------------------------------------------------------
|
||||
ch = Chart(
|
||||
candles=cds,
|
||||
levels=[
|
||||
Level(sl, "SL 49.20 (low 4 candele)", "#c00", label_at="left"),
|
||||
Level(entry, "intrare 50.40", "#188a3e", label_at="left"),
|
||||
Level(tp1, "TP1 51.60 = 1R", "#1a5fb4", label_at="left"),
|
||||
Level(trail, "trail 52.60", "#b06000"),
|
||||
],
|
||||
marks=[
|
||||
Mark(14, "doji 50.10 / 49.20", "#b06000", "below"),
|
||||
Mark(28, "ieșire pe trail ~52.60", "#c00", "above", dy=-6),
|
||||
],
|
||||
mas=[MA(20, "#777", dash="4 3", text="MA(20)")],
|
||||
scale=24.0, gap=15, body_w=9, left=44, width=640, vol_h=70,
|
||||
vol_label="volum", title="Buy Doji — daily (exemplu construit, fără ticker)",
|
||||
caption="Intrare la close-ul confirmării, SL la low-ul ultimelor 4 candele, "
|
||||
"TP1 1:1, apoi trailing stop.",
|
||||
)
|
||||
# --- aserțiunile specificației ---------------------------------------------
|
||||
primul_tp = next(i for i in range(CONF + 1, len(cds)) if cds[i].h >= TP1)
|
||||
assert primul_tp == 19, f"TP1 51.60 atins întâi la bara {primul_tp}, nu 19"
|
||||
steps, iesire = trail(cds)
|
||||
assert steps == [(21, 51.95), (27, 52.60)], steps
|
||||
assert iesire == 28 and abs(cds[28].c - 52.30) < 1e-9, iesire
|
||||
assert steps[-1][1] > ENTRY, "trail sub intrare, ieșirea n-ar fi pe profit"
|
||||
R_IES = round((cds[28].c - ENTRY) / R, 2)
|
||||
assert R_IES == 1.58, R_IES
|
||||
assert cds[13].c < cds[0].c - 10, "downtrend prea mic"
|
||||
print(f"doji {DOJI_H:.2f}/{DOJI_L:.2f} intrare {ENTRY:.2f} SL {SL:.2f} TP1 {TP1:.2f} "
|
||||
f"trail {steps} ieșire bara 28 @ {cds[28].c:.2f} = +{R_IES}R")
|
||||
|
||||
svg = ch.render()
|
||||
assert "<svg" in svg and "\n\n" not in svg, "linie goală în SVG"
|
||||
with open("/workspace/atm-curs-dl/gen/strategii/buy_doji_ansamblu.svg", "w") as f:
|
||||
f.write(svg)
|
||||
print("scris gen/strategii/buy_doji_ansamblu.svg")
|
||||
cds[WAIT].color = "#777" # bara de așteptare: neutră, nu confirmă
|
||||
GREEN, RED, BLUE, ORANGE, GREY = "#188a3e", "#c00", "#1a5fb4", "#b06000", "#666"
|
||||
ch = Chart(
|
||||
candles=cds,
|
||||
levels=[
|
||||
Level(TP1, "ținta 1: 51.60", BLUE, label_at="left"),
|
||||
Level(ENTRY, "intrare 50.40", GREEN, label_at="left"),
|
||||
Level(SL, "stop 49.20", RED, label_at="left"),
|
||||
],
|
||||
zones=[
|
||||
Zone(4, 13, "1. scăderea: vânzătorii obosesc", RED),
|
||||
Zone(14, 15, "2. doji: pauză, încă nu intri", ORANGE, p_top=53.9),
|
||||
Zone(16, 16, "3. confirmarea", GREEN, p_top=52.9),
|
||||
Zone(17, 28, "4. ieșirea: o treime la țintă, restul pe trail", BLUE, p_top=54.9),
|
||||
],
|
||||
arrows=[
|
||||
Arrow(DOJI, "stopul 49.20, 5 cenți sub doji\nsub el, ideea că vânzătorii\nau obosit e moartă",
|
||||
where="below", price=SL, color=RED, dx=-150, dy=0),
|
||||
Arrow(CONF, "AICI INTRI, la close 50.40\nabia acum știi că\ncumpărătorii au câștigat",
|
||||
where="below", price=ENTRY, color=GREEN, dx=-10, dy=64),
|
||||
Arrow(19, "ținta 1: 51.60 = riscul (1.20)\nîncă o dată: vinzi o treime",
|
||||
where="below", price=TP1, color=BLUE, dx=95, dy=74),
|
||||
Arrow(27, "trailing stop: urcă după preț\nla fiecare gap, nu coboară",
|
||||
where="above", color=ORANGE, dx=40),
|
||||
Arrow(28, "ieși la 52.30 (+1.58R)\nprima închidere sub trail",
|
||||
where="right", color=RED, dx=40, dy=-48),
|
||||
],
|
||||
mas=[MA(20, "#999", dash="4 3", text="MA(20)")],
|
||||
scale=40.0, gap=15, body_w=9, left=44, width=640, vol_h=60,
|
||||
vol_label="volum", title="Buy Doji — un trade de la cap la coadă (daily, exemplu construit)",
|
||||
)
|
||||
svg = ch.render()
|
||||
# render() mută `top` intern (titlu, săgeți) și îl restaurează: ch._y() de aici
|
||||
# e mai sus cu atâta. Offsetul se citește din prima linie de Level desenată.
|
||||
import re as _re
|
||||
OFF = (float(_re.search(r'<line x1="24" y1="([\d.]+)"', svg).group(1))
|
||||
- ch._y(ch.levels[0].price))
|
||||
|
||||
def Y(p):
|
||||
return ch._y(p) + OFF
|
||||
|
||||
# linia de trail în trepte (Level ar trage-o pe toată lățimea, înainte să existe)
|
||||
(i1, t1), (i2, t2) = steps
|
||||
x0, xa, xb = ch._cx(i1) - 7, ch._cx(i2) - 7, ch._cx(iesire) + 7
|
||||
ya, yb = Y(t1), Y(t2)
|
||||
step = (f'<polyline points="{x0:.1f},{ya:.1f} {xa:.1f},{ya:.1f} {xa:.1f},{yb:.1f} '
|
||||
f'{xb:.1f},{yb:.1f}" fill="none" stroke="{ORANGE}" stroke-width="2"/>')
|
||||
# parantezele 1R: riscul jos, ținta sus, aceeași înălțime = „1:1"
|
||||
xp = ch._cx(5)
|
||||
br = []
|
||||
for lo, hi, txt, col in ((SL, ENTRY, "1R = 1.20 (riscul)", RED),
|
||||
(ENTRY, TP1, "1R = 1.20 (ținta)", BLUE)):
|
||||
y1, y2 = Y(hi) + 2, Y(lo) - 2
|
||||
br.append(f'<polyline points="{xp - 5:.1f},{y1:.1f} {xp:.1f},{y1:.1f} {xp:.1f},{y2:.1f} '
|
||||
f'{xp - 5:.1f},{y2:.1f}" fill="none" stroke="{col}" stroke-width="1.5"/>')
|
||||
br.append(f'<text x="{xp + 6:.1f}" y="{(y1 + y2) / 2 + 4:.1f}" fill="{col}" '
|
||||
f'style="font-weight:bold">{txt}</text>')
|
||||
body = svg.rsplit("</svg>", 1)[0].rstrip()
|
||||
svg = "\n ".join([body, step, *br]) + "\n</svg>"
|
||||
assert "<svg" in svg and "\n\n" not in svg, "linie goală în SVG"
|
||||
with open(OUT, "w") as f:
|
||||
f.write(svg)
|
||||
print("scris gen/strategii/buy_doji_ansamblu.svg")
|
||||
|
||||
Reference in New Issue
Block a user