diff --git a/audit_diagrams.py b/audit_diagrams.py index 712b0ac..3e706ef 100644 --- a/audit_diagrams.py +++ b/audit_diagrams.py @@ -14,8 +14,10 @@ import re import sys from pathlib import Path -CHAR_W = 5.7 # latime medie per caracter la 12px sans-serif (calibrat pe randare) -LINE_H = 13 # înălțimea unei linii de text +CHAR_W = 5.7 # latime medie per caracter la 12px sans-serif (calibrat pe randare) +LINE_H_RATIO = 1.07 # înălțimea rândului = font-size efectiv * ratio (același prag ca + # LINE_H=13 la 12px, dar scade pentru etichetele de 10px) +DEFAULT_FONT = 12.0 # font-size când nu e precizat nici pe element, nici moștenit _TRANSFORM = re.compile(r'translate\(\s*([-\d.]+)\s*[, ]\s*([-\d.]+)\s*\)') _ELEMENT = re.compile( @@ -25,29 +27,43 @@ _ROLE_ENTRY = re.compile(r'intrar|entry', re.I) _ROLE_TP = re.compile(r'\bTP\d*|\bțint|\btarget', re.I) +def _font_px(css: str) -> float | None: + """font-size în px dintr-un atribut/`style` (ex: `font-size:10px`, `font-size="10"`, `font:12px`).""" + m = re.search(r'font-size\s*[:=]\s*"?([\d.]+)', css) + if m: + return float(m.group(1)) + m = re.search(r'(?` acumulată aplicată. + """Evenimentele SVG în ordine, cu translația și font-size-ul moștenit aplicate. Fiecare `` deschide un cadru; elementele - dinăuntru moștenesc deplasarea, oricât de adânc ar fi imbricate. + dinăuntru moștenesc deplasarea și font-size-ul, oricât de adânc ar fi + imbricate. Font-size-ul de bază vine de pe rădăcina ``. """ - stack = [(0.0, 0.0)] + root = re.search(r']*)>', svg) + base_fs = (_font_px(root.group(1)) if root else None) or DEFAULT_FONT + stack = [((0.0, 0.0), base_fs)] for m in _ELEMENT.finditer(svg): if m.group(1) is not None: # t = _TRANSFORM.search(m.group(1)) dx, dy = (float(t.group(1)), float(t.group(2))) if t else (0.0, 0.0) - px, py = stack[-1] - stack.append((px + dx, py + dy)) + (px, py), fs = stack[-1] + own = _font_px(m.group(1)) + stack.append(((px + dx, py + dy), own if own else fs)) elif m.group(0) == "": if len(stack) > 1: stack.pop() else: - tx, ty = stack[-1] + (tx, ty), fs = stack[-1] if m.group(3) is not None: # ... attrs = m.group(2) x = float(re.search(r'\bx="([-\d.]+)"', attrs).group(1)) if re.search(r'\bx="', attrs) else 0.0 y = float(re.search(r'\by="([-\d.]+)"', attrs).group(1)) if re.search(r'\by="', attrs) else 0.0 - yield "text", x + tx, y + ty, attrs, m.group(3) + own = _font_px(attrs) + yield "text", x + tx, y + ty, attrs, m.group(3), own if own else fs else: # sau alt tag y = re.search(r'\by1="([-\d.]+)"', m.group(4) or "") if y: @@ -60,12 +76,12 @@ def texts(svg: str): for ev in _elements(svg): if ev[0] != "text": continue - _, x, y, attrs, raw = ev + _, x, y, attrs, raw, fs = ev inner = re.sub(r'<[^>]+>', '', raw) anchor = (re.search(r'text-anchor="(\w+)"', attrs) or [None, "start"])[1] w = len(inner) * CHAR_W x0 = x - w if anchor == "end" else (x - w / 2 if anchor == "middle" else x) - yield x0, x0 + w, y, inner.strip() + yield x0, x0 + w, y, inner.strip(), fs def levels(svg: str): @@ -98,16 +114,17 @@ def _audit_one(i: int, svg: str) -> list[str]: problems.append(f"svg {i}: linie goală înăuntru — markdown rupe blocul") tx = list(texts(svg)) - for x0, x1, y, s in tx: + for x0, x1, y, s, _ in tx: if x1 > vw + 2: problems.append(f'svg {i}: text depășește marginea dreaptă ({x1:.0f} > {vw:.0f}): "{s[:45]}"') if y > vh + 2 or y < 0: problems.append(f'svg {i}: text în afara înălțimii (y={y:.0f}, viewBox {vh:.0f}): "{s[:45]}"') for a in range(len(tx)): for b in range(a + 1, len(tx)): - xa0, xa1, ya, sa = tx[a] - xb0, xb1, yb, sb = tx[b] - if abs(ya - yb) < LINE_H and xa0 < xb1 - 4 and xb0 < xa1 - 4: + xa0, xa1, ya, sa, fa = tx[a] + xb0, xb1, yb, sb, fb = tx[b] + line_h = LINE_H_RATIO * max(fa, fb) + if abs(ya - yb) < line_h and xa0 < xb1 - 4 and xb0 < xa1 - 4: problems.append(f'svg {i}: texte suprapuse — "{sa[:28]}" / "{sb[:28]}"') # dacă diagrama are exact un triplet SL/intrare/TP, raportul distanțelor @@ -149,6 +166,23 @@ def selftest(): real = svg('unu' 'doi') assert any("suprapuse" in p for p in _audit_one(0, real)), "suprapunerea reală a fost pierdută" + # pragul de înălțime urmează font-size-ul: 10px la pas de 12px NU se atinge + small = svg('unu' + 'doi') + assert not any("suprapuse" in p for p in _audit_one(0, small)), "10px la pas de 12px raportat fals" + # același caz cu font-size moștenit de pe + small_inherited = svg('unu' + 'doi') + assert not any("suprapuse" in p for p in _audit_one(0, small_inherited)), "font-size moștenit ignorat" + # font-size-ul unui text nu se scurge la frații lui (baza vine de pe ) + leak = svg('titlu' + 'unu' + 'doi') + assert not any("suprapuse" in p for p in _audit_one(0, leak)), "font-size-ul unui frate s-a scurs" + # două rânduri de 10px care chiar se calcă rămân raportate + real_small = svg('unu' + 'doi') + assert any("suprapuse" in p for p in _audit_one(0, real_small)), "suprapunerea reală la 10px a fost pierdută" # triplet SL/intrare/TP cu scară greșită rămâne semnalat bad = svg('SL 90.00' 'intrare 100.00' diff --git a/gen/strategii/atmi_ansamblu.py b/gen/strategii/atmi_ansamblu.py index 5e7621a..08bd18b 100644 --- a/gen/strategii/atmi_ansamblu.py +++ b/gen/strategii/atmi_ansamblu.py @@ -13,6 +13,7 @@ Rulează: python3 gen/strategii/atmi_ansamblu.py Scrie lângă el `atmi_ansamblu.svg`. """ +import re import sys from pathlib import Path @@ -84,11 +85,13 @@ marks = [ Mark(LAST, "ieși cu tot", BLUE, "below", dy=22), ] +caption = ("100.00 / 96.00 / 105.60 sunt cifrele exemplului din curs; PPQ 95.80 e calculat " + "în diagrama pasului 1 (cursul nu dă valorile pivoților).") + ch = Chart(candles=cds, levels=levels, marks=marks, scale=22.0, gap=17, body_w=9, left=44, width=720, vol_h=70, vol_gap=72, top=88, vol_label="volum", - caption="100.00 / 96.00 / 105.60 sunt cifrele exemplului din curs; PPQ 95.80 e calculat " - "în diagrama pasului 1 (cursul nu dă valorile pivoților).") + caption=caption) svg = ch.render() @@ -125,6 +128,46 @@ titlu = ('' i = svg.index(">") + 1 svg = svg[:i] + "\n " + "\n ".join([ppq, no_sl, titlu]) + svg[i:] +# ---------------------------------------------------------------- caption pe două rânduri +# Captionul are 135 de caractere, adică ~794 px de la x=24 într-o pânză lată de +# 720: se rupe pe două rânduri, ca în selectia_ansamblu.py. +def _wrap(text: str, max_chars: int) -> list[str]: + lines, cur = [], "" + for w in text.split(): + if cur and len(cur) + 1 + len(w) > max_chars: + lines.append(cur) + cur = w + else: + cur = f"{cur} {w}".strip() + if cur: + lines.append(cur) + return lines + + +cap_lines = _wrap(caption, 80) +assert len(cap_lines) == 2, cap_lines +m = re.search(r'' + re.escape(caption) + r"", svg) +assert m, "caption-ul nu a fost găsit în SVG" +y0 = int(m.group(1)) +wrapped = "\n ".join(f'{ln}' + for i, ln in enumerate(cap_lines)) +svg = svg[:m.start()] + wrapped + svg[m.end():] +vm = re.search(r'viewBox="0 0 (\d+) (\d+)"', svg) +assert vm, "nu găsesc viewBox-ul" +svg = svg.replace(vm.group(0), + f'viewBox="0 0 {vm.group(1)} {int(vm.group(2)) + 17 * (len(cap_lines) - 1)}"', 1) + +# fiecare rând de caption CHIAR DESENAT trebuie să încapă în lățimea pânzei. +# CHAR_W = 5.7 e lățimea medie per caracter calibrată de audit_diagrams.py; +# assertul prinde exact defectul vechi, când captionul era un singur text de +# 135 de caractere: 24 + 135 × 5.7 = 794 > 720. +CHAR_W = 5.7 +vw = int(vm.group(1)) +for m_cap in re.finditer(r'([^<]*)', svg): + ln = m_cap.group(1) + x1 = 24 + len(ln) * CHAR_W + assert x1 <= vw, f"rând de caption tăiat la marginea dreaptă ({x1:.0f} > {vw}): {ln!r}" + assert svg.count("\n\n") == 0, "linie goală în SVG rupe blocul din markdown" assert ".md:" not in svg, "trimitere fișier:linie în SVG" OUT.write_text(svg) diff --git a/gen/strategii/atmi_ansamblu.svg b/gen/strategii/atmi_ansamblu.svg index 15eddb3..a246a8d 100644 --- a/gen/strategii/atmi_ansamblu.svg +++ b/gen/strategii/atmi_ansamblu.svg @@ -1,4 +1,4 @@ - + zona PPQ 95.80 — aici cumperi fără stop-loss standard — nicio linie sub intrare SPY — daily: corecție ≥4 % → zona PPQ → BIM + verde → intrare → TP +10 % @@ -110,5 +110,6 @@ BIM prima candelă verde → intrare ieși cu tot - 100.00 / 96.00 / 105.60 sunt cifrele exemplului din curs; PPQ 95.80 e calculat în diagrama pasului 1 (cursul nu dă valorile pivoților). + 100.00 / 96.00 / 105.60 sunt cifrele exemplului din curs; PPQ 95.80 e calculat + în diagrama pasului 1 (cursul nu dă valorile pivoților). \ No newline at end of file diff --git a/gen/strategii/selectia_proximitate.py b/gen/strategii/selectia_proximitate.py index c6fea79..d16310f 100644 --- a/gen/strategii/selectia_proximitate.py +++ b/gen/strategii/selectia_proximitate.py @@ -90,7 +90,7 @@ arrows = [ Arrow(DEPARTE, f"close {cds[DEPARTE].c:.2f} = {pct_departe:.2f} % peste suport\n" f"prea departe: până acolo prețul mai are de căzut,\n" f"deci nivelul nu-l poate opri acum", - where="above", color=RED, dx=-60, dy=-4), + where="above", color=RED, dx=-60, dy=-20), Arrow(APROAPE, f"close {cds[APROAPE].c:.2f} = {pct_aproape:.2f} % peste suport\n" f"la nivel: dacă mai scade puțin, dă de ordinele\n" f"de cumpărare care au mai întors prețul de acolo", @@ -165,6 +165,36 @@ svg = svg[:m.start()] + wrapped + svg[m.end():] svg = svg.replace(m_vb.group(0), f'viewBox="0 0 {m_vb.group(1)} {h_svg + 17 * (len(cap_lines) - 1)}"', 1) + +def _text_boxes(fill: str) -> list[tuple[float, float, float, float, str]]: + """Bbox-urile (x0, x1, ytop, ybot) ale textelor cu un anumit fill.""" + out = [] + for mm in re.finditer(r"]*)>([^<]*)", svg): + attrs, txt = mm.group(1), mm.group(2) + if f'fill="{fill}"' not in attrs: + continue + x = float(re.search(r'\bx="([-\d.]+)"', attrs).group(1)) + y = float(re.search(r'\by="([-\d.]+)"', attrs).group(1)) + anchor = (re.search(r'text-anchor="(\w+)"', attrs) or [None, "start"])[1] + w = len(txt) * 5.7 # 12px bold, ca în audit_diagrams.py + x0 = x - w if anchor == "end" else (x - w / 2 if anchor == "middle" else x) + out.append((x0, x0 + w, y - 9, y + 3, txt)) + return out + + +def _se_suprapun(a, b) -> bool: + return a[0] < b[1] and b[0] < a[1] and a[2] < b[3] and b[2] < a[3] + + +# defectul inițial: ultimul rând al etichetei săgeții de sus (y=75) și titlul +# (y=84) aveau 9 px între baseline-uri și se călcau pe orizontală. Titlul nu +# are voie să se suprapună peste nicio etichetă de săgeată. +titluri = _text_boxes("#333") +assert len(titluri) == 1, f"titluri #333 găsite: {len(titluri)}" +for b in _text_boxes(RED): + assert not _se_suprapun(titluri[0], b), \ + f"titlul se suprapune peste eticheta săgeții: {b[4]!r}" + assert " - Cât de aproape de suport e prețul — daily, exemplu construit - - - suport monthly 48.00 - - suport + 3 % = 49.44 - - SMA-20 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - volum - - + + Cât de aproape de suport e prețul — daily, exemplu construit + + + suport monthly 48.00 + + suport + 3 % = 49.44 + + SMA-20 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + volum + + close 51.60 = 7.50 % peste suport prea departe: până acolo prețul mai are de căzut, deci nivelul nu-l poate opri acum - - - close 48.80 = 1.67 % peste suport - la nivel: dacă mai scade puțin, dă de ordinele - de cumpărare care au mai întors prețul de acolo - Pragul de 3 % (cu 4 % „cam departe”) e al cursului; seria și prețurile sunt construite, la - scară, iar procentele sunt calculate din serie. În capitolul Buy Doji, pragul de 1-2 % măsoară - altceva: low-ul doji-ului față de nivel, nu închiderea simbolului. -banda de 3 %: înăuntru simbolul e „la nivel” \ No newline at end of file + + + close 48.80 = 1.67 % peste suport + la nivel: dacă mai scade puțin, dă de ordinele + de cumpărare care au mai întors prețul de acolo + Pragul de 3 % (cu 4 % „cam departe”) e al cursului; seria și prețurile sunt construite, la + scară, iar procentele sunt calculate din serie. În capitolul Buy Doji, pragul de 1-2 % măsoară + altceva: low-ul doji-ului față de nivel, nu închiderea simbolului. +banda de 3 %: înăuntru simbolul e „la nivel” \ No newline at end of file diff --git a/summaries/17_STRATEGII/04_selectia.html b/summaries/17_STRATEGII/04_selectia.html index 95714c4..cf137a1 100644 --- a/summaries/17_STRATEGII/04_selectia.html +++ b/summaries/17_STRATEGII/04_selectia.html @@ -622,127 +622,127 @@ multe semnale azi (sau din unul aflat în cadranul 1 pe RS).

calculat din perioada trecută. Condiția de apropiere:
până la 3 %; la 4 % materialul spune că „e cam departe".

-

- Cât de aproape de suport e prețul — daily, exemplu construit - - - suport monthly 48.00 - - suport + 3 % = 49.44 - - SMA-20 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - volum - - +

+ Cât de aproape de suport e prețul — daily, exemplu construit + + + suport monthly 48.00 + + suport + 3 % = 49.44 + + SMA-20 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + volum + + close 51.60 = 7.50 % peste suport prea departe: până acolo prețul mai are de căzut, deci nivelul nu-l poate opri acum - - - close 48.80 = 1.67 % peste suport - la nivel: dacă mai scade puțin, dă de ordinele - de cumpărare care au mai întors prețul de acolo - Pragul de 3 % (cu 4 % „cam departe”) e al cursului; seria și prețurile sunt construite, la - scară, iar procentele sunt calculate din serie. În capitolul Buy Doji, pragul de 1-2 % măsoară - altceva: low-ul doji-ului față de nivel, nu închiderea simbolului. -banda de 3 %: înăuntru simbolul e „la nivel”

+ + + close 48.80 = 1.67 % peste suport + la nivel: dacă mai scade puțin, dă de ordinele + de cumpărare care au mai întors prețul de acolo + Pragul de 3 % (cu 4 % „cam departe”) e al cursului; seria și prețurile sunt construite, la + scară, iar procentele sunt calculate din serie. În capitolul Buy Doji, pragul de 1-2 % măsoară + altceva: low-ul doji-ului față de nivel, nu închiderea simbolului. +banda de 3 %: înăuntru simbolul e „la nivel”

Pragul de 3 % măsoară închiderea simbolului față de nivel, nu low-ul diff --git a/summaries/17_STRATEGII/04_selectia.md b/summaries/17_STRATEGII/04_selectia.md index c4ab6b5..efd97e5 100644 --- a/summaries/17_STRATEGII/04_selectia.md +++ b/summaries/17_STRATEGII/04_selectia.md @@ -631,127 +631,127 @@ calculat din perioada trecută. Condiția de apropiere: **până la 3 %**; la 4 % materialul spune că „e cam departe". - - Cât de aproape de suport e prețul — daily, exemplu construit - - - suport monthly 48.00 - - suport + 3 % = 49.44 - - SMA-20 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - volum - - + + Cât de aproape de suport e prețul — daily, exemplu construit + + + suport monthly 48.00 + + suport + 3 % = 49.44 + + SMA-20 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + volum + + close 51.60 = 7.50 % peste suport prea departe: până acolo prețul mai are de căzut, deci nivelul nu-l poate opri acum - - - close 48.80 = 1.67 % peste suport - la nivel: dacă mai scade puțin, dă de ordinele - de cumpărare care au mai întors prețul de acolo - Pragul de 3 % (cu 4 % „cam departe”) e al cursului; seria și prețurile sunt construite, la - scară, iar procentele sunt calculate din serie. În capitolul Buy Doji, pragul de 1-2 % măsoară - altceva: low-ul doji-ului față de nivel, nu închiderea simbolului. -banda de 3 %: înăuntru simbolul e „la nivel” + + + close 48.80 = 1.67 % peste suport + la nivel: dacă mai scade puțin, dă de ordinele + de cumpărare care au mai întors prețul de acolo + Pragul de 3 % (cu 4 % „cam departe”) e al cursului; seria și prețurile sunt construite, la + scară, iar procentele sunt calculate din serie. În capitolul Buy Doji, pragul de 1-2 % măsoară + altceva: low-ul doji-ului față de nivel, nu închiderea simbolului. +banda de 3 %: înăuntru simbolul e „la nivel” Pragul de 3 % măsoară **închiderea simbolului** față de nivel, nu low-ul diff --git a/summaries/17_STRATEGII/05_atmi.html b/summaries/17_STRATEGII/05_atmi.html index e9b826f..aac559b 100644 --- a/summaries/17_STRATEGII/05_atmi.html +++ b/summaries/17_STRATEGII/05_atmi.html @@ -83,7 +83,7 @@ nu pui stop, și vinzi toată poziția când ai +10 %. Banii st la următoarea ocazie, unde intră înapoi integral, plus o contribuție nouă.

Tot trade-ul, pe structura exemplului din curs:

-

+

zona PPQ 95.80 — aici cumperi fără stop-loss standard — nicio linie sub intrare SPY — daily: corecție ≥4 % → zona PPQ → BIM + verde → intrare → TP +10 % @@ -195,7 +195,8 @@ la următoarea ocazie, unde intră înapoi integral, plus o contribuție nouă.< BIM prima candelă verde → intrare ieși cu tot - 100.00 / 96.00 / 105.60 sunt cifrele exemplului din curs; PPQ 95.80 e calculat în diagrama pasului 1 (cursul nu dă valorile pivoților). + 100.00 / 96.00 / 105.60 sunt cifrele exemplului din curs; PPQ 95.80 e calculat + în diagrama pasului 1 (cursul nu dă valorile pivoților).

diff --git a/summaries/17_STRATEGII/05_atmi.md b/summaries/17_STRATEGII/05_atmi.md index 607c072..671f69b 100644 --- a/summaries/17_STRATEGII/05_atmi.md +++ b/summaries/17_STRATEGII/05_atmi.md @@ -56,7 +56,7 @@ la următoarea ocazie, unde intră înapoi integral, plus o contribuție nouă. Tot trade-ul, pe structura exemplului din curs: - + zona PPQ 95.80 — aici cumperi fără stop-loss standard — nicio linie sub intrare SPY — daily: corecție ≥4 % → zona PPQ → BIM + verde → intrare → TP +10 % @@ -168,7 +168,8 @@ Tot trade-ul, pe structura exemplului din curs: BIM prima candelă verde → intrare ieși cu tot - 100.00 / 96.00 / 105.60 sunt cifrele exemplului din curs; PPQ 95.80 e calculat în diagrama pasului 1 (cursul nu dă valorile pivoților). + 100.00 / 96.00 / 105.60 sunt cifrele exemplului din curs; PPQ 95.80 e calculat + în diagrama pasului 1 (cursul nu dă valorile pivoților).