Skip to content

Commit dbdc6c4

Browse files
authored
Create Machine Learning: kNN-based Strategy
Made by capissimo
1 parent 67a8f60 commit dbdc6c4

File tree

1 file changed

+231
-0
lines changed

1 file changed

+231
-0
lines changed

Machine Learning: kNN-based Strategy

+231
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
2+
// © capissimo
3+
4+
//@version=5
5+
indicator('Machine Learning: kNN-based Strategy', 'ML-kNN', true, max_labels_count=300, format=format.price, precision=2, timeframe="", timeframe_gaps=true)
6+
7+
// kNN-based Strategy (FX and Crypto)
8+
// Description:
9+
// This strategy uses a classic machine learning algorithm - k Nearest Neighbours (kNN) -
10+
// to let you find a prediction for the next (tomorrow's, next month's, etc.) market move.
11+
// Being an unsupervised machine learning algorithm, kNN is one of the most simple learning algorithms.
12+
13+
// To do a prediction of the next market move, the kNN algorithm uses the historic data,
14+
// collected in 3 arrays - feature1, feature2 and directions, - and finds the k-nearest
15+
// neighbours of the current indicator(s) values.
16+
17+
// The two dimensional kNN algorithm just has a look on what has happened in the past when
18+
// the two indicators had a similar level. It then looks at the k nearest neighbours,
19+
// sees their state and thus classifies the current point.
20+
21+
// The kNN algorithm offers a framework to test all kinds of indicators easily to see if they
22+
// have got any *predictive value*. One can easily add cog, wpr and others.
23+
// Note: TradingViews's playback feature helps to see this strategy in action.
24+
// Warning: Signals ARE repainting.
25+
26+
// Style tags: Trend Following, Trend Analysis
27+
// Asset class: Equities, Futures, ETFs, Currencies and Commodities
28+
// Dataset: FX Minutes/Hours+++/Days
29+
30+
//-- Preset Dates
31+
32+
int startdate = timestamp('01 Jan 2000 00:00:00 GMT+10')
33+
int stopdate = timestamp('31 Dec 2025 23:45:00 GMT+10')
34+
35+
//-- Inputs
36+
37+
StartDate = input.time (startdate, 'Start Date')
38+
StopDate = input.time (stopdate, 'Stop Date')
39+
Indicator = input.string('All', 'Indicator', ['RSI','ROC','CCI','Volume','All'])
40+
ShortWinow = input.int (14, 'Short Period [1..n]', 1)
41+
LongWindow = input.int (28, 'Long Period [2..n]', 2)
42+
BaseK = input.int (252, 'Base No. of Neighbours (K) [5..n]', 5)
43+
Filter = input.bool (false, 'Volatility Filter')
44+
Bars = input.int (300, 'Bar Threshold [2..5000]', 2, 5000)
45+
46+
//-- Constants
47+
48+
var int BUY = 1
49+
var int SELL =-1
50+
var int CLEAR = 0
51+
52+
var int k = math.floor(math.sqrt(BaseK)) // k Value for kNN algo
53+
54+
//-- Variable
55+
56+
// Training data, normalized to the range of [0,...,100]
57+
var array<float> feature1 = array.new_float(0) // [0,...,100]
58+
var array<float> feature2 = array.new_float(0) // ...
59+
var array<int> directions = array.new_int(0) // [-1; +1]
60+
61+
// Result data
62+
var array<int> predictions = array.new_int(0)
63+
var float prediction = 0.0
64+
var array<int> bars = array.new<int>(1, 0) // array used as a container for inter-bar variables
65+
66+
// Signals
67+
var int signal = CLEAR
68+
69+
//-- Functions
70+
71+
minimax(float x, int p, float min, float max) =>
72+
float hi = ta.highest(x, p), float lo = ta.lowest(x, p)
73+
(max - min) * (x - lo)/(hi - lo) + min
74+
75+
cAqua(int 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
76+
cPink(int 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
77+
78+
inside_window(float start, float stop) =>
79+
time >= start and time <= stop ? true : false
80+
81+
//-- Logic
82+
83+
bool window = inside_window(StartDate, StopDate)
84+
85+
// 3 pairs of predictor indicators, long and short each
86+
float rs = ta.rsi(close, LongWindow), float rf = ta.rsi(close, ShortWinow)
87+
float cs = ta.cci(close, LongWindow), float cf = ta.cci(close, ShortWinow)
88+
float os = ta.roc(close, LongWindow), float of = ta.roc(close, ShortWinow)
89+
float vs = minimax(volume, LongWindow, 0, 99), float vf = minimax(volume, ShortWinow, 0, 99)
90+
91+
// TOADD or TOTRYOUT:
92+
// ta.cmo(close, LongWindow), ta.cmo(close, ShortWinow)
93+
// ta.mfi(close, LongWindow), ta.mfi(close, ShortWinow)
94+
// ta.mom(close, LongWindow), ta.mom(close, ShortWinow)
95+
96+
float f1 = switch Indicator
97+
'RSI' => rs
98+
'CCI' => cs
99+
'ROC' => os
100+
'Volume' => vs
101+
=> math.avg(rs, cs, os, vs)
102+
103+
float f2 = switch Indicator
104+
'RSI' => rf
105+
'CCI' => cf
106+
'ROC' => of
107+
'Volume' => vf
108+
=> math.avg(rf, cf, of, vf)
109+
110+
// Classification data, what happens on the next bar
111+
int class_label = int(math.sign(close[1] - close[0])) // eq. close[1]<close[0] ? SELL: close[1]>close[0] ? BUY : CLEAR
112+
113+
// Use particular training period
114+
if window
115+
// Store everything in arrays. Features represent a square 100 x 100 matrix,
116+
// whose row-colum intersections represent class labels, showing historic directions
117+
array.push(feature1, f1)
118+
array.push(feature2, f2)
119+
array.push(directions, class_label)
120+
121+
// Ucomment the followng statement (if barstate.islast) and tab everything below
122+
// between BOBlock and EOBlock marks to see just the recent several signals gradually
123+
// showing up, rather than all the preceding signals
124+
125+
//if barstate.islast
126+
127+
//==BOBlock
128+
129+
// Core logic of the algorithm
130+
int size = array.size(directions)
131+
float maxdist = -999.0
132+
// Loop through the training arrays, getting distances and corresponding directions.
133+
for i=0 to size-1
134+
// Calculate the euclidean distance of current point to all historic points,
135+
// here the metric used might as well be a manhattan distance or any other.
136+
float d = math.sqrt(math.pow(f1 - array.get(feature1, i), 2) + math.pow(f2 - array.get(feature2, i), 2))
137+
138+
if d > maxdist
139+
maxdist := d
140+
if array.size(predictions) >= k
141+
array.shift(predictions)
142+
array.push(predictions, array.get(directions, i))
143+
144+
//==EOBlock
145+
146+
// Note: in this setup there's no need for distances array (i.e. array.push(distances, d)),
147+
// but the drawback is that a sudden max value may shadow all the subsequent values.
148+
// One of the ways to bypass this is to:
149+
// 1) store d in distances array,
150+
// 2) calculate newdirs = bubbleSort(distances, directions), and then
151+
// 3) take a slice with array.slice(newdirs) from the end
152+
153+
// Get the overall prediction of k nearest neighbours
154+
prediction := array.sum(predictions)
155+
156+
bool filter = Filter ? ta.atr(10) > ta.atr(40) : true // filter out by volatility or ex. ta.atr(1) > ta.atr(10)...
157+
158+
// Now that we got a prediction for the next market move, we need to make use of this prediction and
159+
// trade it. The returns then will show if everything works as predicted.
160+
// Over here is a simple long/short interpretation of the prediction,
161+
// but of course one could also use the quality of the prediction (+5 or +1) in some sort of way,
162+
// ex. for position sizing.
163+
164+
bool long = prediction > 0 and filter
165+
bool short = prediction < 0 and filter
166+
bool clear = not(long and short)
167+
168+
if array.get(bars, 0)==Bars // stop by trade duration
169+
signal := CLEAR
170+
array.set(bars, 0, 0)
171+
else
172+
array.set(bars, 0, array.get(bars, 0) + 1)
173+
174+
signal := long ? BUY : short ? SELL : clear ? CLEAR : nz(signal[1])
175+
176+
int changed = ta.change(signal)
177+
bool startLongTrade = changed and signal==BUY
178+
bool startShortTrade = changed and signal==SELL
179+
// bool endLongTrade = changed and signal==SELL
180+
// bool endShortTrade = changed and signal==BUY
181+
bool clear_condition = changed and signal==CLEAR //or (changed and signal==SELL) or (changed and signal==BUY)
182+
183+
float maxpos = ta.highest(high, 10)
184+
float minpos = ta.lowest (low, 10)
185+
186+
//-- Visuals
187+
188+
plotshape(startLongTrade ? minpos : na, 'Buy', shape.labelup, location.belowbar, cAqua(int(prediction*5)), size=size.small) // color intensity correction
189+
plotshape(startShortTrade ? maxpos : na, 'Sell', shape.labeldown, location.abovebar, cPink(int(-prediction*5)), size=size.small)
190+
// plot(endLongTrade ? ohlc4 : na, 'StopBuy', cAqua(6), 3, plot.style_cross)
191+
// plot(endShortTrade ? ohlc4 : na, 'StopSell', cPink(6), 3, plot.style_cross)
192+
plot(clear_condition ? close : na, 'ClearPos', color.yellow, 4, plot.style_cross)
193+
194+
195+
//-- Notification
196+
197+
// if changed and signal==BUY
198+
// alert('Buy Alert', alert.freq_once_per_bar) // alert.freq_once_per_bar_close
199+
// if changed and signal==SELL
200+
// alert('Sell Alert', alert.freq_once_per_bar)
201+
202+
alertcondition(startLongTrade, 'Buy', 'Go long!')
203+
alertcondition(startShortTrade, 'Sell', 'Go short!')
204+
//alertcondition(startLongTrade or startShortTrade, 'Alert', 'Deal Time!')
205+
206+
//-------------------- Backtesting (TODO)
207+
208+
// show_cumtr = input.bool (false, 'Show Trade Return?')
209+
// lot_size = input.float(100.0, 'Lot Size', [0.1,0.2,0.3,0.5,1,2,3,5,10,20,30,50,100,1000,2000,3000,5000,10000])
210+
211+
// var start_lt = 0.
212+
// var long_trades = 0.
213+
// var start_st = 0.
214+
// var short_trades = 0.
215+
216+
// if startLongTrade
217+
// start_lt := ohlc4
218+
// if endLongTrade
219+
// long_trades := (open - start_lt) * lot_size
220+
// if startShortTrade
221+
// start_st := ohlc4
222+
// if endShortTrade
223+
// short_trades := (start_st - open) * lot_size
224+
225+
// cumreturn = ta.cum(long_trades) + ta.cum(short_trades)
226+
227+
// var label lbl = na
228+
// if show_cumtr //and barstate.islast
229+
// lbl := label.new(bar_index+10, close, 'CumReturn: ' + str.tostring(cumreturn, '#.#'), xloc.bar_index, yloc.price,
230+
// color.new(color.blue, 100), label.style_label_left, color.black, size.small, text.align_left)
231+
// label.delete(lbl[1])

0 commit comments

Comments
 (0)