"""Diagramele pentru summaries/06_MODUL_5/04_strategia_7_micis.md.
Toate graficele de preț se construiesc cu svg_candles (o singură scară
px/unitate pe desen), fiecare cifră afirmată în etichetă e verificată prin
assert, apoi se substituie marcatorii din fișierul .md.
Regula de cifre, cerută explicit: pe etichete intră DOAR prețuri rostite în
transcriere (157.47, 155.02, 86, 94, 133, 182, 48, 28, 217.02, 214.25, 108,
180) sau prețuri dintr-un exemplu construit de mine, declarat ca atare în
titlu. Valorile derivate (preț mediu, 1R) apar doar pe diagramele construite,
unde R-urile sunt ale mele.
Cele două schițe fără candele (`divergenta`, `flux`) sunt scrise de mână,
pentru că nu conțin candele, intrare, stop sau țintă.
Rulează: .venv/bin/python gen/strategia_7_micis.py
"""
import os
import re
import sys
sys.path.insert(0, "/workspace/atm-curs-dl")
from svg_candles import Chart, Level, Mark, MA, Zone, Arrow, from_closes # noqa: E402
MD = ("/workspace/atm-curs-dl/summaries/06_MODUL_5/"
"04_strategia_7_micis.md")
SVG = {}
# ----------------------------------------------------------------- unelte mici
def ramp(a, b, n):
"""n close-uri de la a (exclusiv) la b (inclusiv), în pași egali."""
return [round(a + (b - a) * k / n, 2) for k in range(1, n + 1)]
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 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 run_desc(cds, end):
"""Câte bare consecutive, terminând cu `end`, au high-uri descrescătoare.
Regula strategiei e „5-8 candele"; cititorul numără pe desen după
high-uri (`04_6_...txt:142`), deci eticheta zonei se calculează de aici,
nu se scrie de mână. Altfel desenul ajunge să încalce propria regulă.
"""
k = 1
while end - k >= 0 and cds[end - k].h > cds[end - k + 1].h:
k += 1
return k
def zona_downtrend(cds, end, color="#c00"):
"""Zone(…) peste run-ul descrescător care se termină la bara `end`."""
n = run_desc(cds, end)
assert 5 <= n <= 8, \
f"downtrend-ul desenat are {n} candele cu high-uri descrescătoare, " \
f"iar strategia cere 5-8"
return Zone(end - n + 1, end, f"downtrend {n} candele", color)
def deco(ch, name, distante=(), intregi=()):
"""Randează graficul, verifică etichetele, salvează.
`distante` = numere cu două zecimale care au voie în etichete fără să fie
niveluri desenate (R-uri, înălțimi). `intregi` = numere întregi rostite
care au voie în etichete (86, 94, 133, 182, 28, 48, 108, 180).
"""
svg = ch.render()
assert "\n\n" not in svg, f"{name}: linie goală în svg"
assert len(ch.caption) <= 112, f"{name}: caption de {len(ch.caption)} caractere"
desenate = ({f"{lv.price:.2f}" for lv in ch.levels}
| {f"{a.price:.2f}" for a in ch.arrows if a.price 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"]*>(.*?)", 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"
# întregii din etichete: doar niveluri desenate sau numere rostite.
# unitățile de numărătoare (bare, săptămâni) și SMA-ul nu sunt prețuri.
for m in re.finditer(
r"(? sus - 0.06 * (jos - sus), \
f"{name}: nivelul {lv.price} e lipit de marginea de sus"
SVG[name] = svg
print(f" {name}: {len(ch.candles)} bare, {len(ch.levels)} niveluri")
CONSTRUIT = "Exemplu construit (fără ticker), la scară. R-urile sunt ale mele."
# =========================================================== seria construită
# Un singur trade construit, folosit de trei diagrame (harta, pasul 1, țintele),
# ca să nu se schimbe cifrele de la o secțiune la alta.
CL = ([64.00, 65.80, 64.30, 65.90, 64.20, 62.80, 64.00] # 0-6 consolidare
+ [60.50, 57.00, 53.50, 50.00, 46.50, 43.00, 39.90] # 7-13 downtrend, 7 bare
+ [40.00] # 14 doji
+ [45.00] # 15 marubozu de trigger
+ [43.20, 42.60] # 16-17 pullback-ul
+ [43.60, 44.80, 46.00, 47.90, 49.20, 50.70] # 18-23 urcarea
+ [52.00, 51.30, 53.00, 54.50]) # 24-27 continuare
WK = ([(0.40, 0.40)] * 14
+ [(2.00, 1.90)] # 14 fitile de doji
+ [(0.00, 0.00)] # 15 marubozu curat
+ [(0.30, 0.20), (0.20, 0.30)]
+ [(0.35, 0.35)] * 10)
VL = vols(28, 46, 12, i11=58, i12=62, i13=66, i14=70, i15=96, i16=58, i17=52,
i18=60, i19=66, i20=72, i21=78, i22=70, i23=80)
CDS = from_closes(CL, wicks=WK, vols=VL, start=65.00)
DOJI_LO, DOJI_HI = lo(CDS, 14, 14), hi(CDS, 14, 14)
H_DOJI = round(DOJI_HI - DOJI_LO, 2) # 4.00
assert (DOJI_LO, DOJI_HI, H_DOJI) == (38.00, 42.00, 4.00), (DOJI_LO, DOJI_HI)
assert abs(CDS[14].c - CDS[14].o) <= 0.10 * H_DOJI, "candela 14 nu e doji"
assert CDS[15].c > DOJI_HI, "trigger-ul nu închide peste high-ul doji-ului"
assert lo(CDS, 7, 13) < lo(CDS, 0, 6), "downtrend-ul numărat nu coboară"
assert run_desc(CDS, 13) == 7, \
f"înaintea doji-ului trebuie 7 bare cu high descrescător, sunt {run_desc(CDS, 13)}"
TRIG_HI, TRIG_LO = hi(CDS, 15, 15), lo(CDS, 15, 15)
H_TRIG = round(TRIG_HI - TRIG_LO, 2) # 5.00
E1 = CDS[15].c # 45.00, la close
E2 = round(TRIG_HI - H_TRIG / 3, 2) # 43.33, 1/3 din marubozu
E3 = round(TRIG_HI - H_TRIG / 2, 2) # 42.50, 1/2 din marubozu
SL = round(DOJI_LO + H_DOJI / 3, 2) # 39.33, 2/3 din doji
MED = round((E1 + E2 + E3) / 3, 2) # 43.61, prețul mediu
R1 = round(E1 - SL, 2)
R2 = round(MED - SL, 2)
TP1 = round(E1 + R1, 2)
TP2 = round(MED + R2, 2)
assert lo(CDS, 16, 16) <= E2 and lo(CDS, 17, 17) <= E3, "ladder-ul nu se umple"
assert lo(CDS, 16, 17) > SL, "pullback-ul atinge stopul"
assert hi(CDS, 21, 21) >= TP2 and hi(CDS, 23, 23) >= TP1, "țintele nu sunt atinse"
assert TP2 < TP1, "pe prețul mediu ținta de 1R vine mai jos, nu mai sus"
print(f"construit: doji {DOJI_LO}-{DOJI_HI} (h={H_DOJI}) intrări {E1}/{E2}/{E3}"
f" medie {MED} SL {SL} TP1 {TP1} ({R1}) TP2 {TP2} ({R2})")
# ---------------------------------------------------------------- 1. harta
deco(Chart(
candles=CDS,
levels=[Level(SL, f"SL {SL:.2f} = 2/3 din doji", "#c00", label_at="left"),
Level(E1, f"intrare 1/3 la close {E1:.2f}", "#188a3e"),
Level(E2, f"limit 1/3 la {E2:.2f}", "#188a3e", label_at="left"),
Level(E3, f"limit 1/3 la {E3:.2f}", "#188a3e", label_at="left"),
# prețul mediu există abia după ce s-a umplut a treia tranșă:
# linia pornește de acolo, deci nu mai taie eticheta limitului
Level(MED, f"preț mediu {MED:.2f}", "#6a3d9a", i0=17),
Level(TP2, f"TP2 {TP2:.2f} — lectura mea", "#1a5fb4", i0=17),
Level(TP1, f"TP1 {TP1:.2f}", "#1a5fb4")],
marks=[Mark(14, "doji", "#b06000", "below", dy=14),
Mark(15, "buy doji", "#188a3e", dy=-6),
Mark(5, "consolidare", "#888", "below"),
Mark(17, "se umplu limiturile", "#188a3e", "below", dy=0),
Mark(23, "ieși, nu aștepți", "#1a5fb4", dy=-8)],
zones=[zona_downtrend(CDS, 13),
Zone(15, 17, "fereastra de intrare", "#188a3e")],
mas=[MA(20, "#b06000", dash="4 3")],
scale=18.0, gap=17, body_w=9, left=52, width=700, top=76, vol_h=70,
vol_label="volum", title="MICIS, tot trade-ul — weekly (exemplu construit)",
caption=CONSTRUIT), "harta", distante=(R1, R2, H_DOJI))
# ------------------------------------------------- 2. pasul 1 — setup-ul DA
PIV = 36.00
cl2 = ([78.00, 76.50, 77.00, 75.00, 73.50, 74.00, 72.00, 70.00, 68.50, 67.00,
66.00] + CL[:15])
wk2 = [(0.50, 0.50)] * 11 + WK[:15]
vl2 = vols(26, 44, 12, i21=58, i22=62, i23=66, i24=70, i25=88)
cds2 = from_closes(cl2, wicks=wk2, vols=vl2, start=79.00)
assert lo(cds2, 25, 25) == DOJI_LO and hi(cds2, 25, 25) == DOJI_HI, "doji schimbat"
assert lo(cds2, 25, 25) > PIV, "doji-ul trebuie să se oprească pe pivot, nu sub el"
assert vl2[23] < vl2[24] < vl2[25], "volumul nu crește pe ultimele trei bare"
deco(Chart(
candles=cds2,
levels=[Level(PIV, f"pivot quarterly {PIV:.2f}", "#b06000", label_at="left"),
Level(DOJI_LO, f"low-ul doji-ului {DOJI_LO:.2f}", "#6a3d9a")],
marks=[Mark(25, "doji", "#b06000", dy=-18)],
zones=[zona_downtrend(cds2, 24)],
mas=[MA(20, "#b06000", dash="4 3", text="SMA-20")],
scale=8.0, gap=17, body_w=9, left=52, width=700, vol_h=70,
vol_label="volum", title="Pasul 1, DA — doji weekly pe pivotul quarterly",
caption="Exemplu construit (fără ticker). Volumul crește pe ultimele trei "
"bare: asta e acumularea."), "pasul1_da")
# ------------------------------------------------- 3. pasul 1 — setup-ul NU
cl3 = (ramp(41.00, 47.00, 6) + [46.50, 47.50] + ramp(47.50, 53.00, 6)
+ [52.40, 53.20] + ramp(53.20, 59.00, 6) + [58.60, 59.40, 58.80]
+ [59.00] + [58.40, 57.00, 55.00])
wk3 = [(0.35, 0.35)] * 25 + [(1.60, 1.50)] + [(0.40, 0.40)] * 3
# volumul trebuie să scadă pe TOT uptrend-ul, nu doar pe ultimele bare:
# eticheta „volum în scădere" se citește peste zona verde, nu peste 4 bare
vl3 = ([50.0, 52.0, 48.0, 54.0, 50.0, 52.0]
+ [62.0, 60.0, 58.0, 56.0, 54.0, 52.0, 50.0, 48.0, 46.0, 44.0,
42.0, 40.0, 38.0, 36.0, 33.0, 30.0, 28.0, 26.0, 25.0, 24.0]
+ [40.0, 56.0, 68.0])
cds3 = from_closes(cl3, wicks=wk3, vols=vl3, start=40.00)
assert abs(cds3[25].c - cds3[25].o) <= 0.10 * (cds3[25].h - cds3[25].l), \
"candela 25 nu e doji"
assert cds3[25].c > cds3[0].c and hi(cds3, 25, 25) == hi(cds3, 0, 28), \
"doji-ul trebuie să cadă în vârful unui uptrend"
assert vl3[25] == min(vl3), "volumul trebuie să fie cel mai mic la doji"
assert all(vl3[i] > vl3[i + 1] for i in range(6, 25)), \
"barele de volum trebuie să scadă pe toată zona de uptrend, cât ține eticheta"
assert vl3[26] < vl3[27] < vl3[28], "volumul trebuie să revină pe corecție"
deco(Chart(
candles=cds3,
marks=[Mark(25, "doji aici: după uptrend", "#c00", dy=-10),
Mark(16, "volum în scădere pe tot uptrend-ul", "#c00", "below", dy=24),
Mark(28, "urmează corecția, cu volum", "#c00", "below", dy=14),
Mark(12, "uptrend", "#188a3e", "below", dy=38)],
zones=[Zone(6, 24, "", "#188a3e")],
mas=[MA(20, "#b06000", dash="4 3", text="SMA-20")],
scale=11.0, gap=17, body_w=9, left=52, width=700, top=64, vol_h=70,
vol_label="volum", title="Pasul 1, NU — același doji, context greșit",
caption="Exemplu construit (fără ticker), la scară. Doji-ul arată identic; "
"ce îl descalifică e ce e în stânga lui."), "pasul1_nu")
# ------------------------------------- 4. pasul 2 — ladder-ul umplut (JNJ)
J_CL = ([166.00, 164.50, 165.50, 163.00, 161.00, 162.00, 159.50, 157.00,
155.00, 153.00, 151.50, 150.00] # 0-11 downtrend
+ [150.12] # 12 doji
+ [157.47] # 13 marubozu, close = high
+ [155.60, 154.00] # 14-15 pullback
+ [155.50, 157.00, 158.60, 160.50, 162.00, 161.00, 163.50, 165.00,
166.50, 168.00])
J_WK = ([(0.60, 0.60)] * 12 + [(2.40, 2.60)] + [(0.00, 0.00)]
+ [(0.70, 0.70), (0.40, 0.40)] + [(0.60, 0.60)] * 10)
J_VL = vols(26, 44, 12, i12=58, i13=92, i14=54, i15=50, i16=58, i17=64, i18=70)
JC = from_closes(J_CL, wicks=J_WK, vols=J_VL, start=167.00)
J_E1 = JC[13].c
J_HI, J_LO = hi(JC, 13, 13), lo(JC, 13, 13)
J_E2 = round(J_HI - (J_HI - J_LO) / 3, 2)
J_E3 = round(J_HI - (J_HI - J_LO) / 2, 2)
assert J_E1 == 157.47 and J_HI == 157.47, "close-ul rostit e 157.47, pe high"
assert J_E2 == 155.02, f"1/3 din marubozu trebuie să cadă pe 155.02, nu {J_E2}"
assert JC[13].c > hi(JC, 12, 12), "trigger-ul nu închide peste high-ul doji-ului"
assert lo(JC, 14, 14) <= J_E2 and lo(JC, 15, 15) <= J_E3, "ladder-ul nu se umple"
deco(Chart(
candles=JC,
levels=[Level(J_E1, f"intrare 1/3 la close {J_E1:.2f}", "#188a3e"),
# limiturile există abia după close-ul candelei de trigger, deci
# liniile pornesc de la ea: altfel taie eticheta de dedesubt
Level(J_E2, f"limit 1/3 la {J_E2:.2f} = 1/3 din marubozu", "#188a3e",
i0=13),
Level(J_E3, "limit 1/3 la 1/2 din marubozu", "#6a3d9a",
label_at="left")],
marks=[Mark(12, "doji", "#b06000", "below", dy=14),
Mark(13, "marubozu de trigger", "#188a3e", dy=-26),
Mark(15, "pullback-ul umple ambele limituri", "#188a3e", "below", dy=38)],
scale=10.0, gap=17, body_w=9, left=58, width=700, vol_h=70,
vol_label="volum", title="Pasul 2, ladder umplut — JNJ weekly",
caption="Ale lui: close-ul 157.47 și limit-ul de 155.02; barele din jur "
"sunt reconstruite de mine."),
"pasul2_jnj", distante=(J_E3,))
# ------------------------------- 5. pasul 2 — ladder-ul neumplut (AMD 108→180)
A_CL = ([206.00, 198.00, 190.00, 182.00, 176.00, 168.00] # 0-5
+ [152.00, 146.00, 138.00, 130.00, 120.00, 112.00, 104.00, 98.00] # 6-13
+ [97.50] # 14 doji
+ [108.00] # 15 marubozu, close rostit
+ [116.00, 124.00, 121.00, 132.00, 141.00, 138.00, 150.00, 158.00,
166.00, 174.00, 180.00]) # 16-26 pleacă fără tine
A_WK = ([(2.00, 2.00)] * 6 + [(1.60, 1.60)] * 8 + [(7.50, 8.00)]
+ [(0.50, 0.50)] + [(1.60, 1.60)] * 11)
A_VL = vols(27, 44, 12, i14=58, i15=96, i16=78, i17=74, i18=62, i19=76, i22=80)
AC = from_closes(A_CL, wicks=A_WK, vols=A_VL, start=210.00)
A_E1 = AC[15].c
A_HI, A_LO = hi(AC, 15, 15), lo(AC, 15, 15)
A_E2 = round(A_HI - (A_HI - A_LO) / 3, 2)
assert A_E1 == 108.00, "close-ul rostit e 108"
assert hi(AC, 26, 26) >= 180.00, "seria trebuie să ajungă la 180, cât a rostit"
assert lo(AC, 16, 26) > A_E2, "ladder-ul NU trebuie să se umple pe acest desen"
deco(Chart(
candles=AC,
levels=[Level(A_E1, f"intrare 1/3 la close {A_E1:.2f}", "#188a3e"),
# înălțimea candelei nu e rostită pe AMD, deci nivelul de 1/3 nu
# primește cifră — aceeași regulă ca pe JNJ
Level(A_E2, "limit 1/3 — niciodată atins", "#c00",
label_at="left"),
Level(180.00, "unde a ajuns: 180", "#1a5fb4")],
marks=[Mark(14, "doji", "#b06000", "below", dy=14),
Mark(15, "intri cu 1/3 și rămâi cu 1/3", "#188a3e", dy=-30)],
mas=[MA(20, "#b06000", dash="4 3", text="SMA-20")],
scale=2.3, gap=19, body_w=10, left=58, width=700, vol_h=70,
vol_label="volum", title="Pasul 2, ladder neumplut — AMD monthly",
caption="Ale lui: 108 (de unde a plecat) și 180 (unde a ajuns); restul e "
"reconstruit de mine."),
"pasul2_amd", intregi=(108, 180))
# -------------------------------------------- 6. pasul 3 — stopul (NVDA monthly)
N_CL = ([20.00, 23.50, 27.00, 25.00, 31.00, 38.00, 45.00, 42.00, 50.00, 55.00,
62.00, 75.00, 90.00, 108.00, 125.00, 138.00] # 0-15 uptrend lung
+ [150.00, 138.00, 126.00, 115.00, 101.00] # 16-20 downtrend, 5 bare
+ [100.50] # 21 doji
+ [133.00] # 22 marubozu de trigger
+ [141.00, 152.00, 164.00, 175.00, 182.00]) # 23-27 spre 182
# bara 16 e vârful: fitil de respingere deasupra, ca high-ul ei să fie
# cel mai sus din downtrend și numărătoarea după high-uri să dea 5, cât
# spune eticheta
N_WK = ([(1.50, 1.50)] * 16 + [(5.00, 3.00)] + [(3.00, 3.00)] * 4
+ [(13.00, 14.50)]
+ [(0.50, 1.50)] + [(2.00, 2.00)] * 5)
N_VL = vols(28, 46, 12, i19=66, i20=78, i21=84, i22=104, i23=86, i24=80)
NC = from_closes(N_CL, wicks=N_WK, vols=N_VL, start=19.00)
N_LO, N_HI = lo(NC, 21, 21), hi(NC, 21, 21)
assert (N_LO, round(N_HI - N_LO, 2)) == (86.00, 28.00), (N_LO, N_HI)
assert abs(NC[21].c - NC[21].o) <= 0.10 * 28.00, "candela 21 nu e doji"
assert NC[22].c == 133.00 and NC[22].c > N_HI, "trigger-ul închide la 133, peste doji"
assert hi(NC, 27, 27) >= 182.00, "seria trebuie să ajungă la 182"
assert round(133.00 + 48.00, 2) == 181.00, "133 + 48 = 181, el a rostit 182"
SL_23 = round(N_LO + 28.00 / 3, 2) # 95.33 strict; el a citit „pe la 94"
print(f" NVDA: 1/3 de la low = {SL_23} strict, rostit 94; "
f"distanța 133→86 = {(133 - 86) / 86 * 100:.1f}%")
deco(Chart(
candles=NC,
levels=[Level(86.00, "sub low-ul doji-ului: 86", "#c00", label_at="left"),
Level(94.00, "SL 2/3 din doji: 94", "#c00", label_at="left"),
Level(133.00, "intrare 1/3 la close: 133", "#188a3e"),
Level(182.00, "ținta rostită: 182", "#1a5fb4")],
marks=[Mark(21, "doji monthly, înălțime 28", "#b06000", "below", dy=16),
Mark(22, "marubozu de trigger", "#188a3e", dy=-70)],
zones=[zona_downtrend(NC, 20)],
mas=[MA(20, "#b06000", dash="4 3", text="SMA-20")],
scale=2.4, gap=19, body_w=10, left=58, width=700, top=60, vol_h=70,
vol_label="volum", title="Pasul 3, stopul — NVDA monthly",
caption="Ale lui: 86, 94, 133, 182, înălțimea de 28 și urcarea de 48; "
"barele din jur sunt ale mele."),
"pasul3_nvda", intregi=(86, 94, 133, 182, 28, 48))
# ------------------------------------------ 7. pasul 4 — cele două ținte, zoom
cl4 = CL[9:]
wk4 = WK[9:]
vl4 = VL[9:]
cds4 = from_closes(cl4, wicks=wk4, vols=vl4, start=CL[8])
assert lo(cds4, 5, 5) == DOJI_LO and cds4[6].c == E1, "zoom-ul mută seria"
deco(Chart(
candles=cds4,
levels=[Level(SL, f"SL {SL:.2f}", "#c00", label_at="left"),
Level(MED, f"preț mediu {MED:.2f}", "#6a3d9a"),
Level(E1, f"intrare 1/3 la {E1:.2f}", "#188a3e"),
Level(TP2, f"TP2 {TP2:.2f}", "#1a5fb4"),
Level(TP1, f"TP1 {TP1:.2f}", "#1a5fb4")],
arrows=[Arrow(10, f"TP2 = 1R pe medie ({R2:.2f})", price=TP2, where="left",
color="#1a5fb4", dx=-6, dy=-16),
Arrow(12, f"TP1 = 1R pe prima tranșă ({R1:.2f})", price=TP1,
where="above", color="#1a5fb4", dy=-10)],
mas=[MA(20, "#b06000", dash="4 3", text="SMA-20")],
scale=13.0, gap=28, body_w=14, left=60, width=700, top=56,
title="Pasul 4, cele două ținte — weekly (exemplu construit)",
caption="Lectura mea: mediind în jos, TP2 vine înaintea lui TP1. "
"Fraza-sursă e neinteligibilă aici."),
"pasul4_tinte", distante=(R1, R2))
# --------------------------------------------------- 8. pasul 5 — trail-ul
cl5 = (ramp(44.00, 50.00, 5) + [49.20, 48.60, 49.40] # 0-7 trail 1
+ ramp(49.40, 56.00, 5) + [55.00, 54.20, 55.40] # 8-15 trail 2
+ ramp(55.40, 62.00, 5) + [61.00, 60.20, 61.40] # 16-23 trail 3
+ ramp(61.40, 67.00, 4) + [66.20, 65.60]) # 24-29 trail 4
wk5 = [(0.40, 0.40)] * 30
vl5 = vols(30, 50, 12)
cds5 = from_closes(cl5, wicks=wk5, vols=vl5, start=43.00)
# fiecare trail e declanșat de un swing low anume; linia lui pornește de la
# bara aceea, nu din marginea desenului
SW = [6, 14, 22, 29]
t1, t2, t3, t4 = (lo(cds5, 5, 7), lo(cds5, 13, 15), lo(cds5, 21, 23),
lo(cds5, 28, 29))
assert t1 < t2 < t3 < t4, (t1, t2, t3, t4)
# regresia de prins: o linie de trail nu are voie să treacă pe deasupra
# prețului la dreapta ei — altfel desenul arată o poziție deja stopată
for _i0, _t in zip(SW, (t1, t2, t3, t4)):
assert all(c.l >= _t - 1e-9 for c in cds5[_i0:]), \
f"trail-ul {_t} de la bara {_i0} trece peste low-ul unei bare de după"
assert lo(cds5, max(0, _i0 - 1), min(_i0 + 1, len(cds5) - 1)) == _t, \
f"trail-ul {_t} nu e ancorat de swing low-ul de la bara {_i0}"
deco(Chart(
candles=cds5,
# etichetele la dreapta: la stânga ar cădea peste candele, pentru că
# fiecare linie începe acum în mijlocul desenului
levels=[Level(t1, f"trail 1: {t1:.2f}", "#c00", i0=SW[0]),
Level(t2, f"trail 2: {t2:.2f}", "#c00", i0=SW[1]),
Level(t3, f"trail 3: {t3:.2f}", "#c00", i0=SW[2]),
Level(t4, f"trail 4: {t4:.2f}", "#c00", i0=SW[3])],
marks=[Mark(6, "swing low 1", "#c00", "below", dy=14),
Mark(14, "swing low 2", "#c00", "below", dy=14),
Mark(22, "swing low 3", "#c00", "below", dy=14),
Mark(29, "swing low 4", "#c00", "below", dy=14)],
mas=[MA(20, "#b06000", dash="4 3")],
scale=7.0, gap=17, body_w=9, left=52, width=700, top=64, vol_h=70,
vol_label="volum",
title="Pasul 5, trail pe swing low — weekly (decizia a fost pe monthly)",
caption="Fiecare linie de trail pornește de la swing low-ul care o "
"declanșează; stopul nu coboară niciodată."), "pasul5_trail")
# ------------------------------------- 9. când nu merge (Tesla, 217.02/214.25)
T_CL = ([320.00, 312.00, 304.00, 298.00] # 0-3
+ [290.00, 282.00, 272.00, 262.00, 252.00, 244.00, 236.00, 230.00] # 4-11
+ [232.00] # 12 buy hammer
+ [228.00, 222.00, 216.00] # 13-15 scoate stopul
+ [225.00] # 16 al doilea semnal
+ [240.00, 252.00, 262.00, 272.00, 282.00, 290.00, 298.00]) # 17-23
T_WK = ([(3.00, 3.00)] * 12 + [(2.00, 12.98)]
+ [(2.00, 2.00), (2.00, 2.00), (1.00, 1.75)]
+ [(2.00, 2.00)] * 8)
T_VL = vols(24, 46, 12, i12=72, i15=78, i16=84, i17=88, i18=80)
TC = from_closes(T_CL, wicks=T_WK, vols=T_VL, start=326.00)
assert lo(TC, 12, 12) == 217.02, f"low-ul hammer-ului trebuie 217.02, e {lo(TC, 12, 12)}"
assert lo(TC, 13, 15) == 214.25, f"minimul următor trebuie 214.25, e {lo(TC, 13, 15)}"
assert 214.25 < 217.02, "al doilea minim e sub low-ul hammer-ului"
assert len(TC[17:24]) == 7, "urcarea rostită e de 7 săptămâni"
assert len(TC) == 24, "după al doilea semnal se desenează exact cele 7 bare rostite"
assert all(TC[i].c < TC[i + 1].c for i in range(17, 23)), \
"cele 7 bare etichetate ca urcare trebuie să urce toate"
deco(Chart(
candles=TC,
levels=[Level(217.02, "low-ul hammer-ului: 217.02", "#b06000",
label_at="left"),
# cele două minime sunt la 2.77 dolari: eticheta celui de jos
# trece sub propria linie, altfel o taie linia de deasupra
Level(214.25, "minimul de după: 214.25", "#c00", label_dy=18)],
marks=[Mark(12, "buy hammer — primul semnal", "#b06000", dy=-10),
Mark(17, "al doilea semnal, acesta merge", "#188a3e", dy=-10),
Mark(23, "7 săptămâni de urcare", "#188a3e", dy=-10)],
arrows=[Arrow(15, "stopul pus sub 217.02\na fost luat aici",
price=214.25, where="below", color="#c00", dy=4)],
mas=[MA(20, "#b06000", dash="4 3", text="SMA-20")],
scale=2.6, gap=19, body_w=10, left=58, width=700, vol_h=70,
vol_label="volum", title="Când nu merge — Tesla weekly",
caption="Ale lui: 217.02, 214.25 și cele 7 săptămâni de după al doilea "
"semnal; restul barelor e al meu."),
"nu_merge", intregi=())
# ------------------------------------------- 10. filtrul de volum (schiță)
SVG["divergenta"] = """"""
# ---------------------------------------- 11. rutina de scanare (schiță)
SVG["flux"] = """"""
# ---------------------------------------------------------------- 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"" not in md]
assert not lipsa, f"marcatori lipsă în .md: {lipsa}"
for n, svg in SVG.items():
md = re.sub(rf"(\s*