dfm-python 0.6.13__py3-none-any.whl → 0.7.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dfm_python/__init__.py +11 -2
- dfm_python/config/__init__.py +6 -6
- dfm_python/config/constants.py +90 -0
- dfm_python/config/schema/__init__.py +6 -6
- dfm_python/config/schema/model.py +403 -1
- dfm_python/config/schema/params.py +218 -0
- dfm_python/config/schema/results.py +231 -84
- dfm_python/dataset/__init__.py +4 -0
- dfm_python/dataset/afm_dataset.py +119 -0
- dfm_python/dataset/ddfm_dataset.py +21 -0
- dfm_python/dataset/ivdfm_dataset.py +427 -0
- dfm_python/functional/krylov.py +2 -61
- dfm_python/layer/mixing.py +37 -0
- dfm_python/layer/mlp.py +38 -19
- dfm_python/layer/pca.py +46 -0
- dfm_python/layer/regime.py +53 -0
- dfm_python/logger/__init__.py +8 -0
- dfm_python/logger/ivdfm_logger.py +92 -0
- dfm_python/models/__init__.py +10 -0
- dfm_python/models/afm/__init__.py +23 -0
- dfm_python/models/afm/afm.py +385 -0
- dfm_python/models/afm/baselines.py +72 -0
- dfm_python/models/afm/factors.py +192 -0
- dfm_python/models/afm/longconv.py +97 -0
- dfm_python/models/base.py +15 -0
- dfm_python/models/ddfm/ddfm.py +17 -8
- dfm_python/models/ivdfm/__init__.py +18 -0
- dfm_python/models/ivdfm/decoder.py +85 -0
- dfm_python/models/ivdfm/encoder.py +198 -0
- dfm_python/models/ivdfm/ivdfm.py +1526 -396
- dfm_python/models/ivdfm/optuna.py +647 -0
- dfm_python/models/ivdfm/prior.py +633 -0
- dfm_python/numeric/builder.py +247 -0
- dfm_python/numeric/estimator.py +78 -9
- dfm_python/numeric/validator.py +5 -0
- dfm_python/ssm/__init__.py +6 -0
- dfm_python/ssm/base.py +208 -0
- dfm_python/ssm/companion.py +500 -0
- dfm_python/ssm/kalman.py +100 -3
- dfm_python/ssm/residual.py +0 -0
- dfm_python/utils/loss.py +153 -159
- dfm_python-0.7.0.dist-info/METADATA +186 -0
- dfm_python-0.7.0.dist-info/RECORD +75 -0
- {dfm_python-0.6.13.dist-info → dfm_python-0.7.0.dist-info}/WHEEL +1 -1
- dfm_python-0.7.0.dist-info/licenses/LICENSE +21 -0
- dfm_python/functional/cauchy.py +0 -236
- dfm_python-0.6.13.dist-info/METADATA +0 -56
- dfm_python-0.6.13.dist-info/RECORD +0 -61
dfm_python/__init__.py
CHANGED
|
@@ -63,7 +63,7 @@ Note: DFMConfig uses frequency dict to specify series (column names -> frequenci
|
|
|
63
63
|
For detailed documentation, see the README.md file and the tutorial notebooks/scripts.
|
|
64
64
|
"""
|
|
65
65
|
|
|
66
|
-
__version__ = "0.
|
|
66
|
+
__version__ = "0.7.0"
|
|
67
67
|
|
|
68
68
|
# ============================================================================
|
|
69
69
|
# PUBLIC API DEFINITION
|
|
@@ -98,6 +98,7 @@ from .utils.metric import calculate_rmse
|
|
|
98
98
|
# Users can import these directly from dfm_python
|
|
99
99
|
from .dataset.dfm_dataset import DFMDataset
|
|
100
100
|
from .dataset.ddfm_dataset import DDFMDataset
|
|
101
|
+
from .dataset.afm_dataset import AFMDataset
|
|
101
102
|
|
|
102
103
|
# Model implementations
|
|
103
104
|
from .models.base import BaseFactorModel
|
|
@@ -109,6 +110,12 @@ try:
|
|
|
109
110
|
except ImportError:
|
|
110
111
|
DDFM = None
|
|
111
112
|
|
|
113
|
+
# AFM high-level API (Attention Factor Model; PyTorch is mandatory)
|
|
114
|
+
try:
|
|
115
|
+
from .models.afm.afm import AFM
|
|
116
|
+
except ImportError:
|
|
117
|
+
AFM = None
|
|
118
|
+
|
|
112
119
|
__all__ = [
|
|
113
120
|
# Core classes
|
|
114
121
|
'DFM',
|
|
@@ -121,14 +128,16 @@ __all__ = [
|
|
|
121
128
|
'BaseResult', 'DFMResult', 'DDFMResult', 'calculate_rmse',
|
|
122
129
|
]
|
|
123
130
|
|
|
124
|
-
# DDFM high-level API (PyTorch is mandatory)
|
|
131
|
+
# DDFM / AFM high-level API (PyTorch is mandatory)
|
|
125
132
|
__all__.extend([
|
|
126
133
|
'DDFM', # High-level API class
|
|
134
|
+
'AFM', # Attention Factor Model
|
|
127
135
|
])
|
|
128
136
|
|
|
129
137
|
# Dataset classes
|
|
130
138
|
__all__.extend([
|
|
131
139
|
'DFMDataset', # DFM Dataset class
|
|
132
140
|
'DDFMDataset', # DDFM Dataset class
|
|
141
|
+
'AFMDataset', # AFM Dataset class
|
|
133
142
|
])
|
|
134
143
|
|
dfm_python/config/__init__.py
CHANGED
|
@@ -8,9 +8,9 @@ Note: Series are specified via frequency dict mapping column names to frequencie
|
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
10
|
from .schema import (
|
|
11
|
-
BaseModelConfig, DFMConfig, DDFMConfig,
|
|
12
|
-
BaseResult, DFMResult, DDFMResult,
|
|
13
|
-
DFMStateSpaceParams, DDFMStateSpaceParams,
|
|
11
|
+
BaseModelConfig, DFMConfig, DDFMConfig, AFMConfig,
|
|
12
|
+
BaseResult, DFMResult, DDFMResult, AFMResult,
|
|
13
|
+
DFMStateSpaceParams, DDFMStateSpaceParams, AFMModelState,
|
|
14
14
|
)
|
|
15
15
|
# DEFAULT_BLOCK_NAME is imported lazily where needed to avoid circular imports
|
|
16
16
|
from .constants import (
|
|
@@ -97,11 +97,11 @@ __all__ = [
|
|
|
97
97
|
'BaseModelConfig', 'BaseResult',
|
|
98
98
|
# 'DEFAULT_BLOCK_NAME', # Removed to avoid circular import
|
|
99
99
|
# Model-specific configs (from schema.py)
|
|
100
|
-
'DFMConfig', 'DDFMConfig',
|
|
100
|
+
'DFMConfig', 'DDFMConfig', 'AFMConfig',
|
|
101
101
|
# State-space parameters
|
|
102
|
-
'DFMStateSpaceParams', 'DDFMStateSpaceParams',
|
|
102
|
+
'DFMStateSpaceParams', 'DDFMStateSpaceParams', 'AFMModelState',
|
|
103
103
|
# Model-specific results (from results.py)
|
|
104
|
-
'DFMResult', 'DDFMResult',
|
|
104
|
+
'DFMResult', 'DDFMResult', 'AFMResult',
|
|
105
105
|
# Utilities
|
|
106
106
|
'validate_frequency',
|
|
107
107
|
# IO
|
dfm_python/config/constants.py
CHANGED
|
@@ -344,6 +344,53 @@ COMPUTATION_ERROR_TYPES = (RuntimeError, ValueError, TypeError, AttributeError,
|
|
|
344
344
|
DEFAULT_KDFM_AR_ORDER = 1 # Default AR order (VAR lag order p) for KDFM
|
|
345
345
|
DEFAULT_KDFM_MA_ORDER = 0 # Default MA order (MA lag order q) for KDFM (0 = pure VAR)
|
|
346
346
|
|
|
347
|
+
# ============================================================================
|
|
348
|
+
# iVDFM Defaults
|
|
349
|
+
# ============================================================================
|
|
350
|
+
|
|
351
|
+
# iVDFM dimension defaults
|
|
352
|
+
DEFAULT_IVDFM_LATENT_DIM = 3 # Default number of factors
|
|
353
|
+
DEFAULT_IVDFM_AUX_DIM = 1 # Default auxiliary variable dimension
|
|
354
|
+
|
|
355
|
+
# iVDFM regime (K=1 = baseline iVDFM, no regime structure)
|
|
356
|
+
DEFAULT_IVDFM_NUM_REGIMES = 1 # Number of discrete regimes; 1 = baseline iVDFM
|
|
357
|
+
DEFAULT_IVDFM_REGIME_TEMPERATURE = 1.0 # Softmax temperature for π; τ < 1 sharpens (local commitment)
|
|
358
|
+
DEFAULT_IVDFM_MIXING = False # If True, apply global post-factor mixing f -> M f (identity init) before decode
|
|
359
|
+
|
|
360
|
+
# iVDFM network architecture defaults
|
|
361
|
+
DEFAULT_IVDFM_ENCODER_HIDDEN_DIM = 200 # Default encoder hidden dimension
|
|
362
|
+
DEFAULT_IVDFM_ENCODER_N_HIDDEN_LAYERS = 2 # Default encoder hidden layers
|
|
363
|
+
DEFAULT_IVDFM_DECODER_TYPE = "mlp" # Default: 'linear', 'residual', or 'mlp' (mlp = current behavior)
|
|
364
|
+
DEFAULT_IVDFM_DECODER_HIDDEN_DIM = 200 # Default decoder hidden dimension
|
|
365
|
+
DEFAULT_IVDFM_DECODER_N_HIDDEN_LAYERS = 2 # Default decoder hidden layers
|
|
366
|
+
DEFAULT_IVDFM_PRIOR_HIDDEN_DIM = 100 # Default prior network hidden dimension
|
|
367
|
+
DEFAULT_IVDFM_PRIOR_N_HIDDEN_LAYERS = 1 # Default prior network hidden layers
|
|
368
|
+
|
|
369
|
+
# iVDFM dynamics and distribution defaults
|
|
370
|
+
DEFAULT_IVDFM_FACTOR_ORDER = 1 # Default AR order for factors
|
|
371
|
+
DEFAULT_IVDFM_INNOVATION_DIST = 'laplace' # Default innovation distribution
|
|
372
|
+
DEFAULT_IVDFM_DECODER_VAR = 0.01 # Default decoder variance
|
|
373
|
+
DEFAULT_IVDFM_ACTIVATION = 'lrelu' # Default activation function
|
|
374
|
+
DEFAULT_IVDFM_SLOPE = 0.1 # Default leaky ReLU slope
|
|
375
|
+
|
|
376
|
+
# iVDFM training defaults
|
|
377
|
+
DEFAULT_IVDFM_BATCH_SIZE = 32 # Default batch size
|
|
378
|
+
DEFAULT_IVDFM_MAX_EPOCHS = 100 # Default maximum epochs
|
|
379
|
+
DEFAULT_IVDFM_AUX_VARIABLE_TYPE = 'time' # Default auxiliary variable type
|
|
380
|
+
|
|
381
|
+
# iVDFM optimizer defaults
|
|
382
|
+
DEFAULT_IVDFM_OPTIMIZER_WEIGHT_DECAY = 0.0 # Default weight decay for optimizer
|
|
383
|
+
DEFAULT_IVDFM_OPTIMIZER_MOMENTUM = 0.9 # Default momentum for SGD
|
|
384
|
+
|
|
385
|
+
# iVDFM scheduler defaults
|
|
386
|
+
DEFAULT_IVDFM_SCHEDULER_TYPE = 'step' # Default scheduler type: 'step', 'plateau', 'cosine', 'exponential', None
|
|
387
|
+
DEFAULT_IVDFM_SCHEDULER_STEP_SIZE = None # Default step_size for StepLR (auto: max_epochs // 3)
|
|
388
|
+
DEFAULT_IVDFM_SCHEDULER_GAMMA = 0.5 # Default gamma for StepLR/ExponentialLR
|
|
389
|
+
DEFAULT_IVDFM_SCHEDULER_PATIENCE = 10 # Default patience for ReduceLROnPlateau
|
|
390
|
+
DEFAULT_IVDFM_SCHEDULER_FACTOR = 0.1 # Default factor for ReduceLROnPlateau
|
|
391
|
+
DEFAULT_IVDFM_SCHEDULER_MIN_LR = 0.0 # Default min_lr for ReduceLROnPlateau
|
|
392
|
+
DEFAULT_IVDFM_EARLY_STOP_PATIENCE = 20 # Default patience for early stopping (epochs without improvement)
|
|
393
|
+
|
|
347
394
|
# ============================================================================
|
|
348
395
|
# Tutorial Defaults
|
|
349
396
|
# ============================================================================
|
|
@@ -410,11 +457,35 @@ FREQUENCY_HIERARCHY: Dict[str, int] = {
|
|
|
410
457
|
# For frequency gaps larger than this, the missing data approach is used instead
|
|
411
458
|
MAX_TENT_SIZE: int = 12
|
|
412
459
|
|
|
460
|
+
# ============================================================================
|
|
461
|
+
# AFM (Attention Factor Model) defaults
|
|
462
|
+
# ============================================================================
|
|
463
|
+
DEFAULT_AFM_NUM_FACTORS = 5 # K latent factors
|
|
464
|
+
DEFAULT_AFM_FACTOR_MODEL = 'attention' # 'attention' | 'pca'
|
|
465
|
+
DEFAULT_AFM_TRADING = 'longconv' # 'longconv' | 'ou'
|
|
466
|
+
DEFAULT_AFM_EMBED_DIM = 32 # characteristic embedding dimension d
|
|
467
|
+
DEFAULT_AFM_HIST_LEN = 20 # residual-history length s for the filter
|
|
468
|
+
DEFAULT_AFM_N_KERNELS = 32 # number of long convolutions
|
|
469
|
+
DEFAULT_AFM_RIDGE = 1e-2 # ridge penalty for closed-form loadings
|
|
470
|
+
DEFAULT_AFM_SQUASH_LAMBDA = 1e-3 # LongConv Squash kernel regularizer strength
|
|
471
|
+
DEFAULT_AFM_PCA_WINDOW = 252 # trailing window for the PCA benchmark
|
|
472
|
+
DEFAULT_AFM_REESTIM = 21 # PCA re-estimation stride
|
|
473
|
+
DEFAULT_AFM_LAMBDA_VAR = 0.1 # explained-variance regularizer weight
|
|
474
|
+
DEFAULT_AFM_TURNOVER_COST = 5e-4 # 5bps per unit turnover (paper)
|
|
475
|
+
DEFAULT_AFM_SHORT_COST = 1e-4 # 1bp shorting cost (paper)
|
|
476
|
+
DEFAULT_AFM_MAX_EPOCHS = 50 # training epochs
|
|
477
|
+
|
|
413
478
|
# ============================================================================
|
|
414
479
|
# Export all constants
|
|
415
480
|
# ============================================================================
|
|
416
481
|
|
|
417
482
|
__all__ = [
|
|
483
|
+
# AFM
|
|
484
|
+
'DEFAULT_AFM_NUM_FACTORS', 'DEFAULT_AFM_FACTOR_MODEL', 'DEFAULT_AFM_TRADING',
|
|
485
|
+
'DEFAULT_AFM_EMBED_DIM', 'DEFAULT_AFM_HIST_LEN', 'DEFAULT_AFM_N_KERNELS',
|
|
486
|
+
'DEFAULT_AFM_RIDGE', 'DEFAULT_AFM_SQUASH_LAMBDA', 'DEFAULT_AFM_PCA_WINDOW',
|
|
487
|
+
'DEFAULT_AFM_REESTIM', 'DEFAULT_AFM_LAMBDA_VAR', 'DEFAULT_AFM_TURNOVER_COST',
|
|
488
|
+
'DEFAULT_AFM_SHORT_COST', 'DEFAULT_AFM_MAX_EPOCHS',
|
|
418
489
|
# Convergence
|
|
419
490
|
'DEFAULT_CONVERGENCE_THRESHOLD',
|
|
420
491
|
'DEFAULT_EM_THRESHOLD',
|
|
@@ -564,6 +635,25 @@ __all__ = [
|
|
|
564
635
|
# KDFM defaults
|
|
565
636
|
'DEFAULT_KDFM_AR_ORDER',
|
|
566
637
|
'DEFAULT_KDFM_MA_ORDER',
|
|
638
|
+
# iVDFM defaults
|
|
639
|
+
'DEFAULT_IVDFM_LATENT_DIM',
|
|
640
|
+
'DEFAULT_IVDFM_AUX_DIM',
|
|
641
|
+
'DEFAULT_IVDFM_ENCODER_HIDDEN_DIM',
|
|
642
|
+
'DEFAULT_IVDFM_ENCODER_N_HIDDEN_LAYERS',
|
|
643
|
+
'DEFAULT_IVDFM_NUM_REGIMES',
|
|
644
|
+
'DEFAULT_IVDFM_DECODER_TYPE',
|
|
645
|
+
'DEFAULT_IVDFM_DECODER_HIDDEN_DIM',
|
|
646
|
+
'DEFAULT_IVDFM_DECODER_N_HIDDEN_LAYERS',
|
|
647
|
+
'DEFAULT_IVDFM_PRIOR_HIDDEN_DIM',
|
|
648
|
+
'DEFAULT_IVDFM_PRIOR_N_HIDDEN_LAYERS',
|
|
649
|
+
'DEFAULT_IVDFM_FACTOR_ORDER',
|
|
650
|
+
'DEFAULT_IVDFM_INNOVATION_DIST',
|
|
651
|
+
'DEFAULT_IVDFM_DECODER_VAR',
|
|
652
|
+
'DEFAULT_IVDFM_ACTIVATION',
|
|
653
|
+
'DEFAULT_IVDFM_SLOPE',
|
|
654
|
+
'DEFAULT_IVDFM_BATCH_SIZE',
|
|
655
|
+
'DEFAULT_IVDFM_MAX_EPOCHS',
|
|
656
|
+
'DEFAULT_IVDFM_AUX_VARIABLE_TYPE',
|
|
567
657
|
# Tutorial defaults
|
|
568
658
|
'TUTORIAL_MAX_PERIODS',
|
|
569
659
|
'TUTORIAL_MAX_EPOCHS',
|
|
@@ -7,13 +7,13 @@ This package contains:
|
|
|
7
7
|
Note: Series are specified via frequency dict mapping column names to frequencies.
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
|
-
from .model import BaseModelConfig, DFMConfig, DDFMConfig
|
|
11
|
-
from .results import BaseResult, DFMResult, DDFMResult
|
|
12
|
-
from .params import DFMStateSpaceParams, DDFMStateSpaceParams
|
|
10
|
+
from .model import BaseModelConfig, DFMConfig, DDFMConfig, iVDFMConfig, AFMConfig
|
|
11
|
+
from .results import BaseResult, DFMResult, DDFMResult, iVDFMResult, AFMResult
|
|
12
|
+
from .params import DFMStateSpaceParams, DDFMStateSpaceParams, iVDFMModelState, AFMModelState
|
|
13
13
|
|
|
14
14
|
__all__ = [
|
|
15
|
-
'BaseModelConfig', 'DFMConfig', 'DDFMConfig',
|
|
16
|
-
'BaseResult', 'DFMResult', 'DDFMResult',
|
|
17
|
-
'DFMStateSpaceParams', 'DDFMStateSpaceParams',
|
|
15
|
+
'BaseModelConfig', 'DFMConfig', 'DDFMConfig', 'iVDFMConfig', 'AFMConfig',
|
|
16
|
+
'BaseResult', 'DFMResult', 'DDFMResult', 'iVDFMResult', 'AFMResult',
|
|
17
|
+
'DFMStateSpaceParams', 'DDFMStateSpaceParams', 'iVDFMModelState', 'AFMModelState',
|
|
18
18
|
]
|
|
19
19
|
|
|
@@ -48,6 +48,15 @@ from ..constants import (
|
|
|
48
48
|
DEFAULT_BATCH_SIZE,
|
|
49
49
|
DEFAULT_DDFM_WINDOW_SIZE,
|
|
50
50
|
DEFAULT_GRAD_CLIP_VAL,
|
|
51
|
+
DEFAULT_IVDFM_OPTIMIZER_WEIGHT_DECAY,
|
|
52
|
+
DEFAULT_IVDFM_OPTIMIZER_MOMENTUM,
|
|
53
|
+
DEFAULT_IVDFM_SCHEDULER_TYPE,
|
|
54
|
+
DEFAULT_IVDFM_SCHEDULER_STEP_SIZE,
|
|
55
|
+
DEFAULT_IVDFM_SCHEDULER_GAMMA,
|
|
56
|
+
DEFAULT_IVDFM_SCHEDULER_PATIENCE,
|
|
57
|
+
DEFAULT_IVDFM_SCHEDULER_FACTOR,
|
|
58
|
+
DEFAULT_IVDFM_SCHEDULER_MIN_LR,
|
|
59
|
+
DEFAULT_IVDFM_EARLY_STOP_PATIENCE,
|
|
51
60
|
DEFAULT_REGULARIZATION_SCALE,
|
|
52
61
|
DEFAULT_STRUCTURAL_REG_WEIGHT,
|
|
53
62
|
DEFAULT_CONVERGENCE_THRESHOLD,
|
|
@@ -68,8 +77,42 @@ from ..constants import (
|
|
|
68
77
|
DEFAULT_CLOCK_FREQUENCY,
|
|
69
78
|
DEFAULT_KDFM_AR_ORDER,
|
|
70
79
|
DEFAULT_KDFM_MA_ORDER,
|
|
80
|
+
DEFAULT_AFM_NUM_FACTORS,
|
|
81
|
+
DEFAULT_AFM_FACTOR_MODEL,
|
|
82
|
+
DEFAULT_AFM_TRADING,
|
|
83
|
+
DEFAULT_AFM_EMBED_DIM,
|
|
84
|
+
DEFAULT_AFM_HIST_LEN,
|
|
85
|
+
DEFAULT_AFM_N_KERNELS,
|
|
86
|
+
DEFAULT_AFM_RIDGE,
|
|
87
|
+
DEFAULT_AFM_SQUASH_LAMBDA,
|
|
88
|
+
DEFAULT_AFM_PCA_WINDOW,
|
|
89
|
+
DEFAULT_AFM_REESTIM,
|
|
90
|
+
DEFAULT_AFM_LAMBDA_VAR,
|
|
91
|
+
DEFAULT_AFM_TURNOVER_COST,
|
|
92
|
+
DEFAULT_AFM_SHORT_COST,
|
|
93
|
+
DEFAULT_AFM_MAX_EPOCHS,
|
|
71
94
|
FREQUENCY_HIERARCHY,
|
|
72
95
|
DEFAULT_HIERARCHY_VALUE,
|
|
96
|
+
DEFAULT_IVDFM_LATENT_DIM,
|
|
97
|
+
DEFAULT_IVDFM_AUX_DIM,
|
|
98
|
+
DEFAULT_IVDFM_ENCODER_HIDDEN_DIM,
|
|
99
|
+
DEFAULT_IVDFM_ENCODER_N_HIDDEN_LAYERS,
|
|
100
|
+
DEFAULT_IVDFM_NUM_REGIMES,
|
|
101
|
+
DEFAULT_IVDFM_REGIME_TEMPERATURE,
|
|
102
|
+
DEFAULT_IVDFM_MIXING,
|
|
103
|
+
DEFAULT_IVDFM_DECODER_TYPE,
|
|
104
|
+
DEFAULT_IVDFM_DECODER_HIDDEN_DIM,
|
|
105
|
+
DEFAULT_IVDFM_DECODER_N_HIDDEN_LAYERS,
|
|
106
|
+
DEFAULT_IVDFM_PRIOR_HIDDEN_DIM,
|
|
107
|
+
DEFAULT_IVDFM_PRIOR_N_HIDDEN_LAYERS,
|
|
108
|
+
DEFAULT_IVDFM_FACTOR_ORDER,
|
|
109
|
+
DEFAULT_IVDFM_INNOVATION_DIST,
|
|
110
|
+
DEFAULT_IVDFM_DECODER_VAR,
|
|
111
|
+
DEFAULT_IVDFM_ACTIVATION,
|
|
112
|
+
DEFAULT_IVDFM_SLOPE,
|
|
113
|
+
DEFAULT_IVDFM_BATCH_SIZE,
|
|
114
|
+
DEFAULT_IVDFM_MAX_EPOCHS,
|
|
115
|
+
DEFAULT_IVDFM_AUX_VARIABLE_TYPE,
|
|
73
116
|
)
|
|
74
117
|
|
|
75
118
|
|
|
@@ -136,6 +179,7 @@ class BaseModelConfig:
|
|
|
136
179
|
# ========================================================================
|
|
137
180
|
clock: str = 'm' # Required: Base frequency for latent factors (global clock): 'd', 'w', 'm', 'q', 'sa', 'a' (defaults to 'm' for monthly)
|
|
138
181
|
target_scaler: Optional[ScalerType] = None # Fitted sklearn scaler instance (StandardScaler, RobustScaler, etc.) for target series only. Must be a fitted scaler object (call .fit() on target data first). Pass scaler object directly, not string. Feature series are assumed to be manually preprocessed. If None, target series are assumed to be already in the desired scale.
|
|
182
|
+
scaler: Optional[str] = None # Scaler type as string: 'standard', 'robust', 'minmax', 'maxabs', 'quantile', or null. Applied to targets only (not context/auxiliary variables). For iVDFM, this is the preferred way to specify scaler (like DDFM pattern).
|
|
139
183
|
|
|
140
184
|
def __post_init__(self):
|
|
141
185
|
"""Validate basic model structure.
|
|
@@ -626,6 +670,291 @@ class DDFMConfig(BaseModelConfig):
|
|
|
626
670
|
)
|
|
627
671
|
|
|
628
672
|
|
|
673
|
+
@dataclass
|
|
674
|
+
class iVDFMConfig(BaseModelConfig):
|
|
675
|
+
"""Identifiable Variational Dynamic Factor Model configuration.
|
|
676
|
+
|
|
677
|
+
Uses 'scaler' (string) pattern like DDFM, not 'target_scaler' (instance).
|
|
678
|
+
Scaler is applied to targets only, not to context/auxiliary variables.
|
|
679
|
+
"""
|
|
680
|
+
"""Identifiable Variational Dynamic Factor Model configuration.
|
|
681
|
+
|
|
682
|
+
This configuration class extends BaseModelConfig with parameters specific
|
|
683
|
+
to iVDFM models trained using variational inference with identifiable
|
|
684
|
+
innovation priors.
|
|
685
|
+
|
|
686
|
+
Note: iVDFM does NOT use block structure. Use num_factors directly to specify
|
|
687
|
+
the number of factors.
|
|
688
|
+
|
|
689
|
+
The configuration can be built from:
|
|
690
|
+
- Main settings (training parameters) from config files
|
|
691
|
+
- Series definitions via frequency dict (column names -> frequencies)
|
|
692
|
+
"""
|
|
693
|
+
# ========================================================================
|
|
694
|
+
# Model Structure
|
|
695
|
+
# ========================================================================
|
|
696
|
+
num_factors: Optional[int] = None # Number of factors (inferred from config if None)
|
|
697
|
+
window: Optional[int] = None # Sliding window length for training. If None, uses full T (full series as one sequence). Default: None.
|
|
698
|
+
stride: int = 1 # Step size for sliding windows. stride=1 means overlapping windows, stride=window means non-overlapping.
|
|
699
|
+
context: Optional[Union[List[str], List[int]]] = None # Column names (DataFrame) or indices (array) for exogenous context variables. These are concatenated with time features.
|
|
700
|
+
time_context: int = 1 # Dimension of time-based context features. Always included. time_context=1: time step only, time_context>1: adds periodic sine features.
|
|
701
|
+
|
|
702
|
+
# ========================================================================
|
|
703
|
+
# Neural Network Architecture
|
|
704
|
+
# ========================================================================
|
|
705
|
+
encoder_hidden_dim: Union[int, List[int]] = DEFAULT_IVDFM_ENCODER_HIDDEN_DIM # Encoder architecture
|
|
706
|
+
encoder_n_hidden_layers: int = DEFAULT_IVDFM_ENCODER_N_HIDDEN_LAYERS # Number of encoder hidden layers
|
|
707
|
+
decoder_type: str = DEFAULT_IVDFM_DECODER_TYPE # 'linear', 'residual', or 'mlp'
|
|
708
|
+
decoder_hidden_dim: Union[int, List[int]] = DEFAULT_IVDFM_DECODER_HIDDEN_DIM # Decoder MLP (residual/mlp)
|
|
709
|
+
decoder_n_hidden_layers: int = DEFAULT_IVDFM_DECODER_N_HIDDEN_LAYERS # Decoder hidden layers (residual/mlp)
|
|
710
|
+
prior_hidden_dim: Union[int, List[int]] = DEFAULT_IVDFM_PRIOR_HIDDEN_DIM # Prior network architecture
|
|
711
|
+
prior_n_hidden_layers: int = DEFAULT_IVDFM_PRIOR_N_HIDDEN_LAYERS # Number of prior network hidden layers
|
|
712
|
+
activation: str = DEFAULT_IVDFM_ACTIVATION # Activation function
|
|
713
|
+
slope: float = DEFAULT_IVDFM_SLOPE # Leaky ReLU slope
|
|
714
|
+
|
|
715
|
+
# ========================================================================
|
|
716
|
+
# Dynamics and Distribution Parameters
|
|
717
|
+
# ========================================================================
|
|
718
|
+
num_regimes: int = DEFAULT_IVDFM_NUM_REGIMES # K; 1 = baseline iVDFM (one global dynamics, one decoder)
|
|
719
|
+
regime_temperature: float = DEFAULT_IVDFM_REGIME_TEMPERATURE # Softmax τ for π; τ < 1 = sharper (local commitment)
|
|
720
|
+
factor_order: int = DEFAULT_IVDFM_FACTOR_ORDER # AR order for factors
|
|
721
|
+
innovation_distribution: str = DEFAULT_IVDFM_INNOVATION_DIST # Innovation distribution type
|
|
722
|
+
decoder_var: float = DEFAULT_IVDFM_DECODER_VAR # Decoder variance
|
|
723
|
+
beta_kl: float = 1.0 # Weight for KL term in ELBO: ELBO = recon_loss + beta_kl * kl_loss. β<1 reduces KL pressure.
|
|
724
|
+
use_layer_norm: bool = False # Whether to use layer normalization in encoder/decoder networks
|
|
725
|
+
mixing: bool = DEFAULT_IVDFM_MIXING # If True, global post-factor mixing f -> M f before decode (identity init)
|
|
726
|
+
|
|
727
|
+
# ========================================================================
|
|
728
|
+
# Training Parameters
|
|
729
|
+
# ========================================================================
|
|
730
|
+
learning_rate: float = DEFAULT_LEARNING_RATE # Learning rate
|
|
731
|
+
optimizer: str = 'Adam' # Optimizer type
|
|
732
|
+
optimizer_weight_decay: float = DEFAULT_IVDFM_OPTIMIZER_WEIGHT_DECAY # Weight decay (L2 regularization)
|
|
733
|
+
optimizer_momentum: float = DEFAULT_IVDFM_OPTIMIZER_MOMENTUM # Momentum for SGD
|
|
734
|
+
batch_size: int = DEFAULT_IVDFM_BATCH_SIZE # Batch size
|
|
735
|
+
max_epochs: int = DEFAULT_IVDFM_MAX_EPOCHS # Maximum epochs
|
|
736
|
+
tolerance: float = DEFAULT_TOLERANCE # Convergence tolerance
|
|
737
|
+
patience: Optional[int] = DEFAULT_IVDFM_EARLY_STOP_PATIENCE # Early stopping patience (epochs without improvement). None to disable.
|
|
738
|
+
seed: Optional[int] = None # Random seed
|
|
739
|
+
|
|
740
|
+
# ========================================================================
|
|
741
|
+
# Scheduler Parameters
|
|
742
|
+
# ========================================================================
|
|
743
|
+
scheduler_type: Optional[str] = DEFAULT_IVDFM_SCHEDULER_TYPE # Scheduler type: 'step', 'plateau', 'cosine', 'exponential', None
|
|
744
|
+
scheduler_step_size: Optional[int] = DEFAULT_IVDFM_SCHEDULER_STEP_SIZE # Step size for StepLR (None = auto: max_epochs // 3)
|
|
745
|
+
scheduler_gamma: float = DEFAULT_IVDFM_SCHEDULER_GAMMA # Gamma for StepLR/ExponentialLR
|
|
746
|
+
scheduler_patience: int = DEFAULT_IVDFM_SCHEDULER_PATIENCE # Patience for ReduceLROnPlateau
|
|
747
|
+
scheduler_factor: float = DEFAULT_IVDFM_SCHEDULER_FACTOR # Factor for ReduceLROnPlateau
|
|
748
|
+
scheduler_min_lr: float = DEFAULT_IVDFM_SCHEDULER_MIN_LR # Min learning rate for ReduceLROnPlateau
|
|
749
|
+
|
|
750
|
+
def __post_init__(self):
|
|
751
|
+
"""Validate iVDFM configuration."""
|
|
752
|
+
super().__post_init__() # Validate base config
|
|
753
|
+
|
|
754
|
+
from ...utils.errors import ConfigurationError
|
|
755
|
+
|
|
756
|
+
# Validate window
|
|
757
|
+
if self.window is not None and self.window < 1:
|
|
758
|
+
raise ConfigurationError(
|
|
759
|
+
f"window must be >= 1, got {self.window}"
|
|
760
|
+
)
|
|
761
|
+
|
|
762
|
+
# Validate time_context
|
|
763
|
+
if self.time_context < 1:
|
|
764
|
+
raise ConfigurationError(
|
|
765
|
+
f"time_context must be >= 1, got {self.time_context}"
|
|
766
|
+
)
|
|
767
|
+
|
|
768
|
+
# Validate patience if provided
|
|
769
|
+
if self.patience is not None and self.patience < 1:
|
|
770
|
+
raise ConfigurationError(
|
|
771
|
+
f"patience must be >= 1 or None (to disable), got {self.patience}"
|
|
772
|
+
)
|
|
773
|
+
|
|
774
|
+
# Validate num_factors if provided
|
|
775
|
+
if self.num_factors is not None and self.num_factors < 1:
|
|
776
|
+
raise ConfigurationError(
|
|
777
|
+
f"num_factors must be >= 1, got {self.num_factors}"
|
|
778
|
+
)
|
|
779
|
+
|
|
780
|
+
# Validate factor_order
|
|
781
|
+
if self.factor_order < 1:
|
|
782
|
+
raise ConfigurationError(
|
|
783
|
+
f"factor_order must be >= 1, got {self.factor_order}"
|
|
784
|
+
)
|
|
785
|
+
|
|
786
|
+
# Validate innovation_distribution
|
|
787
|
+
valid_dists = {'laplace', 'gaussian', 'student_t', 'gamma', 'beta', 'exponential'}
|
|
788
|
+
if self.innovation_distribution not in valid_dists:
|
|
789
|
+
raise ConfigurationError(
|
|
790
|
+
f"innovation_distribution must be one of {valid_dists}, got '{self.innovation_distribution}'"
|
|
791
|
+
)
|
|
792
|
+
|
|
793
|
+
# Validate num_regimes
|
|
794
|
+
if self.num_regimes < 1:
|
|
795
|
+
raise ConfigurationError(
|
|
796
|
+
f"num_regimes must be >= 1, got {self.num_regimes}"
|
|
797
|
+
)
|
|
798
|
+
# Validate regime_temperature
|
|
799
|
+
if self.regime_temperature <= 0.0:
|
|
800
|
+
raise ConfigurationError(
|
|
801
|
+
f"regime_temperature must be > 0, got {self.regime_temperature}"
|
|
802
|
+
)
|
|
803
|
+
# Validate decoder_type
|
|
804
|
+
if self.decoder_type not in ("linear", "residual", "mlp"):
|
|
805
|
+
raise ConfigurationError(
|
|
806
|
+
f"decoder_type must be 'linear', 'residual', or 'mlp', got '{self.decoder_type}'"
|
|
807
|
+
)
|
|
808
|
+
|
|
809
|
+
# Validate activation
|
|
810
|
+
valid_activations = {'relu', 'lrelu', 'tanh', 'sigmoid'}
|
|
811
|
+
if self.activation not in valid_activations:
|
|
812
|
+
raise ConfigurationError(
|
|
813
|
+
f"activation must be one of {valid_activations}, got '{self.activation}'"
|
|
814
|
+
)
|
|
815
|
+
|
|
816
|
+
# Validate beta_kl
|
|
817
|
+
if self.beta_kl < 0:
|
|
818
|
+
raise ConfigurationError(
|
|
819
|
+
f"beta_kl must be >= 0, got {self.beta_kl}"
|
|
820
|
+
)
|
|
821
|
+
|
|
822
|
+
# Validate optimizer
|
|
823
|
+
from ..constants import VALID_OPTIMIZERS
|
|
824
|
+
if self.optimizer not in VALID_OPTIMIZERS:
|
|
825
|
+
raise ConfigurationError(
|
|
826
|
+
f"optimizer must be one of {VALID_OPTIMIZERS}, got '{self.optimizer}'"
|
|
827
|
+
)
|
|
828
|
+
|
|
829
|
+
# Validate scheduler_type
|
|
830
|
+
valid_schedulers = {'step', 'plateau', 'cosine', 'exponential', None}
|
|
831
|
+
if self.scheduler_type not in valid_schedulers:
|
|
832
|
+
raise ConfigurationError(
|
|
833
|
+
f"scheduler_type must be one of {valid_schedulers}, got '{self.scheduler_type}'"
|
|
834
|
+
)
|
|
835
|
+
|
|
836
|
+
# Validate scheduler parameters
|
|
837
|
+
if self.scheduler_gamma <= 0 or self.scheduler_gamma > 1:
|
|
838
|
+
raise ConfigurationError(
|
|
839
|
+
f"scheduler_gamma must be in (0, 1], got {self.scheduler_gamma}"
|
|
840
|
+
)
|
|
841
|
+
|
|
842
|
+
if self.scheduler_patience < 0:
|
|
843
|
+
raise ConfigurationError(
|
|
844
|
+
f"scheduler_patience must be >= 0, got {self.scheduler_patience}"
|
|
845
|
+
)
|
|
846
|
+
|
|
847
|
+
if self.scheduler_factor <= 0 or self.scheduler_factor > 1:
|
|
848
|
+
raise ConfigurationError(
|
|
849
|
+
f"scheduler_factor must be in (0, 1], got {self.scheduler_factor}"
|
|
850
|
+
)
|
|
851
|
+
|
|
852
|
+
if self.scheduler_min_lr < 0:
|
|
853
|
+
raise ConfigurationError(
|
|
854
|
+
f"scheduler_min_lr must be >= 0, got {self.scheduler_min_lr}"
|
|
855
|
+
)
|
|
856
|
+
|
|
857
|
+
@classmethod
|
|
858
|
+
def _extract_ivdfm(cls, data: Dict[str, Any]) -> Dict[str, Any]:
|
|
859
|
+
"""Extract iVDFM-specific parameters from config dict."""
|
|
860
|
+
from ..constants import (
|
|
861
|
+
DEFAULT_IVDFM_LATENT_DIM,
|
|
862
|
+
DEFAULT_IVDFM_AUX_DIM,
|
|
863
|
+
DEFAULT_IVDFM_ENCODER_HIDDEN_DIM,
|
|
864
|
+
DEFAULT_IVDFM_ENCODER_N_HIDDEN_LAYERS,
|
|
865
|
+
DEFAULT_IVDFM_NUM_REGIMES,
|
|
866
|
+
DEFAULT_IVDFM_REGIME_TEMPERATURE,
|
|
867
|
+
DEFAULT_IVDFM_MIXING,
|
|
868
|
+
DEFAULT_IVDFM_DECODER_TYPE,
|
|
869
|
+
DEFAULT_IVDFM_DECODER_HIDDEN_DIM,
|
|
870
|
+
DEFAULT_IVDFM_DECODER_N_HIDDEN_LAYERS,
|
|
871
|
+
DEFAULT_IVDFM_PRIOR_HIDDEN_DIM,
|
|
872
|
+
DEFAULT_IVDFM_PRIOR_N_HIDDEN_LAYERS,
|
|
873
|
+
DEFAULT_IVDFM_FACTOR_ORDER,
|
|
874
|
+
DEFAULT_IVDFM_INNOVATION_DIST,
|
|
875
|
+
DEFAULT_IVDFM_DECODER_VAR,
|
|
876
|
+
DEFAULT_IVDFM_ACTIVATION,
|
|
877
|
+
DEFAULT_IVDFM_SLOPE,
|
|
878
|
+
DEFAULT_IVDFM_BATCH_SIZE,
|
|
879
|
+
DEFAULT_IVDFM_MAX_EPOCHS,
|
|
880
|
+
DEFAULT_IVDFM_AUX_VARIABLE_TYPE,
|
|
881
|
+
DEFAULT_IVDFM_OPTIMIZER_WEIGHT_DECAY,
|
|
882
|
+
DEFAULT_IVDFM_OPTIMIZER_MOMENTUM,
|
|
883
|
+
DEFAULT_IVDFM_SCHEDULER_TYPE,
|
|
884
|
+
DEFAULT_IVDFM_SCHEDULER_STEP_SIZE,
|
|
885
|
+
DEFAULT_IVDFM_SCHEDULER_GAMMA,
|
|
886
|
+
DEFAULT_IVDFM_SCHEDULER_PATIENCE,
|
|
887
|
+
DEFAULT_IVDFM_SCHEDULER_FACTOR,
|
|
888
|
+
DEFAULT_IVDFM_SCHEDULER_MIN_LR,
|
|
889
|
+
)
|
|
890
|
+
|
|
891
|
+
ivdfm_params = cls._extract_params(data, {
|
|
892
|
+
'num_factors': None,
|
|
893
|
+
'window': None, # Default: None (uses full T - full series as one sequence)
|
|
894
|
+
'stride': 1, # Default: stride=1 (overlapping windows)
|
|
895
|
+
'context': None,
|
|
896
|
+
'time_context': 1, # Default: time step only
|
|
897
|
+
'encoder_hidden_dim': DEFAULT_IVDFM_ENCODER_HIDDEN_DIM,
|
|
898
|
+
'encoder_n_hidden_layers': DEFAULT_IVDFM_ENCODER_N_HIDDEN_LAYERS,
|
|
899
|
+
'num_regimes': DEFAULT_IVDFM_NUM_REGIMES,
|
|
900
|
+
'regime_temperature': DEFAULT_IVDFM_REGIME_TEMPERATURE,
|
|
901
|
+
'decoder_type': DEFAULT_IVDFM_DECODER_TYPE,
|
|
902
|
+
'decoder_hidden_dim': DEFAULT_IVDFM_DECODER_HIDDEN_DIM,
|
|
903
|
+
'decoder_n_hidden_layers': DEFAULT_IVDFM_DECODER_N_HIDDEN_LAYERS,
|
|
904
|
+
'prior_hidden_dim': DEFAULT_IVDFM_PRIOR_HIDDEN_DIM,
|
|
905
|
+
'prior_n_hidden_layers': DEFAULT_IVDFM_PRIOR_N_HIDDEN_LAYERS,
|
|
906
|
+
'activation': DEFAULT_IVDFM_ACTIVATION,
|
|
907
|
+
'slope': DEFAULT_IVDFM_SLOPE,
|
|
908
|
+
'factor_order': DEFAULT_IVDFM_FACTOR_ORDER,
|
|
909
|
+
'innovation_distribution': DEFAULT_IVDFM_INNOVATION_DIST,
|
|
910
|
+
'decoder_var': DEFAULT_IVDFM_DECODER_VAR,
|
|
911
|
+
'beta_kl': 1.0,
|
|
912
|
+
'use_layer_norm': False,
|
|
913
|
+
'mixing': DEFAULT_IVDFM_MIXING,
|
|
914
|
+
'learning_rate': DEFAULT_LEARNING_RATE,
|
|
915
|
+
'optimizer': 'Adam',
|
|
916
|
+
'optimizer_weight_decay': DEFAULT_IVDFM_OPTIMIZER_WEIGHT_DECAY,
|
|
917
|
+
'optimizer_momentum': DEFAULT_IVDFM_OPTIMIZER_MOMENTUM,
|
|
918
|
+
'batch_size': DEFAULT_IVDFM_BATCH_SIZE,
|
|
919
|
+
'max_epochs': DEFAULT_IVDFM_MAX_EPOCHS,
|
|
920
|
+
'tolerance': DEFAULT_TOLERANCE,
|
|
921
|
+
'patience': DEFAULT_IVDFM_EARLY_STOP_PATIENCE,
|
|
922
|
+
'seed': None,
|
|
923
|
+
'scheduler_type': DEFAULT_IVDFM_SCHEDULER_TYPE,
|
|
924
|
+
'scheduler_step_size': DEFAULT_IVDFM_SCHEDULER_STEP_SIZE,
|
|
925
|
+
'scheduler_gamma': DEFAULT_IVDFM_SCHEDULER_GAMMA,
|
|
926
|
+
'scheduler_patience': DEFAULT_IVDFM_SCHEDULER_PATIENCE,
|
|
927
|
+
'scheduler_factor': DEFAULT_IVDFM_SCHEDULER_FACTOR,
|
|
928
|
+
'scheduler_min_lr': DEFAULT_IVDFM_SCHEDULER_MIN_LR,
|
|
929
|
+
'scaler': None, # Scaler string: 'standard', 'robust', 'minmax', 'maxabs', 'quantile', or null
|
|
930
|
+
})
|
|
931
|
+
|
|
932
|
+
return ivdfm_params
|
|
933
|
+
|
|
934
|
+
@classmethod
|
|
935
|
+
def from_dict(cls, data: Dict[str, Any]) -> 'iVDFMConfig':
|
|
936
|
+
"""Create iVDFMConfig from dictionary.
|
|
937
|
+
|
|
938
|
+
Parameters
|
|
939
|
+
----------
|
|
940
|
+
data : Dict[str, Any]
|
|
941
|
+
Configuration dictionary
|
|
942
|
+
|
|
943
|
+
Returns
|
|
944
|
+
-------
|
|
945
|
+
iVDFMConfig
|
|
946
|
+
iVDFM configuration instance
|
|
947
|
+
"""
|
|
948
|
+
# Extract base params
|
|
949
|
+
base_params = cls._extract_base(data)
|
|
950
|
+
|
|
951
|
+
# Extract iVDFM-specific params
|
|
952
|
+
ivdfm_params = cls._extract_ivdfm(data)
|
|
953
|
+
|
|
954
|
+
# Combine and create config
|
|
955
|
+
return cls(**base_params, **ivdfm_params)
|
|
956
|
+
|
|
957
|
+
|
|
629
958
|
# ============================================================================
|
|
630
959
|
# Validation Functions
|
|
631
960
|
# ============================================================================
|
|
@@ -662,6 +991,79 @@ def validate_frequency(frequency: str) -> str:
|
|
|
662
991
|
f"Common frequencies: 'd' (daily), 'w' (weekly), 'm' (monthly), "
|
|
663
992
|
f"'q' (quarterly), 'sa' (semi-annual), 'a' (annual)."
|
|
664
993
|
)
|
|
665
|
-
|
|
994
|
+
|
|
666
995
|
return frequency
|
|
667
996
|
|
|
997
|
+
|
|
998
|
+
@dataclass
|
|
999
|
+
class AFMConfig(BaseModelConfig):
|
|
1000
|
+
"""Attention Factor Model configuration (Epstein et al., 2025).
|
|
1001
|
+
|
|
1002
|
+
Cross-sectional conditional factor model + residual trading policy. Like
|
|
1003
|
+
iVDFM, AFM does not use block structure and is constructed directly via
|
|
1004
|
+
``AFMConfig.from_dict(OmegaConf.to_container(cfg))`` rather than through the
|
|
1005
|
+
block-requiring YAML adapter.
|
|
1006
|
+
"""
|
|
1007
|
+
num_factors: Optional[int] = None # K latent factors
|
|
1008
|
+
factor_model: str = DEFAULT_AFM_FACTOR_MODEL # 'attention' (learned) | 'pca'
|
|
1009
|
+
trading: str = DEFAULT_AFM_TRADING # 'longconv' (learned) | 'ou' (Avellaneda-Lee)
|
|
1010
|
+
embed_dim: int = DEFAULT_AFM_EMBED_DIM # characteristic embedding dim d
|
|
1011
|
+
hist_len: int = DEFAULT_AFM_HIST_LEN # residual-history length s for the filter
|
|
1012
|
+
n_kernels: int = DEFAULT_AFM_N_KERNELS # number of long convolutions
|
|
1013
|
+
ridge: float = DEFAULT_AFM_RIDGE # ridge penalty for closed-form loadings
|
|
1014
|
+
squash_lambda: float = DEFAULT_AFM_SQUASH_LAMBDA # LongConv Squash regularizer
|
|
1015
|
+
pca_window: int = DEFAULT_AFM_PCA_WINDOW # trailing window for the PCA benchmark
|
|
1016
|
+
reestim: int = DEFAULT_AFM_REESTIM # PCA re-estimation stride
|
|
1017
|
+
lambda_var: float = DEFAULT_AFM_LAMBDA_VAR # explained-variance regularizer weight
|
|
1018
|
+
turnover_cost: float = DEFAULT_AFM_TURNOVER_COST # 5bps per unit turnover (paper)
|
|
1019
|
+
short_cost: float = DEFAULT_AFM_SHORT_COST # 1bp shorting cost (paper)
|
|
1020
|
+
risk_free: float = 0.0
|
|
1021
|
+
learning_rate: float = DEFAULT_LEARNING_RATE
|
|
1022
|
+
max_epochs: int = DEFAULT_AFM_MAX_EPOCHS
|
|
1023
|
+
patience: Optional[int] = None
|
|
1024
|
+
seed: Optional[int] = None
|
|
1025
|
+
|
|
1026
|
+
def __post_init__(self):
|
|
1027
|
+
super().__post_init__()
|
|
1028
|
+
from ...utils.errors import ConfigurationError
|
|
1029
|
+
if self.num_factors is not None and self.num_factors < 1:
|
|
1030
|
+
raise ConfigurationError(f"num_factors must be >= 1, got {self.num_factors}")
|
|
1031
|
+
if self.factor_model not in ('attention', 'pca'):
|
|
1032
|
+
raise ConfigurationError(
|
|
1033
|
+
f"factor_model must be 'attention' or 'pca', got '{self.factor_model}'")
|
|
1034
|
+
if self.trading not in ('longconv', 'ou'):
|
|
1035
|
+
raise ConfigurationError(
|
|
1036
|
+
f"trading must be 'longconv' or 'ou', got '{self.trading}'")
|
|
1037
|
+
if self.hist_len < 1:
|
|
1038
|
+
raise ConfigurationError(f"hist_len must be >= 1, got {self.hist_len}")
|
|
1039
|
+
if self.embed_dim < 1:
|
|
1040
|
+
raise ConfigurationError(f"embed_dim must be >= 1, got {self.embed_dim}")
|
|
1041
|
+
|
|
1042
|
+
@classmethod
|
|
1043
|
+
def _extract_afm(cls, data: Dict[str, Any]) -> Dict[str, Any]:
|
|
1044
|
+
"""Extract AFM-specific parameters from a config dict."""
|
|
1045
|
+
return cls._extract_params(data, {
|
|
1046
|
+
'num_factors': None,
|
|
1047
|
+
'factor_model': DEFAULT_AFM_FACTOR_MODEL,
|
|
1048
|
+
'trading': DEFAULT_AFM_TRADING,
|
|
1049
|
+
'embed_dim': DEFAULT_AFM_EMBED_DIM,
|
|
1050
|
+
'hist_len': DEFAULT_AFM_HIST_LEN,
|
|
1051
|
+
'n_kernels': DEFAULT_AFM_N_KERNELS,
|
|
1052
|
+
'ridge': DEFAULT_AFM_RIDGE,
|
|
1053
|
+
'squash_lambda': DEFAULT_AFM_SQUASH_LAMBDA,
|
|
1054
|
+
'pca_window': DEFAULT_AFM_PCA_WINDOW,
|
|
1055
|
+
'reestim': DEFAULT_AFM_REESTIM,
|
|
1056
|
+
'lambda_var': DEFAULT_AFM_LAMBDA_VAR,
|
|
1057
|
+
'turnover_cost': DEFAULT_AFM_TURNOVER_COST,
|
|
1058
|
+
'short_cost': DEFAULT_AFM_SHORT_COST,
|
|
1059
|
+
'risk_free': 0.0, 'learning_rate': DEFAULT_LEARNING_RATE,
|
|
1060
|
+
'max_epochs': DEFAULT_AFM_MAX_EPOCHS, 'patience': None, 'seed': None,
|
|
1061
|
+
})
|
|
1062
|
+
|
|
1063
|
+
@classmethod
|
|
1064
|
+
def from_dict(cls, data: Dict[str, Any]) -> 'AFMConfig':
|
|
1065
|
+
"""Create AFMConfig from a dictionary."""
|
|
1066
|
+
base_params = cls._extract_base(data)
|
|
1067
|
+
afm_params = cls._extract_afm(data)
|
|
1068
|
+
return cls(**base_params, **afm_params)
|
|
1069
|
+
|