#!/usr/bin/env python3 """Все числа TW-041 одним прогоном. Тема Y: как понять, что ваша версия работает. ЗАЧЕМ ЭТОТ ФАЙЛ ЛЕЖИТ В ПРОЕКТЕ. Канал обещает: всё измерено, а не заявлено. Курс много раз говорил «это шум» и «нужно больше сделок», но ни разу не дал зрителю правила, по которому ЕГО собственную правку можно признать или отвергнуть. Здесь это правило — и каждое его число посчитано. СДЕЛКИ — ТЕ ЖЕ 808 ОТБОЕВ, что в уроке о ведении позиции (TW-039, `manage.py`), импортом его функции `trades()`: одни и те же касания, каждое — четырьмя способами ведения (A — не трогать, B — безубыток после +1R, C — половину на +1R, D — оба). Так «разница сделка к сделке» здесь та же, что зритель видел в том уроке. ТРИ ВИДА СРАВНЕНИЯ, которые зритель будет делать на самом деле: 1. своя средняя против числа (нуля или опубликованного результата): погрешность σ/√n — нужно 4σ²/δ² сделок; 2. две версии на РАЗНЫХ сделках (другой вход, другой инструмент): погрешность σ·√(2/n) — нужно 8σ²/δ² в КАЖДОЙ группе (формула урока о журнале, TW-035); 3. две версии на ОДНИХ сделках (другое ведение, другой выход): погрешность σ_разн/√n — нужно 4σ_разн²/δ², а σ_разн меньше σ, потому что большинство сделок у обеих версий кончается одинаково. Порог канала везде один: разница не меньше двух своих погрешностей. ДВА ОПЫТА НА СВОИХ ЖЕ СДЕЛКАХ — не формулой, а пересэмплированием настоящих результатов способа A (бутстрэп, зерно фиксировано, прогон воспроизводим): * версия с настоящим преимуществом +0,10 R — как часто после N сделок её средняя ниже нуля; * версия без преимущества — как часто она хоть раз «доходит до двух погрешностей», если смотреть на итог после каждых десяти сделок, против одного взгляда в конце. """ from __future__ import annotations import importlib.util import math import random import statistics as st import sys from pathlib import Path HERE = Path(__file__).resolve().parent OUT = HERE / "results.txt" MANAGE = HERE.parent / "TW-039-study" / "manage.py" DELTAS = [0.05, 0.10, 0.20, 0.50] TRADES_A_WEEK = 2 EDGE = 0.10 # настоящее преимущество в первом опыте NS = [20, 50, 100, 200, 500, 1000] RUNS = 20000 PEEK_EVERY = 10 PEEK_UPTO = 300 SEED = 41 lines: list[str] = [] def say(s: str = "") -> None: print(s) lines.append(s) def load_manage(): spec = importlib.util.spec_from_file_location("manage", MANAGE) mk = importlib.util.module_from_spec(spec) spec.loader.exec_module(mk) return mk def years(n: float) -> float: return n / (TRADES_A_WEEK * 52) def phi(z: float) -> float: return 0.5 * (1 + math.erf(z / math.sqrt(2))) def main() -> int: for s_ in (sys.stdout, sys.stderr): if hasattr(s_, "reconfigure"): s_.reconfigure(encoding="utf-8", errors="replace") mg = load_manage() T, meta = mg.trades() A = [t["A"] for t in T] n_all = len(T) sigma = st.pstdev(A) say("ДАННЫЕ") say(f" Сделки урока TW-039 (manage.py → trades()), окно {meta['start']} … {meta['end']}") say(f" сделок: {n_all}; способы: A не трогать, B безубыток после +1R, C половину на +1R, D оба") say(f" порог канала: разница не меньше двух своих погрешностей") say("") say("1 · РАЗБРОС ОДНОЙ СДЕЛКИ И РАЗБРОС РАЗНИЦЫ") say(f" способ A: средняя {st.fmean(A):+.3f} R, разброс одной сделки σ = {sigma:.2f} R") sd = {} for w in "BCD": d = [t[w] - t["A"] for t in T] sd[w] = st.pstdev(d) diff = sum(1 for x in d if abs(x) > 1e-9) say(f" {w} − A: разброс разницы {sd[w]:.2f} R; исход отличается у {diff} из {n_all} " f"({diff / n_all * 100:.0f}%), у остальных {n_all - diff} — тот же до цента") say("") say("2 · СКОЛЬКО СДЕЛОК НУЖНО, ЧТОБЫ РАЗНИЦА ДОШЛА ДО ДВУХ ПОГРЕШНОСТЕЙ") say(f" при {TRADES_A_WEEK} сделках в неделю; σ = {sigma:.2f} R, разброс разницы B − A = {sd['B']:.2f} R") say(f" {'разница':>8} {'своя средняя против числа':>27} {'две версии, разные сделки':>27} {'две версии, одни сделки (B−A)':>31}") for delta in DELTAS: one = math.ceil(4 * sigma ** 2 / delta ** 2) two = 2 * math.ceil(8 * sigma ** 2 / delta ** 2) par = math.ceil(4 * sd["B"] ** 2 / delta ** 2) say(f" {delta:7.2f}R {one:>10,} сделок · {years(one):5.1f} лет " f"{two:>10,} сделок · {years(two):5.1f} лет " f"{par:>10,} сделок · {years(par):5.1f} лет") two10 = 2 * math.ceil(8 * sigma ** 2 / 0.1 ** 2) par10 = math.ceil(4 * sd["B"] ** 2 / 0.1 ** 2) say(f" при разнице 0.10R одни сделки вместо разных: {par10:,} против {two10:,} — " f"в {two10 / par10:.0f} раз меньше") say(" те же разницы для C − A и D − A (одни сделки):") for w in "CD": cells = " ".join(f"{d_:.2f}R → {math.ceil(4 * sd[w] ** 2 / d_ ** 2):,}" for d_ in DELTAS) say(f" {w} − A: {cells}") rng = random.Random(SEED) mean_a = st.fmean(A) say("") say(f"3 · НАСТОЯЩЕЕ ПРЕИМУЩЕСТВО +{EDGE:.2f} R — КАК ЧАСТО ОНО ВЫГЛЯДИТ УБЫТКОМ") say(f" результаты способа A, сдвинутые так, что средняя ровно +{EDGE:.2f} R; {RUNS:,} прогонов на каждое N") edge = [x - mean_a + EDGE for x in A] for n in NS: below = 0 for _ in range(RUNS): if sum(rng.choice(edge) for _ in range(n)) < 0: below += 1 say(f" после {n:>5} сделок: средняя ниже нуля в {below / RUNS * 100:5.1f}% прогонов " f"(по формуле {phi(-EDGE * math.sqrt(n) / sigma) * 100:5.1f}%)") say("") say("4 · ПОДГЛЯДЫВАНИЕ — ВЕРСИЯ БЕЗ ПРЕИМУЩЕСТВА") say(f" результаты способа A, сдвинутые к средней ровно 0; {RUNS:,} прогонов по {PEEK_UPTO} сделок") zero = [x - mean_a for x in A] once = peek = 0 first = [] for _ in range(RUNS): xs, s1, s2, hit = [], 0.0, 0.0, None for k in range(1, PEEK_UPTO + 1): x = rng.choice(zero) s1 += x s2 += x * x if k % PEEK_EVERY == 0: m = s1 / k var = max(s2 / k - m * m, 0.0) se = math.sqrt(var / k) if hit is None and se > 0 and m >= 2 * se: hit = k m = s1 / PEEK_UPTO se = math.sqrt(max(s2 / PEEK_UPTO - m * m, 0.0) / PEEK_UPTO) if se > 0 and m >= 2 * se: once += 1 if hit is not None: peek += 1 first.append(hit) say(f" один взгляд после {PEEK_UPTO} сделок: «лучше на две погрешности» в {once / RUNS * 100:.1f}% прогонов") say(f" взгляд после каждых {PEEK_EVERY} сделок: хоть раз «лучше на две погрешности» в {peek / RUNS * 100:.1f}% прогонов") if once: say(f" подглядывание: ложная тревога в {peek / once:.1f} раза чаще, чем при одном взгляде") if first: early = sum(1 for k in first if k <= 50) say(f" из этих ложных тревог {early / len(first) * 100:.0f}% пришлись на первые 50 сделок") say("") say("5 · ВЫВОД — ПРАВИЛО РЕШЕНИЯ") say(" До первой сделки: одна правка, одно сравнение, размер разницы, ради которой") say(" правка стоит хлопот, и число сделок из таблицы раздела 2. Где можно — сравнивать") say(" на одних и тех же сделках: так нужно в разы меньше сделок.") say(" Во время: не останавливаться на удачной серии — подглядывание множит ложные тревоги.") say(" В конце: разница не меньше двух погрешностей — измерено. Меньше — правка") say(" не доказала себя, и остаётся более простая версия.") OUT.write_text("\n".join(lines) + "\n", encoding="utf-8") print(f"\nнаписано: {OUT}") return 0 if __name__ == "__main__": raise SystemExit(main())