dfm-python 0.6.14__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 +40 -9
- dfm_python/config/schema/__init__.py +6 -6
- dfm_python/config/schema/model.py +165 -27
- dfm_python/config/schema/params.py +35 -0
- dfm_python/config/schema/results.py +58 -0
- dfm_python/dataset/__init__.py +2 -0
- dfm_python/dataset/afm_dataset.py +119 -0
- dfm_python/dataset/ivdfm_dataset.py +87 -28
- dfm_python/layer/mixing.py +37 -0
- dfm_python/layer/mlp.py +34 -17
- dfm_python/layer/pca.py +46 -0
- dfm_python/layer/regime.py +53 -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/ivdfm/__init__.py +2 -0
- dfm_python/models/ivdfm/decoder.py +51 -103
- dfm_python/models/ivdfm/encoder.py +11 -6
- dfm_python/models/ivdfm/ivdfm.py +939 -372
- dfm_python/models/ivdfm/optuna.py +647 -0
- dfm_python/models/ivdfm/prior.py +77 -4
- dfm_python/numeric/builder.py +39 -1
- dfm_python/numeric/estimator.py +78 -9
- dfm_python/numeric/validator.py +5 -0
- dfm_python/ssm/companion.py +210 -2
- dfm_python/ssm/kalman.py +100 -3
- dfm_python/ssm/residual.py +0 -0
- dfm_python/utils/loss.py +53 -121
- dfm_python-0.7.0.dist-info/METADATA +186 -0
- dfm_python-0.7.0.dist-info/RECORD +75 -0
- {dfm_python-0.6.14.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-0.6.14.dist-info/METADATA +0 -56
- dfm_python-0.6.14.dist-info/RECORD +0 -64
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
|
@@ -348,18 +348,23 @@ DEFAULT_KDFM_MA_ORDER = 0 # Default MA order (MA lag order q) for KDFM (0 = pur
|
|
|
348
348
|
# iVDFM Defaults
|
|
349
349
|
# ============================================================================
|
|
350
350
|
|
|
351
|
-
# iVDFM
|
|
352
|
-
DEFAULT_IVDFM_SEQUENCE_LENGTH = 100 # Default sequence length for sliding windows
|
|
351
|
+
# iVDFM dimension defaults
|
|
353
352
|
DEFAULT_IVDFM_LATENT_DIM = 3 # Default number of factors
|
|
354
353
|
DEFAULT_IVDFM_AUX_DIM = 1 # Default auxiliary variable dimension
|
|
355
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
|
+
|
|
356
360
|
# iVDFM network architecture defaults
|
|
357
361
|
DEFAULT_IVDFM_ENCODER_HIDDEN_DIM = 200 # Default encoder hidden dimension
|
|
358
|
-
|
|
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)
|
|
359
364
|
DEFAULT_IVDFM_DECODER_HIDDEN_DIM = 200 # Default decoder hidden dimension
|
|
360
|
-
|
|
365
|
+
DEFAULT_IVDFM_DECODER_N_HIDDEN_LAYERS = 2 # Default decoder hidden layers
|
|
361
366
|
DEFAULT_IVDFM_PRIOR_HIDDEN_DIM = 100 # Default prior network hidden dimension
|
|
362
|
-
|
|
367
|
+
DEFAULT_IVDFM_PRIOR_N_HIDDEN_LAYERS = 1 # Default prior network hidden layers
|
|
363
368
|
|
|
364
369
|
# iVDFM dynamics and distribution defaults
|
|
365
370
|
DEFAULT_IVDFM_FACTOR_ORDER = 1 # Default AR order for factors
|
|
@@ -384,6 +389,7 @@ DEFAULT_IVDFM_SCHEDULER_GAMMA = 0.5 # Default gamma for StepLR/ExponentialLR
|
|
|
384
389
|
DEFAULT_IVDFM_SCHEDULER_PATIENCE = 10 # Default patience for ReduceLROnPlateau
|
|
385
390
|
DEFAULT_IVDFM_SCHEDULER_FACTOR = 0.1 # Default factor for ReduceLROnPlateau
|
|
386
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)
|
|
387
393
|
|
|
388
394
|
# ============================================================================
|
|
389
395
|
# Tutorial Defaults
|
|
@@ -451,11 +457,35 @@ FREQUENCY_HIERARCHY: Dict[str, int] = {
|
|
|
451
457
|
# For frequency gaps larger than this, the missing data approach is used instead
|
|
452
458
|
MAX_TENT_SIZE: int = 12
|
|
453
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
|
+
|
|
454
478
|
# ============================================================================
|
|
455
479
|
# Export all constants
|
|
456
480
|
# ============================================================================
|
|
457
481
|
|
|
458
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',
|
|
459
489
|
# Convergence
|
|
460
490
|
'DEFAULT_CONVERGENCE_THRESHOLD',
|
|
461
491
|
'DEFAULT_EM_THRESHOLD',
|
|
@@ -606,15 +636,16 @@ __all__ = [
|
|
|
606
636
|
'DEFAULT_KDFM_AR_ORDER',
|
|
607
637
|
'DEFAULT_KDFM_MA_ORDER',
|
|
608
638
|
# iVDFM defaults
|
|
609
|
-
'DEFAULT_IVDFM_SEQUENCE_LENGTH',
|
|
610
639
|
'DEFAULT_IVDFM_LATENT_DIM',
|
|
611
640
|
'DEFAULT_IVDFM_AUX_DIM',
|
|
612
641
|
'DEFAULT_IVDFM_ENCODER_HIDDEN_DIM',
|
|
613
|
-
'
|
|
642
|
+
'DEFAULT_IVDFM_ENCODER_N_HIDDEN_LAYERS',
|
|
643
|
+
'DEFAULT_IVDFM_NUM_REGIMES',
|
|
644
|
+
'DEFAULT_IVDFM_DECODER_TYPE',
|
|
614
645
|
'DEFAULT_IVDFM_DECODER_HIDDEN_DIM',
|
|
615
|
-
'
|
|
646
|
+
'DEFAULT_IVDFM_DECODER_N_HIDDEN_LAYERS',
|
|
616
647
|
'DEFAULT_IVDFM_PRIOR_HIDDEN_DIM',
|
|
617
|
-
'
|
|
648
|
+
'DEFAULT_IVDFM_PRIOR_N_HIDDEN_LAYERS',
|
|
618
649
|
'DEFAULT_IVDFM_FACTOR_ORDER',
|
|
619
650
|
'DEFAULT_IVDFM_INNOVATION_DIST',
|
|
620
651
|
'DEFAULT_IVDFM_DECODER_VAR',
|
|
@@ -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, iVDFMConfig
|
|
11
|
-
from .results import BaseResult, DFMResult, DDFMResult, iVDFMResult
|
|
12
|
-
from .params import DFMStateSpaceParams, DDFMStateSpaceParams, iVDFMModelState
|
|
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', 'iVDFMConfig',
|
|
16
|
-
'BaseResult', 'DFMResult', 'DDFMResult', 'iVDFMResult',
|
|
17
|
-
'DFMStateSpaceParams', 'DDFMStateSpaceParams', 'iVDFMModelState',
|
|
15
|
+
'BaseModelConfig', 'DFMConfig', 'DDFMConfig', 'iVDFMConfig', 'AFMConfig',
|
|
16
|
+
'BaseResult', 'DFMResult', 'DDFMResult', 'iVDFMResult', 'AFMResult',
|
|
17
|
+
'DFMStateSpaceParams', 'DDFMStateSpaceParams', 'iVDFMModelState', 'AFMModelState',
|
|
18
18
|
]
|
|
19
19
|
|
|
@@ -56,6 +56,7 @@ from ..constants import (
|
|
|
56
56
|
DEFAULT_IVDFM_SCHEDULER_PATIENCE,
|
|
57
57
|
DEFAULT_IVDFM_SCHEDULER_FACTOR,
|
|
58
58
|
DEFAULT_IVDFM_SCHEDULER_MIN_LR,
|
|
59
|
+
DEFAULT_IVDFM_EARLY_STOP_PATIENCE,
|
|
59
60
|
DEFAULT_REGULARIZATION_SCALE,
|
|
60
61
|
DEFAULT_STRUCTURAL_REG_WEIGHT,
|
|
61
62
|
DEFAULT_CONVERGENCE_THRESHOLD,
|
|
@@ -76,17 +77,34 @@ from ..constants import (
|
|
|
76
77
|
DEFAULT_CLOCK_FREQUENCY,
|
|
77
78
|
DEFAULT_KDFM_AR_ORDER,
|
|
78
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,
|
|
79
94
|
FREQUENCY_HIERARCHY,
|
|
80
95
|
DEFAULT_HIERARCHY_VALUE,
|
|
81
|
-
DEFAULT_IVDFM_SEQUENCE_LENGTH,
|
|
82
96
|
DEFAULT_IVDFM_LATENT_DIM,
|
|
83
97
|
DEFAULT_IVDFM_AUX_DIM,
|
|
84
98
|
DEFAULT_IVDFM_ENCODER_HIDDEN_DIM,
|
|
85
|
-
|
|
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,
|
|
86
104
|
DEFAULT_IVDFM_DECODER_HIDDEN_DIM,
|
|
87
|
-
|
|
105
|
+
DEFAULT_IVDFM_DECODER_N_HIDDEN_LAYERS,
|
|
88
106
|
DEFAULT_IVDFM_PRIOR_HIDDEN_DIM,
|
|
89
|
-
|
|
107
|
+
DEFAULT_IVDFM_PRIOR_N_HIDDEN_LAYERS,
|
|
90
108
|
DEFAULT_IVDFM_FACTOR_ORDER,
|
|
91
109
|
DEFAULT_IVDFM_INNOVATION_DIST,
|
|
92
110
|
DEFAULT_IVDFM_DECODER_VAR,
|
|
@@ -676,28 +694,35 @@ class iVDFMConfig(BaseModelConfig):
|
|
|
676
694
|
# Model Structure
|
|
677
695
|
# ========================================================================
|
|
678
696
|
num_factors: Optional[int] = None # Number of factors (inferred from config if None)
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
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.
|
|
682
701
|
|
|
683
702
|
# ========================================================================
|
|
684
703
|
# Neural Network Architecture
|
|
685
704
|
# ========================================================================
|
|
686
705
|
encoder_hidden_dim: Union[int, List[int]] = DEFAULT_IVDFM_ENCODER_HIDDEN_DIM # Encoder architecture
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
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)
|
|
690
710
|
prior_hidden_dim: Union[int, List[int]] = DEFAULT_IVDFM_PRIOR_HIDDEN_DIM # Prior network architecture
|
|
691
|
-
|
|
711
|
+
prior_n_hidden_layers: int = DEFAULT_IVDFM_PRIOR_N_HIDDEN_LAYERS # Number of prior network hidden layers
|
|
692
712
|
activation: str = DEFAULT_IVDFM_ACTIVATION # Activation function
|
|
693
713
|
slope: float = DEFAULT_IVDFM_SLOPE # Leaky ReLU slope
|
|
694
714
|
|
|
695
715
|
# ========================================================================
|
|
696
716
|
# Dynamics and Distribution Parameters
|
|
697
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)
|
|
698
720
|
factor_order: int = DEFAULT_IVDFM_FACTOR_ORDER # AR order for factors
|
|
699
721
|
innovation_distribution: str = DEFAULT_IVDFM_INNOVATION_DIST # Innovation distribution type
|
|
700
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)
|
|
701
726
|
|
|
702
727
|
# ========================================================================
|
|
703
728
|
# Training Parameters
|
|
@@ -709,6 +734,7 @@ class iVDFMConfig(BaseModelConfig):
|
|
|
709
734
|
batch_size: int = DEFAULT_IVDFM_BATCH_SIZE # Batch size
|
|
710
735
|
max_epochs: int = DEFAULT_IVDFM_MAX_EPOCHS # Maximum epochs
|
|
711
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.
|
|
712
738
|
seed: Optional[int] = None # Random seed
|
|
713
739
|
|
|
714
740
|
# ========================================================================
|
|
@@ -727,16 +753,22 @@ class iVDFMConfig(BaseModelConfig):
|
|
|
727
753
|
|
|
728
754
|
from ...utils.errors import ConfigurationError
|
|
729
755
|
|
|
730
|
-
# Validate
|
|
731
|
-
if self.
|
|
756
|
+
# Validate window
|
|
757
|
+
if self.window is not None and self.window < 1:
|
|
732
758
|
raise ConfigurationError(
|
|
733
|
-
f"
|
|
759
|
+
f"window must be >= 1, got {self.window}"
|
|
734
760
|
)
|
|
735
761
|
|
|
736
|
-
# Validate
|
|
737
|
-
if self.
|
|
762
|
+
# Validate time_context
|
|
763
|
+
if self.time_context < 1:
|
|
738
764
|
raise ConfigurationError(
|
|
739
|
-
f"
|
|
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}"
|
|
740
772
|
)
|
|
741
773
|
|
|
742
774
|
# Validate num_factors if provided
|
|
@@ -758,6 +790,22 @@ class iVDFMConfig(BaseModelConfig):
|
|
|
758
790
|
f"innovation_distribution must be one of {valid_dists}, got '{self.innovation_distribution}'"
|
|
759
791
|
)
|
|
760
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
|
+
|
|
761
809
|
# Validate activation
|
|
762
810
|
valid_activations = {'relu', 'lrelu', 'tanh', 'sigmoid'}
|
|
763
811
|
if self.activation not in valid_activations:
|
|
@@ -765,6 +813,12 @@ class iVDFMConfig(BaseModelConfig):
|
|
|
765
813
|
f"activation must be one of {valid_activations}, got '{self.activation}'"
|
|
766
814
|
)
|
|
767
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
|
+
|
|
768
822
|
# Validate optimizer
|
|
769
823
|
from ..constants import VALID_OPTIMIZERS
|
|
770
824
|
if self.optimizer not in VALID_OPTIMIZERS:
|
|
@@ -804,15 +858,18 @@ class iVDFMConfig(BaseModelConfig):
|
|
|
804
858
|
def _extract_ivdfm(cls, data: Dict[str, Any]) -> Dict[str, Any]:
|
|
805
859
|
"""Extract iVDFM-specific parameters from config dict."""
|
|
806
860
|
from ..constants import (
|
|
807
|
-
DEFAULT_IVDFM_SEQUENCE_LENGTH,
|
|
808
861
|
DEFAULT_IVDFM_LATENT_DIM,
|
|
809
862
|
DEFAULT_IVDFM_AUX_DIM,
|
|
810
863
|
DEFAULT_IVDFM_ENCODER_HIDDEN_DIM,
|
|
811
|
-
|
|
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,
|
|
812
869
|
DEFAULT_IVDFM_DECODER_HIDDEN_DIM,
|
|
813
|
-
|
|
870
|
+
DEFAULT_IVDFM_DECODER_N_HIDDEN_LAYERS,
|
|
814
871
|
DEFAULT_IVDFM_PRIOR_HIDDEN_DIM,
|
|
815
|
-
|
|
872
|
+
DEFAULT_IVDFM_PRIOR_N_HIDDEN_LAYERS,
|
|
816
873
|
DEFAULT_IVDFM_FACTOR_ORDER,
|
|
817
874
|
DEFAULT_IVDFM_INNOVATION_DIST,
|
|
818
875
|
DEFAULT_IVDFM_DECODER_VAR,
|
|
@@ -833,20 +890,27 @@ class iVDFMConfig(BaseModelConfig):
|
|
|
833
890
|
|
|
834
891
|
ivdfm_params = cls._extract_params(data, {
|
|
835
892
|
'num_factors': None,
|
|
836
|
-
'
|
|
893
|
+
'window': None, # Default: None (uses full T - full series as one sequence)
|
|
894
|
+
'stride': 1, # Default: stride=1 (overlapping windows)
|
|
837
895
|
'context': None,
|
|
838
|
-
'
|
|
896
|
+
'time_context': 1, # Default: time step only
|
|
839
897
|
'encoder_hidden_dim': DEFAULT_IVDFM_ENCODER_HIDDEN_DIM,
|
|
840
|
-
'
|
|
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,
|
|
841
902
|
'decoder_hidden_dim': DEFAULT_IVDFM_DECODER_HIDDEN_DIM,
|
|
842
|
-
'
|
|
903
|
+
'decoder_n_hidden_layers': DEFAULT_IVDFM_DECODER_N_HIDDEN_LAYERS,
|
|
843
904
|
'prior_hidden_dim': DEFAULT_IVDFM_PRIOR_HIDDEN_DIM,
|
|
844
|
-
'
|
|
905
|
+
'prior_n_hidden_layers': DEFAULT_IVDFM_PRIOR_N_HIDDEN_LAYERS,
|
|
845
906
|
'activation': DEFAULT_IVDFM_ACTIVATION,
|
|
846
907
|
'slope': DEFAULT_IVDFM_SLOPE,
|
|
847
908
|
'factor_order': DEFAULT_IVDFM_FACTOR_ORDER,
|
|
848
909
|
'innovation_distribution': DEFAULT_IVDFM_INNOVATION_DIST,
|
|
849
910
|
'decoder_var': DEFAULT_IVDFM_DECODER_VAR,
|
|
911
|
+
'beta_kl': 1.0,
|
|
912
|
+
'use_layer_norm': False,
|
|
913
|
+
'mixing': DEFAULT_IVDFM_MIXING,
|
|
850
914
|
'learning_rate': DEFAULT_LEARNING_RATE,
|
|
851
915
|
'optimizer': 'Adam',
|
|
852
916
|
'optimizer_weight_decay': DEFAULT_IVDFM_OPTIMIZER_WEIGHT_DECAY,
|
|
@@ -854,6 +918,7 @@ class iVDFMConfig(BaseModelConfig):
|
|
|
854
918
|
'batch_size': DEFAULT_IVDFM_BATCH_SIZE,
|
|
855
919
|
'max_epochs': DEFAULT_IVDFM_MAX_EPOCHS,
|
|
856
920
|
'tolerance': DEFAULT_TOLERANCE,
|
|
921
|
+
'patience': DEFAULT_IVDFM_EARLY_STOP_PATIENCE,
|
|
857
922
|
'seed': None,
|
|
858
923
|
'scheduler_type': DEFAULT_IVDFM_SCHEDULER_TYPE,
|
|
859
924
|
'scheduler_step_size': DEFAULT_IVDFM_SCHEDULER_STEP_SIZE,
|
|
@@ -926,6 +991,79 @@ def validate_frequency(frequency: str) -> str:
|
|
|
926
991
|
f"Common frequencies: 'd' (daily), 'w' (weekly), 'm' (monthly), "
|
|
927
992
|
f"'q' (quarterly), 'sa' (semi-annual), 'a' (annual)."
|
|
928
993
|
)
|
|
929
|
-
|
|
994
|
+
|
|
930
995
|
return frequency
|
|
931
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
|
+
|
|
@@ -514,3 +514,38 @@ DDFMStateSpaceParams = DDFMModelState
|
|
|
514
514
|
# Backward compatibility alias
|
|
515
515
|
DFMStateSpaceParams = DFMModelState
|
|
516
516
|
|
|
517
|
+
|
|
518
|
+
@dataclass
|
|
519
|
+
class AFMModelState:
|
|
520
|
+
"""Attention Factor Model training state for checkpointing."""
|
|
521
|
+
num_iter: int = 0
|
|
522
|
+
converged: bool = False
|
|
523
|
+
loss_now: Optional[float] = None
|
|
524
|
+
sharpe: Optional[float] = None
|
|
525
|
+
explained_variance: Optional[float] = None
|
|
526
|
+
residuals: Optional[np.ndarray] = None # (B, T, N) traded residual portfolios
|
|
527
|
+
book_returns: Optional[np.ndarray] = None # (B, n_eval) after-cost book returns
|
|
528
|
+
|
|
529
|
+
@classmethod
|
|
530
|
+
def from_model(cls, model: Any) -> 'AFMModelState':
|
|
531
|
+
"""Create state from a trained AFM instance."""
|
|
532
|
+
return cls(
|
|
533
|
+
num_iter=getattr(model, '_num_iter', 0),
|
|
534
|
+
converged=getattr(model, '_converged', False),
|
|
535
|
+
loss_now=getattr(model, 'loss_now', None),
|
|
536
|
+
sharpe=getattr(model, '_sharpe', None),
|
|
537
|
+
explained_variance=getattr(model, '_ev', None),
|
|
538
|
+
residuals=getattr(model, '_residuals', None),
|
|
539
|
+
book_returns=getattr(model, '_book_returns', None),
|
|
540
|
+
)
|
|
541
|
+
|
|
542
|
+
def apply_to_model(self, model: Any) -> None:
|
|
543
|
+
"""Restore training diagnostics onto a model instance."""
|
|
544
|
+
model._num_iter = self.num_iter
|
|
545
|
+
model._converged = self.converged
|
|
546
|
+
model.loss_now = self.loss_now
|
|
547
|
+
model._sharpe = self.sharpe
|
|
548
|
+
model._ev = self.explained_variance
|
|
549
|
+
model._residuals = self.residuals
|
|
550
|
+
model._book_returns = self.book_returns
|
|
551
|
+
|
|
@@ -458,6 +458,13 @@ class iVDFMResult(BaseResult):
|
|
|
458
458
|
reconstructions: Optional[np.ndarray] = None # decoded factors, (T, N) or (batch, T, N)
|
|
459
459
|
full_state: Optional[np.ndarray] = None # Augmented state for companion form (T x r*p) for p>1, (T x r) for p=1
|
|
460
460
|
|
|
461
|
+
# Regime and mixing (when num_regimes > 1 or mixing=True)
|
|
462
|
+
num_regimes: Optional[int] = None # K; 1 = baseline (no regime structure)
|
|
463
|
+
regime_temperature: Optional[float] = None # Softmax τ for π (τ < 1 = sharper commitment)
|
|
464
|
+
regime_weights: Optional[np.ndarray] = None # (T, K) or (num_windows, K) π_t from regime_net; for Z = [factors, regime] in stability
|
|
465
|
+
mixing: Optional[bool] = None # Whether post-factor mixing f -> M f was used
|
|
466
|
+
mixing_matrix: Optional[np.ndarray] = None # (r, r) learned M when mixing=True (for inspection)
|
|
467
|
+
|
|
461
468
|
# Training diagnostics
|
|
462
469
|
training_elbo: Optional[float] = None
|
|
463
470
|
training_loss: Optional[float] = None
|
|
@@ -498,6 +505,14 @@ class iVDFMResult(BaseResult):
|
|
|
498
505
|
lines.append(f"Reconstructions shape: {getattr(self.reconstructions, 'shape', None)}")
|
|
499
506
|
if self.full_state is not None:
|
|
500
507
|
lines.append(f"Full state (augmented) shape: {getattr(self.full_state, 'shape', None)}")
|
|
508
|
+
if self.num_regimes is not None:
|
|
509
|
+
lines.append(f"Num regimes: {self.num_regimes}")
|
|
510
|
+
if self.regime_temperature is not None:
|
|
511
|
+
lines.append(f"Regime temperature: {self.regime_temperature}")
|
|
512
|
+
if self.mixing is not None:
|
|
513
|
+
lines.append(f"Mixing: {self.mixing}")
|
|
514
|
+
if self.mixing_matrix is not None:
|
|
515
|
+
lines.append(f"Mixing matrix shape: {getattr(self.mixing_matrix, 'shape', None)}")
|
|
501
516
|
lines.append("")
|
|
502
517
|
lines.append("Training:")
|
|
503
518
|
lines.append(f" Converged: {self.converged}")
|
|
@@ -513,3 +528,46 @@ class iVDFMResult(BaseResult):
|
|
|
513
528
|
|
|
514
529
|
# FitParams moved to config.schema.params as DFMParams
|
|
515
530
|
|
|
531
|
+
|
|
532
|
+
@dataclass
|
|
533
|
+
class AFMResult(BaseResult):
|
|
534
|
+
"""Attention Factor Model results (statistical-arbitrage residual trading).
|
|
535
|
+
|
|
536
|
+
AFM is not a linear state-space model; its primary artifacts are the traded
|
|
537
|
+
residual portfolios, the after-cost book returns, and the objective values.
|
|
538
|
+
``Z``/``x_sm`` alias the residuals so the common accessors still work.
|
|
539
|
+
"""
|
|
540
|
+
residual_portfolios: Optional[np.ndarray] = None # (B, T, N) traded residuals
|
|
541
|
+
book_returns: Optional[np.ndarray] = None # (B, n_eval) after-cost returns
|
|
542
|
+
sharpe: Optional[float] = None # after-cost Sharpe (objective)
|
|
543
|
+
explained_variance: Optional[float] = None # EV regularizer value
|
|
544
|
+
factor_model: Optional[str] = None # 'attention' | 'pca'
|
|
545
|
+
trading: Optional[str] = None # 'longconv' | 'ou'
|
|
546
|
+
num_factors: Optional[int] = None
|
|
547
|
+
|
|
548
|
+
@property
|
|
549
|
+
def factors(self) -> Optional[np.ndarray]:
|
|
550
|
+
return self.Z
|
|
551
|
+
|
|
552
|
+
@property
|
|
553
|
+
def reconstructed(self) -> Optional[np.ndarray]:
|
|
554
|
+
return self.x_sm
|
|
555
|
+
|
|
556
|
+
@property
|
|
557
|
+
def x_hat(self) -> Optional[np.ndarray]:
|
|
558
|
+
return self.x_sm
|
|
559
|
+
|
|
560
|
+
def summary(self) -> str:
|
|
561
|
+
lines = ["=" * 80, "AFM Result Summary", "=" * 80, ""]
|
|
562
|
+
lines.append(f"Factor model: {self.factor_model}, trading: {self.trading}")
|
|
563
|
+
if self.num_factors is not None:
|
|
564
|
+
lines.append(f"Num factors: {self.num_factors}")
|
|
565
|
+
if self.sharpe is not None:
|
|
566
|
+
lines.append(f"After-cost Sharpe (objective): {self.sharpe:.4f}")
|
|
567
|
+
if self.explained_variance is not None:
|
|
568
|
+
lines.append(f"Explained variance: {self.explained_variance:.4f}")
|
|
569
|
+
if self.book_returns is not None:
|
|
570
|
+
lines.append(f"Book returns shape: {getattr(self.book_returns, 'shape', None)}")
|
|
571
|
+
lines += ["", "=" * 80]
|
|
572
|
+
return "\n".join(lines)
|
|
573
|
+
|
dfm_python/dataset/__init__.py
CHANGED
|
@@ -11,6 +11,7 @@ This module provides:
|
|
|
11
11
|
from .ddfm_dataset import DDFMDataset
|
|
12
12
|
from .dfm_dataset import DFMDataset
|
|
13
13
|
from .ivdfm_dataset import iVDFMDataset
|
|
14
|
+
from .afm_dataset import AFMDataset
|
|
14
15
|
from .time import TimeIndex
|
|
15
16
|
|
|
16
17
|
__all__ = [
|
|
@@ -18,6 +19,7 @@ __all__ = [
|
|
|
18
19
|
'DDFMDataset',
|
|
19
20
|
'DFMDataset',
|
|
20
21
|
'iVDFMDataset',
|
|
22
|
+
'AFMDataset',
|
|
21
23
|
# Time utilities
|
|
22
24
|
'TimeIndex',
|
|
23
25
|
]
|