17_STRATEGII/10_dhrt: cele sase diagrame, plus doua unelte reparate
Diagramele capitolului 10 sunt gata (un singur generator pentru toate, aceeasi serie, deci o cifra schimbata cade peste tot). Textul capitolului nu e scris - agentul a atins pragul de context acolo. Doua bug-uri in unelte, gasite de el: - substituie.py avea glob-ul [0A][1-8]_*.md, deci sarea tacut peste capitolele cu doua cifre (09, 10). Capitolul parea doar sa n-aiba marcaje. - eticheta_libera presupunea text centrat cand eticheta n-are text-anchor. In SVG, fara anchor inseamna aliniat la stanga, deci etichetele de Level cu label_at=left erau modelate cu jumatate de latime prea mult la stanga si dadeau alarme false. L-a costat trei iteratii. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ThN7ixZrowca8aTGrK1MUJ
This commit is contained in:
570
gen/strategii/dhrt.py
Normal file
570
gen/strategii/dhrt.py
Normal file
@@ -0,0 +1,570 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Diagramele pentru summaries/17_STRATEGII/10_dhrt.md (strategia 8, DHRT).
|
||||
|
||||
Un singur generator pentru toate cele șase diagrame ale capitolului: toate
|
||||
desenează același trade, pe aceeași serie, deci o cifră schimbată într-un loc
|
||||
trebuie să cadă peste tot — de asta nu sunt șase fișiere.
|
||||
|
||||
REGULA DE CIFRE, din care iese tot restul fișierului: în lecția 8 (DHRT)
|
||||
singurul preț rostit e **51** (ținta de pe XLF, varianta short — transcrierea
|
||||
`05_7_STRATEGIA_8_DHRTMECANICĂ.txt:285`). Capitolul nu are diagramă cu XLF,
|
||||
deci **niciun preț de pe diagramele de aici nu e al cursului**: toate vin din
|
||||
exemplul construit de mine pe geometria lui, iar fiecare diagramă o declară
|
||||
în titlu („exemplu construit"). Cifrele sunt aceleași cu ale sumarizării
|
||||
lecției (`summaries/06_MODUL_5/05_strategia_8_dhrt.md`), ca să nu existe două
|
||||
versiuni ale aceluiași trade în repo.
|
||||
|
||||
Geometria exemplului:
|
||||
cap 52.00 · nivelul umerilor 58.50 · H = 6.50
|
||||
treimea de jos: 52.00 .. 54.17 — stopul stă în ea, 54.17 e cel mai sus
|
||||
la care are voie (riscul cel mai mic permis)
|
||||
linia gâtului 63.00, trasată din interiorul gâtului (vârfurile ating 63.30)
|
||||
intrare 65.00 (close-ul candelei de breakout) · R = 10.83 = 16,7% din preț
|
||||
TP1 = 65.00 + 10.83 = 75.83 (distanța stopului)
|
||||
TP2 = 63.00 + 11.00 = 74.00 (proiecția gâtului) — sub TP1, ordinea nu e fixă
|
||||
|
||||
Rulează: .venv/bin/python gen/strategii/dhrt.py
|
||||
"""
|
||||
import re
|
||||
import sys
|
||||
from dataclasses import replace
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, "/workspace/atm-curs-dl")
|
||||
sys.path.insert(0, "/workspace/atm-curs-dl/gen")
|
||||
from eticheta_libera import eticheta_libera # noqa: E402
|
||||
from svg_candles import Arrow, Candle, Chart, Level, Mark, Zone, from_closes # noqa: E402
|
||||
|
||||
OUT = Path("/workspace/atm-curs-dl/gen/strategii")
|
||||
GREEN, RED, BLUE, PURPLE, ORANGE, GREY, BLACK = (
|
||||
"#188a3e", "#c00", "#1a5fb4", "#6a3d9a", "#b06000", "#555", "#333")
|
||||
|
||||
CAP, UMAR = 52.00, 58.50 # low-ul capului, nivelul umerilor
|
||||
H = round(UMAR - CAP, 2) # 6.50 — înălțimea cap → umăr drept
|
||||
TREIME = round(CAP + H / 3, 2) # 54.17 — marginea de sus a treimii
|
||||
GAT, GAT_VARF = 63.00, 63.30 # linia gâtului / vârfurile zonei
|
||||
ENTRY = 65.00
|
||||
R = round(ENTRY - TREIME, 2) # 10.83
|
||||
TP1 = round(ENTRY + R, 2) # 75.83
|
||||
TP2 = round(GAT + (GAT - CAP), 2) # 74.00
|
||||
CONS_SUS, CONS_JOS = GAT_VARF, UMAR # dreptunghiul umărului drept
|
||||
CONS_JUM = round((CONS_SUS + CONS_JOS) / 2, 2) # 60.90 — jumătatea consolidării
|
||||
|
||||
assert H == 6.50 and TREIME == 54.17 and R == 10.83 and TP1 == 75.83 and TP2 == 74.00
|
||||
assert TP2 < TP1, "pe exemplul ăsta TP1 iese peste TP2 — asta ilustrează capitolul"
|
||||
assert round(R / ENTRY * 100, 1) == 16.7, round(R / ENTRY * 100, 1)
|
||||
assert CONS_JUM == 60.90, CONS_JUM
|
||||
|
||||
SVG = {}
|
||||
|
||||
|
||||
# ------------------------------------------------------------------ utilitare
|
||||
def panou(ch: Chart, w: int):
|
||||
"""Corpul unui Chart, fără <svg>, plus înălțimea lui — pentru stivuire.
|
||||
|
||||
`_render()`, nu `render()`: al doilea restaurează `top`, iar liniile
|
||||
desenate de mână după el ar ieși deplasate față de candele.
|
||||
"""
|
||||
svg = ch._render()
|
||||
m = re.search(r'viewBox="0 0 ([\d.]+) ([\d.]+)"', svg)
|
||||
assert m and float(m.group(1)) <= w, f"panoul iese din {w}px: {m.group(1) if m else '?'}"
|
||||
return svg[svg.index(">") + 1:svg.rindex("</svg>")], float(m.group(2))
|
||||
|
||||
|
||||
def nivel(ch, w, price, texte, color, below=False, la_stanga=True, x0=24):
|
||||
"""Linie punctată + etichetă pe mai multe rânduri, desenată de mână."""
|
||||
y = ch._y(price)
|
||||
out = [f'<line x1="{x0}" y1="{y:.1f}" x2="{w - 14}" y2="{y:.1f}" stroke="{color}" '
|
||||
f'stroke-width="1.5" stroke-dasharray="4 3"/>']
|
||||
for k, s in enumerate(texte):
|
||||
ty = (y + 15 + 14 * k) if below else (y - 6 - 14 * (len(texte) - 1 - k))
|
||||
tx, anchor = (x0 + 6, "start") if la_stanga else (w - 24, "end")
|
||||
out.append(f'<text x="{tx}" y="{ty:.1f}" fill="{color}" text-anchor="{anchor}" '
|
||||
f'style="font-weight:bold">{s}</text>')
|
||||
return out
|
||||
|
||||
|
||||
def zona(ch, i0, i1, label, color, sus=None, jos=None, lift=0.0, pad=10):
|
||||
cds = ch.candles[i0:i1 + 1]
|
||||
x0, x1 = ch._cx(i0) - pad, ch._cx(i1) + pad
|
||||
y0 = ch._y(sus if sus is not None else max(c.h for c in cds)) - 8 - lift
|
||||
y1 = ch._y(jos if jos is not None else min(c.l for c in cds)) + 8
|
||||
return [f'<rect x="{x0:.1f}" y="{y0:.1f}" width="{x1 - x0:.1f}" height="{y1 - y0:.1f}" '
|
||||
f'fill="{color}" fill-opacity="0.08" stroke="{color}" stroke-width="1"/>',
|
||||
f'<text x="{x0:.1f}" y="{y0 - 7:.1f}" fill="{color}" '
|
||||
f'style="font-weight:bold">{label}</text>']
|
||||
|
||||
|
||||
def ancorate(ch: Chart) -> set:
|
||||
"""Etichetele lipite prin construcție de ceva desenat: nivelurile (y − 6 față
|
||||
de linia lor) și textele de săgeată (stau la capătul propriei cozi). Pentru
|
||||
ele se verifică doar candelele, nu și segmentele."""
|
||||
out = {lv.text for lv in ch.levels} | {mk.text for mk in ch.marks}
|
||||
for a in ch.arrows:
|
||||
out |= set(a.text.split("\n"))
|
||||
return out
|
||||
|
||||
|
||||
def stiva(nume, w, panouri, captions, pe_linie=()):
|
||||
"""Lipește panouri unul sub altul și verifică etichetele (pânză + suprapuneri)."""
|
||||
parts, y, bodies = [], 0.0, []
|
||||
for ch, extra in panouri:
|
||||
body, h = panou(ch, w)
|
||||
# extra se construiește DUPĂ randare: `_y()` are nevoie de `_pmax`,
|
||||
# iar `_render()` mută `top` — altfel desenul de mână iese deplasat.
|
||||
extra = list(extra(ch) if callable(extra) else extra)
|
||||
parts.append(f'<g transform="translate(0,{y:.0f})">{"".join(extra)}{body}</g>')
|
||||
bodies.append((body + "".join(extra), y, ancorate(ch) | set(pe_linie)))
|
||||
y += h
|
||||
cap_top = y + 18
|
||||
height = cap_top + 17 * (len(captions) - 1) + 8
|
||||
head = (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">')
|
||||
caps = []
|
||||
for k, c in enumerate(captions):
|
||||
assert "<" not in c and 24 + len(c) * 6.3 <= w, f"{nume}: caption iese din pânză: {c!r}"
|
||||
caps.append(f'<text x="24" y="{cap_top + 17 * k:.0f}" fill="{GREY}">{c}</text>')
|
||||
svg = "\n ".join([head, *parts, *caps, "</svg>"]).replace("\n </svg>", "\n</svg>")
|
||||
verifica(nume, svg, w, height, bodies)
|
||||
SVG[nume] = svg
|
||||
return svg
|
||||
|
||||
|
||||
def singur(nume, ch, w, extra=(), captions=(), pe_linie=()):
|
||||
"""Un singur panou, pe aceeași cale de verificare ca stiva."""
|
||||
return stiva(nume, w, [(ch, extra)], list(captions), pe_linie)
|
||||
|
||||
|
||||
TEXT = re.compile(r'<text x="([-\d.]+)" y="([-\d.]+)"([^>]*)>(.*?)</text>', re.S)
|
||||
|
||||
|
||||
def verifica(nume, svg, w, height, bodies):
|
||||
"""Etichetele stau în pânză, nu se suprapun între ele și nu stau peste desen.
|
||||
|
||||
`pe_linie` = etichetele de nivel: alea stau lipite de linia lor prin
|
||||
construcție (y − 6), deci pentru ele se verifică doar candelele.
|
||||
"""
|
||||
assert "\n\n" not in svg, f"{nume}: linie goală în SVG (markdown rupe blocul)"
|
||||
for body, off, pe_linie in bodies:
|
||||
toate = {m.group(4) for m in TEXT.finditer(body)}
|
||||
eticheta_libera(body, nume, toate - pe_linie, rects=True)
|
||||
eticheta_libera(body, nume, toate & pe_linie, rects=True, segmente=False)
|
||||
et = []
|
||||
for m in TEXT.finditer(body):
|
||||
x, y, attrs, s = float(m.group(1)), float(m.group(2)) + off, m.group(3), m.group(4)
|
||||
bold = "bold" in attrs
|
||||
ww = len(s) * 12 * (0.62 if bold else 0.52)
|
||||
left = (x - ww if 'anchor="end"' in attrs
|
||||
else x - ww / 2 if 'anchor="middle"' in attrs else x)
|
||||
et.append((left, left + ww, y, s))
|
||||
assert left >= -2 and left + ww <= w + 2, f"{nume}: text iese din pânză: {s!r}"
|
||||
assert 0 <= y <= height, f"{nume}: text în afara înălțimii: {s!r} (y={y:.0f})"
|
||||
for i in range(len(et)):
|
||||
for j in range(i + 1, len(et)):
|
||||
li, ri, yi, si = et[i]
|
||||
lj, rj, yj, sj = et[j]
|
||||
if abs(yi - yj) < 12 and li < rj - 4 and lj < ri - 4:
|
||||
raise AssertionError(f"{nume}: texte suprapuse: {si!r} / {sj!r}")
|
||||
|
||||
|
||||
def check(cds, i0=None):
|
||||
"""h/l cuprind o/c, și open = close-ul precedentei (serie fără goluri)."""
|
||||
for i, c in enumerate(cds):
|
||||
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"
|
||||
if i:
|
||||
assert abs(c.o - cds[i - 1].c) < 1e-9, f"open[{i}] != close[{i-1}]"
|
||||
return cds
|
||||
|
||||
|
||||
|
||||
def felie(cds, a, b):
|
||||
"""Copie a barelor a..b-1 — ca o diagramă să nu mute culoarea alteia."""
|
||||
return [replace(c) for c in cds[a:b]]
|
||||
|
||||
|
||||
def bracket(ch, x, p_jos, p_sus, texte, color, dx=8, mijloc=True):
|
||||
"""Acoladă verticală între două prețuri, cu text la mijloc (sau sus)."""
|
||||
y1, y2 = ch._y(p_sus), ch._y(p_jos)
|
||||
out = [f'<polyline points="{x + 5:.1f},{y1:.1f} {x:.1f},{y1:.1f} {x:.1f},{y2:.1f} '
|
||||
f'{x + 5:.1f},{y2:.1f}" fill="none" stroke="{color}" stroke-width="1.5"/>']
|
||||
y0 = (y1 + y2) / 2 + 4 if mijloc else y1 + 12
|
||||
for k, t in enumerate(texte):
|
||||
out.append(f'<text x="{x + dx:.1f}" y="{y0 + 14 * k - 7 * (len(texte) - 1):.1f}" '
|
||||
f'fill="{color}" style="font-weight:bold">{t}</text>')
|
||||
return out
|
||||
|
||||
|
||||
# --------------------------------------------------------------- seria de bază
|
||||
# Patternul întreg, o singură dată: toate diagramele pornesc din `serie()`.
|
||||
# 0-5 coborârea spre umărul stâng 6 umărul stâng (low 58.50)
|
||||
# 7-10 urcarea în gât (high 63.30) 11-15 coborârea în cap
|
||||
# 16 capul (low 52.00) 17-22 urcarea înapoi în gât
|
||||
# 23-32 consolidarea = umărul drept 33 candela de breakout (close 65.00)
|
||||
# 34-43 drumul spre ținte
|
||||
CLOSES = [
|
||||
65.80, 64.10, 62.60, 61.40, 60.10, 59.00, # 0-5
|
||||
58.70, # 6 umărul stâng
|
||||
60.20, 61.60, 62.70, 63.05, # 7-10 gâtul, stânga
|
||||
61.30, 59.20, 57.00, 55.10, 53.20, # 11-15
|
||||
52.30, # 16 capul
|
||||
54.60, 56.90, 58.80, 60.70, 62.20, 63.00, # 17-22 gâtul, dreapta
|
||||
61.10, 59.30, 58.80, 60.40, 62.10, 60.90, # 23-28 consolidarea
|
||||
59.10, 60.30, 59.40, 58.70, # 29-32 consolidarea
|
||||
65.00, # 33 breakout (marubozu)
|
||||
66.40, 68.10, 70.30, 72.20, 74.10, 75.90, # 34-39
|
||||
75.20, 76.40, 75.60, 76.10, # 40-43
|
||||
]
|
||||
WICKS = {6: (0.35, 0.20), 10: (0.25, 0.30), 16: (0.40, 0.30), 22: (0.30, 0.25),
|
||||
24: (0.25, 0.60), 29: (0.30, 0.60), 32: (0.30, 0.20), 33: (0.30, 0.30)}
|
||||
VOLS = [44, 46, 43, 45, 42, 44,
|
||||
66, # umărul stâng — vârf de volum (stâng, pe volum)
|
||||
48, 45, 43, 44,
|
||||
46, 48, 52, 56, 60,
|
||||
82, # capul pe volum
|
||||
50, 46, 44, 43, 45, 44,
|
||||
42, 40, 58, 44, 46, 43,
|
||||
41, 46, 44, 42, # 29-32: volum care se stinge în consolidare
|
||||
96, # breakout: peste vârful anterior de volum
|
||||
70, 62, 58, 56, 54, 52,
|
||||
48, 50, 46, 47]
|
||||
|
||||
|
||||
def serie():
|
||||
cds = from_closes(CLOSES, wick=0.30,
|
||||
wicks=[WICKS.get(i, (0.30, 0.30)) for i in range(len(CLOSES))],
|
||||
vols=[float(v) for v in VOLS], start=66.90)
|
||||
cds[33] = Candle(58.70, 65.30, 58.50, ENTRY, vol=float(VOLS[33])) # marubozu de breakout
|
||||
check(cds)
|
||||
# cifrele pe care le afirmă etichetele, verificate pe serie
|
||||
assert round(cds[6].l, 2) == UMAR, cds[6].l
|
||||
assert round(cds[16].l, 2) == CAP, cds[16].l
|
||||
assert round(max(c.h for c in cds[7:11]), 2) == GAT_VARF, max(c.h for c in cds[7:11])
|
||||
assert round(max(c.h for c in cds[17:33]), 2) == GAT_VARF, max(c.h for c in cds[17:33])
|
||||
assert round(min(c.l for c in cds[23:33]), 2) == UMAR, min(c.l for c in cds[23:33])
|
||||
assert cds[16].l < cds[6].l and cds[16].l < min(c.l for c in cds[23:33]), "capul nu e cel mai jos"
|
||||
assert abs(cds[33].c - ENTRY) < 1e-9 and cds[33].c > GAT_VARF, "breakout-ul nu trece gâtul"
|
||||
assert cds[33].c - cds[33].o >= 6.0, "candela de breakout nu e marubozu"
|
||||
assert max(VOLS[:33]) == 82 and VOLS[33] == 96, "volumul de breakout nu e peste vârful anterior"
|
||||
assert max(c.h for c in cds[34:]) >= TP1, "seria nu atinge TP1"
|
||||
primul_tp2 = next(i for i in range(34, len(cds)) if cds[i].h >= TP2)
|
||||
primul_tp1 = next(i for i in range(34, len(cds)) if cds[i].h >= TP1)
|
||||
assert primul_tp2 < primul_tp1, "TP2 (74.00) trebuie atins înaintea lui TP1 (75.83)"
|
||||
assert min(c.l for c in cds[34:]) > TREIME, "stopul ar fi fost lovit după intrare"
|
||||
return cds
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- 1. ansamblu
|
||||
def d_ansamblu():
|
||||
cds = serie()
|
||||
w = 740
|
||||
niveluri = [
|
||||
Level(TP1, f"TP1 {TP1:.2f} = distanța stopului", BLUE, label_at="left"),
|
||||
Level(TP2, f"TP2 {TP2:.2f} = proiecția gâtului", BLUE, label_at="left"),
|
||||
Level(ENTRY, f"intrare {ENTRY:.2f}", GREEN),
|
||||
Level(GAT, f"linia gâtului {GAT:.2f}", PURPLE),
|
||||
Level(TREIME, f"SL {TREIME:.2f} (cel mai sus permis)", RED, label_at="left"),
|
||||
]
|
||||
ch = Chart(
|
||||
candles=cds,
|
||||
levels=niveluri,
|
||||
zones=[Zone(23, 32, "umărul drept = consolidarea", GREEN, p_top=GAT_VARF + 0.3)],
|
||||
arrows=[
|
||||
Arrow(16, "capul 52.00, pe suport", where="below", color=BLACK, dx=-20, dy=16),
|
||||
Arrow(6, "umărul stâng 58.50", where="below", color=BLACK, dx=-14, dy=46),
|
||||
Arrow(33, "AICI INTRI: close 65.00,\npeste consolidare și peste gât",
|
||||
where="below", price=ENTRY, color=GREEN, dx=-96, dy=78),
|
||||
],
|
||||
scale=15.0, gap=15, body_w=9, left=58, width=w, vol_h=66,
|
||||
vol_label="volum", top=34,
|
||||
title="DHRT — tot trade-ul (exemplu construit: niciun preț de aici nu e rostit în lecție)",
|
||||
)
|
||||
singur("ansamblu", ch, w, (), [
|
||||
"Serie construită (open = close-ul barei anterioare), la scară, pe geometria lecției.",
|
||||
f"cap {CAP:.2f} · umeri {UMAR:.2f} · H {H:.2f} · gât {GAT:.2f} · "
|
||||
f"intrare {ENTRY:.2f} · SL {TREIME:.2f} · R {R:.2f} (16,7% din preț).",
|
||||
f"TP1 ({TP1:.2f}) iese peste TP2 ({TP2:.2f}) — ordinea lor nu e fixă.",
|
||||
], pe_linie=[lv.text for lv in niveluri])
|
||||
|
||||
|
||||
|
||||
# -------------------------------------------------------------------- 4. stop
|
||||
def d_stop():
|
||||
"""H = cap → nivelul umărului drept, împărțit în trei. Stopul stă în treimea
|
||||
de jos; 54.17 e marginea ei de sus, adică cel mai sus la care are voie."""
|
||||
cds = felie(serie(), 12, 36) # capul, urcarea, consolidarea, breakout-ul
|
||||
w = 760
|
||||
i_cap, i_brk = 16 - 12, 33 - 12
|
||||
doua_treimi = round(CAP + 2 * H / 3, 2)
|
||||
assert doua_treimi == 56.33, doua_treimi
|
||||
niveluri = [
|
||||
Level(ENTRY, f"intrare {ENTRY:.2f} = close-ul candelei de breakout", GREEN,
|
||||
label_at="left"),
|
||||
Level(UMAR, f"umărul drept {UMAR:.2f}", BLACK, label_at="left"),
|
||||
Level(doua_treimi, f"{doua_treimi:.2f} = 2/3", GREY, dash="2 4"),
|
||||
Level(TREIME, f"SL {TREIME:.2f} = 1/3 din H", RED, label_at="left"),
|
||||
Level(CAP, f"capul {CAP:.2f}", BLACK, label_at="left"),
|
||||
]
|
||||
ch = Chart(
|
||||
candles=cds,
|
||||
levels=niveluri,
|
||||
arrows=[
|
||||
Arrow(i_cap, f"{TREIME:.2f} e cel mai SUS la care are voie stopul\n"
|
||||
f"(riscul cel mai mic permis); mai jos, oricât",
|
||||
where="below", price=TREIME, color=RED, dx=-30, dy=72),
|
||||
],
|
||||
scale=25.0, gap=20, body_w=11, left=200, width=w,
|
||||
title="Stopul: treimea de jos din H (exemplu construit — cifrele nu sunt rostite în lecție)",
|
||||
)
|
||||
|
||||
def extra(ch):
|
||||
return (bracket(ch, 160, CAP, UMAR, [f"H = {H:.2f}", "cap → umăr"], BLACK, dx=6)
|
||||
+ bracket(ch, ch._cx(i_brk) + 52, TREIME, ENTRY,
|
||||
[f"R = {R:.2f}"], RED, dx=8))
|
||||
|
||||
singur("stop", ch, w, extra, [
|
||||
"H se măsoară de la low-ul capului la nivelul umărului drept — nu la linia gâtului.",
|
||||
f"{CAP:.2f} + {H:.2f}/3 = {TREIME:.2f}. Peste {TREIME:.2f} stopul nu are voie; sub, oricât.",
|
||||
f"R se măsoară de la intrare ({ENTRY:.2f}) la stop, nu de la nivelul umărului.",
|
||||
], pe_linie=([lv.text for lv in niveluri]
|
||||
+ [f"H = {H:.2f}", "cap → umăr", f"R = {R:.2f}"]))
|
||||
|
||||
|
||||
# ------------------------------------------------------------------- 5. ținte
|
||||
def d_tinte():
|
||||
"""TP1 = distanța stopului; TP2 = proiecția gâtului. Linia gâtului se trage
|
||||
din interiorul gâtului (63.00), nu prin vârfuri (63.30) și nu ca medie."""
|
||||
cds = felie(serie(), 16, 44)
|
||||
w = 760
|
||||
i_brk, i_tp2, i_tp1 = 33 - 16, 38 - 16, 39 - 16
|
||||
inaltime = round(GAT - CAP, 2)
|
||||
assert inaltime == 11.00 and round(GAT + inaltime, 2) == TP2
|
||||
niveluri = [
|
||||
Level(TP1, f"TP1 {TP1:.2f} = intrarea + R ({R:.2f})", BLUE, label_at="left"),
|
||||
Level(TP2, f"TP2 {TP2:.2f} = gâtul + {inaltime:.2f}", BLUE, label_at="left"),
|
||||
Level(ENTRY, f"intrare {ENTRY:.2f}", GREEN, label_at="left"),
|
||||
Level(GAT, f"linia gâtului {GAT:.2f}", PURPLE),
|
||||
Level(CAP, f"capul {CAP:.2f}", BLACK),
|
||||
]
|
||||
ch = Chart(
|
||||
candles=cds,
|
||||
levels=niveluri,
|
||||
arrows=[
|
||||
Arrow(6, f"vârfurile gâtului ating {GAT_VARF:.2f}; linia se trage din\n"
|
||||
f"INTERIORUL gâtului, la {GAT:.2f} — nu e media lor",
|
||||
where="above", price=GAT_VARF, color=PURPLE, dx=-30, dy=-50),
|
||||
Arrow(i_tp2, f"TP2 ({TP2:.2f}) vine înaintea lui TP1 ({TP1:.2f})\n"
|
||||
f"pe exemplul ăsta — ordinea nu e fixă",
|
||||
where="below", price=TP2, color=BLUE, dx=-290, dy=30),
|
||||
],
|
||||
scale=15.0, gap=22, body_w=11, left=92, width=w,
|
||||
title="Cele două ținte (exemplu construit — cifrele nu sunt rostite în lecție)",
|
||||
)
|
||||
|
||||
def extra(ch):
|
||||
return (bracket(ch, 58, CAP, GAT, [f"{inaltime:.2f}"], PURPLE, dx=6)
|
||||
+ bracket(ch, ch._cx(i_tp1) + 44, GAT, TP2, [f"{inaltime:.2f}"], BLUE, dx=8))
|
||||
|
||||
singur("tinte", ch, w, extra, [
|
||||
f"TP1 = {ENTRY:.2f} + {R:.2f} = {TP1:.2f}. TP2 = {GAT:.2f} + ({GAT:.2f} − {CAP:.2f}) = {TP2:.2f}.",
|
||||
"Cât de jos tragi linia gâtului mută TP2 cu exact atât — e o alegere a ta, nu o formulă.",
|
||||
"Lecția spune doar «din interiorul gâtului, iau un average»; formulă rostită nu există.",
|
||||
], pe_linie=[lv.text for lv in niveluri] + [f"{inaltime:.2f}"])
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- 3. trigger
|
||||
# Panoul NU: preț care ține consolidarea, dar volum cu semn (stil on balance
|
||||
# volume) care face minime tot mai joase. Seria e construită doar pentru
|
||||
# diagrama asta — niciun preț de aici nu e rostit în lecție.
|
||||
NU_CLOSES = [61.80, 60.20, 61.70, 59.90, 61.90, 60.10, 61.60, 59.80,
|
||||
61.80, 60.00, 61.40, 59.70, 57.40, 56.10]
|
||||
NU_VOLS = [8.0, 5.0, 6.0, -4.0, 3.0, -7.0, 2.0, -10.0,
|
||||
1.0, -13.0, -2.0, -16.0, -9.0, -20.0]
|
||||
|
||||
|
||||
def d_trigger():
|
||||
cds_da = felie(serie(), 12, 37)
|
||||
w = 760
|
||||
i_brk = 33 - 12
|
||||
niv_da = [
|
||||
Level(ENTRY, f"intrare {ENTRY:.2f} = close-ul trigger-ului", GREEN, label_at="left"),
|
||||
Level(GAT, f"linia gâtului {GAT:.2f}", PURPLE, label_at="left"),
|
||||
]
|
||||
ch_da = Chart(
|
||||
candles=cds_da,
|
||||
levels=niv_da,
|
||||
zones=[Zone(23 - 12, 32 - 12, "consolidarea umărului drept", GREEN,
|
||||
p_top=GAT_VARF + 0.3)],
|
||||
arrows=[
|
||||
Arrow(i_brk, "TRIGGER: close peste consolidare ȘI peste gât,\n"
|
||||
"cu volumul peste vârful anterior de volum",
|
||||
where="below", price=ENTRY, color=GREEN, dx=-276, dy=36),
|
||||
],
|
||||
scale=19.0, gap=20, body_w=11, left=60, width=w, vol_h=60,
|
||||
vol_label="volum", title="DA — trigger în umărul drept (exemplu construit)",
|
||||
)
|
||||
|
||||
cds_nu = from_closes(NU_CLOSES, wick=0.35, vols=NU_VOLS, start=62.40)
|
||||
check(cds_nu)
|
||||
minime = [NU_VOLS[i] for i in range(1, len(NU_VOLS) - 1)
|
||||
if NU_VOLS[i] < NU_VOLS[i - 1] and NU_VOLS[i] < NU_VOLS[i + 1]]
|
||||
assert minime == sorted(minime, reverse=True) and len(minime) >= 4, minime
|
||||
assert all(c.l >= UMAR for c in cds_nu[:11]), "prețul trebuie să țină consolidarea"
|
||||
assert cds_nu[-1].c < UMAR, "la final prețul cedează"
|
||||
ch_nu = Chart(
|
||||
candles=cds_nu,
|
||||
levels=[Level(UMAR, f"baza consolidării {UMAR:.2f}", GREY, label_at="left")],
|
||||
arrows=[
|
||||
Arrow(13, "prețul ține consolidarea, dar minimele volumului coboară:\n"
|
||||
"aștepți breakdown, nu intri long",
|
||||
where="below", price=56.20, color=RED, dx=-215, dy=10),
|
||||
],
|
||||
scale=19.0, gap=28, body_w=13, left=72, width=w, vol_h=70, vol_zero=0.0,
|
||||
vol_label="volum cu semn (on balance volume)",
|
||||
title="NU — breakout de preț fără breakout de volum (exemplu construit)",
|
||||
)
|
||||
stiva("trigger", w, [(ch_da, ()), (ch_nu, ())], [
|
||||
"Sus: trigger-ul e candela de breakout; buy doji / buy hammer / buy momentum",
|
||||
"din umărul drept sunt la fel de valide, cu intrarea la close-ul lor.",
|
||||
"Jos: singurul «nu intra» formulat ca regulă mecanică — volumul rupe înaintea prețului.",
|
||||
], pe_linie=[lv.text for lv in niv_da] + [f"baza consolidării {UMAR:.2f}", "0"])
|
||||
|
||||
|
||||
# ------------------------------------------------------------------ 2. setup
|
||||
# Panoul NU: triple bottom la al treilea test, cu volum tot mai mic. Serie
|
||||
# proprie, cu altă scară de preț decât restul capitolului — regulile sunt
|
||||
# aceleași, cifrele nu au legătură cu exemplul construit al trade-ului.
|
||||
TB_CLOSES = [54.50, 53.20, 51.80, 50.40, 50.20, 51.60, 52.80, 51.90, 50.80, 50.30,
|
||||
51.50, 52.40, 51.30, 50.60, 50.25, 51.00, 50.40, 49.60, 48.70]
|
||||
TB_BAZA = 50.00
|
||||
TB_LOWS = (4, 9, 14)
|
||||
TB_VOLS = [40.0, 44.0, 52.0, 60.0, 74.0, 46.0, 42.0, 44.0, 48.0, 54.0,
|
||||
40.0, 38.0, 39.0, 40.0, 38.0, 36.0, 34.0, 44.0, 52.0]
|
||||
|
||||
|
||||
def d_setup():
|
||||
cds_da = felie(serie(), 0, 33)
|
||||
w = 760
|
||||
niv_da = [
|
||||
Level(UMAR, f"pivotul lunii {UMAR:.2f}", BLACK),
|
||||
Level(CAP, f"suportul lunii {CAP:.2f}", BLUE, label_at="left"),
|
||||
]
|
||||
ch_da = Chart(
|
||||
candles=cds_da,
|
||||
levels=niv_da,
|
||||
marks=[Mark(6, "umărul stâng", GREY, "below", dy=14)],
|
||||
zones=[Zone(23, 32, "umărul drept = consolidarea", GREEN, p_top=GAT_VARF + 0.3)],
|
||||
arrows=[
|
||||
Arrow(16, "capul se oprește pe suport, umărul drept pe pivot.\n"
|
||||
"Ajunge unul din două; fără niciunul, setup-ul nu e verificabil.",
|
||||
where="below", price=CAP, color=BLUE, dx=10, dy=22),
|
||||
],
|
||||
scale=15.0, gap=17, body_w=9, left=58, width=w,
|
||||
title="DA — HS răsturnat cu capul și umărul drept pe niveluri (exemplu construit)",
|
||||
)
|
||||
|
||||
cds_nu = from_closes(TB_CLOSES, wick=0.30,
|
||||
wicks=[(0.30, 0.30) if i not in TB_LOWS else
|
||||
(0.25, round(TB_CLOSES[i] - TB_BAZA, 2)) for i in range(19)],
|
||||
vols=TB_VOLS, start=55.60)
|
||||
check(cds_nu)
|
||||
for i in TB_LOWS:
|
||||
assert round(cds_nu[i].l, 2) == TB_BAZA, (i, cds_nu[i].l)
|
||||
assert TB_VOLS[4] > TB_VOLS[9] > TB_VOLS[14], "volumul trebuie să scadă la fiecare picior"
|
||||
assert cds_nu[-1].c < TB_BAZA, "al patrulea test cedează"
|
||||
ch_nu = Chart(
|
||||
candles=cds_nu,
|
||||
levels=[Level(TB_BAZA, f"baza {TB_BAZA:.2f}, testată de trei ori", GREY)],
|
||||
marks=[Mark(4, "1", GREY, "below"), Mark(9, "2", GREY, "below"),
|
||||
Mark(14, "3", RED, "below")],
|
||||
arrows=[
|
||||
Arrow(14, "al treilea test, cu volum mai mic la fiecare picior:\n"
|
||||
"șansele se întorc spre breakdown — nu intri",
|
||||
where="below", price=TB_BAZA, color=RED, dx=-90, dy=42),
|
||||
],
|
||||
scale=38.0, gap=26, body_w=13, left=70, width=w, vol_h=56,
|
||||
vol_label="volum", title="NU — triple bottom la al treilea test (altă scară de preț)",
|
||||
)
|
||||
stiva("setup", w, [(ch_da, ()), (ch_nu, ())], [
|
||||
"Sus: filtrul de la pasul 1 — capul și/sau umărul drept pe un suport, pivot sau PPA.",
|
||||
"Simetria umerilor e analiza autorului, nu un prag: «depinde cum măsori».",
|
||||
"Jos: motivul e citat de autor din literatură, nu din testele lui.",
|
||||
], pe_linie=[lv.text for lv in niv_da]
|
||||
+ [f"baza {TB_BAZA:.2f}, testată de trei ori"])
|
||||
|
||||
|
||||
# ----------------------------------------------------------------- 6. retest
|
||||
# „Locul crimei" = dreptunghiul din care s-a plecat. Ambele panouri pornesc din
|
||||
# aceeași consolidare și din aceeași candelă de breakout; diferă doar adâncimea
|
||||
# întoarcerii. Serii construite, pe aceeași geometrie ca restul capitolului.
|
||||
CONS = [61.00, 59.20, 62.90, 59.00, 61.50, 58.80]
|
||||
CONS_W = [(0.30, 0.30), (0.30, 0.50), (0.40, 0.30), (0.30, 0.50),
|
||||
(0.30, 0.30), (0.30, 0.30)]
|
||||
RETEST_DA = [64.20, 62.40, 61.40, 61.20, 62.30, 64.10, 66.00, 67.60]
|
||||
RETEST_NU = [64.00, 62.10, 60.20, 58.20, 56.80, 55.40]
|
||||
|
||||
|
||||
def _cu_retest(cozi):
|
||||
cds = from_closes(CONS, wicks=CONS_W, start=62.20)
|
||||
cds.append(Candle(CONS[-1], 65.30, 58.60, ENTRY))
|
||||
cds += from_closes(cozi, wick=0.30, start=ENTRY)
|
||||
check(cds)
|
||||
assert round(max(c.h for c in cds[:6]), 2) == CONS_SUS, max(c.h for c in cds[:6])
|
||||
assert round(min(c.l for c in cds[:6]), 2) == CONS_JOS, min(c.l for c in cds[:6])
|
||||
return cds
|
||||
|
||||
|
||||
def d_retest():
|
||||
w = 740
|
||||
niv = lambda: [
|
||||
Level(ENTRY, f"breakout {ENTRY:.2f}", GREEN),
|
||||
Level(CONS_SUS, f"vârful consolidării {CONS_SUS:.2f}", GREY, dash="2 4"),
|
||||
Level(CONS_JUM, f"1/2 din consolidare {CONS_JUM:.2f}", BLUE),
|
||||
Level(CONS_JOS, f"baza consolidării {CONS_JOS:.2f}", RED),
|
||||
]
|
||||
cds_da = _cu_retest(RETEST_DA)
|
||||
fund = round(min(c.l for c in cds_da[7:]), 2)
|
||||
assert fund == CONS_JUM, fund
|
||||
assert cds_da[-1].c > ENTRY, "după retest trade-ul repleacă"
|
||||
ch_da = Chart(
|
||||
candles=cds_da,
|
||||
levels=niv(),
|
||||
zones=[Zone(0, 5, "locul crimei", ORANGE, p_top=CONS_SUS + 0.2, p_bot=CONS_JOS - 0.2)],
|
||||
arrows=[
|
||||
Arrow(10, f"retest până la jumătatea consolidării ({CONS_JUM:.2f}):\n"
|
||||
f"a doua fereastră de intrare",
|
||||
where="below", price=CONS_JUM, color=BLUE, dx=-120, dy=24),
|
||||
],
|
||||
scale=26.0, gap=34, body_w=15, left=80, width=w,
|
||||
title="DA — retest la 1/2 din consolidare (exemplu construit)",
|
||||
)
|
||||
cds_nu = _cu_retest(RETEST_NU)
|
||||
assert min(c.l for c in cds_nu[7:]) < CONS_JOS, "panoul NU trebuie să rupă baza"
|
||||
ch_nu = Chart(
|
||||
candles=cds_nu,
|
||||
levels=niv(),
|
||||
zones=[Zone(0, 5, "locul crimei", ORANGE, p_top=CONS_SUS + 0.2, p_bot=CONS_JOS - 0.2)],
|
||||
arrows=[
|
||||
Arrow(11, "sub bază ideea s-a inversat:\nde aici se shortează, nu se cumpără",
|
||||
where="below", price=CONS_JOS - 1.6, color=RED, dx=-130, dy=10),
|
||||
],
|
||||
scale=26.0, gap=34, body_w=15, left=80, width=w,
|
||||
title="NU — retest care trece sub bază (exemplu construit)",
|
||||
)
|
||||
stiva("retest", w, [(ch_da, ()), (ch_nu, ())], [
|
||||
"Singura cifră a secțiunii: «unul pe doi retestează din consolidare» — jumătatea.",
|
||||
"Între jumătate și bază e zonă de avertizare; sub bază, ideea s-a inversat.",
|
||||
"Retestul nu e garantat: de aceea intrarea se face în tranșe, nu așteptând.",
|
||||
], pe_linie=[lv.text for lv in niv()])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
d_ansamblu()
|
||||
d_setup()
|
||||
d_stop()
|
||||
d_tinte()
|
||||
d_trigger()
|
||||
d_retest()
|
||||
for nume, svg in SVG.items():
|
||||
p = OUT / f"dhrt_{nume}.svg"
|
||||
p.write_text(svg, encoding="utf-8")
|
||||
print(f"scris {p.name} ({len(svg)} octeți)")
|
||||
Reference in New Issue
Block a user