"""ATMI — aceeași corecție: 4.0 % (DA) vs. 3.9 % (NU; cursul zice „3.93 %"). Citește specificația `gen/strategii/atmi_aplus.spec.txt`. O singură serie de 32 de candele daily (open-ul fiecărei bare = close-ul precedentei, deci seria e continuă), care urmează calea DA: urcare la swing high 100.00, corecție exact 4.0 % până la 96.00 (BIM + candelă verde = intrare), apoi urcare la TP +10 % = 105.60. Peste ea, un traseu punctat (calea NU) care se oprește la 96.10 = 3.9 % și apoi coboară ilustrativ — fără intrare, pentru că nu atinge pragul de 4 %. Traseele punctate / benzile se inserează în SVG-ul scos de `svg_candles` folosind coordonatele lui (`ch._cx`, `ch._y`), la fel ca în `az_structura.py` — candelele rămân desenate de `svg_candles`, nu de mână. """ import sys sys.path.insert(0, "/workspace/atm-curs-dl") from svg_candles import Chart, Level, Mark, from_closes # noqa: E402 GRAY, RED, GREEN, BLUE, ORANGE, INK = "#777", "#c00", "#188a3e", "#1a5fb4", "#b06000", "#333" # --- seria: 32 de close-uri, structura din specificație ---------------------- # 0-11 urcare 90.00 -> 100.00 (swing high la bara 11) # 12-17 corecție (cădere) # 18 BIM (hammer de reversie, codiță lungă în jos) # 19 prima candelă verde daily => intrare, close 96.00 # 20-31 urcare 96.00 -> 105.60 (TP +10 %) closes = [ 90.00, 90.90, 91.70, 92.40, 93.20, 94.00, 94.70, 95.40, 96.10, 97.00, 98.30, 100.00, # 0-11 99.30, 98.50, 97.70, 97.10, 96.60, 96.20, # 12-17 95.98, # 18 BIM 96.00, # 19 intrare (verde) 97.00, 97.90, 98.80, 99.70, 100.50, 101.30, 102.10, 102.90, 103.70, 104.50, 105.10, 105.60, # 20-31 ] assert len(closes) == 32 # wick-uri (sus, jos): implicite (0.25, 0.30), cu excepțiile care fixează nivelurile wicks = [(0.25, 0.30)] * 32 wicks[11] = (0.00, 0.30) # swing high: high = close = 100.00 wicks[18] = (0.20, 0.45) # BIM: codiță lungă în jos (hammer) wicks[19] = (0.20, 0.15) # candela de intrare wicks[31] = (0.00, 0.30) # TP: high = close = 105.60 # volum relativ ales de generator (materialul nu dă volum pe exemplu): # crește pe corecție (vânzătorii), vârf pe candela de intrare, scade pe urcare. vols = [48, 50, 46, 52, 54, 50, 56, 52, 58, 60, 64, 72, 70, 74, 78, 82, 86, 80, 84, 88, 72, 66, 62, 58, 54, 50, 46, 62, 68, 72, 78, 86] assert len(vols) == 32 cds = from_closes(closes, wicks=wicks, vols=vols, start=89.40) # --- indicii-cheie și nivelurile --------------------------------------------- SWING_I = 11 # swing high BIM_I = 18 # primul BIM ENTRY_I = 19 # candela verde de intrare SWING = 100.00 PRAG4 = 96.00 # prag corecție 4.0 % => DA NU_STOP = 96.10 # cazul real 3.9 % => NU TP = 105.60 # +10 % față de 96.00 entry = cds[ENTRY_I].c # --- cele 8 assert-uri cerute de specificație -------------------------------- # 1. pragul de 4 % corespunde lui 96.00 assert abs((SWING - PRAG4) / SWING * 100 - 4.00) < 0.05, "96.00 nu e 4.0 %" # 2. cazul NU: 96.10 = 3.9 % (graficul cursului scrie 3.9 %; textul „3.93 %") assert abs((SWING - NU_STOP) / SWING * 100 - 3.90) < 0.02, "96.10 nu e 3.9 %" # 3. calea DA coboară mai jos decât calea NU assert PRAG4 < NU_STOP, "pragul DA nu e sub oprirea NU" # 4. intrarea = close-ul candelei verzi = 96.00 assert abs(entry - 96.00) < 1e-9, f"intrarea {entry} != 96.00" assert cds[ENTRY_I].c >= cds[ENTRY_I].o, "candela de intrare nu e verde" # 5. TP = +10 % assert abs(TP / entry - 1.10) < 0.005, f"TP/entry = {TP / entry:.4f}, nu 1.10" # 6. BIM înainte de candela de intrare assert BIM_I < ENTRY_I, "BIM nu e înainte de candela de intrare" # 7. traseul NU nu atinge pragul de trigger: se oprește la 96.10 > 96.00, deci # rămâne fără intrare; pe traseul punctat nu există nicio candelă / BIM nu_route = [(SWING_I, 100.00), (12, 99.40), (13, 98.65), (14, 97.95), (15, 97.35), (16, 96.80), (17, NU_STOP)] assert min(p for _, p in nu_route) == NU_STOP, "oprirea traseului NU nu e 96.10" assert min(p for _, p in nu_route) > PRAG4, "traseul NU atinge pragul de 4 %" assert not any(i >= BIM_I for i, _ in nu_route), "traseul NU ajunge la candela BIM" # 8. ambele trasee pleacă din același swing high 100.00 assert abs(cds[SWING_I].h - SWING) < 1e-9, f"swing high {cds[SWING_I].h} != 100.00" assert abs(nu_route[0][1] - cds[SWING_I].h) < 1e-9, "traseul NU nu pleacă din swing high" # verificări de context cerute de STYLE.md (structura susține eticheta) assert abs(TP - max(c.h for c in cds[-10:])) < 1e-9, "TP nu e maximul de la final" assert min(closes[12:20]) < 96.10 and min(closes[12:20]) >= 95.90, \ "corecția DA nu coboară în jurul lui 96.00" assert all(b > a for a, b in zip(closes[20:31], closes[21:32])), "urcarea spre TP nu e monotonă" print(f"swing {SWING:.2f} prag DA {PRAG4:.2f} (4.0 %) stop NU {NU_STOP:.2f} (3.9 %) " f"intrare {entry:.2f} TP {TP:.2f} (+{TP / entry - 1:.1%}) BIM bara {BIM_I}") # --- desenul ----------------------------------------------------------------- cds[ENTRY_I].note = ("Regula e pe procent, nu pe cât de dramatică arată linia: " "calea NU pare o cădere mare, dar e 3.9 %.") # traseul NU: decizia (până la 96.10) + continuarea ilustrativă în jos nu_cont = [(17, NU_STOP), (18, 95.70), (19, 95.35), (21, 94.95), (23, 94.65), (25, 94.45)] nu_pts = nu_route + nu_cont[1:] ch = Chart( candles=cds, levels=[ Level(SWING, "100.00 — swing high", INK), Level(PRAG4, "96.00 = 4.0 % — DA, intri", GREEN), Level(TP, "TP +10 % = 105.60", BLUE), ], marks=[ Mark(BIM_I, "BIM", GREEN, "above", dy=-2), Mark(ENTRY_I, "candelă verde → trigger", GREEN, "below", dy=20), ], scale=18.0, gap=16, body_w=9, left=44, width=680, vol_h=70, vol_label="volum", title="SPY — daily: aceeași corecție, 4.0 % (DA) vs. 3.9 % (NU)", caption="Calea DA (candele): 4.0 % la 96.00. Calea NU (punctat): 3.9 % („3.93 %” în curs) — fără intrare.", ) svg = ch.render() # --- elemente suplimentare, pe coordonatele graficului ----------------------- x0, x1 = 24, ch.width - 14 band_top, band_bot = ch._y(96.40), ch._y(95.40) band = (f'') nu_line = ('') # linia pragului NU (96.10) se desenează manual, ca Level-ul gol să nu lase text vid nu_hline = (f'') # punctul de oprire al traseului NU (96.10, strict deasupra pragului de 4 %) stop_dot = (f'') # eticheta traseului NU, sub linia 96.10 (nu se suprapune cu eticheta verde de 96.00) nu_lab = (f'' f'96.10 = 3.9 % — NU, sub prag') # eticheta zonei quarterly, sub banda (fără cifre: cursul nu le dă) zone_lab = (f'' f'zonă quarterly') # legende scurte ale celor două trasee da_lab = (f'calea DA (candelele)') nu_leg = (f'calea NU (continuă în jos)') overlay = [nu_line, nu_hline, stop_dot, nu_lab, zone_lab, da_lab, nu_leg] svg = svg.replace(">", ">\n " + band, 1) # banda, sub candele svg = svg.replace("\n", "\n " + "\n ".join(overlay) + "\n") # traseele, peste assert "\n\n" not in svg, "linie goală în SVG" assert "" in svg and "stroke-dasharray=\"5 4\"" in svg, "traseul NU nu a fost inserat" with open("/workspace/atm-curs-dl/gen/strategii/atmi_aplus.svg", "w") as f: f.write(svg) print("scris gen/strategii/atmi_aplus.svg")