"""Diagramele pentru summaries/08_ANALIZA_ZILNICA/06_structura_trend.md. Toate prețurile sunt construite (fără ticker). Rulează: .venv/bin/python gen/az_structura.py Înlocuiește în .md fiecare bloc ... cu output-ul generatorului. Ca să schimbi o cifră, o schimbi aici și rulezi din nou. Liniile de trend (wedge, triunghi) se calculează din high-urile / low-urile candelelor și se inserează în SVG cu aceleași funcții de coordonate ale graficului (ch._cx, ch._y) — nu sunt desenate de mână. """ import re import sys from pathlib import Path sys.path.insert(0, "/workspace/atm-curs-dl") from svg_candles import Chart, Level, Mark, MA, from_closes # noqa: E402 MD = Path("/workspace/atm-curs-dl/summaries/08_ANALIZA_ZILNICA/06_structura_trend.md") GRAY, RED, GREEN, BLUE, ORANGE, INK = "#777", "#c00", "#188a3e", "#1a5fb4", "#b06000", "#333" STYLE = dict(gap=15, body_w=9, left=44, width=620) svgs = {} def wk(n, over=None, base=(0.3, 0.3)): """wick-uri: `base` peste tot, cu excepțiile din `over` {index: (sus, jos)}""" w = [base] * n for i, v in (over or {}).items(): w[i] = v return w def px_ratio(ch, a, b, c): """raportul (c-b)/(b-a) măsurat în pixeli, după render""" return (ch._y(b) - ch._y(c)) / (ch._y(a) - ch._y(b)) def is_sl(cds, i): return cds[i].l < cds[i - 1].l and cds[i].l < cds[i + 1].l def is_sh(cds, i): return cds[i].h > cds[i - 1].h and cds[i].h > cds[i + 1].h def doji(c): return abs(c.o - c.c) <= 0.15 and (c.h - c.l) >= 3 * abs(c.o - c.c) def line_at(i1, p1, i2, p2, i): return p1 + (p2 - p1) * (i - i1) / (i2 - i1) def with_lines(ch, svg, lines): """lines: (i1, p1, i2, p2, i_end, culoare) — linie de trend prin două puncte, prelungită până la i_end""" out = [] for i1, p1, i2, p2, ie, col in lines: pe = line_at(i1, p1, i2, p2, ie) out.append(f'') return svg.replace("", "\n " + "\n ".join(out), 1) def add(svg, elems): """elemente SVG în plus, desenate peste tot restul""" return svg.replace("\n", "\n " + "\n ".join(elems) + "\n") def lbl_below(ch, price, text, color, at="right"): """eticheta unui Level, SUB linie (când deasupra ar tăia o linie apropiată); nivelul se dă cu text gol""" x, anc = (ch.width - 28, "end") if at == "right" else (30, "start") return f'{text}' def callout(ch, i, price, text, color, anchor="middle", below=False): """text la prețul `price` deasupra (sau sub) barei i, cu o linie subțire până la bară""" c = ch.candles[i] x, ty = ch._cx(i), ch._y(price) tx = x - 4 if anchor == "start" else (x + 4 if anchor == "end" else x) y1, y2 = (ty - 13, ch._y(c.l) + 3) if below else (ty + 3, ch._y(c.h) - 3) return (f'\n ' f'{text}') def dy_line(c, scale, p_line, off, where): """dy pentru un Mark pe candela c, ca baseline-ul textului să stea la `off` px de prețul p_line (off<0 = deasupra). Diferențele de y depind doar de scară, deci merge înainte de render.""" if where == "above": return (c.h - p_line) * scale + off + 10 return (c.l - p_line) * scale + off - 20 def first_hit_up(cds, start, price): return next(i for i in range(start, len(cds)) if cds[i].h >= price - 1e-9) def first_hit_down(cds, start, price): return next(i for i in range(start, len(cds)) if cds[i].l <= price + 1e-9) # =============================================================== 1. scărița + confirmarea swing low-ului stair = [40.0, 40.9, 41.6, 42.4, # 0-3 urcare, SH la 3 41.9, 41.3, 41.0, # 4-6 pullback, SL la 6 41.8, 42.7, 43.5, 44.1, # 7-10 HH la 10 43.6, 43.0, 42.6, # 11-13 HL la 13 43.4, 44.3, 45.2, 45.8, # 14-17 HH la 17 45.3, 44.7, 44.3, # 18-20 HL la 20 45.1, 46.0, 46.9, 47.5, # 21-24 HH la 24 47.0, 46.4, 46.0] # 25-27 bara 27 = low „în formare" SH_I, SL_I = [3, 10, 17, 24], [6, 13, 20, 27] stair_w = {i: (0.7, 0.3) for i in SH_I} | {i: (0.3, 0.7) for i in SL_I} cl = stair + [47.0, # 28 candela din dreapta: confirmă 47.6, 48.3, 48.0, 48.9, 49.6, 49.3, 50.1, 50.6] cds = from_closes(cl, wicks=wk(len(cl), stair_w | {28: (0.2, 0.3)}), start=39.6) for i in SH_I: assert is_sh(cds, i), f"bara {i} nu e swing high" for i in SL_I: assert is_sl(cds, i), f"bara {i} nu e swing low" highs = [cds[i].h for i in SH_I] lows = [cds[i].l for i in SL_I] assert all(b > a for a, b in zip(highs, highs[1:])), "vârfurile nu sunt higher highs" assert all(b > a for a, b in zip(lows, lows[1:])), "minimele nu sunt higher lows" HL = 27 assert cds[HL + 1].c > cds[HL].h and cds[HL + 1].l > cds[HL].l, "candela din dreapta nu confirmă" entry = cds[HL + 1].c sl = round(cds[HL].l - 0.1, 2) step = cds[24].h - cds[20].l tp = round(cds[HL].l + step, 2) risk = entry - sl R = (tp - entry) / risk hit = first_hit_up(cds, HL + 2, tp) assert R >= 1.5, R print(f"S1 HH {[round(x, 2) for x in highs]} HL {[round(x, 2) for x in lows]} entry {entry:.2f} SL {sl:.2f} " f"treaptă {step:.2f} TP {tp:.2f} = {R:.2f}R atins la {hit} ({hit - HL - 1} candele)") ch = Chart(candles=cds, levels=[Level(sl, f"SL {sl:.2f}", RED), Level(entry, f"intrare {entry:.2f}", GREEN, label_at="left"), Level(tp, f"TP {tp:.2f} = {R:.1f}R", BLUE, label_at="left")], marks=[Mark(3, "SH", INK, "above"), Mark(6, "SL", INK, "below"), Mark(10, "HH", GREEN, "above"), Mark(13, "HL", GREEN, "below"), Mark(17, "HH", GREEN, "above", dy=-14), Mark(20, "HL", GREEN, "below", dy=12), Mark(24, "HH", GREEN, "above"), Mark(HL, "HL", GREEN, "below", dy=14)], mas=[MA(20, ORANGE, dash="4 3")], scale=24, title="Exemplu construit (fără ticker) — daily", caption="Linia punctată = SMA-20. Bara 27 devine higher low abia când bara 28 închide peste high-ul ei.", **{**STYLE, "width": 700}) svg_ = add(ch.render(), [callout(ch, HL + 1, 49.0, "confirmare", GREEN)]) assert abs(px_ratio(ch, sl, entry, tp) - R) < 0.01 svgs["scarita_da"] = svg_ S1 = dict(entry=entry, sl=sl, tp=tp, hl=cds[HL].l, hl20=cds[20].l) # =============================================================== 2. scărița NU: candela din dreapta sparge cl = stair + [45.2, # 28 low sub low-ul barei 27 44.6, 45.0, 44.1, 43.4, 43.8, 42.9, 42.5] cds = from_closes(cl, wicks=wk(len(cl), stair_w), start=39.6) last_hl = cds[20].l assert cds[28].l < cds[27].l, "bara 28 nu sparge low-ul barei 27" assert not is_sl(cds, 27), "bara 27 n-ar trebui să mai fie swing low" brk = next(i for i in range(28, len(cds)) if cds[i].c < last_hl) print(f"S2 low 27 {cds[27].l:.2f}, bara 28 low {cds[28].l:.2f} close {cds[28].c:.2f}; " f"ultimul HL confirmat {last_hl:.2f} spart la close {cds[brk].c:.2f} (bara {brk})") ch = Chart(candles=cds, levels=[Level(cds[27].l, f"HL? {cds[27].l:.2f} (în formare)", GRAY), Level(last_hl, f"ultimul HL confirmat {last_hl:.2f}", RED, label_at="left")], marks=[Mark(24, "HH", GREEN, "above"), Mark(20, "HL", GREEN, "below"), Mark(brk, "lower low", RED, "below")], scale=24, title="Exemplu construit (fără ticker) — daily", caption="Candela din dreapta închide sub low: nu există higher low. Sub 43.60 scărița e ruptă.", **{**STYLE, "width": 700}) svgs["scarita_nu"] = add(ch.render(), [callout(ch, 27, 47.2, "HL în formare?", GRAY, anchor="start")]) S2 = dict(low27=cds[27].l, c28=cds[28].c, last_hl=last_hl, brk_c=cds[brk].c) # =============================================================== 3. double bottom, al doilea picior mai sus down = [38.0, 37.2, 37.6, 36.5, 35.6, 35.9, 34.8, 33.9, 34.2, 33.0, 32.1, 31.4] # 0-11 bounce = [31.4, 32.1, 31.8, 32.5, 32.9, 32.3, 31.7] # 13-19 cl = down + [30.6] + bounce + [31.2, # 20 picior 2 32.1, # 21 confirmare 32.6, 32.3, 33.8, 34.3, 34.0, 34.9, 35.6, 35.3, 36.2, 36.8] cds = from_closes(cl, wicks=wk(len(cl), {12: (0.3, 0.6), 17: (0.4, 0.3), 20: (0.3, 0.4), 21: (0.2, 0.3)}), start=38.6) L1, L2, CF = 12, 20, 21 neck = max(c.h for c in cds[L1 + 1:L2]) assert neck == cds[17].h assert is_sl(cds, L1) and is_sl(cds, L2) assert cds[L2].l > cds[L1].l, "al doilea picior trebuie să fie mai sus" assert cds[CF].c > cds[L2].h and cds[CF].l > cds[L2].l entry = cds[CF].c sl = round(cds[L2].l - 0.1, 2) risk = entry - sl tp = round(neck + (neck - cds[L1].l), 2) R = (tp - entry) / risk bo = next(i for i in range(CF, len(cds)) if cds[i].c > neck) hit = first_hit_up(cds, CF + 1, tp) assert hit == len(cds) - 1 and R >= 2.5 print(f"S3 picior1 {cds[L1].l:.2f} picior2 {cds[L2].l:.2f} neck {neck:.2f} entry {entry:.2f} SL {sl:.2f} " f"TP {tp:.2f} = {R:.2f}R; breakout la {bo} close {cds[bo].c:.2f}; TP atins la {hit}") ch = Chart(candles=cds, levels=[Level(neck, f"neckline {neck:.2f}", ORANGE, label_at="left"), Level(sl, f"SL {sl:.2f}", RED), Level(entry, f"intrare {entry:.2f}", GREEN), Level(tp, f"TP {tp:.2f} = {R:.1f}R", BLUE)], marks=[Mark(L1, f"picior 1: {cds[L1].l:.2f}", RED, "below"), Mark(L2, f"picior 2: {cds[L2].l:.2f} (mai sus)", GREEN, "below", dy=12), Mark(bo, "breakout", GREEN, "above", dy=-4)], scale=26, title="Exemplu construit (fără ticker) — daily", caption="Intrare la confirmarea piciorului 2; ținta = neckline + înălțimea pattern-ului.", **{**STYLE, "width": 700}) svg_ = ch.render() assert abs(px_ratio(ch, sl, entry, tp) - R) < 0.01 svgs["db_mai_sus"] = svg_ S3 = dict(l1=cds[L1].l, l2=cds[L2].l, neck=neck, entry=entry, sl=sl, tp=tp, R=R, bo_c=cds[bo].c) # =============================================================== 4. double bottom, al doilea picior mai jos (stop hunt) cl = down + [30.6] + bounce + [31.4 - 0.8, # 20 coborâre 30.3, # 21 picior 2: hammer sub picior 1 31.5, # 22 trigger 32.2, 31.9, 33.0, 33.9, 34.4, 34.1, 35.2, 36.0, 35.6, 36.9] vols = [52, 50, 44, 55, 58, 46, 60, 62, 48, 64, 66, 68, 74, 46, 42, 38, 40, 44, 40, 46, 50, 52, 70, 60, 48, 62, 72, 58, 46, 60, 58, 44, 56] cds = from_closes(cl, wicks=wk(len(cl), {12: (0.3, 0.6), 17: (0.4, 0.3), 21: (0.3, 0.9), 22: (0.2, 0.3)}), vols=vols, start=38.6) L1, L2, TR = 12, 21, 22 neck = max(c.h for c in cds[L1 + 1:L2]) leg1, leg2 = cds[L1].l, cds[L2].l assert is_sl(cds, L1) and is_sl(cds, L2) assert leg2 < leg1, "al doilea picior trebuie să fie mai jos" body = abs(cds[L2].c - cds[L2].o) assert min(cds[L2].o, cds[L2].c) - cds[L2].l >= 2.5 * body, "piciorul 2 nu e hammer" assert vols[L2] < vols[L1] * 0.75, "volumul pe piciorul 2 trebuie să fie clar mai mic" assert cds[TR].c > cds[L2].h and cds[TR].c > leg1, "trigger-ul nu închide peste high-ul hammer-ului" entry = cds[TR].c sl = round(leg2 - 0.1, 2) risk = entry - sl tp = round(neck + (neck - leg2), 2) R = (tp - entry) / risk hit = first_hit_up(cds, TR + 1, tp) assert hit == len(cds) - 1 and R >= 2.2 print(f"S4 picior1 {leg1:.2f} (vol {vols[L1]}) picior2 {leg2:.2f} (vol {vols[L2]}) neck {neck:.2f} " f"entry {entry:.2f} SL {sl:.2f} TP {tp:.2f} = {R:.2f}R atins la {hit}") ch = Chart(candles=cds, levels=[Level(leg1, f"picior 1 {leg1:.2f}", GRAY, label_at="left"), Level(sl, f"SL {sl:.2f}", RED), Level(entry, f"intrare {entry:.2f}", GREEN), Level(tp, f"TP {tp:.2f} = {R:.1f}R", BLUE)], marks=[Mark(L1, "picior 1", RED, "below", dy=8), Mark(L2, "picior 2: stop-uri luate", RED, "below", dy=2), Mark(TR, "trigger", GREEN, "above", dy=-30), Mark(17, f"neckline {neck:.2f}", ORANGE, "above")], scale=24, vol_h=70, title="Exemplu construit (fără ticker) — daily", caption="Piciorul 2 sparge 30.00 cu volum mai mic decât piciorul 1, apoi închide înapoi peste.", **{**STYLE, "width": 700}) svg_ = ch.render() assert abs(px_ratio(ch, sl, entry, tp) - R) < 0.01 svgs["db_mai_jos"] = svg_ S4 = dict(leg1=leg1, leg2=leg2, neck=neck, entry=entry, sl=sl, tp=tp, R=R, v1=vols[L1], v2=vols[L2]) # =============================================================== 5. double bottom NU: e bear flag cl = down + [30.6, # 12 picior 1 31.0, 31.4, 31.1, 31.7, 31.9, 31.6, 32.0, # 13-19 bear flag: urcare lentă, volum mic 31.2, 30.5, # 20-21 30.2, # 22 „picior 2", volum mare 30.9, # 23 cumpărătorul grăbit intră 30.5, 29.6, 28.8, 29.1, 28.0, 27.3, 27.6, 26.8] vols = [52, 50, 44, 55, 58, 46, 60, 62, 48, 64, 66, 68, 74, 40, 38, 36, 38, 34, 36, 38, 62, 66, 78, 42, 60, 84, 76, 50, 80, 72, 48, 70] cds = from_closes(cl, wicks=wk(len(cl), {12: (0.3, 0.6), 22: (0.3, 0.5)}), vols=vols, start=38.6) L1, L2, EN = 12, 22, 23 leg1, leg2 = cds[L1].l, cds[L2].l flag_up = sum(vols[13:20]) / 7 assert leg2 < leg1 and cds[EN].c > cds[L2].h assert vols[L2] > vols[L1], "pe NU vânzătorii vin cu volum mai mare la piciorul 2 (fără divergență)" assert flag_up < sum(vols[0:12]) / 12 * 0.75, "urcarea din bear flag trebuie să aibă volum mic" assert max(c.h for c in cds[13:20]) < 32.6, "bear flag-ul nu ajunge nici la jumătatea downtrend-ului" entry = cds[EN].c sl = round(leg2 - 0.1, 2) stop_bar = first_hit_down(cds, EN + 1, sl) assert cds[stop_bar].c < leg2 print(f"S5 picior1 {leg1:.2f} vol {vols[L1]}, picior2 {leg2:.2f} vol {vols[L2]}, flag high " f"{max(c.h for c in cds[13:20]):.2f}; intrare grăbită {entry:.2f} SL {sl:.2f} atins la {stop_bar} " f"close {cds[stop_bar].c:.2f}; final {cds[-1].c:.2f}") ch = Chart(candles=cds, levels=[Level(entry, f"intrare grăbită {entry:.2f}", GRAY, label_at="left"), Level(leg1, f"picior 1 {leg1:.2f}", ORANGE), Level(sl, "", RED)], marks=[Mark(16, "bear flag? volum mic", GRAY, "above", dy=-6), Mark(L2, "volum mai mare", RED, "below", dy=2), Mark(stop_bar, "SL atins", RED, "below", dy=36)], scale=24, vol_h=70, title="Exemplu construit (fără ticker) — daily", caption="Fără divergență de volum, „double bottom-ul în formare\" e doar un bear flag.", **STYLE) svgs["db_nu"] = add(ch.render(), [lbl_below(ch, sl, f"SL {sl:.2f}", RED, at="left")]) S5 = dict(leg1=leg1, leg2=leg2, entry=entry, sl=sl, v1=vols[L1], v2=vols[L2], stop_c=cds[stop_bar].c) # =============================================================== 6. double top, al doilea vârf mai sus + evening star (short) cl = [52.0, 52.8, 52.5, 53.6, 54.4, 54.1, 55.3, 56.2, 55.9, 57.2, 58.4, # 0-10 uptrend 59.6, # 11 vârf 1 59.0, 58.2, 57.5, 57.3, # 12-15 pullback, neckline 57.9, 58.6, # 16-17 59.8, # 18 evening star (a): cumpărători 59.7, # 19 evening star (b): shooting star, vârf 2 58.9, # 20 evening star (c): vânzători, intrare short 58.3, 58.6, 57.4, 56.5, 56.9, 55.8, 54.9, 55.3, 54.0, 53.4] vols = [48, 50, 42, 52, 54, 44, 56, 58, 46, 60, 62, 64, 50, 52, 54, 48, 44, 46, 50, 58, 70, 62, 44, 66, 80, 50, 70, 66, 48, 64, 60] cds = from_closes(cl, wicks=wk(len(cl), {11: (0.4, 0.3), 15: (0.3, 0.4), 18: (0.2, 0.2), 19: (0.9, 0.2), 20: (0.2, 0.3), 30: (0.3, 0.3)}), vols=vols, start=51.6) T1, T2, EV = 11, 19, 20 top1, top2 = cds[T1].h, cds[T2].h neck = min(c.l for c in cds[T1 + 1:T2]) assert is_sh(cds, T1) and is_sh(cds, T2) assert top2 > top1, "al doilea vârf trebuie să fie mai sus" a, b, c_ = cds[18], cds[19], cds[20] assert a.c > a.o and (a.c - a.o) >= 1.0, "(a) nu e lumânare mare a cumpărătorilor" assert abs(b.c - b.o) <= 0.15 and b.h - max(b.o, b.c) >= 5 * abs(b.c - b.o), "(b) nu e shooting star" assert c_.c < c_.o and c_.c < (a.o + a.c) / 2, "(c) nu închide sub jumătatea lui (a)" entry = c_.c sl = round(top2 + 0.1, 2) risk = sl - entry tp = round(neck - (top2 - neck), 2) R = (entry - tp) / risk brk = next(i for i in range(EV + 1, len(cds)) if cds[i].c < neck) hit = first_hit_down(cds, EV + 1, tp) assert hit == len(cds) - 1 and R >= 2.5 print(f"S6 vârf1 {top1:.2f} vârf2 {top2:.2f} neck {neck:.2f} short {entry:.2f} SL {sl:.2f} TP {tp:.2f} = {R:.2f}R; " f"neckline spart la {brk} close {cds[brk].c:.2f}; TP atins la {hit}") ch = Chart(candles=cds, levels=[Level(sl, f"SL {sl:.2f}", RED), Level(entry, f"intrare short {entry:.2f}", GREEN, label_at="left"), Level(neck, f"neckline {neck:.2f}", ORANGE), Level(tp, f"TP {tp:.2f} = {R:.1f}R", BLUE), Level(63.2, "", "none")], # invizibil: loc deasupra pentru etichetele vârfurilor marks=[Mark(T1, f"vârf 1: {top1:.2f}", INK, "above", dy=-22), Mark(T2, "evening star", RED, "above", dy=-16), Mark(T2, f"vârf 2: {top2:.2f}", RED, "above", dy=-30), ], scale=22, vol_h=70, title="Exemplu construit (fără ticker) — daily", caption="Oglinda double bottom-ului cu piciorul 2 mai jos: vârful 2 ia stop-urile short-urilor.", **{**STYLE, "width": 700}) svg_ = add(ch.render(), [callout(ch, brk, 55.0, "neckline spart", RED, anchor="end", below=True)]) assert abs(px_ratio(ch, tp, entry, sl) - 1 / R) < 0.01 svgs["double_top"] = svg_ S6 = dict(top1=top1, top2=top2, neck=neck, entry=entry, sl=sl, tp=tp, R=R) # =============================================================== 7. cup & handle weekly: DA PIV_M = 47.50 cup = [45.0, 45.9, 46.8, 47.9, 48.9, 49.8, # 0-5 urcare până la marginea stângă 49.0, 47.8, 46.5, 45.2, 44.2, 43.4, 42.9, # 6-12 coborâre rotunjită 42.8, # 13 doji în fundul ceștii 43.6, # 14 buy doji 44.1, 44.9, 45.8, 46.9, 47.9, 48.8, 49.5, # 15-21 partea dreaptă 49.9, # 22 marginea dreaptă 49.5, 49.0, 48.6, 48.9, 49.4] # 23-27 mânerul cup_w = {5: (0.5, 0.3), 12: (0.3, 0.3), 13: (0.5, 0.6), 22: (0.4, 0.3), 25: (0.3, 0.4)} cup_v = [40, 44, 42, 46, 48, 50, 52, 50, 48, 46, 44, 40, 36, 42, 58, 46, 48, 50, 52, 54, 52, 50, 48, 34, 32, 30, 32, 36] cl = cup + [51.0, # 28 breakout din mâner, volum țeavă 51.8, 51.4, 52.6, 53.5, 53.1, 54.4, 55.3, 55.0, 56.4, 57.6, 58.2] cds = from_closes(cl, wicks=wk(len(cl), cup_w | {28: (0.2, 0.3)}), vols=cup_v + [96, 66, 50, 64, 68, 52, 66, 70, 50, 66, 64, 62], start=44.4) RL, RR, DJ, BD, BO = 5, 22, 13, 14, 28 rimL, rimR = cds[RL].h, cds[RR].h cup_low = min(c.l for c in cds[RL:RR]) handle_top = max(c.h for c in cds[RR:BO]) handle_low = min(c.l for c in cds[RR + 1:BO]) depth = rimR - cup_low assert abs(rimL - rimR) <= 0.1, "marginile ceștii nu sunt la același nivel" assert handle_top == rimR assert doji(cds[DJ]) and cds[BD].c > cds[DJ].h and cup_low == cds[DJ].l, "buy doji-ul nu e din fundul ceștii" assert (rimR - handle_low) / depth <= 1 / 3, "mânerul coboară mai mult de 1/3 din ceașcă" assert handle_low > PIV_M, "mânerul trebuie să stea deasupra pivotului monthly" assert cds[BO].c > handle_top and cds[BO].vol == max(c.vol for c in cds), "breakout fără cel mai mare volum" entry = cds[BO].c sl = round(handle_low - 0.1, 2) risk = entry - sl tp = round(rimR + depth, 2) R = (tp - entry) / risk hit = first_hit_up(cds, BO + 1, tp) assert hit == len(cds) - 1 and R >= 2.4 print(f"S7 margini {rimL:.2f}/{rimR:.2f} fund {cup_low:.2f} adâncime {depth:.2f} mâner low {handle_low:.2f} " f"({(rimR - handle_low) / depth:.2f} din adâncime) entry {entry:.2f} SL {sl:.2f} TP {tp:.2f} = {R:.2f}R " f"atins la {hit} ({hit - BO} săptămâni)") ch = Chart(candles=cds, levels=[Level(PIV_M, "", ORANGE), Level(sl, f"SL {sl:.2f}", RED), Level(entry, f"intrare {entry:.2f}", GREEN), Level(tp, f"TP {tp:.2f} = {R:.1f}R", BLUE)], marks=[Mark(RL, "margine", INK, "above", dy=-12), Mark(RR, "margine", INK, "above", dy=-12), Mark(DJ, "ceașca", GRAY, "below", dy=4), Mark(BD, "buy doji", GREEN, "above", dy=-20), Mark(25, "mâner", ORANGE, "above", dy=-2), Mark(BO, "breakout", GREEN, "above", dy=-4)], scale=16, vol_h=70, title="Exemplu construit (fără ticker) — weekly", caption="Trigger = închiderea peste mâner, cu cel mai mare volum din serie; ținta = adâncimea ceștii.", **{**STYLE, "width": 760}) svg_ = add(ch.render(), [lbl_below(ch, PIV_M, f"pivot monthly {PIV_M:.2f}", ORANGE)]) assert abs(px_ratio(ch, sl, entry, tp) - R) < 0.01 svgs["cup_da"] = svg_ S7 = dict(rim=rimR, low=cup_low, depth=depth, hlow=handle_low, entry=entry, sl=sl, tp=tp, R=R, weeks=hit - BO) CUP = dict(entry=entry, sl=sl, tp=tp, handle_top=handle_top, handle_low=handle_low, closes=cl[RR:BO + 1]) # =============================================================== 8. cup & handle NU: breakout fără volum cl = cup + [50.6, # 28 breakout, volum sub media mânerului 50.1, 49.4, 48.7, 47.9, 47.3, 47.8, 46.9, 46.2, 46.6] vv = cup_v + [30, 34, 40, 46, 58, 60, 44, 56, 58, 42] cds = from_closes(cl, wicks=wk(len(cl), cup_w), vols=vv, start=44.4) assert cds[BO].c > handle_top assert vv[BO] < sum(vv[RR:BO]) / (BO - RR), "breakout-ul NU trebuie să aibă volum peste media mânerului" entry = cds[BO].c sl = round(handle_low - 0.1, 2) stop_bar = first_hit_down(cds, BO + 1, sl) back_in = next(i for i in range(BO + 1, len(cds)) if cds[i].c < handle_top) assert back_in == BO + 1 print(f"S8 breakout {entry:.2f} vol {vv[BO]} (media mânerului {sum(vv[RR:BO]) / (BO - RR):.0f}), înapoi sub " f"{handle_top:.2f} la {back_in}, SL {sl:.2f} atins la {stop_bar} ({stop_bar - BO} săpt.), final {cds[-1].c:.2f}") ch = Chart(candles=cds, levels=[Level(PIV_M, "", ORANGE), Level(sl, f"SL {sl:.2f}", RED), Level(entry, f"intrare {entry:.2f}", GREEN)], marks=[Mark(BD, "buy doji", GREEN, "above", dy=-20), Mark(25, "mâner", ORANGE, "above", dy=-2), Mark(BO, "breakout fără volum", RED, "above", dy=-4), Mark(stop_bar, "SL atins", RED, "below", dy=4)], scale=16, vol_h=70, title="Exemplu construit (fără ticker) — weekly", caption="Aceeași ceașcă. Breakout-ul are volum sub media mânerului: cade înapoi.", **{**STYLE, "width": 760}) svgs["cup_nu"] = add(ch.render(), [lbl_below(ch, PIV_M, f"pivot monthly {PIV_M:.2f}", ORANGE)]) S8 = dict(entry=entry, sl=sl, vol=vv[BO], vol_avg=sum(vv[RR:BO]) / (BO - RR), weeks=stop_bar - BO) # =============================================================== 9. fractalul: mânerul weekly e o ceașcă pe daily daily = [49.8, 49.6, 49.8, 49.6, 49.5, # săpt. 23 49.3, 49.4, 49.1, 49.2, 49.0, # săpt. 24 48.8, 48.9, 48.6, 48.7, 48.6, # săpt. 25 — fundul ceștii mici 48.8, 48.7, 48.9, 49.0, 48.9, # săpt. 26 49.1, 49.3, 49.5, 49.6, 49.4, # săpt. 27 — marginea dreaptă (23), mânerul mic începe 49.2, 49.5, 50.1, 50.4, 51.0] # săpt. 28 — mâner mic (25), breakout daily (27) cds = from_closes(daily, wicks=wk(len(daily), {12: (0.3, 0.4)}), start=CUP["closes"][0]) weekly_closes = CUP["closes"][1:] assert [round(daily[4 + 5 * k], 2) for k in range(6)] == weekly_closes, "close-urile de vineri ≠ close-urile weekly" assert abs(min(c.l for c in cds[:25]) - CUP["handle_low"]) < 1e-9, "fundul ceștii mici ≠ low-ul mânerului weekly" assert max(c.h for c in cds[:25]) <= CUP["handle_top"] + 1e-9 MR = 23 mini_rim = max(c.h for c in cds[18:25]) assert mini_rim == cds[MR].h mini_handle_low = min(c.l for c in cds[MR + 1:27]) trig = next(i for i in range(MR + 1, len(cds)) if cds[i].c > mini_rim) entry = cds[trig].c sl = round(mini_handle_low - 0.1, 2) assert trig == 27 and entry < CUP["handle_top"], "trigger-ul daily trebuie să vină sub nivelul de breakout weekly" assert sl > CUP["sl"] and entry < CUP["entry"] R_d = (CUP["tp"] - entry) / (entry - sl) R_w = (CUP["tp"] - CUP["entry"]) / (CUP["entry"] - CUP["sl"]) print(f"S9 margine mică {mini_rim:.2f}, mâner mic low {mini_handle_low:.2f}, trigger daily la {trig} close {entry:.2f} " f"SL {sl:.2f} risc {entry - sl:.2f}; aceeași țintă {CUP['tp']:.2f} = {R_d:.1f}R daily vs {R_w:.1f}R weekly") ch = Chart(candles=cds, levels=[Level(CUP["handle_top"], f"breakout weekly {CUP['handle_top']:.2f}", ORANGE, label_at="left"), Level(entry, "", GREEN), Level(sl, f"SL daily {sl:.2f}", RED)], marks=[Mark(12, "ceașca mică", GRAY, "below", dy=4), Mark(MR, "margine", INK, "above", dy=-26), Mark(25, "mâner mic", ORANGE, "below", dy=0)], scale=70, title="Exemplu construit (fără ticker) — daily, în mânerul weekly", caption="Cele 6 săptămâni ale mânerului, pe daily: trigger-ul vine cu 2 zile înainte de cel weekly.", **{**STYLE, "width": 760, "gap": 19}) svg_ = add(ch.render(), [callout(ch, trig, 48.3, "trigger daily", GREEN, below=True), lbl_below(ch, entry, f"intrare daily {entry:.2f}", GREEN)]) svgs["cup_fractal"] = svg_ S9 = dict(entry=entry, sl=sl, R_d=R_d, R_w=R_w, rim=mini_rim, hlow=mini_handle_low) # =============================================================== 10. descending wedge: DA pre = [55.0, 54.2, 54.5, 53.4, 52.6] # 0-4 downtrend wedge = [52.0, # 5 52.5, # 6 SH1 51.6, 50.6, 49.8, 49.1, # 7-10 (10 = SL1) 49.8, 50.4, 51.0, # 11-13 (13 = SH2) 50.2, 49.2, 48.4, # 14-16 (16 = SL2) 49.1, 49.9, # 17-18 (18 = SH3) 49.0, 48.3, 47.9, # 19-21 (21 = SL3) 48.3, 48.8, # 22-23 (23 = SH4) 48.0, 47.6, 47.3, # 24-26 (26 = SL4) 47.6] # 27 W_SH, W_SL = [6, 13, 18, 23], [10, 16, 21, 26] w_over = {i: (0.5, 0.3) for i in W_SH} | {i: (0.3, 0.5) for i in W_SL} | {27: (0.2, 0.3)} w_vol = [60, 58, 54, 62, 64, 56, 52, 58, 56, 54, 52, 44, 42, 40, 46, 44, 42, 36, 34, 38, 36, 34, 30, 30, 32, 30, 28, 30] BO_W = 28 def wedge_series(after, after_vols, mirror=False): cl_ = pre + wedge + after if mirror: cl_ = [round(100 - x, 2) for x in cl_] ov = w_over if not mirror else {i: (d, u) for i, (u, d) in w_over.items()} return from_closes(cl_, wicks=wk(len(cl_), ov), vols=w_vol + after_vols, start=(55.8 if not mirror else 100 - 55.8)) def wedge_lines(cds_, falling=True): """falling: vârfurile la W_SH, minimele la W_SL; la oglindă (rising) rolurile se inversează""" hs, ls = (W_SH, W_SL) if falling else (W_SL, W_SH) hi = (hs[0], cds_[hs[0]].h, hs[-1], cds_[hs[-1]].h) lo = (ls[0], cds_[ls[0]].l, ls[-1], cds_[ls[-1]].l) return hi, lo, hs, ls def check_wedge(cds_, falling=True): hi, lo, hs, ls = wedge_lines(cds_, falling) for i in range(W_SH[0], BO_W): assert cds_[i].h <= line_at(*hi, i) + 0.12, f"bara {i} străpunge linia de sus" assert cds_[i].l >= line_at(*lo, i) - 0.12, f"bara {i} străpunge linia de jos" for i in hs: assert is_sh(cds_, i), f"bara {i} nu e swing high" for i in ls: assert is_sl(cds_, i), f"bara {i} nu e swing low" s_hi = (hi[3] - hi[1]) / (hi[2] - hi[0]) s_lo = (lo[3] - lo[1]) / (lo[2] - lo[0]) if falling: assert s_hi < s_lo < 0, "nu e descending wedge (linii care coboară și converg)" else: assert 0 < s_hi < s_lo, "nu e ascending wedge (linii care urcă și converg)" for a_, b_ in zip(hs, hs[1:]): assert (cds_[b_].h < cds_[a_].h) == falling for a_, b_ in zip(ls, ls[1:]): assert (cds_[b_].l < cds_[a_].l) == falling return hi, lo after = [48.6, # 28 breakout peste linia de sus, volum țeavă 49.3, 49.0, 50.1, 50.8, 50.5, 51.6, 52.3, 52.1, 52.8] cds = wedge_series(after, [92, 70, 52, 66, 70, 50, 64, 62, 48, 60]) hi, lo = check_wedge(cds) upper_at_bo = line_at(*hi, BO_W) assert cds[BO_W].c > upper_at_bo and cds[BO_W - 1].c < line_at(*hi, BO_W - 1) assert cds[BO_W].vol == max(c.vol for c in cds[W_SH[0]:]) entry = cds[BO_W].c sl = round(cds[W_SL[-1]].l - 0.1, 2) tp = round(cds[W_SH[0]].h, 2) risk = entry - sl R = (tp - entry) / risk hit = first_hit_up(cds, BO_W + 1, tp) assert hit == len(cds) - 1 and R >= 1.8 print(f"S10 SH {[round(cds[i].h, 2) for i in W_SH]} SL {[round(cds[i].l, 2) for i in W_SL]} linia sus la " f"breakout {upper_at_bo:.2f}; entry {entry:.2f} SL {sl:.2f} TP {tp:.2f} = {R:.2f}R atins la {hit}") ch = Chart(candles=cds, levels=[Level(sl, f"SL {sl:.2f}", RED, label_at="left"), Level(entry, f"intrare {entry:.2f}", GREEN), Level(tp, f"TP {tp:.2f} = {R:.1f}R", BLUE)], marks=[Mark(14, "descending wedge", ORANGE, "below", dy=dy_line(cds[14], 30, line_at(*lo, 14), 22, "below"))], scale=30, vol_h=70, title="Exemplu construit (fără ticker) — daily", caption="Minime și maxime tot mai jos, dar tot mai apropiate: vânzătorii obosesc.", **{**STYLE, "width": 700}) svg_ = add(with_lines(ch, ch.render(), [(*hi, BO_W, ORANGE), (*lo, BO_W, ORANGE)]), [callout(ch, BO_W, 50.0, "breakout + volum", GREEN, anchor="end")]) assert abs(px_ratio(ch, sl, entry, tp) - R) < 0.01 svgs["wedge_desc_da"] = svg_ S10 = dict(entry=entry, sl=sl, tp=tp, R=R, line=upper_at_bo, vol=cds[BO_W].vol) # =============================================================== 11. descending wedge NU: breakout fals after = [48.3, # 28 închide peste linie, dar cu volum mic 47.7, 47.1, 46.5, 45.8, 46.2, 45.4] cds = wedge_series(after, [31, 40, 50, 62, 66, 48, 60]) hi, lo = check_wedge(cds) assert cds[BO_W].c > line_at(*hi, BO_W) assert cds[BO_W].vol < sum(w_vol[W_SH[0]:]) / len(w_vol[W_SH[0]:]), "breakout-ul fals are volum sub medie" entry = cds[BO_W].c sl = round(cds[W_SL[-1]].l - 0.1, 2) stop_bar = first_hit_down(cds, BO_W + 1, sl) print(f"S11 breakout fals {entry:.2f} vol {cds[BO_W].vol} (media wedge {sum(w_vol[W_SH[0]:]) / len(w_vol[W_SH[0]:]):.0f}); " f"SL {sl:.2f} atins la {stop_bar} ({stop_bar - BO_W} candele)") ch = Chart(candles=cds, levels=[Level(sl, f"SL {sl:.2f}", RED, label_at="left"), Level(entry, f"intrare {entry:.2f}", GREEN)], marks=[Mark(14, "descending wedge", ORANGE, "below", dy=dy_line(cds[14], 30, line_at(*lo, 14), 22, "below")), Mark(BO_W, "breakout fără volum", RED, "above", dy=-18), Mark(stop_bar, "SL atins", RED, "below", dy=20)], scale=30, vol_h=70, title="Exemplu construit (fără ticker) — daily", caption="Aceeași compresie. Close peste linie, dar cu volum sub media wedge-ului: fals.", **{**STYLE, "width": 700}) svgs["wedge_desc_nu"] = with_lines(ch, ch.render(), [(*hi, BO_W, ORANGE), (*lo, BO_W, ORANGE)]) S11 = dict(entry=entry, sl=sl, vol=cds[BO_W].vol, n=stop_bar - BO_W) # =============================================================== 12. ascending wedge la rezistența monthly (short) after = [48.6, 49.3, 49.0, 50.1, 50.8, 50.5, 51.6, 52.3, 52.1, 52.8] # oglinda breakout-ului din S10 cds = wedge_series(after, [92, 70, 52, 66, 70, 50, 64, 62, 48, 60], mirror=True) hi_, lo_ = check_wedge(cds, falling=False) lower_at_bo = line_at(*lo_, BO_W) assert cds[BO_W].c < lower_at_bo and cds[BO_W - 1].c > line_at(*lo_, BO_W - 1) RES_M = round(cds[W_SL[-1]].h, 2) assert all(c.h <= RES_M + 1e-9 for c in cds[:BO_W]), "rezistența monthly trebuie să fie maximul seriei" entry = cds[BO_W].c sl = round(cds[W_SL[-1]].h + 0.1, 2) tp = round(cds[W_SH[0]].l, 2) risk = sl - entry R = (entry - tp) / risk hit = first_hit_down(cds, BO_W + 1, tp) assert hit == len(cds) - 1 and R >= 1.8 print(f"S12 rezistență monthly {RES_M:.2f}; linia jos la breakdown {lower_at_bo:.2f}; short {entry:.2f} SL {sl:.2f} " f"TP {tp:.2f} = {R:.2f}R atins la {hit}") ch = Chart(candles=cds, levels=[Level(sl, f"SL {sl:.2f}", RED), Level(entry, f"intrare short {entry:.2f}", GREEN, label_at="left"), Level(tp, f"TP {tp:.2f} = {R:.1f}R", BLUE)], marks=[Mark(2, "uptrend", GRAY, "below", dy=8), Mark(14, "ascending wedge", ORANGE, "above", dy=dy_line(cds[14], 30, line_at(*hi_, 14), -8, "above")), Mark(W_SL[-1], "rezistență monthly", RED, "above", dy=-4)], scale=30, vol_h=70, title="Exemplu construit (fără ticker) — daily", caption="Oglinda: vârfuri tot mai sus, dar tot mai mici, lipite de rezistența monthly.", **{**STYLE, "width": 760}) svg_ = add(with_lines(ch, ch.render(), [(*hi_, BO_W, ORANGE), (*lo_, BO_W, ORANGE)]), [callout(ch, BO_W, 49.7, "breakdown + volum", RED, anchor="end", below=True)]) assert abs(px_ratio(ch, tp, entry, sl) - 1 / R) < 0.01 svgs["wedge_asc"] = svg_ S12 = dict(entry=entry, sl=sl, tp=tp, R=R, res=RES_M) # =============================================================== 13. ascending triangle, breakout, retest + bull harami TOP = 50.00 cl = [44.0, 44.9, 45.5, 46.4, 47.3, 48.4, 49.1, # 0-6 49.7, # 7 atinge 50.00 49.0, 48.1, 47.1, # 8-10 (10 = low 1) 47.8, 48.6, 49.3, 49.7, # 11-14 (14 atinge 50.00) 49.1, 48.4, 48.0, # 15-17 (17 = low 2) 48.7, 49.3, 49.7, # 18-20 (20 atinge 50.00) 49.2, 48.8, # 21-22 (22 = low 3) 49.3, 49.6, # 23-24 50.9, # 25 breakout 51.4, 50.9, # 26-27 50.3, # 28 retest: low exact pe 50.00 50.5, # 29 bull harami 51.1, # 30 trigger 51.7, 51.4, 52.3, 52.9, 52.6, 53.2] vols = [40, 44, 42, 46, 48, 52, 50, 54, 44, 42, 46, 40, 38, 42, 44, 36, 34, 38, 36, 34, 38, 30, 32, 30, 34, 88, 60, 44, 40, 38, 62, 56, 42, 58, 60, 44, 56] cds = from_closes(cl, wicks=wk(len(cl), {10: (0.3, 0.5), 17: (0.3, 0.4), 22: (0.3, 0.3)}), vols=vols, start=43.6) T_LO = [10, 17, 22] BO_T, RT, HAR, TR = 25, 28, 29, 30 assert all(c.h <= TOP + 1e-9 for c in cds[5:BO_T]), "o bară din triunghi trece peste 50.00" touch = [i for i in range(5, BO_T) if abs(cds[i].h - TOP) < 1e-9] assert sum(1 for i in touch if i - 1 not in touch) >= 3, "rezistența nu e atinsă de 3 ori separat" t_lows = [cds[i].l for i in T_LO] assert all(b > a for a, b in zip(t_lows, t_lows[1:])), "minimele triunghiului nu urcă" lo_t = (T_LO[0], t_lows[0], T_LO[1], t_lows[1]) for i in range(T_LO[0], BO_T): assert cds[i].l >= line_at(*lo_t, i) - 0.12, f"bara {i} străpunge linia ascendentă" assert cds[BO_T].c > TOP and cds[BO_T].vol == max(vols) assert abs(cds[RT].l - TOP) < 1e-9 and cds[RT].c > TOP, "retestul nu atinge 50.00 cu codița" har, mom = cds[HAR], cds[RT] assert mom.c < mom.o and har.c > har.o and min(har.o, har.c) >= mom.c and max(har.o, har.c) <= mom.o, "nu e bull harami" assert cds[TR].c > har.h entry = cds[TR].c sl = round(TOP - 0.2, 2) height = TOP - t_lows[0] tp = round(TOP + height, 2) risk = entry - sl R = (tp - entry) / risk hit = first_hit_up(cds, TR + 1, tp) assert hit == len(cds) - 1 and R >= 1.75 print(f"S13 low-uri {[round(x, 2) for x in t_lows]} breakout close {cds[BO_T].c:.2f} retest low {cds[RT].l:.2f} " f"harami {har.o:.2f}->{har.c:.2f} entry {entry:.2f} SL {sl:.2f} înălțime {height:.2f} TP {tp:.2f} = {R:.2f}R") ch = Chart(candles=cds, levels=[Level(TOP, f"rezistență = pivot quarterly {TOP:.2f}", ORANGE), Level(sl, "", RED), Level(entry, f"intrare {entry:.2f}", GREEN), Level(tp, f"TP {tp:.2f} = {R:.1f}R", BLUE)], marks=[Mark(17, "ascending triangle", ORANGE, "below", dy=dy_line(cds[17], 30, line_at(*lo_t, 17), 16, "below")), Mark(BO_T, "breakout", GREEN, "above", dy=-4), Mark(RT, "retest", ORANGE, "below", dy=4), Mark(HAR, "harami", GREEN, "below", dy=22)], scale=30, vol_h=70, title="Exemplu construit (fără ticker) — daily", caption="Breakout, cădere înapoi pe fosta rezistență, bull harami acolo: intrare la trigger.", **{**STYLE, "width": 760}) svg_ = add(with_lines(ch, ch.render(), [(*lo_t, BO_T, ORANGE)]), [lbl_below(ch, sl, f"SL {sl:.2f}", RED)]) assert abs(px_ratio(ch, sl, entry, tp) - R) < 0.01 svgs["triunghi_asc"] = svg_ S13 = dict(lows=t_lows, bo=cds[BO_T].c, entry=entry, sl=sl, tp=tp, R=R, height=height) # =============================================================== 14. island top, apoi cumpărarea în gap-ul închis PIV_Q = 44.50 segA = from_closes([40.0, 40.8, 41.5, 41.2, 42.1, 42.9, 42.6, 43.5, 44.0, 44.2], wick=0.3, start=39.6) segB = from_closes([45.6, 46.4, 46.1, 47.0, 47.8, 47.5, 48.4, 49.2, 49.0, 49.6], wick=0.3, start=45.2) segC = from_closes([51.6, 51.9, 51.5, 51.2], wick=0.3, start=51.0) segD = from_closes([49.4, 49.7, 48.8, 48.1, 48.4, 47.5, 46.8, 47.1, 46.2, 45.5, 45.1, 45.0, # hammer: închide gap-ul de jos 45.9, # trigger 46.6, 46.3, 47.4, 48.3, 48.0, 49.2, 50.0], wicks=wk(20, {11: (0.2, 0.6), 12: (0.2, 0.3)}), start=50.0) cds = segA + segB + segC + segD vols = [40, 44, 38, 46, 48, 42, 50, 52, 50, 60, 72, 56, 44, 58, 60, 46, 62, 64, 50, 66, 88, 70, 60, 64, 90, 56, 70, 64, 48, 66, 68, 50, 64, 62, 50, 70, 80, 58, 44, 60, 64, 46, 60, 58] assert len(vols) == len(cds) for c, v in zip(cds, vols): c.vol = v iA, iB, iC, iD = 0, len(segA), len(segA) + len(segB), len(segA) + len(segB) + len(segC) gap1 = (cds[iB - 1].h, cds[iB].l) gap2 = (cds[iC - 1].h, cds[iC].l) isl_low = min(c.l for c in cds[iC:iD]) gap3 = (cds[iD].h, isl_low) assert gap1[1] > gap1[0] and gap2[1] > gap2[0] and gap3[1] > gap3[0], "lipsesc gap-urile" assert all(c.l > gap2[0] for c in cds[iC:iD]) and all(c.h < isl_low for c in cds[iD:]), "insula nu e izolată" assert max(c.h for c in cds[iC:iD]) == max(c.h for c in cds), "insula nu e vârful" HAM, TRI = iD + 11, iD + 12 fill = next(i for i in range(iD, len(cds)) if cds[i].l <= gap1[0]) assert fill == HAM, "gap-ul de jos trebuie închis chiar de hammer" hm = cds[HAM] assert min(hm.o, hm.c) - hm.l >= 4 * abs(hm.o - hm.c), "bara de închidere nu e hammer" assert abs(gap1[0] - PIV_Q) < 1e-9, "marginea de jos a gap-ului vechi = pivotul quarterly" assert cds[TRI].c > hm.h entry = cds[TRI].c sl = round(hm.l - 0.2, 2) tp = round(gap3[0], 2) risk = entry - sl R = (tp - entry) / risk hit = first_hit_up(cds, TRI + 1, tp) assert hit == len(cds) - 1 and R >= 2.5 print(f"S14 gap jos {gap1[0]:.2f}-{gap1[1]:.2f}; gap sus {gap2[0]:.2f}-{gap2[1]:.2f}; insula {isl_low:.2f}-" f"{max(c.h for c in cds[iC:iD]):.2f}; gap down {gap3[0]:.2f}-{gap3[1]:.2f}; hammer low {hm.l:.2f} la {HAM}; " f"entry {entry:.2f} SL {sl:.2f} TP {tp:.2f} = {R:.2f}R atins la {hit}") ch = Chart(candles=cds, levels=[Level(tp, f"TP {tp:.2f} = {R:.1f}R (gap-ul insulei)", BLUE, label_at="left"), Level(entry, f"intrare {entry:.2f}", GREEN, label_at="left"), Level(PIV_Q, f"pivot quarterly {PIV_Q:.2f}", ORANGE, label_at="left"), Level(sl, "", RED)], marks=[Mark(iC + 1, "island top", RED, "above", dy=-4), Mark(iD, "gap down", RED, "below", dy=4), Mark(HAM, "hammer: gap închis", GREEN, "below", dy=4)], scale=22, vol_h=70, title="Exemplu construit (fără ticker) — daily", caption="Insula e bearish. Cumperi abia jos, unde gap-ul vechi s-a închis în pivotul quarterly.", **{**STYLE, "width": 720}) svg_ = add(ch.render(), [lbl_below(ch, sl, f"SL {sl:.2f}", RED), callout(ch, iB, 47.5, "gap up", GRAY, anchor="end")]) assert abs(px_ratio(ch, sl, entry, tp) - R) < 0.01 svgs["island_top"] = svg_ S14 = dict(gap1=gap1, gap2=gap2, gap3=gap3, isl_low=isl_low, isl_high=max(c.h for c in cds[iC:iD]), ham_l=hm.l, entry=entry, sl=sl, tp=tp, R=R) # =============================================================== 15. triple doji la bottom cl = [44.0, 43.1, 43.5, 42.2, 41.0, 41.4, 39.9, 38.6, 36.9, # 8 low anterior 37.8, 38.6, 39.0, # 9-11 (11 = lower high) 38.3, 37.4, 37.8, 36.8, 35.9, 35.2, 34.6, 34.3, # 12-19 sparge low-ul anterior 34.2, 34.3, 34.2, # 20-22 triple doji 35.3, # 23 trigger 35.9, 35.6, 36.7, 37.5, 37.2, 38.4, 39.2] vols = [50, 54, 42, 58, 60, 44, 62, 64, 70, 46, 42, 40, 52, 56, 44, 56, 52, 48, 46, 42, 36, 34, 32, 84, 66, 40, 70, 72, 42, 76, 80] cds = from_closes(cl, wicks=wk(len(cl), {8: (0.3, 0.6), 11: (0.4, 0.3), 20: (0.5, 0.5), 21: (0.5, 0.5), 22: (0.5, 0.5), 23: (0.2, 0.3)}), vols=vols, start=44.6) D3 = [20, 21, 22] prior_low = cds[8].l assert is_sl(cds, 8) and is_sh(cds, 11) assert all(doji(cds[i]) for i in D3), "cele trei bare nu sunt doji" assert min(c.l for c in cds[12:20]) < prior_low, "low-ul anterior nu e spart înainte de doji" assert max(vols[15:20]) < vols[8], "sub low-ul anterior volumul trebuie să fie mai mic (divergență bull de volum)" assert vols[20] > vols[21] > vols[22] and vols[20] < min(vols[15:20]), "pe doji vânzarea trebuie să se epuizeze" up = [vols[i] for i in range(23, len(cl)) if cl[i] > cl[i - 1]] dn = [vols[i] for i in range(23, len(cl)) if cl[i] < cl[i - 1]] assert vols[23] == max(vols) and min(up) > max(dn), "din oversold, volumul trebuie să fie pe barele verzi (acumulare)" assert max(abs(cds[i].c - cds[20].c) for i in D3) <= 0.15, "doji-urile nu stau la același nivel" d_hi, d_lo = max(cds[i].h for i in D3), min(cds[i].l for i in D3) TR = 23 assert cds[TR].c > d_hi and all(cds[i].c <= d_hi for i in D3) entry = cds[TR].c sl = round(d_lo - 0.1, 2) tp = round(cds[11].h, 2) risk = entry - sl R = (tp - entry) / risk hit = first_hit_up(cds, TR + 1, tp) assert hit == len(cds) - 1 and R >= 2 print(f"S15 low anterior {prior_low:.2f}; doji high {d_hi:.2f} low {d_lo:.2f}; entry {entry:.2f} SL {sl:.2f} " f"TP (lower high) {tp:.2f} = {R:.2f}R atins la {hit} ({hit - TR} săpt.)") ch = Chart(candles=cds, levels=[Level(prior_low, f"low anterior {prior_low:.2f}", GRAY, label_at="left"), Level(sl, f"SL {sl:.2f}", RED), Level(entry, f"intrare {entry:.2f}", GREEN), Level(tp, f"TP {tp:.2f} = {R:.1f}R", BLUE)], marks=[Mark(11, "lower high", INK, "above", dy=-2), Mark(21, "triple doji", GREEN, "below", dy=6), Mark(TR, "trigger", GREEN, "above", dy=-28)], scale=26, vol_h=70, title="Exemplu construit (fără ticker) — weekly", caption="Sub low volumul scade, pe doji se stinge; trigger-ul și urcarea vin cu volum: acumulare.", **{**STYLE, "width": 700}) svg_ = ch.render() assert abs(px_ratio(ch, sl, entry, tp) - R) < 0.01 svgs["triple_doji"] = svg_ S15 = dict(prior=prior_low, hi=d_hi, lo=d_lo, entry=entry, sl=sl, tp=tp, R=R, weeks=hit - TR) # =============================================================== 16. inverted head & shoulders cl = [47.0, 46.2, 46.6, 45.4, 44.5, 44.8, 43.6, # 0-6 downtrend 42.6, # 7 umăr stâng 43.1, 43.6, 43.9, # 8-10 neckline 1 43.4, 42.2, 41.0, # 11-13 39.8, # 14 cap 40.8, 41.9, 43.0, 43.9, # 15-18 neckline 2 43.4, 43.0, 42.9, # 19-21 umăr drept 43.6, 43.9, # 22-23 44.8, # 24 breakout 45.4, 45.1, 46.2, 47.0, 46.6, 47.8, 48.5, 48.2, 49.0] vols = [52, 56, 44, 58, 60, 46, 62, 66, 48, 44, 46, 50, 58, 64, 72, 54, 52, 50, 48, 38, 36, 40, 44, 46, 90, 66, 48, 64, 66, 50, 62, 64, 50, 60] cds = from_closes(cl, wicks=wk(len(cl), {7: (0.3, 0.5), 14: (0.3, 0.6), 21: (0.3, 0.4), 24: (0.2, 0.3)}), vols=vols, start=47.6) LS, HD, RS, BO_I = 7, 14, 21, 24 neck = max(c.h for c in cds[LS:RS]) assert is_sl(cds, LS) and is_sl(cds, HD) and is_sl(cds, RS) assert cds[HD].l < cds[LS].l and cds[HD].l < cds[RS].l, "capul nu e cel mai jos" assert cds[RS].l > cds[LS].l, "textul spune: umărul drept mai sus" assert all(c.h <= neck + 1e-9 for c in cds[LS:BO_I]) and cds[BO_I].c > neck assert vols[RS] < vols[LS] and vols[BO_I] == max(vols), "volum: umăr drept mic, breakout maxim" entry = cds[BO_I].c sl = round(cds[RS].l - 0.1, 2) height = neck - cds[HD].l tp = round(neck + height, 2) risk = entry - sl R = (tp - entry) / risk hit = first_hit_up(cds, BO_I + 1, tp) assert hit == len(cds) - 1 and R >= 1.7 print(f"S16 umăr stâng {cds[LS].l:.2f} cap {cds[HD].l:.2f} umăr drept {cds[RS].l:.2f} neck {neck:.2f} " f"entry {entry:.2f} SL {sl:.2f} înălțime {height:.2f} TP {tp:.2f} = {R:.2f}R atins la {hit}") ch = Chart(candles=cds, levels=[Level(neck, "", ORANGE), Level(sl, f"SL {sl:.2f}", RED), Level(entry, f"intrare {entry:.2f}", GREEN), Level(tp, f"TP {tp:.2f} = {R:.1f}R", BLUE)], marks=[Mark(LS, "umăr stâng", INK, "below", dy=4), Mark(HD, "cap", INK, "below", dy=4), Mark(RS, "umăr drept (mai sus)", GREEN, "below", dy=4), Mark(BO_I, "breakout", GREEN, "above", dy=-4)], scale=24, vol_h=70, title="Exemplu construit (fără ticker) — daily", caption="Umărul drept mai sus decât cel stâng: aceeași formă ca o ceașcă cu mâner.", **{**STYLE, "width": 680}) svg_ = add(ch.render(), [lbl_below(ch, neck, f"neckline {neck:.2f}", ORANGE)]) assert abs(px_ratio(ch, sl, entry, tp) - R) < 0.01 svgs["ihs"] = svg_ S16 = dict(ls=cds[LS].l, hd=cds[HD].l, rs=cds[RS].l, neck=neck, entry=entry, sl=sl, tp=tp, R=R) # =============================================================== 17. bull flag înapoi în locul crimei cl = [45.0, 46.1, 46.9, 47.8, 48.6, 49.2, 49.7, # 6 vârful anterior (locul crimei) 49.0, 48.3, 48.8, 48.2, 49.1, # 7-11 consolidare 50.6, 51.8, 52.9, 53.6, 54.5, # 12-16 impuls 55.0, # 17 vârful impulsului 54.4, 53.6, 53.9, 52.8, 52.0, 52.4, 51.4, # 18-24 steagul coboară 50.8, # 25 picior 1 51.5, 51.2, # 26-27 50.4, # 28 picior 2: mai jos, pe locul crimei 51.7, # 29 trigger 52.4, 52.1, 53.3, 54.2, 53.9, 55.0] vols = [44, 50, 48, 54, 56, 52, 60, 46, 42, 40, 38, 48, 84, 76, 70, 66, 62, 60, 46, 42, 38, 40, 36, 34, 36, 40, 30, 32, 42, 72, 60, 44, 62, 66, 48, 62] cds = from_closes(cl, wicks=wk(len(cl), {6: (0.3, 0.3), 17: (0.3, 0.3), 25: (0.3, 0.4), 28: (0.3, 0.4), 29: (0.2, 0.3)}), vols=vols, start=44.6) PH, TOPF, L1, L2, TR = 6, 17, 25, 28, 29 crime = cds[PH].h assert crime == max(c.h for c in cds[:12]), "vârful anterior nu e maximul dinainte de impuls" assert cds[12].c > crime, "impulsul nu sparge vârful anterior" assert cds[TOPF].h == max(c.h for c in cds[:TR]) flag_vol = sum(vols[18:25]) / 7 assert flag_vol < sum(vols[12:18]) / 6 * 0.65, "volumul pe steag trebuie să scadă clar față de impuls" assert is_sl(cds, L1) and is_sl(cds, L2) and cds[L2].l < cds[L1].l, "piciorul 2 trebuie să fie mai jos" assert abs(cds[L2].l - crime) < 1e-9, "piciorul 2 trebuie să atingă exact locul crimei cu codița" assert cds[TR].c > cds[L2].h entry = cds[TR].c sl = round(cds[L2].l - 0.2, 2) tp = round(cds[TOPF].h, 2) risk = entry - sl R = (tp - entry) / risk hit = first_hit_up(cds, TR + 1, tp) assert hit == len(cds) - 1 and R >= 1.8 print(f"S17 locul crimei {crime:.2f}; vârf impuls {cds[TOPF].h:.2f}; picior1 {cds[L1].l:.2f} picior2 {cds[L2].l:.2f}; " f"vol impuls {sum(vols[12:18]) / 6:.0f} vs steag {flag_vol:.0f}; entry {entry:.2f} SL {sl:.2f} TP {tp:.2f} = {R:.2f}R") ch = Chart(candles=cds, levels=[Level(crime, f"locul crimei {crime:.2f}", ORANGE, label_at="left"), Level(sl, "", RED), Level(entry, f"intrare {entry:.2f}", GREEN), Level(tp, f"TP {tp:.2f} = {R:.1f}R", BLUE)], marks=[Mark(PH, "vârf anterior", INK, "above", dy=-16), Mark(L1, "picior 1", RED, "below", dy=8), Mark(L2, "picior 2", RED, "below", dy=12)], mas=[MA(20, GRAY, dash="4 3")], scale=26, vol_h=70, title="Exemplu construit (fără ticker) — daily", caption="Linia punctată = SMA-20. Double bottom DUPĂ un impuls, nu după downtrend: e steag.", **{**STYLE, "width": 700}) svg_ = ch.render() svg_ = add(svg_, [lbl_below(ch, sl, f"SL {sl:.2f}", RED), f'bull flag']) assert abs(px_ratio(ch, sl, entry, tp) - R) < 0.01 svgs["bull_flag"] = svg_ S17 = dict(crime=crime, top=cds[TOPF].h, l1=cds[L1].l, l2=cds[L2].l, entry=entry, sl=sl, tp=tp, R=R) print(f"{len(svgs)} diagrame") # ---------------------------------------------------------------- substituție în .md if MD.exists(): md = MD.read_text(encoding="utf-8") for name, svg in svgs.items(): pat = re.compile(rf".*?", re.S) assert pat.search(md), f"marcatorul {name} lipsește din .md" md = pat.sub(lambda _m, s=svg, n=name: f"\n{s}\n", md) MD.write_text(md, encoding="utf-8") print("scris", MD)