CoolFace
Apppublic

Qionk/a-share-quant

sourceHugging Faceupdated 4mo agoView on Hugging Face
0likes
models.py447 linesDownload Raw Back to predict
1"""2价格预测 - 模型定义3LSTM / GRU / 1D-CNN / CNN-GRU / PatchTST / TFT /4XGBoost / LightGBM / ARIMA / SARIMA / GARCH5"""6 7import os8import numpy as np9 10os.environ["TF_CPP_MIN_LOG_LEVEL"] = "3"11 12from dataclasses import dataclass, field13 14 15@dataclass16class ModelConfig:17    """模型超参数"""18    look_back: int = 3019    n_features: int = 120    epochs: int = 10021    batch_size: int = 3222    early_stop_patience: int = 1023    learning_rate: float = 0.00124    dropout: float = 0.225    lstm_units: list = field(default_factory=lambda: [64, 32])26    gru_units: list = field(default_factory=lambda: [64, 32])27    cnn_filters: list = field(default_factory=lambda: [64, 32])28    cnn_kernel_size: int = 329    # PatchTST30    patchtst_patch_size: int = 1631    patchtst_d_model: int = 12832    patchtst_n_heads: int = 433    patchtst_n_encoder_layers: int = 234    patchtst_ff_dim: int = 25635    patchtst_dropout: float = 0.136    # TFT37    tft_hidden_size: int = 6438    tft_n_heads: int = 439    tft_dropout: float = 0.240    tft_lstm_layers: int = 141    # CNN-GRU42    cnn_gru_filters: list = field(default_factory=lambda: [32, 16])43    cnn_gru_gru_units: list = field(default_factory=lambda: [24])44    cnn_gru_kernel_size: int = 345    # XGBoost46    xgboost_n_estimators: int = 10047    xgboost_max_depth: int = 648    xgboost_learning_rate: float = 0.149    xgboost_subsample: float = 0.850    # LightGBM51    lightgbm_n_estimators: int = 10052    lightgbm_max_depth: int = 653    lightgbm_learning_rate: float = 0.154    lightgbm_num_leaves: int = 3155    lightgbm_subsample: float = 0.856    # Per-model DL learning rates57    lstm_lr: float = 0.00158    gru_lr: float = 0.00159    cnn_lr: float = 0.00160    cnn_gru_lr: float = 0.00161    patchtst_lr: float = 0.00162    tft_lr: float = 0.00163    # SARIMA64    sarima_order: tuple = (1, 1, 1)65    sarima_seasonal_order: tuple = (1, 1, 1, 5)66    # GARCH67    garch_p: int = 168    garch_q: int = 169    garch_dist: str = 't'70 71 72def _set_seed(seed=42):73    import tensorflow as tf74    np.random.seed(seed)75    tf.random.set_seed(seed)76 77 78def build_lstm(config: ModelConfig):79    """80    LSTM 模型:81    LSTM(64, return_sequences) → Dropout → LSTM(32) → Dropout → Dense(1)82    """83    _set_seed()84    import tensorflow as tf85    from tensorflow.keras.models import Sequential86    from tensorflow.keras.layers import LSTM, Dense, Dropout, Input87 88    model = Sequential([89        Input(shape=(config.look_back, config.n_features)),90        LSTM(config.lstm_units[0], return_sequences=True),91        Dropout(config.dropout),92        LSTM(config.lstm_units[1]),93        Dropout(config.dropout),94        Dense(16, activation="relu"),95        Dense(1),96    ])97    model.compile(98        optimizer=tf.keras.optimizers.Adam(learning_rate=config.lstm_lr),99        loss="mse",100    )101    return model102 103 104def build_gru(config: ModelConfig):105    """106    GRU 模型: 与 LSTM 同构,替换为 GRU 层107    """108    _set_seed()109    import tensorflow as tf110    from tensorflow.keras.models import Sequential111    from tensorflow.keras.layers import GRU, Dense, Dropout, Input112 113    model = Sequential([114        Input(shape=(config.look_back, config.n_features)),115        GRU(config.gru_units[0], return_sequences=True),116        Dropout(config.dropout),117        GRU(config.gru_units[1]),118        Dropout(config.dropout),119        Dense(16, activation="relu"),120        Dense(1),121    ])122    model.compile(123        optimizer=tf.keras.optimizers.Adam(learning_rate=config.gru_lr),124        loss="mse",125    )126    return model127 128 129def build_cnn(config: ModelConfig):130    """131    1D-CNN 模型:132    Conv1D(64,3) → MaxPool → Conv1D(32,3) → MaxPool → Flatten → Dense(32) → Dense(1)133    """134    _set_seed()135    import tensorflow as tf136    from tensorflow.keras.models import Sequential137    from tensorflow.keras.layers import (138        Conv1D, MaxPooling1D, Flatten, Dense, Dropout, Input139    )140 141    layers = [Input(shape=(config.look_back, config.n_features))]142 143    seq_len = config.look_back144    layers.append(Conv1D(config.cnn_filters[0], config.cnn_kernel_size,145                         activation="relu", padding="same"))146    if seq_len >= 4:147        layers.append(MaxPooling1D(pool_size=2))148        seq_len = seq_len // 2149 150    layers.append(Conv1D(config.cnn_filters[1], config.cnn_kernel_size,151                         activation="relu", padding="same"))152    if seq_len >= 4:153        layers.append(MaxPooling1D(pool_size=2))154 155    layers.extend([156        Flatten(),157        Dense(32, activation="relu"),158        Dropout(config.dropout),159        Dense(1),160    ])161 162    model = Sequential(layers)163    model.compile(164        optimizer=tf.keras.optimizers.Adam(learning_rate=config.cnn_lr),165        loss="mse",166    )167    return model168 169 170def build_cnn_gru(config: ModelConfig):171    """172    CNN-GRU 混合模型(优化版):173    Conv1D(32) → Conv1D(16) → GRU(24) → Dropout → Dense(1)174    移除 MaxPooling 防信息丢失,单层 GRU 减过拟合,去除冗余 Dense 层175    """176    _set_seed()177    import tensorflow as tf178    from tensorflow.keras.models import Sequential179    from tensorflow.keras.layers import (180        Conv1D, GRU, Dense, Dropout, Input181    )182 183    layers = [Input(shape=(config.look_back, config.n_features))]184    layers.append(Conv1D(config.cnn_gru_filters[0], config.cnn_gru_kernel_size,185                         activation="relu", padding="valid"))186    layers.append(Conv1D(config.cnn_gru_filters[1], config.cnn_gru_kernel_size,187                         activation="relu", padding="valid"))188    layers.append(GRU(config.cnn_gru_gru_units[0], return_sequences=False))189    layers.append(Dropout(config.dropout))190    layers.append(Dense(1))191 192    model = Sequential(layers)193    model.compile(194        optimizer=tf.keras.optimizers.Adam(learning_rate=config.cnn_gru_lr),195        loss="mse",196    )197    return model198 199 200def build_patchtst(config: ModelConfig):201    """202    PatchTST + RevIN: per-instance z-score → Patch → Transformer → inverse203    """204    _set_seed()205    import tensorflow as tf206    from tensorflow.keras import layers, Model207 208    look_back = config.look_back209    n_features = config.n_features210    patch_size = config.patchtst_patch_size211    d_model = config.patchtst_d_model212    n_heads = config.patchtst_n_heads213    n_layers = config.patchtst_n_encoder_layers214    ff_dim = config.patchtst_ff_dim215    drop = config.patchtst_dropout216 217    while look_back // patch_size < 3 and patch_size > 4:218        patch_size = patch_size // 2219 220    n_patches = (look_back + patch_size - 1) // patch_size221    pad_len = n_patches * patch_size - look_back222 223    inputs = layers.Input(shape=(look_back, n_features))224 225    # ── RevIN normalize: (x - mean) / std ──226    eps = 1e-5227    x_mean = layers.Lambda(lambda t: tf.reduce_mean(t, axis=1, keepdims=True))(inputs)228    x_std = layers.Lambda(lambda t: tf.math.reduce_std(t, axis=1, keepdims=True) + 1e-5)(inputs)229    x_norm = layers.Subtract()([inputs, x_mean])230    x_norm = layers.Lambda(lambda t: t[0] / t[1])([x_norm, x_std])231 232    # Save target feature stats for inverse (reshape to (None,1) for broadcasting)233    target_mean = layers.Lambda(lambda t: t[:, 0, 0:1])(x_mean)234    target_std = layers.Lambda(lambda t: t[:, 0, 0:1])(x_std)235 236    x = x_norm237    if pad_len > 0:238        x = layers.ZeroPadding1D(padding=(pad_len, 0))(x)239 240    x = layers.Reshape((n_patches, patch_size * n_features))(x)241    x = layers.Dense(d_model)(x)242 243    pos_emb = layers.Embedding(n_patches, d_model)244    positions = tf.range(n_patches)245    x = x + pos_emb(positions)246 247    for _ in range(n_layers):248        attn_out = layers.MultiHeadAttention(249            num_heads=n_heads, key_dim=d_model // n_heads, dropout=drop250        )(x, x)251        attn_out = layers.Dropout(drop)(attn_out)252        x = layers.LayerNormalization(epsilon=1e-6)(x + attn_out)253 254        ff_out = layers.Dense(ff_dim, activation="gelu")(x)255        ff_out = layers.Dropout(drop)(ff_out)256        ff_out = layers.Dense(d_model)(ff_out)257        ff_out = layers.Dropout(drop)(ff_out)258        x = layers.LayerNormalization(epsilon=1e-6)(x + ff_out)259 260    x = layers.Flatten()(x)261    x = layers.Dense(64, activation="relu")(x)262    x = layers.Dropout(drop)(x)263    x = layers.Dense(1)(x)264 265    # ── RevIN inverse: x * std + mean ──266    outputs = layers.Multiply()([x, target_std])267    outputs = layers.Add()([outputs, target_mean])268 269    model = Model(inputs=inputs, outputs=outputs, name="PatchTST")270    model.compile(271        optimizer=tf.keras.optimizers.Adam(learning_rate=config.patchtst_lr),272        loss="mse",273    )274    return model275 276 277def build_tft(config: ModelConfig):278    """279    Temporal Fusion Transformer (简化版):280    Variable Selection → LSTM Encoder → GRN → Temporal Attention → Output281    """282    _set_seed()283    import tensorflow as tf284    from tensorflow.keras import layers, Model285 286    look_back = config.look_back287    n_features = config.n_features288    hidden = config.tft_hidden_size289    n_heads = config.tft_n_heads290    drop = config.tft_dropout291    n_lstm = config.tft_lstm_layers292 293    inputs = layers.Input(shape=(look_back, n_features))294 295    # Variable Selection Network296    context = layers.Flatten()(inputs)297    context = layers.Dense(hidden, activation="relu")(context)298    var_weights = layers.Dense(n_features, activation="softmax",299                               name="variable_weights")(context)300 301    # Per-feature projection + weighted sum302    split_proj = []303    for i in range(n_features):304        feat_slice = layers.Lambda(lambda x, idx=i: x[:, :, idx:idx+1])(inputs)305        feat_proj = layers.Dense(hidden)(feat_slice)306        split_proj.append(feat_proj)307 308    stacked = layers.Lambda(lambda x: tf.stack(x, axis=2))(split_proj)309 310    var_w_expanded = layers.Lambda(311        lambda x: tf.expand_dims(tf.expand_dims(x, 1), -1)312    )(var_weights)313 314    weighted = layers.Multiply()([stacked, var_w_expanded])315    selected = layers.Lambda(lambda x: tf.reduce_sum(x, axis=2))(weighted)316 317    # LSTM Encoder318    lstm_out = selected319    for i in range(n_lstm):320        lstm_out = layers.LSTM(hidden, return_sequences=True,321                               dropout=drop, name=f"tft_lstm_{i}")(lstm_out)322 323    # Gated Residual Network324    grn_h = layers.Dense(hidden, activation="elu")(lstm_out)325    grn_h = layers.Dense(hidden)(grn_h)326    grn_h = layers.Dropout(drop)(grn_h)327    gate = layers.Dense(hidden, activation="sigmoid")(lstm_out)328    grn_out = layers.Multiply()([gate, grn_h])329    skip = layers.Lambda(lambda x: (1 - x[0]) * x[1])([gate, lstm_out])330    grn_out = layers.Add()([grn_out, skip])331    grn_out = layers.LayerNormalization()(grn_out)332 333    # Temporal Self-Attention with static enrichment334    static_ctx = layers.GlobalAveragePooling1D()(lstm_out)335    static_ctx = layers.RepeatVector(look_back)(static_ctx)336    enriched = layers.Add()([grn_out, static_ctx])337 338    attn_out = layers.MultiHeadAttention(339        num_heads=n_heads, key_dim=hidden // n_heads, dropout=drop340    )(enriched, enriched)341    attn_out = layers.Dropout(drop)(attn_out)342    attn_out = layers.Add()([attn_out, enriched])343    attn_out = layers.LayerNormalization()(attn_out)344 345    # Output346    last_step = layers.Lambda(lambda x: x[:, -1, :])(attn_out)347    out = layers.Dense(hidden // 2, activation="relu")(last_step)348    out = layers.Dropout(drop)(out)349    outputs = layers.Dense(1)(out)350 351    model = Model(inputs=inputs, outputs=outputs, name="TFT")352    model.compile(353        optimizer=tf.keras.optimizers.Adam(learning_rate=config.tft_lr),354        loss="mse",355    )356    return model357 358 359def fit_arima(train_data: np.ndarray, exog: np.ndarray = None, config: ModelConfig = None):360    """361    拟合 ARIMA(1,1,1) + 常数漂移362    train_data: 一维收盘价序列363    """364    from statsmodels.tsa.arima.model import ARIMA365    import warnings366    warnings.filterwarnings("ignore")367 368    model = ARIMA(train_data, order=(1, 1, 1), exog=exog, trend='t')369    result = model.fit()370    return result371 372 373def predict_arima(model, steps: int, exog_future: np.ndarray = None):374    """375    ARIMA 多步预测376    返回: (predictions, confidence_intervals)377    """378    forecast = model.get_forecast(steps=steps, exog=exog_future)379    fc = np.array(forecast.predicted_mean)380    conf = np.array(forecast.conf_int(alpha=0.05))381    return fc, conf382 383 384def returns_to_prices(last_price: float, predicted_returns: np.ndarray) -> np.ndarray:385    """从最后收盘价 + 预测收益率序列 → 价格序列"""386    prices = [last_price]387    for r in predicted_returns:388        prices.append(prices[-1] * (1 + r / 100))389    return np.array(prices[1:])390 391 392def build_xgboost(config: ModelConfig):393    """XGBoost 回归模型"""394    import xgboost as xgb395    return xgb.XGBRegressor(396        n_estimators=config.xgboost_n_estimators,397        max_depth=config.xgboost_max_depth,398        learning_rate=config.xgboost_learning_rate,399        subsample=config.xgboost_subsample,400        random_state=42,401        n_jobs=-1,402        verbosity=0,403    )404 405 406def build_lightgbm(config: ModelConfig):407    """LightGBM 回归模型"""408    import lightgbm as lgb409    return lgb.LGBMRegressor(410        n_estimators=config.lightgbm_n_estimators,411        max_depth=config.lightgbm_max_depth,412        learning_rate=config.lightgbm_learning_rate,413        num_leaves=config.lightgbm_num_leaves,414        random_state=42,415        n_jobs=-1,416        verbose=-1,417    )418 419 420def fit_sarima(train_data: np.ndarray, config: ModelConfig = None):421    """422    拟合 SARIMA 模型423    train_data: 一维收盘价序列424    """425    from statsmodels.tsa.statespace.sarimax import SARIMAX426    import warnings427    warnings.filterwarnings("ignore")428 429    order = config.sarima_order if config else (1, 1, 1)430    seasonal_order = config.sarima_seasonal_order if config else (1, 1, 1, 5)431 432    model = SARIMAX(train_data, order=order, seasonal_order=seasonal_order,433                    trend='t', enforce_stationarity=False, enforce_invertibility=False)434    result = model.fit(disp=False)435    return result436 437 438def predict_sarima(model, steps: int):439    """440    SARIMA 多步预测441    返回: (predictions, confidence_intervals)442    """443    forecast = model.get_forecast(steps=steps)444    fc = np.array(forecast.predicted_mean)445    conf = np.array(forecast.conf_int(alpha=0.05))446    return fc, conf447