-
Notifications
You must be signed in to change notification settings - Fork 26
Expand file tree
/
Copy pathrun-ga-parallel.js
More file actions
339 lines (296 loc) · 10.7 KB
/
Copy pathrun-ga-parallel.js
File metadata and controls
339 lines (296 loc) · 10.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
//run server.js with the following command:
//node --max-old-space-size=8192 server.js
// then run this to run the GA
// node run-ga-parallel.js
const async = require('async');
const randomExt = require('random-ext');
const request = require('request');
// Creates a random gene if prop='all', creates one random property otherwise
function create_gene(prop) {
let candle_values = [120,1440,240,120,2,1,15,30,60];
let properites = {
//here add the indicators and the ranges you want to handle
//in this case my strategy wants to test RSI ranges
rsishort: randomExt.integer(90,60),
rsilong: randomExt.integer(40, 10),
"candleSize": candle_values[randomExt.integer(8,0)],
};
// console.log(properites);
if (prop === 'all')
return properites;
else {
return properites[prop];
}
}
// Creates random population from genes
function create_population(amount) {
let population = [];
for (let i = 0; i < amount; i++) {
population.push(create_gene('all'));
}
return population;
}
// Pairs two parents returning two new childs
function crossover(a, b) {
let len = Object.keys(a).length;
let cross_point = randomExt.integer(len - 1, 1);
let tmp_a = {};
let tmp_b = {};
let curr_point = 0;
for (let i in a) {
if (a.hasOwnProperty(i) && b.hasOwnProperty(i)) {
if (curr_point < cross_point) {
tmp_a[i] = a[i];
tmp_b[i] = b[i];
}
else {
tmp_a[i] = b[i];
tmp_b[i] = a[i];
}
}
curr_point++;
}
return [tmp_a, tmp_b];
}
// Mutates object a at most maxAmount times
function mutate(a, maxAmount) {
let amt = randomExt.integer(maxAmount, 0);
let all_props = Object.keys(a);
let tmp = {};
for (let p in a) {
if (a.hasOwnProperty(p))
tmp[p] = a[p];
}
for (let i = 0; i < amt; i++) {
let position = randomExt.integer(0, a.length);
let prop = all_props[position];
tmp[prop] = create_gene(prop);
}
return tmp;
}
// For the given population and fitness, returns new population and max score
function run_epoch(population, variation, population_amt, mutate_elements, fitness_arr) {
let selection_prob = [];
let fitness_sum = 0;
let max_fitness = [0, 0];
for (let i = 0; i < population_amt; i++) {
if (fitness_arr[i] > max_fitness[0])
max_fitness = [fitness_arr[i], i];
fitness_sum += fitness_arr[i];
}
if (fitness_sum === 0) {
for (let j = 0; j < population_amt; j++) {
selection_prob[j] = 1 / population_amt;
}
}
else {
for (let j = 0; j < population_amt; j++) {
selection_prob[j] = fitness_arr[j] / fitness_sum;
}
}
let new_population = [];
while (new_population.length < population_amt * (1 - variation)) {
let a, b;
let selected_prob = randomExt.float(1, 0);
for (let k = 0; k < population_amt; k++) {
selected_prob -= selection_prob[k];
if (selected_prob <= 0) {
a = population[k];
break;
}
}
selected_prob = randomExt.float(1, 0);
for (k = 0; k < population_amt; k++) {
selected_prob -= selection_prob[k];
if (selected_prob <= 0) {
b = population[k];
break;
}
}
let res = crossover(mutate(a, mutate_elements), mutate(b, mutate_elements));
new_population.push(res[0]);
new_population.push(res[1]);
}
for (let l = 0; l < population_amt * variation; l++) {
new_population.push(create_gene('all'));
}
return [new_population, max_fitness];
}
// Calls api for every element in test_series and returns gain for each
function fitness_api(tests_series, callback) {
const number_of_parallel_queries = 8;
async.mapLimit(tests_series, number_of_parallel_queries, function (data, callback) {
let outconfig = {
"gekkoConfig": {
"watch": {
"exchange": "poloniex",
"currency": "USDT",
"asset": "ETH"
},
"paperTrader": {
"fee": 0.25,
"slippage": 0.05,
"feeTaker": 0.25,
"feeUsing": "maker",
"reportInCurrency": true,
"simulationBalance": {
"asset": 1,
"currency": 100
},
"reportRoundtrips": true,
"enabled": true
},
"writer": {
"enabled": false,
"logpath": ""
},
"tradingAdvisor": {
"enabled": true,
"method": "test-allv2",
"candleSize": data.candleSize,
// "candleSize": 1,
"historySize": 10,
talib: {
enabled: false,
version: '1.0.2'
}
},
//this is just the same as your usual setup in config.js
"add your strategy here, it's the same name you use in config.js": {
"parameters": {
"optInTimePeriod": 14,
"historylength": 14,
//here we reference the indicator range outputs into the config
"rsishort": data.rsishort,
"rsilong": data.rsilong,
},
},
"backtest": {
//use scan or a daterange by commenting / uncommenting out
"daterange": "scan",
/* "daterange": {
"from": "2017-05-04 00:00:00",
"to": "2017-08-18 00:00:00"
*/
}
},
"performanceAnalyzer": {
"riskFreeReturn": 5,
"enabled": true
},
"valid": true
},
"data": {
"candleProps": ["close", "start"],
"indicatorResults": true,
"report": true,
"roundtrips": true,
"trades": true
}
};
request.post({
url: 'http://localhost:3000/api/backtest',
json: outconfig,
headers: {
"Content-Type": "application/json"
}
},
function (err, httpResponse, body) {
if (err)
console.log(err);
// These properties will be outputted every epoch, remove property if not needed
let properties = ['balance', 'profit', 'relativeProfit', 'yearlyProfit', 'relativeYearlyProfit', 'startPrice', 'endPrice', 'trades'];
if(body===false){
}
let report = body.report;
let result = {"profit": 0, "metrics": false};
if (report) {
let picked = properties.reduce(function (o, k) {
o[k] = report[k];
return o;
}, {});
result = {"profit": body.report.profit, "metrics": picked};
}
return callback(err, result);
});
}, function (err, results) {
let profits = [];
let other_metrics = [];
for (let i in results) {
if (results.hasOwnProperty(i)) {
profits.push(results[i]["profit"]);
other_metrics.push(results[i]["metrics"])
}
}
callback(profits, other_metrics);
});
}
// ********************************************************************
// Configurable parameters
// --------------------------------------------------------------------
// How many completely new units will be added to the population
const variation = 0.3; // Population * variation must be a whole number!!
// Population size, better reduce this for larger data
const population_amt = 8;
// How many components maximum to mutate at once
const mutate_elements = 14;
// When the algorithm reaches this value it will stop,
// but you can stop it any time you wish since the last max parameters are outputted every epoch
const target_value = 5000000000;
// --------------------------------------------------------------------
let population = create_population(population_amt);
let population_fitness;
let other_population_metrics;
let epoch_number = 0;
console.log("Starting training with: " + population_amt + " units");
let start_time = new Date().getTime();
let all_time_maximum = {parameters: {}, gain: 0, epoch_number: 0, other_metrics: {}};
async.doWhilst(function (callback) {
start_time = new Date().getTime();
fitness_api(population, function (result, other_metrics) {
population_fitness = result;
other_population_metrics = other_metrics;
callback();
})
}, function () {
let end_time = new Date().getTime();
epoch_number++;
let results = run_epoch(population, variation, population_amt, mutate_elements, population_fitness);
let new_population = results[0];
let max_result = results[1];
let value = max_result[0];
let position = max_result[1];
if (value >= all_time_maximum.gain) {
all_time_maximum.parameters = population[position];
all_time_maximum.other_metrics = other_population_metrics[position];
all_time_maximum.gain = value;
all_time_maximum.epoch_number = epoch_number;
}
console.log("--------------------------------------------------------------");
console.log("Epoch number: " + epoch_number);
console.log("Time it took (seconds): " + (end_time - start_time) / 1000);
console.log("Max profit: " + value + "$" + " max profit position: " + position);
console.log("Max parametars: ");
console.log(population[position]);
console.log("Other metrics: "); // Prints other metrics, they can be turned off on line 190
console.log(other_population_metrics[position]);
// Prints out the whole population with its fitness,
// useful for finding properties that make no sense and debugging
// for (let element in population) {
// console.log("Fitness: "+population_fitness[element]+" Properties:");
// console.log(population[element])
// }
console.log("--------------------------------------------------------------");
console.log("Global maximum: " + all_time_maximum.gain + "$, parameters:");
console.log(all_time_maximum.parameters);
console.log("Other metrics of global maximum:");
console.log("Global maximum so far:");
console.log(all_time_maximum.other_metrics);
console.log("--------------------------------------------------------------");
population = new_population;
return value < target_value;
}, function () {
console.log("Finished!");
console.log("All time maximum:");
console.log(all_time_maximum)
});