"""Diagramele pentru summaries/06_MODUL_5/02_patternuri_complexe_partea_a_treia.md. Construiește toate graficele cu svg_candles (o singură scară px/unitate pe desen), verifică prin assert fiecare cifră pe care o afirmă textul, apoi substituie marcatorii din fișierul .md. Rulează: .venv/bin/python gen/patternuri_complexe_3.py """ 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/02_patternuri_complexe_partea_a_treia.md" SVG = {} 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 check(ch, name, distante=()): """Nicio etichetă nu poate numi un preț care nu e un nivel desenat.""" svg = ch.render() assert "\n\n" not in svg, f"{name}: linie goală în svg" assert len(ch.caption) <= 100, f"{name}: caption de {len(ch.caption)} caractere" desenate = ({f"{lv.price:.2f}" for lv in ch.levels} | {f"{d:.2f}" for d in distante}) for t in re.findall(r"]*>(.*?)", svg, re.S): for m in re.finditer(r"\d+\.\d\d", re.sub(r"<[^>]+>", "", t)): assert m.group(0) in desenate, \ f"{name}: eticheta numește {m.group(0)}, care nu e un nivel desenat" # liniile de nivel nu cad pe marginea de sus a cadrului 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" SVG[name] = svg # ---------------------------------------------------------------- triple bottom closes = ([51.2, 50.3, 49.6, 48.5, 47.9, 46.8, 45.9, 44.8] # 0-7 downtrend + [43.6, 42.6, 42.9] # 8-10 picior 1 + [43.8, 44.6, 44.95] # 11-13 revenire + [44.0, 43.0, 42.5, 42.9] # 14-17 picior 2 + [43.9, 44.7, 44.9] # 18-20 revenire + [44.1, 43.1, 42.4, 43.0] # 21-24 picior 3 + [44.0, 45.3, 45.6] # 25-27 breakout + [44.6, 45.4] # 28-29 retest + intrare + [46.2, 47.0, 47.6, 48.1, 47.8]) # 30-34 spre target wicks = ([(0.25, 0.3)] * 8 + [(0.05, 0.4), (0.3, 0.60), (0.4, 0.3)] + [(0.3, 0.3), (0.3, 0.3), (0.05, 0.3)] + [(0.0, 0.3), (0.3, 0.3), (0.3, 0.45), (0.3, 0.3)] + [(0.3, 0.3), (0.2, 0.3), (0.10, 0.3)] + [(0.0, 0.3), (0.3, 0.3), (0.3, 0.40), (0.3, 0.3)] + [(0.2, 0.3), (0.2, 0.3), (0.3, 0.2)] + [(0.2, 0.35), (0.2, 0.2)] + [(0.2, 0.2), (0.2, 0.2), (0.2, 0.2), (0.3, 0.2), (0.2, 0.3)]) vols = ([52, 49, 46, 44, 41, 39, 44, 48] + [56, 64, 42] + [38, 36, 34] + [40, 46, 55, 38] + [33, 31, 30] + [36, 42, 48, 35] + [52, 96, 78] + [44, 58] + [66, 60, 54, 62, 48]) tb = from_closes(closes, wicks=wicks, vols=vols, start=52.0) REZ, PICIOR = 45.00, 42.00 assert lo(tb, 8, 10) == PICIOR, lo(tb, 8, 10) assert lo(tb, 14, 17) == 42.05, lo(tb, 14, 17) assert lo(tb, 21, 24) == PICIOR, lo(tb, 21, 24) assert hi(tb, 11, 13) == REZ and hi(tb, 18, 20) == REZ assert hi(tb, 8, 24) == REZ, "rezistența nu e maximul bazei" ADANCIME = REZ - PICIOR TARGET = REZ + ADANCIME RETEST_LOW = lo(tb, 28, 28) INTRARE, SL = tb[29].c, 44.10 assert abs(ADANCIME - 3.00) < 1e-9 and abs(TARGET - 48.00) < 1e-9 assert RETEST_LOW == 44.25, RETEST_LOW assert SL < RETEST_LOW < REZ < INTRARE, "ordinea SL / retest / rezistență / intrare" assert SL > PICIOR, "stopul de retest trebuie să fie peste picioarele bazei" assert tb[26].c > REZ >= tb[25].c, "breakout-ul nu e la bara 26" assert hi(tb, 30, 34) >= TARGET, "target-ul nu e atins în serie" VOL_BAZA = sum(vols[8:25]) / len(vols[8:25]) assert vols[26] >= 2 * VOL_BAZA, f"volumul de breakout e doar "\ f"{vols[26] / VOL_BAZA:.1f}x media bazei" R_TB = (TARGET - INTRARE) / (INTRARE - SL) R_TB_LARG = (TARGET - INTRARE) / (INTRARE - 41.90) print(f"triple bottom: intrare {INTRARE:.2f} SL {SL:.2f} target {TARGET:.2f} " f"-> {R_TB:.2f}R (stop sub bază: {R_TB_LARG:.2f}R)") assert abs(R_TB - 2.00) < 0.01, R_TB assert 0.7 < R_TB_LARG < 0.8, R_TB_LARG check(Chart( candles=tb, levels=[Level(TARGET, f"target {TARGET:.2f} = 45.00 + 3.00", "#1a5fb4"), Level(INTRARE, f"intrare {INTRARE:.2f}", "#188a3e", label_at="left"), Level(REZ, f"rezistență {REZ:.2f}", "#c00"), Level(SL, f"SL {SL:.2f}", "#c00", label_at="left"), Level(PICIOR, f"picioarele {PICIOR:.2f}", "#b06000")], marks=[Mark(9, "picior 1", "#b06000", "below"), Mark(16, "picior 2", "#b06000", "below"), Mark(23, "picior 3", "#b06000", "below"), Mark(26, "breakout cu volum dublu", "#188a3e", dy=-24), Mark(28, "retest", "#b06000", "below"),], zones=[Zone(8, 24, "baza triplă", "#1a5fb4")], mas=[MA(20, "#777", dash="4 3")], scale=42, gap=15, body_w=9, left=46, width=620, vol_h=70, top=100, vol_label="volum", title="Triple bottom — exemplu construit (fără ticker), daily", caption="Baza triplă ține 17 bare. Linia gri punctată e SMA-20, pe toate graficele."), "tb", [ADANCIME]) # ------------------------------------------------ triple bottom: fals breakout closes_n = ([49.8, 48.9, 47.8, 46.6, 45.6, 44.7] # 0-5 downtrend + [43.5, 42.6, 43.0] # 6-8 picior 1 + [43.9, 44.6, 44.9] # 9-11 revenire + [44.1, 43.1, 42.5, 43.0] # 12-15 picior 2 + [43.9, 44.7, 44.9] # 16-18 revenire + [44.2, 43.2, 42.5, 43.1] # 19-22 picior 3 + [44.1, 44.85] # 23-24 fals breakout + [44.0, 43.2, 42.4, 41.6, 41.0]) # 25-29 cade prin bază wicks_n = ([(0.25, 0.3)] * 6 + [(0.3, 0.4), (0.3, 0.60), (0.3, 0.3)] + [(0.3, 0.3), (0.3, 0.3), (0.10, 0.3)] + [(0.0, 0.3), (0.3, 0.3), (0.3, 0.50), (0.3, 0.3)] + [(0.3, 0.3), (0.3, 0.3), (0.10, 0.3)] + [(0.0, 0.3), (0.3, 0.3), (0.3, 0.50), (0.3, 0.3)] + [(0.2, 0.3), (0.23, 0.3)] + [(0.2, 0.3), (0.2, 0.3), (0.2, 0.3), (0.2, 0.3), (0.2, 0.4)]) vols_n = ([50, 47, 44, 42, 46, 50] + [55, 62, 40] + [36, 34, 32] + [38, 44, 52, 36] + [31, 30, 29] + [36, 43, 50, 34] + [33, 31] + [46, 54, 61, 70, 66]) tbn = from_closes(closes_n, wicks=wicks_n, vols=vols_n, start=50.4) assert hi(tbn, 23, 24) == 45.08, hi(tbn, 23, 24) assert tbn[24].c < REZ, "falsul breakout nu are voie să închidă peste linie" assert lo(tbn, 6, 8) == PICIOR and lo(tbn, 19, 22) == PICIOR assert hi(tbn, 6, 22) <= REZ, "nicio bară din bază nu trece peste rezistență" assert lo(tbn, 25, 29) < PICIOR, "invalidarea cere un minim sub picioare" assert max(vols_n[23:25]) < min(vols_n[6:8]), "falsul breakout vine cu volum mic" INVALID = lo(tbn, 25, 29) print(f"fals breakout: varf {hi(tbn, 23, 24):.2f} inchidere {tbn[24].c:.2f} " f"invalidare {INVALID:.2f}") check(Chart( candles=tbn, levels=[Level(REZ, f"rezistență {REZ:.2f}", "#c00"), Level(45.08, "vârf 45.08", "#b06000", label_at="left"), Level(PICIOR, f"picioarele {PICIOR:.2f}", "#b06000"), Level(INVALID, f"{INVALID:.2f} — invalidat", "#c00", label_at="left")], marks=[Mark(7, "picior 1", "#b06000", "below"), Mark(14, "picior 2", "#b06000", "below"), Mark(21, "picior 3", "#b06000", "below"), Mark(24, "închide sub linie, volum mic", "#c00"), Mark(28, "sub picioare: nu mai e bază triplă", "#c00", "below")], mas=[MA(20, "#777", dash="4 3")], scale=42, gap=15, body_w=9, left=46, width=620, vol_h=70, top=100, vol_label="volum", title="NU — fals breakout pe aceeași bază triplă (construit), daily", caption="Atinge 45.08, închide sub 45.00 cu volum mic, apoi cade sub picioare."), "tbn", []) # -------------------------------------------------------------- rounding bottom closes_r = ([29.8, 29.1, 28.3, 27.4] # 0-3 coboară de la rim + [26.6, 26.9, 26.4, 26.8] # 4-7 picior 1 + [26.5, 26.2, 26.7, 26.9] # 8-11 picior 2 + [26.6, 26.4, 27.0, 27.4] # 12-15 picior 3 + [27.1, 26.9, 27.6, 28.1] # 16-19 picior 4 + [27.8, 27.6, 28.4, 29.0] # 20-23 picior 5 + [29.5, 29.95, 30.4] # 24-26 breakout + [29.7, 30.3] # 27-28 retest + intrare + [31.0, 31.8, 32.5, 33.2, 33.9, 34.1]) # 29-34 spre target wicks_r = ([(0.00, 0.2), (0.2, 0.2), (0.2, 0.2), (0.2, 0.2)] + [(0.2, 0.2), (0.2, 0.2), (0.2, 0.30), (0.2, 0.2)] + [(0.2, 0.2), (0.2, 0.20), (0.2, 0.2), (0.2, 0.2)] + [(0.2, 0.2), (0.2, 0.25), (0.2, 0.2), (0.2, 0.2)] + [(0.2, 0.2), (0.2, 0.30), (0.2, 0.2), (0.2, 0.2)] + [(0.2, 0.2), (0.2, 0.30), (0.2, 0.2), (0.2, 0.2)] + [(0.2, 0.2), (0.05, 0.2), (0.2, 0.2)] + [(0.2, 0.15), (0.2, 0.2)] + [(0.2, 0.2)] * 5 + [(0.2, 0.3)]) vols_r = ([62, 54, 47, 43] + [40, 36, 38, 33] + [31, 34, 30, 28] + [27, 29, 28, 30] + [31, 33, 35, 37] + [39, 41, 44, 48] + [54, 60, 102] + [52, 68] + [74, 70, 66, 62, 70, 56]) rb = from_closes(closes_r, wicks=wicks_r, vols=vols_r, start=30.0) RIM, FUND = 30.00, 26.00 picioare_rb = [lo(rb, 4, 7), lo(rb, 8, 11), lo(rb, 12, 15), lo(rb, 16, 19), lo(rb, 20, 23)] assert picioare_rb == [26.10, 26.00, 26.15, 26.60, 27.30], picioare_rb assert len(picioare_rb) == 5 and min(picioare_rb) == FUND assert hi(rb, 0, 0) == RIM and hi(rb, 25, 25) == RIM assert hi(rb, 0, 25) == RIM, "rim-ul nu e maximul formațiunii" assert picioare_rb[2:] == sorted(picioare_rb[2:]), \ "picioarele castronului urcă după cel mai adânc" AD_RB = RIM - FUND TARGET_RB = RIM + AD_RB RETEST_RB = lo(rb, 27, 27) INTRARE_RB, SL_RB = rb[28].c, 29.40 assert abs(AD_RB - 4.00) < 1e-9 and abs(TARGET_RB - 34.00) < 1e-9 assert RETEST_RB == 29.55, RETEST_RB assert SL_RB < RETEST_RB < RIM < INTRARE_RB assert rb[26].c > RIM >= rb[25].c, "breakout-ul nu e la bara 26" assert hi(rb, 29, 34) >= TARGET_RB assert min(vols_r[10:16]) < min(vols_r[0:2]) and min(vols_r[10:16]) < vols_r[26], \ "volumul trebuie să fie mic în mijlocul castronului și mare la capete" assert len(rb[4:24]) == 20, "castronul are 20 de bare" R_RB = (TARGET_RB - INTRARE_RB) / (INTRARE_RB - SL_RB) print(f"rounding bottom: 5 picioare {picioare_rb}, intrare {INTRARE_RB:.2f} " f"SL {SL_RB:.2f} target {TARGET_RB:.2f} -> {R_RB:.2f}R") assert abs(R_RB - 4.11) < 0.01, R_RB check(Chart( candles=rb, levels=[Level(TARGET_RB, f"target {TARGET_RB:.2f} = 30.00 + 4.00", "#1a5fb4"), Level(INTRARE_RB, f"intrare {INTRARE_RB:.2f}", "#188a3e", label_at="left"), Level(RIM, f"rezistență {RIM:.2f}", "#c00"), Level(SL_RB, f"SL {SL_RB:.2f}", "#c00", label_at="left"), Level(FUND, f"fundul castronului {FUND:.2f}", "#b06000")], marks=[Mark(6, "1", "#b06000", "below"), Mark(9, "2", "#b06000", "below"), Mark(13, "3", "#b06000", "below"), Mark(17, "4", "#b06000", "below"), Mark(21, "5", "#b06000", "below"), Mark(26, "breakout", "#188a3e"), Mark(27, "retest", "#b06000", "below")], zones=[Zone(4, 23, "castronul", "#1a5fb4")], mas=[MA(20, "#777", dash="4 3")], scale=60, gap=15, body_w=9, left=46, width=620, vol_h=70, top=100, vol_label="volum", title="Rounding bottom — exemplu construit (fără ticker), 60 min", caption="Castronul ține 20 de bare și 5 swing low-uri. Volum mare la capete, mic la mijloc."), "rb", [AD_RB] + picioare_rb) # ------------------------------------------------------------------- triple top closes_t = ([60.8, 61.9, 62.7, 63.8, 64.6, 65.4, 66.3, 67.2] # 0-7 uptrend + [67.9, 67.0, 66.0] # 8-10 vârf 1 + [65.4, 66.2, 67.0, 67.85] # 11-14 vârf 2 + [67.1, 66.2, 65.3, 66.0] # 15-18 + [66.9, 67.8, 66.8, 65.6] # 19-22 vârf 3 + [65.1, 64.6, 64.3] # 23-25 breakdown + [65.0, 64.7] # 26-27 retest + intrare + [64.0, 63.3, 62.6, 62.0, 61.7, 62.2]) # 28-33 spre target wicks_t = ([(0.25, 0.25)] * 8 + [(0.10, 0.25), (0.00, 0.25), (0.25, 0.25)] + [(0.25, 0.40), (0.25, 0.25), (0.25, 0.25), (0.15, 0.25)] + [(0.00, 0.25), (0.25, 0.25), (0.25, 0.30), (0.25, 0.25)] + [(0.25, 0.25), (0.20, 0.25), (0.00, 0.25), (0.25, 0.25)] + [(0.25, 0.25), (0.25, 0.25), (0.25, 0.25)] + [(0.30, 0.25), (0.25, 0.25)] + [(0.25, 0.25)] * 5 + [(0.25, 0.3)]) vols_t = ([40, 42, 44, 46, 45, 43, 41, 44] + [62, 48, 44] + [40, 36, 34, 52] + [42, 38, 44, 33] + [30, 46, 40, 44] + [58, 94, 76] + [48, 62] + [68, 64, 60, 66, 58, 50]) tt = from_closes(closes_t, wicks=wicks_t, vols=vols_t, start=60.0) VARF, SUPORT = 68.00, 65.00 varfuri = [hi(tt, 8, 8), hi(tt, 14, 14), hi(tt, 20, 20)] assert varfuri == [VARF, VARF, VARF], varfuri assert hi(tt, 8, 22) == VARF, "vârfurile sunt maximul formațiunii" assert lo(tt, 11, 11) == SUPORT and lo(tt, 15, 18) == SUPORT assert lo(tt, 8, 22) == SUPORT, "suportul nu e minimul formațiunii" INALTIME = VARF - SUPORT TARGET_TT = SUPORT - INALTIME RETEST_TT = hi(tt, 26, 26) INTRARE_TT, SL_TT = tt[27].c, 65.40 assert abs(INALTIME - 3.00) < 1e-9 and abs(TARGET_TT - 62.00) < 1e-9 assert RETEST_TT == 65.30, RETEST_TT assert INTRARE_TT < SUPORT < RETEST_TT < SL_TT, "ordinea intrare / suport / retest / SL" assert SL_TT < VARF, "stopul de retest stă sub vârfuri" assert tt[24].c < SUPORT <= tt[23].c, "breakdown-ul nu e la bara 24" assert lo(tt, 28, 33) <= TARGET_TT assert len(tt[8:23]) == 15, "vârful triplu are 15 bare" R_TT = (INTRARE_TT - TARGET_TT) / (SL_TT - INTRARE_TT) R_TT_LARG = (INTRARE_TT - TARGET_TT) / (VARF + 0.10 - INTRARE_TT) print(f"triple top: intrare {INTRARE_TT:.2f} SL {SL_TT:.2f} target {TARGET_TT:.2f} " f"-> {R_TT:.2f}R (stop peste vârfuri: {R_TT_LARG:.2f}R)") assert abs(R_TT - 3.86) < 0.01, R_TT assert 0.7 < R_TT_LARG < 0.8, R_TT_LARG check(Chart( candles=tt, levels=[Level(VARF, f"vârfurile {VARF:.2f}", "#b06000"), Level(SL_TT, f"SL {SL_TT:.2f}", "#c00"), Level(SUPORT, f"suport {SUPORT:.2f}", "#c00", label_at="left"), Level(INTRARE_TT, f"intrare short {INTRARE_TT:.2f}", "#188a3e"), Level(TARGET_TT, f"target {TARGET_TT:.2f} = 65.00 − 3.00", "#1a5fb4", label_at="left")], marks=[Mark(8, "vârf 1", "#b06000", dy=-20), Mark(14, "vârf 2", "#b06000", dy=-20), Mark(20, "vârf 3", "#b06000", dy=-20), Mark(24, "breakdown", "#c00", "below"), Mark(26, "retest de dedesubt", "#b06000", dy=-34)], mas=[MA(20, "#777", dash="4 3")], scale=42, gap=15, body_w=9, left=46, width=620, vol_h=70, top=100, vol_label="volum", title="Triple top — exemplu construit (fără ticker), weekly", caption="Vârful triplu ține 15 bare, de la vârful 1 la ultima bară de sub el."), "tt", [INALTIME]) # ------------------------------------ triple top cu vârful din mijloc mai sus closes_h = ([61.0, 62.2, 63.3, 64.4, 65.5, 66.4, 67.0] # 0-6 uptrend + [67.4, 66.6, 65.6] # 7-9 vârf 1 + [65.2, 66.4, 67.6] # 10-12 + [68.6, 69.4, 68.0] # 13-15 vârf 2, mai sus + [66.8, 65.6, 65.2] # 16-18 + [66.3, 67.5, 66.4] # 19-21 vârf 3 + [65.6, 65.1, 64.5] # 22-24 breakdown + [64.0, 63.2, 62.4, 61.6, 60.9, 60.4, 60.8]) # 25-31 spre target wicks_h = ([(0.25, 0.25)] * 7 + [(0.10, 0.25), (0.00, 0.25), (0.25, 0.25)] + [(0.25, 0.20), (0.25, 0.20), (0.25, 0.25)] + [(0.25, 0.25), (0.10, 0.25), (0.00, 0.25)] + [(0.25, 0.25), (0.25, 0.25), (0.25, 0.20)] + [(0.25, 0.20), (0.10, 0.25), (0.00, 0.25)] + [(0.25, 0.25)] * 3 + [(0.25, 0.25)] * 6 + [(0.25, 0.3)]) vols_h = ([40, 42, 44, 46, 48, 46, 44] + [60, 46, 42] + [38, 36, 44] + [52, 86, 58] + [44, 40, 36] + [34, 48, 42] + [50, 62, 90] + [74, 70, 66, 62, 58, 64, 52]) th = from_closes(closes_h, wicks=wicks_h, vols=vols_h, start=60.0) V1, V2, V3 = hi(th, 7, 7), hi(th, 13, 14), hi(th, 19, 20) assert (V1, V2, V3) == (67.50, 69.50, 67.60), (V1, V2, V3) assert V2 > V1 and V2 > V3, "vârful din mijloc trebuie să fie cel mai sus" assert hi(th, 7, 21) == V2, "capul trebuie să fie maximul formațiunii" assert lo(th, 10, 10) == SUPORT and lo(th, 18, 18) == SUPORT assert lo(th, 7, 21) == SUPORT, "suportul nu e minimul formațiunii" AD_H = V2 - SUPORT TARGET_H = SUPORT - AD_H assert abs(AD_H - 4.50) < 1e-9 and abs(TARGET_H - 60.50) < 1e-9 assert lo(th, 25, 31) <= TARGET_H, lo(th, 25, 31) assert th[24].c < SUPORT <= th[23].c, "breakdown-ul nu e la bara 24" print(f"triple top varianta H&S: varfuri {V1:.2f}/{V2:.2f}/{V3:.2f}, " f"masurat din {V2:.2f} -> target {TARGET_H:.2f}") check(Chart( candles=th, levels=[Level(V2, f"vârful 2 (capul) {V2:.2f}", "#b06000"), Level(V1, f"umerii ≈ {V1:.2f}", "#b06000", label_at="left"), Level(SUPORT, f"suport (neckline) {SUPORT:.2f}", "#c00", label_at="left"), Level(TARGET_H, f"target {TARGET_H:.2f} = 65.00 − 4.50", "#1a5fb4")], marks=[Mark(7, "vârf 1", "#b06000"), Mark(14, "vârf 2 — cel mai sus", "#b06000", dy=-20), Mark(20, "vârf 3", "#b06000"), Mark(24, "breakdown", "#c00", "below")], mas=[MA(20, "#777", dash="4 3")], scale=42, gap=15, body_w=9, left=46, width=620, vol_h=70, top=100, vol_label="volum", title="Triple top cu vârful din mijloc mai sus — construit (fără ticker), weekly", caption="Înălțimea se măsoară de la vârful 2, nu de la cele laterale."), "th", [AD_H]) # --------------------------------------------------------------- cup with handle closes_c = ([97.0, 101.0, 106.0, 111.0, 116.0, 119.5] # 0-5 uptrend + rim 1 + [116.0, 110.0, 103.0, 96.0, 89.0, 83.0] # 6-11 coboară ceașca + [80.5, 82.0, 81.0, 85.0] # 12-15 fundul ceștii + [89.0, 94.0, 99.0, 104.0, 109.0, 113.0, 117.0, 119.6] # 16-23 urcă + rim 2 + [116.0, 112.0, 108.5, 112.0] # 24-27 mânerul + [118.0, 121.0] # 28-29 breakout, intrare + [128.0, 136.0, 143.0, 150.0, 157.0, 160.5]) # 30-35 spre target wicks_c = ([(1.0, 1.0)] * 5 + [(0.50, 1.0)] + [(0.00, 1.0)] + [(1.0, 1.0)] * 5 + [(1.0, 0.50), (1.0, 0.50), (1.0, 0.80), (1.0, 1.0)] + [(1.0, 1.0)] * 7 + [(0.40, 1.0)] + [(0.00, 1.0), (1.0, 1.0), (1.0, 0.50), (1.0, 0.50)] + [(1.0, 1.0), (1.0, 1.0)] + [(1.0, 1.0)] * 5 + [(1.0, 1.5)]) vols_c = ([44, 48, 52, 50, 54, 58] + [62, 68, 74, 70, 64, 56] + [50, 44, 42, 46] + [48, 52, 54, 50, 52, 56, 60, 64] + [40, 34, 30, 36] + [58, 104] + [92, 84, 78, 72, 70, 60]) cu = from_closes(closes_c, wicks=wicks_c, vols=vols_c, start=95.0) RIM_C, FUND_C = 120.00, 80.00 assert hi(cu, 5, 5) == RIM_C and hi(cu, 23, 23) == RIM_C, (hi(cu, 5, 5), hi(cu, 23, 23)) assert hi(cu, 0, 23) == RIM_C, "cele două rim-uri sunt maximul ceștii" assert lo(cu, 12, 15) == FUND_C, lo(cu, 12, 15) AD_C = RIM_C - FUND_C TARGET_C = RIM_C + AD_C MANER = lo(cu, 24, 27) PROC_MANER = (RIM_C - MANER) / AD_C LIMITA50 = RIM_C - 0.5 * AD_C INTRARE_C, SL_C = cu[29].c, 107.00 assert abs(AD_C - 40.00) < 1e-9 and abs(TARGET_C - 160.00) < 1e-9 assert MANER == 108.00, MANER assert abs(LIMITA50 - 100.00) < 1e-9 assert MANER > LIMITA50, "mânerul valid nu coboară sub jumătatea ceștii" assert abs(PROC_MANER - 0.30) < 1e-9, PROC_MANER assert SL_C < MANER < RIM_C < INTRARE_C assert cu[29].c > RIM_C >= cu[28].c, "breakout-ul nu e la bara 29" assert hi(cu, 30, 35) >= TARGET_C assert len(cu[24:28]) == 4, "mânerul are 4 bare" R_C = (TARGET_C - INTRARE_C) / (INTRARE_C - SL_C) R_C_LIMITA = (TARGET_C - INTRARE_C) / (INTRARE_C - (LIMITA50 - 1.00)) print(f"cup&handle: maner {MANER:.2f} ({PROC_MANER:.0%}), intrare {INTRARE_C:.2f} " f"SL {SL_C:.2f} target {TARGET_C:.2f} -> {R_C:.2f}R " f"(maner la limita de 50%: {R_C_LIMITA:.2f}R)") assert abs(R_C - 2.79) < 0.01, R_C assert abs(R_C_LIMITA - 1.77) < 0.01, R_C_LIMITA check(Chart( candles=cu, levels=[Level(TARGET_C, f"target {TARGET_C:.2f} = 120.00 + 40.00", "#1a5fb4"), Level(INTRARE_C, f"intrare {INTRARE_C:.2f}", "#188a3e", label_at="left"), Level(RIM_C, f"rezistență {RIM_C:.2f}", "#c00"), Level(MANER, f"minimul mânerului {MANER:.2f}", "#b06000"), Level(SL_C, f"SL {SL_C:.2f}", "#c00", label_at="left"), Level(LIMITA50, f"limita de 50% {LIMITA50:.2f}", "#777"), Level(FUND_C, f"fundul ceștii {FUND_C:.2f}", "#b06000", label_at="left")], marks=[Mark(5, "top 1", "#c00"), Mark(23, "top 2", "#c00", dy=-18), Mark(13, "fundul ceștii", "#b06000", "below"), Mark(29, "breakout", "#188a3e", dy=-14)], zones=[Zone(24, 27, "mânerul", "#b06000")], mas=[MA(20, "#777", dash="4 3")], scale=4.2, gap=15, body_w=9, left=46, width=620, vol_h=70, top=100, vol_label="volum", title="Cup with handle — exemplu construit (fără ticker), monthly", caption="Mânerul: 4 bare, −30% din adâncime. Cele două top-uri la 120.00 = double top."), "cu", [AD_C, RIM_C - MANER]) # ----------------------------------------- cup with handle: mâner prea adânc closes_cn = ([99.0, 105.0, 111.0, 116.0, 119.5] # 0-4 uptrend + rim 1 + [115.0, 108.0, 100.0, 92.0, 84.0] # 5-9 coboară + [80.6, 83.0, 86.0] # 10-12 fundul + [91.0, 97.0, 103.0, 109.0, 114.0, 118.0, 119.6] # 13-19 urcă + rim 2 + [114.0, 108.0, 102.0, 96.5, 99.0] # 20-24 mâner prea adânc + [97.0, 94.0, 90.0, 93.0, 91.0]) # 25-29 recade în ceașcă wicks_cn = ([(1.0, 1.0)] * 4 + [(0.50, 1.0)] + [(0.00, 1.0)] + [(1.0, 1.0)] * 4 + [(1.0, 0.60), (1.0, 0.60), (1.0, 1.0)] + [(1.0, 1.0)] * 6 + [(0.40, 1.0)] + [(0.00, 1.0), (1.0, 1.0), (1.0, 1.0), (1.0, 0.50), (1.0, 0.50)] + [(1.0, 1.0)] * 4 + [(1.0, 1.5)]) vols_cn = ([46, 50, 54, 52, 56] + [60, 66, 72, 68, 62] + [52, 46, 48] + [50, 52, 54, 52, 54, 58, 62] + [56, 62, 70, 76, 48] + [54, 62, 70, 58, 64]) cn = from_closes(closes_cn, wicks=wicks_cn, vols=vols_cn, start=95.0) assert hi(cn, 4, 4) == RIM_C and hi(cn, 19, 19) == RIM_C assert lo(cn, 10, 12) == FUND_C, lo(cn, 10, 12) MANER_N = lo(cn, 20, 24) PROC_N = (RIM_C - MANER_N) / AD_C assert MANER_N == 96.00, MANER_N assert abs(PROC_N - 0.60) < 1e-9, PROC_N assert MANER_N < LIMITA50, "mânerul invalid trebuie să fie sub limita de 50%" assert hi(cn, 20, 29) < RIM_C, "în varianta NU nu există breakout" print(f"cup&handle NU: maner {MANER_N:.2f} = {PROC_N:.0%} din adancime") check(Chart( candles=cn, levels=[Level(RIM_C, f"rezistență {RIM_C:.2f}", "#c00"), Level(LIMITA50, f"limita de 50% {LIMITA50:.2f}", "#777", label_at="left"), Level(MANER_N, f"minimul mânerului {MANER_N:.2f}", "#c00"), Level(FUND_C, f"fundul ceștii {FUND_C:.2f}", "#b06000", label_at="left")], marks=[Mark(4, "top 1", "#c00", dy=-20), Mark(19, "top 2", "#c00", dy=-20), Mark(11, "fundul ceștii", "#b06000", "below"), Mark(23, "rupe 100.00: −60%", "#c00", "below"), Mark(27, "fără breakout, prețul stă în ceașcă", "#c00", "below")], mas=[MA(20, "#777", dash="4 3")], scale=4.2, gap=15, body_w=9, left=46, width=620, vol_h=70, top=100, vol_label="volum", title="NU — mâner mai adânc de 50% (construit, fără ticker), monthly", caption="Mânerul coboară la 96.00, sub limita de 50% desenată la 100.00."), "cn", [AD_C, RIM_C - MANER_N]) # ------------------------------------- varianta bear: inverted cup with handle closes_b = ([101.0, 98.0, 95.0, 92.0, 90.4] # 0-4 downtrend + rim 1 + [94.0, 100.0, 106.0, 112.0, 117.0, 119.5] # 5-10 urcă cupola + [119.8, 117.0, 113.0] # 11-13 vârful cupolei + [108.0, 103.0, 98.0, 95.0, 93.0, 91.5, 90.6] # 14-20 coboară + rim 2 + [93.0, 96.0, 97.6, 94.0] # 21-24 mâner inversat + [92.0, 89.0] # 25-26 breakdown, intrare + [83.0, 77.0, 72.0, 67.0, 63.0, 60.0, 61.0]) # 27-33 spre target wicks_b = ([(0.8, 0.8)] * 4 + [(0.8, 0.40)] + [(0.8, 0.00)] + [(0.8, 0.8)] * 4 + [(0.50, 0.8)] + [(0.20, 0.8), (0.00, 0.8), (0.8, 0.8)] + [(0.8, 0.8)] * 6 + [(0.8, 0.60)] + [(0.8, 0.00), (0.8, 0.8), (0.40, 0.8), (0.40, 0.8)] + [(0.8, 0.8), (0.8, 0.8)] + [(0.8, 0.8)] * 6 + [(0.8, 0.8)]) vols_b = ([48, 52, 56, 54, 58] + [52, 50, 48, 46, 44, 42] + [56, 50, 48] + [52, 56, 60, 58, 54, 50, 56] + [36, 32, 30, 38] + [62, 108] + [96, 88, 82, 76, 72, 70, 58]) cb = from_closes(closes_b, wicks=wicks_b, vols=vols_b, start=103.0) RIM_B, CUPOLA = 90.00, 120.00 assert lo(cb, 4, 4) == RIM_B and lo(cb, 20, 20) == RIM_B, (lo(cb, 4, 4), lo(cb, 20, 20)) assert lo(cb, 0, 20) == RIM_B, "cele două rim-uri sunt minimul cupolei" assert hi(cb, 10, 13) == CUPOLA, hi(cb, 10, 13) AD_B = CUPOLA - RIM_B TARGET_B = RIM_B - AD_B MANER_B = hi(cb, 21, 24) PROC_B = (MANER_B - RIM_B) / AD_B LIMITA50_B = RIM_B + 0.5 * AD_B INTRARE_B, SL_B = cb[26].c, 99.00 assert abs(AD_B - 30.00) < 1e-9 and abs(TARGET_B - 60.00) < 1e-9 assert MANER_B == 98.00, MANER_B assert abs(LIMITA50_B - 105.00) < 1e-9 assert MANER_B < LIMITA50_B, "mânerul inversat valid nu urcă peste jumătatea cupolei" assert INTRARE_B < RIM_B < MANER_B < SL_B assert cb[26].c < RIM_B <= cb[25].c, "breakdown-ul nu e la bara 26" assert lo(cb, 27, 33) <= TARGET_B assert len(cb[21:25]) == 4, "mânerul inversat are 4 bare" R_B = (INTRARE_B - TARGET_B) / (SL_B - INTRARE_B) print(f"cup&handle bear: cupola {CUPOLA:.2f} rim {RIM_B:.2f} maner {MANER_B:.2f} " f"({PROC_B:.0%}), intrare {INTRARE_B:.2f} SL {SL_B:.2f} target {TARGET_B:.2f} " f"-> {R_B:.2f}R") assert abs(R_B - 2.90) < 0.01, R_B assert abs(PROC_B - 0.2667) < 0.001, PROC_B check(Chart( candles=cb, levels=[Level(CUPOLA, f"vârful cupolei {CUPOLA:.2f}", "#b06000"), Level(SL_B, f"SL {SL_B:.2f}", "#c00"), Level(LIMITA50_B, f"limita de 50% {LIMITA50_B:.2f}", "#777", label_at="left"), Level(RIM_B, f"suport {RIM_B:.2f}", "#c00"), Level(INTRARE_B, f"intrare short {INTRARE_B:.2f}", "#188a3e", label_at="left"), Level(TARGET_B, f"target {TARGET_B:.2f} = 90.00 − 30.00", "#1a5fb4")], marks=[Mark(4, "fund 1", "#c00", "below"), Mark(20, "fund 2", "#c00", "below"), Mark(11, "vârful cupolei", "#b06000", dy=-20), Mark(26, "breakdown", "#c00", "below")], zones=[Zone(21, 24, "mânerul inversat", "#b06000")], mas=[MA(20, "#777", dash="4 3")], scale=4.2, gap=15, body_w=9, left=46, width=620, vol_h=70, top=100, vol_label="volum", title="Inverted cup with handle / rounding top — construit (fără ticker), monthly", caption="Mânerul inversat: 4 bare, +27%. Cele două funduri la 90.00 = double bottom."), "cb", [AD_B]) # --------------------------------------------- aceleași reguli între diagrame # stopul stă dincolo de bara de retest (triple bottom / rounding bottom / triple # top) sau dincolo de mâner (cup with handle, în ambele direcții) assert SL < RETEST_LOW and SL_RB < RETEST_RB and SL_TT > RETEST_TT, \ "regula de stop pe retest nu e aceeași în toate diagramele" assert SL_C < MANER and SL_B > MANER_B, "regula de stop pe mâner nu e simetrică" # target = adâncimea/înălțimea proiectată de la linia spartă, în toate diagramele for linie, adanc, target, semn in ((REZ, ADANCIME, TARGET, 1), (RIM, AD_RB, TARGET_RB, 1), (SUPORT, INALTIME, TARGET_TT, -1), (SUPORT, AD_H, TARGET_H, -1), (RIM_C, AD_C, TARGET_C, 1), (RIM_B, AD_B, TARGET_B, -1)): assert abs(target - (linie + semn * adanc)) < 1e-9, (linie, adanc, target) # limita de 50% a mânerului, la fel pe long și pe short assert abs((RIM_C - LIMITA50) / AD_C - 0.5) < 1e-9 assert abs((LIMITA50_B - RIM_B) / AD_B - 0.5) < 1e-9 # ---------------------------------------------------------------- substituire with open(MD, encoding="utf-8") as f: md = f.read() lipsa = [n for n in SVG if f"" not in md] # marcatori obligatorii assert not lipsa, f"marcatori lipsă în .md: {lipsa}" for n, svg in SVG.items(): # marcatorul rămâne în fișier, ca să se poată rula generatorul din nou md = re.sub(rf"(\s*)?", lambda _m: f"\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}")