Skip to content

Commit ee744be

Browse files
committed
test: update tests for ListMaker(ABC)
1 parent 22e918e commit ee744be

File tree

2 files changed

+179
-0
lines changed

2 files changed

+179
-0
lines changed

tests/conftest.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@
33
import pytest
44
import okama as ok
55
from pathlib import Path
6+
import numpy as np
7+
import pandas as pd
8+
9+
# Helper classes for Asset/currency mocks
10+
from tests.asset_list.conftest import _FakeAsset, _FakeCurrencyAsset
611

712
data_folder = Path(__file__).parent / "data"
813

@@ -196,3 +201,40 @@ def init_rebalance_no_rebalancing():
196201
abs_deviation=None,
197202
rel_deviation=None,
198203
)
204+
205+
206+
# Global synthetic_env fixture available to all tests
207+
@pytest.fixture
208+
def synthetic_env(mocker):
209+
"""Three assets over 24 months with deterministic correlation (global fixture).
210+
211+
Makes the synthetic_env fixture available to all tests under tests/.
212+
Patches ListMaker._get_asset_obj_dict and the currency Asset to remove external dependencies.
213+
"""
214+
rng = np.random.default_rng(12345)
215+
idx = pd.period_range("2020-01", periods=24, freq="M")
216+
217+
a1 = pd.Series(rng.normal(0.01 / 12, 0.05, size=len(idx)), index=idx, name="IDX.US")
218+
a2 = pd.Series(rng.normal(0.008 / 12, 0.04, size=len(idx)), index=idx, name="A.US")
219+
a3_noise = rng.normal(0, 0.02, size=len(idx))
220+
a3 = pd.Series(0.5 * a1.values + a3_noise, index=idx, name="B.US")
221+
222+
fake_assets = {
223+
"IDX.US": _FakeAsset("IDX.US", a1, currency="USD", name="Index"),
224+
"A.US": _FakeAsset("A.US", a2, currency="USD", name="Asset A"),
225+
"B.US": _FakeAsset("B.US", a3, currency="USD", name="Asset B"),
226+
}
227+
228+
m_get_dict = mocker.patch(
229+
"okama.common.make_asset_list.ListMaker._get_asset_obj_dict", return_value=fake_assets
230+
)
231+
m_currency_asset = mocker.patch(
232+
"okama.common.make_asset_list.asset.Asset", side_effect=_FakeCurrencyAsset
233+
)
234+
235+
yield {
236+
"index": idx,
237+
"series": {k: v for k, v in [("IDX.US", a1), ("A.US", a2), ("B.US", a3)]},
238+
"m_get_dict": m_get_dict,
239+
"m_currency_asset": m_currency_asset,
240+
}

tests/test_list_maker_mocking.py

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
import pandas as pd
2+
import pytest
3+
4+
import okama as ok
5+
from okama.common.make_asset_list import ListMaker
6+
from tests.asset_list.conftest import _FakeAsset, _FakeCurrencyAsset, _ListDefaults
7+
8+
9+
class DummyList(ListMaker):
10+
"""A minimal concrete subclass for testing the abstract ListMaker."""
11+
12+
def __repr__(self): # pragma: no cover - simple implementation so the class can be instantiated
13+
return f"DummyList({len(self.symbols)} assets)"
14+
15+
16+
@pytest.fixture()
17+
def two_assets_env(mocker):
18+
"""Two deterministic assets A.US and B.US for 3 months, base currency USD."""
19+
dm = _ListDefaults()
20+
21+
fake_assets = {
22+
"A.US": _FakeAsset("A.US", dm.ror_a, currency="USD"),
23+
"B.US": _FakeAsset("B.US", dm.ror_b, currency="USD"),
24+
}
25+
mocker.patch("okama.common.make_asset_list.ListMaker._get_asset_obj_dict", return_value=fake_assets)
26+
# Currency object used inside ListMaker.__init__ for self._currency
27+
mocker.patch("okama.common.make_asset_list.asset.Asset", side_effect=_FakeCurrencyAsset)
28+
29+
return {"dm": dm}
30+
31+
32+
def test_basic_init_and_properties(two_assets_env):
33+
dm = two_assets_env["dm"]
34+
lm = DummyList(["A.US", "B.US"], ccy="USD", inflation=False)
35+
36+
# symbols/tickers
37+
assert lm.symbols == ["A.US", "B.US"]
38+
assert lm.tickers == ["A", "B"]
39+
40+
# indexes and return data are taken from mocks
41+
ror = lm.assets_ror
42+
assert list(ror.columns) == ["A.US", "B.US"]
43+
assert list(ror.index) == list(dm.ror_index)
44+
45+
# base currency
46+
assert lm.currency == "USD"
47+
48+
49+
def test_duplicates_are_removed_and_order_preserved(mocker):
50+
dm = _ListDefaults()
51+
fake_assets = {
52+
"A.US": _FakeAsset("A.US", dm.ror_a, currency="USD"),
53+
"B.US": _FakeAsset("B.US", dm.ror_b, currency="USD"),
54+
}
55+
mocker.patch("okama.common.make_asset_list.ListMaker._get_asset_obj_dict", return_value=fake_assets)
56+
mocker.patch("okama.common.make_asset_list.asset.Asset", side_effect=_FakeCurrencyAsset)
57+
58+
lm = DummyList(["A.US", "B.US", "A.US"], ccy="USD", inflation=False)
59+
# Order of first occurrence is preserved, duplicates removed
60+
assert lm.symbols == ["A.US", "B.US"]
61+
62+
63+
def test_len_iter_getitem(two_assets_env):
64+
lm = DummyList(["A.US", "B.US"], ccy="USD", inflation=False)
65+
assert len(lm) == 2
66+
67+
# __getitem__
68+
first = lm[0]
69+
assert hasattr(first, "symbol") and first.symbol in {"A.US", "B.US"}
70+
71+
# __iter__
72+
seen = [obj.symbol for obj in lm]
73+
assert set(seen) == {"A.US", "B.US"}
74+
75+
76+
def test_validate_period_ok_and_fail(synthetic_env):
77+
# 24 months -> pl.years == 2
78+
lm = DummyList(["IDX.US", "A.US", "B.US"], ccy="USD", inflation=False)
79+
80+
# valid: period of 1 year
81+
lm._validate_period(1)
82+
83+
# invalid: period exceeds available history in years
84+
with pytest.raises(ValueError):
85+
lm._validate_period(3)
86+
87+
88+
def test_get_asset_obj_dict_raises_for_short_history(mocker):
89+
# Prepare a mock for okama.common.make_asset_list.asset.Asset to return an object with short history
90+
from collections import namedtuple
91+
92+
PL = ok.settings.PeriodLength
93+
94+
class TinyAsset:
95+
def __init__(self, symbol: str):
96+
self.symbol = symbol
97+
self.ror = pd.Series(dtype=float)
98+
self.pl = PL(0, 2) # 0 years and 2 months -> should raise ShortPeriodLengthError
99+
100+
mocker.patch("okama.common.make_asset_list.asset.Asset", side_effect=TinyAsset)
101+
102+
with pytest.raises(ok.common.error.ShortPeriodLengthError):
103+
ListMaker._get_asset_obj_dict(["X.US"]) # staticmethod
104+
105+
106+
@pytest.fixture()
107+
def _inflation_env_for_listmaker(mocker):
108+
"""Minimal inflation environment for DummyList(inflation=True)."""
109+
idx = pd.period_range("2020-01", periods=12, freq="M")
110+
ror = pd.Series(0.01, index=idx, name="A.US")
111+
112+
# assets
113+
fake_assets = {"A.US": _FakeAsset("A.US", ror, currency="USD")}
114+
mocker.patch("okama.common.make_asset_list.ListMaker._get_asset_obj_dict", return_value=fake_assets)
115+
mocker.patch("okama.common.make_asset_list.asset.Asset", side_effect=_FakeCurrencyAsset)
116+
117+
# inflation: constant monthly values
118+
infl_monthly = pd.Series(0.002, index=idx.to_timestamp(how="end"), name="USD.INFL")
119+
120+
class _FakeInflation:
121+
def __init__(self, symbol: str, first_date=None, last_date=None):
122+
self.symbol = symbol
123+
self.first_date = infl_monthly.index[0].to_period("M").to_timestamp()
124+
self.last_date = infl_monthly.index[-1].to_period("M").to_timestamp()
125+
self.values_monthly = infl_monthly.to_period("M")
126+
127+
mocker.patch("okama.common.make_asset_list.macro.Inflation", side_effect=_FakeInflation)
128+
return {"idx": idx, "infl": infl_monthly.to_period("M")}
129+
130+
131+
def test_inflation_true_sets_fields_and_aligns_index(_inflation_env_for_listmaker):
132+
lm = DummyList(["A.US"], ccy="USD", inflation=True)
133+
134+
assert hasattr(lm, "inflation")
135+
assert hasattr(lm, "inflation_ts")
136+
assert isinstance(lm.inflation_ts, pd.Series)
137+
assert list(lm.inflation_ts.index) == list(lm.assets_ror.index)

0 commit comments

Comments
 (0)