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
129 lines
6.0 KiB
Python
129 lines
6.0 KiB
Python
"""Buy Doji — trendul dinaintea dojiului: 9 candele vs. 3 candele (exemplu construit).
|
|
|
|
Citește specificația `gen/strategii/buy_doji_trend.spec.txt`. Două panouri
|
|
stivuite, aceeași scară și dojiul pe aceeași verticală, ca să se compare
|
|
direct: sus downtrend de 9 candele (ideal), jos range + 3 candele (acceptat,
|
|
semnal mai slab, scannerul cu Since Bear ≥ 8 nu-l găsește). Candelele
|
|
trendului sunt numerotate sub bară, ca numărătoarea să se vadă.
|
|
"""
|
|
import re
|
|
import sys; sys.path.insert(0, "/workspace/atm-curs-dl")
|
|
from svg_candles import Arrow, Candle, Chart, Mark, Zone
|
|
|
|
OUT = "/workspace/atm-curs-dl/gen/strategii/buy_doji_trend.svg"
|
|
DOJI = (49.65, 50.10, 49.25, 49.60)
|
|
CONF = (49.60, 50.55, 49.45, 50.40)
|
|
|
|
|
|
def series(start, closes):
|
|
"""Fitile implicite 0.15 peste/sub corp; open = close anterior."""
|
|
bars, o = [], start
|
|
for c in closes:
|
|
bars.append((o, max(o, c) + 0.15, min(o, c) - 0.15, c))
|
|
o = c
|
|
return bars + [DOJI, CONF]
|
|
|
|
|
|
L = series(55.50, [55.00, 54.30, 54.60, 53.60, 52.90, 53.10, 52.00, 51.20, 50.60, 49.65])
|
|
R = series(51.40, [51.70, 51.20, 51.60, 51.10, 51.50, 51.30, 51.70, 51.40, 50.90, 50.30, 49.65])
|
|
|
|
# --- assert-urile din spec ---------------------------------------------------
|
|
for bars in (L, R): # 1
|
|
for i, (o, h, l, c) in enumerate(bars):
|
|
assert l <= min(o, c) and h >= max(o, c), f"bara {i}"
|
|
if i:
|
|
assert abs(o - bars[i - 1][3]) < 1e-9, f"open[{i}] != close[{i-1}]"
|
|
assert L[10] == R[11] == DOJI # 2
|
|
assert abs(DOJI[0] - DOJI[3]) <= 0.10 * (DOJI[1] - DOJI[2])
|
|
cL = [b[3] for b in L]; cR = [b[3] for b in R]
|
|
assert cL[9] < cL[0] - 5 and cL[9] == min(cL[0:10]) # 3
|
|
assert sum(cL[i] < cL[i - 1] for i in range(1, 10)) >= 6
|
|
assert max(cR[0:8]) - min(cR[0:8]) <= 0.60 + 1e-9 # 4
|
|
assert cR[8] > cR[9] > cR[10]
|
|
assert L[11][3] > DOJI[1] and R[12][3] > DOJI[1] and L[11][3] == 50.40 # 5
|
|
|
|
GREY, GREEN, BLUE, ORANGE, RED = "#555", "#188a3e", "#1a5fb4", "#b06000", "#c00"
|
|
W, GAP = 540, 18
|
|
common: dict = dict(scale=30.0, gap=GAP, body_w=10, width=W)
|
|
|
|
|
|
def numbers(ch, idx, color):
|
|
"""Numără candelele trendului: 1, 2, 3... sub fiecare bară."""
|
|
return [f'<text x="{ch._cx(i):.1f}" y="{ch._y(ch.candles[i].l) + 14:.1f}" fill="{color}" '
|
|
f'text-anchor="middle" style="font-weight:bold;font-size:11px">{n}</text>'
|
|
for n, i in enumerate(idx, 1)]
|
|
|
|
|
|
def render_keep_offsets(ch):
|
|
"""`Chart.render()` restaurează top/left după desen, deci `_y`/`_cx` de
|
|
după el dau coordonate deplasate față de body-ul randat (titlul și
|
|
etichetele zonelor mută graficul în jos). `_render()` lasă offset-urile
|
|
puse, exact ce ne trebuie ca extra-urile desenate de mână să pice la locul
|
|
lor. Se cheamă o singură dată per Chart."""
|
|
return ch._render()
|
|
|
|
|
|
def fits(ch, svg):
|
|
"""Chart mută graficul dacă un text ar ieși din pânză — aici mutarea ar
|
|
dezalinia cele două panouri, deci verificăm că nu s-a întâmplat."""
|
|
assert f'viewBox="0 0 {ch.width} ' in svg, \
|
|
f"Chart a lărgit pânza ({svg[:60]}): textul unei săgeți iese din {ch.width}px"
|
|
|
|
|
|
def body(svg):
|
|
return svg.split(">", 1)[1].rsplit("</svg>", 1)[0].strip()
|
|
|
|
|
|
# --- panoul de sus: 9 candele ------------------------------------------------
|
|
top = Chart(candles=[Candle(*b) for b in L], left=44 + GAP, # dojiul pe aceeași verticală
|
|
marks=[Mark(11, "confirmare 50.40", GREEN, "above")],
|
|
zones=[Zone(1, 9, "9 candele de scădere — ideal", GREEN)],
|
|
arrows=[Arrow(10, "doji la capătul scăderii\nvânzătorii s-au terminat",
|
|
where="below", color=ORANGE, dx=70)],
|
|
top=54, # titlul se desenează de mână: cel din Chart ar cădea peste eticheta zonei
|
|
**common)
|
|
svg_top = render_keep_offsets(top)
|
|
fits(top, svg_top)
|
|
ext_top = numbers(top, range(1, 10), RED) + [
|
|
'<text x="24" y="22" fill="#333" style="font-weight:bold">Trendul dinaintea dojiului: '
|
|
'9 candele vs. 3 candele — exemplu construit</text>']
|
|
y_cap1 = max(top._y(DOJI[2]) + 50, top._arrow_geom(top.arrows[0])["box"][3] + 24)
|
|
ext_top.append(f'<text x="24" y="{y_cap1:.1f}" fill="{GREEN}" style="font-weight:bold">'
|
|
f'DA — ideal (8-10 candele)</text>')
|
|
|
|
# --- panoul de jos: range + 3 candele ----------------------------------------
|
|
y_sep = y_cap1 + 16
|
|
bot = Chart(candles=[Candle(*b) for b in R], left=44,
|
|
marks=[Mark(11, "doji", ORANGE, "below", dy=22),
|
|
Mark(12, "confirmare 50.40", GREEN, "above")],
|
|
zones=[Zone(8, 10, "doar 3 candele — merge, dar mai slab", ORANGE)],
|
|
arrows=[Arrow(3, "înainte: preț pe loc,\nnu scădere — de aceea\nsemnalul e mai slab",
|
|
where="above", color=GREY)],
|
|
top=int(y_sep + 118), **common) # loc pentru titlul panoului și textul săgeții
|
|
svg_bot = render_keep_offsets(bot)
|
|
fits(bot, svg_bot)
|
|
ext_bot = (numbers(bot, range(8, 11), RED)
|
|
+ [f'<text x="24" y="{y_sep + 24:.1f}" fill="#333" style="font-weight:bold">'
|
|
f'Același doji, după un range</text>'])
|
|
y_cap2 = bot._y(DOJI[2]) + 50
|
|
ext_bot += [f'<text x="24" y="{y_cap2:.1f}" fill="{ORANGE}" style="font-weight:bold">'
|
|
f'ACCEPTAT, semnal mai slab</text>',
|
|
f'<text x="24" y="{y_cap2 + 17:.1f}" fill="{GREY}">'
|
|
f'scannerul cu Since Bear ≥ 8 nu-l vede</text>']
|
|
H = y_cap2 + 30
|
|
|
|
# dojiul e aceeași bară, la aceeași scară, în același loc pe orizontală
|
|
assert abs(top._cx(10) - bot._cx(11)) < 1e-9 and top.scale == bot.scale
|
|
|
|
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">',
|
|
body(svg_top), *ext_top,
|
|
f'<line x1="24" y1="{y_sep:.0f}" x2="{W - 14}" y2="{y_sep:.0f}" stroke="#ccc"/>',
|
|
body(svg_bot), *ext_bot,
|
|
]) + "\n</svg>"
|
|
assert "\n\n" not in svg, "linie goală în SVG"
|
|
with open(OUT, "w") as f:
|
|
f.write(svg)
|
|
print(f"scris {OUT} ({W}x{H:.0f})")
|