buy_doji: refacut ca tutorial pe 3 niveluri, cu diagrame adnotate
Userul a respins mostra de format tutorial: prea lunga, prea multe trimiteri in text, termeni tehnici nedefiniti, prea multe cifre fara desen. Cerintele lui sunt acum contract in STYLE.md (FORMAT TUTORIAL v2). Textul: 8430 -> 4190 de cuvinte. Nivel 1 (din avion, cu mini-glosar) -> nivel 2 (6 pasi mecanici, o diagrama fiecare) -> oglinda short -> nivel 3 (rafinari). Trimiterile fisier:linie ies din corp si se aduna la final, cu legenda de prescurtari. Doua tabele in tot documentul. Toate cele 14 diagrame refacute: zone dreptunghiulare de evidentiere si sageti care spun si de ce, nu doar ce. svg_candles primeste Zone si Arrow; render() nu mai muta obiectul la iesire. Trei corecturi de continut: - un singur trade prin toata procedura (doji 50.10/49.25, intrare 50.40, stop 49.20, 1R 1.20, TP1 51.60); seria e acum o functie comuna, importata de stop/transe/iesire - short: stopul urca la 100.15, ca sa fie deasupra maximului cum cere regula oglindita; cifra cursului ramane in text ca observatie - LABU: trei minime dar doua atingeri ale pivotului 86.00, ceea ce impaca "triple bottom" cu "al doilea test", ambele din transcriere Reparat si bug-ul vechi din buy_doji_sell.svg: titlul peste eticheta dojiului. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ThN7ixZrowca8aTGrK1MUJ
This commit is contained in:
@@ -1,222 +1,150 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Buy Doji — ieșirea: trailing stop pe gap-uri și limita de 8 candele.
|
||||
"""Buy Doji — ieșirea: prima treime la țintă, a doua pe trailing stop, limita de 8 candele.
|
||||
|
||||
Citește `gen/strategii/buy_doji_iesire.spec.txt` și scrie
|
||||
`gen/strategii/buy_doji_iesire.svg`: două panouri stivuite.
|
||||
Citește `gen/strategii/buy_doji_iesire.spec.txt`. Barele vin din
|
||||
`buy_doji_ansamblu.serie()` — același trade ca în restul capitolului
|
||||
(doji 50.10/49.25, intrare 50.40, stop 49.20, ținta 1 51.60).
|
||||
|
||||
A — seria din buy_doji_transe (barele 0-18) continuată cu 19-23. Barele 19
|
||||
și 21 deschid cu GAP (excepție cerută de spec): regula de trail cere o
|
||||
bară cu low peste high-ul precedentei, imposibil dacă open = close
|
||||
anterior. De aceea barele 15-23 sunt construite explicit, nu cu
|
||||
`from_closes`.
|
||||
B — doji 50.10 / 49.20, intrare 50.40, apoi 8 candele fără TP și fără SL.
|
||||
A — prima treime iese la ținta 51.60; apoi două candele cu gap urcă linia
|
||||
de trailing stop (51.95, apoi 52.60); a doua treime iese la prima
|
||||
închidere sub linie, 52.30 (+1.58R).
|
||||
B — același setup, dar prețul stă 8 candele între stop și țintă: ieși la
|
||||
close-ul celei de-a 8-a, 50.70 (+0.25R).
|
||||
|
||||
Barele de după intrare, în ambele panouri, sunt CONSTRUITE.
|
||||
Sursa regulilor: summaries/03_MODUL_2/13_strategia_3_byh.md:196-213.
|
||||
|
||||
Rulare: python3 gen/strategii/buy_doji_iesire.py
|
||||
Barele de după intrare din panoul B sunt CONSTRUITE.
|
||||
"""
|
||||
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
|
||||
sys.path.insert(0, str(Path(__file__).parent))
|
||||
from svg_candles import Chart, Level, Zone, Arrow, from_closes # noqa: E402
|
||||
from buy_doji_ansamblu import serie, CONF, ENTRY, SL, TP1, R, trail # noqa: E402
|
||||
|
||||
OUT = Path(__file__).with_suffix(".svg")
|
||||
W = 660
|
||||
VGAP = 10
|
||||
S = 10 # prima bară arătată
|
||||
GREEN, RED, ORANGE, BLUE, GREY = "#188a3e", "#c00", "#b06000", "#1a5fb4", "#555"
|
||||
W, VGAP = 760, 14
|
||||
|
||||
# --- PANOUL A ----------------------------------------------------------------
|
||||
ENTRY_A, SL_A, TP1_A = 53.70, 51.55, 55.85
|
||||
R_A = round(ENTRY_A - SL_A, 2) # 2.15
|
||||
full = serie()
|
||||
steps, iesire = trail(full) # [(21, 51.95), (27, 52.60)], 28
|
||||
a = full[S:iesire + 1]
|
||||
cf = CONF - S
|
||||
EXIT_A = a[iesire - S].c
|
||||
R_A = round((EXIT_A - ENTRY) / R, 2)
|
||||
tp_i = next(i for i in range(CONF + 1, len(full)) if full[i].h >= TP1)
|
||||
|
||||
a = from_closes([60.40, 59.60, 59.90, 58.80, 58.10, 58.40, 57.20, 56.30,
|
||||
56.60, 55.30, 54.20, 52.40], wick=0.15, start=61.00)
|
||||
a += [Candle(*ohlc) for ohlc in [
|
||||
(52.40, 53.00, 51.60, 52.30), # 12 doji
|
||||
(52.30, 52.85, 52.10, 52.60), # 13 +1
|
||||
(52.60, 53.85, 52.45, 53.70), # 14 confirmare = intrare
|
||||
(53.70, 53.80, 53.10, 53.45), # 15
|
||||
(53.45, 54.20, 53.35, 54.10), # 16
|
||||
(54.10, 54.85, 54.00, 54.70), # 17
|
||||
(54.70, 56.00, 54.60, 55.90), # 18 atinge TP1
|
||||
(56.30, 56.95, 56.20, 56.80), # 19 GAP
|
||||
(56.80, 57.35, 56.60, 57.20), # 20
|
||||
(57.60, 58.10, 57.45, 57.90), # 21 GAP
|
||||
(57.90, 58.00, 57.50, 57.60), # 22
|
||||
(57.60, 57.70, 57.10, 57.20), # 23 close sub trail
|
||||
]]
|
||||
assert len(a) == 24
|
||||
# --- aserțiunile panoului A -------------------------------------------------------
|
||||
assert steps == [(21, 51.95), (27, 52.60)] and iesire == 28, (steps, iesire)
|
||||
for i, t in steps: # gap-urile sunt reale
|
||||
assert full[i].l > full[i - 1].h, f"bara {i} nu deschide cu gap"
|
||||
assert all(full[i].c >= steps[0][1] for i in range(steps[0][0], iesire)), \
|
||||
"ar fi ieșit mai devreme: un close sub linia de trail"
|
||||
assert EXIT_A == 52.30 and R_A == 1.58 and EXIT_A < steps[-1][1]
|
||||
assert tp_i == 19 and full[tp_i].h >= TP1 > full[tp_i - 1].h, tp_i
|
||||
|
||||
for c in a:
|
||||
assert c.l <= min(c.o, c.c) and c.h >= max(c.o, c.c), "h/l nu cuprind o/c"
|
||||
# A1. gap-urile reale
|
||||
assert a[19].l > a[18].h and a[21].l > a[20].h, "barele de gap nu au low > high anterior"
|
||||
# A2. open = close anterior, în afară de 19 și 21 (și 0-14, ca în transe)
|
||||
for i in range(1, 24):
|
||||
if i not in (19, 21):
|
||||
assert abs(a[i].o - a[i - 1].c) < 1e-9, f"A: open[{i}] != close[{i-1}]"
|
||||
# regula de trail, calculată pe serie: primul gap după intrare stabilește
|
||||
# trail-ul, fiecare gap următor îl urcă; ieși la primul close sub el
|
||||
trail, steps, exit_i = None, [], None
|
||||
for i in range(15, 24):
|
||||
if trail is not None and a[i].c < trail:
|
||||
exit_i = i
|
||||
break
|
||||
if a[i].l > a[i - 1].h:
|
||||
trail = a[i].l
|
||||
steps.append((i, trail))
|
||||
assert [(i, round(t, 2)) for i, t in steps] == [(19, 56.20), (21, 57.45)], steps
|
||||
# A3.
|
||||
assert a[22].c >= 57.45 and a[23].c < 57.45 and exit_i == 23, exit_i
|
||||
EXIT_A = a[23].c
|
||||
# A4.
|
||||
assert R_A == 2.15 and round((EXIT_A - ENTRY_A) / R_A, 2) == 1.63
|
||||
# A5. TP1 = 1R, atins prima oară la bara 18
|
||||
assert round(ENTRY_A + R_A, 2) == TP1_A
|
||||
assert next(i for i in range(15, 24) if a[i].h >= TP1_A) == 18
|
||||
assert abs(a[14].c - ENTRY_A) < 1e-9
|
||||
# --- panoul B: 8 candele între stop și țintă --------------------------------------
|
||||
b = full[S:CONF + 1]
|
||||
b += from_closes([50.60, 50.30, 50.90, 50.50, 51.10, 50.80, 51.20, 50.70],
|
||||
wick=0.15, start=ENTRY)
|
||||
opt = b[cf + 1:]
|
||||
EXIT_B = opt[-1].c
|
||||
R_B = round((EXIT_B - ENTRY) / R, 2)
|
||||
assert len(opt) == 8, len(opt)
|
||||
for i in range(1, len(b)):
|
||||
assert abs(b[i].o - b[i - 1].c) < 1e-9, f"B: open[{i}] != close[{i-1}]"
|
||||
assert all(c.h < TP1 and c.l > SL for c in opt), "B: o bară atinge ținta sau stopul"
|
||||
assert (round(max(c.h for c in opt), 2), round(min(c.l for c in opt), 2)) == (51.35, 50.15)
|
||||
assert EXIT_B == 50.70 and R_B == 0.25
|
||||
print(f"iesire A: treapta 1 {steps[0][1]:.2f}, treapta 2 {steps[1][1]:.2f}, "
|
||||
f"ieșire {EXIT_A:.2f} = +{R_A}R (ținta 1 {TP1:.2f} la bara {tp_i}); "
|
||||
f"B: ieșire {EXIT_B:.2f} = +{R_B}R")
|
||||
|
||||
common: dict = dict(scale=40.0, gap=18, body_w=9, left=48, width=W)
|
||||
|
||||
ch_a = Chart(
|
||||
candles=a,
|
||||
levels=[Level(ENTRY_A, "intrare 53.70", GREEN),
|
||||
Level(TP1_A, "TP1 55.85 — tranșa 1: +1R", GREEN, dash="2 3")],
|
||||
scale=40, gap=16, body_w=9, left=44, width=W,
|
||||
title="A — trailing stop pe gap-uri (seria de la intrarea în tranșe)",
|
||||
caption="a treia treime: o lași mai departe (break-even / trend)",
|
||||
levels=[Level(TP1, "ținta 1: 51.60 (= 1R)", GREEN, "2 3"),
|
||||
Level(ENTRY, "intrarea: 50.40", GREY, "4 3"),
|
||||
Level(SL, "stopul: 49.20", RED, "4 3")],
|
||||
zones=[Zone(cf, tp_i - S, "", GREEN), Zone(tp_i - S + 1, iesire - S, "", BLUE)],
|
||||
arrows=[
|
||||
Arrow(tp_i - S, "ținta 1 atinsă: vinzi prima treime\nai luat exact cât riscai (1R)",
|
||||
where="above", price=TP1, color=GREEN, dx=10, dy=-26),
|
||||
Arrow(steps[0][0] - S, "gap: low-ul de azi e peste high-ul de ieri\nlinia de trail urcă la 51.95",
|
||||
where="below", price=steps[0][1], color=ORANGE, dx=130, dy=40),
|
||||
Arrow(steps[1][0] - S, "încă un gap: linia urcă la 52.60\nurcă doar, nu coboară niciodată",
|
||||
where="above", price=steps[1][1], color=ORANGE, dx=150),
|
||||
Arrow(iesire - S, "prima închidere sub linie: ieși\ncu a doua treime, la 52.30 (+1.58R)",
|
||||
where="below", price=EXIT_A, color=RED, dx=110, dy=96),
|
||||
],
|
||||
title="A — prima treime la țintă, a doua pe trailing stop",
|
||||
**common,
|
||||
)
|
||||
|
||||
# --- PANOUL B ----------------------------------------------------------------
|
||||
ENTRY_B, SL_B, TP1_B = 50.40, 49.20, 51.60
|
||||
R_B = round(ENTRY_B - SL_B, 2) # 1.20
|
||||
|
||||
b = from_closes([52.20, 51.40, 50.60, 49.65], wick=0.15, start=53.00)
|
||||
b += [Candle(*ohlc) for ohlc in [
|
||||
(49.65, 50.10, 49.20, 49.60), # 4 doji
|
||||
(49.60, 50.05, 49.45, 49.90), # 5 +1
|
||||
(49.90, 50.55, 49.75, 50.40), # 6 confirmare = intrare
|
||||
]]
|
||||
b += from_closes([50.60, 50.30, 50.90, 50.50, 51.10, 50.80, 51.20, 50.70],
|
||||
wick=0.15, start=b[-1].c)
|
||||
assert len(b) == 15
|
||||
# B1.
|
||||
for i in range(1, 15):
|
||||
assert abs(b[i].o - b[i - 1].c) < 1e-9, f"B: open[{i}] != close[{i-1}]"
|
||||
d = b[4]
|
||||
assert abs(d.o - d.c) <= 0.10 * (d.h - d.l) and (d.h, d.l) == (50.10, 49.20)
|
||||
assert b[6].c > d.h and abs(b[6].c - ENTRY_B) < 1e-9
|
||||
# B2. 8 candele fără TP și fără SL
|
||||
after = b[7:15]
|
||||
assert all(c.h < TP1_B and c.l > SL_B for c in after)
|
||||
assert round(max(c.h for c in after), 2) == 51.35 and round(min(c.l for c in after), 2) == 50.15
|
||||
assert round(ENTRY_B + R_B, 2) == TP1_B
|
||||
# B3.
|
||||
EXIT_B = b[14].c
|
||||
assert round((EXIT_B - ENTRY_B) / R_B, 2) == 0.25
|
||||
|
||||
for k, c in enumerate(after, 1):
|
||||
c.tag = str(k)
|
||||
|
||||
ch_b = Chart(
|
||||
candles=b,
|
||||
levels=[Level(TP1_B, "TP1 51.60", GREEN, dash="2 3"),
|
||||
Level(ENTRY_B, "intrare 50.40", GREEN),
|
||||
Level(SL_B, "SL 49.20", RED)],
|
||||
marks=[Mark(4, "doji", ORANGE, "below")],
|
||||
scale=48, gap=16, body_w=9, left=44, width=W,
|
||||
title="B — limita de 8 candele",
|
||||
levels=[Level(TP1, "ținta 1: 51.60 — neatinsă", GREEN, "2 3"),
|
||||
Level(ENTRY, "intrarea: 50.40", GREY, "4 3"),
|
||||
Level(SL, "stopul: 49.20 — neatins", RED, "4 3")],
|
||||
zones=[Zone(cf + 1, len(b) - 1, "", GREY, p_top=TP1, p_bot=SL)],
|
||||
arrows=[
|
||||
Arrow(len(b) - 1, "a 8-a candelă: ieși oricum, la close 50.70 (+0.25R)\nbanii blocați aici lipsesc din alt trade",
|
||||
where="below", price=EXIT_B, color=GREY, dx=120, dy=40),
|
||||
],
|
||||
title="B — limita de 8 candele: nici ținta, nici stopul",
|
||||
**common,
|
||||
)
|
||||
for k, c in enumerate(opt, 1):
|
||||
c.tag = str(k)
|
||||
|
||||
|
||||
# --- etichete plasate de mână: Mark centrează textul, iar aici textele lungi
|
||||
# ar cădea peste candelele vecine. Le ancorez lângă bară, pe partea liberă.
|
||||
def label(ch, i, price, text, color, anchor="start", dx=0):
|
||||
x = ch._cx(i) + dx
|
||||
return (f'<text x="{x:.1f}" y="{ch._y(price):.1f}" fill="{color}" '
|
||||
f'text-anchor="{anchor}" style="font-weight:bold">{text}</text>')
|
||||
def panou(ch, sus_de=0.0):
|
||||
ch.top += sus_de
|
||||
svg = ch.render()
|
||||
h = float(re.search(r'viewBox="0 0 [\d.]+ ([\d.]+)"', svg).group(1))
|
||||
# render() mută `top`/`left` intern și le restaurează: tot ce desenăm noi
|
||||
# după randare trebuie deplasat cu aceiași pixeli
|
||||
off_y = float(re.search(r'<line x1="24" y1="([\d.]+)"', svg).group(1)) - ch._y(ch.levels[0].price)
|
||||
off_x = float(re.search(r'<line x1="([\d.]+)"[^>]*stroke="#1a1a1a"', svg).group(1)) - ch._cx(0)
|
||||
return svg[svg.index(">") + 1:svg.rindex("</svg>")].strip(), h, off_x, off_y
|
||||
|
||||
|
||||
def panel(ch, extra):
|
||||
svg = ch.render() # render() fixează scala (_pmax, top)
|
||||
h = float(re.search(r'viewBox="0 0 \d+ ([\d.]+)"', svg).group(1))
|
||||
body = svg[svg.index(">") + 1:svg.rindex("</svg>")].strip()
|
||||
return body + "".join("\n " + e for e in extra(ch)), h
|
||||
body_a, h_a, ax, ay = panou(ch_a)
|
||||
Y, X = (lambda p: ch_a._y(p) + ay), (lambda i: ch_a._cx(i) + ax)
|
||||
(i1, t1), (i2, t2) = [(i - S, t) for i, t in steps]
|
||||
extra_a = [
|
||||
# linia de trail, în trepte
|
||||
f'<polyline points="{X(i1) - 7:.1f},{Y(t1):.1f} {X(i2) - 7:.1f},{Y(t1):.1f} '
|
||||
f'{X(i2) - 7:.1f},{Y(t2):.1f} {X(iesire - S) + 7:.1f},{Y(t2):.1f}" '
|
||||
f'fill="none" stroke="{ORANGE}" stroke-width="2"/>',
|
||||
f'<text x="{X(iesire - S) + 14:.1f}" y="{Y(t2) + 4:.1f}" fill="{ORANGE}" style="font-weight:bold">'
|
||||
f'linia de trailing stop</text>',
|
||||
# etichetele zonelor, lângă ele (Zone le-ar ridica deasupra candelelor vecine)
|
||||
f'<text x="{X(cf) - 6:.1f}" y="{Y(SL) + 60:.1f}" fill="{GREEN}" style="font-weight:bold">'
|
||||
f'prima treime: de la intrare la ținta 1:1</text>',
|
||||
f'<text x="{X(tp_i - S + 1) - 6:.1f}" y="{Y(SL) + 78:.1f}" fill="{BLUE}" style="font-weight:bold">'
|
||||
f'a doua treime: merge pe trailing stop</text>',
|
||||
]
|
||||
extra_a.append(f'<text x="24" y="{Y(SL) + 100:.1f}" fill="{GREY}">A treia treime o lași mai '
|
||||
f'departe, cu stopul mutat la intrare (break-even).</text>')
|
||||
h_a = max(h_a, Y(SL) + 112)
|
||||
|
||||
|
||||
def trail_a(ch):
|
||||
x19, x21, x23 = ch._cx(19) - 7, ch._cx(21) - 8, ch._cx(23) + 7
|
||||
y1, y2 = ch._y(steps[0][1]), ch._y(steps[1][1])
|
||||
# golul dintre high-ul barei precedente și low-ul barei de gap, hașurat:
|
||||
# la 0.10-0.20 de preț are doar câțiva pixeli și se pierde sub linia de trail
|
||||
gaps = [f'<rect x="{ch._cx(i - 1) - 7:.1f}" y="{ch._y(a[i].l):.1f}" width="{ch.gap + 14}" '
|
||||
f'height="{ch._y(a[i - 1].h) - ch._y(a[i].l):.1f}" fill="{ORANGE}" opacity="0.35"/>'
|
||||
for i, _ in steps]
|
||||
return gaps + [
|
||||
f'<polyline points="{x19:.1f},{y1:.1f} {x21:.1f},{y1:.1f} {x21:.1f},{y2:.1f} '
|
||||
f'{x23:.1f},{y2:.1f}" fill="none" stroke="{ORANGE}" stroke-width="2"/>',
|
||||
label(ch, 18, 56.25, "TP1: ieși cu 1/3", GREEN, "end", 4),
|
||||
label(ch, 19, 55.35, "gap: low > high anterior → trail 56.20", ORANGE, "start", -4),
|
||||
label(ch, 21, 58.35, "gap → trail 57.45", ORANGE, "middle"),
|
||||
label(ch, 23, 57.30, "close sub trail: ieși cu 1/3", RED, "start", 14),
|
||||
label(ch, 23, 56.88, f"la 57.20 = +{(EXIT_A - ENTRY_A) / R_A:.2f}R", RED, "start", 14),
|
||||
]
|
||||
|
||||
|
||||
def mark_b(ch):
|
||||
return [
|
||||
label(ch, 14, 51.05, "a 8-a candelă: nici TP, nici SL", GREY, "start", 10),
|
||||
label(ch, 14, 50.75, f"→ ieși la 50.70 = +{(EXIT_B - ENTRY_B) / R_B:.2f}R", GREY, "start", 10),
|
||||
]
|
||||
|
||||
|
||||
# panourile se stivuiesc prin `top` (nu prin <g transform>): audit_diagrams
|
||||
# citește coordonatele brute și ar vedea panourile suprapuse.
|
||||
T = 26
|
||||
ch_a.top += T
|
||||
body_a, h_a = panel(ch_a, trail_a)
|
||||
ch_b.top += h_a + VGAP
|
||||
body_b, H_B = panel(ch_b, mark_b)
|
||||
TITLE = "Ieșirea: trailing stop pe gap-uri și limita de 8 candele — exemple construite"
|
||||
CAP = "Barele de după intrare sunt construite. Regulile: 13_strategia_3_byh.md:196-213."
|
||||
cap_y = H_B + 12
|
||||
H = cap_y + 10
|
||||
body_b, h_b, bx, by = panou(ch_b, h_a + VGAP)
|
||||
extra_b = [
|
||||
f'<text x="{ch_b._cx(cf + 1) + bx - 6:.1f}" y="{ch_b._y(TP1) + by - 8:.1f}" fill="{GREY}" '
|
||||
f'style="font-weight:bold">8 candele în care nu se întâmplă nimic</text>',
|
||||
]
|
||||
H = h_b + 8
|
||||
svg = "\n ".join([
|
||||
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'<text x="24" y="18" fill="#333" style="font-weight:bold">{TITLE}</text>',
|
||||
body_a,
|
||||
body_b,
|
||||
f'<text x="24" y="{cap_y:.0f}" fill="{GREY}">{CAP}</text>',
|
||||
body_a, *extra_a,
|
||||
f'<line x1="24" y1="{h_a + VGAP / 2:.0f}" x2="{W - 14}" y2="{h_a + VGAP / 2:.0f}" stroke="#ddd"/>',
|
||||
body_b, *extra_b,
|
||||
]) + "\n</svg>"
|
||||
assert "\n\n" not in svg
|
||||
|
||||
# --- etichete în panză și fără suprapuneri (lățimi aproximate) ---------------
|
||||
TEXT = re.compile(r'<text x="([-\d.]+)" y="([-\d.]+)"([^>]*)>(.*?)</text>', re.S)
|
||||
|
||||
|
||||
def texts(body, y_off, tag):
|
||||
for m in TEXT.finditer(body):
|
||||
x, y, attrs = float(m.group(1)), float(m.group(2)) + y_off, m.group(3)
|
||||
s = m.group(4).replace(">", ">")
|
||||
wpx = len(s) * 12 * (0.62 if "bold" in attrs else 0.52)
|
||||
anchor = re.search(r'text-anchor="(\w+)"', attrs)
|
||||
anchor = anchor.group(1) if anchor else "start"
|
||||
x0 = x - wpx if anchor == "end" else (x - wpx / 2 if anchor == "middle" else x)
|
||||
yield x0, x0 + wpx, y, s, tag
|
||||
|
||||
|
||||
labels = list(texts(body_a, 0, "A")) + list(texts(body_b, 0, "B"))
|
||||
for x0, x1, y, s, tag in labels:
|
||||
assert x0 >= -2 and x1 <= W + 2 and 0 <= y <= H, f"[{tag}] text iese din panză: {s!r}"
|
||||
for i, (l1, r1, y1, s1, t1) in enumerate(labels):
|
||||
for l2, r2, y2, s2, t2 in labels[i + 1:]:
|
||||
assert not (t1 == t2 and abs(y1 - y2) < 12 and l1 < r2 - 4 and l2 < r1 - 4), \
|
||||
f"[{t1}] texte suprapuse: {s1!r} / {s2!r}"
|
||||
|
||||
assert "\n\n" not in svg, "linie goală în SVG"
|
||||
for tx, ty in re.findall(r'<text x="([-\d.]+)" y="([-\d.]+)"', svg):
|
||||
assert 0 <= float(tx) <= W and 0 <= float(ty) <= H, f"text în afara pânzei la ({tx}, {ty})"
|
||||
OUT.write_text(svg, encoding="utf-8")
|
||||
print(f"A: trail {steps}, ieșire bara {exit_i} la {EXIT_A:.2f} = "
|
||||
f"+{(EXIT_A - ENTRY_A) / R_A:.2f}R; B: ieșire la {EXIT_B:.2f} = "
|
||||
f"+{(EXIT_B - ENTRY_B) / R_B:.2f}R")
|
||||
print(f"scris {OUT} ({W}x{H:.0f})")
|
||||
|
||||
Reference in New Issue
Block a user