You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
fithia2/tests/unit/tools/test_yahoo_gainers_hypothes...

131 lines
4.9 KiB
Python

from __future__ import annotations
import datetime as dt
from libs.backtest.daily_gainers import (
DailyGainerCriteria,
collect_daily_gainer_signals,
evaluate_daily_gainer_signals,
)
def _bar(
open_price: float,
high: float,
low: float,
close: float,
volume: int,
) -> dict[str, float | int]:
return {
"open": open_price,
"high": high,
"low": low,
"close": close,
"volume": volume,
}
def test_collect_daily_gainer_signals_ranks_by_return_then_liquidity() -> None:
start = dt.date(2024, 1, 1)
dates = [start + dt.timedelta(days=i) for i in range(24)]
bars_by_symbol: dict[str, dict[dt.date, dict[str, float | int]]] = {
"AAA": {},
"BBB": {},
"CCC": {},
}
for idx, date in enumerate(dates[:-1]):
base_price = 100.0 + idx
for symbol in bars_by_symbol:
bars_by_symbol[symbol][date] = _bar(base_price, base_price + 1, base_price - 1, base_price, 1_000_000)
signal_date = dates[-1]
bars_by_symbol["AAA"][signal_date] = _bar(120.0, 128.0, 118.0, 126.0, 4_000_000)
bars_by_symbol["BBB"][signal_date] = _bar(121.0, 130.0, 119.0, 129.0, 3_000_000)
bars_by_symbol["CCC"][signal_date] = _bar(119.0, 125.0, 112.0, 116.0, 4_000_000)
criteria = DailyGainerCriteria(
min_change=0.03,
max_change=0.35,
min_volume_ratio=1.2,
min_close_location=0.55,
min_avg_dollar_volume=50_000_000.0,
top_n=2,
)
signals_by_date = collect_daily_gainer_signals(
bars_by_symbol=bars_by_symbol,
start_date=signal_date,
end_date=signal_date,
criteria=criteria,
)
signals = signals_by_date[signal_date]
assert [signal.symbol for signal in signals] == ["BBB", "AAA"]
assert [signal.rank for signal in signals] == [1, 2]
def test_evaluate_daily_gainer_signals_uses_next_open_entry() -> None:
signal_date = dt.date(2024, 2, 1)
entry_date = dt.date(2024, 2, 2)
day3 = dt.date(2024, 2, 5)
day5 = dt.date(2024, 2, 7)
bars_by_symbol = {
"AAA": {
dt.date(2024, 1, 2): _bar(100.0, 101.0, 99.0, 100.0, 1_000_000),
dt.date(2024, 1, 3): _bar(101.0, 102.0, 100.0, 101.0, 1_000_000),
dt.date(2024, 1, 4): _bar(102.0, 103.0, 101.0, 102.0, 1_000_000),
dt.date(2024, 1, 5): _bar(103.0, 104.0, 102.0, 103.0, 1_000_000),
dt.date(2024, 1, 8): _bar(104.0, 105.0, 103.0, 104.0, 1_000_000),
dt.date(2024, 1, 9): _bar(105.0, 106.0, 104.0, 105.0, 1_000_000),
dt.date(2024, 1, 10): _bar(106.0, 107.0, 105.0, 106.0, 1_000_000),
dt.date(2024, 1, 11): _bar(107.0, 108.0, 106.0, 107.0, 1_000_000),
dt.date(2024, 1, 12): _bar(108.0, 109.0, 107.0, 108.0, 1_000_000),
dt.date(2024, 1, 16): _bar(109.0, 110.0, 108.0, 109.0, 1_000_000),
dt.date(2024, 1, 17): _bar(110.0, 111.0, 109.0, 110.0, 1_000_000),
dt.date(2024, 1, 18): _bar(111.0, 112.0, 110.0, 111.0, 1_000_000),
dt.date(2024, 1, 19): _bar(112.0, 113.0, 111.0, 112.0, 1_000_000),
dt.date(2024, 1, 22): _bar(113.0, 114.0, 112.0, 113.0, 1_000_000),
dt.date(2024, 1, 23): _bar(114.0, 115.0, 113.0, 114.0, 1_000_000),
dt.date(2024, 1, 24): _bar(115.0, 116.0, 114.0, 115.0, 1_000_000),
dt.date(2024, 1, 25): _bar(116.0, 117.0, 115.0, 116.0, 1_000_000),
dt.date(2024, 1, 26): _bar(117.0, 118.0, 116.0, 117.0, 1_000_000),
dt.date(2024, 1, 29): _bar(118.0, 119.0, 117.0, 118.0, 1_000_000),
dt.date(2024, 1, 30): _bar(119.0, 120.0, 118.0, 119.0, 1_000_000),
dt.date(2024, 1, 31): _bar(120.0, 121.0, 119.0, 120.0, 1_000_000),
signal_date: _bar(123.0, 130.0, 122.0, 128.0, 4_000_000),
entry_date: _bar(129.0, 132.0, 128.0, 131.0, 3_000_000),
day3: _bar(130.0, 136.0, 129.0, 135.0, 3_000_000),
dt.date(2024, 2, 6): _bar(134.0, 138.0, 133.0, 137.0, 3_000_000),
day5: _bar(137.0, 140.0, 136.0, 139.0, 3_000_000),
}
}
signals_by_date = collect_daily_gainer_signals(
bars_by_symbol=bars_by_symbol,
start_date=signal_date,
end_date=signal_date,
criteria=DailyGainerCriteria(
min_change=0.05,
max_change=0.30,
min_volume_ratio=1.2,
min_close_location=0.55,
min_avg_dollar_volume=50_000_000.0,
top_n=1,
),
)
trades = evaluate_daily_gainer_signals(
signals_by_date=signals_by_date,
bars_by_symbol=bars_by_symbol,
)
assert len(trades) == 1
trade = trades[0]
assert trade.entry_date == entry_date
assert trade.entry_price == 129.0
assert trade.entry_gap_pct == 129.0 / 128.0 - 1.0
assert trade.hold_1d_return == 131.0 / 129.0 - 1.0
assert trade.hold_3d_return == 137.0 / 129.0 - 1.0
assert trade.hold_5d_return is None