#!/usr/bin/env python3 """Все числа TW-037 одним прогоном. Тема K-08: одна сделка от разметки до строки журнала. ЗАЧЕМ ЭТОТ ФАЙЛ ЛЕЖИТ В ПРОЕКТЕ. Канал обещает: всё измерено, а не заявлено. Урок показывает ОДНУ сделку целиком, и каждое число в кадре должно стоять здесь — иначе «каждое число на экране» было бы словами. КАК ВЫБРАНА СДЕЛКА — ДО ВЗГЛЯДА НА ИСХОД. Урок разметки (TW-025, расчёт `markup.py`) оставил на графике Apple пять линий на вечер 16 марта 2026 года. Здесь на каждую линию вечером того же дня ставится заявка по правилам модуля K, и сделкой урока становится та, что исполнилась ПЕРВОЙ. Правило выбора не знает, чем сделка кончилась; выиграла бы она или проиграла — урок был бы тем же. ПРАВИЛА — МОДУЛЯ K, ПО ПОРЯДКУ: * дневной ход — среднее high−low за двадцать дней (урок о буфере, K-02); * стоп — половина дневного хода за уровнем: сделка держится днями, а не часами (урок «крипта против акций», K-06; десятая доля хода из K-02 — для сделок на часы); * буфер — пятая часть стопа; вход — лимитной заявкой на буфер ВНУТРИ уровня, защитный стоп — на полный стоп ЗА уровнем (K-02); * размер — риск сделки, делённый на расстояние от входа до стопа: счёт 25 000 долларов, один процент (K-03); * цель — два стопа от входа, выход по времени через десять дней (K-06); * круг стоит 0,05 % позиции на акциях (K-06). ЧЕСТНОСТЬ УЧЁТА — ТА ЖЕ, ЧТО В МОДУЛЕ S: вход только если цена дошла до заявки; в день входа засчитывается стоп, но не цель; если в один день задеты и стоп, и цель — считается стоп; открытие за стопом исполняется по открытию. ДАННЫЕ. `04_assets/market/AAPL-1d.json`. """ from __future__ import annotations import importlib.util import math import statistics as st import sys from pathlib import Path HERE = Path(__file__).resolve().parent OUT = HERE / "results.txt" MARKUP = HERE.parent / "TW-025-study" / "markup.py" DATE = "2026-03-16" # вечер разметки из урока TW-025 ACCOUNT = 25_000.0 RISK = 0.01 DAYS = 20 # дневной ход для заявок — среднее за двадцать дней (K-02) STOP = 0.5 # стоп в дневных ходах: сделка на дни (K-06) TENTH = 0.1 # стоп для сделок на часы (K-02) — только для сравнения BUFFER = 0.2 # буфер — пятая часть стопа (K-02) TARGET = 2.0 # цель — два стопа от входа (K-06) HOLD = 10 # выход по времени — десять дней (K-06) FEE = 0.0005 # круг на акциях — доля позиции (K-06, samesetup.FEE_STOCK) lines: list[str] = [] def say(s: str = "") -> None: print(s) lines.append(s) def load_markup(): """Расчёт урока разметки — импортом, а не копией: две копии разъедутся.""" spec = importlib.util.spec_from_file_location("markup", MARKUP) mk = importlib.util.module_from_spec(spec) spec.loader.exec_module(mk) return mk def plan(level: float, side: str, day_range: float, stop_k: float) -> dict: """Все числа одной заявки — в том порядке, в каком их считает K-02. Стоп и буфер округляются до цента, как их и ставит трейдер (K-02 тоже округляет: «Round it to fifty»). Иначе вход, стоп и риск на акцию расходились бы в кадре на цент: 265.35 − 261.91 = 3.44, а риск 3.45. """ s = round(stop_k * day_range, 2) # стоп b = round(BUFFER * s, 2) # буфер long = side == "long" entry = level + b if long else level - b stop = level - s if long else level + s risk_ps = abs(entry - stop) # риск на акцию = стоп + буфер shares = math.floor(ACCOUNT * RISK / risk_ps) target = entry + TARGET * risk_ps if long else entry - TARGET * risk_ps value = shares * entry cost = FEE * value return {"level": level, "side": side, "s": s, "b": b, "entry": entry, "stop": stop, "risk_ps": risk_ps, "shares": shares, "target": target, "value": value, "cost": cost, "cost_r": cost / (shares * risk_ps)} def first_fill(r: list[dict], i0: int, p: dict) -> int | None: for j in range(i0 + 1, len(r)): if (r[j]["l"] <= p["entry"]) if p["side"] == "long" else (r[j]["h"] >= p["entry"]): return j return None def walk(r: list[dict], j: int, p: dict) -> tuple[int, float, str]: """День выхода, цена выхода и причина — по правилам честного учёта.""" long = p["side"] == "long" for k in range(j, min(j + HOLD + 1, len(r))): b = r[k] hit_stop = (b["l"] <= p["stop"]) if long else (b["h"] >= p["stop"]) gap = (b["o"] <= p["stop"]) if long else (b["o"] >= p["stop"]) if k > j and gap: return k, b["o"], "открытие за стопом" if hit_stop: return k, p["stop"], "стоп" if k == j: continue # в день входа цель не считается if (b["h"] >= p["target"]) if long else (b["l"] <= p["target"]): return k, p["target"], "цель" k = min(j + HOLD, len(r) - 1) return k, r[k]["c"], "время" def result_r(p: dict, exit_price: float) -> float: move = (exit_price - p["entry"]) if p["side"] == "long" else (p["entry"] - exit_price) return move / p["risk_ps"] def main() -> int: for s in (sys.stdout, sys.stderr): if hasattr(s, "reconfigure"): s.reconfigure(encoding="utf-8", errors="replace") mk = load_markup() r = mk.bars("AAPL") a60 = mk.adr(r) upto = len(r) - mk.CHECK day = r[upto - 1] if day["d"][:10] != DATE: sys.exit(f"Дата разметки сместилась: {day['d'][:10]} вместо {DATE}. " f"Данные обновлены — урок TW-025 и этот урок надо пересчитать вместе.") i0 = upto - 1 cand = mk.candidates(r, upto, a60) near = [c for c in cand if c["far"] <= 8] picked = mk.spaced(sorted(near, key=lambda c: (-mk.score(c), c["far"])), a60[upto]) say("ДАННЫЕ") say(f" AAPL {len(r)} баров {r[0]['d'][:10]} … {r[-1]['d'][:10]}") say(f" Вечер разметки: {DATE}, закрытие {day['c']:.2f}") say("") say("1 · ПЯТЬ ЛИНИЙ РАЗМЕТКИ (тот же расчёт, что в TW-025)") for c in picked: side = "long" if c["price"] < day["c"] else "short" say(f" {c['price']:8.2f} {'поддержка, покупка' if side == 'long' else 'сопротивление, продажа':24}" f" до цены {c['far']:.1f} хода") rng = st.fmean(b["h"] - b["l"] for b in r[i0 - DAYS + 1:i0 + 1]) say("") say("2 · ДНЕВНОЙ ХОД ДЛЯ ЗАЯВОК") say(f" среднее high−low за {DAYS} дней по {DATE} включительно: {rng:.2f}") say(f" ({rng / day['c'] * 100:.2f}% цены; урок разметки мерил медианой 60 дней: " f"{a60[upto]:.2f})") plans = [] say("") say("3 · ЧИСЛА КАЖДОЙ ЗАЯВКИ (стоп — половина хода, буфер — пятая часть стопа)") say(f" {'линия':>8} {'сторона':>7} {'стоп':>6} {'буфер':>6} {'вход':>8} " f"{'защит.':>8} {'риск/акц':>8} {'акций':>6} {'цель':>8} {'позиция':>9} {'круг':>7} {'круг,R':>7}") for c in picked: side = "long" if c["price"] < day["c"] else "short" p = plan(c["price"], side, rng, STOP) plans.append(p) say(f" {p['level']:8.2f} {side:>7} {p['s']:6.2f} {p['b']:6.2f} {p['entry']:8.2f} " f"{p['stop']:8.2f} {p['risk_ps']:8.2f} {p['shares']:6d} {p['target']:8.2f} " f"{p['value']:9.0f} {p['cost']:7.2f} {p['cost_r']:7.3f}") say(f" риск сделки: {ACCOUNT:,.0f} × {RISK:.0%} = {ACCOUNT * RISK:.0f} долларов") # Акции целые, поэтому настоящий риск меньше заданного: его урок # называет вслух («two hundred and forty eight dollars, not two fifty»). for p in plans: say(f" {p['level']:.2f}: настоящий риск {p['shares']} × {p['risk_ps']:.2f} = " f"{p['shares'] * p['risk_ps']:.2f} долларов") say("") say("4 · КАКАЯ ЗАЯВКА ИСПОЛНИЛАСЬ ПЕРВОЙ") fills = [] for p in plans: j = first_fill(r, i0, p) fills.append(j) if j is None: # насколько близко цена подошла к заявке — за всё оставшееся время if p["side"] == "long": k = min(range(i0 + 1, len(r)), key=lambda k: r[k]["l"]) gap = r[k]["l"] - p["entry"] say(f" {p['level']:8.2f}: не исполнилась; ближе всего {r[k]['d'][:10]}, " f"минимум {r[k]['l']:.2f} — выше заявки {p['entry']:.2f} на {gap:.2f}") else: k = max(range(i0 + 1, len(r)), key=lambda k: r[k]["h"]) say(f" {p['level']:8.2f}: не исполнилась; ближе всего {r[k]['d'][:10]}") else: say(f" {p['level']:8.2f}: исполнилась {r[j]['d'][:10]}") first = min((j for j in fills if j is not None), default=None) if first is None: sys.exit("Ни одна заявка не исполнилась — урока нет.") p = plans[fills.index(first)] zone = {} for q in plans: # линия в смысле урока разметки: подход ближе четверти хода (markup.NEAR) near_ = mk.NEAR * a60[upto] k = next((k for k in range(i0 + 1, len(r)) if r[k]["l"] - near_ <= q["level"] <= r[k]["h"] + near_), None) zone[q["level"]] = k say(" подход к линии ближе четверти хода (определение урока разметки):") for lv, k in zone.items(): say(f" {lv:8.2f}: {r[k]['d'][:10] if k is not None else 'не было'}") say(f" → сделка урока: {p['level']:.2f}, {('покупка' if p['side'] == 'long' else 'продажа')}" f" по {p['entry']:.2f}, {r[first]['d'][:10]}") say("") say("5 · СДЕЛКА ПО ДНЯМ") k, px, why = walk(r, first, p) for d in range(first, k + 1): b = r[d] say(f" {b['d'][:10]} открытие {b['o']:7.2f} максимум {b['h']:7.2f} " f"минимум {b['l']:7.2f} закрытие {b['c']:7.2f}") res = result_r(p, px) money = res * p["shares"] * p["risk_ps"] say(f" выход {r[k]['d'][:10]} по {px:.2f} — {why}") say(f" результат до издержек: {res:+.2f} R = {money:+.2f} долларов") say(f" издержки круга: {FEE * 100:.2f}% позиции = {p['cost']:.2f} долларов = {p['cost_r']:.3f} R") say(f" после издержек: {res - p['cost_r']:+.2f} R = {money - p['cost']:+.2f} долларов") say("") say("6 · СТРОКА ЖУРНАЛА") say(f" дата {r[first]['d'][:10]} | AAPL | {('покупка' if p['side'] == 'long' else 'продажа')} | " f"уровень {p['level']:.2f} | вход {p['entry']:.2f} | стоп {p['stop']:.2f} | " f"цель {p['target']:.2f} | {p['shares']} акций | выход {r[k]['d'][:10]} {px:.2f} | " f"{res:+.2f} R | издержки {p['cost_r']:.2f} R | итог {res - p['cost_r']:+.2f} R | " f"правила соблюдены: да") say("") say("7 · ДЛЯ СРАВНЕНИЯ — НЕ В СДЕЛКУ") q = plan(p["level"], p["side"], rng, TENTH) jq = first_fill(r, i0, q) say(f" тот же уровень со стопом в десятую долю хода (K-02, сделка на часы):") say(f" стоп {q['s']:.2f}, буфер {q['b']:.2f}, вход {q['entry']:.2f}, " f"защитный {q['stop']:.2f}, {q['shares']} акций, круг {q['cost_r']:.3f} R") say(f" позиция {q['value']:,.0f} долларов — {q['value'] / ACCOUNT:.1f} счёта " f"(у сделки ролика — {p['value']:,.0f}, {p['value'] / ACCOUNT:.2f} счёта)") if jq is None: say(" не исполнилась") else: kq, pxq, whyq = walk(r, jq, q) same = " — в тот же день" if kq == jq else "" say(f" исполнилась {r[jq]['d'][:10]}, выход {r[kq]['d'][:10]} по {pxq:.2f} — " f"{whyq}{same}: {result_r(q, pxq):+.2f} R") m = plan(p["level"], p["side"], a60[upto], STOP) jm = first_fill(r, i0, m) if jm is not None: km, pxm, whym = walk(r, jm, m) say(f" тот же уровень с ходом урока разметки (медиана 60 дней, {a60[upto]:.2f}): " f"вход {m['entry']:.2f} {r[jm]['d'][:10]}, выход {whym}: {result_r(m, pxm):+.2f} R") OUT.write_text("\n".join(lines) + "\n", encoding="utf-8") print(f"\nнаписано: {OUT}") return 0 if __name__ == "__main__": raise SystemExit(main())