mirror of
https://github.com/microsoft/qlib.git
synced 2026-07-09 14:00:55 +08:00
refactor: implement deterministic budget allocation in SoftTopkStrategy (#2077)
* refactor: implement deterministic budget allocation in SoftTopkStrategy * style: fix formatting issues using black * fix: remove unused imports and pass pylint * refactor: simplify SoftTopkStrategy impact limit * style: relocate test files per maintainer request
This commit is contained in:
56
tests/backtest/test_soft_topk_strategy.py
Normal file
56
tests/backtest/test_soft_topk_strategy.py
Normal file
@@ -0,0 +1,56 @@
|
||||
import pandas as pd
|
||||
import pytest
|
||||
from qlib.contrib.strategy.cost_control import SoftTopkStrategy
|
||||
|
||||
|
||||
class MockPosition:
|
||||
def __init__(self, weights):
|
||||
self.weights = weights
|
||||
|
||||
def get_stock_weight_dict(self, only_stock=True):
|
||||
return self.weights
|
||||
|
||||
|
||||
def test_soft_topk_logic():
|
||||
# Initial: A=0.8, B=0.2 (Total=1.0). Target Risk=0.95.
|
||||
# Scores: A and B are low, C and D are topk.
|
||||
scores = pd.Series({"C": 0.9, "D": 0.8, "A": 0.1, "B": 0.1})
|
||||
current_pos = MockPosition({"A": 0.8, "B": 0.2})
|
||||
|
||||
topk = 2
|
||||
risk_degree = 0.95
|
||||
impact_limit = 0.1 # Max change per step
|
||||
|
||||
def create_test_strategy(impact_limit_value):
|
||||
strat = SoftTopkStrategy.__new__(SoftTopkStrategy)
|
||||
strat.topk = topk
|
||||
strat.risk_degree = risk_degree
|
||||
strat.trade_impact_limit = impact_limit_value
|
||||
return strat
|
||||
|
||||
# 1. With impact limit: Expect deterministic sell and limited buy
|
||||
strat_i = create_test_strategy(impact_limit)
|
||||
res_i = strat_i.generate_target_weight_position(scores, current_pos, None, None)
|
||||
|
||||
# A should be exactly 0.8 - 0.1 = 0.7
|
||||
assert abs(res_i["A"] - 0.7) < 1e-8
|
||||
# B should be exactly 0.2 - 0.1 = 0.1
|
||||
assert abs(res_i["B"] - 0.1) < 1e-8
|
||||
# Total sells = 0.2 released. New budget = 0.2 + (0.95 - 1.0) = 0.15.
|
||||
# C and D share 0.15 -> 0.075 each.
|
||||
assert abs(res_i["C"] - 0.075) < 1e-8
|
||||
assert abs(res_i["D"] - 0.075) < 1e-8
|
||||
|
||||
# 2. Without impact limit: Expect full liquidation and full target fill
|
||||
strat_c = create_test_strategy(1.0)
|
||||
res_c = strat_c.generate_target_weight_position(scores, current_pos, None, None)
|
||||
|
||||
# A, B not in topk -> Liquidated
|
||||
assert "A" not in res_c and "B" not in res_c
|
||||
# C, D should reach ideal_per_stock (0.95/2 = 0.475)
|
||||
assert abs(res_c["C"] - 0.475) < 1e-8
|
||||
assert abs(res_c["D"] - 0.475) < 1e-8
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pytest.main([__file__])
|
||||
38
tests/backtest/test_soft_topk_strategy_cold_start.py
Normal file
38
tests/backtest/test_soft_topk_strategy_cold_start.py
Normal file
@@ -0,0 +1,38 @@
|
||||
import pandas as pd
|
||||
import pytest
|
||||
|
||||
from qlib.contrib.strategy.cost_control import SoftTopkStrategy
|
||||
|
||||
|
||||
class MockPosition:
|
||||
def __init__(self, weights):
|
||||
self.weights = weights
|
||||
|
||||
def get_stock_weight_dict(self, only_stock=True):
|
||||
return self.weights
|
||||
|
||||
|
||||
def create_test_strategy(topk, risk_degree, impact_limit):
|
||||
strat = SoftTopkStrategy.__new__(SoftTopkStrategy)
|
||||
strat.topk = topk
|
||||
strat.risk_degree = risk_degree
|
||||
strat.trade_impact_limit = impact_limit
|
||||
return strat
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("impact_limit", "expected_fill"),
|
||||
[
|
||||
(0.1, 0.1),
|
||||
(1.0, 0.475),
|
||||
],
|
||||
)
|
||||
def test_soft_topk_cold_start_impact_limit(impact_limit, expected_fill):
|
||||
scores = pd.Series({"C": 0.9, "D": 0.8, "A": 0.1, "B": 0.1})
|
||||
current_pos = MockPosition({})
|
||||
|
||||
strat = create_test_strategy(topk=2, risk_degree=0.95, impact_limit=impact_limit)
|
||||
res = strat.generate_target_weight_position(scores, current_pos, None, None)
|
||||
|
||||
assert abs(res["C"] - expected_fill) < 1e-8
|
||||
assert abs(res["D"] - expected_fill) < 1e-8
|
||||
Reference in New Issue
Block a user