selectia: diagramele refacute dupa sablon (textul urmeaza)
Agentul a atins pragul de context dupa diagrame; capitolul 04_selectia e inca in formatul vechi si se reia separat. Ce s-a schimbat: ansamblul nu mai arata ULTA mergand la TP2 — ULTA pica chiar filtrul de selectie (RVOL 0.61 si 5.01 % de suport), deci nu putea fi exemplul-vitrina; acum arata doar ce citeste selectia, cu granita "aici se termina selectia" marcata. Funnel-ul trece de la 8 trepte la 6, aliniat cu pasii nivelului 2, si fiecare treapta spune de ce. Cadranele primesc mini-histograme RS, ca sa se recunoasca dintr-un desen, nu dintr-o propozitie. Trimiterile fisier:linie sunt scoase de pe desene. Noi: univers (pragurile de pret si volum), sectoare (dominantul din cele 11), proximitate (banda de 3 % peste suportul monthly, cu observatia ca nu e pragul de 1-2 % din Buy Doji — masoara altceva). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ThN7ixZrowca8aTGrK1MUJ
This commit is contained in:
@@ -1,10 +1,16 @@
|
||||
"""ULTA — daily (pivoți monthly): diagrama de ansamblu a selecției (TIP: PRET).
|
||||
|
||||
Citește `gen/strategii/selectia_ansamblu.spec.txt`. Un simbol care vine dintr-un
|
||||
downtrend weekly, atinge suportul monthly 355.48 și face un buy doji la 373.30
|
||||
cu RVOL 0.61; apoi confirmarea (intrare 374.80) și drumul spre pivot/TP1 383.74,
|
||||
respectiv TP2 402.00. Grafic daily, 32 de candele, serie continuă, cu panou de
|
||||
volum și SMA-20 — construit prin `svg_candles` (from_closes), la scară.
|
||||
Rolul diagramei în capitol: arată **unde se termină selecția**. Filtrul citește
|
||||
contextul (downtrend, suportul monthly 355.48, pivotul monthly 383.74, buy doji
|
||||
la 373.30, RVOL 0.61) și se oprește la „simbolul intră pe listă". Intrarea,
|
||||
stopul și ținta nu apar aici, pentru că nu aparțin selecției — sunt ale
|
||||
capitolului 1 (Buy Doji). Partea de după linia verticală e desenată gri tocmai
|
||||
ca să se vadă că nu mai e treaba filtrului.
|
||||
|
||||
Cifrele cursului: close 373.30, suport monthly 355.48, pivot monthly 383.74,
|
||||
RVOL 0.61 (`04_MODUL_3/01_filtre_scaner_bull_bear_reversal_pp.md`). Procentele
|
||||
de 5.01 % și 2.72 % se recalculează din ele, cu assert. Structura barelor e
|
||||
construită, la scară, prin `svg_candles` (from_closes).
|
||||
|
||||
Rulează:
|
||||
python3 gen/strategii/selectia_ansamblu.py
|
||||
@@ -15,18 +21,20 @@ 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
|
||||
from svg_candles import Chart, Level, Arrow, Zone, MA, from_closes # noqa: E402
|
||||
|
||||
OUT = Path(__file__).resolve().parent / "selectia_ansamblu.svg"
|
||||
|
||||
GRAY, RED, GREEN, BLUE, ORANGE, TEAL = "#777", "#c00", "#188a3e", "#1a5fb4", "#b06000", "#00a0aa"
|
||||
|
||||
SUPORT, PIVOT, DOJI_CLOSE = 355.48, 383.74, 373.30
|
||||
DOJI = 20 # indexul candelei de buy doji
|
||||
|
||||
# ---------------------------------------------------------------- seria (32 bare)
|
||||
# bare 0-11 : downtrend vizibil de la ~395 la ~362 (~0.8%/bara)
|
||||
# bare 12-19 : baza/churn 356-373, minimul atinge suportul 355.48 pe bara 15
|
||||
# bare 0-11 : downtrend vizibil de la ~395 la ~362 (~0.8 %/bară)
|
||||
# bare 12-19 : baza, minimul atinge suportul monthly 355.48 pe bara 15
|
||||
# bara 20 : buy doji, close 373.30 (open = close[19] = 373.00, corp 0.30)
|
||||
# bara 21 : confirmare, inchide peste intrarea 374.80
|
||||
# bare 22-31 : urcare spre pivot/TP1 383.74 (bara 26) si apoi spre TP2 402.00
|
||||
# bare 21-31 : ce urmează — nu mai e treaba selecției, desenat gri
|
||||
CLOSES = [
|
||||
395.00, 391.80, 388.60, 385.20, 382.00, 378.90, 375.70, 372.80, # 0-7
|
||||
369.90, 367.00, 364.20, 361.80, # 8-11
|
||||
@@ -38,92 +46,106 @@ CLOSES = [
|
||||
assert len(CLOSES) == 32, len(CLOSES)
|
||||
|
||||
WICKS = [(0.35, 0.40)] * 32
|
||||
WICKS[0] = (0.40, 0.50) # prima bara, deschide dintr-un varf al downtrend-ului
|
||||
WICKS[0] = (0.40, 0.50) # prima bară, deschide dintr-un vârf al downtrend-ului
|
||||
WICKS[15] = (0.30, 0.72) # low exact 355.48 = suportul monthly
|
||||
WICKS[16] = (0.40, 0.30) # rebound, nu sparge suportul
|
||||
WICKS[20] = (0.80, 0.30) # doji: high 374.10, low 372.70
|
||||
WICKS[31] = (0.55, 0.40) # high 402.05, atinge TP2 402.00
|
||||
WICKS[DOJI] = (1.45, 1.45) # doji: corp 0.30 din 3.20 înălțime = 9 %
|
||||
assert len(WICKS) == 32, len(WICKS)
|
||||
|
||||
# barele 25-31 in crestere; bara 20 (doji) sub media seriei (RVOL 0.61)
|
||||
# bara 20 (doji) sub media seriei: exact ce înseamnă RVOL 0.61
|
||||
VOLS = [
|
||||
42, 44, 41, 46, 43, 45, 40, 44, 47, 43, # 0-9
|
||||
40, 42, 38, 41, 52, # 10-14
|
||||
56, 50, 45, 42, 39, # 15-19
|
||||
30, # 20 doji, sub medie
|
||||
46, # 21 confirmare
|
||||
46, # 21
|
||||
48, 50, 52, 54, 58, 62, 66, 70, 76, 82, # 22-31
|
||||
]
|
||||
assert len(VOLS) == 32, len(VOLS)
|
||||
media_vol = sum(VOLS) / len(VOLS)
|
||||
assert VOLS[20] < media_vol, "bara 20 (doji) trebuie sub media seriei"
|
||||
assert all(a < b for a, b in zip(VOLS[25:32], VOLS[26:32])), "barele 25-31 nu cresc"
|
||||
# RVOL afisat (0.61) ~ raportul barei de doji fata de media seriei
|
||||
assert abs(VOLS[20] / media_vol - 0.61) < 0.02, VOLS[20] / media_vol
|
||||
assert abs(VOLS[DOJI] / media_vol - 0.61) < 0.02, VOLS[DOJI] / media_vol
|
||||
|
||||
cds = from_closes(CLOSES, wicks=WICKS, vols=VOLS, start=396.50)
|
||||
|
||||
# ---------------------------------------------------------------- assert-urile din spec
|
||||
assert abs(cds[20].c - 373.30) < 1e-9, "doji-ul trebuie sa inchida la 373.30"
|
||||
assert abs(max(cds[20].o, cds[20].c) - min(cds[20].o, cds[20].c)) <= 0.40, "bara 20 nu e doji"
|
||||
assert abs(min(c.l for c in cds[12:21]) - 355.48) < 0.05, "suportul 355.48 nu e atins"
|
||||
assert 352.00 < 355.48 < 373.30 < 374.80 < 383.74 < 402.00, "ordinea nivelurilor"
|
||||
risc = 374.80 - 352.00 # 22.80
|
||||
assert abs((383.74 - 374.80) / risc - 0.39) < 0.02, "TP1 nu e ~0.39R"
|
||||
assert abs((402.00 - 374.80) / risc - 1.19) < 0.02, "TP2 nu e ~1.19R"
|
||||
assert cds[-1].c >= 383.74, "TP1 nu e atins in serie"
|
||||
assert min(c.l for c in cds) >= 351.5, "SL-ul de 352.00 nu poate fi sub minimul seriei"
|
||||
# barele de după doji sunt gri: nu mai sunt în sarcina filtrului
|
||||
for cd in cds[DOJI + 1:]:
|
||||
cd.color = "#bbb"
|
||||
|
||||
# ---------------------------------------------------------------- assert-uri de conținut
|
||||
assert abs(cds[DOJI].c - DOJI_CLOSE) < 1e-9, "doji-ul trebuie să închidă la 373.30"
|
||||
corp = abs(cds[DOJI].o - cds[DOJI].c)
|
||||
inaltime = cds[DOJI].h - cds[DOJI].l
|
||||
assert corp / inaltime <= 0.10, f"bara {DOJI} nu e doji: corp {corp / inaltime:.0%} din înălțime"
|
||||
assert abs(min(c.l for c in cds[12:DOJI + 1]) - SUPORT) < 0.05, "suportul 355.48 nu e atins"
|
||||
# de la intrarea în bază și până la doji, prețul stă sub pivot: de asta pivotul
|
||||
# e rezistența cea mai apropiată deasupra doji-ului
|
||||
assert max(c.h for c in cds[12:DOJI + 1]) < PIVOT, "pivotul nu are voie atins în bază"
|
||||
for a, b in zip(cds, cds[1:]):
|
||||
assert abs(a.c - b.o) < 1e-9, "serie cu gol artificial intre bare"
|
||||
assert abs(a.c - b.o) < 1e-9, "serie cu gol artificial între bare"
|
||||
|
||||
# structura ceruta explicit: doji mic cu fitilele date, confirmare peste intrare
|
||||
assert abs(cds[20].h - 374.10) < 0.05 and abs(cds[20].l - 372.70) < 0.05, (cds[20].h, cds[20].l)
|
||||
assert cds[21].c > 374.80, "bara 21 nu confirma intrarea"
|
||||
assert max(cds[i].h for i in range(22, 27)) >= 383.74, "TP1 nu e atins in jurul barei 26"
|
||||
assert cds[31].h >= 402.00, "TP2 nu e atins spre final"
|
||||
# procentele din etichete se calculează, nu se scriu de mână
|
||||
pct_suport = (DOJI_CLOSE - SUPORT) / SUPORT * 100 # 5.01 %
|
||||
pct_pivot = (PIVOT - DOJI_CLOSE) / PIVOT * 100 # 2.72 %
|
||||
assert abs(pct_suport - 5.01) < 0.01, pct_suport
|
||||
assert abs(pct_pivot - 2.72) < 0.01, pct_pivot
|
||||
rvol = VOLS[DOJI] / media_vol
|
||||
|
||||
# ---------------------------------------------------------------- niveluri, etichete
|
||||
levels = [
|
||||
Level(352.00, "SL 352.00", RED, label_at="left"),
|
||||
Level(355.48, "suport monthly 355.48", BLUE, label_at="left"),
|
||||
Level(374.80, "intrare 374.80", GREEN),
|
||||
Level(383.74, "pivot / TP1 383.74", BLUE),
|
||||
Level(402.00, "TP2 402.00", BLUE),
|
||||
Level(SUPORT, f"suport monthly {SUPORT:.2f}", BLUE, label_at="left"),
|
||||
Level(PIVOT, f"pivot monthly {PIVOT:.2f}", BLUE),
|
||||
]
|
||||
|
||||
marks = [
|
||||
# „sub bara 0" ar insemna peste candelele care coboara (staircase); dy-ul
|
||||
# o ridica deasupra varfului de start, unde textul se citeste curat
|
||||
Mark(0, "downtrend weekly", GRAY, "below", dy=-62),
|
||||
Mark(20, "buy doji close 373.30 (RVOL 0.61)", TEAL, "below"),
|
||||
Mark(21, "intrare 374.80", GREEN, "above"),
|
||||
Mark(26, "TP1 383.74 = pivot", BLUE, "above", dy=-8),
|
||||
Mark(31, "TP2 402.00", BLUE, "above", dy=-22),
|
||||
zones = [
|
||||
Zone(0, 11, "1. downtrend — de asta apare în scaner", RED),
|
||||
Zone(12, DOJI, "", BLUE, p_bot=SUPORT - 0.6),
|
||||
]
|
||||
|
||||
caption = ("Ale cursului: close 373.30, suport 355.48, pivot 383.74, RVOL 0.61. "
|
||||
"Intrarea 374.80, SL 352.00 si TP2 402.00 sunt estimari din 04_MODUL_3/01; "
|
||||
"structura barelor dintre niveluri e construita, la scara.")
|
||||
arrows = [
|
||||
Arrow(15, f"2. suportul monthly {SUPORT:.2f} a mai întors prețul o dată:\n"
|
||||
f"acolo stau ordine de cumpărare, deci coborârea are unde să se oprească",
|
||||
where="below", price=SUPORT, color=BLUE, dx=-30, dy=34),
|
||||
Arrow(DOJI, f"3. buy doji, close {DOJI_CLOSE:.2f} — aici se oprește selecția\n"
|
||||
f"{pct_suport:.2f} % peste suport, {pct_pivot:.2f} % sub pivot: trece proximitatea față de pivot\n"
|
||||
f"volumul zilei = {rvol:.2f} din medie (RVOL {rvol:.2f}), sub pragul de 1.00: minus, nu veto",
|
||||
where="above", color=TEAL, dx=-250, dy=-66),
|
||||
Arrow(27, "trigger, stop și țintă sunt\nale capitolului Buy Doji",
|
||||
where="right", color=GRAY, dx=16, dy=-40),
|
||||
]
|
||||
|
||||
caption = ("Ale cursului: close 373.30, suport monthly 355.48, pivot monthly 383.74, RVOL 0.61. "
|
||||
"Procentele sunt calculate din ele. Structura barelor e construită, la scară. "
|
||||
"Intrarea, stopul și ținta nu apar pe desen: nu aparțin selecției.")
|
||||
|
||||
ch = Chart(
|
||||
candles=cds, levels=levels, marks=marks,
|
||||
candles=cds, levels=levels, zones=zones, arrows=arrows,
|
||||
mas=[MA(20, ORANGE, dash="4 3", text="SMA-20")],
|
||||
scale=8.5, gap=15, body_w=9, left=44, width=640, vol_h=70,
|
||||
vol_label="volum",
|
||||
title="ULTA — daily (pivoți monthly)",
|
||||
title="ULTA — daily: ce citește selecția și unde se oprește",
|
||||
caption=caption,
|
||||
)
|
||||
svg = ch.render()
|
||||
|
||||
# ---------------------------------------------------------------- verificarea de scara
|
||||
ys = {p: ch._y(p) for p in (352.00, 355.48, 374.80, 383.74, 402.00)}
|
||||
px_risc = ys[352.00] - ys[374.80] # y creste in jos, deci SL-ul are y mai mare
|
||||
assert abs((ys[374.80] - ys[383.74]) / px_risc - (383.74 - 374.80) / risc) < 1e-9, "TP1 la scara gresita"
|
||||
assert abs((ys[374.80] - ys[402.00]) / px_risc - (402.00 - 374.80) / risc) < 1e-9, "TP2 la scara gresita"
|
||||
# ---------------------------------------------------------------- linia verticală de predare
|
||||
# offsetul intern al randării, citit din prima linie de Level desenată
|
||||
m_off = re.search(r'<line x1="24" y1="([\d.]+)"', svg)
|
||||
assert m_off, "nu găsesc linia primului Level, din care citesc offsetul pe y"
|
||||
OFF = float(m_off.group(1)) - ch._y(levels[0].price)
|
||||
# render() mută și `left`; offsetul pe x se citește din dreptunghiul primei zone
|
||||
m_dx = re.search(r'<rect x="([\d.]+)"[^>]*fill="' + RED + r'"', svg)
|
||||
assert m_dx, "nu găsesc dreptunghiul primei zone, din care citesc offsetul pe x"
|
||||
DX = float(m_dx.group(1)) - (ch._cx(0) - ch.body_w / 2 - 5)
|
||||
x_sep = ch._cx(DOJI) + DX + ch.gap / 2
|
||||
y_top = ch._y(max(c.h for c in cds)) + OFF + 4
|
||||
y_bot = ch._y(min(c.l for c in cds)) + OFF + 16
|
||||
sep = (f'<line x1="{x_sep:.1f}" y1="{y_top:.1f}" x2="{x_sep:.1f}" y2="{y_bot:.1f}" '
|
||||
f'stroke="#444" stroke-width="1.5" stroke-dasharray="6 4"/>'
|
||||
f'<text x="{x_sep - 6:.1f}" y="{y_bot + 14:.1f}" fill="#444" text-anchor="end" '
|
||||
f'style="font-weight:bold">aici se termină selecția</text>'
|
||||
f'<text x="{x_sep + 6:.1f}" y="{y_bot + 14:.1f}" fill="#999">aici începe capitolul 1</text>')
|
||||
svg = svg.replace("</svg>", sep + "</svg>")
|
||||
|
||||
# ---------------------------------------------------------------- finisaj text
|
||||
# caption-ul pe un singur rand depaseste latimea panzei (se taie la viewBox):
|
||||
# il rupem in linii si crestem inaltimea desenului.
|
||||
# ---------------------------------------------------------------- caption pe mai multe rânduri
|
||||
def _wrap(text: str, max_chars: int) -> list[str]:
|
||||
lines, cur = [], ""
|
||||
for w in text.split():
|
||||
@@ -138,25 +160,27 @@ def _wrap(text: str, max_chars: int) -> list[str]:
|
||||
|
||||
|
||||
cap_lines = _wrap(caption, 92)
|
||||
m = re.search(r'<text x="24" y="(\d+)" fill="#555">' + re.escape(caption) + r"</text>\s*</svg>", svg)
|
||||
assert m, "caption-ul nu a fost gasit in SVG"
|
||||
m = re.search(r'<text x="24" y="(\d+)" fill="#555">' + re.escape(caption) + r"</text>", svg)
|
||||
assert m, "caption-ul nu a fost găsit în SVG"
|
||||
y0 = int(m.group(1))
|
||||
wrapped = "\n ".join(f'<text x="24" y="{y0 + 17 * i}" fill="#555">{ln}</text>'
|
||||
for i, ln in enumerate(cap_lines))
|
||||
svg = svg[:m.start()] + wrapped + "\n</svg>"
|
||||
svg = svg[:m.start()] + wrapped + svg[m.end():]
|
||||
vm = re.search(r'viewBox="0 0 (\d+) (\d+)"', svg)
|
||||
assert vm
|
||||
svg = svg.replace(vm.group(0),
|
||||
f'viewBox="0 0 {vm.group(1)} {int(vm.group(2)) + 17 * (len(cap_lines) - 1)}"', 1)
|
||||
f'viewBox="0 0 {vm.group(1)} {int(vm.group(2)) + 17 * (len(cap_lines) - 1) + 24}"', 1)
|
||||
|
||||
# „SMA-20" (x=515, y=269) sta la 12.6px de eticheta „intrare 374.80" (y=281.6),
|
||||
# adica lipite: o ridicam 12px pe linia libera de deasupra.
|
||||
svg, n = re.subn(r'(<text x="\d+" y=")(\d+)(" fill="#b06000">SMA-20</text>)',
|
||||
lambda mm: f'{mm.group(1)}{int(mm.group(2)) - 12}{mm.group(3)}', svg)
|
||||
assert n == 1, f"eticheta SMA-20: {n} potriviri"
|
||||
# ---------------------------------------------------------------- nicio cifră inventată în etichete
|
||||
desenate = {f"{SUPORT:.2f}", f"{PIVOT:.2f}", f"{DOJI_CLOSE:.2f}"}
|
||||
for txt in re.findall(r"<text[^>]*>([^<]*)</text>", svg):
|
||||
for nr in re.findall(r"\d+\.\d\d", txt):
|
||||
assert nr in desenate | {f"{pct_suport:.2f}", f"{pct_pivot:.2f}",
|
||||
f"{rvol:.2f}", "1.00"}, \
|
||||
f"eticheta numește {nr}, care nu e un nivel desenat: {txt!r}"
|
||||
|
||||
assert "<svg" in svg and "\n\n" not in svg, "linie goala in SVG"
|
||||
assert "<svg" in svg and "\n\n" not in svg, "linie goală în SVG"
|
||||
OUT.write_text(svg)
|
||||
print(f"scris {OUT.name}: {len(cds)} candele, doji {cds[20].c:.2f} (RVOL {VOLS[20]/media_vol:.2f}), "
|
||||
f"suport {min(c.l for c in cds[12:21]):.2f}, intrare 374.80, SL 352.00, "
|
||||
f"TP1 383.74 = 0.39R, TP2 402.00 = 1.19R, ultimul close {cds[-1].c:.2f}")
|
||||
print(f"scris {OUT.name}: 32 candele, doji {cds[DOJI].c:.2f} (RVOL {rvol:.2f}), "
|
||||
f"suport {SUPORT:.2f} ({pct_suport:.2f} % sub doji), pivot {PIVOT:.2f} "
|
||||
f"({pct_pivot:.2f} % peste doji), fără intrare/stop/țintă pe desen")
|
||||
|
||||
Reference in New Issue
Block a user