diff --git a/gen/strategii/hammer_mbt10_bh_forma.py b/gen/strategii/hammer_mbt10_bh_forma.py new file mode 100644 index 0000000..4f76ab5 --- /dev/null +++ b/gen/strategii/hammer_mbt10_bh_forma.py @@ -0,0 +1,154 @@ +#!/usr/bin/env python3 +"""Ce e un hammer și ce nu — patru candele cu același high 63.30 și low 62.10. + +Prima imagine a capitolului: cine o vede nu știe ce e un hammer, deci candela +A primește săgeți pe corp și pe fiecare fitil, iar C și D sunt contraexemple +cu aceeași înălțime. Zonă verde peste A și B, roșie peste C și D. + +Desenat direct, nu cu `Chart`: fiecare etichetă trebuie să stea lipită sub +candela ei, pe 2-3 rânduri, plus săgeți cu text pe lateral. + +Rulare: python3 gen/strategii/hammer_mbt10_bh_forma.py +""" +from pathlib import Path + +OUT = Path(__file__).with_suffix(".svg") +GREEN, RED, GREY, BLUE = "#188a3e", "#c00", "#555", "#1a5fb4" + +H, L = 63.30, 62.10 +RNG = H - L # 1.20 +PRAG_SUS = L + 2 / 3 * RNG # 62.90 — corpul trebuie să stea peste el + +# (nume, o, h, l, c, verdict, rânduri de etichetă sub candelă) +CANDLES = [ + ("A", 63.05, H, L, 63.30, "DA", ["DA: corp 0.25,", "fitil jos 0.95 = 3,8x corpul"]), + ("B", 62.90, H, L, 63.30, "DA", ["DA, la limită: corp 0.40,", "fitil jos 0.80 = exact 2x"]), + ("C", 62.60, H, L, 63.30, "NU", ["NU: corp 0.70,", "fitil jos 0.50 = 0,7x corpul"]), + ("D", 62.25, H, L, 62.45, "NU", ["NU: corpul e jos, fitilul", "lung e sus — alt pattern"]), +] + + +def masuri(o, h, l, c): + return abs(c - o), min(o, c) - l, h - max(o, c) # corp, fitil jos, fitil sus + + +# --- verificările ----------------------------------------------------------- +M = {n: masuri(o, h, l, c) for n, o, h, l, c, *_ in CANDLES} +for n, o, h, l, c, *_ in CANDLES: + assert (h, l) == (H, L), (n, h, l) # 1 aceeași bară +assert [tuple(round(v, 2) for v in M[n]) for n in "ABCD"] == [ # 2 cifrele din etichete + (0.25, 0.95, 0.00), (0.40, 0.80, 0.00), (0.70, 0.50, 0.00), (0.20, 0.15, 0.85)], M +for n in "AB": # 3 A și B trec ambele reguli + corp, jos, _ = M[n] + o, c = [(o, c) for nn, o, _, _, c, *_ in CANDLES if nn == n][0] + assert jos >= 2 * corp - 1e-9, (n, jos, corp) + assert min(o, c) >= PRAG_SUS - 1e-9, (n, min(o, c), PRAG_SUS) +assert M["C"][1] < 2 * M["C"][0], "C ar trebui să pice pe regula fitilului" # 4 +oD, cD = CANDLES[3][1], CANDLES[3][4] +assert max(oD, cD) < PRAG_SUS, "D ar trebui să pice pe regula treimii de sus" # 5 +assert round(M["A"][1] / M["A"][0], 1) == 3.8 and round(M["B"][1] / M["B"][0], 1) == 2.0 +assert round(M["C"][1] / M["C"][0], 1) == 0.7 + +# --- geometrie -------------------------------------------------------------- +SCALE, TOP, W = 260, 210, 1000 +XS = [350, 490, 690, 860] +BODY_W = 26 + + +def y(p): + return TOP + (H - p) * SCALE + + +assert y(CANDLES[0][1]) > y(CANDLES[0][4]), "open A trebuie sub close A pe ecran" + +out = [] +t = out.append + + +def text(x, yy, s, color=GREY, anchor="start", bold=False, size=12): + style = f' style="font-weight:bold;font-size:{size}px"' if bold else ( + f' style="font-size:{size}px"' if size != 12 else "") + t(f'{s}') + + +def zone(x0, x1, y0, y1, label, color): + t(f'') + text(x0 + 6, y0 - 7, label, color, bold=True) + + +def arrow(x1, y1, x2, y2, color, lines, anchor="start"): + d = max(((x2 - x1) ** 2 + (y2 - y1) ** 2) ** 0.5, 1e-9) + ux, uy = (x2 - x1) / d, (y2 - y1) / d + bx, by = x2 - 8 * ux, y2 - 8 * uy + t(f'') + t(f'') + dx = -6 if anchor == "end" else (0 if anchor == "middle" else 6) + y0 = (y1 - 10 - 14 * (len(lines) - 1) if anchor == "middle" + else y1 + 4 - 14 * (len(lines) - 1) / 2) + for k, s in enumerate(lines): + text(x1 + dx, y0 + 14 * k, s, color, anchor, bold=(k == 0)) + + +text(24, 30, "Ce e un hammer și ce nu — aceeași bară, 63.30 sus și 62.10 jos", "#333", + bold=True, size=14) + +label_y0 = y(L) + 28 +z_bot = label_y0 + 16 * 2 - 4 +zone(XS[0] - 58, XS[1] + 66, y(H) - 30, z_bot, "hammer", GREEN) +zone(XS[2] - 60, XS[3] + 74, y(H) - 30, z_bot, "nu e hammer", RED) + +for p in (H, L): + t(f'') +text(24, y(H) - 6, "cât de sus a urcat prețul: 63.30") +text(24, y(L) + 14, "cât de jos a coborât: 62.10") +# pragul treimii de sus: linia pe care corpul trebuie s-o depășească +t(f'') +text(24, y(PRAG_SUS) - 4, f"treimea de sus începe la {PRAG_SUS:.2f}", BLUE) + +for (n, o, h, l, c, verdict, lines), x in zip(CANDLES, XS): + color = GREEN if c >= o else RED + vcol = GREEN if verdict == "DA" else RED + t(f'') + top, bot = y(max(o, c)), y(min(o, c)) + t(f'') + for k, s in enumerate(lines): + text(x, label_y0 + 16 * k, s, vcol, "middle", bold=(k == 0)) + +xA = XS[0] +arrow(xA - 60, y(63.10), xA - 15, y(63.17), BLUE, anchor="end", + lines=["corpul: mic și sus în bară", + "ziua s-a închis aproape de maxim —", + "cumpărătorii au ținut până la final"]) +arrow(xA - 60, y(62.55), xA - 3, y(62.60), BLUE, anchor="end", + lines=["fitilul de jos, lung", + "vânzătorii au dus prețul până la 62.10,", + "dar cumpărătorii l-au adus înapoi sus"]) +text(xA + 20, y(63.05) + 12, "open 63.05") +text(xA + 20, y(63.30) - 4, "close = high 63.30") + +xC, xD = XS[2], XS[3] +arrow(xC + 34, y(H) - 112, xC + 14, y(62.95), RED, + lines=["corp prea gros", "fitilul de jos nu e nici cât corpul —", "nimeni n-a fost respins jos"]) +arrow(xD, y(H) - 34, xD, y(63.24), RED, anchor="middle", + lines=["fitilul lung e sus, nu jos", "aici cumpărătorii au fost respinși;", + "e inverted hammer, alt pattern"]) + +cap_y = z_bot + 30 +text(24, cap_y, "Hammer = fitilul de jos e cel puțin dublul corpului ȘI corpul stă în treimea " + "de sus a barei (aici peste 62.90).") +text(24, cap_y + 16, "Pragul „fitil ≥ 2x corpul\" e al cursului; „corpul în treimea de sus\" e " + "traducerea mea a lui „corp mic sus, fitil de sus aproape inexistent\".", GREY) +height = cap_y + 40 + +svg = (f'\n ' + + "\n ".join(out) + "\n\n") +assert "\n\n" not in svg +OUT.write_text(svg, encoding="utf-8") +print(f"scris {OUT.name} ({W}x{height:.0f})") diff --git a/gen/strategii/hammer_mbt10_bh_forma.svg b/gen/strategii/hammer_mbt10_bh_forma.svg new file mode 100644 index 0000000..4ea4eed --- /dev/null +++ b/gen/strategii/hammer_mbt10_bh_forma.svg @@ -0,0 +1,53 @@ + + Ce e un hammer și ce nu — aceeași bară, 63.30 sus și 62.10 jos + + hammer + + nu e hammer + + + cât de sus a urcat prețul: 63.30 + cât de jos a coborât: 62.10 + + treimea de sus începe la 62.90 + + + DA: corp 0.25, + fitil jos 0.95 = 3,8x corpul + + + DA, la limită: corp 0.40, + fitil jos 0.80 = exact 2x + + + NU: corp 0.70, + fitil jos 0.50 = 0,7x corpul + + + NU: corpul e jos, fitilul + lung e sus — alt pattern + + + corpul: mic și sus în bară + ziua s-a închis aproape de maxim — + cumpărătorii au ținut până la final + + + fitilul de jos, lung + vânzătorii au dus prețul până la 62.10, + dar cumpărătorii l-au adus înapoi sus + open 63.05 + close = high 63.30 + + + corp prea gros + fitilul de jos nu e nici cât corpul — + nimeni n-a fost respins jos + + + fitilul lung e sus, nu jos + aici cumpărătorii au fost respinși; + e inverted hammer, alt pattern + Hammer = fitilul de jos e cel puțin dublul corpului ȘI corpul stă în treimea de sus a barei (aici peste 62.90). + Pragul „fitil ≥ 2x corpul" e al cursului; „corpul în treimea de sus" e traducerea mea a lui „corp mic sus, fitil de sus aproape inexistent". + diff --git a/gen/strategii/hammer_mbt10_bh_iesire.py b/gen/strategii/hammer_mbt10_bh_iesire.py new file mode 100644 index 0000000..081cbe2 --- /dev/null +++ b/gen/strategii/hammer_mbt10_bh_iesire.py @@ -0,0 +1,135 @@ +#!/usr/bin/env python3 +"""Buy hammer — ieșirea: jumătate la 1:1, restul pe trail sub swing low-uri. + +Pornește din aceeași serie ca diagrama de ansamblu (barele 0-33, importate) +și îi adaugă o continuare construită (barele 34-40), pentru că seria de +ansamblu se oprește înainte ca trail-ul să apuce să urce de două ori. + +Regula de trail desenată: stopul se mută sub ultimul swing low — cel mai jos +low al unui pullback, confirmat de două bare care nu-l rup. Ieși la prima +închidere sub linie. + +Rulare: python3 gen/strategii/hammer_mbt10_bh_iesire.py +""" +import re +import sys; sys.path.insert(0, "/workspace/atm-curs-dl") +sys.path.insert(0, "/workspace/atm-curs-dl/gen/strategii") +from pathlib import Path +from hammer_mbt10_bh_ansamblu import serie +from svg_candles import Arrow, Candle, Chart, Level, Zone + +OUT = Path(__file__).with_suffix(".svg") +GREEN, RED, BLUE, ORANGE, GREY = "#188a3e", "#c00", "#1a5fb4", "#b06000", "#555" + +INTRARE, SL, R = 63.35, 62.05, 1.30 +TP1 = round(INTRARE + R, 2) # 64.65 — ținta 1:1 a strategiei MBP +TP2R = round(INTRARE + 2 * R, 2) # 65.95 — ținta din exemplul lecției + +cds_all, hammer, conf = serie() +conf.note = "" # nota e a diagramei de ansamblu; aici ar sta peste legendă +# continuarea construită: un pullback, o urcare, al doilea pullback, ieșirea +CONT = [Candle(*b) for b in [ + (65.60, 66.10, 65.45, 65.95), # 34 + (65.95, 66.80, 65.85, 66.60), # 35 + (66.60, 66.90, 66.20, 66.35), # 36 + (66.35, 66.55, 66.00, 66.45), # 37 -> swing low 66.00 + (66.45, 67.30, 66.35, 67.10), # 38 + (67.10, 67.60, 66.90, 67.30), # 39 + (67.30, 67.40, 65.80, 65.90), # 40 -> close sub trail: ieși +]] +full = cds_all + CONT +assert abs(CONT[0].o - cds_all[-1].c) < 1e-9, "continuarea nu pornește din close-ul barei 33" +for i, c in enumerate(full): + assert c.l <= min(c.o, c.c) and c.h >= max(c.o, c.c), f"bara {i}: h/l nu cuprind o/c" + + +def swing_lows(cds, start): + """Cel mai jos low al unui pullback, confirmat de două bare care nu-l rup.""" + out = [] + for i in range(start, len(cds) - 2): + if (cds[i].l < cds[i - 1].l and cds[i + 1].l >= cds[i].l and cds[i + 2].l >= cds[i].l): + out.append((i, round(cds[i].l, 2))) + return out + + +CONF_I = 20 +sw = swing_lows(full, CONF_I + 1) +trepte, iesire = [], None +t = None +for i in range(CONF_I + 1, len(full)): + if t is not None and full[i].c < t: + iesire = i + break + nou = [p for j, p in sw if j + 2 <= i] + if nou and (t is None or nou[-1] > t): + t = nou[-1] + trepte.append((i, t)) + +# --- verificări ------------------------------------------------------------- +assert round(full[CONF_I].c, 2) == INTRARE, "intrarea nu e close-ul barei 20" +prim_tp1 = next(i for i in range(CONF_I + 1, len(full)) if full[i].h >= TP1) +prim_2r = next(i for i in range(CONF_I + 1, len(full)) if full[i].h >= TP2R) +assert (prim_tp1, prim_2r) == (23, 28), (prim_tp1, prim_2r) +assert round(TP1 - INTRARE, 2) == R and round(TP2R - INTRARE, 2) == 2 * R +assert trepte == [(23, 63.05), (34, 65.10), (39, 66.00)], trepte +assert all(b[1] > a[1] for a, b in zip(trepte, trepte[1:])), "trail-ul trebuie doar să urce" +assert iesire == 40 and abs(full[40].c - 65.90) < 1e-9, iesire +assert trepte[0][1] > SL, "prima treaptă nu îmbunătățește stopul inițial" +assert trepte[-1][1] > INTRARE, "ultima treaptă de trail e sub intrare: n-ar proteja profitul" +R_IES = round((full[iesire].c - INTRARE) / R, 2) +assert R_IES == 1.96, R_IES +assert min(c.l for c in full[CONF_I:]) > SL, "stopul inițial n-ar fi fost atins în fereastră" + +I0 = 16 +w = full[I0:] +sh = lambda i: i - I0 # noqa: E731 index în fereastră +ch = Chart( + candles=w, + levels=[Level(TP2R, "65.95 = 2R — ținta din exemplul lecției", BLUE), + Level(TP1, "64.65 = 1:1 — prima jumătate", GREEN), + Level(INTRARE, "intrare 63.35", GREY, label_at="left"), + Level(SL, "SL inițial 62.05", RED, label_at="left")], + zones=[Zone(sh(30), sh(34), "pullback", ORANGE), Zone(sh(36), sh(38), "", ORANGE)], + arrows=[ + Arrow(sh(23), "prima jumătate iese la 64.65:\nexact cât ai riscat (1R)", + price=TP1, where="above", color=GREEN, dx=-150, dy=-40), + Arrow(sh(40), "a doua jumătate iese la 65.90 (+1,96R):\nprima închidere sub linia de trail", + where="below", color=RED, dx=-250, dy=54), + ], + scale=34.0, gap=16, body_w=9, left=48, width=700, vol_h=0, top=58, + title="Buy hammer — ieșirea (daily, ticker ilustrativ XYZ; barele de după 33 sunt construite)", +) +svg = ch.render() +m = re.search(r'viewBox="0 0 (\d+) (\d+)"', svg) +W, H = int(m.group(1)), int(m.group(2)) +OFF = (float(re.search(r'linia de trail') +extra_svg = (f'' + "".join(etichete)) + +linii = [ + ("Trail-ul urcă doar sub un swing low nou — 63.05, apoi 65.10, apoi 66.00 — și nu coboară " + "niciodată; ieși la prima închidere sub el.", "#333"), + ("Lecția de pattern nu dă o țintă proprie — 1:1 vine de la strategia MBP, iar 2R e ținta " + "exemplului din lecție. Continuarea barelor e construită.", GREY), +] +add = 16 * len(linii) + 10 +svg = svg.replace(f'viewBox="0 0 {W} {H}"', f'viewBox="0 0 {W} {H + add}"') +svg = svg.replace("", extra_svg + "".join( + f'{t}' + for i, (t, c) in enumerate(linii)) + "") +assert "\n\n" not in svg, "linie goală în SVG" +OUT.write_text(svg, encoding="utf-8") +print(f"scris {OUT.name} ({W}x{H + add}); TP1 la bara {prim_tp1}, 2R la {prim_2r}, " + f"trail {trepte}, ieșire bara {iesire} @ {full[iesire].c:.2f} = +{R_IES}R") diff --git a/gen/strategii/hammer_mbt10_bh_iesire.svg b/gen/strategii/hammer_mbt10_bh_iesire.svg new file mode 100644 index 0000000..2e6d324 --- /dev/null +++ b/gen/strategii/hammer_mbt10_bh_iesire.svg @@ -0,0 +1,72 @@ + + Buy hammer — ieșirea (daily, ticker ilustrativ XYZ; barele de după 33 sunt construite) + + pullback + + + 65.95 = 2R — ținta din exemplul lecției + + 64.65 = 1:1 — prima jumătate + + intrare 63.35 + + SL inițial 62.05 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + prima jumătate iese la 64.65: + exact cât ai riscat (1R) + + + a doua jumătate iese la 65.90 (+1,96R): + prima închidere sub linia de trail +linia de trailTrail-ul urcă doar sub un swing low nou — 63.05, apoi 65.10, apoi 66.00 — și nu coboară niciodată; ieși la prima închidere sub el.Lecția de pattern nu dă o țintă proprie — 1:1 vine de la strategia MBP, iar 2R e ținta exemplului din lecție. Continuarea barelor e construită. \ No newline at end of file diff --git a/gen/strategii/hammer_mbt10_bh_locatie.py b/gen/strategii/hammer_mbt10_bh_locatie.py new file mode 100644 index 0000000..5e290ff --- /dev/null +++ b/gen/strategii/hammer_mbt10_bh_locatie.py @@ -0,0 +1,124 @@ +#!/usr/bin/env python3 +"""Buy hammer — locația: hammer la un nivel major vs. hammer în mijlocul range-ului. + +Două panouri alăturate cu ACEEAȘI serie de 12 bare, pe aceeași scară și +aceeași axă de preț (candelele stau la aceleași y). Stânga: suport monthly +62.00, la 0,16 % sub low-ul hammer-ului. Dreapta: cel mai apropiat nivel de +sub el e la 58.00, adică un gol de 7,07 %. Nivelurile se desenează de mână, +cu coordonate absolute (liniile din Chart pornesc de la x=24 și ar trece +peste panoul vecin). + +Rulare: python3 gen/strategii/hammer_mbt10_bh_locatie.py +""" +import re +import sys; sys.path.insert(0, "/workspace/atm-curs-dl") +from pathlib import Path +from svg_candles import Arrow, Chart, Mark, from_closes + +OUT = Path(__file__).with_suffix(".svg") +GREEN, RED, BLUE, ORANGE, GREY = "#188a3e", "#c00", "#1a5fb4", "#b06000", "#555" + +CLOSES = [65.40, 64.90, 64.40, 64.70, 64.00, 63.50, 63.05, + 63.30, # 7 = hammer: o 63.05, h = c 63.30, l 62.10 + 63.35, # 8 = confirmare, close peste high-ul hammer-ului + 63.80, 64.20, 64.60] +wicks = [(0.15, 0.15)] * len(CLOSES) +wicks[7] = (0.00, 0.95) +cds = from_closes(CLOSES, wicks=wicks, start=65.90) +ham = cds[7] +SUP_DA, SUP_NU, REZ_NU = 62.00, 58.00, 67.00 + +# --- verificări ------------------------------------------------------------- +for i in range(1, len(cds)): + assert abs(cds[i].o - cds[i - 1].c) < 1e-9, f"open[{i}] != close[{i-1}]" +assert (round(ham.h, 2), round(ham.l, 2), round(ham.c, 2)) == (63.30, 62.10, 63.30) +corp, jos = ham.c - ham.o, ham.o - ham.l +assert jos >= 2 * corp, "bara 7 nu e hammer" +assert cds[8].c > ham.h, "bara 8 nu confirmă" +d_da = round((ham.l - SUP_DA) / SUP_DA * 100, 2) +assert d_da == 0.16 and d_da <= 2, d_da +d_nu = round((ham.l - SUP_NU) / SUP_NU * 100, 2) +assert d_nu == 7.07 and d_nu > 2, d_nu +d_rez = round((REZ_NU - ham.h) / ham.h * 100, 2) +assert d_rez == 5.85 and d_rez > 2, d_rez +assert not any(SUP_NU < p < REZ_NU for p in (SUP_NU, REZ_NU) if False) + +# --- layout ----------------------------------------------------------------- +W, PW = 760, 350 +PX = [24, 24 + PW + 28] +SCALE, TOP = 34.0, 96 + +ARR = { + PX[0]: Arrow(4, "suport monthly: nivel urmărit de mulți —\naici au cumpărători motiv să apară", + price=SUP_DA, where="below", color=BLUE, dx=30, dy=22), + PX[1]: Arrow(7, "aceeași formă, dar nimic sub ea:\nfitilul n-a fost oprit de un nivel,\nci s-a oprit singur", + where="below", color=RED, dx=70, dy=30), +} +charts = [] +for px in PX: + ch = Chart(candles=cds, + marks=[Mark(7, "hammer 63.30 / 62.10", ORANGE, "above", dy=-16)], + arrows=[ARR[px]], + scale=SCALE, gap=17, body_w=10, left=px + 34, width=px + PW, top=TOP) + body = ch._render() + assert f'viewBox="0 0 {px + PW} ' in body, f"panoul {px}: textul unei săgeți iese" + assert (ch.top, ch.left) == (TOP, px + 34), "Chart a mutat graficul: panourile s-ar dezalinia" + charts.append((ch, body.split(">", 1)[1].rsplit("", 1)[0].strip())) +(ch_da, body_da), (ch_nu, body_nu) = charts +assert ch_da.scale == ch_nu.scale and ch_da._pmax == ch_nu._pmax, "scări diferite" +y = ch_da._y + + +def level(px, price, text, color, below=False): + yy = y(price) + ty, anchor, tx = (yy + 14, "end", px + PW - 4) if below else (yy - 6, "end", px + PW - 4) + return [f'', + f'{text}'] + + +def zone(px, p_top, p_bot, label, color, label_at_bottom=False): + y0, y1 = y(p_top), y(p_bot) + ly, anchor, lx = ((y1 - 12, "start", px + 8) if label_at_bottom + else (y0 - 6, "end", px + PW - 4)) + return [f'', + f'{label}'] + + +PCT_DA, PCT_NU = f"{d_da:.2f}".replace(".", ","), f"{d_nu:.2f}".replace(".", ",") +y_bot = y(SUP_NU) +parts = [ + f'', + 'Hammer la un nivel vs. hammer ' + 'în mijlocul range-ului — daily, exemplu construit', + f'DA — hammer pe suportul ' + 'monthly', + f'NU — hammer în mijlocul ' + 'range-ului', + f'', + *zone(PX[0], ham.l, SUP_DA, f"low-ul hammer-ului: {ham.l:.2f}", GREEN), + *zone(PX[1], ham.l, SUP_NU, f"low-ul hammer-ului: {ham.l:.2f}", RED), + f'de la {ham.l:.2f} la {SUP_DA:.2f} = {PCT_DA} % — la nivel', + f'' + f'de la {ham.l:.2f} la {SUP_NU:.2f} = gol de {PCT_NU} % — hammer în aer', + *level(PX[0], SUP_DA, "suport monthly 62.00", BLUE, below=True), + *level(PX[1], REZ_NU, "rezistența range-ului 67.00", BLUE, below=True), + *level(PX[1], SUP_NU, "baza range-ului 58.00", BLUE, below=True), + body_da, body_nu, + f'Completare proprie: materialul cere ' + '„un nivel major (weekly / monthly / quarterly), nu mijlocul range-ului", dar nu dă un prag ' + 'numeric.', + f'Pragul de 1-2 % e împrumutat de la buy ' + f'doji, ca să ai ce măsura: aici {PCT_DA} % în stânga, {PCT_NU} % în dreapta.', + "", +] +svg = "\n ".join(parts).replace("\n ", "\n") +assert "\n\n" not in svg, "linie goală în SVG" +for m in re.finditer(r']*>(.*?)', body_da): + assert float(m.group(1)) < PX[1] - 14, f"text din DA trece în NU: {m.group(2)}" +OUT.write_text(svg, encoding="utf-8") +print(f"scris {OUT.name} ({W}x{y_bot + 66:.0f}); DA {d_da}% NU {d_nu}% (rezistență {d_rez}%)") diff --git a/gen/strategii/hammer_mbt10_bh_locatie.svg b/gen/strategii/hammer_mbt10_bh_locatie.svg new file mode 100644 index 0000000..1ed3e2c --- /dev/null +++ b/gen/strategii/hammer_mbt10_bh_locatie.svg @@ -0,0 +1,79 @@ + + Hammer la un nivel vs. hammer în mijlocul range-ului — daily, exemplu construit + DA — hammer pe suportul monthly + NU — hammer în mijlocul range-ului + + + low-ul hammer-ului: 62.10 + + low-ul hammer-ului: 62.10 + de la 62.10 la 62.00 = 0,16 % — la nivel + de la 62.10 la 58.00 = gol de 7,07 % — hammer în aer + + suport monthly 62.00 + + rezistența range-ului 67.00 + + baza range-ului 58.00 + + + + + + + + + + + + + + + + + + + + + + + + + hammer 63.30 / 62.10 + + + suport monthly: nivel urmărit de mulți — + aici au cumpărători motiv să apară + + + + + + + + + + + + + + + + + + + + + + + + + hammer 63.30 / 62.10 + + + aceeași formă, dar nimic sub ea: + fitilul n-a fost oprit de un nivel, + ci s-a oprit singur + Completare proprie: materialul cere „un nivel major (weekly / monthly / quarterly), nu mijlocul range-ului", dar nu dă un prag numeric. + Pragul de 1-2 % e împrumutat de la buy doji, ca să ai ce măsura: aici 0,16 % în stânga, 7,07 % în dreapta. + \ No newline at end of file diff --git a/gen/strategii/hammer_mbt10_bh_sell.py b/gen/strategii/hammer_mbt10_bh_sell.py new file mode 100644 index 0000000..6e568bb --- /dev/null +++ b/gen/strategii/hammer_mbt10_bh_sell.py @@ -0,0 +1,86 @@ +#!/usr/bin/env python3 +"""Sell hammer — oglinda pe short a trade-ului din Partea A. + +Seria e chiar trade-ul long, oglindit în jurul lui K = 162.30: fiecare preț +p devine K - p, iar fitilele se inversează. Așa, hammer-ul 63.30 / 62.10 +devine 99.00 / 100.20, intrarea 63.35 devine 98.95, stopul 62.05 devine +100.25 și ținta 65.95 devine 96.35 — riscul rămâne exact 1.30, deci cele +două diagrame nu se pot contrazice la cifre. Oglindirea e verificată bară +cu bară prin assert. + +Rulare: python3 gen/strategii/hammer_mbt10_bh_sell.py +""" +import re +import sys; sys.path.insert(0, "/workspace/atm-curs-dl") +sys.path.insert(0, "/workspace/atm-curs-dl/gen/strategii") +from pathlib import Path +from hammer_mbt10_bh_ansamblu import serie +from svg_candles import Arrow, Candle, Chart, Level + +OUT = Path(__file__).with_suffix(".svg") +GREEN, RED, BLUE, ORANGE, GREY = "#188a3e", "#c00", "#1a5fb4", "#b06000", "#555" + +K = 162.30 +lung, ham_l, conf_l = serie() +conf_l.note = "" +cds = [Candle(K - c.o, K - c.l, K - c.h, K - c.c, vol=c.vol) for c in lung] +ham, conf = cds[19], cds[20] +INTRARE, SL, TP = round(K - 63.35, 2), round(K - 62.05, 2), round(K - 65.95, 2) +R = round(SL - INTRARE, 2) + +# --- verificări ------------------------------------------------------------- +for i, (a, b) in enumerate(zip(lung, cds)): + assert abs(b.o - (K - a.o)) < 1e-9 and abs(b.c - (K - a.c)) < 1e-9, i + assert abs(b.h - (K - a.l)) < 1e-9 and abs(b.l - (K - a.h)) < 1e-9, f"bara {i}: fitil neoglindit" + assert b.l <= min(b.o, b.c) and b.h >= max(b.o, b.c), f"bara {i}: h/l nu cuprind o/c" +for i in range(1, len(cds)): + assert abs(cds[i].o - cds[i - 1].c) < 1e-9, f"open[{i}] != close[{i-1}]" +assert (round(ham.l, 2), round(ham.h, 2)) == (99.00, 100.20), ham +corp, sus = ham.o - ham.c, ham.h - ham.o +assert sus >= 2 * corp, "bara 19 nu e sell hammer (fitilul de sus sub dublul corpului)" +assert abs(ham.l - ham.c) < 1e-9, "corpul nu se sprijină pe low: fitil de jos inexistent" +assert cds[18].c > cds[0].c + 8, "urcarea dinainte nu se vede" +assert conf.c < ham.l and round(conf.c, 2) == INTRARE, "confirmarea nu închide sub low-ul lui" +assert round(SL, 2) == round(ham.h + 0.05, 2), "SL nu e la 5 cenți peste high-ul sell hammer-ului" +assert R == 1.30 and round(INTRARE - TP, 2) == 2 * R, (R, TP) +assert TP < INTRARE < SL, "ordinea nivelurilor e greșită pe short" +prim_tp = next(i for i in range(21, len(cds)) if cds[i].l <= TP) +assert prim_tp == 28, prim_tp +assert max(c.h for c in cds[21:prim_tp + 1]) < SL, "stopul ar fi fost atins înainte de țintă" + +ch = Chart( + candles=cds, + levels=[Level(SL, "SL 100.25 — peste high-ul sell hammer-ului", RED), + Level(INTRARE, "intrare short 98.95", GREEN, label_at="left"), + Level(TP, "TP 96.35 = 2R", BLUE)], + arrows=[ + Arrow(19, "sell hammer 99.00 / 100.20:\ncorp mic jos, fitil lung în sus", + where="left", color=ORANGE, dx=-8, dy=-30), + Arrow(20, "close 98.95, sub low-ul lui 99.00:\naici vinzi în lipsă", + where="below", color=GREEN, dx=-120, dy=40), + Arrow(prim_tp, "ținta 96.35: aceeași distanță\nca la long, dar în jos", + price=TP, where="below", color=BLUE, dx=-60, dy=30), + ], + scale=32.0, gap=15, body_w=9, left=44, width=720, top=56, vol_h=70, vol_label="volum", + title="Sell hammer — oglinda pe short (daily, exemplu construit)", +) +svg = ch.render() +m = re.search(r'viewBox="0 0 (\d+) (\d+)"', svg) +W, H = int(m.group(1)), int(m.group(2)) +linii = [ + ("Totul e întors: urcare înainte, corp mic jos și fitil lung în sus, intrarea la o " + "închidere SUB", "#333"), + ("low-ul candelei, stopul PESTE high-ul ei, ținta la aceeași distanță în jos.", "#333"), + ("Completare proprie: cursul numește Sell Hammer ca simetric al lui Buy Hammer, dar " + "nu-i scrie", GREY), + ("regulile separat. Seria e trade-ul long oglindit, deci riscul e tot 1.30.", GREY), +] +add = 16 * len(linii) + 10 +svg = svg.replace(f'viewBox="0 0 {W} {H}"', f'viewBox="0 0 {W} {H + add}"') +svg = svg.replace("", "".join( + f'{t}' + for i, (t, c) in enumerate(linii)) + "") +assert "\n\n" not in svg, "linie goală în SVG" +OUT.write_text(svg, encoding="utf-8") +print(f"scris {OUT.name} ({W}x{H + add}); intrare {INTRARE} SL {SL} TP {TP} R {R}, " + f"țintă atinsă la bara {prim_tp}") diff --git a/gen/strategii/hammer_mbt10_bh_sell.svg b/gen/strategii/hammer_mbt10_bh_sell.svg new file mode 100644 index 0000000..5d015c9 --- /dev/null +++ b/gen/strategii/hammer_mbt10_bh_sell.svg @@ -0,0 +1,125 @@ + + Sell hammer — oglinda pe short (daily, exemplu construit) + + SL 100.25 — peste high-ul sell hammer-ului + + intrare short 98.95 + + TP 96.35 = 2R + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + volum + + + sell hammer 99.00 / 100.20: + corp mic jos, fitil lung în sus + + + close 98.95, sub low-ul lui 99.00: + aici vinzi în lipsă + + + ținta 96.35: aceeași distanță + ca la long, dar în jos +Totul e întors: urcare înainte, corp mic jos și fitil lung în sus, intrarea la o închidere SUBlow-ul candelei, stopul PESTE high-ul ei, ținta la aceeași distanță în jos.Completare proprie: cursul numește Sell Hammer ca simetric al lui Buy Hammer, dar nu-i scrieregulile separat. Seria e trade-ul long oglindit, deci riscul e tot 1.30. \ No newline at end of file diff --git a/gen/strategii/hammer_mbt10_bh_stop.py b/gen/strategii/hammer_mbt10_bh_stop.py new file mode 100644 index 0000000..a578a56 --- /dev/null +++ b/gen/strategii/hammer_mbt10_bh_stop.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python3 +"""Buy hammer — unde stă stopul și câte acțiuni cumperi (același trade XYZ). + +Zoom pe barele 14-27 din seria diagramei de ansamblu (importată, ca să nu +existe două variante ale aceluiași trade). Arată: SL 62.05, cu 5 cenți sub +low-ul hammer-ului, paranteza de risc 1.30 $/acțiune și, sub grafic, calculul +de mărime al lecției: 2 % dintr-un cont de 10.000 $ = 200 $; 200 / 1.30 = 153 +de acțiuni. + +Rulare: python3 gen/strategii/hammer_mbt10_bh_stop.py +""" +import re +import sys; sys.path.insert(0, "/workspace/atm-curs-dl") +sys.path.insert(0, "/workspace/atm-curs-dl/gen/strategii") +from pathlib import Path +from hammer_mbt10_bh_ansamblu import serie +from svg_candles import Arrow, Chart, Level, Zone + +OUT = Path(__file__).with_suffix(".svg") +GREEN, RED, BLUE, ORANGE, GREY = "#188a3e", "#c00", "#1a5fb4", "#b06000", "#555" + +I0, I1 = 14, 27 # fereastra desenată din seria completă +cds_all, hammer, conf = serie() +cds = cds_all[I0:I1 + 1] +HAM, CONF = 19 - I0, 20 - I0 # indicii hammer / confirmare în fereastră + +INTRARE, SL = 63.35, 62.05 +RISC = round(INTRARE - SL, 2) +CONT, PROCENT = 10_000, 0.02 +RISC_LEI = CONT * PROCENT +ACTIUNI = int(RISC_LEI // RISC) + +# --- verificări ------------------------------------------------------------- +assert cds[HAM] is hammer and cds[CONF] is conf, "fereastra nu prinde hammer-ul și confirmarea" +assert round(SL, 2) == round(hammer.l - 0.05, 2), "SL nu e la 5 cenți sub low-ul hammer-ului" +assert round(conf.c, 2) == INTRARE and conf.c > hammer.h, "intrarea nu e close-ul confirmării" +assert RISC == 1.30, RISC +assert RISC_LEI == 200 and ACTIUNI == 153, (RISC_LEI, ACTIUNI) +assert min(c.l for c in cds) == hammer.l, "în fereastră, cel mai jos low nu e al hammer-ului" +assert SL < min(c.l for c in cds), "linia stopului trebuie să stea sub toate fitilele" +# pierderea maximă planificată încape în regula de 2 % +assert ACTIUNI * RISC <= RISC_LEI, "pierderea planificată depășește 2 % din cont" + +ch = Chart( + candles=cds, + levels=[Level(INTRARE, "intrare 63.35 (close-ul confirmării)", GREEN, label_at="left"), + Level(SL, "SL 62.05", RED, label_at="left")], + zones=[Zone(HAM, CONF, "cele două bare care dau stopul", ORANGE)], + arrows=[ + Arrow(HAM, "low-ul hammer-ului: 62.10\nsub el, cererea care a făcut\nfitilul nu mai există", + where="below", price=hammer.l, color=BLUE, dx=-176, dy=26), + Arrow(CONF, "riscul e distanța intrare - SL:\n63.35 - 62.05 = 1.30 $ pe acțiune", + where="above", color=GREY, dx=186, dy=-4), + ], + scale=44.0, gap=26, body_w=13, left=64, width=680, vol_h=0, + title="Buy hammer — stopul și mărimea poziției (daily, ticker ilustrativ XYZ)", +) +svg = ch.render() +m = re.search(r'viewBox="0 0 (\d+) (\d+)"', svg) +W, H = int(m.group(1)), int(m.group(2)) + +# paranteza de risc, desenată de mână: stop -> intrare, cu textul în dreapta +OFF = (float(re.search(r'' + f'1R = 1.30 $') + +linii = [ + ("Mărimea poziției, cu cifrele lecției: 2 % dintr-un cont de 10.000 $ = 200 $ de risc.", "#333"), + (f"200 $ / 1.30 $ pe acțiune = {200 / RISC:.1f} → {ACTIUNI} de acțiuni (rotunjit în jos); " + f"pierderea maximă {ACTIUNI * RISC:.2f} $.", "#333"), + ("Cei 5 cenți de sub low sunt marja folosită în tot capitolul pe exemplele construite de " + "mine; unde cifra vine din material, e spusă acolo.", GREY), +] +extra = 16 * len(linii) + 10 +svg = svg.replace(f'viewBox="0 0 {W} {H}"', f'viewBox="0 0 {W} {H + extra}"') +svg = svg.replace("", brk + "".join( + f'{t}' + for i, (t, c) in enumerate(linii)) + "") +assert "\n\n" not in svg, "linie goală în SVG" +OUT.write_text(svg, encoding="utf-8") +print(f"scris {OUT.name} ({W}x{H + extra}); risc {RISC}, {ACTIUNI} acțiuni") diff --git a/gen/strategii/hammer_mbt10_bh_stop.svg b/gen/strategii/hammer_mbt10_bh_stop.svg new file mode 100644 index 0000000..a033351 --- /dev/null +++ b/gen/strategii/hammer_mbt10_bh_stop.svg @@ -0,0 +1,47 @@ + + Buy hammer — stopul și mărimea poziției (daily, ticker ilustrativ XYZ) + + cele două bare care dau stopul + + intrare 63.35 (close-ul confirmării) + + SL 62.05 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + low-ul hammer-ului: 62.10 + sub el, cererea care a făcut + fitilul nu mai există + + + riscul e distanța intrare - SL: + 63.35 - 62.05 = 1.30 $ pe acțiune + bara 20: confirmare — close 63.35 peste high-ul hammer-ului 63.30 (intrare) +1R = 1.30 $Mărimea poziției, cu cifrele lecției: 2 % dintr-un cont de 10.000 $ = 200 $ de risc.200 $ / 1.30 $ pe acțiune = 153.8 → 153 de acțiuni (rotunjit în jos); pierderea maximă 198.90 $.Cei 5 cenți de sub low sunt marja folosită în tot capitolul pe exemplele construite de mine; unde cifra vine din material, e spusă acolo. \ No newline at end of file diff --git a/gen/strategii/hammer_mbt10_bh_trend.py b/gen/strategii/hammer_mbt10_bh_trend.py new file mode 100644 index 0000000..4543365 --- /dev/null +++ b/gen/strategii/hammer_mbt10_bh_trend.py @@ -0,0 +1,99 @@ +#!/usr/bin/env python3 +"""Aceeași candelă, două contexte: hammer (după scădere) vs. hanging man (după urcare). + +Două panouri alăturate, aceeași scară px/unitate. Bara 9 e IDENTICĂ în ambele +(o 63.05, h = c 63.30, l 62.10) — verificat prin assert; ce diferă e ce s-a +întâmplat înaintea ei. Panourile nu împart axa de preț: seriile trăiesc în +zone de preț diferite, iar comparația cerută aici e de context, nu de nivel. + +Rulare: python3 gen/strategii/hammer_mbt10_bh_trend.py +""" +import re +import sys; sys.path.insert(0, "/workspace/atm-curs-dl") +from pathlib import Path +from svg_candles import Arrow, Chart, Zone, from_closes + +OUT = Path(__file__).with_suffix(".svg") +GREEN, RED, BLUE, ORANGE, GREY = "#188a3e", "#c00", "#1a5fb4", "#b06000", "#555" + +HAM = (63.05, 63.30, 62.10, 63.30) # o, h, l, c — bara 9, în ambele panouri + +JOS = [68.00, 67.20, 66.50, 66.80, 65.60, 64.90, 65.10, 64.20, 63.05, + 63.30, 63.35, 63.80] +SUS = [58.00, 58.70, 59.40, 59.20, 60.30, 61.00, 61.80, 62.40, 63.05, + 63.30, 62.00, 61.40] + + +def serie(closes): + w = [(0.15, 0.15)] * len(closes) + w[9] = (0.00, 0.95) # bara-hammer: high = close, fitil jos 0.95 + return from_closes(closes, wicks=w, start=closes[0] + (0.4 if closes[0] > 60 else -0.4)) + + +L, R = serie(JOS), serie(SUS) + +# --- verificări ------------------------------------------------------------- +for s, nume in ((L, "stânga"), (R, "dreapta")): + for i in range(1, len(s)): + assert abs(s[i].o - s[i - 1].c) < 1e-9, f"{nume}: open[{i}] != close[{i-1}]" + b = s[9] + assert (round(b.o, 2), round(b.h, 2), round(b.l, 2), round(b.c, 2)) == HAM, (nume, b) +assert (L[9].o, L[9].h, L[9].l, L[9].c) == (R[9].o, R[9].h, R[9].l, R[9].c), \ + "bara 9 trebuie să fie identică în ambele panouri" +corp, jos = HAM[3] - HAM[0], HAM[0] - HAM[2] +assert jos >= 2 * corp, "bara 9 nu trece regula de formă" +assert L[8].c < L[0].c - 4, "stânga: scăderea nu se vede" +assert R[8].c > R[0].c + 4, "dreapta: urcarea nu se vede" +assert L[10].c > L[9].h, "stânga: bara 10 nu confirmă peste high-ul hammer-ului" +assert R[10].c < R[9].l, "dreapta: bara 10 nu închide sub low-ul candelei" + +# --- desen ------------------------------------------------------------------ +W, PW = 780, 358 +PX = [24, 24 + PW + 20] +SCALE, TOP, SHIFT = 26.0, 104, 34 + +panels, inalt = [], [] +for px, cds, (zt, zc), arr in ( + (PX[0], L, ("scădere de 9 candele", RED), + [Arrow(9, "hammer 63.30 / 62.10: prețul a fost dus la 62.10\nși readus sus, la 63.30", + where="below", color=ORANGE, dx=-70, dy=34), + Arrow(10, "close 63.35, peste high-ul hammer-ului:\nsemnalul e valid, aici intri", + where="above", color=GREEN, dx=-96, dy=-24)]), + (PX[1], R, ("urcare de 9 candele", GREEN), + [Arrow(9, "aceeași candelă 63.30 / 62.10, dar după o urcare:\nse numește hanging man și avertizează de scădere", + where="below", color=RED, dx=-250, dy=98), + Arrow(10, "close 62.00, sub low-ul ei: scăderea\nîncepe — nu era semnal de cumpărare", + where="above", color=RED, dx=-70, dy=-18)]), +): + ch = Chart(candles=cds, arrows=arr, + zones=[Zone(0, 8, zt, zc)], + scale=SCALE, gap=24, body_w=12, left=px + 46, width=px + PW, top=TOP) + body = ch._render() + assert f'viewBox="0 0 {px + PW} ' in body, f"panoul {px}: ceva iese din cadru" + assert ch.scale == SCALE + corp_panou = body.split(">", 1)[1].rsplit("", 1)[0].strip() + panels.append(f'{corp_panou}') + inalt.append(float(re.search(r'viewBox="0 0 [\d.]+ ([\d.]+)"', body).group(1))) + +H = int(max(inalt)) + SHIFT + 46 +parts = [ + f'', + '' + 'Aceeași candelă, două contexte — daily, exemplu construit', + f'' + 'DA — după o scădere: hammer', + f'' + 'NU — după o urcare: hanging man', + f'', + *panels, + f'Forma nu spune nimic singură: hammer-ul ' + 'e pattern de întoarcere doar după o scădere. Aceeași bară după o urcare', + f'e hanging man și are sensul opus. ' + 'Prețurile sunt construite; regula de context e a cursului.', + "", +] +svg = "\n ".join(parts).replace("\n ", "\n") +assert "\n\n" not in svg, "linie goală în SVG" +OUT.write_text(svg, encoding="utf-8") +print(f"scris {OUT.name} ({W}x{H})") diff --git a/gen/strategii/hammer_mbt10_bh_trend.svg b/gen/strategii/hammer_mbt10_bh_trend.svg new file mode 100644 index 0000000..4acc22d --- /dev/null +++ b/gen/strategii/hammer_mbt10_bh_trend.svg @@ -0,0 +1,76 @@ + + Aceeași candelă, două contexte — daily, exemplu construit + DA — după o scădere: hammer + NU — după o urcare: hanging man + + + scădere de 9 candele + + + + + + + + + + + + + + + + + + + + + + + + + + + hammer 63.30 / 62.10: prețul a fost dus la 62.10 + și readus sus, la 63.30 + + + close 63.35, peste high-ul hammer-ului: + semnalul e valid, aici intri + + urcare de 9 candele + + + + + + + + + + + + + + + + + + + + + + + + + + + aceeași candelă 63.30 / 62.10, dar după o urcare: + se numește hanging man și avertizează de scădere + + + close 62.00, sub low-ul ei: scăderea + începe — nu era semnal de cumpărare + Forma nu spune nimic singură: hammer-ul e pattern de întoarcere doar după o scădere. Aceeași bară după o urcare + e hanging man și are sensul opus. Prețurile sunt construite; regula de context e a cursului. + \ No newline at end of file diff --git a/gen/strategii/hammer_mbt10_bh_trigger.py b/gen/strategii/hammer_mbt10_bh_trigger.py new file mode 100644 index 0000000..f72efa2 --- /dev/null +++ b/gen/strategii/hammer_mbt10_bh_trigger.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python3 +"""Trigger-ul buy hammer: un singur DA și două feluri de NU. + +Trei panouri cu ACELEAȘI șase bare (scădere + hammer 63.30 / 62.10) și cu o +singură bară diferită la final — candela următoare. Axa de preț e comună: +toate trei folosesc același `pmax`, verificat prin assert, deci liniile +63.30 și 62.10 cad la același y pe toate panourile. + +Rulare: python3 gen/strategii/hammer_mbt10_bh_trigger.py +""" +import re +import sys; sys.path.insert(0, "/workspace/atm-curs-dl") +from pathlib import Path +from svg_candles import Arrow, Chart, from_closes + +OUT = Path(__file__).with_suffix(".svg") +GREEN, RED, BLUE, ORANGE, GREY = "#188a3e", "#c00", "#1a5fb4", "#b06000", "#555" + +BAZA = [65.20, 64.60, 64.10, 63.60, 63.05, 63.30] # 5 = hammer +H_HAM, L_HAM = 63.30, 62.10 +ULTIMA = {"DA": 63.35, "DEVREME": 63.20, "MORT": 62.00} + + +def serie(ultim): + closes = BAZA + [ultim] + w = [(0.15, 0.15)] * len(closes) + w[5] = (0.00, 0.95) + return from_closes(closes, wicks=w, start=65.70) + + +S = {k: serie(v) for k, v in ULTIMA.items()} + +# --- verificări ------------------------------------------------------------- +for k, cds in S.items(): + for i in range(1, len(cds)): + assert abs(cds[i].o - cds[i - 1].c) < 1e-9, f"{k}: open[{i}] != close[{i-1}]" + h = cds[5] + assert (round(h.h, 2), round(h.l, 2)) == (H_HAM, L_HAM), (k, h) + assert (h.o - h.l) >= 2 * (h.c - h.o), f"{k}: bara 5 nu e hammer" + assert [round(c.c, 2) for c in cds[:6]] == BAZA, k +assert S["DA"][6].c > H_HAM, "DA: candela următoare nu închide peste high" +assert L_HAM <= S["DEVREME"][6].c <= H_HAM, "DEVREME: închiderea trebuie între low și high" +assert S["MORT"][6].c < L_HAM, "MORT: candela următoare nu închide sub low" + +# --- layout ----------------------------------------------------------------- +W, PW = 760, 240 +PX = [12, 12 + PW + 14, 12 + 2 * (PW + 14)] +SCALE, TOP, SHIFT = 46.0, 96, 30 + +CFG = [ + ("DA", PX[0], GREEN, "DA — închide peste high", + Arrow(6, "close 63.35, peste 63.30:\naici intri", where="above", color=GREEN, dx=-70, dy=-18)), + ("DEVREME", PX[1], RED, "NU — încă sub high", + Arrow(6, "close 63.20, sub 63.30:\nnu s-a întâmplat nimic,\naștepți mai departe", + where="above", color=RED, dx=-78, dy=-18)), + ("MORT", PX[2], RED, "NU — închide sub low", + Arrow(6, "close 62.00, sub 62.10:\nvânzătorii au dus prețul\nsub fitil — setup mort", + where="below", color=RED, dx=-84, dy=26)), +] + +bodies, inalt, charts = [], [], {} +for k, px, col, _, arr in CFG: + ch = Chart(candles=S[k], arrows=[arr], scale=SCALE, gap=20, body_w=11, + left=px + 46, width=px + PW, top=TOP) + body = ch._render() + assert f'viewBox="0 0 {px + PW} ' in body, f"panoul {k}: textul iese din cadru" + assert (ch.top, ch.left) == (TOP, px + 46), f"panoul {k}: Chart a mutat graficul" + charts[k] = ch + inalt.append(float(re.search(r'viewBox="0 0 [\d.]+ ([\d.]+)"', body).group(1))) + bodies.append(f'' + + body.split(">", 1)[1].rsplit("", 1)[0].strip() + '') +pmax = {k: c._pmax for k, c in charts.items()} +assert len(set(pmax.values())) == 1, f"panourile n-au aceeași axă: {pmax}" +y = lambda p: charts["DA"]._y(p) + SHIFT # noqa: E731 + +H = int(max(inalt)) + SHIFT + 44 +parts = [ + f'', + 'Trigger-ul: contează unde se ' + 'închide candela următoare — daily, exemplu construit', +] +for (k, px, col, titlu, _), corp in zip(CFG, bodies): + parts.append(f'{titlu}') + for p, c, txt in ((H_HAM, ORANGE, "high 63.30"), (L_HAM, BLUE, "low 62.10")): + yy = y(p) + parts.append(f'') + parts.append(f'' + f'{txt}') + parts.append(corp) +for px in PX[1:]: + parts.append(f'') +parts += [ + f'Regula e a lecției de pattern: intri la close-ul ' + 'candelei următoare, dacă acea închidere e peste high-ul hammer-ului. O atingere', + f'intraday peste 63.30, urmată de o închidere sub el, ' + 'nu e trigger. Prețurile sunt construite.', + "", +] +svg = "\n ".join(parts).replace("\n ", "\n") +assert "\n\n" not in svg, "linie goală în SVG" +OUT.write_text(svg, encoding="utf-8") +print(f"scris {OUT.name} ({W}x{H})") diff --git a/gen/strategii/hammer_mbt10_bh_trigger.svg b/gen/strategii/hammer_mbt10_bh_trigger.svg new file mode 100644 index 0000000..9d8f636 --- /dev/null +++ b/gen/strategii/hammer_mbt10_bh_trigger.svg @@ -0,0 +1,78 @@ + + Trigger-ul: contează unde se închide candela următoare — daily, exemplu construit + DA — închide peste high + + high 63.30 + + low 62.10 + + + + + + + + + + + + + + + + + close 63.35, peste 63.30: + aici intri + NU — încă sub high + + high 63.30 + + low 62.10 + + + + + + + + + + + + + + + + + close 63.20, sub 63.30: + nu s-a întâmplat nimic, + aștepți mai departe + NU — închide sub low + + high 63.30 + + low 62.10 + + + + + + + + + + + + + + + + + close 62.00, sub 62.10: + vânzătorii au dus prețul + sub fitil — setup mort + + + Regula e a lecției de pattern: intri la close-ul candelei următoare, dacă acea închidere e peste high-ul hammer-ului. O atingere + intraday peste 63.30, urmată de o închidere sub el, nu e trigger. Prețurile sunt construite. + \ No newline at end of file diff --git a/gen/strategii/hammer_mbt10_mbt_confluente.py b/gen/strategii/hammer_mbt10_mbt_confluente.py new file mode 100644 index 0000000..2e9f5bb --- /dev/null +++ b/gen/strategii/hammer_mbt10_mbt_confluente.py @@ -0,0 +1,114 @@ +#!/usr/bin/env python3 +"""MBT-10, pasul „unde": ideea de bottom cu confluențe (exemplu construit). + +Seria de aici e SURSA UNICĂ a trade-ului MBT-10 din Nivelul 2: +`hammer_mbt10_mbt_ppd`, `_mbt_stop` și `_mbt_iesire` importă `serie()` și +pornesc din aceleași bare. Nivelurile 89.90 (intrare la close), 87.30 (SL) și +95.20 (ținta, vârful de unde a pornit coborârea) sunt cifrele exemplului +construit din fișierul principal; suportul weekly 87.50 și pivotul daily +89.60 sunt puse de mine, ca acele cifre să aibă de unde ieși. + +Diagrama asta arată doar contextul: double bottom pe suportul weekly, cu +volum mai mic pe al doilea picior. Nicio intrare desenată — intrarea vine la +pasul următor. + +Rulare: python3 gen/strategii/hammer_mbt10_mbt_confluente.py +""" +import re +import sys; sys.path.insert(0, "/workspace/atm-curs-dl") +from pathlib import Path +from svg_candles import Arrow, Chart, Level, Zone, from_closes + +OUT = Path(__file__).with_suffix(".svg") +GREEN, RED, BLUE, ORANGE, GREY = "#188a3e", "#c00", "#1a5fb4", "#b06000", "#555" + +SUP_W, PPD = 87.50, 89.60 # suport weekly, pivotul daily +INTRARE, SL, TP = 89.90, 87.30, 95.20 +R = round(INTRARE - SL, 2) +FUND1, FUND2 = 10, 15 # cele două picioare ale double bottom-ului +CLOSE_I = 19 # bara care se închide peste pivotul daily + +CLOSES = [96.00, 95.00, 94.20, 94.60, 93.10, 92.00, 91.20, 90.10, 89.00, # 0-8 + 88.20, 87.70, 88.40, 89.10, # 9-12 + 88.60, 88.00, 87.65, 88.30, # 13-16 + 88.90, 89.40, 89.90, # 17-19 + 90.60, 91.40, 92.20, 91.80, 93.00, 94.10, 95.40] # 20-26 +VOLS = [60, 58, 55, 50, 62, 66, 70, 74, 80, + 86, 94, 62, 58, + 55, 60, 66, 54, # piciorul 2: volum vizibil mai mic + 58, 62, 96, + 70, 66, 64, 58, 68, 72, 80] + + +def serie(): + w = [(0.20, 0.25)] * len(CLOSES) + w[FUND1] = (0.20, 0.10) # low 87.60 + w[FUND2] = (0.20, 0.10) # low 87.55 + # barele imediat de după picioare pornesc din același preț: fără fitil lung + # în jos, altfel ele ar face minimul, nu picioarele + w[FUND1 + 1] = w[FUND2 + 1] = (0.20, 0.05) + # bara 18 atinge pivotul intraday, dar se închide sub el: contraexemplul + # de la pasul cu condiția de preț + w[CLOSE_I - 1] = (0.30, 0.25) + cds = from_closes(CLOSES, wicks=w, vols=VOLS, start=96.60) + assert len(cds) == len(VOLS) == 27 + for i in range(1, len(cds)): + assert abs(cds[i].o - cds[i - 1].c) < 1e-9, f"open[{i}] != close[{i-1}]" + l1, l2 = round(cds[FUND1].l, 2), round(cds[FUND2].l, 2) + assert (l1, l2) == (87.60, 87.55), (l1, l2) + assert abs(l1 - l2) <= 0.05 and l2 < l1, "double bottom: al doilea picior, puțin mai jos" + assert round(min(c.l for c in cds), 2) == l2, "cel mai jos low nu e al piciorului 2" + assert l2 > SUP_W > SL, "picioarele trebuie să stea peste suport, iar stopul sub el" + assert VOLS[FUND2] < VOLS[FUND1], "fără divergență de volum pe piciorul 2" + # de la al doilea picior încoace, nicio bară nu închide peste pivotul zilei + # intrării — altfel trigger-ul ar fi venit mai devreme + assert max(c.c for c in cds[FUND2:CLOSE_I]) < PPD < cds[CLOSE_I].c, \ + "o bară dinaintea trigger-ului închide deja peste pivotul daily" + assert round(cds[CLOSE_I].c, 2) == INTRARE + assert cds[CLOSE_I - 1].h > PPD > cds[CLOSE_I - 1].c, \ + "bara 18 trebuie să atingă pivotul intraday, dar să închidă sub el" + assert min(c.l for c in cds[CLOSE_I:]) > SL, "stopul ar fi fost atins după intrare" + prim_tp = next(i for i in range(CLOSE_I + 1, len(cds)) if cds[i].h >= TP) + assert prim_tp == 26, prim_tp + return cds + + +if __name__ == "__main__": + cds = serie() + pct1 = round((cds[FUND1].l - SUP_W) / SUP_W * 100, 2) + pct2 = round((cds[FUND2].l - SUP_W) / SUP_W * 100, 2) + assert pct1 < 0.2 and pct2 < 0.2, (pct1, pct2) + vis = cds[:CLOSE_I] # contextul, fără bara de trigger + + ch = Chart( + candles=vis, + levels=[Level(SUP_W, "suport weekly 87.50", BLUE, label_at="left")], + zones=[Zone(FUND1, FUND2, "double bottom: 87.60 și 87.55", ORANGE)], + arrows=[ + Arrow(FUND1, "piciorul 1: low 87.60,\nla 0,11 % peste suport", + where="below", color=GREY, dx=-150, dy=26), + Arrow(FUND2, "piciorul 2: low 87.55, cu 5 cenți mai jos,\ndar cu volum mai mic decât " + "la piciorul 1 —\nvânzarea a slăbit (divergență de volum)", + where="below", color=RED, dx=-40, dy=64), + ], + scale=30.0, gap=22, body_w=12, left=54, width=700, vol_h=70, vol_label="volum", + title="MBT-10, pasul „unde\" — double bottom pe suportul weekly (exemplu construit)", + ) + svg = ch.render() + m = re.search(r'viewBox="0 0 (\d+) (\d+)"', svg) + W, H = int(m.group(1)), int(m.group(2)) + linii = [ + ("Aici nu intri încă: diagrama arată doar ideea de bottom. MBT-10 e ultima piesă, și " + "vine abia la close-ul unei bare.", "#333"), + ("Cifrele 89.90 / 87.30 / 95.20 ale trade-ului sunt din exemplul construit al " + "materialului;", GREY), + ("suportul weekly 87.50, pivotul daily 89.60 și structura barelor sunt ale mele.", GREY), + ] + add = 16 * len(linii) + 10 + svg = svg.replace(f'viewBox="0 0 {W} {H}"', f'viewBox="0 0 {W} {H + add}"') + svg = svg.replace("", "".join( + f'{t}' + for i, (t, c) in enumerate(linii)) + "") + assert "\n\n" not in svg, "linie goală în SVG" + OUT.write_text(svg, encoding="utf-8") + print(f"scris {OUT.name} ({W}x{H + add}); picioare {pct1}% / {pct2}% peste suport, R={R}") diff --git a/gen/strategii/hammer_mbt10_mbt_confluente.svg b/gen/strategii/hammer_mbt10_mbt_confluente.svg new file mode 100644 index 0000000..a935ec1 --- /dev/null +++ b/gen/strategii/hammer_mbt10_mbt_confluente.svg @@ -0,0 +1,75 @@ + + MBT-10, pasul „unde" — double bottom pe suportul weekly (exemplu construit) + + double bottom: 87.60 și 87.55 + + suport weekly 87.50 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + volum + + + piciorul 1: low 87.60, + la 0,11 % peste suport + + + piciorul 2: low 87.55, cu 5 cenți mai jos, + dar cu volum mai mic decât la piciorul 1 — + vânzarea a slăbit (divergență de volum) +Aici nu intri încă: diagrama arată doar ideea de bottom. MBT-10 e ultima piesă, și vine abia la close-ul unei bare.Cifrele 89.90 / 87.30 / 95.20 ale trade-ului sunt din exemplul construit al materialului;suportul weekly 87.50, pivotul daily 89.60 și structura barelor sunt ale mele. \ No newline at end of file diff --git a/gen/strategii/hammer_mbt10_mbt_ppd.py b/gen/strategii/hammer_mbt10_mbt_ppd.py new file mode 100644 index 0000000..20116a6 --- /dev/null +++ b/gen/strategii/hammer_mbt10_mbt_ppd.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python3 +"""MBT-10 — singura bucată reconstituibilă: close peste pivotul daily. + +Aceleași bare ca la pasul „unde" (importate din `hammer_mbt10_mbt_confluente`), +tăiate în jurul trigger-ului. Bara 18 atinge pivotul 89.60 în timpul zilei, +dar se închide sub el: nu e semnal. Bara 19 se închide la 89.90, peste pivot: +aici intri, la close. + +Rulare: python3 gen/strategii/hammer_mbt10_mbt_ppd.py +""" +import re +import sys; sys.path.insert(0, "/workspace/atm-curs-dl") +sys.path.insert(0, "/workspace/atm-curs-dl/gen/strategii") +from pathlib import Path +from hammer_mbt10_mbt_confluente import CLOSE_I, INTRARE, PPD, SUP_W, serie +from svg_candles import Arrow, Chart, Level, Zone + +OUT = Path(__file__).with_suffix(".svg") +GREEN, RED, BLUE, ORANGE, GREY = "#188a3e", "#c00", "#1a5fb4", "#b06000", "#555" + +cds = serie() +I0, I1 = 9, 22 +w = cds[I0:I1 + 1] +sh = lambda i: i - I0 # noqa: E731 +NU, DA = CLOSE_I - 1, CLOSE_I + +assert cds[NU].h > PPD > cds[NU].c, "bara 18 nu mai e contraexemplul așteptat" +assert cds[DA].c > PPD and round(cds[DA].c, 2) == INTRARE +assert 0 <= sh(NU) < len(w) and sh(DA) < len(w), "fereastra nu prinde cele două bare" +assert max(c.c for c in cds[I0:DA]) < PPD, "o bară din fereastră închide deja peste pivot" + +ch = Chart( + candles=w, + levels=[Level(PPD, "pivotul daily 89.60", BLUE), + Level(SUP_W, "suport weekly 87.50", GREY, label_at="left")], + zones=[Zone(sh(NU), sh(DA), "cele două zile care se compară", ORANGE)], + arrows=[ + Arrow(sh(NU), "a atins 89.70 în timpul zilei,\ndar s-a închis la 89.40, sub pivot:\nnu e semnal", + where="below", color=RED, dx=-190, dy=40), + Arrow(sh(DA), "s-a închis la 89.90, peste pivot:\naici intri, la close-ul zilei", + where="above", color=GREEN, dx=-70, dy=-20), + ], + scale=44.0, gap=26, body_w=13, left=64, width=680, vol_h=60, vol_label="volum", + title="MBT-10 — condiția de preț: close peste pivotul daily (exemplu construit)", +) +svg = ch.render() +m = re.search(r'viewBox="0 0 (\d+) (\d+)"', svg) +W, H = int(m.group(1)), int(m.group(2)) +linii = [ + ("Pivotul daily se recalculează în fiecare zi din maximul, minimul și închiderea zilei " + "precedente;", "#333"), + ("linia desenată e valoarea din ziua intrării, 89.60, nu un nivel fix.", "#333"), + ("Asta e tot ce se poate verifica din MBT-10 fără indicatorul proprietar — „prima " + "componentă\" din el,", GREY), + ("cum îi spune materialul; restul formulei nu e publicat nicăieri.", GREY), +] +add = 16 * len(linii) + 10 +svg = svg.replace(f'viewBox="0 0 {W} {H}"', f'viewBox="0 0 {W} {H + add}"') +svg = svg.replace("", "".join( + f'{t}' + for i, (t, c) in enumerate(linii)) + "") +assert "\n\n" not in svg, "linie goală în SVG" +OUT.write_text(svg, encoding="utf-8") +print(f"scris {OUT.name} ({W}x{H + add})") diff --git a/gen/strategii/hammer_mbt10_mbt_ppd.svg b/gen/strategii/hammer_mbt10_mbt_ppd.svg new file mode 100644 index 0000000..d1c1388 --- /dev/null +++ b/gen/strategii/hammer_mbt10_mbt_ppd.svg @@ -0,0 +1,62 @@ + + MBT-10 — condiția de preț: close peste pivotul daily (exemplu construit) + + cele două zile care se compară + + pivotul daily 89.60 + + suport weekly 87.50 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + volum + + + a atins 89.70 în timpul zilei, + dar s-a închis la 89.40, sub pivot: + nu e semnal + + + s-a închis la 89.90, peste pivot: + aici intri, la close-ul zilei +Pivotul daily se recalculează în fiecare zi din maximul, minimul și închiderea zilei precedente;linia desenată e valoarea din ziua intrării, 89.60, nu un nivel fix.Asta e tot ce se poate verifica din MBT-10 fără indicatorul proprietar — „prima componentă" din el,cum îi spune materialul; restul formulei nu e publicat nicăieri. \ No newline at end of file