feat: train forecasts on trade outcomes
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import math
|
||||
|
||||
import pytest
|
||||
import torch
|
||||
|
||||
from crypto_spot_bot.models import Candle
|
||||
from crypto_spot_bot.time_series import _torch_head_outputs
|
||||
from tools.train_torch_recurrent_forecaster import (
|
||||
OUTPUT_LAYOUT,
|
||||
RecurrentReturnModel,
|
||||
_barrier_outcome,
|
||||
_export_head_state,
|
||||
)
|
||||
|
||||
|
||||
def _candle(index: int, *, open_: float, high: float, low: float, close: float) -> Candle:
|
||||
return Candle(index, open_, high, low, close, 100.0)
|
||||
|
||||
|
||||
def test_barrier_target_uses_next_open_and_marks_take_profit_first() -> None:
|
||||
candles = [
|
||||
_candle(0, open_=90.0, high=101.0, low=89.0, close=100.0),
|
||||
_candle(1, open_=100.0, high=102.0, low=99.0, close=101.0),
|
||||
_candle(2, open_=101.0, high=104.0, low=100.0, close=103.0),
|
||||
]
|
||||
|
||||
net_return, event = _barrier_outcome(
|
||||
candles,
|
||||
end_index=0,
|
||||
horizon=2,
|
||||
stop_loss_percent=0.02,
|
||||
take_profit_percent=0.03,
|
||||
round_trip_cost=0.002,
|
||||
) or (math.nan, math.nan)
|
||||
|
||||
assert event == 1.0
|
||||
assert net_return == pytest.approx(math.log(1.03) - 0.002)
|
||||
|
||||
|
||||
def test_barrier_target_resolves_same_candle_tie_as_stop_loss() -> None:
|
||||
candles = [
|
||||
_candle(0, open_=100.0, high=101.0, low=99.0, close=100.0),
|
||||
_candle(1, open_=100.0, high=104.0, low=97.0, close=101.0),
|
||||
]
|
||||
|
||||
net_return, event = _barrier_outcome(
|
||||
candles,
|
||||
end_index=0,
|
||||
horizon=1,
|
||||
stop_loss_percent=0.02,
|
||||
take_profit_percent=0.03,
|
||||
round_trip_cost=0.002,
|
||||
) or (math.nan, math.nan)
|
||||
|
||||
assert event == 0.0
|
||||
assert net_return == pytest.approx(math.log(0.98) - 0.002)
|
||||
|
||||
|
||||
def test_multitask_head_export_matches_runtime_inference() -> None:
|
||||
torch.manual_seed(7)
|
||||
model = RecurrentReturnModel(
|
||||
architecture="gru",
|
||||
input_size=2,
|
||||
hidden_size=4,
|
||||
num_layers=1,
|
||||
dropout=0.0,
|
||||
output_size=2 * len(OUTPUT_LAYOUT),
|
||||
attention_pooling=False,
|
||||
context_norm=False,
|
||||
multitask_head=True,
|
||||
head_hidden_size=6,
|
||||
)
|
||||
model.eval()
|
||||
context = torch.tensor([[0.2, -0.1, 0.4, 0.3]], dtype=torch.float32)
|
||||
with torch.no_grad():
|
||||
shared = model.head_activation(model.head_hidden(context))
|
||||
returns = model.return_head(shared).view(1, 2, 4)
|
||||
events = model.event_head(shared).view(1, 2, 1)
|
||||
expected = torch.cat((returns, events), dim=2).reshape(-1).tolist()
|
||||
|
||||
entry = {"multitask_head": True, **_export_head_state(model)}
|
||||
actual = _torch_head_outputs(context[0].tolist(), entry, hidden_size=4)
|
||||
|
||||
assert actual == pytest.approx(expected, abs=2e-6)
|
||||
Reference in New Issue
Block a user