strategii: primele 30 de diagrame, cu generatoarele lor

Cate un agent grafician per diagrama, fiecare cu generatorul si SVG-ul lui.
Graficele de pret trec prin svg_candles, cu assert-urile din specificatie
scrise in generator — verifica structura, nu doar ca ruleaza: ca al doilea
picior e mai jos, ca intrarea e close-ul barei de confirmare, ca 1:1 e exact.

Verificare prin sondaj pe buy_doji_ansamblu, cu screenshot citit: serie
continua de 32 de candele, panou de volum, MA(20), doji-ul si ieșirea pe
trail etichetate, cifrele identice cu specificatia.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ThN7ixZrowca8aTGrK1MUJ
This commit is contained in:
Claude Agent
2026-09-18 19:22:13 +00:00
parent af448dda69
commit 01599d2e9d
60 changed files with 9035 additions and 0 deletions

View File

@@ -0,0 +1,123 @@
"""Diagrama de ansamblu ATMI (strategii/05_atmi.md, marcajul `ansamblu`).
„SPY — daily: corecție ≥4 % → SQ/PPQ → BIM + verde → intrare → TP +10 %".
Structura seriei și nivelurile vin din spec-ul
`gen/strategii/atmi_ansamblu.spec.txt`. Toate cifrele desenate (100.00 swing
high, 96.00 intrare, 105.60 = +10 %) sunt din exemplul „DA" al cursului
(`summaries/04_MODUL_3/03_strategia_2_atmi.md:185-247`). Benzile PPQ/SQ sunt
schematice: cursul spune explicit că nu dă cifrele lor
(`...:143`), deci nu poartă prețuri.
Rulează:
python3 gen/strategii/atmi_ansamblu.py
Scrie lângă el `atmi_ansamblu.svg`.
"""
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
OUT = Path(__file__).resolve().parent / "atmi_ansamblu.svg"
GRAY, RED, GREEN, BLUE, ORANGE = "#777", "#c00", "#188a3e", "#1a5fb4", "#b06000"
PURPLE = "#7b2fbf" # folosit în materialul de curs pentru PPQ
# ---------------------------------------------------------------- seria (33 bare)
# 15 bare de urcare ~88 -> 100, 8 de corecție 100 -> 96 (ultima = intrarea),
# 10 de urcare 96 -> 105.60. open-ul fiecărei bare = close-ul precedentei.
UP_A = [88.2, 88.8, 89.5, 90.1, 90.8, 91.6, 92.5, 93.3, 94.2, 95.1,
96.0, 96.9, 97.8, 98.9, 100.00] # 0-14
CORR = [99.3, 98.5, 97.8, 97.2, 96.7, 96.3, 96.00, 96.00] # 15-22
UP_B = [96.6, 97.2, 97.9, 98.6, 99.4, 100.3, 101.4, 102.6, 104.0, 105.60] # 23-32
CLOSES = UP_A + CORR + UP_B
assert len(CLOSES) == 33, len(CLOSES)
PEAK, BIM, ENTRY, LAST = 14, 21, 22, 32
# fitile (sus, jos) per bară; jos pe corecție ținut ≥ 0 ca minimul să fie fix 96.00
WICKS = ([(0.30, 0.30)] * 14
+ [(0.00, 0.30)] # 14: vârful atinge exact 100.00
+ [(0.00, 0.00), (0.05, 0.10), (0.05, 0.10), (0.05, 0.15),
(0.05, 0.15), (0.05, 0.20), (0.05, 0.00), (0.12, 0.00)] # 15-22
+ [(0.25, 0.25)] * 9
+ [(0.00, 0.20)]) # 32: închide/atinge exact 105.60
assert len(WICKS) == len(CLOSES)
VOLS = ([44, 47, 42, 50, 45, 41, 48, 43, 46, 51, 44, 49, 46, 52, 58] # 0-14 urcare
+ [64, 70, 66, 61, 58, 74, 96, 88] # 15-22 corecție + BIM/intrare
+ [55, 58, 52, 60, 57, 63, 59, 66, 72, 84]) # 23-32 urcare spre TP
assert len(VOLS) == len(CLOSES)
cds = from_closes(CLOSES, wicks=WICKS, vols=VOLS, start=88.0)
# ---------------------------------------------------------------- assert-urile din spec
high = 100.00
entry = cds[ENTRY].c
tp = cds[LAST].h
assert abs((high - 96.00) / high * 100 - 4.0) < 0.05 # corecția e 4.0 %
assert abs(entry - 96.00) < 0.05 # intrarea = close-ul candelei verzi
assert abs(tp / entry - 1.10) < 0.005 # TP = +10 %
assert abs(tp - max(c.h for c in cds[-10:])) < 1e-9 # TP atins la final, e maximul barelor
assert BIM < ENTRY # BIM înainte de candela verde
assert abs(min(c.l for c in cds[15:23]) - 96.00) < 1e-9 # minimul corecției = 96.00
assert cds[ENTRY].c >= cds[ENTRY].o # candela de intrare e verde
assert abs(cds[PEAK].h - high) < 1e-9 # swing high = 100.00
assert abs(cds[LAST].c - tp) < 1e-9 # ultima bară închide pe TP
levels = [
Level(high, "100.00 — swing high recent", "#333", label_at="left"),
Level(entry, f"intrare {entry:.2f}", GREEN, label_at="right"),
Level(tp, f"TP +10 % = {tp:.2f}", BLUE, label_at="right"),
]
# nicio linie de stop-loss și niciun nivel sub intrare (absența SL e regula)
assert all("stop" not in lv.text.lower() for lv in levels)
assert all(lv.price >= entry - 1e-9 for lv in levels)
marks = [
Mark(15, "corecție 4.0 %", ORANGE, "above"),
Mark(BIM, "BIM", ORANGE, "above"),
Mark(ENTRY, "BIM + candelă verde → intrare", GREEN, "below", dy=20),
]
ch = Chart(candles=cds, levels=levels, marks=marks,
mas=[MA(20, ORANGE, dash="4 3", text="SMA-20 (ilustrativ)")],
scale=18.0, gap=16, body_w=9, left=44, width=720, vol_h=70, top=70,
vol_label="volum",
caption="Niveluri (100.00 / 96.00 / 105.60) din curs; PPQ/SQ schematice, fără cifre în material.")
svg = ch.render()
def band(price: float, half: float, color: str, label: str) -> str:
"""Bandă schematică orizontală, desenată în spatele candelelor.
Se injectează în SVG după randare, pentru că `Chart` nu are benzi; y-ul
se calculează cu aceeași scară (`ch._y`), deci banda e aliniată corect.
"""
y_top, y_bot = ch._y(price + half), ch._y(price - half)
return (f'<rect x="24" y="{y_top:.1f}" width="{ch.width - 38}" '
f'height="{y_bot - y_top:.1f}" fill="{color}" opacity="0.10"/>'
f'<text x="28" y="{y_top - 4:.1f}" fill="{color}">{label}</text>')
# SQ e zona joasă (intrarea exact pe ea), PPQ zona mai sus, retragere mai mică
assert (entry + 1.60 - 0.35) > (entry + 0.35), "benzile SQ și PPQ nu se suprapun"
sq = band(entry, 0.35, GREEN, "SQ — zonă quarterly")
ppq = band(entry + 1.60, 0.35, PURPLE, "PPQ — zonă quarterly")
# „fără stop-loss standard" stă sub zona de intrare, unde nu are candele
no_sl = (f'<text x="{ch._cx(ENTRY):.0f}" y="{ch._y(entry) + 63:.1f}" fill="{RED}" '
f'text-anchor="middle">fără stop-loss standard</text>'
f'<text x="{ch._cx(ENTRY):.0f}" y="{ch._y(entry) + 76:.1f}" fill="{GRAY}" '
f'text-anchor="middle">(03_strategia_2_atmi.md:164-169)</text>')
# titlul e injectat deasupra, ca rând separat de eticheta TP (care stă chiar
# sub marginea de sus a panoului); altfel cele două texte se calcă la dreapta
titlu = ('<text x="24" y="26" fill="#333" style="font-weight:bold">'
'SPY — daily: corecție ≥4 % → SQ/PPQ → BIM + verde → intrare → TP +10 %</text>')
i = svg.index(">") + 1
svg = svg[:i] + "\n " + "\n ".join([sq, ppq, no_sl, titlu]) + svg[i:]
assert svg.count("\n\n") == 0, "linie goală în SVG rupe blocul din markdown"
OUT.write_text(svg)
print(f"scris {OUT.name}: {len(cds)} candele, intrare {entry:.2f}, TP {tp:.2f}")

View File

@@ -0,0 +1,119 @@
<svg viewBox="0 0 720 530" xmlns="http://www.w3.org/2000/svg" style="width:100%;height:auto;font:12px sans-serif">
<rect x="24" y="236.5" width="682" height="12.6" fill="#188a3e" opacity="0.10"/><text x="28" y="232.5" fill="#188a3e">SQ — zonă quarterly</text>
<rect x="24" y="207.7" width="682" height="12.6" fill="#7b2fbf" opacity="0.10"/><text x="28" y="203.7" fill="#7b2fbf">PPQ — zonă quarterly</text>
<text x="396" y="305.8" fill="#c00" text-anchor="middle">fără stop-loss standard</text><text x="396" y="318.8" fill="#777" text-anchor="middle">(03_strategia_2_atmi.md:164-169)</text>
<text x="24" y="26" fill="#333" style="font-weight:bold">SPY — daily: corecție ≥4 % → SQ/PPQ → BIM + verde → intrare → TP +10 %</text>
<line x1="24" y1="170.8" x2="706" y2="170.8" stroke="#333" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="30" y="164.8" fill="#333">100.00 — swing high recent</text>
<line x1="24" y1="242.8" x2="706" y2="242.8" stroke="#188a3e" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="692" y="236.8" fill="#188a3e" text-anchor="end">intrare 96.00</text>
<line x1="24" y1="70.0" x2="706" y2="70.0" stroke="#1a5fb4" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="692" y="64.0" fill="#1a5fb4" text-anchor="end">TP +10 % = 105.60</text>
<polyline points="348.0,266.9 364.0,259.6 380.0,253.1 396.0,247.3 412.0,241.4 428.0,235.7 444.0,230.0 460.0,224.5 476.0,219.0 492.0,213.5 508.0,207.9 524.0,201.9 540.0,195.5 556.0,188.5" fill="none" stroke="#b06000" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="562" y="182" fill="#b06000">SMA-20 (ilustrativ)</text>
<line x1="44.0" y1="377.8" x2="44.0" y2="392.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="39.5" y="383.2" width="9" height="3.6" fill="#188a3e"/>
<line x1="60.0" y1="367.0" x2="60.0" y2="388.6" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="55.5" y="372.4" width="9" height="10.8" fill="#188a3e"/>
<line x1="76.0" y1="354.4" x2="76.0" y2="377.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="71.5" y="359.8" width="9" height="12.6" fill="#188a3e"/>
<line x1="92.0" y1="343.6" x2="92.0" y2="365.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="87.5" y="349.0" width="9" height="10.8" fill="#188a3e"/>
<line x1="108.0" y1="331.0" x2="108.0" y2="354.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="103.5" y="336.4" width="9" height="12.6" fill="#188a3e"/>
<line x1="124.0" y1="316.6" x2="124.0" y2="341.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="119.5" y="322.0" width="9" height="14.4" fill="#188a3e"/>
<line x1="140.0" y1="300.4" x2="140.0" y2="327.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="135.5" y="305.8" width="9" height="16.2" fill="#188a3e"/>
<line x1="156.0" y1="286.0" x2="156.0" y2="311.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="151.5" y="291.4" width="9" height="14.4" fill="#188a3e"/>
<line x1="172.0" y1="269.8" x2="172.0" y2="296.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="167.5" y="275.2" width="9" height="16.2" fill="#188a3e"/>
<line x1="188.0" y1="253.6" x2="188.0" y2="280.6" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="183.5" y="259.0" width="9" height="16.2" fill="#188a3e"/>
<line x1="204.0" y1="237.4" x2="204.0" y2="264.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="199.5" y="242.8" width="9" height="16.2" fill="#188a3e"/>
<line x1="220.0" y1="221.2" x2="220.0" y2="248.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="215.5" y="226.6" width="9" height="16.2" fill="#188a3e"/>
<line x1="236.0" y1="205.0" x2="236.0" y2="232.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="231.5" y="210.4" width="9" height="16.2" fill="#188a3e"/>
<line x1="252.0" y1="185.2" x2="252.0" y2="215.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="247.5" y="190.6" width="9" height="19.8" fill="#188a3e"/>
<line x1="268.0" y1="170.8" x2="268.0" y2="196.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="263.5" y="170.8" width="9" height="19.8" fill="#188a3e"/>
<line x1="284.0" y1="170.8" x2="284.0" y2="183.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="279.5" y="170.8" width="9" height="12.6" fill="#c00"/>
<line x1="300.0" y1="182.5" x2="300.0" y2="199.6" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="295.5" y="183.4" width="9" height="14.4" fill="#c00"/>
<line x1="316.0" y1="196.9" x2="316.0" y2="212.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="311.5" y="197.8" width="9" height="12.6" fill="#c00"/>
<line x1="332.0" y1="209.5" x2="332.0" y2="223.9" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="327.5" y="210.4" width="9" height="10.8" fill="#c00"/>
<line x1="348.0" y1="220.3" x2="348.0" y2="232.9" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="343.5" y="221.2" width="9" height="9.0" fill="#c00"/>
<line x1="364.0" y1="229.3" x2="364.0" y2="241.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="359.5" y="230.2" width="9" height="7.2" fill="#c00"/>
<line x1="380.0" y1="236.5" x2="380.0" y2="242.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="375.5" y="237.4" width="9" height="5.4" fill="#c00"/>
<line x1="396.0" y1="240.6" x2="396.0" y2="242.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="391.5" y="242.8" width="9" height="1.6" fill="#188a3e"/>
<line x1="412.0" y1="227.5" x2="412.0" y2="247.3" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="407.5" y="232.0" width="9" height="10.8" fill="#188a3e"/>
<line x1="428.0" y1="216.7" x2="428.0" y2="236.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="423.5" y="221.2" width="9" height="10.8" fill="#188a3e"/>
<line x1="444.0" y1="204.1" x2="444.0" y2="225.7" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="439.5" y="208.6" width="9" height="12.6" fill="#188a3e"/>
<line x1="460.0" y1="191.5" x2="460.0" y2="213.1" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="455.5" y="196.0" width="9" height="12.6" fill="#188a3e"/>
<line x1="476.0" y1="177.1" x2="476.0" y2="200.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="471.5" y="181.6" width="9" height="14.4" fill="#188a3e"/>
<line x1="492.0" y1="160.9" x2="492.0" y2="186.1" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="487.5" y="165.4" width="9" height="16.2" fill="#188a3e"/>
<line x1="508.0" y1="141.1" x2="508.0" y2="169.9" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="503.5" y="145.6" width="9" height="19.8" fill="#188a3e"/>
<line x1="524.0" y1="119.5" x2="524.0" y2="150.1" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="519.5" y="124.0" width="9" height="21.6" fill="#188a3e"/>
<line x1="540.0" y1="94.3" x2="540.0" y2="128.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="535.5" y="98.8" width="9" height="25.2" fill="#188a3e"/>
<line x1="556.0" y1="70.0" x2="556.0" y2="102.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="551.5" y="70.0" width="9" height="28.8" fill="#188a3e"/>
<line x1="24" y1="488.2" x2="706" y2="488.2" stroke="#888"/>
<rect x="39.5" y="456.1" width="9" height="32.1" fill="#188a3e" opacity="0.55"/>
<rect x="55.5" y="453.9" width="9" height="34.3" fill="#188a3e" opacity="0.55"/>
<rect x="71.5" y="457.6" width="9" height="30.6" fill="#188a3e" opacity="0.55"/>
<rect x="87.5" y="451.7" width="9" height="36.5" fill="#188a3e" opacity="0.55"/>
<rect x="103.5" y="455.4" width="9" height="32.8" fill="#188a3e" opacity="0.55"/>
<rect x="119.5" y="458.3" width="9" height="29.9" fill="#188a3e" opacity="0.55"/>
<rect x="135.5" y="453.2" width="9" height="35.0" fill="#188a3e" opacity="0.55"/>
<rect x="151.5" y="456.8" width="9" height="31.4" fill="#188a3e" opacity="0.55"/>
<rect x="167.5" y="454.7" width="9" height="33.5" fill="#188a3e" opacity="0.55"/>
<rect x="183.5" y="451.0" width="9" height="37.2" fill="#188a3e" opacity="0.55"/>
<rect x="199.5" y="456.1" width="9" height="32.1" fill="#188a3e" opacity="0.55"/>
<rect x="215.5" y="452.5" width="9" height="35.7" fill="#188a3e" opacity="0.55"/>
<rect x="231.5" y="454.7" width="9" height="33.5" fill="#188a3e" opacity="0.55"/>
<rect x="247.5" y="450.3" width="9" height="37.9" fill="#188a3e" opacity="0.55"/>
<rect x="263.5" y="445.9" width="9" height="42.3" fill="#188a3e" opacity="0.55"/>
<rect x="279.5" y="441.5" width="9" height="46.7" fill="#c00" opacity="0.55"/>
<rect x="295.5" y="437.2" width="9" height="51.0" fill="#c00" opacity="0.55"/>
<rect x="311.5" y="440.1" width="9" height="48.1" fill="#c00" opacity="0.55"/>
<rect x="327.5" y="443.7" width="9" height="44.5" fill="#c00" opacity="0.55"/>
<rect x="343.5" y="445.9" width="9" height="42.3" fill="#c00" opacity="0.55"/>
<rect x="359.5" y="434.2" width="9" height="54.0" fill="#c00" opacity="0.55"/>
<rect x="375.5" y="418.2" width="9" height="70.0" fill="#c00" opacity="0.55"/>
<rect x="391.5" y="424.0" width="9" height="64.2" fill="#188a3e" opacity="0.55"/>
<rect x="407.5" y="448.1" width="9" height="40.1" fill="#188a3e" opacity="0.55"/>
<rect x="423.5" y="445.9" width="9" height="42.3" fill="#188a3e" opacity="0.55"/>
<rect x="439.5" y="450.3" width="9" height="37.9" fill="#188a3e" opacity="0.55"/>
<rect x="455.5" y="444.4" width="9" height="43.8" fill="#188a3e" opacity="0.55"/>
<rect x="471.5" y="446.6" width="9" height="41.6" fill="#188a3e" opacity="0.55"/>
<rect x="487.5" y="442.3" width="9" height="45.9" fill="#188a3e" opacity="0.55"/>
<rect x="503.5" y="445.2" width="9" height="43.0" fill="#188a3e" opacity="0.55"/>
<rect x="519.5" y="440.1" width="9" height="48.1" fill="#188a3e" opacity="0.55"/>
<rect x="535.5" y="435.7" width="9" height="52.5" fill="#188a3e" opacity="0.55"/>
<rect x="551.5" y="426.9" width="9" height="61.2" fill="#188a3e" opacity="0.55"/>
<text x="24" y="412.2" fill="#555">volum</text>
<text x="284.0" y="160.8" fill="#b06000" text-anchor="middle" style="font-weight:bold">corecție 4.0 %</text>
<text x="380.0" y="226.5" fill="#b06000" text-anchor="middle" style="font-weight:bold">BIM</text>
<text x="396.0" y="282.8" fill="#188a3e" text-anchor="middle" style="font-weight:bold">BIM + candelă verde → intrare</text>
<text x="24" y="518" fill="#555">Niveluri (100.00 / 96.00 / 105.60) din curs; PPQ/SQ schematice, fără cifre în material.</text>
</svg>

After

Width:  |  Height:  |  Size: 9.9 KiB

158
gen/strategii/atmi_aplus.py Normal file
View File

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

View File

@@ -0,0 +1,119 @@
<svg viewBox="0 0 680 508" xmlns="http://www.w3.org/2000/svg" style="width:100%;height:auto;font:12px sans-serif">
<rect x="24" y="221.6" width="642" height="18.0" fill="#777" opacity="0.10"/>
<text x="24" y="48" fill="#333" style="font-weight:bold">SPY — daily: aceeași corecție, 4.0 % (DA) vs. 3.9 % (NU)</text>
<line x1="24" y1="156.8" x2="666" y2="156.8" stroke="#333" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="652" y="150.8" fill="#333" text-anchor="end">100.00 — swing high</text>
<line x1="24" y1="228.8" x2="666" y2="228.8" stroke="#188a3e" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="652" y="222.8" fill="#188a3e" text-anchor="end">96.00 = 4.0 % — DA, intri</text>
<line x1="24" y1="56.0" x2="666" y2="56.0" stroke="#1a5fb4" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="652" y="50.0" fill="#1a5fb4" text-anchor="end">TP +10 % = 105.60</text>
<line x1="44.0" y1="332.3" x2="44.0" y2="353.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="39.5" y="336.8" width="9" height="10.8" fill="#188a3e"/>
<line x1="60.0" y1="316.1" x2="60.0" y2="342.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="55.5" y="320.6" width="9" height="16.2" fill="#188a3e"/>
<line x1="76.0" y1="301.7" x2="76.0" y2="326.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="71.5" y="306.2" width="9" height="14.4" fill="#188a3e"/>
<line x1="92.0" y1="289.1" x2="92.0" y2="311.6" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="87.5" y="293.6" width="9" height="12.6" fill="#188a3e"/>
<line x1="108.0" y1="274.7" x2="108.0" y2="299.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="103.5" y="279.2" width="9" height="14.4" fill="#188a3e"/>
<line x1="124.0" y1="260.3" x2="124.0" y2="284.6" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="119.5" y="264.8" width="9" height="14.4" fill="#188a3e"/>
<line x1="140.0" y1="247.7" x2="140.0" y2="270.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="135.5" y="252.2" width="9" height="12.6" fill="#188a3e"/>
<line x1="156.0" y1="235.1" x2="156.0" y2="257.6" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="151.5" y="239.6" width="9" height="12.6" fill="#188a3e"/>
<line x1="172.0" y1="222.5" x2="172.0" y2="245.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="167.5" y="227.0" width="9" height="12.6" fill="#188a3e"/>
<line x1="188.0" y1="206.3" x2="188.0" y2="232.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="183.5" y="210.8" width="9" height="16.2" fill="#188a3e"/>
<line x1="204.0" y1="182.9" x2="204.0" y2="216.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="199.5" y="187.4" width="9" height="23.4" fill="#188a3e"/>
<line x1="220.0" y1="156.8" x2="220.0" y2="192.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="215.5" y="156.8" width="9" height="30.6" fill="#188a3e"/>
<line x1="236.0" y1="152.3" x2="236.0" y2="174.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="231.5" y="156.8" width="9" height="12.6" fill="#c00"/>
<line x1="252.0" y1="164.9" x2="252.0" y2="189.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="247.5" y="169.4" width="9" height="14.4" fill="#c00"/>
<line x1="268.0" y1="179.3" x2="268.0" y2="203.6" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="263.5" y="183.8" width="9" height="14.4" fill="#c00"/>
<line x1="284.0" y1="193.7" x2="284.0" y2="214.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="279.5" y="198.2" width="9" height="10.8" fill="#c00"/>
<line x1="300.0" y1="204.5" x2="300.0" y2="223.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="295.5" y="209.0" width="9" height="9.0" fill="#c00"/>
<line x1="316.0" y1="213.5" x2="316.0" y2="230.6" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="311.5" y="218.0" width="9" height="7.2" fill="#c00"/>
<line x1="332.0" y1="221.6" x2="332.0" y2="237.3" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="327.5" y="225.2" width="9" height="4.0" fill="#c00"/>
<line x1="348.0" y1="225.2" x2="348.0" y2="231.9" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="343.5" y="228.8" width="9" height="1.6" fill="#188a3e"/>
<line x1="364.0" y1="206.3" x2="364.0" y2="234.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="359.5" y="210.8" width="9" height="18.0" fill="#188a3e"/>
<line x1="380.0" y1="190.1" x2="380.0" y2="216.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="375.5" y="194.6" width="9" height="16.2" fill="#188a3e"/>
<line x1="396.0" y1="173.9" x2="396.0" y2="200.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="391.5" y="178.4" width="9" height="16.2" fill="#188a3e"/>
<line x1="412.0" y1="157.7" x2="412.0" y2="183.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="407.5" y="162.2" width="9" height="16.2" fill="#188a3e"/>
<line x1="428.0" y1="143.3" x2="428.0" y2="167.6" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="423.5" y="147.8" width="9" height="14.4" fill="#188a3e"/>
<line x1="444.0" y1="128.9" x2="444.0" y2="153.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="439.5" y="133.4" width="9" height="14.4" fill="#188a3e"/>
<line x1="460.0" y1="114.5" x2="460.0" y2="138.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="455.5" y="119.0" width="9" height="14.4" fill="#188a3e"/>
<line x1="476.0" y1="100.1" x2="476.0" y2="124.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="471.5" y="104.6" width="9" height="14.4" fill="#188a3e"/>
<line x1="492.0" y1="85.7" x2="492.0" y2="110.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="487.5" y="90.2" width="9" height="14.4" fill="#188a3e"/>
<line x1="508.0" y1="71.3" x2="508.0" y2="95.6" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="503.5" y="75.8" width="9" height="14.4" fill="#188a3e"/>
<line x1="524.0" y1="60.5" x2="524.0" y2="81.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="519.5" y="65.0" width="9" height="10.8" fill="#188a3e"/>
<line x1="540.0" y1="56.0" x2="540.0" y2="70.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="535.5" y="56.0" width="9" height="9.0" fill="#188a3e"/>
<line x1="24" y1="449.0" x2="666" y2="449.0" stroke="#888"/>
<rect x="39.5" y="410.8" width="9" height="38.2" fill="#188a3e" opacity="0.55"/>
<rect x="55.5" y="409.2" width="9" height="39.8" fill="#188a3e" opacity="0.55"/>
<rect x="71.5" y="412.4" width="9" height="36.6" fill="#188a3e" opacity="0.55"/>
<rect x="87.5" y="407.6" width="9" height="41.4" fill="#188a3e" opacity="0.55"/>
<rect x="103.5" y="406.0" width="9" height="43.0" fill="#188a3e" opacity="0.55"/>
<rect x="119.5" y="409.2" width="9" height="39.8" fill="#188a3e" opacity="0.55"/>
<rect x="135.5" y="404.5" width="9" height="44.5" fill="#188a3e" opacity="0.55"/>
<rect x="151.5" y="407.6" width="9" height="41.4" fill="#188a3e" opacity="0.55"/>
<rect x="167.5" y="402.9" width="9" height="46.1" fill="#188a3e" opacity="0.55"/>
<rect x="183.5" y="401.3" width="9" height="47.7" fill="#188a3e" opacity="0.55"/>
<rect x="199.5" y="398.1" width="9" height="50.9" fill="#188a3e" opacity="0.55"/>
<rect x="215.5" y="391.7" width="9" height="57.3" fill="#188a3e" opacity="0.55"/>
<rect x="231.5" y="393.3" width="9" height="55.7" fill="#c00" opacity="0.55"/>
<rect x="247.5" y="390.1" width="9" height="58.9" fill="#c00" opacity="0.55"/>
<rect x="263.5" y="387.0" width="9" height="62.0" fill="#c00" opacity="0.55"/>
<rect x="279.5" y="383.8" width="9" height="65.2" fill="#c00" opacity="0.55"/>
<rect x="295.5" y="380.6" width="9" height="68.4" fill="#c00" opacity="0.55"/>
<rect x="311.5" y="385.4" width="9" height="63.6" fill="#c00" opacity="0.55"/>
<rect x="327.5" y="382.2" width="9" height="66.8" fill="#c00" opacity="0.55"/>
<rect x="343.5" y="379.0" width="9" height="70.0" fill="#188a3e" opacity="0.55"/>
<rect x="359.5" y="391.7" width="9" height="57.3" fill="#188a3e" opacity="0.55"/>
<rect x="375.5" y="396.5" width="9" height="52.5" fill="#188a3e" opacity="0.55"/>
<rect x="391.5" y="399.7" width="9" height="49.3" fill="#188a3e" opacity="0.55"/>
<rect x="407.5" y="402.9" width="9" height="46.1" fill="#188a3e" opacity="0.55"/>
<rect x="423.5" y="406.0" width="9" height="43.0" fill="#188a3e" opacity="0.55"/>
<rect x="439.5" y="409.2" width="9" height="39.8" fill="#188a3e" opacity="0.55"/>
<rect x="455.5" y="412.4" width="9" height="36.6" fill="#188a3e" opacity="0.55"/>
<rect x="471.5" y="399.7" width="9" height="49.3" fill="#188a3e" opacity="0.55"/>
<rect x="487.5" y="394.9" width="9" height="54.1" fill="#188a3e" opacity="0.55"/>
<rect x="503.5" y="391.7" width="9" height="57.3" fill="#188a3e" opacity="0.55"/>
<rect x="519.5" y="387.0" width="9" height="62.0" fill="#188a3e" opacity="0.55"/>
<rect x="535.5" y="380.6" width="9" height="68.4" fill="#188a3e" opacity="0.55"/>
<text x="24" y="373.0" fill="#555">volum</text>
<text x="332.0" y="209.6" fill="#188a3e" text-anchor="middle" style="font-weight:bold">BIM</text>
<text x="348.0" y="271.9" fill="#188a3e" text-anchor="middle" style="font-weight:bold">candelă verde → trigger</text>
<text x="24" y="471.0" fill="#188a3e">Regula e pe procent, nu pe cât de dramatică arată linia: calea NU pare o cădere mare, dar e 3.9 %.</text>
<text x="24" y="496" fill="#555">Calea DA (candele): 4.0 % la 96.00. Calea NU (punctat): 3.9 % („3.93 %” în curs) — fără intrare.</text>
<polyline points="220.0,156.8 236.0,167.6 252.0,181.1 268.0,193.7 284.0,204.5 300.0,214.4 316.0,227.0 332.0,234.2 348.0,240.5 380.0,247.7 412.0,253.1 444.0,256.7" fill="none" stroke="#c00" stroke-width="1.8" stroke-dasharray="5 4"/>
<line x1="24" y1="227.0" x2="666" y2="227.0" stroke="#c00" stroke-width="1.5" stroke-dasharray="4 3"/>
<circle cx="316.0" cy="227.0" r="3.5" fill="#c00"/>
<text x="652" y="239.0" fill="#c00" text-anchor="end">96.10 = 3.9 % — NU, sub prag</text>
<text x="652" y="252.6" fill="#777" text-anchor="end">zonă quarterly</text>
<text x="24" y="183.8" fill="#188a3e" style="font-weight:bold">calea DA (candelele)</text>
<text x="444.0" y="272.7" fill="#c00" style="font-weight:bold">calea NU (continuă în jos)</text>
</svg>

After

Width:  |  Height:  |  Size: 9.8 KiB

View File

@@ -0,0 +1,125 @@
"""Diagrama din `gen/strategii/atmi_exemplu.spec.txt` (TIP: PRET).
Exemplul real din curs (ATMI / SPY weekly): corecție de context ≥4 %,
intrare pe 27.01.2005 la 117.43 după BIM + candelă verde, fără stop-loss
standard, urcare lentă până la TP1 +10 % = 129.17, atins ~1 an mai târziu
(data exactă nesigură în ASR).
Serie continuă de 38 de candele weekly (from_closes), panou de volum,
niveluri reale (intrare / TP1) și marcaje ancorate. Toate cifrele vin din
`summaries/04_MODUL_3/03_strategia_2_atmi.md` (liniile citate în spec);
singurul număr construit de mine e swing high-ul ~123.00, care trebuie să
conțină corecția de context.
"""
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
OUT = Path(__file__).resolve().parent / "atmi_exemplu.svg"
# ── cifrele din material ────────────────────────────────────────────────────
ENTRY = 117.43 # 04_MODUL_3/03_strategia_2_atmi.md:358-363
TP = 129.17 # TP1 = +10 % pe 117.43 → 129.17 (":170-171, 358-363")
SWING_HIGH = 123.00 # construit de mine ca să conțină corecția ≥4 %
ENTRY_I = 17 # candela de intrare (verde, precedată de BIM)
TP_I = 37 # ultima candelă: TP1 atins
BIM_I = ENTRY_I - 1 # BIM (bară de inversare bearish) înaintea candelei verzi
# ── structura seriei: 12 sus → 6 corecție → intrare → 20 spre TP ───────────
closes = [
# ~12 candele de urcare spre swing high ~123.00
115.80, 116.50, 117.20, 117.90, 117.60, 118.30,
119.00, 119.70, 120.40, 121.00, 121.70, 122.70,
# ~6 candele de corecție, de la 123.00 în zona de intrare
122.00, 121.10, 120.10, 119.00, 117.00,
117.43, # candela de intrare (verde)
# ~20 candele de urcare de la 117.43 spre 129.17
118.00, 118.55, 119.05, 119.50, 119.95,
120.40, 120.85, 121.25, 121.65, 122.10,
122.50, 122.95, 123.40, 123.85, 124.30,
125.00, 126.00, 127.10, 128.40, 129.17,
]
assert len(closes) == 38, len(closes)
wicks = [(0.25, 0.30)] * len(closes)
wicks[0] = (0.35, 0.35)
wicks[11] = (0.30, 0.25) # high = 122.70 + 0.30 = 123.00 (swing high exact)
wicks[BIM_I] = (0.15, 0.45)
wicks[ENTRY_I] = (0.20, 0.40)
wicks[TP_I] = (0.00, 0.30) # high = 129.17 exact, ca TP să fie max(high)
vols = [42, 40, 44, 47, 39, 45, 48, 52, 50, 55, 58, 62,
57, 61, 66, 72, 84,
70,
58, 55, 53, 57, 54, 56, 59, 55, 57, 60, 58, 61,
63, 60, 64, 66, 68, 72, 76, 88]
cds = from_closes(closes, wicks=wicks, vols=vols, start=115.30)
# ── assert-urile din specificație ───────────────────────────────────────────
entry = cds[ENTRY_I].c
tp = cds[TP_I].c
swing_high = max(c.h for c in cds[:ENTRY_I])
assert abs(entry - 117.43) < 0.05, entry
assert abs(tp - 129.17) < 0.05, tp
assert abs(tp / entry - 1.10) < 0.005, tp / entry
assert (swing_high - entry) / swing_high >= 0.04, (swing_high, entry)
assert abs(entry - cds[ENTRY_I].c) < 0.05
assert abs(max(c.h for c in cds[-10:]) - tp) < 0.05, max(c.h for c in cds[-10:])
# structura care susține afirmațiile din text
assert abs(swing_high - SWING_HIGH) < 0.05, swing_high
assert cds[ENTRY_I].c > cds[ENTRY_I].o, "candela de intrare trebuie să fie verde"
assert cds[BIM_I].c < cds[BIM_I].o, "BIM-ul dinaintea intrării trebuie să fie bearish"
assert any(c.vol is not None for c in cds), "lipsește panoul de volum"
n_entry_to_tp = TP_I - ENTRY_I + 1
assert 18 <= n_entry_to_tp <= 23, n_entry_to_tp
TP_LABEL = "TP1 atins ~1 an mai târziu (data exactă neclară în transcriere — ASR)"
assert "~1 an" in TP_LABEL
levels = [
Level(ENTRY, "intrare 117.43", "#188a3e", label_at="left"),
Level(TP, "TP1 129.17 (+10 %)", "#1a5fb4"),
]
marks = [
Mark(11, f"swing high ~{SWING_HIGH:.2f}", "#c00", "above"),
Mark(BIM_I, "BIM", "#777", "above"),
Mark(ENTRY_I, "27.01.2005", "#188a3e", "below"),
Mark(TP_I, "TP1", "#1a5fb4", "below"),
]
# nu există nicio linie de stop-loss în desen
assert not any("stop" in t.lower() for t in
[lv.text for lv in levels] + [mk.text for mk in marks])
assert len(levels) == 2, "doar intrare și TP; strategic nu există stop standard"
# etichetele lungi merg ca linii de text sub grafic, ca să nu se bată cu
# candelele din dreapta-sus (unde urcarea spre TP nu lasă loc liber)
cds[TP_I - 1].note = "după TP: capital + profit → cash până la următorul trigger valid"
cds[TP_I].note = TP_LABEL
ch = Chart(
candles=cds,
levels=levels,
marks=marks,
mas=[MA(20, "#b06000", dash="4 3")],
scale=9.5, width=660, gap=15, body_w=9, left=40, vol_h=70,
vol_label="volum", title="SPY — weekly: intrare 117.43 (27.01.2005) → TP +10 % = 129.17",
caption="Swing high ~123.00 construit de mine; cifrele de intrare/TP sunt din material.",
)
svg = ch.render()
assert svg.count("\n\n") == 0, "linie goală în SVG — rupe markdown-ul"
OUT.write_text(svg)
print(f"serie {len(cds)} candele | entry {entry:.2f} @ {ENTRY_I} | "
f"tp {tp:.2f} @ {TP_I} | swing high {swing_high:.2f} "
f"(corecție {(swing_high - entry) / swing_high * 100:.2f} %) | "
f"entry→TP {n_entry_to_tp} candele (~1 an)")
print(f"scris {OUT}")

View File

@@ -0,0 +1,131 @@
<svg viewBox="0 0 660 363" xmlns="http://www.w3.org/2000/svg" style="width:100%;height:auto;font:12px sans-serif">
<text x="24" y="48" fill="#333" style="font-weight:bold">SPY — weekly: intrare 117.43 (27.01.2005) → TP +10 % = 129.17</text>
<line x1="24" y1="167.5" x2="646" y2="167.5" stroke="#188a3e" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="30" y="161.5" fill="#188a3e">intrare 117.43</text>
<line x1="24" y1="56.0" x2="646" y2="56.0" stroke="#1a5fb4" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="632" y="50.0" fill="#1a5fb4" text-anchor="end">TP1 129.17 (+10 %)</text>
<polyline points="325.0,152.1 340.0,150.6 355.0,149.2 370.0,147.9 385.0,146.7 400.0,145.1 415.0,143.7 430.0,142.5 445.0,141.3 460.0,140.3 475.0,139.4 490.0,138.6 505.0,138.1 520.0,137.0 535.0,135.1 550.0,132.3 565.0,128.5 580.0,123.1 595.0,117.5" fill="none" stroke="#b06000" stroke-width="1.5" stroke-dasharray="4 3"/>
<line x1="40.0" y1="179.7" x2="40.0" y2="191.1" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="35.5" y="183.0" width="9" height="4.8" fill="#188a3e"/>
<line x1="55.0" y1="174.0" x2="55.0" y2="185.9" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="50.5" y="176.4" width="9" height="6.7" fill="#188a3e"/>
<line x1="70.0" y1="167.3" x2="70.0" y2="179.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="65.5" y="169.7" width="9" height="6.7" fill="#188a3e"/>
<line x1="85.0" y1="160.7" x2="85.0" y2="172.6" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="80.5" y="163.1" width="9" height="6.7" fill="#188a3e"/>
<line x1="100.0" y1="160.7" x2="100.0" y2="168.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="95.5" y="163.1" width="9" height="2.9" fill="#c00"/>
<line x1="115.0" y1="156.9" x2="115.0" y2="168.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="110.5" y="159.3" width="9" height="6.7" fill="#188a3e"/>
<line x1="130.0" y1="150.2" x2="130.0" y2="162.1" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="125.5" y="152.6" width="9" height="6.7" fill="#188a3e"/>
<line x1="145.0" y1="143.6" x2="145.0" y2="155.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="140.5" y="146.0" width="9" height="6.7" fill="#188a3e"/>
<line x1="160.0" y1="136.9" x2="160.0" y2="148.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="155.5" y="139.3" width="9" height="6.7" fill="#188a3e"/>
<line x1="175.0" y1="131.2" x2="175.0" y2="142.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="170.5" y="133.6" width="9" height="5.7" fill="#188a3e"/>
<line x1="190.0" y1="124.6" x2="190.0" y2="136.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="185.5" y="127.0" width="9" height="6.7" fill="#188a3e"/>
<line x1="205.0" y1="114.6" x2="205.0" y2="129.3" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="200.5" y="117.5" width="9" height="9.5" fill="#188a3e"/>
<line x1="220.0" y1="115.1" x2="220.0" y2="127.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="215.5" y="117.5" width="9" height="6.7" fill="#c00"/>
<line x1="235.0" y1="121.7" x2="235.0" y2="135.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="230.5" y="124.1" width="9" height="8.6" fill="#c00"/>
<line x1="250.0" y1="130.3" x2="250.0" y2="145.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="245.5" y="132.7" width="9" height="9.5" fill="#c00"/>
<line x1="265.0" y1="139.8" x2="265.0" y2="155.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="260.5" y="142.2" width="9" height="10.4" fill="#c00"/>
<line x1="280.0" y1="151.2" x2="280.0" y2="175.9" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="275.5" y="152.6" width="9" height="19.0" fill="#c00"/>
<line x1="295.0" y1="165.6" x2="295.0" y2="175.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="290.5" y="167.5" width="9" height="4.1" fill="#188a3e"/>
<line x1="310.0" y1="159.7" x2="310.0" y2="170.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="305.5" y="162.1" width="9" height="5.4" fill="#188a3e"/>
<line x1="325.0" y1="154.5" x2="325.0" y2="165.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="320.5" y="156.9" width="9" height="5.2" fill="#188a3e"/>
<line x1="340.0" y1="149.8" x2="340.0" y2="159.7" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="335.5" y="152.1" width="9" height="4.8" fill="#188a3e"/>
<line x1="355.0" y1="145.5" x2="355.0" y2="155.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="350.5" y="147.9" width="9" height="4.3" fill="#188a3e"/>
<line x1="370.0" y1="141.2" x2="370.0" y2="150.7" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="365.5" y="143.6" width="9" height="4.3" fill="#188a3e"/>
<line x1="385.0" y1="136.9" x2="385.0" y2="146.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="380.5" y="139.3" width="9" height="4.3" fill="#188a3e"/>
<line x1="400.0" y1="132.7" x2="400.0" y2="142.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="395.5" y="135.0" width="9" height="4.3" fill="#188a3e"/>
<line x1="415.0" y1="128.9" x2="415.0" y2="137.9" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="410.5" y="131.2" width="9" height="3.8" fill="#188a3e"/>
<line x1="430.0" y1="125.1" x2="430.0" y2="134.1" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="425.5" y="127.4" width="9" height="3.8" fill="#188a3e"/>
<line x1="445.0" y1="120.8" x2="445.0" y2="130.3" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="440.5" y="123.2" width="9" height="4.3" fill="#188a3e"/>
<line x1="460.0" y1="117.0" x2="460.0" y2="126.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="455.5" y="119.4" width="9" height="3.8" fill="#188a3e"/>
<line x1="475.0" y1="112.7" x2="475.0" y2="122.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="470.5" y="115.1" width="9" height="4.3" fill="#188a3e"/>
<line x1="490.0" y1="108.4" x2="490.0" y2="117.9" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="485.5" y="110.8" width="9" height="4.3" fill="#188a3e"/>
<line x1="505.0" y1="104.2" x2="505.0" y2="113.7" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="500.5" y="106.5" width="9" height="4.3" fill="#188a3e"/>
<line x1="520.0" y1="99.9" x2="520.0" y2="109.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="515.5" y="102.3" width="9" height="4.3" fill="#188a3e"/>
<line x1="535.0" y1="93.2" x2="535.0" y2="105.1" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="530.5" y="95.6" width="9" height="6.7" fill="#188a3e"/>
<line x1="550.0" y1="83.7" x2="550.0" y2="98.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="545.5" y="86.1" width="9" height="9.5" fill="#188a3e"/>
<line x1="565.0" y1="73.3" x2="565.0" y2="89.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="560.5" y="75.7" width="9" height="10.4" fill="#188a3e"/>
<line x1="580.0" y1="60.9" x2="580.0" y2="78.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="575.5" y="63.3" width="9" height="12.4" fill="#188a3e"/>
<line x1="595.0" y1="56.0" x2="595.0" y2="66.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="590.5" y="56.0" width="9" height="7.3" fill="#188a3e"/>
<line x1="24" y1="287.1" x2="646" y2="287.1" stroke="#888"/>
<rect x="35.5" y="253.7" width="9" height="33.4" fill="#188a3e" opacity="0.55"/>
<rect x="50.5" y="255.3" width="9" height="31.8" fill="#188a3e" opacity="0.55"/>
<rect x="65.5" y="252.1" width="9" height="35.0" fill="#188a3e" opacity="0.55"/>
<rect x="80.5" y="249.7" width="9" height="37.4" fill="#188a3e" opacity="0.55"/>
<rect x="95.5" y="256.1" width="9" height="31.0" fill="#c00" opacity="0.55"/>
<rect x="110.5" y="251.3" width="9" height="35.8" fill="#188a3e" opacity="0.55"/>
<rect x="125.5" y="248.9" width="9" height="38.2" fill="#188a3e" opacity="0.55"/>
<rect x="140.5" y="245.7" width="9" height="41.4" fill="#188a3e" opacity="0.55"/>
<rect x="155.5" y="247.3" width="9" height="39.8" fill="#188a3e" opacity="0.55"/>
<rect x="170.5" y="243.3" width="9" height="43.8" fill="#188a3e" opacity="0.55"/>
<rect x="185.5" y="241.0" width="9" height="46.1" fill="#188a3e" opacity="0.55"/>
<rect x="200.5" y="237.8" width="9" height="49.3" fill="#188a3e" opacity="0.55"/>
<rect x="215.5" y="241.7" width="9" height="45.3" fill="#c00" opacity="0.55"/>
<rect x="230.5" y="238.6" width="9" height="48.5" fill="#c00" opacity="0.55"/>
<rect x="245.5" y="234.6" width="9" height="52.5" fill="#c00" opacity="0.55"/>
<rect x="260.5" y="229.8" width="9" height="57.3" fill="#c00" opacity="0.55"/>
<rect x="275.5" y="220.3" width="9" height="66.8" fill="#c00" opacity="0.55"/>
<rect x="290.5" y="231.4" width="9" height="55.7" fill="#188a3e" opacity="0.55"/>
<rect x="305.5" y="241.0" width="9" height="46.1" fill="#188a3e" opacity="0.55"/>
<rect x="320.5" y="243.3" width="9" height="43.8" fill="#188a3e" opacity="0.55"/>
<rect x="335.5" y="244.9" width="9" height="42.2" fill="#188a3e" opacity="0.55"/>
<rect x="350.5" y="241.7" width="9" height="45.3" fill="#188a3e" opacity="0.55"/>
<rect x="365.5" y="244.1" width="9" height="43.0" fill="#188a3e" opacity="0.55"/>
<rect x="380.5" y="242.5" width="9" height="44.5" fill="#188a3e" opacity="0.55"/>
<rect x="395.5" y="240.2" width="9" height="46.9" fill="#188a3e" opacity="0.55"/>
<rect x="410.5" y="243.3" width="9" height="43.8" fill="#188a3e" opacity="0.55"/>
<rect x="425.5" y="241.7" width="9" height="45.3" fill="#188a3e" opacity="0.55"/>
<rect x="440.5" y="239.4" width="9" height="47.7" fill="#188a3e" opacity="0.55"/>
<rect x="455.5" y="241.0" width="9" height="46.1" fill="#188a3e" opacity="0.55"/>
<rect x="470.5" y="238.6" width="9" height="48.5" fill="#188a3e" opacity="0.55"/>
<rect x="485.5" y="237.0" width="9" height="50.1" fill="#188a3e" opacity="0.55"/>
<rect x="500.5" y="239.4" width="9" height="47.7" fill="#188a3e" opacity="0.55"/>
<rect x="515.5" y="236.2" width="9" height="50.9" fill="#188a3e" opacity="0.55"/>
<rect x="530.5" y="234.6" width="9" height="52.5" fill="#188a3e" opacity="0.55"/>
<rect x="545.5" y="233.0" width="9" height="54.1" fill="#188a3e" opacity="0.55"/>
<rect x="560.5" y="229.8" width="9" height="57.3" fill="#188a3e" opacity="0.55"/>
<rect x="575.5" y="226.6" width="9" height="60.5" fill="#188a3e" opacity="0.55"/>
<rect x="590.5" y="217.1" width="9" height="70.0" fill="#188a3e" opacity="0.55"/>
<text x="24" y="211.1" fill="#555">volum</text>
<text x="205.0" y="104.6" fill="#c00" text-anchor="middle" style="font-weight:bold">swing high ~123.00</text>
<text x="280.0" y="141.2" fill="#777" text-anchor="middle" style="font-weight:bold">BIM</text>
<text x="295.0" y="195.4" fill="#188a3e" text-anchor="middle" style="font-weight:bold">27.01.2005</text>
<text x="607.0" y="86.2" fill="#1a5fb4" text-anchor="end" style="font-weight:bold">TP1</text>
<text x="24" y="309.1" fill="#188a3e">după TP: capital + profit → cash până la următorul trigger valid</text>
<text x="24" y="326.1" fill="#188a3e">TP1 atins ~1 an mai târziu (data exactă neclară în transcriere — ASR)</text>
<text x="24" y="351" fill="#555">Swing high ~123.00 construit de mine; cifrele de intrare/TP sunt din material.</text>
</svg>

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -0,0 +1,230 @@
#!/usr/bin/env python3
"""Orizontul de ținere al ATMI — două descrieri incompatibile, pe aceeași axă.
Pentru `strategii/03_atmi.md`, marcajul `<!-- DIAGRAMA: orizont -->`.
Nu e grafic de preț, e o linie de timp comparativă: SVG scris de mână, doar
stdlib — nu se folosește svg_candles / pchart.
Ce arată (toate cifrele / citatele vin din spec, nu se inventează prețuri):
- bara de sus, albastră: „ATMI clasică — fișierul principal", orizont luni
până la ~1 an, fără stop-loss, TP1 atins „aproximativ un an mai târziu";
- bara de jos, portocalie: „ATMI ca adaptare pentru prop — Q&A 11.06.2025",
orizont 2 săptămâni;
- cutia de anotare: pe prop, limita zilnică se calculează și pe pozițiile
deschise, iar la atingere firma închide automat; și nota că descrierile nu
se rezolvă aici, de clarificat cu autorul.
Barele sunt pe o axă comună 0 → 1 an, ca diferența să fie vizibilă; bara de
2 săptămâni e scurtă exact pentru că atât ține, nu din greșeală de scară.
Rulare: python3 gen/strategii/atmi_orizont.py -> gen/strategii/atmi_orizont.svg
"""
from pathlib import Path
from xml.sax.saxutils import escape
OUT = Path(__file__).with_suffix(".svg")
W, H = 700, 670
BLACK, GRAY, BLUE, ORANGE, WHITE = "#000000", "#777777", "#1a5fb4", "#b06000", "#ffffff"
AX_X0, AX_X1 = 60.0, 650.0
AX_W = AX_X1 - AX_X0 # 590 px = 1 an
AX_Y = 112.0
TICKS = [0, 3, 6, 9, 12] # luni
TICK_LABELS = {0: "0", 3: "3 luni", 6: "6 luni", 9: "9 luni", 12: "~1 an"}
WEEKS_YEAR = 52.0
LEN_PROP = AX_W * 2 / WEEKS_YEAR # ~22.7 px = 2 săptămâni
BAR_TOP_Y = 172.0
BAR_PROP_Y = 298.0
BAR_H = 42.0
CARD_X, CARD_Y, CARD_W, CARD_H = 160.0, 292.0, 500.0, 112.0
BOX_X, BOX_Y, BOX_W, BOX_H = 40.0, 440.0, 620.0, 150.0
TEXTS = [] # (x, y, text, size, weight, anchor) pentru verificare
def txt(x, y, s, size=13, weight="400", fill=BLACK, anchor="middle"):
TEXTS.append((x, y, s, size, weight, anchor))
return (
f'<text x="{x:.1f}" y="{y:.1f}" font-size="{size}" font-weight="{weight}" '
f'fill="{fill}" text-anchor="{anchor}">{escape(s)}</text>'
)
def line(x1, y1, x2, y2, stroke=GRAY, width=1.0, dash=None):
d = f' stroke-dasharray="{dash}"' if dash else ""
return (f'<line x1="{x1:.1f}" y1="{y1:.1f}" x2="{x2:.1f}" y2="{y2:.1f}" '
f'stroke="{stroke}" stroke-width="{width}"{d}/>')
def tick_x(months):
return AX_X0 + AX_W * months / 12.0
def build():
p = [
f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 {W} {H}" '
f'width="{W}" height="{H}" role="img" '
f'aria-label="Orizontul de tinere al ATMI: fisiarul principal spune luni '
f'pana la un an, adaptarea pentru prop spune doua saptamani; cele doua '
f'descrieri raman nerezolvate">',
'<g font-family="system-ui, sans-serif">',
f'<rect x="0" y="0" width="{W}" height="{H}" fill="{WHITE}"/>',
txt(W / 2, 34, "Cât ții poziția? Două descrieri, același nume", 19, "700", BLACK),
txt(W / 2, 58, "fișierul principal vs. sfatul din Q&A pentru calificarea "
"pe prop", 13, "400", GRAY),
txt(W / 2, 84, "Aceeași axă de timp pentru ambele bare (0 → 1 an); "
"fără axe de preț.", 12, "400", GRAY),
]
# grid vertical, în spatele barelor
for m in TICKS:
x = tick_x(m)
p.append(line(x, 120, x, 425, "#ddd", 1.0, "3 4"))
# axa de timp
p.append(line(AX_X0, AX_Y, AX_X1, AX_Y, GRAY, 1.2))
for m in TICKS:
x = tick_x(m)
p.append(line(x, AX_Y - 6, x, AX_Y + 6, GRAY, 1.2))
p.append(txt(x, 132, TICK_LABELS[m], 12, "400", GRAY))
# ------- bara de sus: ATMI clasică
p.append(txt(AX_X0, 162, "ATMI clasică — fișierul principal", 15, "700", BLUE,
"start"))
p.append(f'<rect x="{AX_X0}" y="{BAR_TOP_Y}" width="{AX_W:g}" height="{BAR_H:g}" '
f'rx="6" fill="#eef4fd" stroke="{BLUE}" stroke-width="1.5"/>')
p.append(txt(W / 2, BAR_TOP_Y + 26,
"„fără stop-loss, poziția ținută luni/ani”", 13, "400", BLACK))
p.append(line(AX_X1, 164, AX_X1, 226, BLUE, 1.2, "4 3"))
p.append(txt(AX_X1, 244, "TP1 atins — aproximativ un an mai târziu",
13, "400", BLUE, "end"))
p.append(txt(AX_X0, 262, "Sursă: 04_MODUL_3/03_strategia_2_atmi.md:26-29; "
":358-363", 11, "400", GRAY, "start"))
# ------- bara de jos: adaptarea pentru prop
p.append(txt(AX_X0, 282, "ATMI ca adaptare pentru prop — Q&A 11.06.2025",
15, "700", ORANGE, "start"))
p.append(f'<rect x="{AX_X0}" y="{BAR_PROP_Y}" width="{LEN_PROP:.1f}" '
f'height="{BAR_H:g}" rx="6" fill="#fdf0e0" stroke="{ORANGE}" '
f'stroke-width="1.5"/>')
p.append(line(AX_X0 + LEN_PROP, 290, AX_X0 + LEN_PROP, 360, ORANGE, 1.2, "4 3"))
p.append(line(AX_X0 + LEN_PROP, BAR_PROP_Y + BAR_H / 2, CARD_X,
BAR_PROP_Y + BAR_H / 2, ORANGE, 1.0, "3 3"))
p.append(txt(AX_X0 + 30, 312, "2 săptămâni", 11, "700", ORANGE, "start"))
p.append(f'<rect x="{CARD_X}" y="{CARD_Y}" width="{CARD_W}" height="{CARD_H}" '
f'rx="8" fill="{WHITE}" stroke="{ORANGE}" stroke-width="1.5"/>')
p.append(txt(CARD_X + CARD_W / 2, 316,
"„aștepți o corecție și intri pe ATMI la suport quarterly,",
13, "400", BLACK))
p.append(txt(CARD_X + CARD_W / 2, 334, "swing, 2 săptămâni în poziție”",
13, "400", BLACK))
p.append(txt(CARD_X + CARD_W / 2, 358,
"„Ai nevoie de 3 transacții să califici propul.”",
13, "400", BLACK))
p.append(txt(CARD_X + CARD_W / 2, 382,
"Sursă: 09_QA/26_2025-06-11.md:225-227", 11, "400", GRAY))
# ------- cutia de anotare
p.append(f'<rect x="{BOX_X}" y="{BOX_Y}" width="{BOX_W}" height="{BOX_H}" '
f'rx="8" fill="#fafafa" stroke="{GRAY}" stroke-width="1.2"/>')
p.append(txt(BOX_X + 20, 466,
"Anotare separată — mecanismul care face diferența",
14, "700", BLACK, "start"))
p.append(txt(BOX_X + 20, 492,
"Pe prop, limita zilnică se calculează și pe pozițiile deschise,",
13, "400", BLACK, "start"))
p.append(txt(BOX_X + 20, 510, "iar la atingere firma închide automat poziția.",
13, "400", BLACK, "start"))
p.append(txt(BOX_X + 20, 530, "Sursă: 09_QA/26_2025-06-11.md:216-222",
11, "400", GRAY, "start"))
p.append(txt(BOX_X + 20, 560,
"Notă: cele două descrieri nu se rezolvă aici — de clarificat "
"cu autorul.", 13, "700", BLACK, "start"))
p.append(txt(W / 2, 626, "Fără axe de preț și fără să alegem una dintre "
"variante.", 12, "400", GRAY))
p.append(txt(W / 2, 646, "Bara de 2 săptămâni e scurtă exact pentru că "
"atât ține, pe aceeași scară.", 12, "400", GRAY))
p.append('</g></svg>')
return "\n".join(p) + "\n"
# ---------------------------------------------------------------- verificare
def est_w(s, size, weight):
factor = 0.62 if weight == "700" else 0.56
return len(s) * size * factor
def verify(svg):
# 1. geometria barelor, față de axa comună
assert abs(AX_W - (AX_X1 - AX_X0)) < 1e-9
assert abs(LEN_PROP - AX_W * 2 / WEEKS_YEAR) < 1e-9
assert LEN_PROP < AX_W / 20, "bara de 2 săptămâni ar trebui să fie vizibil scurtă"
assert AX_X0 + LEN_PROP < CARD_X, "bara prop intră în card"
assert BAR_TOP_Y + BAR_H < BAR_PROP_Y
assert CARD_X + CARD_W <= AX_X1 + 10
# 2. toate etichetele în panză
for x, y, s, size, weight, anchor in TEXTS:
ew = est_w(s, size, weight)
left = x - ew if anchor == "end" else (x - ew / 2 if anchor == "middle" else x)
right = left + ew
assert -2 <= left and right <= W + 2, f"text în afara panzei: {s!r}"
assert 0 <= y <= H, f"text în afara înălțimii: {s!r} (y={y})"
# 3. etichete care nu se suprapun
for i in range(len(TEXTS)):
xi, yi, si, szi, wi, ai = TEXTS[i]
we = est_w(si, szi, wi)
li = xi - we if ai == "end" else (xi - we / 2 if ai == "middle" else xi)
ri = li + we
for j in range(i + 1, len(TEXTS)):
xj, yj, sj, szj, wj, aj = TEXTS[j]
wej = est_w(sj, szj, wj)
lj = xj - wej if aj == "end" else (xj - wej / 2 if aj == "middle" else xj)
rj = lj + wej
if abs(yi - yj) < 13 and li < rj - 4 and lj < ri - 4:
raise AssertionError(f"texte suprapuse: {si!r} / {sj!r}")
# 4. textul cardului încape în card
for x, y, s, size, weight, anchor in TEXTS:
if CARD_Y <= y <= CARD_Y + CARD_H and CARD_X <= x <= CARD_X + CARD_W:
ew = est_w(s, size, weight)
assert ew <= CARD_W - 24, f"eticheta nu încape în card: {s!r} ({ew:.0f})"
# 5. cutia de anotare: textul nu iese
for x, y, s, size, weight, anchor in TEXTS:
if BOX_Y <= y <= BOX_Y + BOX_H and x >= BOX_X:
ew = est_w(s, size, weight)
assert BOX_X + 20 + ew <= BOX_X + BOX_W - 12, f"text iese din cutie: {s!r}"
# 6. citatele și sursele din spec sunt prezente
for cheie in ("fără stop-loss, poziția ținută luni/ani",
"aproximativ un an mai târziu",
"suport quarterly",
"2 săptămâni în poziție",
"3 transacții să califici propul",
"limita zilnică se calculează",
"închide automat",
"nu se rezolvă aici",
"03_strategia_2_atmi.md:26-29",
"09_QA/26_2025-06-11.md:225-227",
"09_QA/26_2025-06-11.md:216-222"):
assert cheie in svg, f"lipsește din desen: {cheie!r}"
# 7. fără cifre de preț inventate
for interzis in ("$", "USD", "SL ", "intrare "):
assert interzis not in svg, f"nu ar trebui să apară preț: {interzis!r}"
return True
SVG = build()
assert verify(SVG), "verificare eșuată"
OUT.write_text(SVG, encoding="utf-8")
print(f"scris {OUT} ({W}x{H}), verificare OK: {len(TEXTS)} etichete, "
f"bara prop {LEN_PROP:.1f}px din {AX_W:g}px")

View File

@@ -0,0 +1,47 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 700 670" width="700" height="670" role="img" aria-label="Orizontul de tinere al ATMI: fisiarul principal spune luni pana la un an, adaptarea pentru prop spune doua saptamani; cele doua descrieri raman nerezolvate">
<g font-family="system-ui, sans-serif">
<rect x="0" y="0" width="700" height="670" fill="#ffffff"/>
<text x="350.0" y="34.0" font-size="19" font-weight="700" fill="#000000" text-anchor="middle">Cât ții poziția? Două descrieri, același nume</text>
<text x="350.0" y="58.0" font-size="13" font-weight="400" fill="#777777" text-anchor="middle">fișierul principal vs. sfatul din Q&amp;A pentru calificarea pe prop</text>
<text x="350.0" y="84.0" font-size="12" font-weight="400" fill="#777777" text-anchor="middle">Aceeași axă de timp pentru ambele bare (0 → 1 an); fără axe de preț.</text>
<line x1="60.0" y1="120.0" x2="60.0" y2="425.0" stroke="#ddd" stroke-width="1.0" stroke-dasharray="3 4"/>
<line x1="207.5" y1="120.0" x2="207.5" y2="425.0" stroke="#ddd" stroke-width="1.0" stroke-dasharray="3 4"/>
<line x1="355.0" y1="120.0" x2="355.0" y2="425.0" stroke="#ddd" stroke-width="1.0" stroke-dasharray="3 4"/>
<line x1="502.5" y1="120.0" x2="502.5" y2="425.0" stroke="#ddd" stroke-width="1.0" stroke-dasharray="3 4"/>
<line x1="650.0" y1="120.0" x2="650.0" y2="425.0" stroke="#ddd" stroke-width="1.0" stroke-dasharray="3 4"/>
<line x1="60.0" y1="112.0" x2="650.0" y2="112.0" stroke="#777777" stroke-width="1.2"/>
<line x1="60.0" y1="106.0" x2="60.0" y2="118.0" stroke="#777777" stroke-width="1.2"/>
<text x="60.0" y="132.0" font-size="12" font-weight="400" fill="#777777" text-anchor="middle">0</text>
<line x1="207.5" y1="106.0" x2="207.5" y2="118.0" stroke="#777777" stroke-width="1.2"/>
<text x="207.5" y="132.0" font-size="12" font-weight="400" fill="#777777" text-anchor="middle">3 luni</text>
<line x1="355.0" y1="106.0" x2="355.0" y2="118.0" stroke="#777777" stroke-width="1.2"/>
<text x="355.0" y="132.0" font-size="12" font-weight="400" fill="#777777" text-anchor="middle">6 luni</text>
<line x1="502.5" y1="106.0" x2="502.5" y2="118.0" stroke="#777777" stroke-width="1.2"/>
<text x="502.5" y="132.0" font-size="12" font-weight="400" fill="#777777" text-anchor="middle">9 luni</text>
<line x1="650.0" y1="106.0" x2="650.0" y2="118.0" stroke="#777777" stroke-width="1.2"/>
<text x="650.0" y="132.0" font-size="12" font-weight="400" fill="#777777" text-anchor="middle">~1 an</text>
<text x="60.0" y="162.0" font-size="15" font-weight="700" fill="#1a5fb4" text-anchor="start">ATMI clasică — fișierul principal</text>
<rect x="60.0" y="172.0" width="590" height="42" rx="6" fill="#eef4fd" stroke="#1a5fb4" stroke-width="1.5"/>
<text x="350.0" y="198.0" font-size="13" font-weight="400" fill="#000000" text-anchor="middle">„fără stop-loss, poziția ținută luni/ani”</text>
<line x1="650.0" y1="164.0" x2="650.0" y2="226.0" stroke="#1a5fb4" stroke-width="1.2" stroke-dasharray="4 3"/>
<text x="650.0" y="244.0" font-size="13" font-weight="400" fill="#1a5fb4" text-anchor="end">TP1 atins — aproximativ un an mai târziu</text>
<text x="60.0" y="262.0" font-size="11" font-weight="400" fill="#777777" text-anchor="start">Sursă: 04_MODUL_3/03_strategia_2_atmi.md:26-29; :358-363</text>
<text x="60.0" y="282.0" font-size="15" font-weight="700" fill="#b06000" text-anchor="start">ATMI ca adaptare pentru prop — Q&amp;A 11.06.2025</text>
<rect x="60.0" y="298.0" width="22.7" height="42" rx="6" fill="#fdf0e0" stroke="#b06000" stroke-width="1.5"/>
<line x1="82.7" y1="290.0" x2="82.7" y2="360.0" stroke="#b06000" stroke-width="1.2" stroke-dasharray="4 3"/>
<line x1="82.7" y1="319.0" x2="160.0" y2="319.0" stroke="#b06000" stroke-width="1.0" stroke-dasharray="3 3"/>
<text x="90.0" y="312.0" font-size="11" font-weight="700" fill="#b06000" text-anchor="start">2 săptămâni</text>
<rect x="160.0" y="292.0" width="500.0" height="112.0" rx="8" fill="#ffffff" stroke="#b06000" stroke-width="1.5"/>
<text x="410.0" y="316.0" font-size="13" font-weight="400" fill="#000000" text-anchor="middle">„aștepți o corecție și intri pe ATMI la suport quarterly,</text>
<text x="410.0" y="334.0" font-size="13" font-weight="400" fill="#000000" text-anchor="middle">swing, 2 săptămâni în poziție”</text>
<text x="410.0" y="358.0" font-size="13" font-weight="400" fill="#000000" text-anchor="middle">„Ai nevoie de 3 transacții să califici propul.”</text>
<text x="410.0" y="382.0" font-size="11" font-weight="400" fill="#777777" text-anchor="middle">Sursă: 09_QA/26_2025-06-11.md:225-227</text>
<rect x="40.0" y="440.0" width="620.0" height="150.0" rx="8" fill="#fafafa" stroke="#777777" stroke-width="1.2"/>
<text x="60.0" y="466.0" font-size="14" font-weight="700" fill="#000000" text-anchor="start">Anotare separată — mecanismul care face diferența</text>
<text x="60.0" y="492.0" font-size="13" font-weight="400" fill="#000000" text-anchor="start">Pe prop, limita zilnică se calculează și pe pozițiile deschise,</text>
<text x="60.0" y="510.0" font-size="13" font-weight="400" fill="#000000" text-anchor="start">iar la atingere firma închide automat poziția.</text>
<text x="60.0" y="530.0" font-size="11" font-weight="400" fill="#777777" text-anchor="start">Sursă: 09_QA/26_2025-06-11.md:216-222</text>
<text x="60.0" y="560.0" font-size="13" font-weight="700" fill="#000000" text-anchor="start">Notă: cele două descrieri nu se rezolvă aici — de clarificat cu autorul.</text>
<text x="350.0" y="626.0" font-size="12" font-weight="400" fill="#777777" text-anchor="middle">Fără axe de preț și fără să alegem una dintre variante.</text>
<text x="350.0" y="646.0" font-size="12" font-weight="400" fill="#777777" text-anchor="middle">Bara de 2 săptămâni e scurtă exact pentru că atât ține, pe aceeași scară.</text>
</g></svg>

After

Width:  |  Height:  |  Size: 5.7 KiB

View File

@@ -0,0 +1,103 @@
"""Buy Doji — diagrama de ansamblu (daily, exemplu construit, fără ticker).
Citește specificația `gen/strategii/buy_doji_ansamblu.spec.txt`. Seria de
32 de candele e construită din lista de close-uri dată, cu open-ul fiecărei
bare = close-ul precedentului; wick-urile sunt fixate ca nivelurile
(doji 50.10/49.20, intrare 50.40, SL 49.20, TP1 51.60, trail 52.60, ieșire
sub trail la bara 28) să iasă exact din serie, nu doar din etichetă.
"""
import sys; sys.path.insert(0, "/workspace/atm-curs-dl")
from svg_candles import Chart, Level, Mark, MA, from_closes
# --- seria: 32 de close-uri, exact ca în specificație -----------------------
closes = [
60.20, 59.20, 59.60, 58.40, 57.30, 57.70, 56.50, 55.40, 55.80, 54.70, # 0-9
53.50, 52.30, 51.00, 49.65, # 10-13 downtrend
49.60, # 14 doji
49.90, # 15 +1 (nu confirmă)
50.40, # 16 +2 confirmare => intrare
50.90, 51.20, 51.50, 51.80, 52.10, 52.40, 52.20, 52.70, 52.95, 53.20, # 17-26
52.80, 52.30, 52.00, 51.80, 51.90, # 27-31
]
assert len(closes) == 32
# wick-uri: (up, down) implicite (0.25, 0.30) + excepțiile din specificație
wicks = [(0.25, 0.30)] * 32
wicks[14] = (0.45, 0.40) # doji: high 50.10, low 49.20
wicks[15] = (0.15, 0.15) # high 50.05, low 49.45
wicks[16] = (0.15, 0.15) # high 50.55, low 49.75
for k in range(24, 28): # barele 24-27
wicks[k] = (0.25, 0.20)
# volum relativ ales de generator (materialul nu dă volum pe exemplu);
# peste medie pe doji (14) și pe confirmare (16), apoi normal
vols = [42, 44, 40, 46, 43, 41, 45, 42, 44, 47,
43, 45, 48, 52, 78, 46, 88, 55,
50, 48, 52, 47, 45, 49, 46, 44, 48, 51,
54, 47, 45, 46]
assert len(vols) == 32
media_vol = sum(vols) / len(vols)
assert vols[14] > media_vol and vols[16] > media_vol, "volum peste medie pe doji/confirmare"
cds = from_closes(closes, wicks=wicks, vols=vols, start=60.20)
# adnotările pe bara +1 și pe confirmare, ca linii sub grafic (fără suprapunere)
cds[15].color = "#777" # bara de așteptare: neutră, nu confirmă
cds[15].note = "bara 15: close 49.90 sub high-ul dojiului 50.10 — NU confirmă"
cds[16].note = "bara 16: close 50.40 peste 50.10 — confirmare (intrare)"
doji, wait, conf = cds[14], cds[15], cds[16]
entry, sl, tp1, trail = 50.40, 49.20, 51.60, 52.60
# --- cele 9 assert-uri cerute de specificație ------------------------------
# 1. SL-ul e exact low-ul dojiului, cel mai jos din fereastra de 4 candele
assert abs(min(cd.l for cd in cds[13:17]) - 49.20) < 1e-9, "min low[13:17] != 49.20"
# 2. intrarea e close-ul barei 16
assert abs(conf.c - entry) < 1e-9, f"close[16]={conf.c} != 50.40"
# 3. confirmarea închide peste high-ul dojiului
assert abs(doji.h - 50.10) < 1e-9 and conf.c > doji.h, "close[16] nu e peste high[14]"
# 4. bara +1 NU confirmă
assert wait.c <= doji.h, "bara 15 ar confirma, fereastra n-ar avea sens"
# 5. 1:1 exact
assert abs(abs(entry - sl) - abs(tp1 - entry)) < 1e-9 and abs((entry - sl) - 1.20) < 1e-9, \
"risc != recompensă (nu e 1:1 exact)"
# 6. primul high >= TP1 după intrare e bara 19
primul_tp = next(i for i in range(17, len(cds)) if cds[i].h >= tp1)
assert primul_tp == 19, f"TP1 51.60 atins întâi la bara {primul_tp}, nu 19"
# 7. trailing 52.60 = low[27]; ieșire la bara 28 sub el; ieșirea e pe profit
assert abs(cds[27].l - trail) < 1e-9, f"low[27]={cds[27].l} != 52.60"
assert abs(cds[28].c - 52.30) < 1e-9 and cds[28].c < trail, "close[28] nu e sub trail"
assert trail > entry, "trail sub intrare, ieșirea n-ar fi pe profit"
# 8. doji: corp <= 10% din amplitudine
assert abs(doji.o - doji.c) <= 0.10 * (doji.h - doji.l), "bara 14 nu e doji"
# 9. downtrend vizibil
assert cds[13].c < cds[0].c - 10, "downtrend prea mic"
print(f"doji {doji.h:.2f}/{doji.l:.2f} intrare {conf.c:.2f} "
f"SL {sl:.2f} TP1 {tp1:.2f} trail {trail:.2f} ieșire bara 28 @ {cds[28].c:.2f}")
# --- desenul ----------------------------------------------------------------
ch = Chart(
candles=cds,
levels=[
Level(sl, "SL 49.20 (low 4 candele)", "#c00", label_at="left"),
Level(entry, "intrare 50.40", "#188a3e", label_at="left"),
Level(tp1, "TP1 51.60 = 1R", "#1a5fb4", label_at="left"),
Level(trail, "trail 52.60", "#b06000"),
],
marks=[
Mark(14, "doji 50.10 / 49.20", "#b06000", "below"),
Mark(28, "ieșire pe trail ~52.60", "#c00", "above", dy=-6),
],
mas=[MA(20, "#777", dash="4 3", text="MA(20)")],
scale=24.0, gap=15, body_w=9, left=44, width=640, vol_h=70,
vol_label="volum", title="Buy Doji — daily (exemplu construit, fără ticker)",
caption="Intrare la close-ul confirmării, SL la low-ul ultimelor 4 candele, "
"TP1 1:1, apoi trailing stop.",
)
svg = ch.render()
assert "<svg" in svg and "\n\n" not in svg, "linie goală în SVG"
with open("/workspace/atm-curs-dl/gen/strategii/buy_doji_ansamblu.svg", "w") as f:
f.write(svg)
print("scris gen/strategii/buy_doji_ansamblu.svg")

View File

@@ -0,0 +1,116 @@
<svg viewBox="0 0 640 498" xmlns="http://www.w3.org/2000/svg" style="width:100%;height:auto;font:12px sans-serif">
<text x="24" y="48" fill="#333" style="font-weight:bold">Buy Doji — daily (exemplu construit, fără ticker)</text>
<line x1="24" y1="326.0" x2="626" y2="326.0" stroke="#c00" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="30" y="320.0" fill="#c00">SL 49.20 (low 4 candele)</text>
<line x1="24" y1="297.2" x2="626" y2="297.2" stroke="#188a3e" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="30" y="291.2" fill="#188a3e">intrare 50.40</text>
<line x1="24" y1="268.4" x2="626" y2="268.4" stroke="#1a5fb4" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="30" y="262.4" fill="#1a5fb4">TP1 51.60 = 1R</text>
<line x1="24" y1="244.4" x2="626" y2="244.4" stroke="#b06000" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="612" y="238.4" fill="#b06000" text-anchor="end">trail 52.60</text>
<polyline points="329.0,205.1 344.0,215.2 359.0,223.7 374.0,232.3 389.0,239.8 404.0,245.3 419.0,251.0 434.0,255.0 449.0,258.1 464.0,262.3 479.0,265.5 494.0,267.6 509.0,268.0" fill="none" stroke="#777" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="515" y="262" fill="#777">MA(20)</text>
<line x1="44.0" y1="56.0" x2="44.0" y2="69.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="39.5" y="62.0" width="9" height="1.6" fill="#188a3e"/>
<line x1="59.0" y1="56.0" x2="59.0" y2="93.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="54.5" y="62.0" width="9" height="24.0" fill="#c00"/>
<line x1="74.0" y1="70.4" x2="74.0" y2="93.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="69.5" y="76.4" width="9" height="9.6" fill="#188a3e"/>
<line x1="89.0" y1="70.4" x2="89.0" y2="112.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="84.5" y="76.4" width="9" height="28.8" fill="#c00"/>
<line x1="104.0" y1="99.2" x2="104.0" y2="138.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="99.5" y="105.2" width="9" height="26.4" fill="#c00"/>
<line x1="119.0" y1="116.0" x2="119.0" y2="138.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="114.5" y="122.0" width="9" height="9.6" fill="#188a3e"/>
<line x1="134.0" y1="116.0" x2="134.0" y2="158.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="129.5" y="122.0" width="9" height="28.8" fill="#c00"/>
<line x1="149.0" y1="144.8" x2="149.0" y2="184.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="144.5" y="150.8" width="9" height="26.4" fill="#c00"/>
<line x1="164.0" y1="161.6" x2="164.0" y2="184.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="159.5" y="167.6" width="9" height="9.6" fill="#188a3e"/>
<line x1="179.0" y1="161.6" x2="179.0" y2="201.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="174.5" y="167.6" width="9" height="26.4" fill="#c00"/>
<line x1="194.0" y1="188.0" x2="194.0" y2="230.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="189.5" y="194.0" width="9" height="28.8" fill="#c00"/>
<line x1="209.0" y1="216.8" x2="209.0" y2="258.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="204.5" y="222.8" width="9" height="28.8" fill="#c00"/>
<line x1="224.0" y1="245.6" x2="224.0" y2="290.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="219.5" y="251.6" width="9" height="31.2" fill="#c00"/>
<line x1="239.0" y1="276.8" x2="239.0" y2="322.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="234.5" y="282.8" width="9" height="32.4" fill="#c00"/>
<line x1="254.0" y1="304.4" x2="254.0" y2="326.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="249.5" y="315.2" width="9" height="1.6" fill="#c00"/>
<line x1="269.0" y1="305.6" x2="269.0" y2="320.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="264.5" y="309.2" width="9" height="7.2" fill="#777"/>
<line x1="284.0" y1="293.6" x2="284.0" y2="312.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="279.5" y="297.2" width="9" height="12.0" fill="#188a3e"/>
<line x1="299.0" y1="279.2" x2="299.0" y2="304.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="294.5" y="285.2" width="9" height="12.0" fill="#188a3e"/>
<line x1="314.0" y1="272.0" x2="314.0" y2="292.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="309.5" y="278.0" width="9" height="7.2" fill="#188a3e"/>
<line x1="329.0" y1="264.8" x2="329.0" y2="285.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="324.5" y="270.8" width="9" height="7.2" fill="#188a3e"/>
<line x1="344.0" y1="257.6" x2="344.0" y2="278.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="339.5" y="263.6" width="9" height="7.2" fill="#188a3e"/>
<line x1="359.0" y1="250.4" x2="359.0" y2="270.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="354.5" y="256.4" width="9" height="7.2" fill="#188a3e"/>
<line x1="374.0" y1="243.2" x2="374.0" y2="263.6" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="369.5" y="249.2" width="9" height="7.2" fill="#188a3e"/>
<line x1="389.0" y1="243.2" x2="389.0" y2="261.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="384.5" y="249.2" width="9" height="4.8" fill="#c00"/>
<line x1="404.0" y1="236.0" x2="404.0" y2="258.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="399.5" y="242.0" width="9" height="12.0" fill="#188a3e"/>
<line x1="419.0" y1="230.0" x2="419.0" y2="246.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="414.5" y="236.0" width="9" height="6.0" fill="#188a3e"/>
<line x1="434.0" y1="224.0" x2="434.0" y2="240.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="429.5" y="230.0" width="9" height="6.0" fill="#188a3e"/>
<line x1="449.0" y1="224.0" x2="449.0" y2="244.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="444.5" y="230.0" width="9" height="9.6" fill="#c00"/>
<line x1="464.0" y1="233.6" x2="464.0" y2="258.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="459.5" y="239.6" width="9" height="12.0" fill="#c00"/>
<line x1="479.0" y1="245.6" x2="479.0" y2="266.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="474.5" y="251.6" width="9" height="7.2" fill="#c00"/>
<line x1="494.0" y1="252.8" x2="494.0" y2="270.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="489.5" y="258.8" width="9" height="4.8" fill="#c00"/>
<line x1="509.0" y1="255.2" x2="509.0" y2="270.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="504.5" y="261.2" width="9" height="2.4" fill="#188a3e"/>
<line x1="24" y1="422.0" x2="626" y2="422.0" stroke="#888"/>
<rect x="39.5" y="388.6" width="9" height="33.4" fill="#188a3e" opacity="0.55"/>
<rect x="54.5" y="387.0" width="9" height="35.0" fill="#c00" opacity="0.55"/>
<rect x="69.5" y="390.2" width="9" height="31.8" fill="#188a3e" opacity="0.55"/>
<rect x="84.5" y="385.4" width="9" height="36.6" fill="#c00" opacity="0.55"/>
<rect x="99.5" y="387.8" width="9" height="34.2" fill="#c00" opacity="0.55"/>
<rect x="114.5" y="389.4" width="9" height="32.6" fill="#188a3e" opacity="0.55"/>
<rect x="129.5" y="386.2" width="9" height="35.8" fill="#c00" opacity="0.55"/>
<rect x="144.5" y="388.6" width="9" height="33.4" fill="#c00" opacity="0.55"/>
<rect x="159.5" y="387.0" width="9" height="35.0" fill="#188a3e" opacity="0.55"/>
<rect x="174.5" y="384.6" width="9" height="37.4" fill="#c00" opacity="0.55"/>
<rect x="189.5" y="387.8" width="9" height="34.2" fill="#c00" opacity="0.55"/>
<rect x="204.5" y="386.2" width="9" height="35.8" fill="#c00" opacity="0.55"/>
<rect x="219.5" y="383.8" width="9" height="38.2" fill="#c00" opacity="0.55"/>
<rect x="234.5" y="380.6" width="9" height="41.4" fill="#c00" opacity="0.55"/>
<rect x="249.5" y="360.0" width="9" height="62.0" fill="#c00" opacity="0.55"/>
<rect x="264.5" y="385.4" width="9" height="36.6" fill="#777" opacity="0.55"/>
<rect x="279.5" y="352.0" width="9" height="70.0" fill="#188a3e" opacity="0.55"/>
<rect x="294.5" y="378.2" width="9" height="43.8" fill="#188a3e" opacity="0.55"/>
<rect x="309.5" y="382.2" width="9" height="39.8" fill="#188a3e" opacity="0.55"/>
<rect x="324.5" y="383.8" width="9" height="38.2" fill="#188a3e" opacity="0.55"/>
<rect x="339.5" y="380.6" width="9" height="41.4" fill="#188a3e" opacity="0.55"/>
<rect x="354.5" y="384.6" width="9" height="37.4" fill="#188a3e" opacity="0.55"/>
<rect x="369.5" y="386.2" width="9" height="35.8" fill="#188a3e" opacity="0.55"/>
<rect x="384.5" y="383.0" width="9" height="39.0" fill="#c00" opacity="0.55"/>
<rect x="399.5" y="385.4" width="9" height="36.6" fill="#188a3e" opacity="0.55"/>
<rect x="414.5" y="387.0" width="9" height="35.0" fill="#188a3e" opacity="0.55"/>
<rect x="429.5" y="383.8" width="9" height="38.2" fill="#188a3e" opacity="0.55"/>
<rect x="444.5" y="381.4" width="9" height="40.6" fill="#c00" opacity="0.55"/>
<rect x="459.5" y="379.0" width="9" height="43.0" fill="#c00" opacity="0.55"/>
<rect x="474.5" y="384.6" width="9" height="37.4" fill="#c00" opacity="0.55"/>
<rect x="489.5" y="386.2" width="9" height="35.8" fill="#c00" opacity="0.55"/>
<rect x="504.5" y="385.4" width="9" height="36.6" fill="#188a3e" opacity="0.55"/>
<text x="24" y="346.0" fill="#555">volum</text>
<text x="254.0" y="340.0" fill="#b06000" text-anchor="middle" style="font-weight:bold">doji 50.10 / 49.20</text>
<text x="464.0" y="217.6" fill="#c00" text-anchor="middle" style="font-weight:bold">ieșire pe trail ~52.60</text>
<text x="24" y="444.0" fill="#777">bara 15: close 49.90 sub high-ul dojiului 50.10 — NU confirmă</text>
<text x="24" y="461.0" fill="#188a3e">bara 16: close 50.40 peste 50.10 — confirmare (intrare)</text>
<text x="24" y="486" fill="#555">Intrare la close-ul confirmării, SL la low-ul ultimelor 4 candele, TP1 1:1, apoi trailing stop.</text>
</svg>

After

Width:  |  Height:  |  Size: 9.3 KiB

View File

@@ -0,0 +1,247 @@
"""Buy Doji — A+ vs. mediocru (două panouri, exemple construite).
Citește `gen/strategii/buy_doji_aplus.spec.txt` (marcajul `<!-- DIAGRAMA: aplus -->`).
Același pattern (trend → doji → confirmare), context opus:
- stânga (A+): downtrend 52 → 42.60, doji cu low 41.95 chiar pe suportul
42.00 (0.12 % sub nivel), confirmare close 43.60 pe volum peste media
ultimelor 10 bare — acumulare;
- dreapta (NU): uptrend 70 → 76.80, gap-up la open 78.20, doji lipit de
rezistența 78.50, confirmare close 79.30 pe volum sub medie („varză"), apoi
prețul cade înapoi sub 78.00.
Nivelurile 42.00 / 78.50 și cifrele de preț sunt construite de mine ca să
susțină etichetele și assert-urile din spec (materialul nu dă prețuri pentru
acest contrast). Două `Chart`-uri randate separat, lipite într-un SVG wrapper:
coordonatele fiecărui panou rămân corecte prin construcție.
Rulează: python3 gen/strategii/buy_doji_aplus.py
Scrie: gen/strategii/buy_doji_aplus.svg
"""
import re
import sys
from pathlib import Path
sys.path.insert(0, "/workspace/atm-curs-dl")
from svg_candles import Candle, Chart, Level, Mark, MA, from_closes # noqa: E402
OUT = Path(__file__).resolve().parent / "buy_doji_aplus.svg"
GRAY, RED, GREEN, BLUE, ORANGE = "#777", "#c00", "#188a3e", "#1a5fb4", "#b06000"
DOJI_A, CONF_A = 10, 11 # indicii în panoul A+
DOJI_N, CONF_N = 14, 15 # indicii în panoul NU
# ============================================================ PANOUL A+ (24 bare)
# 10 bare de downtrend de la 52.00 spre 42.60, apoi doji, apoi confirmare.
CLOSES_A = [
51.60, 50.80, 50.00, 49.20, 48.40, 47.60, 46.80, 45.50, 44.20, 42.60, # 0-9 downtrend
42.60, # 10 doji (open = close)
43.60, # 11 confirmare
44.10, 44.50, 44.20, 44.80, 45.20, 45.00, 45.50, 45.90, 46.20, 46.50, 46.80, 47.10, # 12-23 acumulare
]
assert len(CLOSES_A) == 24, len(CLOSES_A)
WICKS_A = [(0.30, 0.30)] * 24
WICKS_A[DOJI_A] = (0.30, 0.65) # high 42.90, low 41.95 (pe suportul 42.00)
WICKS_A[CONF_A] = (0.25, 0.30) # high 43.85
VOLS_A = [56, 53, 51, 49, 47, 45, 44, 42, 41, 39, # 0-9 vânzare în scădere
60, # 10 doji: exces de vânzare
86, # 11 confirmare: volum mare
72, 75, 70, 78, 82, 80, 85, 88, 92, 95, 98, 100] # 12-23 acumulare
cds_a = from_closes(CLOSES_A, wicks=WICKS_A, vols=VOLS_A, start=52.00)
doji_a, conf_a = cds_a[DOJI_A], cds_a[CONF_A]
# ============================================================ PANOUL NU (24 bare)
# uptrend 70 → 76.80 (14 bare), gap-up la open 78.20 + doji, confirmare 79.30
# pe volum slab, apoi cădere înapoi sub 78.00. Lista e manuală pentru că gap-ul
# (open > high-ul barei precedente) nu iese din `from_closes`.
BARE_NU = [
# o, h, l, c, vol
(70.00, 70.70, 69.80, 70.50, 48), (70.50, 71.20, 70.30, 71.00, 50),
(71.00, 71.75, 70.85, 71.55, 49), (71.55, 72.30, 71.40, 72.10, 52),
(72.10, 72.80, 71.95, 72.60, 51), (72.60, 73.30, 72.45, 73.10, 54),
(73.10, 73.80, 72.95, 73.60, 53), (73.60, 74.25, 73.45, 74.05, 56),
(74.05, 74.70, 73.90, 74.50, 55), (74.50, 75.15, 74.35, 74.95, 58),
(74.95, 75.60, 74.80, 75.40, 57), (75.40, 76.00, 75.25, 75.80, 60),
(75.80, 76.40, 75.65, 76.20, 59), (76.20, 76.80, 76.05, 76.80, 62), # 13: high 76.80
(78.20, 78.50, 78.10, 78.20, 58), # 14: GAP-UP + doji (o = c = 78.20)
(78.20, 79.50, 78.10, 79.30, 30), # 15: confirmare, volum slab
(79.30, 79.40, 78.40, 78.60, 55), (78.60, 78.70, 77.80, 78.00, 60),
(78.00, 78.10, 77.20, 77.40, 64), (77.40, 77.55, 76.80, 77.00, 62),
(77.00, 77.15, 76.40, 76.60, 66), (76.60, 76.75, 76.10, 76.30, 63),
(76.30, 76.40, 75.80, 76.00, 65), (76.00, 76.15, 75.50, 75.70, 61),
]
assert len(BARE_NU) == 24, len(BARE_NU)
cds_n = [Candle(o, h, l, c, vol=v) for (o, h, l, c, v) in BARE_NU]
doji_n, conf_n = cds_n[DOJI_N], cds_n[CONF_N]
# ============================================================ assert-urile din spec
# 1. A+: low-ul dojiului e la ≤ 0,5 % de 42.00 (41.95 e la 0.12 %)
dist_a = abs(doji_a.l - 42.00) / 42.00
assert dist_a <= 0.005, f"low doji A+ {doji_a.l} la {dist_a * 100:.2f} % de 42.00"
assert abs(doji_a.l - 41.95) < 1e-9, f"low doji A+ = {doji_a.l}, aștept 41.95"
# 2. A+: volumul pe bara de confirmare > media volumelor pe 10 bare
media_a = sum(VOLS_A[CONF_A - 10:CONF_A]) / 10
assert VOLS_A[CONF_A] > media_a, f"vol confirmare {VOLS_A[CONF_A]} <= media {media_a}"
# 3. NU: doji-ul apare după un gap-up (open doji > high bara precedentă)
assert cds_n[DOJI_N].o > cds_n[DOJI_N - 1].h, "doji NU nu e după gap-up"
assert abs(cds_n[DOJI_N].o - 78.20) < 1e-9 and abs(cds_n[DOJI_N - 1].h - 76.80) < 1e-9
# 4. NU: |high doji - 78.50| / 78.50 <= 0.005
dist_n = abs(doji_n.h - 78.50) / 78.50
assert dist_n <= 0.005, f"high doji NU {doji_n.h} la {dist_n * 100:.2f} % de 78.50"
# 5. NU: volumul pe bara de confirmare < media pe 10 bare
media_n = sum(b[4] for b in BARE_NU[CONF_N - 10:CONF_N]) / 10
assert conf_n.vol < media_n, f"vol confirmare NU {conf_n.vol} >= media {media_n}"
# 6. ambele: close-ul de confirmare > high-ul dojiului (tehnic valid)
assert conf_a.c > doji_a.h, f"A+ close {conf_a.c} <= high doji {doji_a.h}"
assert conf_n.c > doji_n.h, f"NU close {conf_n.c} <= high doji {doji_n.h}"
assert abs(conf_a.c - 43.60) < 1e-9 and abs(conf_n.c - 79.30) < 1e-9
# 7. ambele doji: abs(o-c) <= 0.10*(h-l)
for tag, d in (("A+", doji_a), ("NU", doji_n)):
assert abs(d.o - d.c) <= 0.10 * (d.h - d.l), f"bara doji {tag} nu e doji"
# suplimentare: structura promisă de spec
assert min(c.l for c in cds_a[:10]) > doji_a.l, "low-ul dojiului A+ nu e minimul"
assert cds_a[9].c < cds_a[0].c - 8, "downtrend A+ prea mic"
assert cds_n[CONF_N].c > 78.50 and cds_n[-1].c < 78.00, "NU nu cade sub 78.00 la final"
assert cds_n[13].c < 78.00 < cds_n[DOJI_N].o, "NU: uptrendul nu se oprește sub 78.00"
print(f"A+ doji {doji_a.h:.2f}/{doji_a.l:.2f} conf {conf_a.c:.2f} "
f"vol {VOLS_A[CONF_A]} > medie {media_a:.1f}")
print(f"NU doji {doji_n.h:.2f}/{doji_n.l:.2f} conf {conf_n.c:.2f} "
f"vol {conf_n.vol} < medie {media_n:.1f}")
# ============================================================ desenele celor două panouri
PW = 560 # lățimea fiecărui panou
ch_a = Chart(
candles=cds_a,
levels=[Level(42.00, "suport 42.00", GREEN, label_at="left")],
marks=[Mark(DOJI_A, "doji la nivel (low 41.95)", ORANGE, "below"),
Mark(CONF_A, "acumulare", GREEN, "above", dy=-6)],
mas=[], scale=22.0, gap=15, body_w=9, left=44, width=PW, vol_h=60,
vol_label="volum", title="A+ — doji la suport, volum în acumulare",
caption="acumulare: volum peste media ultimelor 10 bare.",
)
ch_n = Chart(
candles=cds_n,
levels=[Level(78.50, "rezistență 78.50", RED, label_at="right")],
marks=[Mark(DOJI_N, "doji 78.50 / 78.10", ORANGE, "below", dy=93)],
mas=[], scale=22.0, gap=15, body_w=9, left=44, width=PW, vol_h=60,
vol_label="volum", title="Mediocru — gap-up în rezistență, volum varză",
caption="gap-up + doji în rezistență, confirmare pe volum slab.",
)
# ============================================================ wrapper: două panouri alăturate
def inner(svg: str) -> str:
"""Conținutul dintre tag-urile <svg> ale unui panou, fără elementul rădăcină."""
return svg[svg.index(">") + 1:svg.rindex("</svg>")]
def vb_height(svg: str) -> float:
m = re.search(r'viewBox="0 0 [\d.]+ ([\d.]+)"', svg)
assert m, "nu găsesc viewBox în panoul randat"
return float(m.group(1))
PANEL_GAP, PAD = 24, 8
TITLE_H, FOOTER_H = 30, 22
svg_a, svg_n = ch_a.render(), ch_n.render()
# „gap-up, volum slab" stă în colțul liber din stânga-sus, cu linie indicatoare
# spre golul dintre bara 13 (high 76.80) și dojiul barei 14 (open 78.20).
GAP_X = (ch_n._cx(DOJI_N - 1) + ch_n._cx(DOJI_N)) / 2
GAP_Y = ch_n._y(77.50)
gap_ann = (
f'<text x="28" y="120" fill="{RED}" style="font-weight:bold">gap-up, volum slab</text>'
f'<line x1="152" y1="116" x2="{GAP_X:.1f}" y2="{GAP_Y:.1f}" stroke="{RED}" stroke-width="1"/>'
f'<circle cx="{GAP_X:.1f}" cy="{GAP_Y:.1f}" r="2.5" fill="{RED}"/>'
)
svg_n = svg_n.replace("</svg>", gap_ann + "</svg>")
HA, HN = vb_height(svg_a), vb_height(svg_n)
assert svg_a.count("\n\n") == 0 and svg_n.count("\n\n") == 0, "linie goală în panou"
TOTAL_W = PAD + PW + PANEL_GAP + PW + PAD
TOTAL_H = PAD + TITLE_H + max(HA, HN) + FOOTER_H + PAD
OFF_A = (PAD, PAD + TITLE_H)
OFF_N = (PAD + PW + PANEL_GAP, PAD + TITLE_H)
FOOTER = ("Surse: 13_strategia_3_byh.md:188-190 (doji la nivel); "
"01_buy_doji_si_strategia_mici.md:227-235 (context invalid).")
wrapper = (
f'<svg viewBox="0 0 {TOTAL_W} {TOTAL_H:.0f}" width="{TOTAL_W}" height="{TOTAL_H:.0f}" '
f'xmlns="http://www.w3.org/2000/svg" style="width:100%;height:auto;font:12px sans-serif">'
f'<text x="{TOTAL_W / 2:.0f}" y="20" fill="#333" text-anchor="middle" '
f'style="font-weight:bold">Setup A+ vs. setup mediocru (două panouri, exemple construite)</text>'
f'<g transform="translate({OFF_A[0]},{OFF_A[1]})">{inner(svg_a)}</g>'
f'<g transform="translate({OFF_N[0]},{OFF_N[1]})">{inner(svg_n)}</g>'
f'<text x="{TOTAL_W / 2:.0f}" y="{TOTAL_H - PAD:.0f}" fill="#555" '
f'text-anchor="middle">{FOOTER}</text>'
f'</svg>'
)
# ============================================================ verificare pe SVG-ul final
def texts(svg: str):
"""Toate (x, y, text-anchor, text) din elementele <text> ale unui fragment."""
out = []
for m in re.finditer(r'<text x="([-\d.]+)" y="([-\d.]+)"([^>]*)>(.*?)</text>', svg):
x, y, attrs, t = float(m.group(1)), float(m.group(2)), m.group(3), m.group(4)
if t.startswith("<"):
continue
am = re.search(r'text-anchor="(\w+)"', attrs)
out.append((x, y, am.group(1) if am else "start", t))
return out
def span(x: float, anchor: str, t: str) -> tuple[float, float]:
"""Intervalul orizontal aproximativ al unui text, față de ancora lui."""
w = len(t) * 12 * 0.56
left = x - w if anchor == "end" else (x - w / 2 if anchor == "middle" else x)
return left, left + w
assert wrapper.count("\n\n") == 0, "linie goală în SVG"
# fiecare text (după translatarea panoului) e în întregime în interiorul pânzei
all_txt = []
for frag, (ox, oy) in ((inner(svg_a), OFF_A), (inner(svg_n), OFF_N)):
for x, y, anchor, t in texts(frag):
gx, gy = x + ox, y + oy
gl, gr = gx + span(0, anchor, t)[0], gx + span(0, anchor, t)[1]
assert ox <= gl and gr <= ox + PW, f"text iese din panou: {t!r} ({gl:.0f}..{gr:.0f})"
assert -1 <= gl and gr <= TOTAL_W + 1, f"text ieșit pe orizontală: {t!r} ({gl:.0f}..{gr:.0f})"
assert 0 <= gy <= TOTAL_H, f"text ieșit pe verticală: {t!r} ({gy:.0f})"
all_txt.append((gx, gy, anchor, t))
# textele din wrapper (titlu + footer), în coordonate globale
for x, y, anchor, t in ((TOTAL_W / 2, 20.0, "middle",
"Setup A+ vs. setup mediocru (două panouri, exemple construite)"),
(TOTAL_W / 2, TOTAL_H - PAD, "middle", FOOTER)):
gl, gr = span(x, anchor, t)
assert -1 <= gl and gr <= TOTAL_W + 1, f"text din wrapper iese din pânză: {t!r}"
assert 0 <= y <= TOTAL_H, f"text din wrapper în afara înălțimii: {t!r}"
all_txt.append((x, y, anchor, t))
assert len(all_txt) >= 8, "prea puține etichete"
# nu se suprapun două texte pe aceeași poziție sau pe rândul apropiat
for i in range(len(all_txt)):
xi, yi, ai, si = all_txt[i]
li, ri = (xi + span(0, ai, si)[0], xi + span(0, ai, si)[1])
for j in range(i + 1, len(all_txt)):
xj, yj, aj, sj = all_txt[j]
lj, rj = xj + span(0, aj, sj)[0], xj + span(0, aj, sj)[1]
if abs(yi - yj) < 12 and li < rj - 4 and lj < ri - 4:
raise AssertionError(f"texte suprapuse: {si!r} / {sj!r}")
# etichetele cerute de spec există în desen
for cheie in ("suport 42.00", "doji la nivel", "acumulare",
"rezistență 78.50", "gap-up, volum slab",
"A+ — doji la suport, volum în acumulare",
"Mediocru — gap-up în rezistență, volum varză"):
assert cheie in wrapper, f"lipsește din desen: {cheie!r}"
OUT.write_text(wrapper, encoding="utf-8")
print(f"scris {OUT.name}: {TOTAL_W}x{TOTAL_H:.0f}, "
f"{len(all_txt)} etichete, verificare OK")

View File

@@ -0,0 +1,162 @@
<svg viewBox="0 0 1160 480" width="1160" height="480" xmlns="http://www.w3.org/2000/svg" style="width:100%;height:auto;font:12px sans-serif"><text x="580" y="20" fill="#333" text-anchor="middle" style="font-weight:bold">Setup A+ vs. setup mediocru (două panouri, exemple construite)</text><g transform="translate(8,38)">
<text x="24" y="48" fill="#333" style="font-weight:bold">A+ — doji la suport, volum în acumulare</text>
<line x1="24" y1="282.6" x2="546" y2="282.6" stroke="#188a3e" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="30" y="276.6" fill="#188a3e">suport 42.00</text>
<line x1="44.0" y1="56.0" x2="44.0" y2="78.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="39.5" y="62.6" width="9" height="8.8" fill="#c00"/>
<line x1="59.0" y1="64.8" x2="59.0" y2="95.6" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="54.5" y="71.4" width="9" height="17.6" fill="#c00"/>
<line x1="74.0" y1="82.4" x2="74.0" y2="113.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="69.5" y="89.0" width="9" height="17.6" fill="#c00"/>
<line x1="89.0" y1="100.0" x2="89.0" y2="130.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="84.5" y="106.6" width="9" height="17.6" fill="#c00"/>
<line x1="104.0" y1="117.6" x2="104.0" y2="148.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="99.5" y="124.2" width="9" height="17.6" fill="#c00"/>
<line x1="119.0" y1="135.2" x2="119.0" y2="166.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="114.5" y="141.8" width="9" height="17.6" fill="#c00"/>
<line x1="134.0" y1="152.8" x2="134.0" y2="183.6" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="129.5" y="159.4" width="9" height="17.6" fill="#c00"/>
<line x1="149.0" y1="170.4" x2="149.0" y2="212.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="144.5" y="177.0" width="9" height="28.6" fill="#c00"/>
<line x1="164.0" y1="199.0" x2="164.0" y2="240.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="159.5" y="205.6" width="9" height="28.6" fill="#c00"/>
<line x1="179.0" y1="227.6" x2="179.0" y2="276.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="174.5" y="234.2" width="9" height="35.2" fill="#c00"/>
<line x1="194.0" y1="262.8" x2="194.0" y2="283.7" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="189.5" y="269.4" width="9" height="1.6" fill="#188a3e"/>
<line x1="209.0" y1="241.9" x2="209.0" y2="276.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="204.5" y="247.4" width="9" height="22.0" fill="#188a3e"/>
<line x1="224.0" y1="229.8" x2="224.0" y2="254.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="219.5" y="236.4" width="9" height="11.0" fill="#188a3e"/>
<line x1="239.0" y1="221.0" x2="239.0" y2="243.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="234.5" y="227.6" width="9" height="8.8" fill="#188a3e"/>
<line x1="254.0" y1="221.0" x2="254.0" y2="240.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="249.5" y="227.6" width="9" height="6.6" fill="#c00"/>
<line x1="269.0" y1="214.4" x2="269.0" y2="240.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="264.5" y="221.0" width="9" height="13.2" fill="#188a3e"/>
<line x1="284.0" y1="205.6" x2="284.0" y2="227.6" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="279.5" y="212.2" width="9" height="8.8" fill="#188a3e"/>
<line x1="299.0" y1="205.6" x2="299.0" y2="223.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="294.5" y="212.2" width="9" height="4.4" fill="#c00"/>
<line x1="314.0" y1="199.0" x2="314.0" y2="223.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="309.5" y="205.6" width="9" height="11.0" fill="#188a3e"/>
<line x1="329.0" y1="190.2" x2="329.0" y2="212.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="324.5" y="196.8" width="9" height="8.8" fill="#188a3e"/>
<line x1="344.0" y1="183.6" x2="344.0" y2="203.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="339.5" y="190.2" width="9" height="6.6" fill="#188a3e"/>
<line x1="359.0" y1="177.0" x2="359.0" y2="196.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="354.5" y="183.6" width="9" height="6.6" fill="#188a3e"/>
<line x1="374.0" y1="170.4" x2="374.0" y2="190.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="369.5" y="177.0" width="9" height="6.6" fill="#188a3e"/>
<line x1="389.0" y1="163.8" x2="389.0" y2="183.6" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="384.5" y="170.4" width="9" height="6.6" fill="#188a3e"/>
<line x1="24" y1="369.7" x2="546" y2="369.7" stroke="#888"/>
<rect x="39.5" y="336.1" width="9" height="33.6" fill="#c00" opacity="0.55"/>
<rect x="54.5" y="337.9" width="9" height="31.8" fill="#c00" opacity="0.55"/>
<rect x="69.5" y="339.1" width="9" height="30.6" fill="#c00" opacity="0.55"/>
<rect x="84.5" y="340.3" width="9" height="29.4" fill="#c00" opacity="0.55"/>
<rect x="99.5" y="341.5" width="9" height="28.2" fill="#c00" opacity="0.55"/>
<rect x="114.5" y="342.7" width="9" height="27.0" fill="#c00" opacity="0.55"/>
<rect x="129.5" y="343.3" width="9" height="26.4" fill="#c00" opacity="0.55"/>
<rect x="144.5" y="344.5" width="9" height="25.2" fill="#c00" opacity="0.55"/>
<rect x="159.5" y="345.1" width="9" height="24.6" fill="#c00" opacity="0.55"/>
<rect x="174.5" y="346.3" width="9" height="23.4" fill="#c00" opacity="0.55"/>
<rect x="189.5" y="333.7" width="9" height="36.0" fill="#188a3e" opacity="0.55"/>
<rect x="204.5" y="318.1" width="9" height="51.6" fill="#188a3e" opacity="0.55"/>
<rect x="219.5" y="326.5" width="9" height="43.2" fill="#188a3e" opacity="0.55"/>
<rect x="234.5" y="324.7" width="9" height="45.0" fill="#188a3e" opacity="0.55"/>
<rect x="249.5" y="327.7" width="9" height="42.0" fill="#c00" opacity="0.55"/>
<rect x="264.5" y="322.9" width="9" height="46.8" fill="#188a3e" opacity="0.55"/>
<rect x="279.5" y="320.5" width="9" height="49.2" fill="#188a3e" opacity="0.55"/>
<rect x="294.5" y="321.7" width="9" height="48.0" fill="#c00" opacity="0.55"/>
<rect x="309.5" y="318.7" width="9" height="51.0" fill="#188a3e" opacity="0.55"/>
<rect x="324.5" y="316.9" width="9" height="52.8" fill="#188a3e" opacity="0.55"/>
<rect x="339.5" y="314.5" width="9" height="55.2" fill="#188a3e" opacity="0.55"/>
<rect x="354.5" y="312.7" width="9" height="57.0" fill="#188a3e" opacity="0.55"/>
<rect x="369.5" y="310.9" width="9" height="58.8" fill="#188a3e" opacity="0.55"/>
<rect x="384.5" y="309.7" width="9" height="60.0" fill="#188a3e" opacity="0.55"/>
<text x="24" y="303.7" fill="#555">volum</text>
<text x="194.0" y="297.7" fill="#b06000" text-anchor="middle" style="font-weight:bold">doji la nivel (low 41.95)</text>
<text x="209.0" y="225.9" fill="#188a3e" text-anchor="middle" style="font-weight:bold">acumulare</text>
<text x="24" y="400" fill="#555">acumulare: volum peste media ultimelor 10 bare.</text>
</g><g transform="translate(592,38)">
<text x="24" y="48" fill="#333" style="font-weight:bold">Mediocru — gap-up în rezistență, volum varză</text>
<line x1="24" y1="78.0" x2="546" y2="78.0" stroke="#c00" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="532" y="72.0" fill="#c00" text-anchor="end">rezistență 78.50</text>
<line x1="44.0" y1="249.6" x2="44.0" y2="269.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="39.5" y="254.0" width="9" height="11.0" fill="#188a3e"/>
<line x1="59.0" y1="238.6" x2="59.0" y2="258.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="54.5" y="243.0" width="9" height="11.0" fill="#188a3e"/>
<line x1="74.0" y1="226.5" x2="74.0" y2="246.3" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="69.5" y="230.9" width="9" height="12.1" fill="#188a3e"/>
<line x1="89.0" y1="214.4" x2="89.0" y2="234.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="84.5" y="218.8" width="9" height="12.1" fill="#188a3e"/>
<line x1="104.0" y1="203.4" x2="104.0" y2="222.1" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="99.5" y="207.8" width="9" height="11.0" fill="#188a3e"/>
<line x1="119.0" y1="192.4" x2="119.0" y2="211.1" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="114.5" y="196.8" width="9" height="11.0" fill="#188a3e"/>
<line x1="134.0" y1="181.4" x2="134.0" y2="200.1" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="129.5" y="185.8" width="9" height="11.0" fill="#188a3e"/>
<line x1="149.0" y1="171.5" x2="149.0" y2="189.1" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="144.5" y="175.9" width="9" height="9.9" fill="#188a3e"/>
<line x1="164.0" y1="161.6" x2="164.0" y2="179.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="159.5" y="166.0" width="9" height="9.9" fill="#188a3e"/>
<line x1="179.0" y1="151.7" x2="179.0" y2="169.3" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="174.5" y="156.1" width="9" height="9.9" fill="#188a3e"/>
<line x1="194.0" y1="141.8" x2="194.0" y2="159.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="189.5" y="146.2" width="9" height="9.9" fill="#188a3e"/>
<line x1="209.0" y1="133.0" x2="209.0" y2="149.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="204.5" y="137.4" width="9" height="8.8" fill="#188a3e"/>
<line x1="224.0" y1="124.2" x2="224.0" y2="140.7" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="219.5" y="128.6" width="9" height="8.8" fill="#188a3e"/>
<line x1="239.0" y1="115.4" x2="239.0" y2="131.9" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="234.5" y="115.4" width="9" height="13.2" fill="#188a3e"/>
<line x1="254.0" y1="78.0" x2="254.0" y2="86.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="249.5" y="84.6" width="9" height="1.6" fill="#188a3e"/>
<line x1="269.0" y1="56.0" x2="269.0" y2="86.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="264.5" y="60.4" width="9" height="24.2" fill="#188a3e"/>
<line x1="284.0" y1="58.2" x2="284.0" y2="80.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="279.5" y="60.4" width="9" height="15.4" fill="#c00"/>
<line x1="299.0" y1="73.6" x2="299.0" y2="93.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="294.5" y="75.8" width="9" height="13.2" fill="#c00"/>
<line x1="314.0" y1="86.8" x2="314.0" y2="106.6" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="309.5" y="89.0" width="9" height="13.2" fill="#c00"/>
<line x1="329.0" y1="98.9" x2="329.0" y2="115.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="324.5" y="102.2" width="9" height="8.8" fill="#c00"/>
<line x1="344.0" y1="107.7" x2="344.0" y2="124.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="339.5" y="111.0" width="9" height="8.8" fill="#c00"/>
<line x1="359.0" y1="116.5" x2="359.0" y2="130.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="354.5" y="119.8" width="9" height="6.6" fill="#c00"/>
<line x1="374.0" y1="124.2" x2="374.0" y2="137.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="369.5" y="126.4" width="9" height="6.6" fill="#c00"/>
<line x1="389.0" y1="129.7" x2="389.0" y2="144.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="384.5" y="133.0" width="9" height="6.6" fill="#c00"/>
<line x1="24" y1="355.4" x2="546" y2="355.4" stroke="#888"/>
<rect x="39.5" y="311.8" width="9" height="43.6" fill="#188a3e" opacity="0.55"/>
<rect x="54.5" y="309.9" width="9" height="45.5" fill="#188a3e" opacity="0.55"/>
<rect x="69.5" y="310.9" width="9" height="44.5" fill="#188a3e" opacity="0.55"/>
<rect x="84.5" y="308.1" width="9" height="47.3" fill="#188a3e" opacity="0.55"/>
<rect x="99.5" y="309.0" width="9" height="46.4" fill="#188a3e" opacity="0.55"/>
<rect x="114.5" y="306.3" width="9" height="49.1" fill="#188a3e" opacity="0.55"/>
<rect x="129.5" y="307.2" width="9" height="48.2" fill="#188a3e" opacity="0.55"/>
<rect x="144.5" y="304.5" width="9" height="50.9" fill="#188a3e" opacity="0.55"/>
<rect x="159.5" y="305.4" width="9" height="50.0" fill="#188a3e" opacity="0.55"/>
<rect x="174.5" y="302.7" width="9" height="52.7" fill="#188a3e" opacity="0.55"/>
<rect x="189.5" y="303.6" width="9" height="51.8" fill="#188a3e" opacity="0.55"/>
<rect x="204.5" y="300.9" width="9" height="54.5" fill="#188a3e" opacity="0.55"/>
<rect x="219.5" y="301.8" width="9" height="53.6" fill="#188a3e" opacity="0.55"/>
<rect x="234.5" y="299.0" width="9" height="56.4" fill="#188a3e" opacity="0.55"/>
<rect x="249.5" y="302.7" width="9" height="52.7" fill="#188a3e" opacity="0.55"/>
<rect x="264.5" y="328.1" width="9" height="27.3" fill="#188a3e" opacity="0.55"/>
<rect x="279.5" y="305.4" width="9" height="50.0" fill="#c00" opacity="0.55"/>
<rect x="294.5" y="300.9" width="9" height="54.5" fill="#c00" opacity="0.55"/>
<rect x="309.5" y="297.2" width="9" height="58.2" fill="#c00" opacity="0.55"/>
<rect x="324.5" y="299.0" width="9" height="56.4" fill="#c00" opacity="0.55"/>
<rect x="339.5" y="295.4" width="9" height="60.0" fill="#c00" opacity="0.55"/>
<rect x="354.5" y="298.1" width="9" height="57.3" fill="#c00" opacity="0.55"/>
<rect x="369.5" y="296.3" width="9" height="59.1" fill="#c00" opacity="0.55"/>
<rect x="384.5" y="299.9" width="9" height="55.5" fill="#c00" opacity="0.55"/>
<text x="24" y="289.4" fill="#555">volum</text>
<text x="254.0" y="199.8" fill="#b06000" text-anchor="middle" style="font-weight:bold">doji 78.50 / 78.10</text>
<text x="24" y="385" fill="#555">gap-up + doji în rezistență, confirmare pe volum slab.</text>
<text x="28" y="120" fill="#c00" style="font-weight:bold">gap-up, volum slab</text><line x1="152" y1="116" x2="246.5" y2="100.0" stroke="#c00" stroke-width="1"/><circle cx="246.5" cy="100.0" r="2.5" fill="#c00"/></g><text x="580" y="472" fill="#555" text-anchor="middle">Surse: 13_strategia_3_byh.md:188-190 (doji la nivel); 01_buy_doji_si_strategia_mici.md:227-235 (context invalid).</text></svg>

After

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -0,0 +1,188 @@
#!/usr/bin/env python3
"""Buy Doji — fereastra de 3 candele: DA (valid) vs. NU (expirat).
Citește `gen/strategii/buy_doji_fereastra.spec.txt` și scrie
`gen/strategii/buy_doji_fereastra.svg`. Două panouri mici, unul sub altul,
cu aceeași serie până la doji: sus confirmarea vine la bara +2 (în fereastră),
jos niciuna din +1..+3 nu confirmă, iar a 4-a închide sub low-ul dojiului.
Toate cifrele din desen vin din specificație: doji h 50.10 / l 49.20,
closes-urile DA/NU, pragul 50.10, low-ul 49.20. Seriile sunt construite
(open = close-ul barei anterioare, mai puțin open-ul primei bare, start-ul),
ca nivelurile afirmate să iasă exact din serie.
Rulare: python3 gen/strategii/buy_doji_fereastra.py
"""
import re
import sys
from pathlib import Path
sys.path.insert(0, "/workspace/atm-curs-dl")
from svg_candles import Chart, Level, Mark, from_closes # noqa: E402
OUT = Path(__file__).with_suffix(".svg")
W = 460
VGAP = 34
SCALE = 45.0 # px / unitate, aceeași pe ambele panouri (comparație)
# --- seriile din specificație ----------------------------------------------
CLOSES_DA = [52.20, 51.40, 50.60, 49.65,
49.60, # doji, h 50.10 / l 49.20
49.90, # +1 — sub 50.10
50.40, # +2 — peste 50.10 => confirmă
50.80]
CLOSES_NU = [52.20, 51.40, 50.60, 49.65,
49.60, # doji, h 50.10 / l 49.20
49.95, # +1
50.05, # +2
49.85, # +3
49.15] # a 4-a — sub low-ul dojiului
assert len(CLOSES_DA) == 8 and len(CLOSES_NU) == 9
assert CLOSES_DA[:5] == CLOSES_NU[:5]
HIGH_DOJI, LOW_DOJI = 50.10, 49.20
# wick-uri: (up, down). Doji-ul (index 4) primește mecioni ca să iasă exact
# h=50.10 / l=49.20; restul au mecioni mici, egali.
wicks_da = [(0.15, 0.15)] * len(CLOSES_DA)
wicks_nu = [(0.15, 0.15)] * len(CLOSES_NU)
wicks_da[4] = wicks_nu[4] = (0.45, 0.40)
START = CLOSES_DA[0] + 0.15 # prima bară roșie, open > close
cds_da = from_closes(CLOSES_DA, wicks=wicks_da, start=START)
cds_nu = from_closes(CLOSES_NU, wicks=wicks_nu, start=START)
doji_da, doji_nu = cds_da[4], cds_nu[4]
entry = cds_da[6].c # intrare la close-ul confirmării
# --- assert-urile din specificație -----------------------------------------
# doji-ul iese exact la nivelurile afirmate
assert abs(doji_da.h - HIGH_DOJI) < 1e-9 and abs(doji_da.l - LOW_DOJI) < 1e-9, \
f"doji DA {doji_da.h}/{doji_da.l} != {HIGH_DOJI}/{LOW_DOJI}"
assert abs(doji_nu.h - HIGH_DOJI) < 1e-9 and abs(doji_nu.l - LOW_DOJI) < 1e-9, \
f"doji NU {doji_nu.h}/{doji_nu.l} != {HIGH_DOJI}/{LOW_DOJI}"
# 1. DA: primul close peste 50.10 DUPĂ doji e bara +2 (index 6), în fereastră;
# bara +1 nu confirmă
primul_da = next(i for i in range(5, len(cds_da)) if cds_da[i].c > HIGH_DOJI)
assert primul_da == 6, f"prima confirmare la index {primul_da}, nu 6 (+2)"
assert primul_da - 4 <= 3, "confirmarea nu e în fereastra de 3 candele"
assert abs(cds_da[5].c - 49.90) < 1e-9 and cds_da[5].c <= HIGH_DOJI, \
"bara +1 nu trebuie să confirme"
assert abs(cds_da[6].c - entry) < 1e-9 and abs(entry - 50.40) < 1e-9, \
"intrarea nu e close-ul de 50.40 al barei +2"
# 2. NU: niciun close din +1..+3 nu depășește 50.10; a 4-a închide sub 49.20
assert all(cds_nu[i].c <= HIGH_DOJI for i in (5, 6, 7)), \
"un close din +1..+3 ar confirma, fereastra n-ar fi expirat"
assert cds_nu[8].c < LOW_DOJI, f"close[+4]={cds_nu[8].c} nu e sub {LOW_DOJI}"
# 3. ambele doji: corp <= 10% din amplitudine
for d in (doji_da, doji_nu):
assert abs(d.o - d.c) <= 0.10 * (d.h - d.l), "bara 4 nu e doji"
# 4. aceeași serie până la doji (aceleași 5 bare)
ser_da = [(round(c.o, 9), round(c.h, 9), round(c.l, 9), round(c.c, 9)) for c in cds_da[:5]]
ser_nu = [(round(c.o, 9), round(c.h, 9), round(c.l, 9), round(c.c, 9)) for c in cds_nu[:5]]
assert ser_da == ser_nu, "panourile nu au aceeași serie până la doji"
# --- desen: două Chart-uri, lipite vertical într-un singur SVG -------------
def _panel(ch: Chart):
svg = ch.render()
m = re.search(r'viewBox="0 0 (\d+) ([\d.]+)"', svg)
body = svg[svg.index(">") + 1:svg.rindex("</svg>")]
return body, float(m.group(2))
# DA — confirmarea vine la +2
cds_da[5].note = "+1: close 49.90 — sub 50.10, nu confirmă"
cds_da[6].note = "+2: close 50.40 — peste 50.10, confirmă (intrare la close)"
ch_da = Chart(
candles=cds_da,
levels=[Level(HIGH_DOJI, "high doji 50.10", "#b06000", label_at="left"),
Level(LOW_DOJI, "low doji 49.20", "#c00", label_at="left")],
marks=[Mark(4, "doji", "#b06000", "above"),
Mark(6, f"confirmare +2 — intrare {entry:.2f}", "#188a3e", "above")],
scale=SCALE, gap=40, body_w=13, left=44, width=W,
title="DA — confirmare la +2, în fereastră",
)
# NU — nicio confirmare în 3 candele, apoi setup mort
cds_nu[5].note = "+1: 49.95; +2: 50.05; +3: 49.85 — toate sub 50.10"
cds_nu[8].note = "a 4-a: close 49.15 — sub low-ul dojiului 49.20, setup mort"
ch_nu = Chart(
candles=cds_nu,
levels=[Level(HIGH_DOJI, "high doji 50.10", "#b06000", label_at="left"),
Level(LOW_DOJI, "low doji 49.20", "#c00", label_at="left")],
marks=[Mark(4, "doji", "#b06000", "above"),
Mark(8, "a 4-a", "#c00", "above")],
scale=SCALE, gap=40, body_w=13, left=44, width=W,
title="NU — nicio confirmare în 3 candele",
)
body_da, h_da = _panel(ch_da)
body_nu, h_nu = _panel(ch_nu)
cap1 = "Serii construite: open = close-ul barei anterioare."
cap2 = "Doji: h 50.10 / l 49.20. Fereastra = maximum 3 candele după doji."
cap_top = h_da + VGAP + h_nu + 20
H = cap_top + 22
parts = [
f'<svg viewBox="0 0 {W} {H:.0f}" xmlns="http://www.w3.org/2000/svg" '
f'style="width:100%;height:auto;font:12px sans-serif">',
f'<g transform="translate(0,0)">{body_da}</g>',
f'<g transform="translate(0,{h_da + VGAP:.0f})">{body_nu}</g>',
f'<text x="24" y="{cap_top:.0f}" fill="#555">{cap1}</text>',
f'<text x="24" y="{cap_top + 17:.0f}" fill="#555">{cap2}</text>',
"</svg>",
]
svg = "\n ".join(parts)
assert "\n\n" not in svg, "linie goală în SVG"
assert "<" not in cap1 and "<" not in cap2
# --- verificarea citirii: etichete în panză, fără suprapuneri ---------------
TEXT = re.compile(r'<text x="([-\d.]+)" y="([-\d.]+)"([^>]*)>(.*?)</text>', re.S)
def _texts(body: str, y_off: float, tag: str):
out = []
for m in TEXT.finditer(body):
x, y = float(m.group(1)), float(m.group(2)) + y_off
attrs, s = m.group(3), m.group(4)
anchor = "start" # implicit în SVG
if 'text-anchor="end"' in attrs:
anchor = "end"
elif 'text-anchor="middle"' in attrs:
anchor = "middle"
bold = "bold" in attrs
w = len(s) * 12 * (0.62 if bold else 0.52)
left = x - w if anchor == "end" else (x - w / 2 if anchor == "middle" else x)
out.append((left, left + w, y, s, tag))
return out
labels = _texts(body_da, 0.0, "DA") + _texts(body_nu, h_da + VGAP, "NU")
for left, right, y, s, tag in labels:
assert left >= -2 and right <= W + 2, f"[{tag}] text iese din panză: {s!r}"
assert 0 <= y <= H, f"[{tag}] text în afara înălțimii: {s!r} (y={y:.0f})"
for i in range(len(labels)):
li, ri, yi, si, ti = labels[i]
for j in range(i + 1, len(labels)):
lj, rj, yj, sj, tj = labels[j]
if ti == tj and abs(yi - yj) < 12 and li < rj - 4 and lj < ri - 4:
raise AssertionError(f"[{ti}] texte suprapuse: {si!r} / {sj!r}")
print(f"DA: doji {doji_da.h:.2f}/{doji_da.l:.2f} +1 {cds_da[5].c:.2f} "
f"+2 {entry:.2f} (confirmă) bare {len(cds_da)}")
print(f"NU: +1 {cds_nu[5].c:.2f} +2 {cds_nu[6].c:.2f} +3 {cds_nu[7].c:.2f} "
f"a 4-a {cds_nu[8].c:.2f} (< {LOW_DOJI}) bare {len(cds_nu)}")
# 5. distanța în pixeli 50.10 -> 50.40 e vizibilă, cu scară comună
dist = ch_da._y(HIGH_DOJI) - ch_da._y(entry)
assert ch_da.scale == ch_nu.scale == SCALE, "scara nu e comună între panouri"
assert dist > 0 and dist > 10, f"distanța 50.10->50.40 = {dist:.1f}px, prea mică"
print(f"scris {OUT} ({W}x{H:.0f}); {len(labels)} etichete verificate; "
f"50.10->50.40 = {dist:.1f}px")
OUT.write_text(svg, encoding="utf-8")

View File

@@ -0,0 +1,60 @@
<svg viewBox="0 0 460 614" xmlns="http://www.w3.org/2000/svg" style="width:100%;height:auto;font:12px sans-serif">
<g transform="translate(0,0)">
<text x="24" y="48" fill="#333" style="font-weight:bold">DA — confirmare la +2, în fereastră</text>
<line x1="24" y1="164.0" x2="446" y2="164.0" stroke="#b06000" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="30" y="158.0" fill="#b06000">high doji 50.10</text>
<line x1="24" y1="204.5" x2="446" y2="204.5" stroke="#c00" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="30" y="198.5" fill="#c00">low doji 49.20</text>
<line x1="44.0" y1="56.0" x2="44.0" y2="76.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="37.5" y="62.7" width="13" height="6.7" fill="#c00"/>
<line x1="84.0" y1="62.7" x2="84.0" y2="112.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="77.5" y="69.5" width="13" height="36.0" fill="#c00"/>
<line x1="124.0" y1="98.8" x2="124.0" y2="148.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="117.5" y="105.5" width="13" height="36.0" fill="#c00"/>
<line x1="164.0" y1="134.8" x2="164.0" y2="191.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="157.5" y="141.5" width="13" height="42.8" fill="#c00"/>
<line x1="204.0" y1="164.0" x2="204.0" y2="204.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="197.5" y="184.3" width="13" height="2.2" fill="#c00"/>
<line x1="244.0" y1="166.3" x2="244.0" y2="193.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="237.5" y="173.0" width="13" height="13.5" fill="#188a3e"/>
<line x1="284.0" y1="143.8" x2="284.0" y2="179.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="277.5" y="150.5" width="13" height="22.5" fill="#188a3e"/>
<line x1="324.0" y1="125.8" x2="324.0" y2="157.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="317.5" y="132.5" width="13" height="18.0" fill="#188a3e"/>
<text x="204.0" y="154.0" fill="#b06000" text-anchor="middle" style="font-weight:bold">doji</text>
<text x="284.0" y="133.8" fill="#188a3e" text-anchor="middle" style="font-weight:bold">confirmare +2 — intrare 50.40</text>
<text x="24" y="226.5" fill="#188a3e">+1: close 49.90 — sub 50.10, nu confirmă</text>
<text x="24" y="243.5" fill="#188a3e">+2: close 50.40 — peste 50.10, confirmă (intrare la close)</text>
</g>
<g transform="translate(0,298)">
<text x="24" y="48" fill="#333" style="font-weight:bold">NU — nicio confirmare în 3 candele</text>
<line x1="24" y1="164.0" x2="446" y2="164.0" stroke="#b06000" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="30" y="158.0" fill="#b06000">high doji 50.10</text>
<line x1="24" y1="204.5" x2="446" y2="204.5" stroke="#c00" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="30" y="198.5" fill="#c00">low doji 49.20</text>
<line x1="44.0" y1="56.0" x2="44.0" y2="76.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="37.5" y="62.7" width="13" height="6.7" fill="#c00"/>
<line x1="84.0" y1="62.7" x2="84.0" y2="112.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="77.5" y="69.5" width="13" height="36.0" fill="#c00"/>
<line x1="124.0" y1="98.8" x2="124.0" y2="148.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="117.5" y="105.5" width="13" height="36.0" fill="#c00"/>
<line x1="164.0" y1="134.8" x2="164.0" y2="191.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="157.5" y="141.5" width="13" height="42.8" fill="#c00"/>
<line x1="204.0" y1="164.0" x2="204.0" y2="204.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="197.5" y="184.3" width="13" height="2.2" fill="#c00"/>
<line x1="244.0" y1="164.0" x2="244.0" y2="193.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="237.5" y="170.7" width="13" height="15.8" fill="#188a3e"/>
<line x1="284.0" y1="159.5" x2="284.0" y2="177.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="277.5" y="166.3" width="13" height="4.5" fill="#188a3e"/>
<line x1="324.0" y1="159.5" x2="324.0" y2="182.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="317.5" y="166.3" width="13" height="9.0" fill="#c00"/>
<line x1="364.0" y1="168.5" x2="364.0" y2="213.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="357.5" y="175.2" width="13" height="31.5" fill="#c00"/>
<text x="204.0" y="154.0" fill="#b06000" text-anchor="middle" style="font-weight:bold">doji</text>
<text x="364.0" y="158.5" fill="#c00" text-anchor="middle" style="font-weight:bold">a 4-a</text>
<text x="24" y="235.5" fill="#188a3e">+1: 49.95; +2: 50.05; +3: 49.85 — toate sub 50.10</text>
<text x="24" y="252.5" fill="#c00">a 4-a: close 49.15 — sub low-ul dojiului 49.20, setup mort</text>
</g>
<text x="24" y="592" fill="#555">Serii construite: open = close-ul barei anterioare.</text>
<text x="24" y="609" fill="#555">Doji: h 50.10 / l 49.20. Fereastra = maximum 3 candele după doji.</text>
</svg>

After

Width:  |  Height:  |  Size: 4.7 KiB

View File

@@ -0,0 +1,190 @@
#!/usr/bin/env python3
"""LABU — Buy Doji la pivotul monthly (Nivel 2.5).
Citește `gen/strategii/buy_doji_labu.spec.txt` și scrie
`gen/strategii/buy_doji_labu.svg`: 49 de candele daily, downtrend → triple
bottom → doji la pivotul monthly 86.00 → confirmare (close 89.50 peste
high-ul dojiului) → bull reversal → higher high, cu SL 81.45 (−9 %) și
TP 116.35 (+30 %).
Toate cifrele din desen vin din specificație: pivot 86.00, intrare 89.50,
SL 81.45, TP 116.35, high/low-ul dojiului (86.30 / 84.20) și low-urile
triple bottom-ului (82.40 / 81.45 / 81.45). Procentele 9 % și 30 % sunt ale
cursului (reale); nivelurile de preț sunt construite de sumarizator —
legenda o spune explicit.
Rulare: python3 gen/strategii/buy_doji_labu.py
"""
import re
import sys
from pathlib import Path
sys.path.insert(0, "/workspace/atm-curs-dl")
from svg_candles import Chart, Level, MA, Mark, from_closes # noqa: E402
OUT = Path(__file__).with_suffix(".svg")
# --- nivelurile din specificație -------------------------------------------
PIVOT = 86.00 # pivot monthly (construit)
ENTRY = 89.50 # intrare = close-ul confirmării (construit)
SL = 81.45 # SL = −9 % față de intrare (construit; procentul e real)
TP = 116.35 # TP = +30 % față de intrare (construit; procentul e real)
DOJI_H, DOJI_L = 86.30, 84.20
# --- seria de close-uri din specificație -----------------------------------
closes = [
# downtrend 0-14
108.0, 106.5, 105.0, 103.0, 101.5, 100.0, 98.0, 96.5, 95.0, 93.5,
92.5, 91.0, 89.5, 87.5, 85.5,
# triple bottom 15 / 23 / 31
82.80, 84.5, 86.5, 88.0, 88.50,
87.0, 85.0, 83.0, 81.50,
83.5, 85.5, 87.0, 87.80, 87.0, 85.0,
83.0, 81.60, 85.00,
84.90, # 33 = doji (o 85.00, c 84.90, h 86.30, l 84.20)
89.50, # 34 = confirmare, close > 86.30 => intrare
# bull reversal / trend up 35-48
91.0, 93.0, 92.0, 95.0, 97.0, 96.0, 99.0, 102.0, 101.0, 105.0, 108.0,
112.0, 116.35, 118.0,
]
assert len(closes) == 49, f"seria are {len(closes)} bare, spec cere 49"
# wick-uri: (up, down). Restul (0.4, 0.5); ajustate ca low-urile/high-urile
# cerute de asserts să iasă exact (bar 15/23/31 = triple bottom; bar 16/24
# nu au voie să coboare sub piciorul din stânga; bar 33 = doji).
wicks = [(0.4, 0.5)] * len(closes)
wicks[15] = (0.4, 0.40) # low 82.40
wicks[16] = (0.4, 0.20) # low 82.60 > 82.40 (piciorul 1 rămâne minim local)
wicks[23] = (0.4, 0.05) # low 81.45
wicks[24] = (0.4, 0.00) # low 81.50 > 81.45 (piciorul 2 rămâne minim local)
wicks[31] = (0.4, 0.15) # low 81.45
wicks[32] = (0.4, 0.10) # low 81.50 (nu sub 81.45)
wicks[33] = (1.30, 0.70) # doji: high 86.30, low 84.20
# volume construite: vânzare peste medie la triple bottom și la confirmare.
# Nu sunt numere din curs (nu sunt etichetate), doar panoul de volum cerut.
vols = [
55, 52, 48, 50, 45, 47, 44, 46, 42, 44,
40, 42, 38, 41, 58,
62, 50, 46, 44, 43,
45, 44, 47, 66, 55,
50, 47, 45, 44, 42,
43, 45, 60, 72, 84,
78, 70, 64, 60, 58,
54, 52, 56, 60, 66,
74, 82, 90, 98,
]
assert len(vols) == len(closes), f"volume {len(vols)} != bare {len(closes)}"
cds = from_closes(closes, wicks=wicks, vols=vols, start=110.0)
assert abs(cds[0].o - 110.0) < 1e-9 and cds[0].c < cds[0].o, "prima bară nu e roșie"
# nota explicativă a dojiului (bloc stânga-jos, nu se suprapune cu candelele)
cds[33].note = ("bara 33 = doji: o 85.00, c 84.90, h 86.30, l 84.20 — "
"corp mic exact pe pivotul monthly 86.00 (indecizie la suport)")
# --- assert-urile din specificație (toate, ca assert reale) ----------------
# 1. pivot 86.00 e straddled de doji
assert cds[33].l <= PIVOT <= cds[33].h, "doji-ul nu straddlează 86.00"
# 2. confirmarea: close[34] == 89.50 == intrare și > high[33]
assert abs(cds[34].c - ENTRY) < 1e-9, f"close[34]={cds[34].c} != intrare 89.50"
assert cds[34].c > cds[33].h, "confirmarea nu închide peste high-ul dojiului"
# 3. SL = min(low[30:35]) (regula: extrema ultimelor 4 candele de la confirmare)
min_30_35 = min(c.l for c in cds[30:35])
assert abs(min_30_35 - SL) < 1e-9, f"min low[30:35]={min_30_35} != SL {SL}"
# 4./5. procentele REALE ale cursului
assert round((ENTRY - SL) / ENTRY * 100, 1) == 9.0, "stopul nu iese −9 %"
assert round((TP - ENTRY) / ENTRY * 100, 1) == 30.0, "TP nu iese +30 %"
# 6. triple bottom: exact trei minime locale sub 86.00, în zona 81-83,
# al doilea și al treilea fără low nou semnificativ față de primul
lows = [c.l for c in cds]
bottom_idx = [i for i in range(1, len(cds) - 1)
if lows[i] < lows[i - 1] and lows[i] < lows[i + 1]
and lows[i] < PIVOT and 81.0 <= lows[i] <= 83.0]
assert bottom_idx == [15, 23, 31], f"triple bottom la {bottom_idx}, nu [15, 23, 31]"
b1, b2, b3 = lows[15], lows[23], lows[31]
tol = 0.02 * b1 # „semnificativ" = > 2 % sub primul low
assert b2 >= b1 - tol and b3 >= b1 - tol, \
f"al doilea/treilea picior coboară semnificativ sub primul: {b1}/{b2}/{b3}"
assert abs(b1 - 82.40) < 1e-9 and abs(b2 - SL) < 1e-9 and abs(b3 - SL) < 1e-9, \
f"low-urile triple bottom: {b1}/{b2}/{b3}"
# 7. TP atins: există i > 34 cu high[i] >= 116.35
assert any(c.h >= TP for c in cds[35:]), "TP 116.35 nu e atins după intrare"
# 8. doji: corp <= 10 % din amplitudine
assert abs(cds[33].o - cds[33].c) <= 0.10 * (cds[33].h - cds[33].l), "bara 33 nu e doji"
assert abs(cds[33].h - DOJI_H) < 1e-9 and abs(cds[33].l - DOJI_L) < 1e-9, \
"doji h/l nu ies 86.30/84.20"
# în plus: confirmarea vine cu volum peste medie (cerut de spec)
mean_vol = sum(vols) / len(vols)
assert vols[34] > mean_vol, f"volumul confirmării {vols[34]} <= media {mean_vol:.0f}"
entry = cds[34].c
w = 820 # lățime mărită: 49 bare × gap 15 + etichetele din dreapta
# (spec sugera 760, dar ultima candelă ajunge la x=764)
ch = Chart(
candles=cds,
levels=[Level(PIVOT, "pivot monthly 86.00 (construit)", "#777",
dash="4 3", label_at="left"),
Level(SL, "SL 81.45 (construit) = −9 % (procent real)", "#c00",
label_at="left"),
Level(ENTRY, "intrare 89.50 (construit)", "#188a3e"),
Level(TP, "TP 116.35 (construit) = +30 % (procent real)", "#1a5fb4")],
marks=[Mark(15, f"bottom 1 {lows[15]:.2f}", "#c00", "below"),
Mark(23, f"bottom 2 {lows[23]:.2f}", "#c00", "below"),
Mark(31, f"bottom 3 {lows[31]:.2f}", "#c00", "below"),
Mark(33, "doji", "#b06000", "above"),
Mark(34, "confirmare (intrare)", "#188a3e", "above", dy=-14),
Mark(48, "higher high — TP atins", "#188a3e", "above")],
mas=[MA(20, "#b06000", dash="4 3", text="SMA-20")],
scale=8.0, gap=15, body_w=9, left=44, width=w, vol_h=70,
vol_label="volum", title="LABU — daily",
caption=("Procentele (stop 9 %, +30 %) sunt ale cursului; nivelurile de "
"preț sunt construite de sumarizator (18_2025-09-23.md:713)."),
)
svg = ch.render()
assert "\n\n" not in svg and "<svg" in svg, "SVG invalid (linie goală?)"
# --- citirea SVG-ului: etichete în panză, fără suprapuneri -----------------
TEXT = re.compile(r'<text x="([-\d.]+)" y="([-\d.]+)"([^>]*)>(.*?)</text>', re.S)
H = float(re.search(r'viewBox="0 0 (\d+) ([\d.]+)"', svg).group(2))
labels = []
for m in TEXT.finditer(svg):
x, y, attrs, s = float(m.group(1)), float(m.group(2)), m.group(3), m.group(4)
anchor = "end" if 'text-anchor="end"' in attrs else \
("middle" if 'text-anchor="middle"' in attrs else "start")
bold = "bold" in attrs
wpx = len(s) * 12 * (0.62 if bold else 0.52)
left = x - wpx if anchor == "end" else (x - wpx / 2 if anchor == "middle" else x)
labels.append((left, left + wpx, y, s))
for left, right, y, s in labels:
assert left >= -2 and right <= w + 2, f"text iese din panză: {s!r} [{left:.0f},{right:.0f}]"
assert 0 <= y <= H, f"text în afara înălțimii: {s!r} (y={y:.0f})"
for i in range(len(labels)):
li, ri, yi, si = labels[i]
for j in range(i + 1, len(labels)):
lj, rj, yj, sj = labels[j]
if abs(yi - yj) < 12 and li < rj - 4 and lj < ri - 4:
raise AssertionError(f"texte suprapuse: {si!r} / {sj!r}\n {labels[i]}\n {labels[j]}")
# candelele sunt o serie continuă, lipită
xs = [ch._cx(i) for i in range(len(cds))]
assert xs[-1] + ch.body_w / 2 < w, f"ultima candelă iese din panză (x={xs[-1]})"
assert all(xs[i + 1] - xs[i] == ch.gap for i in range(len(xs) - 1)), "serie cu goluri"
for a, b in zip(cds, cds[1:]):
assert abs(a.c - b.o) < 1e-9, "open != close-ul barei anterioare"
print(f"LABU: {len(cds)} bare, doji {cds[33].l:.2f}-{cds[33].h:.2f} peste pivot "
f"{PIVOT:.2f}, intrare {entry:.2f}, SL {SL:.2f} (−9 %), TP {TP:.2f} (+30 %)")
print(f"triple bottom: {b1:.2f} / {b2:.2f} / {b3:.2f} "
f"volum confirmare {vols[34]} > medie {mean_vol:.0f}")
print(f"scris {OUT} ({w}x{H:.0f}); {len(labels)} etichete verificate, fără suprapuneri")
OUT.write_text(svg, encoding="utf-8")

View File

@@ -0,0 +1,170 @@
<svg viewBox="0 0 820 507" xmlns="http://www.w3.org/2000/svg" style="width:100%;height:auto;font:12px sans-serif">
<text x="24" y="48" fill="#333" style="font-weight:bold">LABU — daily</text>
<line x1="24" y1="315.2" x2="806" y2="315.2" stroke="#777" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="30" y="309.2" fill="#777">pivot monthly 86.00 (construit)</text>
<line x1="24" y1="351.6" x2="806" y2="351.6" stroke="#c00" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="30" y="345.6" fill="#c00">SL 81.45 (construit) = −9 % (procent real)</text>
<line x1="24" y1="287.2" x2="806" y2="287.2" stroke="#188a3e" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="792" y="281.2" fill="#188a3e" text-anchor="end">intrare 89.50 (construit)</text>
<line x1="24" y1="72.4" x2="806" y2="72.4" stroke="#1a5fb4" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="792" y="66.4" fill="#1a5fb4" text-anchor="end">TP 116.35 (construit) = +30 % (procent real)</text>
<polyline points="329.0,249.9 344.0,258.3 359.0,266.9 374.0,275.7 389.0,284.3 404.0,291.5 419.0,297.3 434.0,301.7 449.0,305.2 464.0,308.4 479.0,311.8 494.0,315.6 509.0,319.3 524.0,321.1 539.0,322.2 554.0,320.6 569.0,317.3 584.0,313.9 599.0,311.7 614.0,308.9 629.0,305.5 644.0,301.9 659.0,296.3 674.0,288.7 689.0,280.9 704.0,272.3 719.0,263.3 734.0,253.3 749.0,241.9 764.0,229.5" fill="none" stroke="#b06000" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="770" y="224" fill="#b06000">SMA-20</text>
<line x1="44.0" y1="120.0" x2="44.0" y2="143.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="39.5" y="123.2" width="9" height="16.0" fill="#c00"/>
<line x1="59.0" y1="136.0" x2="59.0" y2="155.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="54.5" y="139.2" width="9" height="12.0" fill="#c00"/>
<line x1="74.0" y1="148.0" x2="74.0" y2="167.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="69.5" y="151.2" width="9" height="12.0" fill="#c00"/>
<line x1="89.0" y1="160.0" x2="89.0" y2="183.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="84.5" y="163.2" width="9" height="16.0" fill="#c00"/>
<line x1="104.0" y1="176.0" x2="104.0" y2="195.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="99.5" y="179.2" width="9" height="12.0" fill="#c00"/>
<line x1="119.0" y1="188.0" x2="119.0" y2="207.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="114.5" y="191.2" width="9" height="12.0" fill="#c00"/>
<line x1="134.0" y1="200.0" x2="134.0" y2="223.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="129.5" y="203.2" width="9" height="16.0" fill="#c00"/>
<line x1="149.0" y1="216.0" x2="149.0" y2="235.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="144.5" y="219.2" width="9" height="12.0" fill="#c00"/>
<line x1="164.0" y1="228.0" x2="164.0" y2="247.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="159.5" y="231.2" width="9" height="12.0" fill="#c00"/>
<line x1="179.0" y1="240.0" x2="179.0" y2="259.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="174.5" y="243.2" width="9" height="12.0" fill="#c00"/>
<line x1="194.0" y1="252.0" x2="194.0" y2="267.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="189.5" y="255.2" width="9" height="8.0" fill="#c00"/>
<line x1="209.0" y1="260.0" x2="209.0" y2="279.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="204.5" y="263.2" width="9" height="12.0" fill="#c00"/>
<line x1="224.0" y1="272.0" x2="224.0" y2="291.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="219.5" y="275.2" width="9" height="12.0" fill="#c00"/>
<line x1="239.0" y1="284.0" x2="239.0" y2="307.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="234.5" y="287.2" width="9" height="16.0" fill="#c00"/>
<line x1="254.0" y1="300.0" x2="254.0" y2="323.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="249.5" y="303.2" width="9" height="16.0" fill="#c00"/>
<line x1="269.0" y1="316.0" x2="269.0" y2="344.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="264.5" y="319.2" width="9" height="21.6" fill="#c00"/>
<line x1="284.0" y1="324.0" x2="284.0" y2="342.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="279.5" y="327.2" width="9" height="13.6" fill="#188a3e"/>
<line x1="299.0" y1="308.0" x2="299.0" y2="331.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="294.5" y="311.2" width="9" height="16.0" fill="#188a3e"/>
<line x1="314.0" y1="296.0" x2="314.0" y2="315.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="309.5" y="299.2" width="9" height="12.0" fill="#188a3e"/>
<line x1="329.0" y1="292.0" x2="329.0" y2="303.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="324.5" y="295.2" width="9" height="4.0" fill="#188a3e"/>
<line x1="344.0" y1="292.0" x2="344.0" y2="311.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="339.5" y="295.2" width="9" height="12.0" fill="#c00"/>
<line x1="359.0" y1="304.0" x2="359.0" y2="327.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="354.5" y="307.2" width="9" height="16.0" fill="#c00"/>
<line x1="374.0" y1="320.0" x2="374.0" y2="343.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="369.5" y="323.2" width="9" height="16.0" fill="#c00"/>
<line x1="389.0" y1="336.0" x2="389.0" y2="351.6" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="384.5" y="339.2" width="9" height="12.0" fill="#c00"/>
<line x1="404.0" y1="332.0" x2="404.0" y2="351.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="399.5" y="335.2" width="9" height="16.0" fill="#188a3e"/>
<line x1="419.0" y1="316.0" x2="419.0" y2="339.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="414.5" y="319.2" width="9" height="16.0" fill="#188a3e"/>
<line x1="434.0" y1="304.0" x2="434.0" y2="323.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="429.5" y="307.2" width="9" height="12.0" fill="#188a3e"/>
<line x1="449.0" y1="297.6" x2="449.0" y2="311.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="444.5" y="300.8" width="9" height="6.4" fill="#188a3e"/>
<line x1="464.0" y1="297.6" x2="464.0" y2="311.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="459.5" y="300.8" width="9" height="6.4" fill="#c00"/>
<line x1="479.0" y1="304.0" x2="479.0" y2="327.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="474.5" y="307.2" width="9" height="16.0" fill="#c00"/>
<line x1="494.0" y1="320.0" x2="494.0" y2="343.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="489.5" y="323.2" width="9" height="16.0" fill="#c00"/>
<line x1="509.0" y1="336.0" x2="509.0" y2="351.6" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="504.5" y="339.2" width="9" height="11.2" fill="#c00"/>
<line x1="524.0" y1="320.0" x2="524.0" y2="351.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="519.5" y="323.2" width="9" height="27.2" fill="#188a3e"/>
<line x1="539.0" y1="312.8" x2="539.0" y2="329.6" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="534.5" y="323.2" width="9" height="1.6" fill="#c00"/>
<line x1="554.0" y1="284.0" x2="554.0" y2="328.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="549.5" y="287.2" width="9" height="36.8" fill="#188a3e"/>
<line x1="569.0" y1="272.0" x2="569.0" y2="291.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="564.5" y="275.2" width="9" height="12.0" fill="#188a3e"/>
<line x1="584.0" y1="256.0" x2="584.0" y2="279.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="579.5" y="259.2" width="9" height="16.0" fill="#188a3e"/>
<line x1="599.0" y1="256.0" x2="599.0" y2="271.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="594.5" y="259.2" width="9" height="8.0" fill="#c00"/>
<line x1="614.0" y1="240.0" x2="614.0" y2="271.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="609.5" y="243.2" width="9" height="24.0" fill="#188a3e"/>
<line x1="629.0" y1="224.0" x2="629.0" y2="247.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="624.5" y="227.2" width="9" height="16.0" fill="#188a3e"/>
<line x1="644.0" y1="224.0" x2="644.0" y2="239.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="639.5" y="227.2" width="9" height="8.0" fill="#c00"/>
<line x1="659.0" y1="208.0" x2="659.0" y2="239.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="654.5" y="211.2" width="9" height="24.0" fill="#188a3e"/>
<line x1="674.0" y1="184.0" x2="674.0" y2="215.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="669.5" y="187.2" width="9" height="24.0" fill="#188a3e"/>
<line x1="689.0" y1="184.0" x2="689.0" y2="199.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="684.5" y="187.2" width="9" height="8.0" fill="#c00"/>
<line x1="704.0" y1="160.0" x2="704.0" y2="199.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="699.5" y="163.2" width="9" height="32.0" fill="#188a3e"/>
<line x1="719.0" y1="136.0" x2="719.0" y2="167.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="714.5" y="139.2" width="9" height="24.0" fill="#188a3e"/>
<line x1="734.0" y1="104.0" x2="734.0" y2="143.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="729.5" y="107.2" width="9" height="32.0" fill="#188a3e"/>
<line x1="749.0" y1="69.2" x2="749.0" y2="111.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="744.5" y="72.4" width="9" height="34.8" fill="#188a3e"/>
<line x1="764.0" y1="56.0" x2="764.0" y2="76.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="759.5" y="59.2" width="9" height="13.2" fill="#188a3e"/>
<line x1="24" y1="447.6" x2="806" y2="447.6" stroke="#888"/>
<rect x="39.5" y="408.3" width="9" height="39.3" fill="#c00" opacity="0.55"/>
<rect x="54.5" y="410.5" width="9" height="37.1" fill="#c00" opacity="0.55"/>
<rect x="69.5" y="413.3" width="9" height="34.3" fill="#c00" opacity="0.55"/>
<rect x="84.5" y="411.9" width="9" height="35.7" fill="#c00" opacity="0.55"/>
<rect x="99.5" y="415.5" width="9" height="32.1" fill="#c00" opacity="0.55"/>
<rect x="114.5" y="414.0" width="9" height="33.6" fill="#c00" opacity="0.55"/>
<rect x="129.5" y="416.2" width="9" height="31.4" fill="#c00" opacity="0.55"/>
<rect x="144.5" y="414.7" width="9" height="32.9" fill="#c00" opacity="0.55"/>
<rect x="159.5" y="417.6" width="9" height="30.0" fill="#c00" opacity="0.55"/>
<rect x="174.5" y="416.2" width="9" height="31.4" fill="#c00" opacity="0.55"/>
<rect x="189.5" y="419.0" width="9" height="28.6" fill="#c00" opacity="0.55"/>
<rect x="204.5" y="417.6" width="9" height="30.0" fill="#c00" opacity="0.55"/>
<rect x="219.5" y="420.5" width="9" height="27.1" fill="#c00" opacity="0.55"/>
<rect x="234.5" y="418.3" width="9" height="29.3" fill="#c00" opacity="0.55"/>
<rect x="249.5" y="406.2" width="9" height="41.4" fill="#c00" opacity="0.55"/>
<rect x="264.5" y="403.3" width="9" height="44.3" fill="#c00" opacity="0.55"/>
<rect x="279.5" y="411.9" width="9" height="35.7" fill="#188a3e" opacity="0.55"/>
<rect x="294.5" y="414.7" width="9" height="32.9" fill="#188a3e" opacity="0.55"/>
<rect x="309.5" y="416.2" width="9" height="31.4" fill="#188a3e" opacity="0.55"/>
<rect x="324.5" y="416.9" width="9" height="30.7" fill="#188a3e" opacity="0.55"/>
<rect x="339.5" y="415.5" width="9" height="32.1" fill="#c00" opacity="0.55"/>
<rect x="354.5" y="416.2" width="9" height="31.4" fill="#c00" opacity="0.55"/>
<rect x="369.5" y="414.0" width="9" height="33.6" fill="#c00" opacity="0.55"/>
<rect x="384.5" y="400.5" width="9" height="47.1" fill="#c00" opacity="0.55"/>
<rect x="399.5" y="408.3" width="9" height="39.3" fill="#188a3e" opacity="0.55"/>
<rect x="414.5" y="411.9" width="9" height="35.7" fill="#188a3e" opacity="0.55"/>
<rect x="429.5" y="414.0" width="9" height="33.6" fill="#188a3e" opacity="0.55"/>
<rect x="444.5" y="415.5" width="9" height="32.1" fill="#188a3e" opacity="0.55"/>
<rect x="459.5" y="416.2" width="9" height="31.4" fill="#c00" opacity="0.55"/>
<rect x="474.5" y="417.6" width="9" height="30.0" fill="#c00" opacity="0.55"/>
<rect x="489.5" y="416.9" width="9" height="30.7" fill="#c00" opacity="0.55"/>
<rect x="504.5" y="415.5" width="9" height="32.1" fill="#c00" opacity="0.55"/>
<rect x="519.5" y="404.7" width="9" height="42.9" fill="#188a3e" opacity="0.55"/>
<rect x="534.5" y="396.2" width="9" height="51.4" fill="#c00" opacity="0.55"/>
<rect x="549.5" y="387.6" width="9" height="60.0" fill="#188a3e" opacity="0.55"/>
<rect x="564.5" y="391.9" width="9" height="55.7" fill="#188a3e" opacity="0.55"/>
<rect x="579.5" y="397.6" width="9" height="50.0" fill="#188a3e" opacity="0.55"/>
<rect x="594.5" y="401.9" width="9" height="45.7" fill="#c00" opacity="0.55"/>
<rect x="609.5" y="404.7" width="9" height="42.9" fill="#188a3e" opacity="0.55"/>
<rect x="624.5" y="406.2" width="9" height="41.4" fill="#188a3e" opacity="0.55"/>
<rect x="639.5" y="409.0" width="9" height="38.6" fill="#c00" opacity="0.55"/>
<rect x="654.5" y="410.5" width="9" height="37.1" fill="#188a3e" opacity="0.55"/>
<rect x="669.5" y="407.6" width="9" height="40.0" fill="#188a3e" opacity="0.55"/>
<rect x="684.5" y="404.7" width="9" height="42.9" fill="#c00" opacity="0.55"/>
<rect x="699.5" y="400.5" width="9" height="47.1" fill="#188a3e" opacity="0.55"/>
<rect x="714.5" y="394.7" width="9" height="52.9" fill="#188a3e" opacity="0.55"/>
<rect x="729.5" y="389.0" width="9" height="58.6" fill="#188a3e" opacity="0.55"/>
<rect x="744.5" y="383.3" width="9" height="64.3" fill="#188a3e" opacity="0.55"/>
<rect x="759.5" y="377.6" width="9" height="70.0" fill="#188a3e" opacity="0.55"/>
<text x="24" y="371.6" fill="#555">volum</text>
<text x="269.0" y="364.0" fill="#c00" text-anchor="middle" style="font-weight:bold">bottom 1 82.40</text>
<text x="389.0" y="365.6" fill="#c00" text-anchor="middle" style="font-weight:bold">bottom 2 81.45</text>
<text x="509.0" y="365.6" fill="#c00" text-anchor="middle" style="font-weight:bold">bottom 3 81.45</text>
<text x="539.0" y="302.8" fill="#b06000" text-anchor="middle" style="font-weight:bold">doji</text>
<text x="554.0" y="260.0" fill="#188a3e" text-anchor="middle" style="font-weight:bold">confirmare (intrare)</text>
<text x="776.0" y="46.0" fill="#188a3e" text-anchor="end" style="font-weight:bold">higher high — TP atins</text>
<text x="24" y="469.6" fill="#c00">bara 33 = doji: o 85.00, c 84.90, h 86.30, l 84.20 — corp mic exact pe pivotul monthly 86.00 (indecizie la suport)</text>
<text x="24" y="495" fill="#555">Procentele (stop 9 %, +30 %) sunt ale cursului; nivelurile de preț sunt construite de sumarizator (18_2025-09-23.md:713).</text>
</svg>

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -0,0 +1,114 @@
"""Sell Doji — oglinda short a ansamblului Buy Doji (daily, exemplu construit).
Citește specificația `gen/strategii/buy_doji_sell.spec.txt`. Seria de 31 de
candele e construită din lista de close-uri dată, cu open-ul fiecărei bare =
close-ul precedentului; wick-urile sunt fixate ca nivelurile (doji 99.20 /
100.10, intrare short 99.00, SL 100.10, TP 97.90, trail 96.45, ieșire la 96.60)
să iasă exact din serie, nu doar din etichetă.
"""
import sys; sys.path.insert(0, "/workspace/atm-curs-dl")
from svg_candles import Chart, Level, Mark, from_closes
# --- seria: 31 de close-uri, exact ca în specificație -----------------------
closes = [
93.00, 94.10, 93.70, 95.00, 96.10, 95.80, 97.00, 98.10, 97.80, 98.90, # 0-9
99.80, 100.20, 99.90, 99.65, # 10-13 uptrend
99.60, # 14 doji
99.40, # 15 +1 (nu confirmă)
99.00, # 16 +2 confirmare => intrare short
98.50, 98.30, 97.80, 97.40, 97.00, 96.70, 96.40, 96.10, 95.80, # 17-25
96.20, 96.60, 96.40, 96.10, 96.30, # 26-30
]
assert len(closes) == 31
# wick-uri: (up, down) implicite (0.30, 0.25) + excepțiile din specificație
wicks = [(0.30, 0.25)] * 31
# ajustat: cu wu=0.30, high[13] = 99.90+0.30 = 100.20 > SL; îl coborâm la
# 100.10 ca max(high[13:17]) == 100.10 (high-ul ultimelor 4 candele = SL)
wicks[13] = (0.20, 0.25)
wicks[14] = (0.45, 0.40) # doji: high 100.10, low 99.20
wicks[15] = (0.20, 0.15) # high 99.80, low 99.25 (peste 99.20 => nu confirmă)
wicks[16] = (0.20, 0.15) # high 99.60, low 98.85
wicks[25] = (0.35, 0.20) # high 96.45 (= trail), low 95.60 (minimul corecției)
# ajustat: cu wd=0.25, low[26] = 95.80-0.25 = 95.55 < 95.60, adică bara 26 ar
# fi minimul, nu bara 25; wd=0.10 păstrează bara 25 ca minim (low 95.60)
wicks[26] = (0.30, 0.10)
# volum relativ ales de generator (materialul nu dă volum pe exemplu);
# peste medie pe doji (14) și pe confirmare (16) — distribuție — apoi normal
vols = [44, 42, 46, 40, 45, 48, 43, 47, 50, 45,
52, 58, 49, 55, 90, 62, 84,
56, 52, 50, 48, 46, 44, 42, 40, 45,
50, 46, 44, 43, 45]
assert len(vols) == 31
media_vol = sum(vols) / len(vols)
assert vols[14] > media_vol and vols[16] > media_vol, "volum peste medie pe doji/confirmare"
cds = from_closes(closes, wicks=wicks, vols=vols, start=closes[0])
# adnotările pe bara +1 și pe confirmare, ca linii sub grafic (fără suprapunere)
cds[15].color = "#777" # bara de așteptare: neutră, nu confirmă
cds[15].note = "bara 15: close 99.40 peste low-ul dojiului 99.20 — NU confirmă"
cds[16].note = "bara 16: close 99.00 sub 99.20 — confirmare (intrare short)"
doji, wait, conf = cds[14], cds[15], cds[16]
entry, sl, tp = 99.00, 100.10, 97.90
# --- cele 9 assert-uri cerute de specificație ------------------------------
# 1. SL-ul e exact high-ul ultimelor 4 candele
assert abs(max(cd.h for cd in cds[13:17]) - 100.10) < 1e-9, "max high[13:17] != 100.10"
# 2. intrarea e close-ul barei 16
assert abs(conf.c - entry) < 1e-9, f"close[16]={conf.c} != 99.00"
# 3. confirmarea închide sub low-ul dojiului
assert abs(doji.l - 99.20) < 1e-9 and conf.c < doji.l, "close[16] nu e sub low[14]"
# 4. bara +1 NU confirmă
assert wait.c >= doji.l, "bara 15 ar confirma, fereastra n-ar avea sens"
# 5. 1:1 exact
assert abs(abs(sl - entry) - abs(entry - tp)) < 1e-9 and abs((entry - tp) - 1.10) < 1e-9, \
"risc != recompensă (nu e 1:1 exact)"
# 6. primul low <= TP după intrare e bara 19
primul_tp = next(i for i in range(17, len(cds)) if cds[i].l <= tp)
assert primul_tp == 19, f"TP 97.90 atins întâi la bara {primul_tp}, nu 19"
assert abs(cds[18].l - 98.05) < 1e-9 and abs(cds[19].l - 97.55) < 1e-9, "low-urile 18/19 nu ies"
# 7. trailing = high[25] = 96.45; primul close peste el e bara 27; ieșirea e pe profit
trail = cds[25].h
assert abs(trail - 96.45) < 1e-9, f"high[25]={trail} != 96.45"
primul_exit = next(i for i in range(26, len(cds)) if cds[i].c > trail)
assert primul_exit == 27 and abs(cds[27].c - 96.60) < 1e-9, "ieșirea nu e bara 27 @ 96.60"
assert cds[27].c < entry, "ieșirea n-ar fi pe profit"
# 8. doji: corp <= 10% din amplitudine
assert abs(doji.o - doji.c) <= 0.10 * (doji.h - doji.l), "bara 14 nu e doji"
# 9. uptrend vizibil
assert cds[13].c > cds[0].c + 5, "uptrend prea mic"
print(f"doji {doji.l:.2f}/{doji.h:.2f} intrare short {conf.c:.2f} "
f"SL {sl:.2f} TP {tp:.2f} trail {trail:.2f} ieșire bara 27 @ {cds[27].c:.2f}")
# --- desenul ----------------------------------------------------------------
ch = Chart(
candles=cds,
levels=[
# toate etichetele în dreapta: în stânga sus se suprapuneau cu mark-ul
# dojiului (SL la y=62, text până în ~x196; doji la y=58, din ~x193)
Level(sl, "SL 100.10 (high 4 candele)", "#c00"),
Level(entry, "intrare short 99.00", "#188a3e"),
Level(tp, "TP 97.90 = 1R", "#1a5fb4"),
Level(trail, "trail 96.45", "#b06000"),
],
marks=[
Mark(14, "doji 99.20 / 100.10", "#b06000", "above"),
Mark(27, "ieșire pe trail 96.60", "#c00", "above", dy=-6),
],
scale=30.0, gap=15, body_w=9, left=44, width=640, vol_h=70,
vol_label="volum", title="Sell Doji — daily (exemplu construit, fără ticker)",
# scurt: o linie de caption mai lungă de ~90 de caractere iese din viewBox
# (640px) și se taie la randare
caption="Intrare short la close sub low-ul dojiului, SL la high 4 candele, "
"TP 1:1, apoi trail.",
)
svg = ch.render()
assert "<svg" in svg and "\n\n" not in svg, "linie goală în SVG"
with open("/workspace/atm-curs-dl/gen/strategii/buy_doji_sell.svg", "w") as f:
f.write(svg)
print("scris gen/strategii/buy_doji_sell.svg")

View File

@@ -0,0 +1,111 @@
<svg viewBox="0 0 640 460" xmlns="http://www.w3.org/2000/svg" style="width:100%;height:auto;font:12px sans-serif">
<text x="24" y="48" fill="#333" style="font-weight:bold">Sell Doji — daily (exemplu construit, fără ticker)</text>
<line x1="24" y1="68.0" x2="626" y2="68.0" stroke="#c00" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="612" y="62.0" fill="#c00" text-anchor="end">SL 100.10 (high 4 candele)</text>
<line x1="24" y1="101.0" x2="626" y2="101.0" stroke="#188a3e" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="612" y="95.0" fill="#188a3e" text-anchor="end">intrare short 99.00</text>
<line x1="24" y1="134.0" x2="626" y2="134.0" stroke="#1a5fb4" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="612" y="128.0" fill="#1a5fb4" text-anchor="end">TP 97.90 = 1R</text>
<line x1="24" y1="177.5" x2="626" y2="177.5" stroke="#b06000" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="612" y="171.5" fill="#b06000" text-anchor="end">trail 96.45</text>
<line x1="44.0" y1="272.0" x2="44.0" y2="288.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="39.5" y="281.0" width="9" height="1.6" fill="#188a3e"/>
<line x1="59.0" y1="239.0" x2="59.0" y2="288.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="54.5" y="248.0" width="9" height="33.0" fill="#188a3e"/>
<line x1="74.0" y1="239.0" x2="74.0" y2="267.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="69.5" y="248.0" width="9" height="12.0" fill="#c00"/>
<line x1="89.0" y1="212.0" x2="89.0" y2="267.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="84.5" y="221.0" width="9" height="39.0" fill="#188a3e"/>
<line x1="104.0" y1="179.0" x2="104.0" y2="228.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="99.5" y="188.0" width="9" height="33.0" fill="#188a3e"/>
<line x1="119.0" y1="179.0" x2="119.0" y2="204.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="114.5" y="188.0" width="9" height="9.0" fill="#c00"/>
<line x1="134.0" y1="152.0" x2="134.0" y2="204.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="129.5" y="161.0" width="9" height="36.0" fill="#188a3e"/>
<line x1="149.0" y1="119.0" x2="149.0" y2="168.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="144.5" y="128.0" width="9" height="33.0" fill="#188a3e"/>
<line x1="164.0" y1="119.0" x2="164.0" y2="144.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="159.5" y="128.0" width="9" height="9.0" fill="#c00"/>
<line x1="179.0" y1="95.0" x2="179.0" y2="144.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="174.5" y="104.0" width="9" height="33.0" fill="#188a3e"/>
<line x1="194.0" y1="68.0" x2="194.0" y2="111.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="189.5" y="77.0" width="9" height="27.0" fill="#188a3e"/>
<line x1="209.0" y1="56.0" x2="209.0" y2="84.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="204.5" y="65.0" width="9" height="12.0" fill="#188a3e"/>
<line x1="224.0" y1="56.0" x2="224.0" y2="81.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="219.5" y="65.0" width="9" height="9.0" fill="#c00"/>
<line x1="239.0" y1="68.0" x2="239.0" y2="89.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="234.5" y="74.0" width="9" height="7.5" fill="#c00"/>
<line x1="254.0" y1="68.0" x2="254.0" y2="95.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="249.5" y="81.5" width="9" height="1.6" fill="#c00"/>
<line x1="269.0" y1="77.0" x2="269.0" y2="93.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="264.5" y="83.0" width="9" height="6.0" fill="#777"/>
<line x1="284.0" y1="83.0" x2="284.0" y2="105.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="279.5" y="89.0" width="9" height="12.0" fill="#c00"/>
<line x1="299.0" y1="92.0" x2="299.0" y2="123.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="294.5" y="101.0" width="9" height="15.0" fill="#c00"/>
<line x1="314.0" y1="107.0" x2="314.0" y2="129.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="309.5" y="116.0" width="9" height="6.0" fill="#c00"/>
<line x1="329.0" y1="113.0" x2="329.0" y2="144.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="324.5" y="122.0" width="9" height="15.0" fill="#c00"/>
<line x1="344.0" y1="128.0" x2="344.0" y2="156.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="339.5" y="137.0" width="9" height="12.0" fill="#c00"/>
<line x1="359.0" y1="140.0" x2="359.0" y2="168.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="354.5" y="149.0" width="9" height="12.0" fill="#c00"/>
<line x1="374.0" y1="152.0" x2="374.0" y2="177.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="369.5" y="161.0" width="9" height="9.0" fill="#c00"/>
<line x1="389.0" y1="161.0" x2="389.0" y2="186.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="384.5" y="170.0" width="9" height="9.0" fill="#c00"/>
<line x1="404.0" y1="170.0" x2="404.0" y2="195.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="399.5" y="179.0" width="9" height="9.0" fill="#c00"/>
<line x1="419.0" y1="177.5" x2="419.0" y2="203.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="414.5" y="188.0" width="9" height="9.0" fill="#c00"/>
<line x1="434.0" y1="176.0" x2="434.0" y2="200.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="429.5" y="185.0" width="9" height="12.0" fill="#188a3e"/>
<line x1="449.0" y1="164.0" x2="449.0" y2="192.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="444.5" y="173.0" width="9" height="12.0" fill="#188a3e"/>
<line x1="464.0" y1="164.0" x2="464.0" y2="186.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="459.5" y="173.0" width="9" height="6.0" fill="#c00"/>
<line x1="479.0" y1="170.0" x2="479.0" y2="195.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="474.5" y="179.0" width="9" height="9.0" fill="#c00"/>
<line x1="494.0" y1="173.0" x2="494.0" y2="195.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="489.5" y="182.0" width="9" height="6.0" fill="#188a3e"/>
<line x1="24" y1="384.5" x2="626" y2="384.5" stroke="#888"/>
<rect x="39.5" y="350.3" width="9" height="34.2" fill="#188a3e" opacity="0.55"/>
<rect x="54.5" y="351.8" width="9" height="32.7" fill="#188a3e" opacity="0.55"/>
<rect x="69.5" y="348.7" width="9" height="35.8" fill="#c00" opacity="0.55"/>
<rect x="84.5" y="353.4" width="9" height="31.1" fill="#188a3e" opacity="0.55"/>
<rect x="99.5" y="349.5" width="9" height="35.0" fill="#188a3e" opacity="0.55"/>
<rect x="114.5" y="347.2" width="9" height="37.3" fill="#c00" opacity="0.55"/>
<rect x="129.5" y="351.1" width="9" height="33.4" fill="#188a3e" opacity="0.55"/>
<rect x="144.5" y="347.9" width="9" height="36.6" fill="#188a3e" opacity="0.55"/>
<rect x="159.5" y="345.6" width="9" height="38.9" fill="#c00" opacity="0.55"/>
<rect x="174.5" y="349.5" width="9" height="35.0" fill="#188a3e" opacity="0.55"/>
<rect x="189.5" y="344.1" width="9" height="40.4" fill="#188a3e" opacity="0.55"/>
<rect x="204.5" y="339.4" width="9" height="45.1" fill="#188a3e" opacity="0.55"/>
<rect x="219.5" y="346.4" width="9" height="38.1" fill="#c00" opacity="0.55"/>
<rect x="234.5" y="341.7" width="9" height="42.8" fill="#c00" opacity="0.55"/>
<rect x="249.5" y="314.5" width="9" height="70.0" fill="#c00" opacity="0.55"/>
<rect x="264.5" y="336.3" width="9" height="48.2" fill="#777" opacity="0.55"/>
<rect x="279.5" y="319.2" width="9" height="65.3" fill="#c00" opacity="0.55"/>
<rect x="294.5" y="340.9" width="9" height="43.6" fill="#c00" opacity="0.55"/>
<rect x="309.5" y="344.1" width="9" height="40.4" fill="#c00" opacity="0.55"/>
<rect x="324.5" y="345.6" width="9" height="38.9" fill="#c00" opacity="0.55"/>
<rect x="339.5" y="347.2" width="9" height="37.3" fill="#c00" opacity="0.55"/>
<rect x="354.5" y="348.7" width="9" height="35.8" fill="#c00" opacity="0.55"/>
<rect x="369.5" y="350.3" width="9" height="34.2" fill="#c00" opacity="0.55"/>
<rect x="384.5" y="351.8" width="9" height="32.7" fill="#c00" opacity="0.55"/>
<rect x="399.5" y="353.4" width="9" height="31.1" fill="#c00" opacity="0.55"/>
<rect x="414.5" y="349.5" width="9" height="35.0" fill="#c00" opacity="0.55"/>
<rect x="429.5" y="345.6" width="9" height="38.9" fill="#188a3e" opacity="0.55"/>
<rect x="444.5" y="348.7" width="9" height="35.8" fill="#188a3e" opacity="0.55"/>
<rect x="459.5" y="350.3" width="9" height="34.2" fill="#c00" opacity="0.55"/>
<rect x="474.5" y="351.1" width="9" height="33.4" fill="#c00" opacity="0.55"/>
<rect x="489.5" y="349.5" width="9" height="35.0" fill="#188a3e" opacity="0.55"/>
<text x="24" y="308.5" fill="#555">volum</text>
<text x="254.0" y="58.0" fill="#b06000" text-anchor="middle" style="font-weight:bold">doji 99.20 / 100.10</text>
<text x="449.0" y="148.0" fill="#c00" text-anchor="middle" style="font-weight:bold">ieșire pe trail 96.60</text>
<text x="24" y="406.5" fill="#777">bara 15: close 99.40 peste low-ul dojiului 99.20 — NU confirmă</text>
<text x="24" y="423.5" fill="#c00">bara 16: close 99.00 sub 99.20 — confirmare (intrare short)</text>
<text x="24" y="448" fill="#555">Intrare short la close sub low-ul dojiului, SL la high 4 candele, TP 1:1, apoi trail.</text>
</svg>

After

Width:  |  Height:  |  Size: 8.8 KiB

View File

@@ -0,0 +1,118 @@
"""Buy hammer — diagrama de ansamblu (daily, ticker ilustrativ XYZ).
Citește specificația `gen/strategii/hammer_mbt10_bh_ansamblu.spec.txt`.
Seria de 34 de candele e construită din lista de close-uri dată, cu open-ul
fiecărei bare = close-ul precedentului; wick-urile sunt fixate ca nivelurile
(hammer H=C 63.30 / L 62.10, intrare 63.35, SL 62.05, TP 65.95) să iasă
exact din serie, nu doar din etichetă.
"""
import re
import sys; sys.path.insert(0, "/workspace/atm-curs-dl")
from svg_candles import Chart, Level, Mark, from_closes
# --- seria: 34 de close-uri, exact ca în specificație ------------------------
closes = [
72.00, 71.20, 71.50, 70.40, 69.60, 70.00, 68.90, 68.10, 68.40, 67.30, # 0-9
66.50, 66.80, 65.70, 64.90, 65.20, 64.10, 63.40, 63.70, 63.05, # 10-18
63.30, # 19 = hammer: o 63.05, c/h 63.30, l 62.10 (H = C, fitil sus 0)
63.35, # 20 = confirmare: close peste 63.30 => intrare
63.80, 64.20, 64.60, 64.40, 64.90, 65.20, 65.50, 65.80, 66.10, # 21-29
66.00, 65.70, 65.40, 65.60, # 30-33
]
assert len(closes) == 34
# wick-uri: (up, down) implicite (0.25, 0.30) + excepțiile din specificație
wicks = [(0.25, 0.30)] * 34
wicks[19] = (0.00, 0.95) # hammer: high 63.30 (= close), low 62.10
# bara 20: wu=0.15 => high 63.50. Spec-ul dă wd=0.15 cu „low 63.20", dar
# wd se măsoară de la min(o,c)=63.30, deci 0.15 dă 63.15; folosim tuplul
# literal din specificație (0.15, 0.15), low-ul barei 20 nu e etichetat.
wicks[20] = (0.15, 0.15)
# volum relativ ales de generator (materialul nu dă volum pe exemplul construit):
# normal pe downtrend, în creștere pe hammer (19) și pe confirmare (20), normal după
vols = [50, 48, 47, 46, 45, 46, 44, 43, 45, 42,
41, 43, 40, 39, 41, 38, 37, 39, 55,
90, 95,
58, 55, 53, 50, 52, 54, 56, 60, 64,
58, 55, 52, 50]
assert len(vols) == 34
media_vol = sum(vols) / len(vols)
assert vols[19] > media_vol and vols[20] > media_vol, "volum peste medie pe hammer/confirmare"
cds = from_closes(closes, wicks=wicks, vols=vols, start=72.00)
hammer, conf = cds[19], cds[20]
# confirmarea o punem ca notă sub grafic: un Mark deasupra barei 20 s-ar
# suprapune cu fitilul lung al hammer-ului (bara 19)
conf.note = "bara 20: confirmare — close 63.35 peste high-ul hammer-ului 63.30 (intrare)"
intrare, sl, tp = 63.35, 62.05, 65.95
# --- cele 8 assert-uri cerute de specificație -------------------------------
# 1. hammer: H = C = 63.30, L = 62.10
assert abs(cds[19].c - 63.30) < 1e-9 and abs(cds[19].h - 63.30) < 1e-9, "H != C la hammer"
assert abs(cds[19].l - 62.10) < 1e-9, f"low[19]={cds[19].l} != 62.10"
# 2. open[19] = close[18]; corp 0.25; fitil jos 0.95 >= 2*0.25
assert abs(cds[19].o - cds[18].c) < 1e-9 and abs(cds[19].o - 63.05) < 1e-9, "open[19] != close[18]"
corp = hammer.c - hammer.o
fitil_jos = hammer.o - hammer.l
assert abs(corp - 0.25) < 1e-9, f"corp={corp} != 0.25"
assert abs(fitil_jos - 0.95) < 1e-9, f"fitil jos={fitil_jos} != 0.95"
assert fitil_jos >= 2 * corp, "fitilul nu e cel puțin dublul corpului"
# 3. confirmarea închide peste high-ul hammer-ului
assert abs(conf.c - intrare) < 1e-9, f"close[20]={conf.c} != 63.35"
assert conf.c > hammer.h and abs(hammer.h - 63.30) < 1e-9, "close[20] nu e peste high[19]"
# 4. SL = low-ul hammer-ului - 0.05
assert abs(sl - (cds[19].l - 0.05)) < 1e-9, "SL != low[19] - 0.05"
# 5. risc 1.30, TP 2.60 = 2R exact
assert abs(abs(intrare - sl) - 1.30) < 1e-9, "risc != 1.30"
assert abs(abs(tp - intrare) - 2.60) < 1e-9, "TP != 2R exact"
# 6. primul high >= TP e bara 28 (65.80 + wick 0.25 = 66.05)
primul_tp = next(i for i in range(21, len(cds)) if cds[i].h >= tp)
assert primul_tp == 28, f"TP 65.95 atins întâi la bara {primul_tp}, nu 28"
# 7. downtrend vizibil
assert cds[18].c < cds[0].c - 8, "downtrend prea mic"
# 8. ordinea nivelurilor
assert sl < intrare < tp, "SL / intrare / TP nu sunt în ordine"
print(f"hammer H/C {hammer.h:.2f} L {hammer.l:.2f} intrare {conf.c:.2f} "
f"SL {sl:.2f} TP {tp:.2f} = {abs(tp-intrare)/abs(intrare-sl):.1f}R "
f"TP atins la bara {primul_tp} (high {cds[primul_tp].h:.2f})")
# --- desenul ----------------------------------------------------------------
ch = Chart(
candles=cds,
levels=[
Level(sl, "SL 62.05 — sub low-ul hammer-ului", "#c00", label_at="left"),
Level(intrare, "intrare 63.35", "#188a3e"),
Level(tp, "TP 65.95 = 2R", "#1a5fb4"),
],
marks=[
Mark(19, "hammer 63.30 / 62.10", "#b06000", "below"),
],
scale=32.0, gap=15, body_w=9, left=44, width=720, vol_h=70,
vol_label="volum", title="Buy hammer — daily (ticker ilustrativ XYZ, din lecția de pattern)",
caption="", # legenda e pusă manual, pe două rânduri, ca să nu iasă din pânză
)
svg = ch.render()
assert "<svg" in svg and "\n\n" not in svg, "linie goală în SVG"
# legenda din specificație e mai lată decât pânza: o rupem pe două rânduri și
# extindem viewBox-ul ca să nu fie tăiată
m = re.search(r'viewBox="0 0 (\d+) (\d+)"', svg)
W, H = int(m.group(1)), int(m.group(2))
legenda = [
"Exemplu ilustrativ (XYZ) din lecția de pattern (01_MODUL_1/05_patternuri_candele.md:210-215);",
"prețurile sunt cele ale lecției, structura barelor dintre ele e construită.",
]
extra = 16 * len(legenda) + 6
svg = svg.replace(f'viewBox="0 0 {W} {H}"', f'viewBox="0 0 {W} {H + extra}"')
svg = svg.replace("</svg>", "".join(
f'<text x="24" y="{H + 12 + 16 * i}" fill="#555">{t}</text>'
for i, t in enumerate(legenda)) + "</svg>")
assert "\n\n" not in svg, "linie goală în SVG după legendă"
with open("/workspace/atm-curs-dl/gen/strategii/hammer_mbt10_bh_ansamblu.svg", "w") as f:
f.write(svg)
print("scris gen/strategii/hammer_mbt10_bh_ansamblu.svg")

View File

@@ -0,0 +1,115 @@
<svg viewBox="0 0 720 559" xmlns="http://www.w3.org/2000/svg" style="width:100%;height:auto;font:12px sans-serif">
<text x="24" y="48" fill="#333" style="font-weight:bold">Buy hammer — daily (ticker ilustrativ XYZ, din lecția de pattern)</text>
<line x1="24" y1="382.4" x2="706" y2="382.4" stroke="#c00" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="30" y="376.4" fill="#c00">SL 62.05 — sub low-ul hammer-ului</text>
<line x1="24" y1="340.8" x2="706" y2="340.8" stroke="#188a3e" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="692" y="334.8" fill="#188a3e" text-anchor="end">intrare 63.35</text>
<line x1="24" y1="257.6" x2="706" y2="257.6" stroke="#1a5fb4" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="692" y="251.6" fill="#1a5fb4" text-anchor="end">TP 65.95 = 2R</text>
<line x1="44.0" y1="56.0" x2="44.0" y2="73.6" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="39.5" y="64.0" width="9" height="1.6" fill="#188a3e"/>
<line x1="59.0" y1="56.0" x2="59.0" y2="99.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="54.5" y="64.0" width="9" height="25.6" fill="#c00"/>
<line x1="74.0" y1="72.0" x2="74.0" y2="99.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="69.5" y="80.0" width="9" height="9.6" fill="#188a3e"/>
<line x1="89.0" y1="72.0" x2="89.0" y2="124.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="84.5" y="80.0" width="9" height="35.2" fill="#c00"/>
<line x1="104.0" y1="107.2" x2="104.0" y2="150.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="99.5" y="115.2" width="9" height="25.6" fill="#c00"/>
<line x1="119.0" y1="120.0" x2="119.0" y2="150.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="114.5" y="128.0" width="9" height="12.8" fill="#188a3e"/>
<line x1="134.0" y1="120.0" x2="134.0" y2="172.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="129.5" y="128.0" width="9" height="35.2" fill="#c00"/>
<line x1="149.0" y1="155.2" x2="149.0" y2="198.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="144.5" y="163.2" width="9" height="25.6" fill="#c00"/>
<line x1="164.0" y1="171.2" x2="164.0" y2="198.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="159.5" y="179.2" width="9" height="9.6" fill="#188a3e"/>
<line x1="179.0" y1="171.2" x2="179.0" y2="224.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="174.5" y="179.2" width="9" height="35.2" fill="#c00"/>
<line x1="194.0" y1="206.4" x2="194.0" y2="249.6" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="189.5" y="214.4" width="9" height="25.6" fill="#c00"/>
<line x1="209.0" y1="222.4" x2="209.0" y2="249.6" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="204.5" y="230.4" width="9" height="9.6" fill="#188a3e"/>
<line x1="224.0" y1="222.4" x2="224.0" y2="275.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="219.5" y="230.4" width="9" height="35.2" fill="#c00"/>
<line x1="239.0" y1="257.6" x2="239.0" y2="300.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="234.5" y="265.6" width="9" height="25.6" fill="#c00"/>
<line x1="254.0" y1="273.6" x2="254.0" y2="300.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="249.5" y="281.6" width="9" height="9.6" fill="#188a3e"/>
<line x1="269.0" y1="273.6" x2="269.0" y2="326.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="264.5" y="281.6" width="9" height="35.2" fill="#c00"/>
<line x1="284.0" y1="308.8" x2="284.0" y2="348.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="279.5" y="316.8" width="9" height="22.4" fill="#c00"/>
<line x1="299.0" y1="321.6" x2="299.0" y2="348.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="294.5" y="329.6" width="9" height="9.6" fill="#188a3e"/>
<line x1="314.0" y1="321.6" x2="314.0" y2="360.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="309.5" y="329.6" width="9" height="20.8" fill="#c00"/>
<line x1="329.0" y1="342.4" x2="329.0" y2="380.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="324.5" y="342.4" width="9" height="8.0" fill="#188a3e"/>
<line x1="344.0" y1="336.0" x2="344.0" y2="347.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="339.5" y="340.8" width="9" height="1.6" fill="#188a3e"/>
<line x1="359.0" y1="318.4" x2="359.0" y2="350.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="354.5" y="326.4" width="9" height="14.4" fill="#188a3e"/>
<line x1="374.0" y1="305.6" x2="374.0" y2="336.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="369.5" y="313.6" width="9" height="12.8" fill="#188a3e"/>
<line x1="389.0" y1="292.8" x2="389.0" y2="323.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="384.5" y="300.8" width="9" height="12.8" fill="#188a3e"/>
<line x1="404.0" y1="292.8" x2="404.0" y2="316.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="399.5" y="300.8" width="9" height="6.4" fill="#c00"/>
<line x1="419.0" y1="283.2" x2="419.0" y2="316.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="414.5" y="291.2" width="9" height="16.0" fill="#188a3e"/>
<line x1="434.0" y1="273.6" x2="434.0" y2="300.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="429.5" y="281.6" width="9" height="9.6" fill="#188a3e"/>
<line x1="449.0" y1="264.0" x2="449.0" y2="291.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="444.5" y="272.0" width="9" height="9.6" fill="#188a3e"/>
<line x1="464.0" y1="254.4" x2="464.0" y2="281.6" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="459.5" y="262.4" width="9" height="9.6" fill="#188a3e"/>
<line x1="479.0" y1="244.8" x2="479.0" y2="272.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="474.5" y="252.8" width="9" height="9.6" fill="#188a3e"/>
<line x1="494.0" y1="244.8" x2="494.0" y2="265.6" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="489.5" y="252.8" width="9" height="3.2" fill="#c00"/>
<line x1="509.0" y1="248.0" x2="509.0" y2="275.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="504.5" y="256.0" width="9" height="9.6" fill="#c00"/>
<line x1="524.0" y1="257.6" x2="524.0" y2="284.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="519.5" y="265.6" width="9" height="9.6" fill="#c00"/>
<line x1="539.0" y1="260.8" x2="539.0" y2="284.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="534.5" y="268.8" width="9" height="6.4" fill="#188a3e"/>
<line x1="24" y1="478.4" x2="706" y2="478.4" stroke="#888"/>
<rect x="39.5" y="441.6" width="9" height="36.8" fill="#188a3e" opacity="0.55"/>
<rect x="54.5" y="443.0" width="9" height="35.4" fill="#c00" opacity="0.55"/>
<rect x="69.5" y="443.8" width="9" height="34.6" fill="#188a3e" opacity="0.55"/>
<rect x="84.5" y="444.5" width="9" height="33.9" fill="#c00" opacity="0.55"/>
<rect x="99.5" y="445.2" width="9" height="33.2" fill="#c00" opacity="0.55"/>
<rect x="114.5" y="444.5" width="9" height="33.9" fill="#188a3e" opacity="0.55"/>
<rect x="129.5" y="446.0" width="9" height="32.4" fill="#c00" opacity="0.55"/>
<rect x="144.5" y="446.7" width="9" height="31.7" fill="#c00" opacity="0.55"/>
<rect x="159.5" y="445.2" width="9" height="33.2" fill="#188a3e" opacity="0.55"/>
<rect x="174.5" y="447.5" width="9" height="30.9" fill="#c00" opacity="0.55"/>
<rect x="189.5" y="448.2" width="9" height="30.2" fill="#c00" opacity="0.55"/>
<rect x="204.5" y="446.7" width="9" height="31.7" fill="#188a3e" opacity="0.55"/>
<rect x="219.5" y="448.9" width="9" height="29.5" fill="#c00" opacity="0.55"/>
<rect x="234.5" y="449.7" width="9" height="28.7" fill="#c00" opacity="0.55"/>
<rect x="249.5" y="448.2" width="9" height="30.2" fill="#188a3e" opacity="0.55"/>
<rect x="264.5" y="450.4" width="9" height="28.0" fill="#c00" opacity="0.55"/>
<rect x="279.5" y="451.1" width="9" height="27.3" fill="#c00" opacity="0.55"/>
<rect x="294.5" y="449.7" width="9" height="28.7" fill="#188a3e" opacity="0.55"/>
<rect x="309.5" y="437.9" width="9" height="40.5" fill="#c00" opacity="0.55"/>
<rect x="324.5" y="412.1" width="9" height="66.3" fill="#188a3e" opacity="0.55"/>
<rect x="339.5" y="408.4" width="9" height="70.0" fill="#188a3e" opacity="0.55"/>
<rect x="354.5" y="435.7" width="9" height="42.7" fill="#188a3e" opacity="0.55"/>
<rect x="369.5" y="437.9" width="9" height="40.5" fill="#188a3e" opacity="0.55"/>
<rect x="384.5" y="439.3" width="9" height="39.1" fill="#188a3e" opacity="0.55"/>
<rect x="399.5" y="441.6" width="9" height="36.8" fill="#c00" opacity="0.55"/>
<rect x="414.5" y="440.1" width="9" height="38.3" fill="#188a3e" opacity="0.55"/>
<rect x="429.5" y="438.6" width="9" height="39.8" fill="#188a3e" opacity="0.55"/>
<rect x="444.5" y="437.1" width="9" height="41.3" fill="#188a3e" opacity="0.55"/>
<rect x="459.5" y="434.2" width="9" height="44.2" fill="#188a3e" opacity="0.55"/>
<rect x="474.5" y="431.2" width="9" height="47.2" fill="#188a3e" opacity="0.55"/>
<rect x="489.5" y="435.7" width="9" height="42.7" fill="#c00" opacity="0.55"/>
<rect x="504.5" y="437.9" width="9" height="40.5" fill="#c00" opacity="0.55"/>
<rect x="519.5" y="440.1" width="9" height="38.3" fill="#c00" opacity="0.55"/>
<rect x="534.5" y="441.6" width="9" height="36.8" fill="#188a3e" opacity="0.55"/>
<text x="24" y="402.4" fill="#555">volum</text>
<text x="329.0" y="396.4" fill="#b06000" text-anchor="middle" style="font-weight:bold">hammer 63.30 / 62.10</text>
<text x="24" y="500.4" fill="#188a3e">bara 20: confirmare — close 63.35 peste high-ul hammer-ului 63.30 (intrare)</text>
<text x="24" y="533" fill="#555">Exemplu ilustrativ (XYZ) din lecția de pattern (01_MODUL_1/05_patternuri_candele.md:210-215);</text><text x="24" y="549" fill="#555">prețurile sunt cele ale lecției, structura barelor dintre ele e construită.</text></svg>

After

Width:  |  Height:  |  Size: 9.3 KiB

View File

@@ -0,0 +1,279 @@
"""Buy hammer — A+ vs. mediocru (două panouri, exemple construite).
Citește `gen/strategii/hammer_mbt10_bh_aplus.spec.txt`
(marcajul `<!-- DIAGRAMA: bh_aplus -->`, Partea A §2.2).
Același pattern ca formă (downtrend → hammer → confirmare), context opus:
- stânga (A+): downtrend de 12 candele de la 50.00 spre 42.80, hammer-ul cu
low 41.90 chiar pe suportul monthly 42.00 (0,24 % sub nivel; corp 0.10,
fitil jos 0.90), confirmarea închide 42.95 peste high-ul 42.90, pe volum
peste media ultimelor 10 bare — „acumulare";
- dreapta (NU): fără downtrend real (range lateral 44–46), hammer-ul în
mijlocul intervalului (o=45.00, c=h=45.10, l=44.10), departe de orice
nivel (suportul construit 42.50 e la 3,6 % sub low), confirmarea închide
tot 45.15 peste 45.10, dar pe volum slab — context invalid.
Nivelurile 42.00 / 42.50 și cifrele de preț sunt construite (materialul nu
dă prețuri pentru acest contrast); regulile (locația la nivel, downtrend,
volum) sunt din curs. Două `Chart`-uri randate separat, lipite într-un SVG
wrapper: coordonatele fiecărui panou rămân corecte prin construcție.
Notă de serie: specificația listează `close[11]=42.40` (A+) și `44.60` (NU),
dar descrie hammer-ul cu `o=42.80 / c=42.90`, respectiv `o=45.00 / c=45.10`,
iar assert 9 cere `close == high`. Cum open-ul fiecărei bare = close-ul
precedentei (STYLE.md), am ridicat close[11] la 42.80 / 45.00 și close[12]
la 42.90 / 45.10, ca seria să susțină exact OHLC-ul din etichetă. Restul
close-urilor sunt cele din specificație.
Rulează: python3 gen/strategii/hammer_mbt10_bh_aplus.py
Scrie: gen/strategii/hammer_mbt10_bh_aplus.svg
"""
import re
import sys
from pathlib import Path
sys.path.insert(0, "/workspace/atm-curs-dl")
from svg_candles import Chart, Level, Mark, from_closes # noqa: E402
OUT = Path(__file__).resolve().parent / "hammer_mbt10_bh_aplus.svg"
GRAY, RED, GREEN, BLUE, ORANGE = "#777", "#c00", "#188a3e", "#1a5fb4", "#b06000"
HAM_A, CONF_A = 12, 13 # indicii în panoul A+
HAM_N, CONF_N = 12, 13 # indicii în panoul NU
SUPORT_A = 42.00 # suport monthly (A+)
SUPORT_N = 42.50 # cel mai apropiat nivel construit (NU)
# ============================================================ PANOUL A+ (20 bare)
# 12 bare de downtrend 50.00 -> 42.80, hammer la 42.00, confirmare, apoi urcare.
CLOSES_A = [
50.00, 49.10, 49.30, 48.20, 47.30, 47.60, 46.40, 45.50, 44.60, 43.70,
43.00, 42.80,
42.90, # 12 = hammer: o=42.80, c=h=42.90, l=41.90 (H=C, fitil jos 0.90)
42.95, # 13 = confirmare, close > 42.90 => intrare
43.40, 43.80, 44.20, 44.00, 44.50, 44.80,
]
assert len(CLOSES_A) == 20, len(CLOSES_A)
WICKS_A = [(0.25, 0.30)] * 20
WICKS_A[HAM_A] = (0.00, 0.90) # high 42.90, low 41.90 (pe suportul 42.00)
WICKS_A[CONF_A] = (0.20, 0.20) # confirmarea: o=42.90, c=42.95
# Volum ales de generator (materialul nu dă volum): scade pe downtrend, climax
# pe hammer, spike pe confirmare („acumulare"), moderat după.
VOLS_A = [54, 52, 51, 49, 48, 47, 46, 45, 44, 43,
42, 45,
64, # 12 = hammer: exces de vânzare
90, # 13 = confirmare: volum peste medie
74, 70, 66, 62, 60, 58]
assert len(VOLS_A) == 20
cds_a = from_closes(CLOSES_A, wicks=WICKS_A, vols=VOLS_A, start=50.60)
ham_a, conf_a = cds_a[HAM_A], cds_a[CONF_A]
# ============================================================ PANOUL NU (20 bare)
# range lateral 44-46, fără downtrend; hammer în mijloc, confirmare pe volum slab.
CLOSES_N = [
46.00, 45.30, 45.80, 45.10, 45.60, 44.90, 45.40, 44.80, 45.20, 44.70,
45.10, 45.00,
45.10, # 12 = hammer: o=45.00, c=h=45.10, l=44.10 (H=C, fitil jos 0.90)
45.15, # 13 = confirmare, close > 45.10
44.90, 44.70, 44.40, 44.20, 44.00, 43.80,
]
assert len(CLOSES_N) == 20, len(CLOSES_N)
WICKS_N = [(0.25, 0.30)] * 20
WICKS_N[HAM_N] = (0.00, 0.90) # high 45.10, low 44.10
WICKS_N[CONF_N] = (0.20, 0.20)
# Volum ales de generator: lateral, constant, confirmarea pe volum slab.
VOLS_N = [52, 50, 53, 49, 51, 48, 52, 50, 49, 51,
50, 49,
56, # 12
31, # 13 = confirmare: volum slab
52, 50, 48, 51, 49, 47]
assert len(VOLS_N) == 20
cds_n = from_closes(CLOSES_N, wicks=WICKS_N, vols=VOLS_N, start=45.60)
ham_n, conf_n = cds_n[HAM_N], cds_n[CONF_N]
# ============================================================ assert-urile din spec
# 1. A+: low[12] la <= 0,5 % de 42.00 (41.90 e la 0,24 %)
dist_a = abs(ham_a.l - SUPORT_A) / SUPORT_A
assert dist_a <= 0.005, f"low hammer A+ {ham_a.l} la {dist_a * 100:.2f} % de 42.00"
# low-ul hammer-ului chiar trece sub nivel (fitilul sparge suportul)
assert ham_a.l < SUPORT_A <= ham_a.h, "hammer-ul A+ nu are fitilul pe/la nivel"
# 2. A+: close[13] > high[12] (tehnic valid)
assert conf_a.c > ham_a.h, f"A+ close {conf_a.c} <= high hammer {ham_a.h}"
assert abs(conf_a.c - 42.95) < 1e-9
# 3. A+: volumul pe bara 13 > media volumelor pe 10 bare
media_a = sum(VOLS_A[CONF_A - 10:CONF_A]) / 10
assert VOLS_A[CONF_A] > media_a, f"vol confirmare A+ {VOLS_A[CONF_A]} <= media {media_a}"
# 4. A+: close[12] < close[0] - 5 (downtrend vizibil)
assert cds_a[HAM_A].c < cds_a[0].c - 5, "downtrendul A+ nu e vizibil"
# 5. NU: (low[12] - 42.50) / 42.50 > 0.02 (hammer-ul NU e departe de nivel)
dist_n = (ham_n.l - SUPORT_N) / SUPORT_N
assert dist_n > 0.02, f"low hammer NU {ham_n.l} prea aproape de 42.50 ({dist_n:.1%})"
# eticheta „la -3,6 %" din spec: (44.10 - 42.50) / 44.10
pct_n = (ham_n.l - SUPORT_N) / ham_n.l * 100
assert round(pct_n, 1) == 3.6, f"procentul NU e {pct_n:.2f} %, nu 3,6 %"
# 6. NU: close[13] > high[12] (tehnic valid)
assert conf_n.c > ham_n.h, f"NU close {conf_n.c} <= high hammer {ham_n.h}"
assert abs(conf_n.c - 45.15) < 1e-9
# 7. NU: volumul pe bara 13 < media volumelor pe 10 bare
media_n = sum(VOLS_N[CONF_N - 10:CONF_N]) / 10
assert VOLS_N[CONF_N] < media_n, f"vol confirmare NU {VOLS_N[CONF_N]} >= media {media_n}"
# 8. NU: |close[12] - close[0]| <= 1.5 (fără downtrend real, range lateral)
assert abs(cds_n[HAM_N].c - cds_n[0].c) <= 1.5, "NU nu e range lateral"
# 9. ambele hammer: fitil jos >= 2x corp si close == high (fitil de sus inexistent)
for tag, o, l, c, h in (
("A+", ham_a.o, ham_a.l, ham_a.c, ham_a.h),
("NU", ham_n.o, ham_n.l, ham_n.c, ham_n.h)):
assert (o - l) >= 2 * (c - o), f"fitilul hammer-ului {tag} < 2x corpul"
assert abs(c - h) < 1e-9, f"hammer-ul {tag} are fitil de sus"
# OHLC-ul descris în spec (eticheta e adevărul)
assert abs(ham_a.o - 42.80) < 1e-9 and abs(ham_a.c - 42.90) < 1e-9 and abs(ham_a.l - 41.90) < 1e-9
assert abs(ham_n.o - 45.00) < 1e-9 and abs(ham_n.c - 45.10) < 1e-9 and abs(ham_n.l - 44.10) < 1e-9
# context: hammer-ul A+ e la baza downtrendului, NU nu are downtrend
assert min(c.l for c in cds_a[:HAM_A]) > ham_a.l, "low-ul hammer-ului A+ nu e minimul"
assert abs(cds_a[-1].c - 44.80) < 1e-9 and cds_a[-1].c > conf_a.c, "A+ nu urcă după intrare"
print(f"A+ hammer {ham_a.o:.2f}/{ham_a.c:.2f}/{ham_a.h:.2f}/{ham_a.l:.2f} "
f"conf {conf_a.c:.2f} > high {ham_a.h:.2f} vol {VOLS_A[CONF_A]} > medie {media_a:.1f}")
print(f"NU hammer {ham_n.o:.2f}/{ham_n.c:.2f}/{ham_n.h:.2f}/{ham_n.l:.2f} "
f"conf {conf_n.c:.2f} > high {ham_n.h:.2f} vol {VOLS_N[CONF_N]} < medie {media_n:.1f}")
print(f"NU low {ham_n.l:.2f} vs suport {SUPORT_N:.2f} = -{pct_n:.1f} %")
# ============================================================ desenele celor două panouri
PW = 430 # lățimea fiecărui panou
SCALE = 26.0 # aceeași scară în ambele panouri (comparație cinstită)
ch_a = Chart(
candles=cds_a,
levels=[Level(SUPORT_A, "suport monthly 42.00", GREEN, label_at="left")],
marks=[Mark(HAM_A, "hammer la nivel (low 41.90)", ORANGE, "below", dy=0),
Mark(CONF_A, "acumulare", GREEN, "above", dy=-26)],
mas=[], scale=SCALE, gap=15, body_w=9, left=44, width=PW, vol_h=60,
vol_label="volum",
title="A+ — hammer la suport monthly, volum în acumulare",
caption="bara 13: 42.95 > high 42.90, pe volum peste medie.",
)
ch_n = Chart(
candles=cds_n,
levels=[Level(SUPORT_N, "fără nivel apropiat (suport 42.50 la −3,6 %)", GRAY,
label_at="left")],
marks=[Mark(HAM_N, "hammer în mijloc de range", ORANGE, "below", dy=0),
Mark(CONF_N, "volum slab", RED, "above", dy=0)],
mas=[], scale=SCALE, gap=15, body_w=9, left=44, width=PW, vol_h=60,
vol_label="volum",
title="Mediocru — hammer în mijloc de range, fără nivel",
caption="bara 13: 45.15 > 45.10, dar pe volum slab.",
)
# ============================================================ wrapper: două panouri alăturate
def inner(svg: str) -> str:
"""Conținutul dintre tag-urile <svg> ale unui panou, fără elementul rădăcină."""
return svg[svg.index(">") + 1:svg.rindex("</svg>")]
def vb_height(svg: str) -> float:
m = re.search(r'viewBox="0 0 [\d.]+ ([\d.]+)"', svg)
assert m, "nu găsesc viewBox în panoul randat"
return float(m.group(1))
PANEL_GAP, PAD = 24, 8
TITLE_H, FOOTER_H = 30, 38
svg_a, svg_n = ch_a.render(), ch_n.render()
HA, HN = vb_height(svg_a), vb_height(svg_n)
assert svg_a.count("\n\n") == 0 and svg_n.count("\n\n") == 0, "linie goală în panou"
TOTAL_W = PAD + PW + PANEL_GAP + PW + PAD
TOTAL_H = PAD + TITLE_H + max(HA, HN) + FOOTER_H + PAD
OFF_A = (PAD, PAD + TITLE_H)
OFF_N = (PAD + PW + PANEL_GAP, PAD + TITLE_H)
TOP_TITLE = "Buy hammer A+ vs. mediocru (două panouri, exemple construite)"
FOOTER1 = ("Reguli din curs: 26_2025-06-11.md:701-703 (locația la nivel), "
"05_patternuri_candele.md:77-80, 05_volum.md:2169.")
FOOTER2 = ("Nivelurile 42.00 / 42.50 și cifrele de preț sunt construite "
"(materialul nu dă prețuri pentru acest contrast).")
FOOTER = FOOTER1 + " " + FOOTER2
wrapper = (
f'<svg viewBox="0 0 {TOTAL_W} {TOTAL_H:.0f}" width="{TOTAL_W}" height="{TOTAL_H:.0f}" '
f'xmlns="http://www.w3.org/2000/svg" style="width:100%;height:auto;font:12px sans-serif">'
f'<text x="{TOTAL_W / 2:.0f}" y="20" fill="#333" text-anchor="middle" '
f'style="font-weight:bold">{TOP_TITLE}</text>'
f'<g transform="translate({OFF_A[0]},{OFF_A[1]})">{inner(svg_a)}</g>'
f'<g transform="translate({OFF_N[0]},{OFF_N[1]})">{inner(svg_n)}</g>'
f'<text x="{TOTAL_W / 2:.0f}" y="{TOTAL_H - PAD - 14:.0f}" fill="#555" '
f'text-anchor="middle" style="font-size:10px">{FOOTER1}</text>'
f'<text x="{TOTAL_W / 2:.0f}" y="{TOTAL_H - PAD:.0f}" fill="#555" '
f'text-anchor="middle" style="font-size:10px">{FOOTER2}</text>'
f'</svg>'
)
# ============================================================ verificare pe SVG-ul final
def texts(svg: str):
"""Toate (x, y, text-anchor, text) din elementele <text> ale unui fragment."""
out = []
for m in re.finditer(r'<text x="([-\d.]+)" y="([-\d.]+)"([^>]*)>(.*?)</text>', svg):
x, y, attrs, t = float(m.group(1)), float(m.group(2)), m.group(3), m.group(4)
if t.startswith("<"):
continue
am = re.search(r'text-anchor="(\w+)"', attrs)
out.append((x, y, am.group(1) if am else "start", t))
return out
def span(x: float, anchor: str, t: str) -> tuple[float, float]:
"""Intervalul orizontal aproximativ al unui text, față de ancora lui."""
w = len(t) * 12 * 0.56
left = x - w if anchor == "end" else (x - w / 2 if anchor == "middle" else x)
return left, left + w
assert wrapper.count("\n\n") == 0, "linie goală în SVG"
# fiecare text (după translatarea panoului) e în întregime în interiorul pânzei
all_txt = []
for frag, (ox, oy) in ((inner(svg_a), OFF_A), (inner(svg_n), OFF_N)):
for x, y, anchor, t in texts(frag):
gx, gy = x + ox, y + oy
gl, gr = gx + span(0, anchor, t)[0], gx + span(0, anchor, t)[1]
assert ox <= gl and gr <= ox + PW, f"text iese din panou: {t!r} ({gl:.0f}..{gr:.0f})"
assert -1 <= gl and gr <= TOTAL_W + 1, f"text ieșit pe orizontală: {t!r} ({gl:.0f}..{gr:.0f})"
assert 0 <= gy <= TOTAL_H, f"text ieșit pe verticală: {t!r} ({gy:.0f})"
all_txt.append((gx, gy, anchor, t))
# textele din wrapper (titlu + footer), în coordonate globale
for x, y, anchor, t in ((TOTAL_W / 2, 20.0, "middle", TOP_TITLE),
(TOTAL_W / 2, TOTAL_H - PAD - 14, "middle", FOOTER1),
(TOTAL_W / 2, TOTAL_H - PAD, "middle", FOOTER2)):
gl, gr = span(x, anchor, t)
assert -1 <= gl and gr <= TOTAL_W + 1, f"text din wrapper iese din pânză: {t!r}"
assert 0 <= y <= TOTAL_H, f"text din wrapper în afara înălțimii: {t!r}"
all_txt.append((x, y, anchor, t))
assert len(all_txt) >= 10, "prea puține etichete"
# nu se suprapun două texte pe aceeași poziție sau pe rândul apropiat
for i in range(len(all_txt)):
xi, yi, ai, si = all_txt[i]
li, ri = (xi + span(0, ai, si)[0], xi + span(0, ai, si)[1])
for j in range(i + 1, len(all_txt)):
xj, yj, aj, sj = all_txt[j]
lj, rj = xj + span(0, aj, sj)[0], xj + span(0, aj, sj)[1]
if abs(yi - yj) < 12 and li < rj - 4 and lj < ri - 4:
raise AssertionError(f"texte suprapuse: {si!r} / {sj!r}")
# etichetele cerute de spec există în desen
for cheie in ("suport monthly 42.00", "hammer la nivel", "acumulare",
"fără nivel apropiat (suport 42.50 la −3,6 %)", "volum slab",
"A+ — hammer la suport monthly, volum în acumulare",
"Mediocru — hammer în mijloc de range, fără nivel"):
assert cheie in wrapper, f"lipsește din desen: {cheie!r}"
OUT.write_text(wrapper, encoding="utf-8")
print(f"scris {OUT.name}: {TOTAL_W}x{TOTAL_H:.0f}, "
f"{len(all_txt)} etichete, verificare OK")

View File

@@ -0,0 +1,139 @@
<svg viewBox="0 0 900 501" width="900" height="501" xmlns="http://www.w3.org/2000/svg" style="width:100%;height:auto;font:12px sans-serif"><text x="450" y="20" fill="#333" text-anchor="middle" style="font-weight:bold">Buy hammer A+ vs. mediocru (două panouri, exemple construite)</text><g transform="translate(8,38)">
<text x="24" y="48" fill="#333" style="font-weight:bold">A+ — hammer la suport monthly, volum în acumulare</text>
<line x1="24" y1="286.1" x2="416" y2="286.1" stroke="#188a3e" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="30" y="280.1" fill="#188a3e">suport monthly 42.00</text>
<line x1="44.0" y1="56.0" x2="44.0" y2="85.9" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="39.5" y="62.5" width="9" height="15.6" fill="#c00"/>
<line x1="59.0" y1="71.6" x2="59.0" y2="109.3" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="54.5" y="78.1" width="9" height="23.4" fill="#c00"/>
<line x1="74.0" y1="89.8" x2="74.0" y2="109.3" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="69.5" y="96.3" width="9" height="5.2" fill="#188a3e"/>
<line x1="89.0" y1="89.8" x2="89.0" y2="132.7" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="84.5" y="96.3" width="9" height="28.6" fill="#c00"/>
<line x1="104.0" y1="118.4" x2="104.0" y2="156.1" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="99.5" y="124.9" width="9" height="23.4" fill="#c00"/>
<line x1="119.0" y1="134.0" x2="119.0" y2="156.1" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="114.5" y="140.5" width="9" height="7.8" fill="#188a3e"/>
<line x1="134.0" y1="134.0" x2="134.0" y2="179.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="129.5" y="140.5" width="9" height="31.2" fill="#c00"/>
<line x1="149.0" y1="165.2" x2="149.0" y2="202.9" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="144.5" y="171.7" width="9" height="23.4" fill="#c00"/>
<line x1="164.0" y1="188.6" x2="164.0" y2="226.3" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="159.5" y="195.1" width="9" height="23.4" fill="#c00"/>
<line x1="179.0" y1="212.0" x2="179.0" y2="249.7" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="174.5" y="218.5" width="9" height="23.4" fill="#c00"/>
<line x1="194.0" y1="235.4" x2="194.0" y2="267.9" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="189.5" y="241.9" width="9" height="18.2" fill="#c00"/>
<line x1="209.0" y1="253.6" x2="209.0" y2="273.1" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="204.5" y="260.1" width="9" height="5.2" fill="#c00"/>
<line x1="224.0" y1="262.7" x2="224.0" y2="288.7" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="219.5" y="262.7" width="9" height="2.6" fill="#188a3e"/>
<line x1="239.0" y1="256.2" x2="239.0" y2="267.9" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="234.5" y="261.4" width="9" height="1.6" fill="#188a3e"/>
<line x1="254.0" y1="243.2" x2="254.0" y2="269.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="249.5" y="249.7" width="9" height="11.7" fill="#188a3e"/>
<line x1="269.0" y1="232.8" x2="269.0" y2="257.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="264.5" y="239.3" width="9" height="10.4" fill="#188a3e"/>
<line x1="284.0" y1="222.4" x2="284.0" y2="247.1" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="279.5" y="228.9" width="9" height="10.4" fill="#188a3e"/>
<line x1="299.0" y1="222.4" x2="299.0" y2="241.9" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="294.5" y="228.9" width="9" height="5.2" fill="#c00"/>
<line x1="314.0" y1="214.6" x2="314.0" y2="241.9" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="309.5" y="221.1" width="9" height="13.0" fill="#188a3e"/>
<line x1="329.0" y1="206.8" x2="329.0" y2="228.9" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="324.5" y="213.3" width="9" height="7.8" fill="#188a3e"/>
<line x1="24" y1="374.7" x2="416" y2="374.7" stroke="#888"/>
<rect x="39.5" y="338.7" width="9" height="36.0" fill="#c00" opacity="0.55"/>
<rect x="54.5" y="340.0" width="9" height="34.7" fill="#c00" opacity="0.55"/>
<rect x="69.5" y="340.7" width="9" height="34.0" fill="#188a3e" opacity="0.55"/>
<rect x="84.5" y="342.0" width="9" height="32.7" fill="#c00" opacity="0.55"/>
<rect x="99.5" y="342.7" width="9" height="32.0" fill="#c00" opacity="0.55"/>
<rect x="114.5" y="343.4" width="9" height="31.3" fill="#188a3e" opacity="0.55"/>
<rect x="129.5" y="344.0" width="9" height="30.7" fill="#c00" opacity="0.55"/>
<rect x="144.5" y="344.7" width="9" height="30.0" fill="#c00" opacity="0.55"/>
<rect x="159.5" y="345.4" width="9" height="29.3" fill="#c00" opacity="0.55"/>
<rect x="174.5" y="346.0" width="9" height="28.7" fill="#c00" opacity="0.55"/>
<rect x="189.5" y="346.7" width="9" height="28.0" fill="#c00" opacity="0.55"/>
<rect x="204.5" y="344.7" width="9" height="30.0" fill="#c00" opacity="0.55"/>
<rect x="219.5" y="332.0" width="9" height="42.7" fill="#188a3e" opacity="0.55"/>
<rect x="234.5" y="314.7" width="9" height="60.0" fill="#188a3e" opacity="0.55"/>
<rect x="249.5" y="325.4" width="9" height="49.3" fill="#188a3e" opacity="0.55"/>
<rect x="264.5" y="328.0" width="9" height="46.7" fill="#188a3e" opacity="0.55"/>
<rect x="279.5" y="330.7" width="9" height="44.0" fill="#188a3e" opacity="0.55"/>
<rect x="294.5" y="333.4" width="9" height="41.3" fill="#c00" opacity="0.55"/>
<rect x="309.5" y="334.7" width="9" height="40.0" fill="#188a3e" opacity="0.55"/>
<rect x="324.5" y="336.0" width="9" height="38.7" fill="#188a3e" opacity="0.55"/>
<text x="24" y="308.7" fill="#555">volum</text>
<text x="224.0" y="302.7" fill="#b06000" text-anchor="middle" style="font-weight:bold">hammer la nivel (low 41.90)</text>
<text x="239.0" y="220.2" fill="#188a3e" text-anchor="middle" style="font-weight:bold">acumulare</text>
<text x="24" y="405" fill="#555">bara 13: 42.95 > high 42.90, pe volum peste medie.</text>
</g><g transform="translate(462,38)">
<text x="24" y="48" fill="#333" style="font-weight:bold">Mediocru — hammer în mijloc de range, fără nivel</text>
<line x1="24" y1="153.5" x2="416" y2="153.5" stroke="#777" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="30" y="147.5" fill="#777">fără nivel apropiat (suport 42.50 la −3,6 %)</text>
<line x1="44.0" y1="56.0" x2="44.0" y2="80.7" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="39.5" y="62.5" width="9" height="10.4" fill="#188a3e"/>
<line x1="59.0" y1="56.0" x2="59.0" y2="88.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="54.5" y="62.5" width="9" height="18.2" fill="#c00"/>
<line x1="74.0" y1="61.2" x2="74.0" y2="88.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="69.5" y="67.7" width="9" height="13.0" fill="#188a3e"/>
<line x1="89.0" y1="61.2" x2="89.0" y2="93.7" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="84.5" y="67.7" width="9" height="18.2" fill="#c00"/>
<line x1="104.0" y1="66.4" x2="104.0" y2="93.7" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="99.5" y="72.9" width="9" height="13.0" fill="#188a3e"/>
<line x1="119.0" y1="66.4" x2="119.0" y2="98.9" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="114.5" y="72.9" width="9" height="18.2" fill="#c00"/>
<line x1="134.0" y1="71.6" x2="134.0" y2="98.9" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="129.5" y="78.1" width="9" height="13.0" fill="#188a3e"/>
<line x1="149.0" y1="71.6" x2="149.0" y2="101.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="144.5" y="78.1" width="9" height="15.6" fill="#c00"/>
<line x1="164.0" y1="76.8" x2="164.0" y2="101.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="159.5" y="83.3" width="9" height="10.4" fill="#188a3e"/>
<line x1="179.0" y1="76.8" x2="179.0" y2="104.1" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="174.5" y="83.3" width="9" height="13.0" fill="#c00"/>
<line x1="194.0" y1="79.4" x2="194.0" y2="104.1" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="189.5" y="85.9" width="9" height="10.4" fill="#188a3e"/>
<line x1="209.0" y1="79.4" x2="209.0" y2="96.3" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="204.5" y="85.9" width="9" height="2.6" fill="#c00"/>
<line x1="224.0" y1="85.9" x2="224.0" y2="111.9" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="219.5" y="85.9" width="9" height="2.6" fill="#188a3e"/>
<line x1="239.0" y1="79.4" x2="239.0" y2="91.1" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="234.5" y="84.6" width="9" height="1.6" fill="#188a3e"/>
<line x1="254.0" y1="78.1" x2="254.0" y2="98.9" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="249.5" y="84.6" width="9" height="6.5" fill="#c00"/>
<line x1="269.0" y1="84.6" x2="269.0" y2="104.1" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="264.5" y="91.1" width="9" height="5.2" fill="#c00"/>
<line x1="284.0" y1="89.8" x2="284.0" y2="111.9" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="279.5" y="96.3" width="9" height="7.8" fill="#c00"/>
<line x1="299.0" y1="97.6" x2="299.0" y2="117.1" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="294.5" y="104.1" width="9" height="5.2" fill="#c00"/>
<line x1="314.0" y1="102.8" x2="314.0" y2="122.3" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="309.5" y="109.3" width="9" height="5.2" fill="#c00"/>
<line x1="329.0" y1="108.0" x2="329.0" y2="127.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="324.5" y="114.5" width="9" height="5.2" fill="#c00"/>
<line x1="24" y1="239.5" x2="416" y2="239.5" stroke="#888"/>
<rect x="39.5" y="183.8" width="9" height="55.7" fill="#188a3e" opacity="0.55"/>
<rect x="54.5" y="185.9" width="9" height="53.6" fill="#c00" opacity="0.55"/>
<rect x="69.5" y="182.7" width="9" height="56.8" fill="#188a3e" opacity="0.55"/>
<rect x="84.5" y="187.0" width="9" height="52.5" fill="#c00" opacity="0.55"/>
<rect x="99.5" y="184.9" width="9" height="54.6" fill="#188a3e" opacity="0.55"/>
<rect x="114.5" y="188.1" width="9" height="51.4" fill="#c00" opacity="0.55"/>
<rect x="129.5" y="183.8" width="9" height="55.7" fill="#188a3e" opacity="0.55"/>
<rect x="144.5" y="185.9" width="9" height="53.6" fill="#c00" opacity="0.55"/>
<rect x="159.5" y="187.0" width="9" height="52.5" fill="#188a3e" opacity="0.55"/>
<rect x="174.5" y="184.9" width="9" height="54.6" fill="#c00" opacity="0.55"/>
<rect x="189.5" y="185.9" width="9" height="53.6" fill="#188a3e" opacity="0.55"/>
<rect x="204.5" y="187.0" width="9" height="52.5" fill="#c00" opacity="0.55"/>
<rect x="219.5" y="179.5" width="9" height="60.0" fill="#188a3e" opacity="0.55"/>
<rect x="234.5" y="206.3" width="9" height="33.2" fill="#188a3e" opacity="0.55"/>
<rect x="249.5" y="183.8" width="9" height="55.7" fill="#c00" opacity="0.55"/>
<rect x="264.5" y="185.9" width="9" height="53.6" fill="#c00" opacity="0.55"/>
<rect x="279.5" y="188.1" width="9" height="51.4" fill="#c00" opacity="0.55"/>
<rect x="294.5" y="184.9" width="9" height="54.6" fill="#c00" opacity="0.55"/>
<rect x="309.5" y="187.0" width="9" height="52.5" fill="#c00" opacity="0.55"/>
<rect x="324.5" y="189.1" width="9" height="50.4" fill="#c00" opacity="0.55"/>
<text x="24" y="173.5" fill="#555">volum</text>
<text x="224.0" y="131.9" fill="#b06000" text-anchor="middle" style="font-weight:bold">hammer în mijloc de range</text>
<text x="239.0" y="69.4" fill="#c00" text-anchor="middle" style="font-weight:bold">volum slab</text>
<text x="24" y="270" fill="#555">bara 13: 45.15 > 45.10, dar pe volum slab.</text>
</g><text x="450" y="479" fill="#555" text-anchor="middle" style="font-size:10px">Reguli din curs: 26_2025-06-11.md:701-703 (locația la nivel), 05_patternuri_candele.md:77-80, 05_volum.md:2169.</text><text x="450" y="493" fill="#555" text-anchor="middle" style="font-size:10px">Nivelurile 42.00 / 42.50 și cifrele de preț sunt construite (materialul nu dă prețuri pentru acest contrast).</text></svg>

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -0,0 +1,177 @@
#!/usr/bin/env python3
"""LABU — buy hammer la suportul monthly, +20 % în jumătate de lună (Nivel 2.5).
Citește `gen/strategii/hammer_mbt10_bh_labu.spec.txt` și scrie
`gen/strategii/hammer_mbt10_bh_labu.svg`: 38 de candele daily, downtrend +
consolidare până pe suportul monthly 80.00, hammer chiar pe nivel
(o 80.80 / h 80.90 / l 79.60 / c 80.90), confirmarea de la 81.30 (close peste
high-ul hammer-ului) și urcarea în ~11 bare până la 97.56 = +20,0 % față de
intrare.
REAL din curs (`18_2025-09-23.md:721-726`): că e buy hammer la suportul
monthly și că a dat +20 % în jumătate de lună. CONSTRUITE de sumarizator:
suportul 80.00, cifrele hammer-ului (80.80/80.90/79.60), intrarea 81.30, SL
79.60 și seria de prețuri — materialul nu dă entry, SL sau TP pentru cazul
ăsta. Legenda din desen spune exact asta.
Rulare: python3 gen/strategii/hammer_mbt10_bh_labu.py
"""
import re
import sys
from pathlib import Path
sys.path.insert(0, "/workspace/atm-curs-dl")
from svg_candles import Chart, Level, MA, Mark, from_closes # noqa: E402
OUT = Path(__file__).with_suffix(".svg")
# --- nivelurile din specificație (toate CONSTRUITE) ------------------------
SUPPORT = 80.00 # suport monthly (construit)
HAMMER_H = 80.90 # high-ul hammer-ului = close (H=C)
HAMMER_L = 79.60 # low-ul hammer-ului = SL propus
ENTRY = 81.30 # intrare = close-ul confirmării, peste 80.90 (construit)
SL = 79.60 # SL sub low-ul hammer-ului (construit)
PEAK = 97.56 # max close de după intrare: 97.56 / 81.30 = +20,0 % (real)
# --- seria de close-uri din specificație (38 bare, o = close precedent) ----
closes = [
# downtrend + consolidare 0-22
92.00, 90.50, 91.20, 89.40, 88.10, 88.60, 87.00, 85.80, 86.30, 84.90,
83.60, 82.40, 83.00, 81.80, 80.70, 81.20, 80.20, 80.80, 81.50, 80.40,
80.10, 80.60, 80.80,
80.90, # 23 = hammer: o 80.80, c 80.90, h 80.90, l 79.60
81.30, # 24 = confirmare, close > high[23] = intrare
# 25..37 = 13 bare de urcare; max close 97.56 / 81.30 - 1 = +20,0 %
81.90, 82.60, 83.40, 84.20, 85.10, 86.00, 87.00, 88.10, 89.30, 90.60,
92.00, 93.50, 97.56,
]
assert len(closes) == 38, f"seria are {len(closes)} bare, spec cere 38"
# wick-uri: (up, down). Restul (0.30, 0.40); hammer-ul are fitil jos 1.20 și
# zero fitil sus ca low-ul/high-urile cerute de asserts să iasă exact.
wicks = [(0.30, 0.40)] * len(closes)
wicks[23] = (0.00, 1.20) # hammer: h 80.90, l 79.60
# volume construite (nu sunt cifre de curs, doar panoul de volum cerut de
# stil): vânzare în creștere spre suport, explozie la hammer + confirmare.
vols = [
52, 50, 47, 49, 45, 46, 43, 45, 41, 43,
44, 47, 42, 45, 48, 46, 50, 44, 43, 46,
51, 49, 55, 88, 84,
70, 62, 58, 54, 52, 50, 48, 46, 45, 47,
55, 62, 72,
]
assert len(vols) == len(closes), f"volume {len(vols)} != bare {len(closes)}"
cds = from_closes(closes, wicks=wicks, vols=vols, start=94.00)
assert abs(cds[0].o - 94.00) < 1e-9 and cds[0].c < cds[0].o, "prima bară nu e roșie"
for a, b in zip(cds, cds[1:]):
assert abs(a.c - b.o) < 1e-9, "open != close-ul barei anterioare"
# --- assert-urile din specificație (toate, ca assert reale) ----------------
# 1. hammer-ul stă pe suportul monthly 80.00
assert cds[23].l == 79.60 <= SUPPORT <= cds[23].h == HAMMER_H, \
f"hammer 79.60 <= 80.00 <= 80.90 nu iese: {cds[23].l}/{cds[23].h}"
# 2. forma hammer-ului: o = close[22] = 80.80, corp 0.10, fitil jos 1.20,
# 1.20 >= 2 * 0.10, close == high (fără fitil sus)
assert cds[23].o == cds[22].c == 80.80, "open[23] nu e close[22] = 80.80"
body = abs(cds[23].c - cds[23].o)
lower_wick = min(cds[23].o, cds[23].c) - cds[23].l
upper_wick = cds[23].h - max(cds[23].o, cds[23].c)
assert abs(body - 0.10) < 1e-9, f"corpul hammer-ului e {body}, nu 0.10"
assert abs(lower_wick - 1.20) < 1e-9, f"fitilul jos e {lower_wick}, nu 1.20"
assert abs(upper_wick) < 1e-9, f"hammer-ul are fitil sus {upper_wick}"
assert lower_wick >= 2 * body, f"1.20 < 2 * {body} (nu e hammer valid)"
assert cds[23].c == cds[23].h == HAMMER_H, "close[23] != high[23] == 80.90"
# 3. confirmarea: close[24] == 81.30 == intrare și > high[23] == 80.90
assert abs(cds[24].c - ENTRY) < 1e-9, f"close[24]={cds[24].c} != intrare 81.30"
assert cds[24].c > cds[23].h == HAMMER_H, "confirmarea nu închide peste 80.90"
# 4. SL = min(low[22:25]) = low-ul hammer-ului = 79.60
min_22_25 = min(c.l for c in cds[22:25])
assert abs(min_22_25 - SL) < 1e-9, f"min low[22:25]={min_22_25} != SL {SL}"
assert abs(SL - cds[23].l) < 1e-9, "SL nu e low-ul hammer-ului"
# 5. procentul REAL al cursului: +20,0 % de la intrare la vârf
peak = max(c.c for c in cds[25:38])
pct = round((peak / ENTRY - 1) * 100, 1)
assert abs(peak - PEAK) < 1e-9, f"max close[25:38]={peak} != 97.56"
assert pct == 20.0, f"procentul iese {pct}, nu +20,0 %"
assert abs(peak / ENTRY - 1.20) < 1e-9, "97.56 / 81.30 nu e exact 1.20"
# 6. downtrend/consolidare înainte de hammer
assert cds[23].c < cds[0].c - 8, f"close[23]={cds[23].c} nu e cu 8 sub close[0]"
# 7. ordinea nivelurilor
assert SL < ENTRY < peak, f"SL/entry/vârf: {SL}/{ENTRY}/{peak}"
# --- verificarea serii (stil + geometrie) ----------------------------------
entry = cds[24].c
w = 760
ch = Chart(
candles=cds,
levels=[
Level(SUPPORT, "suport monthly 80.00", "#777",
dash="4 3", label_at="left"),
Level(SL, "SL sub low-ul hammer-ului 79.60 (construit)", "#c00",
label_at="right"),
Level(ENTRY, "intrare 81.30 (construit)", "#188a3e", label_at="right"),
],
marks=[
Mark(23, "hammer 80.90 / 79.60 (construit)", "#b06000", "below"),
Mark(37, f"+20 % (cifra cursului): {peak:.2f} / {ENTRY:.2f}",
"#188a3e", "above"),
],
mas=[MA(20, "#b06000", dash="4 3", text="SMA-20")],
scale=8.0, gap=15, body_w=9, left=44, width=w, vol_h=70,
vol_label="volum", title="LABU — daily",
caption="nivelurile de preț sunt construite de sumarizator.",
)
# note pe bare: explicațiile hammer-ului/confirmării și legenda obligatorie
# (textul lung de nivel ar intra peste candele, deci stă în blocul de jos).
cds[0].note = ("bara 23 = hammer: o 80.80, h 80.90, l 79.60, c 80.90 — "
"corp 0.10, fitil jos 1.20 (H=C), pe suportul 80.00")
cds[1].note = ("bara 24 = confirmare: close 81.30 — close peste high-ul "
"hammer-ului 80.90 = intrare 81.30 (construit)")
cds[2].note = ("Procentul (+20 % în jumătate de lună) și contextul (suport "
"monthly) sunt ale cursului (18_2025-09-23.md:721-726);")
svg = ch.render()
assert "\n\n" not in svg and "<svg" in svg, "SVG invalid (linie goală?)"
# --- citirea SVG-ului: etichete în panză, fără suprapuneri -----------------
TEXT = re.compile(r'<text x="([-\d.]+)" y="([-\d.]+)"([^>]*)>(.*?)</text>', re.S)
H = float(re.search(r'viewBox="0 0 (\d+) ([\d.]+)"', svg).group(2))
labels = []
for m in TEXT.finditer(svg):
x, y, attrs, s = float(m.group(1)), float(m.group(2)), m.group(3), m.group(4)
anchor = "end" if 'text-anchor="end"' in attrs else \
("middle" if 'text-anchor="middle"' in attrs else "start")
bold = "bold" in attrs
wpx = len(s) * 12 * (0.62 if bold else 0.52)
left = x - wpx if anchor == "end" else (x - wpx / 2 if anchor == "middle" else x)
labels.append((left, left + wpx, y, s))
for left, right, y, s in labels:
assert left >= -2 and right <= w + 2, f"text iese din panză: {s!r} [{left:.0f},{right:.0f}]"
assert 0 <= y <= H, f"text în afara înălțimii: {s!r} (y={y:.0f})"
for i in range(len(labels)):
li, ri, yi, si = labels[i]
for j in range(i + 1, len(labels)):
lj, rj, yj, sj = labels[j]
if abs(yi - yj) < 12 and li < rj - 4 and lj < ri - 4:
raise AssertionError(f"texte suprapuse: {si!r} / {sj!r}\n {labels[i]}\n {labels[j]}")
# candelele sunt o serie continuă, lipită
xs = [ch._cx(i) for i in range(len(cds))]
assert xs[-1] + ch.body_w / 2 < w, f"ultima candelă iese din panză (x={xs[-1]})"
assert all(xs[i + 1] - xs[i] == ch.gap for i in range(len(xs) - 1)), "serie cu goluri"
print(f"LABU: {len(cds)} bare, hammer {cds[23].l:.2f}/{cds[23].h:.2f} pe suport "
f"{SUPPORT:.2f}, intrare {entry:.2f}, SL {cds[23].l:.2f}, vârf {peak:.2f}")
print(f"procent: {peak:.2f} / {entry:.2f} - 1 = +{pct:.1f} % (cifra cursului)")
print(f"scris {OUT} ({w}x{H:.0f}); {len(labels)} etichete verificate, fără suprapuneri")
OUT.write_text(svg, encoding="utf-8")

View File

@@ -0,0 +1,133 @@
<svg viewBox="0 0 760 391" xmlns="http://www.w3.org/2000/svg" style="width:100%;height:auto;font:12px sans-serif">
<text x="24" y="48" fill="#333" style="font-weight:bold">LABU — daily</text>
<line x1="24" y1="198.9" x2="746" y2="198.9" stroke="#777" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="30" y="192.9" fill="#777">suport monthly 80.00</text>
<line x1="24" y1="202.1" x2="746" y2="202.1" stroke="#c00" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="732" y="196.1" fill="#c00" text-anchor="end">SL sub low-ul hammer-ului 79.60 (construit)</text>
<line x1="24" y1="188.5" x2="746" y2="188.5" stroke="#188a3e" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="732" y="182.5" fill="#188a3e" text-anchor="end">intrare 81.30 (construit)</text>
<polyline points="329.0,159.1 344.0,163.9 359.0,167.8 374.0,172.0 389.0,175.4 404.0,178.1 419.0,180.8 434.0,182.6 449.0,183.5 464.0,184.4 479.0,184.3 494.0,183.3 509.0,181.5 524.0,179.4 539.0,176.4 554.0,172.5 569.0,168.2 584.0,162.8 599.0,156.1" fill="none" stroke="#b06000" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="605" y="150" fill="#b06000">SMA-20</text>
<line x1="44.0" y1="84.5" x2="44.0" y2="106.1" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="39.5" y="86.9" width="9" height="16.0" fill="#c00"/>
<line x1="59.0" y1="100.5" x2="59.0" y2="118.1" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="54.5" y="102.9" width="9" height="12.0" fill="#c00"/>
<line x1="74.0" y1="106.9" x2="74.0" y2="118.1" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="69.5" y="109.3" width="9" height="5.6" fill="#188a3e"/>
<line x1="89.0" y1="106.9" x2="89.0" y2="126.9" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="84.5" y="109.3" width="9" height="14.4" fill="#c00"/>
<line x1="104.0" y1="121.3" x2="104.0" y2="137.3" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="99.5" y="123.7" width="9" height="10.4" fill="#c00"/>
<line x1="119.0" y1="127.7" x2="119.0" y2="137.3" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="114.5" y="130.1" width="9" height="4.0" fill="#188a3e"/>
<line x1="134.0" y1="127.7" x2="134.0" y2="146.1" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="129.5" y="130.1" width="9" height="12.8" fill="#c00"/>
<line x1="149.0" y1="140.5" x2="149.0" y2="155.7" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="144.5" y="142.9" width="9" height="9.6" fill="#c00"/>
<line x1="164.0" y1="146.1" x2="164.0" y2="155.7" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="159.5" y="148.5" width="9" height="4.0" fill="#188a3e"/>
<line x1="179.0" y1="146.1" x2="179.0" y2="162.9" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="174.5" y="148.5" width="9" height="11.2" fill="#c00"/>
<line x1="194.0" y1="157.3" x2="194.0" y2="173.3" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="189.5" y="159.7" width="9" height="10.4" fill="#c00"/>
<line x1="209.0" y1="167.7" x2="209.0" y2="182.9" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="204.5" y="170.1" width="9" height="9.6" fill="#c00"/>
<line x1="224.0" y1="172.5" x2="224.0" y2="182.9" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="219.5" y="174.9" width="9" height="4.8" fill="#188a3e"/>
<line x1="239.0" y1="172.5" x2="239.0" y2="187.7" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="234.5" y="174.9" width="9" height="9.6" fill="#c00"/>
<line x1="254.0" y1="182.1" x2="254.0" y2="196.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="249.5" y="184.5" width="9" height="8.8" fill="#c00"/>
<line x1="269.0" y1="186.9" x2="269.0" y2="196.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="264.5" y="189.3" width="9" height="4.0" fill="#188a3e"/>
<line x1="284.0" y1="186.9" x2="284.0" y2="200.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="279.5" y="189.3" width="9" height="8.0" fill="#c00"/>
<line x1="299.0" y1="190.1" x2="299.0" y2="200.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="294.5" y="192.5" width="9" height="4.8" fill="#188a3e"/>
<line x1="314.0" y1="184.5" x2="314.0" y2="195.7" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="309.5" y="186.9" width="9" height="5.6" fill="#188a3e"/>
<line x1="329.0" y1="184.5" x2="329.0" y2="198.9" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="324.5" y="186.9" width="9" height="8.8" fill="#c00"/>
<line x1="344.0" y1="193.3" x2="344.0" y2="201.3" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="339.5" y="195.7" width="9" height="2.4" fill="#c00"/>
<line x1="359.0" y1="191.7" x2="359.0" y2="201.3" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="354.5" y="194.1" width="9" height="4.0" fill="#188a3e"/>
<line x1="374.0" y1="190.1" x2="374.0" y2="197.3" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="369.5" y="192.5" width="9" height="1.6" fill="#188a3e"/>
<line x1="389.0" y1="191.7" x2="389.0" y2="202.1" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="384.5" y="191.7" width="9" height="1.6" fill="#188a3e"/>
<line x1="404.0" y1="186.1" x2="404.0" y2="194.9" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="399.5" y="188.5" width="9" height="3.2" fill="#188a3e"/>
<line x1="419.0" y1="181.3" x2="419.0" y2="191.7" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="414.5" y="183.7" width="9" height="4.8" fill="#188a3e"/>
<line x1="434.0" y1="175.7" x2="434.0" y2="186.9" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="429.5" y="178.1" width="9" height="5.6" fill="#188a3e"/>
<line x1="449.0" y1="169.3" x2="449.0" y2="181.3" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="444.5" y="171.7" width="9" height="6.4" fill="#188a3e"/>
<line x1="464.0" y1="162.9" x2="464.0" y2="174.9" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="459.5" y="165.3" width="9" height="6.4" fill="#188a3e"/>
<line x1="479.0" y1="155.7" x2="479.0" y2="168.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="474.5" y="158.1" width="9" height="7.2" fill="#188a3e"/>
<line x1="494.0" y1="148.5" x2="494.0" y2="161.3" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="489.5" y="150.9" width="9" height="7.2" fill="#188a3e"/>
<line x1="509.0" y1="140.5" x2="509.0" y2="154.1" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="504.5" y="142.9" width="9" height="8.0" fill="#188a3e"/>
<line x1="524.0" y1="131.7" x2="524.0" y2="146.1" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="519.5" y="134.1" width="9" height="8.8" fill="#188a3e"/>
<line x1="539.0" y1="122.1" x2="539.0" y2="137.3" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="534.5" y="124.5" width="9" height="9.6" fill="#188a3e"/>
<line x1="554.0" y1="111.7" x2="554.0" y2="127.7" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="549.5" y="114.1" width="9" height="10.4" fill="#188a3e"/>
<line x1="569.0" y1="100.5" x2="569.0" y2="117.3" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="564.5" y="102.9" width="9" height="11.2" fill="#188a3e"/>
<line x1="584.0" y1="88.5" x2="584.0" y2="106.1" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="579.5" y="90.9" width="9" height="12.0" fill="#188a3e"/>
<line x1="599.0" y1="56.0" x2="599.0" y2="94.1" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="594.5" y="58.4" width="9" height="32.5" fill="#188a3e"/>
<line x1="24" y1="298.1" x2="746" y2="298.1" stroke="#888"/>
<rect x="39.5" y="256.7" width="9" height="41.4" fill="#c00" opacity="0.55"/>
<rect x="54.5" y="258.3" width="9" height="39.8" fill="#c00" opacity="0.55"/>
<rect x="69.5" y="260.7" width="9" height="37.4" fill="#188a3e" opacity="0.55"/>
<rect x="84.5" y="259.1" width="9" height="39.0" fill="#c00" opacity="0.55"/>
<rect x="99.5" y="262.3" width="9" height="35.8" fill="#c00" opacity="0.55"/>
<rect x="114.5" y="261.5" width="9" height="36.6" fill="#188a3e" opacity="0.55"/>
<rect x="129.5" y="263.9" width="9" height="34.2" fill="#c00" opacity="0.55"/>
<rect x="144.5" y="262.3" width="9" height="35.8" fill="#c00" opacity="0.55"/>
<rect x="159.5" y="265.5" width="9" height="32.6" fill="#188a3e" opacity="0.55"/>
<rect x="174.5" y="263.9" width="9" height="34.2" fill="#c00" opacity="0.55"/>
<rect x="189.5" y="263.1" width="9" height="35.0" fill="#c00" opacity="0.55"/>
<rect x="204.5" y="260.7" width="9" height="37.4" fill="#c00" opacity="0.55"/>
<rect x="219.5" y="264.7" width="9" height="33.4" fill="#188a3e" opacity="0.55"/>
<rect x="234.5" y="262.3" width="9" height="35.8" fill="#c00" opacity="0.55"/>
<rect x="249.5" y="259.9" width="9" height="38.2" fill="#c00" opacity="0.55"/>
<rect x="264.5" y="261.5" width="9" height="36.6" fill="#188a3e" opacity="0.55"/>
<rect x="279.5" y="258.3" width="9" height="39.8" fill="#c00" opacity="0.55"/>
<rect x="294.5" y="263.1" width="9" height="35.0" fill="#188a3e" opacity="0.55"/>
<rect x="309.5" y="263.9" width="9" height="34.2" fill="#188a3e" opacity="0.55"/>
<rect x="324.5" y="261.5" width="9" height="36.6" fill="#c00" opacity="0.55"/>
<rect x="339.5" y="257.5" width="9" height="40.6" fill="#c00" opacity="0.55"/>
<rect x="354.5" y="259.1" width="9" height="39.0" fill="#188a3e" opacity="0.55"/>
<rect x="369.5" y="254.3" width="9" height="43.8" fill="#188a3e" opacity="0.55"/>
<rect x="384.5" y="228.1" width="9" height="70.0" fill="#188a3e" opacity="0.55"/>
<rect x="399.5" y="231.3" width="9" height="66.8" fill="#188a3e" opacity="0.55"/>
<rect x="414.5" y="242.4" width="9" height="55.7" fill="#188a3e" opacity="0.55"/>
<rect x="429.5" y="248.8" width="9" height="49.3" fill="#188a3e" opacity="0.55"/>
<rect x="444.5" y="251.9" width="9" height="46.1" fill="#188a3e" opacity="0.55"/>
<rect x="459.5" y="255.1" width="9" height="43.0" fill="#188a3e" opacity="0.55"/>
<rect x="474.5" y="256.7" width="9" height="41.4" fill="#188a3e" opacity="0.55"/>
<rect x="489.5" y="258.3" width="9" height="39.8" fill="#188a3e" opacity="0.55"/>
<rect x="504.5" y="259.9" width="9" height="38.2" fill="#188a3e" opacity="0.55"/>
<rect x="519.5" y="261.5" width="9" height="36.6" fill="#188a3e" opacity="0.55"/>
<rect x="534.5" y="262.3" width="9" height="35.8" fill="#188a3e" opacity="0.55"/>
<rect x="549.5" y="260.7" width="9" height="37.4" fill="#188a3e" opacity="0.55"/>
<rect x="564.5" y="254.3" width="9" height="43.8" fill="#188a3e" opacity="0.55"/>
<rect x="579.5" y="248.8" width="9" height="49.3" fill="#188a3e" opacity="0.55"/>
<rect x="594.5" y="240.8" width="9" height="57.3" fill="#188a3e" opacity="0.55"/>
<text x="24" y="222.1" fill="#555">volum</text>
<text x="389.0" y="216.1" fill="#b06000" text-anchor="middle" style="font-weight:bold">hammer 80.90 / 79.60 (construit)</text>
<text x="599.0" y="46.0" fill="#188a3e" text-anchor="middle" style="font-weight:bold">+20 % (cifra cursului): 97.56 / 81.30</text>
<text x="24" y="320.1" fill="#c00">bara 23 = hammer: o 80.80, h 80.90, l 79.60, c 80.90 — corp 0.10, fitil jos 1.20 (H=C), pe suportul 80.00</text>
<text x="24" y="337.1" fill="#c00">bara 24 = confirmare: close 81.30 — close peste high-ul hammer-ului 80.90 = intrare 81.30 (construit)</text>
<text x="24" y="354.1" fill="#188a3e">Procentul (+20 % în jumătate de lună) și contextul (suport monthly) sunt ale cursului (18_2025-09-23.md:721-726);</text>
<text x="24" y="379" fill="#555">nivelurile de preț sunt construite de sumarizator.</text>
</svg>

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -0,0 +1,266 @@
#!/usr/bin/env python3
"""Hartă de orientare: buy hammer (public) vs. MBT-10 (cutie neagră), același
trigger de bottom.
Pentru `strategii/06_hammer_mbt10.md`, marcajul `<!-- DIAGRAMA: harta -->`.
Nu e grafic de preț (spec: TIP CONCEPT, CHEIE harta) — SVG scris de mână,
doar stdlib; nu se folosește svg_candles / pchart.
Ce arată (toate citatele și trimiterile vin din spec, nu se inventează):
- bara comună de sus: setup-urile de fond spun UNDE, trigger-ul de candelă
spune CÂND (`02_mbt10.md:9-11`, `43-49`);
- două cutii alăturate: BUY HAMMER (pattern standard, public) și MBT-10
(cutie neagră, indicator proprietar), fiecare cu regulile ei și cu
trimiterile de rigoare;
- săgeata de jos spre caseta finală: ce rămâne utilizabil pe BVB.
Verdictele de BVB din cutii sunt judecata mea (secțiunea Transfer), marcate
ca atare în legendă, exact cum cere specul.
Rulare: python3 gen/strategii/hammer_mbt10_harta.py -> hammer_mbt10_harta.svg
"""
from pathlib import Path
from xml.sax.saxutils import escape
OUT = Path(__file__).with_suffix(".svg")
W, H = 700, 610
BLACK, GRAY, BLUE, GREEN, RED, ORANGE, WHITE = (
"#000000", "#777777", "#1a5fb4", "#188a3e", "#c00", "#b06000", "#ffffff")
BORDER = "#333333"
# ---------------------------------------------------------------- geometrie
BAR_X0, BAR_X1, BAR_Y, BAR_H = 40.0, 660.0, 82.0, 76.0
BOX_W = 302.0
BOX_LX, BOX_RX, BOX_Y = 40.0, 358.0, 190.0
BOX_H = 256.0
PAD = 16.0
ROW0 = BOX_Y + 56.0 # baseline-ul primei reguli
ROW_H = 52.0
VERDICT_SEP = BOX_Y + 216.0
VERDICT_Y = VERDICT_SEP + 26.0
BUS_Y = 460.0 # linia orizontală de unire a celor două cutii
TRI_TIP = 500.0
FINAL_X0, FINAL_Y, FINAL_W, FINAL_H = 110.0, 500.0, 480.0, 56.0
TEXTS = [] # (x, y, text, size, weight, anchor) pentru verificare
def txt(x, y, s, size=13, weight="400", fill=BLACK, anchor="middle"):
TEXTS.append((x, y, s, size, weight, anchor))
return (
f'<text x="{x:.1f}" y="{y:.1f}" font-size="{size}" font-weight="{weight}" '
f'fill="{fill}" text-anchor="{anchor}">{escape(s)}</text>'
)
def line(x1, y1, x2, y2, stroke=GRAY, width=1.0, dash=None):
d = f' stroke-dasharray="{dash}"' if dash else ""
return (f'<line x1="{x1:.1f}" y1="{y1:.1f}" x2="{x2:.1f}" y2="{y2:.1f}" '
f'stroke="{stroke}" stroke-width="{width}"{d}/>')
def rect(x, y, w, h, fill=WHITE, stroke=GRAY, width=1.2):
return (f'<rect x="{x:.1f}" y="{y:.1f}" width="{w:.1f}" height="{h:.1f}" '
f'rx="8" fill="{fill}" stroke="{stroke}" stroke-width="{width}"/>')
def box(bx, title, title_fill, rows, verdict, verdict_fill, fill):
"""O cutie cu chenar: titlu, reguli (linii + trimitere), verdict."""
x = bx + PAD
area = BOX_W - 2 * PAD
out = [rect(bx, BOX_Y, BOX_W, BOX_H, fill=fill, stroke=title_fill, width=1.4)]
out.append(txt(x, BOX_Y + 26, title, 15, "700", title_fill, "start"))
out.append(line(bx + PAD, BOX_Y + 38, bx + BOX_W - PAD, BOX_Y + 38,
title_fill, 1.0))
for i, (mains, ref) in enumerate(rows):
top = ROW0 + i * ROW_H
for k, m in enumerate(mains):
out.append(txt(x, top + 14 + k * 17, m, 13, "400", BLACK, "start"))
if ref is not None:
out.append(txt(x, top + 48, ref, 13, "400", GRAY, "start"))
out.append(line(bx + PAD, VERDICT_SEP, bx + BOX_W - PAD, VERDICT_SEP,
verdict_fill, 1.2))
out.append(txt(x, VERDICT_Y, verdict, 13, "700", verdict_fill, "start"))
return out, area
def build():
p = [
f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 {W} {H}" '
f'width="{W}" height="{H}" role="img" '
f'aria-label="Harta capitolului: acelasi trigger de bottom, doua naturi '
f'- buy hammer, pattern standard public, si MBT-10, cutie neagra cu '
f'indicator proprietar">',
'<g font-family="system-ui, sans-serif">',
f'<rect x="0" y="0" width="{W}" height="{H}" fill="{WHITE}"/>',
txt(W / 2, 34, "Același trigger de bottom, două naturi", 19, "700", BLACK),
txt(W / 2, 58, "buy hammer (public) vs. MBT-10 (cutie neagră)",
14, "400", GRAY),
]
# ------- zona 1: bara comună
p.append(rect(BAR_X0, BAR_Y, BAR_X1 - BAR_X0, BAR_H, fill="#f2f4f7",
stroke=GRAY, width=1.2))
p.append(txt(BAR_X0 + PAD, BAR_Y + 26, "Trigger de bottom: unde + când",
15, "700", BLUE, "start"))
p.append(txt(BAR_X0 + PAD, BAR_Y + 48,
"Setup-urile de fond — suport, locul crimei, double bottom, buy "
"doji,", 13, "400", BLACK, "start"))
p.append(txt(BAR_X0 + PAD, BAR_Y + 66,
"divergență de volum — spun UNDE. Trigger-ul de candelă spune "
"CÂND.", 13, "400", BLACK, "start"))
p.append(txt(BAR_X1 - PAD, BAR_Y + 26, "02_mbt10.md:9-11, 43-49",
13, "400", GRAY, "end"))
# ------- conector bară -> cutii (un cot, fără săgeți redundante)
lx = BOX_LX + BOX_W / 2
rx = BOX_RX + BOX_W / 2
p.append(line(W / 2, BAR_Y + BAR_H, W / 2, 172.0, GRAY, 1.2))
p.append(line(lx, 172.0, rx, 172.0, GRAY, 1.2))
p.append(line(lx, 172.0, lx, BOX_Y, GRAY, 1.2))
p.append(line(rx, 172.0, rx, BOX_Y, GRAY, 1.2))
# ------- zona 2: cele două cutii
left_rows = [
(['„formă de candelă + downtrend”'], "05_patternuri_candele.md:77-88"),
(['„trigger: close peste', 'high-ul hammer-ului”'],
"05_patternuri_candele.md:99-124"),
(['„fără niciun indicator proprietar”'], None),
]
right_rows = [
(['„formula nu e publică nicăieri”'], "02_mbt10.md:21-39"),
(['„reconstituibil doar:', 'close peste pivotul daily”'],
"02_mbt10.md:179-188"),
(['„scanner + indicator proprietar”'], "02_mbt10.md:853-863"),
]
left_svg, left_area = box(
BOX_LX, "BUY HAMMER — pattern standard", BLUE, left_rows,
"„pe BVB: se poate rula long-only”", GREEN, "#eef4fd")
right_svg, right_area = box(
BOX_RX, "MBT-10 — cutie neagră", BORDER, right_rows,
"„pe BVB: nu, ca sistem”", RED, "#f5f5f5")
p += left_svg + right_svg
# ------- zona 3: ambele cutii -> caseta finală
p.append(line(lx, BOX_Y + BOX_H, lx, BUS_Y, GRAY, 1.2))
p.append(line(rx, BOX_Y + BOX_H, rx, BUS_Y, GRAY, 1.2))
p.append(line(lx, BUS_Y, rx, BUS_Y, GRAY, 1.2))
p.append(line(W / 2, BUS_Y, W / 2, TRI_TIP - 11, GRAY, 1.4))
p.append(f'<polygon points="{W/2:.1f},{TRI_TIP:.1f} '
f'{W/2-6:.1f},{TRI_TIP-11:.1f} {W/2+6:.1f},{TRI_TIP-11:.1f}" '
f'fill="{GRAY}"/>')
p.append(rect(FINAL_X0, FINAL_Y, FINAL_W, FINAL_H, fill="#fdf0e0",
stroke=ORANGE, width=1.6))
p.append(txt(W / 2, FINAL_Y + 24,
"Ce rămâne pe BVB: hammer la un nivel major", 14, "700",
BLACK))
p.append(txt(W / 2, FINAL_Y + 44, "+ close peste PPD", 14, "700", BLACK))
# ------- legendă
p.append(txt(W / 2, 578,
"Regulile = materialul cursului (cu trimiteri); verdictul de "
"BVB = judecata mea", 13, "400", GRAY))
p.append(txt(W / 2, 596, "din secțiunea Transfer.", 13, "400", GRAY))
p.append('</g></svg>')
return "\n".join(p) + "\n", left_area, right_area
# ---------------------------------------------------------------- verificare
def est_w(s, size, weight):
factor = 0.62 if weight == "700" else 0.56
return len(s) * size * factor
def box_of(x, y):
for bx in (BOX_LX, BOX_RX):
if bx <= x <= bx + BOX_W and BOX_Y <= y <= BOX_Y + BOX_H:
return bx
return None
def verify(svg, left_area, right_area):
# 1. geometrie: zonele nu se calcă
assert BAR_Y + BAR_H < BOX_Y, "bara comună intră în cutii"
assert BOX_Y + BOX_H < BUS_Y < TRI_TIP <= FINAL_Y, "săgeata nu are loc"
assert FINAL_Y + FINAL_H < 578, "caseta finală intră în legendă"
assert left_area == right_area == BOX_W - 2 * PAD
# 2. toate textele în panză
for x, y, s, size, weight, anchor in TEXTS:
ew = est_w(s, size, weight)
left = x - ew if anchor == "end" else (x - ew / 2 if anchor == "middle" else x)
right = left + ew
assert -2 <= left and right <= W + 2, f"text în afara panzei: {s!r}"
assert 0 <= y <= H, f"text în afara înălțimii: {s!r} (y={y})"
# 3. texte care nu se suprapun
for i in range(len(TEXTS)):
xi, yi, si, szi, wi, ai = TEXTS[i]
we = est_w(si, szi, wi)
li = xi - we if ai == "end" else (xi - we / 2 if ai == "middle" else xi)
ri = li + we
for j in range(i + 1, len(TEXTS)):
xj, yj, sj, szj, wj, aj = TEXTS[j]
wej = est_w(sj, szj, wj)
lj = xj - wej if aj == "end" else (xj - wej / 2 if aj == "middle" else xj)
rj = lj + wej
if abs(yi - yj) < 13 and li < rj - 4 and lj < ri - 4:
raise AssertionError(f"texte suprapuse: {si!r} / {sj!r}")
# 4. textele din cutii încap în cutii
for x, y, s, size, weight, anchor in TEXTS:
b = box_of(x, y)
if b is not None:
ew = est_w(s, size, weight)
assert x + ew <= b + BOX_W - PAD + 1, f"text iese din cutie: {s!r}"
# 5. verdictul e sub linia lui de separare
assert VERDICT_SEP < VERDICT_Y < BOX_Y + BOX_H
# 6. conținutul obligatoriu din spec
for cheie in (
"Trigger de bottom: unde + când",
"Setup-urile de fond — suport, locul crimei, double bottom, buy doji,",
"divergență de volum — spun UNDE. Trigger-ul de candelă spune CÂND.",
"02_mbt10.md:9-11, 43-49",
"BUY HAMMER — pattern standard",
"„formă de candelă + downtrend”",
"05_patternuri_candele.md:77-88",
"„trigger: close peste",
"high-ul hammer-ului”",
"05_patternuri_candele.md:99-124",
"„fără niciun indicator proprietar”",
"„pe BVB: se poate rula long-only”",
"MBT-10 — cutie neagră",
"„formula nu e publică nicăieri”",
"02_mbt10.md:21-39",
"„reconstituibil doar:",
"close peste pivotul daily”",
"02_mbt10.md:179-188",
"„scanner + indicator proprietar”",
"02_mbt10.md:853-863",
"„pe BVB: nu, ca sistem”",
"Ce rămâne pe BVB: hammer la un nivel major",
"+ close peste PPD",
"verdictul de BVB = judecata mea",
"din secțiunea Transfer.",
):
assert cheie in svg, f"lipsește din desen: {cheie!r}"
# 7. fără axe de preț / candele / cifre inventate
for interzis in ("candela", "SL ", "intrare ", "TP1", "TP2", "$", "USD"):
assert interzis not in svg, f"nu ar trebui să apară preț: {interzis!r}"
return True
SVG, LEFT_AREA, RIGHT_AREA = build()
assert verify(SVG, LEFT_AREA, RIGHT_AREA), "verificare eșuată"
OUT.write_text(SVG, encoding="utf-8")
print(f"scris {OUT} ({W}x{H}), verificare OK: {len(TEXTS)} etichete, "
f"cutii {BOX_W:g}x{BOX_H:g} la {BOX_LX:g}/{BOX_RX:g}")

View File

@@ -0,0 +1,48 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 700 610" width="700" height="610" role="img" aria-label="Harta capitolului: acelasi trigger de bottom, doua naturi - buy hammer, pattern standard public, si MBT-10, cutie neagra cu indicator proprietar">
<g font-family="system-ui, sans-serif">
<rect x="0" y="0" width="700" height="610" fill="#ffffff"/>
<text x="350.0" y="34.0" font-size="19" font-weight="700" fill="#000000" text-anchor="middle">Același trigger de bottom, două naturi</text>
<text x="350.0" y="58.0" font-size="14" font-weight="400" fill="#777777" text-anchor="middle">buy hammer (public) vs. MBT-10 (cutie neagră)</text>
<rect x="40.0" y="82.0" width="620.0" height="76.0" rx="8" fill="#f2f4f7" stroke="#777777" stroke-width="1.2"/>
<text x="56.0" y="108.0" font-size="15" font-weight="700" fill="#1a5fb4" text-anchor="start">Trigger de bottom: unde + când</text>
<text x="56.0" y="130.0" font-size="13" font-weight="400" fill="#000000" text-anchor="start">Setup-urile de fond — suport, locul crimei, double bottom, buy doji,</text>
<text x="56.0" y="148.0" font-size="13" font-weight="400" fill="#000000" text-anchor="start">divergență de volum — spun UNDE. Trigger-ul de candelă spune CÂND.</text>
<text x="644.0" y="108.0" font-size="13" font-weight="400" fill="#777777" text-anchor="end">02_mbt10.md:9-11, 43-49</text>
<line x1="350.0" y1="158.0" x2="350.0" y2="172.0" stroke="#777777" stroke-width="1.2"/>
<line x1="191.0" y1="172.0" x2="509.0" y2="172.0" stroke="#777777" stroke-width="1.2"/>
<line x1="191.0" y1="172.0" x2="191.0" y2="190.0" stroke="#777777" stroke-width="1.2"/>
<line x1="509.0" y1="172.0" x2="509.0" y2="190.0" stroke="#777777" stroke-width="1.2"/>
<rect x="40.0" y="190.0" width="302.0" height="256.0" rx="8" fill="#eef4fd" stroke="#1a5fb4" stroke-width="1.4"/>
<text x="56.0" y="216.0" font-size="15" font-weight="700" fill="#1a5fb4" text-anchor="start">BUY HAMMER — pattern standard</text>
<line x1="56.0" y1="228.0" x2="326.0" y2="228.0" stroke="#1a5fb4" stroke-width="1.0"/>
<text x="56.0" y="260.0" font-size="13" font-weight="400" fill="#000000" text-anchor="start">„formă de candelă + downtrend”</text>
<text x="56.0" y="294.0" font-size="13" font-weight="400" fill="#777777" text-anchor="start">05_patternuri_candele.md:77-88</text>
<text x="56.0" y="312.0" font-size="13" font-weight="400" fill="#000000" text-anchor="start">„trigger: close peste</text>
<text x="56.0" y="329.0" font-size="13" font-weight="400" fill="#000000" text-anchor="start">high-ul hammer-ului”</text>
<text x="56.0" y="346.0" font-size="13" font-weight="400" fill="#777777" text-anchor="start">05_patternuri_candele.md:99-124</text>
<text x="56.0" y="364.0" font-size="13" font-weight="400" fill="#000000" text-anchor="start">„fără niciun indicator proprietar”</text>
<line x1="56.0" y1="406.0" x2="326.0" y2="406.0" stroke="#188a3e" stroke-width="1.2"/>
<text x="56.0" y="432.0" font-size="13" font-weight="700" fill="#188a3e" text-anchor="start">„pe BVB: se poate rula long-only”</text>
<rect x="358.0" y="190.0" width="302.0" height="256.0" rx="8" fill="#f5f5f5" stroke="#333333" stroke-width="1.4"/>
<text x="374.0" y="216.0" font-size="15" font-weight="700" fill="#333333" text-anchor="start">MBT-10 — cutie neagră</text>
<line x1="374.0" y1="228.0" x2="644.0" y2="228.0" stroke="#333333" stroke-width="1.0"/>
<text x="374.0" y="260.0" font-size="13" font-weight="400" fill="#000000" text-anchor="start">„formula nu e publică nicăieri”</text>
<text x="374.0" y="294.0" font-size="13" font-weight="400" fill="#777777" text-anchor="start">02_mbt10.md:21-39</text>
<text x="374.0" y="312.0" font-size="13" font-weight="400" fill="#000000" text-anchor="start">„reconstituibil doar:</text>
<text x="374.0" y="329.0" font-size="13" font-weight="400" fill="#000000" text-anchor="start">close peste pivotul daily”</text>
<text x="374.0" y="346.0" font-size="13" font-weight="400" fill="#777777" text-anchor="start">02_mbt10.md:179-188</text>
<text x="374.0" y="364.0" font-size="13" font-weight="400" fill="#000000" text-anchor="start">„scanner + indicator proprietar”</text>
<text x="374.0" y="398.0" font-size="13" font-weight="400" fill="#777777" text-anchor="start">02_mbt10.md:853-863</text>
<line x1="374.0" y1="406.0" x2="644.0" y2="406.0" stroke="#c00" stroke-width="1.2"/>
<text x="374.0" y="432.0" font-size="13" font-weight="700" fill="#c00" text-anchor="start">„pe BVB: nu, ca sistem”</text>
<line x1="191.0" y1="446.0" x2="191.0" y2="460.0" stroke="#777777" stroke-width="1.2"/>
<line x1="509.0" y1="446.0" x2="509.0" y2="460.0" stroke="#777777" stroke-width="1.2"/>
<line x1="191.0" y1="460.0" x2="509.0" y2="460.0" stroke="#777777" stroke-width="1.2"/>
<line x1="350.0" y1="460.0" x2="350.0" y2="489.0" stroke="#777777" stroke-width="1.4"/>
<polygon points="350.0,500.0 344.0,489.0 356.0,489.0" fill="#777777"/>
<rect x="110.0" y="500.0" width="480.0" height="56.0" rx="8" fill="#fdf0e0" stroke="#b06000" stroke-width="1.6"/>
<text x="350.0" y="524.0" font-size="14" font-weight="700" fill="#000000" text-anchor="middle">Ce rămâne pe BVB: hammer la un nivel major</text>
<text x="350.0" y="544.0" font-size="14" font-weight="700" fill="#000000" text-anchor="middle">+ close peste PPD</text>
<text x="350.0" y="578.0" font-size="13" font-weight="400" fill="#777777" text-anchor="middle">Regulile = materialul cursului (cu trimiteri); verdictul de BVB = judecata mea</text>
<text x="350.0" y="596.0" font-size="13" font-weight="400" fill="#777777" text-anchor="middle">din secțiunea Transfer.</text>
</g></svg>

After

Width:  |  Height:  |  Size: 5.5 KiB

View File

@@ -0,0 +1,196 @@
#!/usr/bin/env python3
"""MBT-10 — pullback în locul crimei + hammer, intrare la close 54.60 (ansamblu).
Citește `gen/strategii/hammer_mbt10_mbt_ansamblu.spec.txt` și scrie
`gen/strategii/hammer_mbt10_mbt_ansamblu.svg`: 41 de candele daily pe exemplul
construit al sumarizatorului (`summaries/08_ANALIZA_ZILNICA/02_mbt10.md:165-173`,
`978-984`) — uptrend până la fostul vârf 52.10, spargere, vârf nou 58.90,
pullback în locul crimei, hammer cu codița exact pe 52.10, iar bara următoare
se închide în MBT-10 la 54.60 (intrare la close). SL 52.00, TP 58.90 = 1.65R.
Toate prețurile/nivelurile sunt cele din specificație. Seria de close-uri e
cea dată; open-ul fiecărei bare = close-ul precedentului. MBT-10 nu se
desenează (formula nu e publică) — se marchează doar bara la care semnalul
s-a închis.
Rulare: python3 gen/strategii/hammer_mbt10_mbt_ansamblu.py
"""
import re
import sys
from pathlib import Path
sys.path.insert(0, "/workspace/atm-curs-dl")
from svg_candles import Chart, Level, Mark, from_closes # noqa: E402
OUT = Path(__file__).with_suffix(".svg")
# --- nivelurile din specificație -------------------------------------------
CRIMEA = 52.10 # locul crimei = fostul vârf spart, retestat ca suport
HAMMER_H = 53.40 # high = close al hammer-ului
HAMMER_L = 52.10 # codița hammer-ului, exact pe locul crimei
ENTRY = 54.60 # close-ul barei care s-a închis în MBT-10 = intrare
SL = 52.00 # sub codița hammer-ului și sub locul crimei
TP = 58.90 # vârful anterior = 1.65R (risc 2.60)
# --- seria: 41 de close-uri, exact ca în specificație -----------------------
closes = [
# 0-8: uptrend care duce spre fostul vârf
46.00, 46.80, 47.50, 48.30, 49.00, 49.80, 50.50, 51.20, 52.00,
52.10, # 9 = fostul vârf 52.10 (high cu wick 52.40)
# 10-20: spargerea nivelului și urcarea spre vârful nou
51.40, 50.80, 51.50, 52.40, 53.20, 54.00, 54.80, 55.60, 56.40, 57.20,
58.00,
58.90, # 21 = vârf nou
# 22-28: pullback înapoi în locul crimei
58.20, 57.40, 56.50, 55.60, 54.70, 53.60, 52.20,
# 29-30: hammer pe locul crimei + ziua care se închide în MBT-10
53.40, # 29 = hammer: o 52.20, h/c 53.40, l 52.10 (H = C)
54.60, # 30 = MBT-10 închis la close => intrare
# 31-40: urcarea spre ținta de la vârful anterior
55.20, 55.80, 56.40, 57.00, 57.60, 58.10, 58.60,
58.90, # 38 = TP (vârful anterior)
58.70, 58.30,
]
assert len(closes) == 41, f"seria are {len(closes)} bare, spec cere 41"
# wick-uri (sus, jos): restul (0.30, 0.40); excepțiile din specificație.
# `from_closes` măsoară wick-ul de jos de la min(o, c), nu de la close: pentru
# hammer-ul cu o 52.20 / c 53.40, ca low-ul să iasă exact 52.10, wick-ul de jos
# e 0.10 (nu 1.30, cum dă specificația măsurând de la close).
wicks = [(0.30, 0.40)] * 41
wicks[9] = (0.30, 0.30) # high 52.40, low 51.70 (fostul vârf)
wicks[28] = (0.30, 0.10) # low 52.10: pullback-ul atinge locul crimei
wicks[29] = (0.00, 0.10) # hammer: high = close = 53.40, low = 52.10
# bara 30: specificația dă (0.20, 0.20) cu „low 54.40"; 54.40 ar fi peste
# open-ul 53.40, deci imposibil. Cu (0.20, 0.20) low-ul iese 53.20; nu e
# etichetat nicăieri și nu intră în niciun assert.
wicks[30] = (0.20, 0.20)
# volum „normal", fără argument de volum (specificația o spune explicit):
# doar panoul de volum cerut de stil, fără spike-uri pe hammer/confirmare.
vols = [
50, 49, 51, 50, 52, 51, 50, 52, 51,
52,
50, 49, 51, 50, 52, 51, 50, 52, 51, 52,
51,
52,
50, 49, 50, 51, 50, 49, 50,
51, 52,
50, 51, 52, 51, 50, 52, 51,
52,
51, 50,
]
assert len(vols) == 41
cds = from_closes(closes, wicks=wicks, vols=vols, start=45.60)
# seria continuă: open = close-ul barei anterioare
for a, b in zip(cds, cds[1:]):
assert abs(a.c - b.o) < 1e-9, "open != close-ul barei anterioare"
intrare = cds[30].c
sl, tp = SL, TP
# --- cele 8 assert-uri cerute de specificație ------------------------------
# 1. hammer 29: low exact pe locul crimei, close == high (fără fitil de sus)
assert abs(cds[29].l - CRIMEA) < 1e-9, f"low[29]={cds[29].l} != 52.10"
assert abs(cds[29].c - HAMMER_H) < 1e-9 and abs(cds[29].h - HAMMER_H) < 1e-9, \
f"close[29]/high[29] != 53.40 ({cds[29].c}/{cds[29].h})"
assert cds[29].c == cds[29].h, "hammer-ul are fitil de sus"
# 2. bara 30 se închide la 54.60 = intrare și peste high-ul hammer-ului
assert abs(intrare - ENTRY) < 1e-9, f"close[30]={intrare} != intrare 54.60"
assert intrare > cds[29].h == HAMMER_H, "close[30] nu e peste high[29] == 53.40"
# 3. SL = low-ul hammer-ului - 0.10
assert abs(sl - (cds[29].l - 0.10)) < 1e-9, "SL != low[29] - 0.10"
# 4. risc 2.60; TP 58.90 = 1.65R
risc = intrare - sl
assert abs(risc - 2.60) < 1e-9, f"risc={risc} != 2.60"
assert abs(tp - 58.90) < 1e-9, "TP != 58.90"
assert round((tp - intrare) / (intrare - sl), 2) == 1.65, "TP != 1.65R"
# 5. fostul vârf 52.10 există înainte de breakout (bara 9, high 52.40)
assert any(abs(cds[i].h - 52.10) <= 0.30 for i in range(15)), \
"niciun high din primele 15 bare nu e în 0.30 de 52.10"
assert abs(cds[9].h - 52.40) < 1e-9, f"high[9]={cds[9].h} != 52.40"
# 6. TP atins după intrare
assert max(c.c for c in cds[30:41]) >= 58.90, "TP 58.90 nu e atins după intrare"
# 7. vârful nou 58.90 e la bara 21, înainte de pullback; pullback-ul coboară
# exact la locul crimei (minimul secțiunii 21-29 e codița hammer-ului)
assert abs(cds[21].c - 58.90) < 1e-9, f"close[21]={cds[21].c} != 58.90"
assert abs(min(c.l for c in cds[21:30]) - CRIMEA) < 1e-9, \
f"min low[21:30]={min(c.l for c in cds[21:30])} != 52.10"
# 8. prima etapă e urcare
assert cds[9].c > cds[0].c, "prima etapă nu e uptrend"
print(f"hammer H/C {cds[29].h:.2f} L {cds[29].l:.2f} intrare {intrare:.2f} "
f"SL {sl:.2f} TP {tp:.2f} = {round((tp-intrare)/(intrare-sl),2)}R "
f"vârf nou {cds[21].c:.2f} (bara 21)")
# --- desenul ----------------------------------------------------------------
ch = Chart(
candles=cds,
levels=[
Level(CRIMEA, "locul crimei 52.10 (fost vârf spart)", "#777",
label_at="left"),
Level(SL, "SL 52.00 — sub codița hammer-ului", "#c00"),
Level(ENTRY, "intrare 54.60 (close în MBT-10)", "#188a3e"),
Level(TP, "TP 58.90 = 1.65R", "#1a5fb4"),
],
marks=[
Mark(29, "hammer — codița pe 52.10", "#b06000", "below"),
# dy negativ: bara 30 e urmată imediat de bare mai înalte (31-32), iar
# textul lipit de high-ul barei 30 ar cădea peste corpurile lor.
Mark(30, "MBT-10 închis", "#188a3e", "above", dy=-24),
],
scale=20.0, gap=15, body_w=9, left=44, width=780, vol_h=70,
vol_label="volum",
title="MBT-10 — pullback în locul crimei + hammer, închidere la 54.60 (exemplu construit)",
)
# explicațiile și legenda obligatorie: bloc de text sub grafic (nu se
# suprapun cu candelele, iar textele lungi nu încap pe verticală lângă bare).
cds[0].note = ("Legendă: MBT-10 nu se desenează — formula nu e publică "
"(02_mbt10.md:21-39); se marchează doar bara la care")
cds[1].note = ("semnalul s-a închis. Nivelurile sunt exemplul construit al "
"sumarizatorului (02_mbt10.md:165-173, 978-984).")
cds[2].note = ("bara 9 = fostul vârf 52.10 (locul crimei); bara 21 = vârful "
"nou 58.90.")
cds[3].note = ("bara 29 = hammer: o 52.20, h/c 53.40, l 52.10 (codița exact "
"pe locul crimei); bara 30 = ziua închisă în MBT-10.")
svg = ch.render()
assert "<svg" in svg and "\n\n" not in svg, "SVG invalid (linie goală?)"
# --- citirea SVG-ului: etichete în pânză, fără suprapuneri -----------------
TEXT = re.compile(r'<text x="([-\d.]+)" y="([-\d.]+)"([^>]*)>(.*?)</text>', re.S)
W = float(re.search(r'viewBox="0 0 (\d+) ', svg).group(1))
H = float(re.search(r'viewBox="0 0 \d+ ([\d.]+)"', svg).group(1))
labels = []
for m in TEXT.finditer(svg):
x, y, attrs, s = float(m.group(1)), float(m.group(2)), m.group(3), m.group(4)
anchor = "end" if 'text-anchor="end"' in attrs else \
("middle" if 'text-anchor="middle"' in attrs else "start")
bold = "bold" in attrs
wpx = len(s) * 12 * (0.62 if bold else 0.52)
left = x - wpx if anchor == "end" else (x - wpx / 2 if anchor == "middle" else x)
labels.append((left, left + wpx, y, s))
for left, right, y, s in labels:
assert left >= -2 and right <= W + 2, \
f"text iese din pânză: {s!r} [{left:.0f},{right:.0f}]"
assert 0 <= y <= H, f"text în afara înălțimii: {s!r} (y={y:.0f})"
for i in range(len(labels)):
li, ri, yi, si = labels[i]
for j in range(i + 1, len(labels)):
lj, rj, yj, sj = labels[j]
if abs(yi - yj) < 12 and li < rj - 4 and lj < ri - 4:
raise AssertionError(f"texte suprapuse: {si!r} / {sj!r}\n"
f" {labels[i]}\n {labels[j]}")
# candelele sunt o serie continuă, lipită, în pânză
xs = [ch._cx(i) for i in range(len(cds))]
assert all(xs[i + 1] - xs[i] == ch.gap for i in range(len(xs) - 1)), "serie cu goluri"
assert xs[-1] + ch.body_w / 2 < W, f"ultima candelă iese din pânză (x={xs[-1]})"
print(f"scris {OUT} ({W:.0f}x{H:.0f}); {len(labels)} etichete verificate")
OUT.write_text(svg, encoding="utf-8")

View File

@@ -0,0 +1,142 @@
<svg viewBox="0 0 780 526" xmlns="http://www.w3.org/2000/svg" style="width:100%;height:auto;font:12px sans-serif">
<text x="24" y="48" fill="#333" style="font-weight:bold">MBT-10 — pullback în locul crimei + hammer, închidere la 54.60 (exemplu construit)</text>
<line x1="24" y1="198.0" x2="766" y2="198.0" stroke="#777" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="30" y="192.0" fill="#777">locul crimei 52.10 (fost vârf spart)</text>
<line x1="24" y1="200.0" x2="766" y2="200.0" stroke="#c00" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="752" y="194.0" fill="#c00" text-anchor="end">SL 52.00 — sub codița hammer-ului</text>
<line x1="24" y1="148.0" x2="766" y2="148.0" stroke="#188a3e" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="752" y="142.0" fill="#188a3e" text-anchor="end">intrare 54.60 (close în MBT-10)</text>
<line x1="24" y1="62.0" x2="766" y2="62.0" stroke="#1a5fb4" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="752" y="56.0" fill="#1a5fb4" text-anchor="end">TP 58.90 = 1.65R</text>
<line x1="44.0" y1="314.0" x2="44.0" y2="336.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="39.5" y="320.0" width="9" height="8.0" fill="#188a3e"/>
<line x1="59.0" y1="298.0" x2="59.0" y2="328.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="54.5" y="304.0" width="9" height="16.0" fill="#188a3e"/>
<line x1="74.0" y1="284.0" x2="74.0" y2="312.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="69.5" y="290.0" width="9" height="14.0" fill="#188a3e"/>
<line x1="89.0" y1="268.0" x2="89.0" y2="298.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="84.5" y="274.0" width="9" height="16.0" fill="#188a3e"/>
<line x1="104.0" y1="254.0" x2="104.0" y2="282.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="99.5" y="260.0" width="9" height="14.0" fill="#188a3e"/>
<line x1="119.0" y1="238.0" x2="119.0" y2="268.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="114.5" y="244.0" width="9" height="16.0" fill="#188a3e"/>
<line x1="134.0" y1="224.0" x2="134.0" y2="252.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="129.5" y="230.0" width="9" height="14.0" fill="#188a3e"/>
<line x1="149.0" y1="210.0" x2="149.0" y2="238.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="144.5" y="216.0" width="9" height="14.0" fill="#188a3e"/>
<line x1="164.0" y1="194.0" x2="164.0" y2="224.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="159.5" y="200.0" width="9" height="16.0" fill="#188a3e"/>
<line x1="179.0" y1="192.0" x2="179.0" y2="206.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="174.5" y="198.0" width="9" height="2.0" fill="#188a3e"/>
<line x1="194.0" y1="192.0" x2="194.0" y2="220.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="189.5" y="198.0" width="9" height="14.0" fill="#c00"/>
<line x1="209.0" y1="206.0" x2="209.0" y2="232.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="204.5" y="212.0" width="9" height="12.0" fill="#c00"/>
<line x1="224.0" y1="204.0" x2="224.0" y2="232.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="219.5" y="210.0" width="9" height="14.0" fill="#188a3e"/>
<line x1="239.0" y1="186.0" x2="239.0" y2="218.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="234.5" y="192.0" width="9" height="18.0" fill="#188a3e"/>
<line x1="254.0" y1="170.0" x2="254.0" y2="200.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="249.5" y="176.0" width="9" height="16.0" fill="#188a3e"/>
<line x1="269.0" y1="154.0" x2="269.0" y2="184.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="264.5" y="160.0" width="9" height="16.0" fill="#188a3e"/>
<line x1="284.0" y1="138.0" x2="284.0" y2="168.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="279.5" y="144.0" width="9" height="16.0" fill="#188a3e"/>
<line x1="299.0" y1="122.0" x2="299.0" y2="152.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="294.5" y="128.0" width="9" height="16.0" fill="#188a3e"/>
<line x1="314.0" y1="106.0" x2="314.0" y2="136.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="309.5" y="112.0" width="9" height="16.0" fill="#188a3e"/>
<line x1="329.0" y1="90.0" x2="329.0" y2="120.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="324.5" y="96.0" width="9" height="16.0" fill="#188a3e"/>
<line x1="344.0" y1="74.0" x2="344.0" y2="104.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="339.5" y="80.0" width="9" height="16.0" fill="#188a3e"/>
<line x1="359.0" y1="56.0" x2="359.0" y2="88.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="354.5" y="62.0" width="9" height="18.0" fill="#188a3e"/>
<line x1="374.0" y1="56.0" x2="374.0" y2="84.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="369.5" y="62.0" width="9" height="14.0" fill="#c00"/>
<line x1="389.0" y1="70.0" x2="389.0" y2="100.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="384.5" y="76.0" width="9" height="16.0" fill="#c00"/>
<line x1="404.0" y1="86.0" x2="404.0" y2="118.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="399.5" y="92.0" width="9" height="18.0" fill="#c00"/>
<line x1="419.0" y1="104.0" x2="419.0" y2="136.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="414.5" y="110.0" width="9" height="18.0" fill="#c00"/>
<line x1="434.0" y1="122.0" x2="434.0" y2="154.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="429.5" y="128.0" width="9" height="18.0" fill="#c00"/>
<line x1="449.0" y1="140.0" x2="449.0" y2="176.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="444.5" y="146.0" width="9" height="22.0" fill="#c00"/>
<line x1="464.0" y1="162.0" x2="464.0" y2="198.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="459.5" y="168.0" width="9" height="28.0" fill="#c00"/>
<line x1="479.0" y1="172.0" x2="479.0" y2="198.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="474.5" y="172.0" width="9" height="24.0" fill="#188a3e"/>
<line x1="494.0" y1="144.0" x2="494.0" y2="176.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="489.5" y="148.0" width="9" height="24.0" fill="#188a3e"/>
<line x1="509.0" y1="130.0" x2="509.0" y2="156.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="504.5" y="136.0" width="9" height="12.0" fill="#188a3e"/>
<line x1="524.0" y1="118.0" x2="524.0" y2="144.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="519.5" y="124.0" width="9" height="12.0" fill="#188a3e"/>
<line x1="539.0" y1="106.0" x2="539.0" y2="132.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="534.5" y="112.0" width="9" height="12.0" fill="#188a3e"/>
<line x1="554.0" y1="94.0" x2="554.0" y2="120.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="549.5" y="100.0" width="9" height="12.0" fill="#188a3e"/>
<line x1="569.0" y1="82.0" x2="569.0" y2="108.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="564.5" y="88.0" width="9" height="12.0" fill="#188a3e"/>
<line x1="584.0" y1="72.0" x2="584.0" y2="96.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="579.5" y="78.0" width="9" height="10.0" fill="#188a3e"/>
<line x1="599.0" y1="62.0" x2="599.0" y2="86.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="594.5" y="68.0" width="9" height="10.0" fill="#188a3e"/>
<line x1="614.0" y1="56.0" x2="614.0" y2="76.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="609.5" y="62.0" width="9" height="6.0" fill="#188a3e"/>
<line x1="629.0" y1="56.0" x2="629.0" y2="74.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="624.5" y="62.0" width="9" height="4.0" fill="#c00"/>
<line x1="644.0" y1="60.0" x2="644.0" y2="82.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="639.5" y="66.0" width="9" height="8.0" fill="#c00"/>
<line x1="24" y1="432.0" x2="766" y2="432.0" stroke="#888"/>
<rect x="39.5" y="364.7" width="9" height="67.3" fill="#188a3e" opacity="0.55"/>
<rect x="54.5" y="366.0" width="9" height="66.0" fill="#188a3e" opacity="0.55"/>
<rect x="69.5" y="363.3" width="9" height="68.7" fill="#188a3e" opacity="0.55"/>
<rect x="84.5" y="364.7" width="9" height="67.3" fill="#188a3e" opacity="0.55"/>
<rect x="99.5" y="362.0" width="9" height="70.0" fill="#188a3e" opacity="0.55"/>
<rect x="114.5" y="363.3" width="9" height="68.7" fill="#188a3e" opacity="0.55"/>
<rect x="129.5" y="364.7" width="9" height="67.3" fill="#188a3e" opacity="0.55"/>
<rect x="144.5" y="362.0" width="9" height="70.0" fill="#188a3e" opacity="0.55"/>
<rect x="159.5" y="363.3" width="9" height="68.7" fill="#188a3e" opacity="0.55"/>
<rect x="174.5" y="362.0" width="9" height="70.0" fill="#188a3e" opacity="0.55"/>
<rect x="189.5" y="364.7" width="9" height="67.3" fill="#c00" opacity="0.55"/>
<rect x="204.5" y="366.0" width="9" height="66.0" fill="#c00" opacity="0.55"/>
<rect x="219.5" y="363.3" width="9" height="68.7" fill="#188a3e" opacity="0.55"/>
<rect x="234.5" y="364.7" width="9" height="67.3" fill="#188a3e" opacity="0.55"/>
<rect x="249.5" y="362.0" width="9" height="70.0" fill="#188a3e" opacity="0.55"/>
<rect x="264.5" y="363.3" width="9" height="68.7" fill="#188a3e" opacity="0.55"/>
<rect x="279.5" y="364.7" width="9" height="67.3" fill="#188a3e" opacity="0.55"/>
<rect x="294.5" y="362.0" width="9" height="70.0" fill="#188a3e" opacity="0.55"/>
<rect x="309.5" y="363.3" width="9" height="68.7" fill="#188a3e" opacity="0.55"/>
<rect x="324.5" y="362.0" width="9" height="70.0" fill="#188a3e" opacity="0.55"/>
<rect x="339.5" y="363.3" width="9" height="68.7" fill="#188a3e" opacity="0.55"/>
<rect x="354.5" y="362.0" width="9" height="70.0" fill="#188a3e" opacity="0.55"/>
<rect x="369.5" y="364.7" width="9" height="67.3" fill="#c00" opacity="0.55"/>
<rect x="384.5" y="366.0" width="9" height="66.0" fill="#c00" opacity="0.55"/>
<rect x="399.5" y="364.7" width="9" height="67.3" fill="#c00" opacity="0.55"/>
<rect x="414.5" y="363.3" width="9" height="68.7" fill="#c00" opacity="0.55"/>
<rect x="429.5" y="364.7" width="9" height="67.3" fill="#c00" opacity="0.55"/>
<rect x="444.5" y="366.0" width="9" height="66.0" fill="#c00" opacity="0.55"/>
<rect x="459.5" y="364.7" width="9" height="67.3" fill="#c00" opacity="0.55"/>
<rect x="474.5" y="363.3" width="9" height="68.7" fill="#188a3e" opacity="0.55"/>
<rect x="489.5" y="362.0" width="9" height="70.0" fill="#188a3e" opacity="0.55"/>
<rect x="504.5" y="364.7" width="9" height="67.3" fill="#188a3e" opacity="0.55"/>
<rect x="519.5" y="363.3" width="9" height="68.7" fill="#188a3e" opacity="0.55"/>
<rect x="534.5" y="362.0" width="9" height="70.0" fill="#188a3e" opacity="0.55"/>
<rect x="549.5" y="363.3" width="9" height="68.7" fill="#188a3e" opacity="0.55"/>
<rect x="564.5" y="364.7" width="9" height="67.3" fill="#188a3e" opacity="0.55"/>
<rect x="579.5" y="362.0" width="9" height="70.0" fill="#188a3e" opacity="0.55"/>
<rect x="594.5" y="363.3" width="9" height="68.7" fill="#188a3e" opacity="0.55"/>
<rect x="609.5" y="362.0" width="9" height="70.0" fill="#188a3e" opacity="0.55"/>
<rect x="624.5" y="363.3" width="9" height="68.7" fill="#c00" opacity="0.55"/>
<rect x="639.5" y="364.7" width="9" height="67.3" fill="#c00" opacity="0.55"/>
<text x="24" y="356.0" fill="#555">volum</text>
<text x="479.0" y="218.0" fill="#b06000" text-anchor="middle" style="font-weight:bold">hammer — codița pe 52.10</text>
<text x="494.0" y="110.0" fill="#188a3e" text-anchor="middle" style="font-weight:bold">MBT-10 închis</text>
<text x="24" y="454.0" fill="#188a3e">Legendă: MBT-10 nu se desenează — formula nu e publică (02_mbt10.md:21-39); se marchează doar bara la care</text>
<text x="24" y="471.0" fill="#188a3e">semnalul s-a închis. Nivelurile sunt exemplul construit al sumarizatorului (02_mbt10.md:165-173, 978-984).</text>
<text x="24" y="488.0" fill="#188a3e">bara 9 = fostul vârf 52.10 (locul crimei); bara 21 = vârful nou 58.90.</text>
<text x="24" y="505.0" fill="#188a3e">bara 29 = hammer: o 52.20, h/c 53.40, l 52.10 (codița exact pe locul crimei); bara 30 = ziua închisă în MBT-10.</text>
</svg>

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -0,0 +1,181 @@
"""După gap down — primul MBT-10 te scoate, al doilea merge (exemplu construit).
Citește `gen/strategii/hammer_mbt10_mbt_gapdown.spec.txt`
(marcajul `<!-- DIAGRAMA: mbt_gapdown -->`, Partea B §2.3 Gotchas).
Serie continuă de 33 candele daily, construită din close-urile date în
specificație, cu open-ul fiecărei bare = close-ul precedentei (STYLE.md),
cu două excepții impuse de exemplu:
- bara 13 (ultima dinainte de gap): ca să iasă ȘI ``low[13] = 70.60``
(nivelul de închidere a gap-ului, cerut de assert 1 și 6) ȘI
``high[13] = 71.00`` (structura seriei, „de la ~71"), bara trebuie să
fie un doji ``o = c = 70.60``. De aceea am ridicat ``close[12]`` de la
69.80 (cât dă lista) la 70.60: altfel ``min(open,close)=69.80`` și
low-ul ar ieși 69.80, nu 70.60. Wu=0.40 => high 71.00, wd=0.00 => low
70.60. (Linia „Mecioni" cere wu=0.20 => high 70.80; am ales 71.00,
cifra din structura seriei și din textul „~71".)
- bara 14 (gap down): ``open[14] = 66.40``, NU close[13]=70.60 — singura
bară cu open diferit de close-ul precedent. O construim manual:
o=c=66.40, wu=0.30 => high 66.70, wd=0.30 => low 66.10.
Wick-urile speciale: bara 16 wd=0.20 (low 64.60), bara 18 wd=0.20 (low
63.20), bara 21 wd=0.20 (low 62.00); restul (0.30, 0.40).
Rulează: python3 gen/strategii/hammer_mbt10_mbt_gapdown.py
Scrie: gen/strategii/hammer_mbt10_mbt_gapdown.svg
"""
import re
import sys
from pathlib import Path
sys.path.insert(0, "/workspace/atm-curs-dl")
from svg_candles import Candle, Chart, Level, Mark, from_closes # noqa: E402
OUT = Path(__file__).resolve().parent / "hammer_mbt10_mbt_gapdown.svg"
GRAY, RED, GREEN, BLUE, ORANGE = "#777", "#c00", "#188a3e", "#1a5fb4", "#b06000"
# --- seria: 33 de close-uri, exact ca în specificație ------------------------
# Singura abatere: closes[12] 69.80 -> 70.60, ca bara 13 să poată avea
# low 70.60 (vezi docstring-ul).
closes = [
60.00, 60.80, 61.60, 62.50, 63.30, 64.20, 65.00, 65.80, 66.60, 67.40, # 0-9
68.20, 69.00, 70.60, 70.60, # 10-13
66.40, # 14 = bara de GAP DOWN (open impus 66.40, NU close[13])
65.50, # 15
64.80, # 16 = MBT-10 #1, închidere => intrare #1
64.20, # 17
63.40, # 18 = low 63.20 => stop #1
62.90, # 19
62.50, # 20
62.20, # 21 = MBT-10 #2, închidere => intrare #2
62.80, 63.60, 64.50, 65.50, 66.50, 67.50, 68.50, 69.50, # 22-29
70.60, # 30 = gap închis (TP)
70.90, # 31
70.50, # 32
]
assert len(closes) == 33, len(closes)
WICKS = [(0.30, 0.40)] * 33 # restul barelor
WICKS[13] = (0.40, 0.00) # high 71.00, low 70.60
WICKS[14] = (0.30, 0.30) # high 66.70, low 66.10 (bara de gap)
WICKS[16] = (0.30, 0.20) # low 64.60
WICKS[18] = (0.30, 0.20) # low 63.20
WICKS[21] = (0.30, 0.20) # low 62.00
# Volume relative, alese de generator (materialul nu dă volum aici):
# în scădere pe piciorul coborârii după gap (14 -> 21), apoi recuperare.
vols = [
48, 46, 44, 45, 43, 42, 44, 41, 40, 42, # 0-9
43, 41, 46, 58, # 10-13
90, # 14 = gap down
76, 66, 57, 50, 45, 42, # 15-20
40, # 21 = MBT-10 #2 (minim)
46, 52, 58, 63, 68, 73, 78, 82, 86, 88, 80, # 22-32
]
assert len(vols) == 33, len(vols)
coborare = vols[14:22]
assert all(coborare[i] >= coborare[i + 1] for i in range(len(coborare) - 1)), \
f"volumul nu scade pe piciorul coborârii: {coborare}"
assert vols[-1] > vols[21], "volumul nu recuperează după bara 21"
cds = from_closes(closes, wicks=WICKS, vols=vols, start=60.00)
# bara de gap: open = 66.40, nu close[13] = 70.60
cds[14] = Candle(66.40, 66.70, 66.10, 66.40, vol=vols[14], color=RED)
# --- assert-urile din specificație -----------------------------------------
# 1. gap down real
assert abs(cds[14].o - 66.40) < 1e-9, cds[14].o
assert abs(cds[13].l - 70.60) < 1e-9, f"low[13]={cds[13].l} != 70.60"
assert abs(cds[13].c - 70.60) < 1e-9, cds[13].c
assert cds[14].o < cds[13].l and cds[14].h < cds[13].l, "gapul nu e real"
assert abs(cds[14].o - cds[13].c) > 1e-9, "open[14] nu e excepția de la serie"
# 2. #1
intrare1, sl1 = 64.80, 63.20
assert abs(cds[16].c - intrare1) < 1e-9, f"close[16]={cds[16].c} != 64.80"
assert abs(sl1 - 63.20) < 1e-9
assert min(c.l for c in cds[16:19]) <= sl1, "stopul #1 nu e atins"
assert cds[16].c > sl1, "intrarea #1 e sub stopul ei"
# 3. #2
intrare2, sl2 = 62.20, 60.40
assert abs(cds[21].c - intrare2) < 1e-9, f"close[21]={cds[21].c} != 62.20"
assert abs(sl2 - 60.40) < 1e-9
assert not any(c.l <= sl2 for c in cds[22:]), "un low atinge SL2 după bara 21"
# 4. riscuri
risc1 = intrare1 - sl1 # 1.60
risc2 = intrare2 - sl2 # 1.80
assert abs(risc1 - 1.60) < 1e-9 and abs(risc2 - 1.80) < 1e-9, (risc1, risc2)
# 5. TP = închiderea gap-ului
TP = 70.60
reward2 = TP - intrare2 # 8.40
assert abs(reward2 - 8.40) < 1e-9, reward2
assert round(reward2 / risc2, 1) == 4.7, round(reward2 / risc2, 1)
# 6. low[13] e nivelul de închidere a gap-ului și TP-ul e atins în urcare
assert abs(cds[13].l - TP) < 1e-9, "low[13] != 70.60 (nivelul gap-ului)"
assert max(c.h for c in cds[22:33]) >= TP, "TP 70.60 nu e atins după #2"
primul_tp = next(i for i in range(22, 33) if cds[i].h >= TP)
# 7. același risc în bani: mărimea #2 = mărimea #1 × (risc1 / risc2)
marime1 = 1.0
marime2 = marime1 * (risc1 / risc2)
assert abs(marime2 - 1.60 / 1.80) < 1e-9, marime2
print(f"gap {cds[13].l:.2f} -> {cds[14].o:.2f} "
f"#1 intrare {intrare1:.2f} SL {sl1:.2f} (risc {risc1:.2f}) "
f"#2 intrare {intrare2:.2f} SL {sl2:.2f} (risc {risc2:.2f}) "
f"TP {TP:.2f} = {reward2 / risc2:.1f}R "
f"atins la bara {primul_tp} (high {cds[primul_tp].h:.2f}) "
f"mărime #2 = {marime2:.3f} × mărime #1")
# --- desenul ----------------------------------------------------------------
ch = Chart(
candles=cds,
levels=[
# toate etichetele stau în dreapta (x > ultima candelă), fără suprapuneri
Level(intrare1, "#1 intrare 64.80", GRAY, label_at="right"),
Level(sl1, "#1 SL 63.20", GRAY, label_at="right"),
Level(intrare2, "#2 intrare 62.20", GRAY, label_at="right"),
Level(sl2, "#2 SL 60.40", GRAY, label_at="right"),
Level(TP, "TP 70.60 = 4.7R (gap închis)", BLUE, label_at="right"),
],
marks=[
# decalate pe verticală cât să nu atingă candelele din coborâre
Mark(14, "gap down", RED, "above", dy=-22),
Mark(16, "MBT-10 #1", GRAY, "above", dy=-25),
Mark(18, "stop #1 atins", RED, "below", dy=30),
Mark(21, "MBT-10 #2", GREEN, "below", dy=25),
],
scale=28.0, gap=15, body_w=9, left=44, width=820, vol_h=70,
vol_label="volum",
title="După gap down — primul MBT-10 te scoate, al doilea merge (exemplu construit)",
caption="",
)
svg = ch.render()
assert "<svg" in svg and "\n\n" not in svg, "linie goală în SVG"
# --- blocul de text de jos: cine are același risc + legenda -----------------
m = re.search(r'viewBox="0 0 (\d+) (\d+)"', svg)
W, H = int(m.group(1)), int(m.group(2))
linii = [
"MBT-10 #1: intrare 64.80 / SL 63.20 — risc 1R în bani",
"MBT-10 #2: intrare 62.20 / SL 60.40 — risc 1R în bani (mărime ajustată)",
"Exemplu construit (02_mbt10.md:728-734): #1 și #2 au același risc în bani; "
"#2 singur dă 4.7R.",
"De aceea mărimea se împarte pe 2-3 încercări.",
]
extra = 16 * len(linii) + 6
svg = svg.replace(f'viewBox="0 0 {W} {H}"', f'viewBox="0 0 {W} {H + extra}"')
svg = svg.replace("</svg>", "".join(
f'<text x="24" y="{H + 12 + 16 * i}" fill="#555">{t}</text>'
for i, t in enumerate(linii)) + "</svg>")
assert "\n\n" not in svg, "linie goală în SVG după legendă"
OUT.write_text(svg)
print(f"scris {OUT.relative_to('/workspace/atm-curs-dl')}")

View File

@@ -0,0 +1,118 @@
<svg viewBox="0 0 820 573" xmlns="http://www.w3.org/2000/svg" style="width:100%;height:auto;font:12px sans-serif">
<text x="24" y="48" fill="#333" style="font-weight:bold">După gap down — primul MBT-10 te scoate, al doilea merge (exemplu construit)</text>
<line x1="24" y1="235.2" x2="806" y2="235.2" stroke="#777" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="792" y="229.2" fill="#777" text-anchor="end">#1 intrare 64.80</text>
<line x1="24" y1="280.0" x2="806" y2="280.0" stroke="#777" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="792" y="274.0" fill="#777" text-anchor="end">#1 SL 63.20</text>
<line x1="24" y1="308.0" x2="806" y2="308.0" stroke="#777" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="792" y="302.0" fill="#777" text-anchor="end">#2 intrare 62.20</text>
<line x1="24" y1="358.4" x2="806" y2="358.4" stroke="#777" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="792" y="352.4" fill="#777" text-anchor="end">#2 SL 60.40</text>
<line x1="24" y1="72.8" x2="806" y2="72.8" stroke="#1a5fb4" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="792" y="66.8" fill="#1a5fb4" text-anchor="end">TP 70.60 = 4.7R (gap închis)</text>
<line x1="44.0" y1="361.2" x2="44.0" y2="380.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="39.5" y="369.6" width="9" height="1.6" fill="#188a3e"/>
<line x1="59.0" y1="338.8" x2="59.0" y2="380.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="54.5" y="347.2" width="9" height="22.4" fill="#188a3e"/>
<line x1="74.0" y1="316.4" x2="74.0" y2="358.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="69.5" y="324.8" width="9" height="22.4" fill="#188a3e"/>
<line x1="89.0" y1="291.2" x2="89.0" y2="336.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="84.5" y="299.6" width="9" height="25.2" fill="#188a3e"/>
<line x1="104.0" y1="268.8" x2="104.0" y2="310.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="99.5" y="277.2" width="9" height="22.4" fill="#188a3e"/>
<line x1="119.0" y1="243.6" x2="119.0" y2="288.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="114.5" y="252.0" width="9" height="25.2" fill="#188a3e"/>
<line x1="134.0" y1="221.2" x2="134.0" y2="263.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="129.5" y="229.6" width="9" height="22.4" fill="#188a3e"/>
<line x1="149.0" y1="198.8" x2="149.0" y2="240.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="144.5" y="207.2" width="9" height="22.4" fill="#188a3e"/>
<line x1="164.0" y1="176.4" x2="164.0" y2="218.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="159.5" y="184.8" width="9" height="22.4" fill="#188a3e"/>
<line x1="179.0" y1="154.0" x2="179.0" y2="196.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="174.5" y="162.4" width="9" height="22.4" fill="#188a3e"/>
<line x1="194.0" y1="131.6" x2="194.0" y2="173.6" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="189.5" y="140.0" width="9" height="22.4" fill="#188a3e"/>
<line x1="209.0" y1="109.2" x2="209.0" y2="151.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="204.5" y="117.6" width="9" height="22.4" fill="#188a3e"/>
<line x1="224.0" y1="64.4" x2="224.0" y2="128.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="219.5" y="72.8" width="9" height="44.8" fill="#188a3e"/>
<line x1="239.0" y1="61.6" x2="239.0" y2="72.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="234.5" y="72.8" width="9" height="1.6" fill="#188a3e"/>
<line x1="254.0" y1="182.0" x2="254.0" y2="198.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="249.5" y="190.4" width="9" height="1.6" fill="#c00"/>
<line x1="269.0" y1="182.0" x2="269.0" y2="226.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="264.5" y="190.4" width="9" height="25.2" fill="#c00"/>
<line x1="284.0" y1="207.2" x2="284.0" y2="240.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="279.5" y="215.6" width="9" height="19.6" fill="#c00"/>
<line x1="299.0" y1="226.8" x2="299.0" y2="263.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="294.5" y="235.2" width="9" height="16.8" fill="#c00"/>
<line x1="314.0" y1="243.6" x2="314.0" y2="280.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="309.5" y="252.0" width="9" height="22.4" fill="#c00"/>
<line x1="329.0" y1="266.0" x2="329.0" y2="299.6" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="324.5" y="274.4" width="9" height="14.0" fill="#c00"/>
<line x1="344.0" y1="280.0" x2="344.0" y2="310.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="339.5" y="288.4" width="9" height="11.2" fill="#c00"/>
<line x1="359.0" y1="291.2" x2="359.0" y2="313.6" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="354.5" y="299.6" width="9" height="8.4" fill="#c00"/>
<line x1="374.0" y1="282.8" x2="374.0" y2="319.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="369.5" y="291.2" width="9" height="16.8" fill="#188a3e"/>
<line x1="389.0" y1="260.4" x2="389.0" y2="302.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="384.5" y="268.8" width="9" height="22.4" fill="#188a3e"/>
<line x1="404.0" y1="235.2" x2="404.0" y2="280.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="399.5" y="243.6" width="9" height="25.2" fill="#188a3e"/>
<line x1="419.0" y1="207.2" x2="419.0" y2="254.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="414.5" y="215.6" width="9" height="28.0" fill="#188a3e"/>
<line x1="434.0" y1="179.2" x2="434.0" y2="226.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="429.5" y="187.6" width="9" height="28.0" fill="#188a3e"/>
<line x1="449.0" y1="151.2" x2="449.0" y2="198.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="444.5" y="159.6" width="9" height="28.0" fill="#188a3e"/>
<line x1="464.0" y1="123.2" x2="464.0" y2="170.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="459.5" y="131.6" width="9" height="28.0" fill="#188a3e"/>
<line x1="479.0" y1="95.2" x2="479.0" y2="142.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="474.5" y="103.6" width="9" height="28.0" fill="#188a3e"/>
<line x1="494.0" y1="64.4" x2="494.0" y2="114.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="489.5" y="72.8" width="9" height="30.8" fill="#188a3e"/>
<line x1="509.0" y1="56.0" x2="509.0" y2="84.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="504.5" y="64.4" width="9" height="8.4" fill="#188a3e"/>
<line x1="524.0" y1="56.0" x2="524.0" y2="86.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="519.5" y="64.4" width="9" height="11.2" fill="#c00"/>
<line x1="24" y1="476.8" x2="806" y2="476.8" stroke="#888"/>
<rect x="39.5" y="439.5" width="9" height="37.3" fill="#188a3e" opacity="0.55"/>
<rect x="54.5" y="441.0" width="9" height="35.8" fill="#188a3e" opacity="0.55"/>
<rect x="69.5" y="442.6" width="9" height="34.2" fill="#188a3e" opacity="0.55"/>
<rect x="84.5" y="441.8" width="9" height="35.0" fill="#188a3e" opacity="0.55"/>
<rect x="99.5" y="443.4" width="9" height="33.4" fill="#188a3e" opacity="0.55"/>
<rect x="114.5" y="444.1" width="9" height="32.7" fill="#188a3e" opacity="0.55"/>
<rect x="129.5" y="442.6" width="9" height="34.2" fill="#188a3e" opacity="0.55"/>
<rect x="144.5" y="444.9" width="9" height="31.9" fill="#188a3e" opacity="0.55"/>
<rect x="159.5" y="445.7" width="9" height="31.1" fill="#188a3e" opacity="0.55"/>
<rect x="174.5" y="444.1" width="9" height="32.7" fill="#188a3e" opacity="0.55"/>
<rect x="189.5" y="443.4" width="9" height="33.4" fill="#188a3e" opacity="0.55"/>
<rect x="204.5" y="444.9" width="9" height="31.9" fill="#188a3e" opacity="0.55"/>
<rect x="219.5" y="441.0" width="9" height="35.8" fill="#188a3e" opacity="0.55"/>
<rect x="234.5" y="431.7" width="9" height="45.1" fill="#188a3e" opacity="0.55"/>
<rect x="249.5" y="406.8" width="9" height="70.0" fill="#c00" opacity="0.55"/>
<rect x="264.5" y="417.7" width="9" height="59.1" fill="#c00" opacity="0.55"/>
<rect x="279.5" y="425.5" width="9" height="51.3" fill="#c00" opacity="0.55"/>
<rect x="294.5" y="432.5" width="9" height="44.3" fill="#c00" opacity="0.55"/>
<rect x="309.5" y="437.9" width="9" height="38.9" fill="#c00" opacity="0.55"/>
<rect x="324.5" y="441.8" width="9" height="35.0" fill="#c00" opacity="0.55"/>
<rect x="339.5" y="444.1" width="9" height="32.7" fill="#c00" opacity="0.55"/>
<rect x="354.5" y="445.7" width="9" height="31.1" fill="#c00" opacity="0.55"/>
<rect x="369.5" y="441.0" width="9" height="35.8" fill="#188a3e" opacity="0.55"/>
<rect x="384.5" y="436.4" width="9" height="40.4" fill="#188a3e" opacity="0.55"/>
<rect x="399.5" y="431.7" width="9" height="45.1" fill="#188a3e" opacity="0.55"/>
<rect x="414.5" y="427.8" width="9" height="49.0" fill="#188a3e" opacity="0.55"/>
<rect x="429.5" y="423.9" width="9" height="52.9" fill="#188a3e" opacity="0.55"/>
<rect x="444.5" y="420.0" width="9" height="56.8" fill="#188a3e" opacity="0.55"/>
<rect x="459.5" y="416.1" width="9" height="60.7" fill="#188a3e" opacity="0.55"/>
<rect x="474.5" y="413.0" width="9" height="63.8" fill="#188a3e" opacity="0.55"/>
<rect x="489.5" y="409.9" width="9" height="66.9" fill="#188a3e" opacity="0.55"/>
<rect x="504.5" y="408.4" width="9" height="68.4" fill="#188a3e" opacity="0.55"/>
<rect x="519.5" y="414.6" width="9" height="62.2" fill="#c00" opacity="0.55"/>
<text x="24" y="400.8" fill="#555">volum</text>
<text x="254.0" y="150.0" fill="#c00" text-anchor="middle" style="font-weight:bold">gap down</text>
<text x="284.0" y="172.2" fill="#777" text-anchor="middle" style="font-weight:bold">MBT-10 #1</text>
<text x="314.0" y="330.0" fill="#c00" text-anchor="middle" style="font-weight:bold">stop #1 atins</text>
<text x="359.0" y="358.6" fill="#188a3e" text-anchor="middle" style="font-weight:bold">MBT-10 #2</text>
<text x="24" y="515" fill="#555">MBT-10 #1: intrare 64.80 / SL 63.20 — risc 1R în bani</text><text x="24" y="531" fill="#555">MBT-10 #2: intrare 62.20 / SL 60.40 — risc 1R în bani (mărime ajustată)</text><text x="24" y="547" fill="#555">Exemplu construit (02_mbt10.md:728-734): #1 și #2 au același risc în bani; #2 singur dă 4.7R.</text><text x="24" y="563" fill="#555">De aceea mărimea se împarte pe 2-3 încercări.</text></svg>

After

Width:  |  Height:  |  Size: 9.9 KiB

View File

@@ -0,0 +1,151 @@
"""Diagrama din `gen/strategii/hammer_mbt10_mbt_tesla.spec.txt` (TIP: PRET).
Exemplul real Tesla din Q&A (`03_2026-08-11.md:118-120`): trigger MBT-10 după
o cădere anterioară, intrare la închiderea zilei la 311.21, ieșire la 333.00
șase zile mai târziu = +7.0 % pe acțiune.
Reale: 311.21 / 333.00 / +7.0 % și contextul calitativ. Construită de mine:
structura barelor dintre cele două niveluri. SL-ul lipsește din material, deci
NU se trasează nicio linie de stop (spec, „LIPSĂ").
Serie continuă de 25 de candele daily din lista de close-uri din specificație,
open = close-ul precedentului, wick-uri implicite (0.50, 0.60), fără panou de
volum (materialul nu discută volumul exemplului). Legenda e spartă pe trei
rânduri și lipește sub grafic, cu viewBox extins.
"""
import re
import sys
from pathlib import Path
sys.path.insert(0, "/workspace/atm-curs-dl")
from svg_candles import Chart, Level, Mark, from_closes # noqa: E402
OUT = Path(__file__).resolve().parent / "hammer_mbt10_mbt_tesla.svg"
# ── cifrele reale din material (`03_2026-08-11.md:118-120`) ─────────────────
INTRARE = 311.21 # intrarea la închiderea zilei
REZULTAT = 333.00 # six zile mai târziu
ZILE = 6 # (21 - 15)
PROCENT = 7.0 # +7.0 % pe acțiune
I_INTRARE = 15 # candela de intrare (trigger MBT-10)
I_REZULTAT = 21
# ── seria: 25 de close-uri, exact ca în specificație ───────────────────────
closes = [
360.00, 356.00, 358.00, 352.00, 348.00, 349.00, 343.00, 338.00, 340.00,
334.00, 329.00, 331.00, 325.00, 320.00, 315.00,
311.21, # 15 = trigger MBT-10, intrare la închiderea zilei
314.00, 318.00, 322.00, 326.00, 330.00,
333.00, # 21 = 6 bare după intrare
331.00, 328.00, 330.00,
]
assert len(closes) == 25, len(closes)
# wick-uri implicite pentru toate barele
wicks = [(0.50, 0.60)] * len(closes)
cds = from_closes(closes, wicks=wicks, start=closes[0])
# ── assert-urile cerute de specificație ────────────────────────────────────
# 1. close[15] == 311.21 == intrare (la închiderea zilei)
assert abs(cds[I_INTRARE].c - 311.21) < 1e-9, cds[I_INTRARE].c
assert abs(cds[I_INTRARE].c - INTRARE) < 1e-9, (cds[I_INTRARE].c, INTRARE)
# 2. close[21] == 333.00 și (21 - 15) == 6
assert abs(cds[I_REZULTAT].c - 333.00) < 1e-9, cds[I_REZULTAT].c
assert (I_REZULTAT - I_INTRARE) == 6, I_REZULTAT - I_INTRARE
# 3. procentul REAL
pct = round((cds[I_REZULTAT].c / cds[I_INTRARE].c - 1) * 100, 1)
assert pct == 7.0, pct
# 4. cădere anterioară: close[15] < close[0] - 40
assert cds[I_INTRARE].c < cds[0].c - 40, (cds[I_INTRARE].c, cds[0].c)
# 5. nu se trasează linie de SL; în structură nu apare niciun nivel de stop
# (verificat mai jos, după construirea listelor de niveluri/marcaje)
# 6. entry < exit, iar exit e atins după exact 6 bare (nu mai devreme)
assert cds[I_INTRARE].c < cds[I_REZULTAT].c
for i in range(I_INTRARE + 1, I_REZULTAT):
assert cds[i].c < REZULTAT, f"rezultatul 333.00 e atins deja la bara {i}"
assert cds[I_REZULTAT].c == REZULTAT
# serie continuă: open-ul fiecărei bare = close-ul precedentului
for a, b in zip(cds, cds[1:]):
assert abs(a.c - b.o) < 1e-9, "serie cu gol artificial între bare"
# ── desenul ────────────────────────────────────────────────────────────────
levels = [
Level(INTRARE, "intrare 311.21 (close)", "#188a3e", label_at="left"),
Level(REZULTAT, "rezultat 333.00 = +7.0 % în 6 zile", "#1a5fb4"),
]
marks = [
Mark(I_INTRARE, "trigger MBT-10", "#188a3e", "below"),
Mark(I_REZULTAT, "exact 6 bare", "#1a5fb4", "above", dy=-16),
]
# 5. niciun nivel/marcaj de stop în structură
assert not any("stop" in t.lower() for t in
[lv.text for lv in levels] + [mk.text for mk in marks])
assert not any(t.strip().upper().startswith("SL") for t in
[lv.text for lv in levels] + [mk.text for mk in marks])
ch = Chart(
candles=cds,
levels=levels,
marks=marks,
scale=7.5, gap=15, body_w=9, left=44, width=620,
title="Tesla — daily: trigger MBT-10, intrare 311.21 → 333.00 = +7.0 % în 6 zile",
caption="", # legenda obligatorie e pusă manual, pe rânduri, cu viewBox extins
)
svg = ch.render()
assert "<svg" in svg and svg.count("\n\n") == 0, "linie goală în SVG"
# ── legenda obligatorie, spartă ca să nu iasă din pânză ────────────────────
m = re.search(r'viewBox="0 0 (\d+) (\d+)"', svg)
W, H = int(m.group(1)), int(m.group(2))
legenda = [
"Cifrele 311.21 / 333.00 / +7.0 % sunt reale (03_2026-08-11.md:118-120);",
"structura barelor dintre ele e construită.",
"SL: materialul nu îl dă pentru acest exemplu.",
]
# fiecare rând încape în pânză (≈6 px/caracter la 12px)
for rand in legenda:
assert 24 + len(rand) * 6.0 <= W - 10, f"legenda iese din pânză: {rand!r}"
extra = 16 * len(legenda) + 6
svg = svg.replace(f'viewBox="0 0 {W} {H}"', f'viewBox="0 0 {W} {H + extra}"')
svg = svg.replace("</svg>", "".join(
f'<text x="24" y="{H + 12 + 16 * i}" fill="#555">{t}</text>'
for i, t in enumerate(legenda)) + "</svg>")
assert "\n\n" not in svg, "linie goală în SVG după legendă"
# legenda completă există, rând cu rând
for rand in legenda:
assert rand in svg, f"lipsește din legendă: {rand!r}"
# ── verificare: fiecare etichetă e în pânză și nu se suprapune alta ────────
texte = re.findall(r'<text x="([\d.]+)" y="([\d.]+)"([^>]*)>([^<]*)</text>', svg)
cutii = []
for xs, ys, attrs, continut in texte:
x, y = float(xs), float(ys)
w = len(continut) * (7.44 if "bold" in attrs else 6.72)
if 'text-anchor="end"' in attrs:
x0 = x - w
elif 'text-anchor="middle"' in attrs:
x0 = x - w / 2
else:
x0 = x
assert -2 <= x0 and x0 + w <= W + 2, f"etichetă în afara pânzei: {continut!r}"
assert 0 <= y <= H + extra, f"etichetă în afara înălțimii: {continut!r}"
cutii.append((x0, x0 + w, y, continut))
for i in range(len(cutii)):
for j in range(i + 1, len(cutii)):
a, b = cutii[i], cutii[j]
if abs(a[2] - b[2]) < 13 and a[0] < b[1] - 2 and b[0] < a[1] - 2:
raise AssertionError(f"texte suprapuse: {a[3]!r} / {b[3]!r}")
OUT.write_text(svg, encoding="utf-8")
print(f"serie {len(cds)} candele | intrare {INTRARE:.2f} @ bara {I_INTRARE} | "
f"rezultat {REZULTAT:.2f} @ bara {I_REZULTAT} (+{pct:.1f} % în "
f"{I_REZULTAT - I_INTRARE} zile, atins nu mai devreme)")
print(f"scris {OUT}")

View File

@@ -0,0 +1,59 @@
<svg viewBox="0 0 620 510" xmlns="http://www.w3.org/2000/svg" style="width:100%;height:auto;font:12px sans-serif">
<text x="24" y="48" fill="#333" style="font-weight:bold">Tesla — daily: trigger MBT-10, intrare 311.21 → 333.00 = +7.0 % în 6 zile</text>
<line x1="24" y1="425.7" x2="606" y2="425.7" stroke="#188a3e" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="30" y="419.7" fill="#188a3e">intrare 311.21 (close)</text>
<line x1="24" y1="262.2" x2="606" y2="262.2" stroke="#1a5fb4" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="592" y="256.2" fill="#1a5fb4" text-anchor="end">rezultat 333.00 = +7.0 % în 6 zile</text>
<line x1="44.0" y1="56.0" x2="44.0" y2="64.3" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="39.5" y="59.8" width="9" height="1.6" fill="#188a3e"/>
<line x1="59.0" y1="56.0" x2="59.0" y2="94.3" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="54.5" y="59.8" width="9" height="30.0" fill="#c00"/>
<line x1="74.0" y1="71.0" x2="74.0" y2="94.3" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="69.5" y="74.8" width="9" height="15.0" fill="#188a3e"/>
<line x1="89.0" y1="71.0" x2="89.0" y2="124.3" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="84.5" y="74.8" width="9" height="45.0" fill="#c00"/>
<line x1="104.0" y1="116.0" x2="104.0" y2="154.3" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="99.5" y="119.8" width="9" height="30.0" fill="#c00"/>
<line x1="119.0" y1="138.5" x2="119.0" y2="154.3" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="114.5" y="142.2" width="9" height="7.5" fill="#188a3e"/>
<line x1="134.0" y1="138.5" x2="134.0" y2="191.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="129.5" y="142.2" width="9" height="45.0" fill="#c00"/>
<line x1="149.0" y1="183.5" x2="149.0" y2="229.3" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="144.5" y="187.2" width="9" height="37.5" fill="#c00"/>
<line x1="164.0" y1="206.0" x2="164.0" y2="229.3" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="159.5" y="209.8" width="9" height="15.0" fill="#188a3e"/>
<line x1="179.0" y1="206.0" x2="179.0" y2="259.3" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="174.5" y="209.8" width="9" height="45.0" fill="#c00"/>
<line x1="194.0" y1="251.0" x2="194.0" y2="296.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="189.5" y="254.8" width="9" height="37.5" fill="#c00"/>
<line x1="209.0" y1="273.5" x2="209.0" y2="296.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="204.5" y="277.2" width="9" height="15.0" fill="#188a3e"/>
<line x1="224.0" y1="273.5" x2="224.0" y2="326.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="219.5" y="277.2" width="9" height="45.0" fill="#c00"/>
<line x1="239.0" y1="318.5" x2="239.0" y2="364.3" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="234.5" y="322.2" width="9" height="37.5" fill="#c00"/>
<line x1="254.0" y1="356.0" x2="254.0" y2="401.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="249.5" y="359.8" width="9" height="37.5" fill="#c00"/>
<line x1="269.0" y1="393.5" x2="269.0" y2="430.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="264.5" y="397.2" width="9" height="28.4" fill="#c00"/>
<line x1="284.0" y1="401.0" x2="284.0" y2="430.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="279.5" y="404.8" width="9" height="20.9" fill="#188a3e"/>
<line x1="299.0" y1="371.0" x2="299.0" y2="409.3" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="294.5" y="374.8" width="9" height="30.0" fill="#188a3e"/>
<line x1="314.0" y1="341.0" x2="314.0" y2="379.3" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="309.5" y="344.8" width="9" height="30.0" fill="#188a3e"/>
<line x1="329.0" y1="311.0" x2="329.0" y2="349.3" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="324.5" y="314.8" width="9" height="30.0" fill="#188a3e"/>
<line x1="344.0" y1="281.0" x2="344.0" y2="319.3" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="339.5" y="284.8" width="9" height="30.0" fill="#188a3e"/>
<line x1="359.0" y1="258.5" x2="359.0" y2="289.3" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="354.5" y="262.2" width="9" height="22.5" fill="#188a3e"/>
<line x1="374.0" y1="258.5" x2="374.0" y2="281.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="369.5" y="262.2" width="9" height="15.0" fill="#c00"/>
<line x1="389.0" y1="273.5" x2="389.0" y2="304.3" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="384.5" y="277.2" width="9" height="22.5" fill="#c00"/>
<line x1="404.0" y1="281.0" x2="404.0" y2="304.3" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="399.5" y="284.8" width="9" height="15.0" fill="#188a3e"/>
<text x="269.0" y="444.2" fill="#188a3e" text-anchor="middle" style="font-weight:bold">trigger MBT-10</text>
<text x="359.0" y="232.5" fill="#1a5fb4" text-anchor="middle" style="font-weight:bold">exact 6 bare</text>
<text x="24" y="468" fill="#555">Cifrele 311.21 / 333.00 / +7.0 % sunt reale (03_2026-08-11.md:118-120);</text><text x="24" y="484" fill="#555">structura barelor dintre ele e construită.</text><text x="24" y="500" fill="#555">SL: materialul nu îl dă pentru acest exemplu.</text></svg>

After

Width:  |  Height:  |  Size: 4.9 KiB

View File

@@ -0,0 +1,136 @@
"""Diagrama de ansamblu M2D (strategii/03_m2d.md, marcajul `ansamblu`).
„Setup M2D LONG complet pe TF mic (3 min), cu TF mare (15 min) în Buy":
urcare -> pullback (M = Sell Momentum) -> trigger (A = primul Buy) -> intrare
-> SL de 4 candele -> TP1 la 1:1 -> continuare -> vârf -> ieșire pe bull trail.
Toate close-urile, wick-urile, volumele și nivelurile vin din
`gen/strategii/m2d_ansamblu.spec.txt`. Seria e construită (exemplu generic,
fără ticker), la scara 1.00 $ risc / 2.2R randament — nu e un trade real.
Rulează:
python3 gen/strategii/m2d_ansamblu.py
Scrie lângă el `m2d_ansamblu.svg`.
"""
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
OUT = Path(__file__).resolve().parent / "m2d_ansamblu.svg"
GRAY, RED, GREEN, BLUE, ORANGE = "#777", "#c00", "#188a3e", "#1a5fb4", "#b06000"
# ---------------------------------------------------------------- seria (32 bare)
CLOSES = [
96.8, 97.2, 97.7, 98.1, 98.6, 99.1, 99.5, 100.0, # 0-7 urcare
99.8, 99.4, 99.0, 99.2, 98.9, 99.15, 99.3, # 8-14 pullback (M)
99.8, 100.2, # 15-16 trigger (A)
100.7, 101.2, 101.6, 102.0, 102.4, 102.7, 103.0, # 17-23 continuare
103.3, 103.1, 102.8, 102.7, 102.9, 102.5, 102.2, 102.0, # 24-31 vârf + violare
]
assert len(CLOSES) == 32, len(CLOSES)
# fitile (sus, jos); jos mai mari pe 10-12, ca minimul de 4 candele (11:15) să
# fie clar sub intrare, la exact 98.80 (candela 12).
WICKS = (
[(0.15, 0.10)] * 8 # 0-7
+ [(0.10, 0.15), (0.10, 0.20), (0.10, 0.15), # 8-10
(0.10, 0.15), (0.10, 0.10), (0.10, 0.05), (0.10, 0.10)] # 11-14
+ [(0.15, 0.10), (0.15, 0.10)] # 15-16
+ [(0.15, 0.10)] * 7 # 17-23
+ [(0.20, 0.10), (0.10, 0.15), (0.10, 0.15), (0.12, 0.15), # 24-27
(0.10, 0.15), (0.10, 0.15), (0.10, 0.15), (0.10, 0.20)] # 28-31
)
assert len(WICKS) == 32, len(WICKS)
VOLS = (
[62, 66, 60, 68, 64, 70, 66, 74] # 0-7 trend
+ [44, 40, 38, 36, 34, 33, 35] # 8-14 pullback mic
+ [82, 88] # 15-16 trigger
+ [72, 70, 74, 68, 72, 66, 70] # 17-23 trend
+ [76, 64, 60, 58, 62, 56, 54, 66] # 24-31 vârf + ieșire
)
assert len(VOLS) == 32, len(VOLS)
cds = from_closes(CLOSES, wicks=WICKS, vols=VOLS, start=96.4)
# ---------------------------------------------------------------- assert-urile din spec
entry = cds[15].c # 1. intrarea = close-ul trigger-ului
SL = min(c.l for c in cds[11:15]) # 2. minimul ultimelor 4 dinainte
TP1 = entry + (entry - SL) # 4. TP1 exact 1:1
assert entry == round(cds[15].c, 2), (entry, cds[15].c)
assert SL == min(c.l for c in cds[11:15]), SL
assert SL < entry, (SL, entry)
assert abs(TP1 - (entry + (entry - SL))) < 1e-6
for a, b in zip(cds, cds[1:]): # 5. serie continuă, fără goluri
assert a.c == b.o, (a.c, b.o)
assert cds[15].c > cds[14].c # 6. trigger în sus după pullback
assert any(c.c < c.o for c in cds[8:15]) # 7. pullback = Sell
assert any(c.c > c.o for c in cds[0:8]) # context în sus
exit_price = cds[31].c
assert exit_price > entry # 8. ieșirea e peste intrare
# bull trail = low-ul ultimei candele care a închis peste high-ul precedentei
qual = [i for i in range(1, 31) if cds[i].c > cds[i - 1].h]
assert qual and qual[-1] == 24, qual
bull = cds[24].l
assert cds[31].c < bull, (cds[31].c, bull) # finala închide sub linie = ieșire aici
r_mult = (exit_price - entry) / (entry - SL) # 9. R-ul afișat
assert round(r_mult, 1) == 2.2, r_mult
# ---------------------------------------------------------------- niveluri, etichete, marks
levels = [
Level(entry, f"intrare {entry:.2f}", GREEN, label_at="left"),
Level(SL, f"SL {SL:.2f} (4 candele)", RED, label_at="right"),
Level(TP1, f"TP1 {TP1:.2f} = 1:1", BLUE, label_at="right"),
]
assert [round(lv.price, 2) for lv in levels] == [99.80, 98.80, 100.80]
exit_text = f"ieșire {exit_price:.2f} pe bull trail ({r_mult:.1f}R)"
assert f"{r_mult:.1f}R" in exit_text
marks = [
Mark(0, "TF mare (15 min) în Buy", BLUE, "above", dy=-227),
Mark(11, "M: Sell Momentum (pullback)", RED, "below"),
Mark(15, "A: primul Buy = trigger", GREEN, "above", dy=-122),
# „vârf" stă sub candela 24: deasupra ei, orice text de la y-ul vârfului se
# calcă pe titlul din stânga-sus (ambele pornesc din `top`).
Mark(24, "vârf 103.30", GRAY, "below", dy=25),
Mark(31, "close sub bull trail = ieși", RED, "below", dy=55),
Mark(31, exit_text, RED, "below", dy=73),
]
caption = (f"Serie construită (32 bare, 3 min). R = ({exit_price:.2f} − {entry:.2f}) / "
f"{entry - SL:.2f} = {r_mult:.1f}R.")
assert f"{r_mult:.1f}R" in caption
ch = Chart(
candles=cds, levels=levels, marks=marks,
mas=[MA(20, ORANGE, dash="4 3", text="SMA-20")],
scale=52.0, gap=16, body_w=9, left=44, width=720, vol_h=70,
vol_label="volum",
title="Simbol construit — 3 min (TF mare 15 min în Buy)",
caption=caption,
)
svg = ch.render()
# bull trail: nu e un `Level` (spec-ul cere exact trei niveluri), deci se
# injectează ca linie separată, la aceeași scară (`ch._y`).
yb = ch._y(bull)
bull_layer = (
f'<line x1="24" y1="{yb:.1f}" x2="{ch.width - 14}" y2="{yb:.1f}" '
f'stroke="{ORANGE}" stroke-width="1.5" stroke-dasharray="2 3"/>'
f'<text x="{ch.width - 28}" y="{yb - 6:.1f}" fill="{ORANGE}" '
f'text-anchor="end">bull trail {bull:.2f}</text>'
)
i = svg.index(">") + 1
svg = svg[:i] + "\n " + bull_layer + svg[i:]
assert svg.count("\n\n") == 0, "linie goală în SVG rupe blocul din markdown"
OUT.write_text(svg)
print(f"scris {OUT.name}: {len(cds)} candele, intrare {entry:.2f}, SL {SL:.2f}, "
f"TP1 {TP1:.2f}, ieșire {exit_price:.2f} = {r_mult:.1f}R, bull trail {bull:.2f}")

View File

@@ -0,0 +1,117 @@
<svg viewBox="0 0 720 568" xmlns="http://www.w3.org/2000/svg" style="width:100%;height:auto;font:12px sans-serif">
<line x1="24" y1="87.2" x2="706" y2="87.2" stroke="#b06000" stroke-width="1.5" stroke-dasharray="2 3"/><text x="692" y="81.2" fill="#b06000" text-anchor="end">bull trail 102.90</text>
<text x="24" y="48" fill="#333" style="font-weight:bold">Simbol construit — 3 min (TF mare 15 min în Buy)</text>
<line x1="24" y1="248.4" x2="706" y2="248.4" stroke="#188a3e" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="30" y="242.4" fill="#188a3e">intrare 99.80</text>
<line x1="24" y1="300.4" x2="706" y2="300.4" stroke="#c00" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="692" y="294.4" fill="#c00" text-anchor="end">SL 98.80 (4 candele)</text>
<line x1="24" y1="196.4" x2="706" y2="196.4" stroke="#1a5fb4" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="692" y="190.4" fill="#1a5fb4" text-anchor="end">TP1 100.80 = 1:1</text>
<polyline points="348.0,276.3 364.0,262.8 380.0,249.3 396.0,236.3 412.0,223.6 428.0,211.3 444.0,200.9 460.0,192.4 476.0,185.3 492.0,177.3 508.0,169.2 524.0,160.9 540.0,153.6" fill="none" stroke="#b06000" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="546" y="148" fill="#b06000">SMA-20</text>
<line x1="44.0" y1="396.6" x2="44.0" y2="430.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="39.5" y="404.4" width="9" height="20.8" fill="#188a3e"/>
<line x1="60.0" y1="375.8" x2="60.0" y2="409.6" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="55.5" y="383.6" width="9" height="20.8" fill="#188a3e"/>
<line x1="76.0" y1="349.8" x2="76.0" y2="388.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="71.5" y="357.6" width="9" height="26.0" fill="#188a3e"/>
<line x1="92.0" y1="329.0" x2="92.0" y2="362.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="87.5" y="336.8" width="9" height="20.8" fill="#188a3e"/>
<line x1="108.0" y1="303.0" x2="108.0" y2="342.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="103.5" y="310.8" width="9" height="26.0" fill="#188a3e"/>
<line x1="124.0" y1="277.0" x2="124.0" y2="316.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="119.5" y="284.8" width="9" height="26.0" fill="#188a3e"/>
<line x1="140.0" y1="256.2" x2="140.0" y2="290.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="135.5" y="264.0" width="9" height="20.8" fill="#188a3e"/>
<line x1="156.0" y1="230.2" x2="156.0" y2="269.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="151.5" y="238.0" width="9" height="26.0" fill="#188a3e"/>
<line x1="172.0" y1="232.8" x2="172.0" y2="256.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="167.5" y="238.0" width="9" height="10.4" fill="#c00"/>
<line x1="188.0" y1="243.2" x2="188.0" y2="279.6" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="183.5" y="248.4" width="9" height="20.8" fill="#c00"/>
<line x1="204.0" y1="264.0" x2="204.0" y2="297.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="199.5" y="269.2" width="9" height="20.8" fill="#c00"/>
<line x1="220.0" y1="274.4" x2="220.0" y2="297.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="215.5" y="279.6" width="9" height="10.4" fill="#188a3e"/>
<line x1="236.0" y1="274.4" x2="236.0" y2="300.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="231.5" y="279.6" width="9" height="15.6" fill="#c00"/>
<line x1="252.0" y1="277.0" x2="252.0" y2="297.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="247.5" y="282.2" width="9" height="13.0" fill="#188a3e"/>
<line x1="268.0" y1="269.2" x2="268.0" y2="287.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="263.5" y="274.4" width="9" height="7.8" fill="#188a3e"/>
<line x1="284.0" y1="240.6" x2="284.0" y2="279.6" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="279.5" y="248.4" width="9" height="26.0" fill="#188a3e"/>
<line x1="300.0" y1="219.8" x2="300.0" y2="253.6" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="295.5" y="227.6" width="9" height="20.8" fill="#188a3e"/>
<line x1="316.0" y1="193.8" x2="316.0" y2="232.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="311.5" y="201.6" width="9" height="26.0" fill="#188a3e"/>
<line x1="332.0" y1="167.8" x2="332.0" y2="206.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="327.5" y="175.6" width="9" height="26.0" fill="#188a3e"/>
<line x1="348.0" y1="147.0" x2="348.0" y2="180.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="343.5" y="154.8" width="9" height="20.8" fill="#188a3e"/>
<line x1="364.0" y1="126.2" x2="364.0" y2="160.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="359.5" y="134.0" width="9" height="20.8" fill="#188a3e"/>
<line x1="380.0" y1="105.4" x2="380.0" y2="139.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="375.5" y="113.2" width="9" height="20.8" fill="#188a3e"/>
<line x1="396.0" y1="89.8" x2="396.0" y2="118.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="391.5" y="97.6" width="9" height="15.6" fill="#188a3e"/>
<line x1="412.0" y1="74.2" x2="412.0" y2="102.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="407.5" y="82.0" width="9" height="15.6" fill="#188a3e"/>
<line x1="428.0" y1="56.0" x2="428.0" y2="87.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="423.5" y="66.4" width="9" height="15.6" fill="#188a3e"/>
<line x1="444.0" y1="61.2" x2="444.0" y2="84.6" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="439.5" y="66.4" width="9" height="10.4" fill="#c00"/>
<line x1="460.0" y1="71.6" x2="460.0" y2="100.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="455.5" y="76.8" width="9" height="15.6" fill="#c00"/>
<line x1="476.0" y1="86.2" x2="476.0" y2="105.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="471.5" y="92.4" width="9" height="5.2" fill="#c00"/>
<line x1="492.0" y1="82.0" x2="492.0" y2="105.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="487.5" y="87.2" width="9" height="10.4" fill="#188a3e"/>
<line x1="508.0" y1="82.0" x2="508.0" y2="115.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="503.5" y="87.2" width="9" height="20.8" fill="#c00"/>
<line x1="524.0" y1="102.8" x2="524.0" y2="131.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="519.5" y="108.0" width="9" height="15.6" fill="#c00"/>
<line x1="540.0" y1="118.4" x2="540.0" y2="144.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="535.5" y="123.6" width="9" height="10.4" fill="#c00"/>
<line x1="24" y1="526.4" x2="706" y2="526.4" stroke="#888"/>
<rect x="39.5" y="477.1" width="9" height="49.3" fill="#188a3e" opacity="0.55"/>
<rect x="55.5" y="473.9" width="9" height="52.5" fill="#188a3e" opacity="0.55"/>
<rect x="71.5" y="478.7" width="9" height="47.7" fill="#188a3e" opacity="0.55"/>
<rect x="87.5" y="472.3" width="9" height="54.1" fill="#188a3e" opacity="0.55"/>
<rect x="103.5" y="475.5" width="9" height="50.9" fill="#188a3e" opacity="0.55"/>
<rect x="119.5" y="470.7" width="9" height="55.7" fill="#188a3e" opacity="0.55"/>
<rect x="135.5" y="473.9" width="9" height="52.5" fill="#188a3e" opacity="0.55"/>
<rect x="151.5" y="467.5" width="9" height="58.9" fill="#188a3e" opacity="0.55"/>
<rect x="167.5" y="491.4" width="9" height="35.0" fill="#c00" opacity="0.55"/>
<rect x="183.5" y="494.6" width="9" height="31.8" fill="#c00" opacity="0.55"/>
<rect x="199.5" y="496.2" width="9" height="30.2" fill="#c00" opacity="0.55"/>
<rect x="215.5" y="497.8" width="9" height="28.6" fill="#188a3e" opacity="0.55"/>
<rect x="231.5" y="499.4" width="9" height="27.0" fill="#c00" opacity="0.55"/>
<rect x="247.5" y="500.1" width="9" height="26.2" fill="#188a3e" opacity="0.55"/>
<rect x="263.5" y="498.6" width="9" height="27.8" fill="#188a3e" opacity="0.55"/>
<rect x="279.5" y="461.2" width="9" height="65.2" fill="#188a3e" opacity="0.55"/>
<rect x="295.5" y="456.4" width="9" height="70.0" fill="#188a3e" opacity="0.55"/>
<rect x="311.5" y="469.1" width="9" height="57.3" fill="#188a3e" opacity="0.55"/>
<rect x="327.5" y="470.7" width="9" height="55.7" fill="#188a3e" opacity="0.55"/>
<rect x="343.5" y="467.5" width="9" height="58.9" fill="#188a3e" opacity="0.55"/>
<rect x="359.5" y="472.3" width="9" height="54.1" fill="#188a3e" opacity="0.55"/>
<rect x="375.5" y="469.1" width="9" height="57.3" fill="#188a3e" opacity="0.55"/>
<rect x="391.5" y="473.9" width="9" height="52.5" fill="#188a3e" opacity="0.55"/>
<rect x="407.5" y="470.7" width="9" height="55.7" fill="#188a3e" opacity="0.55"/>
<rect x="423.5" y="465.9" width="9" height="60.5" fill="#188a3e" opacity="0.55"/>
<rect x="439.5" y="475.5" width="9" height="50.9" fill="#c00" opacity="0.55"/>
<rect x="455.5" y="478.7" width="9" height="47.7" fill="#c00" opacity="0.55"/>
<rect x="471.5" y="480.3" width="9" height="46.1" fill="#c00" opacity="0.55"/>
<rect x="487.5" y="477.1" width="9" height="49.3" fill="#188a3e" opacity="0.55"/>
<rect x="503.5" y="481.9" width="9" height="44.5" fill="#c00" opacity="0.55"/>
<rect x="519.5" y="483.4" width="9" height="43.0" fill="#c00" opacity="0.55"/>
<rect x="535.5" y="473.9" width="9" height="52.5" fill="#c00" opacity="0.55"/>
<text x="24" y="450.4" fill="#555">volum</text>
<text x="32.0" y="159.6" fill="#1a5fb4" text-anchor="start" style="font-weight:bold">TF mare (15 min) în Buy</text>
<text x="220.0" y="317.8" fill="#c00" text-anchor="middle" style="font-weight:bold">M: Sell Momentum (pullback)</text>
<text x="284.0" y="108.6" fill="#188a3e" text-anchor="middle" style="font-weight:bold">A: primul Buy = trigger</text>
<text x="428.0" y="132.2" fill="#777" text-anchor="middle" style="font-weight:bold">vârf 103.30</text>
<text x="540.0" y="219.4" fill="#c00" text-anchor="middle" style="font-weight:bold">close sub bull trail = ieși</text>
<text x="540.0" y="237.4" fill="#c00" text-anchor="middle" style="font-weight:bold">ieșire 102.00 pe bull trail (2.2R)</text>
<text x="24" y="556" fill="#555">Serie construită (32 bare, 3 min). R = (102.00 − 99.80) / 1.00 = 2.2R.</text>
</svg>

After

Width:  |  Height:  |  Size: 9.7 KiB

View File

@@ -0,0 +1,289 @@
"""Diagrama din `gen/strategii/m2d_exemplu.spec.txt` (TIP: PRET).
Exemplul real Riot din curs (`14_strategia_1_m2d.md:227-236`): SHORT pe TF mic
3 min, cu TF mare 15 min în Sell. Pullback-ul Buy de pe TF mic (M) e urmat de
primul Sell (A = trigger); SL = cel mai sus high din 4 candele = +0.85%,
TP1 = 1:1 = -0.85%, iar ieșirea se face pe violarea bear trail-ului la +4.55%
(risc/recompensă efectiv ≈ 1:5.3).
Reale din curs: procentele 0.85% / 1.42% / 4.55% și contextul calitativ.
Construită de mine: axa de preț absolută, intrarea fixată la 100.00 ca reper.
Legenda spune explicit lucrul ăsta.
Serie continuă de 30 de candele din lista de close-uri din specificație,
open = close-ul precedentului, wick-uri alese ca să scoată exact nivelurile
(candela 13: high 100.85 = SL), panou de volum dedesubt.
"""
import re
import sys
from pathlib import Path
sys.path.insert(0, "/workspace/atm-curs-dl")
from svg_candles import Chart, Level, Mark, from_closes # noqa: E402
OUT = Path(__file__).resolve().parent / "m2d_exemplu.svg"
# ── nivelurile cerute de specificație ──────────────────────────────────────
ENTRY = 100.00 # close-ul candelei de trigger 14
SL = 100.85 # max high pe candelele 11:15 = +0.85%
TP1 = 99.15 # entry - 0.85% (1:1)
EXIT = 95.45 # close-ul candelei 29 = +4.55%
# ── seria: 30 de close-uri, exact ca în specificație ───────────────────────
closes = [
97.6, 97.1, 96.7, 96.3, 95.9, 95.6, 95.9, # 0-6 coborâre
96.4, 96.9, 97.5, 98.2, 99.0, 99.7, 100.3, # 7-13 pullback Buy (M)
100.00, # 14 trigger Sell (A)
99.5, 99.0, 98.5, 98.0, 97.6, 97.2, 96.9, 96.6, 96.3, 96.1,
95.8, 95.5, 95.2, 95.35, 95.45, # 15-29 bear trail
]
assert len(closes) == 30, len(closes)
# wick-uri: candela 13 are nevoie de un wick sus până la 100.85 (SL-ul de 4
# candele); restul sunt mici, ca seria să arate ca un grafic real.
wicks = [
(0.15, 0.20), (0.12, 0.18), (0.15, 0.15), (0.10, 0.20), (0.12, 0.25),
(0.10, 0.30), (0.25, 0.15),
(0.20, 0.15), (0.15, 0.20), (0.20, 0.15), (0.20, 0.20), (0.20, 0.18),
(0.25, 0.15), (SL - 100.3, 0.15), # 13: high = 100.85
(0.15, 0.25), # 14: trigger Sell
(0.15, 0.20), (0.15, 0.18), (0.12, 0.20), (0.15, 0.18), (0.15, 0.20),
(0.12, 0.18), (0.15, 0.20), (0.12, 0.18), (0.15, 0.20), (0.12, 0.18),
(0.15, 0.20), (0.12, 0.18), (0.15, 0.20), (0.20, 0.15), (0.30, 0.10),
]
assert len(wicks) == 30, len(wicks)
vols = [
48, 45, 42, 40, 44, 50, 46,
42, 44, 40, 43, 47, 52, 68,
85,
60, 55, 52, 48, 46, 44, 42, 40, 43, 41, 44, 42, 40, 45, 72,
]
assert len(vols) == 30, len(vols)
cds = from_closes(closes, wicks=wicks, vols=vols, start=98.0)
# ── assert-urile cerute de specificație ────────────────────────────────────
# 1. entry == close-ul candelei de trigger 14
assert round(cds[14].c, 2) == ENTRY == 100.00, cds[14].c
# 2. SL == max high pe candelele 11:15
max_h = max(c.h for c in cds[11:15])
assert round(max_h, 2) == SL == 100.85, max_h
# 3. SL = +0.85% față de entry
assert abs((SL - ENTRY) / ENTRY - 0.0085) < 1e-4
# 4. TP1 = entry − (SL − entry) = 1:1
assert TP1 == round(ENTRY - (SL - ENTRY), 2) == 99.15, TP1
assert abs((ENTRY - TP1) / ENTRY - 0.0085) < 1e-4
# 5. exit == close-ul candelei 29
assert round(cds[29].c, 2) == EXIT == 95.45, cds[29].c
# 6. R efectiv ≈ 1:5.3
assert abs((ENTRY - EXIT) / (SL - ENTRY) - 5.35) < 0.05
# 7. niciun close după trigger nu trece peste SL
assert max(c.c for c in cds[15:]) < SL, max(c.c for c in cds[15:])
# 8. fiecare open == close-ul barei anterioare
for a, b in zip(cds, cds[1:]):
assert abs(a.c - b.o) < 1e-9, "serie cu gol artificial între bare"
# 9. candela 14 închide în jos după pullback-ul Buy (7-13)
assert cds[14].c < cds[13].c, (cds[13].c, cds[14].c)
assert any(cds[i].c > cds[i].o for i in range(7, 14)), "pullback Buy absent"
# ── geometria, ca să așez marks-urile „below" fără suprapunere ─────────────
SCALE = 42
levels = [
Level(SL, "SL 100.85 = +0.85%", "#c00"),
Level(ENTRY, "intrare 100.00 (trigger Sell)", "#333"),
Level(TP1, "TP1 99.15 = 1:1", "#1a5fb4"),
Level(EXIT, "ieșire 95.45 = +4.55% ≈ 1:5.3", "#188a3e"),
]
prices = [p for c in cds for p in (c.o, c.h, c.l, c.c)] + [lv.price for lv in levels]
pmax, pmin = max(prices), min(prices)
top = 34 + 22 # implicit + titlu, fără segment de trend
body_bottom = top + (pmax - pmin) * SCALE
def y(price: float) -> float:
return top + (pmax - price) * SCALE
# ── așezarea etichetelor: caut un y unde textul nu atinge vreun corp de
# candelă și nu se suprapune cu altă etichetă. Sub grafic sunt doar ~14 px
# liberi (body_bottom .. body_bottom+14), deci etichetele lungi trebuie
# coborâte/diferite pe rânduri. Fără asta, „A" și „M" trec peste candele.
GAP, LEFT, WIDTH, BODY_W = 15, 44, 700, 10
def cx(i: int) -> float:
return LEFT + i * GAP
def text_x(i: int, text: str) -> tuple[float, float]:
"""(x0, x1) al textului bold ancorat pe candela i, ca în svg_candles."""
x = cx(i)
w = len(text) * 7.44
if x < 90:
return x - 12, x - 12 + w
if x > WIDTH - 90:
return x + 12 - w, x + 12
return x - w / 2, x + w / 2
corpuri = []
for i, c in enumerate(cds):
y0, y1 = y(max(c.o, c.c)), y(min(c.o, c.c))
y1 = max(y1, y0 + 1.6)
corpuri.append((cx(i) - BODY_W / 2, cx(i) + BODY_W / 2, y0, y1))
linii = [y(lv.price) for lv in levels]
def liber_fara_puse(i: int, text: str, yv: float) -> bool:
x0, x1 = text_x(i, text)
ty0, ty1 = yv - 10, yv + 3
if x0 < -2 or x1 > WIDTH + 2 or yv < top + 12 or yv > body_bottom + 14:
return False
for bx0, bx1, by0, by1 in corpuri:
if x0 < bx1 - 2 and bx0 < x1 - 2 and ty0 < by1 - 2 and by0 < ty1 - 2:
return False
# nicio linie de nivel (SL/entry/TP1/exit) prin mijlocul textului
for ly in linii:
if ty0 - 1 <= ly <= ty1 + 1:
return False
return True
def conflict(a: tuple, b: tuple) -> bool:
"""Două etichete se bat dacă au aceeași bandă de y și se suprapun pe x."""
ax0, ax1, ay = a
bx0, bx1, by = b
return abs(ay - by) < 13 and ax0 < bx1 - 2 and bx0 < ax1 - 2
puse: list[tuple] = []
definiții = [
(0, "TF mare (15 min) în Sell", "#c00", "above"),
(7, "M: Buy Momentum (pullback)", "#0891b2", "above"),
(14, "A: primul Sell = trigger, intri short", "#c00", "below"),
(17, "deja peste TP1 (1.42% din curs)", "#188a3e", "below"),
(29, "close peste bear trail = ieși", "#188a3e", "below"),
]
def natural_y(i: int, where: str) -> float:
return (y(cds[i].h) - 10 if where == "above"
else min(y(cds[i].l) + 20, body_bottom + 14))
# candidați: toate y-urile care nu ating niciun corp de candelă, ordonate
# după cât de aproape sunt de poziția naturală (cu preferință pentru partea
# cerută de specificație: „above" în sus, „below" în jos)
candidati = []
for i, text, _color, where in definiții:
nat = natural_y(i, where)
ys = [yy for yy in range(int(top + 12), int(body_bottom + 14) + 1)
if liber_fara_puse(i, text, yy)]
if where == "below":
# o etichetă „below" rămâne sub candelă: nu urcă peste preț
ys = [yy for yy in ys if yy >= nat]
ys.sort(key=lambda yv: yv - nat)
else:
# „above" preferă în sus, dar dacă nu încape coboară sub serie
ys.sort(key=lambda yv: (abs(yv - nat), 0 if yv <= nat else 1))
candidati.append(ys)
assert ys, f"nicio poziție liberă pentru {text!r}"
# backtracking: plasez întâi etichetele cu cele mai puține opțiuni
ordine = sorted(range(len(definiții)), key=lambda k: len(candidati[k]))
y_final = [None] * len(definiții)
def rezolva(k: int) -> bool:
if k == len(ordine):
return True
idx = ordine[k]
ci = definiții[idx][0]
for yv in candidati[idx]:
box = (*text_x(ci, definiții[idx][1]), yv)
if all(not conflict(box, p) for p in puse):
puse.append(box)
y_final[idx] = yv
if rezolva(k + 1):
return True
puse.pop()
return False
assert rezolva(0), "nu găsesc o așezare fără suprapuneri pentru etichete"
marks = [Mark(i, text, color, where, dy=y_final[k] - natural_y(i, where))
for k, (i, text, color, where) in enumerate(definiții)]
assert all(y_final), y_final
ch = Chart(
candles=cds,
levels=levels,
marks=marks,
scale=SCALE, gap=GAP, body_w=BODY_W, left=LEFT, width=WIDTH, vol_h=70,
vol_label="volum",
title="RIOT — 3 min (TF mare 15 min în Sell)",
caption="Axa de preț e construită (intrarea = 100.00); procentele sunt reale din curs.",
)
svg = ch.render()
assert "<svg" in svg and svg.count("\n\n") == 0, "linie goală în SVG"
# ── verificare: fiecare etichetă e în pânză și nu se suprapune alta ────────
m = re.search(r'viewBox="0 0 (\d+) (\d+)"', svg)
W, H = int(m.group(1)), int(m.group(2))
texte = re.findall(r'<text x="([\d.]+)" y="([\d.]+)"([^>]*)>([^<]*)</text>', svg)
cutii, cutii_text = [], []
for xs, ys, attrs, continut in texte:
x, yv = float(xs), float(ys)
w = len(continut) * (7.44 if "bold" in attrs else 6.72)
if 'text-anchor="end"' in attrs:
x0 = x - w
elif 'text-anchor="middle"' in attrs:
x0 = x - w / 2
else:
x0 = x
assert -2 <= x0 and x0 + w <= W + 2, f"etichetă în afara pânzei: {continut!r}"
assert 0 <= yv <= H, f"etichetă în afara înălțimii: {continut!r}"
cutii.append((x0, x0 + w, yv, continut))
cutii_text.append((x0, x0 + w, yv - 10, yv + 3, continut))
for i in range(len(cutii)):
for j in range(i + 1, len(cutii)):
a, b = cutii[i], cutii[j]
if abs(a[2] - b[2]) < 13 and a[0] < b[1] - 2 and b[0] < a[1] - 2:
raise AssertionError(f"texte suprapuse: {a[3]!r} / {b[3]!r}")
# ── verificare: nicio etichetă peste un corp de candelă ────────────────────
corpuri = []
for mm in re.finditer(
r'<rect x="([\d.]+)" y="([\d.]+)" width="10" height="([\d.]+)" '
r'fill="#[0-9a-f]{3,6}"/>', svg):
x0, y0, hh = map(float, mm.groups())
corpuri.append((x0, x0 + 10, y0, y0 + hh))
for tx0, tx1, ty0, ty1, continut in cutii_text:
for cx0, cx1, cy0, cy1 in corpuri:
if tx0 < cx1 - 2 and cx0 < tx1 - 2 and ty0 < cy1 - 2 and cy0 < ty1 - 2:
raise AssertionError(
f"etichetă peste candelă: {continut!r} la y~{ty0:.0f} "
f"({tx0:.0f}-{tx1:.0f}) vs rect {cx0:.0f}-{cx1:.0f} "
f"y {cy0:.0f}-{cy1:.0f}")
# legenda mai spune explicit că axa e construită (cerință de fidelitate)
assert "construită" in svg and "procentele sunt reale" in svg, \
"lipsește nota despre axa construită"
OUT.write_text(svg, encoding="utf-8")
print(f"serie {len(cds)} candele | intrare {ENTRY:.2f} @ bara 14 | "
f"SL {SL:.2f} (+0.85%) | TP1 {TP1:.2f} (1:1) | "
f"ieșire {EXIT:.2f} (+4.55%, R {(ENTRY - EXIT) / (SL - ENTRY):.2f})")
print(f"scris {OUT}")

View File

@@ -0,0 +1,109 @@
<svg viewBox="0 0 700 440" xmlns="http://www.w3.org/2000/svg" style="width:100%;height:auto;font:12px sans-serif">
<text x="24" y="48" fill="#333" style="font-weight:bold">RIOT — 3 min (TF mare 15 min în Sell)</text>
<line x1="24" y1="56.0" x2="686" y2="56.0" stroke="#c00" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="672" y="50.0" fill="#c00" text-anchor="end">SL 100.85 = +0.85%</text>
<line x1="24" y1="91.7" x2="686" y2="91.7" stroke="#333" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="672" y="85.7" fill="#333" text-anchor="end">intrare 100.00 (trigger Sell)</text>
<line x1="24" y1="127.4" x2="686" y2="127.4" stroke="#1a5fb4" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="672" y="121.4" fill="#1a5fb4" text-anchor="end">TP1 99.15 = 1:1</text>
<line x1="24" y1="282.8" x2="686" y2="282.8" stroke="#188a3e" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="672" y="276.8" fill="#188a3e" text-anchor="end">ieșire 95.45 = +4.55% ≈ 1:5.3</text>
<line x1="44.0" y1="169.4" x2="44.0" y2="200.9" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="39.0" y="175.7" width="10" height="16.8" fill="#c00"/>
<line x1="59.0" y1="187.5" x2="59.0" y2="221.1" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="54.0" y="192.5" width="10" height="21.0" fill="#c00"/>
<line x1="74.0" y1="207.2" x2="74.0" y2="236.6" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="69.0" y="213.5" width="10" height="16.8" fill="#c00"/>
<line x1="89.0" y1="226.1" x2="89.0" y2="255.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="84.0" y="230.3" width="10" height="16.8" fill="#c00"/>
<line x1="104.0" y1="242.1" x2="104.0" y2="274.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="99.0" y="247.1" width="10" height="16.8" fill="#c00"/>
<line x1="119.0" y1="259.7" x2="119.0" y2="289.1" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="114.0" y="263.9" width="10" height="12.6" fill="#c00"/>
<line x1="134.0" y1="253.4" x2="134.0" y2="282.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="129.0" y="263.9" width="10" height="12.6" fill="#188a3e"/>
<line x1="149.0" y1="234.5" x2="149.0" y2="270.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="144.0" y="242.9" width="10" height="21.0" fill="#188a3e"/>
<line x1="164.0" y1="215.6" x2="164.0" y2="251.3" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="159.0" y="221.9" width="10" height="21.0" fill="#188a3e"/>
<line x1="179.0" y1="188.3" x2="179.0" y2="228.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="174.0" y="196.7" width="10" height="25.2" fill="#188a3e"/>
<line x1="194.0" y1="158.9" x2="194.0" y2="205.1" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="189.0" y="167.3" width="10" height="29.4" fill="#188a3e"/>
<line x1="209.0" y1="125.3" x2="209.0" y2="174.9" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="204.0" y="133.7" width="10" height="33.6" fill="#188a3e"/>
<line x1="224.0" y1="93.8" x2="224.0" y2="140.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="219.0" y="104.3" width="10" height="29.4" fill="#188a3e"/>
<line x1="239.0" y1="56.0" x2="239.0" y2="110.6" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="234.0" y="79.1" width="10" height="25.2" fill="#188a3e"/>
<line x1="254.0" y1="72.8" x2="254.0" y2="102.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="249.0" y="79.1" width="10" height="12.6" fill="#c00"/>
<line x1="269.0" y1="85.4" x2="269.0" y2="121.1" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="264.0" y="91.7" width="10" height="21.0" fill="#c00"/>
<line x1="284.0" y1="106.4" x2="284.0" y2="141.3" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="279.0" y="112.7" width="10" height="21.0" fill="#c00"/>
<line x1="299.0" y1="128.7" x2="299.0" y2="163.1" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="294.0" y="133.7" width="10" height="21.0" fill="#c00"/>
<line x1="314.0" y1="148.4" x2="314.0" y2="183.3" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="309.0" y="154.7" width="10" height="21.0" fill="#c00"/>
<line x1="329.0" y1="169.4" x2="329.0" y2="200.9" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="324.0" y="175.7" width="10" height="16.8" fill="#c00"/>
<line x1="344.0" y1="187.5" x2="344.0" y2="216.9" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="339.0" y="192.5" width="10" height="16.8" fill="#c00"/>
<line x1="359.0" y1="203.0" x2="359.0" y2="230.3" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="354.0" y="209.3" width="10" height="12.6" fill="#c00"/>
<line x1="374.0" y1="216.9" x2="374.0" y2="242.1" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="369.0" y="221.9" width="10" height="12.6" fill="#c00"/>
<line x1="389.0" y1="228.2" x2="389.0" y2="255.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="384.0" y="234.5" width="10" height="12.6" fill="#c00"/>
<line x1="404.0" y1="242.1" x2="404.0" y2="263.1" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="399.0" y="247.1" width="10" height="8.4" fill="#c00"/>
<line x1="419.0" y1="249.2" x2="419.0" y2="276.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="414.0" y="255.5" width="10" height="12.6" fill="#c00"/>
<line x1="434.0" y1="263.1" x2="434.0" y2="288.3" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="429.0" y="268.1" width="10" height="12.6" fill="#c00"/>
<line x1="449.0" y1="274.4" x2="449.0" y2="301.7" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="444.0" y="280.7" width="10" height="12.6" fill="#c00"/>
<line x1="464.0" y1="278.6" x2="464.0" y2="299.6" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="459.0" y="287.0" width="10" height="6.3" fill="#188a3e"/>
<line x1="479.0" y1="270.2" x2="479.0" y2="291.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="474.0" y="282.8" width="10" height="4.2" fill="#188a3e"/>
<line x1="24" y1="397.7" x2="686" y2="397.7" stroke="#888"/>
<rect x="39.0" y="358.2" width="10" height="39.5" fill="#c00" opacity="0.55"/>
<rect x="54.0" y="360.6" width="10" height="37.1" fill="#c00" opacity="0.55"/>
<rect x="69.0" y="363.1" width="10" height="34.6" fill="#c00" opacity="0.55"/>
<rect x="84.0" y="364.8" width="10" height="32.9" fill="#c00" opacity="0.55"/>
<rect x="99.0" y="361.5" width="10" height="36.2" fill="#c00" opacity="0.55"/>
<rect x="114.0" y="356.5" width="10" height="41.2" fill="#c00" opacity="0.55"/>
<rect x="129.0" y="359.8" width="10" height="37.9" fill="#188a3e" opacity="0.55"/>
<rect x="144.0" y="363.1" width="10" height="34.6" fill="#188a3e" opacity="0.55"/>
<rect x="159.0" y="361.5" width="10" height="36.2" fill="#188a3e" opacity="0.55"/>
<rect x="174.0" y="364.8" width="10" height="32.9" fill="#188a3e" opacity="0.55"/>
<rect x="189.0" y="362.3" width="10" height="35.4" fill="#188a3e" opacity="0.55"/>
<rect x="204.0" y="359.0" width="10" height="38.7" fill="#188a3e" opacity="0.55"/>
<rect x="219.0" y="354.9" width="10" height="42.8" fill="#188a3e" opacity="0.55"/>
<rect x="234.0" y="341.7" width="10" height="56.0" fill="#188a3e" opacity="0.55"/>
<rect x="249.0" y="327.7" width="10" height="70.0" fill="#c00" opacity="0.55"/>
<rect x="264.0" y="348.3" width="10" height="49.4" fill="#c00" opacity="0.55"/>
<rect x="279.0" y="352.4" width="10" height="45.3" fill="#c00" opacity="0.55"/>
<rect x="294.0" y="354.9" width="10" height="42.8" fill="#c00" opacity="0.55"/>
<rect x="309.0" y="358.2" width="10" height="39.5" fill="#c00" opacity="0.55"/>
<rect x="324.0" y="359.8" width="10" height="37.9" fill="#c00" opacity="0.55"/>
<rect x="339.0" y="361.5" width="10" height="36.2" fill="#c00" opacity="0.55"/>
<rect x="354.0" y="363.1" width="10" height="34.6" fill="#c00" opacity="0.55"/>
<rect x="369.0" y="364.8" width="10" height="32.9" fill="#c00" opacity="0.55"/>
<rect x="384.0" y="362.3" width="10" height="35.4" fill="#c00" opacity="0.55"/>
<rect x="399.0" y="363.9" width="10" height="33.8" fill="#c00" opacity="0.55"/>
<rect x="414.0" y="361.5" width="10" height="36.2" fill="#c00" opacity="0.55"/>
<rect x="429.0" y="363.1" width="10" height="34.6" fill="#c00" opacity="0.55"/>
<rect x="444.0" y="364.8" width="10" height="32.9" fill="#c00" opacity="0.55"/>
<rect x="459.0" y="360.6" width="10" height="37.1" fill="#188a3e" opacity="0.55"/>
<rect x="474.0" y="338.4" width="10" height="59.3" fill="#188a3e" opacity="0.55"/>
<text x="24" y="321.7" fill="#555">volum</text>
<text x="32.0" y="123.0" fill="#c00" text-anchor="start" style="font-weight:bold">TF mare (15 min) în Sell</text>
<text x="149.0" y="307.0" fill="#0891b2" text-anchor="middle" style="font-weight:bold">M: Buy Momentum (pullback)</text>
<text x="254.0" y="294.0" fill="#c00" text-anchor="middle" style="font-weight:bold">A: primul Sell = trigger, intri short</text>
<text x="299.0" y="264.0" fill="#188a3e" text-anchor="middle" style="font-weight:bold">deja peste TP1 (1.42% din curs)</text>
<text x="479.0" y="312.0" fill="#188a3e" text-anchor="middle" style="font-weight:bold">close peste bear trail = ieși</text>
<text x="24" y="428" fill="#555">Axa de preț e construită (intrarea = 100.00); procentele sunt reale din curs.</text>
</svg>

After

Width:  |  Height:  |  Size: 8.9 KiB

View File

@@ -0,0 +1,268 @@
#!/usr/bin/env python3
"""Secvența M → A a strategiei M2D — schemă conceptuală pe două panouri.
Pentru `strategii/03_m2d.md`, marcajul `<!-- DIAGRAMA: secventa -->`.
Nu e grafic de preț (spec: „schemă pe două panouri, nu candele de trade”):
SVG scris de mână, doar stdlib — nu se folosește svg_candles / pchart.
Ce arată (toate etichetele vin din spec; nicio cifră de preț nu se inventează):
- panoul de sus, LONG: pe TF mare (15 min) polilinia urcă și „rămâne în Buy pe
tot parcursul”; pe TF mic (3 min) urcă → pullback (M: Sell Momentum) → urcă
peste vârful anterior (A: Buy Momentum — TRIGGER, intri long);
- panoul de jos, SHORT: oglindit — TF mare coboară („rămâne în Sell”), TF mic
coboară → pullback în sus (M: Buy Momentum) → coboară sub minimul anterior
(A: Sell Momentum — TRIGGER, intri short);
- două linii verticale punctate leagă momentul M și momentul A de rândul de sus
(verificarea că TF mare rămâne în aceeași direcție);
- textul de subsol: „Secvența contează, nu starea...”.
Rulare: python3 gen/strategii/m2d_secventa.py -> gen/strategii/m2d_secventa.svg
"""
from pathlib import Path
from xml.sax.saxutils import escape
OUT = Path(__file__).with_suffix(".svg")
W, H = 700, 780
BLACK, GRAY, RED, GREEN, BLUE, CYAN, WHITE = (
"#000000", "#777777", "#c00", "#188a3e", "#1a5fb4", "#00b8c4", "#ffffff")
PANEL_BG, PANEL_EDGE = "#fafafa", "#ddd"
# ---------------------------------------------------------------- geometrie
# Fiecare polilinie: listă de (x, y). y mic = sus.
# Index: 0 = start, 1 = vârf/trough anterior, 2 = M, 3 = A, 4 = continuare.
LONG_TOP = [(40, 162), (150, 148), (250, 138), (430, 128), (660, 118)]
LONG_BOT = [(40, 255), (180, 225), (250, 282), (430, 210), (660, 198)]
SHORT_TOP = [(40, 430), (150, 444), (250, 454), (430, 464), (660, 474)]
SHORT_BOT = [(40, 527), (180, 562), (250, 500), (430, 574), (660, 588)]
L_M, L_A, S_M, S_A = 2, 3, 2, 3 # indicii momentelor M și A
PANELS = {
"LONG": (14, 92, 672, 250),
"SHORT": (14, 362, 672, 270),
}
TEXTS = [] # (x, y, text, size, weight, anchor, color) pentru verificare
def txt(x, y, s, size=13, weight="400", fill=BLACK, anchor="middle"):
TEXTS.append((x, y, s, size, weight, anchor, fill))
return (f'<text x="{x:.1f}" y="{y:.1f}" font-size="{size}" '
f'font-weight="{weight}" fill="{fill}" text-anchor="{anchor}">'
f'{escape(s)}</text>')
def line(x1, y1, x2, y2, stroke=GRAY, width=1.0, dash=None):
d = f' stroke-dasharray="{dash}"' if dash else ""
return (f'<line x1="{x1:.1f}" y1="{y1:.1f}" x2="{x2:.1f}" y2="{y2:.1f}" '
f'stroke="{stroke}" stroke-width="{width}"{d}/>')
def poly(pts, stroke, width=2.0):
p = " ".join(f"{x:.1f},{y:.1f}" for x, y in pts)
return f'<polyline points="{p}" fill="none" stroke="{stroke}" stroke-width="{width}"/>'
def dot(x, y, fill, r=5.0):
return f'<circle cx="{x:.1f}" cy="{y:.1f}" r="{r:.1f}" fill="{fill}"/>'
def build():
p = [
f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 {W} {H}" '
f'width="{W}" height="{H}" role="img" aria-label="Secventa M apoi A a '
f'strategiei M2D, pe doua time frame-uri, pentru long si short: time '
f'frame-ul mare ramane in directie cat timp time frame-ul mic face '
f'pullback (M) si apoi da trigger-ul in directia mare (A)">',
'<g font-family="system-ui, sans-serif">',
f'<rect x="0" y="0" width="{W}" height="{H}" fill="{WHITE}"/>',
txt(W / 2, 30, "Secvența M → A: nu e o stare, e o secvență",
19, "700", BLACK),
txt(W / 2, 52, "M2D — același moment, două time frame-uri: "
"TF mare dă direcția, TF mic dă trigger-ul",
13, "400", GRAY),
txt(W / 2, 72, "sus = LONG; jos = SHORT — aceleași momente, în oglindă",
13, "400", GRAY),
]
# ------------------------------------------------------ panouri
for name, (px, py, pw, ph) in PANELS.items():
p.append(f'<rect x="{px}" y="{py}" width="{pw}" height="{ph}" rx="8" '
f'fill="{PANEL_BG}" stroke="{PANEL_EDGE}" stroke-width="1.2"/>')
# ------------------------------------------------------ panoul LONG
p.append(txt(30, 116, "LONG", 15, "700", GREEN, "start"))
p.append(txt(30, 140, "TF mare (ex. 15 min)", 13, "400", GRAY, "start"))
p.append(txt(660, 114, "rămâne în Buy pe tot parcursul", 13, "400",
GREEN, "end"))
for x, y_top, y_bot in ((LONG_TOP[L_M][0], LONG_TOP[L_M][1], LONG_BOT[L_M][1]),
(LONG_TOP[L_A][0], LONG_TOP[L_A][1], LONG_BOT[L_A][1])):
p.append(line(x, y_top, x, y_bot, "#999999", 1.2, "4 4"))
p.append(poly(LONG_TOP, GREEN))
p.append(dot(*LONG_TOP[1], CYAN))
p.append(txt(185, 126, "Buy Momentum", 13, "400", CYAN, "start"))
p.append(txt(30, 202, "TF mic (ex. 3 min)", 13, "400", GRAY, "start"))
p.append(poly(LONG_BOT, "#333333"))
p.append(dot(*LONG_BOT[L_M], RED))
p.append(txt(262, 296, "M: Sell Momentum (pullback)", 13, "400",
RED, "start"))
p.append(dot(*LONG_BOT[L_A], GREEN, r=6))
p.append(txt(660, 240, "A: Buy Momentum — TRIGGER, intri long",
13, "400", GREEN, "end"))
# ------------------------------------------------------ panoul SHORT
p.append(txt(30, 386, "SHORT", 15, "700", RED, "start"))
p.append(txt(30, 410, "TF mare (ex. 15 min)", 13, "400", GRAY, "start"))
p.append(txt(660, 494, "rămâne în Sell pe tot parcursul", 13, "400",
RED, "end"))
for x, y_top, y_bot in ((SHORT_TOP[S_M][0], SHORT_TOP[S_M][1], SHORT_BOT[S_M][1]),
(SHORT_TOP[S_A][0], SHORT_TOP[S_A][1], SHORT_BOT[S_A][1])):
p.append(line(x, y_top, x, y_bot, "#999999", 1.2, "4 4"))
p.append(poly(SHORT_TOP, RED))
p.append(dot(*SHORT_TOP[1], RED))
p.append(txt(168, 432, "Sell Momentum", 13, "400", RED, "start"))
p.append(txt(30, 470, "TF mic (ex. 3 min)", 13, "400", GRAY, "start"))
p.append(poly(SHORT_BOT, "#333333"))
p.append(dot(*SHORT_BOT[S_M], CYAN))
p.append(txt(238, 494, "M: Buy Momentum (pullback)", 13, "400",
CYAN, "end"))
p.append(dot(*SHORT_BOT[S_A], RED, r=6))
p.append(txt(660, 610, "A: Sell Momentum — TRIGGER, intri short",
13, "400", RED, "end"))
# ------------------------------------------------------ subsol
p.append(f'<rect x="40" y="648" width="620" height="116" rx="8" '
f'fill="#ffffff" stroke="{GRAY}" stroke-width="1.2"/>')
p.append(txt(W / 2, 676,
"Secvența contează, nu starea: mare în Buy → mic în Sell → "
"mic în Buy", 13, "400", BLACK))
p.append(txt(W / 2, 696,
"(cu mare încă în Buy). Un Buy direct pe TF mic, fără Sell "
"între ele,", 13, "400", BLACK))
p.append(txt(W / 2, 716, "NU e trigger (vezi 1.3).", 13, "400", BLACK))
p.append(txt(W / 2, 746,
"Sursă: 14_strategia_1_m2d.md:30-38, 172-180; "
"09_QA/08_2026-01-20.md:65-67", 13, "400", GRAY))
p.append('</g></svg>')
return "\n".join(p) + "\n"
# ---------------------------------------------------------------- verificare
def est_w(s, size, weight):
factor = 0.62 if weight == "700" else 0.56
return len(s) * size * factor
def verify(svg):
# 1. geometria: panourile nu se suprapun, sunt în panză
long_ = PANELS["LONG"]
short_ = PANELS["SHORT"]
assert long_[1] + long_[3] < short_[1], "panourile LONG/SHORT se suprapun"
assert short_[1] + short_[3] < 648, "panoul SHORT intră în subsol"
for _, (px, py, pw, ph) in PANELS.items():
assert px >= 0 and py >= 0 and px + pw <= W and py + ph <= H
# 2. secvența LONG: TF mare rămâne în Buy (urcă monoton: y strict descrescător)
ys = [y for _, y in LONG_TOP]
assert all(ys[i] > ys[i + 1] for i in range(len(ys) - 1)), \
"LONG: TF mare nu urcă monoton (nu rămâne în Buy)"
# M = pullback (minim local), A = reluare peste vârful anterior
assert LONG_BOT[L_M][1] > LONG_BOT[L_M - 1][1] and \
LONG_BOT[L_M][1] > LONG_BOT[L_M + 1][1], "LONG: M nu e un minim local"
assert LONG_BOT[L_A][1] < LONG_BOT[1][1], \
"LONG: A nu urcă peste vârful anterior (pullback-ul)"
assert LONG_TOP[L_M][0] < LONG_TOP[L_A][0], "LONG: M trebuie înainte de A"
assert LONG_TOP[L_M][0] == LONG_BOT[L_M][0] and \
LONG_TOP[L_A][0] == LONG_BOT[L_A][0], \
"LONG: liniile verticale nu leagă exact M și A de rândul de sus"
# 3. secvența SHORT: oglindit
ys = [y for _, y in SHORT_TOP]
assert all(ys[i] < ys[i + 1] for i in range(len(ys) - 1)), \
"SHORT: TF mare nu coboară monoton (nu rămâne în Sell)"
assert SHORT_BOT[S_M][1] < SHORT_BOT[S_M - 1][1] and \
SHORT_BOT[S_M][1] < SHORT_BOT[S_M + 1][1], "SHORT: M nu e un maxim local"
assert SHORT_BOT[S_A][1] > SHORT_BOT[1][1], \
"SHORT: A nu coboară sub minimul anterior"
assert SHORT_TOP[S_M][0] < SHORT_TOP[S_A][0], "SHORT: M trebuie înainte de A"
assert SHORT_TOP[S_M][0] == SHORT_BOT[S_M][0] and \
SHORT_TOP[S_A][0] == SHORT_BOT[S_A][0], \
"SHORT: liniile verticale nu leagă exact M și A de rândul de sus"
# 4. verticalele chiar traversează de la rândul de sus până la moment
assert LONG_TOP[2][1] < LONG_BOT[L_M][1]
assert LONG_TOP[3][1] < LONG_BOT[L_A][1]
assert SHORT_TOP[2][1] < SHORT_BOT[S_M][1]
assert SHORT_TOP[3][1] < SHORT_BOT[S_A][1]
# 5. toate etichetele în panză
for x, y, s, size, weight, anchor, _ in TEXTS:
ew = est_w(s, size, weight)
left = x - ew if anchor == "end" else (x - ew / 2 if anchor == "middle" else x)
right = left + ew
assert -2 <= left and right <= W + 2, f"text în afara panzei: {s!r}"
assert 0 <= y <= H, f"text în afara înălțimii: {s!r} (y={y})"
# 6. etichete care nu se suprapun
for i in range(len(TEXTS)):
xi, yi, si, szi, wi, ai, _ = TEXTS[i]
we = est_w(si, szi, wi)
li = xi - we if ai == "end" else (xi - we / 2 if ai == "middle" else xi)
ri = li + we
for j in range(i + 1, len(TEXTS)):
xj, yj, sj, szj, wj, aj, _ = TEXTS[j]
wej = est_w(sj, szj, wj)
lj = xj - wej if aj == "end" else (xj - wej / 2 if aj == "middle" else xj)
rj = lj + wej
if abs(yi - yj) < 13 and li < rj - 4 and lj < ri - 4:
raise AssertionError(f"texte suprapuse: {si!r} / {sj!r}")
# 7. niciun text nu se tastează peste o polilinie (coliziune text-linie)
segments = [(LONG_TOP, PANELS["LONG"]), (LONG_BOT, PANELS["LONG"]),
(SHORT_TOP, PANELS["SHORT"]), (SHORT_BOT, PANELS["SHORT"])]
for x, y, s, size, weight, anchor, _ in TEXTS:
ew = est_w(s, size, weight)
left = x - ew if anchor == "end" else (x - ew / 2 if anchor == "middle" else x)
right = left + ew
top, bot = y - size + 1, y
for pts, _ in segments:
for (x1, y1), (x2, y2) in zip(pts, pts[1:]):
xa, xb = min(x1, x2), max(x1, x2)
if xb < left - 2 or xa > right + 2:
continue
for xc in (max(xa, left), min(xb, right)):
t = 0 if x2 == x1 else (xc - x1) / (x2 - x1)
yc = y1 + t * (y2 - y1)
if top - 1 <= yc <= bot + 1:
raise AssertionError(
f"text peste polilinie: {s!r} (y={y}, linia y={yc:.0f})")
# 8. etichetele obligatorii din spec sunt prezente
for cheie in ("LONG", "SHORT",
"TF mare (ex. 15 min)", "TF mic (ex. 3 min)",
"rămâne în Buy pe tot parcursul",
"rămâne în Sell pe tot parcursul",
"Buy Momentum", "Sell Momentum",
"M: Sell Momentum (pullback)",
"A: Buy Momentum — TRIGGER, intri long",
"M: Buy Momentum (pullback)",
"A: Sell Momentum — TRIGGER, intri short",
"Secvența contează, nu starea",
"NU e trigger (vezi 1.3)"):
assert cheie in svg, f"lipsește din desen: {cheie!r}"
# 9. fără cifre de preț inventate (spec: diagramă fără niveluri)
for interzis in ("$", "%", "€", "intrare ", "SL ", "TP"):
assert interzis not in svg, f"nu ar trebui să apară preț: {interzis!r}"
return True
SVG = build()
assert verify(SVG), "verificare eșuată"
OUT.write_text(SVG, encoding="utf-8")
print(f"scris {OUT} ({W}x{H}), verificare OK: {len(TEXTS)} etichete, "
f"{len(LONG_TOP) + len(LONG_BOT) + len(SHORT_TOP) + len(SHORT_BOT)} puncte")

View File

@@ -0,0 +1,42 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 700 780" width="700" height="780" role="img" aria-label="Secventa M apoi A a strategiei M2D, pe doua time frame-uri, pentru long si short: time frame-ul mare ramane in directie cat timp time frame-ul mic face pullback (M) si apoi da trigger-ul in directia mare (A)">
<g font-family="system-ui, sans-serif">
<rect x="0" y="0" width="700" height="780" fill="#ffffff"/>
<text x="350.0" y="30.0" font-size="19" font-weight="700" fill="#000000" text-anchor="middle">Secvența M → A: nu e o stare, e o secvență</text>
<text x="350.0" y="52.0" font-size="13" font-weight="400" fill="#777777" text-anchor="middle">M2D — același moment, două time frame-uri: TF mare dă direcția, TF mic dă trigger-ul</text>
<text x="350.0" y="72.0" font-size="13" font-weight="400" fill="#777777" text-anchor="middle">sus = LONG; jos = SHORT — aceleași momente, în oglindă</text>
<rect x="14" y="92" width="672" height="250" rx="8" fill="#fafafa" stroke="#ddd" stroke-width="1.2"/>
<rect x="14" y="362" width="672" height="270" rx="8" fill="#fafafa" stroke="#ddd" stroke-width="1.2"/>
<text x="30.0" y="116.0" font-size="15" font-weight="700" fill="#188a3e" text-anchor="start">LONG</text>
<text x="30.0" y="140.0" font-size="13" font-weight="400" fill="#777777" text-anchor="start">TF mare (ex. 15 min)</text>
<text x="660.0" y="114.0" font-size="13" font-weight="400" fill="#188a3e" text-anchor="end">rămâne în Buy pe tot parcursul</text>
<line x1="250.0" y1="138.0" x2="250.0" y2="282.0" stroke="#999999" stroke-width="1.2" stroke-dasharray="4 4"/>
<line x1="430.0" y1="128.0" x2="430.0" y2="210.0" stroke="#999999" stroke-width="1.2" stroke-dasharray="4 4"/>
<polyline points="40.0,162.0 150.0,148.0 250.0,138.0 430.0,128.0 660.0,118.0" fill="none" stroke="#188a3e" stroke-width="2.0"/>
<circle cx="150.0" cy="148.0" r="5.0" fill="#00b8c4"/>
<text x="185.0" y="126.0" font-size="13" font-weight="400" fill="#00b8c4" text-anchor="start">Buy Momentum</text>
<text x="30.0" y="202.0" font-size="13" font-weight="400" fill="#777777" text-anchor="start">TF mic (ex. 3 min)</text>
<polyline points="40.0,255.0 180.0,225.0 250.0,282.0 430.0,210.0 660.0,198.0" fill="none" stroke="#333333" stroke-width="2.0"/>
<circle cx="250.0" cy="282.0" r="5.0" fill="#c00"/>
<text x="262.0" y="296.0" font-size="13" font-weight="400" fill="#c00" text-anchor="start">M: Sell Momentum (pullback)</text>
<circle cx="430.0" cy="210.0" r="6.0" fill="#188a3e"/>
<text x="660.0" y="240.0" font-size="13" font-weight="400" fill="#188a3e" text-anchor="end">A: Buy Momentum — TRIGGER, intri long</text>
<text x="30.0" y="386.0" font-size="15" font-weight="700" fill="#c00" text-anchor="start">SHORT</text>
<text x="30.0" y="410.0" font-size="13" font-weight="400" fill="#777777" text-anchor="start">TF mare (ex. 15 min)</text>
<text x="660.0" y="494.0" font-size="13" font-weight="400" fill="#c00" text-anchor="end">rămâne în Sell pe tot parcursul</text>
<line x1="250.0" y1="454.0" x2="250.0" y2="500.0" stroke="#999999" stroke-width="1.2" stroke-dasharray="4 4"/>
<line x1="430.0" y1="464.0" x2="430.0" y2="574.0" stroke="#999999" stroke-width="1.2" stroke-dasharray="4 4"/>
<polyline points="40.0,430.0 150.0,444.0 250.0,454.0 430.0,464.0 660.0,474.0" fill="none" stroke="#c00" stroke-width="2.0"/>
<circle cx="150.0" cy="444.0" r="5.0" fill="#c00"/>
<text x="168.0" y="432.0" font-size="13" font-weight="400" fill="#c00" text-anchor="start">Sell Momentum</text>
<text x="30.0" y="470.0" font-size="13" font-weight="400" fill="#777777" text-anchor="start">TF mic (ex. 3 min)</text>
<polyline points="40.0,527.0 180.0,562.0 250.0,500.0 430.0,574.0 660.0,588.0" fill="none" stroke="#333333" stroke-width="2.0"/>
<circle cx="250.0" cy="500.0" r="5.0" fill="#00b8c4"/>
<text x="238.0" y="494.0" font-size="13" font-weight="400" fill="#00b8c4" text-anchor="end">M: Buy Momentum (pullback)</text>
<circle cx="430.0" cy="574.0" r="6.0" fill="#c00"/>
<text x="660.0" y="610.0" font-size="13" font-weight="400" fill="#c00" text-anchor="end">A: Sell Momentum — TRIGGER, intri short</text>
<rect x="40" y="648" width="620" height="116" rx="8" fill="#ffffff" stroke="#777777" stroke-width="1.2"/>
<text x="350.0" y="676.0" font-size="13" font-weight="400" fill="#000000" text-anchor="middle">Secvența contează, nu starea: mare în Buy → mic în Sell → mic în Buy</text>
<text x="350.0" y="696.0" font-size="13" font-weight="400" fill="#000000" text-anchor="middle">(cu mare încă în Buy). Un Buy direct pe TF mic, fără Sell între ele,</text>
<text x="350.0" y="716.0" font-size="13" font-weight="400" fill="#000000" text-anchor="middle">NU e trigger (vezi 1.3).</text>
<text x="350.0" y="746.0" font-size="13" font-weight="400" fill="#777777" text-anchor="middle">Sursă: 14_strategia_1_m2d.md:30-38, 172-180; 09_QA/08_2026-01-20.md:65-67</text>
</g></svg>

After

Width:  |  Height:  |  Size: 4.8 KiB

View File

@@ -0,0 +1,201 @@
"""Diagrama de ansamblu MBP / MBPA++ (strategii, marcajul `ansamblu`).
„SPY — 15 min: bandă + TICUS pe aceeași candelă → trigger → intrare → SL →
TP1 → trail". Graficul întreg al trade-ului long din exemplul cursului, cu
panoul TICUS dedesubt, legat de candela M printr-o linie punctată verticală
(aceeași candelă sus, aceeași valoare jos).
Toate cifrele desenate vin din curs
(`summaries/05_MODUL_4/03_strategia_6_mbp_mbappe.md`):
- M: close 623.87 < banda inferioară 623.90, TICUS -1150 (liniile 336-342);
- bandă superioară 624.70 și linia 624.30 (SMA20 / TP1) — diagrama
principală (liniile 114-148);
- trigger Buy Momentum → entry 623.95 (liniile 336-342);
- SL 623.60 = lowest low din ultimele 4 candele, trigger inclus (93-94);
- TP1 624.30 (+0.35, 1:1) (95-96);
- „realizat ~1:4 datorită pozițiilor pe trail" (428-429). Prețul de ieșire
~625.35 = entry + 4R este ilustrativ, derivat de mine (nu e preț din curs).
Structura barelor dintre niveluri e construită de mine; o construiesc astfel
încât fiecare nivel afirmat în text să iasă exact din serie (assert-uri mai
jos). Nu există nivel de suport major cu cifră în exemplul din curs.
Rulează:
python3 gen/strategii/mbp_ansamblu.py
Scrie lângă el `mbp_ansamblu.svg`.
"""
import re
import sys
from pathlib import Path
sys.path.insert(0, "/workspace/atm-curs-dl")
from svg_candles import Chart, Level, Mark, from_closes # noqa: E402
OUT = Path(__file__).resolve().parent / "mbp_ansamblu.svg"
GRAY, RED, GREEN, BLUE, ORANGE = "#777", "#c00", "#188a3e", "#1a5fb4", "#b06000"
# ---------------------------------------------------------------- seria (30 bare)
# 14 bare de downtrend 625.60 -> 624.15 (open-ul fiecărei bare = close-ul
# precedentei), apoi M (index 14), trigger (15), 14 bare de urcare spre 625.35.
DOWN = [625.60, 625.42, 625.55, 625.28, 625.05, 625.12, 624.88,
624.65, 624.72, 624.50, 624.35, 624.30, 624.10, 624.15] # 0-13
M_IX, TR_IX = 14, 15
UP = [624.00, 624.08, 624.16, 624.30, 624.40, 624.50, 624.60, 624.70,
624.82, 624.95, 625.08, 625.20, 625.28, 625.35] # 16-29
CLOSES = DOWN + [623.87, 623.95] + UP # M, trigger
assert len(CLOSES) == 30, len(CLOSES)
# fitile (sus, jos) per bară. Ultimele 3 bare înainte de M au minimele
# 623.95 / 623.80 / 623.72 (peste 623.60), ca SL-ul calculat să fie exact
# 623.60 (low-ul lui M).
WICKS = ([(0.05, 0.18)] + [(0.18, 0.18)] * 10
+ [(0.18, 0.35), # 11: low 623.95
(0.18, 0.30), # 12: low 623.80 (open 624.30, close 624.10)
(0.10, 0.38)] # 13: low 623.72 (open 624.10, close 624.15)
+ [(0.05, 0.27), # 14 M: high 624.20, low 623.60
(0.05, 0.12)] # 15 trigger: high 624.00, low 623.75
+ [(0.05, 0.12)] * 3 # 16-18
+ [(0.00, 0.12)] # 19: high atinge exact 624.30 (TP1)
+ [(0.05, 0.12)] * 9
+ [(0.00, 0.12)]) # 29: high = close = 625.35 (ieșirea ilustrativă)
assert len(WICKS) == len(CLOSES), len(WICKS)
# TICUS: un singur bar relevant, pe candela M, la -1150 (sub -1000)
TICUS = {M_IX: -1150.0}
# fără panou de volum: panoul de dedesubt e cel de TICUS
cds = from_closes(CLOSES, wicks=WICKS, start=625.75)
# ---------------------------------------------------------------- assert-urile din spec
entry = cds[TR_IX].c
assert abs(entry - 623.95) < 0.01, entry
assert entry == cds[TR_IX].c # intrarea = close-ul trigger-ului
assert cds[M_IX].c < 623.90 # M închide sub banda inferioară
assert cds[M_IX].c < cds[M_IX].o # M e roșie (close < open)
ticus_m = TICUS[M_IX]
assert ticus_m <= -1000, ticus_m # =-1150, pragul depășit
assert abs(cds[M_IX].c - 623.87) < 0.005
assert abs(cds[M_IX].h - 624.20) < 0.005
assert abs(cds[TR_IX].h - 624.00) < 0.005
assert abs(cds[TR_IX].l - 623.75) < 0.005
# SL = cel mai jos minim din ultimele 4 candele încheiate de trigger, inclusiv
window = cds[TR_IX - 3:TR_IX + 1]
assert len(window) == 4
sl = min(c.l for c in window)
assert abs(sl - 623.60) < 0.01, sl
assert sl == cds[M_IX].l # low-ul lui M e minimul ferestrei
assert cds[TR_IX].l > sl # trigger-ul nu face low nou
# cele trei candele dinaintea lui M au minime peste 623.60 (ex. 623.95/623.80/623.72)
for cd in cds[TR_IX - 4:TR_IX - 1]:
assert cd.l > sl, (cd.o, cd.c, cd.l)
risc = entry - sl
assert abs(risc - 0.35) < 0.005, risc
tp1 = entry + risc
assert abs(tp1 - (entry + (entry - sl))) < 0.005
assert abs(tp1 - 624.30) < 0.005, tp1
# TP1 nu se atinge prea devreme, ci 4 bare după intrare (cum spune spec-ul)
assert max(c.h for c in cds[TR_IX + 1:TR_IX + 4]) < tp1 - 1e-9
assert abs(cds[TR_IX + 4].h - tp1) < 1e-9
exit_ill = cds[-1].c # ~625.35, ilustrativ (derivat)
assert abs(exit_ill - 625.35) < 0.01, exit_ill
assert abs((exit_ill - entry) / risc - 4.0) < 0.15, (exit_ill - entry) / risc
# nu există nicio linie de stop desenată deasupra intrării
assert sl < entry
levels = [
Level(623.90, "623.90 — bandă inferioară", BLUE, dash="4 3", label_at="left"),
Level(624.70, "624.70 — bandă superioară", BLUE, dash="4 3", label_at="right"),
Level(624.30, "624.30 — SMA20 / TP1", GRAY, dash="4 3", label_at="left"),
Level(sl, f"SL {sl:.2f} (lowest low, 4 candele)", RED, label_at="left"),
Level(entry, f"trigger Buy Momentum → intrare {entry:.2f}", GREEN, label_at="right"),
Level(625.35, "ieșire ilustrativă ~625.35 (≈ 1:4)", GREEN, dash="2 3", label_at="right"),
]
assert all("stop" not in lv.text.lower() or lv.price < entry for lv in levels)
assert all(not ("sl" in lv.text.lower() and lv.price > entry) for lv in levels)
marks = [
Mark(M_IX, "M: roșie, close 623.87 sub bandă", RED, "below"),
]
# titlul si eticheta de trend sunt injectate manual (nu prin Chart.title, care
# ar sta lipit de prima candelă, si nici prin Chart.trend, care s-ar bate cu
# titlul) ca sa rămână spațiu liber deasupra seriei.
ch = Chart(candles=cds, levels=levels, marks=marks,
scale=150.0, gap=16, body_w=10, left=48, width=700, top=96)
svg = ch.render()
TITLE = "SPY — 15 min: bandă + TICUS pe aceeași candelă → trigger → intrare → SL → TP1 → trail"
# ---------------------------------------------------------------- panoul TICUS
# `Chart` nu are panou de indicator; îl adaug după randare, cu aceeași scară
# pe orizontală (ch._cx), ca barul TICUS al lui M să cadă exact sub candela M.
m = re.search(r'viewBox="0 0 (\d+) ([0-9.]+)"', svg)
W, H0 = int(m.group(1)), float(m.group(2))
ticus_top = H0 + 34
y0 = ticus_top + 70 # linia 0
px1000 = 50.0 # 1000 unități TICUS = 50 px
def ty(v: float) -> float:
return y0 - v / 1000.0 * px1000
xm = ch._cx(M_IX)
bar_top, bar_bot = y0, ty(ticus_m) # bar de la 0 în jos la -1150
panel = [
f'<text x="24" y="{ticus_top:.0f}" fill="#333">TICUS — presiunea pieței (↑ = peste 0)</text>',
# linii de prag
f'<line x1="24" y1="{ty(1000):.1f}" x2="{W - 14}" y2="{ty(1000):.1f}" stroke="{RED}" stroke-width="1.5" stroke-dasharray="4 3"/>',
f'<text x="28" y="{ty(1000) - 4:.1f}" fill="{RED}">+1000 — supracumpărare</text>',
f'<line x1="24" y1="{ty(500):.1f}" x2="{W - 14}" y2="{ty(500):.1f}" stroke="#caa000" stroke-width="1" stroke-dasharray="2 2"/>',
f'<text x="28" y="{ty(500) - 4:.1f}" fill="#caa000">+500 (doar referință vizuală)</text>',
f'<line x1="24" y1="{ty(0):.1f}" x2="{W - 14}" y2="{ty(0):.1f}" stroke="#888" stroke-width="1.5"/>',
f'<text x="{W - 12}" y="{ty(0) + 4:.1f}" fill="#666" text-anchor="end">0</text>',
f'<line x1="24" y1="{ty(-500):.1f}" x2="{W - 14}" y2="{ty(-500):.1f}" stroke="#caa000" stroke-width="1" stroke-dasharray="2 2"/>',
f'<text x="28" y="{ty(-500) + 13:.1f}" fill="#caa000">-500 (doar referință vizuală)</text>',
f'<line x1="24" y1="{ty(-1000):.1f}" x2="{W - 14}" y2="{ty(-1000):.1f}" stroke="{GREEN}" stroke-width="1.5" stroke-dasharray="4 3"/>',
f'<text x="28" y="{ty(-1000) + 13:.1f}" fill="{GREEN}">-1000 — oversold</text>',
# barul TICUS al candelei M, sub -1000 (colorat roșu)
f'<rect x="{xm - 5:.1f}" y="{bar_top:.1f}" width="10" height="{bar_bot - bar_top:.1f}" fill="{RED}"/>',
f'<text x="{xm + 10:.1f}" y="{bar_bot + 4:.1f}" fill="{RED}">M: TICUS -1150 (sub -1000)</text>',
]
# linia punctată verticală: aceeași candelă sus și jos (pornește sub mark-ul
# lui M, ca să nu taie textul „M: roșie...")
y_candle_m = ch._y(cds[M_IX].l) + 26
panel.insert(0, f'<line x1="{xm:.1f}" y1="{y_candle_m:.1f}" x2="{xm:.1f}" y2="{bar_bot:.1f}" '
f'stroke="#aaa" stroke-width="1" stroke-dasharray="3 3"/>')
legend_top = ty(-1000) + 44
legend = [
"SPY 15 min. Cifre din curs: M close 623.87 (sub bandă 623.90), TICUS -1150 pe aceeași candelă,",
"trigger Buy Momentum → entry 623.95, SL 623.60 (lowest low din 4 candele, trigger inclus),",
"TP1 624.30 (+0.35, 1:1). Ieșirea ~625.35 = entry + 4R (≈1:4) e ilustrativă — preț derivat din R,",
"nu din curs. Structura candelelor (30 bare continue) e construită de mine.",
]
legend_svg = [f'<text x="24" y="{legend_top + 17 * i:.1f}" fill="#555">{t}</text>'
for i, t in enumerate(legend)]
header = [
f'<text x="24" y="26" fill="#333" style="font-weight:bold">{TITLE}</text>',
'<text x="24" y="64" fill="#999">trend descendent anterior (14 bare)</text>',
]
new_h = legend_top + 17 * (len(legend) - 1) + 14
svg = re.sub(r'(viewBox="0 0 \d+ )[0-9.]+(")', lambda mm: mm.group(1) + f"{new_h:.0f}" + mm.group(2), svg, count=1)
svg = svg.replace("\n</svg>", "\n " + "\n ".join(header + panel + legend_svg) + "\n</svg>")
assert svg.count("\n\n") == 0, "linie goală în SVG rupe blocul din markdown"
# barul TICUS cade exact sub candela M (aceeași candelă sus și jos)
bar_svg = f'<rect x="{xm - 5:.1f}" y="{bar_top:.1f}" width="10" height="{bar_bot - bar_top:.1f}" fill="{RED}"/>'
assert bar_svg in svg, "barul TICUS nu a fost desenat"
assert abs((float(re.search(r'x="([\d.]+)"', bar_svg).group(1)) + 5) - ch._cx(M_IX)) < 1e-9
# aceeași scară px/unitate: SL-intrare și intrare-TP1 sunt egale și vizual (1:1)
assert abs((ch._y(sl) - ch._y(entry)) - (ch._y(entry) - ch._y(tp1))) < 1e-9
assert f'x1="{xm:.1f}"' in svg
OUT.write_text(svg)
print(f"scris {OUT.name}: {len(cds)} candele, entry {entry:.2f}, SL {sl:.2f}, "
f"TP1 {tp1:.2f}, ieșire ilustrativă {exit_ill:.2f} ({(exit_ill-entry)/risc:.2f}R), "
f"ticus(M)={ticus_m:.0f}, x(M)={xm:.0f}")

View File

@@ -0,0 +1,95 @@
<svg viewBox="0 0 700 715" xmlns="http://www.w3.org/2000/svg" style="width:100%;height:auto;font:12px sans-serif">
<line x1="24" y1="381.0" x2="686" y2="381.0" stroke="#1a5fb4" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="30" y="375.0" fill="#1a5fb4">623.90 — bandă inferioară</text>
<line x1="24" y1="261.0" x2="686" y2="261.0" stroke="#1a5fb4" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="672" y="255.0" fill="#1a5fb4" text-anchor="end">624.70 — bandă superioară</text>
<line x1="24" y1="321.0" x2="686" y2="321.0" stroke="#777" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="30" y="315.0" fill="#777">624.30 — SMA20 / TP1</text>
<line x1="24" y1="426.0" x2="686" y2="426.0" stroke="#c00" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="30" y="420.0" fill="#c00">SL 623.60 (lowest low, 4 candele)</text>
<line x1="24" y1="373.5" x2="686" y2="373.5" stroke="#188a3e" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="672" y="367.5" fill="#188a3e" text-anchor="end">trigger Buy Momentum → intrare 623.95</text>
<line x1="24" y1="163.5" x2="686" y2="163.5" stroke="#188a3e" stroke-width="1.5" stroke-dasharray="2 3"/>
<text x="672" y="157.5" fill="#188a3e" text-anchor="end">ieșire ilustrativă ~625.35 (≈ 1:4)</text>
<line x1="48.0" y1="96.0" x2="48.0" y2="153.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="43.0" y="103.5" width="10" height="22.5" fill="#c00"/>
<line x1="64.0" y1="99.0" x2="64.0" y2="180.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="59.0" y="126.0" width="10" height="27.0" fill="#c00"/>
<line x1="80.0" y1="106.5" x2="80.0" y2="180.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="75.0" y="133.5" width="10" height="19.5" fill="#188a3e"/>
<line x1="96.0" y1="106.5" x2="96.0" y2="201.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="91.0" y="133.5" width="10" height="40.5" fill="#c00"/>
<line x1="112.0" y1="147.0" x2="112.0" y2="235.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="107.0" y="174.0" width="10" height="34.5" fill="#c00"/>
<line x1="128.0" y1="171.0" x2="128.0" y2="235.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="123.0" y="198.0" width="10" height="10.5" fill="#188a3e"/>
<line x1="144.0" y1="171.0" x2="144.0" y2="261.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="139.0" y="198.0" width="10" height="36.0" fill="#c00"/>
<line x1="160.0" y1="207.0" x2="160.0" y2="295.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="155.0" y="234.0" width="10" height="34.5" fill="#c00"/>
<line x1="176.0" y1="231.0" x2="176.0" y2="295.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="171.0" y="258.0" width="10" height="10.5" fill="#188a3e"/>
<line x1="192.0" y1="231.0" x2="192.0" y2="318.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="187.0" y="258.0" width="10" height="33.0" fill="#c00"/>
<line x1="208.0" y1="264.0" x2="208.0" y2="340.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="203.0" y="291.0" width="10" height="22.5" fill="#c00"/>
<line x1="224.0" y1="286.5" x2="224.0" y2="373.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="219.0" y="313.5" width="10" height="7.5" fill="#c00"/>
<line x1="240.0" y1="294.0" x2="240.0" y2="396.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="235.0" y="321.0" width="10" height="30.0" fill="#c00"/>
<line x1="256.0" y1="328.5" x2="256.0" y2="408.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="251.0" y="343.5" width="10" height="7.5" fill="#188a3e"/>
<line x1="272.0" y1="336.0" x2="272.0" y2="426.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="267.0" y="343.5" width="10" height="42.0" fill="#c00"/>
<line x1="288.0" y1="366.0" x2="288.0" y2="403.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="283.0" y="373.5" width="10" height="12.0" fill="#188a3e"/>
<line x1="304.0" y1="358.5" x2="304.0" y2="391.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="299.0" y="366.0" width="10" height="7.5" fill="#188a3e"/>
<line x1="320.0" y1="346.5" x2="320.0" y2="384.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="315.0" y="354.0" width="10" height="12.0" fill="#188a3e"/>
<line x1="336.0" y1="334.5" x2="336.0" y2="372.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="331.0" y="342.0" width="10" height="12.0" fill="#188a3e"/>
<line x1="352.0" y1="321.0" x2="352.0" y2="360.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="347.0" y="321.0" width="10" height="21.0" fill="#188a3e"/>
<line x1="368.0" y1="298.5" x2="368.0" y2="339.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="363.0" y="306.0" width="10" height="15.0" fill="#188a3e"/>
<line x1="384.0" y1="283.5" x2="384.0" y2="324.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="379.0" y="291.0" width="10" height="15.0" fill="#188a3e"/>
<line x1="400.0" y1="268.5" x2="400.0" y2="309.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="395.0" y="276.0" width="10" height="15.0" fill="#188a3e"/>
<line x1="416.0" y1="253.5" x2="416.0" y2="294.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="411.0" y="261.0" width="10" height="15.0" fill="#188a3e"/>
<line x1="432.0" y1="235.5" x2="432.0" y2="279.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="427.0" y="243.0" width="10" height="18.0" fill="#188a3e"/>
<line x1="448.0" y1="216.0" x2="448.0" y2="261.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="443.0" y="223.5" width="10" height="19.5" fill="#188a3e"/>
<line x1="464.0" y1="196.5" x2="464.0" y2="241.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="459.0" y="204.0" width="10" height="19.5" fill="#188a3e"/>
<line x1="480.0" y1="178.5" x2="480.0" y2="222.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="475.0" y="186.0" width="10" height="18.0" fill="#188a3e"/>
<line x1="496.0" y1="166.5" x2="496.0" y2="204.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="491.0" y="174.0" width="10" height="12.0" fill="#188a3e"/>
<line x1="512.0" y1="163.5" x2="512.0" y2="192.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="507.0" y="163.5" width="10" height="10.5" fill="#188a3e"/>
<text x="272.0" y="440.0" fill="#c00" text-anchor="middle" style="font-weight:bold">M: roșie, close 623.87 sub bandă</text>
<text x="24" y="26" fill="#333" style="font-weight:bold">SPY — 15 min: bandă + TICUS pe aceeași candelă → trigger → intrare → SL → TP1 → trail</text>
<text x="24" y="64" fill="#999">trend descendent anterior (14 bare)</text>
<line x1="272.0" y1="452.0" x2="272.0" y2="613.5" stroke="#aaa" stroke-width="1" stroke-dasharray="3 3"/>
<text x="24" y="486" fill="#333">TICUS — presiunea pieței (↑ = peste 0)</text>
<line x1="24" y1="506.0" x2="686" y2="506.0" stroke="#c00" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="28" y="502.0" fill="#c00">+1000 — supracumpărare</text>
<line x1="24" y1="531.0" x2="686" y2="531.0" stroke="#caa000" stroke-width="1" stroke-dasharray="2 2"/>
<text x="28" y="527.0" fill="#caa000">+500 (doar referință vizuală)</text>
<line x1="24" y1="556.0" x2="686" y2="556.0" stroke="#888" stroke-width="1.5"/>
<text x="688" y="560.0" fill="#666" text-anchor="end">0</text>
<line x1="24" y1="581.0" x2="686" y2="581.0" stroke="#caa000" stroke-width="1" stroke-dasharray="2 2"/>
<text x="28" y="594.0" fill="#caa000">-500 (doar referință vizuală)</text>
<line x1="24" y1="606.0" x2="686" y2="606.0" stroke="#188a3e" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="28" y="619.0" fill="#188a3e">-1000 — oversold</text>
<rect x="267.0" y="556.0" width="10" height="57.5" fill="#c00"/>
<text x="282.0" y="617.5" fill="#c00">M: TICUS -1150 (sub -1000)</text>
<text x="24" y="650.0" fill="#555">SPY 15 min. Cifre din curs: M close 623.87 (sub bandă 623.90), TICUS -1150 pe aceeași candelă,</text>
<text x="24" y="667.0" fill="#555">trigger Buy Momentum → entry 623.95, SL 623.60 (lowest low din 4 candele, trigger inclus),</text>
<text x="24" y="684.0" fill="#555">TP1 624.30 (+0.35, 1:1). Ieșirea ~625.35 = entry + 4R (≈1:4) e ilustrativă — preț derivat din R,</text>
<text x="24" y="701.0" fill="#555">nu din curs. Structura candelelor (30 bare continue) e construită de mine.</text>
</svg>

After

Width:  |  Height:  |  Size: 8.0 KiB

View File

@@ -0,0 +1,242 @@
"""Diagrama din `gen/strategii/mbp_exemplu.spec.txt` (TIP: PRET).
Trade-ul short complet din material (SPY, 15 min, 25.11.2025): ~12 candele
de urcare violentă spre vârful ~661, candela M (verde, peste bandă + TICUS
≥ +1000), trigger Sell Momentum → intrare short 660.00, SL 662.71
(risc 0.41 %), TP1 657.29 (1:1) atins în prima candelă, apoi trail pe 7
candele de 15 min până la ieșirea la 646.80 = 2 % = ~4.9R.
Serie continuă de 32 de candele (`from_closes`, open = close anterior), cu
context înainte de urcare, panou TICUS dedesubt (bar ilustrativ la candela M)
și legendă. Toate cifrele din desen vin din specificație; singurele valori
construite de mine sunt structura barelor dintre niveluri și bara TICUS
(marcată explicit „ilustrativ", pentru că valoarea exactă nu e în material).
Rulare: python3 gen/strategii/mbp_exemplu.py
"""
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
OUT = Path(__file__).with_suffix(".svg")
# ── cifrele din material ────────────────────────────────────────────────────
ENTRY = 660.00 # 09_QA/11_2025-11-25.md:565, 632-636
SL = 662.71 # risc 0.41 % → (sl-entry)/entry*100
TP1 = 657.29 # entry - (sl - entry) = 1:1
EXIT = 646.80 # ieșire pe trail = 2 % = ~4.9R
BAND = 660.90 # bandă superioară schematică (peste close-ul lui M)
W, GAP, BODY_W, LEFT = 660, 15, 9, 40
SCALE = 20.0
M_I, E_I, TP1_I, EXIT_I = 23, 24, 25, 31 # M, trigger, TP1, ieșire
N_RISE = M_I - 11 # 12 candele de urcare (idx 12..23)
# ── seria: context → urcare violentă → M → trigger → TP1 → trail ────────────
closes = [
# idx 0-11: context în jurul lui 648, înainte de urcarea violentă
649.00, 648.60, 649.20, 648.40, 648.90, 648.10,
648.70, 647.90, 648.50, 648.00, 648.60, 648.30,
# idx 12-23: 12 candele de urcare de la ~648 la vârful ~661; idx 23 = M
648.60, 649.40, 650.30, 651.20, 652.10, 653.10,
654.10, 655.20, 656.30, 657.50, 658.80, 660.50,
# idx 24: trigger Sell Momentum → close = intrarea short
660.00,
# idx 25: prima candelă după intrare atinge TP1 657.29
657.80,
# idx 26-31: trail până la ieșirea la 646.80 (6 candele)
656.40, 655.00, 653.40, 651.60, 649.40, 646.80,
]
assert len(closes) == 32, len(closes)
wicks = [(0.35, 0.35)] * 12 # context
wicks += [(0.30, 0.25)] * (M_I - 12) # urcarea (idx 12..22)
wicks += [(0.50, 0.20)] # M: high = 661.00 (vârful)
wicks += [(0.20, 0.40), # trigger
(0.25, 0.51), # TP1: low = 657.80-0.51 = 657.29
(0.30, 0.30), (0.30, 0.30), (0.30, 0.30),
(0.30, 0.30), (0.30, 0.30), (0.30, 0.30)] # trail
assert len(wicks) == len(closes)
cds = from_closes(closes, wicks=wicks, start=648.90)
# ── assert-urile din specificație ───────────────────────────────────────────
entry = cds[E_I].c
sl = SL
tp1 = TP1
exit_ = cds[EXIT_I].c
assert abs(entry - 660.00) < 0.01, entry
assert entry == cds[E_I].c, "intrarea nu e close-ul candelei trigger"
assert abs((sl - entry) / entry * 100 - 0.41) < 0.01, (sl - entry) / entry * 100
assert abs((sl - entry) - 2.71) < 0.01, sl - entry
assert abs(tp1 - (entry - (sl - entry))) < 0.01, (entry - (sl - entry), tp1)
assert cds[TP1_I].l <= tp1 + 1e-9, "prima candelă după intrare nu atinge TP1"
assert abs(cds[TP1_I].l - tp1) < 0.01, cds[TP1_I].l
assert abs((entry - exit_) / entry * 100 - 2.0) < 0.05, (entry - exit_) / entry * 100
assert abs((entry - exit_) / (sl - entry) - 4.9) < 0.1, (entry - exit_) / (sl - entry)
assert EXIT_I - E_I == 7, EXIT_I - E_I
assert sl > entry > tp1, "short: SL deasupra intrării, TP1 sub intrare"
assert BAND > cds[M_I].c, "banda schematică nu e deasupra close-ului lui M"
# structura care susține afirmațiile din text
assert cds[M_I].c > cds[M_I].o, "candela M trebuie să fie verde"
assert cds[E_I].c < cds[E_I].o, "candela trigger trebuie să fie roșie"
assert M_I == E_I - 1, "trigger-ul nu e imediat după M"
assert N_RISE == 12, N_RISE
assert 30 <= len(cds) <= 34, len(cds)
assert max(cds[i].c for i in range(12, M_I + 1)) == cds[M_I].c, "M nu e vârful urcării"
assert max(c.h for c in cds) == cds[M_I].h, "vârful seriei nu e la M"
assert all(cds[i].c > cds[i - 1].c for i in range(13, M_I + 1)), "urcarea nu e monotonă"
# ── panoul de preț ──────────────────────────────────────────────────────────
levels = [
Level(SL, "SL 662.71 (risc 0.41 %)", "#c00", label_at="right"),
Level(ENTRY, "intrare short 660.00", "#188a3e", label_at="left"),
Level(BAND, "bandă superioară (schematic)", "#777", dash="2 4", label_at="left"),
Level(TP1, "TP1 657.29 (1:1)", "#1a5fb4", label_at="left"),
Level(EXIT, "ieșire trail 646.80 = 2 % = ~4.9R", "#1a5fb4", label_at="left"),
]
marks = [
Mark(M_I, "M: candelă verde peste bandă + TICUS ≥ +1000", "#188a3e", "above"),
]
cds[M_I].note = ("M: valori exacte NEACOPERIT în material (bandă + TICUS); "
"bara ~+1000 din panou e ilustrativă")
cds[E_I].note = "trigger Sell Momentum → intrare short 660.00 (risc 0.41 % = 2.71)"
cds[TP1_I].note = "TP1 657.29 (1:1) atins în prima candelă după intrare"
cds[EXIT_I].note = ("ieșire pe trail 646.80 = 2 % = ~4.9R, după 7 candele "
"de 15 min de la intrare")
ch = Chart(
candles=cds,
levels=levels,
marks=marks,
mas=[MA(20, "#b06000", dash="4 3", text="SMA-20")],
scale=SCALE, width=W, gap=GAP, body_w=BODY_W, left=LEFT,
title="SPY — 15 min: MBAP short 25.11.2025 (risc 0.41 % → ieșire 4.9R)",
caption="",
)
price_svg = ch.render()
# corpul graficului de preț și înălțimea lui (pentru lipirea panoului TICUS)
m = re.search(r'viewBox="0 0 (\d+) ([\d.]+)"', price_svg)
H1 = float(m.group(2))
body1 = price_svg[price_svg.index(">") + 1:price_svg.rindex("</svg>")].strip("\n")
# ── panoul TICUS dedesubt (bar ilustrativ la candela M) ─────────────────────
VGAP = 26
Y0 = H1 + VGAP
TICUS_H = 100
zero_y = Y0 + 84 # linia 0
plus_y = Y0 + 32 # linia +1000 (punctată)
bar_top = plus_y - 18 # bara depășește +1000
m_x = LEFT + M_I * GAP
assert bar_top < plus_y, "bara TICUS nu depășește linia +1000"
assert plus_y < zero_y, "linia +1000 nu e deasupra liniei 0"
ticus = [
f'<text x="24" y="{Y0 + 16:.0f}" fill="#333" style="font-weight:bold">'
f'Panou TICUS — linia 0 și pragul +1000 (ilustrativ)</text>',
f'<line x1="24" y1="{plus_y:.1f}" x2="{W - 14}" y2="{plus_y:.1f}" '
f'stroke="#b06000" stroke-width="1.2" stroke-dasharray="2 4"/>',
f'<text x="{W - 28}" y="{plus_y - 5:.1f}" fill="#b06000" text-anchor="end">'
f'+1000 (prag)</text>',
f'<line x1="24" y1="{zero_y:.1f}" x2="{W - 14}" y2="{zero_y:.1f}" stroke="#888"/>',
f'<text x="24" y="{zero_y - 5:.1f}" fill="#555">0</text>',
f'<rect x="{m_x - BODY_W / 2:.1f}" y="{bar_top:.1f}" width="{BODY_W}" '
f'height="{zero_y - bar_top:.1f}" fill="#188a3e" opacity="0.85"/>',
f'<text x="{m_x:.1f}" y="{bar_top - 5:.1f}" fill="#777" text-anchor="middle">'
f'ilustrativ</text>',
f'<text x="{m_x:.1f}" y="{zero_y + 16:.1f}" fill="#777" text-anchor="middle">'
f'candela M</text>',
]
# ── legenda (cifrele/sursele) ───────────────────────────────────────────────
legend = [
"Legendă: procentele (risc 0.41 %, ieșire 2 %) sunt ale instructorului.",
"Prețurile absolute (660.00 / 662.71 / 657.29 / 646.80) sunt construite de sumarizator —",
"09_QA/11_2025-11-25.md:716-719.",
"Structura de reținut: risc mic → TP1 (1:1) atins imediat → trail lung (7 candele de 15 min).",
"Valoarea exactă a benzii și a TICUS la candela M nu e dată în sesiune; bara ~+1000 e ilustrativă.",
"„Taurii urcă pe scări, urșii sar de la etaj\" — de ce short-ul se consumă în ore.",
"(09_QA/11_2025-11-25.md:643-648).",
]
cap_top = Y0 + TICUS_H + 20
H = cap_top + 16 * (len(legend) - 1) + 14
parts = [
f'<svg viewBox="0 0 {W} {H:.0f}" xmlns="http://www.w3.org/2000/svg" '
f'style="width:100%;height:auto;font:12px sans-serif">',
'<g transform="translate(0,0)">',
body1,
"</g>",
*ticus,
*[f'<text x="24" y="{cap_top + 16 * i:.1f}" fill="#555">{t}</text>'
for i, t in enumerate(legend)],
"</svg>",
]
svg = "\n ".join(parts)
assert "\n\n" not in svg, "linie goală în SVG — rupe markdown-ul"
assert svg.count("<svg") == 1 and svg.count("</svg>") == 1, "SVG-ul trebuie un singur root"
# ── verificare de citire: etichete în panză, fără suprapuneri ───────────────
TEXT = re.compile(r'<text x="([-\d.]+)" y="([-\d.]+)"([^>]*)>(.*?)</text>', re.S)
def _fonts():
"""Lățimea reală a textului (DejaVuSans ~ system-ui); fallback estimativ."""
try:
from PIL import ImageFont
base = "/usr/share/fonts/truetype/dejavu/"
return (ImageFont.truetype(base + "DejaVuSans.ttf", 12),
ImageFont.truetype(base + "DejaVuSans-Bold.ttf", 12))
except Exception:
return None, None
_F_NORM, _F_BOLD = _fonts()
def _tw(s: str, bold: bool) -> float:
if _F_BOLD is not None:
return (_F_BOLD if bold else _F_NORM).getlength(s)
return len(s) * 12 * (0.62 if bold else 0.52)
labels = []
for mm in TEXT.finditer(svg):
x, y = float(mm.group(1)), float(mm.group(2))
attrs, s = mm.group(3), mm.group(4)
anchor = "start"
if 'text-anchor="end"' in attrs:
anchor = "end"
elif 'text-anchor="middle"' in attrs:
anchor = "middle"
w = _tw(s, "bold" in attrs)
left_ = x - w if anchor == "end" else (x - w / 2 if anchor == "middle" else x)
labels.append((left_, left_ + w, y, s))
for left_, right_, y, s in labels:
assert left_ >= -2 and right_ <= W + 2, f"text iese din panză: {s!r}"
assert 0 <= y <= H, f"text în afara înălțimii: {s!r} (y={y:.0f})"
for i in range(len(labels)):
li, ri, yi, si = labels[i]
for j in range(i + 1, len(labels)):
lj, rj, yj, sj = labels[j]
if abs(yi - yj) < 12 and li < rj - 4 and lj < ri - 4:
raise AssertionError(f"texte suprapuse: {si!r} / {sj!r} (y={yi:.0f}/{yj:.0f})")
OUT.write_text(svg, encoding="utf-8")
print(f"serie {len(cds)} candele | M@{M_I} {cds[M_I].c:.2f} (h {cds[M_I].h:.2f}) | "
f"trigger@{E_I} {entry:.2f} | SL {sl:.2f} ({(sl-entry)/entry*100:.2f} %) | "
f"TP1 {tp1:.2f} (low {cds[TP1_I].l:.2f}) | ieșire@{EXIT_I} {exit_:.2f} "
f"({(entry-exit_)/entry*100:.2f} %, {(entry-exit_)/(sl-entry):.2f}R)")
print(f"scris {OUT} ({W}x{H:.0f}); {len(labels)} etichete verificate, fără suprapuneri")

View File

@@ -0,0 +1,101 @@
<svg viewBox="0 0 660 730" xmlns="http://www.w3.org/2000/svg" style="width:100%;height:auto;font:12px sans-serif">
<g transform="translate(0,0)">
<text x="24" y="48" fill="#333" style="font-weight:bold">SPY — 15 min: MBAP short 25.11.2025 (risc 0.41 % → ieșire 4.9R)</text>
<line x1="24" y1="56.0" x2="646" y2="56.0" stroke="#c00" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="632" y="50.0" fill="#c00" text-anchor="end">SL 662.71 (risc 0.41 %)</text>
<line x1="24" y1="110.2" x2="646" y2="110.2" stroke="#188a3e" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="30" y="104.2" fill="#188a3e">intrare short 660.00</text>
<line x1="24" y1="92.2" x2="646" y2="92.2" stroke="#777" stroke-width="1.5" stroke-dasharray="2 4"/>
<text x="30" y="86.2" fill="#777">bandă superioară (schematic)</text>
<line x1="24" y1="164.4" x2="646" y2="164.4" stroke="#1a5fb4" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="30" y="158.4" fill="#1a5fb4">TP1 657.29 (1:1)</text>
<line x1="24" y1="374.2" x2="646" y2="374.2" stroke="#1a5fb4" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="30" y="368.2" fill="#1a5fb4">ieșire trail 646.80 = 2 % = ~4.9R</text>
<polyline points="325.0,314.0 340.0,306.7 355.0,297.8 370.0,288.2 385.0,276.1 400.0,265.0 415.0,255.3 430.0,247.6 445.0,240.5 460.0,235.6 475.0,232.0 490.0,231.2 505.0,232.7" fill="none" stroke="#b06000" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="511" y="227" fill="#b06000">SMA-20</text>
<line x1="40.0" y1="323.2" x2="40.0" y2="339.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="35.5" y="330.2" width="9" height="2.0" fill="#188a3e"/>
<line x1="55.0" y1="323.2" x2="55.0" y2="345.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="50.5" y="330.2" width="9" height="8.0" fill="#c00"/>
<line x1="70.0" y1="319.2" x2="70.0" y2="345.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="65.5" y="326.2" width="9" height="12.0" fill="#188a3e"/>
<line x1="85.0" y1="319.2" x2="85.0" y2="349.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="80.5" y="326.2" width="9" height="16.0" fill="#c00"/>
<line x1="100.0" y1="325.2" x2="100.0" y2="349.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="95.5" y="332.2" width="9" height="10.0" fill="#188a3e"/>
<line x1="115.0" y1="325.2" x2="115.0" y2="355.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="110.5" y="332.2" width="9" height="16.0" fill="#c00"/>
<line x1="130.0" y1="329.2" x2="130.0" y2="355.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="125.5" y="336.2" width="9" height="12.0" fill="#188a3e"/>
<line x1="145.0" y1="329.2" x2="145.0" y2="359.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="140.5" y="336.2" width="9" height="16.0" fill="#c00"/>
<line x1="160.0" y1="333.2" x2="160.0" y2="359.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="155.5" y="340.2" width="9" height="12.0" fill="#188a3e"/>
<line x1="175.0" y1="333.2" x2="175.0" y2="357.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="170.5" y="340.2" width="9" height="10.0" fill="#c00"/>
<line x1="190.0" y1="331.2" x2="190.0" y2="357.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="185.5" y="338.2" width="9" height="12.0" fill="#188a3e"/>
<line x1="205.0" y1="331.2" x2="205.0" y2="351.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="200.5" y="338.2" width="9" height="6.0" fill="#c00"/>
<line x1="220.0" y1="332.2" x2="220.0" y2="349.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="215.5" y="338.2" width="9" height="6.0" fill="#188a3e"/>
<line x1="235.0" y1="316.2" x2="235.0" y2="343.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="230.5" y="322.2" width="9" height="16.0" fill="#188a3e"/>
<line x1="250.0" y1="298.2" x2="250.0" y2="327.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="245.5" y="304.2" width="9" height="18.0" fill="#188a3e"/>
<line x1="265.0" y1="280.2" x2="265.0" y2="309.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="260.5" y="286.2" width="9" height="18.0" fill="#188a3e"/>
<line x1="280.0" y1="262.2" x2="280.0" y2="291.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="275.5" y="268.2" width="9" height="18.0" fill="#188a3e"/>
<line x1="295.0" y1="242.2" x2="295.0" y2="273.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="290.5" y="248.2" width="9" height="20.0" fill="#188a3e"/>
<line x1="310.0" y1="222.2" x2="310.0" y2="253.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="305.5" y="228.2" width="9" height="20.0" fill="#188a3e"/>
<line x1="325.0" y1="200.2" x2="325.0" y2="233.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="320.5" y="206.2" width="9" height="22.0" fill="#188a3e"/>
<line x1="340.0" y1="178.2" x2="340.0" y2="211.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="335.5" y="184.2" width="9" height="22.0" fill="#188a3e"/>
<line x1="355.0" y1="154.2" x2="355.0" y2="189.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="350.5" y="160.2" width="9" height="24.0" fill="#188a3e"/>
<line x1="370.0" y1="128.2" x2="370.0" y2="165.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="365.5" y="134.2" width="9" height="26.0" fill="#188a3e"/>
<line x1="385.0" y1="90.2" x2="385.0" y2="138.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="380.5" y="100.2" width="9" height="34.0" fill="#188a3e"/>
<line x1="400.0" y1="96.2" x2="400.0" y2="118.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="395.5" y="100.2" width="9" height="10.0" fill="#c00"/>
<line x1="415.0" y1="105.2" x2="415.0" y2="164.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="410.5" y="110.2" width="9" height="44.0" fill="#c00"/>
<line x1="430.0" y1="148.2" x2="430.0" y2="188.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="425.5" y="154.2" width="9" height="28.0" fill="#c00"/>
<line x1="445.0" y1="176.2" x2="445.0" y2="216.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="440.5" y="182.2" width="9" height="28.0" fill="#c00"/>
<line x1="460.0" y1="204.2" x2="460.0" y2="248.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="455.5" y="210.2" width="9" height="32.0" fill="#c00"/>
<line x1="475.0" y1="236.2" x2="475.0" y2="284.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="470.5" y="242.2" width="9" height="36.0" fill="#c00"/>
<line x1="490.0" y1="272.2" x2="490.0" y2="328.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="485.5" y="278.2" width="9" height="44.0" fill="#c00"/>
<line x1="505.0" y1="316.2" x2="505.0" y2="380.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="500.5" y="322.2" width="9" height="52.0" fill="#c00"/>
<text x="385.0" y="80.2" fill="#188a3e" text-anchor="middle" style="font-weight:bold">M: candelă verde peste bandă + TICUS ≥ +1000</text>
<text x="24" y="402.2" fill="#188a3e">M: valori exacte NEACOPERIT în material (bandă + TICUS); bara ~+1000 din panou e ilustrativă</text>
<text x="24" y="419.2" fill="#c00">trigger Sell Momentum → intrare short 660.00 (risc 0.41 % = 2.71)</text>
<text x="24" y="436.2" fill="#c00">TP1 657.29 (1:1) atins în prima candelă după intrare</text>
<text x="24" y="453.2" fill="#c00">ieșire pe trail 646.80 = 2 % = ~4.9R, după 7 candele de 15 min de la intrare</text>
</g>
<text x="24" y="516" fill="#333" style="font-weight:bold">Panou TICUS — linia 0 și pragul +1000 (ilustrativ)</text>
<line x1="24" y1="532.0" x2="646" y2="532.0" stroke="#b06000" stroke-width="1.2" stroke-dasharray="2 4"/>
<text x="632" y="527.0" fill="#b06000" text-anchor="end">+1000 (prag)</text>
<line x1="24" y1="584.0" x2="646" y2="584.0" stroke="#888"/>
<text x="24" y="579.0" fill="#555">0</text>
<rect x="380.5" y="514.0" width="9" height="70.0" fill="#188a3e" opacity="0.85"/>
<text x="385.0" y="509.0" fill="#777" text-anchor="middle">ilustrativ</text>
<text x="385.0" y="600.0" fill="#777" text-anchor="middle">candela M</text>
<text x="24" y="620.0" fill="#555">Legendă: procentele (risc 0.41 %, ieșire 2 %) sunt ale instructorului.</text>
<text x="24" y="636.0" fill="#555">Prețurile absolute (660.00 / 662.71 / 657.29 / 646.80) sunt construite de sumarizator —</text>
<text x="24" y="652.0" fill="#555">09_QA/11_2025-11-25.md:716-719.</text>
<text x="24" y="668.0" fill="#555">Structura de reținut: risc mic → TP1 (1:1) atins imediat → trail lung (7 candele de 15 min).</text>
<text x="24" y="684.0" fill="#555">Valoarea exactă a benzii și a TICUS la candela M nu e dată în sesiune; bara ~+1000 e ilustrativă.</text>
<text x="24" y="700.0" fill="#555">„Taurii urcă pe scări, urșii sar de la etaj" — de ce short-ul se consumă în ore.</text>
<text x="24" y="716.0" fill="#555">(09_QA/11_2025-11-25.md:643-648).</text>
</svg>

After

Width:  |  Height:  |  Size: 8.4 KiB

View File

@@ -0,0 +1,204 @@
"""Diagrama „aceeași candelă" MBP / MBPA++ (marcajul `samecandle`).
Două panouri alături, fiecare cu un grafic de 10 candele de 15 min și un panou
TICUS dedesubt, legate printr-o linie punctată verticală de la candela M la
valoarea ei TICUS. Singura diferență dintre panouri: pe care candelă e TICUS
extrem.
- Panoul DA: M (roșie, index 5) închide 623.87 sub banda inferioară 623.90
ȘI TICUS(M) = -1150 (≤ -1000) — pe EXACT aceeași candelă. M îndeplinit.
- Panoul NU: aceeași structură de preț, dar TICUS(M) = -600 (peste prag),
iar extremul de -1200 e pe candela m-1, a cărei închidere e peste bandă.
M neîndeplinit, deși vizual „pare" același setup.
Regula și cifrele vin din `summaries/05_MODUL_4/03_strategia_6_mbp_mbappe.md`:
- regula (bandă și TICUS pe ACEEAȘI candelă) — liniile 179-221, 439-442;
- verificarea prin click pe candelă, nu din ochi — liniile 217-221, 436-438;
- pragurile ±1000, ±500 doar referință vizuală — liniile 56-64;
- M close 623.87 sub banda 623.90 și TICUS(M) -1150 (exemplul real) —
liniile 336-342.
Cifrele din panoul NU (TICUS(M) = -600, TICUS(m-1) = -1200) sunt construite de
mine ca ilustrare a cazului „aproape, dar nu" (spec, rândurile 27-31).
Structura candelelor e construită de mine, ca fiecare nivel afirmat să iasă
exact din serie (assert-urile de mai jos).
Rulează:
python3 gen/strategii/mbp_samecandle.py
Scrie lângă el `mbp_samecandle.svg`.
"""
import re
import sys
from pathlib import Path
sys.path.insert(0, "/workspace/atm-curs-dl")
from svg_candles import Chart, Level, from_closes # noqa: E402
OUT = Path(__file__).resolve().parent / "mbp_samecandle.svg"
GRAY, RED, GREEN, BLUE, ORANGE = "#777", "#c00", "#188a3e", "#1a5fb4", "#b06000"
# ---------------------------------------------------------------- seria (10 bare)
# 5 bare de coborâre până la M (index 5), apoi 4 bare care își revin. Open-ul
# fiecărei bare = close-ul precedentei. Aceeași serie în ambele panouri.
CLOSES = [625.35, 625.10, 624.90, 624.65, 624.30, # 0-4 downtrend
623.87, # 5 = M, roșie, sub bandă
623.98, 624.12, 624.28, 624.45] # 6-9 revenire
WICKS = [(0.08, 0.08), (0.10, 0.10), (0.08, 0.10), (0.10, 0.10), (0.08, 0.12),
(0.06, 0.10), # M: high 624.36, low 623.77
(0.06, 0.06), (0.08, 0.06), (0.06, 0.06), (0.08, 0.05)]
assert len(CLOSES) == 10 and len(WICKS) == 10, (len(CLOSES), len(WICKS))
BAND = 623.90
M = 5
cds = from_closes(CLOSES, wicks=WICKS, start=625.55)
# ---------------------------------------------------------------- assert-urile din spec
assert abs(cds[M].c - 623.87) < 0.005, cds[M].c # exemplul real din curs
assert cds[M].c < BAND, cds[M].c # M închide sub banda inferioară
assert cds[M].c < cds[M].o, (cds[M].o, cds[M].c) # M e roșie
assert abs(cds[M].l - 623.77) < 0.005, cds[M].l
assert cds[M].l == min(c.l for c in cds) # M are minimul seriei
assert cds[M - 1].c > BAND, cds[M - 1].c # candela m-1 închide PESTE bandă
assert [round(c.c, 2) for c in cds] == CLOSES # close-urile sunt respectate
# TICUS: valoarea pe candelă. DA: doar M, sub prag. NU: extremul pe m-1.
TICUS_DA = {M: -1150.0}
TICUS_NU = {M - 1: -1200.0, M: -600.0}
assert TICUS_DA[M] <= -1000, TICUS_DA[M] # DA: ticus(M) under -1000
assert TICUS_NU[M] > -1000, TICUS_NU[M] # NU: ticus(M) peste prag
assert TICUS_NU[M - 1] <= -1000, TICUS_NU[M - 1] # NU: extremul e pe m-1
assert not any(i == M and v <= -1000 for i, v in TICUS_NU.items()), \
"NU: nu are voie să existe bar TICUS sub -1000 aliniat cu candela M"
# ---------------------------------------------------------------- construcția panourilor
SCALE, GAP, BODY, LEFT, W = 85.0, 16, 10, 44, 380
def _ref_line(Wv, y, color, dash, text, lab_dy):
dash_attr = f' stroke-dasharray="{dash}"' if dash else ""
return (f'<line x1="24" y1="{y:.1f}" x2="{Wv - 14}" y2="{y:.1f}" stroke="{color}" '
f'stroke-width="1"{dash_attr}/>'
f'<text x="26" y="{y + lab_dy:.1f}" fill="{color}">{text}</text>')
def build_panel(ticus, labels):
"""Un panou = grafic de preț (svg_candles) + panou TICUS dedesubt."""
ch = Chart(candles=cds,
levels=[Level(BAND, "bandă 623.90", BLUE, dash="4 3", label_at="right")],
scale=SCALE, gap=GAP, body_w=BODY, left=LEFT, width=W)
raw = ch.render()
mm = re.search(r'viewBox="0 0 (\d+) ([0-9.]+)"', raw)
Wv, H0 = int(mm.group(1)), float(mm.group(2))
inner = raw[raw.index(">") + 1: raw.rindex("</svg>")].strip("\n")
ticus_top = H0 + 30
y0 = H0 + 100 # linia 0 a TICUS
PX = 44.0 # 1000 unități TICUS = 44 px
bar_label_x = ch._cx(M) + 16 # etichetele barelor stau în coloană, la dreapta
def ty(v):
return y0 - v / 1000.0 * PX
total_h = y0 + 96
parts = []
# linia punctată verticală: aceeași candelă sus și jos (desenată prima, ca
# etichetele să vină peste ea)
for i, v in sorted(ticus.items()):
xb = ch._cx(i)
assert -1 <= xb <= Wv + 1, xb
parts.append(f'<line x1="{xb:.1f}" y1="{ch._y(cds[i].l):.1f}" x2="{xb:.1f}" '
f'y2="{ty(v):.1f}" stroke="#aaa" stroke-width="1" stroke-dasharray="3 3"/>')
parts.append(f'<text x="14" y="{ticus_top:.0f}" fill="#333" '
f'style="font-weight:bold">TICUS</text>')
# praguri: 0 (linie plină), ±1000 (punctate), ±500 (fine, doar referință)
parts.append(_ref_line(Wv, ty(1000), RED, "4 3", "+1000 (prag)", -5))
parts.append(_ref_line(Wv, ty(500), "#caa000", "2 2", "+500", -5))
parts.append(_ref_line(Wv, ty(0), "#888", "", "0", 4))
parts.append(_ref_line(Wv, ty(-500), "#caa000", "2 2", "-500", 13))
parts.append(_ref_line(Wv, ty(-1000), GREEN, "4 3", "-1000 (prag)", 13))
# barele TICUS (centrate pe x-ul candelei)
for i, v in sorted(ticus.items()):
xb = ch._cx(i)
yv = ty(v)
col = RED if v <= -1000 else ORANGE
parts.append(f'<rect x="{xb - BODY / 2:.1f}" y="{min(y0, yv):.1f}" '
f'width="{BODY}" height="{abs(yv - y0):.1f}" fill="{col}"/>')
# eticheta de valoare lângă fiecare bar (aceeași coloană x, rânduri diferite)
for i, (txt, dy) in sorted(labels.items()):
col = RED if ticus[i] <= -1000 else ORANGE
parts.append(f'<text x="{bar_label_x:.1f}" y="{ty(ticus[i]) + dy:.1f}" '
f'fill="{col}">{txt}</text>')
# adnotarea candelei M, în zona de preț, sub bară (zona e liberă)
parts.append(f'<text x="{ch._cx(M):.1f}" y="{ch._y(cds[M].l) + 18:.1f}" fill="{RED}" '
f'text-anchor="middle" style="font-weight:bold">'
f'M: close 623.87 &lt; bandă 623.90</text>')
# alinierea TICUS(M) cu candela M: barul se centrează exact pe x-ul candelei
xm = ch._cx(M)
x_bar_m = ch._cx(M) # centrul barei TICUS a lui M
assert abs(x_bar_m - xm) < 1e-9
assert ticus[M] <= -1000 if ticus is TICUS_DA else ticus[M] > -1000
body = inner + "\n " + "\n ".join(parts)
# bara TICUS a lui M chiar a fost scrisă cu centrul pe x-ul candelei M
assert f'<rect x="{xm - BODY / 2:.1f}"' in body, "bara TICUS(M) nu e aliniată cu M"
assert f'<line x1="{xm:.1f}"' in body, "linia punctată verticală nu pleacă din M"
return ch, Wv, H0, total_h, body
labels_da = {M: ("M: TICUS -1150 (sub -1000)", 18)}
labels_nu = {M - 1: ("TICUS(m-1) = -1200 (extrem)", 20),
M: ("TICUS(M) = -600 (nu atinge -1000)", 20)}
ch_da, Wv, H0, PH, inner_da = build_panel(TICUS_DA, labels_da)
ch_nu, Wv2, H02, PH2, inner_nu = build_panel(TICUS_NU, labels_nu)
assert Wv == Wv2 and PH == PH2, (PH, PH2)
# aceeași structură de preț => aceleași x-uri ale candelelor în ambele panouri
assert all(abs(ch_da._cx(i) - ch_nu._cx(i)) < 1e-9 for i in range(len(cds)))
# ---------------------------------------------------------------- compunerea SVG-ului
TOP = 48
FOOTER = [
"Regula: banda (close sub 623.90) și TICUS ≤ -1000 trebuie pe ACEEAȘI candelă.",
"Pragurile TICUS sunt ±1000 (overbought / oversold); ±500 sunt doar referință vizuală.",
"Verificarea se face prin click pe candelă, nu din ochi — TICUS(M) se citește exact pe candela M.",
"DA: TICUS(M) = -1150 (exemplul real din curs).",
"NU: TICUS(M) = -600, TICUS(m-1) = -1200 — cifre construite de mine ca ilustrare.",
"Structura candelelor (10 bare de 15 min, aceeași în ambele panouri) e construită de mine.",
]
root_h = TOP + PH + 22 + 17 * len(FOOTER) + 8
out = [f'<svg viewBox="0 0 780 {root_h:.0f}" xmlns="http://www.w3.org/2000/svg" '
f'style="width:100%;height:auto;font-family:system-ui,sans-serif;font-size:13px">',
f'<svg x="8" y="{TOP}" width="{Wv}" height="{PH:.0f}" viewBox="0 0 {Wv} {PH:.0f}">{inner_da}</svg>',
f'<svg x="396" y="{TOP}" width="{Wv}" height="{PH:.0f}" viewBox="0 0 {Wv} {PH:.0f}">{inner_nu}</svg>',
f'<text x="8" y="22" fill="{GREEN}" style="font-weight:bold">DA — aceeași candelă, M îndeplinit</text>',
f'<text x="396" y="16" fill="{RED}" style="font-weight:bold">NU — TICUS extrem pe altă candelă,</text>',
f'<text x="396" y="32" fill="{RED}" style="font-weight:bold">M neîndeplinit</text>']
fy = TOP + PH + 22
for i, line in enumerate(FOOTER):
out.append(f'<text x="8" y="{fy + 17 * i:.0f}" fill="#555">{line}</text>')
out.append("</svg>")
svg = "\n ".join(out)
# ---------------------------------------------------------------- verificări pe SVG-ul final
assert svg.count("\n\n") == 0, "linie goală în SVG rupe blocul din markdown"
assert "DA — aceeași candelă, M îndeplinit" in svg
assert "NU — TICUS extrem pe altă candelă," in svg
assert "M neîndeplinit" in svg
assert "click pe candelă, nu din ochi" in svg
assert svg.count("<svg") == 3 and svg.count("</svg>") == 3, "cele două panouri + rădăcina"
OUT.write_text(svg)
print(f"scris {OUT.name}: {len(cds)} candele/panou, M close {cds[M].c:.2f} "
f"(< {BAND}), TICUS DA {TICUS_DA[M]:.0f} / NU {TICUS_NU[M]:.0f} "
f"(extrem {TICUS_NU[M - 1]:.0f} pe m-1), x(M)={ch_da._cx(M):.0f}, "
f"înălțime {root_h:.0f}px")

View File

@@ -0,0 +1,78 @@
<svg viewBox="0 0 780 594" xmlns="http://www.w3.org/2000/svg" style="width:100%;height:auto;font-family:system-ui,sans-serif;font-size:13px">
<svg x="8" y="48" width="380" height="414" viewBox="0 0 380 414"> <line x1="24" y1="181.1" x2="366" y2="181.1" stroke="#1a5fb4" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="352" y="175.1" fill="#1a5fb4" text-anchor="end">bandă 623.90</text>
<line x1="44.0" y1="34.0" x2="44.0" y2="64.6" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="39.0" y="40.8" width="10" height="17.0" fill="#c00"/>
<line x1="60.0" y1="49.3" x2="60.0" y2="87.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="55.0" y="57.8" width="10" height="21.2" fill="#c00"/>
<line x1="76.0" y1="72.2" x2="76.0" y2="104.6" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="71.0" y="79.0" width="10" height="17.0" fill="#c00"/>
<line x1="92.0" y1="87.5" x2="92.0" y2="125.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="87.0" y="96.1" width="10" height="21.2" fill="#c00"/>
<line x1="108.0" y1="110.5" x2="108.0" y2="157.3" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="103.0" y="117.3" width="10" height="29.8" fill="#c00"/>
<line x1="124.0" y1="142.0" x2="124.0" y2="192.1" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="119.0" y="147.1" width="10" height="36.5" fill="#c00"/>
<line x1="140.0" y1="169.2" x2="140.0" y2="188.7" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="135.0" y="174.2" width="10" height="9.4" fill="#188a3e"/>
<line x1="156.0" y1="155.5" x2="156.0" y2="179.3" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="151.0" y="162.3" width="10" height="11.9" fill="#188a3e"/>
<line x1="172.0" y1="143.7" x2="172.0" y2="167.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="167.0" y="148.8" width="10" height="13.6" fill="#188a3e"/>
<line x1="188.0" y1="127.5" x2="188.0" y2="153.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="183.0" y="134.3" width="10" height="14.5" fill="#188a3e"/>
<line x1="124.0" y1="192.1" x2="124.0" y2="368.6" stroke="#aaa" stroke-width="1" stroke-dasharray="3 3"/>
<text x="14" y="248" fill="#333" style="font-weight:bold">TICUS</text>
<line x1="24" y1="274.0" x2="366" y2="274.0" stroke="#c00" stroke-width="1" stroke-dasharray="4 3"/><text x="26" y="269.0" fill="#c00">+1000 (prag)</text>
<line x1="24" y1="296.0" x2="366" y2="296.0" stroke="#caa000" stroke-width="1" stroke-dasharray="2 2"/><text x="26" y="291.0" fill="#caa000">+500</text>
<line x1="24" y1="318.0" x2="366" y2="318.0" stroke="#888" stroke-width="1"/><text x="26" y="322.0" fill="#888">0</text>
<line x1="24" y1="340.0" x2="366" y2="340.0" stroke="#caa000" stroke-width="1" stroke-dasharray="2 2"/><text x="26" y="353.0" fill="#caa000">-500</text>
<line x1="24" y1="362.0" x2="366" y2="362.0" stroke="#188a3e" stroke-width="1" stroke-dasharray="4 3"/><text x="26" y="375.0" fill="#188a3e">-1000 (prag)</text>
<rect x="119.0" y="318.0" width="10" height="50.6" fill="#c00"/>
<text x="140.0" y="386.6" fill="#c00">M: TICUS -1150 (sub -1000)</text>
<text x="124.0" y="210.1" fill="#c00" text-anchor="middle" style="font-weight:bold">M: close 623.87 &lt; bandă 623.90</text></svg>
<svg x="396" y="48" width="380" height="414" viewBox="0 0 380 414"> <line x1="24" y1="181.1" x2="366" y2="181.1" stroke="#1a5fb4" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="352" y="175.1" fill="#1a5fb4" text-anchor="end">bandă 623.90</text>
<line x1="44.0" y1="34.0" x2="44.0" y2="64.6" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="39.0" y="40.8" width="10" height="17.0" fill="#c00"/>
<line x1="60.0" y1="49.3" x2="60.0" y2="87.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="55.0" y="57.8" width="10" height="21.2" fill="#c00"/>
<line x1="76.0" y1="72.2" x2="76.0" y2="104.6" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="71.0" y="79.0" width="10" height="17.0" fill="#c00"/>
<line x1="92.0" y1="87.5" x2="92.0" y2="125.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="87.0" y="96.1" width="10" height="21.2" fill="#c00"/>
<line x1="108.0" y1="110.5" x2="108.0" y2="157.3" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="103.0" y="117.3" width="10" height="29.8" fill="#c00"/>
<line x1="124.0" y1="142.0" x2="124.0" y2="192.1" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="119.0" y="147.1" width="10" height="36.5" fill="#c00"/>
<line x1="140.0" y1="169.2" x2="140.0" y2="188.7" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="135.0" y="174.2" width="10" height="9.4" fill="#188a3e"/>
<line x1="156.0" y1="155.5" x2="156.0" y2="179.3" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="151.0" y="162.3" width="10" height="11.9" fill="#188a3e"/>
<line x1="172.0" y1="143.7" x2="172.0" y2="167.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="167.0" y="148.8" width="10" height="13.6" fill="#188a3e"/>
<line x1="188.0" y1="127.5" x2="188.0" y2="153.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="183.0" y="134.3" width="10" height="14.5" fill="#188a3e"/>
<line x1="108.0" y1="157.3" x2="108.0" y2="370.8" stroke="#aaa" stroke-width="1" stroke-dasharray="3 3"/>
<line x1="124.0" y1="192.1" x2="124.0" y2="344.4" stroke="#aaa" stroke-width="1" stroke-dasharray="3 3"/>
<text x="14" y="248" fill="#333" style="font-weight:bold">TICUS</text>
<line x1="24" y1="274.0" x2="366" y2="274.0" stroke="#c00" stroke-width="1" stroke-dasharray="4 3"/><text x="26" y="269.0" fill="#c00">+1000 (prag)</text>
<line x1="24" y1="296.0" x2="366" y2="296.0" stroke="#caa000" stroke-width="1" stroke-dasharray="2 2"/><text x="26" y="291.0" fill="#caa000">+500</text>
<line x1="24" y1="318.0" x2="366" y2="318.0" stroke="#888" stroke-width="1"/><text x="26" y="322.0" fill="#888">0</text>
<line x1="24" y1="340.0" x2="366" y2="340.0" stroke="#caa000" stroke-width="1" stroke-dasharray="2 2"/><text x="26" y="353.0" fill="#caa000">-500</text>
<line x1="24" y1="362.0" x2="366" y2="362.0" stroke="#188a3e" stroke-width="1" stroke-dasharray="4 3"/><text x="26" y="375.0" fill="#188a3e">-1000 (prag)</text>
<rect x="103.0" y="318.0" width="10" height="52.8" fill="#c00"/>
<rect x="119.0" y="318.0" width="10" height="26.4" fill="#b06000"/>
<text x="140.0" y="390.8" fill="#c00">TICUS(m-1) = -1200 (extrem)</text>
<text x="140.0" y="364.4" fill="#b06000">TICUS(M) = -600 (nu atinge -1000)</text>
<text x="124.0" y="210.1" fill="#c00" text-anchor="middle" style="font-weight:bold">M: close 623.87 &lt; bandă 623.90</text></svg>
<text x="8" y="22" fill="#188a3e" style="font-weight:bold">DA — aceeași candelă, M îndeplinit</text>
<text x="396" y="16" fill="#c00" style="font-weight:bold">NU — TICUS extrem pe altă candelă,</text>
<text x="396" y="32" fill="#c00" style="font-weight:bold">M neîndeplinit</text>
<text x="8" y="484" fill="#555">Regula: banda (close sub 623.90) și TICUS ≤ -1000 trebuie pe ACEEAȘI candelă.</text>
<text x="8" y="501" fill="#555">Pragurile TICUS sunt ±1000 (overbought / oversold); ±500 sunt doar referință vizuală.</text>
<text x="8" y="518" fill="#555">Verificarea se face prin click pe candelă, nu din ochi — TICUS(M) se citește exact pe candela M.</text>
<text x="8" y="535" fill="#555">DA: TICUS(M) = -1150 (exemplul real din curs).</text>
<text x="8" y="552" fill="#555">NU: TICUS(M) = -600, TICUS(m-1) = -1200 — cifre construite de mine ca ilustrare.</text>
<text x="8" y="569" fill="#555">Structura candelelor (10 bare de 15 min, aceeași în ambele panouri) e construită de mine.</text>
</svg>

After

Width:  |  Height:  |  Size: 7.3 KiB

View File

@@ -0,0 +1,166 @@
"""Diagrama de ansamblu MDSRT long (strategii, marcajul `ansamblu`).
„SPY — 5 min: al doilea picior mai jos la 589.80, sub suportul 590.00 →
divergență bull (M) preț-vs-volum → candela-trigger Buy Hammer/Doji →
intrare 590.20 → SL 589.40 (risc 0.80) → TP1 591.00 (1:1) → poziția rămasă
pe trail, ieșire la close 592.40 (≈ 2.75R)."
Toate close-urile, wick-urile speciale și nivelurile vin din
`gen/strategii/mdsrt_ansamblu.spec.txt`. Structura M→A→P, suportul 590.00,
minimul 589.80 și riscul 0.80 sunt din exemplul cursului
(`05_MODUL_4/02_strategia_5_mdsrt.md:159-177, 246, 308-311`). Intrare/SL/TP
sunt exemplul construit al autorului sumarului, marcat explicit ca NU citat
din curs (`...:166-177, 439-445`); transcrierea se întrerupe înainte de
planul numeric. Volumele sunt relative, alese de generator (materialul nu dă
volum pentru acest exemplu).
Rulează:
python3 gen/strategii/mdsrt_ansamblu.py
Scrie lângă el `mdsrt_ansamblu.svg`.
"""
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
OUT = Path(__file__).resolve().parent / "mdsrt_ansamblu.svg"
GRAY, RED, GREEN, BLUE, ORANGE = "#777", "#c00", "#188a3e", "#1a5fb4", "#b06000"
# ---------------------------------------------------------------- seria (30 bare)
CLOSES = [
596.00, 595.20, 595.60, 594.80, 594.10, 593.40, 593.70, 592.90, # 0-7
592.20, 591.60, 591.90, 591.30, 590.80, 590.30, 590.55, 590.10, # 8-15
589.85, 590.05, # 16-17
589.80, # 18 = al doilea picior mai jos + candela-trigger (hammer)
590.20, # 19 = intrare (close = entry; sparge high-ul trigger-ului)
590.60, 590.90, 591.00, # 20-22 (22 = TP1 1:1)
591.30, 591.10, 591.45, 591.70, 591.95, 592.15, # 23-28
592.40, # 29 = ieșire pe trail (close)
]
assert len(CLOSES) == 30, len(CLOSES)
# fitile (sus, jos): (0.25, 0.30) peste tot, cu excepțiile din spec.
WICKS = [(0.25, 0.30)] * 30
WICKS[18] = (0.10, 0.40) # hammer: high 590.15, low 589.40 (= SL)
WICKS[19] = (0.10, 0.05) # intrare: high 590.30, low 589.75
# bara 21 coborâtă sub TP1 (high 590.95 < 591.00), ca TP1 să fie atins exact
# prima dată la bara 22 (altfel assert-ul 5 ar pica pe „primul high").
WICKS[21] = (0.05, 0.30)
# volum relativ (ales de generator): 16-19 crescător, 18-19 peste medie,
# vol[18] > vol[10] (higher low pe volum la lower low pe preț).
VOLS = [
48, 46, 44, 45, 43, 41, 40, 42, 44, 46, # 0-9
52, 38, 40, 42, 39, 44, # 10-15
50, 58, 72, 85, # 16-19 absorbție la minim
70, 64, 58, 60, 55, 52, 50, 48, 46, 44, # 20-29
]
assert len(VOLS) == 30, len(VOLS)
cds = from_closes(CLOSES, wicks=WICKS, vols=VOLS, start=596.60)
# ---------------------------------------------------------------- assert-urile din spec
entry = cds[19].c
sl = cds[18].l
tp1 = cds[22].c
# 1. minimul și SL-ul pe bara 18
assert cds[18].c == 589.80, cds[18].c
assert cds[18].l == 589.40, cds[18].l
# 2. al doilea picior rupe suportul/pivotul 590.00
assert cds[18].l < 590.00, cds[18].l
# 3. intrarea = close-ul barei 19 și sparge high-ul trigger-ului
assert cds[19].c == 590.20, cds[19].c
assert cds[19].c == entry
assert cds[19].c > cds[18].h, (cds[19].c, cds[18].h)
# 4. TP1 exact 1:1
assert abs((entry - sl) - (tp1 - entry)) < 1e-9, (entry - sl, tp1 - entry)
assert abs((entry - sl) - 0.80) < 1e-9
# 5. TP1 atins la bara 22 (și nu mai devreme)
assert cds[22].c == 591.00, cds[22].c
assert cds[22].c == tp1
assert max(c.h for c in cds[20:22]) < tp1, [c.h for c in cds[20:22]]
# 6. ieșirea pe trail e pe profit
assert cds[29].c == 592.40, cds[29].c
assert cds[29].c > entry, (cds[29].c, entry)
# 7. hammer: corp mic, coadă lungă în jos
body = abs(cds[18].o - cds[18].c)
rng = cds[18].h - cds[18].l
tail = min(cds[18].o, cds[18].c) - cds[18].l
assert body == 0.25, body
assert body <= 0.5 * rng, (body, rng)
assert tail > body, (tail, body)
# 8. downtrend vizibil
assert cds[13].c < cds[0].c - 5, (cds[13].c, cds[0].c)
# 9. divergență pe volum: lower low pe preț, higher low pe volum
assert cds[18].l < cds[10].l, (cds[18].l, cds[10].l)
assert VOLS[18] > VOLS[10], (VOLS[18], VOLS[10])
# barele 16-19 au volum crescător; 18-19 peste media seriei
assert VOLS[16] < VOLS[17] < VOLS[18] < VOLS[19], VOLS[16:20]
avg_vol = sum(VOLS) / len(VOLS)
assert VOLS[18] > avg_vol and VOLS[19] > avg_vol, (VOLS[18], VOLS[19], avg_vol)
# 10. fiecare open == close-ul barei anterioare
for a, b in zip(cds, cds[1:]):
assert a.c == b.o, (a.c, b.o)
r_mult = (cds[29].c - entry) / (entry - sl)
assert abs(r_mult - 2.75) < 1e-9, r_mult
# ---------------------------------------------------------------- niveluri, marks
levels = [
Level(590.00, "suport / pivot 590.00", BLUE, label_at="left"),
Level(sl, f"SL {sl:.2f} — risc 0.80", RED, label_at="left"),
# stânga: altfel eticheta din dreapta se calcă pe textul „SMA-20" al mediei
Level(tp1, f"TP1 {tp1:.2f} = 1:1", GREEN, label_at="left"),
]
marks = [
Mark(18, f"minim 589.80 — divergență (M)", RED, "below"),
# ridicat deasupra vârfurilor candelelor de urcare (altfel textul cade peste
# fitilele barelor 20-22) — bara 19 e totuși exact sub el
Mark(19, f"intrare {entry:.2f}", GREEN, "above", dy=-46),
Mark(29, f"ieșire trail {cds[29].c:.2f}", GREEN, "above"),
]
# titlul e injectat pe două linii: textul din spec (82 caractere) nu încape
# pe un singur rând în 640 px fără să fie tăiat la margine
TITLE = ["SPY — 5 min (exemplu construit pe scara lecției;",
"cifrele planului nu vin din curs)"]
ch = Chart(
candles=cds, levels=levels, marks=marks,
mas=[MA(20, ORANGE, dash="4 3", text="SMA-20")],
scale=48.0, gap=15, body_w=9, left=44, width=640, top=64, vol_h=70,
vol_label="volum",
title="", caption="",
)
svg = ch.render()
title_svg = "\n ".join(
f'<text x="24" y="{26 + 18 * i}" fill="#333" style="font-weight:bold">{t}</text>'
for i, t in enumerate(TITLE))
i = svg.index(">") + 1
svg = svg[:i] + "\n " + title_svg + svg[i:]
# ---------------------------------------------------------------- legenda (onestitate)
m = re.search(r'viewBox="0 0 (\d+) ([0-9.]+)"', svg)
W, H0 = int(m.group(1)), float(m.group(2))
legend = [
"Legendă: entry/SL/TP/trail sunt exemplul construit al autorului sumarului,",
"nu citate din curs (transcrierea se întrerupe înainte de planul numeric).",
]
legend_svg = [f'<text x="24" y="{H0 + 16 + 17 * i:.1f}" fill="#555">{t}</text>'
for i, t in enumerate(legend)]
new_h = H0 + 16 + 17 * (len(legend) - 1) + 14
svg = re.sub(r'(viewBox="0 0 \d+ )[0-9.]+(")',
lambda mm: mm.group(1) + f"{new_h:.0f}" + mm.group(2), svg, count=1)
svg = svg.replace("\n</svg>", "\n " + "\n ".join(legend_svg) + "\n</svg>")
assert svg.count("\n\n") == 0, "linie goală în SVG rupe blocul din markdown"
assert f"TP1 {tp1:.2f}" in svg and f"intrare {entry:.2f}" in svg
OUT.write_text(svg)
print(f"scris {OUT.name}: {len(cds)} candele, suport 590.00, minim {cds[18].l:.2f}, "
f"intrare {entry:.2f}, SL {sl:.2f} (risc 0.80), TP1 {tp1:.2f} (1:1), "
f"ieșire {cds[29].c:.2f} = {r_mult:.2f}R, vol[18]={VOLS[18]} > vol[10]={VOLS[10]}")

View File

@@ -0,0 +1,109 @@
<svg viewBox="0 0 640 591" xmlns="http://www.w3.org/2000/svg" style="width:100%;height:auto;font:12px sans-serif">
<text x="24" y="26" fill="#333" style="font-weight:bold">SPY — 5 min (exemplu construit pe scara lecției;</text>
<text x="24" y="44" fill="#333" style="font-weight:bold">cifrele planului nu vin din curs)</text>
<line x1="24" y1="392.8" x2="626" y2="392.8" stroke="#1a5fb4" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="30" y="386.8" fill="#1a5fb4">suport / pivot 590.00</text>
<line x1="24" y1="421.6" x2="626" y2="421.6" stroke="#c00" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="30" y="415.6" fill="#c00">SL 589.40 — risc 0.80</text>
<line x1="24" y1="344.8" x2="626" y2="344.8" stroke="#188a3e" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="30" y="338.8" fill="#188a3e">TP1 591.00 = 1:1</text>
<polyline points="329.0,286.4 344.0,299.3 359.0,309.6 374.0,320.7 389.0,329.1 404.0,336.3 419.0,341.0 434.0,345.8 449.0,348.0 464.0,348.2 479.0,346.2" fill="none" stroke="#b06000" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="485" y="340" fill="#b06000">SMA-20</text>
<line x1="44.0" y1="64.0" x2="44.0" y2="119.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="39.5" y="76.0" width="9" height="28.8" fill="#c00"/>
<line x1="59.0" y1="92.8" x2="59.0" y2="157.6" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="54.5" y="104.8" width="9" height="38.4" fill="#c00"/>
<line x1="74.0" y1="112.0" x2="74.0" y2="157.6" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="69.5" y="124.0" width="9" height="19.2" fill="#188a3e"/>
<line x1="89.0" y1="112.0" x2="89.0" y2="176.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="84.5" y="124.0" width="9" height="38.4" fill="#c00"/>
<line x1="104.0" y1="150.4" x2="104.0" y2="210.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="99.5" y="162.4" width="9" height="33.6" fill="#c00"/>
<line x1="119.0" y1="184.0" x2="119.0" y2="244.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="114.5" y="196.0" width="9" height="33.6" fill="#c00"/>
<line x1="134.0" y1="203.2" x2="134.0" y2="244.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="129.5" y="215.2" width="9" height="14.4" fill="#188a3e"/>
<line x1="149.0" y1="203.2" x2="149.0" y2="268.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="144.5" y="215.2" width="9" height="38.4" fill="#c00"/>
<line x1="164.0" y1="241.6" x2="164.0" y2="301.6" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="159.5" y="253.6" width="9" height="33.6" fill="#c00"/>
<line x1="179.0" y1="275.2" x2="179.0" y2="330.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="174.5" y="287.2" width="9" height="28.8" fill="#c00"/>
<line x1="194.0" y1="289.6" x2="194.0" y2="330.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="189.5" y="301.6" width="9" height="14.4" fill="#188a3e"/>
<line x1="209.0" y1="289.6" x2="209.0" y2="344.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="204.5" y="301.6" width="9" height="28.8" fill="#c00"/>
<line x1="224.0" y1="318.4" x2="224.0" y2="368.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="219.5" y="330.4" width="9" height="24.0" fill="#c00"/>
<line x1="239.0" y1="342.4" x2="239.0" y2="392.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="234.5" y="354.4" width="9" height="24.0" fill="#c00"/>
<line x1="254.0" y1="354.4" x2="254.0" y2="392.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="249.5" y="366.4" width="9" height="12.0" fill="#188a3e"/>
<line x1="269.0" y1="354.4" x2="269.0" y2="402.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="264.5" y="366.4" width="9" height="21.6" fill="#c00"/>
<line x1="284.0" y1="376.0" x2="284.0" y2="414.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="279.5" y="388.0" width="9" height="12.0" fill="#c00"/>
<line x1="299.0" y1="378.4" x2="299.0" y2="414.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="294.5" y="390.4" width="9" height="9.6" fill="#188a3e"/>
<line x1="314.0" y1="385.6" x2="314.0" y2="421.6" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="309.5" y="390.4" width="9" height="12.0" fill="#c00"/>
<line x1="329.0" y1="378.4" x2="329.0" y2="404.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="324.5" y="383.2" width="9" height="19.2" fill="#188a3e"/>
<line x1="344.0" y1="352.0" x2="344.0" y2="397.6" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="339.5" y="364.0" width="9" height="19.2" fill="#188a3e"/>
<line x1="359.0" y1="347.2" x2="359.0" y2="378.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="354.5" y="349.6" width="9" height="14.4" fill="#188a3e"/>
<line x1="374.0" y1="332.8" x2="374.0" y2="364.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="369.5" y="344.8" width="9" height="4.8" fill="#188a3e"/>
<line x1="389.0" y1="318.4" x2="389.0" y2="359.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="384.5" y="330.4" width="9" height="14.4" fill="#188a3e"/>
<line x1="404.0" y1="318.4" x2="404.0" y2="354.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="399.5" y="330.4" width="9" height="9.6" fill="#c00"/>
<line x1="419.0" y1="311.2" x2="419.0" y2="354.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="414.5" y="323.2" width="9" height="16.8" fill="#188a3e"/>
<line x1="434.0" y1="299.2" x2="434.0" y2="337.6" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="429.5" y="311.2" width="9" height="12.0" fill="#188a3e"/>
<line x1="449.0" y1="287.2" x2="449.0" y2="325.6" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="444.5" y="299.2" width="9" height="12.0" fill="#188a3e"/>
<line x1="464.0" y1="277.6" x2="464.0" y2="313.6" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="459.5" y="289.6" width="9" height="9.6" fill="#188a3e"/>
<line x1="479.0" y1="265.6" x2="479.0" y2="304.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="474.5" y="277.6" width="9" height="12.0" fill="#188a3e"/>
<line x1="24" y1="517.6" x2="626" y2="517.6" stroke="#888"/>
<rect x="39.5" y="478.1" width="9" height="39.5" fill="#c00" opacity="0.55"/>
<rect x="54.5" y="479.7" width="9" height="37.9" fill="#c00" opacity="0.55"/>
<rect x="69.5" y="481.4" width="9" height="36.2" fill="#188a3e" opacity="0.55"/>
<rect x="84.5" y="480.5" width="9" height="37.1" fill="#c00" opacity="0.55"/>
<rect x="99.5" y="482.2" width="9" height="35.4" fill="#c00" opacity="0.55"/>
<rect x="114.5" y="483.8" width="9" height="33.8" fill="#c00" opacity="0.55"/>
<rect x="129.5" y="484.7" width="9" height="32.9" fill="#188a3e" opacity="0.55"/>
<rect x="144.5" y="483.0" width="9" height="34.6" fill="#c00" opacity="0.55"/>
<rect x="159.5" y="481.4" width="9" height="36.2" fill="#c00" opacity="0.55"/>
<rect x="174.5" y="479.7" width="9" height="37.9" fill="#c00" opacity="0.55"/>
<rect x="189.5" y="474.8" width="9" height="42.8" fill="#188a3e" opacity="0.55"/>
<rect x="204.5" y="486.3" width="9" height="31.3" fill="#c00" opacity="0.55"/>
<rect x="219.5" y="484.7" width="9" height="32.9" fill="#c00" opacity="0.55"/>
<rect x="234.5" y="483.0" width="9" height="34.6" fill="#c00" opacity="0.55"/>
<rect x="249.5" y="485.5" width="9" height="32.1" fill="#188a3e" opacity="0.55"/>
<rect x="264.5" y="481.4" width="9" height="36.2" fill="#c00" opacity="0.55"/>
<rect x="279.5" y="476.4" width="9" height="41.2" fill="#c00" opacity="0.55"/>
<rect x="294.5" y="469.8" width="9" height="47.8" fill="#188a3e" opacity="0.55"/>
<rect x="309.5" y="458.3" width="9" height="59.3" fill="#c00" opacity="0.55"/>
<rect x="324.5" y="447.6" width="9" height="70.0" fill="#188a3e" opacity="0.55"/>
<rect x="339.5" y="460.0" width="9" height="57.6" fill="#188a3e" opacity="0.55"/>
<rect x="354.5" y="464.9" width="9" height="52.7" fill="#188a3e" opacity="0.55"/>
<rect x="369.5" y="469.8" width="9" height="47.8" fill="#188a3e" opacity="0.55"/>
<rect x="384.5" y="468.2" width="9" height="49.4" fill="#188a3e" opacity="0.55"/>
<rect x="399.5" y="472.3" width="9" height="45.3" fill="#c00" opacity="0.55"/>
<rect x="414.5" y="474.8" width="9" height="42.8" fill="#188a3e" opacity="0.55"/>
<rect x="429.5" y="476.4" width="9" height="41.2" fill="#188a3e" opacity="0.55"/>
<rect x="444.5" y="478.1" width="9" height="39.5" fill="#188a3e" opacity="0.55"/>
<rect x="459.5" y="479.7" width="9" height="37.9" fill="#188a3e" opacity="0.55"/>
<rect x="474.5" y="481.4" width="9" height="36.2" fill="#188a3e" opacity="0.55"/>
<text x="24" y="441.6" fill="#555">volum</text>
<text x="314.0" y="435.6" fill="#c00" text-anchor="middle" style="font-weight:bold">minim 589.80 — divergență (M)</text>
<text x="329.0" y="322.4" fill="#188a3e" text-anchor="middle" style="font-weight:bold">intrare 590.20</text>
<text x="479.0" y="255.6" fill="#188a3e" text-anchor="middle" style="font-weight:bold">ieșire trail 592.40</text>
<text x="24" y="560.0" fill="#555">Legendă: entry/SL/TP/trail sunt exemplul construit al autorului sumarului,</text>
<text x="24" y="577.0" fill="#555">nu citate din curs (transcrierea se întrerupe înainte de planul numeric).</text>
</svg>

After

Width:  |  Height:  |  Size: 8.8 KiB

View File

@@ -0,0 +1,222 @@
"""Diagrama din `gen/strategii/mdsrt_exemplu.spec.txt` (TIP: PRET).
Exemplul real din Q&A 15.07.2025 (`09_QA/24_2025-07-15.md`): SPY pe 5 min,
double bottom cu al doilea picior mai jos (MDSRT pe 5 min, suportul = pivotul
daily). Downtrend → picior 1 la 622.40 → revenire → picior 2 la 621.40 (rupe
pivotul 622.00) → Buy Hammer care închide la 622.60, înapoi peste pivot. După
trigger, un plan construit de mine (marcat) duce trade-ul până la TP1.
Reale din curs: nivelurile 622.40 / 622.00 / 621.40 / 622.60 și cele 8 bare
dintre picioare. Construcția mea: entry 622.60, SL 621.30, TP1 623.90 (1:1) —
materialul nu le dă (transcrierea planului se întrerupe). Volumele sunt
relative, alese de generator.
Rulează:
python3 gen/strategii/mdsrt_exemplu.py
Scrie lângă el `mdsrt_exemplu.svg`.
"""
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
OUT = Path(__file__).resolve().parent / "mdsrt_exemplu.svg"
GRAY, RED, GREEN, BLUE, ORANGE = "#777", "#c00", "#188a3e", "#1a5fb4", "#b06000"
# ── nivelurile reale din curs + planul construit (marcat) ──────────────────
PIVOT = 622.00 # pivot daily, suportul
LEG1 = 622.40 # swing low picior 1
LEG2 = 621.40 # picior 2 (mai jos, rupe pivotul)
ENTRY = 622.60 # close-ul Buy Hammer (construit)
SL = 621.30 # 0.10 sub low-ul piciorului 2 (construit)
TP1 = 623.90 # entry + risc = 1:1 (construit)
# ── seria: 30 de close-uri, exact ca în specificație ───────────────────────
CLOSES = [
627.00, 626.40, 626.70, 626.00, 625.40, 624.80, 624.40, 623.80, # 0-7
623.20, 622.80, # 8-9
622.40, # 10 = picior 1 (swing low 622.40)
622.70, 623.10, 623.50, 623.20, 622.90, 622.60, 622.30, # 11-17
622.60, # 18 = picior 2: low 621.40, Buy Hammer care închide 622.60
623.00, 623.40, 623.90, # 19-21, 21 = TP1 1:1 (construit)
624.20, 624.00, 624.40, 624.80, 625.20, 625.60, # 22-27
625.30, 625.70, # 28-29
]
assert len(CLOSES) == 30, len(CLOSES)
# wick-uri (sus, jos): alternanță naturală; excepțiile sunt piciorul 1 și
# hammer-ul de la piciorul 2 (cerute exact de specificație).
WICKS = [
(0.10, 0.10), (0.12, 0.14), (0.15, 0.12), (0.18, 0.15), (0.20, 0.16),
(0.18, 0.14), (0.16, 0.13), (0.15, 0.12), (0.14, 0.12), (0.12, 0.10),
(0.15, 0.05), # 10 = picior 1: low 622.40 - 0.05 = 622.35
(0.12, 0.12), (0.15, 0.14), (0.18, 0.15), (0.16, 0.13), (0.14, 0.12),
(0.12, 0.10), (0.10, 0.12),
(0.10, 0.90), # 18 = hammer: high 622.70, low 621.40
(0.12, 0.12), (0.14, 0.12), (0.15, 0.13),
(0.16, 0.15), (0.14, 0.12), (0.15, 0.13), (0.16, 0.14), (0.17, 0.14),
(0.18, 0.15), (0.14, 0.12), (0.15, 0.13),
]
assert len(WICKS) == 30, len(WICKS)
# volum relativ (ales de generator; materialul nu dă volumul pentru exemplu):
# scade pe downtrend, spike de absorbție pe hammer, higher low vs piciorul 1.
VOLS = [
58, 56, 54, 52, 50, 49, 48, 47, 46, 45, # 0-9
44, # 10 = picior 1
43, 42, 41, 40, 39, 38, 39, # 11-17
100, # 18 = hammer, maximul seriei
62, 58, 54, # 19-21
52, 50, 49, 48, 47, 46, 45, 44, # 22-29
]
assert len(VOLS) == 30, len(VOLS)
cds = from_closes(CLOSES, wicks=WICKS, vols=VOLS, start=627.00)
# ── assert-urile din specificație ──────────────────────────────────────────
# 1. picior 1: low 622.35, close 622.40
assert abs(cds[10].l - 622.35) < 1e-9, cds[10].l
assert cds[10].c == 622.40, cds[10].c
# 2. picior 2 mai jos (621.40 < 622.35) și rupe pivotul daily 622.00
assert abs(cds[18].l - LEG2) < 1e-9, cds[18].l
assert cds[18].l < cds[10].l, (cds[18].l, cds[10].l)
assert cds[18].l < PIVOT, cds[18].l
# 3. trigger-ul închide înapoi peste pivot
assert cds[18].c == 622.60, cds[18].c
assert cds[18].c > PIVOT, cds[18].c
# 4. 8 candele între picioare (peste minimul de 5)
assert 18 - 10 == 8
# 5. hammer: coadă lungă în jos (0.90) > corp (0.30)
corp = abs(cds[18].o - cds[18].c)
coada = min(cds[18].o, cds[18].c) - cds[18].l
assert abs(corp - 0.30) < 1e-9, corp
assert abs(coada - 0.90) < 1e-9, coada
assert coada > corp, (coada, corp)
# 6. entry = close-ul trigger-ului (construit), SL 0.10 sub low-ul piciorului 2
assert cds[18].c == ENTRY
assert abs(cds[18].l - SL - 0.10) < 1e-9, (cds[18].l, SL)
risk = ENTRY - SL
assert abs(risk - 1.30) < 1e-9, risk
# 7. TP1 simetric 1:1
assert abs((ENTRY - SL) - (TP1 - ENTRY)) < 1e-9
# 8. după trigger, primul close care atinge 623.90 e bara 21 (nu mai devreme).
# Seria pornește la 627.00, deci „atinge" se citește de jos în sus, din zona
# picioarelor: barele 10-20 rămân sub TP1, bara 21 îl atinge.
assert all(c.c < TP1 for c in cds[10:21]), [c.c for c in cds[10:21]]
assert max(c.h for c in cds[19:21]) < TP1, [c.h for c in cds[19:21]]
assert cds[21].c == TP1, cds[21].c
# 9. divergență: lower low pe preț, higher low pe volum
assert cds[18].l < cds[10].l, (cds[18].l, cds[10].l)
assert VOLS[18] > VOLS[10], (VOLS[18], VOLS[10])
assert VOLS[18] == max(VOLS)
# 10. fiecare open == close-ul barei anterioare
for a, b in zip(cds, cds[1:]):
assert a.c == b.o, (a.c, b.o)
# ── geometria, ca să așez marks-urile fără suprapunere de text ─────────────
SCALE, GAP, LEFT, WIDTH, BODY_W, VOL_H = 62, 15, 44, 640, 9, 70
def cx(i: int) -> float:
return LEFT + i * GAP
levels = [
Level(PIVOT, "pivot daily 622.00", ORANGE, label_at="left"),
Level(SL, "SL 621.30 (construit)", RED, label_at="left"),
Level(ENTRY, "intrare 622.60 (construit)", GREEN, label_at="left"),
Level(TP1, "TP1 623.90 = 1:1 (construit)", GREEN, label_at="left"),
Level(LEG1, "picior 1 = 622.40", BLUE, label_at="right"),
]
# dy-uri calculate manual ca etichetele „below" să nu calce linia pivotului
# (bara 10) și ca „hammer" să nu calce eticheta intrării (bara 18).
marks = [
Mark(10, "picior 1 = 622.40", BLUE, "below", dy=15),
Mark(18, "picior 2 = 621.40 (mai jos) · Buy Hammer", RED, "below"),
Mark(18, "hammer închide 622.60, înapoi peste pivot", GREEN, "above", dy=-20),
]
ch = Chart(
candles=cds, levels=levels, marks=marks,
mas=[MA(20, ORANGE, dash="4 3", text="SMA-20")],
scale=SCALE, gap=GAP, body_w=BODY_W, left=LEFT, width=WIDTH, vol_h=VOL_H,
vol_label="volum",
title="SPY — 5 min (Q&amp;A 15.07.2025; intrare/SL/TP construite)",
caption="",
)
svg = ch.render()
# ── legenda de onestitate (assert 11): ce e real, ce e construit ───────────
legend = [
"Legendă: nivelurile 622.40 / 622.00 / 621.40 / 622.60 și cele 8 candele dintre",
"picioare sunt reale, din curs. Intrarea 622.60, SL-ul 621.30 și TP-ul 623.90",
"sunt construcția mea — materialul nu le dă.",
]
m = re.search(r'viewBox="0 0 (\d+) ([0-9.]+)"', svg)
W, H0 = int(m.group(1)), float(m.group(2))
legend_svg = [f'<text x="24" y="{H0 + 16 + 17 * i:.1f}" fill="#555">{t}</text>'
for i, t in enumerate(legend)]
new_h = H0 + 16 + 17 * (len(legend) - 1) + 14
svg = re.sub(r'(viewBox="0 0 \d+ )[0-9.]+(")',
lambda mm: mm.group(1) + f"{new_h:.0f}" + mm.group(2), svg, count=1)
svg = svg.replace("\n</svg>", "\n " + "\n ".join(legend_svg) + "\n</svg>")
assert svg.count("\n\n") == 0, "linie goală în SVG rupe blocul din markdown"
assert "sunt reale, din curs" in svg and "construcția mea" in svg, \
"legenda nu spune explicit ce e real și ce e construit"
# toate etichetele cerute de specificație sunt prezente
for eticheta in ("pivot daily 622.00", "picior 1 = 622.40",
"picior 2 = 621.40 (mai jos) · Buy Hammer",
"hammer închide 622.60, înapoi peste pivot",
"intrare 622.60 (construit)", "SL 621.30 (construit)",
"TP1 623.90 = 1:1 (construit)"):
assert eticheta in svg, f"lipsește eticheta: {eticheta!r}"
assert "SPY — 5 min" in svg, "lipsește titlul"
# 1:1 desenat la 1:1 (STYLE.md: raportul din preț = raportul din pixeli)
ys = {p: ch._y(p) for p in (SL, ENTRY, TP1)}
assert abs((ys[SL] - ys[ENTRY]) - (ys[ENTRY] - ys[TP1])) < 1e-6, ys
# ── verificare: fiecare etichetă e în pânză și nu se suprapune alta ────────
m = re.search(r'viewBox="0 0 (\d+) ([0-9.]+)"', svg)
W, H = int(m.group(1)), float(m.group(2))
texte = re.findall(r'<text x="([\d.]+)" y="([\d.]+)"([^>]*)>([^<]*)</text>', svg)
cutii = []
for xs, ys, attrs, continut in texte:
x, yv = float(xs), float(ys)
w = len(continut) * (7.44 if "bold" in attrs else 6.72)
if 'text-anchor="end"' in attrs:
x0 = x - w
elif 'text-anchor="middle"' in attrs:
x0 = x - w / 2
else:
x0 = x
assert -2 <= x0 and x0 + w <= W + 2, f"etichetă în afara pânzei: {continut!r}"
assert 0 <= yv <= H, f"etichetă în afara înălțimii: {continut!r}"
cutii.append((x0, x0 + w, yv, continut))
for i in range(len(cutii)):
for j in range(i + 1, len(cutii)):
a, b = cutii[i], cutii[j]
if abs(a[2] - b[2]) < 13 and a[0] < b[1] - 2 and b[0] < a[1] - 2:
raise AssertionError(f"texte suprapuse: {a[3]!r} / {b[3]!r}")
OUT.write_text(svg, encoding="utf-8")
print(f"scris {OUT.name}: {len(cds)} candele | picior 1 {cds[10].l:.2f} (close "
f"{cds[10].c:.2f}) | picior 2 {cds[18].l:.2f} (mai jos) | hammer închide "
f"{cds[18].c:.2f} | intrare {ENTRY:.2f} | SL {SL:.2f} (risc {risk:.2f}) | "
f"TP1 {TP1:.2f} = {(TP1 - ENTRY) / risk:.2f}:1 la bara 21 | "
f"vol[18]={VOLS[18]} > vol[10]={VOLS[10]}")

View File

@@ -0,0 +1,113 @@
<svg viewBox="0 0 640 603" xmlns="http://www.w3.org/2000/svg" style="width:100%;height:auto;font:12px sans-serif">
<text x="24" y="48" fill="#333" style="font-weight:bold">SPY — 5 min (Q&amp;A 15.07.2025; intrare/SL/TP construite)</text>
<line x1="24" y1="373.4" x2="626" y2="373.4" stroke="#b06000" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="30" y="367.4" fill="#b06000">pivot daily 622.00</text>
<line x1="24" y1="416.8" x2="626" y2="416.8" stroke="#c00" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="30" y="410.8" fill="#c00">SL 621.30 (construit)</text>
<line x1="24" y1="336.2" x2="626" y2="336.2" stroke="#188a3e" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="30" y="330.2" fill="#188a3e">intrare 622.60 (construit)</text>
<line x1="24" y1="255.6" x2="626" y2="255.6" stroke="#188a3e" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="30" y="249.6" fill="#188a3e">TP1 623.90 = 1:1 (construit)</text>
<line x1="24" y1="348.6" x2="626" y2="348.6" stroke="#1a5fb4" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="612" y="342.6" fill="#1a5fb4" text-anchor="end">picior 1 = 622.40</text>
<polyline points="329.0,253.2 344.0,264.3 359.0,272.1 374.0,279.8 389.0,286.0 404.0,289.1 419.0,289.1 434.0,286.6 449.0,281.1 464.0,274.5 479.0,265.6" fill="none" stroke="#b06000" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="485" y="260" fill="#b06000">SMA-20</text>
<line x1="44.0" y1="57.2" x2="44.0" y2="69.6" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="39.5" y="63.4" width="9" height="1.6" fill="#188a3e"/>
<line x1="59.0" y1="56.0" x2="59.0" y2="109.3" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="54.5" y="63.4" width="9" height="37.2" fill="#c00"/>
<line x1="74.0" y1="72.7" x2="74.0" y2="108.1" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="69.5" y="82.0" width="9" height="18.6" fill="#188a3e"/>
<line x1="89.0" y1="70.9" x2="89.0" y2="134.7" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="84.5" y="82.0" width="9" height="43.4" fill="#c00"/>
<line x1="104.0" y1="113.0" x2="104.0" y2="172.6" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="99.5" y="125.4" width="9" height="37.2" fill="#c00"/>
<line x1="119.0" y1="151.5" x2="119.0" y2="208.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="114.5" y="162.6" width="9" height="37.2" fill="#c00"/>
<line x1="134.0" y1="189.9" x2="134.0" y2="232.7" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="129.5" y="199.8" width="9" height="24.8" fill="#c00"/>
<line x1="149.0" y1="215.3" x2="149.0" y2="269.3" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="144.5" y="224.6" width="9" height="37.2" fill="#c00"/>
<line x1="164.0" y1="253.2" x2="164.0" y2="306.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="159.5" y="261.8" width="9" height="37.2" fill="#c00"/>
<line x1="179.0" y1="291.6" x2="179.0" y2="330.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="174.5" y="299.0" width="9" height="24.8" fill="#c00"/>
<line x1="194.0" y1="314.5" x2="194.0" y2="351.7" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="189.5" y="323.8" width="9" height="24.8" fill="#c00"/>
<line x1="209.0" y1="322.6" x2="209.0" y2="356.1" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="204.5" y="330.0" width="9" height="18.6" fill="#188a3e"/>
<line x1="224.0" y1="295.9" x2="224.0" y2="338.7" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="219.5" y="305.2" width="9" height="24.8" fill="#188a3e"/>
<line x1="239.0" y1="269.3" x2="239.0" y2="314.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="234.5" y="280.4" width="9" height="24.8" fill="#188a3e"/>
<line x1="254.0" y1="270.5" x2="254.0" y2="307.1" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="249.5" y="280.4" width="9" height="18.6" fill="#c00"/>
<line x1="269.0" y1="290.4" x2="269.0" y2="325.1" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="264.5" y="299.0" width="9" height="18.6" fill="#c00"/>
<line x1="284.0" y1="310.2" x2="284.0" y2="342.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="279.5" y="317.6" width="9" height="18.6" fill="#c00"/>
<line x1="299.0" y1="330.0" x2="299.0" y2="362.3" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="294.5" y="336.2" width="9" height="18.6" fill="#c00"/>
<line x1="314.0" y1="330.0" x2="314.0" y2="410.6" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="309.5" y="336.2" width="9" height="18.6" fill="#188a3e"/>
<line x1="329.0" y1="304.0" x2="329.0" y2="343.7" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="324.5" y="311.4" width="9" height="24.8" fill="#188a3e"/>
<line x1="344.0" y1="278.0" x2="344.0" y2="318.9" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="339.5" y="286.6" width="9" height="24.8" fill="#188a3e"/>
<line x1="359.0" y1="246.3" x2="359.0" y2="294.7" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="354.5" y="255.6" width="9" height="31.0" fill="#188a3e"/>
<line x1="374.0" y1="227.1" x2="374.0" y2="264.9" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="369.5" y="237.0" width="9" height="18.6" fill="#188a3e"/>
<line x1="389.0" y1="228.4" x2="389.0" y2="256.9" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="384.5" y="237.0" width="9" height="12.4" fill="#c00"/>
<line x1="404.0" y1="215.3" x2="404.0" y2="257.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="399.5" y="224.6" width="9" height="24.8" fill="#188a3e"/>
<line x1="419.0" y1="189.9" x2="419.0" y2="233.3" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="414.5" y="199.8" width="9" height="24.8" fill="#188a3e"/>
<line x1="434.0" y1="164.5" x2="434.0" y2="208.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="429.5" y="175.0" width="9" height="24.8" fill="#188a3e"/>
<line x1="449.0" y1="139.1" x2="449.0" y2="184.3" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="444.5" y="150.2" width="9" height="24.8" fill="#188a3e"/>
<line x1="464.0" y1="141.6" x2="464.0" y2="176.3" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="459.5" y="150.2" width="9" height="18.6" fill="#c00"/>
<line x1="479.0" y1="134.7" x2="479.0" y2="176.9" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="474.5" y="144.0" width="9" height="24.8" fill="#188a3e"/>
<line x1="24" y1="512.8" x2="626" y2="512.8" stroke="#888"/>
<rect x="39.5" y="472.2" width="9" height="40.6" fill="#188a3e" opacity="0.55"/>
<rect x="54.5" y="473.6" width="9" height="39.2" fill="#c00" opacity="0.55"/>
<rect x="69.5" y="475.0" width="9" height="37.8" fill="#188a3e" opacity="0.55"/>
<rect x="84.5" y="476.4" width="9" height="36.4" fill="#c00" opacity="0.55"/>
<rect x="99.5" y="477.8" width="9" height="35.0" fill="#c00" opacity="0.55"/>
<rect x="114.5" y="478.5" width="9" height="34.3" fill="#c00" opacity="0.55"/>
<rect x="129.5" y="479.2" width="9" height="33.6" fill="#c00" opacity="0.55"/>
<rect x="144.5" y="479.9" width="9" height="32.9" fill="#c00" opacity="0.55"/>
<rect x="159.5" y="480.6" width="9" height="32.2" fill="#c00" opacity="0.55"/>
<rect x="174.5" y="481.3" width="9" height="31.5" fill="#c00" opacity="0.55"/>
<rect x="189.5" y="482.0" width="9" height="30.8" fill="#c00" opacity="0.55"/>
<rect x="204.5" y="482.7" width="9" height="30.1" fill="#188a3e" opacity="0.55"/>
<rect x="219.5" y="483.4" width="9" height="29.4" fill="#188a3e" opacity="0.55"/>
<rect x="234.5" y="484.1" width="9" height="28.7" fill="#188a3e" opacity="0.55"/>
<rect x="249.5" y="484.8" width="9" height="28.0" fill="#c00" opacity="0.55"/>
<rect x="264.5" y="485.5" width="9" height="27.3" fill="#c00" opacity="0.55"/>
<rect x="279.5" y="486.2" width="9" height="26.6" fill="#c00" opacity="0.55"/>
<rect x="294.5" y="485.5" width="9" height="27.3" fill="#c00" opacity="0.55"/>
<rect x="309.5" y="442.8" width="9" height="70.0" fill="#188a3e" opacity="0.55"/>
<rect x="324.5" y="469.4" width="9" height="43.4" fill="#188a3e" opacity="0.55"/>
<rect x="339.5" y="472.2" width="9" height="40.6" fill="#188a3e" opacity="0.55"/>
<rect x="354.5" y="475.0" width="9" height="37.8" fill="#188a3e" opacity="0.55"/>
<rect x="369.5" y="476.4" width="9" height="36.4" fill="#188a3e" opacity="0.55"/>
<rect x="384.5" y="477.8" width="9" height="35.0" fill="#c00" opacity="0.55"/>
<rect x="399.5" y="478.5" width="9" height="34.3" fill="#188a3e" opacity="0.55"/>
<rect x="414.5" y="479.2" width="9" height="33.6" fill="#188a3e" opacity="0.55"/>
<rect x="429.5" y="479.9" width="9" height="32.9" fill="#188a3e" opacity="0.55"/>
<rect x="444.5" y="480.6" width="9" height="32.2" fill="#188a3e" opacity="0.55"/>
<rect x="459.5" y="481.3" width="9" height="31.5" fill="#c00" opacity="0.55"/>
<rect x="474.5" y="482.0" width="9" height="30.8" fill="#188a3e" opacity="0.55"/>
<text x="24" y="436.8" fill="#555">volum</text>
<text x="194.0" y="386.7" fill="#1a5fb4" text-anchor="middle" style="font-weight:bold">picior 1 = 622.40</text>
<text x="314.0" y="430.6" fill="#c00" text-anchor="middle" style="font-weight:bold">picior 2 = 621.40 (mai jos) · Buy Hammer</text>
<text x="314.0" y="300.0" fill="#188a3e" text-anchor="middle" style="font-weight:bold">hammer închide 622.60, înapoi peste pivot</text>
<text x="24" y="555.0" fill="#555">Legendă: nivelurile 622.40 / 622.00 / 621.40 / 622.60 și cele 8 candele dintre</text>
<text x="24" y="572.0" fill="#555">picioare sunt reale, din curs. Intrarea 622.60, SL-ul 621.30 și TP-ul 623.90</text>
<text x="24" y="589.0" fill="#555">sunt construcția mea — materialul nu le dă.</text>
</svg>

After

Width:  |  Height:  |  Size: 9.2 KiB

View File

@@ -0,0 +1,254 @@
#!/usr/bin/env python3
"""Cum se masoara divergenta MDSRT — schema pe doua panouri (pret + indicator).
Pentru `strategii/07_mdsrt.md`, marcajul `<!-- DIAGRAMA: masurare -->` (Nivel 1,
§1.2 „Setup", dupa pasul 5). NU e grafic de pret cu candele: spec-ul cere o
schema de regula, cu linii, fara axe de pret reale, fara ticker, fara cifre de
pret. De aceea se scrie SVG de mana, doar stdlib — nu se folosesc
svg_candles / pchart.
Ce arata (toate etichetele si pragurile vin din spec / din Q&A):
- panoul de sus: linia pretului intr-un downtrend; L1 primul swing low
important, L2 al doilea, mai jos → lower low;
- panoul de jos: linia indicatorului (Power / volum / RS) cu I1 si I2, al
doilea mai sus → higher low = divergenta bull;
- bracket orizontal intre L1 si L2: „minim 5 candele intre puncte";
- bracket orizontal in stanga lui L2, pana la 60 de pasi in spate:
„maxim 60 de candele in spate";
- al treilea swing low mic, la 2-3 pasi de L2, taiat cu cruce rosie si
etichetat „2-3 candele → NU e divergenta".
Pragurile, cu sursa:
- „ultimele 2 swing low-uri importante" + „60 de candele in spate si minim 5
candele intre divergente": `09_QA/24_2025-07-15.md:151-155`;
- „Daca aveti pe 3 candele divergenta, nu e": `09_QA/23_2025-07-29.md:873-874`;
- mecanismul pret-vs-volum (lower low pe pret, higher low pe indicator):
`09_QA/19_2025-09-08.md:168-177`;
- perechea de low-uri se muta pe masura ce apare un low nou, mai jos:
`09_QA/24_2025-07-15.md:159-162`.
Ruleaza: .venv/bin/python gen/strategii/mdsrt_masurare.py
-> gen/strategii/mdsrt_masurare.svg
"""
from pathlib import Path
from xml.sax.saxutils import escape
OUT = Path(__file__).with_suffix(".svg")
W, H = 700, 620
BLACK, GRAY, RED, GREEN, BLUE, ORANGE, WHITE = (
"#000000", "#777777", "#c00", "#188a3e", "#1a5fb4", "#b06000", "#ffffff")
# ---- grila de timp: un pas = o candela. L2 e la 60 de pasi de la marginea
# stanga a panoului, ca bracket-ul „maxim 60 de candele" sa aiba exact 60 de
# pasi spre stanga si sa acopere si L1.
X0, STEPW = 60.0, 7.0
L1_STEP, L2_STEP, L3_STEP = 54.0, 60.0, 62.5
X_L1, X_L2, X_L3 = X0 + L1_STEP * STEPW, X0 + L2_STEP * STEPW, X0 + L3_STEP * STEPW
# ---- coordonatele verticale ale punctelor
Y_L1, Y_L2, Y_L3 = 252.0, 278.0, 272.0 # pret: L2 mai jos (lower low)
Y_I1, Y_I2 = 486.0, 468.0 # indicator: I2 mai sus (higher low)
PRICE_BOX = (40.0, 76.0, 620.0, 250.0) # y 76 .. 326
IND_BOX = (40.0, 344.0, 620.0, 200.0) # y 344 .. 544
BR60_Y = 134.0 # bracket-ul de 60 de candele, sus
BR5_Y = 300.0 # bracket-ul de 5 candele, sub cele doua low-uri
TEXTS = [] # (x, y, text, size, weight, anchor) pentru verificarea finala
def txt(x, y, s, size=13, weight="400", fill=BLACK, anchor="middle"):
TEXTS.append((x, y, s, size, weight, anchor))
return (f'<text x="{x:.1f}" y="{y:.1f}" font-size="{size}" '
f'font-weight="{weight}" fill="{fill}" text-anchor="{anchor}">'
f'{escape(s)}</text>')
def line(x1, y1, x2, y2, stroke=BLACK, width=1.4, dash=None):
d = f' stroke-dasharray="{dash}"' if dash else ""
return (f'<line x1="{x1:.1f}" y1="{y1:.1f}" x2="{x2:.1f}" y2="{y2:.1f}" '
f'stroke="{stroke}" stroke-width="{width}"{d}/>')
def poly(points, stroke=BLACK, width=1.8):
pts = " ".join(f"{x:.1f},{y:.1f}" for x, y in points)
return (f'<polyline points="{pts}" fill="none" stroke="{stroke}" '
f'stroke-width="{width}" stroke-linejoin="round"/>')
def dot(x, y, color=BLACK, r=3.6):
return f'<circle cx="{x:.1f}" cy="{y:.1f}" r="{r}" fill="{color}"/>'
def hbracket(x0, x1, y, color, tick=7.0):
"""Bracket orizontal de dimensiune: linie + capete verticale."""
return [
line(x0, y, x1, y, color, 1.4),
line(x0, y - tick, x0, y + tick, color, 1.4),
line(x1, y - tick, x1, y + tick, color, 1.4),
]
def cross(cx, cy, color=RED, arm=10.0):
"""Crucea rosie care marcheaza al treilea low (NU e divergenta)."""
return [
line(cx - arm, cy - arm, cx + arm, cy + arm, color, 2.0),
line(cx - arm, cy + arm, cx + arm, cy - arm, color, 2.0),
]
def build():
p = [
f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 {W} {H}" '
f'width="{W}" height="{H}" role="img" '
f'aria-label="Cum se masoara divergenta: pretul face un lower low (L2 sub '
f'L1), indicatorul face un higher low (I2 peste I1); intre L1 si L2 trebuie '
f'minim 5 candele si cel mult 60 in spate, iar un low la 2-3 candele nu '
f'conteaza">',
'<g font-family="system-ui, sans-serif">',
f'<rect x="0" y="0" width="{W}" height="{H}" fill="{WHITE}"/>',
txt(W / 2, 34, "Divergența: cum o măsori corect", 19, "700", BLACK),
txt(W / 2, 58, "Aceeași structură pe preț și pe indicator: două swing "
"low-uri, două minime", 13, "400", GRAY),
]
# ---------------- panoul de sus: pretul
bx, by, bw, bh = PRICE_BOX
p.append(f'<rect x="{bx}" y="{by}" width="{bw}" height="{bh}" rx="8" '
f'fill="{WHITE}" stroke="{GRAY}" stroke-width="1.2"/>')
p.append(txt(bx + 16, by + 20, "PREȚ (schematic) — downtrend, lower low",
14, "700", BLACK, "start"))
# bracket „maxim 60 de candele in spate": de la L2 spre stanga, 60 de pasi
x60_left, x60_right = X0, X_L2
p.extend(hbracket(x60_left, x60_right, BR60_Y, BLACK))
p.append(txt((x60_left + x60_right) / 2, BR60_Y - 12,
"maxim 60 de candele în spate", 13, "400", BLACK))
# linia pretului: downtrend cu L1, apoi L2 mai jos, apoi un low mic L3
price = [(0, 158), (6, 178), (12, 166), (18, 192), (24, 180), (30, 206),
(36, 194), (42, 218), (48, 232), (L1_STEP, Y_L1), (57, 226),
(L2_STEP, Y_L2), (61.3, 248), (L3_STEP, Y_L3), (65, 250)]
p.append(poly([(X0 + s * STEPW, y) for s, y in price], BLACK, 1.8))
p.extend([dot(X_L1, Y_L1, BLACK), dot(X_L2, Y_L2, BLACK), dot(X_L3, Y_L3, BLACK)])
p.append(txt(X_L1 - 10, Y_L1 + 6, "L1", 13, "700", BLACK, "end"))
p.append(txt(X_L2 - 12, Y_L2 + 8, "L2", 13, "700", BLACK, "end"))
# bracket „minim 5 candele intre puncte": intre L1 si L2
p.extend(hbracket(X_L1, X_L2, BR5_Y, BLACK))
p.append(txt((X_L1 + X_L2) / 2, BR5_Y + 18,
"minim 5 candele între puncte", 13, "400", BLACK))
# al treilea low: cruce rosie + „NU" + eticheta
p.extend(cross(X_L3, Y_L3, RED, arm=10.0))
p.append(txt(X_L3, 234, "NU", 14, "700", RED))
p.append(txt(525, 246, "2-3 candele", 13, "700", RED, "start"))
p.append(txt(525, 266, "NU e divergență", 13, "700", RED, "start"))
# ---------------- panoul de jos: indicatorul
bx, by, bw, bh = IND_BOX
p.append(f'<rect x="{bx}" y="{by}" width="{bw}" height="{bh}" rx="8" '
f'fill="{WHITE}" stroke="{GRAY}" stroke-width="1.2"/>')
p.append(txt(bx + 16, by + 20,
"INDICATOR (Power / volum / RS) — higher low",
14, "700", BLUE, "start"))
ind = [(0, 400), (6, 424), (12, 410), (18, 436), (24, 422), (30, 448),
(36, 434), (42, 458), (48, 466), (L1_STEP, Y_I1), (57, 458),
(L2_STEP, Y_I2), (65, 440)]
p.append(poly([(X0 + s * STEPW, y) for s, y in ind], BLUE, 1.8))
p.extend([dot(X_L1, Y_I1, BLUE), dot(X_L2, Y_I2, BLUE)])
p.append(txt(X_L1 - 10, Y_I1 + 6, "I1", 13, "700", BLUE, "end"))
p.append(txt(X_L2 + 10, Y_I2 + 6, "I2", 13, "700", BLUE, "start"))
# ---------------- legenda de jos
p.append(txt(W / 2, 570, "Prețul face un lower low (L2 sub L1), indicatorul "
"face un higher low (I2 peste I1):", 13, "400", BLACK))
p.append(txt(W / 2, 590, "divergență bull. Dar dacă al doilea punct e la "
"doar 2-3 candele, nu se numără.", 13, "400", BLACK))
p.append('</g></svg>')
return "\n".join(p) + "\n"
# ---------------------------------------------------------------- verificare
def est_w(s, size, weight):
factor = 0.62 if weight == "700" else 0.56
return len(s) * size * factor
def verify(svg):
# (1) geometria schemei, exact ce cere spec-ul
assert Y_L2 > Y_L1, "L2 trebuie desenat mai jos decat L1 (lower low)"
assert Y_I2 < Y_I1, "I2 trebuie desenat mai sus decat I1 (higher low)"
step_l1_l2 = (X_L2 - X_L1) / STEPW
step_l2_l3 = (X_L3 - X_L2) / STEPW
assert step_l1_l2 >= 5.0 - 1e-9, f"prea putine candele L1-L2: {step_l1_l2}"
assert 2.0 - 1e-9 <= step_l2_l3 <= 3.0 + 1e-9, f"L3 nu e la 2-3 pasi: {step_l2_l3}"
# (4) bracket-ul de 60 de candele acopera de la L2 spre stanga si trece de L1
x60_left, x60_right = X0, X_L2
assert x60_right == X_L2, "bracket-ul de 60 porneste din L2"
assert x60_left <= X_L1, "bracket-ul de 60 nu ajunge pana la L1"
assert x60_right - x60_left >= (X_L2 - X_L1), "bracket prea scurt fata de L1"
assert (x60_right - x60_left) / STEPW == 60.0, "nu sunt exact 60 de pasi"
# (5) „NU" si crucea rosie chiar pe al treilea low
assert "NU" in svg
assert RED in svg and "L3" not in svg # L3 e marcat doar prin cruce, nu cu eticheta
cross_lines = cross(X_L3, Y_L3)
for ln in cross_lines:
assert f'x1="{X_L3 - 10:.1f}"' in ln or f'x2="{X_L3 - 10:.1f}"' in ln
assert f'y1="{Y_L3 - 10:.1f}"' in ln or f'y2="{Y_L3 - 10:.1f}"' in ln
# etichetele obligatorii din spec sunt in desen
for cheie in ("maxim 60 de candele în spate", "minim 5 candele între puncte",
"2-3 candele", "NU e divergență", "lower low", "higher low",
"L1", "L2", "I1", "I2"):
assert cheie in svg, f"lipsește din desen: {cheie!r}"
# (2) toate etichetele in panza
for x, y, s, size, weight, anchor in TEXTS:
ew = est_w(s, size, weight)
left = x - ew if anchor == "end" else (x - ew / 2 if anchor == "middle" else x)
right = left + ew
assert -2 <= left and right <= W + 2, f"text in afara panzei: {s!r}"
assert 0 <= y <= H, f"text in afara inaltimii: {s!r} (y={y})"
# (3) fara doua texte pe aceeasi pozitie
for i in range(len(TEXTS)):
xi, yi, si, szi, wi, ai = TEXTS[i]
we = est_w(si, szi, wi)
li = xi - we if ai == "end" else (xi - we / 2 if ai == "middle" else xi)
ri = li + we
for j in range(i + 1, len(TEXTS)):
xj, yj, sj, szj, wj, aj = TEXTS[j]
wej = est_w(sj, szj, wj)
lj = xj - wej if aj == "end" else (xj - wej / 2 if aj == "middle" else xj)
rj = lj + wej
if abs(yi - yj) < 13 and li < rj - 4 and lj < ri - 4:
raise AssertionError(f"texte suprapuse: {si!r} / {sj!r}")
# etichetele panourilor incap in panouri
for x, y, s, size, weight, anchor in TEXTS:
for (px, py, pw, ph) in (PRICE_BOX, IND_BOX):
if py <= y <= py + ph and px - 2 <= x <= px + pw + 2:
assert x >= px + 12, f"eticheta lipita de margine: {s!r}"
assert est_w(s, size, weight) <= pw - 24, f"prea lata in panou: {s!r}"
# fara linie goala in SVG
assert "" not in svg.split("\n")[:-1], "SVG-ul are linie goala"
assert "\n\n" not in svg, "SVG-ul are linie goala"
return True
SVG = build()
assert verify(SVG), "verificare esuata"
OUT.write_text(SVG, encoding="utf-8")
print(f"scris {OUT} ({W}x{H}), verificare OK: {len(TEXTS)} etichete, "
f"L1-L2={(X_L2 - X_L1) / STEPW:.1f} pasi, "
f"L2-L3={(X_L3 - X_L2) / STEPW:.1f} pasi, "
f"fereastra 60 = {(X_L2 - X0) / STEPW:.0f} pasi")

View File

@@ -0,0 +1,36 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 700 620" width="700" height="620" role="img" aria-label="Cum se masoara divergenta: pretul face un lower low (L2 sub L1), indicatorul face un higher low (I2 peste I1); intre L1 si L2 trebuie minim 5 candele si cel mult 60 in spate, iar un low la 2-3 candele nu conteaza">
<g font-family="system-ui, sans-serif">
<rect x="0" y="0" width="700" height="620" fill="#ffffff"/>
<text x="350.0" y="34.0" font-size="19" font-weight="700" fill="#000000" text-anchor="middle">Divergența: cum o măsori corect</text>
<text x="350.0" y="58.0" font-size="13" font-weight="400" fill="#777777" text-anchor="middle">Aceeași structură pe preț și pe indicator: două swing low-uri, două minime</text>
<rect x="40.0" y="76.0" width="620.0" height="250.0" rx="8" fill="#ffffff" stroke="#777777" stroke-width="1.2"/>
<text x="56.0" y="96.0" font-size="14" font-weight="700" fill="#000000" text-anchor="start">PREȚ (schematic) — downtrend, lower low</text>
<line x1="60.0" y1="134.0" x2="480.0" y2="134.0" stroke="#000000" stroke-width="1.4"/>
<line x1="60.0" y1="127.0" x2="60.0" y2="141.0" stroke="#000000" stroke-width="1.4"/>
<line x1="480.0" y1="127.0" x2="480.0" y2="141.0" stroke="#000000" stroke-width="1.4"/>
<text x="270.0" y="122.0" font-size="13" font-weight="400" fill="#000000" text-anchor="middle">maxim 60 de candele în spate</text>
<polyline points="60.0,158.0 102.0,178.0 144.0,166.0 186.0,192.0 228.0,180.0 270.0,206.0 312.0,194.0 354.0,218.0 396.0,232.0 438.0,252.0 459.0,226.0 480.0,278.0 489.1,248.0 497.5,272.0 515.0,250.0" fill="none" stroke="#000000" stroke-width="1.8" stroke-linejoin="round"/>
<circle cx="438.0" cy="252.0" r="3.6" fill="#000000"/>
<circle cx="480.0" cy="278.0" r="3.6" fill="#000000"/>
<circle cx="497.5" cy="272.0" r="3.6" fill="#000000"/>
<text x="428.0" y="258.0" font-size="13" font-weight="700" fill="#000000" text-anchor="end">L1</text>
<text x="468.0" y="286.0" font-size="13" font-weight="700" fill="#000000" text-anchor="end">L2</text>
<line x1="438.0" y1="300.0" x2="480.0" y2="300.0" stroke="#000000" stroke-width="1.4"/>
<line x1="438.0" y1="293.0" x2="438.0" y2="307.0" stroke="#000000" stroke-width="1.4"/>
<line x1="480.0" y1="293.0" x2="480.0" y2="307.0" stroke="#000000" stroke-width="1.4"/>
<text x="459.0" y="318.0" font-size="13" font-weight="400" fill="#000000" text-anchor="middle">minim 5 candele între puncte</text>
<line x1="487.5" y1="262.0" x2="507.5" y2="282.0" stroke="#c00" stroke-width="2.0"/>
<line x1="487.5" y1="282.0" x2="507.5" y2="262.0" stroke="#c00" stroke-width="2.0"/>
<text x="497.5" y="234.0" font-size="14" font-weight="700" fill="#c00" text-anchor="middle">NU</text>
<text x="525.0" y="246.0" font-size="13" font-weight="700" fill="#c00" text-anchor="start">2-3 candele</text>
<text x="525.0" y="266.0" font-size="13" font-weight="700" fill="#c00" text-anchor="start">NU e divergență</text>
<rect x="40.0" y="344.0" width="620.0" height="200.0" rx="8" fill="#ffffff" stroke="#777777" stroke-width="1.2"/>
<text x="56.0" y="364.0" font-size="14" font-weight="700" fill="#1a5fb4" text-anchor="start">INDICATOR (Power / volum / RS) — higher low</text>
<polyline points="60.0,400.0 102.0,424.0 144.0,410.0 186.0,436.0 228.0,422.0 270.0,448.0 312.0,434.0 354.0,458.0 396.0,466.0 438.0,486.0 459.0,458.0 480.0,468.0 515.0,440.0" fill="none" stroke="#1a5fb4" stroke-width="1.8" stroke-linejoin="round"/>
<circle cx="438.0" cy="486.0" r="3.6" fill="#1a5fb4"/>
<circle cx="480.0" cy="468.0" r="3.6" fill="#1a5fb4"/>
<text x="428.0" y="492.0" font-size="13" font-weight="700" fill="#1a5fb4" text-anchor="end">I1</text>
<text x="490.0" y="474.0" font-size="13" font-weight="700" fill="#1a5fb4" text-anchor="start">I2</text>
<text x="350.0" y="570.0" font-size="13" font-weight="400" fill="#000000" text-anchor="middle">Prețul face un lower low (L2 sub L1), indicatorul face un higher low (I2 peste I1):</text>
<text x="350.0" y="590.0" font-size="13" font-weight="400" fill="#000000" text-anchor="middle">divergență bull. Dar dacă al doilea punct e la doar 2-3 candele, nu se numără.</text>
</g></svg>

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

@@ -0,0 +1,199 @@
#!/usr/bin/env python3
"""MDSRT — minimul obligatoriu (M complet) vs. varianta Trinity.
Diagramă conceptuală (nu grafic de preț): pentru
`summaries/strategii/07_mdsrt.md`, Nivel 2, §2.1 „Ce dă edge", după tabel
(marcajul `<!-- DIAGRAMA: trinity -->`). Citește specificația
`gen/strategii/mdsrt_trinity.spec.txt` și scrie
`gen/strategii/mdsrt_trinity.svg`.
NU folosește `svg_candles`: SVG-ul e construit ca string, fără dependințe.
Conținutul, cu sursa fiecărei afirmații:
- Power obligatoriu; dacă nu e în divergență, setup-ul nu se califică:
`05_MODUL_4/02_strategia_5_mdsrt.md:59-61, 432-434`.
- Minim 2 indicatori: Power + (Volume SAU Relative Strength):
`05_MODUL_4/02_strategia_5_mdsrt.md:85-88`.
- Trinity = toți trei în divergență simultan, „setup de 5 stele":
`05_MODUL_4/02_strategia_5_mdsrt.md:6-8, 34-42, 87-88`.
- Sensul din selecția simbolului (aliniere în cadranul 1; trei divergențe
simultane NU sunt Trinity): `08_ANALIZA_ZILNICA/11_selectia_simbolului.md:612-627`.
Rulează: .venv/bin/python gen/strategii/mdsrt_trinity.py
"""
import html
from pathlib import Path
W, H = 700, 550
BLACK = "#000000"
GRAY = "#777777"
RED = "#c00"
GREEN = "#188a3e"
BLUE = "#1a5fb4"
texts = [] # (x, y, text, size, weight, fill, w_est)
boxes = [] # dict-uri: doar casetele, pentru numărătoarea din assert-uri
def add(x, y, s, size=13, weight="normal", fill=BLACK):
factor = 0.62 if weight == "bold" else 0.56
w = len(s) * size * factor
texts.append((x, y, s, size, weight, fill, w))
return (x, y, s, size, weight, fill, w)
def box(x, y, w, h, *, fill="#ffffff", stroke=GRAY, sw=1.5, dash=None, row=None):
b = {"x": x, "y": y, "w": w, "h": h, "row": row, "lines": []}
boxes.append(b)
return b
def box_line(b, dx, dy, s, size=14, weight="normal", fill=BLACK):
cx = b["x"] + b["w"] / 2.0 + dx if dx is not None else b["x"] + b["w"] / 2.0
add(cx, b["y"] + dy, s, size, weight, fill)
b["lines"].append(s)
parts = []
def rect(b, fill, stroke, sw=1.5, dash=None):
d = f' stroke-dasharray="{dash}"' if dash else ""
return (f'<rect x="{b["x"]}" y="{b["y"]}" width="{b["w"]}" '
f'height="{b["h"]}" fill="{fill}" stroke="{stroke}" '
f'stroke-width="{sw}"{d}/>')
# --------------------------------------------------------------- titlu
add(W / 2, 34, "MDSRT — minimul obligatoriu vs. Trinity", 20, "bold")
add(W / 2, 58,
"Minim Power + unul din ceilalți e obligatoriu; toți trei în divergență = Trinity.",
13, "normal", GRAY)
# ------------------------------------------------- rând 1 — minim obligatoriu
add(W / 2, 100, "Minimul obligatoriu (M complet)", 16, "bold")
B1 = box(40, 112, 250, 56, row=1)
B2 = box(310, 112, 350, 56, row=1)
parts.append(rect(B1, GREEN, GREEN))
parts.append(rect(B2, "#ffffff", GREEN, sw=2, dash="5 3"))
box_line(B1, None, 31, "Power (obligatoriu)", 15, "bold", "#ffffff")
box_line(B2, None, 22, "Volume SAU Relative Strength", 14, "normal", GREEN)
box_line(B2, None, 44, "— oricare una", 13, "normal", GREEN)
add(W / 2, 196, "Power lipsește → setup-ul NU se califică", 14, "bold", RED)
# ------------------------------------------------- rând 2 — Trinity (opțional)
add(W / 2, 250, "Trinity (opțional, semnal mai puternic)", 16, "bold")
TITLES = ["Power (obligatoriu)", "Volume", "Relative Strength"]
B3 = box(30, 262, 200, 76, row=2)
B4 = box(250, 262, 200, 76, row=2)
B5 = box(470, 262, 200, 76, row=2)
for b in (B3, B4, B5):
parts.append(rect(b, GREEN, GREEN))
for b, t in zip((B3, B4, B5), TITLES):
box_line(b, None, 34, t, 14, "bold", "#ffffff")
box_line(b, None, 56, "divergență", 14, "bold", "#ffffff")
add(W / 2, 360, "toți trei în divergență simultan, pe aceeași zonă de preț",
14, "normal", BLACK)
add(W / 2, 386, "comparat în curs cu un «setup de 5 stele»", 14, "bold", GREEN)
# --------------------------------------------------------------- subsol
parts.append(f'<line x1="40" y1="420" x2="660" y2="420" stroke="{GRAY}" '
f'stroke-width="1"/>')
add(W / 2, 448,
"La MDSRT, „Trinity” se folosește local cu sensul „toți trei în divergență”.",
13, "normal", GRAY)
add(W / 2, 468,
"Lecția de selecție dă aceluiași cuvânt alt sens:", 13, "normal", GRAY)
add(W / 2, 486,
"aliniere în cadranul 1, nu divergență (contradicția 2.4).",
13, "normal", GRAY)
add(W / 2, 512,
"Sursa: 05_MODUL_4/02_strategia_5_mdsrt.md:6-8, 34-42, 59-61, 85-88;",
13, "normal", GRAY)
add(W / 2, 530,
"08_ANALIZA_ZILNICA/11_selectia_simbolului.md:612-627.",
13, "normal", GRAY)
# --------------------------------------------------------------- emitere
head = (f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 {W} {H}" '
f'width="{W}" height="{H}" font-family="system-ui, sans-serif" role="img" '
f'aria-label="MDSRT: minimul obligatoriu este Power (obligatoriu) plus '
f'Volume sau Relative Strength; varianta Trinity are toți trei '
f'indicatori în divergență simultan, pe aceeași zonă de preț">')
parts.insert(0, f'<rect x="0" y="0" width="{W}" height="{H}" fill="#ffffff"/>')
parts.insert(0, head)
body = []
for x, y, s, size, weight, fill, _ in texts:
body.append(
f'<text x="{x:.1f}" y="{y:.1f}" font-size="{size}" '
f'font-weight="{weight}" fill="{fill}" text-anchor="middle">'
f'{html.escape(s, quote=False)}</text>')
svg = "\n".join(parts + body + ["</svg>"]) + "\n"
# =============================================================== VERIFICARE
# 1. Rândul 1 conține exact 2 casete (Power + „Volume SAU Relative Strength"),
# nu 3.
row1 = [b for b in boxes if b["row"] == 1]
assert len(row1) == 2, f"rândul 1 are {len(row1)} casete, trebuie exact 2"
# 2. Rândul 2 conține exact 3 casete (Power, Volume, Relative Strength).
row2 = [b for b in boxes if b["row"] == 2]
assert len(row2) == 3, f"rândul 2 are {len(row2)} casete, trebuie exact 3"
assert [b["lines"][0] for b in row2] == TITLES, "titlurile din rândul 2 diferă"
# 3. Textul „Power (obligatoriu)" apare în ambele rânduri.
r1_labels = [s for b in row1 for s in b["lines"]]
r2_labels = [s for b in row2 for s in b["lines"]]
assert "Power (obligatoriu)" in r1_labels, "lipsește din rândul 1"
assert "Power (obligatoriu)" in r2_labels, "lipsește din rândul 2"
assert svg.count(">Power (obligatoriu)</text>") == 2, \
"„Power (obligatoriu)” nu apare exact de două ori"
# 4. Eticheta „Power lipsește → setup-ul NU se califică" e lângă rândul 1.
assert "Power lipsește → setup-ul NU se califică" in svg, "lipsește eticheta rând 1"
# 5. Nota de subsol despre cele două sensuri ale lui „Trinity" e prezentă.
assert "se folosește local cu sensul" in svg and "alt sens" in svg \
and "cadranul 1, nu divergență" in svg and "contradicția 2.4" in svg, \
"lipsește nota despre cele două sensuri ale lui Trinity"
# stil: fără umbre/gradienti, text >= 13px, totul în panză, fără suprapuneri
assert "filter=" not in svg and "gradient" not in svg, "fără umbre/gradienti"
assert 'font-family="system-ui, sans-serif"' in svg, "lipsește fontul cerut"
rects_collide = []
for x, y, s, size, weight, fill, w in texts:
assert size >= 13, f"font sub 13px: {s!r} ({size})"
left, right = x - w / 2.0, x + w / 2.0
top, bottom = y - size * 0.78, y + size * 0.22
assert 0 <= left and right <= W, f"etichetă în afara panzei (orizontal): {s!r}"
assert 0 <= top and bottom <= H, f"etichetă în afara panzei (vertical): {s!r}"
rects_collide.append((left, right, top, bottom, s))
for i in range(len(rects_collide)):
for j in range(i + 1, len(rects_collide)):
l1, r1, t1, b1, s1 = rects_collide[i]
l2, r2, t2, b2, s2 = rects_collide[j]
if r1 > l2 and r2 > l1 and b1 > t2 and b2 > t1:
raise AssertionError(f"etichete suprapuse: {s1!r} / {s2!r}")
for required in ("Minimul obligatoriu (M complet)",
"Volume SAU Relative Strength",
"oricare una",
"Trinity (opțional, semnal mai puternic)",
"divergență",
"pe aceeași zonă de preț",
"setup de 5 stele"):
assert required in svg, f"lipsește din desen: {required!r}"
OUT = Path(__file__).with_suffix(".svg")
OUT.write_text(svg, encoding="utf-8")
print(f"scris {OUT} ({len(svg)} bytes, {len(texts)} etichete, "
f"{len(boxes)} casete, {W}x{H}), verificare OK")

View File

@@ -0,0 +1,30 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 700 550" width="700" height="550" font-family="system-ui, sans-serif" role="img" aria-label="MDSRT: minimul obligatoriu este Power (obligatoriu) plus Volume sau Relative Strength; varianta Trinity are toți trei indicatori în divergență simultan, pe aceeași zonă de preț">
<rect x="0" y="0" width="700" height="550" fill="#ffffff"/>
<rect x="40" y="112" width="250" height="56" fill="#188a3e" stroke="#188a3e" stroke-width="1.5"/>
<rect x="310" y="112" width="350" height="56" fill="#ffffff" stroke="#188a3e" stroke-width="2" stroke-dasharray="5 3"/>
<rect x="30" y="262" width="200" height="76" fill="#188a3e" stroke="#188a3e" stroke-width="1.5"/>
<rect x="250" y="262" width="200" height="76" fill="#188a3e" stroke="#188a3e" stroke-width="1.5"/>
<rect x="470" y="262" width="200" height="76" fill="#188a3e" stroke="#188a3e" stroke-width="1.5"/>
<line x1="40" y1="420" x2="660" y2="420" stroke="#777777" stroke-width="1"/>
<text x="350.0" y="34.0" font-size="20" font-weight="bold" fill="#000000" text-anchor="middle">MDSRT — minimul obligatoriu vs. Trinity</text>
<text x="350.0" y="58.0" font-size="13" font-weight="normal" fill="#777777" text-anchor="middle">Minim Power + unul din ceilalți e obligatoriu; toți trei în divergență = Trinity.</text>
<text x="350.0" y="100.0" font-size="16" font-weight="bold" fill="#000000" text-anchor="middle">Minimul obligatoriu (M complet)</text>
<text x="165.0" y="143.0" font-size="15" font-weight="bold" fill="#ffffff" text-anchor="middle">Power (obligatoriu)</text>
<text x="485.0" y="134.0" font-size="14" font-weight="normal" fill="#188a3e" text-anchor="middle">Volume SAU Relative Strength</text>
<text x="485.0" y="156.0" font-size="13" font-weight="normal" fill="#188a3e" text-anchor="middle">— oricare una</text>
<text x="350.0" y="196.0" font-size="14" font-weight="bold" fill="#c00" text-anchor="middle">Power lipsește → setup-ul NU se califică</text>
<text x="350.0" y="250.0" font-size="16" font-weight="bold" fill="#000000" text-anchor="middle">Trinity (opțional, semnal mai puternic)</text>
<text x="130.0" y="296.0" font-size="14" font-weight="bold" fill="#ffffff" text-anchor="middle">Power (obligatoriu)</text>
<text x="130.0" y="318.0" font-size="14" font-weight="bold" fill="#ffffff" text-anchor="middle">divergență</text>
<text x="350.0" y="296.0" font-size="14" font-weight="bold" fill="#ffffff" text-anchor="middle">Volume</text>
<text x="350.0" y="318.0" font-size="14" font-weight="bold" fill="#ffffff" text-anchor="middle">divergență</text>
<text x="570.0" y="296.0" font-size="14" font-weight="bold" fill="#ffffff" text-anchor="middle">Relative Strength</text>
<text x="570.0" y="318.0" font-size="14" font-weight="bold" fill="#ffffff" text-anchor="middle">divergență</text>
<text x="350.0" y="360.0" font-size="14" font-weight="normal" fill="#000000" text-anchor="middle">toți trei în divergență simultan, pe aceeași zonă de preț</text>
<text x="350.0" y="386.0" font-size="14" font-weight="bold" fill="#188a3e" text-anchor="middle">comparat în curs cu un «setup de 5 stele»</text>
<text x="350.0" y="448.0" font-size="13" font-weight="normal" fill="#777777" text-anchor="middle">La MDSRT, „Trinity” se folosește local cu sensul „toți trei în divergență”.</text>
<text x="350.0" y="468.0" font-size="13" font-weight="normal" fill="#777777" text-anchor="middle">Lecția de selecție dă aceluiași cuvânt alt sens:</text>
<text x="350.0" y="486.0" font-size="13" font-weight="normal" fill="#777777" text-anchor="middle">aliniere în cadranul 1, nu divergență (contradicția 2.4).</text>
<text x="350.0" y="512.0" font-size="13" font-weight="normal" fill="#777777" text-anchor="middle">Sursa: 05_MODUL_4/02_strategia_5_mdsrt.md:6-8, 34-42, 59-61, 85-88;</text>
<text x="350.0" y="530.0" font-size="13" font-weight="normal" fill="#777777" text-anchor="middle">08_ANALIZA_ZILNICA/11_selectia_simbolului.md:612-627.</text>
</svg>

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@@ -0,0 +1,139 @@
"""Diagrama de ansamblu MP² — bottom major NVDA (strategii/02_mp2.md, Nivel 0).
„NVDA — weekly (pre-split 1:10)": downtrend masiv → bază/bottom oct. 2022 →
crossing-ul pivotului weekly (mic) peste cel monthly (mare) → candela de
trigger care închide peste pivotul mic (M+A) → intrare 13.80 → SL la swing
low-ul absolut 10.63 (−23 %) → urcare spre TP 17.25 (+25 %, ~9 săptămâni).
Cifrele (13.80 / 10.63 / 17.25 / −23 % / +25 %) sunt din
`summaries/04_MODUL_3/04_strategia_2_mp_patrat.md:341-357,435`.
Traseul de preț și cele două linii de pivot sunt schematice: cursul nu dă
valorile pivoților, doar faptul că micul trece deasupra marelui la crossing
(`...:326-335`).
Rulează:
python3 gen/strategii/mp2_ansamblu.py
Scrie lângă el `mp2_ansamblu.svg`.
"""
import sys
from pathlib import Path
sys.path.insert(0, "/workspace/atm-curs-dl")
from svg_candles import Chart, Level, Mark, from_closes # noqa: E402
OUT = Path(__file__).resolve().parent / "mp2_ansamblu.svg"
GRAY, RED, GREEN, BLUE = "#777", "#c00", "#188a3e", "#1a5fb4"
# ------------------------------------------------------------------ seria (38 bare)
# 16 bare de downtrend 32.00 -> 11.40 (minimul absolut 10.63 e fitilul barei 15),
# 10 bare de bază/recuperare 11.80 -> 13.80 (bara 25 = intrarea, după M+A),
# 12 bare de urcare 14.10 -> 17.25 (ținta). open-ul = close-ul precedentului.
DOWN = [32.00, 30.80, 29.60, 28.70, 27.40, 26.50, 25.30, 24.20, 23.60,
22.30, 21.10, 19.90, 18.50, 16.90, 15.20, 11.40] # 0-15
BASE = [11.80, 12.10, 12.35, 12.20, 12.55, 12.85, 13.05, 13.35, 13.55,
13.80] # 16-25
UP = [14.10, 14.45, 14.80, 15.10, 15.45, 15.80, 16.10, 15.90, 16.30,
16.60, 16.95, 17.25] # 26-37
CLOSES = DOWN + BASE + UP
assert len(CLOSES) == 38, len(CLOSES)
CAPIT, ENTRY, LAST = 15, 25, 37
# fitile (sus, jos) per bară; bara 15 are fitilul de jos până la minimul absolut
WICKS = ([(0.35, 0.35)] * 14
+ [(0.30, 0.30)] # 14
+ [(0.20, 0.77)] # 15: low = 11.40 - 0.77 = 10.63
+ [(0.30, 0.30)] * 9 # 16-24
+ [(0.25, 0.20)] # 25: candela de intrare
+ [(0.30, 0.30)] * 12) # 26-37
assert len(WICKS) == len(CLOSES)
VOLS = ([52, 55, 50, 58, 54, 49, 56, 52, 60, 57, 63, 68, 72, 80, 90, 120] # 0-15
+ [100, 72, 60, 55, 58, 62, 66, 70, 78, 95] # 16-25
+ [88, 80, 74, 70, 68, 72, 76, 70, 74, 78, 84, 92]) # 26-37
assert len(VOLS) == len(CLOSES)
cds = from_closes(CLOSES, wicks=WICKS, vols=VOLS, start=32.60)
# ------------------------------------------------- liniile de pivot (trepte)
# Stepped, schematice (cursul nu dă valorile): monthly „mare" se recalculează
# rar, weekly „mic" mai des. Crossing: micul trece deasupra marelui exact pe
# candela de intrare.
PMARE = [31.00] * 10 + [21.00] * 10 + [13.55] * 8 + [13.20] * 10 # monthly
PMIC = ([30.50] * 5 + [29.00] * 5 + [20.00] * 4 + [17.00] * 4
+ [12.60] * 3 + [12.95] * 2 + [13.20] * 2 + [13.70] * 3
+ [14.20] * 4 + [14.60] * 6) # weekly
assert len(PMARE) == len(PMIC) == len(CLOSES)
# ---------------------------------------------------------- assert-urile din spec
entry = cds[ENTRY].c
sl, tp = 10.63, cds[LAST].c
assert abs(entry - 13.80) < 0.05 # close-ul intrării == 13.80
assert abs(sl - 10.63) < 0.05 and sl < entry # SL = swing low absolut, sub entry
assert abs(tp - 17.25) < 0.10 and tp > entry # TP = 17.25, peste entry
# spec: `abs((entry/sl - 1)*100 - 23)`; formula din spec e inversată (dă +29.8,
# nu −23). Pierderea corectă din entry până la SL e (sl/entry - 1), deci o scriu așa:
loss_pct = (sl / entry - 1) * 100
assert abs(loss_pct + 23) < 1.5, loss_pct # SL ≈ −23 %
assert abs((tp / entry - 1) * 100 - 25) < 1.5 # TP ≈ +25 %
assert abs(min(c.l for c in cds) - 10.63) < 0.01 # minimul absolut al seriei = SL
assert min(c.l for c in cds[-5:-1]) > 10.63 # stopul de 4 bare ar fi mult mai sus
# crossing-ul pivoților, înainte/după candela de intrare
assert all(PMIC[i] < PMARE[i] for i in range(ENTRY - 4, ENTRY)), "mic < mare înainte de intrare"
assert all(PMIC[i] > PMARE[i] for i in range(ENTRY, ENTRY + 4)), "mic > mare după intrare"
assert cds[ENTRY].c > PMIC[ENTRY], "candela de trigger nu închide peste pivotul mic"
assert abs(cds[CAPIT].l - sl) < 1e-9, "fitilul barei 15 nu atinge exact SL-ul"
levels = [
Level(13.80, "entry ~13.80 (după M+A)", GREEN, label_at="left"),
Level(sl, "SL swing low absolut 10.63, −23%", RED, label_at="left"),
Level(tp, "TP ~+25% (17.25), ~9 săpt.", BLUE, label_at="left"),
]
marks = [
Mark(CAPIT, f"minim absolut {sl:.2f}", RED, "below", dy=-6),
Mark(ENTRY, "M+A", BLUE, "above"),
]
ch = Chart(candles=cds, levels=levels, marks=marks,
scale=17.0, gap=15, body_w=9, left=44, width=720, top=64, vol_h=70,
vol_label="volum", title="NVDA — weekly (pre-split 1:10)",
caption="Bottom oct. 2022: entry ~13.80, −23%, ~+25% în ~9 săpt. "
"Traseu și pivoți construiți (cursul nu dă valorile lor).")
svg = ch.render()
def step_points(vals: list[float]) -> str:
"""Polilinie în trepte: orizontal pe fiecare palier, vertical la schimbare."""
pts: list[tuple[float, float]] = []
for i, v in enumerate(vals):
x, y = ch._cx(i), ch._y(v)
if i == 0:
pts.append((x, y))
elif v != vals[i - 1]:
pts.append((x, ch._y(vals[i - 1]))) # orizontal până la bara nouă
pts.append((x, y)) # salt vertical la nivelul nou
else:
pts.append((x, y))
return " ".join(f"{x:.1f},{y:.1f}" for x, y in pts)
# injectate imediat după tag-ul de deschidere => desenate în spatele candelelor
inject = [
f'<polyline points="{step_points(PMARE)}" fill="none" stroke="{GRAY}" stroke-width="1.6"/>',
f'<polyline points="{step_points(PMIC)}" fill="none" stroke="{BLUE}" stroke-width="1.6"/>',
f'<text x="712" y="{ch._y(PMIC[-1]) - 6:.1f}" fill="{BLUE}" text-anchor="end">'
f'pivot weekly (mic)</text>',
f'<text x="712" y="{ch._y(PMARE[-1]) + 14:.1f}" fill="{GRAY}" text-anchor="end">'
f'pivot monthly (mare)</text>',
]
i = svg.index(">") + 1
svg = svg[:i] + "\n " + "\n ".join(inject) + svg[i:]
assert svg.count("\n\n") == 0, "linie goală în SVG rupe blocul din markdown"
OUT.write_text(svg)
print(f"scris {OUT.name}: {len(cds)} candele, entry {entry:.2f}, "
f"SL {sl:.2f} ({loss_pct:+.1f}%), TP {tp:.2f} "
f"({(tp/entry - 1) * 100:+.1f}%), crossing la bara {ENTRY}")

View File

@@ -0,0 +1,132 @@
<svg viewBox="0 0 720 603" xmlns="http://www.w3.org/2000/svg" style="width:100%;height:auto;font:12px sans-serif">
<polyline points="44.0,119.2 59.0,119.2 74.0,119.2 89.0,119.2 104.0,119.2 119.0,119.2 134.0,119.2 149.0,119.2 164.0,119.2 179.0,119.2 194.0,119.2 194.0,289.2 209.0,289.2 224.0,289.2 239.0,289.2 254.0,289.2 269.0,289.2 284.0,289.2 299.0,289.2 314.0,289.2 329.0,289.2 344.0,289.2 344.0,415.8 359.0,415.8 374.0,415.8 389.0,415.8 404.0,415.8 419.0,415.8 434.0,415.8 449.0,415.8 464.0,415.8 464.0,421.8 479.0,421.8 494.0,421.8 509.0,421.8 524.0,421.8 539.0,421.8 554.0,421.8 569.0,421.8 584.0,421.8 599.0,421.8" fill="none" stroke="#777" stroke-width="1.6"/>
<polyline points="44.0,127.7 59.0,127.7 74.0,127.7 89.0,127.7 104.0,127.7 119.0,127.7 119.0,153.2 134.0,153.2 149.0,153.2 164.0,153.2 179.0,153.2 194.0,153.2 194.0,306.2 209.0,306.2 224.0,306.2 239.0,306.2 254.0,306.2 254.0,357.2 269.0,357.2 284.0,357.2 299.0,357.2 314.0,357.2 314.0,432.0 329.0,432.0 344.0,432.0 359.0,432.0 359.0,426.0 374.0,426.0 389.0,426.0 389.0,421.8 404.0,421.8 419.0,421.8 419.0,413.3 434.0,413.3 449.0,413.3 464.0,413.3 464.0,404.8 479.0,404.8 494.0,404.8 509.0,404.8 524.0,404.8 524.0,398.0 539.0,398.0 554.0,398.0 569.0,398.0 584.0,398.0 599.0,398.0" fill="none" stroke="#1a5fb4" stroke-width="1.6"/>
<text x="712" y="392.0" fill="#1a5fb4" text-anchor="end">pivot weekly (mic)</text>
<text x="712" y="435.8" fill="#777" text-anchor="end">pivot monthly (mare)</text>
<text x="24" y="78" fill="#333" style="font-weight:bold">NVDA — weekly (pre-split 1:10)</text>
<line x1="24" y1="411.6" x2="706" y2="411.6" stroke="#188a3e" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="30" y="405.6" fill="#188a3e">entry ~13.80 (după M+A)</text>
<line x1="24" y1="465.4" x2="706" y2="465.4" stroke="#c00" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="30" y="459.4" fill="#c00">SL swing low absolut 10.63, −23%</text>
<line x1="24" y1="352.9" x2="706" y2="352.9" stroke="#1a5fb4" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="30" y="346.9" fill="#1a5fb4">TP ~+25% (17.25), ~9 săpt.</text>
<line x1="44.0" y1="86.0" x2="44.0" y2="108.1" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="39.5" y="92.0" width="9" height="10.2" fill="#c00"/>
<line x1="59.0" y1="96.2" x2="59.0" y2="128.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="54.5" y="102.2" width="9" height="20.4" fill="#c00"/>
<line x1="74.0" y1="116.6" x2="74.0" y2="148.9" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="69.5" y="122.6" width="9" height="20.4" fill="#c00"/>
<line x1="89.0" y1="137.0" x2="89.0" y2="164.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="84.5" y="143.0" width="9" height="15.3" fill="#c00"/>
<line x1="104.0" y1="152.3" x2="104.0" y2="186.3" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="99.5" y="158.3" width="9" height="22.1" fill="#c00"/>
<line x1="119.0" y1="174.4" x2="119.0" y2="201.6" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="114.5" y="180.4" width="9" height="15.3" fill="#c00"/>
<line x1="134.0" y1="189.7" x2="134.0" y2="222.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="129.5" y="195.7" width="9" height="20.4" fill="#c00"/>
<line x1="149.0" y1="210.1" x2="149.0" y2="240.7" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="144.5" y="216.1" width="9" height="18.7" fill="#c00"/>
<line x1="164.0" y1="228.8" x2="164.0" y2="250.9" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="159.5" y="234.8" width="9" height="10.2" fill="#c00"/>
<line x1="179.0" y1="239.0" x2="179.0" y2="273.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="174.5" y="245.0" width="9" height="22.1" fill="#c00"/>
<line x1="194.0" y1="261.1" x2="194.0" y2="293.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="189.5" y="267.1" width="9" height="20.4" fill="#c00"/>
<line x1="209.0" y1="281.5" x2="209.0" y2="313.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="204.5" y="287.5" width="9" height="20.4" fill="#c00"/>
<line x1="224.0" y1="301.9" x2="224.0" y2="337.6" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="219.5" y="307.9" width="9" height="23.8" fill="#c00"/>
<line x1="239.0" y1="325.7" x2="239.0" y2="364.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="234.5" y="331.7" width="9" height="27.2" fill="#c00"/>
<line x1="254.0" y1="353.8" x2="254.0" y2="392.9" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="249.5" y="358.9" width="9" height="28.9" fill="#c00"/>
<line x1="269.0" y1="384.4" x2="269.0" y2="465.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="264.5" y="387.8" width="9" height="64.6" fill="#c00"/>
<line x1="284.0" y1="440.5" x2="284.0" y2="457.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="279.5" y="445.6" width="9" height="6.8" fill="#188a3e"/>
<line x1="299.0" y1="435.4" x2="299.0" y2="450.7" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="294.5" y="440.5" width="9" height="5.1" fill="#188a3e"/>
<line x1="314.0" y1="431.1" x2="314.0" y2="445.6" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="309.5" y="436.2" width="9" height="4.2" fill="#188a3e"/>
<line x1="329.0" y1="431.1" x2="329.0" y2="443.9" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="324.5" y="436.2" width="9" height="2.6" fill="#c00"/>
<line x1="344.0" y1="427.7" x2="344.0" y2="443.9" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="339.5" y="432.8" width="9" height="6.0" fill="#188a3e"/>
<line x1="359.0" y1="422.6" x2="359.0" y2="437.9" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="354.5" y="427.7" width="9" height="5.1" fill="#188a3e"/>
<line x1="374.0" y1="419.2" x2="374.0" y2="432.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="369.5" y="424.3" width="9" height="3.4" fill="#188a3e"/>
<line x1="389.0" y1="414.1" x2="389.0" y2="429.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="384.5" y="419.2" width="9" height="5.1" fill="#188a3e"/>
<line x1="404.0" y1="410.7" x2="404.0" y2="424.3" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="399.5" y="415.8" width="9" height="3.4" fill="#188a3e"/>
<line x1="419.0" y1="407.3" x2="419.0" y2="419.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="414.5" y="411.6" width="9" height="4.2" fill="#188a3e"/>
<line x1="434.0" y1="401.4" x2="434.0" y2="416.7" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="429.5" y="406.5" width="9" height="5.1" fill="#188a3e"/>
<line x1="449.0" y1="395.4" x2="449.0" y2="411.6" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="444.5" y="400.5" width="9" height="5.9" fill="#188a3e"/>
<line x1="464.0" y1="389.5" x2="464.0" y2="405.6" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="459.5" y="394.6" width="9" height="6.0" fill="#188a3e"/>
<line x1="479.0" y1="384.4" x2="479.0" y2="399.7" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="474.5" y="389.5" width="9" height="5.1" fill="#188a3e"/>
<line x1="494.0" y1="378.4" x2="494.0" y2="394.6" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="489.5" y="383.5" width="9" height="5.9" fill="#188a3e"/>
<line x1="509.0" y1="372.5" x2="509.0" y2="388.6" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="504.5" y="377.6" width="9" height="6.0" fill="#188a3e"/>
<line x1="524.0" y1="367.4" x2="524.0" y2="382.7" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="519.5" y="372.5" width="9" height="5.1" fill="#188a3e"/>
<line x1="539.0" y1="367.4" x2="539.0" y2="381.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="534.5" y="372.5" width="9" height="3.4" fill="#c00"/>
<line x1="554.0" y1="364.0" x2="554.0" y2="381.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="549.5" y="369.1" width="9" height="6.8" fill="#188a3e"/>
<line x1="569.0" y1="358.9" x2="569.0" y2="374.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="564.5" y="364.0" width="9" height="5.1" fill="#188a3e"/>
<line x1="584.0" y1="352.9" x2="584.0" y2="369.1" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="579.5" y="358.0" width="9" height="5.9" fill="#188a3e"/>
<line x1="599.0" y1="347.8" x2="599.0" y2="363.1" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="594.5" y="352.9" width="9" height="5.1" fill="#188a3e"/>
<line x1="24" y1="561.4" x2="706" y2="561.4" stroke="#888"/>
<rect x="39.5" y="531.1" width="9" height="30.3" fill="#c00" opacity="0.55"/>
<rect x="54.5" y="529.4" width="9" height="32.1" fill="#c00" opacity="0.55"/>
<rect x="69.5" y="532.3" width="9" height="29.2" fill="#c00" opacity="0.55"/>
<rect x="84.5" y="527.6" width="9" height="33.8" fill="#c00" opacity="0.55"/>
<rect x="99.5" y="529.9" width="9" height="31.5" fill="#c00" opacity="0.55"/>
<rect x="114.5" y="532.9" width="9" height="28.6" fill="#c00" opacity="0.55"/>
<rect x="129.5" y="528.8" width="9" height="32.7" fill="#c00" opacity="0.55"/>
<rect x="144.5" y="531.1" width="9" height="30.3" fill="#c00" opacity="0.55"/>
<rect x="159.5" y="526.4" width="9" height="35.0" fill="#c00" opacity="0.55"/>
<rect x="174.5" y="528.2" width="9" height="33.2" fill="#c00" opacity="0.55"/>
<rect x="189.5" y="524.7" width="9" height="36.8" fill="#c00" opacity="0.55"/>
<rect x="204.5" y="521.8" width="9" height="39.7" fill="#c00" opacity="0.55"/>
<rect x="219.5" y="519.4" width="9" height="42.0" fill="#c00" opacity="0.55"/>
<rect x="234.5" y="514.8" width="9" height="46.7" fill="#c00" opacity="0.55"/>
<rect x="249.5" y="508.9" width="9" height="52.5" fill="#c00" opacity="0.55"/>
<rect x="264.5" y="491.4" width="9" height="70.0" fill="#c00" opacity="0.55"/>
<rect x="279.5" y="503.1" width="9" height="58.3" fill="#188a3e" opacity="0.55"/>
<rect x="294.5" y="519.4" width="9" height="42.0" fill="#188a3e" opacity="0.55"/>
<rect x="309.5" y="526.4" width="9" height="35.0" fill="#188a3e" opacity="0.55"/>
<rect x="324.5" y="529.4" width="9" height="32.1" fill="#c00" opacity="0.55"/>
<rect x="339.5" y="527.6" width="9" height="33.8" fill="#188a3e" opacity="0.55"/>
<rect x="354.5" y="525.3" width="9" height="36.2" fill="#188a3e" opacity="0.55"/>
<rect x="369.5" y="522.9" width="9" height="38.5" fill="#188a3e" opacity="0.55"/>
<rect x="384.5" y="520.6" width="9" height="40.8" fill="#188a3e" opacity="0.55"/>
<rect x="399.5" y="515.9" width="9" height="45.5" fill="#188a3e" opacity="0.55"/>
<rect x="414.5" y="506.0" width="9" height="55.4" fill="#188a3e" opacity="0.55"/>
<rect x="429.5" y="510.1" width="9" height="51.3" fill="#188a3e" opacity="0.55"/>
<rect x="444.5" y="514.8" width="9" height="46.7" fill="#188a3e" opacity="0.55"/>
<rect x="459.5" y="518.3" width="9" height="43.2" fill="#188a3e" opacity="0.55"/>
<rect x="474.5" y="520.6" width="9" height="40.8" fill="#188a3e" opacity="0.55"/>
<rect x="489.5" y="521.8" width="9" height="39.7" fill="#188a3e" opacity="0.55"/>
<rect x="504.5" y="519.4" width="9" height="42.0" fill="#188a3e" opacity="0.55"/>
<rect x="519.5" y="517.1" width="9" height="44.3" fill="#188a3e" opacity="0.55"/>
<rect x="534.5" y="520.6" width="9" height="40.8" fill="#c00" opacity="0.55"/>
<rect x="549.5" y="518.3" width="9" height="43.2" fill="#188a3e" opacity="0.55"/>
<rect x="564.5" y="515.9" width="9" height="45.5" fill="#188a3e" opacity="0.55"/>
<rect x="579.5" y="512.4" width="9" height="49.0" fill="#188a3e" opacity="0.55"/>
<rect x="594.5" y="507.8" width="9" height="53.7" fill="#188a3e" opacity="0.55"/>
<text x="24" y="485.4" fill="#555">volum</text>
<text x="269.0" y="479.4" fill="#c00" text-anchor="middle" style="font-weight:bold">minim absolut 10.63</text>
<text x="419.0" y="397.3" fill="#1a5fb4" text-anchor="middle" style="font-weight:bold">M+A</text>
<text x="24" y="591" fill="#555">Bottom oct. 2022: entry ~13.80, −23%, ~+25% în ~9 săpt. Traseu și pivoți construiți (cursul nu dă valorile lor).</text>
</svg>

After

Width:  |  Height:  |  Size: 11 KiB

272
gen/strategii/mp2_aplus.py Normal file
View File

@@ -0,0 +1,272 @@
"""MP2 crossing long — setup A+ vs. setup mediocru (două panouri).
Citește `gen/strategii/mp2_aplus.spec.txt` (TIP: PRET).
Panoul stânga (A+): SPY, aprilie — downtrend spre suportul quarterly 554.00,
bază cu Inverted Head & Shoulders (umăr stâng 566, head 555, umăr drept 568,
neckline 578.00, adică și un double bottom la ~554.5), intrare 577.00 pe prima
candelă verde după umărul drept, SL 554.40 chiar peste suport, 1R = 22.60,
TP1 599.60 (1:1).
Panoul dreapta (Mediocru): integral construit (cifre rotunde), NU din
material — range 100.00–110.00, crossing în mijlocul range-ului la 105.00
fără niciun pivot/suport dedesubte, intrare 105.00, SL 103.00, TP1 107.00.
Eticheta „exemplu construit, nu date reale" e cerută explicit de spec.
Două `Chart`-uri randate separat, lipite într-un SVG wrapper: fiecare panou
are scara lui, deci coordonatele rămân corecte prin construcție.
Rulează: python3 gen/strategii/mp2_aplus.py
Scrie: gen/strategii/mp2_aplus.svg
"""
import re
import sys
from pathlib import Path
sys.path.insert(0, "/workspace/atm-curs-dl")
from svg_candles import Candle, Chart, Level, Mark, from_closes # noqa: E402
OUT = Path(__file__).resolve().parent / "mp2_aplus.svg"
GRAY, RED, GREEN, BLUE, ORANGE = "#777", "#c00", "#188a3e", "#1a5fb4", "#b06000"
# ============================================================ PANOUL A+ (33 bare)
# 12 bare de cădere, 10 bare de bază (IHS), candela de intrare, 10 bare de urcare.
ENTRY_A = 22 # prima candelă verde după umărul drept
LS_A, HEAD_A, RS_A = 14, 17, 20 # umăr stâng / head / umăr drept
SUP_A, SL_A, NECK_A, TP1_A = 554.00, 554.40, 578.00, 599.60
CLOSES_A = [
596.0, 592.5, 589.0, 585.5, 582.5, 579.5, 576.5, 573.5, 571.0, 568.5, 566.5, 564.0, # 0-11 cădere
561.0, 559.5, # 12-13
566.0, # 14 umăr stâng (566)
561.5, # 15
555.8, 555.0, # 16-17 double bottom + head (555)
558.5, 563.0, # 18-19
568.0, # 20 umăr drept (568)
566.5, # 21 pullback
577.0, # 22 INTRARE 577.00
580.5, 583.5, 585.0, 583.5, 587.0, 590.0, 592.0, 594.0, 596.0, 597.5, # 23-32 urcare
]
assert len(CLOSES_A) == 33, len(CLOSES_A)
WICKS_A = [(0.30, 0.30)] * 33
WICKS_A[LS_A] = (0.00, 0.30) # high exact 566.00
WICKS_A[16] = (0.30, 1.20) # low 554.60 (primul picior al double bottom)
WICKS_A[HEAD_A] = (0.30, 0.55) # low 554.45, deasupra suportului 554.00
WICKS_A[RS_A] = (0.00, 0.30) # high exact 568.00
WICKS_A[ENTRY_A] = (0.25, 0.25)
VOLS_A = [58, 56, 54, 52, 50, 48, 47, 45, 44, 42, 41, 40,
38, 44, 52, 46, 61, 66, 58, 54, 62, 50,
88,
74, 70, 66, 61, 68, 63, 59, 57, 55, 60]
cds_a = from_closes(CLOSES_A, wicks=WICKS_A, vols=VOLS_A, start=598.0)
# ============================================================ PANOUL MEDIOCRU (28 bare)
# Integral construit. Range 100.00–110.00, crossing în mijloc la 105.00.
ENTRY_M = 7
SL_M, TP1_M = 103.00, 107.00
RANGE_LO, RANGE_HI = 100.00, 110.00
CLOSES_M = [
100.5, 101.1, 101.8, 102.5, 103.2, 103.9, 104.6, # 0-6 urcare spre 105
105.0, # 7 crossing / intrare 105.00
105.8, 106.5, 107.0, 106.2, 104.8, 103.4, 102.0, # 8-14 oscilație în range
103.0, 104.5, 106.0, 107.5, 108.8, 109.5, 108.6, # 15-21
107.2, 105.8, 104.4, 103.2, 104.5, 106.0, # 22-27
]
assert len(CLOSES_M) == 28, len(CLOSES_M)
WICKS_M = [(0.30, 0.30)] * 28
WICKS_M[ENTRY_M] = (0.30, 0.20)
WICKS_M[21] = (0.30, 0.30) # vârf 109.80
VOLS_M = [42, 44, 46, 48, 50, 52, 55, 72,
64, 60, 58, 56, 60, 54, 52,
50, 53, 56, 58, 62, 60, 58,
55, 52, 50, 48, 51, 54]
cds_m = from_closes(CLOSES_M, wicks=WICKS_M, vols=VOLS_M, start=100.5)
# ============================================================ assert-urile din spec
entry_a = cds_a[ENTRY_A].c
# A+: intrare 577.00
assert abs(entry_a - 577.00) < 0.05, f"intrare A+ = {entry_a}, aștept 577.00"
# A+: SL 554.40, peste suportul quarterly 554.00 (nu sub el)
assert abs(SL_A - 554.40) < 0.05 and SL_A > SUP_A, f"SL A+ {SL_A} vs suport {SUP_A}"
# A+: 1R = 22.60
r_a = entry_a - SL_A
assert abs(r_a - 22.60) < 0.05, f"1R A+ = {r_a}, aștept 22.60"
# A+: head 555 < umărul stâng 566 și < umărul drept 568
head_a, ls_a, rs_a = cds_a[HEAD_A].c, cds_a[LS_A].h, cds_a[RS_A].h
assert head_a < ls_a and head_a < rs_a, f"head {head_a} vs umeri {ls_a}/{rs_a}"
# A+: TP1 = entry + 1R = 599.60
assert abs(TP1_A - (entry_a + r_a)) < 0.05, f"TP1 A+ {TP1_A}, entry+1R {entry_a + r_a:.2f}"
# suplimentar: structura promisă de spec
assert cds_a[HEAD_A].l > SUP_A, f"low head {cds_a[HEAD_A].l} a spart suportul {SUP_A}"
assert cds_a[HEAD_A].l == min(c.l for c in cds_a), "head-ul nu e minimul seriei"
assert CLOSES_A[0] - CLOSES_A[HEAD_A] > 35, "downtrendul A+ e prea mic"
assert entry_a > cds_a[RS_A].c, "intrarea nu e deasupra umărului drept"
# Mediocru: intrare 105.00, SL 103.00, TP1 107.00
entry_m = cds_m[ENTRY_M].c
assert abs(entry_m - 105.00) < 0.05, f"intrare mediocru = {entry_m}, aștept 105.00"
assert abs(SL_M - 103.00) < 0.05 and abs(TP1_M - 107.00) < 0.05, "SL/TP mediocru greșite"
# Mediocru: niciun nivel de suport între SL 103.00 și intrarea 105.00
levels_m = [RANGE_LO, RANGE_HI, entry_m, SL_M, TP1_M]
assert not any(SL_M < p < entry_m for p in levels_m), \
f"există suport între {SL_M} și {entry_m}: {levels_m}"
# Mediocru: oscilația stă în range
assert all(RANGE_LO <= c.c <= RANGE_HI for c in cds_m), "bare în afara range-ului"
assert min(c.l for c in cds_m) >= RANGE_LO, "low sub range"
print(f"A+ : intrare {entry_a:.2f} SL {SL_A:.2f} 1R {r_a:.2f} "
f"head {head_a:.0f} < umeri {ls_a:.0f}/{rs_a:.0f} TP1 {TP1_A:.2f}")
print(f"MED: intrare {entry_m:.2f} SL {SL_M:.2f} TP1 {TP1_M:.2f} "
f"niveluri între SL și intrare: {[p for p in levels_m if SL_M < p < entry_m]}")
# ============================================================ desenele celor două panouri
PW = 560
ch_a = Chart(
candles=cds_a,
levels=[Level(SUP_A, f"suport quarterly {SUP_A:.2f}", GRAY, label_at="left"),
Level(SL_A, f"SL {SL_A:.2f}", RED, label_at="right"),
Level(entry_a, f"intrare {entry_a:.2f}", GREEN, label_at="left"),
Level(NECK_A, f"neckline {NECK_A:.2f}", ORANGE, label_at="right"),
Level(TP1_A, f"TP1 {TP1_A:.2f} = 1:1", BLUE, label_at="right")],
marks=[Mark(LS_A, f"umăr stâng {ls_a:.0f}", GRAY, "above", dy=-4),
Mark(HEAD_A, f"head {head_a:.0f} / double bottom", RED, "below", dy=8),
Mark(RS_A, f"umăr drept {rs_a:.0f}", GRAY, "above", dy=-4),
Mark(ENTRY_A, "crossing", GREEN, "above", dy=-6)],
scale=7.5, gap=15, body_w=9, left=44, width=PW, vol_h=64,
vol_label="volum", title="A+ — crossing la confluență (SPY, aprilie)",
trend="down",
caption="Confluență: suport 554.00 + double bottom 554.45 + neckline 578.00",
)
ch_m = Chart(
candles=cds_m,
levels=[Level(RANGE_HI, f"range {RANGE_HI:.2f}", GRAY, label_at="right"),
Level(RANGE_LO, f"range {RANGE_LO:.2f}", GRAY, label_at="left"),
Level(SL_M, f"SL {SL_M:.2f}", RED, label_at="right"),
Level(entry_m, f"intrare {entry_m:.2f}", GREEN, label_at="right"),
Level(TP1_M, f"TP1 {TP1_M:.2f}", BLUE, label_at="right")],
marks=[Mark(ENTRY_M, "crossing (mijloc)", GREEN, "above", dy=-4)],
scale=34.0, gap=15, body_w=9, left=44, width=PW, vol_h=64,
vol_label="volum", title="Mediocru — crossing în mijlocul range-ului (construit)",
caption="exemplu construit, nu date reale — SL nu stă pe niciun pivot",
)
# ============================================================ wrapper: două panouri alăturate
def inner(svg: str) -> str:
"""Conținutul dintre tag-urile <svg> ale unui panou, fără elementul rădăcină."""
return svg[svg.index(">") + 1:svg.rindex("</svg>")]
def vb_height(svg: str) -> float:
m = re.search(r'viewBox="0 0 [\d.]+ ([\d.]+)"', svg)
assert m, "nu găsesc viewBox în panoul randat"
return float(m.group(1))
PANEL_GAP, PAD = 24, 8
TITLE_H, FOOTER_H = 30, 62
svg_a, svg_m = ch_a.render(), ch_m.render()
HA, HM = vb_height(svg_a), vb_height(svg_m)
assert svg_a.count("\n\n") == 0 and svg_m.count("\n\n") == 0, "linie goală în panou"
TOTAL_W = PAD + PW + PANEL_GAP + PW + PAD
TOTAL_H = PAD + TITLE_H + max(HA, HM) + FOOTER_H + PAD
OFF_A = (PAD, PAD + TITLE_H)
OFF_M = (PAD + PW + PANEL_GAP, PAD + TITLE_H)
TITLU = "MP² crossing long — setup A+ vs. setup mediocru (aceeași strategie)"
LEGENDA = [
"Legendă: ce separă cele două sunt nivelurile independente dedesubte "
"(suport quarterly + double bottom + neckline),",
"nu un filtru pus în scaner. Panoul din dreapta e exemplu construit, nu date reale.",
"Surse A+: 09_QA/01_2026-08-25.md:389-394, 485-490, 501-507.",
]
wrapper = (
f'<svg viewBox="0 0 {TOTAL_W} {TOTAL_H:.0f}" width="{TOTAL_W}" height="{TOTAL_H:.0f}" '
f'xmlns="http://www.w3.org/2000/svg" style="width:100%;height:auto;font:12px sans-serif">'
f'<text x="{TOTAL_W / 2:.0f}" y="20" fill="#333" text-anchor="middle" '
f'style="font-weight:bold">{TITLU}</text>'
f'<g transform="translate({OFF_A[0]},{OFF_A[1]})">{inner(svg_a)}</g>'
f'<g transform="translate({OFF_M[0]},{OFF_M[1]})">{inner(svg_m)}</g>'
+ "".join(
f'<text x="{TOTAL_W / 2:.0f}" y="{TOTAL_H - FOOTER_H + 18 + 18 * i:.0f}" '
f'fill="#555" text-anchor="middle">{ln}</text>'
for i, ln in enumerate(LEGENDA)
)
+ '</svg>'
)
# ============================================================ verificarea pe SVG-ul final
def texts(svg: str):
"""Toate (x, y, text-anchor, text) din elementele <text> ale unui fragment."""
out = []
for m in re.finditer(r'<text x="([-\d.]+)" y="([-\d.]+)"([^>]*)>(.*?)</text>', svg):
x, y, attrs, t = float(m.group(1)), float(m.group(2)), m.group(3), m.group(4)
if t.startswith("<"):
continue
am = re.search(r'text-anchor="(\w+)"', attrs)
out.append((x, y, am.group(1) if am else "start", t))
return out
def span(x: float, anchor: str, t: str) -> tuple[float, float]:
"""Intervalul orizontal aproximativ al unui text, față de ancora lui."""
w = len(t) * 12 * 0.56
left = x - w if anchor == "end" else (x - w / 2 if anchor == "middle" else x)
return left, left + w
assert wrapper.count("\n\n") == 0, "linie goală în SVG"
# fiecare text (după translatarea panoului) e în întregime în interiorul pânzei
all_txt = []
for frag, (ox, oy) in ((inner(svg_a), OFF_A), (inner(svg_m), OFF_M)):
for x, y, anchor, t in texts(frag):
gx, gy = x + ox, y + oy
gl, gr = gx + span(0, anchor, t)[0], gx + span(0, anchor, t)[1]
assert ox <= gl and gr <= ox + PW, f"text iese din panou: {t!r} ({gl:.0f}..{gr:.0f})"
assert -1 <= gl and gr <= TOTAL_W + 1, f"text ieșit pe orizontală: {t!r}"
assert 0 <= gy <= TOTAL_H, f"text ieșit pe verticală: {t!r} ({gy:.0f})"
all_txt.append((gx, gy, anchor, t))
# textele din wrapper (titlu + legenda), în coordonate globale
wtxt = [(TOTAL_W / 2, 20.0, "middle", TITLU)]
wtxt += [(TOTAL_W / 2, TOTAL_H - FOOTER_H + 18 + 18 * i, "middle", ln)
for i, ln in enumerate(LEGENDA)]
for x, y, anchor, t in wtxt:
gl, gr = span(x, anchor, t)
assert -1 <= gl and gr <= TOTAL_W + 1, f"text din wrapper iese din pânză: {t!r}"
assert 0 <= y <= TOTAL_H, f"text din wrapper în afara înălțimii: {t!r}"
all_txt.append((x, y, anchor, t))
assert len(all_txt) >= 12, f"prea puține etichete: {len(all_txt)}"
# nu se suprapun două texte pe aceeași poziție sau pe rândul apropiat
for i in range(len(all_txt)):
xi, yi, ai, si = all_txt[i]
li, ri = (xi + span(0, ai, si)[0], xi + span(0, ai, si)[1])
for j in range(i + 1, len(all_txt)):
xj, yj, aj, sj = all_txt[j]
lj, rj = xj + span(0, aj, sj)[0], xj + span(0, aj, sj)[1]
if abs(yi - yj) < 12 and li < rj - 4 and lj < ri - 4:
raise AssertionError(f"texte suprapuse: {si!r} / {sj!r}")
# etichetele cerute de spec există în desen
for cheie in ("suport quarterly 554.00", f"SL {SL_A:.2f}", "intrare 577.00",
"neckline 578.00", "TP1 599.60 = 1:1",
f"umăr stâng {ls_a:.0f}", f"head {head_a:.0f}", f"umăr drept {rs_a:.0f}",
"A+ — crossing la confluență (SPY, aprilie)",
"Mediocru — crossing în mijlocul range-ului (construit)",
"range 110.00", "range 100.00", "SL 103.00", "intrare 105.00", "TP1 107.00",
"crossing (mijloc)", "exemplu construit, nu date reale"):
assert cheie in wrapper, f"lipsește din desen: {cheie!r}"
OUT.write_text(wrapper, encoding="utf-8")
print(f"scris {OUT.name}: {TOTAL_W}x{TOTAL_H:.0f}, "
f"{len(all_txt)} etichete, verificare OK")

221
gen/strategii/mp2_aplus.svg Normal file
View File

@@ -0,0 +1,221 @@
<svg viewBox="0 0 1160 694" width="1160" height="694" xmlns="http://www.w3.org/2000/svg" style="width:100%;height:auto;font:12px sans-serif"><text x="580" y="20" fill="#333" text-anchor="middle" style="font-weight:bold">MP² crossing long — setup A+ vs. setup mediocru (aceeași strategie)</text><g transform="translate(8,38)">
<text x="24" y="48" fill="#333" style="font-weight:bold">A+ — crossing la confluență (SPY, aprilie)</text>
<line x1="24" y1="454.0" x2="546" y2="454.0" stroke="#777" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="30" y="448.0" fill="#777">suport quarterly 554.00</text>
<line x1="24" y1="451.0" x2="546" y2="451.0" stroke="#c00" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="532" y="445.0" fill="#c00" text-anchor="end">SL 554.40</text>
<line x1="24" y1="281.5" x2="546" y2="281.5" stroke="#188a3e" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="30" y="275.5" fill="#188a3e">intrare 577.00</text>
<line x1="24" y1="274.0" x2="546" y2="274.0" stroke="#b06000" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="532" y="268.0" fill="#b06000" text-anchor="end">neckline 578.00</text>
<line x1="24" y1="112.0" x2="546" y2="112.0" stroke="#1a5fb4" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="532" y="106.0" fill="#1a5fb4" text-anchor="end">TP1 599.60 = 1:1</text>
<polyline points="24,75.8 36,121.8" fill="none" stroke="#999" stroke-width="2"/>
<text x="24" y="63.8" fill="#999">downtrend anterior</text>
<line x1="44.0" y1="121.8" x2="44.0" y2="141.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="39.5" y="124.0" width="9" height="15.0" fill="#c00"/>
<line x1="59.0" y1="136.8" x2="59.0" y2="167.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="54.5" y="139.0" width="9" height="26.2" fill="#c00"/>
<line x1="74.0" y1="163.0" x2="74.0" y2="193.7" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="69.5" y="165.3" width="9" height="26.2" fill="#c00"/>
<line x1="89.0" y1="189.3" x2="89.0" y2="220.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="84.5" y="191.5" width="9" height="26.2" fill="#c00"/>
<line x1="104.0" y1="215.5" x2="104.0" y2="242.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="99.5" y="217.8" width="9" height="22.5" fill="#c00"/>
<line x1="119.0" y1="238.0" x2="119.0" y2="265.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="114.5" y="240.3" width="9" height="22.5" fill="#c00"/>
<line x1="134.0" y1="260.5" x2="134.0" y2="287.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="129.5" y="262.8" width="9" height="22.5" fill="#c00"/>
<line x1="149.0" y1="283.0" x2="149.0" y2="310.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="144.5" y="285.3" width="9" height="22.5" fill="#c00"/>
<line x1="164.0" y1="305.5" x2="164.0" y2="328.7" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="159.5" y="307.8" width="9" height="18.8" fill="#c00"/>
<line x1="179.0" y1="324.3" x2="179.0" y2="347.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="174.5" y="326.5" width="9" height="18.8" fill="#c00"/>
<line x1="194.0" y1="343.0" x2="194.0" y2="362.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="189.5" y="345.3" width="9" height="15.0" fill="#c00"/>
<line x1="209.0" y1="358.0" x2="209.0" y2="381.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="204.5" y="360.3" width="9" height="18.8" fill="#c00"/>
<line x1="224.0" y1="376.8" x2="224.0" y2="403.7" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="219.5" y="379.0" width="9" height="22.5" fill="#c00"/>
<line x1="239.0" y1="399.3" x2="239.0" y2="415.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="234.5" y="401.5" width="9" height="11.2" fill="#c00"/>
<line x1="254.0" y1="364.0" x2="254.0" y2="415.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="249.5" y="364.0" width="9" height="48.8" fill="#188a3e"/>
<line x1="269.0" y1="361.8" x2="269.0" y2="400.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="264.5" y="364.0" width="9" height="33.8" fill="#c00"/>
<line x1="284.0" y1="395.5" x2="284.0" y2="449.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="279.5" y="397.8" width="9" height="42.8" fill="#c00"/>
<line x1="299.0" y1="438.3" x2="299.0" y2="450.6" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="294.5" y="440.5" width="9" height="6.0" fill="#c00"/>
<line x1="314.0" y1="418.0" x2="314.0" y2="448.7" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="309.5" y="420.3" width="9" height="26.2" fill="#188a3e"/>
<line x1="329.0" y1="384.3" x2="329.0" y2="422.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="324.5" y="386.5" width="9" height="33.8" fill="#188a3e"/>
<line x1="344.0" y1="349.0" x2="344.0" y2="388.7" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="339.5" y="349.0" width="9" height="37.5" fill="#188a3e"/>
<line x1="359.0" y1="346.8" x2="359.0" y2="362.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="354.5" y="349.0" width="9" height="11.2" fill="#c00"/>
<line x1="374.0" y1="279.6" x2="374.0" y2="362.1" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="369.5" y="281.5" width="9" height="78.8" fill="#188a3e"/>
<line x1="389.0" y1="253.0" x2="389.0" y2="283.7" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="384.5" y="255.3" width="9" height="26.2" fill="#188a3e"/>
<line x1="404.0" y1="230.5" x2="404.0" y2="257.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="399.5" y="232.8" width="9" height="22.5" fill="#188a3e"/>
<line x1="419.0" y1="219.3" x2="419.0" y2="235.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="414.5" y="221.5" width="9" height="11.2" fill="#188a3e"/>
<line x1="434.0" y1="219.3" x2="434.0" y2="235.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="429.5" y="221.5" width="9" height="11.2" fill="#c00"/>
<line x1="449.0" y1="204.3" x2="449.0" y2="235.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="444.5" y="206.5" width="9" height="26.2" fill="#188a3e"/>
<line x1="464.0" y1="181.8" x2="464.0" y2="208.7" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="459.5" y="184.0" width="9" height="22.5" fill="#188a3e"/>
<line x1="479.0" y1="166.8" x2="479.0" y2="186.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="474.5" y="169.0" width="9" height="15.0" fill="#188a3e"/>
<line x1="494.0" y1="151.8" x2="494.0" y2="171.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="489.5" y="154.0" width="9" height="15.0" fill="#188a3e"/>
<line x1="509.0" y1="136.8" x2="509.0" y2="156.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="504.5" y="139.0" width="9" height="15.0" fill="#188a3e"/>
<line x1="524.0" y1="125.5" x2="524.0" y2="141.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="519.5" y="127.8" width="9" height="11.2" fill="#188a3e"/>
<line x1="24" y1="544.0" x2="546" y2="544.0" stroke="#888"/>
<rect x="39.5" y="501.8" width="9" height="42.2" fill="#c00" opacity="0.55"/>
<rect x="54.5" y="503.3" width="9" height="40.7" fill="#c00" opacity="0.55"/>
<rect x="69.5" y="504.7" width="9" height="39.3" fill="#c00" opacity="0.55"/>
<rect x="84.5" y="506.2" width="9" height="37.8" fill="#c00" opacity="0.55"/>
<rect x="99.5" y="507.6" width="9" height="36.4" fill="#c00" opacity="0.55"/>
<rect x="114.5" y="509.1" width="9" height="34.9" fill="#c00" opacity="0.55"/>
<rect x="129.5" y="509.8" width="9" height="34.2" fill="#c00" opacity="0.55"/>
<rect x="144.5" y="511.3" width="9" height="32.7" fill="#c00" opacity="0.55"/>
<rect x="159.5" y="512.0" width="9" height="32.0" fill="#c00" opacity="0.55"/>
<rect x="174.5" y="513.5" width="9" height="30.5" fill="#c00" opacity="0.55"/>
<rect x="189.5" y="514.2" width="9" height="29.8" fill="#c00" opacity="0.55"/>
<rect x="204.5" y="514.9" width="9" height="29.1" fill="#c00" opacity="0.55"/>
<rect x="219.5" y="516.4" width="9" height="27.6" fill="#c00" opacity="0.55"/>
<rect x="234.5" y="512.0" width="9" height="32.0" fill="#c00" opacity="0.55"/>
<rect x="249.5" y="506.2" width="9" height="37.8" fill="#188a3e" opacity="0.55"/>
<rect x="264.5" y="510.5" width="9" height="33.5" fill="#c00" opacity="0.55"/>
<rect x="279.5" y="499.6" width="9" height="44.4" fill="#c00" opacity="0.55"/>
<rect x="294.5" y="496.0" width="9" height="48.0" fill="#c00" opacity="0.55"/>
<rect x="309.5" y="501.8" width="9" height="42.2" fill="#188a3e" opacity="0.55"/>
<rect x="324.5" y="504.7" width="9" height="39.3" fill="#188a3e" opacity="0.55"/>
<rect x="339.5" y="498.9" width="9" height="45.1" fill="#188a3e" opacity="0.55"/>
<rect x="354.5" y="507.6" width="9" height="36.4" fill="#c00" opacity="0.55"/>
<rect x="369.5" y="480.0" width="9" height="64.0" fill="#188a3e" opacity="0.55"/>
<rect x="384.5" y="490.2" width="9" height="53.8" fill="#188a3e" opacity="0.55"/>
<rect x="399.5" y="493.1" width="9" height="50.9" fill="#188a3e" opacity="0.55"/>
<rect x="414.5" y="496.0" width="9" height="48.0" fill="#188a3e" opacity="0.55"/>
<rect x="429.5" y="499.6" width="9" height="44.4" fill="#c00" opacity="0.55"/>
<rect x="444.5" y="494.5" width="9" height="49.5" fill="#188a3e" opacity="0.55"/>
<rect x="459.5" y="498.2" width="9" height="45.8" fill="#188a3e" opacity="0.55"/>
<rect x="474.5" y="501.1" width="9" height="42.9" fill="#188a3e" opacity="0.55"/>
<rect x="489.5" y="502.5" width="9" height="41.5" fill="#188a3e" opacity="0.55"/>
<rect x="504.5" y="504.0" width="9" height="40.0" fill="#188a3e" opacity="0.55"/>
<rect x="519.5" y="500.4" width="9" height="43.6" fill="#188a3e" opacity="0.55"/>
<text x="24" y="474.0" fill="#555">volum</text>
<text x="254.0" y="350.0" fill="#777" text-anchor="middle" style="font-weight:bold">umăr stâng 566</text>
<text x="299.0" y="468.0" fill="#c00" text-anchor="middle" style="font-weight:bold">head 555 / double bottom</text>
<text x="344.0" y="335.0" fill="#777" text-anchor="middle" style="font-weight:bold">umăr drept 568</text>
<text x="374.0" y="263.6" fill="#188a3e" text-anchor="middle" style="font-weight:bold">crossing</text>
<text x="24" y="574" fill="#555">Confluență: suport 554.00 + double bottom 554.45 + neckline 578.00</text>
</g><g transform="translate(592,38)">
<text x="24" y="48" fill="#333" style="font-weight:bold">Mediocru — crossing în mijlocul range-ului (construit)</text>
<line x1="24" y1="56.0" x2="546" y2="56.0" stroke="#777" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="532" y="50.0" fill="#777" text-anchor="end">range 110.00</text>
<line x1="24" y1="396.0" x2="546" y2="396.0" stroke="#777" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="30" y="390.0" fill="#777">range 100.00</text>
<line x1="24" y1="294.0" x2="546" y2="294.0" stroke="#c00" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="532" y="288.0" fill="#c00" text-anchor="end">SL 103.00</text>
<line x1="24" y1="226.0" x2="546" y2="226.0" stroke="#188a3e" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="532" y="220.0" fill="#188a3e" text-anchor="end">intrare 105.00</text>
<line x1="24" y1="158.0" x2="546" y2="158.0" stroke="#1a5fb4" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="532" y="152.0" fill="#1a5fb4" text-anchor="end">TP1 107.00</text>
<line x1="44.0" y1="368.8" x2="44.0" y2="389.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="39.5" y="379.0" width="9" height="1.6" fill="#188a3e"/>
<line x1="59.0" y1="348.4" x2="59.0" y2="389.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="54.5" y="358.6" width="9" height="20.4" fill="#188a3e"/>
<line x1="74.0" y1="324.6" x2="74.0" y2="368.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="69.5" y="334.8" width="9" height="23.8" fill="#188a3e"/>
<line x1="89.0" y1="300.8" x2="89.0" y2="345.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="84.5" y="311.0" width="9" height="23.8" fill="#188a3e"/>
<line x1="104.0" y1="277.0" x2="104.0" y2="321.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="99.5" y="287.2" width="9" height="23.8" fill="#188a3e"/>
<line x1="119.0" y1="253.2" x2="119.0" y2="297.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="114.5" y="263.4" width="9" height="23.8" fill="#188a3e"/>
<line x1="134.0" y1="229.4" x2="134.0" y2="273.6" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="129.5" y="239.6" width="9" height="23.8" fill="#188a3e"/>
<line x1="149.0" y1="215.8" x2="149.0" y2="246.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="144.5" y="226.0" width="9" height="13.6" fill="#188a3e"/>
<line x1="164.0" y1="188.6" x2="164.0" y2="236.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="159.5" y="198.8" width="9" height="27.2" fill="#188a3e"/>
<line x1="179.0" y1="164.8" x2="179.0" y2="209.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="174.5" y="175.0" width="9" height="23.8" fill="#188a3e"/>
<line x1="194.0" y1="147.8" x2="194.0" y2="185.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="189.5" y="158.0" width="9" height="17.0" fill="#188a3e"/>
<line x1="209.0" y1="147.8" x2="209.0" y2="195.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="204.5" y="158.0" width="9" height="27.2" fill="#c00"/>
<line x1="224.0" y1="175.0" x2="224.0" y2="243.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="219.5" y="185.2" width="9" height="47.6" fill="#c00"/>
<line x1="239.0" y1="222.6" x2="239.0" y2="290.6" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="234.5" y="232.8" width="9" height="47.6" fill="#c00"/>
<line x1="254.0" y1="270.2" x2="254.0" y2="338.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="249.5" y="280.4" width="9" height="47.6" fill="#c00"/>
<line x1="269.0" y1="283.8" x2="269.0" y2="338.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="264.5" y="294.0" width="9" height="34.0" fill="#188a3e"/>
<line x1="284.0" y1="232.8" x2="284.0" y2="304.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="279.5" y="243.0" width="9" height="51.0" fill="#188a3e"/>
<line x1="299.0" y1="181.8" x2="299.0" y2="253.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="294.5" y="192.0" width="9" height="51.0" fill="#188a3e"/>
<line x1="314.0" y1="130.8" x2="314.0" y2="202.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="309.5" y="141.0" width="9" height="51.0" fill="#188a3e"/>
<line x1="329.0" y1="86.6" x2="329.0" y2="151.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="324.5" y="96.8" width="9" height="44.2" fill="#188a3e"/>
<line x1="344.0" y1="62.8" x2="344.0" y2="107.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="339.5" y="73.0" width="9" height="23.8" fill="#188a3e"/>
<line x1="359.0" y1="62.8" x2="359.0" y2="113.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="354.5" y="73.0" width="9" height="30.6" fill="#c00"/>
<line x1="374.0" y1="93.4" x2="374.0" y2="161.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="369.5" y="103.6" width="9" height="47.6" fill="#c00"/>
<line x1="389.0" y1="141.0" x2="389.0" y2="209.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="384.5" y="151.2" width="9" height="47.6" fill="#c00"/>
<line x1="404.0" y1="188.6" x2="404.0" y2="256.6" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="399.5" y="198.8" width="9" height="47.6" fill="#c00"/>
<line x1="419.0" y1="236.2" x2="419.0" y2="297.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="414.5" y="246.4" width="9" height="40.8" fill="#c00"/>
<line x1="434.0" y1="232.8" x2="434.0" y2="297.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="429.5" y="243.0" width="9" height="44.2" fill="#188a3e"/>
<line x1="449.0" y1="181.8" x2="449.0" y2="253.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="444.5" y="192.0" width="9" height="51.0" fill="#188a3e"/>
<line x1="24" y1="486.0" x2="546" y2="486.0" stroke="#888"/>
<rect x="39.5" y="448.7" width="9" height="37.3" fill="#188a3e" opacity="0.55"/>
<rect x="54.5" y="446.9" width="9" height="39.1" fill="#188a3e" opacity="0.55"/>
<rect x="69.5" y="445.1" width="9" height="40.9" fill="#188a3e" opacity="0.55"/>
<rect x="84.5" y="443.3" width="9" height="42.7" fill="#188a3e" opacity="0.55"/>
<rect x="99.5" y="441.6" width="9" height="44.4" fill="#188a3e" opacity="0.55"/>
<rect x="114.5" y="439.8" width="9" height="46.2" fill="#188a3e" opacity="0.55"/>
<rect x="129.5" y="437.1" width="9" height="48.9" fill="#188a3e" opacity="0.55"/>
<rect x="144.5" y="422.0" width="9" height="64.0" fill="#188a3e" opacity="0.55"/>
<rect x="159.5" y="429.1" width="9" height="56.9" fill="#188a3e" opacity="0.55"/>
<rect x="174.5" y="432.7" width="9" height="53.3" fill="#188a3e" opacity="0.55"/>
<rect x="189.5" y="434.4" width="9" height="51.6" fill="#188a3e" opacity="0.55"/>
<rect x="204.5" y="436.2" width="9" height="49.8" fill="#c00" opacity="0.55"/>
<rect x="219.5" y="432.7" width="9" height="53.3" fill="#c00" opacity="0.55"/>
<rect x="234.5" y="438.0" width="9" height="48.0" fill="#c00" opacity="0.55"/>
<rect x="249.5" y="439.8" width="9" height="46.2" fill="#c00" opacity="0.55"/>
<rect x="264.5" y="441.6" width="9" height="44.4" fill="#188a3e" opacity="0.55"/>
<rect x="279.5" y="438.9" width="9" height="47.1" fill="#188a3e" opacity="0.55"/>
<rect x="294.5" y="436.2" width="9" height="49.8" fill="#188a3e" opacity="0.55"/>
<rect x="309.5" y="434.4" width="9" height="51.6" fill="#188a3e" opacity="0.55"/>
<rect x="324.5" y="430.9" width="9" height="55.1" fill="#188a3e" opacity="0.55"/>
<rect x="339.5" y="432.7" width="9" height="53.3" fill="#188a3e" opacity="0.55"/>
<rect x="354.5" y="434.4" width="9" height="51.6" fill="#c00" opacity="0.55"/>
<rect x="369.5" y="437.1" width="9" height="48.9" fill="#c00" opacity="0.55"/>
<rect x="384.5" y="439.8" width="9" height="46.2" fill="#c00" opacity="0.55"/>
<rect x="399.5" y="441.6" width="9" height="44.4" fill="#c00" opacity="0.55"/>
<rect x="414.5" y="443.3" width="9" height="42.7" fill="#c00" opacity="0.55"/>
<rect x="429.5" y="440.7" width="9" height="45.3" fill="#188a3e" opacity="0.55"/>
<rect x="444.5" y="438.0" width="9" height="48.0" fill="#188a3e" opacity="0.55"/>
<text x="24" y="416.0" fill="#555">volum</text>
<text x="149.0" y="201.8" fill="#188a3e" text-anchor="middle" style="font-weight:bold">crossing (mijloc)</text>
<text x="24" y="516" fill="#555">exemplu construit, nu date reale — SL nu stă pe niciun pivot</text>
</g><text x="580" y="650" fill="#555" text-anchor="middle">Legendă: ce separă cele două sunt nivelurile independente dedesubte (suport quarterly + double bottom + neckline),</text><text x="580" y="668" fill="#555" text-anchor="middle">nu un filtru pus în scaner. Panoul din dreapta e exemplu construit, nu date reale.</text><text x="580" y="686" fill="#555" text-anchor="middle">Surse A+: 09_QA/01_2026-08-25.md:389-394, 485-490, 501-507.</text></svg>

After

Width:  |  Height:  |  Size: 18 KiB

View File

@@ -0,0 +1,228 @@
#!/usr/bin/env python3
"""MP² — crossing vs. buy the dip: două panouri una sub alta.
Citește `gen/strategii/mp2_crossing.spec.txt` și scrie
`gen/strategii/mp2_crossing.svg`.
Aceleași două linii de pivot pe ambele panouri:
- pivot weekly (cel MARE) = 62.00, orizontal;
- pivot daily (cel MIC), stepped — se recalculează zilnic și „sare" pe nivel nou.
Sus, „DA — crossing (+2)": pivotul daily urcă din 61.70 și traversează 62.00 în
sus (marcat cu cerc: „crossing: 61.70 → 62.20"). Trigger = prima candelă verde
după crossing care închide peste pivotul daily (bara 11, close 62.40).
Jos, „NU — continuare/buy the dip (+1)": pivotul daily e deja deasupra lui 62.00
și rămâne acolo; prețul face închideri peste el, dar nimic nu s-a re-încrucișat,
deci semnalele ulterioare sunt buy the dip, nu crossing.
Cifrele (62.00 / 61.70 / 62.20) sunt din `summaries/09_QA/09_2025-12-09.md:69-83`
și distincția crossing vs. buy the dip din `summaries/09_QA/16_2025-10-07.md:35-43`.
Seriile de preț sunt construite (open = close-ul barei anterioare) ca nivelurile
afirmate să iasă exact din ele.
Rulare: python3 gen/strategii/mp2_crossing.py
"""
import re
import sys
from pathlib import Path
sys.path.insert(0, "/workspace/atm-curs-dl")
from svg_candles import Chart, Level, Mark, from_closes # noqa: E402
OUT = Path(__file__).with_suffix(".svg")
W = 480
VGAP = 44
SCALE = 95.0 # px / unitate, aceeași pe ambele panouri
WEEKLY = 62.00
GRAY, RED, GREEN, BLUE, ORANGE = "#777", "#c00", "#188a3e", "#1a5fb4", "#b06000"
# ---------------------------------------------------------------- panoul DA
# pivotul daily: 61.70 (sub 62.00) până la bara 9, apoi 62.20 (peste 62.00)
PDA = [61.70] * 10 + [62.20] * 12
CLOSES_DA = [61.30, 61.15, 61.35, 61.20, 61.45, 61.30, 61.55, 61.40,
61.60, 61.75, # 0-9: sub pivot 61.70/62.00
62.05, # 10: pivotul sare la 62.20; close inca sub el
62.40, # 11: trigger — verde, close peste 62.20
62.55, 62.50, 62.65, 62.60, 62.75, 62.70,
62.85, 62.80, 62.90, 62.95] # 12-21: urca peste pivot
WICKS_DA = [(0.08, 0.10)] * len(CLOSES_DA)
WICKS_DA[10] = (0.16, 0.10) # bara crossing-ului
WICKS_DA[11] = (0.22, 0.10) # bara de trigger
assert len(CLOSES_DA) == len(PDA) == 22
# ---------------------------------------------------------------- panoul NU
# pivotul daily e deja deasupra lui 62.00 pe toată durata (62.30 → 62.20)
PNU = [62.30] * 9 + [62.20] * 13
CLOSES_NU = [61.55, 61.70, 61.90, 61.80, 62.05,
62.35, # 5: primul close peste pivotul daily
62.25, 62.45, 62.55,
62.40, 62.60, 62.50, 62.70, 62.65, 62.80, 62.75,
62.90, 62.85, 63.00, 62.95, 63.10, 63.05]
WICKS_NU = [(0.08, 0.10)] * len(CLOSES_NU)
assert len(CLOSES_NU) == len(PNU) == 22
START_DA = CLOSES_DA[0] + 0.12
START_NU = CLOSES_NU[0] + 0.12
cds_da = from_closes(CLOSES_DA, wicks=WICKS_DA, start=START_DA)
cds_nu = from_closes(CLOSES_NU, wicks=WICKS_NU, start=START_NU)
# ------------------------------------------------------ assert-urile din spec
# 1. DA: pivotul daily e < 62.00 înainte de crossing și > 62.00 după
cross_da = next(i for i in range(1, len(PDA)) if PDA[i] > WEEKLY)
assert cross_da == 10, f"crossing-ul DA e la bara {cross_da}, nu 10"
assert all(v < WEEKLY for v in PDA[:cross_da]), "pivot daily DA nu e sub 62.00 înainte de crossing"
assert PDA[cross_da - 1] == 61.70 and PDA[cross_da] == 62.20, \
f"crossing DA {PDA[cross_da - 1]} -> {PDA[cross_da]}, nu 61.70 -> 62.20"
assert all(v > WEEKLY for v in PDA[cross_da:]), "pivot daily DA nu e peste 62.00 după crossing"
# 2. DA: trigger = prima candelă verde după crossing care închide peste pivotul daily
trigger = next(i for i in range(cross_da, len(cds_da))
if cds_da[i].c >= cds_da[i].o and cds_da[i].c > PDA[i])
assert trigger == 11, f"trigger-ul DA e la bara {trigger}, nu 11"
assert cds_da[trigger].c > PDA[trigger], "candela de trigger nu închide peste pivotul daily"
assert cds_da[10].c <= PDA[10], "bara crossing-ului nu trebuie să închidă peste pivot"
entry = cds_da[trigger].c
# 3. NU: toate valorile pivot_daily > 62.00 — nimic re-încrucișat
assert all(v > WEEKLY for v in PNU), "un pivot daily NU e sub 62.00"
assert not any(v < WEEKLY for v in PNU), "există pivot daily NU sub 62.00 => ar fi crossing"
assert min(PNU) > WEEKLY and max(PNU) > WEEKLY
# prețul face închideri peste pivotul daily, dar pivotul nu cade sub 62.00
prim_nu = next(i for i in range(len(cds_nu)) if cds_nu[i].c > PNU[i])
assert prim_nu == 5 and cds_nu[prim_nu].c > PNU[prim_nu], "NU nu face close peste pivotul daily"
assert min(PNU) == 62.20 and max(PNU) == 62.30, "pivotii NU nu sunt 62.20 / 62.30"
# 4. liniile sunt „stepped" (au cel puțin un salt), nu drepte
assert len(set(PDA)) >= 2 and len(set(PNU)) >= 2, "liniile de pivot nu sunt stepped"
# --------------------------------------------------- desen: câte un panou
def step_points(ch: Chart, vals: list[float]) -> str:
"""Polilinie în trepte: orizontal pe palier, salt vertical la schimbare."""
pts: list[tuple[float, float]] = []
for i, v in enumerate(vals):
x, y = ch._cx(i), ch._y(v)
if i == 0:
pts.append((x, y))
elif v != vals[i - 1]:
pts.append((x, ch._y(vals[i - 1])))
pts.append((x, y))
else:
pts.append((x, y))
return " ".join(f"{x:.1f},{y:.1f}" for x, y in pts)
def render_pivot_panel(cds, pivots, title) -> tuple[str, float, Chart]:
"""Randază un panou cu pivot weekly (Level) + pivot daily (polilinie injectată)."""
ch = Chart(candles=cds,
levels=[Level(WEEKLY, f"pivot weekly (cel MARE) {WEEKLY:.2f}",
BLUE, dash="", label_at="left")],
marks=[],
scale=SCALE, gap=15, body_w=9, left=44, width=W,
title=title)
svg = ch.render() # fixează _pmax/_y și adaugă +22 pentru titlu
inject = [
f'<polyline points="{step_points(ch, pivots)}" fill="none" '
f'stroke="{ORANGE}" stroke-width="1.8"/>',
f'<text x="{W - 10}" y="{ch._y(pivots[-1]) - 6:.1f}" fill="{ORANGE}" '
f'text-anchor="end" style="font-weight:bold">pivot daily (cel MIC)</text>',
]
i = svg.index(">") + 1
svg = svg[:i] + "\n " + "\n ".join(inject) + svg[i:]
m = re.search(r'viewBox="0 0 (\d+) ([\d.]+)"', svg)
body = svg[svg.index(">") + 1:svg.rindex("</svg>")]
return body, float(m.group(2)), ch
body_da, h_da, ch_da = render_pivot_panel(
cds_da, PDA, "DA — crossing (+2)")
# marcajul crossing-ului: cerc pe saltul pivotului + eticheta din spec
xc = ch_da._cx(cross_da)
yc = (ch_da._y(PDA[cross_da - 1]) + ch_da._y(PDA[cross_da])) / 2
circle = (f'<circle cx="{xc:.1f}" cy="{yc:.1f}" r="13" fill="none" '
f'stroke="{GREEN}" stroke-width="2"/>')
cross_txt = (f'<text x="{xc + 16:.1f}" y="{ch_da._y(PDA[cross_da - 1]) + 18:.1f}" '
f'fill="{GREEN}" style="font-weight:bold">crossing: 61.70 → 62.20</text>')
mark_txt = (f'<text x="{ch_da._cx(trigger):.1f}" y="{ch_da._y(cds_da[trigger].h) - 10:.1f}" '
f'fill="{GREEN}" text-anchor="middle" style="font-weight:bold">'
f'intrare: trigger (prima candelă verde)</text>')
i = body_da.rindex("</text>") + len("</text>") # după candele/marks, ca să fie vizibile
body_da = body_da[:i] + "\n " + circle + "\n " + cross_txt + "\n " + mark_txt + body_da[i:]
# panoul NU: eticheta din spec pe semnalele ulterioare
body_nu, h_nu, ch_nu = render_pivot_panel(
cds_nu, PNU, "NU — continuare/buy the dip (+1)")
body_nu = body_nu.rstrip() + (f'\n <text x="{ch_nu._cx(13):.1f}" y="{h_nu - 12:.1f}" '
f'fill="{GRAY}" text-anchor="middle" style="font-weight:bold">'
f'semnale ulterioare = BUY THE DIP, nu crossing</text>')
# ------------------------------------------------- lipirea celor două panouri
CAPS = [
"Pivot weekly 62.00 = linia orizontală (pivotul MARE).",
"Pivot daily (cel MIC) se recalculează zilnic și sare pe nivel nou.",
"Crossing = pivotul mic traversează 62.00 în sus.",
"Fără re-încrucișare, semnalele ulterioare sunt buy the dip,",
"nu crossing.",
]
cap_top = h_da + VGAP + h_nu + 16
H = cap_top + 17 * len(CAPS) + 10
parts = [
f'<svg viewBox="0 0 {W} {H:.0f}" xmlns="http://www.w3.org/2000/svg" '
f'style="width:100%;height:auto;font:12px sans-serif">',
f'<g transform="translate(0,0)">{body_da}</g>',
f'<g transform="translate(0,{h_da + VGAP:.0f})">{body_nu}</g>',
]
parts += [f'<text x="24" y="{cap_top + 17 * i:.0f}" fill="#555">{c}</text>'
for i, c in enumerate(CAPS)]
parts.append("</svg>")
svg = "\n ".join(parts)
assert "\n\n" not in svg, "linie goală în SVG"
# ------------------------------------- verificarea citirii: etichete în panză
TEXT = re.compile(r'<text x="([-\d.]+)" y="([-\d.]+)"([^>]*)>(.*?)</text>', re.S)
def _texts(body: str, y_off: float, tag: str):
out = []
for m in TEXT.finditer(body):
x, y = float(m.group(1)), float(m.group(2)) + y_off
attrs, s = m.group(3), m.group(4)
anchor = "start"
if 'text-anchor="end"' in attrs:
anchor = "end"
elif 'text-anchor="middle"' in attrs:
anchor = "middle"
bold = "bold" in attrs
w = len(s) * 12 * (0.62 if bold else 0.52)
left = x - w if anchor == "end" else (x - w / 2 if anchor == "middle" else x)
out.append((left, left + w, y, s, tag))
return out
labels = _texts(body_da, 0.0, "DA") + _texts(body_nu, h_da + VGAP, "NU")
for i, c in enumerate(CAPS): # și caption-urile, care nu-s în panouri
labels.append((24, 24 + len(c) * 12 * 0.52, cap_top + 17 * i, c, "CAP"))
for left, right, y, s, tag in labels:
assert left >= -2 and right <= W + 2, f"[{tag}] text iese din panză: {s!r}"
assert 0 <= y <= H, f"[{tag}] text în afara înălțimii: {s!r} (y={y:.0f})"
for i in range(len(labels)):
li, ri, yi, si, ti = labels[i]
for j in range(i + 1, len(labels)):
lj, rj, yj, sj, tj = labels[j]
if ti == tj and abs(yi - yj) < 12 and li < rj - 4 and lj < ri - 4:
raise AssertionError(f"[{ti}] texte suprapuse: {si!r} / {sj!r}")
dist = abs(ch_da._y(WEEKLY) - ch_da._y(PDA[cross_da - 1])) # 62.00 - 61.70, în px
assert dist > 10, f"distanța 61.70->62.00 = {dist:.1f}px, prea mică"
print(f"DA: crossing la bara {cross_da} (61.70 -> 62.20), trigger bara {trigger} "
f"close {entry:.2f} > pivot {PDA[trigger]:.2f}; bare {len(cds_da)}")
print(f"NU: pivot daily {min(PNU):.2f}..{max(PNU):.2f} (> 62.00), primul close "
f"peste pivot bara {prim_nu} ({cds_nu[prim_nu].c:.2f}); bare {len(cds_nu)}")
print(f"scris {OUT.name} ({W}x{H:.0f}); {len(labels)} etichete verificate; "
f"61.70->62.00 = {dist:.1f}px")
OUT.write_text(svg, encoding="utf-8")

View File

@@ -0,0 +1,112 @@
<svg viewBox="0 0 480 671" xmlns="http://www.w3.org/2000/svg" style="width:100%;height:auto;font:12px sans-serif">
<g transform="translate(0,0)">
<polyline points="44.0,182.3 59.0,182.3 74.0,182.3 89.0,182.3 104.0,182.3 119.0,182.3 134.0,182.3 149.0,182.3 164.0,182.3 179.0,182.3 194.0,182.3 194.0,134.8 209.0,134.8 224.0,134.8 239.0,134.8 254.0,134.8 269.0,134.8 284.0,134.8 299.0,134.8 314.0,134.8 329.0,134.8 344.0,134.8 359.0,134.8" fill="none" stroke="#b06000" stroke-width="1.8"/>
<text x="470" y="128.8" fill="#b06000" text-anchor="end" style="font-weight:bold">pivot daily (cel MIC)</text>
<text x="24" y="48" fill="#333" style="font-weight:bold">DA — crossing (+2)</text>
<line x1="24" y1="153.9" x2="466" y2="153.9" stroke="#1a5fb4" stroke-width="1.5" stroke-dasharray=""/>
<text x="30" y="147.9" fill="#1a5fb4">pivot weekly (cel MARE) 62.00</text>
<circle cx="194.0" cy="158.6" r="13" fill="none" stroke="#188a3e" stroke-width="2"/>
<text x="210.0" y="200.3" fill="#188a3e" style="font-weight:bold">crossing: 61.70 → 62.20</text>
<text x="209.0" y="85.0" fill="#188a3e" text-anchor="middle" style="font-weight:bold">intrare: trigger (prima candelă verde)</text>
<line x1="44.0" y1="201.4" x2="44.0" y2="229.9" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="39.5" y="209.0" width="9" height="11.4" fill="#c00"/>
<line x1="59.0" y1="212.8" x2="59.0" y2="244.1" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="54.5" y="220.4" width="9" height="14.2" fill="#c00"/>
<line x1="74.0" y1="208.0" x2="74.0" y2="244.1" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="69.5" y="215.6" width="9" height="19.0" fill="#188a3e"/>
<line x1="89.0" y1="208.0" x2="89.0" y2="239.3" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="84.5" y="215.6" width="9" height="14.2" fill="#c00"/>
<line x1="104.0" y1="198.5" x2="104.0" y2="239.3" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="99.5" y="206.1" width="9" height="23.8" fill="#188a3e"/>
<line x1="119.0" y1="198.5" x2="119.0" y2="229.9" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="114.5" y="206.1" width="9" height="14.3" fill="#c00"/>
<line x1="134.0" y1="189.0" x2="134.0" y2="229.9" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="129.5" y="196.6" width="9" height="23.8" fill="#188a3e"/>
<line x1="149.0" y1="189.0" x2="149.0" y2="220.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="144.5" y="196.6" width="9" height="14.2" fill="#c00"/>
<line x1="164.0" y1="184.3" x2="164.0" y2="220.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="159.5" y="191.8" width="9" height="19.0" fill="#188a3e"/>
<line x1="179.0" y1="170.0" x2="179.0" y2="201.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="174.5" y="177.6" width="9" height="14.2" fill="#188a3e"/>
<line x1="194.0" y1="133.9" x2="194.0" y2="187.1" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="189.5" y="149.1" width="9" height="28.5" fill="#188a3e"/>
<line x1="209.0" y1="95.0" x2="209.0" y2="158.6" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="204.5" y="115.9" width="9" height="33.3" fill="#188a3e"/>
<line x1="224.0" y1="94.0" x2="224.0" y2="125.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="219.5" y="101.6" width="9" height="14.2" fill="#188a3e"/>
<line x1="239.0" y1="94.0" x2="239.0" y2="115.9" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="234.5" y="101.6" width="9" height="4.7" fill="#c00"/>
<line x1="254.0" y1="84.5" x2="254.0" y2="115.9" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="249.5" y="92.1" width="9" height="14.2" fill="#188a3e"/>
<line x1="269.0" y1="84.5" x2="269.0" y2="106.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="264.5" y="92.1" width="9" height="4.7" fill="#c00"/>
<line x1="284.0" y1="75.0" x2="284.0" y2="106.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="279.5" y="82.6" width="9" height="14.2" fill="#188a3e"/>
<line x1="299.0" y1="75.0" x2="299.0" y2="96.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="294.5" y="82.6" width="9" height="4.7" fill="#c00"/>
<line x1="314.0" y1="65.5" x2="314.0" y2="96.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="309.5" y="73.1" width="9" height="14.2" fill="#188a3e"/>
<line x1="329.0" y1="65.5" x2="329.0" y2="87.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="324.5" y="73.1" width="9" height="4.8" fill="#c00"/>
<line x1="344.0" y1="60.8" x2="344.0" y2="87.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="339.5" y="68.4" width="9" height="9.5" fill="#188a3e"/>
<line x1="359.0" y1="56.0" x2="359.0" y2="77.9" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="354.5" y="63.6" width="9" height="4.8" fill="#188a3e"/>
</g>
<g transform="translate(0,314)">
<polyline points="44.0,139.6 59.0,139.6 74.0,139.6 89.0,139.6 104.0,139.6 119.0,139.6 134.0,139.6 149.0,139.6 164.0,139.6 179.0,139.6 179.0,149.1 194.0,149.1 209.0,149.1 224.0,149.1 239.0,149.1 254.0,149.1 269.0,149.1 284.0,149.1 299.0,149.1 314.0,149.1 329.0,149.1 344.0,149.1 359.0,149.1" fill="none" stroke="#b06000" stroke-width="1.8"/>
<text x="470" y="143.1" fill="#b06000" text-anchor="end" style="font-weight:bold">pivot daily (cel MIC)</text>
<text x="24" y="48" fill="#333" style="font-weight:bold">NU — continuare/buy the dip (+1)</text>
<line x1="24" y1="168.1" x2="466" y2="168.1" stroke="#1a5fb4" stroke-width="1.5" stroke-dasharray=""/>
<text x="30" y="162.1" fill="#1a5fb4">pivot weekly (cel MARE) 62.00</text>
<line x1="44.0" y1="191.9" x2="44.0" y2="220.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="39.5" y="199.5" width="9" height="11.4" fill="#c00"/>
<line x1="59.0" y1="189.0" x2="59.0" y2="220.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="54.5" y="196.6" width="9" height="14.3" fill="#188a3e"/>
<line x1="74.0" y1="170.0" x2="74.0" y2="206.1" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="69.5" y="177.6" width="9" height="19.0" fill="#188a3e"/>
<line x1="89.0" y1="170.0" x2="89.0" y2="196.6" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="84.5" y="177.6" width="9" height="9.5" fill="#c00"/>
<line x1="104.0" y1="155.8" x2="104.0" y2="196.6" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="99.5" y="163.4" width="9" height="23.8" fill="#188a3e"/>
<line x1="119.0" y1="127.2" x2="119.0" y2="172.9" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="114.5" y="134.8" width="9" height="28.5" fill="#188a3e"/>
<line x1="134.0" y1="127.2" x2="134.0" y2="153.9" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="129.5" y="134.8" width="9" height="9.5" fill="#c00"/>
<line x1="149.0" y1="117.7" x2="149.0" y2="153.9" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="144.5" y="125.3" width="9" height="19.0" fill="#188a3e"/>
<line x1="164.0" y1="108.3" x2="164.0" y2="134.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="159.5" y="115.9" width="9" height="9.5" fill="#188a3e"/>
<line x1="179.0" y1="108.3" x2="179.0" y2="139.6" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="174.5" y="115.9" width="9" height="14.2" fill="#c00"/>
<line x1="194.0" y1="103.5" x2="194.0" y2="139.6" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="189.5" y="111.1" width="9" height="19.0" fill="#188a3e"/>
<line x1="209.0" y1="103.5" x2="209.0" y2="130.1" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="204.5" y="111.1" width="9" height="9.5" fill="#c00"/>
<line x1="224.0" y1="94.0" x2="224.0" y2="130.1" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="219.5" y="101.6" width="9" height="19.0" fill="#188a3e"/>
<line x1="239.0" y1="94.0" x2="239.0" y2="115.9" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="234.5" y="101.6" width="9" height="4.8" fill="#c00"/>
<line x1="254.0" y1="84.5" x2="254.0" y2="115.9" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="249.5" y="92.1" width="9" height="14.2" fill="#188a3e"/>
<line x1="269.0" y1="84.5" x2="269.0" y2="106.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="264.5" y="92.1" width="9" height="4.7" fill="#c00"/>
<line x1="284.0" y1="75.0" x2="284.0" y2="106.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="279.5" y="82.6" width="9" height="14.2" fill="#188a3e"/>
<line x1="299.0" y1="75.0" x2="299.0" y2="96.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="294.5" y="82.6" width="9" height="4.7" fill="#c00"/>
<line x1="314.0" y1="65.5" x2="314.0" y2="96.8" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="309.5" y="73.1" width="9" height="14.2" fill="#188a3e"/>
<line x1="329.0" y1="65.5" x2="329.0" y2="87.3" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="324.5" y="73.1" width="9" height="4.7" fill="#c00"/>
<line x1="344.0" y1="56.0" x2="344.0" y2="87.3" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="339.5" y="63.6" width="9" height="14.2" fill="#188a3e"/>
<line x1="359.0" y1="56.0" x2="359.0" y2="77.9" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="354.5" y="63.6" width="9" height="4.8" fill="#c00"/>
<text x="239.0" y="234.0" fill="#777" text-anchor="middle" style="font-weight:bold">semnale ulterioare = BUY THE DIP, nu crossing</text></g>
<text x="24" y="576" fill="#555">Pivot weekly 62.00 = linia orizontală (pivotul MARE).</text>
<text x="24" y="593" fill="#555">Pivot daily (cel MIC) se recalculează zilnic și sare pe nivel nou.</text>
<text x="24" y="610" fill="#555">Crossing = pivotul mic traversează 62.00 în sus.</text>
<text x="24" y="627" fill="#555">Fără re-încrucișare, semnalele ulterioare sunt buy the dip,</text>
<text x="24" y="644" fill="#555">nu crossing.</text>
</svg>

After

Width:  |  Height:  |  Size: 9.2 KiB

View File

@@ -0,0 +1,147 @@
"""Diagrama din `gen/strategii/mp2_exemplu.spec.txt` (TIP: PRET).
Exemplul complet din curs: SPY long la bottom-ul de aprilie 2020 — double
bottom + Inverted Head & Shoulders la suport quarterly, trigger MP² long
(pivotul mic închide peste cel mare), prima candelă verde după umărul drept.
Intrare 577.00, SL 554.40 (1R = 22.60), TP1 599.60, ținta 4R era 667.00, iar
mișcarea reală s-a oprit la 646.00 (~3R) într-o lună.
Toate cifrele vin din `summaries/09_QA/01_2026-08-25.md:384-394,485-494`
(liniile citate în spec). Serie continuă de 38 de candele daily
(`from_closes`), panou de volum, SMA-20, niveluri reale și marcaje ancorate.
"""
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
OUT = Path(__file__).resolve().parent / "mp2_exemplu.svg"
# ── cifrele din material (09_QA/01_2026-08-25.md:384-394,485-494) ───────────
ENTRY = 577.00 # candela de intrare (prima verde după umărul drept)
SL = 554.40 # sub umărul drept, chiar peste suportul quarterly
TP1 = 599.60 # 577.00 + 22.60 (1R)
TARGET_4R = 667.00 # ținta declarată; 4R exact = 667.40
REAL = 646.00 # mișcarea reală (~3R, +12% efectiv)
QUARTERLY = 554.00 # suport quarterly
SHOULDER_L = 566.0 # umăr stâng
HEAD = 555.0 # head (cel mai jos)
SHOULDER_R = 568.0 # umăr drept
NECKLINE = 578.00
# ── structura seriei (spec): ~14 coborâre → ~10 bază → intrare → ~12 urcare ─
closes = [
# 0-13: ~14 candele de coborâre de la ~600 spre umărul stâng 566
600.0, 597.0, 594.0, 596.5, 591.0, 587.5, 590.0,
584.0, 579.5, 581.5, 576.0, 571.0, 567.0,
566.0, # 13 umăr stâng
# 14-24: baza — double bottom + Inverted Head & Shoulders
570.5, 578.0, 572.0, 567.0, 560.0,
555.0, # 19 head (cel mai jos)
561.5, 568.0, 578.0, 573.0,
568.0, # 24 umăr drept
# 25: candela de intrare, prima verde după umărul drept
577.00,
# 26-37: ~12 candele de urcare spre 646.00 (marcajul „real")
581.0, 585.5, 589.0, 584.0, 590.5, 596.0, 602.0,
609.0, 616.0, 624.0, 635.0, 646.0,
]
assert len(closes) == 38, len(closes)
ENTRY_I = 25
HEAD_I = 19
SHL_I = 13
SHR_I = 24
wicks = [(0.4, 0.4)] * len(closes)
wicks[SHL_I] = (0.4, 0.5) # low umăr stâng 565.50
wicks[HEAD_I] = (0.4, 0.6) # low head = 555.0 - 0.6 = 554.40 (suportul atins)
wicks[SHR_I] = (0.4, 0.5)
wicks[15] = (0.4, 0.4) # vârf 578.0 = neckline
wicks[22] = (0.4, 0.4) # vârf 578.0 = neckline
wicks[ENTRY_I] = (0.7, 0.4) # intrare verde, wick mic
wicks[37] = (0.0, 0.3) # high = 646.00 exact (mișcarea reală se oprește aici)
vols = [
# coborâre
48, 46, 50, 44, 52, 49, 45, 53, 55, 47, 50, 54, 58, 62,
# bază (head-ul are volum mare — capitulare)
60, 72, 65, 58, 66, 88, 70, 64, 74, 60, 56,
# intrare
92,
# urcare
78, 72, 68, 60, 66, 74, 82, 88, 80, 84, 90, 95,
]
assert len(vols) == len(closes)
cds = from_closes(closes, wicks=wicks, vols=vols, start=602.0)
# ── assert-urile din specificație ───────────────────────────────────────────
entry = cds[ENTRY_I].c
assert abs(entry - 577.00) < 0.05, entry
assert abs(SL - 554.40) < 1e-9, SL
assert SL > QUARTERLY, (SL, QUARTERLY) # peste suportul quarterly
assert abs((ENTRY - SL) - 22.60) < 0.05, ENTRY - SL # 1R
assert abs(TP1 - (ENTRY + 22.60)) < 0.05, TP1 # TP1 = intrare + 1R
target_4r = ENTRY + 4 * (ENTRY - SL)
assert abs(target_4r - 667.40) < 0.05, target_4r # 4R exact
assert abs(target_4r - TARGET_4R) < 0.5, target_4r # ≈ 667.00, rotunjit în curs
assert abs((REAL - ENTRY) / (ENTRY - SL) - 3.05) < 0.02, (REAL - ENTRY) / (ENTRY - SL)
assert abs((REAL - ENTRY) / (ENTRY - SL) - 4.0) > 0.5, "mișcarea reală NU e 4R"
# structura care susține afirmațiile din text
assert abs(cds[SHL_I].c - SHOULDER_L) < 0.05, cds[SHL_I].c
assert abs(cds[HEAD_I].c - HEAD) < 0.05, cds[HEAD_I].c
assert abs(cds[SHR_I].c - SHOULDER_R) < 0.05, cds[SHR_I].c
assert HEAD < SHOULDER_L and HEAD < SHOULDER_R, (HEAD, SHOULDER_L, SHOULDER_R)
assert cds[SHL_I].c == min(c.c for c in cds[SHL_I - 1:SHL_I + 2]), "umărul stâng nu e minim local"
assert cds[HEAD_I].c == min(c.c for c in cds[HEAD_I - 1:HEAD_I + 2]), "head-ul nu e minim local"
assert cds[SHR_I].c == min(c.c for c in cds[SHR_I - 1:SHR_I + 2]), "umărul drept nu e minim local"
min_pre = min(c.l for c in cds[:ENTRY_I])
assert abs(min_pre - 554.0) < 1.0, min_pre # suportul quarterly atins
assert abs(min_pre - SL) < 0.05, (min_pre, SL) # SL chiar la minimul head-ului
assert cds[ENTRY_I].c > cds[ENTRY_I].o, "candela de intrare trebuie verde"
assert cds[ENTRY_I - 1].c < cds[ENTRY_I - 1].o, "umărul drept e roșu"
assert abs(cds[37].c - REAL) < 0.05, cds[37].c
assert all(c.vol is not None for c in cds), "lipsește panoul de volum"
assert len(cds) == 38
# ── desenul ─────────────────────────────────────────────────────────────────
levels = [
Level(TARGET_4R, "ținta 4R era 667.00 (+16%)", "#555"),
Level(REAL, "646.00 — +12% efectiv (~3R)", "#188a3e", label_at="left"),
Level(TP1, "TP1 599.60", "#1a5fb4"),
Level(NECKLINE, "neckline 578.00", "#1a5fb4"),
Level(SL, "SL 554.40", "#c00"),
Level(QUARTERLY, "suport quarterly 554.00", "#777", label_at="left"),
]
marks = [
Mark(SHL_I, "umăr stâng 566", "#1a1a1a", "below"),
Mark(HEAD_I, "head 555", "#1a1a1a", "below"),
Mark(SHR_I, "umăr drept 568", "#1a1a1a", "below"),
Mark(ENTRY_I, "intrare 577.00", "#188a3e", "above", dy=-10),
]
ch = Chart(
candles=cds,
levels=levels,
marks=marks,
mas=[MA(20, "#b06000", dash="4 3", text="SMA-20")],
scale=3.8, width=720, gap=15, body_w=9, left=44, vol_h=70,
vol_label="volum", title="SPY — daily",
caption=("Intrare 577.00 = prima verde după umărul drept (MP² long). "
"1R = 22.60; 646.00 ~3R, într-o lună."),
)
svg = ch.render()
assert svg.count("\n\n") == 0, "linie goală în SVG — rupe markdown-ul"
OUT.write_text(svg)
print(f"serie {len(cds)} candele | intrare {entry:.2f} @ {ENTRY_I} | "
f"SL {SL:.2f} | 1R {ENTRY - SL:.2f} | TP1 {TP1:.2f} | "
f"4R {target_4r:.2f} (ținta declarată {TARGET_4R:.2f}) | "
f"real {REAL:.2f} = {(REAL - ENTRY) / (ENTRY - SL):.2f}R | "
f"min pre-intrare {min_pre:.2f}")
print(f"scris {OUT}")

View File

@@ -0,0 +1,138 @@
<svg viewBox="0 0 720 623" xmlns="http://www.w3.org/2000/svg" style="width:100%;height:auto;font:12px sans-serif">
<text x="24" y="48" fill="#333" style="font-weight:bold">SPY — daily</text>
<line x1="24" y1="56.0" x2="706" y2="56.0" stroke="#555" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="692" y="50.0" fill="#555" text-anchor="end">ținta 4R era 667.00 (+16%)</text>
<line x1="24" y1="135.8" x2="706" y2="135.8" stroke="#188a3e" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="30" y="129.8" fill="#188a3e">646.00 — +12% efectiv (~3R)</text>
<line x1="24" y1="312.1" x2="706" y2="312.1" stroke="#1a5fb4" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="692" y="306.1" fill="#1a5fb4" text-anchor="end">TP1 599.60</text>
<line x1="24" y1="394.2" x2="706" y2="394.2" stroke="#1a5fb4" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="692" y="388.2" fill="#1a5fb4" text-anchor="end">neckline 578.00</text>
<line x1="24" y1="483.9" x2="706" y2="483.9" stroke="#c00" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="692" y="477.9" fill="#c00" text-anchor="end">SL 554.40</text>
<line x1="24" y1="485.4" x2="706" y2="485.4" stroke="#777" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="30" y="479.4" fill="#777">suport quarterly 554.00</text>
<polyline points="329.0,389.7 344.0,397.1 359.0,402.6 374.0,405.6 389.0,410.1 404.0,414.4 419.0,416.4 434.0,418.1 449.0,417.9 464.0,416.1 479.0,415.6 494.0,412.8 509.0,408.1 524.0,401.4 539.0,393.2 554.0,384.6 569.0,375.9 584.0,363.9 599.0,348.9" fill="none" stroke="#b06000" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="605" y="343" fill="#b06000">SMA-20</text>
<line x1="44.0" y1="301.5" x2="44.0" y2="312.1" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="39.5" y="303.0" width="9" height="7.6" fill="#c00"/>
<line x1="59.0" y1="309.1" x2="59.0" y2="323.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="54.5" y="310.6" width="9" height="11.4" fill="#c00"/>
<line x1="74.0" y1="320.5" x2="74.0" y2="334.9" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="69.5" y="322.0" width="9" height="11.4" fill="#c00"/>
<line x1="89.0" y1="322.4" x2="89.0" y2="334.9" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="84.5" y="323.9" width="9" height="9.5" fill="#188a3e"/>
<line x1="104.0" y1="322.4" x2="104.0" y2="346.3" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="99.5" y="323.9" width="9" height="20.9" fill="#c00"/>
<line x1="119.0" y1="343.3" x2="119.0" y2="359.6" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="114.5" y="344.8" width="9" height="13.3" fill="#c00"/>
<line x1="134.0" y1="347.1" x2="134.0" y2="359.6" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="129.5" y="348.6" width="9" height="9.5" fill="#188a3e"/>
<line x1="149.0" y1="347.1" x2="149.0" y2="372.9" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="144.5" y="348.6" width="9" height="22.8" fill="#c00"/>
<line x1="164.0" y1="369.9" x2="164.0" y2="390.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="159.5" y="371.4" width="9" height="17.1" fill="#c00"/>
<line x1="179.0" y1="379.4" x2="179.0" y2="390.0" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="174.5" y="380.9" width="9" height="7.6" fill="#188a3e"/>
<line x1="194.0" y1="379.4" x2="194.0" y2="403.3" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="189.5" y="380.9" width="9" height="20.9" fill="#c00"/>
<line x1="209.0" y1="400.3" x2="209.0" y2="422.3" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="204.5" y="401.8" width="9" height="19.0" fill="#c00"/>
<line x1="224.0" y1="419.3" x2="224.0" y2="437.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="219.5" y="420.8" width="9" height="15.2" fill="#c00"/>
<line x1="239.0" y1="434.5" x2="239.0" y2="441.7" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="234.5" y="436.0" width="9" height="3.8" fill="#c00"/>
<line x1="254.0" y1="421.2" x2="254.0" y2="441.3" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="249.5" y="422.7" width="9" height="17.1" fill="#188a3e"/>
<line x1="269.0" y1="392.7" x2="269.0" y2="424.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="264.5" y="394.2" width="9" height="28.5" fill="#188a3e"/>
<line x1="284.0" y1="392.7" x2="284.0" y2="418.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="279.5" y="394.2" width="9" height="22.8" fill="#c00"/>
<line x1="299.0" y1="415.5" x2="299.0" y2="437.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="294.5" y="417.0" width="9" height="19.0" fill="#c00"/>
<line x1="314.0" y1="434.5" x2="314.0" y2="464.1" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="309.5" y="436.0" width="9" height="26.6" fill="#c00"/>
<line x1="329.0" y1="461.1" x2="329.0" y2="483.9" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="324.5" y="462.6" width="9" height="19.0" fill="#c00"/>
<line x1="344.0" y1="455.4" x2="344.0" y2="483.1" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="339.5" y="456.9" width="9" height="24.7" fill="#188a3e"/>
<line x1="359.0" y1="430.7" x2="359.0" y2="458.4" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="354.5" y="432.2" width="9" height="24.7" fill="#188a3e"/>
<line x1="374.0" y1="392.7" x2="374.0" y2="433.7" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="369.5" y="394.2" width="9" height="38.0" fill="#188a3e"/>
<line x1="389.0" y1="392.7" x2="389.0" y2="414.7" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="384.5" y="394.2" width="9" height="19.0" fill="#c00"/>
<line x1="404.0" y1="411.7" x2="404.0" y2="434.1" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="399.5" y="413.2" width="9" height="19.0" fill="#c00"/>
<line x1="419.0" y1="395.3" x2="419.0" y2="433.7" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="414.5" y="398.0" width="9" height="34.2" fill="#188a3e"/>
<line x1="434.0" y1="381.3" x2="434.0" y2="399.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="429.5" y="382.8" width="9" height="15.2" fill="#188a3e"/>
<line x1="449.0" y1="364.2" x2="449.0" y2="384.3" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="444.5" y="365.7" width="9" height="17.1" fill="#188a3e"/>
<line x1="464.0" y1="350.9" x2="464.0" y2="367.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="459.5" y="352.4" width="9" height="13.3" fill="#188a3e"/>
<line x1="479.0" y1="350.9" x2="479.0" y2="372.9" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="474.5" y="352.4" width="9" height="19.0" fill="#c00"/>
<line x1="494.0" y1="345.2" x2="494.0" y2="372.9" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="489.5" y="346.7" width="9" height="24.7" fill="#188a3e"/>
<line x1="509.0" y1="324.3" x2="509.0" y2="348.2" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="504.5" y="325.8" width="9" height="20.9" fill="#188a3e"/>
<line x1="524.0" y1="301.5" x2="524.0" y2="327.3" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="519.5" y="303.0" width="9" height="22.8" fill="#188a3e"/>
<line x1="539.0" y1="274.9" x2="539.0" y2="304.5" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="534.5" y="276.4" width="9" height="26.6" fill="#188a3e"/>
<line x1="554.0" y1="248.3" x2="554.0" y2="277.9" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="549.5" y="249.8" width="9" height="26.6" fill="#188a3e"/>
<line x1="569.0" y1="217.9" x2="569.0" y2="251.3" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="564.5" y="219.4" width="9" height="30.4" fill="#188a3e"/>
<line x1="584.0" y1="176.1" x2="584.0" y2="220.9" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="579.5" y="177.6" width="9" height="41.8" fill="#188a3e"/>
<line x1="599.0" y1="135.8" x2="599.0" y2="178.7" stroke="#1a1a1a" stroke-width="1.2"/>
<rect x="594.5" y="135.8" width="9" height="41.8" fill="#188a3e"/>
<line x1="24" y1="581.4" x2="706" y2="581.4" stroke="#888"/>
<rect x="39.5" y="546.0" width="9" height="35.4" fill="#c00" opacity="0.55"/>
<rect x="54.5" y="547.5" width="9" height="33.9" fill="#c00" opacity="0.55"/>
<rect x="69.5" y="544.6" width="9" height="36.8" fill="#c00" opacity="0.55"/>
<rect x="84.5" y="549.0" width="9" height="32.4" fill="#188a3e" opacity="0.55"/>
<rect x="99.5" y="543.1" width="9" height="38.3" fill="#c00" opacity="0.55"/>
<rect x="114.5" y="545.3" width="9" height="36.1" fill="#c00" opacity="0.55"/>
<rect x="129.5" y="548.2" width="9" height="33.2" fill="#188a3e" opacity="0.55"/>
<rect x="144.5" y="542.3" width="9" height="39.1" fill="#c00" opacity="0.55"/>
<rect x="159.5" y="540.9" width="9" height="40.5" fill="#c00" opacity="0.55"/>
<rect x="174.5" y="546.8" width="9" height="34.6" fill="#188a3e" opacity="0.55"/>
<rect x="189.5" y="544.6" width="9" height="36.8" fill="#c00" opacity="0.55"/>
<rect x="204.5" y="541.6" width="9" height="39.8" fill="#c00" opacity="0.55"/>
<rect x="219.5" y="538.7" width="9" height="42.7" fill="#c00" opacity="0.55"/>
<rect x="234.5" y="535.7" width="9" height="45.7" fill="#c00" opacity="0.55"/>
<rect x="249.5" y="537.2" width="9" height="44.2" fill="#188a3e" opacity="0.55"/>
<rect x="264.5" y="528.3" width="9" height="53.1" fill="#188a3e" opacity="0.55"/>
<rect x="279.5" y="533.5" width="9" height="47.9" fill="#c00" opacity="0.55"/>
<rect x="294.5" y="538.7" width="9" height="42.7" fill="#c00" opacity="0.55"/>
<rect x="309.5" y="532.8" width="9" height="48.6" fill="#c00" opacity="0.55"/>
<rect x="324.5" y="516.6" width="9" height="64.8" fill="#c00" opacity="0.55"/>
<rect x="339.5" y="529.8" width="9" height="51.6" fill="#188a3e" opacity="0.55"/>
<rect x="354.5" y="534.2" width="9" height="47.2" fill="#188a3e" opacity="0.55"/>
<rect x="369.5" y="526.9" width="9" height="54.5" fill="#188a3e" opacity="0.55"/>
<rect x="384.5" y="537.2" width="9" height="44.2" fill="#c00" opacity="0.55"/>
<rect x="399.5" y="540.1" width="9" height="41.3" fill="#c00" opacity="0.55"/>
<rect x="414.5" y="513.6" width="9" height="67.8" fill="#188a3e" opacity="0.55"/>
<rect x="429.5" y="523.9" width="9" height="57.5" fill="#188a3e" opacity="0.55"/>
<rect x="444.5" y="528.3" width="9" height="53.1" fill="#188a3e" opacity="0.55"/>
<rect x="459.5" y="531.3" width="9" height="50.1" fill="#188a3e" opacity="0.55"/>
<rect x="474.5" y="537.2" width="9" height="44.2" fill="#c00" opacity="0.55"/>
<rect x="489.5" y="532.8" width="9" height="48.6" fill="#188a3e" opacity="0.55"/>
<rect x="504.5" y="526.9" width="9" height="54.5" fill="#188a3e" opacity="0.55"/>
<rect x="519.5" y="521.0" width="9" height="60.4" fill="#188a3e" opacity="0.55"/>
<rect x="534.5" y="516.6" width="9" height="64.8" fill="#188a3e" opacity="0.55"/>
<rect x="549.5" y="522.5" width="9" height="58.9" fill="#188a3e" opacity="0.55"/>
<rect x="564.5" y="519.5" width="9" height="61.9" fill="#188a3e" opacity="0.55"/>
<rect x="579.5" y="515.1" width="9" height="66.3" fill="#188a3e" opacity="0.55"/>
<rect x="594.5" y="511.4" width="9" height="70.0" fill="#188a3e" opacity="0.55"/>
<text x="24" y="505.4" fill="#555">volum</text>
<text x="239.0" y="461.7" fill="#1a1a1a" text-anchor="middle" style="font-weight:bold">umăr stâng 566</text>
<text x="329.0" y="499.4" fill="#1a1a1a" text-anchor="middle" style="font-weight:bold">head 555</text>
<text x="404.0" y="454.1" fill="#1a1a1a" text-anchor="middle" style="font-weight:bold">umăr drept 568</text>
<text x="419.0" y="375.3" fill="#188a3e" text-anchor="middle" style="font-weight:bold">intrare 577.00</text>
<text x="24" y="611" fill="#555">Intrare 577.00 = prima verde după umărul drept (MP² long). 1R = 22.60; 646.00 ~3R, într-o lună.</text>
</svg>

After

Width:  |  Height:  |  Size: 11 KiB