Künstliche Intelligenz und maschinelles Lernen für den Devisenhandel Teil 5 – Funktionen | von ml bull | Juni 2023

0
28


Diese Artikelserie widmet sich dem Verständnis von KI/ML und ihrem Zusammenhang mit dem Devisenhandel. Die meisten Artikel im Ether konzentrieren sich auf die Vorhersage eines Preises und sind nahezu nutzlos, wenn es darum geht, worthwhile Handelsstrategien zu finden. Deshalb konzentrieren wir uns hier darauf.

Ich handele seit 20 Jahren mit Devisen, wobei ich in den letzten etwa 5 Jahren sowohl traditionelle Statistik- und Diagrammanalysen als auch KI/ML verwendet habe. Mit einem Bachelor of Engineering, einem Grasp und mehreren Zertifikaten in maschinellem Lernen wollte ich einige der Fallstricke teilen, die ich jahrelang erlernt habe, und erklären, warum es schwierig, aber nicht unmöglich ist, ein System zum Laufen zu bringen.

In den ersten vier Artikeln haben wir:
1. Das einfachste „Hallo Welt“-Beispiel erstellt. Wir haben Daten gesammelt, ein Modell erstellt und unser Ergebnis gemessen
2. Wir verwenden Klassengewichtungen, um unser Modell „in den Ballpark“ zu bringen und vielleicht etwas besser als nur zu raten und unsere Messung zu verbessern.
3. Im dritten Teil haben wir unter die Decke der logistischen Regression geschaut, um ihre Grenzen zu finden und herauszufinden, wohin wir als nächstes gehen könnten.
4. Wir haben uns die Normalisierung und ihre Auswirkungen angesehen und erkannt, dass unsere Hypothese möglicherweise unwahr ist.
5. In diesem Artikel geht es darum, unsere Hypothese durch das Hinzufügen weiterer Funktionen zu stärken

Hierbei handelt es sich in keiner Weise um eine Finanzberatung und es handelt sich nicht um eine Empfehlung für eine bestimmte Handelsstrategie, sondern soll vielmehr dazu beitragen, einige Particulars des Devisenmarkts zu verstehen und ML-Techniken darauf anzuwenden.

Es ist wahrscheinlich, dass unsere vorherige Hypothese unwahr struggle (etwas im Schlusskurs der letzten 4 Stunden sagte eine plötzliche Preisbewegung in den nächsten 4 Stunden voraus). Daher müssen wir eine völlig neue Hypothese ausprobieren (und das werden wir in zukünftigen Artikeln tun) oder Options hinzufügen/ändern, um zu sehen, ob wir dafür sorgen können, dass unsere Options besser mit unserer Variablen y_true korrelieren. Dieser Prozess wird als Characteristic Engineering bezeichnet.

In diesem Artikel werden wir den vorherigen Code verwenden, aber die zusätzlichen einfachen Funktionen hinzufügen. In unseren Rohdaten haben wir Daten für das AUDUSD-Preispaar und EURUSD-Preispaare zu Beginn jeder Stunde mit:
– Eröffnungspreis: Der Preis zu Beginn jedes 1-Stunden-Zeitraums
– Hoher Preis: Der höchste in dieser Stunde erzielte Preis
– Niedriger Preis: Der niedrigste in dieser Stunde erzielte Preis
– Schlusskurs: Der Preis am Ende des 1-Stunden-Zeitraums
– Volumen: Die Anzahl der Preisänderungen während dieses Zeitraums (nicht unbedingt die Anzahl der Trades, sondern ein Indikator dafür. Beachten Sie, dass dies normalerweise sehr Dealer-spezifisch ist)

Beispiel für ein Balkendiagramm

Diese „OHLC“-Werte (Open, Excessive, Low, Shut) werden normalerweise in Balken- oder Kerzenform dargestellt (Balkenform oben).

Derzeit umfassen unsere Funktionen
– Schlusskurs in der letzten Periode (was „jetzt“ wichtig ist)
– Schlusskurs vor 1 Periode (60 Min.).
– Schlusskurs vor 2 Perioden (120 Minuten).
– Schlusskurs vor 3 Perioden (180 Minuten).

Denken Sie daran, dass wir zu Beginn jeder Periode mit einer Vorhersage beginnen. Daher werden die aktuellen Perioden „eröffnet“ und die letzten Perioden „geschlossen“ gleich (oder sehr ähnlich) sein. Daher sind „öffnen“ und „schließen“ im Wesentlichen dasselbe und die Verwendung beider würde keinen Sinn ergeben. Wir können additionally Folgendes verwenden:

In den angegebenen Daten verfügbare Funktionen

Dies gibt uns 8 Options zu jedem Zeitraum oder 32 Options insgesamt. Wir müssen auch eine „Foundation“ einbeziehen, damit wir eine Grundlage haben, um „Punkte“ vom Rohpreis zu verschieben, wenn wir normalisieren (letzter Artikel)

Unsere Hypothese lautet additionally: Werden weitere Funktionen, und insbesondere die oben genannten Funktionen, unser Modell verbessern? Lass es uns herausfinden:

Vereinfachen wir zunächst die Ladedaten, um die Funktionsverarbeitung getrennt zu halten, da wir sie in den kommenden Wochen weiterentwickeln werden

import numpy as np
import pandas as pd
from datetime import datetime

def load_data():
url = 'https://uncooked.githubusercontent.com/the-ml-bull/Hello_World/most important/Fx60.csv'
dateparse = lambda x: datetime.strptime(x, '%d/%m/%Y %H:%M')

df = pd.read_csv(url, parse_dates=['date'], date_parser=dateparse)

return df

Erstellen Sie eine quick neue Funktion „create_x_values“, die die Foundation jedes Options erstellt (ohne Normalisierung). Anstelle fester Namen für jedes Characteristic durchlaufen wir jetzt jeden Namen und die Anzahl der zurückliegenden Perioden, um das neue Characteristic zu erstellen. In anderen Artikeln werden wir dies noch weiter vertiefen. Es gibt auch die Namen der Options zurück, sodass wir sie in anderen Teilen des Codes verwenden können. Durch die Bezeichnung „x“ können wir sie von unseren generischen Characteristic-Namen trennen.

def create_x_values(df, feature_names):

x_values_df = pd.DataFrame()

# loop thorugh characteristic title and "again durations" to return
x_feature_names = []
for characteristic in feature_names:
for interval in [1,2,3,4]:
# create the title (eg 'x_audusd_close_t-1')
feature_name = 'x_' + characteristic + '_t-' + str(interval)
x_feature_names.append(feature_name)
x_values_df[feature_name] = df[feature].shift(interval)

# Add "beginning" values when utilized in normalization
x_values_df['x_audusd_open'] = df['audusd_open'].shift(4)
x_values_df['x_eurusd_open'] = df['eurusd_open'].shift(4)
x_values_df['audusd_open'] = df['audusd_open']
x_values_df['eurusd_open'] = df['eurusd_open']

# add all future y values for future durations
for interval in [0,1,2,3]:
title = 'y_t-' + str(interval)
x_values_df[name] = df['audusd_close'].shift(-period)

# y is factors 4 durations into the longer term - the open worth now (not shut)
x_values_df['y_future'] = df['audusd_close'].shift(-3)
x_values_df['y_change_price'] = x_values_df['y_future'] - df['audusd_open']
x_values_df['y_change_points'] = x_values_df['y_change_price'] * 100000
x_values_df['y'] = np.the place(x_values_df['y_change_points'] >= 200, 1, 0)

# and reset df and finished
x_values_df = x_values_df.copy()
return x_values_df, x_feature_names

Unser Normalisierungsrouting hat sich am meisten verändert. Konzentriert sich nur auf die Funktionen, die wir benötigen (die x-Bezeichnung), und unterstützt verschiedene Startwerte für audusd, eurusd und volumes. In Punkten und Prozent skalieren wir das Volumen auch manuell, um es in „ungefähr“ die gleichen Maßstäbe zu bringen und die „_norm“-Felder zurückzugeben, damit wir sie an anderer Stelle verwenden können.

from sklearn.linear_model import LogisticRegression
from sklearn.preprocessing import MinMaxScaler, StandardScaler

def normalize_data(df, x_fields, methodology):

norm_df = df.copy()
y_fields = ['y_t-0', 'y_t-1', 'y_t-2', 'y_t-3']

if methodology == 'worth':
for subject in x_fields:
norm_df[field + '_norm'] = df[field]

for subject in y_fields:
norm_df[field + '_norm'] = df[field]

if methodology == 'factors':
for subject in x_fields:
if 'quantity' in subject:
norm_df[field + '_norm'] = df[field] / 100
elif 'audusd' in subject:
norm_df[field + '_norm'] = (df[field] - df['x_audusd_open']) * 100000
elif 'eurusd' in subject:
norm_df[field + '_norm'] = (df[field] - df['x_eurusd_open']) * 100000

for subject in y_fields:
norm_df[field + '_norm'] = (df[field] - df['audusd_open']) * 100000

if methodology == 'share':
for subject in x_fields:
if 'quantity' in subject:
norm_df[field + '_norm'] = df[field] / 10000
elif 'audusd' in subject:
norm_df[field + '_norm'] = (df[field] - df['x_audusd_open']) / df[field] * 100
elif 'eurusd' in subject:
norm_df[field + '_norm'] = (df[field] - df['x_eurusd_open']) / df[field] * 100

for subject in y_fields:
norm_df[field + '_norm'] = (df[field] - df['audusd_open']) / df[field] * 100

if methodology == 'minmax':
scaler = MinMaxScaler()
scaled = scaler.fit_transform(df[x_fields + y_fields])
norm_field_names = [x + '_norm' for x in x_fields + y_fields]
norm_df[norm_field_names] = scaled

if methodology == 'stddev':
scaler = StandardScaler()
scaled = scaler.fit_transform(df[x_fields + y_fields])
norm_field_names = [x + '_norm' for x in x_fields + y_fields]
norm_df[norm_field_names] = scaled

x_feature_names_norm = [x + '_norm' for x in x_fields]
return norm_df, x_feature_names_norm

Keine wirklichen Änderungen an unseren Zug-/Wert-, Klassengewichtungs- oder metrischen Funktionen. Lassen Sie uns es nun für jede Normalisierungsmethode ausführen und das Ergebnis mit der Single-Characteristic-Model der letzten Woche vergleichen

def get_train_val(df, x_feature_names_norm):
#
# Create Practice and Val datasets
#

x = df[x_feature_names_norm]
y = df['y']
y_points = df['y_change_points']

# Observe Fx "follows" (time collection) so randomization is NOT a good suggestion
# create practice and val datasets.
no_train_samples = int(len(x) * 0.7)
x_train = x[4:no_train_samples]
y_train = y[4:no_train_samples]

x_val = x[no_train_samples:-3]
y_val = y[no_train_samples:-3]
y_val_change_points = y_points[no_train_samples:-3]

return x_train, y_train, x_val, y_val, y_val_change_points

def get_class_weights(y_train, show=True):

#
# Create class weights
#
from sklearn.utils.class_weight import compute_class_weight

num_ones = np.sum(y_train)
num_zeros = len(y_train) - num_ones

courses = np.distinctive(y_train)
class_weights = compute_class_weight(class_weight='balanced', courses=courses, y=y_train)
class_weights = dict(zip(courses, class_weights))

if show:
print('Within the coaching set we now have 0s {} ({:.2f}%), 1s {} ({:.2f}%)'.format(num_zeros, num_zeros/len(y_train)*100, num_ones, num_ones/len(y_train)*100))
print('class weights {}'.format(class_weights))

return class_weights
from sklearn.metrics import log_loss, confusion_matrix, precision_score, recall_score, f1_score

def show_metrics(lr, x, y_true, y_change_points, show=True):

# predict from teh val set meas we now have predictions and true values as binaries
y_pred = lr.predict(x)

#fundamental error varieties
log_loss_error = log_loss(y_true, y_pred)
rating = lr.rating(x, y_true)

#
# Custom-made metrics
#
tp = np.the place((y_pred == 1) & (y_change_points >= 0), 1, 0).sum()
fp = np.the place((y_pred == 1) & (y_change_points < 0), 1, 0).sum()
tn = np.the place((y_pred == 0) & (y_change_points < 0), 1, 0).sum()
fn = np.the place((y_pred == 0) & (y_change_points >= 0), 1, 0).sum()

precision = 0
if (tp + fp) > 0:
precision = tp / (tp + fp)

recall = 0
if (tp + fn) > 0:
recall = tp / (tp + fn)

f1 = 0
if (precision + recall) > 0:
f1 = 2 * precision * recall / (precision + recall)

# output the errors
if show:
print('Errors Loss: {:.4f}'.format(log_loss_error))
print('Errors Rating: {:.2f}%'.format(rating*100))
print('Errors tp: {} ({:.2f}%)'.format(tp, tp/len(y_val)*100))
print('Errors fp: {} ({:.2f}%)'.format(fp, fp/len(y_val)*100))
print('Errors tn: {} ({:.2f}%)'.format(tn, tn/len(y_val)*100))
print('Errors fn: {} ({:.2f}%)'.format(fn, fn/len(y_val)*100))
print('Errors Precision: {:.2f}%'.format(precision*100))
print('Errors Recall: {:.2f}%'.format(recall*100))
print('Errors F1: {:.2f}'.format(f1*100))

errors = {
'loss': log_loss_error,
'rating': rating,
'tp': tp,
'fp': fp,
'tn': tn,
'fn': fn,
'precision': precision,
'recall': recall,
'f1': f1
}

return errors

Lassen Sie es additionally alle Normalisierungsmethoden durchlaufen.


for norm_method in ['price', 'points', 'percentage', 'minmax', 'stddev']:
df = load_data()

feature_names =['audusd_open', 'audusd_close', 'audusd_high', 'audusd_low', 'audusd_volume',
'eurusd_open', 'eurusd_close', 'eurusd_high', 'eurusd_low', 'eurusd_volume']
df, x_feature_names = create_x_values(df, feature_names)

norm_df, x_feature_names_norm = normalize_data(df, x_feature_names, methodology=norm_method)
x_train, y_train, x_val, y_val, y_val_change_points = get_train_val(norm_df, x_feature_names_norm)
class_weights = get_class_weights(y_train, show=False)

lr = LogisticRegression(class_weight=class_weights)
lr.match(x_train, y_train)

print('Errrors for methodology {}'.format(norm_method))
errors = show_metrics(lr, x_val, y_val, y_val_change_points, show=True)

Wir können sehen, dass es keine große Verbesserung gibt. Es gibt jedoch einige Dinge, die es zu beachten gilt.

  1. „konvergierte nicht“-Fehler. Punkte, Prozentsätze und Minmax-Normalisierung sind hier kursiv hervorgehoben. Das bedeutet, dass die Mathematik, die versucht, die beste „lineare“ Lösung zu finden, diese nicht minimieren konnte, bevor sie ihre Iterationsgrenze erreichte. Der Algorithmus ist auf die Verwendung von 100 Iterationen begrenzt, Sie können dies jedoch problemlos mit auf 1000 ändern max_iter=1000 durch Hinzufügen in der LogisticRegression-Zeile. Dadurch wird das Downside behoben, aber wir sollten es uns ansehen, ohne dies zu tun, da es einige interessante Punkte hinzufügt. Der Fehler „Kann nicht konvergieren“ wird wahrscheinlich durch einige Dinge verursacht.
    – Nicht genügend Daten verfügbar
    – Zu viele Funktionen (im Vergleich zu nicht genügend Daten)
    – Die Daten sind nicht mit den Merkmalen korreliert
    Wir erreichen durchweg Genauigkeiten > 50 %, aber nur knapp. Daher ist es wahrscheinlich (unsere Schlussfolgerung), dass es in der Vergangenheit etwas geben könnte, das die Zukunft vorhersagt, aber es ist sehr, sehr schwach oder nicht existent. Daher müssen wir all diese Dinge (Anzahl der Options, Datenmenge und Daten-/Characteristic-Korrelation) ausbalancieren, um das richtige Gleichgewicht zu erreichen.
  2. Die Punktemethode zeichnet sich im Vergleich zu anderen Methoden durch einen sehr hohen F1-Rating aus. Ist das eine Anomalie? Es stimmte nicht überein, additionally könnte es sich durchaus um einen zufälligen Zufall handeln, oder sind wir da auf etwas gestoßen? Der Trainingsverlust ist groß (erinnern Sie sich, dass in unserem ersten Artikel vorgeschlagen wurde, dass Verlust und Ertrag nicht unbedingt korrelieren?)

So wie es aussieht, könnte dies als Handelssystem nicht funktionieren, aber wir können einige Optimierungen vornehmen und es gibt einige Lücken in unserer Messung. Wir werden diese im nächsten Artikel untersuchen!



Source link

HINTERLASSEN SIE EINE ANTWORT

Please enter your comment!
Please enter your name here