diff --git a/PROGRESS.md b/PROGRESS.md
index 4bff70e..08c2e53 100644
--- a/PROGRESS.md
+++ b/PROGRESS.md
@@ -1,4 +1,85 @@
-# Progres — atm-curs-dl (actualizat 2026-09-19, 18:30 UTC)
+# Progres — atm-curs-dl (actualizat 2026-09-19, 21:15 UTC)
+
+---
+
+# 19.09.2026, 21:15 UTC — `01_buy_doji` refăcut după respingerea userului
+
+## Ce a reclamat userul la mostra de format tutorial
+
+A citit `01_buy_doji.html` și l-a respins. Cererile lui, care sunt acum
+contract în `STYLE.md`, secțiunea **„FORMAT TUTORIAL v2"** (prima din fișier):
+niveluri 1-2-3 de sus în jos; fiecare termen tehnic explicat inline la prima
+folosire („iesire pe trail" nu se presupune știut); zero trimiteri
+`fișier:linie` în corpul textului; diagrame „ca la proști", cu săgeți care
+spun **și de ce**, și cu zona dreptunghiulară de evidențiere care i-a plăcut
+la pasul 7; tranzacția în oglindă pe short cu **diagrama ei completă**; mai
+puține tabele; mai scurt.
+
+## Starea: gata, comis. Nimic nu mai rulează.
+
+- Textul: de la **8430 la 4190 de cuvinte**, de la **2451 la ~570 de linii**.
+ Nivel 1 (din avion, cu mini-glosar de 9 termeni) → nivel 2 (6 pași
+ mecanici, fiecare cu diagrama lui și „Treci mai departe doar dacă…") →
+ oglinda short → nivel 3 (rafinări). Sursele, toate, la final, cu legendă de
+ prescurtări. Două tabele în tot documentul.
+- **Toate cele 14 diagrame refăcute** cu zone și săgeți. `substituie.py`: 14
+ puse, 0 lipsă. Auditul: 8 raportări, toate fals-pozitive ``
+ (perechi de etichete identice din panouri stivuite, în `aplus` și
+ `fereastra`) — verificate vizual, nu se atinge nimic.
+- **Bug-ul vechi din `buy_doji_sell.svg`** (titlul peste eticheta
+ „doji 99.20 / 100.10") — reparat.
+
+## Decizii de conținut luate azi (ca să nu fie redeschise)
+
+1. **Un singur trade prin toată procedura.** Înainte, pașii 2-4 foloseau
+ doji 50.10 iar pașii 5-6 doji 53.00 — exact confuzia reclamată. Cifrele
+ canonice acum: doji **50.10 / 49.25**, intrare **50.40**, stop **49.20**,
+ 1R = **1.20**, TP1 = **51.60**. Low-ul doji-ului a urcat de la 49.20 la
+ 49.25 ca stopul să fie *sub* el, nu pe el. `buy_doji_ansamblu.serie()` e
+ sursa unică, importată de `stop`, `transe` și `iesire` — coerența e acum
+ structurală, nu ținută prin disciplină.
+2. **Short: stop 100.15, nu 100.10.** Exemplul cursului punea stopul exact pe
+ high, contrazicând regula oglindită de la pasul 5. Am ales coerența cu
+ regula; cifra cursului (100.10 / 97.90) e păstrată în text ca observație.
+3. **LABU: „triple bottom" și „al doilea test" nu se contrazic.** Ambele sunt
+ în transcriere. Seria arată acum **trei minime** (82.40 / 82.00 / 81.45)
+ dar **două atingeri** ale pivotului 86.00 — între primele două prețul urcă
+ la 88.90 și trece pe deasupra, înainte de al treilea urcă doar la 85.80.
+ Generatorul are o aserțiune care definește „test" ca minim precedat de o
+ revenire peste nivel, deci regula rezistă la ajustări viitoare.
+4. Panoul de jos de la pasul 5 e **alt traseu**, nu seria comună (fundul mai
+ face un picior). Textul o spune explicit acum — altfel „a șasea candelă"
+ vs. „bara 19" pare eroare.
+
+## Ce urmează
+
+- **Userul trebuie să aprobe formatul** înainte să-l aplicăm la celelalte 8
+ capitole din `17_STRATEGII/`. Asta a fost înțelegerea de la început.
+- Neatins, rămas de la `cap02`: în diagramele capitolului 02, SVG-ul cu Excel
+ etichetează „CROSSING BULL" ce e doar PPD > PPW, iar legenda SVG-ului
+ A+/mediocru dă confluența ca regulă a cursului, deși e completare.
+- `aplus.spec.txt` cere doji cu high 79.00, generatorul are 78.50
+ (preexistent, nu din lotul de azi).
+
+## CAPCANE NOI (din lotul ăsta)
+
+- **`Chart.render()` restaura `top`/`left`/`width` la ieșire**, deci orice
+ coordonată calculată cu `_y()`/`_cx()` **după** randare ieșea deplasată cu
+ ~22 px. Reparat azi în `svg_candles.py` (randarea nu mai mută obiectul);
+ generatoarele care își calculează singure offsetul din SVG-ul randat merg
+ mai departe — formula dă zero. Dacă ai nevoie de coordonate după randare,
+ folosește `_render()` și pune o aserțiune că `top`, `left` și lățimea din
+ `viewBox` n-au fost modificate: altfel o săgeată cu text prea lung mută
+ tăcut graficul și panourile stivuite se dezaliniază fără să pice nimic.
+- **Eticheta unei `Zone` nu stă deasupra dreptunghiului**, ci deasupra celei
+ mai înalte candele din dreptul textului. La o zonă îngustă cu text lung
+ ajunge la zeci de pixeli de banda pe care o marchează. Soluție: `text=""`
+ și eticheta desenată manual, sau `p_top` distanțat pe verticală.
+- **Agenții apar în `ListAgents` cu numele sesiunii-părinte**, identic cu al
+ sesiunii principale, nu cu numele lor de agent. Am cerut din greșeală
+ închiderea a patru agenți activi crezând că sunt sesiuni vechi. Au refuzat
+ corect, pentru că cererea era formulată „dacă ai ceva în lucru, refuză și
+ spune-mi ce ai atins". **Formulați mereu cererile de shutdown așa.**
---
diff --git a/STYLE.md b/STYLE.md
index 404f69d..a8bc8b4 100644
--- a/STYLE.md
+++ b/STYLE.md
@@ -3,6 +3,59 @@
Referință obligatorie înainte de a scrie/genera orice fișier nou în
`summaries/`. Exemplul de referință: `summaries/05_MODUL_4/01_patternuri_complexe_partea_a_doua.md`.
+## FORMAT TUTORIAL v2 — corecțiile userului după ce a citit `01_buy_doji` (19.09.2026, seara)
+
+Userul a citit capitolul rescris în format tutorial și l-a respins. Ce a cerut,
+punct cu punct. **Astea bat orice regulă de mai jos care le contrazice.**
+
+### Structura: trei niveluri, de sus în jos
+
+1. **Nivelul 1 — din avion.** O pagină. Ce e strategia, o diagramă mare cu
+ tot trade-ul adnotat, fraza de decizie, mini-glosarul termenilor. Cine
+ citește doar atât trebuie să știe deja ce caută pe ecran.
+2. **Nivelul 2 — procedura mecanică.** Pașii, în ordinea în care îi faci.
+ Fiecare pas: o propoziție de acțiune, **o diagramă**, maximum ~300 de
+ cuvinte, și „Treci mai departe doar dacă…". Aici omul nu alege nimic — dă
+ din mână, mecanic.
+3. **Nivelul 3 — rafinări.** Confirmările care fac un setup A+, perechile de
+ TF, tranșele, exemplul real, transferul pe prop/BVB, contradicțiile.
+ Începe cu o linie: „asta citești după 20 de trade-uri făcute mecanic".
+
+### Limbaj
+
+- **Explică fiecare termen tehnic la prima folosire, inline, într-o
+ propoziție.** „ieșire pe trail", „retest", „1R", „tranșe", „doji" — nimic
+ nu se presupune știut. Dacă termenul are formă pe grafic, desenează-l.
+- Propoziții normale, nu telegrafice. Fără stil abrupt de notiță.
+- Cifrele se plimbă cu o diagramă. Un pas care e doar text cu numere e un pas
+ prost scris.
+
+### Referințe
+
+- **Zero trimiteri `fișier:linie` în corpul textului.** Ele îngreunează
+ cititul. Toate se mută la finalul fiecărui nivel, într-un bloc „Surse",
+ grupate pe pas. În text, cel mult un link markdown către un alt capitol
+ din `summaries/`.
+
+### Diagrame — „ca la proști"
+
+- Fiecare diagramă de preț arată **săgeți cu text**: „aici intri", „aici pui
+ stopul — sub el ideea e moartă", „aici prima țintă".
+- **Zonă dreptunghiulară de evidențiere** peste regiunea despre care e
+ vorba (userului i-a plăcut explicit cea de la pasul 7). Folosește-o.
+- Fiecare săgeată spune și **de ce**, nu doar ce.
+- **Tranzacția în oglindă (short) are diagrama ei completă**, nu o
+ propoziție „e invers". Userul e vizual.
+
+### Volum
+
+- Mai puține tabele. Un tabel doar dacă compari 3+ lucruri pe aceleași
+ criterii. Altfel: listă sau paragraf.
+- Mai scurt. Userul: „te lungesti foarte mult", „daca imi dai extrem de
+ multe informatii nu stiu care sunt relevante". Ce nu intră în procedura
+ mecanică merge la nivelul 3 sau se taie.
+- Exemplele concrete și setup-urile rămân — alea îi plac. Abstracțiile pleacă.
+
## FORMAT TUTORIAL pentru capitolele de strategie (cerut de user, 19.09.2026)
Userul a citit `17_STRATEGII/A3_intrarea.md` și a spus: conținutul îi place,
diff --git a/gen/strategii/buy_doji_ansamblu.py b/gen/strategii/buy_doji_ansamblu.py
index 3ab5937..dce1759 100644
--- a/gen/strategii/buy_doji_ansamblu.py
+++ b/gen/strategii/buy_doji_ansamblu.py
@@ -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 "')
+ # 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'')
+ br.append(f'{txt}')
+ body = svg.rsplit("", 1)[0].rstrip()
+ svg = "\n ".join([body, step, *br]) + "\n"
+ assert "")
+ Coordonatele se citesc din SVG-ul randat (barele de volum sunt rect-urile cu
+ opacity 0.55, în ordinea candelelor), ca să nu depindă de deplasarea internă
+ a lui Chart. Eticheta stă sub linia de bază, unde nu acoperă nimic;
+ `box` = dreptunghi peste bare, altfel săgeată de jos în sus, sub bară.
+ """
+ bars = [tuple(map(float, m)) for m in re.findall(
+ r'', svg)]
+ base = bars[0][1] + bars[0][3]
+ x0, x1 = bars[i0][0] - 4, bars[i1][0] + bars[i1][2] + 4
+ top = min(b[1] for b in bars[i0:i1 + 1]) - 4
+ if box:
+ mark = (f'')
+ ty = base + 15
+ else:
+ cx = (x0 + x1) / 2
+ mark = (f''
+ f'')
+ ty = base + 34
+ label = (f'{text}')
+ # dreptunghiul/săgeata în spatele barelor, eticheta la final
+ i = svg.index('", label + "")
+
+
+def grow(svg: str, dh: float) -> str:
+ """Mărește viewBox-ul unui panou cu dh px jos, pentru eticheta de sub volum."""
+ h = vb_height(svg)
+ return svg.replace(f'0 0 {PW} {h:.0f}"', f'0 0 {PW} {h + dh:.0f}"', 1)
+
+
+PANEL_GAP, PAD = 24, 8
+TITLE_H, FOOTER_H = 30, 0
+svg_a = grow(vol_note(ch_a.render(), CONF_A, CONF_A + 4,
+ "volum în creștere = acumulare", GREEN, box=True), 10)
+svg_n = grow(vol_note(ch_n.render(), CONF_N, CONF_N,
+ "volum slab: nimeni mare nu cumpără", RED, box=False), 30)
HA, HN = vb_height(svg_a), vb_height(svg_n)
assert svg_a.count("\n\n") == 0 and svg_n.count("\n\n") == 0, "linie goală în panou"
@@ -168,9 +205,6 @@ TOTAL_H = PAD + TITLE_H + max(HA, HN) + FOOTER_H + PAD
OFF_A = (PAD, PAD + TITLE_H)
OFF_N = (PAD + PW + PANEL_GAP, PAD + TITLE_H)
-FOOTER = ("Surse: 13_strategia_3_byh.md:188-190 (doji la nivel); "
- "01_buy_doji_si_strategia_mici.md:227-235 (context invalid).")
-
wrapper = (
f''
@@ -178,8 +212,6 @@ wrapper = (
f'style="font-weight:bold">Setup A+ vs. setup mediocru (două panouri, exemple construite)'
f'{inner(svg_a)}'
f'{inner(svg_n)}'
- f'{FOOTER}'
f''
)
@@ -217,8 +249,7 @@ for frag, (ox, oy) in ((inner(svg_a), OFF_A), (inner(svg_n), OFF_N)):
all_txt.append((gx, gy, anchor, t))
# textele din wrapper (titlu + footer), în coordonate globale
for x, y, anchor, t in ((TOTAL_W / 2, 20.0, "middle",
- "Setup A+ vs. setup mediocru (două panouri, exemple construite)"),
- (TOTAL_W / 2, TOTAL_H - PAD, "middle", FOOTER)):
+ "Setup A+ vs. setup mediocru (două panouri, exemple construite)"),):
gl, gr = span(x, anchor, t)
assert -1 <= gl and gr <= TOTAL_W + 1, f"text din wrapper iese din pânză: {t!r}"
assert 0 <= y <= TOTAL_H, f"text din wrapper în afara înălțimii: {t!r}"
@@ -236,8 +267,10 @@ for i in range(len(all_txt)):
raise AssertionError(f"texte suprapuse: {si!r} / {sj!r}")
# etichetele cerute de spec există în desen
-for cheie in ("suport 42.00", "doji la nivel", "acumulare",
- "rezistență 78.50", "gap-up, volum slab",
+assert "Surse" not in wrapper and ".md:" not in wrapper, "trimiteri la surse în SVG"
+for cheie in ("suport 42.00", "doji chiar pe nivel", "vânzarea s-a oprit", "volum în creștere = acumulare",
+ "rezistență 78.50", "gap-up în rezistență", "cei întârziați",
+ "semnalul a mințit", "volum slab: nimeni mare nu cumpără",
"A+ — doji la suport, volum în acumulare",
"Mediocru — gap-up în rezistență, volum varză"):
assert cheie in wrapper, f"lipsește din desen: {cheie!r}"
diff --git a/gen/strategii/buy_doji_aplus.spec.txt b/gen/strategii/buy_doji_aplus.spec.txt
index 372b6ab..41b6119 100644
--- a/gen/strategii/buy_doji_aplus.spec.txt
+++ b/gen/strategii/buy_doji_aplus.spec.txt
@@ -29,11 +29,18 @@ STRUCTURA SERIEI
Panoul A+ (24 candele): downtrend de la 52 la ~42.60 (10 bare), apoi doji
cu low 41.95 / high 42.90 care „stă" pe suportul 42.00 (low-ul e la 0.1 %
sub nivel), apoi confirmare close 43.60 (> high-ul dojiului) cu volum peste
-media pe 10 bare. Etichete: „suport 42.00", „doji la nivel", „acumulare".
+media pe 10 bare. Adnotări: zonă verde peste doji („doji chiar pe nivel: vânzarea s-a oprit pe
+suport"), zonă verde peste barele de volum ale confirmării („volum în
+creștere = acumulare"), săgeată spre confirmare („confirmarea, pe volum
+mare: cumpărătorii mari intră"). Level „suport 42.00".
Panoul NU (24 candele): uptrend de la 70 la ~77, apoi un gap-up de la 76.80
la open 78.20, doji cu high 79.00 / low 78.10 chiar sub rezistența 78.50,
apoi confirmare close 79.30 pe volum sub media pe 10 bare, după care prețul
-cade înapoi sub 78.00. Etichete: „rezistență 78.50", „gap-up, volum slab".
+cade înapoi sub 78.00. Adnotări: zonă roșie peste gap-up + doji + confirmare, cu săgeata „gap-up în
+rezistență: aici cumpără cei întârziați"; săgeată spre volumul confirmării
+(„volum slab: nimeni mare nu cumpără"); săgeată spre căderea de după („cade
+înapoi sub 78.00: semnalul a mințit"). Level „rezistență 78.50".
+Fără linie de surse în SVG — trimiterile stau la finalul capitolului.
Nivelurile 42.00 / 78.50 / cifrele de preț sunt construite (materialul nu dă
prețuri pentru acest contrast); regulile și citatele de mai sus sunt din curs.
diff --git a/gen/strategii/buy_doji_aplus.svg b/gen/strategii/buy_doji_aplus.svg
index 12b7ffb..ddd20a8 100644
--- a/gen/strategii/buy_doji_aplus.svg
+++ b/gen/strategii/buy_doji_aplus.svg
@@ -1,5 +1,6 @@
-Setup A+ vs. setup mediocru (două panouri, exemple construite)
+Setup A+ vs. setup mediocru (două panouri, exemple construite)A+ — doji la suport, volum în acumulare
+ suport 42.00
@@ -50,113 +51,125 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- volum
- doji la nivel (low 41.95)
- acumulare
- acumulare: volum peste media ultimelor 10 bare.
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ volum
+
+
+ doji chiar pe nivel:
+ vânzarea s-a oprit pe suport
+
+
+ confirmarea, pe volum mare:
+ cumpărătorii mari intră
+volum în creștere = acumulareMediocru — gap-up în rezistență, volum varză
-
- rezistență 78.50
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- volum
- doji 78.50 / 78.10
- gap-up + doji în rezistență, confirmare pe volum slab.
-gap-up, volum slabSurse: 13_strategia_3_byh.md:188-190 (doji la nivel); 01_buy_doji_si_strategia_mici.md:227-235 (context invalid).
\ No newline at end of file
+
+
+ rezistență 78.50
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ volum
+
+
+ gap-up în rezistență:
+ aici cumpără cei întârziați
+
+
+ cade înapoi sub 78.00:
+ semnalul a mințit
+volum slab: nimeni mare nu cumpără
\ No newline at end of file
diff --git a/gen/strategii/buy_doji_directie.py b/gen/strategii/buy_doji_directie.py
index 46c1c19..4d3421c 100644
--- a/gen/strategii/buy_doji_directie.py
+++ b/gen/strategii/buy_doji_directie.py
@@ -12,7 +12,7 @@ Rulare: python3 gen/strategii/buy_doji_directie.py
from pathlib import Path
OUT = Path(__file__).with_suffix(".svg")
-W = 720
+W = 900
def trend(start, closes, wick=0.15):
@@ -55,10 +55,11 @@ for name, (o, h, l, c) in (("doji weekly", WK[7]), ("doji daily", DY[9])):
assert WK[8][3] > WK[7][1], "weekly: fără confirmare"
assert DY[10][3] < DY[9][2], "daily: fără confirmare Sell Doji"
ENTRY = DY[10][3]
-SL = max(b[1] for b in DY[7:11])
+# stopul: 5 cenți peste high-ul ultimelor 4 candele, ca la pasul 5 (oglindit)
+SL = round(max(b[1] for b in DY[7:11]) + 0.05, 2)
RISK = round(SL - ENTRY, 2)
TP1 = round(ENTRY - RISK, 2)
-assert eq(ENTRY, 83.60) and eq(SL, 84.80) and eq(RISK, 1.20) and eq(TP1, 82.40)
+assert eq(ENTRY, 83.60) and eq(SL, 84.85) and eq(RISK, 1.25) and eq(TP1, 82.35)
LOW_AFTER = min(b[2] for b in DY[11:])
assert eq(LOW_AFTER, 83.00) and LOW_AFTER > TP1, "TP1 atins"
HIT = next(i for i in range(11, 17) if DY[i][1] >= SL - 1e-9)
@@ -89,6 +90,16 @@ def rect(x, y, w, h, fill, opacity, stroke="none"):
f'fill-opacity="{opacity}" stroke="{stroke}" rx="3"/>')
+def arrow(x1, y1, x2, y2, color):
+ """Săgeată de la (x1,y1) la vârful (x2,y2): linie + cap triunghi."""
+ d = max(((x2 - x1) ** 2 + (y2 - y1) ** 2) ** 0.5, 1e-9)
+ ux, uy = (x2 - x1) / d, (y2 - y1) / d
+ bx, by = x2 - 9 * ux, y2 - 9 * uy
+ line(x1, y1, bx, by, color, 1.8)
+ out.append(f'')
+
+
def candles(bars, x0, gap, yf):
for i, (o, h, l, c) in enumerate(bars):
cx = x0 + i * gap
@@ -112,11 +123,12 @@ rect(xw(7) - 8, yw(WK[8][1]) - 4, WGAP + 16, yw(WK[7][2]) - yw(WK[8][1]) + 8,
GREEN, 0.12, GREEN)
candles(WK, WX0, WGAP, yw)
text(xw(7), yw(WK[7][2]) + 16, "doji weekly", fill=ORANGE, anchor="middle", bold=True)
-text(xw(9) + 14, yw(WK[8][3]) + 4, "Buy Doji weekly: direcția = long", fill=GREEN, bold=True)
-# insigna de direcție
-rect(470, 66, 230, 58, GREEN, 0.10, GREEN)
-text(585, 90, "WEEKLY: LONG ↑", fill=GREEN, anchor="middle", bold=True, size=16)
-text(585, 110, "confirmare 81.30 > high doji 79.80", fill=GREEN, anchor="middle", size=11)
+# săgeata weekly: ce + de ce
+arrow(300, yw(WK[8][3]) - 6, xw(8) + 12, yw(WK[8][3]) - 6, GREEN)
+text(306, yw(WK[8][3]) - 10, "weekly: Buy Doji confirmat (close 81.30, peste high-ul doji-ului 79.80)",
+ fill=GREEN, bold=True)
+text(306, yw(WK[8][3]) + 8, "deci direcția permisă e LONG: pe daily cauți doar Buy Doji",
+ fill=GREEN)
w_bottom = yw(min(b[2] for b in WK))
# ---- panoul de jos: daily -----------------------------------------------------
@@ -136,42 +148,33 @@ rect(xd(9) - 8, yd(DY[9][1]) - 3, DGAP + 16, yd(DY[10][2]) - yd(DY[9][1]) + 6, O
line(LX1, y_sl, LX2, y_sl, RED, 1.5, "6 3")
line(xd(10) - 8, y_e, LX2, y_e, ORANGE, 1.5, "6 3")
line(xd(10) - 8, y_tp, LX2, y_tp, GRAY, 1.5, "2 3")
-text(COL, y_sl + 4, "SL 84.80 = high 4 candele", fill=RED, size=11)
+text(COL, y_sl + 4, "stop 84.85 = 5 cenți peste high-ul de 4 candele", fill=RED, size=11)
text(COL, y_e + 4, "intrare short 83.60", fill=ORANGE, size=11)
-text(COL, y_tp + 4, "TP1 82.40 — neatins", fill=GRAY, size=11)
+text(COL, y_tp + 4, "ținta 82.35 — neatinsă (a scăzut o zi, la 83.00)",
+ fill=GRAY, size=11)
candles(DY, DX0, DGAP, yd)
-# paranteza „short de 1-2 zile" peste barele 10-11
-xa, xb, yb = xd(10) - 6, xd(11) + 6, y_sl - 12
-out.append(f'')
-text(xb, yb - 6, "short de 1-2 zile: permis", fill=GRAY, anchor="end", bold=True)
-
-# SL lovit la bara 14
+# SL lovit la bara 14: săgeată din dreapta sus
cx14 = xd(HIT)
out.append(f'')
-y_lbl = DT - 22
-line(cx14, y_sl - 8, cx14, y_lbl + 4, RED, 1.2)
-text(cx14 - 6, y_lbl, "SL lovit: poziția short contra weekly", fill=RED, anchor="middle", bold=True)
+y_hit = DT - 40
+arrow(560, y_hit + 6, cx14 + 9, y_sl - 9, RED)
+text(568, y_hit, "aici te-a scos: stopul 84.85, lovit a patra zi", fill=RED, bold=True)
+text(568, y_hit + 18, "banii mari de pe weekly cumpără mai departe", fill=RED)
-# Sell Doji: etichetă sub panou, cu leader spre confirmare
+# intrarea short: săgeată de jos-dreapta spre close-ul confirmării
y_bot = yd(min(b[2] for b in DY))
-y_sd = y_bot + 22
-line(xd(10), yd(DY[10][2]) + 4, xd(10), y_sd - 12, ORANGE, 1.2)
-text(xd(10) - 20, y_sd, "Sell Doji daily: tehnic valid (close 83.60 < low doji 83.95)",
+x_e = xd(12) + 6
+arrow(x_e, y_tp + 30, xd(10) + 7, y_e + 4, ORANGE)
+text(x_e + 6, y_tp + 28, "aici ai intrat SHORT, la 83.60: Sell Doji corect ca formă",
fill=ORANGE, bold=True)
+text(x_e + 6, y_tp + 46, "(close sub low-ul doji-ului 83.95) — dar contra weekly-ului",
+ fill=ORANGE)
-# insigna de contrast
-rect(470, DT - 36, 230, 58, RED, 0.08, RED)
-text(585, DT - 12, "short daily = CONTRA ✗", fill=RED, anchor="middle", bold=True, size=16)
-text(585, DT + 8, "weekly zice long; daily urcă mai departe", fill=RED, anchor="middle", size=11)
-
-y0 = y_sd + 32
-text(20, y0, "Short scurt înăuntrul structurii weekly: da. Poziție short contra ei: nu.",
- fill=INK, bold=True)
-text(20, y0 + 18, "Seria e construită: un singur caz ilustrează regula, nu o dovedește.", fill="#555")
-H = y0 + 30
+y0 = max(y_bot, y_tp + 46) + 34
+text(20, y0, "Seria e construită: un singur caz ilustrează regula, nu o dovedește.", fill="#555")
+H = y0 + 24
svg = (f'\n ' + "\n ".join(out) + "\n")
diff --git a/gen/strategii/buy_doji_directie.spec.txt b/gen/strategii/buy_doji_directie.spec.txt
index 4d3e344..a518fa8 100644
--- a/gen/strategii/buy_doji_directie.spec.txt
+++ b/gen/strategii/buy_doji_directie.spec.txt
@@ -38,19 +38,32 @@ open[0] = 81.30, fitile 0.15 peste/sub corp (uptrend de 9 candele).
NIVELURI (panoul daily)
-----------------------
-intrare short 83.60 (portocaliu), SL 84.80 roșu („SL 84.80 = high 4 candele"),
-TP1 82.40 punctat („TP1 82.40 — neatins").
-Paranteză orizontală peste barele 10-11: „short de 1-2 zile: permis" (gri).
-Mark(14, "SL lovit: poziția short contra weekly", roșu, above).
+intrare short 83.60 (portocaliu), stop 84.85 roșu („stop 84.85 = 5 cenți peste
+high-ul de 4 candele"), ținta 82.35 punctată („neatinsă — a scăzut o zi, la 83.00").
+Stopul e 5 cenți peste extremă, ca oglinda pasului 5 din capitol (51.60 -> 51.55).
+Paranteza „short de 1-2 zile: permis" a fost scoasă (nuanță de nivel 3, încărca
+pasul mecanic), la fel și insignele de colț — locul lor îl iau săgețile.
+
+SĂGEȚI (format tutorial v2: ce ȘI de ce, 1 pe weekly + 2 pe daily)
+- weekly, spre confirmare: „weekly: Buy Doji confirmat (close 81.30, peste
+ high-ul doji-ului 79.80)" / „deci direcția permisă e LONG: pe daily cauți
+ doar Buy Doji".
+- daily, spre close-ul confirmării: „aici ai intrat SHORT, la 83.60: Sell Doji
+ corect ca formă (close sub low-ul doji-ului 83.95) — dar contra weekly-ului".
+- daily, spre bara 14 pe linia stopului: „aici te-a scos: stopul 84.85, lovit a
+ patra zi" / „banii mari de pe weekly cumpără mai departe".
+Zone: verde peste doji-ul + confirmarea weekly, portocalie peste Sell Doji-ul
+daily (portocaliu, nu roșu, ca să nu se confunde cu linia stopului).
+Lățime 900 (textele săgeților stau în dreapta candelelor).
ASSERT-URI
----------
1. open[i] == close[i-1] pe ambele panouri; h/l cuprind o/c.
2. Doji weekly și doji daily: abs(o-c) <= 0.10*(h-l).
3. weekly close[8] > high[7]; daily close[10] < low[9].
-4. SL == max(h for bare daily 7..10) == 84.80; risc == 1.20; TP1 == 82.40.
-5. min(l for bare daily 11..16) == 83.00 > 82.40 (TP1 neatins).
-6. primul i>10 cu high >= 84.80 este 14.
+4. SL == max(h for bare daily 7..10) + 0.05 == 84.85; risc == 1.25; TP1 == 82.35.
+5. min(l for bare daily 11..16) == 83.00 > 82.35 (TP1 neatins).
+6. primul i>10 cu high >= 84.85 este 14.
SURSA
-----
diff --git a/gen/strategii/buy_doji_directie.svg b/gen/strategii/buy_doji_directie.svg
index 14a4e18..69605a1 100644
--- a/gen/strategii/buy_doji_directie.svg
+++ b/gen/strategii/buy_doji_directie.svg
@@ -1,4 +1,4 @@
-
+NU: Sell Doji daily contra unui Buy Doji weekly — exemplu construitweekly: direcția
@@ -23,19 +23,19 @@
doji weekly
- Buy Doji weekly: direcția = long
-
- WEEKLY: LONG ↑
- confirmare 81.30 > high doji 79.80
-
+
+
+ weekly: Buy Doji confirmat (close 81.30, peste high-ul doji-ului 79.80)
+ deci direcția permisă e LONG: pe daily cauți doar Buy Doji
+ daily: săptămânile de după confirmare (open 81.30 = close-ul ei)
-
+
-
- SL 84.80 = high 4 candele
+
+ stop 84.85 = 5 cenți peste high-ul de 4 candeleintrare short 83.60
- TP1 82.40 — neatins
+ ținta 82.35 — neatinsă (a scăzut o zi, la 83.00)
@@ -70,16 +70,14 @@
-
- short de 1-2 zile: permis
-
-
- SL lovit: poziția short contra weekly
-
- Sell Doji daily: tehnic valid (close 83.60 < low doji 83.95)
-
- short daily = CONTRA ✗
- weekly zice long; daily urcă mai departe
- Short scurt înăuntrul structurii weekly: da. Poziție short contra ei: nu.
- Seria e construită: un singur caz ilustrează regula, nu o dovedește.
+
+
+
+ aici te-a scos: stopul 84.85, lovit a patra zi
+ banii mari de pe weekly cumpără mai departe
+
+
+ aici ai intrat SHORT, la 83.60: Sell Doji corect ca formă
+ (close sub low-ul doji-ului 83.95) — dar contra weekly-ului
+ Seria e construită: un singur caz ilustrează regula, nu o dovedește.
\ No newline at end of file
diff --git a/gen/strategii/buy_doji_doji_forma.py b/gen/strategii/buy_doji_doji_forma.py
index 255b7bf..7e3cf33 100644
--- a/gen/strategii/buy_doji_doji_forma.py
+++ b/gen/strategii/buy_doji_doji_forma.py
@@ -1,30 +1,31 @@
#!/usr/bin/env python3
-"""Ce e doji și ce nu — patru candele cu același high 50.10 și low 49.20.
+"""Ce e un doji și ce nu — patru candele cu același high 50.10 și low 49.20.
Citește `gen/strategii/buy_doji_doji_forma.spec.txt` și scrie
-`gen/strategii/buy_doji_doji_forma.svg`. Definiție de o candelă (stilul
-vechi, candele izolate): diferă doar corpul, deci testul „corp ≤ 10 % din
-range, fitil în ambele părți" se vede direct pe formă.
+`gen/strategii/buy_doji_doji_forma.svg`. E prima imagine din capitol: cine o
+vede nu știe ce e un doji, deci candela A primește săgeți pe corp și pe
+fiecare fitil, cu explicația de ce arată așa, iar C și D sunt contraexemple.
+Zonă verde peste A și B („doji"), zonă roșie peste C și D („nu e doji").
Desenat direct, nu cu `Chart`: `tag`/`note` din svg_candles pun etichetele
pe un singur rând sau în blocul de jos, iar aici fiecare etichetă trebuie să
-stea lipită sub candela ei, pe 2-3 rânduri.
+stea lipită sub candela ei, pe 2-3 rânduri, plus săgeți cu text pe lateral.
Rulare: python3 gen/strategii/buy_doji_doji_forma.py
"""
from pathlib import Path
OUT = Path(__file__).with_suffix(".svg")
-GREEN, RED, GREY = "#188a3e", "#c00", "#555"
+GREEN, RED, GREY, BLUE = "#188a3e", "#c00", "#555", "#1a5fb4"
H, L = 50.10, 49.20
RNG = H - L
# (nume, o, h, l, c, verdict, rânduri de etichetă sub candelă)
CANDLES = [
- ("A", 49.60, 50.10, 49.20, 49.55, "DA", ["DA: corp 0.05", "= 5.6 % din range"]),
- ("B", 49.62, 50.10, 49.20, 49.53, "DA", ["DA, la limită:", "corp 0.09 = 10 %", "(parametrul 10)"]),
+ ("A", 49.60, 50.10, 49.20, 49.55, "DA", ["DA: corp 0.05", "= 5,6 % din înălțime"]),
+ ("B", 49.62, 50.10, 49.20, 49.53, "DA", ["DA, la limită:", "corp 0.09 = 10 %"]),
("C", 49.80, 50.10, 49.20, 49.50, "NU", ["NU: corp 0.30", "= 33 %"]),
- ("D", 50.05, 50.10, 49.20, 50.10, "NU", ["NU: corp mic (5.6 %),", "dar fără fitil sus"]),
+ ("D", 50.05, 50.10, 49.20, 50.10, "NU", ["NU: corp mic (5,6 %),", "dar fără fitil sus"]),
]
# --- assert-urile din spec --------------------------------------------------
@@ -39,8 +40,8 @@ _, oD, hD, _, cD, *_ = CANDLES[3]
assert max(oD, cD) == hD
# --- geometrie ---------------------------------------------------------------
-SCALE, TOP, W = 260, 72, 740
-XS = [150, 300, 450, 600] # centrele candelelor
+SCALE, TOP, W = 260, 210, 1000
+XS = [350, 490, 690, 860] # centrele candelelor: A, B (doji) | C, D (nu)
BODY_W = 26
@@ -61,16 +62,45 @@ def text(x, yy, s, color=GREY, anchor="start", bold=False, size=12):
t(f'{s}')
-text(24, 22, "Ce e doji și ce nu — aceeași bară 50.10 / 49.20", "#333", bold=True)
+def zone(x0, x1, y0, y1, label, color):
+ t(f'')
+ text(x0 + 6, y0 - 7, label, color, bold=True)
+
+
+def arrow(x1, y1, x2, y2, color, lines, anchor="start"):
+ """Săgeată de la (x1,y1) [coada, lângă text] la (x2,y2) [vârf, pe țintă]."""
+ d = max(((x2 - x1) ** 2 + (y2 - y1) ** 2) ** 0.5, 1e-9)
+ ux, uy = (x2 - x1) / d, (y2 - y1) / d
+ bx, by = x2 - 8 * ux, y2 - 8 * uy
+ t(f'')
+ t(f'')
+ dx = -6 if anchor == "end" else (0 if anchor == "middle" else 6)
+ # la săgeata verticală textul stă deasupra cozii, altfel intră peste ea
+ y0 = (y1 - 10 - 14 * (len(lines) - 1) if anchor == "middle"
+ else y1 + 4 - 14 * (len(lines) - 1) / 2)
+ for k, s in enumerate(lines):
+ text(x1 + dx, y0 + 14 * k, s, color, anchor, bold=(k == 0))
+
+
+text(24, 30, "Ce e un doji și ce nu — aceeași bară, 50.10 sus și 49.20 jos", "#333",
+ bold=True, size=14)
+
+# zonele: A+B sunt doji, C+D nu
+label_y0 = y(L) + 28
+z_bot = label_y0 + 16 * 2 - 4
+zone(XS[0] - 58, XS[1] + 62, y(H) - 30, z_bot, "doji", GREEN)
+zone(XS[2] - 62, XS[3] + 74, y(H) - 30, z_bot, "nu e doji", RED)
# high și low comune: linii subțiri pe toată lățimea
for p in (H, L):
- t(f'')
-text(XS[0] - 36, y(H) + 4, "high 50.10", anchor="end")
-text(XS[0] - 36, y(L) + 4, "low 49.20", anchor="end")
+text(24, y(H) - 6, "cât de sus a urcat prețul: 50.10")
+text(24, y(L) + 14, "cât de jos a coborât: 49.20")
-label_y0 = y(L) + 24
for (n, o, h, l, c, verdict, lines), x in zip(CANDLES, XS):
color = GREEN if c >= o else RED
vcol = GREEN if verdict == "DA" else RED
@@ -78,25 +108,34 @@ for (n, o, h, l, c, verdict, lines), x in zip(CANDLES, XS):
top, bot = y(max(o, c)), y(min(o, c))
t(f'')
- text(x, y(H) - 14, verdict, vcol, "middle", bold=True, size=18)
for k, s in enumerate(lines):
text(x, label_y0 + 16 * k, s, vcol, "middle", bold=(k == 0))
-# prețurile pe A: open la stânga (mai sus), close la dreapta (mai jos)
-x = XS[0]
-text(x - 36, y(oA) + 2, "open 49.60", anchor="end")
-t(f'')
-text(x + 36, y(cA) + 8, "close 49.55")
-t(f'')
+# --- săgețile pe A: corp, fitil sus, fitil jos -------------------------------
+xA = XS[0]
+arrow(xA - 60, y(49.92), xA - 3, y(49.85), BLUE, anchor="end",
+ lines=["fitilul de sus", "cumpărătorii au împins, dar n-au ținut"])
+arrow(xA - 60, y(49.575), xA - 15, y(49.575), BLUE, anchor="end",
+ lines=["corpul: aproape o linie",
+ "open și close aproape egale — nimeni",
+ "n-a câștigat ziua"])
+arrow(xA - 60, y(49.30), xA - 3, y(49.375), BLUE, anchor="end",
+ lines=["fitilul de jos", "vânzătorii au împins, dar n-au ținut"])
+# prețurile de open/close, în dreapta candelei A
+text(xA + 20, y(oA) - 2, "open 49.60")
+text(xA + 20, y(cA) + 10, "close 49.55")
-# D: close = high, deci fitilul de sus lipsește
-x = XS[3]
-text(x + 20, y(H) + 12, "← fără fitil sus", RED)
+# --- săgețile pe contraexemple ----------------------------------------------
+xC, xD = XS[2], XS[3]
+arrow(xC + 34, y(H) - 112, xC + 14, y(49.78), RED,
+ lines=["corp prea gros (33 %)", "aici una din tabere a câștigat —", "nu mai e indecizie"])
+arrow(xD, y(H) - 34, xD, y(H) - 4, RED, anchor="middle",
+ lines=["fără fitil sus", "regula cere fitil în ambele părți"])
-cap_y = label_y0 + 16 * 3 + 14
-text(24, cap_y, f"Test doji: corp ≤ 10 % din range ({RNG:.2f}) și fitil în ambele părți. "
- "Scară: 0.10 = 26 px.")
-height = cap_y + 12
+cap_y = z_bot + 30
+text(24, cap_y, f"Doji = corpul e cel mult 10 % din înălțimea totală a barei "
+ f"(aici înălțimea e {RNG:.2f}) și are fitil și sus, și jos.")
+height = cap_y + 16
svg = (f'\n '
diff --git a/gen/strategii/buy_doji_doji_forma.spec.txt b/gen/strategii/buy_doji_doji_forma.spec.txt
index 8f2c2ec..e676526 100644
--- a/gen/strategii/buy_doji_doji_forma.spec.txt
+++ b/gen/strategii/buy_doji_doji_forma.spec.txt
@@ -1,10 +1,11 @@
SPEC DIAGRAMĂ — buy_doji_doji_forma
===================================
tip: DEFINIȚIE de o candelă (stil vechi permis de STYLE.md: 4 candele
- izolate, gap 92, body_w 26), fără trend, fără volum.
-destinație: summaries/17_STRATEGII/01_buy_doji.md, Pasul 3
+ izolate, body_w 26), fără trend, fără volum. Adnotată „ca la
+ proști": 2 zone dreptunghiulare + 5 săgeți cu text (CE + DE CE).
+destinație: summaries/17_STRATEGII/01_buy_doji.md, nivelul 1 („Ce e un doji")
cheie markdown:
-titlu SVG: „Ce e doji și ce nu — aceeași bară 50.10 / 49.20"
+titlu SVG: „Ce e un doji și ce nu — aceeași bară, 50.10 sus și 49.20 jos"
CE TREBUIE SĂ DEMONSTREZE
-------------------------
@@ -17,9 +18,25 @@ A (49.60, 50.10, 49.20, 49.55) „DA: corp 0.05 = 5.6 % din range"
B (49.62, 50.10, 49.20, 49.53) „DA, la limită: corp 0.09 = 10 % (parametrul 10)"
C (49.80, 50.10, 49.20, 49.50) „NU: corp 0.30 = 33 %"
D (50.05, 50.10, 49.20, 50.10) „NU: corp mic (5.6 %), dar fără fitil sus"
-Deasupra fiecărei candele: „DA" verde / „NU" roșu.
-Etichete de preț pe A: „high 50.10", „low 49.20", „open 49.60", „close 49.55"
-(open deasupra lui close, pentru că open > close; verifică ordinea pe axă).
+Verdictul e în eticheta de sub candelă („DA: …" verde / „NU: …" roșu) și în
+eticheta zonei; nu se mai scrie DA/NU mare deasupra barei (se bătea cap în cap
+cu textul săgeților).
+Etichete de preț pe A: „cât de sus a urcat prețul: 50.10", „cât de jos a
+coborât: 49.20", „open 49.60", „close 49.55" (open deasupra lui close, pentru
+că open > close; verifică ordinea pe axă).
+
+ZONE (dreptunghiuri de evidențiere, fill-opacity 0.10)
+------------------------------------------------------
+verde peste A și B, etichetă „doji"; roșie peste C și D, etichetă „nu e doji".
+
+SĂGEȚI (text pe 2-3 rânduri: CE, apoi DE CE)
+--------------------------------------------
+pe A, din stânga: spre fitilul de sus („fitilul de sus / cumpărătorii au
+împins, dar n-au ținut"); spre corp („corpul: aproape o linie / open și close
+aproape egale — nimeni n-a câștigat ziua"); spre fitilul de jos („fitilul de
+jos / vânzătorii au împins, dar n-au ținut").
+pe C, de sus: „corp prea gros (33 %) / aici una din tabere a câștigat — nu mai
+e indecizie". pe D, de sus: „fără fitil sus / regula cere fitil în ambele părți".
ASSERT-URI
----------
diff --git a/gen/strategii/buy_doji_doji_forma.svg b/gen/strategii/buy_doji_doji_forma.svg
index 40cabe9..b88848d 100644
--- a/gen/strategii/buy_doji_doji_forma.svg
+++ b/gen/strategii/buy_doji_doji_forma.svg
@@ -1,34 +1,52 @@
-
- Ce e doji și ce nu — aceeași bară 50.10 / 49.20
-
-
- high 50.10
- low 49.20
-
-
- DA
- DA: corp 0.05
- = 5.6 % din range
-
-
- DA
- DA, la limită:
- corp 0.09 = 10 %
- (parametrul 10)
-
-
- NU
- NU: corp 0.30
- = 33 %
-
-
- NU
- NU: corp mic (5.6 %),
- dar fără fitil sus
- open 49.60
-
- close 49.55
-
- ← fără fitil sus
- Test doji: corp ≤ 10 % din range (0.90) și fitil în ambele părți. Scară: 0.10 = 26 px.
+
+ Ce e un doji și ce nu — aceeași bară, 50.10 sus și 49.20 jos
+
+ doji
+
+ nu e doji
+
+
+ cât de sus a urcat prețul: 50.10
+ cât de jos a coborât: 49.20
+
+
+ DA: corp 0.05
+ = 5,6 % din înălțime
+
+
+ DA, la limită:
+ corp 0.09 = 10 %
+
+
+ NU: corp 0.30
+ = 33 %
+
+
+ NU: corp mic (5,6 %),
+ dar fără fitil sus
+
+
+ fitilul de sus
+ cumpărătorii au împins, dar n-au ținut
+
+
+ corpul: aproape o linie
+ open și close aproape egale — nimeni
+ n-a câștigat ziua
+
+
+ fitilul de jos
+ vânzătorii au împins, dar n-au ținut
+ open 49.60
+ close 49.55
+
+
+ corp prea gros (33 %)
+ aici una din tabere a câștigat —
+ nu mai e indecizie
+
+
+ fără fitil sus
+ regula cere fitil în ambele părți
+ Doji = corpul e cel mult 10 % din înălțimea totală a barei (aici înălțimea e 0.90) și are fitil și sus, și jos.
diff --git a/gen/strategii/buy_doji_fereastra.py b/gen/strategii/buy_doji_fereastra.py
index 135b6bd..f78dd10 100644
--- a/gen/strategii/buy_doji_fereastra.py
+++ b/gen/strategii/buy_doji_fereastra.py
@@ -6,10 +6,14 @@ Citește `gen/strategii/buy_doji_fereastra.spec.txt` și scrie
cu aceeași serie până la doji: sus confirmarea vine la bara +2 (în fereastră),
jos niciuna din +1..+3 nu confirmă, iar a 4-a închide sub low-ul dojiului.
-Toate cifrele din desen vin din specificație: doji h 50.10 / l 49.20,
-closes-urile DA/NU, pragul 50.10, low-ul 49.20. Seriile sunt construite
-(open = close-ul barei anterioare, mai puțin open-ul primei bare, start-ul),
-ca nivelurile afirmate să iasă exact din serie.
+Adnotat „ca la proști": zonă dreptunghiulară peste cele 3 candele ale
+ferestrei pe ambele panouri, plus săgeți cu text care spun CE și DE CE
+(„aici NU intri — s-a închis sub high-ul dojiului", „AICI INTRI, la close").
+
+Toate cifrele din desen vin din specificație: doji h 50.10 / l 49.25,
+closes-urile DA/NU, pragul 50.10. Seriile sunt construite (open = close-ul
+barei anterioare, mai puțin open-ul primei bare, start-ul), ca nivelurile
+afirmate să iasă exact din serie.
Rulare: python3 gen/strategii/buy_doji_fereastra.py
"""
@@ -18,21 +22,23 @@ import sys
from pathlib import Path
sys.path.insert(0, "/workspace/atm-curs-dl")
-from svg_candles import Chart, Level, Mark, from_closes # noqa: E402
+from svg_candles import Arrow, Chart, Mark, from_closes # noqa: E402
OUT = Path(__file__).with_suffix(".svg")
-W = 460
-VGAP = 34
-SCALE = 45.0 # px / unitate, aceeași pe ambele panouri (comparație)
+W = 640
+VGAP = 0
+SCALE = 60.0 # px / unitate, aceeași pe ambele panouri (comparație)
+LEFT, GAP = 64, 40
+ORANGE, RED, GREEN, BLUE, GREY = "#b06000", "#c00", "#188a3e", "#1a5fb4", "#555"
# --- seriile din specificație ----------------------------------------------
CLOSES_DA = [52.20, 51.40, 50.60, 49.65,
- 49.60, # doji, h 50.10 / l 49.20
+ 49.60, # doji, h 50.10 / l 49.25
49.90, # +1 — sub 50.10
50.40, # +2 — peste 50.10 => confirmă
50.80]
CLOSES_NU = [52.20, 51.40, 50.60, 49.65,
- 49.60, # doji, h 50.10 / l 49.20
+ 49.60, # doji, h 50.10 / l 49.25
49.95, # +1
50.05, # +2
49.85, # +3
@@ -40,13 +46,13 @@ CLOSES_NU = [52.20, 51.40, 50.60, 49.65,
assert len(CLOSES_DA) == 8 and len(CLOSES_NU) == 9
assert CLOSES_DA[:5] == CLOSES_NU[:5]
-HIGH_DOJI, LOW_DOJI = 50.10, 49.20
+HIGH_DOJI, LOW_DOJI = 50.10, 49.25
-# wick-uri: (up, down). Doji-ul (index 4) primește mecioni ca să iasă exact
-# h=50.10 / l=49.20; restul au mecioni mici, egali.
+# wick-uri: (up, down). Doji-ul (index 4) primește fitile ca să iasă exact
+# h=50.10 / l=49.25; restul au fitile mici, egale.
wicks_da = [(0.15, 0.15)] * len(CLOSES_DA)
wicks_nu = [(0.15, 0.15)] * len(CLOSES_NU)
-wicks_da[4] = wicks_nu[4] = (0.45, 0.40)
+wicks_da[4] = wicks_nu[4] = (0.45, 0.35)
START = CLOSES_DA[0] + 0.15 # prima bară roșie, open > close
cds_da = from_closes(CLOSES_DA, wicks=wicks_da, start=START)
@@ -72,7 +78,7 @@ assert abs(cds_da[5].c - 49.90) < 1e-9 and cds_da[5].c <= HIGH_DOJI, \
assert abs(cds_da[6].c - entry) < 1e-9 and abs(entry - 50.40) < 1e-9, \
"intrarea nu e close-ul de 50.40 al barei +2"
-# 2. NU: niciun close din +1..+3 nu depășește 50.10; a 4-a închide sub 49.20
+# 2. NU: niciun close din +1..+3 nu depășește 50.10; a 4-a închide sub 49.25
assert all(cds_nu[i].c <= HIGH_DOJI for i in (5, 6, 7)), \
"un close din +1..+3 ar confirma, fereastra n-ar fi expirat"
assert cds_nu[8].c < LOW_DOJI, f"close[+4]={cds_nu[8].c} nu e sub {LOW_DOJI}"
@@ -87,54 +93,102 @@ ser_nu = [(round(c.o, 9), round(c.h, 9), round(c.l, 9), round(c.c, 9)) for c in
assert ser_da == ser_nu, "panourile nu au aceeași serie până la doji"
# --- desen: două Chart-uri, lipite vertical într-un singur SVG -------------
+cds_nu[7].note = "+1: 49.95 · +2: 50.05 · +3: 49.85 — toate sub 50.10"
+
+ch_da = Chart(
+ candles=cds_da,
+ marks=[Mark(4, "doji", ORANGE, "above")],
+ arrows=[
+ Arrow(5, "aici NU intri: s-a închis sub 50.10\nsemnalul cere o ÎNCHIDERE peste",
+ where="below", color=RED),
+ Arrow(6, "AICI INTRI, la close 50.40\nprima închidere peste\nhigh-ul dojiului",
+ price=entry, where="right", color=GREEN, dx=20, dy=46),
+ ],
+ scale=SCALE, gap=GAP, body_w=13, left=LEFT, width=W,
+ title="DA — confirmare la +2, în fereastră",
+)
+
+ch_nu = Chart(
+ candles=cds_nu,
+ marks=[Mark(4, "doji", ORANGE, "above"), Mark(8, "a 4-a", RED, "above")],
+ arrows=[
+ Arrow(7, "a treia s-a închis tot sub 50.10\nfereastra s-a terminat: ștergi setup-ul",
+ where="above", color=RED, dx=135),
+ Arrow(8, "a închis sub low-ul dojiului\nsetup mort — nu mai intri",
+ where="below", color=RED),
+ ],
+ scale=SCALE, gap=GAP, body_w=13, left=LEFT, width=W,
+ title="NU — nicio confirmare în 3 candele",
+)
+
+
def _panel(ch: Chart):
- svg = ch.render()
- m = re.search(r'viewBox="0 0 (\d+) ([\d.]+)"', svg)
+ """`_render()`, nu `render()`: al doilea restaurează top/left după desen,
+ iar liniile și zonele desenate de mână ar pica deplasate față de body."""
+ svg = ch._render()
+ m = re.search(r'viewBox="0 0 ([\d.]+) ([\d.]+)"', svg)
+ assert m, "viewBox lipsă"
+ assert float(m.group(1)) == W, f"textul unei săgeți iese din {W}px"
body = svg[svg.index(">") + 1:svg.rindex("")]
return body, float(m.group(2))
-# DA — confirmarea vine la +2
-cds_da[5].note = "+1: close 49.90 — sub 50.10, nu confirmă"
-cds_da[6].note = "+2: close 50.40 — peste 50.10, confirmă (intrare la close)"
-ch_da = Chart(
- candles=cds_da,
- levels=[Level(HIGH_DOJI, "high doji 50.10", "#b06000", label_at="left"),
- Level(LOW_DOJI, "low doji 49.20", "#c00", label_at="left")],
- marks=[Mark(4, "doji", "#b06000", "above"),
- Mark(6, f"confirmare +2 — intrare {entry:.2f}", "#188a3e", "above")],
- scale=SCALE, gap=40, body_w=13, left=44, width=W,
- title="DA — confirmare la +2, în fereastră",
-)
+def level(ch, price, text, color, below=False, at_left=True):
+ """Linie punctată pe toată lățimea + etichetă, desenată de mână: eticheta
+ din `Level` stă lipită de linie și cade peste candelele care o traversează."""
+ y = ch._y(price)
+ out = [f'']
+ for k, s in enumerate(text):
+ ty = (y + 15 + 14 * k) if below else (y - 6 - 14 * (len(text) - 1 - k))
+ tx, anchor = (30, "start") if at_left else (W - 24, "end")
+ out.append(f'{s}')
+ return out
+
+
+def zone(ch, i0, i1, label, color, lift=0.0):
+ """Dreptunghi de evidențiere peste candelele i0..i1, cu eticheta deasupra.
+
+ `lift` ridică marginea de sus (și deci eticheta), ca să nu cadă peste
+ săgețile care intră în zonă de sus."""
+ cds = ch.candles[i0:i1 + 1]
+ x0, x1 = ch._cx(i0) - 16, ch._cx(i1) + 16
+ y0 = ch._y(max(c.h for c in cds)) - 8 - lift
+ y1 = ch._y(min(c.l for c in cds)) + 8
+ return [f'',
+ f'{label}']
-# NU — nicio confirmare în 3 candele, apoi setup mort
-cds_nu[5].note = "+1: 49.95; +2: 50.05; +3: 49.85 — toate sub 50.10"
-cds_nu[8].note = "a 4-a: close 49.15 — sub low-ul dojiului 49.20, setup mort"
-ch_nu = Chart(
- candles=cds_nu,
- levels=[Level(HIGH_DOJI, "high doji 50.10", "#b06000", label_at="left"),
- Level(LOW_DOJI, "low doji 49.20", "#c00", label_at="left")],
- marks=[Mark(4, "doji", "#b06000", "above"),
- Mark(8, "a 4-a", "#c00", "above")],
- scale=SCALE, gap=40, body_w=13, left=44, width=W,
- title="NU — nicio confirmare în 3 candele",
-)
body_da, h_da = _panel(ch_da)
body_nu, h_nu = _panel(ch_nu)
+extra_da = (zone(ch_da, 5, 7, "fereastra: 3 candele", BLUE)
+ + level(ch_da, HIGH_DOJI,
+ ["high-ul dojiului 50.10", "trebuie ÎNCHIS peste,", "nu doar atins"],
+ ORANGE, below=True)
+ + level(ch_da, LOW_DOJI, ["low-ul dojiului 49.25"], RED, below=True))
+extra_nu = (zone(ch_nu, 5, 7, "fereastra: 3 candele", BLUE)
+ + level(ch_nu, HIGH_DOJI,
+ ["high-ul dojiului 50.10", "trebuie ÎNCHIS peste,", "nu doar atins"],
+ ORANGE, below=True)
+ + level(ch_nu, LOW_DOJI, ["low-ul dojiului 49.25"], RED, below=True))
+
cap1 = "Serii construite: open = close-ul barei anterioare."
-cap2 = "Doji: h 50.10 / l 49.20. Fereastra = maximum 3 candele după doji."
+cap2 = "Doji: h 50.10 / l 49.25. Fereastra = maximum 3 candele după doji."
cap_top = h_da + VGAP + h_nu + 20
H = cap_top + 22
parts = [
f'',
- f'{body_da}',
- f'{body_nu}',
- f'{cap1}',
- f'{cap2}',
+ # zonele și liniile întâi, ca să stea în spatele candelelor
+ f'{"".join(extra_da)}{body_da}',
+ f'{"".join(extra_nu)}{body_nu}',
+ f'{cap1}',
+ f'{cap2}',
"",
]
svg = "\n ".join(parts)
@@ -162,7 +216,8 @@ def _texts(body: str, y_off: float, tag: str):
return out
-labels = _texts(body_da, 0.0, "DA") + _texts(body_nu, h_da + VGAP, "NU")
+labels = (_texts(body_da + "".join(extra_da), 0.0, "DA")
+ + _texts(body_nu + "".join(extra_nu), h_da + VGAP, "NU"))
for left, right, y, s, tag in labels:
assert left >= -2 and right <= W + 2, f"[{tag}] text iese din panză: {s!r}"
assert 0 <= y <= H, f"[{tag}] text în afara înălțimii: {s!r} (y={y:.0f})"
diff --git a/gen/strategii/buy_doji_fereastra.spec.txt b/gen/strategii/buy_doji_fereastra.spec.txt
index 7041d53..19c5aa6 100644
--- a/gen/strategii/buy_doji_fereastra.spec.txt
+++ b/gen/strategii/buy_doji_fereastra.spec.txt
@@ -5,7 +5,7 @@ MARCAJ .md: (Nivel 1, §1.2 Setup)
CE ARATĂ
Două panouri mici, unul sub altul, cu aceeași serie până la doji:
-- Panoul DA: doji high 50.10; bara +1 închide 49.90 (sub high); bara +2
+- Panoul DA: doji high 50.10 / low 49.25; bara +1 închide 49.90 (sub high); bara +2
închide 50.40 (peste 50.10) => Buy Doji valid, intrare la close.
- Panoul NU: doji high 50.10; barele +1, +2, +3 închid 49.95, 50.05, 49.85
(toate sub high); a 4-a bară închide 49.15, sub low-ul dojiului => setup
@@ -15,7 +15,8 @@ numeric. Diferența e strict numerică, nu vizuală
(`13_strategia_3_byh.md:179-180`).
NIVELURILE ȘI ETICHETELE (cu trimitere)
-- high doji = 50.10, low doji = 49.20 (exemplul construit al lecției):
+- high doji = 50.10, low doji = 49.25 (low-ul e coborât de la 49.20 ca stopul
+ de 49.20 din pașii 5-6 să iasă SUB low, nu exact pe el):
`13_strategia_3_byh.md:158-167`.
- fereastra = maximum 3 candele după doji: `13_strategia_3_byh.md:151-156`.
- „setup expirat, nu intri" la lipsa confirmării: `13_strategia_3_byh.md:168-176`;
@@ -23,23 +24,39 @@ NIVELURILE ȘI ETICHETELE (cu trimitere)
STRUCTURA SERIEI
Panoul DA (8 candele): close-uri [52.20, 51.40, 50.60, 49.65,
- 49.60 (doji, h 50.10 / l 49.20),
+ 49.60 (doji, h 50.10 / l 49.25),
49.90 (+1), 50.40 (+2, confirmă), 50.80]
Panoul NU (9 candele): close-uri [52.20, 51.40, 50.60, 49.65,
- 49.60 (doji, h 50.10 / l 49.20),
+ 49.60 (doji, h 50.10 / l 49.25),
49.95 (+1), 50.05 (+2), 49.85 (+3), 49.15 (a 4-a, sub low)]
-Mecioni: doji wu=0.45/wd=0.40 ca să dea exact h=50.10, l=49.20; restul
+Fitile: doji wu=0.45/wd=0.35 ca să dea exact h=50.10, l=49.25; restul
(0.15, 0.15). Serie continuă, open = close-ul precedentului.
CE VERIFICĂ GENERATORUL CU assert
1. Panoul DA: primul close > 50.10 e bara +2 (index 6 din panou), adică în
fereastră; close[+1] <= 50.10.
-2. Panoul NU: niciun close din +1..+3 nu depășește 50.10; close[+4] < 49.20.
+2. Panoul NU: niciun close din +1..+3 nu depășește 50.10; close[+4] < 49.25.
3. Ambele doji: abs(o-c) <= 0.10*(h-l).
4. Ambele panouri au exact aceeași serie până la doji (aceleași 5 bare).
5. Distanța în pixeli de la 50.10 la 50.40 (DA) să fie > 0 și vizibil
(scara comună pe panou).
-STIL: două `Chart`-uri separate sau un SVG cu două grupuri; gap=40, body_w=13,
-width=460, fără vol_h (nu se vorbește de volum aici). Titluri
-„DA — confirmare la +2, în fereastră" / „NU — nicio confirmare în 3 candele".
-Fără linie goală în SVG.
+ADNOTĂRI („ca la proști")
+- ZONĂ albastră peste barele +1..+3 pe ambele panouri, etichetă
+ „fereastra: 3 candele".
+- Panoul DA: săgeată pe +1 („aici NU intri: s-a închis sub 50.10 / semnalul cere
+ o ÎNCHIDERE peste"); săgeată din dreapta pe close-ul barei +2 („AICI INTRI, la
+ close 50.40 / prima închidere peste / high-ul dojiului").
+- Panoul NU: săgeată pe +3 („a treia s-a închis tot sub 50.10 / fereastra s-a
+ terminat: ștergi setup-ul"); săgeată pe a 4-a („a închis sub low-ul dojiului /
+ setup mort — nu mai intri").
+- Liniile 50.10 și 49.25 și etichetele lor se desenează de mână, nu cu `Level`:
+ eticheta din `Level` stă lipită de linie, la dreapta, și cade peste candele.
+ Eticheta lui 50.10 e pe trei rânduri, jos-stânga: „high-ul dojiului 50.10 /
+ trebuie ÎNCHIS peste, / nu doar atins".
+
+STIL: două `Chart`-uri în două grupuri ``; scale=60, gap=40,
+body_w=13, left=64, width=640, fără vol_h (nu se vorbește de volum aici).
+Titluri „DA — confirmare la +2, în fereastră" / „NU — nicio confirmare în 3
+candele". Randare cu `ch._render()` (vezi nota din buy_doji_trend.spec.txt).
+Fără linie goală în SVG. Generatorul verifică singur că nicio etichetă nu iese
+din pânză și că două etichete din același panou nu se suprapun.
diff --git a/gen/strategii/buy_doji_fereastra.svg b/gen/strategii/buy_doji_fereastra.svg
index 7ef8659..a5c2fcd 100644
--- a/gen/strategii/buy_doji_fereastra.svg
+++ b/gen/strategii/buy_doji_fereastra.svg
@@ -1,60 +1,65 @@
-
-
+
+ fereastra: 3 candelehigh-ul dojiului 50.10trebuie ÎNCHIS peste,nu doar atinslow-ul dojiului 49.25DA — confirmare la +2, în fereastră
-
- high doji 50.10
-
- low doji 49.20
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- doji
- confirmare +2 — intrare 50.40
- +1: close 49.90 — sub 50.10, nu confirmă
- +2: close 50.40 — peste 50.10, confirmă (intrare la close)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ doji
+
+
+ aici NU intri: s-a închis sub 50.10
+ semnalul cere o ÎNCHIDERE peste
+
+
+ AICI INTRI, la close 50.40
+ prima închidere peste
+ high-ul dojiului
-
+ fereastra: 3 candelehigh-ul dojiului 50.10trebuie ÎNCHIS peste,nu doar atinslow-ul dojiului 49.25NU — nicio confirmare în 3 candele
-
- high doji 50.10
-
- low doji 49.20
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- doji
- a 4-a
- +1: 49.95; +2: 50.05; +3: 49.85 — toate sub 50.10
- a 4-a: close 49.15 — sub low-ul dojiului 49.20, setup mort
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ doji
+ a 4-a
+
+
+ a treia s-a închis tot sub 50.10
+ fereastra s-a terminat: ștergi setup-ul
+
+
+ a închis sub low-ul dojiului
+ setup mort — nu mai intri
+ +1: 49.95 · +2: 50.05 · +3: 49.85 — toate sub 50.10
- Serii construite: open = close-ul barei anterioare.
- Doji: h 50.10 / l 49.20. Fereastra = maximum 3 candele după doji.
+ Serii construite: open = close-ul barei anterioare.
+ Doji: h 50.10 / l 49.25. Fereastra = maximum 3 candele după doji.
\ No newline at end of file
diff --git a/gen/strategii/buy_doji_iesire.py b/gen/strategii/buy_doji_iesire.py
index fe91dbe..2476c91 100644
--- a/gen/strategii/buy_doji_iesire.py
+++ b/gen/strategii/buy_doji_iesire.py
@@ -1,222 +1,150 @@
-#!/usr/bin/env python3
-"""Buy Doji — ieșirea: trailing stop pe gap-uri și limita de 8 candele.
+"""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` și scrie
-`gen/strategii/buy_doji_iesire.svg`: două panouri stivuite.
+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 — seria din buy_doji_transe (barele 0-18) continuată cu 19-23. Barele 19
- și 21 deschid cu GAP (excepție cerută de spec): regula de trail cere o
- bară cu low peste high-ul precedentei, imposibil dacă open = close
- anterior. De aceea barele 15-23 sunt construite explicit, nu cu
- `from_closes`.
-B — doji 50.10 / 49.20, intrare 50.40, apoi 8 candele fără TP și fără SL.
+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, în ambele panouri, sunt CONSTRUITE.
-Sursa regulilor: summaries/03_MODUL_2/13_strategia_3_byh.md:196-213.
-
-Rulare: python3 gen/strategii/buy_doji_iesire.py
+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")
-from svg_candles import Candle, Chart, Level, Mark, from_closes # noqa: E402
+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")
-W = 660
-VGAP = 10
+S = 10 # prima bară arătată
GREEN, RED, ORANGE, BLUE, GREY = "#188a3e", "#c00", "#b06000", "#1a5fb4", "#555"
+W, VGAP = 760, 14
-# --- PANOUL A ----------------------------------------------------------------
-ENTRY_A, SL_A, TP1_A = 53.70, 51.55, 55.85
-R_A = round(ENTRY_A - SL_A, 2) # 2.15
+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)
-a = from_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], wick=0.15, start=61.00)
-a += [Candle(*ohlc) for ohlc in [
- (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 = intrare
- (53.70, 53.80, 53.10, 53.45), # 15
- (53.45, 54.20, 53.35, 54.10), # 16
- (54.10, 54.85, 54.00, 54.70), # 17
- (54.70, 56.00, 54.60, 55.90), # 18 atinge TP1
- (56.30, 56.95, 56.20, 56.80), # 19 GAP
- (56.80, 57.35, 56.60, 57.20), # 20
- (57.60, 58.10, 57.45, 57.90), # 21 GAP
- (57.90, 58.00, 57.50, 57.60), # 22
- (57.60, 57.70, 57.10, 57.20), # 23 close sub trail
-]]
-assert len(a) == 24
+# --- 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
-for c in a:
- assert c.l <= min(c.o, c.c) and c.h >= max(c.o, c.c), "h/l nu cuprind o/c"
-# A1. gap-urile reale
-assert a[19].l > a[18].h and a[21].l > a[20].h, "barele de gap nu au low > high anterior"
-# A2. open = close anterior, în afară de 19 și 21 (și 0-14, ca în transe)
-for i in range(1, 24):
- if i not in (19, 21):
- assert abs(a[i].o - a[i - 1].c) < 1e-9, f"A: open[{i}] != close[{i-1}]"
-# regula de trail, calculată pe serie: primul gap după intrare stabilește
-# trail-ul, fiecare gap următor îl urcă; ieși la primul close sub el
-trail, steps, exit_i = None, [], None
-for i in range(15, 24):
- if trail is not None and a[i].c < trail:
- exit_i = i
- break
- if a[i].l > a[i - 1].h:
- trail = a[i].l
- steps.append((i, trail))
-assert [(i, round(t, 2)) for i, t in steps] == [(19, 56.20), (21, 57.45)], steps
-# A3.
-assert a[22].c >= 57.45 and a[23].c < 57.45 and exit_i == 23, exit_i
-EXIT_A = a[23].c
-# A4.
-assert R_A == 2.15 and round((EXIT_A - ENTRY_A) / R_A, 2) == 1.63
-# A5. TP1 = 1R, atins prima oară la bara 18
-assert round(ENTRY_A + R_A, 2) == TP1_A
-assert next(i for i in range(15, 24) if a[i].h >= TP1_A) == 18
-assert abs(a[14].c - ENTRY_A) < 1e-9
+# --- 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(ENTRY_A, "intrare 53.70", GREEN),
- Level(TP1_A, "TP1 55.85 — tranșa 1: +1R", GREEN, dash="2 3")],
- scale=40, gap=16, body_w=9, left=44, width=W,
- title="A — trailing stop pe gap-uri (seria de la intrarea în tranșe)",
- caption="a treia treime: o lași mai departe (break-even / trend)",
+ 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),
+ 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=110, dy=96),
+ ],
+ title="A — prima treime la țintă, a doua pe trailing stop",
+ **common,
)
-# --- PANOUL B ----------------------------------------------------------------
-ENTRY_B, SL_B, TP1_B = 50.40, 49.20, 51.60
-R_B = round(ENTRY_B - SL_B, 2) # 1.20
-
-b = from_closes([52.20, 51.40, 50.60, 49.65], wick=0.15, start=53.00)
-b += [Candle(*ohlc) for ohlc in [
- (49.65, 50.10, 49.20, 49.60), # 4 doji
- (49.60, 50.05, 49.45, 49.90), # 5 +1
- (49.90, 50.55, 49.75, 50.40), # 6 confirmare = intrare
-]]
-b += from_closes([50.60, 50.30, 50.90, 50.50, 51.10, 50.80, 51.20, 50.70],
- wick=0.15, start=b[-1].c)
-assert len(b) == 15
-# B1.
-for i in range(1, 15):
- assert abs(b[i].o - b[i - 1].c) < 1e-9, f"B: open[{i}] != close[{i-1}]"
-d = b[4]
-assert abs(d.o - d.c) <= 0.10 * (d.h - d.l) and (d.h, d.l) == (50.10, 49.20)
-assert b[6].c > d.h and abs(b[6].c - ENTRY_B) < 1e-9
-# B2. 8 candele fără TP și fără SL
-after = b[7:15]
-assert all(c.h < TP1_B and c.l > SL_B for c in after)
-assert round(max(c.h for c in after), 2) == 51.35 and round(min(c.l for c in after), 2) == 50.15
-assert round(ENTRY_B + R_B, 2) == TP1_B
-# B3.
-EXIT_B = b[14].c
-assert round((EXIT_B - ENTRY_B) / R_B, 2) == 0.25
-
-for k, c in enumerate(after, 1):
- c.tag = str(k)
-
ch_b = Chart(
candles=b,
- levels=[Level(TP1_B, "TP1 51.60", GREEN, dash="2 3"),
- Level(ENTRY_B, "intrare 50.40", GREEN),
- Level(SL_B, "SL 49.20", RED)],
- marks=[Mark(4, "doji", ORANGE, "below")],
- scale=48, gap=16, body_w=9, left=44, width=W,
- title="B — limita de 8 candele",
+ 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,
)
+for k, c in enumerate(opt, 1):
+ c.tag = str(k)
-# --- etichete plasate de mână: Mark centrează textul, iar aici textele lungi
-# ar cădea peste candelele vecine. Le ancorez lângă bară, pe partea liberă.
-def label(ch, i, price, text, color, anchor="start", dx=0):
- x = ch._cx(i) + dx
- return (f'{text}')
+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']*stroke="#1a1a1a"', svg).group(1)) - ch._cx(0)
+ return svg[svg.index(">") + 1:svg.rindex("")].strip(), h, off_x, off_y
-def panel(ch, extra):
- svg = ch.render() # render() fixează scala (_pmax, top)
- h = float(re.search(r'viewBox="0 0 \d+ ([\d.]+)"', svg).group(1))
- body = svg[svg.index(">") + 1:svg.rindex("")].strip()
- return body + "".join("\n " + e for e in extra(ch)), h
+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'',
+ f''
+ f'linia de trailing stop',
+ # etichetele zonelor, lângă ele (Zone le-ar ridica deasupra candelelor vecine)
+ f''
+ f'prima treime: de la intrare la ținta 1:1',
+ f''
+ f'a doua treime: merge pe trailing stop',
+]
+extra_a.append(f'A treia treime o lași mai '
+ f'departe, cu stopul mutat la intrare (break-even).')
+h_a = max(h_a, Y(SL) + 112)
-
-def trail_a(ch):
- x19, x21, x23 = ch._cx(19) - 7, ch._cx(21) - 8, ch._cx(23) + 7
- y1, y2 = ch._y(steps[0][1]), ch._y(steps[1][1])
- # golul dintre high-ul barei precedente și low-ul barei de gap, hașurat:
- # la 0.10-0.20 de preț are doar câțiva pixeli și se pierde sub linia de trail
- gaps = [f''
- for i, _ in steps]
- return gaps + [
- f'',
- label(ch, 18, 56.25, "TP1: ieși cu 1/3", GREEN, "end", 4),
- label(ch, 19, 55.35, "gap: low > high anterior → trail 56.20", ORANGE, "start", -4),
- label(ch, 21, 58.35, "gap → trail 57.45", ORANGE, "middle"),
- label(ch, 23, 57.30, "close sub trail: ieși cu 1/3", RED, "start", 14),
- label(ch, 23, 56.88, f"la 57.20 = +{(EXIT_A - ENTRY_A) / R_A:.2f}R", RED, "start", 14),
- ]
-
-
-def mark_b(ch):
- return [
- label(ch, 14, 51.05, "a 8-a candelă: nici TP, nici SL", GREY, "start", 10),
- label(ch, 14, 50.75, f"→ ieși la 50.70 = +{(EXIT_B - ENTRY_B) / R_B:.2f}R", GREY, "start", 10),
- ]
-
-
-# panourile se stivuiesc prin `top` (nu prin ): audit_diagrams
-# citește coordonatele brute și ar vedea panourile suprapuse.
-T = 26
-ch_a.top += T
-body_a, h_a = panel(ch_a, trail_a)
-ch_b.top += h_a + VGAP
-body_b, H_B = panel(ch_b, mark_b)
-TITLE = "Ieșirea: trailing stop pe gap-uri și limita de 8 candele — exemple construite"
-CAP = "Barele de după intrare sunt construite. Regulile: 13_strategia_3_byh.md:196-213."
-cap_y = H_B + 12
-H = cap_y + 10
+body_b, h_b, bx, by = panou(ch_b, h_a + VGAP)
+extra_b = [
+ f'8 candele în care nu se întâmplă nimic',
+]
+H = h_b + 8
svg = "\n ".join([
f'',
- f'{TITLE}',
- body_a,
- body_b,
- f'{CAP}',
+ body_a, *extra_a,
+ f'',
+ body_b, *extra_b,
]) + "\n"
-assert "\n\n" not in svg
-
-# --- etichete în panză și fără suprapuneri (lățimi aproximate) ---------------
-TEXT = re.compile(r']*)>(.*?)', re.S)
-
-
-def texts(body, y_off, tag):
- for m in TEXT.finditer(body):
- x, y, attrs = float(m.group(1)), float(m.group(2)) + y_off, m.group(3)
- s = m.group(4).replace(">", ">")
- wpx = len(s) * 12 * (0.62 if "bold" in attrs else 0.52)
- anchor = re.search(r'text-anchor="(\w+)"', attrs)
- anchor = anchor.group(1) if anchor else "start"
- x0 = x - wpx if anchor == "end" else (x - wpx / 2 if anchor == "middle" else x)
- yield x0, x0 + wpx, y, s, tag
-
-
-labels = list(texts(body_a, 0, "A")) + list(texts(body_b, 0, "B"))
-for x0, x1, y, s, tag in labels:
- assert x0 >= -2 and x1 <= W + 2 and 0 <= y <= H, f"[{tag}] text iese din panză: {s!r}"
-for i, (l1, r1, y1, s1, t1) in enumerate(labels):
- for l2, r2, y2, s2, t2 in labels[i + 1:]:
- assert not (t1 == t2 and abs(y1 - y2) < 12 and l1 < r2 - 4 and l2 < r1 - 4), \
- f"[{t1}] texte suprapuse: {s1!r} / {s2!r}"
-
+assert "\n\n" not in svg, "linie goală în SVG"
+for tx, ty in re.findall(r'
-titlu SVG: „Ieșirea: trailing stop pe gap-uri și limita de 8 candele — exemple construite"
-stil: candele lipite (gap 15-18, body_w 9-10), fără volum.
- EXCEPȚIE CERUTĂ: în panoul A, barele 19 și 21 deschid cu GAP
- (open != close anterior). Regula de trail are nevoie de o bară
- cu low peste high-ul precedentei, ceea ce fără gap e imposibil.
- Toate celelalte bare: open = close anterior.
+titluri SVG: A „prima treime la țintă, a doua pe trailing stop";
+ B „limita de 8 candele: nici ținta, nici stopul"
+serie: `buy_doji_ansamblu.serie()` — A: barele 10-28; B: barele 10-16
+ plus 8 bare construite.
-PANOUL A — trailing stop (seria buy_doji_transe, continuată)
-------------------------------------------------------------
-Barele 0-18: identice cu buy_doji_transe. Apoi:
-19 (56.30, 56.95, 56.20, 56.80) GAP: low 56.20 > high[18] 56.00 → trail = 56.20
-20 (56.80, 57.35, 56.60, 57.20)
-21 (57.60, 58.10, 57.45, 57.90) GAP: low 57.45 > high[20] 57.35 → trail urcă la 57.45
-22 (57.90, 58.00, 57.50, 57.60) close 57.60 ≥ 57.45: rămâi
-23 (57.60, 57.70, 57.10, 57.20) close 57.20 < 57.45: ieși
-Niveluri: intrare 53.70; TP1 55.85 („tranșa 1: +1R"); trail în trepte:
-segment 56.20 de la bara 19 la 20, segment 57.45 de la bara 21 la 23.
-Marks: Mark(18, "TP1: ieși cu 1/3"), Mark(19, "gap: low > high anterior → trail 56.20"),
-Mark(21, "trail 57.45"), Mark(23, "close sub trail: ieși cu 1/3 la 57.20 = +1.63R").
-Text sub panou: „a treia treime: o lași mai departe (break-even / trend)".
+PANOUL A — ieșirea în trepte
+----------------------------
+Intrare 50.40, stop 49.20, 1R = 1.20.
+- ținta 1 51.60 atinsă la bara 19: iese prima treime (+1R);
+- bara 21 deschide cu gap (low 51.95 > high[20] 51.85): linia de trailing stop
+ se așază la 51.95;
+- bara 27, alt gap (low 52.60 > high[26] 52.55): linia urcă la 52.60;
+- bara 28 închide la 52.30, sub linie: iese a doua treime, +1.58R;
+- a treia treime rămâne mai departe, cu stopul la intrare (break-even).
+Adnotări: zonă verde de la intrare la țintă, zonă albastră pe porțiunea de
+trail, linia de trail desenată în trepte, patru săgeți (ținta atinsă, primul
+gap, al doilea gap, ieșirea).
PANOUL B — limita de 8 candele
------------------------------
-open[0] = 53.00; close-uri: 52.20, 51.40, 50.60, 49.65, fitile 0.15;
- 4 doji (49.65, 50.10, 49.20, 49.60)
- 5 +1 (49.60, 50.05, 49.45, 49.90)
- 6 confirmare (49.90, 50.55, 49.75, 50.40) intrare 50.40
- 7-14 close-uri: 50.60, 50.30, 50.90, 50.50, 51.10, 50.80, 51.20, 50.70
- (fitile 0.15; open = close anterior)
-Niveluri: intrare 50.40, SL 49.20, TP1 51.60.
-Numerotare 1..8 sub barele 7-14. Mark(14, "a 8-a candelă: nici TP, nici SL → ieși la 50.70 = +0.25R").
+Aceleași bare până la confirmare, apoi 8 bare construite, close-uri 50.60,
+50.30, 50.90, 50.50, 51.10, 50.80, 51.20, 50.70 (fitile 0.15, open = close
+anterior). Niciuna nu atinge ținta 51.60 sau stopul 49.20 (max high 51.35, min
+low 50.15). La close-ul celei de-a 8-a ieși oricum: 50.70 = +0.25R.
+Adnotări: zonă gri între stop și țintă peste cele 8 bare, numerotate 1..8, și o
+săgeată: „banii blocați aici lipsesc din alt trade".
ASSERT-URI
----------
-A1. low[19] > high[18] și low[21] > high[20] (gap-urile reale).
-A2. open[i] == close[i-1] pentru i în 15..23, cu excepția 19 și 21.
-A3. close[22] >= 57.45 și close[23] < 57.45; primul close sub trail după bara 19 e 23.
-A4. round((57.20-53.70)/2.15, 2) == 1.63.
-A5. primul i>14 cu high >= 55.85 este 18.
-B1. open[i] == close[i-1] peste tot; doji abs(o-c) <= 0.10*(h-l); close[6] > 50.10.
-B2. pentru barele 7-14: high < 51.60 și low > 49.20 (max high 51.35, min low 50.15).
-B3. round((50.70-50.40)/1.20, 2) == 0.25.
+A1. regula de trail, calculată pe serie, dă [(21, 51.95), (27, 52.60)];
+ barele 21 și 27 au low peste high-ul barei precedente (gap-uri reale).
+A2. niciun close între bara 21 și ieșire nu e sub 51.95 (n-ar fi ieșit mai devreme).
+A3. ieșirea: close 52.30 la bara 28, sub 52.60; (52.30 - 50.40) / 1.20 == 1.58.
+A4. ținta 51.60 e atinsă prima dată la bara 19 (bara 18 nu o atinge).
+B1. open[i] == close[i-1] pe toată seria; exact 8 bare după intrare.
+B2. toate cele 8 au high < 51.60 și low > 49.20; max high 51.35, min low 50.15.
+B3. ieșire 50.70; (50.70 - 50.40) / 1.20 == 0.25.
+B4. niciun text nu iese din pânză.
SURSA
-----
- Trail: low-ul barei cu low peste high-ul precedentei; ieși la close sub el:
summaries/03_MODUL_2/13_strategia_3_byh.md:196-200.
-- 3 poziții (1/3 TP, 1/3 trail, 1/3 mai departe): :204-213 (tabel marcat de
- sumarizator ca completare, :341-350).
+- Trei poziții (1/3 la țintă, 1/3 pe trail, 1/3 mai departe): :204-213.
- Limita de 8 candele: :201-202.
-- Doji 50.10/49.20, intrare 50.40, SL 49.20: exemplul lecției, :158-167.
-- Barele după intrare, în ambele panouri, sunt CONSTRUITE.
+- Barele de după intrare din panoul B sunt CONSTRUITE.
diff --git a/gen/strategii/buy_doji_iesire.svg b/gen/strategii/buy_doji_iesire.svg
index 72477fb..fcd9763 100644
--- a/gen/strategii/buy_doji_iesire.svg
+++ b/gen/strategii/buy_doji_iesire.svg
@@ -1,114 +1,122 @@
-
- Ieșirea: trailing stop pe gap-uri și limita de 8 candele — exemple construite
- A — trailing stop pe gap-uri (seria de la intrarea în tranșe)
-
- intrare 53.70
-
- TP1 55.85 — tranșa 1: +1R
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- a treia treime: o lași mai departe (break-even / trend)
-
-
-
- TP1: ieși cu 1/3
- gap: low > high anterior → trail 56.20
- gap → trail 57.45
- close sub trail: ieși cu 1/3
- la 57.20 = +1.63R
- B — limita de 8 candele
-
- TP1 51.60
-
- intrare 50.40
-
- SL 49.20
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 1
-
-
- 2
-
-
- 3
-
-
- 4
-
-
- 5
-
-
- 6
-
-
- 7
-
-
- 8
- doji
- a 8-a candelă: nici TP, nici SL
- → ieși la 50.70 = +0.25R
- Barele de după intrare sunt construite. Regulile: 13_strategia_3_byh.md:196-213.
+
+ A — prima treime la țintă, a doua pe trailing stop
+
+
+
+ ținta 1: 51.60 (= 1R)
+
+ intrarea: 50.40
+
+ stopul: 49.20
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ținta 1 atinsă: vinzi prima treime
+ ai luat exact cât riscai (1R)
+
+
+ gap: low-ul de azi e peste high-ul de ieri
+ linia de trail urcă la 51.95
+
+
+ încă un gap: linia urcă la 52.60
+ urcă doar, nu coboară niciodată
+
+
+ prima închidere sub linie: ieși
+ cu a doua treime, la 52.30 (+1.58R)
+
+ linia de trailing stop
+ prima treime: de la intrare la ținta 1:1
+ a doua treime: merge pe trailing stop
+ A treia treime o lași mai departe, cu stopul mutat la intrare (break-even).
+
+ B — limita de 8 candele: nici ținta, nici stopul
+
+
+ ținta 1: 51.60 — neatinsă
+
+ intrarea: 50.40
+
+ stopul: 49.20 — neatins
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1
+
+
+ 2
+
+
+ 3
+
+
+ 4
+
+
+ 5
+
+
+ 6
+
+
+ 7
+
+
+ 8
+
+
+ a 8-a candelă: ieși oricum, la close 50.70 (+0.25R)
+ banii blocați aici lipsesc din alt trade
+ 8 candele în care nu se întâmplă nimic
\ No newline at end of file
diff --git a/gen/strategii/buy_doji_labu.py b/gen/strategii/buy_doji_labu.py
index c95cd06..1e6eb6b 100644
--- a/gen/strategii/buy_doji_labu.py
+++ b/gen/strategii/buy_doji_labu.py
@@ -2,16 +2,19 @@
"""LABU — Buy Doji la pivotul monthly (Nivel 2.5).
Citește `gen/strategii/buy_doji_labu.spec.txt` și scrie
-`gen/strategii/buy_doji_labu.svg`: 49 de candele daily, downtrend → triple
-bottom → doji la pivotul monthly 86.00 → confirmare (close 89.50 peste
-high-ul dojiului) → bull reversal → higher high, cu SL 81.45 (−9 %) și
+`gen/strategii/buy_doji_labu.svg`: 57 de candele daily, downtrend → triple
+bottom (trei minime aproape egale, dar pivotul lunar atins doar de două ori)
+→ doji la pivotul monthly 86.00 → confirmare (close 89.50 peste high-ul
+dojiului) → bull reversal → higher high, cu SL 81.45 (−9 %) și
TP 116.35 (+30 %).
Toate cifrele din desen vin din specificație: pivot 86.00, intrare 89.50,
SL 81.45, TP 116.35, high/low-ul dojiului (86.30 / 84.20) și low-urile
triple bottom-ului (82.40 / 81.45 / 81.45). Procentele 9 % și 30 % sunt ale
cursului (reale); nivelurile de preț sunt construite de sumarizator —
-legenda o spune explicit.
+legenda o spune explicit. Capitolul spune și „triple bottom", și „doji la al
+doilea test al pivotului": desenul le satisface pe amândouă — trei minime în
+zona 81-83, dintre care doar primele două vin după o urcare peste 86.00.
Rulare: python3 gen/strategii/buy_doji_labu.py
"""
@@ -20,7 +23,7 @@ import sys
from pathlib import Path
sys.path.insert(0, "/workspace/atm-curs-dl")
-from svg_candles import Chart, Level, MA, Mark, from_closes # noqa: E402
+from svg_candles import Arrow, Chart, Level, MA, Mark, Zone, from_closes # noqa: E402
OUT = Path(__file__).with_suffix(".svg")
@@ -29,50 +32,88 @@ PIVOT = 86.00 # pivot monthly (construit)
ENTRY = 89.50 # intrare = close-ul confirmării (construit)
SL = 81.45 # SL = −9 % față de intrare (construit; procentul e real)
TP = 116.35 # TP = +30 % față de intrare (construit; procentul e real)
-DOJI_H, DOJI_L = 86.30, 84.20
+DOJI_H, DOJI_L = 86.80, 84.90
+I_DOJI, I_CONF = 33, 34
+B1, B2, B3 = 16, 25, 31 # cele trei picioare ale triplului fund
+ # (B1, B2 = testele pivotului; B3 rămâne sub el)
+I_RETEST = 37 # revenirea care testează intrarea și ține
+FLAG = (42, 45) # consolidarea scurtă (bull flag)
+I_TP = 55 # bara pe care prețul atinge 116.35
-# --- seria de close-uri din specificație -----------------------------------
+# --- seria de close-uri -----------------------------------------------------
+# Textul capitolului e sursa: „triple bottom" (trei minime aproape egale) și
+# „doji la al doilea test al pivotului". Nu se bat cap în cap: sunt trei
+# minime de preț, dar doar două dintre ele vin după o urcare peste 86.00 —
+# al treilea e o alunecare care nu mai atinge pivotul de sus în jos.
+# Restul: „+30 % într-o lună" (21 de bare daily de la confirmare) și etapele
+# „retest → bull flag → breakout". Prețurile sunt construite în jurul
+# nivelurilor deja folosite în capitol (86.00 / 89.50 / 81.45 / 97.55 / 116.35).
closes = [
# downtrend 0-14
108.0, 106.5, 105.0, 103.0, 101.5, 100.0, 98.0, 96.5, 95.0, 93.5,
92.5, 91.0, 89.5, 87.5, 85.5,
- # triple bottom 15 / 23 / 31
- 82.80, 84.5, 86.5, 88.0, 88.50,
- 87.0, 85.0, 83.0, 81.50,
- 83.5, 85.5, 87.0, 87.80, 87.0, 85.0,
- 83.0, 81.60, 85.00,
- 84.90, # 33 = doji (o 85.00, c 84.90, h 86.30, l 84.20)
- 89.50, # 34 = confirmare, close > 86.30 => intrare
- # bull reversal / trend up 35-48
- 91.0, 93.0, 92.0, 95.0, 97.0, 96.0, 99.0, 102.0, 101.0, 105.0, 108.0,
- 112.0, 116.35, 118.0,
+ # 15-16: primul test al pivotului (bottom 1, low 82.40)
+ 83.5, 82.80,
+ # 17-20: revenire peste pivot (88.90 high) — de aici vine al doilea test
+ 84.5, 86.5, 88.0, 88.5,
+ # 21-24: coboară înapoi prin pivot: al doilea test al lui 86.00
+ 87.0, 85.5, 84.0, 82.8,
+ # 25: bottom 2 (low 82.00), la capătul celui de-al doilea test
+ 82.30,
+ # 26-29: un pull-up scurt care NU mai ajunge la pivot (high max 85.80)
+ 83.5, 84.8, 85.4, 84.2,
+ # 30: alunecă iar, fără să fi atins pivotul între timp
+ 83.0,
+ # 31: bottom 3 (low 81.45 = stopul) — al treilea minim, nu un test nou
+ 81.90,
+ # 32: bara de întoarcere, închide peste pivot
+ 86.10,
+ # 33 = doji: o 86.10, c 86.00, h 86.80, l 84.90 — corpul chiar pe pivot
+ 86.00,
+ # 34 = confirmare: close 89.50 peste high-ul dojiului => intrare
+ 89.50,
+ # 35-36: pornirea
+ 92.0, 94.0,
+ # 37: retest — cade la 89.60, pe intrare, și ține
+ 91.0,
+ # 38-41: reia urcarea și trece de ținta 1:1 (97.55)
+ 93.5, 95.5, 98.5, 100.0,
+ # 42-45: bull flag — patru candele care se odihnesc
+ 99.2, 99.8, 98.9, 99.5,
+ # 46-54: breakout și continuare
+ 102.5, 104.0, 103.0, 106.0, 108.5, 107.5, 110.5, 112.5, 114.0,
+ # 55: +30 % față de intrare; 56: încă un higher high
+ 116.35, 117.5,
]
-assert len(closes) == 49, f"seria are {len(closes)} bare, spec cere 49"
+assert len(closes) == 57, f"seria are {len(closes)} bare, aștept 57"
-# wick-uri: (up, down). Restul (0.4, 0.5); ajustate ca low-urile/high-urile
-# cerute de asserts să iasă exact (bar 15/23/31 = triple bottom; bar 16/24
-# nu au voie să coboare sub piciorul din stânga; bar 33 = doji).
+# wick-uri: (sus, jos) față de corp. Restul (0.4, 0.5); ajustate ca low-urile
+# cerute de assert-uri să iasă exact.
wicks = [(0.4, 0.5)] * len(closes)
-wicks[15] = (0.4, 0.40) # low 82.40
-wicks[16] = (0.4, 0.20) # low 82.60 > 82.40 (piciorul 1 rămâne minim local)
-wicks[23] = (0.4, 0.05) # low 81.45
-wicks[24] = (0.4, 0.00) # low 81.50 > 81.45 (piciorul 2 rămâne minim local)
-wicks[31] = (0.4, 0.15) # low 81.45
-wicks[32] = (0.4, 0.10) # low 81.50 (nu sub 81.45)
-wicks[33] = (1.30, 0.70) # doji: high 86.30, low 84.20
+wicks[B1] = (0.4, 0.40) # bottom 1: low 82.40
+wicks[17] = (0.4, 0.00) # low 82.80 > 82.40 (piciorul 1 rămâne minim local)
+wicks[B2] = (0.4, 0.30) # bottom 2: low 82.00
+wicks[26] = (0.4, 0.00) # low 82.30 > 82.00 (piciorul 2 rămâne minim local)
+wicks[B3] = (0.4, 0.45) # bottom 3: low 81.45 = stopul
+wicks[32] = (0.4, 0.00) # low 81.90 (nu coboară sub 81.45)
+wicks[I_DOJI] = (0.70, 1.10) # doji: high 86.80, low 84.90
+wicks[35] = (0.4, 0.00) # prima bară după intrare nu se mai duce sub 89.50
+wicks[I_RETEST] = (0.4, 1.40) # retest: low 89.60, peste intrarea de 89.50
-# volume construite: vânzare peste medie la triple bottom și la confirmare.
-# Nu sunt numere din curs (nu sunt etichetate), doar panoul de volum cerut.
+# volume construite: vânzare peste medie la cele două picioare, volum mare pe
+# confirmare, volum mic pe flag. Nu sunt numere din curs.
vols = [
55, 52, 48, 50, 45, 47, 44, 46, 42, 44,
40, 42, 38, 41, 58,
- 62, 50, 46, 44, 43,
- 45, 44, 47, 66, 55,
- 50, 47, 45, 44, 42,
- 43, 45, 60, 72, 84,
- 78, 70, 64, 60, 58,
- 54, 52, 56, 60, 66,
- 74, 82, 90, 98,
+ 62, 68, 50, 46, 44,
+ 43, 45, 44, 47, 46,
+ 45, 47, 52, 45, 44,
+ 48, 70, 62, 58, 88,
+ 76, 70, 58, 62, 66,
+ 72, 70, 44, 40, 38,
+ 42, 74, 70, 64, 68,
+ 72, 66, 74, 78, 82,
+ 90, 96,
]
assert len(vols) == len(closes), f"volume {len(vols)} != bare {len(closes)}"
@@ -80,80 +121,134 @@ cds = from_closes(closes, wicks=wicks, vols=vols, start=110.0)
assert abs(cds[0].o - 110.0) < 1e-9 and cds[0].c < cds[0].o, "prima bară nu e roșie"
# nota explicativă a dojiului (bloc stânga-jos, nu se suprapune cu candelele)
-cds[33].note = ("bara 33 = doji: o 85.00, c 84.90, h 86.30, l 84.20 — "
- "corp mic exact pe pivotul monthly 86.00 (indecizie la suport)")
+cds[I_DOJI].note = ("doji = corp mic (o 86.10, c 86.00): cumpărătorii și vânzătorii sunt la "
+ "egalitate, exact pe pivotul lunar 86.00")
+
+TP1 = round(ENTRY + (ENTRY - SL), 2) # ținta 1:1: cât ai riscat
+assert abs(TP1 - 97.55) < 1e-9, f"ținta 1:1 = {TP1}, capitolul spune 97.55"
+R = (TP - ENTRY) / (ENTRY - SL)
+assert 3.2 <= R <= 3.4, f"{R:.2f}R, capitolul spune „cam 3,3R”"
+I_TP1 = next(i for i in range(35, len(cds)) if cds[i].h >= TP1) # prima bară peste 1:1
+cds[I_TP1].note = (f"ținta 1:1 ({TP1:.2f}) e atinsă pe bara {I_TP1}: acolo vinzi o treime, "
+ "restul merge mai departe — de asta nu vinzi tot la 1:1")
# --- assert-urile din specificație (toate, ca assert reale) ----------------
# 1. pivot 86.00 e straddled de doji
-assert cds[33].l <= PIVOT <= cds[33].h, "doji-ul nu straddlează 86.00"
+assert cds[I_DOJI].l <= PIVOT <= cds[I_DOJI].h, "doji-ul nu straddlează 86.00"
+# corpul dojiului stă pe pivot, nu la un dolar sub el (eticheta spune „exact pe")
+assert min(cds[I_DOJI].o, cds[I_DOJI].c) <= PIVOT <= max(cds[I_DOJI].o, cds[I_DOJI].c) + 0.15, \
+ f"corpul dojiului ({cds[I_DOJI].o}/{cds[I_DOJI].c}) nu e pe pivotul {PIVOT}"
-# 2. confirmarea: close[34] == 89.50 == intrare și > high[33]
-assert abs(cds[34].c - ENTRY) < 1e-9, f"close[34]={cds[34].c} != intrare 89.50"
-assert cds[34].c > cds[33].h, "confirmarea nu închide peste high-ul dojiului"
+# 2. confirmarea: close[34] == 89.50 == intrare și > high-ul dojiului
+assert abs(cds[I_CONF].c - ENTRY) < 1e-9, f"close[{I_CONF}]={cds[I_CONF].c} != intrare 89.50"
+assert cds[I_CONF].c > cds[I_DOJI].h, "confirmarea nu închide peste high-ul dojiului"
-# 3. SL = min(low[30:35]) (regula: extrema ultimelor 4 candele de la confirmare)
-min_30_35 = min(c.l for c in cds[30:35])
-assert abs(min_30_35 - SL) < 1e-9, f"min low[30:35]={min_30_35} != SL {SL}"
+# 3. SL = extrema ultimelor 4 candele de la confirmare
+min_win = min(c.l for c in cds[I_CONF - 4:I_CONF + 1])
+assert abs(min_win - SL) < 1e-9, f"min low[{I_CONF - 4}:{I_CONF + 1}]={min_win} != SL {SL}"
# 4./5. procentele REALE ale cursului
assert round((ENTRY - SL) / ENTRY * 100, 1) == 9.0, "stopul nu iese −9 %"
assert round((TP - ENTRY) / ENTRY * 100, 1) == 30.0, "TP nu iese +30 %"
-# 6. triple bottom: exact trei minime locale sub 86.00, în zona 81-83,
-# al doilea și al treilea fără low nou semnificativ față de primul
+# 6. triple bottom, dar pivotul atins de două ori — aserțiunea care ține
+# laolaltă cele două formulări din capitol („trei minime aproape egale" și
+# „doji la al doilea test al pivotului 86.00").
lows = [c.l for c in cds]
bottom_idx = [i for i in range(1, len(cds) - 1)
if lows[i] < lows[i - 1] and lows[i] < lows[i + 1]
and lows[i] < PIVOT and 81.0 <= lows[i] <= 83.0]
-assert bottom_idx == [15, 23, 31], f"triple bottom la {bottom_idx}, nu [15, 23, 31]"
-b1, b2, b3 = lows[15], lows[23], lows[31]
-tol = 0.02 * b1 # „semnificativ" = > 2 % sub primul low
-assert b2 >= b1 - tol and b3 >= b1 - tol, \
- f"al doilea/treilea picior coboară semnificativ sub primul: {b1}/{b2}/{b3}"
-assert abs(b1 - 82.40) < 1e-9 and abs(b2 - SL) < 1e-9 and abs(b3 - SL) < 1e-9, \
- f"low-urile triple bottom: {b1}/{b2}/{b3}"
+assert bottom_idx == [B1, B2, B3], f"minimele fundului: {bottom_idx}, aștept [{B1}, {B2}, {B3}]"
+assert len(bottom_idx) == 3, f"{len(bottom_idx)} minime locale în zona fundului, aștept 3"
+b1, b2, b3 = (lows[i] for i in bottom_idx)
+# „aproape egale": nici unul nu e cu mai mult de 2 % sub primul
+assert max(b1, b2, b3) - min(b1, b2, b3) <= 0.02 * b1, \
+ f"minimele nu sunt aproape egale: {b1}/{b2}/{b3}"
+assert abs(b1 - 82.40) < 1e-9 and abs(b2 - 82.00) < 1e-9 and abs(b3 - SL) < 1e-9, \
+ f"low-urile triplului fund: {b1}/{b2}/{b3}"
+# un minim e „atingere a pivotului" doar dacă prețul a fost peste 86.00 după
+# minimul dinainte: coboară prin pivot ca să ajungă acolo. Al treilea nu e.
+tests = []
+for k, i in enumerate(bottom_idx):
+ prev = bottom_idx[k - 1] + 1 if k else 0
+ if max(c.h for c in cds[prev:i]) >= PIVOT:
+ tests.append(i)
+assert tests == [B1, B2], f"atingeri ale pivotului la {tests}, aștept [{B1}, {B2}]"
+assert len(tests) == 2, f"{len(tests)} atingeri ale pivotului 86.00, aștept 2"
+# între primele două picioare prețul urcă clar peste pivot (al doilea test)
+assert max(c.h for c in cds[B1 + 1:B2]) > PIVOT + 1.5, "nu e revenire peste pivot înainte de B2"
+# între al doilea și al treilea NU mai ajunge la pivot: al treilea nu e test
+assert max(c.h for c in cds[B2 + 1:B3]) < PIVOT, \
+ f"pull-up-ul dintre B2 și B3 atinge pivotul ({max(c.h for c in cds[B2 + 1:B3]):.2f})"
-# 7. TP atins: există i > 34 cu high[i] >= 116.35
-assert any(c.h >= TP for c in cds[35:]), "TP 116.35 nu e atins după intrare"
+# 7. TP atins la ~o lună de bursă de la confirmare (capitolul: „într-o lună")
+assert abs(cds[I_TP].c - TP) < 1e-9, f"close[{I_TP}]={cds[I_TP].c} != {TP}"
+assert max(c.h for c in cds[I_CONF + 1:I_TP]) < TP, "TP atins mai devreme de bara 55"
+bars_to_tp = I_TP - I_CONF
+assert 20 <= bars_to_tp <= 22, f"{bars_to_tp} bare până la +30 %, aștept ~21 (o lună)"
# 8. doji: corp <= 10 % din amplitudine
-assert abs(cds[33].o - cds[33].c) <= 0.10 * (cds[33].h - cds[33].l), "bara 33 nu e doji"
-assert abs(cds[33].h - DOJI_H) < 1e-9 and abs(cds[33].l - DOJI_L) < 1e-9, \
- "doji h/l nu ies 86.30/84.20"
+assert abs(cds[I_DOJI].o - cds[I_DOJI].c) <= 0.10 * (cds[I_DOJI].h - cds[I_DOJI].l), \
+ "bara doji nu e doji"
+assert abs(cds[I_DOJI].h - DOJI_H) < 1e-9 and abs(cds[I_DOJI].l - DOJI_L) < 1e-9, \
+ "doji h/l nu ies 86.80/84.90"
-# în plus: confirmarea vine cu volum peste medie (cerut de spec)
+# 9. retestul: revine până pe intrare și ține (nu o rupe)
+assert ENTRY <= cds[I_RETEST].l <= ENTRY + 0.5, \
+ f"retestul nu atinge intrarea: low {cds[I_RETEST].l}"
+assert min(c.l for c in cds[I_CONF + 1:]) >= ENTRY, "prețul cade sub intrare după confirmare"
+
+# 10. bull flag: 4 candele strânse, sub high-ul barei dinainte, urmate de breakout
+f0, f1 = FLAG
+flag_h = max(c.h for c in cds[f0:f1 + 1])
+flag_l = min(c.l for c in cds[f0:f1 + 1])
+assert flag_h - flag_l <= 2.5, f"flag prea larg: {flag_h - flag_l:.2f}"
+assert flag_h <= cds[f0 - 1].h, "flagul face high nou — nu mai e consolidare"
+assert cds[f1 + 1].c > flag_h, "nu există breakout după flag"
+
+# în plus: confirmarea vine cu volum peste medie, flagul cu volum sub medie
mean_vol = sum(vols) / len(vols)
-assert vols[34] > mean_vol, f"volumul confirmării {vols[34]} <= media {mean_vol:.0f}"
+assert vols[I_CONF] > mean_vol, f"volumul confirmării {vols[I_CONF]} <= media {mean_vol:.0f}"
+assert max(vols[f0:f1 + 1]) < mean_vol, "flagul nu vine pe volum scăzut"
entry = cds[34].c
-w = 820 # lățime mărită: 49 bare × gap 15 + etichetele din dreapta
- # (spec sugera 760, dar ultima candelă ajunge la x=764)
+w = 990 # 57 bare × gap 15 + loc pentru etichetele din dreapta
ch = Chart(
candles=cds,
- levels=[Level(PIVOT, "pivot monthly 86.00 (construit)", "#777",
- dash="4 3", label_at="left"),
- Level(SL, "SL 81.45 (construit) = −9 % (procent real)", "#c00",
- label_at="left"),
- Level(ENTRY, "intrare 89.50 (construit)", "#188a3e"),
- Level(TP, "TP 116.35 (construit) = +30 % (procent real)", "#1a5fb4")],
- marks=[Mark(15, f"bottom 1 {lows[15]:.2f}", "#c00", "below"),
- Mark(23, f"bottom 2 {lows[23]:.2f}", "#c00", "below"),
- Mark(31, f"bottom 3 {lows[31]:.2f}", "#c00", "below"),
- Mark(33, "doji", "#b06000", "above"),
- Mark(34, "confirmare (intrare)", "#188a3e", "above", dy=-14),
- Mark(48, "higher high — TP atins", "#188a3e", "above")],
+ levels=[Level(PIVOT, "pivot lunar 86.00", "#777", dash="4 3", label_at="left"),
+ Level(SL, "stop 81.45 = 9 % sub intrare (procent real)", "#c00", label_at="left"),
+ Level(ENTRY, "intrare 89.50", "#188a3e"),
+ Level(TP1, f"ținta 1:1 {TP1:.2f}", "#777", dash="2 3", label_at="left"),
+ Level(TP, "116.35 = +30 % față de intrare (procent real)", "#1a5fb4", label_at="left")],
+ zones=[Zone(0, B3, "1. setup: scădere lungă, apoi trei minime aproape egale — pivotul 86.00 atins de două ori", "#777"),
+ Zone(I_DOJI, I_CONF, "2. doji + confirmare", "#b06000"),
+ # p_top sub 116.35: altfel eticheta zonei 3 se așază peste linia țintei
+ Zone(I_CONF + 1, I_TP - 1, "3. trendul pornește", "#188a3e", p_top=113.0),
+ Zone(I_TP, len(cds) - 1, "4. ținta", "#1a5fb4")],
+ arrows=[Arrow(I_CONF, "aici intri, la close 89.50:\nabia acum știi că au câștigat cumpărătorii",
+ ENTRY, where="above", color="#188a3e", dx=-150),
+ Arrow(B3, "al treilea minim: 81.45,\nfără o nouă atingere a pivotului\naici pui stopul, sub minime",
+ SL, where="below", color="#c00", dx=-40),
+ Arrow(I_RETEST, "retest: cade înapoi pe intrare\nși ține — trendul e sănătos",
+ cds[I_RETEST].l, where="below", color="#188a3e", dx=110, dy=56),
+ Arrow(FLAG[0] + 1, "aici se odihnește, nu se strică:\nbull flag, pe volum mic",
+ cds[FLAG[0] + 1].h, where="above", color="#188a3e", dx=-20),
+ Arrow(I_TP, f"a ajuns la 116.35: +30 % în 21 de zile de bursă,\ncam {R:.1f}R din cât ai riscat".replace("3.3R", "3,3R"),
+ TP, where="left", color="#1a5fb4", dx=-30, dy=-40)],
+ marks=[Mark(B1, f"test 1 · {lows[B1]:.2f}", "#c00", "below"),
+ Mark(B2, f"test 2 · {lows[B2]:.2f}", "#c00", "below")],
mas=[MA(20, "#b06000", dash="4 3", text="SMA-20")],
scale=8.0, gap=15, body_w=9, left=44, width=w, vol_h=70,
vol_label="volum", title="LABU — daily",
- caption=("Procentele (stop 9 %, +30 %) sunt ale cursului; nivelurile de "
- "preț sunt construite de sumarizator (18_2025-09-23.md:713)."),
+ caption=("Procentele (stop 9 %, +30 %) sunt ale cursului; "
+ "nivelurile de preț sunt puse de sumarizator."),
)
svg = ch.render()
assert "\n\n" not in svg and "]*)>(.*?)', re.S)
-H = float(re.search(r'viewBox="0 0 (\d+) ([\d.]+)"', svg).group(2))
+W_SVG, H = (float(x) for x in re.search(r'viewBox="0 0 (\d+) ([\d.]+)"', svg).groups())
labels = []
for m in TEXT.finditer(svg):
x, y, attrs, s = float(m.group(1)), float(m.group(2)), m.group(3), m.group(4)
@@ -165,7 +260,7 @@ for m in TEXT.finditer(svg):
labels.append((left, left + wpx, y, s))
for left, right, y, s in labels:
- assert left >= -2 and right <= w + 2, f"text iese din panză: {s!r} [{left:.0f},{right:.0f}]"
+ assert left >= -2 and right <= W_SVG + 2, f"text iese din panză: {s!r} [{left:.0f},{right:.0f}]"
assert 0 <= y <= H, f"text în afara înălțimii: {s!r} (y={y:.0f})"
for i in range(len(labels)):
@@ -177,14 +272,16 @@ for i in range(len(labels)):
# candelele sunt o serie continuă, lipită
xs = [ch._cx(i) for i in range(len(cds))]
-assert xs[-1] + ch.body_w / 2 < w, f"ultima candelă iese din panză (x={xs[-1]})"
+assert ".md:" not in svg and "Surse" not in svg, "trimiteri la surse în SVG"
+assert xs[-1] + ch.body_w / 2 < W_SVG, f"ultima candelă iese din panză (x={xs[-1]})"
assert all(xs[i + 1] - xs[i] == ch.gap for i in range(len(xs) - 1)), "serie cu goluri"
for a, b in zip(cds, cds[1:]):
assert abs(a.c - b.o) < 1e-9, "open != close-ul barei anterioare"
print(f"LABU: {len(cds)} bare, doji {cds[33].l:.2f}-{cds[33].h:.2f} peste pivot "
f"{PIVOT:.2f}, intrare {entry:.2f}, SL {SL:.2f} (−9 %), TP {TP:.2f} (+30 %)")
-print(f"triple bottom: {b1:.2f} / {b2:.2f} / {b3:.2f} "
+print(f"triple bottom: {b1:.2f} / {b2:.2f} / {b3:.2f}; pivotul 86.00 atins de "
+ f"{len(tests)} ori (barele {tests}); +30 % după {bars_to_tp} bare "
f"volum confirmare {vols[34]} > medie {mean_vol:.0f}")
-print(f"scris {OUT} ({w}x{H:.0f}); {len(labels)} etichete verificate, fără suprapuneri")
+print(f"scris {OUT} ({W_SVG:.0f}x{H:.0f}); {len(labels)} etichete verificate, fără suprapuneri")
OUT.write_text(svg, encoding="utf-8")
diff --git a/gen/strategii/buy_doji_labu.spec.txt b/gen/strategii/buy_doji_labu.spec.txt
index 62240bc..86c912f 100644
--- a/gen/strategii/buy_doji_labu.spec.txt
+++ b/gen/strategii/buy_doji_labu.spec.txt
@@ -4,8 +4,9 @@ TITLU: LABU — daily, Buy Doji la pivotul monthly (Nivel 2.5)
MARCAJ .md: (Nivel 2, §2.5 Exemplul complet)
CE ARATĂ
-Exemplul complet din material: downtrend → consolidare → triple bottom →
-doji la pivotul monthly 86.00 → candela verde care închide peste high-ul
+Exemplul complet din material: downtrend → consolidare → triple bottom (trei
+minime aproape egale, dar pivotul monthly atins doar de două ori — „al doilea
+test") → doji la pivotul monthly 86.00 → candela verde care închide peste high-ul
dojiului (intrare 89.50) → bull reversal pe candela mare → retest → bull
flag → breakout → higher high, higher low. SL 81.45 (−9 %), TP 116.35
(+30 %), atins într-o lună.
@@ -17,12 +18,38 @@ CE E REAL ȘI CE E CONSTRUIT (obligatoriu în legendă)
higher high/higher low), `18_2025-09-23.md:716-719`; „al doilea test al
pivotului monthly, fără low nou" și „corp mic exact pe pivotul monthly:
indecizie la suport", `18_2025-09-23.md:710-712`.
+- REZOLVAT (19.09.2026): transcrierea și capitolul spun și „triple bottom"
+ (`:716-719`), și „al doilea test al pivotului monthly" (`:710-712`). Nu se
+ contrazic: sunt trei minime de PREȚ, dar nivelul 86.00 e atins doar de două
+ ori. Versiunea dinainte desena doar două minime și contrazicea „triple
+ bottom"; seria a fost reconstruită cu TREI minime aproape egale (82.40 /
+ 82.00 / 81.45), dintre care doar primele două vin după o urcare peste 86.00.
+ Între minimul 2 și minimul 3 prețul se ridică doar până la 85.80 — sub
+ pivot, deci al treilea minim nu e un test nou. Generatorul asertează
+ explicit: 3 minime locale în zona 81-83 și 2 atingeri ale lui 86.00.
- CONSTRUITE de sumarizator (nu prețuri de curs): pivot 86.00, intrare
89.50, SL 81.45, TP 116.35 — marcate ca „nivelurile sunt ale mele"
(`18_2025-09-23.md:713`). Legenda diagramei trebuie să spună exact asta.
- Materialul nu dă niciun exemplu numeric complet de la intrare la ieșire
(`13_strategia_3_byh.md:298-308`; `byh.md:256-280`).
+ADNOTĂRI (patru zone numerotate, ca subsecțiunile capitolului)
+- Zona 1 (barele 0-31): „1. setup: scădere lungă, apoi trei minime aproape
+ egale — pivotul 86.00 atins de două ori"; Zona 2 (33-34): „2. doji + confirmare"; Zona 3 (35-54):
+ „3. trendul pornește"; Zona 4 (55-56): „4. ținta".
+- Marcaje sub minimele 1 și 2 („test 1 · 82.40", „test 2 · 82.00") — doar
+ ele sunt atingeri ale pivotului; al treilea minim e numit de săgeata stopului.
+- Săgeți: retestul („retest: cade înapoi pe intrare și ține — trendul e
+ sănătos"), bull flag-ul („aici se odihnește, nu se strică: bull flag, pe
+ volum mic"), intrarea („aici intri, la close 89.50: abia acum știi că au
+ câștigat cumpărătorii"), stopul, pe al treilea minim („al treilea minim:
+ 81.45, fără o nouă atingere a pivotului, aici pui stopul, sub minime"), rezultatul („a ajuns la 116.35: +30 % într-o
+ lună, cam 3,3R din cât ai riscat").
+- Linie punctată pentru ținta 1:1 (97.55), cu nota de dedesubt: acolo vinzi
+ o treime, restul merge mai departe.
+- Fără trimiteri fișier:linie în SVG; legenda „procentele sunt ale cursului,
+ prețurile sunt puse de sumarizator" rămâne.
+
NIVELURILE ȘI ETICHETELE (cu trimitere)
- „pivot monthly 86.00" (`18_2025-09-23.md:615`)
- „intrare 89.50" (`18_2025-09-23.md:619`)
@@ -30,22 +57,37 @@ NIVELURILE ȘI ETICHETELE (cu trimitere)
- „TP +30 % = 116.35" (`18_2025-09-23.md:620-621`)
Etichete marcate „(construit)" sau „(procent real)" în legendă.
-STRUCTURA SERIEI (49 candele daily, open = close-ul precedentului)
-Sugestie de close-uri:
-Etapa downtrend + triple bottom (0-33):
+STRUCTURA SERIEI (57 candele daily, open = close-ul precedentului)
+Seria e reconstruită după textul capitolului (19.09.2026): triple bottom cu
+DOUĂ atingeri ale pivotului („al doilea test"), 21 de bare de la confirmare la +30 % („într-o
+lună"), retest al intrării la bara 37 și bull flag la barele 42-45. Corpul
+dojiului stă pe pivot (o 86.10, c 86.00, h 86.80, l 84.90), nu cu un dolar
+sub el. Nivelurile din capitol rămân: 86.00 / 89.50 / 81.45 / 97.55 / 116.35.
+Close-uri:
[108.0, 106.5, 105.0, 103.0, 101.5, 100.0, 98.0, 96.5, 95.0, 93.5,
- 92.5, 91.0, 89.5, 87.5, 85.5, 82.80, 84.5, 86.5, 88.0, 88.50,
- 87.0, 85.0, 83.0, 81.50, 83.5, 85.5, 87.0, 87.80, 87.0, 85.0,
- 83.0, 81.60, 85.00,
- 84.90, # 33 = doji (o 85.00, c 84.90, h 86.30, l 84.20); straddlează 86.00
- 89.50, # 34 = confirmare, close > 86.30 => intrare
- 91.0, 93.0, 92.0, 95.0, 97.0, 96.0, 99.0, 102.0, 101.0, 105.0, 108.0,
- 112.0, 116.35, 118.0]
-Mecioni: barele 15, 23, 31 au low-urile triple bottom-ului — bara 15 low
-82.40, bara 23 low 81.45, bara 31 low 81.45; bara 32 (revenirea) wd=0.10
-(low 81.50, nu sub 81.45); bara 33 (doji) high 86.30, low 84.20; restul
-(0.4, 0.5). Ajustează wick-urile ca low-urile/high-urile cerute la asserts
-să iasă exact (în special min(low[30:35]) == 81.45).
+ 92.5, 91.0, 89.5, 87.5, 85.5,
+ 83.5, 82.80, # 16 = primul test (low 82.40)
+ 84.5, 86.5, 88.0, 88.5, # 17-20 = urcare peste pivot (high 88.90)
+ 87.0, 85.5, 84.0, 82.8, # 21-24 = coborâre prin pivot = testul 2
+ 82.30, # 25 = minimul 2 (low 82.00)
+ 83.5, 84.8, 85.4, 84.2, # 26-29 = pull-up sub pivot (high max 85.80)
+ 83.0, # 30 = alunecă iar, fără test nou
+ 81.90, # 31 = minimul 3 (low 81.45 = stopul)
+ 86.10, # 32 = bara de întoarcere, peste pivot
+ 86.00, # 33 = doji (o 86.10, h 86.80, l 84.90)
+ 89.50, # 34 = confirmare => intrare
+ 92.0, 94.0,
+ 91.0, # 37 = retest (low 89.60, peste intrare)
+ 93.5, 95.5, 98.5, 100.0, # 40 = prima bară peste ținta 1:1
+ 99.2, 99.8, 98.9, 99.5, # 42-45 = bull flag, volum mic
+ 102.5, 104.0, 103.0, 106.0, 108.5, 107.5, 110.5, 112.5, 114.0,
+ 116.35, 117.5] # 55 = +30 %, la 21 de bare de confirmare
+Wick-uri: implicit (0.4, 0.5); bara 16 (0.4, 0.40) => low 82.40; bara 17
+(0.4, 0.00) ca primul picior să rămână minim local; bara 25 (0.4, 0.30) =>
+low 82.00; bara 26 (0.4, 0.00) ca al doilea picior să rămână minim local;
+bara 31 (0.4, 0.45) => low 81.45; bara 32 (0.4, 0.00) ca să nu coboare sub stop; bara 33
+(0.70, 1.10) = doji; bara 35 (0.4, 0.00) și bara 37 (0.4, 1.40) => retestul
+atinge 89.60 și ține.
CE VERIFICĂ GENERATORUL CU assert
1. pivot 86.00 e straddled de doji: low[33] <= 86.00 <= high[33]
@@ -54,13 +96,22 @@ CE VERIFICĂ GENERATORUL CU assert
confirmare, regula din `13_strategia_3_byh.md:192-193`)
4. round((entry - sl) / entry * 100, 1) == 9.0 (procentul REAL al cursului)
5. round((tp - entry) / entry * 100, 1) == 30.0 (procentul REAL al cursului)
-6. triple bottom: există trei minime locale succesive sub 86.00, în zona
- 81-83, al doilea și al treilea fără low nou semnificativ față de primul
-7. există i>34 cu high[i] >= 116.35
+6. triple bottom cu două teste: exact TREI minime locale sub 86.00, în zona
+ 81-83 (barele 16, 25 și 31), diferența dintre ele <= 2 % („aproape
+ egale"); dintre ele, exact DOUĂ sunt atingeri ale pivotului 86.00 —
+ minimele precedate de un high >= 86.00 (barele 16 și 25). Între 16 și 25
+ prețul urcă peste 86.00 + 1.5; între 25 și 31 maximul rămâne sub 86.00,
+ deci al treilea minim nu e un test nou.
+7. close[55] == 116.35, nimic peste 116.35 înainte, și 20 <= 55-34 <= 22
+ („într-o lună")
+9. retestul (bara 37) atinge 89.50-90.00 și nicio bară de după confirmare nu
+ închide/coboară sub intrare
+10. bull flag (42-45): amplitudine <= 2.5, fără high nou față de bara 41,
+ breakout pe bara 46, volum sub medie
8. doji: abs(o-c) <= 0.10*(h-l)
-STIL: gap=15, body_w=9, left=44, width=760, vol_h=70 (volumul confirmă:
+STIL: gap=15, body_w=9, left=44, width=990, vol_h=70 (volumul confirmă:
„cel mai mare volum din serie" e în varianta MICI, nu obligatoriu aici — dar
confirmarea are volum peste medie). scale astfel încât 86.00 → 116.35 să
încapă; linie punctată pentru pivotul 86.00. Legendă obligatorie:
„Procentele (stop 9 %, +30 %) sunt ale cursului; nivelurile de preț sunt
-construite de sumarizator (18_2025-09-23.md:713)." Fără linie goală în SVG.
+puse de sumarizator." (fără trimitere fișier:linie în SVG) Fără linie goală în SVG.
diff --git a/gen/strategii/buy_doji_labu.svg b/gen/strategii/buy_doji_labu.svg
index a262517..ad08404 100644
--- a/gen/strategii/buy_doji_labu.svg
+++ b/gen/strategii/buy_doji_labu.svg
@@ -1,170 +1,222 @@
-
- LABU — daily
-
- pivot monthly 86.00 (construit)
-
- SL 81.45 (construit) = −9 % (procent real)
-
- intrare 89.50 (construit)
-
- TP 116.35 (construit) = +30 % (procent real)
-
- SMA-20
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- volum
- bottom 1 82.40
- bottom 2 81.45
- bottom 3 81.45
- doji
- confirmare (intrare)
- higher high — TP atins
- bara 33 = doji: o 85.00, c 84.90, h 86.30, l 84.20 — corp mic exact pe pivotul monthly 86.00 (indecizie la suport)
- Procentele (stop 9 %, +30 %) sunt ale cursului; nivelurile de preț sunt construite de sumarizator (18_2025-09-23.md:713).
+
+ LABU — daily
+
+ 1. setup: scădere lungă, apoi trei minime aproape egale — pivotul 86.00 atins de două ori
+
+ 2. doji + confirmare
+
+ 3. trendul pornește
+
+ 4. ținta
+
+ pivot lunar 86.00
+
+ stop 81.45 = 9 % sub intrare (procent real)
+
+ intrare 89.50
+
+ ținta 1:1 97.55
+
+ 116.35 = +30 % față de intrare (procent real)
+
+ SMA-20
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ volum
+ test 1 · 82.40
+ test 2 · 82.00
+
+
+ aici intri, la close 89.50:
+ abia acum știi că au câștigat cumpărătorii
+
+
+ al treilea minim: 81.45,
+ fără o nouă atingere a pivotului
+ aici pui stopul, sub minime
+
+
+ retest: cade înapoi pe intrare
+ și ține — trendul e sănătos
+
+
+ aici se odihnește, nu se strică:
+ bull flag, pe volum mic
+
+
+ a ajuns la 116.35: +30 % în 21 de zile de bursă,
+ cam 3,3R din cât ai riscat
+ doji = corp mic (o 86.10, c 86.00): cumpărătorii și vânzătorii sunt la egalitate, exact pe pivotul lunar 86.00
+ ținta 1:1 (97.55) e atinsă pe bara 40: acolo vinzi o treime, restul merge mai departe — de asta nu vinzi tot la 1:1
+ Procentele (stop 9 %, +30 %) sunt ale cursului; nivelurile de preț sunt puse de sumarizator.
\ No newline at end of file
diff --git a/gen/strategii/buy_doji_locatie.py b/gen/strategii/buy_doji_locatie.py
index 89e622b..994199f 100644
--- a/gen/strategii/buy_doji_locatie.py
+++ b/gen/strategii/buy_doji_locatie.py
@@ -3,9 +3,9 @@
Citește `gen/strategii/buy_doji_locatie.spec.txt`. Două panouri alăturate cu
ACEEAȘI serie de 10 bare, pe aceeași scară și aceeași axă de preț (candelele
-stau la aceleași y). Stânga: pivot monthly S1 49.00, la 0,41 % sub low-ul
+stau la aceleași y). Stânga: pivot lunar 49.00, la 0,51 % sub low-ul
dojiului. Dreapta: S1 46.00 și PP 53.50 — cel mai apropiat nivel sub doji e
-la 6,96 %. Nivelurile se desenează de mână, cu coordonate absolute (liniile
+la 7,07 %. Nivelurile se desenează de mână, cu coordonate absolute (liniile
din Chart pornesc mereu de la x=24, ar trece peste panoul vecin).
Rulare: python3 gen/strategii/buy_doji_locatie.py
@@ -13,13 +13,13 @@ Rulare: python3 gen/strategii/buy_doji_locatie.py
import re
import sys
sys.path.insert(0, "/workspace/atm-curs-dl")
-from svg_candles import Chart, Mark, from_closes # noqa: E402
+from svg_candles import Arrow, Chart, Mark, from_closes # noqa: E402
OUT = "/workspace/atm-curs-dl/gen/strategii/buy_doji_locatie.svg"
CLOSES = [52.20, 51.40, 50.60, 49.65, 49.60, 49.90, 50.40, 50.80, 51.10, 51.50]
wicks = [(0.15, 0.15)] * len(CLOSES)
-wicks[4] = (0.45, 0.40) # doji: h 50.10 / l 49.20
+wicks[4] = (0.45, 0.35) # doji: h 50.10 / l 49.25
cds = from_closes(CLOSES, wicks=wicks, start=53.00)
doji = cds[4]
S1_DA, S1_NU, PP_NU = 49.00, 46.00, 53.50
@@ -28,16 +28,16 @@ NIVELURI_NU = [S1_NU, PP_NU]
# --- assert-urile din spec ---------------------------------------------------
for i in range(1, len(cds)): # 1
assert cds[i].o == cds[i - 1].c, f"open[{i}] != close[{i-1}]"
-assert abs(doji.h - 50.10) < 1e-9 and abs(doji.l - 49.20) < 1e-9
+assert abs(doji.h - 50.10) < 1e-9 and abs(doji.l - 49.25) < 1e-9
assert abs(doji.o - doji.c) <= 0.10 * (doji.h - doji.l), "bara 4 nu e doji"
assert cds[6].c > doji.h, "bara 6 nu confirmă"
d_da = round((doji.l - S1_DA) / S1_DA * 100, 2) # 2
-assert d_da == 0.41 and d_da <= 2
+assert d_da == 0.51 and d_da <= 2
d_nu = round((doji.l - S1_NU) / S1_NU * 100, 2) # 3
-assert d_nu == 6.96 and d_nu > 2 and (PP_NU - doji.h) > 0
+assert d_nu == 7.07 and d_nu > 2 and (PP_NU - doji.h) > 0
assert not any(S1_NU < p < PP_NU for p in NIVELURI_NU)
d_pp = round((PP_NU - doji.l) / doji.l * 100, 2) # PP e deasupra, mai departe
-assert d_pp == 8.74 and d_pp > d_nu
+assert d_pp == 8.63 and d_pp > d_nu
# --- layout: două panouri, aceeași axă --------------------------------------
BLUE, GREEN, RED, ORANGE, GREY = "#1a5fb4", "#188a3e", "#c00", "#b06000", "#555"
@@ -45,61 +45,67 @@ W, PW = 760, 350 # lățime totală, lățime panou
PX = [24, 24 + PW + 28] # x-ul stâng al fiecărui panou
SCALE, TOP = 36.0, 96 # px/unitate și y-ul celui mai mare preț
+ARR = {
+ PX[0]: Arrow(8, "pivot lunar: nivel urmărit\nde mulți — aici apar\ncumpărătorii",
+ price=S1_DA, where="below", color=BLUE),
+ PX[1]: Arrow(4, "aceeași formă, dar nimic sub el\nnu are cine să-l susțină",
+ where="below", color=RED, dx=100),
+}
charts = []
for px in PX:
ch = Chart(candles=cds, marks=[Mark(4, "doji", ORANGE, "above", dy=-16),
Mark(6, "confirmare", GREEN, "above", dy=-26)],
+ arrows=[ARR[px]],
scale=SCALE, gap=17, body_w=10, left=px + 34, width=px + PW, top=TOP)
- body = ch.render()
+ # `_render()`, nu `render()`: al doilea restaurează top/left, iar zonele și
+ # etichetele desenate de mână ar pica deplasate față de body-ul randat
+ body = ch._render()
+ assert f'viewBox="0 0 {px + PW} ' in body, "textul unei săgeți iese din panou"
+ assert (ch.top, ch.left) == (TOP, px + 34), "Chart a mutat graficul: panourile s-ar dezalinia"
charts.append((ch, body.split(">", 1)[1].rsplit("", 1)[0].strip()))
(ch_da, body_da), (ch_nu, body_nu) = charts
assert ch_da.scale == ch_nu.scale and ch_da._pmax == ch_nu._pmax, "scări diferite" # 4
y = ch_da._y # axa e comună: același y pe ambele panouri
-def level(px, price, text, color):
+def level(px, price, text, color, below=False):
yy = y(price)
+ ty, anchor, tx = (yy + 14, "start", px + 6) if below else (yy - 6, "end", px + PW - 4)
return [f'',
- f'{text}']
+ f'{text}']
-def arrow(x, y1, y2, color): # săgeată dublă verticală între y1 și y2
- return [f'',
- f'',
- f'']
+def zone(px, p_top, p_bot, label, color, label_at_bottom=False):
+ """Dreptunghi de evidențiere pe toată lățimea panoului, între două prețuri."""
+ y0, y1 = y(p_top), y(p_bot)
+ ly, anchor, lx = ((y1 - 12, "start", px + 8) if label_at_bottom
+ else (y0 - 6, "end", px + PW - 4))
+ return [f'',
+ f'{label}']
-xd_da, xd_nu = ch_da._cx(4), ch_nu._cx(4)
-y_low, y_bot = y(doji.l), y(S1_NU)
-ly = y(S1_DA) + 24 # textul de distanță, sub linia 49.00
+y_bot = y(S1_NU)
parts = [
f'',
f''
- f'Doji la nivel vs. doji în aer — daily, pivoți monthly (exemplu construit)',
+ f'Doji la nivel vs. doji în aer — daily, pivoți lunari (exemplu construit)',
f'DA — doji la nivel',
f'NU — doji în aer',
f'',
- *level(PX[0], S1_DA, "pivot monthly S1 49.00", BLUE),
- *level(PX[1], PP_NU, "PP 53.50", BLUE),
- *level(PX[1], S1_NU, "S1 46.00", BLUE),
+ *zone(PX[0], doji.l, S1_DA, f"{d_da:.2f} % — la nivel".replace(".", ","), GREEN),
+ *zone(PX[1], doji.l, S1_NU, f"gol de {d_nu:.2f} % sub doji — doji în aer".replace(".", ","),
+ RED, label_at_bottom=True),
+ *level(PX[0], S1_DA, "pivotul lunar 49.00", BLUE, below=True),
+ *level(PX[1], PP_NU, "pivotul lunar 53.50", BLUE),
+ *level(PX[1], S1_NU, "pivotul lunar 46.00", BLUE, below=True),
body_da, body_nu,
- # DA: low-ul dojiului atinge practic linia
- f'',
- f'low 49.20 = +0,41 % față de 49.00',
- f''
- f'→ la nivel (daily: ≤ 1-2 %)',
- # NU: distanța până la cel mai apropiat nivel
- *arrow(xd_nu, y_low + 3, y_bot - 2, RED),
- f''
- f'cel mai apropiat nivel: 46.00, la 6,96 %',
- f'→ doji în aer',
- f''
- f'(PP 53.50 e deasupra, la 8,74 % de low)',
- f'60 min: prag ≤ 0,5 %, pivoți weekly · '
- f'daily: 1-2 %, pivoți monthly · weekly: pivoți quarterly',
+ f'daily: e „la nivel" dacă low-ul '
+ f'dojiului e la cel mult 1-2 % de pivotul lunar (aici {d_da:.2f} %; în dreapta '
+ f'{d_nu:.2f} %).'.replace("0.51 %", "0,51 %").replace("7.07 %", "7,07 %"),
f'Aceeași serie, aceeași scară; '
f'nivelurile 49.00 / 46.00 / 53.50 sunt construite.',
"",
diff --git a/gen/strategii/buy_doji_locatie.spec.txt b/gen/strategii/buy_doji_locatie.spec.txt
index cab91a1..84b86b1 100644
--- a/gen/strategii/buy_doji_locatie.spec.txt
+++ b/gen/strategii/buy_doji_locatie.spec.txt
@@ -3,39 +3,44 @@ SPEC DIAGRAMĂ — buy_doji_locatie
tip: PRET, două panouri cu ACEEAȘI serie, niveluri diferite
destinație: summaries/17_STRATEGII/01_buy_doji.md, Pasul 4
cheie markdown:
-titlu SVG: „Doji la nivel vs. doji în aer — daily, pivoți monthly (exemplu construit)"
+titlu SVG: „Doji la nivel vs. doji în aer — daily, pivoți lunari (exemplu construit)"
stil: candele lipite (gap 15-18, body_w 9-10), fără volum; open = close anterior.
CE TREBUIE SĂ DEMONSTREZE
-------------------------
Aceeași formă (trend → doji → confirmare), dar în stânga low-ul dojiului e
-la 0,41 % de pivotul monthly (sub pragul de 1-2 % pe daily), iar în dreapta
-cel mai apropiat nivel e la 6,96 % dedesubt. Pattern-ul nu te ajută să vezi
+la 0,51 % de pivotul lunar (sub pragul de 1-2 % pe daily), iar în dreapta
+cel mai apropiat nivel e la 7,07 % dedesubt. Pattern-ul nu te ajută să vezi
diferența; doar măsurătoarea.
SERIA (identică pe ambele panouri, 10 bare)
-------------------------------------------
Seria panoului DA din buy_doji_fereastra.spec.txt, extinsă cu 3 bare:
-close-uri 52.20, 51.40, 50.60, 49.65, 49.60 (doji: o 49.65 h 50.10 l 49.20),
+close-uri 52.20, 51.40, 50.60, 49.65, 49.60 (doji: o 49.65 h 50.10 l 49.25),
49.90, 50.40 (confirmare), 50.80, 51.10, 51.50; open[0] = 53.00; fitile 0.15.
NIVELURI
--------
-Stânga (DA): „pivot monthly S1 49.00" (albastru), eticheta de distanță lângă
-doji: „low 49.20 = +0,41 % față de 49.00 → la nivel (daily: ≤ 1-2 %)".
-Dreapta (NU): „S1 46.00" jos, „PP 53.50" sus; eticheta: „cel mai apropiat
-nivel: 46.00, la 6,96 % → doji în aer".
-Sub ambele, text mic: „60 min: prag ≤ 0,5 %, pivoți weekly · daily: 1-2 %,
-pivoți monthly · weekly: pivoți quarterly".
+Stânga (DA): „pivotul lunar 49.00" (albastru) + ZONĂ verde între low-ul dojiului
+(49.25) și 49.00, etichetă „0,51 % — la nivel"; SĂGEATĂ spre linia 49.00:
+„pivot lunar: nivel urmărit / de mulți — aici apar / cumpărătorii".
+Dreapta (NU): „pivotul lunar 46.00" jos, „pivotul lunar 53.50" sus; ZONĂ roșie
+între 49.25 și 46.00, etichetă „gol de 7,07 % sub doji — doji în aer"; SĂGEATĂ
+spre doji: „aceeași formă, dar nimic sub el / nu are cine să-l susțină".
+Sub ambele, un singur rând: „daily: e «la nivel» dacă low-ul dojiului e la cel
+mult 1-2 % de pivotul lunar (aici 0,51 %; în dreapta 7,07 %)". Rândul cu
+pragurile pe 60 min / weekly s-a mutat la nivelul 3.
ASSERT-URI
----------
-1. open[i] == close[i-1]; doji abs(o-c) <= 0.10*(h-l); close[6] > 50.10.
-2. round((49.20-49.00)/49.00*100, 2) == 0.41 și <= 2.
-3. round((49.20-46.00)/46.00*100, 2) == 6.96 și > 2; (53.50-50.10) > 0
+1. open[i] == close[i-1]; doji h 50.10 / l 49.25; abs(o-c) <= 0.10*(h-l);
+ close[6] > 50.10.
+2. round((49.25-49.00)/49.00*100, 2) == 0.51 și <= 2.
+3. round((49.25-46.00)/46.00*100, 2) == 7.07 și > 2; (53.50-50.10) > 0
(niciun nivel între 46.00 și 53.50 pe panoul NU).
4. Scara (px/unitate) identică pe cele două panouri (ca să se vadă că forma e
- aceeași).
+ aceeași); Chart nu are voie să mute graficul (ar dezalinia panourile) — se
+ randează cu `_render()` și se verifică top/left/lățimea.
SURSA
-----
diff --git a/gen/strategii/buy_doji_locatie.svg b/gen/strategii/buy_doji_locatie.svg
index a4671bd..35f5e14 100644
--- a/gen/strategii/buy_doji_locatie.svg
+++ b/gen/strategii/buy_doji_locatie.svg
@@ -1,14 +1,18 @@
- Doji la nivel vs. doji în aer — daily, pivoți monthly (exemplu construit)
+ Doji la nivel vs. doji în aer — daily, pivoți lunari (exemplu construit)DA — doji la nivelNU — doji în aer
+
+ 0,51 % — la nivel
+
+ gol de 7,07 % sub doji — doji în aer
- pivot monthly S1 49.00
+ pivotul lunar 49.00
- PP 53.50
+ pivotul lunar 53.50
- S1 46.00
+ pivotul lunar 46.00
@@ -17,7 +21,7 @@
-
+
@@ -31,6 +35,11 @@
dojiconfirmare
+
+
+ pivot lunar: nivel urmărit
+ de mulți — aici apar
+ cumpărătorii
@@ -39,7 +48,7 @@
-
+
@@ -53,15 +62,10 @@
dojiconfirmare
-
- low 49.20 = +0,41 % față de 49.00
- → la nivel (daily: ≤ 1-2 %)
-
-
-
- cel mai apropiat nivel: 46.00, la 6,96 %
- → doji în aer
- (PP 53.50 e deasupra, la 8,74 % de low)
- 60 min: prag ≤ 0,5 %, pivoți weekly · daily: 1-2 %, pivoți monthly · weekly: pivoți quarterly
+
+
+ aceeași formă, dar nimic sub el
+ nu are cine să-l susțină
+ daily: e „la nivel" dacă low-ul dojiului e la cel mult 1-2 % de pivotul lunar (aici 0,51 %; în dreapta 7,07 %).Aceeași serie, aceeași scară; nivelurile 49.00 / 46.00 / 53.50 sunt construite.
\ No newline at end of file
diff --git a/gen/strategii/buy_doji_perechi_tf.py b/gen/strategii/buy_doji_perechi_tf.py
index 2951c5c..c8e19b3 100644
--- a/gen/strategii/buy_doji_perechi_tf.py
+++ b/gen/strategii/buy_doji_perechi_tf.py
@@ -133,14 +133,28 @@ def candles(bars, x0, gap, yf):
out.append('')
+ 'markerHeight="7" orient="auto">'
+ '')
text(20, 22, "Aceeași regulă pe trei perechi — exemplu construit (fără ticker)", bold=True, size=14)
+def zone(x0, y0_, x1, y1, color):
+ out.append(f'')
+
+
+def bracket(x, ya, yb_, color, label):
+ """Paranteză verticală între două prețuri, cu eticheta la dreapta."""
+ out.append(f'')
+ text(x + 5, (ya + yb_) / 2 + 4, label, fill=color, bold=True)
+
+
y0 = 40
-ROW_H = 290
for key, f, tf_big, tf_small, cap, exp in ROWS:
big, small = scaled(BIG_A, f), scaled(SMALL_A, f)
g = check(key, big, small, exp)
+ first = key == "A"
# titlu rând + subtitluri
text(20, y0 + 14, f"{key}. {tf_big} → {tf_small}", bold=True, size=13)
@@ -155,60 +169,79 @@ for key, f, tf_big, tf_small, cap, exp in ROWS:
yb = lambda p, t=top, m=bmax, s=bs: t + (m - p) * s # noqa: E731
ys = lambda p, t=top, m=smax, s=ss: t + (m - p) * s # noqa: E731
- # TF mare
+ # TF mare: zonă peste doji + confirmare, eticheta dedesubt
+ zx0, zx1 = BX0 + 9 * BGAP - 9, BX0 + 10 * BGAP + 9
+ zone(zx0, yb(big[10][1]) - 5, zx1, yb(g["dl"]) + 5, GREEN)
+ text((zx0 + zx1) / 2, yb(g["dl"]) + 20, "doji + confirmare:", fill=GREEN, anchor="middle", bold=True)
+ text((zx0 + zx1) / 2, yb(g["dl"]) + 34, "aici se decide direcția", fill=GREEN,
+ anchor="middle", bold=True)
hline(BX0 - 16, BX0 + 10 * BGAP + 12, yb(g["dh"]), GRAY, "3 3")
text(BX0 - 14, yb(g["dh"]) + 14, f"high doji {tf_big} {g['dh']:.2f}", fill="#666")
candles(big, BX0, BGAP, yb)
- text(BX0 + 9 * BGAP, yb(g["dl"]) + 15, "doji", fill="#b06000", anchor="middle", bold=True)
- text(BX0 + 10 * BGAP - 4, yb(big[10][2]) + 15, "confirmare: long permis",
- fill=GREEN, bold=True)
- # zoom: de la close-ul confirmării mari la open-ul primei bare mici (același preț)
+ # săgeata-zoom: de la close-ul confirmării mari la open-ul primei bare mici (același preț)
x1, y1 = BX0 + 10 * BGAP + 8, yb(g["conf"])
x2, y2 = SX0 - 10, ys(g["conf"])
out.append(f'')
- text(300, y1 + 2, f"zoom pe {tf_small},", fill="#555")
- text(300, y1 + 16, "după confirmare", fill="#555")
+ text(290, y1 + 2, "apoi cobori", fill="#555")
+ text(290, y1 + 16, "pe graficul mic", fill="#555")
# TF mic: niveluri
y_dh, y_e, y_sl, y_tp = ys(g["dh"]), ys(g["entry"]), ys(g["sl"]), ys(g["tp"])
assert abs((y_sl - y_e) / (y_e - y_tp) - 1) < 1e-6, f"{key}: SL-intrare != intrare-TP1 în px"
+ # zonă peste doji-ul mic + confirmare, eticheta dedesubt
+ sx0, sx1 = SX0 + 8 * SGAP - 9, SX0 + 9 * SGAP + 9
+ zone(sx0, y_e - 6, sx1, y_sl + 5, GREEN)
+ text((sx0 + sx1) / 2, y_sl + 20, "doji la nivel:", fill=GREEN, anchor="middle", bold=True)
+ text((sx0 + sx1) / 2, y_sl + 34, "aici intri", fill=GREEN, anchor="middle", bold=True)
hline(SL_X1, SL_X2, y_dh, GRAY, "3 3")
hline(SL_X1, SL_X2, y_e, GREEN, "6 3")
hline(SL_X1, SL_X2, y_sl, RED, "6 3")
hline(SL_X1, SL_X2, y_tp, GREEN, "2 3")
- text(COL_X, y_tp + 4, f"TP1 1:1 {g['tp']:.2f}", fill=GREEN, size=11)
+ text(COL_X, y_tp + 4, f"ținta 1:1 {g['tp']:.2f}", fill=GREEN, size=11)
text(COL_X, y_e + 4, f"intrare {g['entry']:.2f}", fill=GREEN, size=11)
text(COL_X, y_dh - 5, f"high doji {g['dh']:.2f}", fill="#666", size=11)
- text(COL_X, y_sl + 12, f"SL {g['sl']:.2f}", fill=RED, size=11)
- text(COL_X, y_sl + 25, "= low 4 candele", fill=RED, size=11)
+ text(COL_X, y_sl + 12, f"stop {g['sl']:.2f}", fill=RED, size=11)
+ if first:
+ text(COL_X, y_sl + 25, "sub ultimele", fill=RED, size=11)
+ text(COL_X, y_sl + 38, "4 candele", fill=RED, size=11)
candles(small, SX0, SGAP, ys)
- # paranteză peste pullback-ul de 8 candele (barele 0..7)
- yb8 = y_tp - 12
- xa, xb = SX0 - 5, SX0 + 7 * SGAP + 5
- out.append(f'')
- text((xa + xb) / 2, yb8 - 5, "pullback: 8 candele", fill="#555", anchor="middle")
- text(SX0 + 8 * SGAP + 4, y_sl + 15, "doji la nivel", fill="#b06000", anchor="end", bold=True)
- text(SX0 + 9 * SGAP - 8, y_e - 5, "intrare", fill=GREEN, anchor="end", bold=True)
-
- # text sub rând
- text(20, top + PLOT_H + 50, cap.format(p=g["pct"], pb=g["b_pct"]), fill=INK, bold=True)
+ if first:
+ # paranteză peste pullback-ul de 8 candele (barele 0..7), doar pe primul rând
+ yb8 = y_tp - 12
+ xa, xb = SX0 - 5, SX0 + 7 * SGAP + 5
+ out.append(f'')
+ text((xa + xb) / 2, yb8 - 5, "pullback: 8 candele", fill="#555", anchor="middle")
+ # parantezele de stop: același setup, măsurat pe TF mare și pe TF mic
+ bx = zx1 + 8
+ bracket(bx, yb(g["b_entry"]), yb(g["b_sl"]), RED, f"stop {g['b_pct']:.2f} %".replace(".", ","))
+ kx = SX0 + 9 * SGAP + 14
+ bracket(kx, y_e, y_sl, RED, f"stop {g['pct']:.2f} %".replace(".", ","))
+ ratio = g["b_pct"] / g["pct"]
+ assert 4 <= ratio < 4.5, f"raportul stopurilor {ratio:.2f} nu e „de 4 ori”"
+ # săgeata dintre ele, sub rând
+ ya = top + PLOT_H + 58
+ out.append(f'')
+ text((bx + kx) / 2 + 20, ya + 17, "același setup, stop de 4 ori mai mic", fill=RED,
+ anchor="middle", bold=True)
+ text((bx + kx) / 2 + 20, ya + 31, "→ de 4 ori mai multe acțiuni la același risc", fill=RED,
+ anchor="middle", bold=True)
print(f"{key}: doji mare {g['dh']:.2f}/{g['dl']:.2f} conf {g['conf']:.2f} | intrare {g['entry']:.2f} "
f"SL {g['sl']:.2f} risc {g['risk']:.2f} ({g['pct']:.2f} %) TP1 {g['tp']:.2f} | "
f"doar mare: risc {g['b_risk']:.2f} ({g['b_pct']:.2f} %)")
- y0 += ROW_H
+ y0 += 320 if first else 270
if key != "C":
out.append(f'')
-text(20, y0 + 4, "Cifre construite, aceeași formă scalată pe trei perechi.", fill="#555")
-text(20, y0 + 21, "Procentele reale depind de simbol (LABU daily: stop 9 %).", fill="#555")
-H = y0 + 32
+H = y0 - 20
svg = (f'\n ' + "\n ".join(out) + "\n")
assert "\n\n" not in svg
+assert ".md:" not in svg and "Surse" not in svg, "trimiteri la surse în SVG"
OUT.write_text(svg, encoding="utf-8")
print(f"scris {OUT} ({W}x{H:.0f})")
diff --git a/gen/strategii/buy_doji_perechi_tf.spec.txt b/gen/strategii/buy_doji_perechi_tf.spec.txt
index 870834a..6d82100 100644
--- a/gen/strategii/buy_doji_perechi_tf.spec.txt
+++ b/gen/strategii/buy_doji_perechi_tf.spec.txt
@@ -63,15 +63,17 @@ intrare verde; SL roșu; TP1 verde punctat.
ETICHETE
--------
Titlu rând: A „weekly → daily" B „monthly → weekly" C „daily → 60 min"
-Pe graficul mare: Mark(9, "doji"), Mark(10, "confirmare: long permis").
-Pe graficul mic: Mark(8, "doji la nivel"), Mark(9, "intrare"),
- Level SL „SL = low 4 candele", Level TP „TP1 1:1".
-Sub fiecare rând, un text de o linie:
- A: „stop 1.13 % (pe weekly ar fi 4.77 %) · 8 candele = 8 zile de bursă"
- B: „stop 2.59 % (pe monthly ar fi 10.85 %) · 8 candele = 8 săptămâni"
- C: „stop 0.47 % (pe daily ar fi 1.98 %) · 8 candele ≈ 1,2 zile de bursă (7 bare/zi)"
-Legendă jos: „Cifre construite, aceeași formă scalată pe trei perechi.
-Procentele reale depind de simbol (LABU daily: stop 9 %)."
+Pe graficul mare: zonă peste doji + confirmare, etichetată dedesubt
+ „doji + confirmare: aici se decide direcția".
+Pe graficul mic: zonă peste doji + confirmare, etichetată „doji la nivel:
+ aici intri"; Level „stop
" (pe rândul A și „sub ultimele 4 candele"),
+ Level „ținta 1:1
", Level „intrare
".
+Săgeata-zoom dintre grafice: „apoi cobori pe graficul mic".
+Doar pe rândul A: paranteză de stop pe TF-ul mare („stop 4,77 %") și pe cel
+mic („stop 1,13 %"), cu o săgeată între ele: „același setup, stop de 4 ori
+mai mic → de 4 ori mai multe acțiuni la același risc".
+Restul cifrelor (praguri, „8 candele" pe fiecare pereche, cât ții) stau în
+tabelul din capitol, nu în SVG: diagrama nu repetă tabelul.
ASSERT-URI OBLIGATORII ÎN GENERATOR
-----------------------------------
diff --git a/gen/strategii/buy_doji_perechi_tf.svg b/gen/strategii/buy_doji_perechi_tf.svg
index 75d03d6..b38e7e9 100644
--- a/gen/strategii/buy_doji_perechi_tf.svg
+++ b/gen/strategii/buy_doji_perechi_tf.svg
@@ -1,9 +1,12 @@
-
-
+
+ Aceeași regulă pe trei perechi — exemplu construit (fără ticker)A. weekly → dailyweekly: direcțiadaily: intrarea
+
+ doji + confirmare:
+ aici se decide direcțiahigh doji weekly 105.20
@@ -28,20 +31,22 @@
- doji
- confirmare: long permis
- zoom pe daily,
- după confirmare
+ apoi cobori
+ pe graficul mic
+
+ doji la nivel:
+ aici intri
- TP1 1:1 107.50
+ ținta 1:1 107.50intrare 106.30high doji 105.20
- SL 105.10
- = low 4 candele
+ stop 105.10
+ sub ultimele
+ 4 candele
@@ -74,163 +79,161 @@
pullback: 8 candele
- doji la nivel
- intrare
- stop 1.13 % (pe weekly ar fi 4.77 %) · 8 candele = 8 zile de bursă
-
- B. monthly → weekly
- monthly: direcția
- weekly: intrarea
-
- high doji monthly 113.00
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- doji
- confirmare: long permis
-
- zoom pe weekly,
- după confirmare
-
-
-
-
- TP1 1:1 118.75
- intrare 115.75
- high doji 113.00
- SL 112.75
- = low 4 candele
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- pullback: 8 candele
- doji la nivel
- intrare
- stop 2.59 % (pe monthly ar fi 10.85 %) · 8 candele = 8 săptămâni
-
- C. daily → 60 min
- daily: direcția
- 60 min: intrarea
-
- high doji daily 102.08
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- doji
- confirmare: long permis
-
- zoom pe 60 min,
- după confirmare
-
-
-
-
- TP1 1:1 103.00
- intrare 102.52
- high doji 102.08
- SL 102.04
- = low 4 candele
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- pullback: 8 candele
- doji la nivel
- intrare
- stop 0.47 % (pe daily ar fi 1.98 %) · 8 candele ≈ 1,2 zile de bursă (7 bare/zi)
- Cifre construite, aceeași formă scalată pe trei perechi.
- Procentele reale depind de simbol (LABU daily: stop 9 %).
+
+ stop 4,77 %
+
+ stop 1,13 %
+
+ același setup, stop de 4 ori mai mic
+ → de 4 ori mai multe acțiuni la același risc
+
+ B. monthly → weekly
+ monthly: direcția
+ weekly: intrarea
+
+ doji + confirmare:
+ aici se decide direcția
+
+ high doji monthly 113.00
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ apoi cobori
+ pe graficul mic
+
+ doji la nivel:
+ aici intri
+
+
+
+
+ ținta 1:1 118.75
+ intrare 115.75
+ high doji 113.00
+ stop 112.75
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ C. daily → 60 min
+ daily: direcția
+ 60 min: intrarea
+
+ doji + confirmare:
+ aici se decide direcția
+
+ high doji daily 102.08
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ apoi cobori
+ pe graficul mic
+
+ doji la nivel:
+ aici intri
+
+
+
+
+ ținta 1:1 103.00
+ intrare 102.52
+ high doji 102.08
+ stop 102.04
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/gen/strategii/buy_doji_scaner.py b/gen/strategii/buy_doji_scaner.py
index e1cf6f3..908bd16 100644
--- a/gen/strategii/buy_doji_scaner.py
+++ b/gen/strategii/buy_doji_scaner.py
@@ -27,24 +27,21 @@ WIDTHS = [300, 280, 260, 240, 220, 200]
# (linii în cutie, [(tip, text)]); tip: "n" = notă gri, "c" = capcană
BOXES = [
(["Univers:", "All Stocks (~13.000)", "S&P 500 · Russell 3000"],
- [("n", "mai mare = găsește mai mult, durează mai mult")]),
+ [("n", "mai mare = mai mulți candidați, scanare mai lentă")]),
(["Trigger: jbtgrshdATM", "BDATM = 0", "(Sell Doji: SDATM = 0)"],
- [("c", "Interval = TF-ul scanerului, setat manual"),
- ("c", "0 = ultima candelă închisă; 1 = cu o zi în urmă")]),
+ [("c", "intervalul nu se aliniază singur cu TF-ul: îl pui de mână"),
+ ("c", "vede doar candela închisă: daily rulat la prânz arată ieri")]),
(["Nivel: RSV1ATM", "% of PP ≤ 0.5 (60 min)", "% of PP ≤ 2 (daily)"],
[("n", "Pivot Interval: 2 = weekly (pe 60 min), 3 = monthly (pe daily)"),
- ("c", "RS/SR = 1 la filtrare; cu 100, pragul se scrie ×100")]),
+ ("c", "RS/SR: 1 la filtrare, 100 doar la afișare")]),
(["Lichiditate: Volume Colors", "Average Volume ≥ 500.000",
"Average Length = 10"],
[("n", "volum daily, indiferent de TF-ul scanerului")]),
(["Opțional:", "Since Bear ≥ 8", "Bull Reversal = 1", "RVOL doar Display"],
- [("c", "Bear Reversal e selectat implicit — comută pe Bull"),
- ("c", "în zile extreme, pragurile rigide golesc lista")]),
+ [("c", "pornește pe Bear Reversal: comută pe Bull")]),
(["Lista de candidați", "→ o verifici pe grafic", "(pașii 2-6).",
"Scanerul nu e undă verde."], []),
]
-FOOT = ("Delay: scanerul vede candela închisă — daily rulat la 12:00 "
- "arată triggerul de ieri.")
def est_w(s, fs, bold=False):
@@ -94,10 +91,18 @@ for i, (lines, notes) in enumerate(BOXES):
assert x0 >= x + 6 and x1 <= x + w - 6, f"textul nu încape în cutia {i}: {ln!r}"
if note_lines:
assert x + w + 24 <= NOTE_X, f"cutia {i} prea aproape de note"
- svg.append(f'')
ny = y + (h - n_note * LH) / 2 + 12
for group in note_lines:
+ kind0 = group[0][0]
+ cy = ny - 4
+ # capcana arată spre filtrul pe care îl strică (vârful pe cutie)
+ col = RED if kind0 == "c" else "#bbbbbb"
+ dash = '' if kind0 == "c" else ' stroke-dasharray="3 3"'
+ svg.append(f'')
+ if kind0 == "c":
+ svg.append(f'')
for kind, ln in group:
put(NOTE_X, ny, ln, NOTE_FS, RED if kind == "c" else GRAY)
ny += LH
@@ -108,10 +113,7 @@ for i, (lines, notes) in enumerate(BOXES):
f'L {BOX_CX} {y + 14:g} Z" fill="{BLUE}"/>')
y += 18
-y += 30
-for j, ln in enumerate(textwrap.wrap(FOOT, 88)):
- put(20, y + j * LH, ln, 12.5, BLACK)
-H = int(y + LH + 20)
+H = int(y + 20)
# ------------------------------------------------------------------ verificare
assert all(a > b for a, b in zip(WIDTHS, WIDTHS[1:])), "lățimile nu scad strict"
@@ -133,6 +135,7 @@ out = "\n".join([
f'ordinea filtrelor și capcanele">',
f'',
*svg, ""]) + "\n"
+assert ".md:" not in out and "Surse" not in out, "trimiteri la surse în SVG"
assert "\n\n" not in out and not any(not ln.strip() for ln in out.splitlines()), "linie goală"
OUT.write_text(out, encoding="utf-8")
print(f"scris {OUT} ({W}x{H}), verificare OK")
diff --git a/gen/strategii/buy_doji_scaner.spec.txt b/gen/strategii/buy_doji_scaner.spec.txt
index 61fe780..25d9e6e 100644
--- a/gen/strategii/buy_doji_scaner.spec.txt
+++ b/gen/strategii/buy_doji_scaner.spec.txt
@@ -5,28 +5,28 @@ destinație: summaries/17_STRATEGII/01_buy_doji.md, Pasul 1
cheie markdown:
titlu SVG: „Scanerul Buy Doji în TradeStation — ordinea filtrelor și capcanele"
stil: cutii dreptunghiulare stivuite, tot mai înguste (pâlnie), lățime 640;
- în dreapta fiecărei cutii, o notă roșie „capcană" unde există.
+ în dreapta fiecărei cutii, o notă roșie „capcană" unde există,
+ legată de cutie printr-o săgeată roșie (vârful pe filtrul
+ pe care îl strică).
Fără cifre de „câte simboluri rămân" — materialul nu le dă.
CUTIILE (text exact, de sus în jos)
-----------------------------------
1. „Univers: All Stocks (~13.000) · S&P 500 · Russell 3000"
- notă gri: „mai mare = găsește mai mult, durează mai mult"
+ notă gri: „mai mare = mai mulți candidați, scanare mai lentă"
2. „Trigger: jbtgrshdATM → BDATM = 0 (Sell Doji: SDATM = 0)"
- capcană: „Interval = TF-ul scanerului, setat manual"
- capcană: „0 = ultima candelă închisă; 1 = cu o zi în urmă"
+ capcană: „intervalul nu se aliniază singur cu TF-ul: îl pui de mână"
+ capcană: „vede doar candela închisă: daily rulat la prânz arată ieri"
3. „Nivel: RSV1ATM → % of PP ≤ 0.5 (60 min) / ≤ 2 (daily)"
notă: „Pivot Interval: 2 = weekly (pe 60 min), 3 = monthly (pe daily)"
- capcană: „RS/SR = 1 la filtrare; cu 100, pragul se scrie ×100"
+ capcană: „RS/SR: 1 la filtrare, 100 doar la afișare"
4. „Lichiditate: Volume Colors → Average Volume ≥ 500.000, Average Length = 10"
notă: „volum daily, indiferent de TF-ul scanerului"
5. „Opțional: Since Bear ≥ 8 · Bull Reversal = 1 · RVOL doar Display"
- capcană: „Bear Reversal e selectat implicit — comută pe Bull"
- capcană: „în zile extreme, pragurile rigide golesc lista"
+ capcană: „pornește pe Bear Reversal: comută pe Bull"
6. (cutie verde, cea mai îngustă) „Lista de candidați → o verifici pe grafic
(pașii 2-6). Scanerul nu e undă verde."
-Sub pâlnie, o linie: „Delay: scanerul vede candela închisă — daily rulat la
-12:00 arată triggerul de ieri."
+Fără linie de subsol: întârzierea scanerului e capcana de la cutia 2.
ASSERT-URI
----------
diff --git a/gen/strategii/buy_doji_scaner.svg b/gen/strategii/buy_doji_scaner.svg
index f45248f..0015111 100644
--- a/gen/strategii/buy_doji_scaner.svg
+++ b/gen/strategii/buy_doji_scaner.svg
@@ -1,57 +1,60 @@
-
-
+
+Scanerul Buy Doji în TradeStationordinea filtrelor și capcaneleUnivers:All Stocks (~13.000)S&P 500 · Russell 3000
-
-mai mare = găsește mai mult, durează mai
-mult
+
+mai mare = mai mulți candidați, scanare mai
+lentăTrigger: jbtgrshdATMBDATM = 0(Sell Doji: SDATM = 0)
-
-capcană: Interval = TF-ul scanerului, setat
-manual
-capcană: 0 = ultima candelă închisă; 1 = cu
-o zi în urmă
+
+
+capcană: intervalul nu se aliniază singur cu
+TF-ul: îl pui de mână
+
+
+capcană: vede doar candela închisă: daily
+rulat la prânz arată ieriNivel: RSV1ATM% of PP ≤ 0.5 (60 min)% of PP ≤ 2 (daily)
-
+Pivot Interval: 2 = weekly (pe 60 min), 3 =monthly (pe daily)
-capcană: RS/SR = 1 la filtrare; cu 100,
-pragul se scrie ×100
+
+
+capcană: RS/SR: 1 la filtrare, 100 doar la
+afișareLichiditate: Volume ColorsAverage Volume ≥ 500.000Average Length = 10
-
+volum daily, indiferent de TF-ul scanerului
-
-Opțional:
-Since Bear ≥ 8
-Bull Reversal = 1
-RVOL doar Display
-
-capcană: Bear Reversal e selectat implicit —
-comută pe Bull
-capcană: în zile extreme, pragurile rigide
-golesc lista
-
-
-Lista de candidați
-→ o verifici pe grafic
-(pașii 2-6).
-Scanerul nu e undă verde.
-Delay: scanerul vede candela închisă — daily rulat la 12:00 arată triggerul de ieri.
+
+Opțional:
+Since Bear ≥ 8
+Bull Reversal = 1
+RVOL doar Display
+
+
+capcană: pornește pe Bear Reversal: comută
+pe Bull
+
+
+Lista de candidați
+→ o verifici pe grafic
+(pașii 2-6).
+Scanerul nu e undă verde.
diff --git a/gen/strategii/buy_doji_sell.py b/gen/strategii/buy_doji_sell.py
index 067690a..dab2ab1 100644
--- a/gen/strategii/buy_doji_sell.py
+++ b/gen/strategii/buy_doji_sell.py
@@ -3,11 +3,12 @@
Citește specificația `gen/strategii/buy_doji_sell.spec.txt`. Seria de 31 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 99.20 /
-100.10, intrare short 99.00, SL 100.10, TP 97.90, trail 96.45, ieșire la 96.60)
+100.10, intrare short 99.00, SL 100.15 = 5 cenți peste high-ul de 4 candele,
+TP 97.85 = 1R de 1.15, trail 96.45, ieșire la 96.60)
să iasă exact din serie, nu doar din etichetă.
"""
import sys; sys.path.insert(0, "/workspace/atm-curs-dl")
-from svg_candles import Chart, Level, Mark, from_closes
+from svg_candles import Arrow, Chart, Level, Zone, from_closes
# --- seria: 31 de close-uri, exact ca în specificație -----------------------
closes = [
@@ -46,17 +47,16 @@ assert vols[14] > media_vol and vols[16] > media_vol, "volum peste medie pe doji
cds = from_closes(closes, wicks=wicks, vols=vols, start=closes[0])
-# 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 99.40 peste low-ul dojiului 99.20 — NU confirmă"
-cds[16].note = "bara 16: close 99.00 sub 99.20 — confirmare (intrare short)"
doji, wait, conf = cds[14], cds[15], cds[16]
-entry, sl, tp = 99.00, 100.10, 97.90
+# stopul 5 cenți DEASUPRA high-ului, oglinda pasului 5 (51.60 -> 51.55 pe long);
+# cifrele vechi (SL 100.10, TP 97.90) puneau stopul exact pe high
+entry, sl, tp = 99.00, 100.15, 97.85
# --- cele 9 assert-uri cerute de specificație ------------------------------
-# 1. SL-ul e exact high-ul ultimelor 4 candele
+# 1. SL-ul e 5 cenți peste high-ul ultimelor 4 candele (100.10)
assert abs(max(cd.h for cd in cds[13:17]) - 100.10) < 1e-9, "max high[13:17] != 100.10"
+assert abs(sl - 100.10 - 0.05) < 1e-9, "SL nu e 5 cenți peste high"
# 2. intrarea e close-ul barei 16
assert abs(conf.c - entry) < 1e-9, f"close[16]={conf.c} != 99.00"
# 3. confirmarea închide sub low-ul dojiului
@@ -64,11 +64,11 @@ assert abs(doji.l - 99.20) < 1e-9 and conf.c < doji.l, "close[16] nu e sub low[1
# 4. bara +1 NU confirmă
assert wait.c >= doji.l, "bara 15 ar confirma, fereastra n-ar avea sens"
# 5. 1:1 exact
-assert abs(abs(sl - entry) - abs(entry - tp)) < 1e-9 and abs((entry - tp) - 1.10) < 1e-9, \
+assert abs(abs(sl - entry) - abs(entry - tp)) < 1e-9 and abs((entry - tp) - 1.15) < 1e-9, \
"risc != recompensă (nu e 1:1 exact)"
# 6. primul low <= TP după intrare e bara 19
primul_tp = next(i for i in range(17, len(cds)) if cds[i].l <= tp)
-assert primul_tp == 19, f"TP 97.90 atins întâi la bara {primul_tp}, nu 19"
+assert primul_tp == 19, f"TP 97.85 atins întâi la bara {primul_tp}, nu 19"
assert abs(cds[18].l - 98.05) < 1e-9 and abs(cds[19].l - 97.55) < 1e-9, "low-urile 18/19 nu ies"
# 7. trailing = high[25] = 96.45; primul close peste el e bara 27; ieșirea e pe profit
trail = cds[25].h
@@ -85,26 +85,43 @@ print(f"doji {doji.l:.2f}/{doji.h:.2f} intrare short {conf.c:.2f} "
f"SL {sl:.2f} TP {tp:.2f} trail {trail:.2f} ieșire bara 27 @ {cds[27].c:.2f}")
# --- desenul ----------------------------------------------------------------
+RED, GREEN, BLUE, ORANGE, GRAY = "#c00", "#188a3e", "#1a5fb4", "#b06000", "#555"
ch = Chart(
candles=cds,
levels=[
- # toate etichetele în dreapta: în stânga sus se suprapuneau cu mark-ul
- # dojiului (SL la y=62, text până în ~x196; doji la y=58, din ~x193)
- Level(sl, "SL 100.10 (high 4 candele)", "#c00"),
- Level(entry, "intrare short 99.00", "#188a3e"),
- Level(tp, "TP 97.90 = 1R", "#1a5fb4"),
- Level(trail, "trail 96.45", "#b06000"),
+ # etichetele în dreapta, dincolo de ultima candelă (width > ultima
+ # candelă); titlul stă în stânga sus, deci nu mai are cu ce să se lovească
+ Level(sl, "stop 100.15", RED),
+ Level(entry, "intrare short 99.00", ORANGE),
+ Level(tp, "ținta 97.85", GREEN),
],
- marks=[
- Mark(14, "doji 99.20 / 100.10", "#b06000", "above"),
- Mark(27, "ieșire pe trail 96.60", "#c00", "above", dy=-6),
+ zones=[
+ Zone(0, 9, "1. creștere de 10 candele", BLUE),
+ Zone(14, 14, "2. doji", ORANGE),
+ # ca unealta „short position" din TradingView: roșu = risc, SUS;
+ # verde = câștig, JOS — pe short totul e întors față de long
+ Zone(16, 19, "", RED, p_top=sl, p_bot=entry),
+ Zone(16, 19, "", GREEN, p_top=entry, p_bot=tp),
+ # linia de trail doar unde contează (înălțime 0 nu se randează)
+ # (de la bara 25), nu pe toată lățimea
+ Zone(25, 30, "", GRAY, p_top=trail, p_bot=trail - 0.02),
],
- scale=30.0, gap=15, body_w=9, left=44, width=640, vol_h=70,
- vol_label="volum", title="Sell Doji — daily (exemplu construit, fără ticker)",
- # scurt: o linie de caption mai lungă de ~90 de caractere iese din viewBox
- # (640px) și se taie la randare
- caption="Intrare short la close sub low-ul dojiului, SL la high 4 candele, "
- "TP 1:1, apoi trail.",
+ arrows=[
+ Arrow(18, "STOPUL SUS, la 100.15 — 5 cenți peste maximul ultimelor 4 candele:\n"
+ "dacă prețul urcă până aici, creșterea continuă și ideea e moartă",
+ where="above", price=sl, color=RED, dx=110),
+ Arrow(16, "AICI INTRI SHORT, la 99.00:\na doua candelă după doji e prima\n"
+ "care se ÎNCHIDE sub low-ul lui (99.20)",
+ where="below", color=ORANGE, dx=-70),
+ Arrow(19, "ținta JOS, la 97.85: tot 1.15, exact cât ai riscat;\naici închizi prima treime",
+ where="right", price=tp, color=GREEN, dx=50, dy=-32),
+ Arrow(27, "trail-ul a coborât după preț (96.45);\nprima închidere peste el: ieși cu restul, 96.60",
+ where="below", color=GRAY, dx=-40),
+ ],
+ scale=45.0, gap=20, body_w=9, left=44, width=790, vol_h=60,
+ vol_label="volum", title="Sell Doji pe short — daily (exemplu construit)",
+ caption="Pe short câștigi când prețul COBOARĂ: stopul stă SUS, ținta JOS, "
+ "trail-ul coboară după preț. Roșu = ce riști, verde = ce câștigi.",
)
svg = ch.render()
diff --git a/gen/strategii/buy_doji_sell.spec.txt b/gen/strategii/buy_doji_sell.spec.txt
index 4943dbc..5b1cc62 100644
--- a/gen/strategii/buy_doji_sell.spec.txt
+++ b/gen/strategii/buy_doji_sell.spec.txt
@@ -1,16 +1,23 @@
TIP: PRET
CHEIE: sell
-TITLU: Sell Doji — daily (exemplu construit, fără ticker)
+TITLU: Sell Doji pe short — daily (exemplu construit)
MARCAJ .md: (Nivel 1, §1.3 Trigger, oglinda short)
CE ARATĂ
Graficul întreg al unui trade Sell Doji short, oglinda ansamblului long:
uptrend de 14 candele, doji, o bară de așteptare care NU confirmă, apoi o
-bară care închide sub low, intrare short la close, SL deasupra high-ului
-ultimelor 4 candele, TP 1:1 în jos, apoi trailing. Panou de volum dedesubt.
+bară care închide sub low, intrare short la close, SL 5 cenți deasupra
+high-ului ultimelor 4 candele, TP 1:1 în jos, apoi trailing. Panou de volum.
+Format tutorial v2: zone de evidențiere („1. creștere de 10 candele",
+„2. doji", cutie roșie = riscul, între intrare și stop, cutie verde = câștigul,
+între intrare și țintă — ca unealta „short position" din TradingView; segment
+gri = linia de trail) și 4 săgeți care spun ce ȘI de ce: stop, intrare, țintă,
+ieșire pe trail. Etichetele nivelurilor în dreapta, dincolo de ultima candelă.
NIVELURILE ȘI ETICHETELE (cu trimitere)
-- doji 99.20-100.10, intrare short 99.00, SL 100.10, TP 97.90 (1:1).
+- doji 99.20-100.10, intrare short 99.00, SL 100.15, TP 97.85 (1:1, 1R = 1.15).
+ SL mutat de la 100.10 (exact pe high) la 100.15, ca oglinda pasului 5 din
+ capitol, unde stopul long stă „puțin sub" low (51.60 -> 51.55).
Sursa principală construiește exact aceste cifre:
`13_strategia_3_byh.md:258-259` („short: doji 99.20–100.10, intrare
99.00, SL 100.10, TP 97.90, trail ~97.30").
@@ -37,16 +44,16 @@ Mecioni (up, down) implicite (0.30, 0.25); excepții:
Volum: peste medie pe doji și pe confirmare (distribuție), apoi normal.
CE VERIFICĂ GENERATORUL CU assert
-1. max(high[13:17]) == 100.10 == SL (cel mai înalt high din 4 candele)
+1. max(high[13:17]) == 100.10 și SL == 100.10 + 0.05 == 100.15
2. close[16] == 99.00 == intrare
3. close[16] < low[14] == 99.20 (confirmarea chiar închide sub low)
4. close[15] >= low[14] (bara +1 NU confirmă)
-5. abs(sl - entry) == abs(entry - tp) == 1.10 (1:1 exact)
-6. primul i>16 cu low[i] <= 97.90 este bara 19 (low[18]=98.05, low[19]=97.55)
+5. abs(sl - entry) == abs(entry - tp) == 1.15 (1:1 exact)
+6. primul i>16 cu low[i] <= 97.85 este bara 19 (low[18]=98.05, low[19]=97.55)
7. linia de trail = high[25] = 96.45; primul close > 96.45 este barul 27
(96.60); și 96.60 < intrare (profit pe short)
8. doji: abs(o-c) <= 0.10*(h-l)
9. trend: close[13] > close[0] + 5
-STIL: gap=15, body_w=9, left=44, width=640, vol_h=70, scale ~30. Etichete
-„SL 100.10 (high 4 candele)", „intrare short 99.00", „TP 97.90 = 1R".
+STIL: gap=20, body_w=9, left=44, width=790, vol_h=60, scale 45. Etichete
+„stop 100.15", „intrare short 99.00", „ținta 97.85".
Fără linie goală în SVG.
diff --git a/gen/strategii/buy_doji_sell.svg b/gen/strategii/buy_doji_sell.svg
index 1c929ff..0d8d9d7 100644
--- a/gen/strategii/buy_doji_sell.svg
+++ b/gen/strategii/buy_doji_sell.svg
@@ -1,111 +1,129 @@
-
- Sell Doji — daily (exemplu construit, fără ticker)
-
- SL 100.10 (high 4 candele)
-
- intrare short 99.00
-
- TP 97.90 = 1R
-
- trail 96.45
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- volum
- doji 99.20 / 100.10
- ieșire pe trail 96.60
- bara 15: close 99.40 peste low-ul dojiului 99.20 — NU confirmă
- bara 16: close 99.00 sub 99.20 — confirmare (intrare short)
- Intrare short la close sub low-ul dojiului, SL la high 4 candele, TP 1:1, apoi trail.
+
+ Sell Doji pe short — daily (exemplu construit)
+
+ 1. creștere de 10 candele
+
+ 2. doji
+
+
+
+
+ stop 100.15
+
+ intrare short 99.00
+
+ ținta 97.85
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ volum
+
+
+ STOPUL SUS, la 100.15 — 5 cenți peste maximul ultimelor 4 candele:
+ dacă prețul urcă până aici, creșterea continuă și ideea e moartă
+
+
+ AICI INTRI SHORT, la 99.00:
+ a doua candelă după doji e prima
+ care se ÎNCHIDE sub low-ul lui (99.20)
+
+
+ ținta JOS, la 97.85: tot 1.15, exact cât ai riscat;
+ aici închizi prima treime
+
+
+ trail-ul a coborât după preț (96.45);
+ prima închidere peste el: ieși cu restul, 96.60
+ Pe short câștigi când prețul COBOARĂ: stopul stă SUS, ținta JOS, trail-ul coboară după preț. Roșu = ce riști, verde = ce câștigi.
\ No newline at end of file
diff --git a/gen/strategii/buy_doji_stop.py b/gen/strategii/buy_doji_stop.py
index c363e30..0db2cc1 100644
--- a/gen/strategii/buy_doji_stop.py
+++ b/gen/strategii/buy_doji_stop.py
@@ -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'',
- f'',
-]
-for i, n in zip(range(11, 15), "4321"):
- extra_top.append(f'{n}')
-extra_top.append(f'(5)')
-yp = yb + 8
-extra_top.append(f'')
-extra_top.append(f''
- f'ultimele 4 candele (3 = doji, 1 = confirmarea)')
-extra_top.append(f''
- 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.')
-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ă
+# --- 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'',
-]
-# 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'") + 1:svg.rindex("")].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'{n}')
+nums.append(f'(5)')
+nums.append(f''
+ f'cu 4 sau cu 5 candele iese la fel: în scădere, low-ul doji-ului e oricum cel mai jos')
+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'pe aici trece al doilea picior')
+H = h_jos + 8
svg = "\n ".join([
- svg_bot.split(">", 1)[0] + ">",
- svg_top.split(">", 1)[1].rsplit("", 1)[0].strip(), *extra_top,
- f'',
- svg_bot.split(">", 1)[1].rsplit("
", 1)[0].strip(), *extra_bot,
+ f'',
+ body_sus, *nums,
+ f'',
+ body_jos, banda,
]) + "\n"
-H = float(re.search(r'viewBox="0 0 \d+ ([\d.]+)"', svg).group(1))
-assert "
-titlu SVG: „Unde pui stopul: 4 candele înapoi, sub structură — exemplu construit"
-stil: ca buy_doji_transe (aceleași bare 0-14, copiate sau importate).
+titluri SVG: sus „Pasul 5 — stopul: numeri 4 candele înapoi de la confirmare";
+ jos „Același trade: stop «la ochi» (sub candela de confirmare) vs stopul din regulă"
+serie: `buy_doji_ansamblu.serie()`, barele 6-16 (același trade ca în tot
+ capitolul: doji 50.10/49.25, intrare 50.40, stop 49.20).
CE TREBUIE SĂ DEMONSTREZE
-------------------------
-Sus: cum numeri cele 4 candele (confirmarea și cele 3 dinainte), și de ce
-în downtrend nu contează dacă numeri 4 sau 5: low-ul dojiului e oricum cel
-mai jos. Jos: același trade, cu un stop pus „la ochi" (52.90, sub intrare
-cu 0.80) față de stopul din regulă (51.55). Piciorul al doilea coboară la
-52.00: lovește stopul la ochi, nu atinge stopul din regulă; apoi prețul
-ajunge la TP1.
+Sus: cum numeri cele 4 candele (confirmarea și cele 3 dinainte) și de ce în
+scădere nu contează dacă numeri 4 sau 5 — low-ul doji-ului e oricum cel mai jos.
+Jos: același trade, cu un stop pus „la ochi" la 49.70 (sub candela de
+confirmare, care are low 49.75) 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ă; apoi prețul ajunge la ținta 51.60.
-SERIA
------
-Barele 0-14: identice cu buy_doji_transe (downtrend, doji 12, +1 13,
-confirmare 14).
-Panoul de sus: doar barele 0-14 (se oprește la intrare).
-Panoul de jos: barele 0-14, apoi:
-15 (53.70, 53.75, 52.70, 52.90)
-16 (52.90, 53.05, 52.00, 52.20) <- al doilea picior, higher low 52.00
-17 (52.20, 53.40, 52.10, 53.30)
-18 (53.30, 54.30, 53.20, 54.20)
-19 (54.20, 55.10, 54.10, 55.00)
-20 (55.00, 56.00, 54.90, 55.95) <- TP1 55.85 atins
+BARELE DE DUPĂ INTRARE (panoul de jos, CONSTRUITE)
+--------------------------------------------------
++1 (50.40, 50.50, 49.85, 49.95)
++2 (49.95, 50.05, 49.50, 49.60) <- al doilea picior, low 49.50
++3 (49.60, 50.35, 49.55, 50.25)
++4 (50.25, 50.90, 50.15, 50.80)
++5 (50.80, 51.35, 50.70, 51.25)
++6 (51.25, 51.80, 51.15, 51.70) <- atinge ținta 51.60
-MARKS / NIVELURI
-----------------
-Sus: numerotare „4 3 2 1" sub barele 11, 12, 13, 14 (1 = confirmarea);
-paranteză „ultimele 4 candele"; Level 51.60 „cel mai jos low: 51.60 (doji)";
-Level 51.55 roșu „SL 51.55".
-Jos: Level 52.90 portocaliu „stop la ochi 52.90 — lovit"; Level 51.55 roșu
-„SL din regulă 51.55 — ținut"; Level 55.85 verde punctat „TP1 55.85";
-Mark(16, "al doilea picior: 52.00, higher low", below).
+ADNOTĂRI
+--------
+Sus: zonă albastră peste cele 4 candele numărate, „numeri 4 candele, începând
+cu confirmarea"; numerele 4 3 2 1 sub ele și (5) sub a cincea; Level „stopul:
+49.20"; săgeți: „cel mai jos low din cele 4: 49.25 (e al doji-ului)",
+„stopul: 49.20, cu 5 cenți mai jos / sub el, ideea că vânzătorii au obosit e
+moartă", „confirmarea: close 50.40 / de aici numeri înapoi".
+Jos: Level-uri 51.60 / 50.40 / 49.70 / 49.20; zonă roșie între 49.70 și 49.20
+peste barele +1 și +2, etichetă „pe aici trece al doilea picior"; săgeți:
+stopul la ochi măturat, al doilea picior 49.50 („fundurile fac des asta"),
+stopul din regulă ține, și „prețul ajunge la țintă / dar numai cu stopul corect
+mai erai în trade".
ASSERT-URI
----------
-1. open[i] == close[i-1] pe barele 15-20; h/l cuprind o/c.
-2. min(l[11:15]) == min(l[10:15]) == 51.60 (4 sau 5 candele: același rezultat).
-3. min(l[15:21]) == 52.00; 52.00 < 52.90 și 52.00 > 51.55.
-4. primul i>14 cu low <= 52.90 este 15; niciun low din 15-20 <= 51.55.
-5. primul i>14 cu high >= 55.85 este 20.
-6. 52.00 > 51.60 (al doilea picior e higher low).
+1. h/l cuprind o/c; open[i] == close[i-1] pe toată seria panoului de jos.
+2. min(low) pe ultimele 4 candele == pe ultimele 5 == 49.25 (low-ul doji-ului);
+ 49.25 - 0.05 == 49.20.
+3. min(low) după intrare == 49.50, cu 49.20 < 49.50 < 49.70.
+4. prima bară de după intrare cu low <= 49.70 e a doua (al doilea picior);
+ nicio bară de după intrare nu are low <= 49.20.
+5. ținta 51.60 e atinsă abia la a 6-a bară de după intrare.
+6. 49.50 > 49.25 (al doilea picior e higher low).
+7. 50.40 - 49.70 == 0.70 < 1.20 (stopul la ochi e mai aproape de intrare) și
+ 49.25 < 49.70 < low-ul candelei de confirmare (49.75).
+8. niciun text nu iese din pânză.
SURSA
-----
-- SL = extrema ultimelor 4 candele de la confirmare: summaries/03_MODUL_2/13_strategia_3_byh.md:192-193;
- în practică swing low-ul: summaries/09_QA/22_2025-08-04.md:23-29.
-- Stopul sub structură, nu „undeva jos"; bottom-ul mai poate face un picior:
+- SL = extrema ultimelor 4 candele de la confirmare: summaries/03_MODUL_2/
+ 13_strategia_3_byh.md:192-193; în practică swing low-ul: summaries/09_QA/
+ 22_2025-08-04.md:23-29.
+- Stopul sub structură; bottom-ul mai poate face un picior:
summaries/08_ANALIZA_ZILNICA/01_buy_doji_si_strategia_mici.md:833-836.
-- Stopul „la ochi" 52.90 și barele 15-20 sunt CONSTRUITE (contrast).
+- Stopul „la ochi" 49.70 și barele de după intrare sunt CONSTRUITE (contrast).
diff --git a/gen/strategii/buy_doji_stop.svg b/gen/strategii/buy_doji_stop.svg
index 66765ae..d3b51c3 100644
--- a/gen/strategii/buy_doji_stop.svg
+++ b/gen/strategii/buy_doji_stop.svg
@@ -1,104 +1,112 @@
-
- Unde pui stopul: 4 candele înapoi, sub structură — exemplu construit
-
- cel mai jos low: 51.60 (doji)
-
- SL 51.55
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- confirmare 53.70
-
-
- 4
- 3
- 2
- 1
- (5)
-
- ultimele 4 candele (3 = doji, 1 = confirmarea)
- Cu 4 sau cu 5 candele iese la fel: în downtrend low-ul dojiului e oricum cel mai jos. SL = 51.60 − 0.05.
-
- Același trade: stop la ochi (0.80 sub intrare) vs stop din regulă
-
- TP1 55.85
-
- intrare 53.70
-
- stop la ochi 52.90 — lovit
-
- SL din regulă 51.55 — ținut
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- al doilea picior: 52.00, higher low
- TP1
- Barele 15-20 și stopul la ochi sunt construite, pentru contrast.
-
+
+ Pasul 5 — stopul: numeri 4 candele înapoi de la confirmare
+
+ numeri 4 candele, începând cu confirmarea
+
+ stopul: 49.20
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ cel mai jos low din cele 4: 49.25
+ (e al doji-ului)
+
+
+ stopul: 49.20, cu 5 cenți mai jos
+ sub el, ideea că vânzătorii
+ au obosit e moartă
+
+
+ confirmarea: close 50.40
+ de aici numeri înapoi
+ 4
+ 3
+ 2
+ 1
+ (5)
+ cu 4 sau cu 5 candele iese la fel: în scădere, low-ul doji-ului e oricum cel mai jos
+
+ Același trade: stop „la ochi” (sub candela de confirmare) vs stopul din regulă
+
+
+ ținta 1: 51.60
+
+ intrarea: 50.40
+
+ stop pus la ochi: 49.70
+
+ stopul din regulă: 49.20
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ stopul pus la ochi (49.70) e măturat
+ era prea aproape de intrare
+
+
+ al doilea picior al fundului: 49.50
+ fundurile fac des asta
+
+
+ stopul din regulă (49.20) ține
+ al doilea picior rămâne peste el
+
+
+ prețul ajunge la țintă
+ dar numai cu stopul corect
+ mai erai în trade
+ Barele de după intrare și stopul la ochi sunt construite, pentru contrast.
+ pe aici trece al doilea picior
\ No newline at end of file
diff --git a/gen/strategii/buy_doji_transe.py b/gen/strategii/buy_doji_transe.py
index 0ff9598..0bbfb25 100644
--- a/gen/strategii/buy_doji_transe.py
+++ b/gen/strategii/buy_doji_transe.py
@@ -1,132 +1,134 @@
-"""Buy Doji — intrarea în trei tranșe (exemplu construit, doji 53.00 / 51.60).
+"""Buy Doji — intrarea în trei tranșe, pe același trade ca restul capitolului.
-Citește specificația `gen/strategii/buy_doji_transe.spec.txt`. Barele 0-14
-sunt aceleași ca în buy_doji_stop.py. Nivelurile de 1/3 și 1/2 se calculează
-din corpul candelei de confirmare, nu se scriu de mână.
+Citește `gen/strategii/buy_doji_transe.spec.txt`. Barele vin din
+`buy_doji_ansamblu.serie()`: doji 50.10/49.25, confirmare cu close 50.40,
+stop 49.20. Nivelurile de 1/3 și 1/2 se calculează din corpul candelei de
+confirmare (open 49.90 → close 50.40), nu se scriu de mână.
+
+Lecția: împărțind intrarea în trei, prețul mediu scade de la 50.40 la
+50.26, deci riscul pe acțiune de la 1.20 la 1.06 — dacă prețul revine.
+Bara de după confirmare coboară la 50.10 și umple ambele ordine limită.
"""
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_transe.svg"
+sys.path.insert(0, "/workspace/atm-curs-dl")
+sys.path.insert(0, str(Path(__file__).parent))
+from svg_candles import 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 identice cu buy_doji_stop.py --------------------------
-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
- (53.70, 53.80, 53.10, 53.45), # 15 umple 1/3 și 1/2
- (53.45, 54.20, 53.35, 54.10),
- (54.10, 54.85, 54.00, 54.70),
- (54.70, 56.00, 54.60, 55.90)] # 18 TP1
-o_, h_, l_, c_ = (list(x) for x in zip(*bars))
+OUT = Path(__file__).with_suffix(".svg")
+S = 12 # prima bară arătată (zoom pe fereastra de intrare)
+GREEN, RED, ORANGE, BLUE, GREY = "#188a3e", "#c00", "#b06000", "#1a5fb4", "#555"
+W, SCALE = 930, 100.0
-# --- nivelurile, calculate din corpul barei 14 ---------------------------------
-op, T1 = o_[14], c_[14]
-corp = T1 - op
+cds = serie()[S:20] # până la bara care atinge ținta
+d, cf = DOJI - S, CONF - S # doji, confirmare (indici locali)
+op, T1 = cds[cf].o, cds[cf].c # corpul candelei de confirmare
+corp = round(T1 - op, 2)
T2, T3 = round(T1 - corp / 3, 2), round(T1 - corp / 2, 2)
MED = round((T1 + T2 + T3) / 3, 2)
-SL = round(min(l_[11:15]) - 0.05, 2)
-TP1 = round(T1 + (T1 - SL), 2)
+RISC_MED, RISC_CLOSE = round(MED - SL, 2), round(T1 - SL, 2)
+TP_MED = round(MED + RISC_MED, 2) # ținta de 1R socotită de la prețul mediu
+retest = cds[cf + 1]
+
+# --- aserțiuni ------------------------------------------------------------------
+assert (op, T1) == (49.90, ENTRY) and corp == 0.50, (op, T1, corp)
+assert (T2, T3) == (50.23, 50.15), (T2, T3)
+assert MED == 50.26 and RISC_MED == 1.06 and RISC_CLOSE == R == 1.20
+assert retest.l <= T3 < T2, "bara de după confirmare nu umple ambele ordine"
+assert round(retest.l, 2) == 50.10, retest.l
+assert min(c.l for c in cds[cf - 3:cf + 1]) == DOJI_L and round(DOJI_L - 0.05, 2) == SL
+assert round(T1 + RISC_CLOSE, 2) == TP1
+# ținta de 1R vine mai devreme dacă prețul mediu de intrare e mai bun
+assert TP_MED == 51.32 == round(MED + RISC_MED, 2) and TP_MED < TP1
+assert next(i for i in range(cf + 1, len(cds)) if cds[i].h >= TP1) == len(cds) - 1
+# măsurat pe toată candela (nu doar pe corp) 1/2 iese tot T3; 1/3 iese 50.28
+hi, lo = cds[cf].h, cds[cf].l
+assert round(hi - (hi - lo) / 3, 2) == 50.28 and round((hi + lo) / 2, 2) == T3
+print(f"transe: T1 {T1:.2f} T2 {T2:.2f} T3 {T3:.2f} medie {MED:.2f} "
+ f"risc {RISC_MED:.2f} (față de {RISC_CLOSE:.2f}) retest low {retest.l:.2f}")
+
+
+def dy_sub(price, rand):
+ """Săgeata pleacă din nivel și coboară sub linia stopului, pe rândul `rand`."""
+ return (price - SL) * SCALE + rand
-# --- assert-urile din spec -----------------------------------------------------
-for i in range(1, len(bars)): # 1
- assert abs(o_[i] - c_[i - 1]) < 1e-9, f"open[{i}] != close[{i-1}]"
-for i in range(len(bars)):
- assert l_[i] <= min(o_[i], c_[i]) and h_[i] >= max(o_[i], c_[i]), f"bara {i}"
-assert abs(o_[12] - c_[12]) <= 0.10 * (h_[12] - l_[12]) + 1e-9 # 2
-assert c_[13] <= h_[12] < c_[14] and h_[12] == 53.00
-assert min(l_[11:15]) == l_[12] == 51.60 and SL == 51.55 # 3
-assert (op, T1) == (52.60, 53.70) and (T2, T3) == (53.33, 53.15) # 4
-assert l_[15] <= T3 < T2 # 5
-assert MED == 53.39 and round(MED - SL, 2) == 1.84 # 6
-assert round(T1 - SL, 2) == 2.15
-assert TP1 == 55.85 # 7
-assert next(i for i in range(15, len(bars)) if h_[i] >= TP1) == 18
-hi, lo = h_[14], l_[14] # 8
-assert round(hi - (hi - lo) / 3, 2) == 53.38 and round((hi + lo) / 2, 2) == T3
-GREY, RED, ORANGE, GREEN, BLUE = "#555", "#c00", "#b06000", "#188a3e", "#1a5fb4"
-W = 780
ch = Chart(
- candles=[Candle(*b) for b in bars],
- levels=[Level(TP1, f"TP1 {TP1:.2f} = 1R de la close", GREEN, "2 2"),
- Level(SL, f"SL {SL:.2f} (low 4 candele 51.60, minus 5 cenți)", RED, "4 3")],
- marks=[Mark(14, f"confirmare {T1:.2f}", GREEN, "above", dy=-38)],
- title="Intrarea în trei tranșe — daily, exemplu construit (doji 53.00 / 51.60)",
- scale=58.0, gap=18, body_w=10, left=44, width=W,
+ candles=cds,
+ levels=[Level(TP1, "ținta 1: 51.60 = 1R față de close", GREEN, "2 2"),
+ Level(TP_MED, f"ținta 1: {TP_MED:.2f} = 1R față de prețul mediu {MED:.2f}", BLUE, "2 2"),
+ Level(SL, "stopul: 49.20", RED, "4 3", "left")],
+ zones=[Zone(cf, cf, "", GREEN, p_top=T1, p_bot=op)],
+ arrows=[
+ Arrow(cf, "1/3 la close 50.40\nca să nu ratezi trade-ul dacă pleacă fără tine",
+ where="right", price=T1, color=GREEN, dx=150, dy=dy_sub(T1, 40)),
+ Arrow(cf, "1/3 cu ordin limită la 50.23 (o treime din corp)\nmai ieftin, dacă prețul revine",
+ where="right", price=T2, color=ORANGE, dx=150, dy=dy_sub(T2, 74)),
+ Arrow(cf, "1/3 cu ordin limită la 50.15 (jumătatea corpului)\nși mai ieftin",
+ where="right", price=T3, color=ORANGE, dx=150, dy=dy_sub(T3, 108)),
+ Arrow(cf - 1, "media mai bună coboară ținta:\nieși la 51.32, nu la 51.60",
+ where="above", price=TP_MED, color=BLUE, dx=150),
+ Arrow(cf + 1, "retestul: prețul revine la 50.10\nși umple amândouă ordinele limită",
+ where="below", price=retest.l, color=BLUE, dx=-150, dy=20),
+ ],
+ scale=SCALE, gap=30, body_w=10, left=56, width=W,
+ title="Intrarea în trei tranșe — același trade (doji 50.10 / 49.25)",
+ caption="Paranteza de pe candela de confirmare e corpul ei (49.90 → 50.40): pe el se măsoară 1/3 și 1/2. Stopul e unul singur. Exemplu construit.",
)
svg = ch.render()
-y, x = ch._y, ch._cx
+OFF = (float(re.search(r']*stroke="#1a1a1a"', svg).group(1))
+ - ch._cx(0))
+
+
+def Y(p):
+ return ch._y(p) + OFF
+
+
+def x(i):
+ return ch._cx(i) + OFF_X
ex = []
-
-# linia de ghidaj de la confirmare la eticheta ei (eticheta stă sus, peste bara 17)
-ex.append(f'')
-
-# cele trei tranșe: linii de la bara 13 până la paranteza corpului
-XB = 404 # paranteza corpului barei 14
-for p, col, dash in ((T1, GREEN, ""), (T2, ORANGE, "3 3"), (T3, ORANGE, "3 3")):
- d = f' stroke-dasharray="{dash}"' if dash else ""
- ex.append(f'')
-# paranteza: open 52.60 → close 53.70, cu diviziuni la 1/3 și 1/2
-ex.append(f'')
-ex.append(f'')
-ex.append(f'')
-
-# etichetele, răsfirate la dreapta, cu linii de ghidaj
-yc, XL = y((T1 + T3) / 2), 440
-labels = [(T1, f"1/3 la close {T1:.2f}", GREEN, ""),
- (MED, f"medie {MED:.2f} → risc {MED - SL:.2f} pe acțiune (tot la close: {T1 - SL:.2f})", "#777", "2 2"),
- (T2, f"1/3 la 1/3 din corp {T2:.2f}", ORANGE, "3 3"),
- (T3, f"1/3 la 1/2 din corp {T3:.2f}", ORANGE, "3 3"),
- (op, f"corp: open {op:.2f} → close {T1:.2f} = {corp:.2f}", GREY, "")]
-for k, (p, txt, col, dash) in enumerate(labels):
- ly = yc + (k - 1.5) * 22
- d = f' stroke-dasharray="{dash}"' if dash else ""
- ex.append(f'')
- ex.append(f'{txt}')
-
-# umpleri: T1 la close-ul barei 14, T2 și T3 pe low-ul barei 15; TP1 pe bara 18
-for i, p, col in ((14, T1, GREEN), (15, T2, ORANGE), (15, T3, ORANGE), (18, TP1, GREEN)):
- ex.append(f'')
+# fără etichetă lângă paranteză: oriunde lângă candele s-ar suprapune peste ele
+# sau peste liniile săgeților. O numește legenda de sub grafic.
+# nivelurile de tranșă, ca segmente scurte peste candela de confirmare
+for p, col in ((T1, GREEN), (T2, ORANGE), (T3, ORANGE)):
+ dash = "" if p == T1 else ' stroke-dasharray="3 3"'
+ ex.append(f'')
+# unde se umple fiecare tranșă: T1 la close, T2 și T3 pe low-ul barei următoare
+for i, p, col in ((cf, T1, GREEN), (cf + 1, T2, ORANGE), (cf + 1, T3, ORANGE)):
+ ex.append(f'')
-
-# etichetele de sub candele, pe rânduri (Mark "below" le-ar pune pe același rând)
-yb = y(SL) + 20
-rows = [(15, f"low {l_[15]:.2f}: umple T2 și T3", ORANGE),
- (13, f"+1: {c_[13]:.2f}, sub high", GREY),
- (12, f"doji {h_[12]:.2f} / {l_[12]:.2f}", BLUE)]
-for r, (i, txt, col) in enumerate(rows):
- ry = yb + 16 * r
- ex.append(f'')
- ex.append(f'')
+ ex.append(f'{txt}')
-
-# legenda, pe două rânduri
-ly = yb + 16 * len(rows) + 14
-ex.append(f'Măsurat pe corp (open {op:.2f} → close {T1:.2f}). '
- f'Măsurat pe toată candela ({lo:.2f} → {hi:.2f}), 1/3 ar fi '
- f'{hi - (hi - lo) / 3:.2f}; 1/2 iese tot {(hi + lo) / 2:.2f}.')
-ex.append(f'Stopul e unul singur, pentru toată '
- f'poziția. Barele 0-11 și 15-18 sunt construite.')
-H = ly + 17 + 12
-
body = svg.rsplit("", 1)[0].rstrip()
svg = "\n ".join([body, *ex]) + "\n"
-svg = re.sub(r'viewBox="0 0 (\d+) [\d.]+"', rf'viewBox="0 0 \1 {H:.0f}"', svg, count=1)
-assert "]*)>(.*?)', svg):
+ ty = float(re.search(r'y="([-\d.]+)"', attrs).group(1))
+ w = len(txt) * 6.2
+ x0 = float(tx) - (w if 'text-anchor="end"' in attrs else w / 2 if 'middle' in attrs else 0)
+ assert 0 <= x0 and x0 + w <= vw and 0 <= ty <= vh, f"text în afara pânzei: {txt!r}"
+assert "\n\n" not in svg, "linie goală în SVG"
+OUT.write_text(svg, encoding="utf-8")
+print(f"scris {OUT} ({vw:.0f}x{vh:.0f})")
diff --git a/gen/strategii/buy_doji_transe.spec.txt b/gen/strategii/buy_doji_transe.spec.txt
index 70e1ae0..5acaae1 100644
--- a/gen/strategii/buy_doji_transe.spec.txt
+++ b/gen/strategii/buy_doji_transe.spec.txt
@@ -1,69 +1,53 @@
SPEC DIAGRAMĂ — buy_doji_transe
===============================
-tip: PRET
-destinație: summaries/17_STRATEGII/01_buy_doji.md, Pasul 7 (intrarea)
+tip: PRET (un panou, zoom pe fereastra de intrare)
+destinație: summaries/17_STRATEGII/01_buy_doji.md, nivelul 3 (tranșele)
cheie markdown:
-titlu SVG: „Intrarea în trei tranșe — daily, exemplu construit (doji 53.00 / 51.60)"
-stil: candele lipite (gap 15-18, body_w 9-10), fără volum;
- open = close anterior, fitile implicite 0.15 peste/sub corp la barele 0-11.
+titlu SVG: „Intrarea în trei tranșe — același trade (doji 50.10 / 49.25)"
+serie: `buy_doji_ansamblu.serie()`, barele 12-19.
CE TREBUIE SĂ DEMONSTREZE
-------------------------
-Pe candela de confirmare: o treime din poziție la close (53.70), o treime
-cu limit la 1/3 din corp (53.33), o treime cu limit la 1/2 (53.15). Bara de
-după coboară până la 53.10 și le umple pe amândouă. Stopul e unul singur.
-Prețul mediu 53.39 → risc 1.84 pe acțiune, față de 2.15 cu tot la close.
-
-SERIA (19 bare, (o,h,l,c))
---------------------------
-open[0] = 61.00; close-uri 0-11 (downtrend de 12 candele):
-60.40, 59.60, 59.90, 58.80, 58.10, 58.40, 57.20, 56.30, 56.60, 55.30, 54.20, 52.40
-12 doji (52.40, 53.00, 51.60, 52.30)
-13 +1 (52.30, 52.85, 52.10, 52.60) close sub 53.00: încă nimic
-14 confirmare (52.60, 53.85, 52.45, 53.70) close peste 53.00: Buy Doji
-15 (53.70, 53.80, 53.10, 53.45) umple 1/3 și 1/2
-16 (53.45, 54.20, 53.35, 54.10)
-17 (54.10, 54.85, 54.00, 54.70)
-18 (54.70, 56.00, 54.60, 55.90) atinge TP1 55.85 (a 4-a după intrare)
+Pe candela de confirmare (open 49.90 → close 50.40, corp 0.50): o treime din
+poziție la close 50.40, o treime cu ordin limită la 1/3 din corp (50.23), o
+treime la 1/2 din corp (50.15). Bara următoare coboară la 50.10 (retest) și
+umple amândouă ordinele limită. Prețul mediu iese 50.26, deci riscul pe acțiune
+scade de la 1.20 (tot la close) la 1.06. Stopul rămâne unul singur, 49.20.
NIVELURI
--------
-T1 53.70 verde „1/3 la close 53.70"
-T2 53.33 portocaliu punctat „1/3 la 1/3 din corp 53.33"
-T3 53.15 portocaliu punctat „1/3 la 1/2 din corp 53.15"
-preț mediu 53.39 gri punctat „medie 53.39"
-SL 51.55 roșu „SL 51.55 (low 4 candele 51.60, minus 5 cenți)"
-TP1 55.85 verde punctat „TP1 55.85 = 1R de la close"
+T1 50.40 (close) · T2 50.23 (o treime din corp) · T3 50.15 (jumătatea corpului)
+preț mediu 50.26 · stop 49.20
+ținta de 1R față de close: 51.60 (atinsă la ultima bară arătată)
+ținta de 1R față de prețul mediu: 51.32 (50.26 + 1.06) — vine mai devreme,
+ ăsta e avantajul tranșelor; desenată ca al doilea nivel punctat
+Măsurat pe toată candela (49.75 → 50.55), 1/3 ar fi 50.28, iar 1/2 iese tot 50.15.
-MARKS
------
-Mark(12, "doji 53.00 / 51.60"), Mark(13, "+1: 52.60, sub high"),
-Mark(14, "confirmare 53.70"), Mark(15, "low 53.10: umple T2 și T3", below)
-
-LEGENDĂ
--------
-„Măsurat pe corp (open 52.60 → close 53.70). Măsurat pe toată candela
-(52.45 → 53.85), 1/3 ar fi 53.38; 1/2 iese tot 53.15."
+ADNOTĂRI
+--------
+Zonă verde peste corpul candelei de confirmare; paranteză cu corpul
+(49.90 → 50.40) în stânga ei; trei segmente scurte pentru T1/T2/T3 și cerculețe
+acolo unde se umple fiecare tranșă (T1 la close, T2 și T3 pe low-ul barei
+următoare); trei săgeți, câte una pe tranșă, cu motivul („ca să nu ratezi
+trade-ul dacă pleacă fără tine" / „mai ieftin, dacă prețul revine" / „și mai
+ieftin"); o săgeată pe retest; două paranteze de risc la dreapta: 1.20 tot la
+close vs 1.06 cu tranșe.
ASSERT-URI
----------
-1. open[i] == close[i-1]; h/l cuprind o/c.
-2. doji: abs(o-c) <= 0.10*(h-l); close[13] <= 53.00 < close[14].
-3. min(l[11:15]) == 51.60 (low-ul dojiului e cel mai jos din 4); SL == 51.55.
-4. corp = 53.70 - 52.60; round(53.70 - corp/3, 2) == 53.33; round(53.70 - corp/2, 2) == 53.15.
-5. low[15] <= 53.15 (ambele limit-uri umplute).
-6. round((53.70+53.33+53.15)/3, 2) == 53.39; round(53.39-51.55, 2) == 1.84;
- round(53.70-51.55, 2) == 2.15.
-7. TP1 == 53.70 + 2.15 == 55.85; primul i>14 cu high >= 55.85 este 18.
-8. round(53.85 - (53.85-52.45)/3, 2) == 53.38; (53.85+52.45)/2 == 53.15.
+1. corpul candelei de confirmare: open 49.90, close 50.40, corp 0.50.
+2. T2 == 50.23 și T3 == 50.15 (calculate din corp, nu scrise de mână).
+3. medie == 50.26; medie - stop == 1.06; close - stop == 1.20 == 1R.
+3b. ținta de la medie == 51.32 == medie + 1.06, și e sub 51.60.
+4. low-ul barei următoare == 50.10 <= T3 (ambele ordine limită se umplu).
+5. min(low) pe ultimele 4 candele de la confirmare == 49.25; stop 49.20.
+6. close + 1.20 == 51.60, atinsă prima dată la ultima bară arătată.
+7. măsurat pe toată candela: 1/3 == 50.28, 1/2 == 50.15 (tot T3).
+8. niciun text nu iese din pânză.
SURSA
-----
-- Doji 53.00/51.60, close 52.60 apoi 53.70, SL 51.55, TP1 55.85 atins la a 4-a:
- summaries/08_ANALIZA_ZILNICA/01_buy_doji_si_strategia_mici.md:136-144 (exemplu construit
- al sumarului; seria de aici îl redesenează cu aceleași niveluri).
-- Tranșele 1/3 la close, 1/3 la 1/3, 1/3 la 1/2: summaries/09_QA/23_2025-07-29.md:206-213;
- transcripts/09_Q_and_A/19_08092025.txt:1067-1073 (citat în A3_intrarea.md:276-279).
-- Măsurat de la low sau de la open: summaries/09_QA/20_2025-08-25.md:178-180;
- „Iei high-ul și low-ul și calculezi": summaries/09_QA/23_2025-07-29.md:210-211.
-- Barele 0-11 și 15-18 sunt CONSTRUITE.
+- Tranșele 1/3 la close, 1/3 la 1/3 din corp, 1/3 la 1/2: summaries/09_QA/
+ 23_2025-07-29.md:206-213; transcripts/09_Q_and_A/19_08092025.txt:1067-1073.
+- Măsurat de la low sau de la open: summaries/09_QA/20_2025-08-25.md:178-180.
+- Barele sunt CONSTRUITE (aceeași serie ca restul capitolului).
diff --git a/gen/strategii/buy_doji_transe.svg b/gen/strategii/buy_doji_transe.svg
index 79c3ac2..76ea005 100644
--- a/gen/strategii/buy_doji_transe.svg
+++ b/gen/strategii/buy_doji_transe.svg
@@ -1,75 +1,58 @@
-
- Intrarea în trei tranșe — daily, exemplu construit (doji 53.00 / 51.60)
-
- TP1 55.85 = 1R de la close
-
- SL 51.55 (low 4 candele 51.60, minus 5 cenți)
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- confirmare 53.70
-
-
-
-
-
-
-
-
- 1/3 la close 53.70
-
- medie 53.39 → risc 1.84 pe acțiune (tot la close: 2.15)
-
- 1/3 la 1/3 din corp 53.33
-
- 1/3 la 1/2 din corp 53.15
-
- corp: open 52.60 → close 53.70 = 1.10
-
-
-
-
-
- low 53.10: umple T2 și T3
-
- +1: 52.60, sub high
-
- doji 53.00 / 51.60
- Măsurat pe corp (open 52.60 → close 53.70). Măsurat pe toată candela (52.45 → 53.85), 1/3 ar fi 53.38; 1/2 iese tot 53.15.
- Stopul e unul singur, pentru toată poziția. Barele 0-11 și 15-18 sunt construite.
+
+ Intrarea în trei tranșe — același trade (doji 50.10 / 49.25)
+
+
+ ținta 1: 51.60 = 1R față de close
+
+ ținta 1: 51.32 = 1R față de prețul mediu 50.26
+
+ stopul: 49.20
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1/3 la close 50.40
+ ca să nu ratezi trade-ul dacă pleacă fără tine
+
+
+ 1/3 cu ordin limită la 50.23 (o treime din corp)
+ mai ieftin, dacă prețul revine
+
+
+ 1/3 cu ordin limită la 50.15 (jumătatea corpului)
+ și mai ieftin
+
+
+ media mai bună coboară ținta:
+ ieși la 51.32, nu la 51.60
+
+
+ retestul: prețul revine la 50.10
+ și umple amândouă ordinele limită
+ Paranteza de pe candela de confirmare e corpul ei (49.90 → 50.40): pe el se măsoară 1/3 și 1/2. Stopul e unul singur. Exemplu construit.
+
+
+
+
+
+
+
+
+ risc 1.20 pe acțiune (tot la close)
+
+ risc 1.06 pe acțiune (cu tranșe, preț mediu 50.26)
\ No newline at end of file
diff --git a/gen/strategii/buy_doji_trend.py b/gen/strategii/buy_doji_trend.py
index b29ca56..41e0965 100644
--- a/gen/strategii/buy_doji_trend.py
+++ b/gen/strategii/buy_doji_trend.py
@@ -8,10 +8,10 @@ trendului sunt numerotate sub bară, ca numărătoarea să se vadă.
"""
import re
import sys; sys.path.insert(0, "/workspace/atm-curs-dl")
-from svg_candles import Candle, Chart, Mark
+from svg_candles import Arrow, Candle, Chart, Mark, Zone
OUT = "/workspace/atm-curs-dl/gen/strategii/buy_doji_trend.svg"
-DOJI = (49.65, 50.10, 49.20, 49.60)
+DOJI = (49.65, 50.10, 49.25, 49.60)
CONF = (49.60, 50.55, 49.45, 50.40)
@@ -54,12 +54,20 @@ def numbers(ch, idx, color):
for n, i in enumerate(idx, 1)]
-def bracket(ch, a, b, y, text, color):
- xa, xb = ch._cx(a) - 6, ch._cx(b) + 6
- return [f'',
- f'{text}']
+def render_keep_offsets(ch):
+ """`Chart.render()` restaurează top/left după desen, deci `_y`/`_cx` de
+ după el dau coordonate deplasate față de body-ul randat (titlul și
+ etichetele zonelor mută graficul în jos). `_render()` lasă offset-urile
+ puse, exact ce ne trebuie ca extra-urile desenate de mână să pice la locul
+ lor. Se cheamă o singură dată per Chart."""
+ return ch._render()
+
+
+def fits(ch, svg):
+ """Chart mută graficul dacă un text ar ieși din pânză — aici mutarea ar
+ dezalinia cele două panouri, deci verificăm că nu s-a întâmplat."""
+ assert f'viewBox="0 0 {ch.width} ' in svg, \
+ f"Chart a lărgit pânza ({svg[:60]}): textul unei săgeți iese din {ch.width}px"
def body(svg):
@@ -68,32 +76,35 @@ def body(svg):
# --- panoul de sus: 9 candele ------------------------------------------------
top = Chart(candles=[Candle(*b) for b in L], left=44 + GAP, # dojiul pe aceeași verticală
- marks=[Mark(10, "doji", ORANGE, "below", dy=8),
- Mark(11, "confirmare 50.40", GREEN, "above")],
- title="Trendul dinaintea dojiului: 9 candele vs. 3 candele — exemplu construit",
+ marks=[Mark(11, "confirmare 50.40", GREEN, "above")],
+ zones=[Zone(1, 9, "9 candele de scădere — ideal", GREEN)],
+ arrows=[Arrow(10, "doji la capătul scăderii\nvânzătorii s-au terminat",
+ where="below", color=ORANGE, dx=70)],
+ top=54, # titlul se desenează de mână: cel din Chart ar cădea peste eticheta zonei
**common)
-svg_top = top.render()
-y_br = top._y(max(b[1] for b in L[1:10])) - 10
-ext_top = numbers(top, range(1, 10), RED) + bracket(top, 1, 9, y_br, "downtrend 9 candele", RED)
-y_cap1 = top._y(DOJI[2]) + 50
+svg_top = render_keep_offsets(top)
+fits(top, svg_top)
+ext_top = numbers(top, range(1, 10), RED) + [
+ 'Trendul dinaintea dojiului: '
+ '9 candele vs. 3 candele — exemplu construit']
+y_cap1 = max(top._y(DOJI[2]) + 50, top._arrow_geom(top.arrows[0])["box"][3] + 24)
ext_top.append(f''
f'DA — ideal (8-10 candele)')
# --- panoul de jos: range + 3 candele ----------------------------------------
y_sep = y_cap1 + 16
bot = Chart(candles=[Candle(*b) for b in R], left=44,
- marks=[Mark(11, "doji", ORANGE, "below", dy=8),
+ marks=[Mark(11, "doji", ORANGE, "below", dy=22),
Mark(12, "confirmare 50.40", GREEN, "above")],
- top=int(y_sep + 62), **common) # titlul și paranteza range-ului, desenate mai jos
-svg_bot = bot.render()
-y_rg = bot._y(max(b[1] for b in R[0:8])) - 10
+ zones=[Zone(8, 10, "doar 3 candele — merge, dar mai slab", ORANGE)],
+ arrows=[Arrow(3, "înainte: preț pe loc,\nnu scădere — de aceea\nsemnalul e mai slab",
+ where="above", color=GREY)],
+ top=int(y_sep + 118), **common) # loc pentru titlul panoului și textul săgeții
+svg_bot = render_keep_offsets(bot)
+fits(bot, svg_bot)
ext_bot = (numbers(bot, range(8, 11), RED)
- + bracket(bot, 8, 10, bot._y(R[8][1]) - 10, "doar 3 candele", RED)
- + bracket(bot, 0, 7, y_rg, "", GREY)
+ [f''
- f'Același doji, după un range',
- f''
- f'range: nu se numără'])
+ f'Același doji, după un range'])
y_cap2 = bot._y(DOJI[2]) + 50
ext_bot += [f''
f'ACCEPTAT, semnal mai slab',
diff --git a/gen/strategii/buy_doji_trend.spec.txt b/gen/strategii/buy_doji_trend.spec.txt
index 182b198..81a4e95 100644
--- a/gen/strategii/buy_doji_trend.spec.txt
+++ b/gen/strategii/buy_doji_trend.spec.txt
@@ -5,11 +5,12 @@ destinație: summaries/17_STRATEGII/01_buy_doji.md, Pasul 3, după doji_form
cheie markdown:
titlu SVG: „Trendul dinaintea dojiului: 9 candele vs. 3 candele — exemplu construit"
stil: candele lipite (gap 15-18, body_w 9-10), fără volum; open = close anterior;
+ adnotări „ca la proști": zone dreptunghiulare + săgeți cu text (CE + DE CE).
fitile implicite 0.15 peste/sub corp, cu excepțiile de mai jos.
CE TREBUIE SĂ DEMONSTREZE
-------------------------
-Același doji (h 50.10 / l 49.20) și aceeași confirmare (close 50.40), după
+Același doji (h 50.10 / l 49.25) și aceeași confirmare (close 50.40), după
două trenduri diferite. Stânga = ideal (9 candele). Dreapta = acceptat, dar
semnal mai slab (3 candele după un range) — și e cazul pe care scannerul cu
„Since Bear ≥ 8" nu-l găsește.
@@ -18,18 +19,22 @@ PANOUL STÂNGA — 12 bare
-----------------------
open[0] = 55.50; close-uri 0-9: 55.00, 54.30, 54.60, 53.60, 52.90, 53.10, 52.00,
51.20, 50.60, 49.65 (barele 1-9 = trendul de 9 candele)
-10 doji (49.65, 50.10, 49.20, 49.60)
+10 doji (49.65, 50.10, 49.25, 49.60)
11 confirmare (49.60, 50.55, 49.45, 50.40)
-Mark(1..9 paranteză, "downtrend 9 candele"), Mark(10,"doji"), Mark(11,"confirmare 50.40").
+Zone(1..9) verde, etichetă „9 candele de scădere — ideal"; numerele 1..9 sub bare;
+săgeată pe doji: „doji la capătul scăderii / vânzătorii s-au terminat".
+Mark(11,"confirmare 50.40").
Text sub panou: „DA — ideal (8-10 candele)".
PANOUL DREAPTA — 13 bare
------------------------
open[0] = 51.40; close-uri 0-7 (range): 51.70, 51.20, 51.60, 51.10, 51.50,
51.30, 51.70, 51.40; apoi 8: 50.90, 9: 50.30, 10: 49.65 (3 candele în jos)
-11 doji (49.65, 50.10, 49.20, 49.60)
+11 doji (49.65, 50.10, 49.25, 49.60)
12 confirmare (49.60, 50.55, 49.45, 50.40)
-Mark(8..10 paranteză, "doar 3 candele"), Mark(12,"confirmare 50.40").
+Zone(8..10) portocalie, etichetă „doar 3 candele — merge, dar mai slab"; numerele
+1..3 sub bare; săgeată pe range (bara 3): „înainte: preț pe loc, / nu scădere — de
+aceea / semnalul e mai slab". Mark(11,"doji"), Mark(12,"confirmare 50.40").
Text sub panou: „ACCEPTAT, semnal mai slab · scannerul cu Since Bear ≥ 8 nu-l vede".
ASSERT-URI
@@ -42,6 +47,14 @@ ASSERT-URI
close[8] > close[9] > close[10] (exact 3 candele în jos).
5. Confirmarea închide peste 50.10 pe ambele.
+NOTĂ DE RANDARE
+---------------
+Extra-urile desenate de mână (numere, titlu) folosesc `ch._render()`, nu
+`ch.render()`: al doilea restaurează top/left după desen, deci `_y`/`_cx` ar da
+coordonate deplasate cu offset-ul pe care Chart îl pune pentru titlu și pentru
+etichetele zonelor. Titlul panoului de sus e desenat de mână din același motiv
+(titlul din Chart cade exact peste eticheta zonei).
+
SURSA
-----
- 8-10 candele ideal; 2-3 candele au funcționat, semnal mai slab:
diff --git a/gen/strategii/buy_doji_trend.svg b/gen/strategii/buy_doji_trend.svg
index 45f998f..dc1a20d 100644
--- a/gen/strategii/buy_doji_trend.svg
+++ b/gen/strategii/buy_doji_trend.svg
@@ -1,81 +1,86 @@
-
- Trendul dinaintea dojiului: 9 candele vs. 3 candele — exemplu construit
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- doji
- confirmare 50.40
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
-
- downtrend 9 candele
- DA — ideal (8-10 candele)
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- doji
- confirmare 50.40
- 1
- 2
- 3
-
- doar 3 candele
-
-
- Același doji, după un range
- range: nu se numără
- ACCEPTAT, semnal mai slab
- scannerul cu Since Bear ≥ 8 nu-l vede
+
+
+ 9 candele de scădere — ideal
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ confirmare 50.40
+
+
+ doji la capătul scăderii
+ vânzătorii s-au terminat
+ 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ Trendul dinaintea dojiului: 9 candele vs. 3 candele — exemplu construit
+ DA — ideal (8-10 candele)
+
+
+ doar 3 candele — merge, dar mai slab
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ doji
+ confirmare 50.40
+
+
+ înainte: preț pe loc,
+ nu scădere — de aceea
+ semnalul e mai slab
+ 1
+ 2
+ 3
+ Același doji, după un range
+ ACCEPTAT, semnal mai slab
+ scannerul cu Since Bear ≥ 8 nu-l vede
\ No newline at end of file
diff --git a/summaries/17_STRATEGII/00_harta.html b/summaries/17_STRATEGII/00_harta.html
index 48e04ab..5ed3d27 100644
--- a/summaries/17_STRATEGII/00_harta.html
+++ b/summaries/17_STRATEGII/00_harta.html
@@ -214,6 +214,6 @@ capitolului respectiv, secțiunea 2.2 (A+ vs. mediocru), apoi A2.
Patru sesiuni de analiză zilnică nu sunt ținute de autor (25, 26, 29 și
30 iunie 2026 — un înlocuitor). Regulile care se sprijineau doar pe zilele
alea au fost scoase din material încă de la sumarizare.