selectia_rvol: 'sub jumatate fata de normal' pentru 0,61 -> '61 % dintr-o zi normala'. mdsrt_exemplu: legenda spunea ca nivelurile SPY sunt 'reale, din curs'; sunt reconstruite dupa ilustratia unui sumar Q&A. mbt_iesire: 2.04R -> 2,0R, cifra sursei, prin rotunjire la afisare, fara sa se atinga nicio serie; assert-ul verifica acum textul din SVG-ul randat, nu doar valoarea bruta. atmi_exemplu: '13 luni mai tarziu' era citit ca fapt al materialului - acum spune ca 13 e cifra desenului, materialul da 'aproximativ un an'; si cele patru marcaje de completare din desenele atmi sunt uniformizate. Plus cei trei agenti opencode scrisi pentru rundele astea: verif_strat, corector_strat, reparator_grafic. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ThN7ixZrowca8aTGrK1MUJ
101 lines
4.9 KiB
Python
101 lines
4.9 KiB
Python
"""ATMI — Pasul 5: ce înseamnă „fără stop-loss", desenat.
|
|
|
|
Același trade ca în restul capitolului (intrare 96.00, țintă +10 % = 105.60),
|
|
dar arătând drumul, nu doar capetele: între intrare și țintă poziția trece
|
|
printr-un drawdown — pierdere flotantă — de 20 % pe poziție, fără ca nimic
|
|
să te scoată din ea. Cifra de 20-30 % pe poziție fără levier e a cursului
|
|
(COVID 2020, 2018); traseul barelor e completare proprie.
|
|
|
|
Coloana din dreapta traduce aceeași cădere de 20 % a indicelui în pierdere
|
|
pe poziție, pe trei variante de instrument — calculul e în cod.
|
|
|
|
Rulează: python3 gen/strategii/atmi_risc.py
|
|
"""
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
sys.path.insert(0, "/workspace/atm-curs-dl")
|
|
from svg_candles import Chart, Level, Mark, from_closes # noqa: E402
|
|
|
|
OUT = Path(__file__).resolve().parent / "atmi_risc.svg"
|
|
GRAY, RED, GREEN, BLUE, INK, PURPLE = "#777", "#c00", "#188a3e", "#1a5fb4", "#333", "#7b2fbf"
|
|
|
|
ENTRY, TP, PPQ = 96.00, 105.60, 95.80
|
|
DD_PCT = 20.0 # drawdown pe poziție, fără levier
|
|
FUND = round(ENTRY * (1 - DD_PCT / 100), 2) # 76.80
|
|
|
|
# 3 bare de context + intrarea + 14 bare în jos + 17 bare de revenire
|
|
CLOSES = ([98.20, 97.10, 96.40, ENTRY]
|
|
+ [94.60, 92.80, 90.90, 89.40, 90.70, 88.60, 86.30, 84.40,
|
|
85.60, 83.10, 81.00, 79.20, 78.10, FUND]
|
|
+ [78.40, 80.60, 82.90, 84.50, 86.80, 88.40, 90.10, 92.30,
|
|
94.00, ENTRY, 97.80, 99.40, 100.90, 102.20, 103.40, 104.50, TP])
|
|
ENTRY_I, FUND_I, BE_I, TP_I = 3, 17, 27, len(CLOSES) - 1
|
|
assert CLOSES[ENTRY_I] == ENTRY and CLOSES[FUND_I] == FUND
|
|
assert CLOSES[BE_I] == ENTRY and CLOSES[TP_I] == TP
|
|
|
|
wicks = [(0.35, 0.35)] * len(CLOSES)
|
|
wicks[ENTRY_I] = (0.30, 0.10)
|
|
wicks[FUND_I] = (0.30, 0.00) # fundul e exact -20 %
|
|
wicks[FUND_I + 1] = (0.35, 0.00) # nicio bară de după nu coboară sub fund
|
|
wicks[TP_I] = (0.00, 0.35) # ținta e exact maximul seriei
|
|
cds = from_closes(CLOSES, wicks=wicks, vols=[44 + (i * 11) % 41 for i in range(len(CLOSES))],
|
|
start=98.90)
|
|
|
|
# --- ce susține textul -----------------------------------------------------
|
|
fund = round(min(c.l for c in cds), 2)
|
|
assert fund == FUND, fund
|
|
assert round((ENTRY - fund) / ENTRY * 100, 1) == DD_PCT
|
|
assert round(max(c.h for c in cds), 2) == TP
|
|
assert round(TP / ENTRY, 3) == 1.100
|
|
assert cds[FUND_I].l == fund and all(c.l >= fund for c in cds)
|
|
assert FUND < PPQ, "fundul trebuie să fie sub pivotul pe care ai cumpărat"
|
|
assert BE_I > FUND_I and CLOSES[BE_I] == ENTRY, "lipsește revenirea pe zero"
|
|
DURATA_JOS = BE_I - ENTRY_I # câte bare stai sub prețul de intrare
|
|
assert DURATA_JOS == 24, DURATA_JOS
|
|
|
|
# aceeași cădere de 20 % pe indice, tradusă pe trei instrumente
|
|
VARIANTE = [("ETF simplu (SPY)", 1), ("ETF cu levier 2x", 2), ("ETF cu levier 3x (SPXL)", 3)]
|
|
PIERDERI = [(n, round(DD_PCT * k, 1)) for n, k in VARIANTE]
|
|
assert PIERDERI[-1][1] == 60.0, PIERDERI
|
|
COVID = 34.0 # SPY în 2020, cifra cursului
|
|
assert round(COVID * 3, 1) > 100.0, "3x pe -34 % trebuie să treacă de -100 %"
|
|
|
|
levels = [
|
|
Level(TP, f"TP +10 % = {TP:.2f}", BLUE),
|
|
Level(ENTRY, f"intrare {ENTRY:.2f}", GREEN, label_at="right"),
|
|
Level(fund, f"fund {fund:.2f} = -{DD_PCT:.0f} % pe poziție", RED, label_at="left"),
|
|
]
|
|
marks = [
|
|
Mark(FUND_I, f"-{DD_PCT:.0f} %: nimic nu te scoate", RED, "below"),
|
|
Mark(BE_I, "abia aici ești pe zero", GRAY, "above", dy=-22),
|
|
Mark(TP_I, "TP", BLUE, "below"),
|
|
]
|
|
|
|
ch = Chart(candles=cds, levels=levels, marks=marks,
|
|
scale=5.6, gap=16, body_w=9, left=56, width=720, vol_h=60, top=152,
|
|
label_gap=46,
|
|
caption="Cifra de 20-30 % pe poziție fără levier e a cursului; "
|
|
"Completare proprie: traseul barelor, ca să se vadă cât durează.")
|
|
svg = ch.render()
|
|
|
|
titlu = ('<text x="24" y="24" fill="#333" style="font-weight:bold">'
|
|
'SPY — daily: fără stop, între intrare și țintă treci prin toată căderea</text>')
|
|
sub = (f'<text x="24" y="42" fill="#555" style="font-size:11px">'
|
|
f'{DURATA_JOS} de bare sub prețul de intrare, fără niciun ordin care să te scoată '
|
|
f'— ăsta e costul regulii „fără stop-loss".</text>')
|
|
box = [f'<rect x="424" y="58" width="284" height="76" fill="#fff4f4" stroke="{RED}" '
|
|
f'stroke-opacity="0.5"/>',
|
|
f'<text x="436" y="76" fill="{RED}" style="font-size:11px;font-weight:bold">'
|
|
f'aceeași cădere de {DD_PCT:.0f} % pe indice, pe poziție:</text>']
|
|
for i, (nume, pct) in enumerate(PIERDERI):
|
|
box.append(f'<text x="436" y="{94 + i * 15}" fill="{INK}" style="font-size:11px">'
|
|
f'{nume}: -{pct:.0f} %</text>')
|
|
|
|
i = svg.index(">") + 1
|
|
svg = svg[:i] + "\n " + "\n ".join([titlu, sub] + box) + svg[i:]
|
|
assert "\n\n" not in svg and ".md:" not in svg
|
|
OUT.write_text(svg)
|
|
print(f"scris {OUT.name}: intrare {ENTRY} fund {fund} (-{DD_PCT} %) TP {TP}, "
|
|
f"{DURATA_JOS} bare sub apă")
|