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