"""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 Candle, Chart, Mark
OUT = "/workspace/atm-curs-dl/gen/strategii/buy_doji_trend.svg"
DOJI = (49.65, 50.10, 49.20, 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'{n}'
for n, i in enumerate(idx, 1)]
def bracket(ch, a, b, y, text, color):
xa, xb = ch._cx(a) - 6, ch._cx(b) + 6
return [f'',
f'{text}']
def body(svg):
return svg.split(">", 1)[1].rsplit("", 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(10, "doji", ORANGE, "below", dy=8),
Mark(11, "confirmare 50.40", GREEN, "above")],
title="Trendul dinaintea dojiului: 9 candele vs. 3 candele — exemplu construit",
**common)
svg_top = top.render()
y_br = top._y(max(b[1] for b in L[1:10])) - 10
ext_top = numbers(top, range(1, 10), RED) + bracket(top, 1, 9, y_br, "downtrend 9 candele", RED)
y_cap1 = top._y(DOJI[2]) + 50
ext_top.append(f''
f'DA — ideal (8-10 candele)')
# --- 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=8),
Mark(12, "confirmare 50.40", GREEN, "above")],
top=int(y_sep + 62), **common) # titlul și paranteza range-ului, desenate mai jos
svg_bot = bot.render()
y_rg = bot._y(max(b[1] for b in R[0:8])) - 10
ext_bot = (numbers(bot, range(8, 11), RED)
+ bracket(bot, 8, 10, bot._y(R[8][1]) - 10, "doar 3 candele", RED)
+ bracket(bot, 0, 7, y_rg, "", GREY)
+ [f''
f'Același doji, după un range',
f''
f'range: nu se numără'])
y_cap2 = bot._y(DOJI[2]) + 50
ext_bot += [f''
f'ACCEPTAT, semnal mai slab',
f''
f'scannerul cu Since Bear ≥ 8 nu-l vede']
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'"
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})")