buy_doji: reparatiile de diagrame din verificare (11 puncte)
doji_forma nu mai foloseste 49.20 ca low al dojiului — era fix pretul stopului, in prima diagrama pe care o vede un incepator. Bara e pe 50.10/49.25, cu procentele recalculate. LABU: stopul e etichetat "pe cel mai jos dintre cele trei minime", nu "sub minime"; zonele arata doua teste ale pivotului, cu "revenire peste pivot 88.90" intre ele; numarul barei a iesit din desen, nota spune "a 6-a bara dupa intrare", derivat din serie. perechi_tf nu mai pune pretul TF-ului mare pe panoul TF-ului mic. aplus nu mai numeste un nivel care nu e desenat. directie nu mai zice "saptamanile" pe un panou daily. sell acopera toata cresterea, 14 bare. transe: cele trei sageti nu mai converg intr-un ghem — fiecare are linia ei cu pretul la capat; linia de intrare e desenata, ca parantezele de risc sa plece de undeva. locatie arata 49.25, cifra din care iese 0,51 %. stop si iesire: linii si sageti care taiau texte, mutate. Jumatate din probleme erau aceeasi greseala: un pret scris de mana intr-o eticheta, ramas in urma cand se schimba seria. Sunt acum aserttiuni care o fac imposibila; PROGRESS.md spune care merita generalizata. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ThN7ixZrowca8aTGrK1MUJ
This commit is contained in:
@@ -21,7 +21,7 @@ from buy_doji_ansamblu import serie, DOJI, CONF, DOJI_L, ENTRY, SL, TP1, R # no
|
||||
OUT = Path(__file__).with_suffix(".svg")
|
||||
S = 12 # prima bară arătată (zoom pe fereastra de intrare)
|
||||
GREEN, RED, ORANGE, BLUE, GREY = "#188a3e", "#c00", "#b06000", "#1a5fb4", "#555"
|
||||
W, SCALE = 930, 100.0
|
||||
W, SCALE = 1320, 240.0
|
||||
|
||||
cds = serie()[S:20] # până la bara care atinge ținta
|
||||
d, cf = DOJI - S, CONF - S # doji, confirmare (indici locali)
|
||||
@@ -60,15 +60,10 @@ ch = Chart(
|
||||
candles=cds,
|
||||
levels=[Level(TP1, "ținta 1: 51.60 = 1R față de close", GREEN, "2 2"),
|
||||
Level(TP_MED, f"ținta 1: {TP_MED:.2f} = 1R față de prețul mediu {MED:.2f}", BLUE, "2 2"),
|
||||
Level(ENTRY, "intrarea (close-ul de confirmare): 50.40", GREEN, "4 3"),
|
||||
Level(SL, "stopul: 49.20", RED, "4 3", "left")],
|
||||
zones=[Zone(cf, cf, "", GREEN, p_top=T1, p_bot=op)],
|
||||
arrows=[
|
||||
Arrow(cf, "1/3 la close 50.40\nca să nu ratezi trade-ul dacă pleacă fără tine",
|
||||
where="right", price=T1, color=GREEN, dx=150, dy=dy_sub(T1, 40)),
|
||||
Arrow(cf, "1/3 cu ordin limită la 50.23 (o treime din corp)\nmai ieftin, dacă prețul revine",
|
||||
where="right", price=T2, color=ORANGE, dx=150, dy=dy_sub(T2, 74)),
|
||||
Arrow(cf, "1/3 cu ordin limită la 50.15 (jumătatea corpului)\nși mai ieftin",
|
||||
where="right", price=T3, color=ORANGE, dx=150, dy=dy_sub(T3, 108)),
|
||||
Arrow(cf - 1, "media mai bună coboară ținta:\nieși la 51.32, nu la 51.60",
|
||||
where="above", price=TP_MED, color=BLUE, dx=150),
|
||||
Arrow(cf + 1, "retestul: prețul revine la 50.10\nși umple amândouă ordinele limită",
|
||||
@@ -76,7 +71,6 @@ ch = Chart(
|
||||
],
|
||||
scale=SCALE, gap=30, body_w=10, left=56, width=W,
|
||||
title="Intrarea în trei tranșe — același trade (doji 50.10 / 49.25)",
|
||||
caption="Paranteza de pe candela de confirmare e corpul ei (49.90 → 50.40): pe el se măsoară 1/3 și 1/2. Stopul e unul singur. Exemplu construit.",
|
||||
)
|
||||
svg = ch.render()
|
||||
OFF = (float(re.search(r'<line x1="24" y1="([\d.]+)"', svg).group(1))
|
||||
@@ -95,6 +89,25 @@ def Y(p):
|
||||
|
||||
def x(i):
|
||||
return ch._cx(i) + OFF_X
|
||||
CW, LH = 6.2, 14 # lățime caracter bold 12px, înălțime rând
|
||||
|
||||
|
||||
def sageata(x1, y1, x2, y2, col, randuri):
|
||||
"""Săgeată desenată de mână: coada la (x1,y1) lângă text, vârful la (x2,y2)."""
|
||||
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
|
||||
out = [f'<line x1="{x1:.1f}" y1="{y1:.1f}" x2="{bx:.1f}" y2="{by:.1f}" '
|
||||
f'stroke="{col}" stroke-width="1.8"/>',
|
||||
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="{col}"/>']
|
||||
y0 = y1 + 4 - LH * (len(randuri) - 1) / 2
|
||||
for k, t in enumerate(randuri):
|
||||
out.append(f'<text x="{x1 + 6:.1f}" y="{y0 + LH * k:.1f}" fill="{col}" '
|
||||
f'style="font-weight:bold">{t}</text>')
|
||||
return out
|
||||
|
||||
|
||||
ex = []
|
||||
# paranteza corpului, în stânga candelei de confirmare, cu diviziunile 1/3 și 1/2
|
||||
xb = x(cf) - 17
|
||||
@@ -102,33 +115,79 @@ ex.append(f'<polyline points="{xb + 5:.1f},{Y(T1):.1f} {xb:.1f},{Y(T1):.1f} {xb:
|
||||
f'{xb + 5:.1f},{Y(op):.1f}" fill="none" stroke="{GREY}" stroke-width="2"/>')
|
||||
# fără etichetă lângă paranteză: oriunde lângă candele s-ar suprapune peste ele
|
||||
# sau peste liniile săgeților. O numește legenda de sub grafic.
|
||||
# nivelurile de tranșă, ca segmente scurte peste candela de confirmare
|
||||
for p, col in ((T1, GREEN), (T2, ORANGE), (T3, ORANGE)):
|
||||
dash = "" if p == T1 else ' stroke-dasharray="3 3"'
|
||||
ex.append(f'<line x1="{xb:.1f}" y1="{Y(p):.1f}" x2="{x(cf) + 8:.1f}" y2="{Y(p):.1f}" '
|
||||
f'stroke="{col}" stroke-width="1.5"{dash}/>')
|
||||
|
||||
# Fiecare tranșă are linia ei de nivel, prelungită la dreapta pe o lungime
|
||||
# diferită, cu prețul scris la capăt. Săgeata tranșei se termină pe capătul
|
||||
# liniei ei: trei puncte de sosire distanțate și pe orizontală, și pe verticală
|
||||
# (altfel, la aceeași candelă, cele trei vârfuri ies într-un ghem de 10 px).
|
||||
XT1, XT2, XT3 = x(len(cds) - 1) + 230, x(len(cds) - 1) + 150, x(len(cds) - 1) + 70
|
||||
TRANSE = ((T1, GREEN, XT1, "", ["1/3 la close 50.40",
|
||||
"ca să nu ratezi trade-ul dacă pleacă fără tine"]),
|
||||
(T2, ORANGE, XT2, ' stroke-dasharray="3 3"',
|
||||
["1/3 cu ordin limită la 50.23 (o treime din corp)",
|
||||
"mai ieftin, dacă prețul revine"]),
|
||||
(T3, ORANGE, XT3, ' stroke-dasharray="3 3"',
|
||||
["1/3 cu ordin limită la 50.15 (jumătatea corpului)",
|
||||
"și mai ieftin"]))
|
||||
XTAIL = x(len(cds) - 1) + 114 # coada tuturor celor trei săgeți, sub stop
|
||||
for k, (p_, col, xend, dash, randuri) in enumerate(TRANSE):
|
||||
if p_ != T1: # T1 e chiar nivelul de intrare, deja desenat
|
||||
ex.append(f'<line x1="{xb:.1f}" y1="{Y(p_):.1f}" x2="{xend:.1f}" y2="{Y(p_):.1f}" '
|
||||
f'stroke="{col}" stroke-width="1.5"{dash}/>')
|
||||
# prețul deasupra liniei, nu pe ea
|
||||
ex.append(f'<text x="{xend + 6:.1f}" y="{Y(p_) - 6:.1f}" fill="{col}" '
|
||||
f'style="font-weight:bold">{p_:.2f}</text>')
|
||||
ex += sageata(XTAIL, Y(SL) + 46 + 40 * k, xend, Y(p_), col, randuri)
|
||||
# unde se umple fiecare tranșă: T1 la close, T2 și T3 pe low-ul barei următoare
|
||||
for i, p, col in ((cf, T1, GREEN), (cf + 1, T2, ORANGE), (cf + 1, T3, ORANGE)):
|
||||
ex.append(f'<circle cx="{x(i):.1f}" cy="{Y(p):.1f}" r="4" fill="#fff" '
|
||||
for i, p_, col in ((cf, T1, GREEN), (cf + 1, T2, ORANGE), (cf + 1, T3, ORANGE)):
|
||||
ex.append(f'<circle cx="{x(i):.1f}" cy="{Y(p_):.1f}" r="4" fill="#fff" '
|
||||
f'stroke="{col}" stroke-width="2"/>')
|
||||
# cele două paranteze de risc, la dreapta: tot la close vs cu tranșe
|
||||
for xp, hi_p, txt, col in ((x(len(cds) - 1) + 40, T1,
|
||||
f"risc {RISC_CLOSE:.2f} pe acțiune (tot la close)", RED),
|
||||
(x(len(cds) - 1) + 290, MED,
|
||||
f"risc {RISC_MED:.2f} pe acțiune (cu tranșe, preț mediu {MED:.2f})", BLUE)):
|
||||
# cele două paranteze de risc: tot la close vs cu tranșe. Pleacă amândouă de la
|
||||
# un nivel desenat (intrarea 50.40, respectiv media 50.26) și coboară la stop.
|
||||
# Stau una lângă alta, cu textele la dreapta amândurora, la înălțimi diferite —
|
||||
# altfel textul uneia trece peste paranteza celeilalte.
|
||||
XTXT = x(len(cds) - 1) + 664
|
||||
for xp, hi_p, ty, txt, col in (
|
||||
(x(len(cds) - 1) + 560, T1, Y(T1) + 50,
|
||||
f"risc {RISC_CLOSE:.2f} pe acțiune (tot la close, de la intrarea {T1:.2f})", RED),
|
||||
(x(len(cds) - 1) + 640, MED, Y(SL) - 50,
|
||||
f"risc {RISC_MED:.2f} pe acțiune (cu tranșe, de la prețul mediu {MED:.2f})", BLUE)):
|
||||
y1, y2 = Y(hi_p), Y(SL)
|
||||
ex.append(f'<polyline points="{xp - 5:.1f},{y1:.1f} {xp:.1f},{y1:.1f} {xp:.1f},{y2:.1f} '
|
||||
f'{xp - 5:.1f},{y2:.1f}" fill="none" stroke="{col}" stroke-width="1.5"/>')
|
||||
ex.append(f'<text x="{xp + 6:.1f}" y="{(y1 + y2) / 2 + 4:.1f}" fill="{col}" '
|
||||
ex.append(f'<line x1="{xp:.1f}" y1="{ty - 4:.1f}" x2="{XTXT - 6:.1f}" y2="{ty - 4:.1f}" '
|
||||
f'stroke="{col}" stroke-width="1" stroke-dasharray="2 3"/>')
|
||||
ex.append(f'<text x="{XTXT:.1f}" y="{ty:.1f}" fill="{col}" '
|
||||
f'style="font-weight:bold">{txt}</text>')
|
||||
# nivelul prețului mediu: segment scurt, ca paranteza albastră să plece de pe o linie
|
||||
ex.append(f'<line x1="{XT1 + 60:.1f}" y1="{Y(MED):.1f}" x2="{x(len(cds) - 1) + 640:.1f}" '
|
||||
f'y2="{Y(MED):.1f}" stroke="{BLUE}" stroke-width="1.2" stroke-dasharray="2 3"/>')
|
||||
ex.append(f'<text x="{XT1 + 60:.1f}" y="{Y(MED) + 16:.1f}" fill="{BLUE}">'
|
||||
f'prețul mediu al celor trei tranșe: {MED:.2f}</text>')
|
||||
|
||||
|
||||
body = svg.rsplit("</svg>", 1)[0].rstrip()
|
||||
svg = "\n ".join([body, *ex]) + "\n</svg>"
|
||||
vw, vh = (float(v) for v in re.search(r'viewBox="0 0 ([\d.]+) ([\d.]+)"', svg).groups())
|
||||
# textele desenate manual trec sub pânza calculată de Chart: crește înălțimea
|
||||
y_max = max(float(m) for m in re.findall(r'<text[^>]*\sy="([\d.]+)"', svg))
|
||||
vh = max(vh, y_max + 34)
|
||||
CAPTION = ("Paranteza de pe candela de confirmare e corpul ei (49.90 → 50.40): pe el se măsoară "
|
||||
"1/3 și 1/2. Stopul e unul singur. Exemplu construit.")
|
||||
svg = svg.replace("</svg>", f' <text x="24" y="{vh - 12:.1f}" fill="{GREY}">{CAPTION}</text>\n</svg>')
|
||||
svg = re.sub(r'viewBox="0 0 [\d.]+ [\d.]+"', f'viewBox="0 0 {vw:.0f} {vh:.0f}"', svg, count=1)
|
||||
|
||||
for tx, attrs, txt in re.findall(r'<text x="([-\d.]+)"([^>]*)>(.*?)</text>', svg):
|
||||
ty = float(re.search(r'y="([-\d.]+)"', attrs).group(1))
|
||||
w = len(txt) * 6.2
|
||||
w = len(txt) * CW
|
||||
x0 = float(tx) - (w if 'text-anchor="end"' in attrs else w / 2 if 'middle' in attrs else 0)
|
||||
assert 0 <= x0 and x0 + w <= vw and 0 <= ty <= vh, f"text în afara pânzei: {txt!r}"
|
||||
assert 0 <= x0 and x0 + w <= vw, f"text în afara pânzei pe orizontală: {txt!r}"
|
||||
assert 0 <= ty <= vh, f"text în afara pânzei pe verticală: {txt!r}"
|
||||
assert "\n\n" not in svg, "linie goală în SVG"
|
||||
# cele trei săgeți de tranșă trebuie să sosească la înălțimi și la x-uri distincte
|
||||
tips = [(XT1, Y(T1)), (XT2, Y(T2)), (XT3, Y(T3))]
|
||||
for (ax, ay), (bx_, by_) in ((tips[0], tips[1]), (tips[1], tips[2]), (tips[0], tips[2])):
|
||||
assert abs(ax - bx_) >= 60 and abs(ay - by_) >= 18, \
|
||||
f"vârfuri de săgeată prea apropiate: {(ax, ay)} vs {(bx_, by_)}"
|
||||
OUT.write_text(svg, encoding="utf-8")
|
||||
print(f"scris {OUT} ({vw:.0f}x{vh:.0f})")
|
||||
|
||||
Reference in New Issue
Block a user