Verificare independenta fata de harvest/ si transcrieri. Niciun punct
contestat.
Conținut:
- citat inventat, scos: "un wedge in compresie cere volum pentru breakout
masiv" nu exista in transcriere (verificat cu grep, 0 apariții acum). El
spune ca vrea volum mai mare ("as vad sa vad si volumul mai tare"), iar
breakout-ul masiv il leaga de reintrarea in MBT-10. Nu formuleaza nicio
regula. Acelasi citat fals era si in 04, corectat acolo.
- exemplul de triple doji contrazicea regula lui: avea volum de vanzare in
crestere, cand el cere acumulare si divergenta bull de volum (19.08).
Contrazicea si propriul tabel DA/NU de dedesubt, si documentul 05.
Reparat DESENUL, nu textul: volumul scade sub low-ul anterior (divergenta
bull), scade pe cele trei doji (epuizare), trigger-ul are volumul maxim
din serie, iar pe urcare barele verzi au mai mult volum decat cele rosii.
Trei assert-uri noi pe relatiile astea. Tabelul DA/NU a rămas neschimbat
si acum se potriveste.
- trei atribuiri "din curs" erau de fapt din analiza zilnica, verificate
fisier cu fisier in module: conditia McClellan pentru double top (19.08),
inverted H&S pe 60 min ca trigger (25.08, 18.08), si RSP + McClellan +
stochastic (17.08, 19.08). In module McClellan apare doar ca nume. A
patra trimitere, cea catre modulele 2, 3 si 4, e corecta si a rămas.
- doua caption-uri contraziceau desenul ("volumul e cel mai mic din serie",
"cel mai mic volum de la fundul cestii" - de fapt egalitate); corectate
dupa ce arata desenul, unde assert-ul exista deja.
- 44.50 era numit marginea de sus a gap-ului 44.50-44.90, deci de jos.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MtSTyTmt6AbCL9j5ajEDm1
897 lines
51 KiB
Python
897 lines
51 KiB
Python
"""Diagramele pentru summaries/08_ANALIZA_ZILNICA/06_structura_trend.md.
|
|
|
|
Toate prețurile sunt construite (fără ticker). Rulează:
|
|
.venv/bin/python gen/az_structura.py
|
|
Înlocuiește în .md fiecare bloc <!--SVG:nume--> ... <!--/SVG--> cu output-ul
|
|
generatorului. Ca să schimbi o cifră, o schimbi aici și rulezi din nou.
|
|
|
|
Liniile de trend (wedge, triunghi) se calculează din high-urile / low-urile
|
|
candelelor și se inserează în SVG cu aceleași funcții de coordonate ale
|
|
graficului (ch._cx, ch._y) — nu sunt desenate de mână.
|
|
"""
|
|
import re
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
sys.path.insert(0, "/workspace/atm-curs-dl")
|
|
from svg_candles import Chart, Level, Mark, MA, from_closes # noqa: E402
|
|
|
|
MD = Path("/workspace/atm-curs-dl/summaries/08_ANALIZA_ZILNICA/06_structura_trend.md")
|
|
GRAY, RED, GREEN, BLUE, ORANGE, INK = "#777", "#c00", "#188a3e", "#1a5fb4", "#b06000", "#333"
|
|
STYLE = dict(gap=15, body_w=9, left=44, width=620)
|
|
svgs = {}
|
|
|
|
|
|
def wk(n, over=None, base=(0.3, 0.3)):
|
|
"""wick-uri: `base` peste tot, cu excepțiile din `over` {index: (sus, jos)}"""
|
|
w = [base] * n
|
|
for i, v in (over or {}).items():
|
|
w[i] = v
|
|
return w
|
|
|
|
|
|
def px_ratio(ch, a, b, c):
|
|
"""raportul (c-b)/(b-a) măsurat în pixeli, după render"""
|
|
return (ch._y(b) - ch._y(c)) / (ch._y(a) - ch._y(b))
|
|
|
|
|
|
def is_sl(cds, i):
|
|
return cds[i].l < cds[i - 1].l and cds[i].l < cds[i + 1].l
|
|
|
|
|
|
def is_sh(cds, i):
|
|
return cds[i].h > cds[i - 1].h and cds[i].h > cds[i + 1].h
|
|
|
|
|
|
def doji(c):
|
|
return abs(c.o - c.c) <= 0.15 and (c.h - c.l) >= 3 * abs(c.o - c.c)
|
|
|
|
|
|
def line_at(i1, p1, i2, p2, i):
|
|
return p1 + (p2 - p1) * (i - i1) / (i2 - i1)
|
|
|
|
|
|
def with_lines(ch, svg, lines):
|
|
"""lines: (i1, p1, i2, p2, i_end, culoare) — linie de trend prin două puncte, prelungită până la i_end"""
|
|
out = []
|
|
for i1, p1, i2, p2, ie, col in lines:
|
|
pe = line_at(i1, p1, i2, p2, ie)
|
|
out.append(f'<line x1="{ch._cx(i1):.1f}" y1="{ch._y(p1):.1f}" x2="{ch._cx(ie):.1f}" '
|
|
f'y2="{ch._y(pe):.1f}" stroke="{col}" stroke-width="1.6"/>')
|
|
return svg.replace("</text>", "</text>\n " + "\n ".join(out), 1)
|
|
|
|
|
|
def add(svg, elems):
|
|
"""elemente SVG în plus, desenate peste tot restul"""
|
|
return svg.replace("\n</svg>", "\n " + "\n ".join(elems) + "\n</svg>")
|
|
|
|
|
|
def lbl_below(ch, price, text, color, at="right"):
|
|
"""eticheta unui Level, SUB linie (când deasupra ar tăia o linie apropiată); nivelul se dă cu text gol"""
|
|
x, anc = (ch.width - 28, "end") if at == "right" else (30, "start")
|
|
return f'<text x="{x}" y="{ch._y(price) + 14:.1f}" fill="{color}" text-anchor="{anc}">{text}</text>'
|
|
|
|
|
|
def callout(ch, i, price, text, color, anchor="middle", below=False):
|
|
"""text la prețul `price` deasupra (sau sub) barei i, cu o linie subțire până la bară"""
|
|
c = ch.candles[i]
|
|
x, ty = ch._cx(i), ch._y(price)
|
|
tx = x - 4 if anchor == "start" else (x + 4 if anchor == "end" else x)
|
|
y1, y2 = (ty - 13, ch._y(c.l) + 3) if below else (ty + 3, ch._y(c.h) - 3)
|
|
return (f'<line x1="{x:.1f}" y1="{y1:.1f}" x2="{x:.1f}" y2="{y2:.1f}" stroke="{color}" stroke-width="1"/>\n '
|
|
f'<text x="{tx:.1f}" y="{ty:.1f}" fill="{color}" text-anchor="{anchor}" '
|
|
f'style="font-weight:bold">{text}</text>')
|
|
|
|
|
|
def dy_line(c, scale, p_line, off, where):
|
|
"""dy pentru un Mark pe candela c, ca baseline-ul textului să stea la `off` px de prețul p_line
|
|
(off<0 = deasupra). Diferențele de y depind doar de scară, deci merge înainte de render."""
|
|
if where == "above":
|
|
return (c.h - p_line) * scale + off + 10
|
|
return (c.l - p_line) * scale + off - 20
|
|
|
|
|
|
def first_hit_up(cds, start, price):
|
|
return next(i for i in range(start, len(cds)) if cds[i].h >= price - 1e-9)
|
|
|
|
|
|
def first_hit_down(cds, start, price):
|
|
return next(i for i in range(start, len(cds)) if cds[i].l <= price + 1e-9)
|
|
|
|
|
|
# =============================================================== 1. scărița + confirmarea swing low-ului
|
|
stair = [40.0, 40.9, 41.6, 42.4, # 0-3 urcare, SH la 3
|
|
41.9, 41.3, 41.0, # 4-6 pullback, SL la 6
|
|
41.8, 42.7, 43.5, 44.1, # 7-10 HH la 10
|
|
43.6, 43.0, 42.6, # 11-13 HL la 13
|
|
43.4, 44.3, 45.2, 45.8, # 14-17 HH la 17
|
|
45.3, 44.7, 44.3, # 18-20 HL la 20
|
|
45.1, 46.0, 46.9, 47.5, # 21-24 HH la 24
|
|
47.0, 46.4, 46.0] # 25-27 bara 27 = low „în formare"
|
|
SH_I, SL_I = [3, 10, 17, 24], [6, 13, 20, 27]
|
|
stair_w = {i: (0.7, 0.3) for i in SH_I} | {i: (0.3, 0.7) for i in SL_I}
|
|
|
|
cl = stair + [47.0, # 28 candela din dreapta: confirmă
|
|
47.6, 48.3, 48.0, 48.9, 49.6, 49.3, 50.1, 50.6]
|
|
cds = from_closes(cl, wicks=wk(len(cl), stair_w | {28: (0.2, 0.3)}), start=39.6)
|
|
for i in SH_I:
|
|
assert is_sh(cds, i), f"bara {i} nu e swing high"
|
|
for i in SL_I:
|
|
assert is_sl(cds, i), f"bara {i} nu e swing low"
|
|
highs = [cds[i].h for i in SH_I]
|
|
lows = [cds[i].l for i in SL_I]
|
|
assert all(b > a for a, b in zip(highs, highs[1:])), "vârfurile nu sunt higher highs"
|
|
assert all(b > a for a, b in zip(lows, lows[1:])), "minimele nu sunt higher lows"
|
|
HL = 27
|
|
assert cds[HL + 1].c > cds[HL].h and cds[HL + 1].l > cds[HL].l, "candela din dreapta nu confirmă"
|
|
entry = cds[HL + 1].c
|
|
sl = round(cds[HL].l - 0.1, 2)
|
|
step = cds[24].h - cds[20].l
|
|
tp = round(cds[HL].l + step, 2)
|
|
risk = entry - sl
|
|
R = (tp - entry) / risk
|
|
hit = first_hit_up(cds, HL + 2, tp)
|
|
assert R >= 1.5, R
|
|
print(f"S1 HH {[round(x, 2) for x in highs]} HL {[round(x, 2) for x in lows]} entry {entry:.2f} SL {sl:.2f} "
|
|
f"treaptă {step:.2f} TP {tp:.2f} = {R:.2f}R atins la {hit} ({hit - HL - 1} candele)")
|
|
ch = Chart(candles=cds,
|
|
levels=[Level(sl, f"SL {sl:.2f}", RED),
|
|
Level(entry, f"intrare {entry:.2f}", GREEN, label_at="left"),
|
|
Level(tp, f"TP {tp:.2f} = {R:.1f}R", BLUE, label_at="left")],
|
|
marks=[Mark(3, "SH", INK, "above"), Mark(6, "SL", INK, "below"),
|
|
Mark(10, "HH", GREEN, "above"), Mark(13, "HL", GREEN, "below"),
|
|
Mark(17, "HH", GREEN, "above", dy=-14), Mark(20, "HL", GREEN, "below", dy=12),
|
|
Mark(24, "HH", GREEN, "above"), Mark(HL, "HL", GREEN, "below", dy=14)],
|
|
mas=[MA(20, ORANGE, dash="4 3")],
|
|
scale=24, title="Exemplu construit (fără ticker) — daily",
|
|
caption="Linia punctată = SMA-20. Bara 27 devine higher low abia când bara 28 închide peste high-ul ei.",
|
|
**{**STYLE, "width": 700})
|
|
svg_ = add(ch.render(), [callout(ch, HL + 1, 49.0, "confirmare", GREEN)])
|
|
assert abs(px_ratio(ch, sl, entry, tp) - R) < 0.01
|
|
svgs["scarita_da"] = svg_
|
|
S1 = dict(entry=entry, sl=sl, tp=tp, hl=cds[HL].l, hl20=cds[20].l)
|
|
|
|
# =============================================================== 2. scărița NU: candela din dreapta sparge
|
|
cl = stair + [45.2, # 28 low sub low-ul barei 27
|
|
44.6, 45.0, 44.1, 43.4, 43.8, 42.9, 42.5]
|
|
cds = from_closes(cl, wicks=wk(len(cl), stair_w), start=39.6)
|
|
last_hl = cds[20].l
|
|
assert cds[28].l < cds[27].l, "bara 28 nu sparge low-ul barei 27"
|
|
assert not is_sl(cds, 27), "bara 27 n-ar trebui să mai fie swing low"
|
|
brk = next(i for i in range(28, len(cds)) if cds[i].c < last_hl)
|
|
print(f"S2 low 27 {cds[27].l:.2f}, bara 28 low {cds[28].l:.2f} close {cds[28].c:.2f}; "
|
|
f"ultimul HL confirmat {last_hl:.2f} spart la close {cds[brk].c:.2f} (bara {brk})")
|
|
ch = Chart(candles=cds,
|
|
levels=[Level(cds[27].l, f"HL? {cds[27].l:.2f} (în formare)", GRAY),
|
|
Level(last_hl, f"ultimul HL confirmat {last_hl:.2f}", RED, label_at="left")],
|
|
marks=[Mark(24, "HH", GREEN, "above"), Mark(20, "HL", GREEN, "below"),
|
|
Mark(brk, "lower low", RED, "below")],
|
|
scale=24, title="Exemplu construit (fără ticker) — daily",
|
|
caption="Candela din dreapta închide sub low: nu există higher low. Sub 43.60 scărița e ruptă.",
|
|
**{**STYLE, "width": 700})
|
|
svgs["scarita_nu"] = add(ch.render(), [callout(ch, 27, 47.2, "HL în formare?", GRAY, anchor="start")])
|
|
S2 = dict(low27=cds[27].l, c28=cds[28].c, last_hl=last_hl, brk_c=cds[brk].c)
|
|
|
|
# =============================================================== 3. double bottom, al doilea picior mai sus
|
|
down = [38.0, 37.2, 37.6, 36.5, 35.6, 35.9, 34.8, 33.9, 34.2, 33.0, 32.1, 31.4] # 0-11
|
|
bounce = [31.4, 32.1, 31.8, 32.5, 32.9, 32.3, 31.7] # 13-19
|
|
cl = down + [30.6] + bounce + [31.2, # 20 picior 2
|
|
32.1, # 21 confirmare
|
|
32.6, 32.3, 33.8, 34.3, 34.0, 34.9, 35.6, 35.3, 36.2, 36.8]
|
|
cds = from_closes(cl, wicks=wk(len(cl), {12: (0.3, 0.6), 17: (0.4, 0.3), 20: (0.3, 0.4), 21: (0.2, 0.3)}),
|
|
start=38.6)
|
|
L1, L2, CF = 12, 20, 21
|
|
neck = max(c.h for c in cds[L1 + 1:L2])
|
|
assert neck == cds[17].h
|
|
assert is_sl(cds, L1) and is_sl(cds, L2)
|
|
assert cds[L2].l > cds[L1].l, "al doilea picior trebuie să fie mai sus"
|
|
assert cds[CF].c > cds[L2].h and cds[CF].l > cds[L2].l
|
|
entry = cds[CF].c
|
|
sl = round(cds[L2].l - 0.1, 2)
|
|
risk = entry - sl
|
|
tp = round(neck + (neck - cds[L1].l), 2)
|
|
R = (tp - entry) / risk
|
|
bo = next(i for i in range(CF, len(cds)) if cds[i].c > neck)
|
|
hit = first_hit_up(cds, CF + 1, tp)
|
|
assert hit == len(cds) - 1 and R >= 2.5
|
|
print(f"S3 picior1 {cds[L1].l:.2f} picior2 {cds[L2].l:.2f} neck {neck:.2f} entry {entry:.2f} SL {sl:.2f} "
|
|
f"TP {tp:.2f} = {R:.2f}R; breakout la {bo} close {cds[bo].c:.2f}; TP atins la {hit}")
|
|
ch = Chart(candles=cds,
|
|
levels=[Level(neck, f"neckline {neck:.2f}", ORANGE, label_at="left"),
|
|
Level(sl, f"SL {sl:.2f}", RED),
|
|
Level(entry, f"intrare {entry:.2f}", GREEN),
|
|
Level(tp, f"TP {tp:.2f} = {R:.1f}R", BLUE)],
|
|
marks=[Mark(L1, f"picior 1: {cds[L1].l:.2f}", RED, "below"),
|
|
Mark(L2, f"picior 2: {cds[L2].l:.2f} (mai sus)", GREEN, "below", dy=12),
|
|
Mark(bo, "breakout", GREEN, "above", dy=-4)],
|
|
scale=26, title="Exemplu construit (fără ticker) — daily",
|
|
caption="Intrare la confirmarea piciorului 2; ținta = neckline + înălțimea pattern-ului.",
|
|
**{**STYLE, "width": 700})
|
|
svg_ = ch.render()
|
|
assert abs(px_ratio(ch, sl, entry, tp) - R) < 0.01
|
|
svgs["db_mai_sus"] = svg_
|
|
S3 = dict(l1=cds[L1].l, l2=cds[L2].l, neck=neck, entry=entry, sl=sl, tp=tp, R=R, bo_c=cds[bo].c)
|
|
|
|
# =============================================================== 4. double bottom, al doilea picior mai jos (stop hunt)
|
|
cl = down + [30.6] + bounce + [31.4 - 0.8, # 20 coborâre
|
|
30.3, # 21 picior 2: hammer sub picior 1
|
|
31.5, # 22 trigger
|
|
32.2, 31.9, 33.0, 33.9, 34.4, 34.1, 35.2, 36.0, 35.6, 36.9]
|
|
vols = [52, 50, 44, 55, 58, 46, 60, 62, 48, 64, 66, 68, 74,
|
|
46, 42, 38, 40, 44, 40, 46, 50, 52, 70, 60, 48, 62, 72, 58, 46, 60, 58, 44, 56]
|
|
cds = from_closes(cl, wicks=wk(len(cl), {12: (0.3, 0.6), 17: (0.4, 0.3), 21: (0.3, 0.9), 22: (0.2, 0.3)}),
|
|
vols=vols, start=38.6)
|
|
L1, L2, TR = 12, 21, 22
|
|
neck = max(c.h for c in cds[L1 + 1:L2])
|
|
leg1, leg2 = cds[L1].l, cds[L2].l
|
|
assert is_sl(cds, L1) and is_sl(cds, L2)
|
|
assert leg2 < leg1, "al doilea picior trebuie să fie mai jos"
|
|
body = abs(cds[L2].c - cds[L2].o)
|
|
assert min(cds[L2].o, cds[L2].c) - cds[L2].l >= 2.5 * body, "piciorul 2 nu e hammer"
|
|
assert vols[L2] < vols[L1] * 0.75, "volumul pe piciorul 2 trebuie să fie clar mai mic"
|
|
assert cds[TR].c > cds[L2].h and cds[TR].c > leg1, "trigger-ul nu închide peste high-ul hammer-ului"
|
|
entry = cds[TR].c
|
|
sl = round(leg2 - 0.1, 2)
|
|
risk = entry - sl
|
|
tp = round(neck + (neck - leg2), 2)
|
|
R = (tp - entry) / risk
|
|
hit = first_hit_up(cds, TR + 1, tp)
|
|
assert hit == len(cds) - 1 and R >= 2.2
|
|
print(f"S4 picior1 {leg1:.2f} (vol {vols[L1]}) picior2 {leg2:.2f} (vol {vols[L2]}) neck {neck:.2f} "
|
|
f"entry {entry:.2f} SL {sl:.2f} TP {tp:.2f} = {R:.2f}R atins la {hit}")
|
|
ch = Chart(candles=cds,
|
|
levels=[Level(leg1, f"picior 1 {leg1:.2f}", GRAY, label_at="left"),
|
|
Level(sl, f"SL {sl:.2f}", RED),
|
|
Level(entry, f"intrare {entry:.2f}", GREEN),
|
|
Level(tp, f"TP {tp:.2f} = {R:.1f}R", BLUE)],
|
|
marks=[Mark(L1, "picior 1", RED, "below", dy=8),
|
|
Mark(L2, "picior 2: stop-uri luate", RED, "below", dy=2),
|
|
Mark(TR, "trigger", GREEN, "above", dy=-30),
|
|
Mark(17, f"neckline {neck:.2f}", ORANGE, "above")],
|
|
scale=24, vol_h=70, title="Exemplu construit (fără ticker) — daily",
|
|
caption="Piciorul 2 sparge 30.00 cu volum mai mic decât piciorul 1, apoi închide înapoi peste.",
|
|
**{**STYLE, "width": 700})
|
|
svg_ = ch.render()
|
|
assert abs(px_ratio(ch, sl, entry, tp) - R) < 0.01
|
|
svgs["db_mai_jos"] = svg_
|
|
S4 = dict(leg1=leg1, leg2=leg2, neck=neck, entry=entry, sl=sl, tp=tp, R=R, v1=vols[L1], v2=vols[L2])
|
|
|
|
# =============================================================== 5. double bottom NU: e bear flag
|
|
cl = down + [30.6, # 12 picior 1
|
|
31.0, 31.4, 31.1, 31.7, 31.9, 31.6, 32.0, # 13-19 bear flag: urcare lentă, volum mic
|
|
31.2, 30.5, # 20-21
|
|
30.2, # 22 „picior 2", volum mare
|
|
30.9, # 23 cumpărătorul grăbit intră
|
|
30.5, 29.6, 28.8, 29.1, 28.0, 27.3, 27.6, 26.8]
|
|
vols = [52, 50, 44, 55, 58, 46, 60, 62, 48, 64, 66, 68, 74,
|
|
40, 38, 36, 38, 34, 36, 38, 62, 66, 78, 42, 60, 84, 76, 50, 80, 72, 48, 70]
|
|
cds = from_closes(cl, wicks=wk(len(cl), {12: (0.3, 0.6), 22: (0.3, 0.5)}), vols=vols, start=38.6)
|
|
L1, L2, EN = 12, 22, 23
|
|
leg1, leg2 = cds[L1].l, cds[L2].l
|
|
flag_up = sum(vols[13:20]) / 7
|
|
assert leg2 < leg1 and cds[EN].c > cds[L2].h
|
|
assert vols[L2] > vols[L1], "pe NU vânzătorii vin cu volum mai mare la piciorul 2 (fără divergență)"
|
|
assert flag_up < sum(vols[0:12]) / 12 * 0.75, "urcarea din bear flag trebuie să aibă volum mic"
|
|
assert max(c.h for c in cds[13:20]) < 32.6, "bear flag-ul nu ajunge nici la jumătatea downtrend-ului"
|
|
entry = cds[EN].c
|
|
sl = round(leg2 - 0.1, 2)
|
|
stop_bar = first_hit_down(cds, EN + 1, sl)
|
|
assert cds[stop_bar].c < leg2
|
|
print(f"S5 picior1 {leg1:.2f} vol {vols[L1]}, picior2 {leg2:.2f} vol {vols[L2]}, flag high "
|
|
f"{max(c.h for c in cds[13:20]):.2f}; intrare grăbită {entry:.2f} SL {sl:.2f} atins la {stop_bar} "
|
|
f"close {cds[stop_bar].c:.2f}; final {cds[-1].c:.2f}")
|
|
ch = Chart(candles=cds,
|
|
levels=[Level(entry, f"intrare grăbită {entry:.2f}", GRAY, label_at="left"),
|
|
Level(leg1, f"picior 1 {leg1:.2f}", ORANGE),
|
|
Level(sl, "", RED)],
|
|
marks=[Mark(16, "bear flag? volum mic", GRAY, "above", dy=-6),
|
|
Mark(L2, "volum mai mare", RED, "below", dy=2),
|
|
Mark(stop_bar, "SL atins", RED, "below", dy=36)],
|
|
scale=24, vol_h=70, title="Exemplu construit (fără ticker) — daily",
|
|
caption="Fără divergență de volum, „double bottom-ul în formare\" e doar un bear flag.", **STYLE)
|
|
svgs["db_nu"] = add(ch.render(), [lbl_below(ch, sl, f"SL {sl:.2f}", RED, at="left")])
|
|
S5 = dict(leg1=leg1, leg2=leg2, entry=entry, sl=sl, v1=vols[L1], v2=vols[L2], stop_c=cds[stop_bar].c)
|
|
|
|
# =============================================================== 6. double top, al doilea vârf mai sus + evening star (short)
|
|
cl = [52.0, 52.8, 52.5, 53.6, 54.4, 54.1, 55.3, 56.2, 55.9, 57.2, 58.4, # 0-10 uptrend
|
|
59.6, # 11 vârf 1
|
|
59.0, 58.2, 57.5, 57.3, # 12-15 pullback, neckline
|
|
57.9, 58.6, # 16-17
|
|
59.8, # 18 evening star (a): cumpărători
|
|
59.7, # 19 evening star (b): shooting star, vârf 2
|
|
58.9, # 20 evening star (c): vânzători, intrare short
|
|
58.3, 58.6, 57.4, 56.5, 56.9, 55.8, 54.9, 55.3, 54.0, 53.4]
|
|
vols = [48, 50, 42, 52, 54, 44, 56, 58, 46, 60, 62, 64, 50, 52, 54, 48, 44, 46, 50, 58, 70,
|
|
62, 44, 66, 80, 50, 70, 66, 48, 64, 60]
|
|
cds = from_closes(cl, wicks=wk(len(cl), {11: (0.4, 0.3), 15: (0.3, 0.4), 18: (0.2, 0.2), 19: (0.9, 0.2),
|
|
20: (0.2, 0.3), 30: (0.3, 0.3)}), vols=vols, start=51.6)
|
|
T1, T2, EV = 11, 19, 20
|
|
top1, top2 = cds[T1].h, cds[T2].h
|
|
neck = min(c.l for c in cds[T1 + 1:T2])
|
|
assert is_sh(cds, T1) and is_sh(cds, T2)
|
|
assert top2 > top1, "al doilea vârf trebuie să fie mai sus"
|
|
a, b, c_ = cds[18], cds[19], cds[20]
|
|
assert a.c > a.o and (a.c - a.o) >= 1.0, "(a) nu e lumânare mare a cumpărătorilor"
|
|
assert abs(b.c - b.o) <= 0.15 and b.h - max(b.o, b.c) >= 5 * abs(b.c - b.o), "(b) nu e shooting star"
|
|
assert c_.c < c_.o and c_.c < (a.o + a.c) / 2, "(c) nu închide sub jumătatea lui (a)"
|
|
entry = c_.c
|
|
sl = round(top2 + 0.1, 2)
|
|
risk = sl - entry
|
|
tp = round(neck - (top2 - neck), 2)
|
|
R = (entry - tp) / risk
|
|
brk = next(i for i in range(EV + 1, len(cds)) if cds[i].c < neck)
|
|
hit = first_hit_down(cds, EV + 1, tp)
|
|
assert hit == len(cds) - 1 and R >= 2.5
|
|
print(f"S6 vârf1 {top1:.2f} vârf2 {top2:.2f} neck {neck:.2f} short {entry:.2f} SL {sl:.2f} TP {tp:.2f} = {R:.2f}R; "
|
|
f"neckline spart la {brk} close {cds[brk].c:.2f}; TP atins la {hit}")
|
|
ch = Chart(candles=cds,
|
|
levels=[Level(sl, f"SL {sl:.2f}", RED),
|
|
Level(entry, f"intrare short {entry:.2f}", GREEN, label_at="left"),
|
|
Level(neck, f"neckline {neck:.2f}", ORANGE),
|
|
Level(tp, f"TP {tp:.2f} = {R:.1f}R", BLUE),
|
|
Level(63.2, "", "none")], # invizibil: loc deasupra pentru etichetele vârfurilor
|
|
marks=[Mark(T1, f"vârf 1: {top1:.2f}", INK, "above", dy=-22),
|
|
Mark(T2, "evening star", RED, "above", dy=-16),
|
|
Mark(T2, f"vârf 2: {top2:.2f}", RED, "above", dy=-30),
|
|
],
|
|
scale=22, vol_h=70, title="Exemplu construit (fără ticker) — daily",
|
|
caption="Oglinda double bottom-ului cu piciorul 2 mai jos: vârful 2 ia stop-urile short-urilor.",
|
|
**{**STYLE, "width": 700})
|
|
svg_ = add(ch.render(), [callout(ch, brk, 55.0, "neckline spart", RED, anchor="end", below=True)])
|
|
assert abs(px_ratio(ch, tp, entry, sl) - 1 / R) < 0.01
|
|
svgs["double_top"] = svg_
|
|
S6 = dict(top1=top1, top2=top2, neck=neck, entry=entry, sl=sl, tp=tp, R=R)
|
|
|
|
# =============================================================== 7. cup & handle weekly: DA
|
|
PIV_M = 47.50
|
|
cup = [45.0, 45.9, 46.8, 47.9, 48.9, 49.8, # 0-5 urcare până la marginea stângă
|
|
49.0, 47.8, 46.5, 45.2, 44.2, 43.4, 42.9, # 6-12 coborâre rotunjită
|
|
42.8, # 13 doji în fundul ceștii
|
|
43.6, # 14 buy doji
|
|
44.1, 44.9, 45.8, 46.9, 47.9, 48.8, 49.5, # 15-21 partea dreaptă
|
|
49.9, # 22 marginea dreaptă
|
|
49.5, 49.0, 48.6, 48.9, 49.4] # 23-27 mânerul
|
|
cup_w = {5: (0.5, 0.3), 12: (0.3, 0.3), 13: (0.5, 0.6), 22: (0.4, 0.3), 25: (0.3, 0.4)}
|
|
cup_v = [40, 44, 42, 46, 48, 50, 52, 50, 48, 46, 44, 40, 36, 42, 58, 46, 48, 50, 52, 54, 52, 50, 48,
|
|
34, 32, 30, 32, 36]
|
|
cl = cup + [51.0, # 28 breakout din mâner, volum țeavă
|
|
51.8, 51.4, 52.6, 53.5, 53.1, 54.4, 55.3, 55.0, 56.4, 57.6, 58.2]
|
|
cds = from_closes(cl, wicks=wk(len(cl), cup_w | {28: (0.2, 0.3)}),
|
|
vols=cup_v + [96, 66, 50, 64, 68, 52, 66, 70, 50, 66, 64, 62], start=44.4)
|
|
RL, RR, DJ, BD, BO = 5, 22, 13, 14, 28
|
|
rimL, rimR = cds[RL].h, cds[RR].h
|
|
cup_low = min(c.l for c in cds[RL:RR])
|
|
handle_top = max(c.h for c in cds[RR:BO])
|
|
handle_low = min(c.l for c in cds[RR + 1:BO])
|
|
depth = rimR - cup_low
|
|
assert abs(rimL - rimR) <= 0.1, "marginile ceștii nu sunt la același nivel"
|
|
assert handle_top == rimR
|
|
assert doji(cds[DJ]) and cds[BD].c > cds[DJ].h and cup_low == cds[DJ].l, "buy doji-ul nu e din fundul ceștii"
|
|
assert (rimR - handle_low) / depth <= 1 / 3, "mânerul coboară mai mult de 1/3 din ceașcă"
|
|
assert handle_low > PIV_M, "mânerul trebuie să stea deasupra pivotului monthly"
|
|
assert cds[BO].c > handle_top and cds[BO].vol == max(c.vol for c in cds), "breakout fără cel mai mare volum"
|
|
entry = cds[BO].c
|
|
sl = round(handle_low - 0.1, 2)
|
|
risk = entry - sl
|
|
tp = round(rimR + depth, 2)
|
|
R = (tp - entry) / risk
|
|
hit = first_hit_up(cds, BO + 1, tp)
|
|
assert hit == len(cds) - 1 and R >= 2.4
|
|
print(f"S7 margini {rimL:.2f}/{rimR:.2f} fund {cup_low:.2f} adâncime {depth:.2f} mâner low {handle_low:.2f} "
|
|
f"({(rimR - handle_low) / depth:.2f} din adâncime) entry {entry:.2f} SL {sl:.2f} TP {tp:.2f} = {R:.2f}R "
|
|
f"atins la {hit} ({hit - BO} săptămâni)")
|
|
ch = Chart(candles=cds,
|
|
levels=[Level(PIV_M, "", ORANGE),
|
|
Level(sl, f"SL {sl:.2f}", RED),
|
|
Level(entry, f"intrare {entry:.2f}", GREEN),
|
|
Level(tp, f"TP {tp:.2f} = {R:.1f}R", BLUE)],
|
|
marks=[Mark(RL, "margine", INK, "above", dy=-12), Mark(RR, "margine", INK, "above", dy=-12),
|
|
Mark(DJ, "ceașca", GRAY, "below", dy=4), Mark(BD, "buy doji", GREEN, "above", dy=-20),
|
|
Mark(25, "mâner", ORANGE, "above", dy=-2), Mark(BO, "breakout", GREEN, "above", dy=-4)],
|
|
scale=16, vol_h=70, title="Exemplu construit (fără ticker) — weekly",
|
|
caption="Trigger = închiderea peste mâner, cu cel mai mare volum din serie; ținta = adâncimea ceștii.",
|
|
**{**STYLE, "width": 760})
|
|
svg_ = add(ch.render(), [lbl_below(ch, PIV_M, f"pivot monthly {PIV_M:.2f}", ORANGE)])
|
|
assert abs(px_ratio(ch, sl, entry, tp) - R) < 0.01
|
|
svgs["cup_da"] = svg_
|
|
S7 = dict(rim=rimR, low=cup_low, depth=depth, hlow=handle_low, entry=entry, sl=sl, tp=tp, R=R, weeks=hit - BO)
|
|
CUP = dict(entry=entry, sl=sl, tp=tp, handle_top=handle_top, handle_low=handle_low, closes=cl[RR:BO + 1])
|
|
|
|
# =============================================================== 8. cup & handle NU: breakout fără volum
|
|
cl = cup + [50.6, # 28 breakout, volum sub media mânerului
|
|
50.1, 49.4, 48.7, 47.9, 47.3, 47.8, 46.9, 46.2, 46.6]
|
|
vv = cup_v + [30, 34, 40, 46, 58, 60, 44, 56, 58, 42]
|
|
cds = from_closes(cl, wicks=wk(len(cl), cup_w), vols=vv, start=44.4)
|
|
assert cds[BO].c > handle_top
|
|
assert vv[BO] < sum(vv[RR:BO]) / (BO - RR), "breakout-ul NU trebuie să aibă volum peste media mânerului"
|
|
entry = cds[BO].c
|
|
sl = round(handle_low - 0.1, 2)
|
|
stop_bar = first_hit_down(cds, BO + 1, sl)
|
|
back_in = next(i for i in range(BO + 1, len(cds)) if cds[i].c < handle_top)
|
|
assert back_in == BO + 1
|
|
print(f"S8 breakout {entry:.2f} vol {vv[BO]} (media mânerului {sum(vv[RR:BO]) / (BO - RR):.0f}), înapoi sub "
|
|
f"{handle_top:.2f} la {back_in}, SL {sl:.2f} atins la {stop_bar} ({stop_bar - BO} săpt.), final {cds[-1].c:.2f}")
|
|
ch = Chart(candles=cds,
|
|
levels=[Level(PIV_M, "", ORANGE),
|
|
Level(sl, f"SL {sl:.2f}", RED),
|
|
Level(entry, f"intrare {entry:.2f}", GREEN)],
|
|
marks=[Mark(BD, "buy doji", GREEN, "above", dy=-20), Mark(25, "mâner", ORANGE, "above", dy=-2),
|
|
Mark(BO, "breakout fără volum", RED, "above", dy=-4),
|
|
Mark(stop_bar, "SL atins", RED, "below", dy=4)],
|
|
scale=16, vol_h=70, title="Exemplu construit (fără ticker) — weekly",
|
|
caption="Aceeași ceașcă. Breakout-ul are volum sub media mânerului: cade înapoi.",
|
|
**{**STYLE, "width": 760})
|
|
svgs["cup_nu"] = add(ch.render(), [lbl_below(ch, PIV_M, f"pivot monthly {PIV_M:.2f}", ORANGE)])
|
|
S8 = dict(entry=entry, sl=sl, vol=vv[BO], vol_avg=sum(vv[RR:BO]) / (BO - RR), weeks=stop_bar - BO)
|
|
|
|
# =============================================================== 9. fractalul: mânerul weekly e o ceașcă pe daily
|
|
daily = [49.8, 49.6, 49.8, 49.6, 49.5, # săpt. 23
|
|
49.3, 49.4, 49.1, 49.2, 49.0, # săpt. 24
|
|
48.8, 48.9, 48.6, 48.7, 48.6, # săpt. 25 — fundul ceștii mici
|
|
48.8, 48.7, 48.9, 49.0, 48.9, # săpt. 26
|
|
49.1, 49.3, 49.5, 49.6, 49.4, # săpt. 27 — marginea dreaptă (23), mânerul mic începe
|
|
49.2, 49.5, 50.1, 50.4, 51.0] # săpt. 28 — mâner mic (25), breakout daily (27)
|
|
cds = from_closes(daily, wicks=wk(len(daily), {12: (0.3, 0.4)}), start=CUP["closes"][0])
|
|
weekly_closes = CUP["closes"][1:]
|
|
assert [round(daily[4 + 5 * k], 2) for k in range(6)] == weekly_closes, "close-urile de vineri ≠ close-urile weekly"
|
|
assert abs(min(c.l for c in cds[:25]) - CUP["handle_low"]) < 1e-9, "fundul ceștii mici ≠ low-ul mânerului weekly"
|
|
assert max(c.h for c in cds[:25]) <= CUP["handle_top"] + 1e-9
|
|
MR = 23
|
|
mini_rim = max(c.h for c in cds[18:25])
|
|
assert mini_rim == cds[MR].h
|
|
mini_handle_low = min(c.l for c in cds[MR + 1:27])
|
|
trig = next(i for i in range(MR + 1, len(cds)) if cds[i].c > mini_rim)
|
|
entry = cds[trig].c
|
|
sl = round(mini_handle_low - 0.1, 2)
|
|
assert trig == 27 and entry < CUP["handle_top"], "trigger-ul daily trebuie să vină sub nivelul de breakout weekly"
|
|
assert sl > CUP["sl"] and entry < CUP["entry"]
|
|
R_d = (CUP["tp"] - entry) / (entry - sl)
|
|
R_w = (CUP["tp"] - CUP["entry"]) / (CUP["entry"] - CUP["sl"])
|
|
print(f"S9 margine mică {mini_rim:.2f}, mâner mic low {mini_handle_low:.2f}, trigger daily la {trig} close {entry:.2f} "
|
|
f"SL {sl:.2f} risc {entry - sl:.2f}; aceeași țintă {CUP['tp']:.2f} = {R_d:.1f}R daily vs {R_w:.1f}R weekly")
|
|
ch = Chart(candles=cds,
|
|
levels=[Level(CUP["handle_top"], f"breakout weekly {CUP['handle_top']:.2f}", ORANGE, label_at="left"),
|
|
Level(entry, "", GREEN),
|
|
Level(sl, f"SL daily {sl:.2f}", RED)],
|
|
marks=[Mark(12, "ceașca mică", GRAY, "below", dy=4),
|
|
Mark(MR, "margine", INK, "above", dy=-26),
|
|
Mark(25, "mâner mic", ORANGE, "below", dy=0)],
|
|
scale=70, title="Exemplu construit (fără ticker) — daily, în mânerul weekly",
|
|
caption="Cele 6 săptămâni ale mânerului, pe daily: trigger-ul vine cu 2 zile înainte de cel weekly.",
|
|
**{**STYLE, "width": 760, "gap": 19})
|
|
svg_ = add(ch.render(), [callout(ch, trig, 48.3, "trigger daily", GREEN, below=True),
|
|
lbl_below(ch, entry, f"intrare daily {entry:.2f}", GREEN)])
|
|
svgs["cup_fractal"] = svg_
|
|
S9 = dict(entry=entry, sl=sl, R_d=R_d, R_w=R_w, rim=mini_rim, hlow=mini_handle_low)
|
|
|
|
# =============================================================== 10. descending wedge: DA
|
|
pre = [55.0, 54.2, 54.5, 53.4, 52.6] # 0-4 downtrend
|
|
wedge = [52.0, # 5
|
|
52.5, # 6 SH1
|
|
51.6, 50.6, 49.8, 49.1, # 7-10 (10 = SL1)
|
|
49.8, 50.4, 51.0, # 11-13 (13 = SH2)
|
|
50.2, 49.2, 48.4, # 14-16 (16 = SL2)
|
|
49.1, 49.9, # 17-18 (18 = SH3)
|
|
49.0, 48.3, 47.9, # 19-21 (21 = SL3)
|
|
48.3, 48.8, # 22-23 (23 = SH4)
|
|
48.0, 47.6, 47.3, # 24-26 (26 = SL4)
|
|
47.6] # 27
|
|
W_SH, W_SL = [6, 13, 18, 23], [10, 16, 21, 26]
|
|
w_over = {i: (0.5, 0.3) for i in W_SH} | {i: (0.3, 0.5) for i in W_SL} | {27: (0.2, 0.3)}
|
|
w_vol = [60, 58, 54, 62, 64, 56, 52, 58, 56, 54, 52, 44, 42, 40, 46, 44, 42, 36, 34, 38, 36, 34, 30, 30,
|
|
32, 30, 28, 30]
|
|
BO_W = 28
|
|
|
|
|
|
def wedge_series(after, after_vols, mirror=False):
|
|
cl_ = pre + wedge + after
|
|
if mirror:
|
|
cl_ = [round(100 - x, 2) for x in cl_]
|
|
ov = w_over if not mirror else {i: (d, u) for i, (u, d) in w_over.items()}
|
|
return from_closes(cl_, wicks=wk(len(cl_), ov), vols=w_vol + after_vols,
|
|
start=(55.8 if not mirror else 100 - 55.8))
|
|
|
|
|
|
def wedge_lines(cds_, falling=True):
|
|
"""falling: vârfurile la W_SH, minimele la W_SL; la oglindă (rising) rolurile se inversează"""
|
|
hs, ls = (W_SH, W_SL) if falling else (W_SL, W_SH)
|
|
hi = (hs[0], cds_[hs[0]].h, hs[-1], cds_[hs[-1]].h)
|
|
lo = (ls[0], cds_[ls[0]].l, ls[-1], cds_[ls[-1]].l)
|
|
return hi, lo, hs, ls
|
|
|
|
|
|
def check_wedge(cds_, falling=True):
|
|
hi, lo, hs, ls = wedge_lines(cds_, falling)
|
|
for i in range(W_SH[0], BO_W):
|
|
assert cds_[i].h <= line_at(*hi, i) + 0.12, f"bara {i} străpunge linia de sus"
|
|
assert cds_[i].l >= line_at(*lo, i) - 0.12, f"bara {i} străpunge linia de jos"
|
|
for i in hs:
|
|
assert is_sh(cds_, i), f"bara {i} nu e swing high"
|
|
for i in ls:
|
|
assert is_sl(cds_, i), f"bara {i} nu e swing low"
|
|
s_hi = (hi[3] - hi[1]) / (hi[2] - hi[0])
|
|
s_lo = (lo[3] - lo[1]) / (lo[2] - lo[0])
|
|
if falling:
|
|
assert s_hi < s_lo < 0, "nu e descending wedge (linii care coboară și converg)"
|
|
else:
|
|
assert 0 < s_hi < s_lo, "nu e ascending wedge (linii care urcă și converg)"
|
|
for a_, b_ in zip(hs, hs[1:]):
|
|
assert (cds_[b_].h < cds_[a_].h) == falling
|
|
for a_, b_ in zip(ls, ls[1:]):
|
|
assert (cds_[b_].l < cds_[a_].l) == falling
|
|
return hi, lo
|
|
|
|
|
|
after = [48.6, # 28 breakout peste linia de sus, volum țeavă
|
|
49.3, 49.0, 50.1, 50.8, 50.5, 51.6, 52.3, 52.1, 52.8]
|
|
cds = wedge_series(after, [92, 70, 52, 66, 70, 50, 64, 62, 48, 60])
|
|
hi, lo = check_wedge(cds)
|
|
upper_at_bo = line_at(*hi, BO_W)
|
|
assert cds[BO_W].c > upper_at_bo and cds[BO_W - 1].c < line_at(*hi, BO_W - 1)
|
|
assert cds[BO_W].vol == max(c.vol for c in cds[W_SH[0]:])
|
|
entry = cds[BO_W].c
|
|
sl = round(cds[W_SL[-1]].l - 0.1, 2)
|
|
tp = round(cds[W_SH[0]].h, 2)
|
|
risk = entry - sl
|
|
R = (tp - entry) / risk
|
|
hit = first_hit_up(cds, BO_W + 1, tp)
|
|
assert hit == len(cds) - 1 and R >= 1.8
|
|
print(f"S10 SH {[round(cds[i].h, 2) for i in W_SH]} SL {[round(cds[i].l, 2) for i in W_SL]} linia sus la "
|
|
f"breakout {upper_at_bo:.2f}; entry {entry:.2f} SL {sl:.2f} TP {tp:.2f} = {R:.2f}R atins la {hit}")
|
|
ch = Chart(candles=cds,
|
|
levels=[Level(sl, f"SL {sl:.2f}", RED, label_at="left"),
|
|
Level(entry, f"intrare {entry:.2f}", GREEN),
|
|
Level(tp, f"TP {tp:.2f} = {R:.1f}R", BLUE)],
|
|
marks=[Mark(14, "descending wedge", ORANGE, "below", dy=dy_line(cds[14], 30, line_at(*lo, 14), 22, "below"))],
|
|
scale=30, vol_h=70, title="Exemplu construit (fără ticker) — daily",
|
|
caption="Minime și maxime tot mai jos, dar tot mai apropiate: vânzătorii obosesc.",
|
|
**{**STYLE, "width": 700})
|
|
svg_ = add(with_lines(ch, ch.render(), [(*hi, BO_W, ORANGE), (*lo, BO_W, ORANGE)]),
|
|
[callout(ch, BO_W, 50.0, "breakout + volum", GREEN, anchor="end")])
|
|
assert abs(px_ratio(ch, sl, entry, tp) - R) < 0.01
|
|
svgs["wedge_desc_da"] = svg_
|
|
S10 = dict(entry=entry, sl=sl, tp=tp, R=R, line=upper_at_bo, vol=cds[BO_W].vol)
|
|
|
|
# =============================================================== 11. descending wedge NU: breakout fals
|
|
after = [48.3, # 28 închide peste linie, dar cu volum mic
|
|
47.7, 47.1, 46.5, 45.8, 46.2, 45.4]
|
|
cds = wedge_series(after, [31, 40, 50, 62, 66, 48, 60])
|
|
hi, lo = check_wedge(cds)
|
|
assert cds[BO_W].c > line_at(*hi, BO_W)
|
|
assert cds[BO_W].vol < sum(w_vol[W_SH[0]:]) / len(w_vol[W_SH[0]:]), "breakout-ul fals are volum sub medie"
|
|
entry = cds[BO_W].c
|
|
sl = round(cds[W_SL[-1]].l - 0.1, 2)
|
|
stop_bar = first_hit_down(cds, BO_W + 1, sl)
|
|
print(f"S11 breakout fals {entry:.2f} vol {cds[BO_W].vol} (media wedge {sum(w_vol[W_SH[0]:]) / len(w_vol[W_SH[0]:]):.0f}); "
|
|
f"SL {sl:.2f} atins la {stop_bar} ({stop_bar - BO_W} candele)")
|
|
ch = Chart(candles=cds,
|
|
levels=[Level(sl, f"SL {sl:.2f}", RED, label_at="left"),
|
|
Level(entry, f"intrare {entry:.2f}", GREEN)],
|
|
marks=[Mark(14, "descending wedge", ORANGE, "below", dy=dy_line(cds[14], 30, line_at(*lo, 14), 22, "below")),
|
|
Mark(BO_W, "breakout fără volum", RED, "above", dy=-18),
|
|
Mark(stop_bar, "SL atins", RED, "below", dy=20)],
|
|
scale=30, vol_h=70, title="Exemplu construit (fără ticker) — daily",
|
|
caption="Aceeași compresie. Close peste linie, dar cu volum sub media wedge-ului: fals.",
|
|
**{**STYLE, "width": 700})
|
|
svgs["wedge_desc_nu"] = with_lines(ch, ch.render(), [(*hi, BO_W, ORANGE), (*lo, BO_W, ORANGE)])
|
|
S11 = dict(entry=entry, sl=sl, vol=cds[BO_W].vol, n=stop_bar - BO_W)
|
|
|
|
# =============================================================== 12. ascending wedge la rezistența monthly (short)
|
|
after = [48.6, 49.3, 49.0, 50.1, 50.8, 50.5, 51.6, 52.3, 52.1, 52.8] # oglinda breakout-ului din S10
|
|
cds = wedge_series(after, [92, 70, 52, 66, 70, 50, 64, 62, 48, 60], mirror=True)
|
|
hi_, lo_ = check_wedge(cds, falling=False)
|
|
lower_at_bo = line_at(*lo_, BO_W)
|
|
assert cds[BO_W].c < lower_at_bo and cds[BO_W - 1].c > line_at(*lo_, BO_W - 1)
|
|
RES_M = round(cds[W_SL[-1]].h, 2)
|
|
assert all(c.h <= RES_M + 1e-9 for c in cds[:BO_W]), "rezistența monthly trebuie să fie maximul seriei"
|
|
entry = cds[BO_W].c
|
|
sl = round(cds[W_SL[-1]].h + 0.1, 2)
|
|
tp = round(cds[W_SH[0]].l, 2)
|
|
risk = sl - entry
|
|
R = (entry - tp) / risk
|
|
hit = first_hit_down(cds, BO_W + 1, tp)
|
|
assert hit == len(cds) - 1 and R >= 1.8
|
|
print(f"S12 rezistență monthly {RES_M:.2f}; linia jos la breakdown {lower_at_bo:.2f}; short {entry:.2f} SL {sl:.2f} "
|
|
f"TP {tp:.2f} = {R:.2f}R atins la {hit}")
|
|
ch = Chart(candles=cds,
|
|
levels=[Level(sl, f"SL {sl:.2f}", RED),
|
|
Level(entry, f"intrare short {entry:.2f}", GREEN, label_at="left"),
|
|
Level(tp, f"TP {tp:.2f} = {R:.1f}R", BLUE)],
|
|
marks=[Mark(2, "uptrend", GRAY, "below", dy=8),
|
|
Mark(14, "ascending wedge", ORANGE, "above", dy=dy_line(cds[14], 30, line_at(*hi_, 14), -8, "above")),
|
|
Mark(W_SL[-1], "rezistență monthly", RED, "above", dy=-4)],
|
|
scale=30, vol_h=70, title="Exemplu construit (fără ticker) — daily",
|
|
caption="Oglinda: vârfuri tot mai sus, dar tot mai mici, lipite de rezistența monthly.",
|
|
**{**STYLE, "width": 760})
|
|
svg_ = add(with_lines(ch, ch.render(), [(*hi_, BO_W, ORANGE), (*lo_, BO_W, ORANGE)]),
|
|
[callout(ch, BO_W, 49.7, "breakdown + volum", RED, anchor="end", below=True)])
|
|
assert abs(px_ratio(ch, tp, entry, sl) - 1 / R) < 0.01
|
|
svgs["wedge_asc"] = svg_
|
|
S12 = dict(entry=entry, sl=sl, tp=tp, R=R, res=RES_M)
|
|
|
|
# =============================================================== 13. ascending triangle, breakout, retest + bull harami
|
|
TOP = 50.00
|
|
cl = [44.0, 44.9, 45.5, 46.4, 47.3, 48.4, 49.1, # 0-6
|
|
49.7, # 7 atinge 50.00
|
|
49.0, 48.1, 47.1, # 8-10 (10 = low 1)
|
|
47.8, 48.6, 49.3, 49.7, # 11-14 (14 atinge 50.00)
|
|
49.1, 48.4, 48.0, # 15-17 (17 = low 2)
|
|
48.7, 49.3, 49.7, # 18-20 (20 atinge 50.00)
|
|
49.2, 48.8, # 21-22 (22 = low 3)
|
|
49.3, 49.6, # 23-24
|
|
50.9, # 25 breakout
|
|
51.4, 50.9, # 26-27
|
|
50.3, # 28 retest: low exact pe 50.00
|
|
50.5, # 29 bull harami
|
|
51.1, # 30 trigger
|
|
51.7, 51.4, 52.3, 52.9, 52.6, 53.2]
|
|
vols = [40, 44, 42, 46, 48, 52, 50, 54, 44, 42, 46, 40, 38, 42, 44, 36, 34, 38, 36, 34, 38, 30, 32, 30, 34,
|
|
88, 60, 44, 40, 38, 62, 56, 42, 58, 60, 44, 56]
|
|
cds = from_closes(cl, wicks=wk(len(cl), {10: (0.3, 0.5), 17: (0.3, 0.4), 22: (0.3, 0.3)}), vols=vols, start=43.6)
|
|
T_LO = [10, 17, 22]
|
|
BO_T, RT, HAR, TR = 25, 28, 29, 30
|
|
assert all(c.h <= TOP + 1e-9 for c in cds[5:BO_T]), "o bară din triunghi trece peste 50.00"
|
|
touch = [i for i in range(5, BO_T) if abs(cds[i].h - TOP) < 1e-9]
|
|
assert sum(1 for i in touch if i - 1 not in touch) >= 3, "rezistența nu e atinsă de 3 ori separat"
|
|
t_lows = [cds[i].l for i in T_LO]
|
|
assert all(b > a for a, b in zip(t_lows, t_lows[1:])), "minimele triunghiului nu urcă"
|
|
lo_t = (T_LO[0], t_lows[0], T_LO[1], t_lows[1])
|
|
for i in range(T_LO[0], BO_T):
|
|
assert cds[i].l >= line_at(*lo_t, i) - 0.12, f"bara {i} străpunge linia ascendentă"
|
|
assert cds[BO_T].c > TOP and cds[BO_T].vol == max(vols)
|
|
assert abs(cds[RT].l - TOP) < 1e-9 and cds[RT].c > TOP, "retestul nu atinge 50.00 cu codița"
|
|
har, mom = cds[HAR], cds[RT]
|
|
assert mom.c < mom.o and har.c > har.o and min(har.o, har.c) >= mom.c and max(har.o, har.c) <= mom.o, "nu e bull harami"
|
|
assert cds[TR].c > har.h
|
|
entry = cds[TR].c
|
|
sl = round(TOP - 0.2, 2)
|
|
height = TOP - t_lows[0]
|
|
tp = round(TOP + height, 2)
|
|
risk = entry - sl
|
|
R = (tp - entry) / risk
|
|
hit = first_hit_up(cds, TR + 1, tp)
|
|
assert hit == len(cds) - 1 and R >= 1.75
|
|
print(f"S13 low-uri {[round(x, 2) for x in t_lows]} breakout close {cds[BO_T].c:.2f} retest low {cds[RT].l:.2f} "
|
|
f"harami {har.o:.2f}->{har.c:.2f} entry {entry:.2f} SL {sl:.2f} înălțime {height:.2f} TP {tp:.2f} = {R:.2f}R")
|
|
ch = Chart(candles=cds,
|
|
levels=[Level(TOP, f"rezistență = pivot quarterly {TOP:.2f}", ORANGE),
|
|
Level(sl, "", RED),
|
|
Level(entry, f"intrare {entry:.2f}", GREEN),
|
|
Level(tp, f"TP {tp:.2f} = {R:.1f}R", BLUE)],
|
|
marks=[Mark(17, "ascending triangle", ORANGE, "below", dy=dy_line(cds[17], 30, line_at(*lo_t, 17), 16, "below")),
|
|
Mark(BO_T, "breakout", GREEN, "above", dy=-4),
|
|
Mark(RT, "retest", ORANGE, "below", dy=4),
|
|
Mark(HAR, "harami", GREEN, "below", dy=22)],
|
|
scale=30, vol_h=70, title="Exemplu construit (fără ticker) — daily",
|
|
caption="Breakout, cădere înapoi pe fosta rezistență, bull harami acolo: intrare la trigger.",
|
|
**{**STYLE, "width": 760})
|
|
svg_ = add(with_lines(ch, ch.render(), [(*lo_t, BO_T, ORANGE)]), [lbl_below(ch, sl, f"SL {sl:.2f}", RED)])
|
|
assert abs(px_ratio(ch, sl, entry, tp) - R) < 0.01
|
|
svgs["triunghi_asc"] = svg_
|
|
S13 = dict(lows=t_lows, bo=cds[BO_T].c, entry=entry, sl=sl, tp=tp, R=R, height=height)
|
|
|
|
# =============================================================== 14. island top, apoi cumpărarea în gap-ul închis
|
|
PIV_Q = 44.50
|
|
segA = from_closes([40.0, 40.8, 41.5, 41.2, 42.1, 42.9, 42.6, 43.5, 44.0, 44.2], wick=0.3, start=39.6)
|
|
segB = from_closes([45.6, 46.4, 46.1, 47.0, 47.8, 47.5, 48.4, 49.2, 49.0, 49.6], wick=0.3, start=45.2)
|
|
segC = from_closes([51.6, 51.9, 51.5, 51.2], wick=0.3, start=51.0)
|
|
segD = from_closes([49.4, 49.7, 48.8, 48.1, 48.4, 47.5, 46.8, 47.1, 46.2, 45.5, 45.1,
|
|
45.0, # hammer: închide gap-ul de jos
|
|
45.9, # trigger
|
|
46.6, 46.3, 47.4, 48.3, 48.0, 49.2, 50.0],
|
|
wicks=wk(20, {11: (0.2, 0.6), 12: (0.2, 0.3)}), start=50.0)
|
|
cds = segA + segB + segC + segD
|
|
vols = [40, 44, 38, 46, 48, 42, 50, 52, 50, 60, 72, 56, 44, 58, 60, 46, 62, 64, 50, 66,
|
|
88, 70, 60, 64, 90, 56, 70, 64, 48, 66, 68, 50, 64, 62, 50, 70, 80, 58, 44, 60, 64, 46, 60, 58]
|
|
assert len(vols) == len(cds)
|
|
for c, v in zip(cds, vols):
|
|
c.vol = v
|
|
iA, iB, iC, iD = 0, len(segA), len(segA) + len(segB), len(segA) + len(segB) + len(segC)
|
|
gap1 = (cds[iB - 1].h, cds[iB].l)
|
|
gap2 = (cds[iC - 1].h, cds[iC].l)
|
|
isl_low = min(c.l for c in cds[iC:iD])
|
|
gap3 = (cds[iD].h, isl_low)
|
|
assert gap1[1] > gap1[0] and gap2[1] > gap2[0] and gap3[1] > gap3[0], "lipsesc gap-urile"
|
|
assert all(c.l > gap2[0] for c in cds[iC:iD]) and all(c.h < isl_low for c in cds[iD:]), "insula nu e izolată"
|
|
assert max(c.h for c in cds[iC:iD]) == max(c.h for c in cds), "insula nu e vârful"
|
|
HAM, TRI = iD + 11, iD + 12
|
|
fill = next(i for i in range(iD, len(cds)) if cds[i].l <= gap1[0])
|
|
assert fill == HAM, "gap-ul de jos trebuie închis chiar de hammer"
|
|
hm = cds[HAM]
|
|
assert min(hm.o, hm.c) - hm.l >= 4 * abs(hm.o - hm.c), "bara de închidere nu e hammer"
|
|
assert abs(gap1[0] - PIV_Q) < 1e-9, "marginea de jos a gap-ului vechi = pivotul quarterly"
|
|
assert cds[TRI].c > hm.h
|
|
entry = cds[TRI].c
|
|
sl = round(hm.l - 0.2, 2)
|
|
tp = round(gap3[0], 2)
|
|
risk = entry - sl
|
|
R = (tp - entry) / risk
|
|
hit = first_hit_up(cds, TRI + 1, tp)
|
|
assert hit == len(cds) - 1 and R >= 2.5
|
|
print(f"S14 gap jos {gap1[0]:.2f}-{gap1[1]:.2f}; gap sus {gap2[0]:.2f}-{gap2[1]:.2f}; insula {isl_low:.2f}-"
|
|
f"{max(c.h for c in cds[iC:iD]):.2f}; gap down {gap3[0]:.2f}-{gap3[1]:.2f}; hammer low {hm.l:.2f} la {HAM}; "
|
|
f"entry {entry:.2f} SL {sl:.2f} TP {tp:.2f} = {R:.2f}R atins la {hit}")
|
|
ch = Chart(candles=cds,
|
|
levels=[Level(tp, f"TP {tp:.2f} = {R:.1f}R (gap-ul insulei)", BLUE, label_at="left"),
|
|
Level(entry, f"intrare {entry:.2f}", GREEN, label_at="left"),
|
|
Level(PIV_Q, f"pivot quarterly {PIV_Q:.2f}", ORANGE, label_at="left"),
|
|
Level(sl, "", RED)],
|
|
marks=[Mark(iC + 1, "island top", RED, "above", dy=-4),
|
|
Mark(iD, "gap down", RED, "below", dy=4),
|
|
Mark(HAM, "hammer: gap închis", GREEN, "below", dy=4)],
|
|
scale=22, vol_h=70, title="Exemplu construit (fără ticker) — daily",
|
|
caption="Insula e bearish. Cumperi abia jos, unde gap-ul vechi s-a închis în pivotul quarterly.",
|
|
**{**STYLE, "width": 720})
|
|
svg_ = add(ch.render(), [lbl_below(ch, sl, f"SL {sl:.2f}", RED),
|
|
callout(ch, iB, 47.5, "gap up", GRAY, anchor="end")])
|
|
assert abs(px_ratio(ch, sl, entry, tp) - R) < 0.01
|
|
svgs["island_top"] = svg_
|
|
S14 = dict(gap1=gap1, gap2=gap2, gap3=gap3, isl_low=isl_low, isl_high=max(c.h for c in cds[iC:iD]),
|
|
ham_l=hm.l, entry=entry, sl=sl, tp=tp, R=R)
|
|
|
|
# =============================================================== 15. triple doji la bottom
|
|
cl = [44.0, 43.1, 43.5, 42.2, 41.0, 41.4, 39.9, 38.6,
|
|
36.9, # 8 low anterior
|
|
37.8, 38.6, 39.0, # 9-11 (11 = lower high)
|
|
38.3, 37.4, 37.8, 36.8, 35.9, 35.2, 34.6, 34.3, # 12-19 sparge low-ul anterior
|
|
34.2, 34.3, 34.2, # 20-22 triple doji
|
|
35.3, # 23 trigger
|
|
35.9, 35.6, 36.7, 37.5, 37.2, 38.4, 39.2]
|
|
vols = [50, 54, 42, 58, 60, 44, 62, 64, 70, 46, 42, 40, 52, 56, 44, 56, 52, 48, 46, 42,
|
|
36, 34, 32, 84, 66, 40, 70, 72, 42, 76, 80]
|
|
cds = from_closes(cl, wicks=wk(len(cl), {8: (0.3, 0.6), 11: (0.4, 0.3), 20: (0.5, 0.5), 21: (0.5, 0.5),
|
|
22: (0.5, 0.5), 23: (0.2, 0.3)}), vols=vols, start=44.6)
|
|
D3 = [20, 21, 22]
|
|
prior_low = cds[8].l
|
|
assert is_sl(cds, 8) and is_sh(cds, 11)
|
|
assert all(doji(cds[i]) for i in D3), "cele trei bare nu sunt doji"
|
|
assert min(c.l for c in cds[12:20]) < prior_low, "low-ul anterior nu e spart înainte de doji"
|
|
assert max(vols[15:20]) < vols[8], "sub low-ul anterior volumul trebuie să fie mai mic (divergență bull de volum)"
|
|
assert vols[20] > vols[21] > vols[22] and vols[20] < min(vols[15:20]), "pe doji vânzarea trebuie să se epuizeze"
|
|
up = [vols[i] for i in range(23, len(cl)) if cl[i] > cl[i - 1]]
|
|
dn = [vols[i] for i in range(23, len(cl)) if cl[i] < cl[i - 1]]
|
|
assert vols[23] == max(vols) and min(up) > max(dn), "din oversold, volumul trebuie să fie pe barele verzi (acumulare)"
|
|
assert max(abs(cds[i].c - cds[20].c) for i in D3) <= 0.15, "doji-urile nu stau la același nivel"
|
|
d_hi, d_lo = max(cds[i].h for i in D3), min(cds[i].l for i in D3)
|
|
TR = 23
|
|
assert cds[TR].c > d_hi and all(cds[i].c <= d_hi for i in D3)
|
|
entry = cds[TR].c
|
|
sl = round(d_lo - 0.1, 2)
|
|
tp = round(cds[11].h, 2)
|
|
risk = entry - sl
|
|
R = (tp - entry) / risk
|
|
hit = first_hit_up(cds, TR + 1, tp)
|
|
assert hit == len(cds) - 1 and R >= 2
|
|
print(f"S15 low anterior {prior_low:.2f}; doji high {d_hi:.2f} low {d_lo:.2f}; entry {entry:.2f} SL {sl:.2f} "
|
|
f"TP (lower high) {tp:.2f} = {R:.2f}R atins la {hit} ({hit - TR} săpt.)")
|
|
ch = Chart(candles=cds,
|
|
levels=[Level(prior_low, f"low anterior {prior_low:.2f}", GRAY, label_at="left"),
|
|
Level(sl, f"SL {sl:.2f}", RED),
|
|
Level(entry, f"intrare {entry:.2f}", GREEN),
|
|
Level(tp, f"TP {tp:.2f} = {R:.1f}R", BLUE)],
|
|
marks=[Mark(11, "lower high", INK, "above", dy=-2),
|
|
Mark(21, "triple doji", GREEN, "below", dy=6),
|
|
Mark(TR, "trigger", GREEN, "above", dy=-28)],
|
|
scale=26, vol_h=70, title="Exemplu construit (fără ticker) — weekly",
|
|
caption="Sub low volumul scade, pe doji se stinge; trigger-ul și urcarea vin cu volum: acumulare.",
|
|
**{**STYLE, "width": 700})
|
|
svg_ = ch.render()
|
|
assert abs(px_ratio(ch, sl, entry, tp) - R) < 0.01
|
|
svgs["triple_doji"] = svg_
|
|
S15 = dict(prior=prior_low, hi=d_hi, lo=d_lo, entry=entry, sl=sl, tp=tp, R=R, weeks=hit - TR)
|
|
|
|
# =============================================================== 16. inverted head & shoulders
|
|
cl = [47.0, 46.2, 46.6, 45.4, 44.5, 44.8, 43.6, # 0-6 downtrend
|
|
42.6, # 7 umăr stâng
|
|
43.1, 43.6, 43.9, # 8-10 neckline 1
|
|
43.4, 42.2, 41.0, # 11-13
|
|
39.8, # 14 cap
|
|
40.8, 41.9, 43.0, 43.9, # 15-18 neckline 2
|
|
43.4, 43.0, 42.9, # 19-21 umăr drept
|
|
43.6, 43.9, # 22-23
|
|
44.8, # 24 breakout
|
|
45.4, 45.1, 46.2, 47.0, 46.6, 47.8, 48.5, 48.2, 49.0]
|
|
vols = [52, 56, 44, 58, 60, 46, 62, 66, 48, 44, 46, 50, 58, 64, 72, 54, 52, 50, 48, 38, 36, 40, 44, 46,
|
|
90, 66, 48, 64, 66, 50, 62, 64, 50, 60]
|
|
cds = from_closes(cl, wicks=wk(len(cl), {7: (0.3, 0.5), 14: (0.3, 0.6), 21: (0.3, 0.4), 24: (0.2, 0.3)}),
|
|
vols=vols, start=47.6)
|
|
LS, HD, RS, BO_I = 7, 14, 21, 24
|
|
neck = max(c.h for c in cds[LS:RS])
|
|
assert is_sl(cds, LS) and is_sl(cds, HD) and is_sl(cds, RS)
|
|
assert cds[HD].l < cds[LS].l and cds[HD].l < cds[RS].l, "capul nu e cel mai jos"
|
|
assert cds[RS].l > cds[LS].l, "textul spune: umărul drept mai sus"
|
|
assert all(c.h <= neck + 1e-9 for c in cds[LS:BO_I]) and cds[BO_I].c > neck
|
|
assert vols[RS] < vols[LS] and vols[BO_I] == max(vols), "volum: umăr drept mic, breakout maxim"
|
|
entry = cds[BO_I].c
|
|
sl = round(cds[RS].l - 0.1, 2)
|
|
height = neck - cds[HD].l
|
|
tp = round(neck + height, 2)
|
|
risk = entry - sl
|
|
R = (tp - entry) / risk
|
|
hit = first_hit_up(cds, BO_I + 1, tp)
|
|
assert hit == len(cds) - 1 and R >= 1.7
|
|
print(f"S16 umăr stâng {cds[LS].l:.2f} cap {cds[HD].l:.2f} umăr drept {cds[RS].l:.2f} neck {neck:.2f} "
|
|
f"entry {entry:.2f} SL {sl:.2f} înălțime {height:.2f} TP {tp:.2f} = {R:.2f}R atins la {hit}")
|
|
ch = Chart(candles=cds,
|
|
levels=[Level(neck, "", ORANGE),
|
|
Level(sl, f"SL {sl:.2f}", RED),
|
|
Level(entry, f"intrare {entry:.2f}", GREEN),
|
|
Level(tp, f"TP {tp:.2f} = {R:.1f}R", BLUE)],
|
|
marks=[Mark(LS, "umăr stâng", INK, "below", dy=4), Mark(HD, "cap", INK, "below", dy=4),
|
|
Mark(RS, "umăr drept (mai sus)", GREEN, "below", dy=4),
|
|
Mark(BO_I, "breakout", GREEN, "above", dy=-4)],
|
|
scale=24, vol_h=70, title="Exemplu construit (fără ticker) — daily",
|
|
caption="Umărul drept mai sus decât cel stâng: aceeași formă ca o ceașcă cu mâner.",
|
|
**{**STYLE, "width": 680})
|
|
svg_ = add(ch.render(), [lbl_below(ch, neck, f"neckline {neck:.2f}", ORANGE)])
|
|
assert abs(px_ratio(ch, sl, entry, tp) - R) < 0.01
|
|
svgs["ihs"] = svg_
|
|
S16 = dict(ls=cds[LS].l, hd=cds[HD].l, rs=cds[RS].l, neck=neck, entry=entry, sl=sl, tp=tp, R=R)
|
|
|
|
# =============================================================== 17. bull flag înapoi în locul crimei
|
|
cl = [45.0, 46.1, 46.9, 47.8, 48.6, 49.2,
|
|
49.7, # 6 vârful anterior (locul crimei)
|
|
49.0, 48.3, 48.8, 48.2, 49.1, # 7-11 consolidare
|
|
50.6, 51.8, 52.9, 53.6, 54.5, # 12-16 impuls
|
|
55.0, # 17 vârful impulsului
|
|
54.4, 53.6, 53.9, 52.8, 52.0, 52.4, 51.4, # 18-24 steagul coboară
|
|
50.8, # 25 picior 1
|
|
51.5, 51.2, # 26-27
|
|
50.4, # 28 picior 2: mai jos, pe locul crimei
|
|
51.7, # 29 trigger
|
|
52.4, 52.1, 53.3, 54.2, 53.9, 55.0]
|
|
vols = [44, 50, 48, 54, 56, 52, 60, 46, 42, 40, 38, 48, 84, 76, 70, 66, 62, 60, 46, 42, 38, 40, 36, 34, 36,
|
|
40, 30, 32, 42, 72, 60, 44, 62, 66, 48, 62]
|
|
cds = from_closes(cl, wicks=wk(len(cl), {6: (0.3, 0.3), 17: (0.3, 0.3), 25: (0.3, 0.4), 28: (0.3, 0.4),
|
|
29: (0.2, 0.3)}), vols=vols, start=44.6)
|
|
PH, TOPF, L1, L2, TR = 6, 17, 25, 28, 29
|
|
crime = cds[PH].h
|
|
assert crime == max(c.h for c in cds[:12]), "vârful anterior nu e maximul dinainte de impuls"
|
|
assert cds[12].c > crime, "impulsul nu sparge vârful anterior"
|
|
assert cds[TOPF].h == max(c.h for c in cds[:TR])
|
|
flag_vol = sum(vols[18:25]) / 7
|
|
assert flag_vol < sum(vols[12:18]) / 6 * 0.65, "volumul pe steag trebuie să scadă clar față de impuls"
|
|
assert is_sl(cds, L1) and is_sl(cds, L2) and cds[L2].l < cds[L1].l, "piciorul 2 trebuie să fie mai jos"
|
|
assert abs(cds[L2].l - crime) < 1e-9, "piciorul 2 trebuie să atingă exact locul crimei cu codița"
|
|
assert cds[TR].c > cds[L2].h
|
|
entry = cds[TR].c
|
|
sl = round(cds[L2].l - 0.2, 2)
|
|
tp = round(cds[TOPF].h, 2)
|
|
risk = entry - sl
|
|
R = (tp - entry) / risk
|
|
hit = first_hit_up(cds, TR + 1, tp)
|
|
assert hit == len(cds) - 1 and R >= 1.8
|
|
print(f"S17 locul crimei {crime:.2f}; vârf impuls {cds[TOPF].h:.2f}; picior1 {cds[L1].l:.2f} picior2 {cds[L2].l:.2f}; "
|
|
f"vol impuls {sum(vols[12:18]) / 6:.0f} vs steag {flag_vol:.0f}; entry {entry:.2f} SL {sl:.2f} TP {tp:.2f} = {R:.2f}R")
|
|
ch = Chart(candles=cds,
|
|
levels=[Level(crime, f"locul crimei {crime:.2f}", ORANGE, label_at="left"),
|
|
Level(sl, "", RED),
|
|
Level(entry, f"intrare {entry:.2f}", GREEN),
|
|
Level(tp, f"TP {tp:.2f} = {R:.1f}R", BLUE)],
|
|
marks=[Mark(PH, "vârf anterior", INK, "above", dy=-16),
|
|
Mark(L1, "picior 1", RED, "below", dy=8),
|
|
Mark(L2, "picior 2", RED, "below", dy=12)],
|
|
mas=[MA(20, GRAY, dash="4 3")],
|
|
scale=26, vol_h=70, title="Exemplu construit (fără ticker) — daily",
|
|
caption="Linia punctată = SMA-20. Double bottom DUPĂ un impuls, nu după downtrend: e steag.",
|
|
**{**STYLE, "width": 700})
|
|
svg_ = ch.render()
|
|
svg_ = add(svg_, [lbl_below(ch, sl, f"SL {sl:.2f}", RED),
|
|
f'<text x="{ch._cx(21) - 4:.1f}" y="{ch._y(54.7):.1f}" fill="{ORANGE}" '
|
|
f'style="font-weight:bold">bull flag</text>'])
|
|
assert abs(px_ratio(ch, sl, entry, tp) - R) < 0.01
|
|
svgs["bull_flag"] = svg_
|
|
S17 = dict(crime=crime, top=cds[TOPF].h, l1=cds[L1].l, l2=cds[L2].l, entry=entry, sl=sl, tp=tp, R=R)
|
|
|
|
print(f"{len(svgs)} diagrame")
|
|
|
|
# ---------------------------------------------------------------- substituție în .md
|
|
if MD.exists():
|
|
md = MD.read_text(encoding="utf-8")
|
|
for name, svg in svgs.items():
|
|
pat = re.compile(rf"<!--SVG:{name}-->.*?<!--/SVG-->", re.S)
|
|
assert pat.search(md), f"marcatorul {name} lipsește din .md"
|
|
md = pat.sub(lambda _m, s=svg, n=name: f"<!--SVG:{n}-->\n{s}\n<!--/SVG-->", md)
|
|
MD.write_text(md, encoding="utf-8")
|
|
print("scris", MD)
|