"""Diagrama de ansamblu MDSRT long (strategii, marcajul `ansamblu`). „SPY — 5 min: al doilea picior mai jos la 589.80, sub suportul 590.00 → divergență bull (M) preț-vs-volum → candela-trigger Buy Hammer/Doji → intrare 590.20 → SL 589.40 (risc 0.80) → TP1 591.00 (1:1) → poziția rămasă pe trail, ieșire la close 592.40 (≈ 2.75R)." Toate close-urile, wick-urile speciale și nivelurile vin din `gen/strategii/mdsrt_ansamblu.spec.txt`. Structura M→A→P, suportul 590.00, minimul 589.80 și riscul 0.80 sunt din exemplul cursului (`05_MODUL_4/02_strategia_5_mdsrt.md:159-177, 246, 308-311`). Intrare/SL/TP sunt exemplul construit al autorului sumarului, marcat explicit ca NU citat din curs (`...:166-177, 439-445`); transcrierea se întrerupe înainte de planul numeric. Volumele sunt relative, alese de generator (materialul nu dă volum pentru acest exemplu). Rulează: python3 gen/strategii/mdsrt_ansamblu.py Scrie lângă el `mdsrt_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, MA, from_closes # noqa: E402 OUT = Path(__file__).resolve().parent / "mdsrt_ansamblu.svg" GRAY, RED, GREEN, BLUE, ORANGE = "#777", "#c00", "#188a3e", "#1a5fb4", "#b06000" # ---------------------------------------------------------------- seria (30 bare) CLOSES = [ 596.00, 595.20, 595.60, 594.80, 594.10, 593.40, 593.70, 592.90, # 0-7 592.20, 591.60, 591.90, 591.30, 590.80, 590.30, 590.55, 590.10, # 8-15 589.85, 590.05, # 16-17 589.80, # 18 = al doilea picior mai jos + candela-trigger (hammer) 590.20, # 19 = intrare (close = entry; sparge high-ul trigger-ului) 590.60, 590.90, 591.00, # 20-22 (22 = TP1 1:1) 591.30, 591.10, 591.45, 591.70, 591.95, 592.15, # 23-28 592.40, # 29 = ieșire pe trail (close) ] assert len(CLOSES) == 30, len(CLOSES) # fitile (sus, jos): (0.25, 0.30) peste tot, cu excepțiile din spec. WICKS = [(0.25, 0.30)] * 30 WICKS[18] = (0.10, 0.40) # hammer: high 590.15, low 589.40 (= SL) WICKS[19] = (0.10, 0.05) # intrare: high 590.30, low 589.75 # bara 21 coborâtă sub TP1 (high 590.95 < 591.00), ca TP1 să fie atins exact # prima dată la bara 22 (altfel assert-ul 5 ar pica pe „primul high"). WICKS[21] = (0.05, 0.30) # volum relativ (ales de generator): 16-19 crescător, 18-19 peste medie, # vol[18] > vol[10] (higher low pe volum la lower low pe preț). VOLS = [ 48, 46, 44, 45, 43, 41, 40, 42, 44, 46, # 0-9 52, 38, 40, 42, 39, 44, # 10-15 50, 58, 72, 85, # 16-19 absorbție la minim 70, 64, 58, 60, 55, 52, 50, 48, 46, 44, # 20-29 ] assert len(VOLS) == 30, len(VOLS) cds = from_closes(CLOSES, wicks=WICKS, vols=VOLS, start=596.60) # ---------------------------------------------------------------- assert-urile din spec entry = cds[19].c sl = cds[18].l tp1 = cds[22].c # 1. minimul și SL-ul pe bara 18 assert cds[18].c == 589.80, cds[18].c assert cds[18].l == 589.40, cds[18].l # 2. al doilea picior rupe suportul/pivotul 590.00 assert cds[18].l < 590.00, cds[18].l # 3. intrarea = close-ul barei 19 și sparge high-ul trigger-ului assert cds[19].c == 590.20, cds[19].c assert cds[19].c == entry assert cds[19].c > cds[18].h, (cds[19].c, cds[18].h) # 4. TP1 exact 1:1 assert abs((entry - sl) - (tp1 - entry)) < 1e-9, (entry - sl, tp1 - entry) assert abs((entry - sl) - 0.80) < 1e-9 # 5. TP1 atins la bara 22 (și nu mai devreme) assert cds[22].c == 591.00, cds[22].c assert cds[22].c == tp1 assert max(c.h for c in cds[20:22]) < tp1, [c.h for c in cds[20:22]] # 6. ieșirea pe trail e pe profit assert cds[29].c == 592.40, cds[29].c assert cds[29].c > entry, (cds[29].c, entry) # 7. hammer: corp mic, coadă lungă în jos body = abs(cds[18].o - cds[18].c) rng = cds[18].h - cds[18].l tail = min(cds[18].o, cds[18].c) - cds[18].l assert body == 0.25, body assert body <= 0.5 * rng, (body, rng) assert tail > body, (tail, body) # 8. downtrend vizibil assert cds[13].c < cds[0].c - 5, (cds[13].c, cds[0].c) # 9. divergență pe volum: lower low pe preț, higher low pe volum assert cds[18].l < cds[10].l, (cds[18].l, cds[10].l) assert VOLS[18] > VOLS[10], (VOLS[18], VOLS[10]) # barele 16-19 au volum crescător; 18-19 peste media seriei assert VOLS[16] < VOLS[17] < VOLS[18] < VOLS[19], VOLS[16:20] avg_vol = sum(VOLS) / len(VOLS) assert VOLS[18] > avg_vol and VOLS[19] > avg_vol, (VOLS[18], VOLS[19], avg_vol) # 10. fiecare open == close-ul barei anterioare for a, b in zip(cds, cds[1:]): assert a.c == b.o, (a.c, b.o) r_mult = (cds[29].c - entry) / (entry - sl) assert abs(r_mult - 2.75) < 1e-9, r_mult # ---------------------------------------------------------------- niveluri, marks levels = [ Level(590.00, "suport / pivot 590.00", BLUE, label_at="left"), Level(sl, f"SL {sl:.2f} — risc 0.80", RED, label_at="left"), # stânga: altfel eticheta din dreapta se calcă pe textul „SMA-20" al mediei Level(tp1, f"TP1 {tp1:.2f} = 1:1", GREEN, label_at="left"), ] marks = [ Mark(18, f"minim 589.80 — divergență (M)", RED, "below"), # ridicat deasupra vârfurilor candelelor de urcare (altfel textul cade peste # fitilele barelor 20-22) — bara 19 e totuși exact sub el Mark(19, f"intrare {entry:.2f}", GREEN, "above", dy=-46), Mark(29, f"ieșire trail {cds[29].c:.2f}", GREEN, "above"), ] # titlul e injectat pe două linii: textul din spec (82 caractere) nu încape # pe un singur rând în 640 px fără să fie tăiat la margine TITLE = ["SPY — 5 min (exemplu construit pe scara lecției;", "cifrele planului nu vin din curs)"] ch = Chart( candles=cds, levels=levels, marks=marks, mas=[MA(20, ORANGE, dash="4 3", text="SMA-20")], scale=48.0, gap=15, body_w=9, left=44, width=640, top=64, vol_h=70, vol_label="volum", title="", caption="", ) svg = ch.render() title_svg = "\n ".join( f'{t}' for i, t in enumerate(TITLE)) i = svg.index(">") + 1 svg = svg[:i] + "\n " + title_svg + svg[i:] # ---------------------------------------------------------------- legenda (onestitate) m = re.search(r'viewBox="0 0 (\d+) ([0-9.]+)"', svg) W, H0 = int(m.group(1)), float(m.group(2)) legend = [ "Legendă: entry/SL/TP/trail sunt exemplul construit al autorului sumarului,", "nu citate din curs (transcrierea se întrerupe înainte de planul numeric).", ] legend_svg = [f'{t}' for i, t in enumerate(legend)] new_h = H0 + 16 + 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(legend_svg) + "\n") assert svg.count("\n\n") == 0, "linie goală în SVG rupe blocul din markdown" assert f"TP1 {tp1:.2f}" in svg and f"intrare {entry:.2f}" in svg OUT.write_text(svg) print(f"scris {OUT.name}: {len(cds)} candele, suport 590.00, minim {cds[18].l:.2f}, " f"intrare {entry:.2f}, SL {sl:.2f} (risc 0.80), TP1 {tp1:.2f} (1:1), " f"ieșire {cds[29].c:.2f} = {r_mult:.2f}R, vol[18]={VOLS[18]} > vol[10]={VOLS[10]}")