import numpy as np import pandas as pd from src.live.basket_trend_worker import BasketTrendWorker def _ramp_df(n=300, slope=1.0): c = np.linspace(100, 100 + slope * n, n) ts = (pd.date_range("2024-01-01", periods=n, freq="4h", tz="UTC").astype("int64") // 10**6) return pd.DataFrame({"timestamp": ts, "open": c, "high": c, "low": c, "close": c, "volume": 1.0}) def test_basket_goes_long_in_uptrend(tmp_path): w = BasketTrendWorker(universe=["AAA", "BBB"], tf="4h", capital=1000.0, data_dir=tmp_path) data = {"AAA": _ramp_df(slope=1.0), "BBB": _ramp_df(slope=1.0)} w.tick(data) assert w.positions["AAA"] == 1.0 and w.positions["BBB"] == 1.0 def test_basket_flat_in_downtrend(tmp_path): w = BasketTrendWorker(universe=["AAA"], tf="4h", capital=1000.0, data_dir=tmp_path) data = {"AAA": _ramp_df(slope=-1.0)} w.tick(data) assert w.positions["AAA"] == 0.0 def test_basket_persists_and_resumes(tmp_path): w = BasketTrendWorker(universe=["AAA"], tf="4h", capital=1000.0, data_dir=tmp_path) w.tick({"AAA": _ramp_df(slope=1.0)}) w2 = BasketTrendWorker(universe=["AAA"], tf="4h", capital=1000.0, data_dir=tmp_path) assert w2.positions["AAA"] == 1.0 # stato ripreso da status.json def _ramp_df_now(n=300, slope=1.0, forming_close=None): """Serie 4h che termina ADESSO: l'ultima riga e' la candela IN FORMAZIONE.""" import time c = np.linspace(100, 100 + slope * n, n) if forming_close is not None: c = c.copy() c[-1] = forming_close bar = 4 * 3_600_000 now_ms = int(time.time() * 1000) ts = now_ms - bar * np.arange(n - 1, -1, -1) return pd.DataFrame({"timestamp": ts, "open": c, "high": c, "low": c, "close": c, "volume": 1.0}) def test_basket_ignores_forming_bar(tmp_path): # downtrend su barre COMPLETE (target flat); la candela in formazione e' un # spike estremo che flipperebbe EMA20>EMA100 se contata -> va ignorata. w = BasketTrendWorker(universe=["AAA"], tf="4h", capital=1000.0, data_dir=tmp_path) w.tick({"AAA": _ramp_df_now(slope=-0.3, forming_close=1e6)}) assert w.positions["AAA"] == 0.0 def test_basket_fee_includes_leverage(tmp_path): # flip 0->1: fee = cap * POS * LEV * fee_rt/2 (reference honest_improve2:150) w = BasketTrendWorker(universe=["AAA"], tf="4h", capital=1000.0, data_dir=tmp_path) w.tick({"AAA": _ramp_df(slope=1.0)}) expected = 1000.0 - 1000.0 * w.position_size * w.leverage * (w.fee_rt / 2) assert np.isclose(w.capital, expected)