0% found this document useful (0 votes)
128 views9 pages

Lorentzian Classification Strategy - Teamviewer

This document outlines a trading strategy called 'RoyalPrince Strategy' that utilizes Lorentzian classification for machine learning predictions in financial markets. It includes user-defined settings for various features, filters, and display options, as well as helper functions for calculating distances and feature series. The strategy aims to predict price movements over the next four bars using historical data and machine learning techniques.

Uploaded by

Paulo Gaspar
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
128 views9 pages

Lorentzian Classification Strategy - Teamviewer

This document outlines a trading strategy called 'RoyalPrince Strategy' that utilizes Lorentzian classification for machine learning predictions in financial markets. It includes user-defined settings for various features, filters, and display options, as well as helper functions for calculating distances and feature series. The strategy aims to predict price movements over the next four bars using historical data and machine learning techniques.

Uploaded by

Paulo Gaspar
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
You are on page 1/ 9

// This source code is subject to the terms of the Mozilla Public License 2.

0 at
https://mozilla.org/MPL/2.0/
// ©rp4000

// @version=5
strategy('RoyalPrince Strategy using Lorentzian Classification by RoyalPrince',
'Lorentzian Classification', true, precision=4,
max_labels_count=500,currency=currency.USD, initial_capital=50,
commission_value=0.00, default_qty_type = strategy.percent_of_equity,
default_qty_value = 100)

import jdehorty/MLExtensions/1 as knn


import jdehorty/KernelFunctions/2 as kernels

type Settings
float source
int neighborsCount
int maxBarsBack
int featureCount
int colorCompression
bool showExits
bool useDynamicExits

type Label
int long
int short
int neutral

type FeatureArrays
array<float> f1
array<float> f2
array<float> f3
array<float> f4
array<float> f5

type FeatureSeries
float f1
float f2
float f3
float f4
float f5

type MLModel
int firstBarIndex
array<int> trainingLabels
int loopSize
float lastDistance
array<float> distancesArray
array<int> predictionsArray
int prediction

type FilterSettings
bool useVolatilityFilter
bool useRegimeFilter
bool useConfidenceFilter
bool useAdxFilter
float regimeThreshold
int adxThreshold
type Filter
bool volatility
bool regime
bool adx

// ==========================
// ==== Helper Functions ====
// ==========================

series_from(feature_string, _close, _high, _low, _hlc3, f_paramA, f_paramB) =>


switch feature_string
"RSI" => knn.n_rsi(_close, f_paramA, f_paramB)
"WT" => knn.n_wt(_hlc3, f_paramA, f_paramB)
"CCI" => knn.n_cci(_close, f_paramA, f_paramB)
"ADX" => knn.n_adx(_high, _low, _close, f_paramA)

get_lorentzian_distance(int i, int featureCount, FeatureSeries featureSeries,


FeatureArrays featureArrays) =>
switch featureCount
5 => math.log(1+math.abs(featureSeries.f1 - array.get(featureArrays.f1,
i))) +
math.log(1+math.abs(featureSeries.f2 - array.get(featureArrays.f2,
i))) +
math.log(1+math.abs(featureSeries.f3 - array.get(featureArrays.f3,
i))) +
math.log(1+math.abs(featureSeries.f4 - array.get(featureArrays.f4,
i))) +
math.log(1+math.abs(featureSeries.f5 - array.get(featureArrays.f5,
i)))
4 => math.log(1+math.abs(featureSeries.f1 - array.get(featureArrays.f1,
i))) +
math.log(1+math.abs(featureSeries.f2 - array.get(featureArrays.f2,
i))) +
math.log(1+math.abs(featureSeries.f3 - array.get(featureArrays.f3,
i))) +
math.log(1+math.abs(featureSeries.f4 - array.get(featureArrays.f4,
i)))
3 => math.log(1+math.abs(featureSeries.f1 - array.get(featureArrays.f1,
i))) +
math.log(1+math.abs(featureSeries.f2 - array.get(featureArrays.f2,
i))) +
math.log(1+math.abs(featureSeries.f3 - array.get(featureArrays.f3,
i)))
2 => math.log(1+math.abs(featureSeries.f1 - array.get(featureArrays.f1,
i))) +
math.log(1+math.abs(featureSeries.f2 - array.get(featureArrays.f2,
i)))

Settings settings =
Settings.new(
input.source(title='Source', defval=hlc3, group="General Settings",
tooltip="Source of the input data"),
input.int(title='Neighbors Count', defval=8, group="General Settings", minval=1,
maxval=100, step=1, tooltip="Number of neighbors to consider"),
input.int(title="Max Bars Back", defval=2000, group="General Settings"),
input.int(title="Feature Count", defval=5, group="Feature Engineering",
minval=2, maxval=5, tooltip="Number of features to use for ML predictions."),
input.int(title="Color Compression", defval=1, group="General Settings",
minval=1, maxval=10, tooltip="Compression factor for adjusting the intensity of the
color scale."),
input.bool(title="Show Exits", defval=false, group="General Settings",
tooltip="Show the exit threshold on the chart.", inline="exits"),
input.bool(title="Use Dynamic Exits", defval=false, group="General Settings",
tooltip="Attempts to let profits ride by dynamically adjusting the exit threshold
based on kernel regression.", inline="exits")
)

// Feature Variables: User-Defined Inputs for calculating Feature Series.


// Note: Due to type coercian related limitations during feature calculation, these
settings are not house within a custom type.
f1_string = input.string(title="Feature 1", options=["RSI", "WT", "CCI", "ADX"],
defval="RSI", inline = "01", tooltip="The first feature to use for ML
predictions.", group="Feature Engineering")
f1_paramA = input.int(title="Parameter A", tooltip="The primary parameter of
feature 1.", defval=14, inline = "02", group="Feature Engineering")
f1_paramB = input.int(title="Parameter B", tooltip="The secondary parameter of
feature 2 (if applicable).", defval=1, inline = "02", group="Feature Engineering")
f2_string = input.string(title="Feature 2", options=["RSI", "WT", "CCI", "ADX"],
defval="WT", inline = "03", tooltip="The second feature to use for ML
predictions.", group="Feature Engineering")
f2_paramA = input.int(title="Parameter A", tooltip="The primary parameter of
feature 2.", defval=10, inline = "04", group="Feature Engineering")
f2_paramB = input.int(title="Parameter B", tooltip="The secondary parameter of
feature 2 (if applicable).", defval=11, inline = "04", group="Feature Engineering")
f3_string = input.string(title="Feature 3", options=["RSI", "WT", "CCI", "ADX"],
defval="CCI", inline = "05", tooltip="The third feature to use for ML
predictions.", group="Feature Engineering")
f3_paramA = input.int(title="Parameter A", tooltip="The primary parameter of
feature 3.", defval=20, inline = "06", group="Feature Engineering")
f3_paramB = input.int(title="Parameter B", tooltip="The secondary parameter of
feature 3 (if applicable).", defval=1, inline = "06", group="Feature Engineering")
f4_string = input.string(title="Feature 4", options=["RSI", "WT", "CCI", "ADX"],
defval="ADX", inline = "07", tooltip="The fourth feature to use for ML
predictions.", group="Feature Engineering")
f4_paramA = input.int(title="Parameter A", tooltip="The primary parameter of
feature 4.", defval=20, inline = "08", group="Feature Engineering")
f4_paramB = input.int(title="Parameter B", tooltip="The secondary parameter of
feature 4 (if applicable).", defval=2, inline = "08", group="Feature Engineering")
f5_string = input.string(title="Feature 5", options=["RSI", "WT", "CCI", "ADX"],
defval="RSI", inline = "09", tooltip="The fifth feature to use for ML
predictions.", group="Feature Engineering")
f5_paramA = input.int(title="Parameter A", tooltip="The primary parameter of
feature 5.", defval=9, inline = "10", group="Feature Engineering")
f5_paramB = input.int(title="Parameter B", tooltip="The secondary parameter of
feature 5 (if applicable).", defval=1, inline = "10", group="Feature Engineering")

// FeatureSeries Object: Calculated Feature Series based on Feature Variables


featureSeries =
FeatureSeries.new(
series_from(f1_string, close, high, low, hlc3, f1_paramA, f1_paramB), // f1
series_from(f2_string, close, high, low, hlc3, f2_paramA, f2_paramB), // f2
series_from(f3_string, close, high, low, hlc3, f3_paramA, f3_paramB), // f3
series_from(f4_string, close, high, low, hlc3, f4_paramA, f4_paramB), // f4
series_from(f5_string, close, high, low, hlc3, f5_paramA, f5_paramB) // f5
)

// FeatureArrays Variables: Storage of Feature Series as Feature Arrays Optimized


for ML
// Note: These arrays cannot be dynamically created within the FeatureArrays Object
Initialization and thus must be set-up in advance.
var f1Array = array.new_float()
var f2Array = array.new_float()
var f3Array = array.new_float()
var f4Array = array.new_float()
var f5Array = array.new_float()
array.push(f1Array, featureSeries.f1)
array.push(f2Array, featureSeries.f2)
array.push(f3Array, featureSeries.f3)
array.push(f4Array, featureSeries.f4)
array.push(f5Array, featureSeries.f5)

// FeatureArrays Object: Storage of the calculated FeatureArrays into a single


object
featureArrays =
FeatureArrays.new(
f1Array, // f1
f2Array, // f2
f3Array, // f3
f4Array, // f4
f5Array // f5
)

// Label Object: Used for classifying historical data as training data for the ML
Model
Label direction =
Label.new(
long=1,
short=-1,
neutral=0
)

// Settings object for user-defined settings


FilterSettings filterSettings =
FilterSettings.new(
input.bool(title="Use Volatility Filter", defval=true, tooltip="Whether to use
the volatility filter.", group="Filters"),
input.bool(title="Use Regime Filter", defval=true, tooltip="Whether to use the
trend detection filter.", group="Filters"),
input.bool(title="Use Confidence Filter", defval=false, tooltip="Only show
confident signals.", group="Filters"),
input.bool(title="Use ADX Filter", defval=false, tooltip="Whether to use the ADX
filter.", group="Filters"),
input.float(title="Regime Threshold", defval=-0.1, minval=-10, maxval=10,
step=0.1, tooltip="Threshold for detecting Trending/Ranging markets.",
group="Filters"),
input.int(title="ADX Threshold", defval=20, minval=0, maxval=100, step=1,
tooltip="Threshold for detecting Trending/Ranging markets.", group="Filters")
)

// Filter object for filtering the ML predictions


Filter filter =
Filter.new(
knn.filter_volatility(1, 10, filterSettings.useVolatilityFilter),
knn.regime_filter(ohlc4, filterSettings.regimeThreshold,
filterSettings.useRegimeFilter),
knn.filter_adx(settings.source, 14, filterSettings.adxThreshold,
filterSettings.useAdxFilter)
)

// Derived from General Settings


maxBarsBackIndex = last_bar_index >= settings.maxBarsBack ? last_bar_index -
settings.maxBarsBack : 0

// Nadaraya-Watson Kernel Regression Settings


useKernelTrend = input.bool(true, "Trade with Kernel", group="Filters",
inline="kernel")
showKernelEstimate = input.bool(true, "Show Kernel Estimate", group="Filters",
inline="kernel")
h = input.int(8, 'Lookback Window', minval=3, tooltip='The number of bars used for
the estimation. This is a sliding value that represents the most recent historical
bars. Recommended range: 3-50', group="Filters", inline="kernel")
r = input.float(8., 'Relative Weighting', step=0.25, tooltip='Relative weighting of
time frames. As this value approaches zero, the longer time frames will exert more
influence on the estimation. As this value approaches infinity, the behavior of the
Rational Quadratic Kernel will become identical to the Gaussian kernel. Recommended
range: 0.25-25', group="Filters", inline="kernel")
x = input.int(25, "Start Regression at Bar", tooltip='Bar index on which to start
regression. The first bars of a chart are often highly volatile, and omission of
these initial bars often leads to a better overall fit. Recommended range: 5-25',
group="Filters", inline="kernel")

// Display Settings
showBarColors = input.bool(true, "Show Bar Colors", tooltip="Whether to show the
bar colors.", group="Display Settings")
showBarPredictions = input.bool(defval = true, title = "Show Bar Prediction
Values", tooltip = "Will show the ML model's evaluation of each bar as an
integer.", group="Display Settings")
useAtrOffset = input.bool(defval = false, title = "Use ATR Offset", tooltip = "Will
use the ATR offset instead of the bar prediction offset.", group="Display
Settings")
barPredictionsOffset = input.float(0, "Bar Prediction Offset", minval=0,
tooltip="The offset of the bar predictions as a percentage from the bar high or
close.", group="Display Settings")

// Backtest Settings
show_info = input.bool(true, 'Show Backtest Results', tooltip='Displays the win
rate of the given configuration.', group='Backtesting')

// =================================
// ==== Next Bar Classification ====
// =================================

// This model specializes specifically in predicting the direction of price action


over the course of the next 4 bars
y_train_series = close[4] < close[0] ? direction.short : close[4] > close[0] ?
direction.long : direction.neutral
var y_train_array = array.new_int(0)

// Variables used for ML Logic


var predictions = array.new_float(0)
var prediction = 0.
var signal = direction.neutral
var distances = array.new_float(0)

array.push(y_train_array, y_train_series)
lastDistance = -1.0
size = math.min(settings.maxBarsBack-1, array.size(y_train_array)-1)
sizeLoop = math.min(settings.maxBarsBack-1, size)

if bar_index >= maxBarsBackIndex //{


for i = 0 to sizeLoop //{
d = get_lorentzian_distance(i, settings.featureCount, featureSeries,
featureArrays)
if d >= lastDistance and i%4
lastDistance := d
array.push(distances, d)
array.push(predictions, math.round(array.get(y_train_array, i)))
if array.size(predictions) > settings.neighborsCount //{
lastDistance := array.get(distances,
math.round(settings.neighborsCount*3/4))
array.shift(distances)
array.shift(predictions)
//}
//}
//}
prediction := array.sum(predictions)
//}

// User Defined Filters: Used for adjusting the frequency of the ML Model's
predictions
filter_all = filter.volatility and filter.regime and filter.adx

// Filtered Signal: The model's prediction of future price movement direction with
user-defined filters applied
signal := prediction > 0 and filter_all ? direction.long : prediction < 0 and
filter_all ? direction.short : nz(signal[1])

// Bar-Count Filters: Represents strict filters based on a pre-defined holding


period of 4 bars
var int barsHeld = 0
barsHeld := ta.change(signal) ? 0 : barsHeld + 1
isHeldFourBars = barsHeld == 4
isHeldLessThanFourBars = 0 < barsHeld and barsHeld < 4

// Fractal Filters: Derived from relative appearances of signals in a given time


series fractal/segment with a default length of 4 bars
isDifferentSignalType = ta.change(signal)
isStopLossHit = ta.change(signal) and (ta.change(signal[1]) or ta.change(signal[2])
or ta.change(signal[3]))
isBuySignal = signal == direction.long
isSellSignal = signal == direction.short
isLastSignalBuy = signal[4] == direction.long
isLastSignalSell = signal[4] == direction.short
isNewBuySignal = isBuySignal and isDifferentSignalType
isNewSellSignal = isSellSignal and isDifferentSignalType

// Kernel Regression Filters: Filters based on Nadaraya-Watson Kernel Regression


using the Rational Quadratic Kernel
c_green = color.new(#009988, 20)
c_red = color.new(#CC3311, 20)
yhat = kernels.rationalQuadratic(settings.source, h, r, x)
kernelEstimate = useKernelTrend ? yhat : na
isBearish = useKernelTrend ? yhat[1] > yhat : true
isBullish = useKernelTrend ? yhat[1] < yhat : true
wasBearish = useKernelTrend ? yhat[2] > yhat[1] : true
wasBullish = useKernelTrend ? yhat[2] < yhat[1] : true
isBearishChange = useKernelTrend ? isBearish and wasBullish : true
isBullishChange = useKernelTrend ? isBullish and wasBearish : true
plot(kernelEstimate, color=isBullish ? color.new(#009988, 20) : color.new(#CC3311,
20), linewidth=2)

// ===========================
// ==== Entries and Exits ====
// ===========================

// Entry Conditions: Booleans for ML Model Position Entries


startLongTrade = isNewBuySignal and isBullish
startShortTrade = isNewSellSignal and isBearish

// Dynamic Exit Conditions: Booleans for ML Model Position Exits based on Fractal
Filters and Kernel Regression Filters
lastSignalWasBullish = ta.barssince(startLongTrade) < ta.barssince(startShortTrade)
lastSignalWasBearish = ta.barssince(startShortTrade) < ta.barssince(startLongTrade)
barsSinceRedEntry = ta.barssince(startShortTrade)
barsSinceRedExit = ta.barssince(isBullishChange)
barsSinceGreenEntry = ta.barssince(startLongTrade)
barsSinceGreenExit = ta.barssince(isBearishChange)
isValidShortExit = barsSinceRedExit > barsSinceRedEntry
isValidLongExit = barsSinceGreenExit > barsSinceGreenEntry
endLongTradeDynamic = (isBearishChange and isValidLongExit[1])
endShortTradeDynamic = (isBullishChange and isValidShortExit[1])

// Fixed Exit Conditions: Booleans for ML Model Position Exits based on a Bar-Count
Filters
endLongTradeStrict = ((isHeldFourBars and isLastSignalBuy) or
(isHeldLessThanFourBars and isNewSellSignal)) and (barsSinceGreenEntry <
barsSinceRedEntry)
endShortTradeStrict = ((isHeldFourBars and isLastSignalSell) or
(isHeldLessThanFourBars and isNewBuySignal)) and (barsSinceGreenEntry >
barsSinceRedEntry)

endLongTrade = settings.useDynamicExits ? endLongTradeDynamic : endLongTradeStrict


endShortTrade = settings.useDynamicExits ? endShortTradeDynamic :
endShortTradeStrict

// =========================
// ==== Plotting Labels ====
// =========================

// Note: These will not repaint once the most recent bar has fully closed. By
default, signals appear over the last closed bar; to override this behavior set
offset=0.
plotshape(startLongTrade ? low : na, 'Buy', shape.labelup, location.belowbar,
color=knn.color_green(prediction), size=size.small, offset=0)
plotshape(startShortTrade ? high : na, 'Sell', shape.labeldown, location.abovebar,
knn.color_red(-prediction), size=size.small, offset=0)
plotshape(endLongTrade and settings.showExits ? high : na, 'StopBuy', shape.xcross,
location.absolute, color=#3AFF17, size=size.tiny, offset=-1)
plotshape(endShortTrade and settings.showExits ? low : na, 'StopSell',
shape.xcross, location.absolute, color=#FD1707, size=size.tiny, offset=-1)

// ================
// ==== Alerts ====
// ================

alertcondition(startShortTrade, title='Bearish ML Change', message='KNN: {{ticker}}


({{interval}}) turned Bearish ▼ [{{close}}]')
alertcondition(startLongTrade, title='Bullish ML Change', message='KNN: {{ticker}}
({{interval}}) turned Bullish ▲ [{{close}}]')
alertcondition(endLongTrade, title='Stop Buy', message='KNN: {{ticker}}
({{interval}}) stopped Buy [{{close}}]')
alertcondition(endShortTrade, title='Stop Sell', message='KNN: {{ticker}}
({{interval}}) stopped Sell [{{close}}]')

// =========================
// ==== Display Signals ====
// =========================

atrSpaced = useAtrOffset ? ta.atr(1) : na


compressionFactor = settings.neighborsCount / settings.colorCompression
c_pred = prediction > 0 ? color.from_gradient(prediction, 0, compressionFactor,
#787b86, #009988) : prediction <= 0 ? color.from_gradient(prediction, -
compressionFactor, 0, #CC3311, #787b86) : na
c_label = showBarPredictions ? c_pred : na
c_bars = showBarColors ? c_pred : na
x_val = bar_index
y_val = useAtrOffset ? prediction > 0 ? high + atrSpaced: low - atrSpaced :
prediction > 0 ? high + hl2*barPredictionsOffset/20 : low -
hl2*barPredictionsOffset/30
label.new(x_val, y_val, str.tostring(prediction), xloc.bar_index, yloc.price,
color.new(color.white, 100), label.style_label_up, c_label, size.normal,
text.align_left)
barcolor(color.new(c_bars, 50))

// =====================
// ==== Backtesting ====
// =====================

// The following can be used to stream signals to a backtest adapter


backTestStream = switch
startLongTrade => 1
endLongTrade => 2
startShortTrade => -1
endShortTrade => -2
plot(backTestStream, "Backtest Stream", display=display.none)

// The following can be used to display real-time winrate approximations. This can
be a useful mechanism for obtaining real-time feedback during Feature Engineering.
// Note: In this context, a "Stop-Loss" is defined instances where the ML Signal
prematurely flips directions before an exit signal can be generated.
[totalWins, totalLosses, totalStopLosses, totalTrades, tradeStatsHeader,
winLossRatio, winRate] = knn.backtest(high, low, open, startLongTrade,
endLongTrade, startShortTrade, endShortTrade, isStopLossHit, maxBarsBackIndex,
bar_index)

init_table() =>
c_transparent = color.new(color.black, 100)
table.new(position.top_right, columns=2, rows=7,
frame_color=color.new(color.black, 100), frame_width=1, border_width=1,
border_color=c_transparent)

update_table(tbl, tradeStatsHeader, totalTrades, totalWins, totalLosses,


winLossRatio, winRate, stopLosses) =>
c_transparent = color.new(color.black, 100)
table.cell(tbl, 0, 0, tradeStatsHeader, text_halign=text.align_center,
text_color=color.gray, text_size=size.normal)
table.cell(tbl, 0, 1, 'Winrate', text_halign=text.align_center,
bgcolor=c_transparent, text_color=color.gray, text_size=size.normal)
table.cell(tbl, 1, 1, str.tostring(totalWins / totalTrades, '#.#%'),
text_halign=text.align_center, bgcolor=c_transparent, text_color=color.gray,
text_size=size.normal)
table.cell(tbl, 0, 2, 'Trades', text_halign=text.align_center,
bgcolor=c_transparent, text_color=color.gray, text_size=size.normal)
table.cell(tbl, 1, 2, str.tostring(totalTrades, '#') + ' (' +
str.tostring(totalWins, '#') + '|' + str.tostring(totalLosses, '#') + ')',
text_halign=text.align_center, bgcolor=c_transparent, text_color=color.gray,
text_size=size.normal)
table.cell(tbl, 0, 5, 'WL Ratio', text_halign=text.align_center,
bgcolor=c_transparent, text_color=color.gray, text_size=size.normal)
table.cell(tbl, 1, 5, str.tostring(totalWins / totalLosses, '0.00'),
text_halign=text.align_center, bgcolor=c_transparent, text_color=color.gray,
text_size=size.normal)
table.cell(tbl, 0, 6, 'Stop-Loss Hits', text_halign=text.align_center,
bgcolor=c_transparent, text_color=color.gray, text_size=size.normal)
table.cell(tbl, 1, 6, str.tostring(totalStopLosses, '#'),
text_halign=text.align_center, bgcolor=c_transparent, text_color=color.gray,
text_size=size.normal)

if show_info
var tbl = knn.init_table()
if barstate.islast
update_table(tbl, tradeStatsHeader, totalTrades, totalWins, totalLosses,
winLossRatio, winRate, totalStopLosses)

// ================
// ==== Order ====
// ================
if (startLongTrade)
strategy.entry("My Long Entry Id", strategy.long, comment = "ENTER")
if (endLongTrade)
strategy.close("My Long Entry Id", comment = "EXIT")

You might also like