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, weshalb hier der Schwerpunkt liegt.
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 werde ich einige der Fallstricke vorstellen, 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 Artikeln haben wir:
1. Das einfachste „Hallo Welt“-Beispiel erstellt. Wir haben Daten gesammelt, ein Modell erstellt und unser Ergebnis gemessen. Dies bildete unsere Grundlage, auf der wir aufbauen konnten.
2. Wir verwenden Klassengewichtungen, um unser Modell „in den Ballpark“ zu bringen und möglicherweise „etwas besser als zu raten“ und unsere Messung zu verbessern.
3. Im dritten Artikel haben wir unter dem Deckmantel der logistischen Regression nachgeschaut, um ihre Grenzen herauszufinden und herauszufinden, wohin wir als nächstes gehen könnten.
4. Wir haben über die Normalisierung und ihre Auswirkungen nachgedacht und festgestellt, dass unsere Hypothese möglicherweise unwahr ist.
5. Eine kurze Einführung in die Verwendung anderer oder neuer Funktionen und deren Auswirkungen.
6. Die Bewertungen dieses Artikels sind ein Messrahmen. Es ist unsere Vergleichsbasis und muss daher stimmen, damit wir Modell- und Funktionskombinationen vergleichen können. Wir untersuchen einige der Fallstricke, da dies kompliziert werden kann
Hierbei handelt es sich in keiner Weise um eine Finanzberatung, und es wird weder eine bestimmte Handelsstrategie befürwortet noch vorgeschlagen, dass eine worthwhile Strategie möglich ist, sondern es soll vielmehr dazu beitragen, einige Particulars des Devisenmarkts zu verstehen und ML-Techniken darauf anzuwenden.
Lassen Sie uns zunächst unser Modell zusammenfassen.
Unsere Hypothese ist, dass etwas in den OHLC-Daten der vorangegangenen vier Perioden eine plötzliche Veränderung von 200 Punkten oder mehr (zur Vereinfachung messen wir nur „oben“ oder „lang“) in den nächsten vier Perioden vorhersagt. Wir haben Klassengewichte, verschiedene Normalisierungstechniken und mehrere Funktionen hinzugefügt, alles ohne großen Erfolg.
Unsere aktuelle Messung verwendet das typische logistische Regressionsmodell (tp, fp, Präzision, Rückruf, f1 usw.) und wir haben später festgestellt, dass dies nicht geeignet ist (siehe vorherige Artikel).
Obwohl die Kennzahlen intestine sind, werden nur Bewegungen über 200 Punkte gemessen. Eine Bewegung über 0 stellt jedoch einen Gewinn dar. Wir prognostizieren (in unserem Coaching) eine Preisänderung um 200 Punkte, aber wenn der Preis um 100 steigt, ist es immer noch ein Gewinn. Deshalb haben wir in Artikel 2 (aufbauend auf „Whats up World“) unseren Messalgorithmus geändert, um dies zu berücksichtigen.
Allerdings spiegelt auch dies nicht wider, wie der Devisenhandel tatsächlich funktioniert.
Ich kenne professionelle Dealer seit vielen Jahren und quick alle verwenden Take-Revenue und Cease-Loss. Das heißt, sie handeln mit Gewinnaussicht und steigen aus, sobald sie diesen Gewinn erzielen. Sie haben auch einen maximalen Verlust, den sie akzeptieren und beenden, wenn dieser erreicht ist. Dies kann jederzeit passieren, selbst wenn ihre Bewegungshypothese vier Stunden im Voraus liegt, kann es in der ersten, zweiten oder dritten Periode passieren.
In der obigen Tabelle oben
Periode 0 – Der Preis steigt um mehr als 200 Punkte (ein Gewinn mit Blick auf einen Take-Revenue)
Punkt 1: Der Preis sinkt und erreicht ungefähr wieder den Ausgangswert
Periode 2 – Der Preis setzt seinen Abwärtstrend fort
Periode 3 – Preis ist < -100 Punkte (ein Verlust)
Nur sehr wenige Händler arbeiten ohne Cease-Loss, da dieser unser Konto durch den automatischen Handel vor massiven Rückgängen schützt. Daher müssen TP und SL bei unserer Messung unseres Erfolgs berücksichtigt werden.
Wir gehen davon aus (diese werden später zu Metaparametern):
– Wir steigen nach maximal 4 Perioden aus (unsere Prognose warfare 200 Punkte nach vier Perioden und steigen dann zu jedem Preis aus, das ist die Periode 0, 1, 2, 3)
– Ein Take-Revenue von 200 Punkten. Wenn der Preis zu irgendeinem Zeitpunkt um 200 Punkte steigt, steigen wir aus und nehmen den Gewinn mit
– Ein Cease-Loss von -150 Punkten. Wenn der Preis zu irgendeinem Zeitpunkt um 150 Punkte sinkt, akzeptieren wir keinen Verlust mehr (um uns davor zu schützen, dass der Preis noch weiter sinken könnte).
Jeder Händler hat seine eigene Methode zur Gewinn- und Verlustermittlung. Als allgemeine Regel gilt jedoch, dass das Aufwärtspotenzial höher sein muss als das Abwärtspotenzial, da Risiken bestehen. Es gibt Faustregeln bezüglich der „Aufwärts- und Abwärtsseite“, aber wir werden sie in einem zukünftigen Artikel behandeln.
Dies betrifft zwei Teile unseres Algorithmus, die Messung UND die Bestimmung von y_true, und wir müssen beide aktualisieren
Dies ist unsere größte Änderung in diesem Artikel. Wir aktualisieren die Artwork und Weise, wie wir y messen, um einen Cease-Loss und Take-Revenue einzubeziehen. Dann müssen wir unseren Code so ändern, dass er jede Terminperiode durchläuft und dabei im „y-Bereich“ (4 Perioden in unserer aktuellen Hypothese) nach einem Cease-Loss oder einer Gewinnmitnahme sucht. Beachten Sie, dass es möglich ist, dass ein Zeitraum sowohl einen Cease-Loss als auch einen Take-Revenue anzeigt. Daher gehen wir vom Cease-Loss aus (im schlimmsten Fall für den Gewinn). Außerdem kennen wir jetzt die „Punkte“, die tatsächlich bewegt werden, begrenzt durch den Cease-Loss, Take-Revenue oder was am Ende erreicht wurde, was es uns ermöglicht, eine Gewinnmetrik hinzuzufügen.
Beachten Sie, dass:
– Wir haben „Datum“ zu unseren Funktionen hinzugefügt. Wir werden dies (noch) nicht als Funktion verwenden, sondern für die nachstehende Darstellung.
– Bei der Betrachtung des „Take Revenue“ vergleichen wir erneut den „hohen“ Preis in der Periode.
– Wir berücksichtigen den „Cease-Loss“, den wir mit dem „niedrigen“ Preis in diesem Zeitraum vergleichen
– Wir haben jetzt die „Punkte“ verschoben (um einen Gewinn zu bestimmen) und den Ausstiegszeitraum (0, 1, 2 oder 3 – für die Diagrammerstellung).
# create new holder for all values
x_values_df = pd.DataFrame()#
# X values look again
#
# loop thorugh function identify and "again intervals" to return
x_feature_names = []
for function in feature_names:
for interval in [1,2,3,4]:
# create the identify (eg 'x_audusd_close_t-1')
feature_name = 'x_' + function + '_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 and date for reference
x_values_df['date'] = df['date']
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']
#
# Y values look ahead
#
# add all future y values for future intervals
# set y=1 for >= 200 factors
# set y=-1 for <= -150 (for later use)
# set y=0 for all else
# set y_points to precise factors completed with (for later calculation of revenue)
x_values_df['y'] = -2 # begin wtih -2 to seek for not -1,0,1
x_values_df['y_points'] = 0 # begin with no level motion
for interval in [0,1,2,3]: # loop thorugh every ahead interval in y (4 ahead intervals)
# names to retailer future worth and alter factors
identify = 'y_t-' + str(interval) #identify of future y worth
price_name = 'y_change_price_' + str(interval) # identify of future y factors change
points_name = 'y_change_points_' + str(interval) # identify of future y factors change
# add vital future values to spreadsheet
x_values_df[name] = df['audusd_close'].shift(-period)
x_values_df[name + '_low'] = df['audusd_low'].shift(-period)
x_values_df[name + '_high'] = df['audusd_high'].shift(-period)
# calculate change in factors
x_values_df[price_name] = x_values_df[name] - df['audusd_open']
x_values_df[points_name] = x_values_df[price_name] * 100000
x_values_df[price_name + '_low'] = x_values_df[name + '_low'] - df['audusd_open']
x_values_df[points_name + '_low'] = x_values_df[price_name + '_low'] * 100000
x_values_df[price_name + '_high'] = x_values_df[name + '_high'] - df['audusd_open']
x_values_df[points_name + '_high'] = x_values_df[price_name + '_high'] * 100000
# get and calculate all "down" values the place y isnt already set
# down "down" first, for case the place bar goes each up and down in the identical interval assume down first (worst case for revenue)
down_df = x_values_df[(x_values_df['y'] == -2) & (x_values_df[points_name + '_low'] <= -150)]
x_values_df.loc[down_df.index, 'y'] = -1
x_values_df.loc[down_df.index, 'y_points'] = -150
x_values_df.loc[down_df.index, 'y_finish_period'] = interval
# get and calculate all "up" vales the place y isnt already set
up_df = x_values_df[(x_values_df['y'] == -2) & (x_values_df[points_name + '_high'] >= 200)]
x_values_df.loc[up_df.index, 'y'] = 1
x_values_df.loc[up_df.index, 'y_points'] = 200
x_values_df.loc[up_df.index, 'y_finish_period'] = interval
# if no interval triggered tp/sl then no motion (y=0) and factors are no matter it's on the finish of the interval
none_df = x_values_df[x_values_df['y'] == -2]
x_values_df.loc[none_df.index, 'y'] = 0
x_values_df.loc[none_df.index, 'y_points'] = x_values_df[points_name]
x_values_df.loc[none_df.index, 'y_finish_period'] = 3
# set down (at present -1) to 0 since we arent utilizing it (but - we are going to later)
x_values_df.loc[x_values_df[x_values_df['y'] == -1].index, 'y'] = 0
# if factors exceeds tp or sl then reset to sl/tp since these limits are fastened in buying and selling
x_values_df.loc[x_values_df['y_points'] < -150, 'y_points'] = -150
x_values_df.loc[x_values_df['y_points'] > 200, 'y_points'] = 200
# and reset df (avoids indexing problems later) and performed
x_values_df = x_values_df.copy()
return x_values_df, x_feature_names
Unsere y-Variable wird oben aktualisiert und durchläuft den bereits vorhandenen Code, sodass der Messalgorithmus unverändert funktioniert. Da wir jedoch jetzt über eine „Punkte“-Metrik verfügen, können wir unseren effektiven Gewinn überprüfen. Dies ist eine ziemlich einfache Änderung unseres Messalgorithmus.
Ein paar Anmerkungen:
- Wir haben unsere Division mit der Numpy-Division-Funktion vereinfacht, die automatisch die Division durch 0 übernimmt.
- Wir haben eine „Gewinn“-Metrik hinzugefügt, die die kumulierten Punkte darstellt. Denken Sie daran, dass wir einen Take-Revenue von 200 Punkten und einen Cease-Loss von 150 Punkten haben. (Unser potenzieller Gewinn ist additionally höher als unser potenzieller Verlust).
def divide(a, b):a = np.asarray(a).astype(float)
b = np.asarray(b).astype(float)
end result = np.divide(a, b, out=np.zeros_like(a), the place=b != 0)
return end result
def show_metrics(lr, x, y_true, y_points, show=True):
x = x.to_numpy()
y_true = y_true.to_numpy()
y_points = y_points.to_numpy()
# predict from teh val set meas we've 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)
#
# Personalized metrics to confusion matrix
#
tp = np.the place((y_pred == 1) & (y_points >= 0), 1, 0).sum()
fp = np.the place((y_pred == 1) & (y_points < 0), 1, 0).sum()
tn = np.the place((y_pred == 0) & (y_points < 0), 1, 0).sum()
fn = np.the place((y_pred == 0) & (y_points >= 0), 1, 0).sum()
# derived from confusion matrix
precision = float(divide(tp, (tp+fp)))
recall = float(divide(tp, (tp + fn)))
f1 = float(divide((precision*recall), (precision + recall)))
# revenue calculation (if predicted use factors, in any other case 0)
revenue = np.the place(y_pred==1, y_points, 0).sum()
# 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))
print('revenue: {:.2f} factors'.format(revenue))
errors = {
'loss': log_loss_error,
'rating': rating,
'tp': tp,
'fp': fp,
'tn': tn,
'fn': fn,
'precision': precision,
'recall': recall,
'f1': f1,
'revenue': revenue
}
return errors
Fügen wir zunächst etwas Code hinzu, um den „Gewinn“ zu berechnen (eigentlich eine Commerce-für-Commerce-Berechnung) und dann jeden Commerce grafisch darzustellen, damit wir visualisieren können, was vor sich geht, und überprüfen können, ob es sinnvoll ist.
def get_trades(lr, x, y_change_points):y_pred = lr.predict(x)
trades = []
for ix in vary(len(y_pred)):
if y_pred[ix] == 1:
gained = False
revenue = y_change_points.iloc[ix]
if revenue > 0:
gained = True
trades.append([ix, won, profit])
trades_df = pd.DataFrame(trades, columns=['ix', 'won', 'profit'])
return trades_df
def chart_trades(trades, features_df, raw_df, number_of_charts):
rows = math.ceil(number_of_charts / 3)
fig, axes = plt.subplots(nrows=rows, ncols=3)
for chart_ix in vary(number_of_charts):
# get indexes and ate for chart
trade_ix = random.randint(0, len(trades))
data_ix = trades['ix'].iloc[trade_ix] + 35373
date = features_df['date'].iloc[data_ix]
# prepard information for OHLC candles
mpf_df = pd.DataFrame()
mpf_df[['date', 'Open', 'High', 'Low', 'Close', 'Volume']] =
raw_df[['date', 'audusd_open', 'audusd_high', 'audusd_low', 'audusd_close', 'audusd_volume']]
.iloc[data_ix-4:data_ix+4].to_numpy()
mpf_df['date'] = pd.to_datetime(mpf_df['date'])
mpf_df = mpf_df.set_index('date')
mpf_df = mpf_df[['Open', 'High', 'Low', 'Close', 'Volume']].astype(float)
# plot the chart
ax = axes.flatten()[chart_ix]
mpf.plot(mpf_df, ax=ax, quantity=False, datetime_format='', sort='candle')
# add title
ax.set_title(date.strftime('%d-%m-%Y %H:%M'))
# add marker to seperate historical past and future
ax.plot([4, 4], [mpf_df['High'].iloc[4], ax.get_ylim()[1]], shade='y', marker='o', linewidth=3.0)
ax.plot([4, 4], [ax.get_ylim()[0], mpf_df['Low'].iloc[4]], shade='y', marker='o', linewidth=3.0)
# horizontal traces indicator tp and sl
open_price = mpf_df['Open'].iloc[4]
ax.axhline(open_price+(200/100000), shade='inexperienced', linewidth=1.5)
ax.axhline(open_price-(150/100000), shade='purple', linewidth=1.5)
# plot line from begin to shut place
close_period = int(features_df['y_finish_period'].iloc[data_ix])
revenue = features_df['y_points'].iloc[data_ix]
close_price = open_price + (revenue / 100000)
ax.plot([4,4+close_period], [mpf_df['Open'].iloc[4], close_price], shade='r', marker='x', linewidth=1.5)
# textual content field indicating end result
txt = 'Win: {}nProfit: {:.2f}nExit: {}'.format(trades.iloc[trade_ix]['won'], trades.iloc[trade_ix]['profit'], close_period)
props = dict(boxstyle='spherical', facecolor='wheat', alpha=0.5)
ax.textual content(0.05, 0.75, txt, model='italic', rework=ax.transAxes, dimension=10, bbox=props)
plt.present()
return
Diese Artwork der Visualisierungsübung ist sehr nützlich und ich empfehle sie immer. Es wird Ihnen helfen, zu „erkennen“, was vor sich geht, Ihre Diagramme sorgfältig zu überprüfen und, wenn etwas keinen Sinn ergibt, noch einmal nachzuschauen und herauszufinden, warum, es ist wahrscheinlich, dass Sie irgendwo Fehler gemacht haben.
Jetzt können wir unseren Testalgorithmus ausführen und die Metriken aktualisieren.
#
# Fundamental loop to check completely different normalization methods
#
for norm_method in ['price', 'points', 'percentage', 'minmax', 'stddev']:# load uncooked information
raw_df = load_data()
# create options
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_xy_values(raw_df, feature_names)
# put together information for studying (normalize, cut up and sophistication weights)
norm_df, x_feature_names_norm = normalize_data(df, x_feature_names, technique=norm_method)
x_train, y_train, x_val, y_val, y_val_change_points, no_train_samples = get_train_val(norm_df, x_feature_names_norm)
class_weights = get_class_weights(y_train, show=False)
# practice the mannequin
lr = LogisticRegression(class_weight=class_weights, max_iter=1000)
lr.match(x_train, y_train)
# if we wish to see all precise trades and chart them
trades_df = get_trades(lr, x_val, y_val_change_points)
print('trades {}, wins {}, revenue {:.2f}'.format(len(trades_df), trades_df['won'].sum(), trades_df['profit'].sum()))
# if we wish to chart trades
chart_trades(trades_df, df, raw_df, number_of_charts=9)
# to indicate customary errors
print('Errrors for technique {}'.format(norm_method))
errors = show_metrics(lr, x_val, y_val, y_val_change_points, show=True)
Wir können sofort erkennen, dass sich die Modellleistung verschlechtert hat und die Handelsverluste erheblich wären. Dies wirft einige Fragen auf:
1. Warum hat sich die Modellleistung verschlechtert?
Die Genauigkeiten sind nicht ganz niedrig (ca. 43 %), was deutlich unter den Schätzungen liegt. Wir haben zwei Dinge getan. Durch die Berücksichtigung eines TP/SL wurde ein weiterer Schritt hinzugefügt, der das Ergebnis Y und das, was tatsächlich passiert, entkoppelt. Wir haben auch eine große Anzahl von Funktionen, mit denen die logistische Regression nur schwer umgehen kann.
2. Warum sind die Handelsverluste so groß?
Dies lässt sich leichter erkennen, wenn man berechnet, wie viele Balken nach oben bzw. nach unten gehen.
def check_simultaneous_sl_tp(trades_df, features_df, raw_df):trade_ixs = trades_df['ix'].tolist()
outcomes = []
for ix in vary(35373, len(features_df)):
low = raw_df['audusd_low'].iloc[ix]
excessive = raw_df['audusd_high'].iloc[ix]
open = raw_df['audusd_open'].iloc[ix]
points_high = spherical((excessive - open) * 100000, 2)
points_low = spherical((low - open) * 100000, 2)
gone_high, gone_low, during_trade = False, False, False
if points_high > 200:
gone_high = True
if points_low < -150:
gone_low = True
if (ix-35373) in trade_ixs:
during_trade = True
outcomes.append([ix, gone_high, gone_low, during_trade])
results_df = pd.DataFrame(outcomes, columns=['ix', 'high', 'low', 'trade'])
print('Whole Intervals {}'.format(len(results_df)))
print('Highs {}'.format(len(results_df[results_df['high'] == True])))
print('Lows {}'.format(len(results_df[results_df['low'] == True])))
print('Trades {}'.format(len(results_df[results_df['trade'] == True])))
print('Not In Commerce Highs AND Lows collectively {}'.format(len(results_df[(results_df['high'] == True) & (results_df['low'] == True)])))
print('In Commerce Highs AND Lows collectively {}'.format(len(results_df[(results_df['high'] == True) & (results_df['low'] == True) & (results_df['trade'] == True)])))
return results_df
Gesamtperioden 15161
Höchstwerte 787
Tiefststände 1743
Trades 5087
Nicht im Handelshochs UND -tiefs zusammen 43
Im Handel Hochs UND Tiefs zusammen 29
Wir können sehen, dass es viel mehr Abwärtsbewegungen als Aufwärtsbewegungen gibt und 29 Trades im gleichen Zeitraum > TP und < SL haben (wir gehen von SL aus, wissen aber eigentlich nicht, in welche Richtung es ging).
Das Debuggen wird etwas komplizierter, daher bin ich tatsächlich auf VS Code umgestiegen, was meiner Meinung nach einfachere Debugfunktionen für Colab bietet. Ich empfehle Ihnen, es sich anzusehen, aber ich werde weiterhin Updates in Colab veröffentlichen.
Es ist äußerst wichtig, dass Sie Ihre Berechnungen immer wieder überprüfen. Denken Sie daran: Müll rein – Müll raus! Prüfen, doppelt prüfen und dreifach prüfen. Wenn Sie Ihre Eingabedaten falsch eingeben (x oder y), wird es nie funktionieren, oder Sie werden feststellen, dass es im Coaching, aber nicht in der Praxis funktioniert, und zu Handelsverlusten führt.
Die Aktualisierung der Messung um Cease-Loss und Take-Earnings hat unser Modell verschlechtert, aber wir müssen es integrieren, da es ein wichtiger Bestandteil des Devisenhandels ist.
Wenn wir das Modell „wie es ist“ verwenden würden, wäre unser Handelsverlust erheblich.
Es gibt einige Richtungen, in die wir gehen können. Wir können an der Optimierung unserer Metaparameter arbeiten (wie Cease-Loss, Take-Revenue, Normalisierungsmethode usw.) oder wir können an unserer Präzision arbeiten, was schlechter ist als Raten. Dies ist die Richtung, die wir einschlagen werden (beachten Sie, dass ich über die Metrik spreche, die wir verbessern möchten, und nicht über eine bestimmte Technik, um dies zu erreichen).
Im nächsten Artikel werden wir uns auf die Verbesserung unserer Präzision mit neuen abgeleiteten Funktionen und weiterer Function-Entwicklung konzentrieren.