Buy hammer: forma (patru candele cu aceeasi inaltime, DA/limita/NU), trend (aceeasi bara dupa scadere e hammer, dupa urcare e hanging man — verificat prin assert), locatie, trigger in trei panouri, stop, iesire, si oglinda pe short obtinuta prin oglindire bara cu bara. MBT-10: confluente si close peste pivotul daily. Doua serii partajate (bh_ansamblu si mbt_confluente expun serie()), ca sa nu existe doua variante ale aceluiasi trade. Capitolul se imparte in doua proceduri separate, nu una amestecata: partea A buy hammer in 5 pasi cu oglinda pe short, partea B MBT-10 in 5 pasi, cu nivelul 1 comun. Textul e neatins — agentul a atins pragul de context. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ThN7ixZrowca8aTGrK1MUJ
155 lines
7.0 KiB
Python
155 lines
7.0 KiB
Python
#!/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'<text x="{x:.1f}" y="{yy:.1f}" fill="{color}" text-anchor="{anchor}"{style}>{s}</text>')
|
|
|
|
|
|
def zone(x0, x1, y0, y1, label, color):
|
|
t(f'<rect x="{x0:.1f}" y="{y0:.1f}" width="{x1 - x0:.1f}" height="{y1 - y0:.1f}" '
|
|
f'fill="{color}" fill-opacity="0.10" stroke="{color}" stroke-width="1"/>')
|
|
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'<line x1="{x1:.1f}" y1="{y1:.1f}" x2="{bx:.1f}" y2="{by:.1f}" '
|
|
f'stroke="{color}" stroke-width="1.6"/>')
|
|
t(f'<polygon points="{x2:.1f},{y2:.1f} {bx - 4 * uy:.1f},{by + 4 * ux:.1f} '
|
|
f'{bx + 4 * uy:.1f},{by - 4 * ux:.1f}" fill="{color}"/>')
|
|
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'<line x1="{XS[0] - 100}" y1="{y(p):.1f}" x2="{W - 24}" y2="{y(p):.1f}" '
|
|
f'stroke="#bbb" stroke-dasharray="4 3"/>')
|
|
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'<line x1="{XS[0] - 100}" y1="{y(PRAG_SUS):.1f}" x2="{W - 24}" y2="{y(PRAG_SUS):.1f}" '
|
|
f'stroke="{BLUE}" stroke-width="1.2" stroke-dasharray="2 3"/>')
|
|
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'<line x1="{x}" y1="{y(h):.1f}" x2="{x}" y2="{y(l):.1f}" stroke="#1a1a1a" stroke-width="1.4"/>')
|
|
top, bot = y(max(o, c)), y(min(o, c))
|
|
t(f'<rect x="{x - BODY_W / 2:.1f}" y="{top:.1f}" width="{BODY_W}" '
|
|
f'height="{max(bot - top, 1.6):.1f}" fill="{color}"/>')
|
|
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'<svg viewBox="0 0 {W} {height:.0f}" xmlns="http://www.w3.org/2000/svg" '
|
|
f'style="width:100%;height:auto;font:12px sans-serif">\n '
|
|
+ "\n ".join(out) + "\n</svg>\n")
|
|
assert "\n\n" not in svg
|
|
OUT.write_text(svg, encoding="utf-8")
|
|
print(f"scris {OUT.name} ({W}x{height:.0f})")
|