Skip to content

Commit c2d61f3

Browse files
committed
[Refactor] Simplify package structure and enhance API surface; add multi-strategy comparison tools
1 parent 7c797f3 commit c2d61f3

12 files changed

Lines changed: 84 additions & 135 deletions

src/quanteval/__init__.py

Lines changed: 13 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -1,64 +1,29 @@
1-
"""
2-
QuantEval - 量化策略与因子评估平台
3-
A comprehensive platform for evaluating quantitative trading strategies and factors.
1+
"""QuantEval public package surface.
42
5-
Author: KarhouTam
6-
License: MIT
3+
Expose a minimal, stable API for most users. Keep surface area small to
4+
avoid importing heavy internals on package import.
75
"""
86

97
__version__ = '1.0.0'
108

11-
# Core components
9+
# Core
1210
from quanteval.core.strategy import Strategy
13-
from quanteval.core.backtester import (
14-
Backtester,
15-
BacktestResults,
16-
MultiBacktestResults,
17-
MetricsSnapshot,
18-
)
11+
from quanteval.core.backtester import Backtester, BacktestResults
1912
from quanteval.core.transaction import TransactionCost, HKTransactionCost
2013

21-
# Data management
14+
# Data
2215
from quanteval.data.loader import DataLoader
2316

2417
# Metrics
2518
from quanteval.metrics.performance import PerformanceMetrics
2619

27-
# Factors
20+
# Factor base
2821
from quanteval.factors.base import Factor
29-
from quanteval.factors.technical import (
30-
SMA,
31-
EMA,
32-
RSI,
33-
MACD,
34-
BollingerBands,
35-
ATR,
36-
Momentum,
37-
VolumeMA,
38-
ROC,
39-
StochasticOscillator,
40-
)
41-
from quanteval.aggregators import (
42-
FactorAggregator,
43-
EqualWeightAggregator,
44-
ICWeightedAggregator,
45-
)
46-
from quanteval.strategies.multi_factor_model import MultiFactorModel
47-
48-
# Benchmark strategies
49-
from quanteval.strategies.dual_ma import DualMAStrategy
50-
from quanteval.strategies.bollinger_mean_reversion import BollingerMeanReversionStrategy
51-
from quanteval.strategies.buy_hold import BuyAndHoldStrategy
5222

53-
# Optimization
54-
from quanteval.optimization import GridSearch, WalkForwardAnalysis
55-
56-
# Comparison
57-
from quanteval.comparison import StrategyComparator, StrategyComparison
23+
# Small set of utility helpers
5824
from quanteval.utils.helpers import (
5925
calculate_returns,
6026
calculate_cumulative_returns,
61-
align_series,
6227
configure_chinese_font,
6328
)
6429

@@ -67,8 +32,7 @@
6732
'Strategy',
6833
'Backtester',
6934
'BacktestResults',
70-
'MultiBacktestResults',
71-
'MetricsSnapshot',
35+
# Transaction Costs
7236
'TransactionCost',
7337
'HKTransactionCost',
7438
# Data
@@ -77,37 +41,10 @@
7741
'PerformanceMetrics',
7842
# Factors
7943
'Factor',
80-
'SMA',
81-
'EMA',
82-
'RSI',
83-
'MACD',
84-
'BollingerBands',
85-
'ATR',
86-
'Momentum',
87-
'VolumeMA',
88-
'ROC',
89-
'StochasticOscillator',
90-
# Factor Aggregators
91-
'FactorAggregator',
92-
'EqualWeightAggregator',
93-
'ICWeightedAggregator',
94-
# Factor Models
95-
'FactorModel',
96-
'CompositeFactorModel',
97-
'MomentumValueModel',
98-
# Strategies
99-
'DualMAStrategy',
100-
'BollingerMeanReversionStrategy',
101-
'BuyAndHoldStrategy',
102-
# Optimization
103-
'GridSearch',
104-
'WalkForwardAnalysis',
105-
'MultiFactorModel',
106-
# Comparison
107-
'StrategyComparator',
108-
'StrategyComparison',
44+
# Utilities
10945
'calculate_returns',
110-
'calculate_cumulative_returns',
11146
'align_series',
112-
'configure_chinese_font',
47+
'calculate_cumulative_returns',
11348
]
49+
50+
configure_chinese_font()

src/quanteval/strategies/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from quanteval.strategies.dual_ma import DualMAStrategy
77
from quanteval.strategies.dual_thrust import DualThrustStrategy
88
from quanteval.strategies.rsi_reversion import RSIStrategy
9+
from quanteval.strategies.multi_factor_model import MultiFactorModel
910

1011
__all__ = [
1112
'DualMAStrategy',
@@ -14,4 +15,5 @@
1415
'DualThrustStrategy',
1516
'RSIStrategy',
1617
'DonchianChannel',
18+
'MultiFactorModel',
1719
]

src/quanteval/utils/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,13 @@
66
align_series,
77
configure_chinese_font,
88
)
9+
from quanteval.utils.comparison import StrategyComparator, StrategyComparison
910

1011
__all__ = [
1112
'calculate_returns',
1213
'calculate_cumulative_returns',
1314
'align_series',
1415
'configure_chinese_font',
16+
'StrategyComparator',
17+
'StrategyComparison',
1518
]
Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,10 @@
1616

1717
logger = logging.getLogger(__name__)
1818

19-
from .core.strategy import Strategy
20-
from .core.backtester import Backtester, BacktestResults
21-
from .core.transaction import TransactionCost
22-
from .utils.helpers import configure_chinese_font
19+
from ..core.strategy import Strategy
20+
from ..core.backtester import Backtester, BacktestResults
21+
from ..core.transaction import TransactionCost
22+
from .helpers import configure_chinese_font
2323

2424

2525
@dataclass
@@ -514,7 +514,7 @@ def create_equal_weight_portfolio(
514514
benchmark_equity = None if benchmark_returns is None else (1 + benchmark_returns).cumprod()
515515

516516
# Calculate metrics
517-
from .metrics.performance import PerformanceMetrics
517+
from ..metrics.performance import PerformanceMetrics
518518

519519
perf = PerformanceMetrics(
520520
strategy_returns=portfolio_returns,

tests/test_comparison.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import pandas as pd
22

3-
from quanteval import BollingerMeanReversionStrategy, DualMAStrategy, StrategyComparator
3+
from quanteval.strategies import BollingerMeanReversionStrategy, DualMAStrategy
4+
from quanteval.utils.comparison import StrategyComparator
45

56

67
def test_strategy_comparison_builds_metrics_table(sample_market_data: pd.DataFrame) -> None:

tests/test_core.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import pandas as pd
22
import pytest
33

4-
from quanteval import Backtester, DualMAStrategy, TransactionCost
4+
from quanteval.core.backtester import Backtester
5+
from quanteval.strategies import DualMAStrategy
6+
from quanteval.core.transaction import TransactionCost
57

68

79
def test_backtester_adds_returns_when_missing(sample_market_data: pd.DataFrame) -> None:

tests/test_data_loader.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import pandas as pd
55
import pytest
66

7-
from quanteval import DataLoader
7+
from quanteval.data.loader import DataLoader
88

99

1010
def test_load_stock_uses_cache_when_available(

tests/test_factors.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import pandas as pd
22

3-
from quanteval import (
3+
from quanteval.factors import (
44
ATR,
55
BollingerBands,
66
MACD,

tests/test_multi_factor_model.py

Lines changed: 47 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -4,77 +4,76 @@
44

55
from quanteval.strategies.multi_factor_model import MultiFactorModel
66
from quanteval.aggregators.weighted import (
7-
EqualWeightAggregator,
8-
ICWeightedAggregator,
7+
EqualWeightAggregator,
8+
ICWeightedAggregator,
99
)
1010
from quanteval.factors.base import Factor
1111

1212

1313
def test_equal_weight_aggregator_without_normalize(sample_market_data: pd.DataFrame) -> None:
14-
data = sample_market_data
15-
idx = data.index
16-
s1 = data['Close'].pct_change().fillna(0)
17-
s2 = -s1
14+
data = sample_market_data
15+
idx = data.index
16+
s1 = data['Close'].pct_change().fillna(0)
17+
s2 = -s1
1818

19-
agg = EqualWeightAggregator(normalize=False)
20-
composite = agg.aggregate({'s1': s1, 's2': s2}, data)
19+
agg = EqualWeightAggregator(normalize=False)
20+
composite = agg.aggregate({'s1': s1, 's2': s2}, data)
2121

22-
expected = (s1.reindex(idx) + s2.reindex(idx)) / 2
23-
pd.testing.assert_series_equal(composite, expected, check_names=False)
22+
expected = (s1.reindex(idx) + s2.reindex(idx)) / 2
23+
pd.testing.assert_series_equal(composite, expected, check_names=False)
2424

2525

2626
def test_icweighted_fallback_to_equal_weights(sample_market_data: pd.DataFrame) -> None:
27-
# Force forward returns to be NaN so IC is unavailable and aggregator falls
28-
# back to equal weights.
29-
data = sample_market_data.copy()
30-
data['Close'] = np.nan
31-
idx = data.index
27+
# Force forward returns to be NaN so IC is unavailable and aggregator falls
28+
# back to equal weights.
29+
data = sample_market_data.copy()
30+
data['Close'] = np.nan
31+
idx = data.index
3232

33-
s1 = pd.Series(np.arange(len(idx)), index=idx, name='s1')
34-
s2 = pd.Series(np.arange(len(idx)) * 2.0, index=idx, name='s2')
33+
s1 = pd.Series(np.arange(len(idx)), index=idx, name='s1')
34+
s2 = pd.Series(np.arange(len(idx)) * 2.0, index=idx, name='s2')
3535

36-
agg = ICWeightedAggregator(normalize=False, ic_window=3, min_periods=1)
37-
composite = agg.aggregate({'s1': s1, 's2': s2}, data)
36+
agg = ICWeightedAggregator(normalize=False, ic_window=3, min_periods=1)
37+
composite = agg.aggregate({'s1': s1, 's2': s2}, data)
3838

39-
expected = (s1 + s2) / 2
40-
pd.testing.assert_series_equal(composite, expected, check_names=False)
39+
expected = (s1 + s2) / 2
40+
pd.testing.assert_series_equal(composite, expected, check_names=False)
4141

4242

4343
class ConstantFactor(Factor):
44-
def __init__(self, name: str, series: pd.Series):
45-
super().__init__(name=name)
46-
self._series = series
44+
def __init__(self, name: str, series: pd.Series):
45+
super().__init__(name=name)
46+
self._series = series
4747

48-
def calculate(self, data: pd.DataFrame) -> pd.Series:
49-
return self._series.reindex(data.index)
48+
def calculate(self, data: pd.DataFrame) -> pd.Series:
49+
return self._series.reindex(data.index)
5050

5151

5252
def test_multi_factor_model_signals_and_type_error(sample_market_data: pd.DataFrame) -> None:
53-
data = sample_market_data.iloc[:4]
54-
idx = data.index
53+
data = sample_market_data.iloc[:4]
54+
idx = data.index
5555

56-
s = pd.Series([0.0, 1.0, 0.0, 1.0], index=idx)
57-
f1 = ConstantFactor('f1', s)
58-
f2 = ConstantFactor('f2', s)
56+
s = pd.Series([0.0, 1.0, 0.0, 1.0], index=idx)
57+
f1 = ConstantFactor('f1', s)
58+
f2 = ConstantFactor('f2', s)
5959

60-
agg = EqualWeightAggregator(normalize=False)
61-
model = MultiFactorModel([f1, f2], aggregator=agg, threshold=0.5)
60+
agg = EqualWeightAggregator(normalize=False)
61+
model = MultiFactorModel([f1, f2], aggregator=agg, threshold=0.5)
6262

63-
signals = model.generate_signals(data)
64-
expected = (((s + s) / 2) > 0.5).astype(float).ffill().fillna(0.0)
65-
expected.name = signals.name
66-
pd.testing.assert_series_equal(signals, expected)
63+
signals = model.generate_signals(data)
64+
expected = (((s + s) / 2) > 0.5).astype(float).ffill().fillna(0.0)
65+
expected.name = signals.name
66+
pd.testing.assert_series_equal(signals, expected)
6767

68-
# Factor returning a dict should raise TypeError
69-
class DictFactor(Factor):
70-
def __init__(self):
71-
super().__init__(name='dict')
68+
# Factor returning a dict should raise TypeError
69+
class DictFactor(Factor):
70+
def __init__(self):
71+
super().__init__(name='dict')
7272

73-
def calculate(self, data: pd.DataFrame):
74-
return {'a': pd.Series([1] * len(data), index=data.index)}
75-
76-
bad = DictFactor()
77-
model2 = MultiFactorModel([bad], aggregator=agg)
78-
with pytest.raises(TypeError):
79-
model2.generate_signals(data)
73+
def calculate(self, data: pd.DataFrame):
74+
return {'a': pd.Series([1] * len(data), index=data.index)}
8075

76+
bad = DictFactor()
77+
model2 = MultiFactorModel([bad], aggregator=agg)
78+
with pytest.raises(TypeError):
79+
model2.generate_signals(data)

tests/test_optimization.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import pandas as pd
22

3-
from quanteval import DualMAStrategy, GridSearch, WalkForwardAnalysis
3+
from quanteval.strategies import DualMAStrategy
4+
from quanteval.optimization import GridSearch, WalkForwardAnalysis
45

56

67
def test_grid_search_finds_best_params(sample_market_data: pd.DataFrame) -> None:

0 commit comments

Comments
 (0)