diff --git a/gen/eticheta_libera.py b/gen/eticheta_libera.py index c12a6eb..98046e3 100644 --- a/gen/eticheta_libera.py +++ b/gen/eticheta_libera.py @@ -23,7 +23,10 @@ def _bbox(m, min_px): """Caseta etichetei, lărgită cu marginea cerută.""" tx, ty, attrs, s = float(m.group(1)), float(m.group(2)), m.group(3), m.group(4) w = CW * len(s) - x0 = tx if 'anchor="start"' in attrs else tx - w if 'anchor="end"' in attrs else tx - w / 2 + # fara text-anchor, SVG aliniaza la stanga (start), nu centrat. Presupunerea + # ca e centrat dadea alarme false pe etichetele de Level cu label_at="left", + # care se deseneaza fara anchor. + x0 = tx - w if 'anchor="end"' in attrs else tx if 'anchor="middle"' not in attrs else tx - w / 2 return s, (x0 - min_px, x0 + w + min_px, ty - 9 - min_px, ty + 3 + min_px) diff --git a/gen/strategii/dhrt.py b/gen/strategii/dhrt.py new file mode 100644 index 0000000..a94b1f4 --- /dev/null +++ b/gen/strategii/dhrt.py @@ -0,0 +1,570 @@ +#!/usr/bin/env python3 +"""Diagramele pentru summaries/17_STRATEGII/10_dhrt.md (strategia 8, DHRT). + +Un singur generator pentru toate cele șase diagrame ale capitolului: toate +desenează același trade, pe aceeași serie, deci o cifră schimbată într-un loc +trebuie să cadă peste tot — de asta nu sunt șase fișiere. + +REGULA DE CIFRE, din care iese tot restul fișierului: în lecția 8 (DHRT) +singurul preț rostit e **51** (ținta de pe XLF, varianta short — transcrierea +`05_7_STRATEGIA_8_DHRTMECANICĂ.txt:285`). Capitolul nu are diagramă cu XLF, +deci **niciun preț de pe diagramele de aici nu e al cursului**: toate vin din +exemplul construit de mine pe geometria lui, iar fiecare diagramă o declară +în titlu („exemplu construit"). Cifrele sunt aceleași cu ale sumarizării +lecției (`summaries/06_MODUL_5/05_strategia_8_dhrt.md`), ca să nu existe două +versiuni ale aceluiași trade în repo. + +Geometria exemplului: + cap 52.00 · nivelul umerilor 58.50 · H = 6.50 + treimea de jos: 52.00 .. 54.17 — stopul stă în ea, 54.17 e cel mai sus + la care are voie (riscul cel mai mic permis) + linia gâtului 63.00, trasată din interiorul gâtului (vârfurile ating 63.30) + intrare 65.00 (close-ul candelei de breakout) · R = 10.83 = 16,7% din preț + TP1 = 65.00 + 10.83 = 75.83 (distanța stopului) + TP2 = 63.00 + 11.00 = 74.00 (proiecția gâtului) — sub TP1, ordinea nu e fixă + +Rulează: .venv/bin/python gen/strategii/dhrt.py +""" +import re +import sys +from dataclasses import replace +from pathlib import Path + +sys.path.insert(0, "/workspace/atm-curs-dl") +sys.path.insert(0, "/workspace/atm-curs-dl/gen") +from eticheta_libera import eticheta_libera # noqa: E402 +from svg_candles import Arrow, Candle, Chart, Level, Mark, Zone, from_closes # noqa: E402 + +OUT = Path("/workspace/atm-curs-dl/gen/strategii") +GREEN, RED, BLUE, PURPLE, ORANGE, GREY, BLACK = ( + "#188a3e", "#c00", "#1a5fb4", "#6a3d9a", "#b06000", "#555", "#333") + +CAP, UMAR = 52.00, 58.50 # low-ul capului, nivelul umerilor +H = round(UMAR - CAP, 2) # 6.50 — înălțimea cap → umăr drept +TREIME = round(CAP + H / 3, 2) # 54.17 — marginea de sus a treimii +GAT, GAT_VARF = 63.00, 63.30 # linia gâtului / vârfurile zonei +ENTRY = 65.00 +R = round(ENTRY - TREIME, 2) # 10.83 +TP1 = round(ENTRY + R, 2) # 75.83 +TP2 = round(GAT + (GAT - CAP), 2) # 74.00 +CONS_SUS, CONS_JOS = GAT_VARF, UMAR # dreptunghiul umărului drept +CONS_JUM = round((CONS_SUS + CONS_JOS) / 2, 2) # 60.90 — jumătatea consolidării + +assert H == 6.50 and TREIME == 54.17 and R == 10.83 and TP1 == 75.83 and TP2 == 74.00 +assert TP2 < TP1, "pe exemplul ăsta TP1 iese peste TP2 — asta ilustrează capitolul" +assert round(R / ENTRY * 100, 1) == 16.7, round(R / ENTRY * 100, 1) +assert CONS_JUM == 60.90, CONS_JUM + +SVG = {} + + +# ------------------------------------------------------------------ utilitare +def panou(ch: Chart, w: int): + """Corpul unui Chart, fără , plus înălțimea lui — pentru stivuire. + + `_render()`, nu `render()`: al doilea restaurează `top`, iar liniile + desenate de mână după el ar ieși deplasate față de candele. + """ + svg = ch._render() + m = re.search(r'viewBox="0 0 ([\d.]+) ([\d.]+)"', svg) + assert m and float(m.group(1)) <= w, f"panoul iese din {w}px: {m.group(1) if m else '?'}" + return svg[svg.index(">") + 1:svg.rindex("")], float(m.group(2)) + + +def nivel(ch, w, price, texte, color, below=False, la_stanga=True, x0=24): + """Linie punctată + etichetă pe mai multe rânduri, desenată de mână.""" + y = ch._y(price) + out = [f''] + for k, s in enumerate(texte): + ty = (y + 15 + 14 * k) if below else (y - 6 - 14 * (len(texte) - 1 - k)) + tx, anchor = (x0 + 6, "start") if la_stanga else (w - 24, "end") + out.append(f'{s}') + return out + + +def zona(ch, i0, i1, label, color, sus=None, jos=None, lift=0.0, pad=10): + cds = ch.candles[i0:i1 + 1] + x0, x1 = ch._cx(i0) - pad, ch._cx(i1) + pad + y0 = ch._y(sus if sus is not None else max(c.h for c in cds)) - 8 - lift + y1 = ch._y(jos if jos is not None else min(c.l for c in cds)) + 8 + return [f'', + f'{label}'] + + +def ancorate(ch: Chart) -> set: + """Etichetele lipite prin construcție de ceva desenat: nivelurile (y − 6 față + de linia lor) și textele de săgeată (stau la capătul propriei cozi). Pentru + ele se verifică doar candelele, nu și segmentele.""" + out = {lv.text for lv in ch.levels} | {mk.text for mk in ch.marks} + for a in ch.arrows: + out |= set(a.text.split("\n")) + return out + + +def stiva(nume, w, panouri, captions, pe_linie=()): + """Lipește panouri unul sub altul și verifică etichetele (pânză + suprapuneri).""" + parts, y, bodies = [], 0.0, [] + for ch, extra in panouri: + body, h = panou(ch, w) + # extra se construiește DUPĂ randare: `_y()` are nevoie de `_pmax`, + # iar `_render()` mută `top` — altfel desenul de mână iese deplasat. + extra = list(extra(ch) if callable(extra) else extra) + parts.append(f'{"".join(extra)}{body}') + bodies.append((body + "".join(extra), y, ancorate(ch) | set(pe_linie))) + y += h + cap_top = y + 18 + height = cap_top + 17 * (len(captions) - 1) + 8 + head = (f'') + caps = [] + for k, c in enumerate(captions): + assert "<" not in c and 24 + len(c) * 6.3 <= w, f"{nume}: caption iese din pânză: {c!r}" + caps.append(f'{c}') + svg = "\n ".join([head, *parts, *caps, ""]).replace("\n ", "\n") + verifica(nume, svg, w, height, bodies) + SVG[nume] = svg + return svg + + +def singur(nume, ch, w, extra=(), captions=(), pe_linie=()): + """Un singur panou, pe aceeași cale de verificare ca stiva.""" + return stiva(nume, w, [(ch, extra)], list(captions), pe_linie) + + +TEXT = re.compile(r']*)>(.*?)', re.S) + + +def verifica(nume, svg, w, height, bodies): + """Etichetele stau în pânză, nu se suprapun între ele și nu stau peste desen. + + `pe_linie` = etichetele de nivel: alea stau lipite de linia lor prin + construcție (y − 6), deci pentru ele se verifică doar candelele. + """ + assert "\n\n" not in svg, f"{nume}: linie goală în SVG (markdown rupe blocul)" + for body, off, pe_linie in bodies: + toate = {m.group(4) for m in TEXT.finditer(body)} + eticheta_libera(body, nume, toate - pe_linie, rects=True) + eticheta_libera(body, nume, toate & pe_linie, rects=True, segmente=False) + et = [] + for m in TEXT.finditer(body): + x, y, attrs, s = float(m.group(1)), float(m.group(2)) + off, m.group(3), m.group(4) + bold = "bold" in attrs + ww = len(s) * 12 * (0.62 if bold else 0.52) + left = (x - ww if 'anchor="end"' in attrs + else x - ww / 2 if 'anchor="middle"' in attrs else x) + et.append((left, left + ww, y, s)) + assert left >= -2 and left + ww <= w + 2, f"{nume}: text iese din pânză: {s!r}" + assert 0 <= y <= height, f"{nume}: text în afara înălțimii: {s!r} (y={y:.0f})" + for i in range(len(et)): + for j in range(i + 1, len(et)): + li, ri, yi, si = et[i] + lj, rj, yj, sj = et[j] + if abs(yi - yj) < 12 and li < rj - 4 and lj < ri - 4: + raise AssertionError(f"{nume}: texte suprapuse: {si!r} / {sj!r}") + + +def check(cds, i0=None): + """h/l cuprind o/c, și open = close-ul precedentei (serie fără goluri).""" + 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: + assert abs(c.o - cds[i - 1].c) < 1e-9, f"open[{i}] != close[{i-1}]" + return cds + + + +def felie(cds, a, b): + """Copie a barelor a..b-1 — ca o diagramă să nu mute culoarea alteia.""" + return [replace(c) for c in cds[a:b]] + + +def bracket(ch, x, p_jos, p_sus, texte, color, dx=8, mijloc=True): + """Acoladă verticală între două prețuri, cu text la mijloc (sau sus).""" + y1, y2 = ch._y(p_sus), ch._y(p_jos) + out = [f''] + y0 = (y1 + y2) / 2 + 4 if mijloc else y1 + 12 + for k, t in enumerate(texte): + out.append(f'{t}') + return out + + +# --------------------------------------------------------------- seria de bază +# Patternul întreg, o singură dată: toate diagramele pornesc din `serie()`. +# 0-5 coborârea spre umărul stâng 6 umărul stâng (low 58.50) +# 7-10 urcarea în gât (high 63.30) 11-15 coborârea în cap +# 16 capul (low 52.00) 17-22 urcarea înapoi în gât +# 23-32 consolidarea = umărul drept 33 candela de breakout (close 65.00) +# 34-43 drumul spre ținte +CLOSES = [ + 65.80, 64.10, 62.60, 61.40, 60.10, 59.00, # 0-5 + 58.70, # 6 umărul stâng + 60.20, 61.60, 62.70, 63.05, # 7-10 gâtul, stânga + 61.30, 59.20, 57.00, 55.10, 53.20, # 11-15 + 52.30, # 16 capul + 54.60, 56.90, 58.80, 60.70, 62.20, 63.00, # 17-22 gâtul, dreapta + 61.10, 59.30, 58.80, 60.40, 62.10, 60.90, # 23-28 consolidarea + 59.10, 60.30, 59.40, 58.70, # 29-32 consolidarea + 65.00, # 33 breakout (marubozu) + 66.40, 68.10, 70.30, 72.20, 74.10, 75.90, # 34-39 + 75.20, 76.40, 75.60, 76.10, # 40-43 +] +WICKS = {6: (0.35, 0.20), 10: (0.25, 0.30), 16: (0.40, 0.30), 22: (0.30, 0.25), + 24: (0.25, 0.60), 29: (0.30, 0.60), 32: (0.30, 0.20), 33: (0.30, 0.30)} +VOLS = [44, 46, 43, 45, 42, 44, + 66, # umărul stâng — vârf de volum (stâng, pe volum) + 48, 45, 43, 44, + 46, 48, 52, 56, 60, + 82, # capul pe volum + 50, 46, 44, 43, 45, 44, + 42, 40, 58, 44, 46, 43, + 41, 46, 44, 42, # 29-32: volum care se stinge în consolidare + 96, # breakout: peste vârful anterior de volum + 70, 62, 58, 56, 54, 52, + 48, 50, 46, 47] + + +def serie(): + cds = from_closes(CLOSES, wick=0.30, + wicks=[WICKS.get(i, (0.30, 0.30)) for i in range(len(CLOSES))], + vols=[float(v) for v in VOLS], start=66.90) + cds[33] = Candle(58.70, 65.30, 58.50, ENTRY, vol=float(VOLS[33])) # marubozu de breakout + check(cds) + # cifrele pe care le afirmă etichetele, verificate pe serie + assert round(cds[6].l, 2) == UMAR, cds[6].l + assert round(cds[16].l, 2) == CAP, cds[16].l + assert round(max(c.h for c in cds[7:11]), 2) == GAT_VARF, max(c.h for c in cds[7:11]) + assert round(max(c.h for c in cds[17:33]), 2) == GAT_VARF, max(c.h for c in cds[17:33]) + assert round(min(c.l for c in cds[23:33]), 2) == UMAR, min(c.l for c in cds[23:33]) + assert cds[16].l < cds[6].l and cds[16].l < min(c.l for c in cds[23:33]), "capul nu e cel mai jos" + assert abs(cds[33].c - ENTRY) < 1e-9 and cds[33].c > GAT_VARF, "breakout-ul nu trece gâtul" + assert cds[33].c - cds[33].o >= 6.0, "candela de breakout nu e marubozu" + assert max(VOLS[:33]) == 82 and VOLS[33] == 96, "volumul de breakout nu e peste vârful anterior" + assert max(c.h for c in cds[34:]) >= TP1, "seria nu atinge TP1" + primul_tp2 = next(i for i in range(34, len(cds)) if cds[i].h >= TP2) + primul_tp1 = next(i for i in range(34, len(cds)) if cds[i].h >= TP1) + assert primul_tp2 < primul_tp1, "TP2 (74.00) trebuie atins înaintea lui TP1 (75.83)" + assert min(c.l for c in cds[34:]) > TREIME, "stopul ar fi fost lovit după intrare" + return cds + + +# ---------------------------------------------------------------- 1. ansamblu +def d_ansamblu(): + cds = serie() + w = 740 + niveluri = [ + Level(TP1, f"TP1 {TP1:.2f} = distanța stopului", BLUE, label_at="left"), + Level(TP2, f"TP2 {TP2:.2f} = proiecția gâtului", BLUE, label_at="left"), + Level(ENTRY, f"intrare {ENTRY:.2f}", GREEN), + Level(GAT, f"linia gâtului {GAT:.2f}", PURPLE), + Level(TREIME, f"SL {TREIME:.2f} (cel mai sus permis)", RED, label_at="left"), + ] + ch = Chart( + candles=cds, + levels=niveluri, + zones=[Zone(23, 32, "umărul drept = consolidarea", GREEN, p_top=GAT_VARF + 0.3)], + arrows=[ + Arrow(16, "capul 52.00, pe suport", where="below", color=BLACK, dx=-20, dy=16), + Arrow(6, "umărul stâng 58.50", where="below", color=BLACK, dx=-14, dy=46), + Arrow(33, "AICI INTRI: close 65.00,\npeste consolidare și peste gât", + where="below", price=ENTRY, color=GREEN, dx=-96, dy=78), + ], + scale=15.0, gap=15, body_w=9, left=58, width=w, vol_h=66, + vol_label="volum", top=34, + title="DHRT — tot trade-ul (exemplu construit: niciun preț de aici nu e rostit în lecție)", + ) + singur("ansamblu", ch, w, (), [ + "Serie construită (open = close-ul barei anterioare), la scară, pe geometria lecției.", + f"cap {CAP:.2f} · umeri {UMAR:.2f} · H {H:.2f} · gât {GAT:.2f} · " + f"intrare {ENTRY:.2f} · SL {TREIME:.2f} · R {R:.2f} (16,7% din preț).", + f"TP1 ({TP1:.2f}) iese peste TP2 ({TP2:.2f}) — ordinea lor nu e fixă.", + ], pe_linie=[lv.text for lv in niveluri]) + + + +# -------------------------------------------------------------------- 4. stop +def d_stop(): + """H = cap → nivelul umărului drept, împărțit în trei. Stopul stă în treimea + de jos; 54.17 e marginea ei de sus, adică cel mai sus la care are voie.""" + cds = felie(serie(), 12, 36) # capul, urcarea, consolidarea, breakout-ul + w = 760 + i_cap, i_brk = 16 - 12, 33 - 12 + doua_treimi = round(CAP + 2 * H / 3, 2) + assert doua_treimi == 56.33, doua_treimi + niveluri = [ + Level(ENTRY, f"intrare {ENTRY:.2f} = close-ul candelei de breakout", GREEN, + label_at="left"), + Level(UMAR, f"umărul drept {UMAR:.2f}", BLACK, label_at="left"), + Level(doua_treimi, f"{doua_treimi:.2f} = 2/3", GREY, dash="2 4"), + Level(TREIME, f"SL {TREIME:.2f} = 1/3 din H", RED, label_at="left"), + Level(CAP, f"capul {CAP:.2f}", BLACK, label_at="left"), + ] + ch = Chart( + candles=cds, + levels=niveluri, + arrows=[ + Arrow(i_cap, f"{TREIME:.2f} e cel mai SUS la care are voie stopul\n" + f"(riscul cel mai mic permis); mai jos, oricât", + where="below", price=TREIME, color=RED, dx=-30, dy=72), + ], + scale=25.0, gap=20, body_w=11, left=200, width=w, + title="Stopul: treimea de jos din H (exemplu construit — cifrele nu sunt rostite în lecție)", + ) + + def extra(ch): + return (bracket(ch, 160, CAP, UMAR, [f"H = {H:.2f}", "cap → umăr"], BLACK, dx=6) + + bracket(ch, ch._cx(i_brk) + 52, TREIME, ENTRY, + [f"R = {R:.2f}"], RED, dx=8)) + + singur("stop", ch, w, extra, [ + "H se măsoară de la low-ul capului la nivelul umărului drept — nu la linia gâtului.", + f"{CAP:.2f} + {H:.2f}/3 = {TREIME:.2f}. Peste {TREIME:.2f} stopul nu are voie; sub, oricât.", + f"R se măsoară de la intrare ({ENTRY:.2f}) la stop, nu de la nivelul umărului.", + ], pe_linie=([lv.text for lv in niveluri] + + [f"H = {H:.2f}", "cap → umăr", f"R = {R:.2f}"])) + + +# ------------------------------------------------------------------- 5. ținte +def d_tinte(): + """TP1 = distanța stopului; TP2 = proiecția gâtului. Linia gâtului se trage + din interiorul gâtului (63.00), nu prin vârfuri (63.30) și nu ca medie.""" + cds = felie(serie(), 16, 44) + w = 760 + i_brk, i_tp2, i_tp1 = 33 - 16, 38 - 16, 39 - 16 + inaltime = round(GAT - CAP, 2) + assert inaltime == 11.00 and round(GAT + inaltime, 2) == TP2 + niveluri = [ + Level(TP1, f"TP1 {TP1:.2f} = intrarea + R ({R:.2f})", BLUE, label_at="left"), + Level(TP2, f"TP2 {TP2:.2f} = gâtul + {inaltime:.2f}", BLUE, label_at="left"), + Level(ENTRY, f"intrare {ENTRY:.2f}", GREEN, label_at="left"), + Level(GAT, f"linia gâtului {GAT:.2f}", PURPLE), + Level(CAP, f"capul {CAP:.2f}", BLACK), + ] + ch = Chart( + candles=cds, + levels=niveluri, + arrows=[ + Arrow(6, f"vârfurile gâtului ating {GAT_VARF:.2f}; linia se trage din\n" + f"INTERIORUL gâtului, la {GAT:.2f} — nu e media lor", + where="above", price=GAT_VARF, color=PURPLE, dx=-30, dy=-50), + Arrow(i_tp2, f"TP2 ({TP2:.2f}) vine înaintea lui TP1 ({TP1:.2f})\n" + f"pe exemplul ăsta — ordinea nu e fixă", + where="below", price=TP2, color=BLUE, dx=-290, dy=30), + ], + scale=15.0, gap=22, body_w=11, left=92, width=w, + title="Cele două ținte (exemplu construit — cifrele nu sunt rostite în lecție)", + ) + + def extra(ch): + return (bracket(ch, 58, CAP, GAT, [f"{inaltime:.2f}"], PURPLE, dx=6) + + bracket(ch, ch._cx(i_tp1) + 44, GAT, TP2, [f"{inaltime:.2f}"], BLUE, dx=8)) + + singur("tinte", ch, w, extra, [ + f"TP1 = {ENTRY:.2f} + {R:.2f} = {TP1:.2f}. TP2 = {GAT:.2f} + ({GAT:.2f} − {CAP:.2f}) = {TP2:.2f}.", + "Cât de jos tragi linia gâtului mută TP2 cu exact atât — e o alegere a ta, nu o formulă.", + "Lecția spune doar «din interiorul gâtului, iau un average»; formulă rostită nu există.", + ], pe_linie=[lv.text for lv in niveluri] + [f"{inaltime:.2f}"]) + + +# ---------------------------------------------------------------- 3. trigger +# Panoul NU: preț care ține consolidarea, dar volum cu semn (stil on balance +# volume) care face minime tot mai joase. Seria e construită doar pentru +# diagrama asta — niciun preț de aici nu e rostit în lecție. +NU_CLOSES = [61.80, 60.20, 61.70, 59.90, 61.90, 60.10, 61.60, 59.80, + 61.80, 60.00, 61.40, 59.70, 57.40, 56.10] +NU_VOLS = [8.0, 5.0, 6.0, -4.0, 3.0, -7.0, 2.0, -10.0, + 1.0, -13.0, -2.0, -16.0, -9.0, -20.0] + + +def d_trigger(): + cds_da = felie(serie(), 12, 37) + w = 760 + i_brk = 33 - 12 + niv_da = [ + Level(ENTRY, f"intrare {ENTRY:.2f} = close-ul trigger-ului", GREEN, label_at="left"), + Level(GAT, f"linia gâtului {GAT:.2f}", PURPLE, label_at="left"), + ] + ch_da = Chart( + candles=cds_da, + levels=niv_da, + zones=[Zone(23 - 12, 32 - 12, "consolidarea umărului drept", GREEN, + p_top=GAT_VARF + 0.3)], + arrows=[ + Arrow(i_brk, "TRIGGER: close peste consolidare ȘI peste gât,\n" + "cu volumul peste vârful anterior de volum", + where="below", price=ENTRY, color=GREEN, dx=-276, dy=36), + ], + scale=19.0, gap=20, body_w=11, left=60, width=w, vol_h=60, + vol_label="volum", title="DA — trigger în umărul drept (exemplu construit)", + ) + + cds_nu = from_closes(NU_CLOSES, wick=0.35, vols=NU_VOLS, start=62.40) + check(cds_nu) + minime = [NU_VOLS[i] for i in range(1, len(NU_VOLS) - 1) + if NU_VOLS[i] < NU_VOLS[i - 1] and NU_VOLS[i] < NU_VOLS[i + 1]] + assert minime == sorted(minime, reverse=True) and len(minime) >= 4, minime + assert all(c.l >= UMAR for c in cds_nu[:11]), "prețul trebuie să țină consolidarea" + assert cds_nu[-1].c < UMAR, "la final prețul cedează" + ch_nu = Chart( + candles=cds_nu, + levels=[Level(UMAR, f"baza consolidării {UMAR:.2f}", GREY, label_at="left")], + arrows=[ + Arrow(13, "prețul ține consolidarea, dar minimele volumului coboară:\n" + "aștepți breakdown, nu intri long", + where="below", price=56.20, color=RED, dx=-215, dy=10), + ], + scale=19.0, gap=28, body_w=13, left=72, width=w, vol_h=70, vol_zero=0.0, + vol_label="volum cu semn (on balance volume)", + title="NU — breakout de preț fără breakout de volum (exemplu construit)", + ) + stiva("trigger", w, [(ch_da, ()), (ch_nu, ())], [ + "Sus: trigger-ul e candela de breakout; buy doji / buy hammer / buy momentum", + "din umărul drept sunt la fel de valide, cu intrarea la close-ul lor.", + "Jos: singurul «nu intra» formulat ca regulă mecanică — volumul rupe înaintea prețului.", + ], pe_linie=[lv.text for lv in niv_da] + [f"baza consolidării {UMAR:.2f}", "0"]) + + +# ------------------------------------------------------------------ 2. setup +# Panoul NU: triple bottom la al treilea test, cu volum tot mai mic. Serie +# proprie, cu altă scară de preț decât restul capitolului — regulile sunt +# aceleași, cifrele nu au legătură cu exemplul construit al trade-ului. +TB_CLOSES = [54.50, 53.20, 51.80, 50.40, 50.20, 51.60, 52.80, 51.90, 50.80, 50.30, + 51.50, 52.40, 51.30, 50.60, 50.25, 51.00, 50.40, 49.60, 48.70] +TB_BAZA = 50.00 +TB_LOWS = (4, 9, 14) +TB_VOLS = [40.0, 44.0, 52.0, 60.0, 74.0, 46.0, 42.0, 44.0, 48.0, 54.0, + 40.0, 38.0, 39.0, 40.0, 38.0, 36.0, 34.0, 44.0, 52.0] + + +def d_setup(): + cds_da = felie(serie(), 0, 33) + w = 760 + niv_da = [ + Level(UMAR, f"pivotul lunii {UMAR:.2f}", BLACK), + Level(CAP, f"suportul lunii {CAP:.2f}", BLUE, label_at="left"), + ] + ch_da = Chart( + candles=cds_da, + levels=niv_da, + marks=[Mark(6, "umărul stâng", GREY, "below", dy=14)], + zones=[Zone(23, 32, "umărul drept = consolidarea", GREEN, p_top=GAT_VARF + 0.3)], + arrows=[ + Arrow(16, "capul se oprește pe suport, umărul drept pe pivot.\n" + "Ajunge unul din două; fără niciunul, setup-ul nu e verificabil.", + where="below", price=CAP, color=BLUE, dx=10, dy=22), + ], + scale=15.0, gap=17, body_w=9, left=58, width=w, + title="DA — HS răsturnat cu capul și umărul drept pe niveluri (exemplu construit)", + ) + + cds_nu = from_closes(TB_CLOSES, wick=0.30, + wicks=[(0.30, 0.30) if i not in TB_LOWS else + (0.25, round(TB_CLOSES[i] - TB_BAZA, 2)) for i in range(19)], + vols=TB_VOLS, start=55.60) + check(cds_nu) + for i in TB_LOWS: + assert round(cds_nu[i].l, 2) == TB_BAZA, (i, cds_nu[i].l) + assert TB_VOLS[4] > TB_VOLS[9] > TB_VOLS[14], "volumul trebuie să scadă la fiecare picior" + assert cds_nu[-1].c < TB_BAZA, "al patrulea test cedează" + ch_nu = Chart( + candles=cds_nu, + levels=[Level(TB_BAZA, f"baza {TB_BAZA:.2f}, testată de trei ori", GREY)], + marks=[Mark(4, "1", GREY, "below"), Mark(9, "2", GREY, "below"), + Mark(14, "3", RED, "below")], + arrows=[ + Arrow(14, "al treilea test, cu volum mai mic la fiecare picior:\n" + "șansele se întorc spre breakdown — nu intri", + where="below", price=TB_BAZA, color=RED, dx=-90, dy=42), + ], + scale=38.0, gap=26, body_w=13, left=70, width=w, vol_h=56, + vol_label="volum", title="NU — triple bottom la al treilea test (altă scară de preț)", + ) + stiva("setup", w, [(ch_da, ()), (ch_nu, ())], [ + "Sus: filtrul de la pasul 1 — capul și/sau umărul drept pe un suport, pivot sau PPA.", + "Simetria umerilor e analiza autorului, nu un prag: «depinde cum măsori».", + "Jos: motivul e citat de autor din literatură, nu din testele lui.", + ], pe_linie=[lv.text for lv in niv_da] + + [f"baza {TB_BAZA:.2f}, testată de trei ori"]) + + +# ----------------------------------------------------------------- 6. retest +# „Locul crimei" = dreptunghiul din care s-a plecat. Ambele panouri pornesc din +# aceeași consolidare și din aceeași candelă de breakout; diferă doar adâncimea +# întoarcerii. Serii construite, pe aceeași geometrie ca restul capitolului. +CONS = [61.00, 59.20, 62.90, 59.00, 61.50, 58.80] +CONS_W = [(0.30, 0.30), (0.30, 0.50), (0.40, 0.30), (0.30, 0.50), + (0.30, 0.30), (0.30, 0.30)] +RETEST_DA = [64.20, 62.40, 61.40, 61.20, 62.30, 64.10, 66.00, 67.60] +RETEST_NU = [64.00, 62.10, 60.20, 58.20, 56.80, 55.40] + + +def _cu_retest(cozi): + cds = from_closes(CONS, wicks=CONS_W, start=62.20) + cds.append(Candle(CONS[-1], 65.30, 58.60, ENTRY)) + cds += from_closes(cozi, wick=0.30, start=ENTRY) + check(cds) + assert round(max(c.h for c in cds[:6]), 2) == CONS_SUS, max(c.h for c in cds[:6]) + assert round(min(c.l for c in cds[:6]), 2) == CONS_JOS, min(c.l for c in cds[:6]) + return cds + + +def d_retest(): + w = 740 + niv = lambda: [ + Level(ENTRY, f"breakout {ENTRY:.2f}", GREEN), + Level(CONS_SUS, f"vârful consolidării {CONS_SUS:.2f}", GREY, dash="2 4"), + Level(CONS_JUM, f"1/2 din consolidare {CONS_JUM:.2f}", BLUE), + Level(CONS_JOS, f"baza consolidării {CONS_JOS:.2f}", RED), + ] + cds_da = _cu_retest(RETEST_DA) + fund = round(min(c.l for c in cds_da[7:]), 2) + assert fund == CONS_JUM, fund + assert cds_da[-1].c > ENTRY, "după retest trade-ul repleacă" + ch_da = Chart( + candles=cds_da, + levels=niv(), + zones=[Zone(0, 5, "locul crimei", ORANGE, p_top=CONS_SUS + 0.2, p_bot=CONS_JOS - 0.2)], + arrows=[ + Arrow(10, f"retest până la jumătatea consolidării ({CONS_JUM:.2f}):\n" + f"a doua fereastră de intrare", + where="below", price=CONS_JUM, color=BLUE, dx=-120, dy=24), + ], + scale=26.0, gap=34, body_w=15, left=80, width=w, + title="DA — retest la 1/2 din consolidare (exemplu construit)", + ) + cds_nu = _cu_retest(RETEST_NU) + assert min(c.l for c in cds_nu[7:]) < CONS_JOS, "panoul NU trebuie să rupă baza" + ch_nu = Chart( + candles=cds_nu, + levels=niv(), + zones=[Zone(0, 5, "locul crimei", ORANGE, p_top=CONS_SUS + 0.2, p_bot=CONS_JOS - 0.2)], + arrows=[ + Arrow(11, "sub bază ideea s-a inversat:\nde aici se shortează, nu se cumpără", + where="below", price=CONS_JOS - 1.6, color=RED, dx=-130, dy=10), + ], + scale=26.0, gap=34, body_w=15, left=80, width=w, + title="NU — retest care trece sub bază (exemplu construit)", + ) + stiva("retest", w, [(ch_da, ()), (ch_nu, ())], [ + "Singura cifră a secțiunii: «unul pe doi retestează din consolidare» — jumătatea.", + "Între jumătate și bază e zonă de avertizare; sub bază, ideea s-a inversat.", + "Retestul nu e garantat: de aceea intrarea se face în tranșe, nu așteptând.", + ], pe_linie=[lv.text for lv in niv()]) + + +if __name__ == "__main__": + d_ansamblu() + d_setup() + d_stop() + d_tinte() + d_trigger() + d_retest() + for nume, svg in SVG.items(): + p = OUT / f"dhrt_{nume}.svg" + p.write_text(svg, encoding="utf-8") + print(f"scris {p.name} ({len(svg)} octeți)") diff --git a/gen/strategii/dhrt_ansamblu.svg b/gen/strategii/dhrt_ansamblu.svg new file mode 100644 index 0000000..35fafff --- /dev/null +++ b/gen/strategii/dhrt_ansamblu.svg @@ -0,0 +1,164 @@ + + + DHRT — tot trade-ul (exemplu construit: niciun preț de aici nu e rostit în lecție) + + umărul drept = consolidarea + + TP1 75.83 = distanța stopului + + TP2 74.00 = proiecția gâtului + + intrare 65.00 + + linia gâtului 63.00 + + SL 54.17 (cel mai sus permis) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + volum + + + capul 52.00, pe suport + + + umărul stâng 58.50 + + + AICI INTRI: close 65.00, + peste consolidare și peste gât + + Serie construită (open = close-ul barei anterioare), la scară, pe geometria lecției. + cap 52.00 · umeri 58.50 · H 6.50 · gât 63.00 · intrare 65.00 · SL 54.17 · R 10.83 (16,7% din preț). + TP1 (75.83) iese peste TP2 (74.00) — ordinea lor nu e fixă. + \ No newline at end of file diff --git a/gen/strategii/dhrt_retest.svg b/gen/strategii/dhrt_retest.svg new file mode 100644 index 0000000..cd17701 --- /dev/null +++ b/gen/strategii/dhrt_retest.svg @@ -0,0 +1,95 @@ + + + DA — retest la 1/2 din consolidare (exemplu construit) + + locul crimei + + breakout 65.00 + + vârful consolidării 63.30 + + 1/2 din consolidare 60.90 + + baza consolidării 58.50 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + retest până la jumătatea consolidării (60.90): + a doua fereastră de intrare + + + NU — retest care trece sub bază (exemplu construit) + + locul crimei + + breakout 65.00 + + vârful consolidării 63.30 + + 1/2 din consolidare 60.90 + + baza consolidării 58.50 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + sub bază ideea s-a inversat: + de aici se shortează, nu se cumpără + + Singura cifră a secțiunii: «unul pe doi retestează din consolidare» — jumătatea. + Între jumătate și bază e zonă de avertizare; sub bază, ideea s-a inversat. + Retestul nu e garantat: de aceea intrarea se face în tranșe, nu așteptând. + \ No newline at end of file diff --git a/gen/strategii/dhrt_setup.svg b/gen/strategii/dhrt_setup.svg new file mode 100644 index 0000000..3996fd3 --- /dev/null +++ b/gen/strategii/dhrt_setup.svg @@ -0,0 +1,156 @@ + + + DA — HS răsturnat cu capul și umărul drept pe niveluri (exemplu construit) + + umărul drept = consolidarea + + pivotul lunii 58.50 + + suportul lunii 52.00 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + umărul stâng + + + capul se oprește pe suport, umărul drept pe pivot. + Ajunge unul din două; fără niciunul, setup-ul nu e verificabil. + + + NU — triple bottom la al treilea test (altă scară de preț) + + baza 50.00, testată de trei ori + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + volum + 1 + 2 + 3 + + + al treilea test, cu volum mai mic la fiecare picior: + șansele se întorc spre breakdown — nu intri + + Sus: filtrul de la pasul 1 — capul și/sau umărul drept pe un suport, pivot sau PPA. + Simetria umerilor e analiza autorului, nu un prag: «depinde cum măsori». + Jos: motivul e citat de autor din literatură, nu din testele lui. + \ No newline at end of file diff --git a/gen/strategii/dhrt_stop.svg b/gen/strategii/dhrt_stop.svg new file mode 100644 index 0000000..dd14fc2 --- /dev/null +++ b/gen/strategii/dhrt_stop.svg @@ -0,0 +1,70 @@ + + H = 6.50cap → umărR = 10.83 + Stopul: treimea de jos din H (exemplu construit — cifrele nu sunt rostite în lecție) + + intrare 65.00 = close-ul candelei de breakout + + umărul drept 58.50 + + 56.33 = 2/3 + + SL 54.17 = 1/3 din H + + capul 52.00 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 54.17 e cel mai SUS la care are voie stopul + (riscul cel mai mic permis); mai jos, oricât + + H se măsoară de la low-ul capului la nivelul umărului drept — nu la linia gâtului. + 52.00 + 6.50/3 = 54.17. Peste 54.17 stopul nu are voie; sub, oricât. + R se măsoară de la intrare (65.00) la stop, nu de la nivelul umărului. + \ No newline at end of file diff --git a/gen/strategii/dhrt_tinte.svg b/gen/strategii/dhrt_tinte.svg new file mode 100644 index 0000000..d4d1ed9 --- /dev/null +++ b/gen/strategii/dhrt_tinte.svg @@ -0,0 +1,82 @@ + + 11.0011.00 + Cele două ținte (exemplu construit — cifrele nu sunt rostite în lecție) + + TP1 75.83 = intrarea + R (10.83) + + TP2 74.00 = gâtul + 11.00 + + intrare 65.00 + + linia gâtului 63.00 + + capul 52.00 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + vârfurile gâtului ating 63.30; linia se trage din + INTERIORUL gâtului, la 63.00 — nu e media lor + + + TP2 (74.00) vine înaintea lui TP1 (75.83) + pe exemplul ăsta — ordinea nu e fixă + + TP1 = 65.00 + 10.83 = 75.83. TP2 = 63.00 + (63.00 − 52.00) = 74.00. + Cât de jos tragi linia gâtului mută TP2 cu exact atât — e o alegere a ta, nu o formulă. + Lecția spune doar «din interiorul gâtului, iau un average»; formulă rostită nu există. + \ No newline at end of file diff --git a/gen/strategii/dhrt_trigger.svg b/gen/strategii/dhrt_trigger.svg new file mode 100644 index 0000000..8e55c85 --- /dev/null +++ b/gen/strategii/dhrt_trigger.svg @@ -0,0 +1,149 @@ + + + DA — trigger în umărul drept (exemplu construit) + + consolidarea umărului drept + + intrare 65.00 = close-ul trigger-ului + + linia gâtului 63.00 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + volum + + + TRIGGER: close peste consolidare ȘI peste gât, + cu volumul peste vârful anterior de volum + + + NU — breakout de preț fără breakout de volum (exemplu construit) + + baza consolidării 58.50 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + + + + + + + + + + + + + + + volum cu semn (on balance volume) + + + prețul ține consolidarea, dar minimele volumului coboară: + aștepți breakdown, nu intri long + + Sus: trigger-ul e candela de breakout; buy doji / buy hammer / buy momentum + din umărul drept sunt la fel de valide, cu intrarea la close-ul lor. + Jos: singurul «nu intra» formulat ca regulă mecanică — volumul rupe înaintea prețului. + \ No newline at end of file diff --git a/gen/strategii/substituie.py b/gen/strategii/substituie.py index a35473e..4f6e707 100644 --- a/gen/strategii/substituie.py +++ b/gen/strategii/substituie.py @@ -19,7 +19,10 @@ import sys from pathlib import Path ROOT = Path(__file__).resolve().parent.parent.parent -CAPITOLE = sorted((ROOT / "summaries" / "17_STRATEGII").glob("[0A][1-8]_*.md")) +_S = ROOT / "summaries" / "17_STRATEGII" +# numerele au ajuns la doua cifre (09, 10): un glob pe [0A][1-8] le sarea, si +# sarea tacut — capitolul parea doar sa n-aiba marcaje. +CAPITOLE = sorted({*_S.glob("[0-9][0-9]_*.md"), *_S.glob("A[0-9]_*.md")}) # fie marcajul gol (prima punere), fie blocul deja ancorat (repunere) MARCAJ = re.compile( r"^[ \t]*[ \t]*$"