Opt erau blocante, adica un cititor ar fi executat gresit pe cont: - 56% era legat de o intrare la 133 care da de fapt 35,3%; cifra se propaga in dimensionarea pozitiei. Acum spune ca 56% e rostit separat si ca raportul nu se verifica. - "2/3 taie jumatate din risc" era fals (29,3% vs 35,3%). - definitia lui buy doji se contrazicea cu regula de 4 luni din acelasi document. - diagrama de trail arata stopul deasupra pretului: liniile traversau tot graficul, deci in stanga stateau peste candele. Acum fiecare porneste din swing low-ul care o declanseaza, cu cele patru swing low-uri marcate. - linia gatului era data ca "media celor doua varfuri", ceea ce nu reproduce 63.00 pe propriul desen; e din interiorul gatului. - singura regula mecanica de "nu intra" nu era desenata cu mecanismul ei: panoul de volum avea bare egale de la o baza, in loc de histograma cu semn. - "SL minim 54.17" se citea invers decat intelege textul: e pretul cel mai sus permis, nu stopul cel mai stramt. - lectura "TP2 vine inaintea lui TP1" era prezentata ca geometrie in doua diagrame, desi fraza-sursa e neinteligibila si sursa sprijina la fel de bine varianta in care se recalculeaza si TP si SL. Acum e marcata ca lectura chiar acolo unde e folosita, cu varianta alternativa alaturi. svg_candles are trei adaugiri optionale (Level.i0, Level.label_dy, Chart.vol_zero) cerute de reparatiile de mai sus; selftest-ul trece si diagramele existente nu se schimba daca nu sunt regenerate. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ThN7ixZrowca8aTGrK1MUJ
586 lines
34 KiB
Python
586 lines
34 KiB
Python
"""Diagramele pentru summaries/06_MODUL_5/05_strategia_8_dhrt.md.
|
||
|
||
Toate graficele cu candele / intrare / stop / ținte se construiesc cu
|
||
svg_candles (o singură scară px/unitate pe desen), fiecare cifră din etichete
|
||
e verificată prin assert, apoi se substituie marcatorii <!--SVG:nume--> din .md.
|
||
|
||
Regula de cifre: pe etichete intră DOAR prețuri rostite în transcriere (51, la
|
||
XLF) sau prețuri din exemplul construit de mine, declarat ca atare în titlu și
|
||
în caption. Valorile derivate din geometria cursului (SL la 1/3, TP1 = R,
|
||
TP2 = proiecția gâtului) apar doar pe desenele construite, unde seria e a mea.
|
||
|
||
Cele două schițe fără candele (`hs_pe_volum`, `cadrane`) sunt scrise de mână.
|
||
|
||
Rulează: .venv/bin/python gen/strategia_8_dhrt.py
|
||
"""
|
||
import os
|
||
import re
|
||
import sys
|
||
|
||
sys.path.insert(0, "/workspace/atm-curs-dl")
|
||
from svg_candles import Chart, Level, Mark, MA, Zone, from_closes # noqa: E402
|
||
|
||
MD = ("/workspace/atm-curs-dl/summaries/06_MODUL_5/"
|
||
"05_strategia_8_dhrt.md")
|
||
|
||
SVG = {}
|
||
|
||
|
||
# ----------------------------------------------------------------- unelte mici
|
||
def vols(n, base=44, span=12, **over):
|
||
v = [float(base + ((i * 7) % span) - span // 2) for i in range(n)]
|
||
for k, x in over.items():
|
||
v[int(k[1:])] = float(x)
|
||
return v
|
||
|
||
|
||
def floor_lows(cds, a, b, price):
|
||
"""Scurtează fitilele de jos ale barelor a..b până la `price`.
|
||
|
||
Nu atinge corpurile: dacă un open sau close ar fi sub nivel, crapă. Lungimea
|
||
fitilului e oricum un parametru liber al desenului; aici o fixez ca mai
|
||
multe bare să se oprească exact pe nivelul pe care îl afirmă eticheta.
|
||
"""
|
||
for c in cds[a:b + 1]:
|
||
assert min(c.o, c.c) >= price, f"corpul barei trece sub {price}"
|
||
if c.l < price:
|
||
c.l = price
|
||
return price
|
||
|
||
|
||
def lo(cds, a, b):
|
||
return round(min(c.l for c in cds[a:b + 1]), 2)
|
||
|
||
|
||
def hi(cds, a, b):
|
||
return round(max(c.h for c in cds[a:b + 1]), 2)
|
||
|
||
|
||
def deco(ch, name, distante=(), intregi=()):
|
||
"""Randează, verifică etichetele, salvează.
|
||
|
||
`distante` = numere cu două zecimale permise în etichete fără să fie
|
||
niveluri desenate (R-uri, înălțimi de pattern). `intregi` = întregi
|
||
rostiți în transcriere, permiși în etichete.
|
||
"""
|
||
svg = ch.render()
|
||
assert "\n\n" not in svg, f"{name}: linie goală în svg"
|
||
assert len(ch.caption) <= 118, f"{name}: caption de {len(ch.caption)} caractere"
|
||
desenate = ({f"{lv.price:.2f}" for lv in ch.levels}
|
||
| {f"{z.p_top:.2f}" for z in ch.zones if z.p_top is not None}
|
||
| {f"{z.p_bot:.2f}" for z in ch.zones if z.p_bot is not None}
|
||
| {f"{d:.2f}" for d in distante})
|
||
permise_int = ({str(i) for i in intregi}
|
||
| {f"{lv.price:.0f}" for lv in ch.levels})
|
||
for t in re.findall(r"<text\b[^>]*>(.*?)</text>", svg, re.S):
|
||
plain = re.sub(r"<[^>]+>", "", t)
|
||
for m in re.finditer(r"\d+\.\d\d", plain):
|
||
assert m.group(0) in desenate, \
|
||
f"{name}: eticheta numește {m.group(0)}, care nu e un nivel desenat"
|
||
for m in re.finditer(
|
||
r"(?<![\d.\-/])(\d{2,3})(?![\d.])"
|
||
r"(?!\s*(?:candele|bare|săptămâni|luni|perioade|%))", plain):
|
||
assert m.group(1) in permise_int, \
|
||
f"{name}: eticheta numește întregul {m.group(1)}, nepermis"
|
||
sus = min(ch._y(c.h) for c in ch.candles)
|
||
jos = max(ch._y(c.l) for c in ch.candles)
|
||
for lv in ch.levels:
|
||
assert ch._y(lv.price) > sus - 0.06 * (jos - sus), \
|
||
f"{name}: nivelul {lv.price} e lipit de marginea de sus"
|
||
assert ch._y(lv.price) < jos + 0.06 * (jos - sus), \
|
||
f"{name}: nivelul {lv.price} e lipit de marginea de jos"
|
||
SVG[name] = svg
|
||
print(f" {name}: {len(ch.candles)} bare, {len(ch.levels)} niveluri")
|
||
|
||
|
||
CONSTRUIT = ("Exemplu construit (fără ticker), la scară. Geometria e a cursului; "
|
||
"prețurile și R-urile sunt ale mele.")
|
||
|
||
# ===================================================== seria principală (HS răsturnat)
|
||
# O singură serie construită, folosită de harta, pasul 3 (stopul) și pasul 4
|
||
# (țintele), ca să nu se schimbe cifrele de la o secțiune la alta.
|
||
CL = (
|
||
[71.00, 69.00, 67.00, 65.00, 63.00, 61.00] # 0-5 downtrend anterior
|
||
+ [59.50, 58.80, 60.50, 62.00] # 6-9 umărul stâng
|
||
+ [62.80, 62.00, 63.00] # 10-12 gâtul, stânga (high 63.00)
|
||
+ [61.00, 58.00, 56.00, 54.00, 53.00, 52.30] # 13-18 căderea în cap
|
||
+ [54.00, 56.50, 58.50, 60.50, 62.00, 63.00] # 19-24 urcarea din cap
|
||
+ [62.30, 61.00, 59.30, 58.80, 59.60, 59.00] # 25-30 umărul drept
|
||
+ [59.10] # 31 doji
|
||
+ [65.00] # 32 breakout = intrare
|
||
+ [63.90, 64.60, 66.50, 69.00, 71.00] # 33-37 retest, apoi urcă
|
||
+ [73.00, 71.80, 74.00, 76.10, 77.50]) # 38-42 TP2, TP1, trail
|
||
WK = (
|
||
[(0.40, 0.40)] * 6
|
||
+ [(0.30, 0.30), (0.30, 0.30), (0.30, 0.30), (0.30, 0.30)]
|
||
+ [(0.20, 0.30), (0.30, 0.30), (0.00, 0.30)] # 12: high-ul gâtului stâng
|
||
+ [(0.30, 0.30)] * 5 + [(0.30, 0.30)]
|
||
+ [(0.30, 0.30)] * 5 + [(0.00, 0.30)] # 24: high-ul gâtului drept
|
||
+ [(0.30, 0.30), (0.30, 0.30), (0.30, 0.30), (0.30, 0.30),
|
||
(0.30, 0.30), (0.40, 0.30)]
|
||
+ [(0.90, 0.50)] # 31: fitile de doji
|
||
+ [(0.20, 0.20)] # 32: candela de breakout
|
||
+ [(0.40, 0.40)] * 5
|
||
+ [(0.40, 0.40)] * 5)
|
||
VL = vols(43, 46, 12, i27=56, i28=60, i29=64, i30=68, i31=74, i32=112,
|
||
i33=62, i34=66, i35=78, i36=84, i37=80, i39=86, i40=92)
|
||
CDS = from_closes(CL, wicks=WK, vols=VL, start=73.00)
|
||
|
||
CAP = lo(CDS, 13, 18) # low-ul capului
|
||
UMD = lo(CDS, 25, 31) # nivelul umărului drept
|
||
UMS = lo(CDS, 6, 9) # nivelul umărului stâng
|
||
H = round(UMD - CAP, 2) # înălțimea cap -> umăr drept
|
||
SL = round(CAP + H / 3, 2) # 1/3 de la cap: marginea de sus a treimii
|
||
E = CDS[32].c # intrarea, la close-ul candelei de breakout
|
||
R = round(E - SL, 2)
|
||
TP1 = round(E + R, 2)
|
||
# „măsoară din interiorul gâtului pe aici, iau un average" (`05_7_...txt:1304`):
|
||
# nivelul se ia din interiorul gâtului, NU de la cele mai de sus vârfuri. Pe
|
||
# desen vârfurile sunt mai sus (barele 13 și 25 ating 63.30), de aceea eticheta
|
||
# nu are voie să spună „media celor două vârfuri".
|
||
GAT = round((hi(CDS, 12, 12) + hi(CDS, 24, 24)) / 2, 2)
|
||
H_HS = round(GAT - CAP, 2)
|
||
TP2 = round(GAT + H_HS, 2)
|
||
|
||
assert (CAP, UMD, UMS) == (52.00, 58.50, 58.50), (CAP, UMD, UMS)
|
||
assert H == 6.50, H
|
||
assert SL == 54.17 and CAP < SL < UMD, (CAP, SL, UMD)
|
||
assert abs(CDS[31].c - CDS[31].o) <= 0.15 * (CDS[31].h - CDS[31].l), \
|
||
"candela 31 nu e doji"
|
||
assert CDS[32].c > hi(CDS, 31, 31) and CDS[32].c > GAT, \
|
||
"candela de breakout trebuie să închidă peste doji ȘI peste gât"
|
||
assert lo(CDS, 33, 37) > SL, "retestul nu trebuie să atingă stopul"
|
||
assert hi(CDS, 38, 42) >= TP1 > TP2, \
|
||
"aici TP1 (=R) iese peste TP2 (proiecția gâtului) — exact cazul din lecție"
|
||
assert lo(CDS, 13, 18) < lo(CDS, 6, 9) and lo(CDS, 13, 18) < lo(CDS, 25, 31), \
|
||
"capul trebuie să fie mai jos decât ambii umeri"
|
||
assert VL[32] > max(VL[25:32]), "candela de breakout nu are volumul cel mai mare"
|
||
# regresia de prins: eticheta gâtului spune „din interiorul gâtului, nu de la
|
||
# vârfuri", deci nivelul trebuie să stea strict sub cel mai de sus vârf al zonei
|
||
assert GAT < max(hi(CDS, 10, 13), hi(CDS, 24, 25)), \
|
||
f"gâtul {GAT} nu e sub vârfurile zonei: eticheta ar minți"
|
||
print(f"construit: cap {CAP} umeri {UMS}/{UMD} H {H} SL {SL} intrare {E}"
|
||
f" R {R} TP1 {TP1} gât {GAT} (h {H_HS}) TP2 {TP2}")
|
||
|
||
# ------------------------------------------------------------------- 1. harta
|
||
deco(Chart(
|
||
candles=CDS,
|
||
levels=[Level(SL, f"SL {SL:.2f}", "#c00", label_at="left"),
|
||
Level(E, f"intrare {E:.2f}", "#188a3e"),
|
||
Level(GAT, f"linia gâtului {GAT:.2f}", "#6a3d9a"),
|
||
Level(TP2, f"TP2 {TP2:.2f} = proiecția gâtului", "#1a5fb4",
|
||
label_at="left"),
|
||
Level(TP1, f"TP1 {TP1:.2f} = distanța stopului", "#1a5fb4")],
|
||
marks=[Mark(7, "umărul stâng", "#6a3d9a", "below", dy=14),
|
||
Mark(17, "capul", "#6a3d9a", "below", dy=16),
|
||
Mark(31, "doji", "#b06000", "below", dy=8),
|
||
Mark(32, "buy doji = trigger", "#188a3e", dy=-40),
|
||
Mark(34, "pullback la nivelul de breakout", "#b06000", "below", dy=22),
|
||
Mark(41, "apoi trail", "#1a5fb4", dy=-8)],
|
||
zones=[Zone(25, 31, "umărul drept", "#188a3e")],
|
||
mas=[MA(20, "#b06000", dash="4 3")],
|
||
scale=11.0, gap=15, body_w=9, left=58, width=700, top=46, vol_h=70,
|
||
vol_label="volum", title="DHRT, tot trade-ul — HS răsturnat (exemplu construit)",
|
||
caption=CONSTRUIT), "harta", distante=(R, H, H_HS))
|
||
|
||
# ===================================== 2. pasul 1, DA — capul în suport, umărul pe pivot
|
||
S_CL = (
|
||
[48.00, 46.50, 45.00, 43.50, 42.00] # 0-4 downtrend
|
||
+ [40.60, 40.10, 41.50, 43.00] # 5-8 umărul stâng
|
||
+ [43.60, 42.80, 43.50] # 9-11 gâtul
|
||
+ [41.50, 39.00, 37.50, 36.30] # 12-15 căderea în cap
|
||
+ [37.80, 39.50, 41.00, 42.60, 43.40] # 16-20 urcarea
|
||
+ [42.40, 41.20, 40.30, 40.10, 40.80, 40.40] # 21-26 umărul drept
|
||
+ [40.50, 44.20, 45.60, 47.00, 48.50]) # 27-31 doji, breakout, urcă
|
||
S_WK = ([(0.40, 0.40)] * 5
|
||
+ [(0.30, 0.30), (0.30, 0.10), (0.30, 0.10), (0.30, 0.30)]
|
||
+ [(0.30, 0.30), (0.30, 0.30), (0.50, 0.30)]
|
||
+ [(0.30, 0.30), (0.30, 0.30), (0.30, 0.30), (0.30, 0.30)]
|
||
+ [(0.30, 0.30)] * 4 + [(0.40, 0.30)]
|
||
+ [(0.30, 0.30), (0.30, 0.30), (0.30, 0.30), (0.30, 0.10),
|
||
(0.30, 0.10), (0.30, 0.30)]
|
||
+ [(0.70, 0.40)] + [(0.20, 0.20)] + [(0.40, 0.40)] * 3)
|
||
S_VL = vols(32, 44, 12, i23=54, i24=58, i25=62, i26=66, i27=70, i28=104, i29=84)
|
||
SC = from_closes(S_CL, wicks=S_WK, vols=S_VL, start=49.00)
|
||
SUP, PIV = lo(SC, 12, 15), lo(SC, 21, 27)
|
||
assert SUP == 36.00 and PIV == 40.00, (SUP, PIV)
|
||
assert PIV > SUP, "umărul drept trebuie să stea peste suportul capului"
|
||
assert abs(lo(SC, 5, 8) - PIV) <= 0.20, "umerii trebuie să fie aproximativ simetrici"
|
||
assert abs(SC[27].c - SC[27].o) <= 0.15 * (SC[27].h - SC[27].l), "bara 27 nu e doji"
|
||
assert SC[28].c > hi(SC, 27, 27) and SC[28].c > hi(SC, 9, 11), \
|
||
"breakout-ul trebuie să treacă peste doji și peste gât"
|
||
deco(Chart(
|
||
candles=SC,
|
||
levels=[Level(SUP, f"suport monthly {SUP:.2f}", "#b06000", label_at="left"),
|
||
Level(PIV, f"pivot monthly {PIV:.2f}", "#6a3d9a", label_at="left")],
|
||
marks=[Mark(6, "umărul stâng", "#6a3d9a", dy=-22),
|
||
Mark(14, "capul, în suport", "#b06000", "below", dy=16),
|
||
Mark(27, "doji", "#b06000", "below", dy=28),
|
||
Mark(28, "buy doji = trigger", "#188a3e", dy=-44)],
|
||
zones=[Zone(21, 27, "umărul drept, pullback pe pivot", "#188a3e")],
|
||
mas=[MA(20, "#b06000", dash="4 3", text="SMA-20")],
|
||
scale=14.0, gap=17, body_w=9, left=58, width=700, top=46, vol_h=70,
|
||
vol_label="volum", title="Pasul 1, DA — capul în suport, umărul drept pe pivot",
|
||
caption="Exemplu construit (fără ticker), la scară. Volumul crește pe "
|
||
"ultimele bare ale umărului și explodează pe breakout."), "pas1_da")
|
||
|
||
# ======================================== 3. pasul 1, NU — triple bottom la al 4-lea test
|
||
N_CL = (
|
||
[38.00, 36.50, 35.00, 33.60, 32.40] # 0-4 downtrend
|
||
+ [31.10, 32.80, 34.00] # 5-7 piciorul 1
|
||
+ [33.00, 31.90, 31.20, 32.60, 33.80] # 8-12 piciorul 2
|
||
+ [32.90, 31.80, 31.10, 32.20, 33.20] # 13-17 piciorul 3
|
||
+ [32.40, 31.60, 31.00, 31.40, 30.20, 28.60, 27.00]) # 18-24 al 4-lea: rupe
|
||
N_WK = ([(0.40, 0.40)] * 5
|
||
+ [(0.30, 0.10), (0.30, 0.30), (0.30, 0.30)]
|
||
+ [(0.30, 0.30), (0.30, 0.30), (0.30, 0.10), (0.30, 0.30), (0.30, 0.30)]
|
||
+ [(0.30, 0.30), (0.30, 0.30), (0.30, 0.10), (0.30, 0.30), (0.30, 0.30)]
|
||
+ [(0.30, 0.30), (0.30, 0.30), (0.30, 0.20), (0.30, 0.30),
|
||
(0.30, 0.30), (0.30, 0.30), (0.30, 0.30)])
|
||
N_VL = vols(25, 54, 14, i5=74, i10=58, i15=46, i20=36, i21=40, i22=62, i23=70,
|
||
i24=76)
|
||
NC = from_closes(N_CL, wicks=N_WK, vols=N_VL, start=39.00)
|
||
BAZA = floor_lows(NC, 5, 17, 31.00)
|
||
assert lo(NC, 5, 17) == 31.00, lo(NC, 5, 17)
|
||
_pic = [min(c.c for c in NC[a:b + 1]) for a, b in ((5, 7), (8, 12), (13, 17))]
|
||
assert max(_pic) - min(_pic) <= 0.30, \
|
||
f"cele trei picioare trebuie să închidă aproximativ la același nivel: {_pic}"
|
||
assert lo(NC, 18, 24) < BAZA, "al 4-lea test trebuie să rupă baza"
|
||
assert N_VL[5] > N_VL[10] > N_VL[15] > N_VL[20], \
|
||
"volumul trebuie să scadă de la un picior la altul"
|
||
deco(Chart(
|
||
candles=NC,
|
||
levels=[Level(BAZA, f"baza testată de 3 ori: {BAZA:.2f}", "#c00")],
|
||
marks=[Mark(5, "1", "#6a3d9a", "below", dy=14),
|
||
Mark(10, "2", "#6a3d9a", "below", dy=14),
|
||
Mark(15, "3", "#6a3d9a", "below", dy=14),
|
||
Mark(20, "al 4-lea test: nu mai ține", "#c00", "below", dy=96),
|
||
Mark(8, "volumul scade la fiecare picior", "#c00", dy=-8)],
|
||
zones=[Zone(18, 24, "breakdown, nu breakout", "#c00")],
|
||
mas=[MA(20, "#b06000", dash="4 3", text="SMA-20")],
|
||
scale=20.0, gap=19, body_w=9, left=58, width=700, top=46, vol_h=70,
|
||
label_gap=60,
|
||
vol_label="volum", title="Pasul 1, NU — triple bottom la al 4-lea test",
|
||
caption="Exemplu construit (fără ticker), la scară. Cu cât baza e retestată "
|
||
"mai des și volumul scade, cresc șansele să cadă."),
|
||
"pas1_nu")
|
||
|
||
# ========================================= 4. pasul 2, DA — trigger-ul din umărul drept
|
||
T_CL = ([55.00, 56.50, 58.00, 59.50, 61.00, 62.20, 63.00] # 0-6 urcarea din cap
|
||
+ [62.40, 61.30, 60.20, 59.40, 59.90, 59.20, 59.70,
|
||
59.10, 59.80, 59.30] # 7-16 consolidarea
|
||
+ [59.40] # 17 doji
|
||
+ [65.00] # 18 buy doji/breakout
|
||
+ [64.20, 65.80, 67.50, 69.00]) # 19-22 continuă
|
||
T_WK = ([(0.40, 0.40)] * 7
|
||
+ [(0.30, 0.30)] * 10
|
||
+ [(0.90, 0.70)] # 17 doji
|
||
+ [(0.20, 0.20)] # 18 breakout
|
||
+ [(0.40, 0.40)] * 4)
|
||
T_VL = vols(23, 44, 10, i4=78, i8=46, i9=42, i10=40, i11=44, i12=48, i13=52,
|
||
i14=58, i15=64, i16=70, i17=76, i18=118, i19=70, i20=84, i21=90)
|
||
TC = from_closes(T_CL, wicks=T_WK, vols=T_VL, start=54.00)
|
||
CONS_HI = hi(TC, 7, 17)
|
||
assert abs(TC[17].c - TC[17].o) <= 0.15 * (TC[17].h - TC[17].l), "bara 17 nu e doji"
|
||
assert TC[18].c > CONS_HI, "breakout-ul trebuie să închidă peste vârful consolidării"
|
||
assert T_VL[18] > T_VL[4], "volumul de breakout trebuie să treacă peste swing high-ul lui"
|
||
assert T_VL[10] < T_VL[13] < T_VL[16] < T_VL[17], "volumul nu e în acumulare"
|
||
deco(Chart(
|
||
candles=TC,
|
||
levels=[Level(CONS_HI, f"vârful consolidării {CONS_HI:.2f}", "#6a3d9a",
|
||
label_at="left")],
|
||
marks=[Mark(17, "doji: setup-ul", "#b06000", "below", dy=26),
|
||
Mark(18, "buy doji = trigger", "#188a3e", dy=-14),
|
||
Mark(11, "↓ volum în acumulare: bare tot mai mari", "#188a3e",
|
||
"below", dy=26),
|
||
Mark(4, "↓ swing high de volum", "#1a5fb4", "below", dy=70)],
|
||
zones=[Zone(7, 17, "umărul drept: 11 bare de consolidare", "#188a3e")],
|
||
mas=[MA(20, "#b06000", dash="4 3", text="SMA-20")],
|
||
scale=16.0, gap=26, body_w=11, left=58, width=700, top=46, vol_h=80,
|
||
vol_label="volum", title="Pasul 2, DA — trigger valid din umărul drept",
|
||
caption="Exemplu construit (fără ticker). Volumul de breakout trece peste "
|
||
"swing high-ul anterior de volum."), "pas2_da")
|
||
|
||
# ========================================== 5. pasul 2, NU — volumul rupe în jos
|
||
X_CL = ([55.00, 56.50, 58.00, 59.50, 61.00, 62.20, 63.00]
|
||
+ [62.40, 61.30, 60.20, 59.40, 59.90, 59.20, 59.70,
|
||
59.10, 59.80, 59.30]
|
||
+ [59.40] # 17 același doji
|
||
+ [64.40] # 18 breakout, dar fără volum
|
||
+ [62.00, 60.00, 57.50, 55.00]) # 19-22 breakdown
|
||
X_WK = T_WK
|
||
# Regula de „nu intra" din sursă e despre o histogramă CU SEMN care cade sub
|
||
# swing low-ul ei („histograma asta cade sub swing low-ul de aici […] iar prețul
|
||
# încă nu e acolo"), nu despre bare brute pornite de la o bază: pe bare
|
||
# nesemnate „minime tot mai joase" nu înseamnă nimic vizual. De aceea panoul de
|
||
# jos al acestei diagrame e desenat în jurul unui zero (`vol_zero`), cu trei
|
||
# minime tot mai joase, iar bara candelei de breakout fals e mică și sub zero.
|
||
ZERO = 50.0
|
||
X_VL = [60.0, 64.0, 68.0, 74.0, 80.0, 72.0, 66.0, # 0-6 urcarea
|
||
56.0, 50.0, 42.0, # 7-9 minimul 1
|
||
48.0, 46.0, 38.0, # 10-12 minimul 2
|
||
44.0, 42.0, 34.0, 40.0, # 13-16 minimul 3
|
||
36.0, # 17 doji
|
||
44.0, # 18 breakout fals
|
||
30.0, 24.0, 20.0, 16.0] # 19-22 breakdown
|
||
XC = from_closes(X_CL, wicks=X_WK, vols=X_VL, start=54.00)
|
||
assert abs(XC[17].c - XC[17].o) <= 0.15 * (XC[17].h - XC[17].l), "bara 17 nu e doji"
|
||
assert XC[18].c > hi(XC, 7, 17), "breakout-ul de preț există și aici"
|
||
assert X_VL[18] < X_VL[4], "volumul de breakout NU trebuie să treacă swing high-ul"
|
||
assert X_VL[18] < ZERO, \
|
||
"pe candela de breakout histograma trebuie să fie deja sub zero"
|
||
_SW = (9, 12, 15)
|
||
for _i in _SW:
|
||
assert X_VL[_i - 1] > X_VL[_i] < X_VL[_i + 1], \
|
||
f"bara {_i} nu e un minim local al histogramei"
|
||
assert X_VL[_i] < ZERO, f"minimul de la bara {_i} trebuie să fie sub zero"
|
||
assert X_VL[_SW[0]] > X_VL[_SW[1]] > X_VL[_SW[2]], \
|
||
f"minimele histogramei nu coboară: {[X_VL[i] for i in _SW]}"
|
||
assert XC[22].c < lo(XC, 7, 17), "după breakout-ul fals prețul rupe consolidarea"
|
||
deco(Chart(
|
||
candles=XC,
|
||
levels=[Level(lo(XC, 7, 17), f"baza {lo(XC, 7, 17):.2f}", "#c00",
|
||
label_at="left")],
|
||
marks=[Mark(17, "doji", "#b06000", "below", dy=26),
|
||
Mark(18, "închide peste consolidare, dar volumul e deja sub zero",
|
||
"#c00", dy=-10),
|
||
Mark(11, "histograma rupe sub propriul swing low, de trei ori",
|
||
"#c00", "below", dy=46),
|
||
Mark(22, "breakdown", "#c00", "below", dy=14)],
|
||
zones=[Zone(7, 17, "aceeași consolidare de 11 bare", "#c00")],
|
||
mas=[MA(20, "#b06000", dash="4 3", text="SMA-20")],
|
||
scale=14.0, gap=26, body_w=11, left=58, width=700, top=46, vol_h=80,
|
||
vol_zero=ZERO,
|
||
vol_label="histogramă de volum, cu semn (stil on balance volume)",
|
||
title="Pasul 2, NU — breakout de preț fără breakout de volum",
|
||
caption="Exemplu construit. Prețul e identic cu al diagramei de deasupra; "
|
||
"panoul de jos e histograma cu semn, nu volumul brut."), "pas2_nu")
|
||
|
||
# ================================================== 6. pasul 3 — stopul, treimea de jos
|
||
deco(Chart(
|
||
candles=CDS[6:36],
|
||
levels=[Level(UMD, f"umăr drept {UMD:.2f}", "#6a3d9a"),
|
||
Level(CAP, f"cap {CAP:.2f} — baza lui H", "#6a3d9a",
|
||
label_at="left"),
|
||
Level(SL, f"SL cel mai sus permis: {SL:.2f} = cap + H/3", "#c00"),
|
||
Level(E, f"intrare {E:.2f}; distanța până la SL e R", "#188a3e",
|
||
label_at="left")],
|
||
marks=[Mark(12, "capul", "#6a3d9a", "below", dy=16),
|
||
Mark(22, "banda roșie = treimea de jos din H", "#c00",
|
||
"below", dy=104)],
|
||
zones=[Zone(8, 27, "", "#c00", p_top=SL, p_bot=CAP)],
|
||
scale=13.0, gap=20, body_w=10, left=58, width=700, top=46,
|
||
title="Pasul 3, stopul — H de la cap la umărul drept, apoi o treime",
|
||
caption=CONSTRUIT), "pas3_stop", distante=(H, R))
|
||
|
||
# ========================================================= 7. pasul 4 — cele două ținte
|
||
deco(Chart(
|
||
candles=CDS[18:],
|
||
levels=[Level(CAP, f"capul {CAP:.2f} = baza înălțimii HS", "#6a3d9a"),
|
||
Level(SL, f"SL {SL:.2f}", "#c00", label_at="left"),
|
||
Level(E, f"intrare {E:.2f}", "#188a3e", label_at="left"),
|
||
Level(GAT, f"linia gâtului {GAT:.2f}, din interiorul gâtului "
|
||
f"(nu de la vârfuri)", "#6a3d9a", label_at="left"),
|
||
Level(TP2, f"TP2 {TP2:.2f} = gât + înălțimea HS "
|
||
f"({GAT:.2f} − {CAP:.2f} = {H_HS:.2f})", "#1a5fb4",
|
||
label_at="left"),
|
||
Level(TP1, f"TP1 {TP1:.2f} = intrare + R", "#1a5fb4",
|
||
label_at="left")],
|
||
marks=[Mark(14, "buy doji", "#188a3e", dy=-10)],
|
||
mas=[MA(20, "#b06000", dash="4 3", text="SMA-20")],
|
||
scale=12.0, gap=24, body_w=10, left=58, width=700, top=46,
|
||
title="Pasul 4, țintele — TP1 = R, TP2 = proiecția gâtului",
|
||
caption="Exemplu construit (fără ticker), la scară. Aici TP1 iese PESTE TP2; "
|
||
"ordinea lor nu e fixă."), "pas4_tinte", distante=(R, H_HS))
|
||
|
||
# ================================================ 8. retestul „locului crimei", DA
|
||
Y_CL = ([50.00, 51.20, 52.40, 53.00, 52.20, 53.40, 52.60, 53.20,
|
||
52.40, 53.00] # 0-9 consolidarea
|
||
+ [56.00] # 10 breakout
|
||
+ [54.60, 53.00, 52.70] # 11-13 retest în consolidare
|
||
+ [52.80] # 14 doji
|
||
+ [57.20, 58.80, 60.50, 62.00]) # 15-18 pleacă
|
||
Y_WK = ([(0.30, 0.30)] * 10 + [(0.20, 0.20)]
|
||
+ [(0.30, 0.30), (0.30, 0.30), (0.30, 0.30)]
|
||
+ [(0.60, 0.50)] + [(0.30, 0.30)] * 4)
|
||
# barele de retest trebuie să fie mai mici decât ale consolidării: legenda
|
||
# spune „volum mic pe retest, mare pe repornire"
|
||
Y_VL = vols(19, 44, 10, i10=96, i11=30, i12=26, i13=24, i14=28, i15=88, i16=82)
|
||
YC = from_closes(Y_CL, wicks=Y_WK, vols=Y_VL, start=49.00)
|
||
C_HI, C_LO = hi(YC, 0, 9), lo(YC, 0, 9)
|
||
JUM = round((C_HI + C_LO) / 2, 2)
|
||
assert YC[10].c > C_HI, "bara 10 trebuie să facă breakout peste consolidare"
|
||
assert JUM < lo(YC, 11, 14) < C_HI, \
|
||
"retestul trebuie să intre în consolidare, dar să se oprească peste jumătate"
|
||
assert YC[18].c > YC[10].c, "după retest prețul trebuie să plece mai sus"
|
||
assert max(Y_VL[11:15]) < min(Y_VL[0:10]), \
|
||
"barele de retest trebuie să fie sub toate barele consolidării"
|
||
assert Y_VL[15] > max(Y_VL[0:15]) * 0.9, "repornirea trebuie să vină cu volum"
|
||
# dreptunghiul consolidării e ancorat exact de cele două cifre din etichete,
|
||
# fără cei 8 px de padding impliciți: altfel capacul nu cade pe 53.70
|
||
assert JUM == round((C_HI + C_LO) / 2, 2), "jumătatea nu e a intervalului declarat"
|
||
deco(Chart(
|
||
candles=YC,
|
||
levels=[Level(C_HI, f"vârful consolidării {C_HI:.2f} = locul crimei", "#6a3d9a"),
|
||
Level(JUM, f"jumătatea consolidării {JUM:.2f} "
|
||
f"= ({C_HI:.2f} + {C_LO:.2f}) / 2", "#b06000"),
|
||
Level(C_LO, f"baza consolidării {C_LO:.2f}", "#6a3d9a")],
|
||
marks=[Mark(10, "breakout", "#188a3e", dy=-10),
|
||
Mark(13, "retest, se oprește peste jumătate", "#188a3e", "below", dy=20),
|
||
Mark(14, "doji aici: a doua intrare", "#188a3e", "below", dy=38)],
|
||
zones=[Zone(0, 9, "consolidarea", "#6a3d9a", p_top=C_HI, p_bot=C_LO)],
|
||
scale=22.0, gap=30, body_w=12, left=58, width=700, top=46, vol_h=70,
|
||
vol_label="volum", title="Retestul, DA — se oprește în jumătatea de sus",
|
||
caption="Exemplu construit (fără ticker). Volum mic pe retest, mare pe "
|
||
"repornire: nu vin vânzători noi."), "retest_da")
|
||
|
||
# ================================================ 9. retestul „locului crimei", NU
|
||
Z_CL = ([50.00, 51.20, 52.40, 53.00, 52.20, 53.40, 52.60, 53.20,
|
||
52.40, 53.00]
|
||
+ [56.00]
|
||
+ [54.80, 53.20, 51.80, 50.40] # 11-14 trece tot jos
|
||
+ [49.00, 47.60, 46.00, 44.50]) # 15-18 breakdown
|
||
Z_WK = ([(0.30, 0.30)] * 10 + [(0.20, 0.20)] + [(0.30, 0.30)] * 8)
|
||
Z_VL = vols(19, 44, 10, i10=96, i11=58, i12=64, i13=72, i14=80, i15=88, i16=94)
|
||
ZC = from_closes(Z_CL, wicks=Z_WK, vols=Z_VL, start=49.00)
|
||
assert hi(ZC, 0, 9) == C_HI and lo(ZC, 0, 9) == C_LO, \
|
||
"consolidarea trebuie să fie identică cu cea din diagrama DA"
|
||
assert ZC[10].c > C_HI, "și aici există breakout"
|
||
assert ZC[14].c < JUM and ZC[18].c < C_LO, "retestul trece sub bază: breakdown"
|
||
assert Z_VL[13] > Z_VL[11], "volumul crește pe coborâre — vin vânzători"
|
||
deco(Chart(
|
||
candles=ZC,
|
||
levels=[Level(C_HI, f"vârful consolidării {C_HI:.2f}", "#6a3d9a"),
|
||
Level(JUM, f"jumătatea consolidării {JUM:.2f}", "#b06000"),
|
||
Level(C_LO, f"baza {C_LO:.2f}", "#c00")],
|
||
marks=[Mark(10, "același breakout", "#188a3e", dy=-10),
|
||
Mark(13, "retestul trece sub jumătate", "#c00", "below", dy=52),
|
||
Mark(17, "volum în creștere pe coborâre", "#c00", "below", dy=16)],
|
||
zones=[Zone(0, 9, "aceeași consolidare", "#6a3d9a", p_top=C_HI, p_bot=C_LO)],
|
||
scale=15.0, gap=30, body_w=12, left=58, width=700, top=46, vol_h=70,
|
||
vol_label="volum", title="Retestul, NU — trece sub jumătate și rupe baza",
|
||
caption="Exemplu construit (fără ticker). Același breakout; diferă cât de "
|
||
"adânc cade retestul și ce face volumul."),
|
||
"retest_nu")
|
||
|
||
# ================================================= 10. varianta short — ascending wedge
|
||
W_CL = ([52.10, 52.50, 52.90] # 0-2 uptrend anterior
|
||
+ [53.40, 52.80, 52.10] # 3-5 vârf 1, picior 1
|
||
+ [52.80, 53.40, 53.90, 53.20, 52.70] # 6-10 vârf 2, picior 2
|
||
+ [53.20, 53.70, 54.10, 53.60, 53.20] # 11-15 vârf 3, picior 3
|
||
+ [53.60, 53.95] # 16-17 ultimul test
|
||
+ [53.90] # 18 sell doji
|
||
+ [53.10, 52.50, 52.00, 51.50, 51.00, 50.90, 51.20]) # 19-25 cade spre 51
|
||
W_WK = ([(0.25, 0.25)] * 18 + [(0.55, 0.50)] + [(0.25, 0.25)] * 7)
|
||
W_VL = vols(26, 48, 10, i3=62, i8=50, i10=40, i12=36, i13=34, i15=30, i16=28,
|
||
i17=26, i18=30, i19=78, i20=86, i21=92, i22=96, i23=88)
|
||
WC = from_closes(W_CL, wicks=W_WK, vols=W_VL, start=51.80)
|
||
TINTA = 51.00
|
||
L1, L2, L3 = lo(WC, 4, 6), lo(WC, 9, 11), lo(WC, 14, 16)
|
||
H1, H2, H3 = hi(WC, 2, 4), hi(WC, 7, 9), hi(WC, 12, 14)
|
||
assert L1 < L2 < L3, f"minimele wedge-ului trebuie să urce: {L1} {L2} {L3}"
|
||
assert H1 < H2 < H3, f"maximele trebuie să urce și ele: {H1} {H2} {H3}"
|
||
assert (H1 - L1) > (H2 - L2) > (H3 - L3), \
|
||
f"wedge înseamnă convergență: {H1-L1:.2f} {H2-L2:.2f} {H3-L3:.2f}"
|
||
assert abs(WC[18].c - WC[18].o) <= 0.20 * (WC[18].h - WC[18].l), "bara 18 nu e doji"
|
||
assert WC[19].c < lo(WC, 17, 18), "sell doji-ul trebuie să rupă în jos"
|
||
assert lo(WC, 21, 25) <= TINTA, "seria trebuie să atingă ținta rostită, 51"
|
||
assert W_VL[17] == min(W_VL), "volumul trebuie să se stingă în vârful wedge-ului"
|
||
assert max(W_VL[19:23]) > max(W_VL[:19]), "volumul trebuie să explodeze pe coborâre"
|
||
print(f" wedge: picioare {L1}/{L2}/{L3} vârfuri {H1}/{H2}/{H3} "
|
||
f"deschideri {H1-L1:.2f}/{H2-L2:.2f}/{H3-L3:.2f}")
|
||
deco(Chart(
|
||
candles=WC,
|
||
levels=[Level(TINTA, "ținta rostită în lecție: 51", "#1a5fb4",
|
||
label_at="left")],
|
||
marks=[Mark(16, "volumul se stinge", "#c00", "below", dy=30),
|
||
Mark(18, "sell doji = trigger de short", "#c00", dy=-10),
|
||
Mark(23, "ținta atinsă", "#1a5fb4", "below", dy=16),
|
||
Mark(10, "minimele urcă mai repede decât maximele", "#c00",
|
||
"below", dy=40)],
|
||
zones=[Zone(2, 18, "", "#c00")],
|
||
mas=[MA(20, "#b06000", dash="4 3", text="SMA-20")],
|
||
scale=65.0, gap=23, body_w=10, left=58, width=700, top=46, vol_h=70,
|
||
vol_label="volum", title="Varianta short — XLF, ascending wedge",
|
||
caption="Al lui: ținta de 51 și patternul pe XLF. Barele sunt ale mele; "
|
||
"wedge-ul se îngustează, nu urcă paralel."),
|
||
"short_wedge", intregi=(51,))
|
||
|
||
# ============================================ 11. HS răsturnat și pe volum (schiță)
|
||
SVG["hs_pe_volum"] = """<svg viewBox="0 0 700 410" xmlns="http://www.w3.org/2000/svg" style="width:100%;max-width:700px;height:auto;font:12px sans-serif">
|
||
<text x="20" y="22" fill="#333" style="font-weight:bold">Secretul: același HS răsturnat, de două ori — pe preț și pe volum</text>
|
||
<text x="20" y="44" fill="#555">Sus prețul, jos linia de trend a volumului. Când ambele desenează aceeași formă, setup-ul e mai tare.</text>
|
||
<rect x="20" y="60" width="660" height="150" fill="#fbfbfb" stroke="#ddd"/>
|
||
<text x="30" y="78" fill="#6a3d9a" style="font-weight:bold">PREȚ</text>
|
||
<polyline points="40,80 90,120 140,150 190,118 240,135 290,180 340,195 390,160 440,145 490,152 540,140 590,96 650,74" fill="none" stroke="#1a1a1a" stroke-width="2"/>
|
||
<line x1="150" y1="131" x2="610" y2="131" stroke="#6a3d9a" stroke-width="1.4" stroke-dasharray="4 3"/>
|
||
<text x="614" y="127" fill="#6a3d9a">linia gâtului</text>
|
||
<circle cx="140" cy="150" r="4" fill="#6a3d9a"/>
|
||
<text x="112" y="172" fill="#6a3d9a">umăr stâng</text>
|
||
<circle cx="340" cy="195" r="4" fill="#6a3d9a"/>
|
||
<text x="318" y="212" fill="#6a3d9a">cap</text>
|
||
<circle cx="490" cy="152" r="4" fill="#6a3d9a"/>
|
||
<text x="464" y="174" fill="#6a3d9a">umăr drept</text>
|
||
<text x="556" y="90" fill="#188a3e" style="font-weight:bold">breakout</text>
|
||
<rect x="20" y="226" width="660" height="150" fill="#fbfbfb" stroke="#ddd"/>
|
||
<text x="30" y="244" fill="#1a5fb4" style="font-weight:bold">VOLUM (linia de trend)</text>
|
||
<polyline points="40,268 90,292 140,322 190,296 240,308 290,336 340,352 390,322 440,292 490,298 540,264 590,248 650,236" fill="none" stroke="#1a5fb4" stroke-width="2"/>
|
||
<line x1="140" y1="322" x2="490" y2="322" stroke="#1a5fb4" stroke-width="1" stroke-dasharray="3 3"/>
|
||
<circle cx="140" cy="322" r="4" fill="#1a5fb4"/>
|
||
<text x="112" y="342" fill="#1a5fb4">umăr stâng</text>
|
||
<circle cx="340" cy="352" r="4" fill="#1a5fb4"/>
|
||
<text x="318" y="370" fill="#1a5fb4">cap</text>
|
||
<circle cx="490" cy="298" r="4" fill="#1a5fb4"/>
|
||
<text x="430" y="288" fill="#1a5fb4">umăr drept, mai sus decât cel stâng</text>
|
||
<text x="20" y="398" fill="#555">Umărul drept de pe volum mai sus decât cel stâng: cumpărătorii revin cu mai multă forță decât prima dată. Asta cauți.</text>
|
||
</svg>"""
|
||
|
||
# ================================================ 12. cadranele de filtru (schiță)
|
||
SVG["cadrane"] = """<svg viewBox="0 0 700 330" xmlns="http://www.w3.org/2000/svg" style="width:100%;max-width:700px;height:auto;font:12px sans-serif">
|
||
<text x="20" y="22" fill="#333" style="font-weight:bold">„Cadran long" — cele două cadrane în care ai voie să iei trigger-ul</text>
|
||
<rect x="60" y="44" width="150" height="100" fill="#eef4ff" stroke="#1a5fb4"/>
|
||
<text x="70" y="68" fill="#1a5fb4" style="font-weight:bold">improving</text>
|
||
<text x="70" y="88" fill="#333">se îmbunătățește,</text>
|
||
<text x="70" y="106" fill="#333">încă sub piață</text>
|
||
<text x="70" y="130" fill="#188a3e" style="font-weight:bold">DA</text>
|
||
<rect x="210" y="44" width="150" height="100" fill="#e9f7ee" stroke="#188a3e"/>
|
||
<text x="220" y="68" fill="#188a3e" style="font-weight:bold">outperforming</text>
|
||
<text x="220" y="88" fill="#333">bate piața</text>
|
||
<text x="220" y="130" fill="#188a3e" style="font-weight:bold">DA</text>
|
||
<rect x="60" y="144" width="150" height="100" fill="#fdeeee" stroke="#c00"/>
|
||
<text x="70" y="168" fill="#c00" style="font-weight:bold">lagging</text>
|
||
<text x="70" y="188" fill="#333">rămâne în urmă</text>
|
||
<text x="70" y="230" fill="#c00" style="font-weight:bold">NU</text>
|
||
<rect x="210" y="144" width="150" height="100" fill="#fdeeee" stroke="#c00"/>
|
||
<text x="220" y="168" fill="#c00" style="font-weight:bold">weakening</text>
|
||
<text x="220" y="188" fill="#333">se deteriorează</text>
|
||
<text x="220" y="230" fill="#c00" style="font-weight:bold">NU</text>
|
||
<text x="60" y="266" fill="#555">slab ←</text>
|
||
<text x="330" y="266" fill="#555">→ puternic</text>
|
||
<text x="400" y="62" fill="#333" style="font-weight:bold">Ordinea de importanță, după autor</text>
|
||
<text x="400" y="86" fill="#555">1. volumul — oricare dintre cele două</text>
|
||
<text x="400" y="106" fill="#555">2. relative strength în cadran long</text>
|
||
<text x="400" y="126" fill="#555">3. power în cadran long</text>
|
||
<text x="400" y="156" fill="#333" style="font-weight:bold">Toate trei aliniate = Trinity</text>
|
||
<text x="400" y="178" fill="#555">Nu e obligatoriu: „nu poți să le ai</text>
|
||
<text x="400" y="196" fill="#555">chiar pe toate". Volumul singur e</text>
|
||
<text x="400" y="214" fill="#555">suficient ca filtru minim.</text>
|
||
<text x="20" y="296" fill="#555">Relative strength se citește față de SPY, deci are sens doar pe equities (acțiuni, ETF-uri de sector).</text>
|
||
<text x="20" y="316" fill="#555">Pe commodities schimbi reperul pe o clasă similară: petrol față de gaze, aur față de argint, grâu față de porumb.</text>
|
||
</svg>"""
|
||
|
||
# ---------------------------------------------------------------- substituire
|
||
if os.path.exists(MD):
|
||
with open(MD, encoding="utf-8") as f:
|
||
md = f.read()
|
||
lipsa = [n for n in SVG if f"<!--SVG:{n}-->" not in md]
|
||
assert not lipsa, f"marcatori lipsă în .md: {lipsa}"
|
||
for n, svg in SVG.items():
|
||
md = re.sub(rf"<!--SVG:{n}-->(\s*<svg\b[\s\S]*?</svg>)?",
|
||
lambda _m: f"<!--SVG:{n}-->\n{svg}", md, count=1)
|
||
with open(MD, "w", encoding="utf-8") as f:
|
||
f.write(md)
|
||
print(f"\n{len(SVG)} diagrame substituite in {MD}")
|
||
else:
|
||
print(f"\n{len(SVG)} diagrame generate; {MD} nu exista inca")
|