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:
@@ -23,7 +23,7 @@ import sys
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, "/workspace/atm-curs-dl")
|
||||
from svg_candles import Candle, Chart, Level, Mark, MA, from_closes # noqa: E402
|
||||
from svg_candles import Arrow, Candle, Chart, Level, Zone, from_closes # noqa: E402
|
||||
|
||||
OUT = Path(__file__).resolve().parent / "buy_doji_aplus.svg"
|
||||
|
||||
@@ -118,19 +118,25 @@ PW = 560 # lățimea fiecărui panou
|
||||
ch_a = Chart(
|
||||
candles=cds_a,
|
||||
levels=[Level(42.00, "suport 42.00", GREEN, label_at="left")],
|
||||
marks=[Mark(DOJI_A, "doji la nivel (low 41.95)", ORANGE, "below"),
|
||||
Mark(CONF_A, "acumulare", GREEN, "above", dy=-6)],
|
||||
zones=[Zone(DOJI_A, DOJI_A, "", GREEN, p_top=43.2, p_bot=41.7)],
|
||||
arrows=[Arrow(DOJI_A, "doji chiar pe nivel:\nvânzarea s-a oprit pe suport",
|
||||
where="below", color=GREEN, dx=-40),
|
||||
Arrow(CONF_A, "confirmarea, pe volum mare:\ncumpărătorii mari intră",
|
||||
where="above", color=GREEN, dx=60)],
|
||||
mas=[], scale=22.0, gap=15, body_w=9, left=44, width=PW, vol_h=60,
|
||||
vol_label="volum", title="A+ — doji la suport, volum în acumulare",
|
||||
caption="acumulare: volum peste media ultimelor 10 bare.",
|
||||
)
|
||||
ch_n = Chart(
|
||||
candles=cds_n,
|
||||
levels=[Level(78.50, "rezistență 78.50", RED, label_at="right")],
|
||||
marks=[Mark(DOJI_N, "doji 78.50 / 78.10", ORANGE, "below", dy=93)],
|
||||
# eticheta zonei ar urca peste titlul panoului (zona e chiar în vârf):
|
||||
# textul stă pe săgeata din stânga
|
||||
zones=[Zone(DOJI_N - 1, CONF_N, "", RED, p_top=79.62)],
|
||||
arrows=[Arrow(DOJI_N, "gap-up în rezistență:\naici cumpără cei întârziați",
|
||||
where="left", color=RED, dx=-30, dy=30),
|
||||
Arrow(18, "cade înapoi sub 78.00:\nsemnalul a mințit", where="below", color=RED, dx=40)],
|
||||
mas=[], scale=22.0, gap=15, body_w=9, left=44, width=PW, vol_h=60,
|
||||
vol_label="volum", title="Mediocru — gap-up în rezistență, volum varză",
|
||||
caption="gap-up + doji în rezistență, confirmare pe volum slab.",
|
||||
)
|
||||
|
||||
# ============================================================ wrapper: două panouri alăturate
|
||||
@@ -145,20 +151,51 @@ def vb_height(svg: str) -> float:
|
||||
return float(m.group(1))
|
||||
|
||||
|
||||
PANEL_GAP, PAD = 24, 8
|
||||
TITLE_H, FOOTER_H = 30, 22
|
||||
svg_a, svg_n = ch_a.render(), ch_n.render()
|
||||
def vol_note(svg: str, i0: int, i1: int, text: str, color: str, box: bool) -> str:
|
||||
"""Evidențiere în panoul de volum (Zone/Arrow din svg_candles lucrează doar pe preț).
|
||||
|
||||
# „gap-up, volum slab" stă în colțul liber din stânga-sus, cu linie indicatoare
|
||||
# spre golul dintre bara 13 (high 76.80) și dojiul barei 14 (open 78.20).
|
||||
GAP_X = (ch_n._cx(DOJI_N - 1) + ch_n._cx(DOJI_N)) / 2
|
||||
GAP_Y = ch_n._y(77.50)
|
||||
gap_ann = (
|
||||
f'<text x="28" y="120" fill="{RED}" style="font-weight:bold">gap-up, volum slab</text>'
|
||||
f'<line x1="152" y1="116" x2="{GAP_X:.1f}" y2="{GAP_Y:.1f}" stroke="{RED}" stroke-width="1"/>'
|
||||
f'<circle cx="{GAP_X:.1f}" cy="{GAP_Y:.1f}" r="2.5" fill="{RED}"/>'
|
||||
)
|
||||
svg_n = svg_n.replace("</svg>", gap_ann + "</svg>")
|
||||
Coordonatele se citesc din SVG-ul randat (barele de volum sunt rect-urile cu
|
||||
opacity 0.55, în ordinea candelelor), ca să nu depindă de deplasarea internă
|
||||
a lui Chart. Eticheta stă sub linia de bază, unde nu acoperă nimic;
|
||||
`box` = dreptunghi peste bare, altfel săgeată de jos în sus, sub bară.
|
||||
"""
|
||||
bars = [tuple(map(float, m)) for m in re.findall(
|
||||
r'<rect x="([\d.]+)" y="([\d.]+)" width="([\d.]+)" height="([\d.]+)" '
|
||||
r'fill="#[0-9a-f]+" opacity="0.55"/>', svg)]
|
||||
base = bars[0][1] + bars[0][3]
|
||||
x0, x1 = bars[i0][0] - 4, bars[i1][0] + bars[i1][2] + 4
|
||||
top = min(b[1] for b in bars[i0:i1 + 1]) - 4
|
||||
if box:
|
||||
mark = (f'<rect x="{x0:.1f}" y="{top:.1f}" width="{x1 - x0:.1f}" height="{base - top:.1f}" '
|
||||
f'fill="{color}" fill-opacity="0.12" stroke="{color}" stroke-width="1"/>')
|
||||
ty = base + 15
|
||||
else:
|
||||
cx = (x0 + x1) / 2
|
||||
mark = (f'<line x1="{cx:.1f}" y1="{base + 22:.1f}" x2="{cx:.1f}" y2="{base + 9:.1f}" '
|
||||
f'stroke="{color}" stroke-width="1.8"/>'
|
||||
f'<polygon points="{cx:.1f},{base + 2:.1f} {cx - 4:.1f},{base + 10:.1f} '
|
||||
f'{cx + 4:.1f},{base + 10:.1f}" fill="{color}"/>')
|
||||
ty = base + 34
|
||||
label = (f'<text x="{x0:.1f}" y="{ty:.1f}" fill="{color}" '
|
||||
f'style="font-weight:bold">{text}</text>')
|
||||
# dreptunghiul/săgeata în spatele barelor, eticheta la final
|
||||
i = svg.index('<rect', svg.index(f'y1="{base:.1f}"'))
|
||||
svg = svg[:i] + mark + svg[i:]
|
||||
return svg.replace("</svg>", label + "</svg>")
|
||||
|
||||
|
||||
def grow(svg: str, dh: float) -> str:
|
||||
"""Mărește viewBox-ul unui panou cu dh px jos, pentru eticheta de sub volum."""
|
||||
h = vb_height(svg)
|
||||
return svg.replace(f'0 0 {PW} {h:.0f}"', f'0 0 {PW} {h + dh:.0f}"', 1)
|
||||
|
||||
|
||||
PANEL_GAP, PAD = 24, 8
|
||||
TITLE_H, FOOTER_H = 30, 0
|
||||
svg_a = grow(vol_note(ch_a.render(), CONF_A, CONF_A + 4,
|
||||
"volum în creștere = acumulare", GREEN, box=True), 10)
|
||||
svg_n = grow(vol_note(ch_n.render(), CONF_N, CONF_N,
|
||||
"volum slab: nimeni mare nu cumpără", RED, box=False), 30)
|
||||
|
||||
HA, HN = vb_height(svg_a), vb_height(svg_n)
|
||||
assert svg_a.count("\n\n") == 0 and svg_n.count("\n\n") == 0, "linie goală în panou"
|
||||
@@ -168,9 +205,6 @@ TOTAL_H = PAD + TITLE_H + max(HA, HN) + FOOTER_H + PAD
|
||||
OFF_A = (PAD, PAD + TITLE_H)
|
||||
OFF_N = (PAD + PW + PANEL_GAP, PAD + TITLE_H)
|
||||
|
||||
FOOTER = ("Surse: 13_strategia_3_byh.md:188-190 (doji la nivel); "
|
||||
"01_buy_doji_si_strategia_mici.md:227-235 (context invalid).")
|
||||
|
||||
wrapper = (
|
||||
f'<svg viewBox="0 0 {TOTAL_W} {TOTAL_H:.0f}" width="{TOTAL_W}" height="{TOTAL_H:.0f}" '
|
||||
f'xmlns="http://www.w3.org/2000/svg" style="width:100%;height:auto;font:12px sans-serif">'
|
||||
@@ -178,8 +212,6 @@ wrapper = (
|
||||
f'style="font-weight:bold">Setup A+ vs. setup mediocru (două panouri, exemple construite)</text>'
|
||||
f'<g transform="translate({OFF_A[0]},{OFF_A[1]})">{inner(svg_a)}</g>'
|
||||
f'<g transform="translate({OFF_N[0]},{OFF_N[1]})">{inner(svg_n)}</g>'
|
||||
f'<text x="{TOTAL_W / 2:.0f}" y="{TOTAL_H - PAD:.0f}" fill="#555" '
|
||||
f'text-anchor="middle">{FOOTER}</text>'
|
||||
f'</svg>'
|
||||
)
|
||||
|
||||
@@ -217,8 +249,7 @@ for frag, (ox, oy) in ((inner(svg_a), OFF_A), (inner(svg_n), OFF_N)):
|
||||
all_txt.append((gx, gy, anchor, t))
|
||||
# textele din wrapper (titlu + footer), în coordonate globale
|
||||
for x, y, anchor, t in ((TOTAL_W / 2, 20.0, "middle",
|
||||
"Setup A+ vs. setup mediocru (două panouri, exemple construite)"),
|
||||
(TOTAL_W / 2, TOTAL_H - PAD, "middle", FOOTER)):
|
||||
"Setup A+ vs. setup mediocru (două panouri, exemple construite)"),):
|
||||
gl, gr = span(x, anchor, t)
|
||||
assert -1 <= gl and gr <= TOTAL_W + 1, f"text din wrapper iese din pânză: {t!r}"
|
||||
assert 0 <= y <= TOTAL_H, f"text din wrapper în afara înălțimii: {t!r}"
|
||||
@@ -236,8 +267,10 @@ for i in range(len(all_txt)):
|
||||
raise AssertionError(f"texte suprapuse: {si!r} / {sj!r}")
|
||||
|
||||
# etichetele cerute de spec există în desen
|
||||
for cheie in ("suport 42.00", "doji la nivel", "acumulare",
|
||||
"rezistență 78.50", "gap-up, volum slab",
|
||||
assert "Surse" not in wrapper and ".md:" not in wrapper, "trimiteri la surse în SVG"
|
||||
for cheie in ("suport 42.00", "doji chiar pe nivel", "vânzarea s-a oprit", "volum în creștere = acumulare",
|
||||
"rezistență 78.50", "gap-up în rezistență", "cei întârziați",
|
||||
"semnalul a mințit", "volum slab: nimeni mare nu cumpără",
|
||||
"A+ — doji la suport, volum în acumulare",
|
||||
"Mediocru — gap-up în rezistență, volum varză"):
|
||||
assert cheie in wrapper, f"lipsește din desen: {cheie!r}"
|
||||
|
||||
Reference in New Issue
Block a user