"""Diagramele pentru summaries/17_STRATEGII/09_micis.md (MICI-S, strategia 7). Un singur generator pentru cele șapte diagrame ale capitolului: capitolul are o serie construită folosită de trei desene, deci dacă fiecare diagramă ar sta în fișierul ei, seria s-ar dubla și s-ar desincroniza la prima retușare. Regula de cifre, aceeași din `gen/strategia_7_micis.py`: pe etichete intră DOAR prețuri rostite în transcriere (157.47, 155.02, 86, 94, 133, 182, 28, 48, 217.02, 214.25) sau prețuri dintr-un exemplu declarat construit în titlu. Valorile derivate (prețul mediu, 1R, 1/2 din marubozu) nu primesc etichetă pe desenele cu ticker, ci doar pe cele construite, unde captionul spune că R-urile sunt ale mele. Scrie gen/strategii/micis_.svg și substituie marcajele din capitol. Rulează: .venv/bin/python gen/strategii/micis_diagrame.py """ import re import sys from pathlib import Path sys.path.insert(0, "/workspace/atm-curs-dl") sys.path.insert(0, "/workspace/atm-curs-dl/gen") from eticheta_libera import eticheta_libera # noqa: E402 from svg_candles import MA, Arrow, Chart, Level, Mark, Zone, from_closes # noqa: E402 ROOT = Path("/workspace/atm-curs-dl") MD = ROOT / "summaries/17_STRATEGII/09_micis.md" OUT = ROOT / "gen/strategii" RED, GREEN, BLUE, ORANGE, INK, GRAY = "#c00", "#188a3e", "#1a5fb4", "#b06000", "#333", "#888" PURPLE = "#6a3d9a" CONSTRUIT = "Exemplu construit (fără ticker), la scară. R-urile sunt ale mele." SVG = {} # ----------------------------------------------------------------- unelte mici def ramp(a, b, n): 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. Cititorul numără downtrend-ul după high-uri, nu după culori, deci eticheta zonei se calculează de aici — altfel desenul încalcă propria regulă de „5-8 candele". """ 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=RED): n = run_desc(cds, end) assert 5 <= n <= 8, f"downtrend-ul desenat are {n} candele, regula cere 5-8" return Zone(end - n + 1, end, f"downtrend {n} candele", color) def deco(ch, name, distante=(), intregi=(), libere=(), libere_nivel=(), extra=None): """Randează, verifică etichetele, salvează. `distante` = numere cu două zecimale permise în etichete fără să fie niveluri desenate. `intregi` = întregi rostiți. `libere` / `libere_nivel` = etichete pe care `eticheta_libera` le verifică (a doua grupă: etichete de nivel, lipite de linia lor prin construcție, deci doar candelele contează). """ svg = ch.render() if extra is not None: svg = svg.replace("\n", "\n " + "\n ".join(extra(ch)) + "\n") assert "\n\n" not in svg, f"{name}: linie goală în svg" assert ".md:" not in svg, f"{name}: trimitere fișier:linie în svg" assert len(ch.caption) <= 130, 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" for m in re.finditer(r"(? sus - 0.06 * (jos - sus), \ f"{name}: nivelul {lv.price} e lipit de marginea de sus" if libere: eticheta_libera(svg, name, set(libere), rects=True) if libere_nivel: eticheta_libera(svg, name, set(libere_nivel), rects=True, segmente=False) SVG[name] = svg print(f" {name}: {len(ch.candles)} bare, {len(ch.levels)} niveluri") # =========================================================== seria construită # Un trade construit, folosit de `ansamblu` și de `tinte`, ca să nu se schimbe # cifrele de la o secțiune la alta. Aceleași cifre ca în sumarizarea lecției. 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)] + [(0.00, 0.00)] + [(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) 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 run_desc(CDS, 13) == 7, "înaintea doji-ului trebuie 7 bare descrescătoare" TRIG_HI, TRIG_LO = hi(CDS, 15, 15), lo(CDS, 15, 15) H_TRIG = round(TRIG_HI - TRIG_LO, 2) 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 R1, R2 = round(E1 - SL, 2), round(MED - SL, 2) TP1, TP2 = round(E1 + R1, 2), round(MED + R2, 2) assert SL == 39.33 and MED == 43.61 and TP1 == 50.67 and TP2 == 47.89 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" # stopul de 2/3 stă la o treime deasupra low-ului, nu la jumătate assert abs((SL - DOJI_LO) - H_DOJI / 3) < 0.01, "stopul nu e la 2/3 din doji" print(f"construit: doji {DOJI_LO}-{DOJI_HI} intrări {E1}/{E2}/{E3} medie {MED}" f" SL {SL} TP1 {TP1} ({R1}) TP2 {TP2} ({R2})") # -------------------------------------------------------------- 1. ansamblu deco(Chart( candles=CDS, levels=[Level(SL, f"SL {SL:.2f} = 2/3 din doji", RED, label_at="left"), Level(E1, f"1/3 la close {E1:.2f}", GREEN), Level(E2, f"limit 1/3 la {E2:.2f}", GREEN, label_at="left"), # la stânga ar cădea peste eticheta limitului de deasupra Level(E3, f"limit 1/3 la {E3:.2f}", GREEN), Level(MED, f"preț mediu {MED:.2f}", PURPLE, i0=17), Level(TP2, f"TP2 {TP2:.2f} — lectura mea", BLUE, i0=17), Level(TP1, f"TP1 {TP1:.2f}", BLUE)], marks=[Mark(14, "doji", ORANGE, "below", dy=14), Mark(15, "buy doji", GREEN, dy=-6), Mark(3, "consolidare", GRAY, "below", dy=18), Mark(17, "limituri umplute", GREEN, "below", dy=52)], zones=[zona_downtrend(CDS, 13), Zone(15, 17, "fereastra de intrare", GREEN)], mas=[MA(20, ORANGE, dash="4 3")], scale=18.0, gap=17, body_w=9, left=52, width=700, top=76, vol_h=70, vol_label="volum", title="MICI-S, tot trade-ul — weekly (exemplu construit)", caption=CONSTRUIT), "ansamblu", distante=(R1, R2, H_DOJI), libere=("doji", "buy doji", "limituri umplute"), libere_nivel=(f"TP1 {TP1:.2f}", f"SL {SL:.2f} = 2/3 din doji")) # ------------------------------------------- 2. ladder-ul măsurat pe marubozu 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 J_E3 < J_E2, "1/2 din marubozu e sub 1/3 din marubozu" 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" assert run_desc(JC, 11) >= 5, "downtrend-ul din stânga doji-ului e prea scurt" deco(Chart( candles=JC, levels=[Level(J_E1, f"1/3 la close {J_E1:.2f}", GREEN), Level(J_E2, f"limit 1/3 la {J_E2:.2f} = 1/3 din marubozu", GREEN, i0=13), Level(J_E3, "limit 1/3 la 1/2 din marubozu", PURPLE, label_at="left")], marks=[Mark(12, "doji", ORANGE, "below", dy=14), Mark(13, "marubozu de trigger", GREEN, dy=-26), Mark(15, "pullback-ul umple ambele limituri", GREEN, "below", dy=38)], scale=10.0, gap=17, body_w=9, left=58, width=700, vol_h=70, vol_label="volum", title="Ladder-ul, măsurat pe marubozu — JNJ weekly", caption="Ale lui: close-ul 157.47 și limit-ul de 155.02. Nivelul de 1/2 și " "barele din jur sunt ale mele."), "ladder", distante=(J_E3,), libere=("doji", "marubozu de trigger", "pullback-ul umple ambele limituri")) # ------------------------------------------------------- 3. stopul (NVDA) 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 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" # cifrele pe care le afirmă textul capitolului, verificate pe desen assert abs((133.00 - 86.00) / 133.00 * 100 - 35.3) < 0.1, "distanța 133→86 nu e 35,3%" assert abs((133.00 - 94.00) / 133.00 * 100 - 29.3) < 0.1, "distanța 133→94 nu e 29,3%" SL_STRICT = round(86.00 + 28.00 / 3, 2) assert SL_STRICT == 95.33, SL_STRICT print(f" NVDA: 2/3 din doji = {SL_STRICT} strict, el citește 94 „din ochi”") deco(Chart( candles=NC, levels=[Level(86.00, "sub low-ul doji-ului: 86", RED, label_at="left"), Level(94.00, "SL 2/3 din doji: 94", RED, label_at="left"), Level(133.00, "1/3 la close: 133", GREEN), Level(182.00, "ținta rostită: 182", BLUE)], marks=[Mark(21, "doji monthly, înălțime 28", ORANGE, "below", dy=34), Mark(22, "marubozu de trigger", GREEN, dy=-118)], zones=[zona_downtrend(NC, 20)], mas=[MA(20, ORANGE, 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="Stopul, cele două variante — NVDA monthly", caption="Ale lui: 86, 94 (citit din ochi), 133, 182 și înălțimea de 28; " "barele din jur sunt ale mele."), "stop", intregi=(86, 94, 133, 182, 28), libere=("doji monthly, înălțime 28", "marubozu de trigger")) # ---------------------------------------------- 4. cele două ținte (zoom) cl4, wk4, vl4 = CL[9:], WK[9:], 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}", RED, label_at="left"), Level(MED, f"preț mediu {MED:.2f}", PURPLE), Level(E1, f"1/3 la {E1:.2f}", GREEN), Level(TP2, f"TP2 {TP2:.2f}", BLUE), Level(TP1, f"TP1 {TP1:.2f}", BLUE)], arrows=[Arrow(10, f"TP2 = 1R pe prețul mediu ({R2:.2f}) — lectura mea", price=TP2, where="left", color=BLUE, dx=-6, dy=-16), Arrow(12, f"TP1 = 1R pe prima tranșă ({R1:.2f})", price=TP1, where="above", color=BLUE, dy=-10)], mas=[MA(20, ORANGE, dash="4 3", text="SMA-20")], scale=13.0, gap=28, body_w=14, left=60, width=700, top=56, title="Cele două ținte — weekly (exemplu construit)", caption="Lectura mea: mediind în jos, TP2 vine înaintea lui TP1. " "Fraza-sursă e neinteligibilă exact aici."), # etichetele de săgeată sunt legate de desen prin coada lor, deci se # verifică doar etichetele de nivel "tinte", distante=(R1, R2), libere_nivel=(f"TP1 {TP1:.2f}", f"TP2 {TP2:.2f}", f"SL {SL:.2f}")) # ------------------------------- 5. trail-ul, pe timeframe-ul de sub decizie 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 cds5 = from_closes(cl5, wicks=[(0.40, 0.40)] * 30, vols=vols(30, 50, 12), start=43.00) 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) 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}" # trail-ul pe TF-ul deciziei: un singur swing low în toată fereastra, mai jos # decât toate cele patru mutări de pe TF-ul mic TF_MARE = lo(cds5, 0, 3) assert TF_MARE < t1 < t2 < t3 < t4, "trail-ul de pe TF-ul mare nu e cel mai de jos" assert all(c.l >= TF_MARE - 1e-9 for c in cds5), "trail-ul de pe TF-ul mare e lovit" def trail_tf_mare(ch): """Linia groasă din caption: stopul dacă trail-ul ar sta pe TF-ul deciziei.""" y = ch._y(TF_MARE) return [f'', f'trail pe TF-ul deciziei: {TF_MARE:.2f}, ' f'o singură mutare'] deco(Chart( candles=cds5, levels=[Level(t1, f"trail 1: {t1:.2f}", RED, i0=SW[0]), Level(t2, f"trail 2: {t2:.2f}", RED, i0=SW[1]), Level(t3, f"trail 3: {t3:.2f}", RED, i0=SW[2]), Level(t4, f"trail 4: {t4:.2f}", RED, i0=SW[3])], marks=[Mark(6, "swing low 1", RED, "below", dy=14), Mark(14, "swing low 2", RED, "below", dy=14), Mark(22, "swing low 3", RED, "below", dy=14), Mark(29, "swing low 4", RED, "below", dy=34)], scale=7.0, gap=17, body_w=9, left=52, width=700, top=64, vol_h=70, vol_label="volum", title="Trail pe swing low, pe timeframe-ul de sub decizie (exemplu construit)", caption="Patru mutări pe TF-ul mic; pe TF-ul deciziei, o singură dată " "(linia groasă)."), "trail", distante=(TF_MARE,), extra=trail_tf_mare, libere=("swing low 1", "swing low 4")) # ------------------------------------- 6. când nu merge (Tesla, 217.02/214.25) T_CL = ([320.00, 312.00, 304.00, 298.00] + [290.00, 282.00, 272.00, 262.00, 252.00, 244.00, 236.00, 230.00] + [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]) 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 round(217.02 - 214.25, 2) == 2.77, "distanța dintre minime nu e 2.77" assert len(TC) == 24 and len(TC[17:24]) == 7, "urcarea rostită e de 7 săptămâni" 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", ORANGE, label_at="left"), Level(214.25, "minimul de după: 214.25", RED, label_dy=18)], marks=[Mark(12, "primul semnal", ORANGE, dy=-72), Mark(17, "al doilea semnal: acesta merge", GREEN, dy=-34), Mark(23, "7 săptămâni de urcare", GREEN, dy=-10)], arrows=[Arrow(15, "stopul pus sub 217.02\na fost luat aici", price=214.25, where="below", color=RED, dy=4)], mas=[MA(20, ORANGE, 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", libere=("primul semnal", "7 săptămâni de urcare")) # ------------------------------------------------- 7. rutina de scanare SVG["rutina"] = """ Rutina: scanezi doji-uri, aștepți buy doji-ul în radar screen 1. Scanner, pe candelă închisă doar DOJI, nu buy doji 2. Lista, ținută patru luni într-un excel, NYSE + Nasdaq 3. Radar screen lista stă acolo și clipocește 4. Clipocește = buy doji îl vezi înainte de close Scanner-ul vede doar candela închisă: un buy doji weekly l-ai afla vineri după 23:00, iar unul monthly în ultima noapte a lunii. Radar screen-ul citește candela în formare, deci îl prinzi cu ore sau zile înainte să se închidă săptămâna ori luna. Când te uiți Weekly: vineri, cu una-două ore înainte de închidere, sau chiar de joi. Monthly: cu două-trei zile înainte de finalul lunii. De ce patru luni Breakout-ul peste doji poate veni și în a patra perioadă, deci lista nu se golește mai repede. """ # ---------------------------------------------------------------- scriere for name, svg in SVG.items(): (OUT / f"micis_{name}.svg").write_text(svg, encoding="utf-8") print(f"{len(SVG)} svg-uri scrise în {OUT}") if MD.exists(): md = MD.read_text(encoding="utf-8") lipsa = [n for n in SVG if f"" not in md] assert not lipsa, f"marcaje lipsă în capitol: {lipsa}" for name, svg in SVG.items(): md = re.sub(rf"(\n[\s\S]*?\n)?", lambda _m: (f"\n{svg.strip()}\n" f""), md, count=1) MD.write_text(md, encoding="utf-8") print(f"{len(SVG)} diagrame substituite în {MD.name}") else: print(f"{MD.name} nu există încă — doar svg-urile sunt scrise")