svg_candles: serie continua, panou de volum, medii mobile, etichete ancorate

Userul a cerut ca diagramele de pret sa arate ca un grafic real (TradingView):
linie continua de pret si volum, ca sa se vada trendul. Candelele izolate cu
spatii mari nu spun nimic despre trend.

Adaugat in svg_candles.py:
- from_closes() / walk(): serii continue, open = close-ul barei anterioare,
  cu structura controlata exact din close-uri
- panou de volum (vol_h), bare colorate pe directia candelei
- MA: medii mobile calculate din close-uri
- Mark: etichete ancorate pe candele anume (la 30 de bare, tag-urile per
  candela se suprapun)
- title, Level.label_at
- selftest extins: volum proportional, serie fara goluri, structura pastrata

STYLE.md: regula noua, cu cele doua stiluri (serie de 25-45 de bare pentru
orice grafic de pret; stilul vechi de 3-6 candele doar pentru definitii de
pattern-uri de o candela) si obligatia de assert in generator.

gen/exemplu_grafic_real.py: referinta de copiat.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MtSTyTmt6AbCL9j5ajEDm1
This commit is contained in:
Claude Agent
2026-09-13 07:46:00 +00:00
parent a1c78c1d08
commit 6b2d9eb1e5
4 changed files with 389 additions and 145 deletions

View File

@@ -110,6 +110,38 @@ Verificare automată de pus în orice pagină cu trade-uri: pentru fiecare
diagramă cu trei niveluri (SL / intrare / TP), raportul dintre distanțele în
preț trebuie să fie identic cu raportul dintre distanțele în pixeli.
## Cum trebuie să arate un grafic de preț (cerut de user, 2026-09-13)
Userul citește diagramele ca să vadă **trendul** și să facă analogie cu un
grafic real din TradingView. Candelele izolate, cu spații mari între ele, nu
arată ca un grafic și nu-i spun nimic despre trend. Deci:
**Orice diagramă care arată preț sau volum se desenează ca serie continuă:**
- **25-45 de candele**, lipite (`gap=15-18`, `body_w=9-10`), nu 3-6 candele
izolate. Pattern-ul trebuie să aibă context în stânga și în dreapta lui.
- **Open-ul fiecărei bare = close-ul barei anterioare.** Folosește
`from_closes(...)` (definești close-urile, deci controlezi exact structura)
sau `walk(...)`. Fără goluri artificiale între bare.
- **Panou de volum dedesubt** (`vol_h=70`), cu bare colorate pe direcția
candelei, de fiecare dată când textul vorbește despre volum.
- **Medie mobilă** (`mas=[MA(20, ...)]`) când textul vorbește despre trend
sau despre SMA — se calculează singură din close-uri.
- **Etichetele se ancorează pe candele** cu `marks=[Mark(i, ...)]`, nu cu
`tag` per candelă: la 30 de bare, etichetele per candelă se suprapun.
- `title="SIMBOL — timeframe"` sus, ca pe un chart real.
Stilul vechi (3-6 candele, `gap=92`) rămâne valabil **doar** pentru
definiții de pattern-uri de o candelă (ce e un marubozu, ce e un doji),
unde contează forma barei, nu trendul.
**Verificare obligatorie în generator:** după ce construiești seria, extrage
din ea nivelurile pe care le afirmi în text (`min(c.l for c in cds[a:b])`) și
pune `assert` că se potrivesc — ex. că al doilea picior chiar e mai jos decât
primul, că intrarea e egală cu close-ul ultimei bare, că R-ul din etichetă e
cel calculat. Ochiul nu prinde o structură care contrazice eticheta; calculul
da. (Găsit ca bug real de agenții de verificare, 13.09.2026: o intrare short
descrisă „sub low-ul barei de retest", desenată deasupra lui.)
## Diagrame (obligatorii, nu opționale — userul e vizual learner)
- **Orice paragraf "Exemplu real" cu cifre concrete are nevoie de propria
diagramă chiar acolo, lipită de text** — nu doar undeva mai jos în fișier,

View File

@@ -0,0 +1,42 @@
"""Exemplu de referință pentru stilul „grafic real" cerut de user pe 13.09.2026.
Rulează-l ca să vezi cum se construiește o serie continuă cu volum, medie
mobilă și etichete ancorate. Copiază structura în generatoarele tale.
vezi STYLE.md, secțiunea „Cum trebuie să arate un grafic de preț".
"""
import sys; sys.path.insert(0,"/workspace/atm-curs-dl")
from svg_candles import Chart, Level, Mark, MA, from_closes
# close-urile definesc exact structura; wick-urile fixeaza minimele/maximele
closes = [113.0,111.4,109.9,110.6,108.8,107.4,106.0,106.9,105.2,104.0, # downtrend
104.6,103.2,102.4,103.8,105.0, # picior 1 (low 101.50)
104.2,103.0,101.9,101.1,102.6, # picior 2 (low 100.20)
104.1,105.2,106.8, # breakout peste 105.50
105.9,106.0, # retest
106.6,107.0] # intrare 107.00
wicks = [(0.4,0.4)]*10 + [(0.5,0.5),(0.4,0.6),(0.5,0.90),(0.6,0.4),(0.5,0.4)] \
+ [(0.4,0.5),(0.4,0.6),(0.5,0.5),(0.4,0.90),(0.5,0.4)] \
+ [(0.5,0.3),(0.4,0.3),(0.2,0.3)] + [(0.3,0.6),(0.4,0.4)] + [(0.3,0.3),(0.2,0.3)]
vols = [46,44,41,38,52,47,44,39,48,43, 41,44,39,42,58, 47,44,40,43,70, 86,70,63, 47,44, 58,55]
cds = from_closes(closes, wicks=wicks, vols=vols, start=114.4)
lo1 = min(c.l for c in cds[10:15]); lo2 = min(c.l for c in cds[15:20])
peak = max(c.h for c in cds[13:15]); entry = cds[-1].c
assert lo2 < lo1, (lo1, lo2)
print(f"picior1 {lo1:.2f} picior2 {lo2:.2f} varf {peak:.2f} intrare {entry:.2f}")
risc = entry - 104.60
print(f"risc {risc:.2f} TP1 {(117-entry)/risc:.2f}R TP2 {(124-entry)/risc:.2f}R")
ch = Chart(candles=cds,
levels=[Level(104.60,"SL 104.60","#c00",label_at="left"),
Level(entry,f"intrare {entry:.2f}","#188a3e"),
Level(117.00,"TP1 117.00 = 4.2R","#1a5fb4"),
Level(124.00,"TP2 124.00 = 7.1R","#1a5fb4")],
marks=[Mark(13,f"picior 1: {lo1:.2f}","#c00","below"), Mark(19,f"picior 2: {lo2:.2f} (mai jos)","#c00","below"),
Mark(22,"breakout","#188a3e"), Mark(24,"retest","#b06000","below"),
Mark(26,"intrare","#188a3e")],
mas=[MA(20,"#b06000",dash="4 3",text="SMA-20")],
scale=8.0, gap=15, body_w=9, left=44, width=620, vol_h=70,
vol_label="volum", title="Disney — daily",
caption="Ale lui: pretul de 107 si tintele 117 / 124. Structura si volumul sunt construite de mine, la scara.")
print(ch.render())

View File

@@ -20,12 +20,12 @@ aceeași înălțime oriunde pe desen, inclusiv între intrare, stop-loss și
Corpul arată distanța dintre open și close. Fitilele arată cât de departe a
ajuns prețul în fiecare direcție **înainte să fie împins înapoi**.
<svg viewBox="0 0 620 254" xmlns="http://www.w3.org/2000/svg" style="width:100%;height:auto;font:12px sans-serif">
<line x1="90" y1="34.0" x2="90" y2="161.5" stroke="#1a1a1a" stroke-width="1.5"/>
<rect x="77" y="41.5" width="26" height="90.0" fill="#188a3e"/>
<text x="90" y="179.5" fill="#188a3e" text-anchor="middle" style="font-weight:bold">bull</text>
<line x1="230" y1="34.0" x2="230" y2="161.5" stroke="#1a1a1a" stroke-width="1.5"/>
<rect x="217" y="41.5" width="26" height="90.0" fill="#c00"/>
<text x="230" y="179.5" fill="#c00" text-anchor="middle" style="font-weight:bold">bear</text>
<line x1="90.0" y1="34.0" x2="90.0" y2="161.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="77.0" y="41.5" width="26" height="90.0" fill="#188a3e"/>
<text x="90.0" y="179.5" fill="#188a3e" text-anchor="middle" style="font-weight:bold">bull</text>
<line x1="230.0" y1="34.0" x2="230.0" y2="161.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="217.0" y="41.5" width="26" height="90.0" fill="#c00"/>
<text x="230.0" y="179.5" fill="#c00" text-anchor="middle" style="font-weight:bold">bear</text>
<text x="24" y="199.5" fill="#188a3e">bull — O 50.00, H 50.65, L 49.80, C 50.60 (close peste open)</text>
<text x="24" y="216.5" fill="#c00">bear — O 50.60, H 50.65, L 49.80, C 50.00 (close sub open)</text>
<text x="24" y="242" fill="#555">Aceleași patru prețuri, ordine inversă a lui open și close. Scară: 0.10 = 15 px.</text>
@@ -81,9 +81,9 @@ fitil de sus aproape inexistent.
<svg viewBox="0 0 620 285" xmlns="http://www.w3.org/2000/svg" style="width:100%;height:auto;font:12px sans-serif">
<polyline points="24,44.0 102,90.0" fill="none" stroke="#999" stroke-width="2"/>
<text x="24" y="32.0" fill="#999">downtrend anterior</text>
<line x1="110" y1="90.0" x2="110" y2="210.0" stroke="#1a1a1a" stroke-width="1.5"/>
<rect x="97" y="90.0" width="26" height="25.0" fill="#188a3e"/>
<text x="110" y="228.0" fill="#188a3e" text-anchor="middle" style="font-weight:bold">hammer</text>
<line x1="110.0" y1="90.0" x2="110.0" y2="210.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="97.0" y="90.0" width="26" height="25.0" fill="#188a3e"/>
<text x="110.0" y="228.0" fill="#188a3e" text-anchor="middle" style="font-weight:bold">hammer</text>
<text x="24" y="248.0" fill="#188a3e">O 63.05, H = C 63.30, L 62.10 — fitil de 0.95, corp de 0.25</text>
<text x="24" y="273" fill="#555">Fitilul e de aproape 4 ori corpul. Scară: 0.10 = 10 px.</text>
</svg>
@@ -103,23 +103,23 @@ aici, și unde pui stopul ca să afli repede că te-ai înșelat.
<svg viewBox="0 0 620 227" xmlns="http://www.w3.org/2000/svg" style="width:100%;height:auto;font:12px sans-serif">
<line x1="24" y1="91.6" x2="606" y2="91.6" stroke="#888" stroke-width="1.5" stroke-dasharray="2 2"/>
<text x="592" y="85.6" fill="#888" text-anchor="end">prag de confirmare 63.30</text>
<line x1="110" y1="91.6" x2="110" y2="168.4" stroke="#1a1a1a" stroke-width="1.5"/>
<rect x="97" y="91.6" width="26" height="16.0" fill="#188a3e"/>
<text x="110" y="186.4" fill="#188a3e" text-anchor="middle" style="font-weight:bold">hammer</text>
<line x1="220" y1="34.0" x2="220" y2="104.4" stroke="#1a1a1a" stroke-width="1.5"/>
<rect x="207" y="40.4" width="26" height="57.6" fill="#188a3e"/>
<text x="220" y="186.4" fill="#188a3e" text-anchor="middle" style="font-weight:bold">DA</text>
<line x1="110.0" y1="91.6" x2="110.0" y2="168.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="97.0" y="91.6" width="26" height="16.0" fill="#188a3e"/>
<text x="110.0" y="186.4" fill="#188a3e" text-anchor="middle" style="font-weight:bold">hammer</text>
<line x1="220.0" y1="34.0" x2="220.0" y2="104.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="207.0" y="40.4" width="26" height="57.6" fill="#188a3e"/>
<text x="220.0" y="186.4" fill="#188a3e" text-anchor="middle" style="font-weight:bold">DA</text>
<text x="24" y="206.4" fill="#188a3e">DA — candela următoare închide la 64.10, peste high-ul hammer-ului (63.30)</text>
</svg>
<svg viewBox="0 0 620 183" xmlns="http://www.w3.org/2000/svg" style="width:100%;height:auto;font:12px sans-serif">
<line x1="24" y1="110.8" x2="606" y2="110.8" stroke="#888" stroke-width="1.5" stroke-dasharray="2 2"/>
<text x="592" y="104.8" fill="#888" text-anchor="end">nivel de invalidare 62.10</text>
<line x1="110" y1="34.0" x2="110" y2="110.8" stroke="#1a1a1a" stroke-width="1.5"/>
<rect x="97" y="34.0" width="26" height="16.0" fill="#188a3e"/>
<text x="110" y="141.6" fill="#188a3e" text-anchor="middle" style="font-weight:bold">hammer</text>
<line x1="220" y1="35.3" x2="220" y2="123.6" stroke="#1a1a1a" stroke-width="1.5"/>
<rect x="207" y="40.4" width="26" height="76.8" fill="#c00"/>
<text x="220" y="141.6" fill="#c00" text-anchor="middle" style="font-weight:bold">NU</text>
<line x1="110.0" y1="34.0" x2="110.0" y2="110.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="97.0" y="34.0" width="26" height="16.0" fill="#188a3e"/>
<text x="110.0" y="141.6" fill="#188a3e" text-anchor="middle" style="font-weight:bold">hammer</text>
<line x1="220.0" y1="35.3" x2="220.0" y2="123.6" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="207.0" y="40.4" width="26" height="76.8" fill="#c00"/>
<text x="220.0" y="141.6" fill="#c00" text-anchor="middle" style="font-weight:bold">NU</text>
<text x="24" y="161.6" fill="#c00">NU — închide la 62.00, sub low-ul hammer-ului: downtrend-ul continuă</text>
</svg>
@@ -133,12 +133,12 @@ aici, și unde pui stopul ca să afli repede că te-ai înșelat.
<text x="592" y="286.8" fill="#c00" text-anchor="end">SL 62.05 — sub low-ul hammer-ului</text>
<polyline points="24,181.8 102,227.8" fill="none" stroke="#999" stroke-width="2"/>
<text x="24" y="169.8" fill="#999">downtrend anterior</text>
<line x1="110" y1="227.8" x2="110" y2="290.2" stroke="#1a1a1a" stroke-width="1.5"/>
<rect x="97" y="227.8" width="26" height="13.0" fill="#188a3e"/>
<text x="110" y="310.8" fill="#188a3e" text-anchor="middle" style="font-weight:bold">hammer</text>
<line x1="220" y1="181.0" x2="220" y2="227.8" stroke="#1a1a1a" stroke-width="1.5"/>
<rect x="207" y="186.2" width="26" height="36.4" fill="#188a3e"/>
<text x="220" y="310.8" fill="#188a3e" text-anchor="middle" style="font-weight:bold">confirmare</text>
<line x1="110.0" y1="227.8" x2="110.0" y2="290.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="97.0" y="227.8" width="26" height="13.0" fill="#188a3e"/>
<text x="110.0" y="310.8" fill="#188a3e" text-anchor="middle" style="font-weight:bold">hammer</text>
<line x1="220.0" y1="181.0" x2="220.0" y2="227.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="207.0" y="186.2" width="26" height="36.4" fill="#188a3e"/>
<text x="220.0" y="310.8" fill="#188a3e" text-anchor="middle" style="font-weight:bold">confirmare</text>
<text x="24" y="330.8" fill="#188a3e">hammer — low-ul 62.10 e nivelul care ține ipoteza</text>
<text x="24" y="347.8" fill="#188a3e">confirmare — închide 64.10, peste 63.30</text>
<text x="24" y="373" fill="#555">Scară: 1.00 = 52 px. Distanța intrare→TP e exact dublul distanței intrare→SL.</text>
@@ -162,9 +162,9 @@ Nu „cât simți".
<svg viewBox="0 0 620 285" xmlns="http://www.w3.org/2000/svg" style="width:100%;height:auto;font:12px sans-serif">
<polyline points="24,44.0 122,90.0" fill="none" stroke="#999" stroke-width="2"/>
<text x="24" y="32.0" fill="#999">downtrend anterior</text>
<line x1="130" y1="90.0" x2="130" y2="210.0" stroke="#1a1a1a" stroke-width="1.5"/>
<rect x="117" y="180.0" width="26" height="25.0" fill="#188a3e"/>
<text x="130" y="228.0" fill="#188a3e" text-anchor="middle" style="font-weight:bold">inverted hammer</text>
<line x1="130.0" y1="90.0" x2="130.0" y2="210.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="117.0" y="180.0" width="26" height="25.0" fill="#188a3e"/>
<text x="130.0" y="228.0" fill="#188a3e" text-anchor="middle" style="font-weight:bold">inverted hammer</text>
<text x="24" y="248.0" fill="#188a3e">O 62.15, H 63.30, L 62.10, C 62.40</text>
<text x="24" y="273" fill="#555">Fitilul de sus e de aproape 4 ori corpul. Scară: 0.10 = 10 px.</text>
</svg>
@@ -195,9 +195,9 @@ final încă în favoarea taberei vechi.
<svg viewBox="0 0 620 291" xmlns="http://www.w3.org/2000/svg" style="width:100%;height:auto;font:12px sans-serif">
<polyline points="24,200.0 122,154.0" fill="none" stroke="#999" stroke-width="2"/>
<text x="24" y="220.0" fill="#999">uptrend anterior</text>
<line x1="130" y1="34.0" x2="130" y2="154.0" stroke="#1a1a1a" stroke-width="1.5"/>
<rect x="117" y="34.0" width="26" height="25.0" fill="#188a3e"/>
<text x="130" y="234.0" fill="#188a3e" text-anchor="middle" style="font-weight:bold">hanging man</text>
<line x1="130.0" y1="34.0" x2="130.0" y2="154.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="117.0" y="34.0" width="26" height="25.0" fill="#188a3e"/>
<text x="130.0" y="234.0" fill="#188a3e" text-anchor="middle" style="font-weight:bold">hanging man</text>
<text x="24" y="254.0" fill="#188a3e">O 63.05, H = C 63.30, L 62.10 — exact aceleași cifre ca la hammer</text>
<text x="24" y="279" fill="#555">Formă identică cu hammer. Contextul de trend e singura diferență.</text>
</svg>
@@ -223,9 +223,9 @@ creștere. Geometric identic cu inverted hammer.
<svg viewBox="0 0 620 279" xmlns="http://www.w3.org/2000/svg" style="width:100%;height:auto;font:12px sans-serif">
<polyline points="24,188.0 122,142.0" fill="none" stroke="#999" stroke-width="2"/>
<text x="24" y="208.0" fill="#999">uptrend anterior</text>
<line x1="130" y1="34.0" x2="130" y2="142.0" stroke="#1a1a1a" stroke-width="1.5"/>
<rect x="117" y="124.0" width="26" height="13.5" fill="#c00"/>
<text x="130" y="222.0" fill="#c00" text-anchor="middle" style="font-weight:bold">shooting star</text>
<line x1="130.0" y1="34.0" x2="130.0" y2="142.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="117.0" y="124.0" width="26" height="13.5" fill="#c00"/>
<text x="130.0" y="222.0" fill="#c00" text-anchor="middle" style="font-weight:bold">shooting star</text>
<text x="24" y="242.0" fill="#c00">O 64.10, H 65.10, L 63.90, C 63.95</text>
<text x="24" y="267" fill="#555">Scară: 0.10 = 9 px.</text>
</svg>
@@ -253,12 +253,12 @@ devine rezistență până e depășit la închidere.
<text x="592" y="199.6" fill="#188a3e" text-anchor="end">TP 61.25 — 2R</text>
<polyline points="24,135.0 102,89.0" fill="none" stroke="#999" stroke-width="2"/>
<text x="24" y="155.0" fill="#999">uptrend anterior</text>
<line x1="110" y1="36.2" x2="110" y2="89.0" stroke="#1a1a1a" stroke-width="1.5"/>
<rect x="97" y="80.2" width="26" height="6.6" fill="#c00"/>
<text x="110" y="285.6" fill="#c00" text-anchor="middle" style="font-weight:bold">shooting star</text>
<line x1="220" y1="82.4" x2="220" y2="102.2" stroke="#1a1a1a" stroke-width="1.5"/>
<rect x="207" y="84.6" width="26" height="13.2" fill="#c00"/>
<text x="220" y="285.6" fill="#c00" text-anchor="middle" style="font-weight:bold">confirmare</text>
<line x1="110.0" y1="36.2" x2="110.0" y2="89.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="97.0" y="80.2" width="26" height="6.6" fill="#c00"/>
<text x="110.0" y="285.6" fill="#c00" text-anchor="middle" style="font-weight:bold">shooting star</text>
<line x1="220.0" y1="82.4" x2="220.0" y2="102.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="207.0" y="84.6" width="26" height="13.2" fill="#c00"/>
<text x="220.0" y="285.6" fill="#c00" text-anchor="middle" style="font-weight:bold">confirmare</text>
<text x="24" y="305.6" fill="#c00">shooting star — high-ul 65.10 e oferta</text>
<text x="24" y="322.6" fill="#c00">confirmare — închide 63.70, sub low-ul 63.90</text>
<text x="24" y="348" fill="#555">Scară: 1.00 = 44 px. Risc 1.30 în sus, țintă 2.60 în jos — exact dublul.</text>
@@ -268,9 +268,9 @@ devine rezistență până e depășit la închidere.
**Ce arată:** open și close practic egale, cu fitile în ambele direcții.
<svg viewBox="0 0 620 229" xmlns="http://www.w3.org/2000/svg" style="width:100%;height:auto;font:12px sans-serif">
<line x1="130" y1="34.0" x2="130" y2="154.0" stroke="#1a1a1a" stroke-width="1.5"/>
<rect x="117" y="89.0" width="26" height="2.0" fill="#c00"/>
<text x="130" y="172.0" fill="#c00" text-anchor="middle" style="font-weight:bold">doji</text>
<line x1="130.0" y1="34.0" x2="130.0" y2="154.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="117.0" y="89.0" width="26" height="2.0" fill="#c00"/>
<text x="130.0" y="172.0" fill="#c00" text-anchor="middle" style="font-weight:bold">doji</text>
<text x="24" y="192.0" fill="#c00">O 62.75, C 62.73 — corp de 0.02, interval total de 1.20</text>
<text x="24" y="217" fill="#555">Prețul s-a mișcat 1.20 și a închis exact de unde a plecat. Scară: 0.10 = 10 px.</text>
</svg>
@@ -300,12 +300,12 @@ Fără pasul 3, un doji e zgomot. Apar des.
<text x="592" y="286.8" fill="#c00" text-anchor="end">SL 62.05 — sub low-ul doji</text>
<polyline points="24,181.8 102,227.8" fill="none" stroke="#999" stroke-width="2"/>
<text x="24" y="169.8" fill="#999">downtrend anterior</text>
<line x1="110" y1="227.8" x2="110" y2="290.2" stroke="#1a1a1a" stroke-width="1.5"/>
<rect x="97" y="256.4" width="26" height="2.0" fill="#c00"/>
<text x="110" y="310.8" fill="#c00" text-anchor="middle" style="font-weight:bold">doji</text>
<line x1="220" y1="186.2" x2="220" y2="259.0" stroke="#1a1a1a" stroke-width="1.5"/>
<rect x="207" y="191.4" width="26" height="62.4" fill="#188a3e"/>
<text x="220" y="310.8" fill="#188a3e" text-anchor="middle" style="font-weight:bold">confirmare</text>
<line x1="110.0" y1="227.8" x2="110.0" y2="290.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="97.0" y="256.4" width="26" height="1.6" fill="#c00"/>
<text x="110.0" y="310.8" fill="#c00" text-anchor="middle" style="font-weight:bold">doji</text>
<line x1="220.0" y1="186.2" x2="220.0" y2="259.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="207.0" y="191.4" width="26" height="62.4" fill="#188a3e"/>
<text x="220.0" y="310.8" fill="#188a3e" text-anchor="middle" style="font-weight:bold">confirmare</text>
<text x="24" y="330.8" fill="#c00">doji — extremele 63.30 și 62.10 sunt pragurile</text>
<text x="24" y="347.8" fill="#188a3e">confirmare — închide 64.00, peste 63.30</text>
<text x="24" y="373" fill="#555">Scară: 1.00 = 52 px. Risc 1.30, țintă 2.60 — exact dublul.</text>
@@ -323,9 +323,9 @@ doji-ului, nu dincolo de o extremă. Atunci nu s-a rezolvat nimic — aștepți
**Ce arată:** fitile lungi în ambele direcții, corp mic **dar nu neglijabil**.
<svg viewBox="0 0 620 219" xmlns="http://www.w3.org/2000/svg" style="width:100%;height:auto;font:12px sans-serif">
<line x1="130" y1="34.0" x2="130" y2="144.0" stroke="#1a1a1a" stroke-width="1.5"/>
<rect x="117" y="79.0" width="26" height="20.0" fill="#188a3e"/>
<text x="130" y="162.0" fill="#188a3e" text-anchor="middle" style="font-weight:bold">spinning top</text>
<line x1="130.0" y1="34.0" x2="130.0" y2="144.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="117.0" y="79.0" width="26" height="20.0" fill="#188a3e"/>
<text x="130.0" y="162.0" fill="#188a3e" text-anchor="middle" style="font-weight:bold">spinning top</text>
<text x="24" y="182.0" fill="#188a3e">O 62.60, C 62.80 — corp de 0.20 într-un interval de 1.10</text>
<text x="24" y="207" fill="#555">Corpul e sub 20% din interval. Scară: 0.10 = 10 px.</text>
</svg>
@@ -357,12 +357,12 @@ doar o ajustare de risc.
**Ce arată:** corp maxim, **fără fitile**. La cel bull: open = low și
close = high. La cel bear: invers.
<svg viewBox="0 0 620 266" xmlns="http://www.w3.org/2000/svg" style="width:100%;height:auto;font:12px sans-serif">
<line x1="140" y1="34.0" x2="140" y2="174.0" stroke="#1a1a1a" stroke-width="1.5"/>
<rect x="127" y="34.0" width="26" height="140.0" fill="#188a3e"/>
<text x="140" y="192.0" fill="#188a3e" text-anchor="middle" style="font-weight:bold">marubozu bull</text>
<line x1="290" y1="34.0" x2="290" y2="174.0" stroke="#1a1a1a" stroke-width="1.5"/>
<rect x="277" y="34.0" width="26" height="140.0" fill="#c00"/>
<text x="290" y="192.0" fill="#c00" text-anchor="middle" style="font-weight:bold">marubozu bear</text>
<line x1="140.0" y1="34.0" x2="140.0" y2="174.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="127.0" y="34.0" width="26" height="140.0" fill="#188a3e"/>
<text x="140.0" y="192.0" fill="#188a3e" text-anchor="middle" style="font-weight:bold">marubozu bull</text>
<line x1="290.0" y1="34.0" x2="290.0" y2="174.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="277.0" y="34.0" width="26" height="140.0" fill="#c00"/>
<text x="290.0" y="192.0" fill="#c00" text-anchor="middle" style="font-weight:bold">marubozu bear</text>
<text x="24" y="212.0" fill="#188a3e">bull — O = L = 62.00 (jos), C = H = 64.00 (sus)</text>
<text x="24" y="229.0" fill="#c00">bear — O = H = 64.00 (sus), C = L = 62.00 (jos)</text>
<text x="24" y="254" fill="#555">La cel bull, open-ul e la minim; la cel bear, open-ul e la maxim. Scară: 1.00 = 70 px.</text>
@@ -387,15 +387,15 @@ jumătatea corpului, 63.00 în exemplul de sus.
<svg viewBox="0 0 620 304" xmlns="http://www.w3.org/2000/svg" style="width:100%;height:auto;font:12px sans-serif">
<line x1="24" y1="125.0" x2="606" y2="125.0" stroke="#b06000" stroke-width="1.5" stroke-dasharray="5 3"/>
<text x="592" y="119.0" fill="#b06000" text-anchor="end">midpoint 63.00</text>
<line x1="95" y1="55.0" x2="95" y2="195.0" stroke="#1a1a1a" stroke-width="1.5"/>
<rect x="82" y="55.0" width="26" height="140.0" fill="#188a3e"/>
<text x="95" y="213.0" fill="#188a3e" text-anchor="middle" style="font-weight:bold">marubozu</text>
<line x1="200" y1="58.5" x2="200" y2="121.5" stroke="#1a1a1a" stroke-width="1.5"/>
<rect x="187" y="62.0" width="26" height="52.5" fill="#c00"/>
<text x="200" y="213.0" fill="#c00" text-anchor="middle" style="font-weight:bold">pullback</text>
<line x1="305" y1="34.0" x2="305" y2="118.0" stroke="#1a1a1a" stroke-width="1.5"/>
<rect x="292" y="37.5" width="26" height="73.5" fill="#188a3e"/>
<text x="305" y="213.0" fill="#188a3e" text-anchor="middle" style="font-weight:bold">reluare</text>
<line x1="95.0" y1="55.0" x2="95.0" y2="195.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="82.0" y="55.0" width="26" height="140.0" fill="#188a3e"/>
<text x="95.0" y="213.0" fill="#188a3e" text-anchor="middle" style="font-weight:bold">marubozu</text>
<line x1="200.0" y1="58.5" x2="200.0" y2="121.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="187.0" y="62.0" width="26" height="52.5" fill="#c00"/>
<text x="200.0" y="213.0" fill="#c00" text-anchor="middle" style="font-weight:bold">pullback</text>
<line x1="305.0" y1="34.0" x2="305.0" y2="118.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="292.0" y="37.5" width="26" height="73.5" fill="#188a3e"/>
<text x="305.0" y="213.0" fill="#188a3e" text-anchor="middle" style="font-weight:bold">reluare</text>
<text x="24" y="233.0" fill="#188a3e">marubozu bull — corp de 2.00, fără fitile</text>
<text x="24" y="250.0" fill="#c00">pullback — coboară până la 63.05, aproape de midpoint</text>
<text x="24" y="267.0" fill="#188a3e">reluare — cumpărătorii revin din midpoint</text>
@@ -427,12 +427,12 @@ opusă. În varianta strictă folosită în curs, îi depășește și fitilele.
<svg viewBox="0 0 620 362" xmlns="http://www.w3.org/2000/svg" style="width:100%;height:auto;font:12px sans-serif">
<polyline points="24,84.0 122,130.0" fill="none" stroke="#999" stroke-width="2"/>
<text x="24" y="72.0" fill="#999">downtrend anterior</text>
<line x1="130" y1="130.0" x2="130" y2="250.0" stroke="#1a1a1a" stroke-width="1.5"/>
<rect x="117" y="140.0" width="26" height="100.0" fill="#c00"/>
<text x="130" y="288.0" fill="#c00" text-anchor="middle" style="font-weight:bold">candela 1</text>
<line x1="250" y1="90.0" x2="250" y2="270.0" stroke="#1a1a1a" stroke-width="1.5"/>
<rect x="237" y="100.0" width="26" height="160.0" fill="#188a3e"/>
<text x="250" y="288.0" fill="#188a3e" text-anchor="middle" style="font-weight:bold">candela 2</text>
<line x1="130.0" y1="130.0" x2="130.0" y2="250.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="117.0" y="140.0" width="26" height="100.0" fill="#c00"/>
<text x="130.0" y="288.0" fill="#c00" text-anchor="middle" style="font-weight:bold">candela 1</text>
<line x1="250.0" y1="90.0" x2="250.0" y2="270.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="237.0" y="100.0" width="26" height="160.0" fill="#188a3e"/>
<text x="250.0" y="288.0" fill="#188a3e" text-anchor="middle" style="font-weight:bold">candela 2</text>
<text x="24" y="308.0" fill="#c00">candela 1 (mică, bear) — O 62.80, H 62.85, L 62.25, C 62.30</text>
<text x="24" y="325.0" fill="#188a3e">candela 2 (mare, bull) — O 62.20, H 63.05, L 62.15, C 63.00</text>
<text x="24" y="350" fill="#555">Corpul candelei 2 (62.20→63.00) cuprinde complet corpul candelei 1 (62.80→62.30). Scară: 0.10 = 20 px.</text>
@@ -468,12 +468,12 @@ Fără condiția 1, aștepți confirmare ca la restul.
<text x="592" y="270.0" fill="#c00" text-anchor="end">SL 62.10 — sub low-ul c2</text>
<polyline points="24,183.5 102,229.5" fill="none" stroke="#999" stroke-width="2"/>
<text x="24" y="171.5" fill="#999">downtrend anterior</text>
<line x1="110" y1="229.5" x2="110" y2="266.7" stroke="#1a1a1a" stroke-width="1.5"/>
<rect x="97" y="232.6" width="26" height="31.0" fill="#c00"/>
<text x="110" y="294.0" fill="#c00" text-anchor="middle" style="font-weight:bold">c1</text>
<line x1="220" y1="217.1" x2="220" y2="272.9" stroke="#1a1a1a" stroke-width="1.5"/>
<rect x="207" y="220.2" width="26" height="49.6" fill="#188a3e"/>
<text x="220" y="294.0" fill="#188a3e" text-anchor="middle" style="font-weight:bold">c2 — engulfing</text>
<line x1="110.0" y1="229.5" x2="110.0" y2="266.7" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="97.0" y="232.6" width="26" height="31.0" fill="#c00"/>
<text x="110.0" y="294.0" fill="#c00" text-anchor="middle" style="font-weight:bold">c1</text>
<line x1="220.0" y1="217.1" x2="220.0" y2="272.9" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="207.0" y="220.2" width="26" height="49.6" fill="#188a3e"/>
<text x="220.0" y="294.0" fill="#188a3e" text-anchor="middle" style="font-weight:bold">c2 — engulfing</text>
<text x="24" y="314.0" fill="#c00">candela 1 — cei care au vândut aici sunt acum pe pierdere</text>
<text x="24" y="331.0" fill="#188a3e">candela 2 — intri la închiderea ei</text>
<text x="24" y="356" fill="#555">Scară: 1.00 = 62 px. Risc 1.00, țintă 2.00 — exact dublul.</text>
@@ -498,12 +498,12 @@ complet **în interiorul corpului** candelei 1. Numele vine din japoneză,
<svg viewBox="0 0 620 372" xmlns="http://www.w3.org/2000/svg" style="width:100%;height:auto;font:12px sans-serif">
<polyline points="24,44.0 122,90.0" fill="none" stroke="#999" stroke-width="2"/>
<text x="24" y="32.0" fill="#999">downtrend anterior</text>
<line x1="130" y1="90.0" x2="130" y2="280.0" stroke="#1a1a1a" stroke-width="1.5"/>
<rect x="117" y="99.5" width="26" height="171.0" fill="#c00"/>
<text x="130" y="298.0" fill="#c00" text-anchor="middle" style="font-weight:bold">candela 1</text>
<line x1="250" y1="199.2" x2="250" y2="237.2" stroke="#1a1a1a" stroke-width="1.5"/>
<rect x="237" y="204.0" width="26" height="28.5" fill="#188a3e"/>
<text x="250" y="298.0" fill="#188a3e" text-anchor="middle" style="font-weight:bold">candela 2</text>
<line x1="130.0" y1="90.0" x2="130.0" y2="280.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="117.0" y="99.5" width="26" height="171.0" fill="#c00"/>
<text x="130.0" y="298.0" fill="#c00" text-anchor="middle" style="font-weight:bold">candela 1</text>
<line x1="250.0" y1="199.2" x2="250.0" y2="237.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="237.0" y="204.0" width="26" height="28.5" fill="#188a3e"/>
<text x="250.0" y="298.0" fill="#188a3e" text-anchor="middle" style="font-weight:bold">candela 2</text>
<text x="24" y="318.0" fill="#c00">candela 1 (mare, bear) — O 64.00, H 64.10, L 62.10, C 62.20</text>
<text x="24" y="335.0" fill="#188a3e">candela 2 (mică, bull) — O 62.60, C 62.90, interval total 0.40</text>
<text x="24" y="360" fill="#555">Candela 2 se tranzacționează într-un interval de 0.40, în interiorul corpului de 1.80 al candelei 1.</text>
@@ -544,12 +544,12 @@ corpul candelei 1 — doar trece de jumătatea lui (midpoint).
<text x="592" y="174.0" fill="#b06000" text-anchor="end">midpoint 63.15</text>
<polyline points="24,44.0 122,90.0" fill="none" stroke="#999" stroke-width="2"/>
<text x="24" y="32.0" fill="#999">downtrend anterior</text>
<line x1="130" y1="90.0" x2="130" y2="270.0" stroke="#1a1a1a" stroke-width="1.5"/>
<rect x="117" y="102.0" width="26" height="156.0" fill="#c00"/>
<text x="130" y="306.0" fill="#c00" text-anchor="middle" style="font-weight:bold">candela 1</text>
<line x1="250" y1="144.0" x2="250" y2="288.0" stroke="#1a1a1a" stroke-width="1.5"/>
<rect x="237" y="150.0" width="26" height="132.0" fill="#188a3e"/>
<text x="250" y="306.0" fill="#188a3e" text-anchor="middle" style="font-weight:bold">candela 2</text>
<line x1="130.0" y1="90.0" x2="130.0" y2="270.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="117.0" y="102.0" width="26" height="156.0" fill="#c00"/>
<text x="130.0" y="306.0" fill="#c00" text-anchor="middle" style="font-weight:bold">candela 1</text>
<line x1="250.0" y1="144.0" x2="250.0" y2="288.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="237.0" y="150.0" width="26" height="132.0" fill="#188a3e"/>
<text x="250.0" y="306.0" fill="#188a3e" text-anchor="middle" style="font-weight:bold">candela 2</text>
<text x="24" y="326.0" fill="#c00">candela 1 (bear) — O 63.80, C 62.50, midpoint 63.15</text>
<text x="24" y="343.0" fill="#188a3e">candela 2 (bull) — deschide sub 62.50 și închide la 63.40, peste midpoint</text>
<text x="24" y="368" fill="#555">Candela 2 recuperează peste jumătate din corpul candelei 1, dar nu tot. Scară: 0.10 = 12 px.</text>
@@ -592,15 +592,15 @@ care închide peste midpoint-ul primei.
<text x="592" y="179.0" fill="#b06000" text-anchor="end">midpoint 63.10 — pragul de validare</text>
<polyline points="24,44.0 87,90.0" fill="none" stroke="#999" stroke-width="2"/>
<text x="24" y="32.0" fill="#999">downtrend anterior</text>
<line x1="95" y1="90.0" x2="95" y2="280.0" stroke="#1a1a1a" stroke-width="1.5"/>
<rect x="82" y="99.5" width="26" height="171.0" fill="#c00"/>
<text x="95" y="317.0" fill="#c00" text-anchor="middle" style="font-weight:bold">c1 — bear</text>
<line x1="200" y1="270.5" x2="200" y2="299.0" stroke="#1a1a1a" stroke-width="1.5"/>
<rect x="187" y="284.7" width="26" height="4.7" fill="#c00"/>
<text x="200" y="317.0" fill="#c00" text-anchor="middle" style="font-weight:bold">c2 — indecizie</text>
<line x1="305" y1="147.0" x2="305" y2="284.7" stroke="#1a1a1a" stroke-width="1.5"/>
<rect x="292" y="156.5" width="26" height="123.5" fill="#188a3e"/>
<text x="305" y="317.0" fill="#188a3e" text-anchor="middle" style="font-weight:bold">c3 — bull</text>
<line x1="95.0" y1="90.0" x2="95.0" y2="280.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="82.0" y="99.5" width="26" height="171.0" fill="#c00"/>
<text x="95.0" y="317.0" fill="#c00" text-anchor="middle" style="font-weight:bold">c1 — bear</text>
<line x1="200.0" y1="270.5" x2="200.0" y2="299.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="187.0" y="284.7" width="26" height="4.7" fill="#c00"/>
<text x="200.0" y="317.0" fill="#c00" text-anchor="middle" style="font-weight:bold">c2 — indecizie</text>
<line x1="305.0" y1="147.0" x2="305.0" y2="284.7" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="292.0" y="156.5" width="26" height="123.5" fill="#188a3e"/>
<text x="305.0" y="317.0" fill="#188a3e" text-anchor="middle" style="font-weight:bold">c3 — bull</text>
<text x="24" y="337.0" fill="#c00">candela 1 — O 64.00, C 62.20, midpoint 63.10</text>
<text x="24" y="354.0" fill="#c00">candela 2 — corp mic, izolată jos: nici vânzătorii nu mai coboară</text>
<text x="24" y="371.0" fill="#188a3e">candela 3 — închide 63.40, peste midpoint-ul 63.10</text>
@@ -638,15 +638,15 @@ tratezi ca atare.
<text x="592" y="285.6" fill="#c00" text-anchor="end">SL 61.85 — sub low-ul c2</text>
<polyline points="24,151.1 87,197.1" fill="none" stroke="#999" stroke-width="2"/>
<text x="24" y="139.1" fill="#999">downtrend anterior</text>
<line x1="95" y1="197.1" x2="95" y2="281.1" stroke="#1a1a1a" stroke-width="1.5"/>
<rect x="82" y="201.3" width="26" height="75.6" fill="#c00"/>
<text x="95" y="309.6" fill="#c00" text-anchor="middle" style="font-weight:bold">c1</text>
<line x1="200" y1="276.9" x2="200" y2="289.5" stroke="#1a1a1a" stroke-width="1.5"/>
<rect x="187" y="283.2" width="26" height="2.1" fill="#c00"/>
<text x="200" y="309.6" fill="#c00" text-anchor="middle" style="font-weight:bold">c2</text>
<line x1="305" y1="222.3" x2="305" y2="283.2" stroke="#1a1a1a" stroke-width="1.5"/>
<rect x="292" y="226.5" width="26" height="54.6" fill="#188a3e"/>
<text x="305" y="309.6" fill="#188a3e" text-anchor="middle" style="font-weight:bold">c3</text>
<line x1="95.0" y1="197.1" x2="95.0" y2="281.1" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="82.0" y="201.3" width="26" height="75.6" fill="#c00"/>
<text x="95.0" y="309.6" fill="#c00" text-anchor="middle" style="font-weight:bold">c1</text>
<line x1="200.0" y1="276.9" x2="200.0" y2="289.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="187.0" y="283.2" width="26" height="2.1" fill="#c00"/>
<text x="200.0" y="309.6" fill="#c00" text-anchor="middle" style="font-weight:bold">c2</text>
<line x1="305.0" y1="222.3" x2="305.0" y2="283.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="292.0" y="226.5" width="26" height="54.6" fill="#188a3e"/>
<text x="305.0" y="309.6" fill="#188a3e" text-anchor="middle" style="font-weight:bold">c3</text>
<text x="24" y="329.6" fill="#c00">low-ul candelei 2 (61.90) e nivelul de invalidare</text>
<text x="24" y="346.6" fill="#188a3e">intri la închiderea candelei 3, la 63.45</text>
<text x="24" y="372" fill="#555">Scară: 1.00 = 42 px. Risc 1.60, țintă 3.20 — exact dublul.</text>
@@ -663,15 +663,15 @@ prin gap-uri — high-ul ei e sub low-ul candelei 1 **și** sub low-ul candelei
<svg viewBox="0 0 620 430" xmlns="http://www.w3.org/2000/svg" style="width:100%;height:auto;font:12px sans-serif">
<polyline points="24,44.0 87,90.0" fill="none" stroke="#999" stroke-width="2"/>
<text x="24" y="32.0" fill="#999">downtrend anterior</text>
<line x1="95" y1="90.0" x2="95" y2="255.0" stroke="#1a1a1a" stroke-width="1.5"/>
<rect x="82" y="101.0" width="26" height="143.0" fill="#c00"/>
<text x="95" y="339.0" fill="#c00" text-anchor="middle" style="font-weight:bold">c1 — bear</text>
<line x1="200" y1="293.5" x2="200" y2="321.0" stroke="#1a1a1a" stroke-width="1.5"/>
<rect x="187" y="304.5" width="26" height="5.5" fill="#c00"/>
<text x="200" y="339.0" fill="#c00" text-anchor="middle" style="font-weight:bold">c2 — abandonată</text>
<line x1="305" y1="123.0" x2="305" y2="271.5" stroke="#1a1a1a" stroke-width="1.5"/>
<rect x="292" y="134.0" width="26" height="132.0" fill="#188a3e"/>
<text x="305" y="339.0" fill="#188a3e" text-anchor="middle" style="font-weight:bold">c3 — bull</text>
<line x1="95.0" y1="90.0" x2="95.0" y2="255.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="82.0" y="101.0" width="26" height="143.0" fill="#c00"/>
<text x="95.0" y="339.0" fill="#c00" text-anchor="middle" style="font-weight:bold">c1 — bear</text>
<line x1="200.0" y1="293.5" x2="200.0" y2="321.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="187.0" y="304.5" width="26" height="5.5" fill="#c00"/>
<text x="200.0" y="339.0" fill="#c00" text-anchor="middle" style="font-weight:bold">c2 — abandonată</text>
<line x1="305.0" y1="123.0" x2="305.0" y2="271.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="292.0" y="134.0" width="26" height="132.0" fill="#188a3e"/>
<text x="305.0" y="339.0" fill="#188a3e" text-anchor="middle" style="font-weight:bold">c3 — bull</text>
<text x="24" y="359.0" fill="#c00">candela 1 — low-ul ei e 62.60</text>
<text x="24" y="376.0" fill="#c00">candela 2 — high-ul ei (62.25) e SUB low-ul candelei 1: gap</text>
<text x="24" y="393.0" fill="#188a3e">candela 3 — low-ul ei (62.45) e PESTE high-ul candelei 2: al doilea gap</text>
@@ -701,15 +701,15 @@ nu apare.
fiecare deschizând în interiorul corpului precedentei și închizând peste
high-ul ei (sau sub low, la variantă bear).
<svg viewBox="0 0 620 323" xmlns="http://www.w3.org/2000/svg" style="width:100%;height:auto;font:12px sans-serif">
<line x1="110" y1="167.0" x2="110" y2="247.8" stroke="#1a1a1a" stroke-width="1.5"/>
<rect x="97" y="171.7" width="26" height="71.2" fill="#188a3e"/>
<text x="110" y="265.8" fill="#188a3e" text-anchor="middle" style="font-weight:bold">1</text>
<line x1="215" y1="100.5" x2="215" y2="205.0" stroke="#1a1a1a" stroke-width="1.5"/>
<rect x="202" y="105.2" width="26" height="95.0" fill="#188a3e"/>
<text x="215" y="265.8" fill="#188a3e" text-anchor="middle" style="font-weight:bold">2</text>
<line x1="320" y1="34.0" x2="320" y2="138.5" stroke="#1a1a1a" stroke-width="1.5"/>
<rect x="307" y="38.7" width="26" height="95.0" fill="#188a3e"/>
<text x="320" y="265.8" fill="#188a3e" text-anchor="middle" style="font-weight:bold">3</text>
<line x1="110.0" y1="167.0" x2="110.0" y2="247.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="97.0" y="171.7" width="26" height="71.2" fill="#188a3e"/>
<text x="110.0" y="265.8" fill="#188a3e" text-anchor="middle" style="font-weight:bold">1</text>
<line x1="215.0" y1="100.5" x2="215.0" y2="205.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="202.0" y="105.2" width="26" height="95.0" fill="#188a3e"/>
<text x="215.0" y="265.8" fill="#188a3e" text-anchor="middle" style="font-weight:bold">2</text>
<line x1="320.0" y1="34.0" x2="320.0" y2="138.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="307.0" y="38.7" width="26" height="95.0" fill="#188a3e"/>
<text x="320.0" y="265.8" fill="#188a3e" text-anchor="middle" style="font-weight:bold">3</text>
<text x="24" y="285.8" fill="#188a3e">fiecare deschide în corpul precedentei și închide peste high-ul ei</text>
<text x="24" y="311" fill="#555">Three White Soldiers. Varianta bear (Three Black Crows) e oglinda exactă.</text>
</svg>

View File

@@ -9,6 +9,17 @@ inversată. Aici coordonatele se calculează din prețuri, deci scara e
corectă prin construcție.
Convenție: un singur `scale` (px per unitate de preț) pentru tot desenul.
Două stiluri de desen:
1. **Ilustrativ** (implicit, `gap=92`, 3-6 candele): pentru definiții de
pattern-uri, unde contează forma unei candele, nu trendul.
2. **Grafic real** (`gap=14`, `body_w=9`, 25-45 de candele, `vol_h=70`):
serie continuă de preț cu panou de volum dedesubt, ca pe TradingView.
Userul a cerut explicit stilul ăsta pentru orice diagramă unde trebuie
să se vadă un trend sau relația preț-volum. Etichetează candelele care
contează cu `marks`, nu cu `tag` — la 40 de candele etichetele per
candelă se suprapun.
"""
from dataclasses import dataclass, field
@@ -20,9 +31,10 @@ class Candle:
h: float
l: float
c: float
tag: str = "" # eticheta de sub candelă
note: str = "" # a doua linie de etichetă
tag: str = "" # eticheta de sub candelă (stil ilustrativ)
note: str = "" # linie de text în blocul de sub grafic
color: str | None = None # implicit: verde dacă c >= o
vol: float | None = None # volumul barei, pentru panoul de volum
@dataclass
@@ -31,12 +43,34 @@ class Level:
text: str
color: str = "#1a5fb4"
dash: str = "4 3"
label_at: str = "right" # "right" | "left" — unde stă eticheta
@dataclass
class Mark:
"""Etichetă ancorată pe o candelă anume, pentru serii lungi."""
i: int # indexul candelei
text: str
color: str = "#1a5fb4"
where: str = "above" # "above" | "below" — față de candelă
dy: float = 0.0 # ajustare fină, dacă se suprapune cu altceva
@dataclass
class MA:
"""Medie mobilă simplă, calculată din close-uri."""
period: int
color: str = "#b06000"
dash: str = ""
text: str = ""
@dataclass
class Chart:
candles: list[Candle]
levels: list[Level] = field(default_factory=list)
marks: list[Mark] = field(default_factory=list)
mas: list[MA] = field(default_factory=list)
scale: float = 60.0 # px per unitate de preț
width: int = 620
body_w: int = 26
@@ -46,33 +80,60 @@ class Chart:
label_gap: int = 34 # spațiu sub cea mai joasă candelă pentru etichete
caption: str = ""
trend: str = "" # "down" / "up" / "" — segment de trend înainte
vol_h: float = 0.0 # înălțimea panoului de volum (0 = fără panou)
vol_gap: float = 26.0 # spațiu între panoul de preț și cel de volum
vol_label: str = "volum"
title: str = "" # ex. "SPY — daily"
def _y(self, price: float) -> float:
return self.top + (self._pmax - price) * self.scale
def _cx(self, i: int) -> float:
return self.left + i * self.gap
def render(self) -> str:
prices = [p for c in self.candles for p in (c.o, c.h, c.l, c.c)]
prices += [lv.price for lv in self.levels]
self._pmax, pmin = max(prices), min(prices)
if self.title:
self.top += 22
# segmentul de trend iese din zona candelelor: rezervă loc pentru el,
# altfel se suprapune peste etichete sau peste marginea desenului
if self.trend == "down":
self.top += 56
body_bottom = self.top + (self._pmax - pmin) * self.scale
extra = 62 if self.trend == "up" else 0
panel_bottom = body_bottom + extra
vols = [c.vol for c in self.candles if c.vol is not None]
vol_top = vol_base = None
if self.vol_h and vols:
vol_top = panel_bottom + self.vol_gap
vol_base = vol_top + self.vol_h
panel_bottom = vol_base
n_notes = sum(1 for c in self.candles if c.note)
height = body_bottom + extra + self.label_gap + 17 * n_notes + (24 if self.caption else 8)
tags = any(c.tag for c in self.candles)
height = (panel_bottom + (self.label_gap if tags else 18)
+ 17 * n_notes + (24 if self.caption else 8))
out = [f'<svg viewBox="0 0 {self.width} {height:.0f}" xmlns="http://www.w3.org/2000/svg" '
f'style="width:100%;height:auto;font:12px sans-serif">']
# nivelurile: linie pe toată lățimea, eticheta deasupra liniei la dreapta
if self.title:
out.append(f'<text x="24" y="{self.top - (56 if self.trend == "down" else 0) - 8:.0f}" '
f'fill="#333" style="font-weight:bold">{self.title}</text>')
# nivelurile: linie pe toată lățimea, eticheta deasupra liniei
for lv in self.levels:
y = self._y(lv.price)
out.append(f'<line x1="24" y1="{y:.1f}" x2="{self.width - 14}" y2="{y:.1f}" '
f'stroke="{lv.color}" stroke-width="1.5" stroke-dasharray="{lv.dash}"/>')
out.append(f'<text x="{self.width - 28}" y="{y - 6:.1f}" fill="{lv.color}" '
f'text-anchor="end">{lv.text}</text>')
if lv.label_at == "left":
out.append(f'<text x="30" y="{y - 6:.1f}" fill="{lv.color}">{lv.text}</text>')
else:
out.append(f'<text x="{self.width - 28}" y="{y - 6:.1f}" fill="{lv.color}" '
f'text-anchor="end">{lv.text}</text>')
x0 = self.left
if self.trend:
@@ -86,22 +147,72 @@ class Chart:
out.append(f'<text x="24" y="{ty:.1f}" fill="#999">'
f'{"downtrend anterior" if self.trend == "down" else "uptrend anterior"}</text>')
# mediile mobile, sub candele ca ordine de desen
closes = [c.c for c in self.candles]
for ma in self.mas:
pts = []
for i in range(len(closes)):
if i + 1 >= ma.period:
avg = sum(closes[i + 1 - ma.period:i + 1]) / ma.period
pts.append(f"{self._cx(i):.1f},{self._y(avg):.1f}")
if len(pts) >= 2:
dash = f' stroke-dasharray="{ma.dash}"' if ma.dash else ""
out.append(f'<polyline points="{" ".join(pts)}" fill="none" '
f'stroke="{ma.color}" stroke-width="1.5"{dash}/>')
if ma.text:
last = pts[-1].split(",")
out.append(f'<text x="{float(last[0]) + 6:.0f}" y="{float(last[1]) - 6:.0f}" '
f'fill="{ma.color}">{ma.text}</text>')
for i, cd in enumerate(self.candles):
cx = x0 + i * self.gap
cx = self._cx(i)
top_body, bot_body = max(cd.o, cd.c), min(cd.o, cd.c)
color = cd.color or ("#188a3e" if cd.c >= cd.o else "#c00")
out.append(f'<line x1="{cx}" y1="{self._y(cd.h):.1f}" x2="{cx}" y2="{self._y(cd.l):.1f}" '
f'stroke="#1a1a1a" stroke-width="1.5"/>')
h = max(self._y(bot_body) - self._y(top_body), 2.0)
out.append(f'<rect x="{cx - self.body_w // 2}" y="{self._y(top_body):.1f}" '
out.append(f'<line x1="{cx:.1f}" y1="{self._y(cd.h):.1f}" x2="{cx:.1f}" '
f'y2="{self._y(cd.l):.1f}" stroke="#1a1a1a" stroke-width="1.2"/>')
h = max(self._y(bot_body) - self._y(top_body), 1.6)
out.append(f'<rect x="{cx - self.body_w / 2:.1f}" y="{self._y(top_body):.1f}" '
f'width="{self.body_w}" height="{h:.1f}" fill="{color}"/>')
if cd.tag:
out.append(f'<text x="{cx}" y="{body_bottom + extra + 18:.1f}" fill="{color}" '
out.append(f'<text x="{cx:.1f}" y="{panel_bottom + 18:.1f}" fill="{color}" '
f'text-anchor="middle" style="font-weight:bold">{cd.tag}</text>')
# detaliile OHLC ca bloc stânga-jos, câte o linie per candelă: centrate sub
# panoul de volum: bare colorate pe direcția candelei
if vol_top is not None:
assert vol_base is not None
vmax = max(vols)
out.append(f'<line x1="24" y1="{vol_base:.1f}" x2="{self.width - 14}" '
f'y2="{vol_base:.1f}" stroke="#888"/>')
for i, cd in enumerate(self.candles):
if cd.vol is None:
continue
bh = cd.vol / vmax * self.vol_h
color = cd.color or ("#188a3e" if cd.c >= cd.o else "#c00")
out.append(f'<rect x="{self._cx(i) - self.body_w / 2:.1f}" '
f'y="{vol_base - bh:.1f}" width="{self.body_w}" '
f'height="{bh:.1f}" fill="{color}" opacity="0.55"/>')
out.append(f'<text x="24" y="{vol_top - 6:.1f}" fill="#555">{self.vol_label}</text>')
# etichete ancorate pe candele anume
for mk in self.marks:
cd = self.candles[mk.i]
cx = self._cx(mk.i)
if mk.where == "above":
y = self._y(cd.h) - 10 + mk.dy
else:
# sub candelă, dar niciodată peste panoul de volum
y = min(self._y(cd.l) + 20 + mk.dy, body_bottom + 14)
anchor = "middle"
if cx < 90:
anchor, cx = "start", cx - 12
elif cx > self.width - 90:
anchor, cx = "end", cx + 12
out.append(f'<text x="{cx:.1f}" y="{y:.1f}" fill="{mk.color}" '
f'text-anchor="{anchor}" style="font-weight:bold">{mk.text}</text>')
# detaliile ca bloc stânga-jos, câte o linie per candelă: centrate sub
# candelă se suprapun între ele de îndată ce textul depășește `gap`.
ny = body_bottom + extra + self.label_gap + 4
ny = panel_bottom + (self.label_gap if tags else 18) + 4
for cd in self.candles:
if cd.note:
color = cd.color or ("#188a3e" if cd.c >= cd.o else "#c00")
@@ -114,8 +225,46 @@ class Chart:
return "\n ".join(out).replace("\n </svg>", "\n</svg>")
def walk(start: float, moves: list[tuple[float, float, float]],
vols: list[float] | None = None) -> list[Candle]:
"""Construiește o serie continuă de candele dintr-un preț de start.
Fiecare `move` e (delta_close, wick_sus, wick_jos) față de open-ul barei,
iar open-ul fiecărei bare e close-ul barei anterioare — deci seria nu are
goluri artificiale, exact ca pe un grafic real.
"""
out, o = [], start
for i, (dc, wu, wd) in enumerate(moves):
c = o + dc
h = max(o, c) + wu
l = min(o, c) - wd
out.append(Candle(o, h, l, c, vol=(vols[i] if vols else None)))
o = c
return out
def from_closes(closes: list[float], wick: float = 0.0,
wicks: list[tuple[float, float]] | None = None,
vols: list[float] | None = None,
start: float | None = None) -> list[Candle]:
"""Serie continuă de candele dintr-o listă de close-uri.
Open-ul fiecărei bare = close-ul precedentei, deci structura (swing
high-uri, swing low-uri) e exact cea din lista de close-uri — util când
diagrama trebuie să susțină o afirmație despre niveluri.
"""
out = []
o = start if start is not None else closes[0]
for i, c in enumerate(closes):
wu, wd = wicks[i] if wicks else (wick, wick)
out.append(Candle(o, max(o, c) + wu, min(o, c) - wd, c,
vol=(vols[i] if vols else None)))
o = c
return out
def selftest():
"""O unitate de preț trebuie să dea aceeași distanță oriunde pe desen."""
"""Scara trebuie să fie aceeași oriunde, iar volumul proporțional."""
ch = Chart(candles=[Candle(o=63.05, h=63.30, l=62.10, c=63.30)],
levels=[Level(62.05, "SL"), Level(63.35, "entry"), Level(65.95, "TP 2R")],
scale=60)
@@ -125,7 +274,28 @@ def selftest():
castig = ys[63.35] - ys[65.95] # px, în sus
assert abs(castig / risc - 2.0) < 0.01, f"2R desenat la {castig/risc:.2f}R"
assert "<svg" in svg and svg.count("\n\n") == 0, "linie goală în svg rupe markdown-ul"
print("selftest ok: 2R desenat la exact 2R, fără linii goale")
# serie continuă: open-ul fiecărei bare = close-ul precedentei
cds = walk(100.0, [(1.0, 0.2, 0.3), (-0.5, 0.4, 0.1), (2.0, 0.1, 0.2)],
vols=[10.0, 20.0, 40.0])
for a, b in zip(cds, cds[1:]):
assert abs(a.c - b.o) < 1e-9, "serie cu gol artificial între bare"
# panoul de volum: înălțimea barei ∝ volum
ch2 = Chart(candles=cds, scale=40, gap=14, body_w=9, vol_h=70)
svg2 = ch2.render()
hs = [float(m) for m in __import__("re").findall(r'height="([\d.]+)" fill="#\w+" opacity', svg2)]
assert len(hs) == 3, f"panoul de volum a desenat {len(hs)} bare"
assert abs(hs[2] / hs[0] - 4.0) < 0.01, f"volum 4x desenat la {hs[2]/hs[0]:.2f}x"
assert abs(hs[1] / hs[0] - 2.0) < 0.01, f"volum 2x desenat la {hs[1]/hs[0]:.2f}x"
# structura din close-uri e păstrată exact
cl = [100.0, 98.0, 99.0, 96.5, 98.5, 101.0]
ser = from_closes(cl, wick=0.2)
assert [round(c.c, 2) for c in ser] == cl, "close-urile nu sunt respectate"
assert min(c.l for c in ser[:3]) > min(c.l for c in ser[3:5]), \
"al doilea picior ar trebui să fie mai jos"
print("selftest ok: 2R la exact 2R, serie fără goluri, volum proporțional")
if __name__ == "__main__":