diff --git a/gen/eticheta_libera.py b/gen/eticheta_libera.py new file mode 100644 index 0000000..c12a6eb --- /dev/null +++ b/gen/eticheta_libera.py @@ -0,0 +1,103 @@ +"""O etichetă de diagramă nu are voie să stea peste desen. + +`audit_diagrams.py` prinde doar text peste text. Astea sunt celelalte trei +feluri de suprapunere, găsite vizual pe 02.10.2026 și ușor de regresat la +următoarea mutare de etichetă: + +- `` — niveluri, laturi de wedge, bracket-uri, cozi de săgeată; +- `` — mediile mobile (punctul orb al primei versiuni); +- `` — corpurile candelelor și barele de volum. + +Verificarea e opt-in, pe etichete numite: pe un grafic de candele aproape +orice text atinge ceva, iar o regulă globală ar bloca tot repo-ul. + + eticheta_libera(svg, "wedge_desc", {"baza = 4.00"}, rects=True) +""" +import re + +CW = 6.2 # lățimea unui caracter bold de 11-12px +WICK = "#1a1a1a" # fitilele: desenul le traversează oricum, prin construcție + + +def _bbox(m, min_px): + """Caseta etichetei, lărgită cu marginea cerută.""" + tx, ty, attrs, s = float(m.group(1)), float(m.group(2)), m.group(3), m.group(4) + w = CW * len(s) + x0 = tx if 'anchor="start"' in attrs else tx - w if 'anchor="end"' in attrs else tx - w / 2 + return s, (x0 - min_px, x0 + w + min_px, ty - 9 - min_px, ty + 3 + min_px) + + +def _segmente(svg): + """Toate segmentele desenate, ca perechi de capete: linii + polilinii.""" + for m in re.finditer(r' x1: + return False + if ax == bx: + ys = (ay, by) + else: # y-ul segmentului pe porțiunea comună + ys = tuple(ay + (by - ay) * (min(max(x, min(ax, bx)), max(ax, bx)) - ax) / (bx - ax) + for x in (x0, x1)) + return min(ys) <= bot and max(ys) >= top + + +def eticheta_libera(svg, name, etichete, min_px=5.0, rects=False, segmente=True): + """Ridică AssertionError dacă vreuna din `etichete` stă peste desen. + + `segmente=False` pentru etichetele de nivel: alea stau lipite de linia lor + prin construcție (y − 6), deci doar candelele sunt o suprapunere reală. + """ + segs = list(_segmente(svg)) if segmente else [] + boxes = [] + if rects: + for m in re.finditer(r'', svg): + x, y, w, h = map(float, m.groups()[:4]) + if "fill-opacity" in m.group(5): # zonele colorate stau în fundal + continue + boxes.append((x, y, x + w, y + h)) + for m in re.finditer(r']*)>([^<]*)', svg): + if m.group(4) not in etichete: + continue + s, box = _bbox(m, min_px) + for kind, ax, ay, bx, by in segs: + if _atinge(box, ax, ay, bx, by): + raise AssertionError(f"{name}: eticheta '{s}' sta pe {kind} " + f"({ax:.0f},{ay:.0f})-({bx:.0f},{by:.0f})") + for rx0, ry0, rx1, ry1 in boxes: + if rx0 < box[1] and rx1 > box[0] and ry0 < box[3] and ry1 > box[2]: + raise AssertionError(f"{name}: eticheta '{s}' sta pe dreptunghiul " + f"({rx0:.0f},{ry0:.0f})-({rx1:.0f},{ry1:.0f})") + + +def demo(): + svg = ('' + '' + '' + 'pe linie' + 'pe curba' + 'pe candela' + 'libera') + for et in ("pe linie", "pe curba", "pe candela"): + try: + eticheta_libera(svg, "demo", {et}, rects=True) + except AssertionError: + pass + else: + raise AssertionError(f"'{et}' trebuia prinsa") + eticheta_libera(svg, "demo", {"libera"}, rects=True) + print("eticheta_libera: ok") + + +if __name__ == "__main__": + demo() diff --git a/gen/patternuri_complexe_3.py b/gen/patternuri_complexe_3.py index 876e3ed..5a54f74 100644 --- a/gen/patternuri_complexe_3.py +++ b/gen/patternuri_complexe_3.py @@ -10,7 +10,9 @@ import re import sys sys.path.insert(0, "/workspace/atm-curs-dl") +sys.path.insert(0, "/workspace/atm-curs-dl/gen") from svg_candles import Chart, Level, Mark, MA, Zone, from_closes # noqa: E402 +from eticheta_libera import eticheta_libera # noqa: E402 MD = "/workspace/atm-curs-dl/summaries/06_MODUL_5/02_patternuri_complexe_partea_a_treia.md" @@ -25,7 +27,7 @@ def hi(cds, a, b): return round(max(c.h for c in cds[a:b + 1]), 2) -def check(ch, name, distante=()): +def check(ch, name, distante=(), clear=(), clear_rect=()): """Nicio etichetă nu poate numi un preț care nu e un nivel desenat.""" svg = ch.render() assert "\n\n" not in svg, f"{name}: linie goală în svg" @@ -42,6 +44,10 @@ def check(ch, name, distante=()): for lv in ch.levels: assert ch._y(lv.price) > sus - 0.06 * (jos - sus), \ f"{name}: nivelul {lv.price} e lipit de marginea de sus" + if clear: + eticheta_libera(svg, name, set(clear)) + if clear_rect: # etichete de nivel: doar peste candele e o problemă + eticheta_libera(svg, name, set(clear_rect), rects=True, segmente=False) SVG[name] = svg @@ -410,13 +416,14 @@ check(Chart( Level(FUND_C, f"fundul ceștii {FUND_C:.2f}", "#b06000", label_at="left")], marks=[Mark(5, "top 1", "#c00"), Mark(23, "top 2", "#c00", dy=-18), Mark(13, "fundul ceștii", "#b06000", "below"), - Mark(29, "breakout", "#188a3e", dy=-14)], + Mark(29, "breakout", "#188a3e", dy=-68)], zones=[Zone(24, 27, "mânerul", "#b06000")], mas=[MA(20, "#777", dash="4 3")], - scale=4.2, gap=15, body_w=9, left=46, width=620, vol_h=70, top=100, + scale=4.2, gap=15, body_w=9, left=46, width=700, vol_h=70, top=100, vol_label="volum", title="Cup with handle — exemplu construit (fără ticker), monthly", caption="Mânerul: 4 bare, −30% din adâncime. Două top-uri la 120.00 = double top. Gri punctat: SMA-20."), - "cu", [AD_C, RIM_C - MANER]) + "cu", [AD_C, RIM_C - MANER], + clear=("breakout",), clear_rect=(f"minimul mânerului {MANER:.2f}", "breakout")) # ----------------------------------------- cup with handle: mâner prea adânc closes_cn = ([99.0, 105.0, 111.0, 116.0, 119.5] # 0-4 uptrend + rim 1 diff --git a/gen/patternuri_complexe_4.py b/gen/patternuri_complexe_4.py index 00fd662..b933837 100644 --- a/gen/patternuri_complexe_4.py +++ b/gen/patternuri_complexe_4.py @@ -15,7 +15,9 @@ import re import sys sys.path.insert(0, "/workspace/atm-curs-dl") +sys.path.insert(0, "/workspace/atm-curs-dl/gen") from svg_candles import Chart, Level, Mark, MA, Zone, Arrow, from_closes # noqa: E402 +from eticheta_libera import eticheta_libera # noqa: E402 MD = ("/workspace/atm-curs-dl/summaries/06_MODUL_5/" "03_patternuri_complexe_partea_a_patra.md") @@ -58,31 +60,7 @@ def txt(i, p, s, color="#8b5cf6", anchor="middle", dx=0.0, dy=0.0, bold=True): return dict(i=i, p=p, s=s, color=color, anchor=anchor, dx=dx, dy=dy, bold=bold) -def _desprinse(svg, name, etichete, min_px=5.0): - """Etichetele date nu au voie sa stea pe nicio linie (regresia 11/12).""" - # fitilele candelelor (#1a1a1a) nu se numara: desenul le traverseaza oricum - lines = [tuple(map(float, m.groups()[:4])) for m in re.finditer( - r']*)>([^<]*)', svg): - tx, ty, attrs, s = float(m.group(1)), float(m.group(2)), m.group(3), m.group(4) - if s not in etichete: - continue - w = 6.2 * len(s) - x0 = tx if 'anchor="start"' in attrs else tx - w if 'anchor="end"' in attrs else tx - w / 2 - x1, top, bot = x0 + w, ty - 9 - min_px, ty + 3 + min_px - for lx1, ly1, lx2, ly2 in lines: - if max(lx1, lx2) < x0 or min(lx1, lx2) > x1: - continue - ys = [ly1 + (ly2 - ly1) * (max(min(x, max(lx1, lx2)), min(lx1, lx2)) - lx1) - / ((lx2 - lx1) or 1) for x in (x0, x1)] if lx1 != lx2 else [ly1, ly2] - if min(ys) <= bot and max(ys) >= top: - raise AssertionError( - f"{name}: eticheta '{s}' sta pe linia " - f"({lx1:.0f},{ly1:.0f})-({lx2:.0f},{ly2:.0f})") - - -def deco(ch, name, segs=(), texts=(), distante=(), clear=()): +def deco(ch, name, segs=(), texts=(), distante=(), clear=(), rects=False): """Randează graficul, injectează liniile/textele, verifică, salvează.""" saved = ch.top, ch.left, ch.width try: @@ -114,7 +92,7 @@ def deco(ch, name, segs=(), texts=(), distante=(), clear=()): assert m.group(0) in desenate, \ f"{name}: eticheta numește {m.group(0)}, care nu e un nivel desenat" if clear: - _desprinse(svg, name, set(clear)) + eticheta_libera(svg, name, set(clear), rects=rects) sus = min(ch._y(c.h) for c in ch.candles) jos = max(ch._y(c.l) for c in ch.candles) for lv in ch.levels: @@ -126,6 +104,9 @@ def deco(ch, name, segs=(), texts=(), distante=(), clear=()): CAP = ("Exemplu construit (fără ticker), la scară: lecția nu rostește nicio " "cifră. Regula de măsurare e a cursului.") +# la bull flag eticheta SMA-20 ar cadea exact pe linia TP1: o mut in caption +CAP_SMA = ("Exemplu construit (fără ticker), la scară. Portocaliu punctat: SMA-20." + " Regula de măsurare e a cursului.") # ============================================================ 1. wedge descendent U0, L0 = 52.00, 48.00 # laturile la deschiderea triunghiului @@ -190,12 +171,12 @@ deco(Chart( Mark(13, "picior 1", "#c00", "below", dy=16), Mark(19, "picior 2 (mai jos)", "#c00", "below"), Mark(25, "hammer in capul triunghiului", "#b06000", "below", dy=8), - Mark(27, "breakout cu volum", "#188a3e")], + Mark(27, "breakout cu volum", "#188a3e", "above", dy=-24)], zones=[Zone(10, 26, "wedge descendent = consolidare contra-trend (17 bare)", "#8b5cf6")], arrows=[Arrow(29, "stopul sta sub low-ul candelei de breakout:\n" "daca pretul se intoarce in triunghi, ideea a murit", - price=SL_D, where="below", color="#c00", dy=30, dx=44)], + price=SL_D, where="below", color="#c00", dy=30, dx=56)], mas=[MA(20, "#b06000", dash="4 3", text="SMA-20")], scale=24, gap=16, body_w=9, left=46, width=730, top=62, vol_h=70, title="Wedge descendent — bull", @@ -206,11 +187,13 @@ deco(Chart( seg(13, lo_d(13), 27, lo_d(27)), seg(10.0, L0, 10.0, U0, "#8b5cf6", "", 2.2), seg(35.6, E_D, 35.6, TP_D, "#1a5fb4", "", 2.2)], - texts=[txt(10.1, (U0 + L0) / 2, f"baza = {BAZA_D:.2f}", anchor="start", dx=4), + texts=[txt(10.1, (U0 + L0) / 2, f"baza = {BAZA_D:.2f}", anchor="start", + dx=4, dy=16), txt(35.8, (E_D + TP_D) / 2, f"aceeasi {BAZA_D:.2f}", "#1a5fb4", anchor="start", dx=4)], distante=(BAZA_D,), - clear=("picior 1", "breakout cu volum", "hammer in capul triunghiului")) + clear=("picior 1", "breakout cu volum", "hammer in capul triunghiului", + f"baza = {BAZA_D:.2f}", f"aceeasi {BAZA_D:.2f}")) # ================================================ 2. wedge vs flag (forma) cl = (ramp(29.6, 34.00, 6) # 0-5 urcare @@ -449,10 +432,10 @@ deco(Chart( Mark(20, "C = aici era by the dip", "#b06000", "below"), Mark(23, "breakout cu volum", "#188a3e", dy=-36)], zones=[Zone(11, 22, "flamura (12 bare)", "#999")], - mas=[MA(20, "#b06000", dash="4 3", text="SMA-20")], + mas=[MA(20, "#b06000", dash="4 3")], scale=13, gap=16, body_w=9, left=46, width=730, top=62, vol_h=70, title="Bull flag = ABCD long", - caption=CAP), + caption=CAP_SMA), "bull_flag", segs=[seg(11, 40.00, 24, up_f(24), "#1a5fb4"), seg(11, 37.00, 14, lo_f(14), "#1a5fb4", "4 3", 1.2), diff --git a/summaries/06_MODUL_5/02_patternuri_complexe_partea_a_treia.html b/summaries/06_MODUL_5/02_patternuri_complexe_partea_a_treia.html index a372d02..eb78017 100644 --- a/summaries/06_MODUL_5/02_patternuri_complexe_partea_a_treia.html +++ b/summaries/06_MODUL_5/02_patternuri_complexe_partea_a_treia.html @@ -910,23 +910,23 @@ diferența față de rounding bottom, pentru că formele sunt aproape identice:< vezi după o urcare, e o ceașcă. Aceeași curbă, două interpretări diferite, pentru că trendul dinainte e diferit.

-

+

Cup with handle — exemplu construit (fără ticker), monthly mânerul - - target 160.00 = 120.00 + 40.00 - + + target 160.00 = 120.00 + 40.00 + intrare 121.00 - - rezistență 120.00 - - minimul mânerului 108.00 - + + rezistență 120.00 + + minimul mânerului 108.00 + SL 107.00 - - limita de 50% 100.00 - + + limita de 50% 100.00 + fundul ceștii 80.00 @@ -1001,7 +1001,7 @@ pentru că trendul dinainte e diferit.

- + @@ -1042,7 +1042,7 @@ pentru că trendul dinainte e diferit.

top 1 top 2 fundul ceștii - breakout + breakout Mânerul: 4 bare, −30% din adâncime. Două top-uri la 120.00 = double top. Gri punctat: SMA-20.

Părțile formei, cu numele din curs: diff --git a/summaries/06_MODUL_5/02_patternuri_complexe_partea_a_treia.md b/summaries/06_MODUL_5/02_patternuri_complexe_partea_a_treia.md index 9c3638b..b39dd14 100644 --- a/summaries/06_MODUL_5/02_patternuri_complexe_partea_a_treia.md +++ b/summaries/06_MODUL_5/02_patternuri_complexe_partea_a_treia.md @@ -920,23 +920,23 @@ vezi după o urcare, e o ceașcă. Aceeași curbă, două interpretări diferite pentru că trendul dinainte e diferit. - + Cup with handle — exemplu construit (fără ticker), monthly mânerul - - target 160.00 = 120.00 + 40.00 - + + target 160.00 = 120.00 + 40.00 + intrare 121.00 - - rezistență 120.00 - - minimul mânerului 108.00 - + + rezistență 120.00 + + minimul mânerului 108.00 + SL 107.00 - - limita de 50% 100.00 - + + limita de 50% 100.00 + fundul ceștii 80.00 @@ -1011,7 +1011,7 @@ pentru că trendul dinainte e diferit. - + @@ -1052,7 +1052,7 @@ pentru că trendul dinainte e diferit. top 1 top 2 fundul ceștii - breakout + breakout Mânerul: 4 bare, −30% din adâncime. Două top-uri la 120.00 = double top. Gri punctat: SMA-20. diff --git a/summaries/06_MODUL_5/03_patternuri_complexe_partea_a_patra.html b/summaries/06_MODUL_5/03_patternuri_complexe_partea_a_patra.html index 7a4674a..819d806 100644 --- a/summaries/06_MODUL_5/03_patternuri_complexe_partea_a_patra.html +++ b/summaries/06_MODUL_5/03_patternuri_complexe_partea_a_patra.html @@ -241,18 +241,18 @@ cursului (buy doji, buy momentum, buy hammer, bull engulfing) și dacă zona e picior 1 picior 2 (mai jos) hammer in capul triunghiului - breakout cu volum - - - stopul sta sub low-ul candelei de breakout: - daca pretul se intoarce in triunghi, ideea a murit + breakout cu volum + + + stopul sta sub low-ul candelei de breakout: + daca pretul se intoarce in triunghi, ideea a murit Exemplu construit (fără ticker), la scară: lecția nu rostește nicio cifră. Regula de măsurare e a cursului. - baza = 4.00 + baza = 4.00 aceeasi 4.00

Măsurarea, cuvânt cu cuvânt din curs: „dacă măsori distanța de aici @@ -851,7 +851,6 @@ urmat de o flamură: un canal îngust, cu laturi paralele, înclinat u TP2 47.80 = batul steagului = 5.6R - SMA-20 @@ -963,7 +962,7 @@ urmat de o flamură: un canal îngust, cu laturi paralele, înclinat u B C = aici era by the dip breakout cu volum - Exemplu construit (fără ticker), la scară: lecția nu rostește nicio cifră. Regula de măsurare e a cursului. + Exemplu construit (fără ticker), la scară. Portocaliu punctat: SMA-20. Regula de măsurare e a cursului. diff --git a/summaries/06_MODUL_5/03_patternuri_complexe_partea_a_patra.md b/summaries/06_MODUL_5/03_patternuri_complexe_partea_a_patra.md index dd58b71..815046a 100644 --- a/summaries/06_MODUL_5/03_patternuri_complexe_partea_a_patra.md +++ b/summaries/06_MODUL_5/03_patternuri_complexe_partea_a_patra.md @@ -219,18 +219,18 @@ cursului (buy doji, buy momentum, buy hammer, bull engulfing) și dacă zona e picior 1 picior 2 (mai jos) hammer in capul triunghiului - breakout cu volum - - - stopul sta sub low-ul candelei de breakout: - daca pretul se intoarce in triunghi, ideea a murit + breakout cu volum + + + stopul sta sub low-ul candelei de breakout: + daca pretul se intoarce in triunghi, ideea a murit Exemplu construit (fără ticker), la scară: lecția nu rostește nicio cifră. Regula de măsurare e a cursului. - baza = 4.00 + baza = 4.00 aceeasi 4.00 @@ -860,7 +860,6 @@ urmat de o *flamură*: un canal îngust, cu laturi paralele, înclinat ușor TP2 47.80 = batul steagului = 5.6R - SMA-20 @@ -972,7 +971,7 @@ urmat de o *flamură*: un canal îngust, cu laturi paralele, înclinat ușor B C = aici era by the dip breakout cu volum - Exemplu construit (fără ticker), la scară: lecția nu rostește nicio cifră. Regula de măsurare e a cursului. + Exemplu construit (fără ticker), la scară. Portocaliu punctat: SMA-20. Regula de măsurare e a cursului.