Привет

Первая запись: переход от дискреционной торговли к алгоритмам и бэктестированию gap fade стратегий.

Запуск исследовательского журнала algos.trade. После нескольких лет дискреционной торговли акциями малой капитализации США (US small caps), преимущественно по стратегии утреннего затухания гэпов (gap fade) на speculari-fund.blogspot.com, исследовательская работа и исполнение перенесены в систематический код.

Этот блог задуман как исследовательский блокнот: прикладная статистика, бэктесты, распределения доходностей и инфраструктура прямого исполнения на рынке. Никаких прогнозов, «сигналов» и околорыночного шума.

Сводные метрики базовой модели

Примечание: приведенные ниже числовые показатели являются демонстрационным примером (placeholder) для проверки оформления таблиц и не представляют собой реальные торговые результаты.

Ниже приведены предварительные сводные результаты бэктеста базовой модели затухания утренних гэпов на выборке за 2024–2026 гг. (тикеры с капитализацией до $1B и ценой акции от $1 до $15):

Метрика Значение Описание
Trades 1,428 Количество исполненных сделок
Win Rate 61.4% Доля прибыльных сделок
Profit Factor 1.84 Отношение валовой прибыли к убыткам
Sharpe Ratio 2.12 Коэффициент Шарпа (годовой, risk-free = 0%)
Max Drawdown -8.6% Максимальная просадка эквити
Avg Holding Time 42 min Среднее время нахождения в позиции
Avg Win / Avg Loss 1.16 Отношение среднего выигрыша к среднему проигрышу

Модуль фильтрации кандидатов

Фрагмент Python-пайплайна предварительного отбора кандидатов на премаркете по величине гэпа и относительному объему (RVOL):

from dataclasses import dataclass
import pandas as pd

@dataclass(frozen=True)
class GapFilterConfig:
    min_gap_pct: float = 15.0
    min_premarket_volume: int = 250_000
    min_rvol: float = 2.0
    min_price: float = 1.0
    max_price: float = 20.0

def filter_gap_candidates(df: pd.DataFrame, cfg: GapFilterConfig) -> pd.DataFrame:
    """
    Фильтрация пула тикеров премаркета для gap fade модели.
    Ожидаются колонки: symbol, prev_close, pm_open, pm_volume, avg_daily_vol.
    """
    gap_pct = ((df["pm_open"] - df["prev_close"]) / df["prev_close"]) * 100.0
    rvol = df["pm_volume"] / (df["avg_daily_vol"] * 0.1)

    mask = (
        (gap_pct >= cfg.min_gap_pct)
        & (df["pm_volume"] >= cfg.min_premarket_volume)
        & (rvol >= cfg.min_rvol)
        & (df["pm_open"].between(cfg.min_price, cfg.max_price))
    )

    result = df.loc[mask].copy()
    result["gap_pct"] = gap_pct.round(2)
    result["rvol"] = rvol.round(2)
    return result.sort_values(by="gap_pct", ascending=False).reset_index(drop=True)

В следующих записях разберем учет транзакционных издержек, шорт-локейтов (borrow fees) и специфику моделирования проскальзывания в утренние минуты открытого рынка.