The indicator supposedly is quite good in backtesting and would love to use with ThinkorSwim. The script can be found here:
https://www.tradingview.com/script/8cAQrM6a-Machine-Learning-kNN-based-Strategy-update/
Could someone please convert it, I'd greatly appreciate it. It's an open source script:
// This source code is subject to the terms of the Mozilla Public License 2.0 at
https://mozilla.org/MPL/2.0/
// © capissimo
//@version=4
study("Machine Learning: kNN-based Strategy (update)", '', true, precision=4, max_labels_count=200)
// kNN-based Strategy (FX and Crypto)
// Description:
/////////////////////////////////////////////////////////////////////////////////
// This update to the popular kNN-based strategy features:
// improvements in the business logic,
// an adjustible k value for the kNN model,
// one more feature (MOM),
// a streamlined signal filter and
// some other minor fixes.
//
// Now this script works in all timeframes !
//
// I intentionally decided to publish this script separately
// in order for the users to see the differences.
/////////////////////////////////////////////////////////////////////////////////
// This strategy uses a classic machine learning algorithm - k Nearest Neighbours (kNN) -
// to let you find a prediction for the next (tomorrow's, next month's, etc.) market move.
// Being an unsupervised machine learning algorithm, kNN is one of the most simple learning algorithms.
// To do a prediction of the next market move, the kNN algorithm uses the historic data,
// collected in 3 arrays - feature1, feature2 and directions, - and finds the k-nearest
// neighbours of the current indicator(s) values.
// The two dimensional kNN algorithm just has a look on what has happened in the past when
// the two indicators had a similar level. It then looks at the k nearest neighbours,
// sees their state and thus classifies the current point.
// The kNN algorithm offers a framework to test all kinds of indicators easily to see if they
// have got any
predictive value. One can easily add cog, wpr and others.
// Note: TradingViews's playback feature helps to see this strategy in action.
// Style tags: Trend Following, Trend Analysis
// Asset class: Equities, Futures, ETFs, Currencies and Commodities
// Dataset: FX Minutes/Hours+++/Days
//-------------------- Inputs
K = input(63, 'K Value for kNN Model |5..n|', minval=5)
ind = input('All', 'Indicator', options=['RSI','ROC','CCI','MOM','All'])
fast = input(14, 'Fast Period |1..n|', minval=1)
slow = input(28, 'Slow Period |2..n|', minval=2)
ftype = input('Both','Filter Signals by', options=['Volatility','Volume','Both','None'])
holding_p = input(1, 'Holding Period |1..n|', minval=1)
tthres = input(99.9, 'Time Threshold |0.1..100.0|', minval=0.0, maxval=100.0, step=0.1)
//-------------------- Global Variables
var int BUY = 1
var int SELL = -1
var int HOLD = 0
var int hp_counter = 0
var float k = floor(sqrt(K))
//-------------------- Custom Functions
cAqua(g) => g>9?#0080FFff:g>8?#0080FFe5:g>7?#0080FFcc:g>6?#0080FFb2:g>5?#0080FF99:g>4?#0080FF7f:g>3?#0080FF66:g>2?#0080FF4c:g>1?#0080FF33:#00C0FF19
cPink(g) => g>9?#FF0080ff:g>8?#FF0080e5:g>7?#FF0080cc:g>6?#FF0080b2:g>5?#FF008099:g>4?#FF00807f:g>3?#FF008066:g>2?#FF00804c:g>1?#FF008033:#FF008019
volumeBreak(thres) =>
rsivol = rsi(volume, 14)
osc = hma(rsivol, 10)
osc > thres
volatilityBreak(volmin, volmax) => atr(volmin) > atr(volmax)
scale(x, p) => (x - lowest(x, p)) / (highest(x, p) - lowest(x, p)) //-- scale to the range of [0..1]
//-------------------- Logic
// 4 pairs of predictor indicators, long and short each
rs = rsi(close, slow), rf = rsi(close, fast)
cs = cci(close, slow), cf = cci(close, fast)
os = roc(close, slow), of = roc(close, fast)
// added mom as a feature:
// it requires scaling to the range of [0,...,100]
// the choice of 63 is arbitrary, needs optimizing
ms = scale(mom(close, slow), 63) * 100, mf = scale(mom(close, fast), 63) * 100
// TOADD or TOTRYOUT:
// cmo(close, slow), cmo(close, fast)
// mfi(close, slow), mfi(close, fast)
// mom(close, slow), mom(close, fast)
f1 = ind=='RSI' ? rs : ind=='ROC' ? os : ind=='CCI' ? cs : ind=='MOM' ? ms : avg(rs, os, cs, ms)
f2 = ind=='RSI' ? rf : ind=='ROC' ? of : ind=='CCI' ? cf : ind=='MOM' ? mf : avg(rf, of, cf, mf)
// Classification data, what happens on the next bar
class = close[1]<close[0] ? SELL: close[1]>close[0] ? BUY : HOLD
// Training data, normalized to the range of [0,...,100]
var feature1 = array.new_float(0) // [0,...,100]
var feature2 = array.new_float(0) // ...
var directions = array.new_int(0) // [-1; +1]
// Result data
var predictions = array.new_int(0)
var prediction = 0.
var signal = HOLD
// Store everything in arrays. Features represent a square 100 x 100 matrix,
// whose row-colum intersections represent class labels, showing historic directions
array.push(feature1, f1)
array.push(feature2, f2)
array.push(directions, class)
// Ucomment the followng statement (if barstate.islast) and tab everything below
// between BOBlock and EOBlock marks to see just the recent several signals gradually
// showing up, rather than all the preceding signals
//if barstate.islast
//==BOBlock
// Core logic of the algorithm
size = array.size(directions)
maxdist = -999.
// Loop through the training arrays, getting distances and corresponding directions.
for i=0 to size-1
// Calculate the euclidean distance of current point to all historic points,
// here the metric used might as well be a manhattan distance or any other.
d = sqrt(pow(f1 - array.get(feature1, i), 2) + pow(f2 - array.get(feature2, i), 2))
if d > maxdist
maxdist := d
if array.size(predictions) >= k
array.shift(predictions)
array.push(predictions, array.get(directions, i))
//==EOBlock
// Note: in this setup there's no need for distances array (i.e. array.push(distances, d)),
// but the drawback is that a sudden max value may shadow all the subsequent values.
// One of the ways to bypass this is to:
// 1) store d in distances array,
// 2) calculate newdirs = bubbleSort(distances, directions), and then
// 3) take a slice with array.slice(newdirs) from the end
// Get the overall prediction of k nearest neighbours
prediction := array.sum(predictions)
// Now that we got a prediction for the next market move, we need to make use of this prediction and
// trade it. The returns then will show if everything works as predicted.
// Over here is a simple long/short interpretation of the prediction,
// but of course one could also use the quality of the prediction (+5 or +1) in some sort of way,
// ex. for position sizing.
tbase = (time - time[1]) / 1000
tcurr = (timenow - time_close[1]) / 1000
barlife = tcurr / tbase
filter = ftype=='Volatility' ? volatilityBreak(1, 10)
: ftype=='Volume' ? volumeBreak(49)
: ftype=='Both' ? volatilityBreak(1, 10) and volumeBreak(49)
: true
signal := prediction > 0 and barlife > tthres and filter ? BUY : prediction < 0 and barlife > tthres and filter ? SELL : nz(signal[1]) // HOLD
changed = change(signal)
hp_counter := changed ? 0 : hp_counter + 1
startLongTrade = changed and signal==BUY
startShortTrade = changed and signal==SELL
endLongTrade = (changed and signal==SELL) or (signal==BUY and hp_counter==holding_p and not changed)
endShortTrade = (changed and signal==BUY) or (signal==SELL and hp_counter==holding_p and not changed)
//-------------------- Rendering
plotshape(startLongTrade ? low : na, 'Buy', shape.labelup, location.belowbar, cAqua(prediction*5), size=size.small) // color intensity correction
plotshape(startShortTrade ? high : na, 'Sell', shape.labeldown, location.abovebar, cPink(-prediction*5), size=size.small)
plot(endLongTrade ? high : na, 'StopBuy', cAqua(6), 2, plot.style_cross)
plot(endShortTrade ? low : na, 'StopSell', cPink(6), 2, plot.style_cross)
//-------------------- Notification
if changed and signal==BUY
alert("Buy Alert", alert.freq_once_per_bar) // alert.freq_once_per_bar_close
if changed and signal==SELL
alert("Sell Alert", alert.freq_once_per_bar)
alertcondition(startLongTrade, "Buy Alert", "Go Long!")
alertcondition(startShortTrade, "Sell Alert", "Go Short!")
//-------------------- Backtesting
show_info = input(true, '===Information===')
lot_size = input(0.01, 'Lot Size', options=[0.01,0.1,0.2,0.3,0.5,1,2,3,5,10,20,30,50,100,1000])
bidask = (open+high+low)/3
var float start_long_trade = bidask
var float long_trades = 0.
var float start_short_trade = bidask
var float short_trades = 0.
var int wins = 0
var int trade_count = 0
if startLongTrade
start_long_trade := bidask
if endLongTrade
ldiff = (bidask - start_long_trade)
wins := ldiff > 0 ? 1 : 0
long_trades := ldiff * lot_size
trade_count := 1
if startShortTrade
start_short_trade := bidask
if endShortTrade
sdiff = (start_short_trade - bidask)
wins := sdiff > 0 ? 1 : 0
short_trades := sdiff * lot_size
trade_count := 1
cumreturn = cum(long_trades) + cum(short_trades) //-- cumulative return
totaltrades = cum(trade_count)
totalwins = cum(wins)
totallosses = totaltrades - totalwins == 0 ? 1 : totaltrades - totalwins
//------------------- Information
var label lbl = na
info = 'K Value: ' + tostring(K, '#') + ' (' + tostring(k, '#.##') + ')'
+ '\nHPeriod: ' + tostring(holding_p, '#')
+ '\nCR=' + tostring(cumreturn, '#.#')
+ '\nTrades: ' + tostring(totaltrades, '#')
+ '\nWin/Loss: ' + tostring(totalwins/totallosses, '#.##')
+ '\nWinrate: ' + tostring(totalwins/totaltrades, '#.#%')
+ '\nBar Time: ' + tostring(barlife, '#.#%')
if show_info and barstate.islast
lbl := label.new(bar_index, ohlc4, info, xloc.bar_index, yloc.price,
color.new(color.blue, 100), label.style_label_left, color.black, size.small, text.align_left)
label.delete(lbl[1])