Apprentissage automatique financier : division des ensembles de données et modèle de référence

Le 115e article original se concentre sur "la croissance personnelle et la liberté de richesse, la logique du fonctionnement mondial, l'investissement quantitatif de l'IA".

Le surajustement est un phénomène très courant lors de la formation de modèles. Le soi-disant surajustement signifie que les performances sur l'ensemble d'entraînement sont très bonnes, mais que les performances sur l'ensemble de test sont très médiocres. Afin de réduire le surajustement et d'améliorer la capacité de généralisation du modèle, il existe de nombreuses mesures pour atténuer le problème du surajustement dans la pratique. L'une des méthodes courantes consiste à conserver une partie des données dans l'ensemble de données existant comme ensemble de test, c'est-à-dire à diviser les données d'origine en X_train, X_test, X_train est utilisé pour former le modèle et X_test est utilisé pour vérifier et ajuster le modèle.

La stratégie quantitative pilotée par l'apprentissage automatique est différente de la quantification traditionnelle en ce que ses signaux de trading sont donnés par le modèle, il doit donc suivre le processus d'apprentissage automatique. L'apprentissage automatique doit diviser l'ensemble de données : ensemble d'apprentissage, ensemble de test. Le modèle est « appris » sur l'ensemble d'apprentissage et les transactions formées sont évaluées sur l'ensemble de test.

La différence avec l'échantillonnage aléatoire d'apprentissage automatique traditionnel est que, puisque nous devons effectuer des backtests "continus" sur l'ensemble de test, nous ne pouvons pas brouiller au hasard la population globale et en faire partie, mais diviser la période en deux segments proportionnellement. Une section est utilisée comme ensemble d'apprentissage et une section est utilisée comme ensemble de test. Par conséquent, au lieu d'utiliser la fonction train_test_split de sklearn, nous implémentons une fonction de segmentation d'ensemble de données pour les séries chronologiques financières.

Le plus couramment utilisé est de sélectionner par date. Par exemple, les données avant le jour "2017-01-01" seront utilisées comme jeu d'entraînement pour la formation du modèle, et les données après ce jour seront utilisées comme jeu de test. , c'est-à-dire pour les futurs backtests.

Le code de base est la fonction suivante :

x_cols correspond à toutes les colonnes de caractéristiques et y_col à la colonne d'étiquette. Si la date divisée est vide, la date sera calculée en fonction de 80 % de l'ensemble d'apprentissage.

Le format de données renvoyé est similaire à train_test_split de sklearn.

importer numpy en tant que np
importer la date et l'heure en tant que dt
depuis engine.datafeed.dataloader importer Dataloader


classe OneStepTimeSeriesSplit :
    """ Génère des tuples de paires train_idx, test_idx
    Suppose que l'index contient un niveau étiqueté 'date'"""

    def __init__(self, n_splits=3, test_period_length=1, shuffle=False) :
        self.n_splits = n_splits
        self.test_period_length = test_period_length
        self.shuffle = mélanger

    @méthodestatique
    def morceaux(l, n):
        for i in range(0, len(l), n):
            imprimer(l[i:i + n])
            rendement l[i:i + n]

    def split(self, X, y=Aucun, groupes=Aucun) :
        unique_dates = (X.index
                        # .get_level_values('date')
                        .unique()
                        .sort_values(croissant=Faux)
        [:self.n_splits * self.test_period_length])

        dates = X.reset_index()[['date']]
        pour test_date dans self.chunks(unique_dates, self.test_period_length):
            train_idx = dates[dates.date < min(test_date)].index
            test_idx = dates[dates.date.isin(test_date)].index
            si self.shuffle :
                np.random.shuffle(list(train_idx))
            rendement train_idx, test_idx

    def get_n_splits(self, X, y, groups=None):
        retourne self.n_splits


def get_date_by_percent(start_date, end_date, pourcentage):
    jours = (date_fin - date_début).jours
    target_days = np.trunc(jours * pourcentage)
    target_date = start_date + dt.timedelta(days=target_days)
    # jours d'impression, target_days, target_date
    renvoyer date_cible


def split_df(df, x_cols, y_col, split_date=None, split_ratio=0.8):
    sinon split_date :
        split_date = get_date_by_percent(df.index[0], df.index[df.shape[0] - 1], split_ratio)

    input_data = df[x_cols]
    output_data = df[y_col]

    # Créer des ensembles d'entraînement et de test
    X_train = input_data[input_data.index < split_date]
    X_test = input_data[input_data.index >= split_date]
    Y_train = output_data[output_data.index < split_date]
    Y_test = output_data[output_data.index >= split_date]

    retour X_train, X_test, Y_train, Y_test


jeu de données de classe :
    def __init__(self, symboles, feature_names, feature_fields, split_date, label_name='label', label_field=None):
        self.feature_names = feature_names
        self.feature_fields = feature_fields
        self.split_date = split_date
        self.label_name = label_name
        self.label_field = 'Sign(Ref($close,-1)/$close -1)' si label_field est None else label_field

        noms = self.feature_names + [self.label_name]
        champs = self.feature_fields + [self.label_field]
        chargeur = DataLoader ()
        self.df = loader.load_one_df(symboles, noms, champs)

    def get_split_dataset(self):
        X_train, X_test, Y_train, Y_test = split_df(self.df, x_cols=self.feature_names, y_col=self.label_name,
                                                    split_date=self.split_date)
        retour X_train, X_test, Y_train, Y_test

    def get_train_data(self):
        X_train, X_test, Y_train, Y_test = split_df(self.df, x_cols=self.feature_names, y_col=self.label_name,
                                                    split_date=self.split_date)
        retour X_train, Y_train

    def get_test_data(self):
        X_train, X_test, Y_train, Y_test = split_df(self.df, x_cols=self.feature_names, y_col=self.label_name,
                                                    split_date=self.split_date)
        renvoie X_test, Y_test

    def get_X_y_data(self):
        X = self.df[self.feature_names]
        y = self.df[self.label_name]
        retourner X, y




si __nom__ == '__main__' :
    code = ['000300.SH', 'SPX']
    noms = []
    champs = []
    champs += ["Corr($close/Ref($close,1), Log($volume/Ref($volume, 1)+1), 30)"]
    noms += ["CORR30"]

    dataset = Dataset(codes, noms, champs, split_date='2020-01-01')
    X_train, Y_train = dataset.get_train_data()
    impression(X_train, Y_train)

Formation modèle :

codes = ['000300.SH', '399006.SZ']
noms = []
champs = []

champs += ["Corr($close/Ref($close,1), Log($volume/Ref($volume, 1)+1), 30)"]
noms += ["CORR30"]

champs += ["Corr($close/Ref($close,1), Log($volume/Ref($volume, 1)+1), 60)"]
noms += ["CORR60"]

champs += ["Ref($close, 5)/$close"]
noms += ["ROC5"]

champs += ["(2*$fermer-$haut-$bas)/$ouvrir"]
noms += ['KSFT']

champs += ["($close-Min($low, 5))/(Max($high, 5)-Min($low, 5)+1e-12)"]
noms += ["RSV5"]

champs += ["($haut-$bas)/$ouvert"]
noms += ['KLEN']

champs += ["$fermer"]
noms += ['fermer']

champs += ['KF(Pente($close,20))']
noms += ['KF']

champs += ['$close/Ref($close,20)-1']
noms += ['ROC_20']

champs += ['KF($ROC_20)']
noms += ['KF_ROC_20']

dataset = Dataset(codes, noms, champs, split_date='2020-01-01')

de sklearn.ensemble importer RandomForestClassifier
depuis sklearn.svm importer LinearSVC, SVC
de sklearn.linear_model importer LogisticRegression
depuis engine.ml.boosting_models importer gb_clf

pour le modèle dans [gb_clf, LogisticRegression(), RandomForestClassifier(), SVC()] :
    m = ModelRunner(modèle, jeu de données)
    m.fit()
    m.predict()

L'apprentissage d'ensemble est bien meilleur que l'apprentissage automatique traditionnel pour les résultats de données tabulaires. Le même ensemble de données, le meilleur est Random Sensen, qui est en fait lightGBM (version non réglée).

Continuez demain.

Rotation ETF + timing RSRS, plus filtre Kaman : annualisé 48,41 %, ratio de Sharpe 1,89

ETF momentum rotation + market timing : 30% stratégie annualisée

Mes projets Open Source et Knowledge Planet

 

Guess you like

Origin blog.csdn.net/weixin_38175458/article/details/128042440