"""Diagrama de ansamblu MBP / MBPA++ (strategii, marcajul `ansamblu`). „SPY — 15 min: bandă + TICUS pe aceeași candelă → trigger → intrare → SL → TP1 → trail". Graficul întreg al trade-ului long din exemplul cursului, cu panoul TICUS dedesubt, legat de candela M printr-o linie punctată verticală (aceeași candelă sus, aceeași valoare jos). Toate cifrele desenate vin din curs (`summaries/05_MODUL_4/03_strategia_6_mbp_mbappe.md`): - M: close 623.87 < banda inferioară 623.90, TICUS -1150 (liniile 336-342); - bandă superioară 624.70 și linia 624.30 (SMA20 / TP1) — diagrama principală (liniile 114-148); - trigger Buy Momentum → entry 623.95 (liniile 336-342); - SL 623.60 = lowest low din ultimele 4 candele, trigger inclus (93-94); - TP1 624.30 (+0.35, 1:1) (95-96); - „realizat ~1:4 datorită pozițiilor pe trail" (428-429). Prețul de ieșire ~625.35 = entry + 4R este ilustrativ, derivat de mine (nu e preț din curs). Structura barelor dintre niveluri e construită de mine; o construiesc astfel încât fiecare nivel afirmat în text să iasă exact din serie (assert-uri mai jos). Nu există nivel de suport major cu cifră în exemplul din curs. Rulează: python3 gen/strategii/mbp_ansamblu.py Scrie lângă el `mbp_ansamblu.svg`. """ import re import sys from pathlib import Path sys.path.insert(0, "/workspace/atm-curs-dl") from svg_candles import Chart, Level, Mark, from_closes # noqa: E402 OUT = Path(__file__).resolve().parent / "mbp_ansamblu.svg" GRAY, RED, GREEN, BLUE, ORANGE = "#777", "#c00", "#188a3e", "#1a5fb4", "#b06000" # ---------------------------------------------------------------- seria (30 bare) # 14 bare de downtrend 625.60 -> 624.15 (open-ul fiecărei bare = close-ul # precedentei), apoi M (index 14), trigger (15), 14 bare de urcare spre 625.35. DOWN = [625.60, 625.42, 625.55, 625.28, 625.05, 625.12, 624.88, 624.65, 624.72, 624.50, 624.35, 624.30, 624.10, 624.15] # 0-13 M_IX, TR_IX = 14, 15 UP = [624.00, 624.08, 624.16, 624.30, 624.40, 624.50, 624.60, 624.70, 624.82, 624.95, 625.08, 625.20, 625.28, 625.35] # 16-29 CLOSES = DOWN + [623.87, 623.95] + UP # M, trigger assert len(CLOSES) == 30, len(CLOSES) # fitile (sus, jos) per bară. Ultimele 3 bare înainte de M au minimele # 623.95 / 623.80 / 623.72 (peste 623.60), ca SL-ul calculat să fie exact # 623.60 (low-ul lui M). WICKS = ([(0.05, 0.18)] + [(0.18, 0.18)] * 10 + [(0.18, 0.35), # 11: low 623.95 (0.18, 0.30), # 12: low 623.80 (open 624.30, close 624.10) (0.10, 0.38)] # 13: low 623.72 (open 624.10, close 624.15) + [(0.05, 0.27), # 14 M: high 624.20, low 623.60 (0.05, 0.12)] # 15 trigger: high 624.00, low 623.75 + [(0.05, 0.12)] * 3 # 16-18 + [(0.00, 0.12)] # 19: high atinge exact 624.30 (TP1) + [(0.05, 0.12)] * 9 + [(0.00, 0.12)]) # 29: high = close = 625.35 (ieșirea ilustrativă) assert len(WICKS) == len(CLOSES), len(WICKS) # TICUS: un singur bar relevant, pe candela M, la -1150 (sub -1000) TICUS = {M_IX: -1150.0} # fără panou de volum: panoul de dedesubt e cel de TICUS cds = from_closes(CLOSES, wicks=WICKS, start=625.75) # ---------------------------------------------------------------- assert-urile din spec entry = cds[TR_IX].c assert abs(entry - 623.95) < 0.01, entry assert entry == cds[TR_IX].c # intrarea = close-ul trigger-ului assert cds[M_IX].c < 623.90 # M închide sub banda inferioară assert cds[M_IX].c < cds[M_IX].o # M e roșie (close < open) ticus_m = TICUS[M_IX] assert ticus_m <= -1000, ticus_m # =-1150, pragul depășit assert abs(cds[M_IX].c - 623.87) < 0.005 assert abs(cds[M_IX].h - 624.20) < 0.005 assert abs(cds[TR_IX].h - 624.00) < 0.005 assert abs(cds[TR_IX].l - 623.75) < 0.005 # SL = cel mai jos minim din ultimele 4 candele încheiate de trigger, inclusiv window = cds[TR_IX - 3:TR_IX + 1] assert len(window) == 4 sl = min(c.l for c in window) assert abs(sl - 623.60) < 0.01, sl assert sl == cds[M_IX].l # low-ul lui M e minimul ferestrei assert cds[TR_IX].l > sl # trigger-ul nu face low nou # cele trei candele dinaintea lui M au minime peste 623.60 (ex. 623.95/623.80/623.72) for cd in cds[TR_IX - 4:TR_IX - 1]: assert cd.l > sl, (cd.o, cd.c, cd.l) risc = entry - sl assert abs(risc - 0.35) < 0.005, risc tp1 = entry + risc assert abs(tp1 - (entry + (entry - sl))) < 0.005 assert abs(tp1 - 624.30) < 0.005, tp1 # TP1 nu se atinge prea devreme, ci 4 bare după intrare (cum spune spec-ul) assert max(c.h for c in cds[TR_IX + 1:TR_IX + 4]) < tp1 - 1e-9 assert abs(cds[TR_IX + 4].h - tp1) < 1e-9 exit_ill = cds[-1].c # ~625.35, ilustrativ (derivat) assert abs(exit_ill - 625.35) < 0.01, exit_ill assert abs((exit_ill - entry) / risc - 4.0) < 0.15, (exit_ill - entry) / risc # nu există nicio linie de stop desenată deasupra intrării assert sl < entry levels = [ Level(623.90, "623.90 — bandă inferioară", BLUE, dash="4 3", label_at="left"), Level(624.70, "624.70 — bandă superioară", BLUE, dash="4 3", label_at="right"), Level(624.30, "624.30 — SMA20 / TP1", GRAY, dash="4 3", label_at="left"), Level(sl, f"SL {sl:.2f} (lowest low, 4 candele)", RED, label_at="left"), Level(entry, f"trigger Buy Momentum → intrare {entry:.2f}", GREEN, label_at="right"), Level(625.35, "ieșire ilustrativă ~625.35 (≈ 1:4)", GREEN, dash="2 3", label_at="right"), ] assert all("stop" not in lv.text.lower() or lv.price < entry for lv in levels) assert all(not ("sl" in lv.text.lower() and lv.price > entry) for lv in levels) marks = [ Mark(M_IX, "M: roșie, close 623.87 sub bandă", RED, "below"), ] # titlul si eticheta de trend sunt injectate manual (nu prin Chart.title, care # ar sta lipit de prima candelă, si nici prin Chart.trend, care s-ar bate cu # titlul) ca sa rămână spațiu liber deasupra seriei. ch = Chart(candles=cds, levels=levels, marks=marks, scale=150.0, gap=16, body_w=10, left=48, width=700, top=96) svg = ch.render() TITLE = "SPY — 15 min: bandă + TICUS pe aceeași candelă → trigger → intrare → SL → TP1 → trail" # ---------------------------------------------------------------- panoul TICUS # `Chart` nu are panou de indicator; îl adaug după randare, cu aceeași scară # pe orizontală (ch._cx), ca barul TICUS al lui M să cadă exact sub candela M. m = re.search(r'viewBox="0 0 (\d+) ([0-9.]+)"', svg) W, H0 = int(m.group(1)), float(m.group(2)) ticus_top = H0 + 34 y0 = ticus_top + 70 # linia 0 px1000 = 50.0 # 1000 unități TICUS = 50 px def ty(v: float) -> float: return y0 - v / 1000.0 * px1000 xm = ch._cx(M_IX) bar_top, bar_bot = y0, ty(ticus_m) # bar de la 0 în jos la -1150 panel = [ f'TICUS — presiunea pieței (↑ = peste 0)', # linii de prag f'', f'+1000 — supracumpărare', f'', f'+500 (doar referință vizuală)', f'', f'0', f'', f'-500 (doar referință vizuală)', f'', f'-1000 — oversold', # barul TICUS al candelei M, sub -1000 (colorat roșu) f'', f'M: TICUS -1150 (sub -1000)', ] # linia punctată verticală: aceeași candelă sus și jos (pornește sub mark-ul # lui M, ca să nu taie textul „M: roșie...") y_candle_m = ch._y(cds[M_IX].l) + 26 panel.insert(0, f'') legend_top = ty(-1000) + 44 legend = [ "SPY 15 min. Cifre din curs: M close 623.87 (sub bandă 623.90), TICUS -1150 pe aceeași candelă,", "trigger Buy Momentum → entry 623.95, SL 623.60 (lowest low din 4 candele, trigger inclus),", "TP1 624.30 (+0.35, 1:1). Ieșirea ~625.35 = entry + 4R (≈1:4) e ilustrativă — preț derivat din R,", "nu din curs. Structura candelelor (30 bare continue) e construită de mine.", ] legend_svg = [f'{t}' for i, t in enumerate(legend)] header = [ f'{TITLE}', 'trend descendent anterior (14 bare)', ] new_h = legend_top + 17 * (len(legend) - 1) + 14 svg = re.sub(r'(viewBox="0 0 \d+ )[0-9.]+(")', lambda mm: mm.group(1) + f"{new_h:.0f}" + mm.group(2), svg, count=1) svg = svg.replace("\n", "\n " + "\n ".join(header + panel + legend_svg) + "\n") assert svg.count("\n\n") == 0, "linie goală în SVG rupe blocul din markdown" # barul TICUS cade exact sub candela M (aceeași candelă sus și jos) bar_svg = f'' assert bar_svg in svg, "barul TICUS nu a fost desenat" assert abs((float(re.search(r'x="([\d.]+)"', bar_svg).group(1)) + 5) - ch._cx(M_IX)) < 1e-9 # aceeași scară px/unitate: SL-intrare și intrare-TP1 sunt egale și vizual (1:1) assert abs((ch._y(sl) - ch._y(entry)) - (ch._y(entry) - ch._y(tp1))) < 1e-9 assert f'x1="{xm:.1f}"' in svg OUT.write_text(svg) print(f"scris {OUT.name}: {len(cds)} candele, entry {entry:.2f}, SL {sl:.2f}, " f"TP1 {tp1:.2f}, ieșire ilustrativă {exit_ill:.2f} ({(exit_ill-entry)/risc:.2f}R), " f"ticus(M)={ticus_m:.0f}, x(M)={xm:.0f}")