#!/usr/bin/env python3 """Все числа TW-018 одним прогоном. Тема F-02: что бар говорит и чего не говорит. ЗАЧЕМ ЭТОТ ФАЙЛ ЛЕЖИТ В ПРОЕКТЕ. Канал обещает: всё измерено, а не заявлено. Обещание стоит ровно столько, сколько стоит возможность пересчитать. ЧТО ПРОВЕРЯЕТСЯ. Принято считать, что бар говорит о борьбе сторон. Это верно, и здесь оно измеряется: сколько дневного хода отыгрывается назад. Дальше проверяется обратное — то, чего бар не говорит, хотя все читают. 1. Доля тела в диапазоне. Если стороны и правда борются, тело должно быть заметно меньше диапазона: большая часть пройденного возвращается. 2. Где бар закрылся — в верхней трети, в середине, в нижней — и вырос ли следующий день. Расхожее чтение: закрытие у максимума значит силу. 3. Длинная нижняя тень, «молот». Расхожее чтение: отбой, разворот вверх. ВАЖНО ПРО БАЗУ СРАВНЕНИЯ. Доля растущих дней сама по себе около 52-53 %, и без неё любой процент выглядит осмысленным. Поэтому каждая группа сравнивается с остальными барами того же набора, а не с половиной. ЧЕГО ЗДЕСЬ НЕТ И БЫТЬ НЕ МОЖЕТ. Порядок событий внутри бара. Четыре числа не говорят, что было раньше — максимум или минимум, — и никакой расчёт этого не восстановит. Это не ограничение данных, это ограничение самой записи, и в ролике оно показано схемой. ДАННЫЕ. `04_assets/market/*-1d.json`, шесть инструментов целиком. """ from __future__ import annotations import json import statistics as st import sys from pathlib import Path STUDIO = Path(__file__).resolve().parents[3] MARKET = STUDIO / "tickwright" / "04_assets" / "market" NAMES = ["AAPL", "TSLA", "SPY", "BTCUSDT", "ETHUSDT", "SOLUSDT"] WICK = 0.6 # «длинная тень» — больше этой доли диапазона def bars(name: str) -> list[dict]: d = json.loads((MARKET / f"{name}-1d.json").read_text(encoding="utf-8")) return d["bars"] if isinstance(d, dict) and "bars" in d else d def main() -> int: for s in (sys.stdout, sys.stderr): if hasattr(s, "reconfigure"): s.reconfigure(encoding="utf-8", errors="replace") rows = {n: bars(n) for n in NAMES} print("ДАННЫЕ") for n, r in rows.items(): print(f" {n:9} {len(r)} баров {r[0]['d']} … {r[-1]['d']}") print("\n1 · СКОЛЬКО ХОДА ОТЫГРЫВАЕТСЯ НАЗАД") bodies = [abs(b["c"] - b["o"]) / (b["h"] - b["l"]) for r in rows.values() for b in r if b["h"] > b["l"]] med = st.median(bodies) print(f" баров в счёте: {len(bodies)}") print(f" медиана тела: {med:.2f} диапазона") print(f" значит {(1 - med) * 100:.0f}% пройденного возвращается") print(f" тело меньше трети диапазона: " f"{sum(1 for x in bodies if x < 1 / 3) / len(bodies) * 100:.0f}% баров") print("\n2 · ГДЕ ЗАКРЫЛСЯ БАР → ВЫРОС ЛИ СЛЕДУЮЩИЙ ДЕНЬ") groups: dict[str, list[int]] = {} for r in rows.values(): for i in range(len(r) - 1): b = r[i] rng = b["h"] - b["l"] if rng <= 0: continue pos = (b["c"] - b["l"]) / rng key = ("верхняя треть" if pos > 2 / 3 else "нижняя треть" if pos < 1 / 3 else "середина") groups.setdefault(key, []).append(1 if r[i + 1]["c"] > b["c"] else 0) for k in ("верхняя треть", "середина", "нижняя треть"): v = groups[k] print(f" {k:15} {len(v):5} баров · следующий день выше в " f"{sum(v) / len(v) * 100:.1f}%") every = [x for v in groups.values() for x in v] print(f" {'все бары':15} {len(every):5} баров · выше в " f"{sum(every) / len(every) * 100:.1f}% ← база сравнения") print(f"\n3 · ДЛИННАЯ НИЖНЯЯ ТЕНЬ (больше {WICK:.0%} диапазона)") ham, other = [], [] for r in rows.values(): for i in range(len(r) - 1): b = r[i] rng = b["h"] - b["l"] if rng <= 0: continue wick = (min(b["o"], b["c"]) - b["l"]) / rng up = 1 if r[i + 1]["c"] > b["c"] else 0 (ham if wick > WICK else other).append(up) print(f" таких баров {len(ham)} · следующий день выше в " f"{sum(ham) / len(ham) * 100:.1f}%") print(f" все остальные {len(other)} · выше в " f"{sum(other) / len(other) * 100:.1f}%") print("") print("4 · НАПРАВЛЕНИЕ ДНЯ РЕШАЕТСЯ ПОСЛЕ ОТКРЫТИЯ") flip = tot = 0 for r in (rows[n] for n in NAMES if not n.endswith('USDT')): for j in range(1, len(r)): prev = r[j - 1]["c"] tot += 1 if (r[j]["o"] > prev) != (r[j]["c"] > prev): flip += 1 print(f" баров, где знак дня перевернулся после открытия: " f"{flip / tot * 100:.0f}%") print("") print("5 · ФОРМА НЕ ГОВОРИТ НИЧЕГО, А РАЗМЕР ГОВОРИТ") big, small = [], [] for r in rows.values(): adr = sum(x["h"] - x["l"] for x in r) / len(r) for j in range(len(r) - 1): rng = r[j]["h"] - r[j]["l"] nxt = abs(r[j + 1]["c"] - r[j]["c"]) / adr if rng > 1.5 * adr: big.append(nxt) elif rng < 0.6 * adr: small.append(nxt) print(f" после большого бара (>1.5 обычного хода): следующий день " f"двигает {st.median(big):.2f} хода, n={len(big)}") print(f" после маленького (<0.6 хода): " f"{st.median(small):.2f} хода, n={len(small)}") print(" Разница втрое — там, где форма не дала ничего.") print("\nВЫВОД. Бар честно показывает, что стороны спорили: больше") print("половины пройденного возвращается назад. Но ни его форма, ни") print("место закрытия не говорят ничего о следующем дне — разница") print("с базой в пределах половины процента, а у «молота» она даже") print("в минус. Читать в одном баре предсказание нечего.") print("А вот РАЗМЕР бара работает: после крупного дня рынок") print("проходит втрое больше, чем после тихого. Читается") print("не форма, а сравнение с обычным ходом инструмента.") return 0 def _run() -> int: """Прогон со снимком: что напечатано, то и легло в `results.txt`. Пока файл клали руками, он отставал от скрипта, и сценарий писался по устаревшим числам. Здесь отставать нечему. Снимок пишется только при удачном прогоне. Обрубок опаснее отсутствия: он выглядит как настоящий отчёт. """ import io real = sys.stdout for s in (real, sys.stderr): if hasattr(s, "reconfigure"): s.reconfigure(encoding="utf-8", errors="replace") buf = io.StringIO() class _Tee: """Поток, который пишет в два места и не спорит с вызовами скрипта.""" encoding = "utf-8" errors = "replace" def write(self, text): real.write(text) buf.write(text) return len(text) def flush(self): real.flush() def reconfigure(self, **kw): pass def isatty(self): return False sys.stdout = _Tee() try: code = main() finally: sys.stdout = real if code == 0: out = Path(__file__).with_name("results.txt") out.write_text(buf.getvalue(), encoding="utf-8", newline="\n") print(f"\nснимок: {out}") return code if __name__ == "__main__": raise SystemExit(_run())