Include cazul NU - pullback-ul care nu mai vine si pleaca fara tine - ceruta de user ca jumatate obligatorie a oricarei reguli. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MtSTyTmt6AbCL9j5ajEDm1
304 lines
18 KiB
Python
304 lines
18 KiB
Python
"""Diagramele pentru summaries/08_ANALIZA_ZILNICA/03_locul_crimei_si_pullback.md.
|
|
|
|
Toate prețurile sunt construite (fără ticker). Rulează:
|
|
.venv/bin/python gen/az_pullback.py
|
|
Înlocuiește în .md fiecare bloc <!--SVG:nume--> ... <!--/SVG--> cu output-ul
|
|
generatorului. Ca să schimbi o cifră, o schimbi aici și rulezi din nou.
|
|
"""
|
|
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
|
|
|
|
MD = Path("/workspace/atm-curs-dl/summaries/08_ANALIZA_ZILNICA/03_locul_crimei_si_pullback.md")
|
|
GRAY, RED, GREEN, BLUE, ORANGE = "#777", "#c00", "#188a3e", "#1a5fb4", "#b06000"
|
|
STYLE = dict(gap=15, body_w=9, left=44, width=700)
|
|
svgs = {}
|
|
|
|
|
|
def px_ratio(ch, a, b, c):
|
|
"""raportul (c-b)/(b-a) măsurat în pixeli, după render"""
|
|
return (ch._y(b) - ch._y(c)) / (ch._y(a) - ch._y(b))
|
|
|
|
|
|
def wk(n, **over):
|
|
"""fitile implicite (0.3, 0.3), cu excepții pe index: wk(30, b7=(0.4, 0.3))"""
|
|
return [over.get(f"b{i}", (0.3, 0.3)) for i in range(n)]
|
|
|
|
|
|
def eq(a, b):
|
|
return abs(a - b) < 0.005
|
|
|
|
|
|
# ---------------------------------------------------------------- baza comună D1/D2: uptrend, platou, marubozu
|
|
BASE = [42.8, 43.9, 43.5, 44.7, 45.8, 45.3, 46.6, 47.7, 48.9, 49.4, # 0-9 uptrend, 9 = vârful
|
|
48.6, 47.9, 48.5, 49.2, 48.4, 47.8, 48.3, 49.0, 48.6, 48.0, # 10-19 consolidare sub vârf
|
|
52.0] # 20 marubozu de breakout
|
|
BASE_W = [(0.3, 0.3)] * 9 + [(0.6, 0.3)] + [(0.3, 0.3)] * 10 + [(0.1, 0.1)]
|
|
PEAK, MB = 9, 20
|
|
|
|
|
|
def check_base(cds):
|
|
crime = cds[PEAK].h
|
|
assert eq(crime, 50.0), crime
|
|
assert all(c.h < crime for c in cds[PEAK + 1:MB]), "consolidarea trece peste fostul vârf"
|
|
o, c = cds[MB].o, cds[MB].c
|
|
assert eq(o, 48.0) and eq(c, 52.0), (o, c)
|
|
assert (c - o) >= 3 * max(abs(x.c - x.o) for x in cds[PEAK + 1:MB]), "bara 20 nu arată a marubozu"
|
|
half, third = (o + c) / 2, c - (c - o) / 3
|
|
assert eq(half, crime), "1/2 din marubozu trebuie să cadă pe locul crimei"
|
|
return crime, o, half, third
|
|
|
|
|
|
# ---------------------------------------------------------------- 1. breakout: nu intri pe lumânare, intri la retest
|
|
closes = BASE + [52.8, 52.6, # 21-22 continuare, vârful de după breakout
|
|
52.0, 51.2, 50.6, # 23-25 pullback; 25 atinge cu codița locul crimei
|
|
51.5, # 26 trigger: close peste high-ul barei 25
|
|
52.3, 53.0, 52.7, 53.8, 54.6, 54.2, 55.3, 55.9]
|
|
wicks = BASE_W + [(0.4, 0.3), (0.2, 0.3), (0.2, 0.3), (0.2, 0.3), (0.1, 0.6), (0.2, 0.2)] + [(0.3, 0.3)] * 8
|
|
cds = from_closes(closes, wicks=wicks, start=42.0)
|
|
crime, mb_open, half, third = check_base(cds)
|
|
RT, TR = 25, 26
|
|
assert eq(cds[RT].l, crime) and all(c.l > crime for c in cds[MB + 1:RT]), "retestul nu atinge locul crimei"
|
|
assert cds[TR].c > cds[RT].h, "trigger-ul nu închide peste high-ul barei de retest"
|
|
entry = cds[TR].c
|
|
sl = round(crime - 0.8, 2)
|
|
risk = entry - sl
|
|
tp = round(entry + 2 * risk, 2)
|
|
hit = next(i for i in range(TR + 1, len(cds)) if cds[i].h >= tp)
|
|
cds = cds[:hit + 1] # seria se oprește la atingerea țintei
|
|
chase = cds[21].c # cine a cumpărat a doua zi după marubozu
|
|
assert min(c.l for c in cds[22:TR + 1]) < chase - 2, "cel care a alergat după preț nu ajunge pe minus vizibil"
|
|
print(f"D1 locul crimei {crime:.2f} marubozu {mb_open:.2f}->{cds[MB].c:.2f} 1/2 {half:.2f} 1/3 {third:.2f} "
|
|
f"entry {entry:.2f} SL {sl:.2f} risc {risk:.2f} TP {tp:.2f} hit {hit}")
|
|
BRK = dict(entry=entry, sl=sl, tp=tp)
|
|
ch = Chart(candles=cds,
|
|
levels=[Level(mb_open, f"open marubozu {mb_open:.2f}", GRAY, label_at="left"),
|
|
Level(crime, f"locul crimei = 1/2 marubozu {crime:.2f}", ORANGE),
|
|
Level(sl, f"SL {sl:.2f}", RED, label_at="left"),
|
|
Level(entry, f"intrare {entry:.2f}", GREEN),
|
|
Level(tp, f"TP {tp:.2f} = 2R", BLUE)],
|
|
marks=[Mark(PEAK, "fostul vârf", ORANGE, "above", dy=-4),
|
|
Mark(15, "consolidare", GRAY, "below", dy=4),
|
|
Mark(MB, "marubozu: NU aici", RED, "above", dy=-40),
|
|
Mark(RT, "retest", GREEN, "below", dy=26)],
|
|
scale=26, title="Exemplu construit (fără ticker) — daily",
|
|
caption="Nu cumperi lumânarea de breakout. Aștepți retestul, pe locul crimei.", **STYLE)
|
|
svg_ = ch.render()
|
|
assert abs(px_ratio(ch, sl, entry, tp) - 2.0) < 0.01
|
|
svgs["breakout_retest"] = svg_
|
|
|
|
# ---------------------------------------------------------------- 2. pullback-ul care nu vine
|
|
closes = BASE + [53.0, 53.6, 53.3, 54.4, 55.2, 54.9, 56.0, 56.8, 56.5, 57.6, 58.3, 58.0, 59.1, 59.8]
|
|
wicks = BASE_W + [(0.3, 0.3)] * 14
|
|
cds = from_closes(closes, wicks=wicks, start=42.0)
|
|
crime, mb_open, half, third = check_base(cds)
|
|
low_after = min(c.l for c in cds[MB + 1:])
|
|
assert low_after > third + 1.0, "pullback-ul chiar nu trebuie să se apropie de 1/3"
|
|
print(f"D2 1/3 {third:.2f} 1/2 {half:.2f} cel mai jos low după marubozu {low_after:.2f} ultimul close {cds[-1].c:.2f}")
|
|
ch = Chart(candles=cds,
|
|
levels=[Level(round(third, 2), f"limit la 1/3: {third:.2f} — neatins", GRAY),
|
|
Level(half, f"1/2 = locul crimei {half:.2f} — neatins", ORANGE, label_at="left")],
|
|
marks=[Mark(PEAK, "fostul vârf", ORANGE, "above", dy=-24),
|
|
Mark(MB, "marubozu", RED, "below", dy=4),
|
|
Mark(31, "aici nu mai cumperi", GRAY, "below", dy=50)],
|
|
scale=24, title="Exemplu construit (fără ticker) — daily",
|
|
caption="Retestul nu vine. Ai ratat trade-ul, nu alergi după el.", **STYLE)
|
|
svgs["pullback_nu_vine"] = ch.render()
|
|
|
|
# ---------------------------------------------------------------- 3. gap: 1/2 gap pe fostul vârf
|
|
pre = [36.8, 37.9, 38.7, 39.9, 40.6, # 0-4, 4 = swing high 41.00
|
|
39.8, 39.0, 38.3, 38.8, 38.0, 38.9, 39.4, 39.1, 39.6] # 5-13, high-ul barei 13 = 40.00
|
|
pre_w = [(0.3, 0.3)] * 4 + [(0.4, 0.3)] + [(0.3, 0.3)] * 8 + [(0.4, 0.3)]
|
|
post = [43.6, 44.5, 45.2, 44.6, 43.5, # 14 gap up (open 42.40), 16 = vârful după gap
|
|
42.2, 41.4, # 19-20 pullback; 20 atinge cu codița 1/2 gap
|
|
42.5, # 21 trigger: close peste high-ul barei 20
|
|
43.4, 44.1, 43.8, 44.9, 45.7]
|
|
post_w = [(0.3, 0.4), (0.3, 0.3), (0.3, 0.3), (0.3, 0.3), (0.3, 0.3), (0.3, 0.3), (0.1, 0.4), (0.2, 0.2)] + [(0.3, 0.3)] * 5
|
|
cds = from_closes(pre, wicks=pre_w, start=36.0) + from_closes(post, wicks=post_w, start=42.4)
|
|
G, RT, TR, TOP = 14, 20, 21, 16
|
|
old_high = cds[4].h
|
|
gap_lo, gap_hi = cds[G - 1].h, cds[G].l
|
|
gap_half = (gap_lo + gap_hi) / 2
|
|
assert eq(old_high, 41.0) and all(c.h < old_high for c in cds[5:G]), "bara 4 nu e fostul vârf"
|
|
assert eq(gap_lo, 40.0) and eq(gap_hi, 42.0), (gap_lo, gap_hi)
|
|
assert eq(gap_half, old_high), "1/2 gap trebuie să coincidă cu fostul vârf"
|
|
assert eq(cds[RT].l, gap_half) and all(c.l >= gap_hi - 0.005 for c in cds[G:RT - 1]), "retestul nu intră în gap la 1/2"
|
|
assert cds[TR].c > cds[RT].h
|
|
entry = cds[TR].c
|
|
sl = round(gap_half - 0.6, 2)
|
|
tp = round(cds[TOP].h, 2)
|
|
risk = entry - sl
|
|
R = (tp - entry) / risk
|
|
hit = next(i for i in range(TR + 1, len(cds)) if cds[i].h >= tp)
|
|
cds = cds[:hit + 1]
|
|
assert R > 1.35, R
|
|
print(f"D3 gap {gap_lo:.2f}-{gap_hi:.2f} 1/2 {gap_half:.2f} entry {entry:.2f} SL {sl:.2f} TP {tp:.2f} = {R:.2f}R hit {hit}")
|
|
GAP = dict(lo=gap_lo, hi=gap_hi, half=gap_half, entry=entry, sl=sl, tp=tp, R=R)
|
|
ch = Chart(candles=cds,
|
|
levels=[Level(gap_hi, f"sus gap {gap_hi:.2f}", GRAY, label_at="left"),
|
|
Level(gap_half, f"1/2 gap = fostul vârf {gap_half:.2f}", ORANGE, label_at="left"),
|
|
Level(sl, f"SL {sl:.2f}", RED),
|
|
Level(entry, f"intrare {entry:.2f}", GREEN),
|
|
Level(tp, f"TP {tp:.2f} = {R:.1f}R", BLUE)],
|
|
marks=[Mark(G, f"gap {gap_lo:.0f}-{gap_hi:.0f}", BLUE, "below", dy=4),
|
|
Mark(RT, "retest 1/2 gap", GREEN, "below", dy=30)],
|
|
scale=40, title="Exemplu construit (fără ticker) — daily",
|
|
caption="Gap-ul deschis de sub preț e riscul; umplerea lui pe jumătate e intrarea.", **STYLE)
|
|
svg_ = ch.render()
|
|
assert abs(px_ratio(ch, sl, entry, tp) - R) < 0.01
|
|
svgs["gap_jumatate"] = svg_
|
|
|
|
# ---------------------------------------------------------------- 4. confluență de suporturi + ținta de 1/2
|
|
closes = [47.9, 48.8, 48.4, 49.6, 50.5, 50.1, 51.2, 51.7, # 0-7 uptrend, 7 = fostul vârf 52.00
|
|
50.9, 50.2, 49.8, 50.4, # 8-11 pullback
|
|
51.3, 52.4, 53.5, 54.6, 55.9, 57.1, # 12-17 breakout peste 52
|
|
56.4, 56.3, # 18-19 dip: swing low 56.00
|
|
57.2, 58.1, 58.9, 59.7, # 20-23 spre vârf 60.00
|
|
59.0, 58.1, 57.0, 56.5, # 24-27 cădere
|
|
57.1, 56.2, # 28-29 bounce mic din 56, apoi spart
|
|
55.1, 54.0, 53.1, 52.5, # 30-33 cădere în confluență
|
|
53.4, # 34 trigger
|
|
53.8, 54.5, 54.2, 55.3, 55.8] # 35-39
|
|
wicks = wk(len(closes), b8=(0.1, 0.3), b20=(0.3, 0.2), b24=(0.1, 0.3), b33=(0.1, 0.5), b34=(0.2, 0.2))
|
|
cds = from_closes(closes, wicks=wicks, start=47.2)
|
|
OLD, SWL, TOPI, RT, TR = 7, 19, 23, 33, 34
|
|
crime = cds[OLD].h
|
|
peak = cds[TOPI].h
|
|
support1 = cds[SWL].l
|
|
S2 = 52.00 # nivel pus de mine: S2 monthly în aceeași zonă
|
|
assert eq(crime, 52.0) and all(c.h < crime for c in cds[8:12]), crime
|
|
assert eq(peak, 60.0) and peak == max(c.h for c in cds), peak
|
|
assert eq(support1, 56.0) and cds[SWL].l < cds[18].l and cds[SWL].l < cds[20].l, "bara 19 nu e swing low"
|
|
brk1 = next(i for i in range(TOPI + 1, len(cds)) if cds[i].c < support1)
|
|
assert brk1 == 30 and all(c.l > support1 for c in cds[TOPI:28]), brk1
|
|
assert eq(cds[RT].l, crime) and all(c.l > crime for c in cds[TOPI:RT]), "retestul nu atinge confluența"
|
|
assert cds[TR].c > cds[RT].h
|
|
entry = cds[TR].c
|
|
sl = round(crime - 0.3, 2)
|
|
tp = round(crime + (peak - crime) / 2, 2)
|
|
risk = entry - sl
|
|
R = (tp - entry) / risk
|
|
assert eq(tp, support1), "ținta de 1/2 cade chiar pe suportul spart"
|
|
hit = next(i for i in range(TR + 1, len(cds)) if cds[i].h >= tp)
|
|
cds = cds[:hit + 1]
|
|
assert R >= 1.5, R
|
|
print(f"D4 vârf {peak:.2f} suport singur {support1:.2f} spart la {brk1} confluență {crime:.2f} entry {entry:.2f} "
|
|
f"SL {sl:.2f} TP {tp:.2f} = {R:.2f}R hit {hit}")
|
|
CONF = dict(peak=peak, s1=support1, crime=crime, entry=entry, sl=sl, tp=tp, R=R)
|
|
ch = Chart(candles=cds,
|
|
levels=[Level(peak, f"vârf {peak:.2f}", GRAY),
|
|
Level(tp, f"TP {tp:.2f} = 1/2 distanță = fost suport ({R:.1f}R)", BLUE, label_at="left"),
|
|
Level(entry, f"intrare {entry:.2f}", GREEN),
|
|
Level(sl, f"SL {sl:.2f}", RED)],
|
|
marks=[Mark(OLD, f"fostul vârf {crime:.2f}", ORANGE, "above", dy=-4),
|
|
Mark(SWL, "suport singur", GRAY, "below", dy=4),
|
|
Mark(RT, f"confluență {crime:.2f}: locul crimei + S2", GREEN, "below", dy=4)],
|
|
scale=22, title="Exemplu construit (fără ticker) — daily",
|
|
caption="Primul suport, singur, nu ține. Intri unde se suprapun două niveluri.",
|
|
**{**STYLE, "width": 760})
|
|
svg_ = ch.render()
|
|
assert abs(px_ratio(ch, sl, entry, tp) - R) < 0.01
|
|
svgs["confluenta_tinta"] = svg_
|
|
|
|
# ---------------------------------------------------------------- 5. bull flag în locul crimei, stop luat, reintrare
|
|
closes = [46.2, 47.0, 46.7, 47.8, 48.6, 48.3, 49.2, 49.6, # 0-7 uptrend, 7 = fostul vârf 50.00
|
|
48.9, 48.3, 48.7, 49.3, 48.8, # 8-12 consolidare
|
|
50.6, 51.9, 53.0, 54.1, 54.6, # 13-17 breakout, 17 = vârful 55.00
|
|
53.9, 54.2, 53.2, 53.5, 52.4, 52.7, 51.5, # 18-24 bull flag: lower highs
|
|
50.5, # 25 picior 1: codița atinge 50.00
|
|
51.8, # 26 trigger 1 → intrare 1
|
|
50.4, 49.7, # 27-28 picior 2 mai jos: ia SL 1
|
|
50.8, # 29 trigger 2: close peste high-ul barei 28 → intrare 2
|
|
51.2, 52.0, 51.7, 52.9, 53.8, 53.5, 54.7, 55.4] # 30-37
|
|
wicks = wk(len(closes), b7=(0.4, 0.3), b17=(0.4, 0.3), b25=(0.1, 0.5), b26=(0.2, 0.2), b28=(0.1, 0.7))
|
|
cds = from_closes(closes, wicks=wicks, start=45.6)
|
|
OLD, TOPI, L1, T1, L2, T2 = 7, 17, 25, 26, 28, 29
|
|
crime, top = cds[OLD].h, cds[TOPI].h
|
|
assert eq(crime, 50.0) and all(c.h < crime for c in cds[8:13]), crime
|
|
assert eq(top, 55.0) and top == max(c.h for c in cds[:30]), top
|
|
flag_highs = [cds[i].h for i in (19, 21, 23)]
|
|
assert flag_highs == sorted(flag_highs, reverse=True) and flag_highs[0] < top, "flag-ul nu are lower highs"
|
|
assert eq(cds[L1].l, crime), "piciorul 1 nu atinge locul crimei"
|
|
assert cds[T1].c > cds[L1].h
|
|
entry1 = cds[T1].c
|
|
sl1 = round(crime - 0.6, 2)
|
|
leg2 = cds[L2].l
|
|
assert leg2 < sl1 < cds[L1].l, "piciorul 2 trebuie să fie mai jos și să ia stop-ul 1"
|
|
assert cds[L2].c < crime and cds[T2].c > cds[L2].h and cds[T2].c > crime, "trigger 2 nu recuperează locul crimei"
|
|
entry2 = cds[T2].c
|
|
sl2 = round(leg2 - 0.3, 2)
|
|
tp = round(top, 2)
|
|
risk1, risk2 = entry1 - sl1, entry2 - sl2
|
|
R2 = (tp - entry2) / risk2
|
|
hit = next(i for i in range(T2 + 1, len(cds)) if cds[i].h >= tp)
|
|
cds = cds[:hit + 1]
|
|
assert R2 >= 1.95, R2
|
|
print(f"D5 locul crimei {crime:.2f} vârf {top:.2f} intrare1 {entry1:.2f} SL1 {sl1:.2f} (risc {risk1:.2f}) "
|
|
f"picior2 {leg2:.2f} intrare2 {entry2:.2f} SL2 {sl2:.2f} (risc {risk2:.2f}) TP {tp:.2f} = {R2:.2f}R hit {hit}")
|
|
FLAG = dict(crime=crime, top=top, e1=entry1, sl1=sl1, leg2=leg2, e2=entry2, sl2=sl2, tp=tp, R2=R2, r1=risk1, r2=risk2)
|
|
ch = Chart(candles=cds,
|
|
levels=[Level(tp, f"TP {tp:.2f} = vârful = {R2:.1f}R", BLUE),
|
|
Level(entry2, f"intrare 2: {entry2:.2f}", GREEN),
|
|
Level(crime, f"locul crimei {crime:.2f}", ORANGE, label_at="left"),
|
|
Level(sl1, f"SL 1 {sl1:.2f} (lovit)", GRAY),
|
|
Level(sl2, f"SL 2 {sl2:.2f}", RED)],
|
|
marks=[Mark(OLD, "fostul vârf", ORANGE, "above", dy=-24),
|
|
Mark(20, "bull flag", GRAY, "below", dy=14),
|
|
Mark(T1, "intrare 1", GREEN, "above", dy=-30),
|
|
Mark(L2, "picior 2", RED, "below", dy=4)],
|
|
scale=34, title="Exemplu construit (fără ticker) — daily",
|
|
caption="Stop-ul 1 e luat de al doilea picior. Reintri la trigger, cu același risc în bani.", **STYLE)
|
|
svg_ = ch.render()
|
|
assert abs(px_ratio(ch, sl2, entry2, tp) - R2) < 0.01
|
|
svgs["bull_flag_reintrare"] = svg_
|
|
|
|
# ---------------------------------------------------------------- 6. inversul: fostul suport devine rezistență (short)
|
|
closes = [57.6, 56.9, 57.3, 56.4, 55.6, 55.9, 54.8, 54.3, # 0-7 downtrend, 7 = fostul swing low 54.00
|
|
55.0, 55.6, 55.2, 54.8, 55.3, # 8-12 consolidare deasupra
|
|
51.6, # 13 marubozu de breakdown
|
|
51.0, 51.3, 52.2, 53.0, # 14-17 pullback în sus
|
|
53.5, # 18 retest: codița atinge 54.00 de dedesubt
|
|
52.5, # 19 sell trigger: close sub low-ul barei 18
|
|
51.8, 52.2, 51.1, 50.4, 50.8, 49.6, 49.0, 49.4, 48.3, 47.9, 48.2, 47.3]
|
|
wicks = wk(len(closes), b8=(0.3, 0.1), b13=(0.1, 0.1), b18=(0.5, 0.2), b19=(0.2, 0.2))
|
|
cds = from_closes(closes, wicks=wicks, start=58.2)
|
|
SWL, MBD, RT, TR = 7, 13, 18, 19
|
|
old_low = cds[SWL].l
|
|
assert eq(old_low, 54.0) and all(c.l > old_low for c in cds[8:13]), old_low
|
|
assert cds[MBD].c < old_low and (cds[MBD].o - cds[MBD].c) > 3, "bara 13 nu e breakdown"
|
|
assert eq(cds[RT].h, old_low) and all(c.h < old_low for c in cds[MBD + 1:RT]), "retestul nu atinge fostul suport"
|
|
assert cds[TR].c < cds[RT].l, "sell trigger nu închide sub low-ul barei de retest"
|
|
entry = cds[TR].c
|
|
sl = round(old_low + 1.0, 2)
|
|
risk = sl - entry
|
|
tp = round(entry - 2 * risk, 2)
|
|
hit = next(i for i in range(TR + 1, len(cds)) if cds[i].l <= tp)
|
|
cds = cds[:hit + 1]
|
|
print(f"D6 fost suport {old_low:.2f} retest high {cds[RT].h:.2f} entry {entry:.2f} SL {sl:.2f} risc {risk:.2f} TP {tp:.2f} hit {hit}")
|
|
SHORT = dict(lvl=old_low, entry=entry, sl=sl, tp=tp)
|
|
ch = Chart(candles=cds,
|
|
levels=[Level(sl, f"SL {sl:.2f}", RED),
|
|
Level(old_low, f"fost suport = rezistență {old_low:.2f}", ORANGE),
|
|
Level(entry, f"intrare short {entry:.2f}", GREEN, label_at="left"),
|
|
Level(tp, f"TP {tp:.2f} = 2R", BLUE)],
|
|
marks=[Mark(MBD, "breakdown", RED, "below", dy=16),
|
|
Mark(RT, "retest de jos", ORANGE, "above", dy=-26)],
|
|
scale=28, title="Exemplu construit (fără ticker) — daily",
|
|
caption=f"Fostul suport {old_low:.2f}, spart, respinge prețul la retest.", **STYLE)
|
|
svg_ = ch.render()
|
|
assert abs(px_ratio(ch, sl, entry, tp) - 2.0) < 0.01
|
|
svgs["invers_short"] = svg_
|
|
|
|
# ---------------------------------------------------------------- substituție în .md
|
|
if MD.exists():
|
|
md = MD.read_text(encoding="utf-8")
|
|
for name, svg in svgs.items():
|
|
pat = re.compile(rf"<!--SVG:{name}-->.*?<!--/SVG-->", re.S)
|
|
assert pat.search(md), f"marcatorul {name} lipsește din .md"
|
|
md = pat.sub(lambda _m: f"<!--SVG:{name}-->\n{svg}\n<!--/SVG-->", md)
|
|
MD.write_text(md, encoding="utf-8")
|
|
print("scris", MD)
|