"""
Generator de diagrame de candele pentru summaries/.
Motivul existenței: diagramele desenate de mână ies la scară greșită. În
`01_MODUL_1/05_patternuri_candele.md` ținta de 2R era desenată la distanța
de 1R față de intrare (75 px/unitate în jos, 41 px/unitate în sus), iar la
Marubozu eticheta de open era pusă deasupra celei de close — axa de preț
inversată. Aici coordonatele se calculează din prețuri, deci scara e
corectă prin construcție.
Convenție: un singur `scale` (px per unitate de preț) pentru tot desenul.
"""
from dataclasses import dataclass, field
@dataclass
class Candle:
o: float
h: float
l: float
c: float
tag: str = "" # eticheta de sub candelă
note: str = "" # a doua linie de etichetă
color: str | None = None # implicit: verde dacă c >= o
@dataclass
class Level:
price: float
text: str
color: str = "#1a5fb4"
dash: str = "4 3"
@dataclass
class Chart:
candles: list[Candle]
levels: list[Level] = field(default_factory=list)
scale: float = 60.0 # px per unitate de preț
width: int = 620
body_w: int = 26
gap: int = 92 # distanță între candele
left: int = 64 # x-ul primei candele
top: int = 34 # spațiu deasupra celui mai mare preț
label_gap: int = 34 # spațiu sub cea mai joasă candelă pentru etichete
caption: str = ""
trend: str = "" # "down" / "up" / "" — segment de trend înainte
def _y(self, price: float) -> float:
return self.top + (self._pmax - price) * self.scale
def render(self) -> str:
prices = [p for c in self.candles for p in (c.o, c.h, c.l, c.c)]
prices += [lv.price for lv in self.levels]
self._pmax, pmin = max(prices), min(prices)
# segmentul de trend iese din zona candelelor: rezervă loc pentru el,
# altfel se suprapune peste etichete sau peste marginea desenului
if self.trend == "down":
self.top += 56
body_bottom = self.top + (self._pmax - pmin) * self.scale
extra = 62 if self.trend == "up" else 0
n_notes = sum(1 for c in self.candles if c.note)
height = body_bottom + extra + self.label_gap + 17 * n_notes + (24 if self.caption else 8)
out = [f'")
return "\n ".join(out).replace("\n ", "\n")
def selftest():
"""O unitate de preț trebuie să dea aceeași distanță oriunde pe desen."""
ch = Chart(candles=[Candle(o=63.05, h=63.30, l=62.10, c=63.30)],
levels=[Level(62.05, "SL"), Level(63.35, "entry"), Level(65.95, "TP 2R")],
scale=60)
svg = ch.render()
ys = {lv.price: ch._y(lv.price) for lv in ch.levels}
risc = ys[62.05] - ys[63.35] # px, în jos
castig = ys[63.35] - ys[65.95] # px, în sus
assert abs(castig / risc - 2.0) < 0.01, f"2R desenat la {castig/risc:.2f}R"
assert "