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
145 lines
6.5 KiB
Python
145 lines
6.5 KiB
Python
#!/usr/bin/env python3
|
|
"""Ce e un doji și ce nu — patru candele cu același high 50.10 și low 49.20.
|
|
|
|
Citește `gen/strategii/buy_doji_doji_forma.spec.txt` și scrie
|
|
`gen/strategii/buy_doji_doji_forma.svg`. E prima imagine din capitol: cine o
|
|
vede nu știe ce e un doji, deci candela A primește săgeți pe corp și pe
|
|
fiecare fitil, cu explicația de ce arată așa, iar C și D sunt contraexemple.
|
|
Zonă verde peste A și B („doji"), zonă roșie peste C și D („nu e doji").
|
|
|
|
Desenat direct, nu cu `Chart`: `tag`/`note` din svg_candles pun etichetele
|
|
pe un singur rând sau în blocul de jos, iar aici fiecare etichetă trebuie să
|
|
stea lipită sub candela ei, pe 2-3 rânduri, plus săgeți cu text pe lateral.
|
|
|
|
Rulare: python3 gen/strategii/buy_doji_doji_forma.py
|
|
"""
|
|
from pathlib import Path
|
|
|
|
OUT = Path(__file__).with_suffix(".svg")
|
|
GREEN, RED, GREY, BLUE = "#188a3e", "#c00", "#555", "#1a5fb4"
|
|
|
|
H, L = 50.10, 49.20
|
|
RNG = H - L
|
|
# (nume, o, h, l, c, verdict, rânduri de etichetă sub candelă)
|
|
CANDLES = [
|
|
("A", 49.60, 50.10, 49.20, 49.55, "DA", ["DA: corp 0.05", "= 5,6 % din înălțime"]),
|
|
("B", 49.62, 50.10, 49.20, 49.53, "DA", ["DA, la limită:", "corp 0.09 = 10 %"]),
|
|
("C", 49.80, 50.10, 49.20, 49.50, "NU", ["NU: corp 0.30", "= 33 %"]),
|
|
("D", 50.05, 50.10, 49.20, 50.10, "NU", ["NU: corp mic (5,6 %),", "dar fără fitil sus"]),
|
|
]
|
|
|
|
# --- assert-urile din spec --------------------------------------------------
|
|
ratio = {n: round(abs(c - o) / (h - l), 4) for n, o, h, l, c, *_ in CANDLES}
|
|
for n, o, h, l, c, *_ in CANDLES:
|
|
assert h == H and l == L, (n, h, l) # 1
|
|
assert ratio == {"A": 0.0556, "B": 0.1000, "C": 0.3333, "D": 0.0556}, ratio # 2
|
|
for n, o, h, l, c, *_ in CANDLES[:2]: # 3
|
|
assert ratio[n] <= 0.10 and min(o, c) > l and max(o, c) < h, n
|
|
assert ratio["C"] > 0.10 # 4
|
|
_, oD, hD, _, cD, *_ = CANDLES[3]
|
|
assert max(oD, cD) == hD
|
|
|
|
# --- geometrie ---------------------------------------------------------------
|
|
SCALE, TOP, W = 260, 210, 1000
|
|
XS = [350, 490, 690, 860] # centrele candelelor: A, B (doji) | C, D (nu)
|
|
BODY_W = 26
|
|
|
|
|
|
def y(p):
|
|
return TOP + (H - p) * SCALE
|
|
|
|
|
|
oA, cA = CANDLES[0][1], CANDLES[0][4]
|
|
assert y(oA) < y(cA), "open A trebuie să fie mai sus pe ecran decât close A" # 5
|
|
|
|
out = []
|
|
t = out.append
|
|
|
|
|
|
def text(x, yy, s, color=GREY, anchor="start", bold=False, size=12):
|
|
style = f' style="font-weight:bold;font-size:{size}px"' if bold else (
|
|
f' style="font-size:{size}px"' if size != 12 else "")
|
|
t(f'<text x="{x:.1f}" y="{yy:.1f}" fill="{color}" text-anchor="{anchor}"{style}>{s}</text>')
|
|
|
|
|
|
def zone(x0, x1, y0, y1, label, color):
|
|
t(f'<rect x="{x0:.1f}" y="{y0:.1f}" width="{x1 - x0:.1f}" height="{y1 - y0:.1f}" '
|
|
f'fill="{color}" fill-opacity="0.10" stroke="{color}" stroke-width="1"/>')
|
|
text(x0 + 6, y0 - 7, label, color, bold=True)
|
|
|
|
|
|
def arrow(x1, y1, x2, y2, color, lines, anchor="start"):
|
|
"""Săgeată de la (x1,y1) [coada, lângă text] la (x2,y2) [vârf, pe țintă]."""
|
|
d = max(((x2 - x1) ** 2 + (y2 - y1) ** 2) ** 0.5, 1e-9)
|
|
ux, uy = (x2 - x1) / d, (y2 - y1) / d
|
|
bx, by = x2 - 8 * ux, y2 - 8 * uy
|
|
t(f'<line x1="{x1:.1f}" y1="{y1:.1f}" x2="{bx:.1f}" y2="{by:.1f}" '
|
|
f'stroke="{color}" stroke-width="1.6"/>')
|
|
t(f'<polygon points="{x2:.1f},{y2:.1f} {bx - 4 * uy:.1f},{by + 4 * ux:.1f} '
|
|
f'{bx + 4 * uy:.1f},{by - 4 * ux:.1f}" fill="{color}"/>')
|
|
dx = -6 if anchor == "end" else (0 if anchor == "middle" else 6)
|
|
# la săgeata verticală textul stă deasupra cozii, altfel intră peste ea
|
|
y0 = (y1 - 10 - 14 * (len(lines) - 1) if anchor == "middle"
|
|
else y1 + 4 - 14 * (len(lines) - 1) / 2)
|
|
for k, s in enumerate(lines):
|
|
text(x1 + dx, y0 + 14 * k, s, color, anchor, bold=(k == 0))
|
|
|
|
|
|
text(24, 30, "Ce e un doji și ce nu — aceeași bară, 50.10 sus și 49.20 jos", "#333",
|
|
bold=True, size=14)
|
|
|
|
# zonele: A+B sunt doji, C+D nu
|
|
label_y0 = y(L) + 28
|
|
z_bot = label_y0 + 16 * 2 - 4
|
|
zone(XS[0] - 58, XS[1] + 62, y(H) - 30, z_bot, "doji", GREEN)
|
|
zone(XS[2] - 62, XS[3] + 74, y(H) - 30, z_bot, "nu e doji", RED)
|
|
|
|
# high și low comune: linii subțiri pe toată lățimea
|
|
for p in (H, L):
|
|
t(f'<line x1="{XS[0] - 100}" y1="{y(p):.1f}" x2="{W - 24}" y2="{y(p):.1f}" '
|
|
f'stroke="#bbb" stroke-dasharray="4 3"/>')
|
|
text(24, y(H) - 6, "cât de sus a urcat prețul: 50.10")
|
|
text(24, y(L) + 14, "cât de jos a coborât: 49.20")
|
|
|
|
for (n, o, h, l, c, verdict, lines), x in zip(CANDLES, XS):
|
|
color = GREEN if c >= o else RED
|
|
vcol = GREEN if verdict == "DA" else RED
|
|
t(f'<line x1="{x}" y1="{y(h):.1f}" x2="{x}" y2="{y(l):.1f}" stroke="#1a1a1a" stroke-width="1.4"/>')
|
|
top, bot = y(max(o, c)), y(min(o, c))
|
|
t(f'<rect x="{x - BODY_W / 2:.1f}" y="{top:.1f}" width="{BODY_W}" '
|
|
f'height="{max(bot - top, 1.6):.1f}" fill="{color}"/>')
|
|
for k, s in enumerate(lines):
|
|
text(x, label_y0 + 16 * k, s, vcol, "middle", bold=(k == 0))
|
|
|
|
# --- săgețile pe A: corp, fitil sus, fitil jos -------------------------------
|
|
xA = XS[0]
|
|
arrow(xA - 60, y(49.92), xA - 3, y(49.85), BLUE, anchor="end",
|
|
lines=["fitilul de sus", "cumpărătorii au împins, dar n-au ținut"])
|
|
arrow(xA - 60, y(49.575), xA - 15, y(49.575), BLUE, anchor="end",
|
|
lines=["corpul: aproape o linie",
|
|
"open și close aproape egale — nimeni",
|
|
"n-a câștigat ziua"])
|
|
arrow(xA - 60, y(49.30), xA - 3, y(49.375), BLUE, anchor="end",
|
|
lines=["fitilul de jos", "vânzătorii au împins, dar n-au ținut"])
|
|
# prețurile de open/close, în dreapta candelei A
|
|
text(xA + 20, y(oA) - 2, "open 49.60")
|
|
text(xA + 20, y(cA) + 10, "close 49.55")
|
|
|
|
# --- săgețile pe contraexemple ----------------------------------------------
|
|
xC, xD = XS[2], XS[3]
|
|
arrow(xC + 34, y(H) - 112, xC + 14, y(49.78), RED,
|
|
lines=["corp prea gros (33 %)", "aici una din tabere a câștigat —", "nu mai e indecizie"])
|
|
arrow(xD, y(H) - 34, xD, y(H) - 4, RED, anchor="middle",
|
|
lines=["fără fitil sus", "regula cere fitil în ambele părți"])
|
|
|
|
cap_y = z_bot + 30
|
|
text(24, cap_y, f"Doji = corpul e cel mult 10 % din înălțimea totală a barei "
|
|
f"(aici înălțimea e {RNG:.2f}) și are fitil și sus, și jos.")
|
|
height = cap_y + 16
|
|
|
|
svg = (f'<svg viewBox="0 0 {W} {height:.0f}" xmlns="http://www.w3.org/2000/svg" '
|
|
f'style="width:100%;height:auto;font:12px sans-serif">\n '
|
|
+ "\n ".join(out) + "\n</svg>\n")
|
|
OUT.write_text(svg, encoding="utf-8")
|
|
print(f"scris {OUT} ({W}x{height:.0f})")
|