@gbozee/ultimate 0.0.2-7 → 0.0.2-71
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/frontend/frontend-index.js +1318 -0
- package/dist/frontend-index.d.ts +544 -0
- package/dist/frontend-index.js +2124 -0
- package/dist/index.cjs +58854 -0
- package/dist/index.d.ts +1252 -222
- package/dist/index.js +40254 -17091
- package/package.json +15 -4
|
@@ -0,0 +1,1318 @@
|
|
|
1
|
+
// src/helpers/pnl.ts
|
|
2
|
+
function determine_position_size(entry, stop, budget) {
|
|
3
|
+
let stop_percent = Math.abs(entry - stop) / entry;
|
|
4
|
+
let size = budget / stop_percent / entry;
|
|
5
|
+
return size;
|
|
6
|
+
}
|
|
7
|
+
function determine_risk(entry, stop, quantity) {
|
|
8
|
+
let stop_percent = Math.abs(entry - stop) / entry;
|
|
9
|
+
let risk = quantity * stop_percent * entry;
|
|
10
|
+
return risk;
|
|
11
|
+
}
|
|
12
|
+
function determine_close_price(entry, pnl, quantity, kind, single = false, leverage = 1) {
|
|
13
|
+
const dollar_value = entry / leverage;
|
|
14
|
+
const position = dollar_value * quantity;
|
|
15
|
+
if (position) {
|
|
16
|
+
let percent = pnl / position;
|
|
17
|
+
let difference = position * percent / quantity;
|
|
18
|
+
let result;
|
|
19
|
+
if (kind === "long") {
|
|
20
|
+
result = difference + entry;
|
|
21
|
+
} else {
|
|
22
|
+
result = entry - difference;
|
|
23
|
+
}
|
|
24
|
+
if (single) {
|
|
25
|
+
return result;
|
|
26
|
+
}
|
|
27
|
+
return result;
|
|
28
|
+
}
|
|
29
|
+
return 0;
|
|
30
|
+
}
|
|
31
|
+
function determine_amount_to_sell(entry, quantity, sell_price, pnl, kind, places = "%.3f") {
|
|
32
|
+
const _pnl = determine_pnl(entry, sell_price, quantity, kind);
|
|
33
|
+
const ratio = pnl / to_f(Math.abs(_pnl), places);
|
|
34
|
+
quantity = quantity * ratio;
|
|
35
|
+
return to_f(quantity, places);
|
|
36
|
+
}
|
|
37
|
+
function determine_pnl(entry, close_price, quantity, kind, contract_size, places = "%.2f") {
|
|
38
|
+
if (contract_size) {
|
|
39
|
+
const direction = kind === "long" ? 1 : -1;
|
|
40
|
+
return quantity * contract_size * direction * (1 / entry - 1 / close_price);
|
|
41
|
+
}
|
|
42
|
+
let difference = entry - close_price;
|
|
43
|
+
if (kind === "long") {
|
|
44
|
+
difference = close_price - entry;
|
|
45
|
+
}
|
|
46
|
+
return to_f(difference * quantity, places);
|
|
47
|
+
}
|
|
48
|
+
function position(entry, quantity, kind, leverage = 1) {
|
|
49
|
+
const direction = { long: 1, short: -1 };
|
|
50
|
+
return parseFloat((direction[kind] * quantity * (entry / leverage)).toFixed(3));
|
|
51
|
+
}
|
|
52
|
+
function to_f(value, places) {
|
|
53
|
+
if (value) {
|
|
54
|
+
let pp = parseInt(places.replace("%.", "").replace("f", ""));
|
|
55
|
+
return parseFloat(value.toFixed(pp));
|
|
56
|
+
}
|
|
57
|
+
return value;
|
|
58
|
+
}
|
|
59
|
+
var value = {
|
|
60
|
+
determine_risk,
|
|
61
|
+
determine_position_size,
|
|
62
|
+
determine_close_price,
|
|
63
|
+
determine_pnl,
|
|
64
|
+
position,
|
|
65
|
+
determine_amount_to_sell,
|
|
66
|
+
to_f
|
|
67
|
+
};
|
|
68
|
+
var pnl_default = value;
|
|
69
|
+
|
|
70
|
+
// src/helpers/trade_utils.ts
|
|
71
|
+
function to_f2(value2, places = "%.1f") {
|
|
72
|
+
let v = typeof value2 === "string" ? parseFloat(value2) : value2;
|
|
73
|
+
const formattedValue = places.replace("%.", "").replace("f", "");
|
|
74
|
+
return parseFloat(v.toFixed(parseInt(formattedValue)));
|
|
75
|
+
}
|
|
76
|
+
function determine_position_size2({
|
|
77
|
+
entry,
|
|
78
|
+
stop,
|
|
79
|
+
budget,
|
|
80
|
+
percent,
|
|
81
|
+
min_size,
|
|
82
|
+
notional_value,
|
|
83
|
+
as_coin = true,
|
|
84
|
+
places = "%.3f"
|
|
85
|
+
}) {
|
|
86
|
+
let stop_percent = stop ? Math.abs(entry - stop) / entry : percent;
|
|
87
|
+
if (stop_percent && budget) {
|
|
88
|
+
let size = budget / stop_percent;
|
|
89
|
+
let notion_value = size * entry;
|
|
90
|
+
if (notional_value && notional_value > notion_value) {
|
|
91
|
+
size = notional_value / entry;
|
|
92
|
+
}
|
|
93
|
+
if (as_coin) {
|
|
94
|
+
size = size / entry;
|
|
95
|
+
if (min_size && min_size === 1) {
|
|
96
|
+
return to_f2(Math.round(size), places);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return to_f2(size, places);
|
|
100
|
+
}
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
function determine_average_entry_and_size(orders, places = "%.3f", price_places = "%.1f") {
|
|
104
|
+
const sum_values = orders.reduce((sum, order) => sum + order.price * order.quantity, 0);
|
|
105
|
+
const total_quantity = orders.reduce((sum, order) => sum + order.quantity, 0);
|
|
106
|
+
const avg_price = total_quantity ? to_f2(sum_values / total_quantity, price_places) : 0;
|
|
107
|
+
return {
|
|
108
|
+
entry: avg_price,
|
|
109
|
+
price: avg_price,
|
|
110
|
+
quantity: to_f2(total_quantity, places)
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
var createArray = (start, stop, step) => {
|
|
114
|
+
const result = [];
|
|
115
|
+
let current = start;
|
|
116
|
+
while (current <= stop) {
|
|
117
|
+
result.push(current);
|
|
118
|
+
current += step;
|
|
119
|
+
}
|
|
120
|
+
return result;
|
|
121
|
+
};
|
|
122
|
+
var groupIntoPairsWithSumLessThan = (arr, targetSum, key = "quantity", firstSize = 0) => {
|
|
123
|
+
if (firstSize) {
|
|
124
|
+
const totalSize = arr.reduce((sum, order) => sum + order[key], 0);
|
|
125
|
+
const remainingSize = totalSize - firstSize;
|
|
126
|
+
let newSum = 0;
|
|
127
|
+
let newArray = [];
|
|
128
|
+
let lastIndex = 0;
|
|
129
|
+
for (let i = 0;i < arr.length; i++) {
|
|
130
|
+
if (newSum < remainingSize) {
|
|
131
|
+
newSum += arr[i][key];
|
|
132
|
+
newArray.push(arr[i]);
|
|
133
|
+
lastIndex = i;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
const lastGroup = arr.slice(lastIndex + 1);
|
|
137
|
+
const previousPair = groupInPairs(newArray, key, targetSum);
|
|
138
|
+
if (lastGroup.length > 0) {
|
|
139
|
+
previousPair.push(lastGroup);
|
|
140
|
+
}
|
|
141
|
+
return previousPair;
|
|
142
|
+
}
|
|
143
|
+
return groupInPairs(arr, key, targetSum);
|
|
144
|
+
};
|
|
145
|
+
function groupInPairs(_arr, key, targetSum) {
|
|
146
|
+
const result = [];
|
|
147
|
+
let currentSum = 0;
|
|
148
|
+
let currentGroup = [];
|
|
149
|
+
for (let i = 0;i < _arr.length; i++) {
|
|
150
|
+
currentSum += _arr[i][key];
|
|
151
|
+
currentGroup.push(_arr[i]);
|
|
152
|
+
if (currentSum >= targetSum) {
|
|
153
|
+
result.push(currentGroup);
|
|
154
|
+
currentGroup = [];
|
|
155
|
+
currentSum = 0;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return result;
|
|
159
|
+
}
|
|
160
|
+
var computeTotalAverageForEachTrade = (trades, config) => {
|
|
161
|
+
let _take_profit = config.take_profit;
|
|
162
|
+
let kind = config.kind;
|
|
163
|
+
let entryToUse = kind === "short" ? Math.min(config.entry, config.stop) : Math.max(config.entry, config.stop);
|
|
164
|
+
let _currentEntry = config.currentEntry || entryToUse;
|
|
165
|
+
let less = trades.filter((p) => kind === "long" ? p.entry <= _currentEntry : p.entry >= _currentEntry);
|
|
166
|
+
let rrr = trades.map((r, i) => {
|
|
167
|
+
let considered = [];
|
|
168
|
+
if (kind === "long") {
|
|
169
|
+
considered = trades.filter((p) => p.entry > _currentEntry);
|
|
170
|
+
} else {
|
|
171
|
+
considered = trades.filter((p) => p.entry < _currentEntry);
|
|
172
|
+
}
|
|
173
|
+
const x_pnl = 0;
|
|
174
|
+
const remaining = less.filter((o) => {
|
|
175
|
+
if (kind === "long") {
|
|
176
|
+
return o.entry >= r.entry;
|
|
177
|
+
}
|
|
178
|
+
return o.entry <= r.entry;
|
|
179
|
+
});
|
|
180
|
+
if (remaining.length === 0) {
|
|
181
|
+
return { ...r, pnl: x_pnl };
|
|
182
|
+
}
|
|
183
|
+
const start = kind === "long" ? Math.max(...remaining.map((o) => o.entry)) : Math.min(...remaining.map((o) => o.entry));
|
|
184
|
+
considered = considered.map((o) => ({ ...o, entry: start }));
|
|
185
|
+
considered = considered.concat(remaining);
|
|
186
|
+
let avg_entry = determine_average_entry_and_size([
|
|
187
|
+
...considered.map((o) => ({
|
|
188
|
+
price: o.entry,
|
|
189
|
+
quantity: o.quantity
|
|
190
|
+
})),
|
|
191
|
+
{
|
|
192
|
+
price: _currentEntry,
|
|
193
|
+
quantity: config.currentQty || 0
|
|
194
|
+
}
|
|
195
|
+
], config.decimal_places, config.price_places);
|
|
196
|
+
let _pnl = r.pnl;
|
|
197
|
+
let sell_price = r.sell_price;
|
|
198
|
+
let entry_pnl = r.pnl;
|
|
199
|
+
if (_take_profit) {
|
|
200
|
+
_pnl = pnl_default.determine_pnl(avg_entry.price, _take_profit, avg_entry.quantity, kind);
|
|
201
|
+
sell_price = _take_profit;
|
|
202
|
+
entry_pnl = pnl_default.determine_pnl(r.entry, _take_profit, avg_entry.quantity, kind);
|
|
203
|
+
}
|
|
204
|
+
const loss = pnl_default.determine_pnl(avg_entry.price, r.stop, avg_entry.quantity, kind);
|
|
205
|
+
let new_stop = r.new_stop;
|
|
206
|
+
const entry_loss = pnl_default.determine_pnl(r.entry, new_stop, avg_entry.quantity, kind);
|
|
207
|
+
let min_profit = 0;
|
|
208
|
+
let min_entry_profit = 0;
|
|
209
|
+
if (config.min_profit) {
|
|
210
|
+
min_profit = pnl_default.determine_close_price(avg_entry.price, config.min_profit, avg_entry.quantity, kind);
|
|
211
|
+
min_entry_profit = pnl_default.determine_close_price(r.entry, config.min_profit, avg_entry.quantity, kind);
|
|
212
|
+
}
|
|
213
|
+
let x_fee = r.fee;
|
|
214
|
+
if (config.fee) {
|
|
215
|
+
x_fee = config.fee * r.stop * avg_entry.quantity;
|
|
216
|
+
}
|
|
217
|
+
let tp_close = pnl_default.determine_close_price(r.entry, Math.abs(entry_loss) * (config.rr || 1) + x_fee, avg_entry.quantity, kind);
|
|
218
|
+
return {
|
|
219
|
+
...r,
|
|
220
|
+
x_fee: to_f2(x_fee, "%.2f"),
|
|
221
|
+
avg_entry: avg_entry.price,
|
|
222
|
+
avg_size: avg_entry.quantity,
|
|
223
|
+
entry_pnl: to_f2(entry_pnl, "%.2f"),
|
|
224
|
+
entry_loss: to_f2(entry_loss, "%.2f"),
|
|
225
|
+
min_entry_pnl: to_f2(min_entry_profit, "%.2f"),
|
|
226
|
+
pnl: _pnl,
|
|
227
|
+
neg_pnl: to_f2(loss, "%.2f"),
|
|
228
|
+
sell_price,
|
|
229
|
+
close_p: to_f2(tp_close, "%.2f"),
|
|
230
|
+
min_pnl: to_f2(min_profit, "%.2f"),
|
|
231
|
+
new_stop
|
|
232
|
+
};
|
|
233
|
+
});
|
|
234
|
+
return rrr;
|
|
235
|
+
};
|
|
236
|
+
function createGapPairs(arr, gap, item) {
|
|
237
|
+
if (arr.length === 0) {
|
|
238
|
+
return [];
|
|
239
|
+
}
|
|
240
|
+
const result = [];
|
|
241
|
+
const firstElement = arr[0];
|
|
242
|
+
for (let i = arr.length - 1;i >= 0; i--) {
|
|
243
|
+
const current = arr[i];
|
|
244
|
+
const gapIndex = i - gap;
|
|
245
|
+
const pairedElement = gapIndex < 0 ? firstElement : arr[gapIndex];
|
|
246
|
+
if (current !== pairedElement) {
|
|
247
|
+
result.push([current, pairedElement]);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
if (item) {
|
|
251
|
+
let r = result.find((o) => o[0] === item);
|
|
252
|
+
return r ? [r] : [];
|
|
253
|
+
}
|
|
254
|
+
return result;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// src/helpers/trade_signal.ts
|
|
258
|
+
function determine_close_price2({
|
|
259
|
+
entry,
|
|
260
|
+
pnl,
|
|
261
|
+
quantity,
|
|
262
|
+
leverage = 1,
|
|
263
|
+
kind = "long"
|
|
264
|
+
}) {
|
|
265
|
+
const dollar_value = entry / leverage;
|
|
266
|
+
const position2 = dollar_value * quantity;
|
|
267
|
+
if (position2) {
|
|
268
|
+
const percent = pnl / position2;
|
|
269
|
+
const difference = position2 * percent / quantity;
|
|
270
|
+
const result = kind === "long" ? difference + entry : entry - difference;
|
|
271
|
+
return result;
|
|
272
|
+
}
|
|
273
|
+
return 0;
|
|
274
|
+
}
|
|
275
|
+
function determine_pnl2(entry, close_price, quantity, kind = "long", contract_size) {
|
|
276
|
+
if (contract_size) {
|
|
277
|
+
const direction = kind === "long" ? 1 : -1;
|
|
278
|
+
return quantity * contract_size * direction * (1 / entry - 1 / close_price);
|
|
279
|
+
}
|
|
280
|
+
const difference = kind === "long" ? close_price - entry : entry - close_price;
|
|
281
|
+
return difference * quantity;
|
|
282
|
+
}
|
|
283
|
+
function* _get_zones({
|
|
284
|
+
current_price,
|
|
285
|
+
focus,
|
|
286
|
+
percent_change,
|
|
287
|
+
places = "%.5f"
|
|
288
|
+
}) {
|
|
289
|
+
let last = focus;
|
|
290
|
+
let focus_high = last * (1 + percent_change);
|
|
291
|
+
let focus_low = last * Math.pow(1 + percent_change, -1);
|
|
292
|
+
if (focus_high > current_price) {
|
|
293
|
+
while (focus_high > current_price) {
|
|
294
|
+
yield to_f2(last, places);
|
|
295
|
+
focus_high = last;
|
|
296
|
+
last = focus_high * Math.pow(1 + percent_change, -1);
|
|
297
|
+
focus_low = last * Math.pow(1 + percent_change, -1);
|
|
298
|
+
}
|
|
299
|
+
} else {
|
|
300
|
+
if (focus_high <= current_price) {
|
|
301
|
+
while (focus_high <= current_price) {
|
|
302
|
+
yield to_f2(focus_high, places);
|
|
303
|
+
focus_low = focus_high;
|
|
304
|
+
last = focus_low * (1 + percent_change);
|
|
305
|
+
focus_high = last * (1 + percent_change);
|
|
306
|
+
}
|
|
307
|
+
} else {
|
|
308
|
+
while (focus_low <= current_price) {
|
|
309
|
+
yield to_f2(focus_high, places);
|
|
310
|
+
focus_low = focus_high;
|
|
311
|
+
last = focus_low * (1 + percent_change);
|
|
312
|
+
focus_high = last * (1 + percent_change);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
class Signal {
|
|
319
|
+
focus;
|
|
320
|
+
budget;
|
|
321
|
+
percent_change = 0.02;
|
|
322
|
+
price_places = "%.5f";
|
|
323
|
+
decimal_places = "%.0f";
|
|
324
|
+
zone_risk = 1;
|
|
325
|
+
fee = 0.08 / 100;
|
|
326
|
+
support;
|
|
327
|
+
risk_reward = 4;
|
|
328
|
+
resistance;
|
|
329
|
+
risk_per_trade;
|
|
330
|
+
increase_size = false;
|
|
331
|
+
additional_increase = 0;
|
|
332
|
+
minimum_pnl = 0;
|
|
333
|
+
take_profit;
|
|
334
|
+
increase_position = false;
|
|
335
|
+
minimum_size;
|
|
336
|
+
first_order_size;
|
|
337
|
+
gap = 10;
|
|
338
|
+
max_size = 0;
|
|
339
|
+
constructor({
|
|
340
|
+
focus,
|
|
341
|
+
budget,
|
|
342
|
+
percent_change = 0.02,
|
|
343
|
+
price_places = "%.5f",
|
|
344
|
+
decimal_places = "%.0f",
|
|
345
|
+
zone_risk = 1,
|
|
346
|
+
fee = 0.06 / 100,
|
|
347
|
+
support,
|
|
348
|
+
risk_reward = 4,
|
|
349
|
+
resistance,
|
|
350
|
+
risk_per_trade,
|
|
351
|
+
increase_size = false,
|
|
352
|
+
additional_increase = 0,
|
|
353
|
+
minimum_pnl = 0,
|
|
354
|
+
take_profit,
|
|
355
|
+
increase_position = false,
|
|
356
|
+
minimum_size = 0,
|
|
357
|
+
first_order_size = 0,
|
|
358
|
+
gap = 10,
|
|
359
|
+
max_size = 0
|
|
360
|
+
}) {
|
|
361
|
+
this.minimum_size = minimum_size;
|
|
362
|
+
this.first_order_size = first_order_size;
|
|
363
|
+
this.focus = focus;
|
|
364
|
+
this.budget = budget;
|
|
365
|
+
this.percent_change = percent_change;
|
|
366
|
+
this.price_places = price_places;
|
|
367
|
+
this.decimal_places = decimal_places;
|
|
368
|
+
this.zone_risk = zone_risk;
|
|
369
|
+
this.fee = fee;
|
|
370
|
+
this.support = support;
|
|
371
|
+
this.risk_reward = risk_reward;
|
|
372
|
+
this.resistance = resistance;
|
|
373
|
+
this.risk_per_trade = risk_per_trade;
|
|
374
|
+
this.increase_size = increase_size;
|
|
375
|
+
this.additional_increase = additional_increase;
|
|
376
|
+
this.minimum_pnl = minimum_pnl;
|
|
377
|
+
this.take_profit = take_profit;
|
|
378
|
+
this.increase_position = increase_position;
|
|
379
|
+
this.gap = gap;
|
|
380
|
+
this.max_size = max_size;
|
|
381
|
+
}
|
|
382
|
+
build_entry({
|
|
383
|
+
current_price,
|
|
384
|
+
stop_loss,
|
|
385
|
+
pnl,
|
|
386
|
+
stop_percent,
|
|
387
|
+
kind = "long",
|
|
388
|
+
risk,
|
|
389
|
+
no_of_trades = 1,
|
|
390
|
+
take_profit
|
|
391
|
+
}) {
|
|
392
|
+
let _stop_loss = stop_loss;
|
|
393
|
+
if (!_stop_loss && stop_percent) {
|
|
394
|
+
_stop_loss = kind === "long" ? current_price * Math.pow(1 + stop_percent, -1) : current_price * Math.pow(1 + stop_percent, 1);
|
|
395
|
+
}
|
|
396
|
+
const percent_change = _stop_loss ? Math.max(current_price, _stop_loss) / Math.min(current_price, _stop_loss) - 1 : this.percent_change;
|
|
397
|
+
const _no_of_trades = no_of_trades || this.risk_reward;
|
|
398
|
+
let _resistance = current_price * Math.pow(1 + percent_change, 1);
|
|
399
|
+
const derivedConfig = {
|
|
400
|
+
...this,
|
|
401
|
+
percent_change,
|
|
402
|
+
focus: current_price,
|
|
403
|
+
resistance: _resistance,
|
|
404
|
+
risk_per_trade: risk / this.risk_reward,
|
|
405
|
+
minimum_pnl: pnl,
|
|
406
|
+
risk_reward: _no_of_trades,
|
|
407
|
+
take_profit: take_profit || this.take_profit,
|
|
408
|
+
support: kind === "long" ? _stop_loss : this.support
|
|
409
|
+
};
|
|
410
|
+
const instance = new Signal(derivedConfig);
|
|
411
|
+
if (kind === "short") {
|
|
412
|
+
}
|
|
413
|
+
let result = instance.get_bulk_trade_zones({ current_price, kind });
|
|
414
|
+
return result;
|
|
415
|
+
return result?.filter((x) => {
|
|
416
|
+
let pp = parseFloat(this.decimal_places.replace("%.", "").replace("f", ""));
|
|
417
|
+
if (pp < 3) {
|
|
418
|
+
return true;
|
|
419
|
+
}
|
|
420
|
+
if (kind === "long") {
|
|
421
|
+
return x.entry > x.stop + 0.5;
|
|
422
|
+
}
|
|
423
|
+
return x.entry + 0.5 < x.stop;
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
get risk() {
|
|
427
|
+
return this.budget * this.percent_change;
|
|
428
|
+
}
|
|
429
|
+
get min_trades() {
|
|
430
|
+
return parseInt(this.risk.toString());
|
|
431
|
+
}
|
|
432
|
+
get min_price() {
|
|
433
|
+
const number = this.price_places.replace("%.", "").replace("f", "");
|
|
434
|
+
return 1 * Math.pow(10, -parseInt(number));
|
|
435
|
+
}
|
|
436
|
+
build_opposite_order({
|
|
437
|
+
current_price,
|
|
438
|
+
kind = "long"
|
|
439
|
+
}) {
|
|
440
|
+
let _current_price = current_price;
|
|
441
|
+
if (kind === "long") {
|
|
442
|
+
_current_price = current_price * Math.pow(1 + this.percent_change, -1);
|
|
443
|
+
}
|
|
444
|
+
const result = this.special_build_orders({
|
|
445
|
+
current_price: _current_price,
|
|
446
|
+
kind
|
|
447
|
+
});
|
|
448
|
+
const first_price = result[result.length - 1].entry;
|
|
449
|
+
const stop = result[0].stop;
|
|
450
|
+
const instance = new Signal({ ...this, take_profit: stop });
|
|
451
|
+
const new_kind = kind === "long" ? "short" : "long";
|
|
452
|
+
return instance.build_orders({
|
|
453
|
+
current_price: first_price,
|
|
454
|
+
kind: new_kind
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
special_build_orders({
|
|
458
|
+
current_price,
|
|
459
|
+
kind = "long"
|
|
460
|
+
}) {
|
|
461
|
+
let orders = this.build_orders({ current_price, kind });
|
|
462
|
+
if (orders?.length > 1) {
|
|
463
|
+
orders = this.build_orders({ current_price: orders[1].entry, kind });
|
|
464
|
+
}
|
|
465
|
+
if (orders.length > 0) {
|
|
466
|
+
const new_kind = kind === "long" ? "short" : "long";
|
|
467
|
+
let opposite_order = this.build_orders({
|
|
468
|
+
current_price: orders[orders.length - 1].entry,
|
|
469
|
+
kind: new_kind
|
|
470
|
+
});
|
|
471
|
+
this.take_profit = opposite_order[0].stop;
|
|
472
|
+
orders = this.build_orders({
|
|
473
|
+
current_price: orders[orders.length - 1].entry,
|
|
474
|
+
kind
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
return orders;
|
|
478
|
+
}
|
|
479
|
+
build_orders({
|
|
480
|
+
current_price,
|
|
481
|
+
kind = "long",
|
|
482
|
+
limit = false,
|
|
483
|
+
replace_focus = false,
|
|
484
|
+
max_index = 0,
|
|
485
|
+
min_index = 2
|
|
486
|
+
}) {
|
|
487
|
+
const focus = this.focus;
|
|
488
|
+
if (replace_focus) {
|
|
489
|
+
this.focus = current_price;
|
|
490
|
+
}
|
|
491
|
+
const new_kind = kind === "long" ? "short" : "long";
|
|
492
|
+
const take_profit = this.take_profit;
|
|
493
|
+
this.take_profit = undefined;
|
|
494
|
+
let result = this.get_bulk_trade_zones({
|
|
495
|
+
current_price,
|
|
496
|
+
kind: new_kind,
|
|
497
|
+
limit
|
|
498
|
+
});
|
|
499
|
+
if (result?.length) {
|
|
500
|
+
let oppositeStop = result[0]["sell_price"];
|
|
501
|
+
let oppositeEntry = result[result.length - 1]["entry"];
|
|
502
|
+
let tradeLength = this.risk_reward + 1;
|
|
503
|
+
let percentChange = Math.abs(1 - Math.max(oppositeEntry, oppositeStop) / Math.min(oppositeEntry, oppositeStop)) / tradeLength;
|
|
504
|
+
let newTrades = [];
|
|
505
|
+
for (let x = 0;x < tradeLength; x++) {
|
|
506
|
+
newTrades.push(oppositeStop * Math.pow(1 + percentChange, x));
|
|
507
|
+
}
|
|
508
|
+
if (kind === "short") {
|
|
509
|
+
newTrades = [];
|
|
510
|
+
for (let x = 0;x < tradeLength; x++) {
|
|
511
|
+
newTrades.push(oppositeStop * Math.pow(1 + percentChange, x * -1));
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
this.take_profit = take_profit;
|
|
515
|
+
newTrades = newTrades.map((r) => this.to_f(r));
|
|
516
|
+
if (kind === "long") {
|
|
517
|
+
if (newTrades[1] > current_price) {
|
|
518
|
+
const start = newTrades[0];
|
|
519
|
+
newTrades = [];
|
|
520
|
+
for (let x = 0;x < tradeLength; x++) {
|
|
521
|
+
newTrades.push(start * Math.pow(1 + percentChange, x * -1));
|
|
522
|
+
}
|
|
523
|
+
newTrades.sort();
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
const newR = this.process_orders({
|
|
527
|
+
current_price,
|
|
528
|
+
stop_loss: newTrades[0],
|
|
529
|
+
trade_zones: newTrades,
|
|
530
|
+
kind
|
|
531
|
+
});
|
|
532
|
+
return newR;
|
|
533
|
+
}
|
|
534
|
+
this.focus = focus;
|
|
535
|
+
return result;
|
|
536
|
+
}
|
|
537
|
+
build_orders_old({
|
|
538
|
+
current_price,
|
|
539
|
+
kind = "long",
|
|
540
|
+
limit = false,
|
|
541
|
+
replace_focus = false,
|
|
542
|
+
max_index = 0,
|
|
543
|
+
min_index = 2
|
|
544
|
+
}) {
|
|
545
|
+
const focus = this.focus;
|
|
546
|
+
if (replace_focus) {
|
|
547
|
+
this.focus = current_price;
|
|
548
|
+
}
|
|
549
|
+
const result = this.get_bulk_trade_zones({ current_price, kind, limit });
|
|
550
|
+
if (result?.length) {
|
|
551
|
+
let next_focus;
|
|
552
|
+
if (kind == "long") {
|
|
553
|
+
next_focus = current_price * (1 + this.percent_change);
|
|
554
|
+
} else {
|
|
555
|
+
next_focus = current_price * Math.pow(1 + this.percent_change, -1);
|
|
556
|
+
}
|
|
557
|
+
let new_result = this.get_bulk_trade_zones({
|
|
558
|
+
current_price: next_focus,
|
|
559
|
+
kind,
|
|
560
|
+
limit
|
|
561
|
+
});
|
|
562
|
+
if (new_result?.length) {
|
|
563
|
+
for (let i of result) {
|
|
564
|
+
let condition = kind === "long" ? (a, b) => a >= b : (a, b) => a <= b;
|
|
565
|
+
let potentials = new_result.filter((x) => condition(x["entry"], i["risk_sell"])).map((x) => x["entry"]);
|
|
566
|
+
if (potentials.length && max_index) {
|
|
567
|
+
if (kind === "long") {
|
|
568
|
+
console.log("slice: ", potentials.slice(0, max_index));
|
|
569
|
+
i["risk_sell"] = Math.max(...potentials.slice(0, max_index));
|
|
570
|
+
} else {
|
|
571
|
+
i["risk_sell"] = Math.min(...potentials.slice(0, max_index));
|
|
572
|
+
}
|
|
573
|
+
i["pnl"] = this.to_df(determine_pnl2(i["entry"], i["risk_sell"], i["quantity"], kind));
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
this.focus = focus;
|
|
579
|
+
return result;
|
|
580
|
+
}
|
|
581
|
+
get_bulk_trade_zones({
|
|
582
|
+
current_price,
|
|
583
|
+
kind = "long",
|
|
584
|
+
limit = false
|
|
585
|
+
}) {
|
|
586
|
+
const futures = this.get_future_zones({ current_price, kind });
|
|
587
|
+
const original = this.zone_risk;
|
|
588
|
+
if (futures) {
|
|
589
|
+
const values = futures;
|
|
590
|
+
if (values) {
|
|
591
|
+
let trade_zones = values.sort();
|
|
592
|
+
if (this.resistance) {
|
|
593
|
+
trade_zones = trade_zones.filter((x) => this.resistance ? x <= this.resistance : true);
|
|
594
|
+
if (kind === "short") {
|
|
595
|
+
trade_zones = trade_zones.sort((a, b) => b - a);
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
if (trade_zones.length > 0) {
|
|
599
|
+
const stop_loss = trade_zones[0];
|
|
600
|
+
const result = this.process_orders({
|
|
601
|
+
current_price,
|
|
602
|
+
stop_loss,
|
|
603
|
+
trade_zones,
|
|
604
|
+
kind
|
|
605
|
+
});
|
|
606
|
+
if (!result.length) {
|
|
607
|
+
if (kind === "long") {
|
|
608
|
+
let m_z = this.get_margin_range(futures[0]);
|
|
609
|
+
if (m_z && m_z[0] < current_price && current_price !== m_z[1]) {
|
|
610
|
+
return this.get_bulk_trade_zones({
|
|
611
|
+
current_price: m_z[1],
|
|
612
|
+
kind,
|
|
613
|
+
limit
|
|
614
|
+
});
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
this.zone_risk = original;
|
|
619
|
+
return result;
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
this.zone_risk = original;
|
|
624
|
+
}
|
|
625
|
+
get_future_zones({
|
|
626
|
+
current_price,
|
|
627
|
+
kind = "long",
|
|
628
|
+
raw
|
|
629
|
+
}) {
|
|
630
|
+
if (raw) {
|
|
631
|
+
}
|
|
632
|
+
const margin_range = this.get_margin_range(current_price, kind);
|
|
633
|
+
let margin_zones = this.get_margin_zones({ current_price });
|
|
634
|
+
let remaining_zones = margin_zones.filter((x) => JSON.stringify(x) != JSON.stringify(margin_range));
|
|
635
|
+
if (margin_range) {
|
|
636
|
+
const difference = Math.abs(margin_range[0] - margin_range[1]);
|
|
637
|
+
const spread = to_f2(difference / this.risk_reward, this.price_places);
|
|
638
|
+
let entries;
|
|
639
|
+
const percent_change = this.percent_change / this.risk_reward;
|
|
640
|
+
if (kind === "long") {
|
|
641
|
+
entries = Array.from({ length: Math.floor(this.risk_reward) + 1 }, (_, x) => to_f2(margin_range[1] - spread * x, this.price_places));
|
|
642
|
+
} else {
|
|
643
|
+
entries = Array.from({ length: Math.floor(this.risk_reward) + 1 }, (_, x) => to_f2(margin_range[1] * Math.pow(1 + percent_change, x), this.price_places));
|
|
644
|
+
}
|
|
645
|
+
if (Math.min(...entries) < this.to_f(current_price) && this.to_f(current_price) < Math.max(...entries)) {
|
|
646
|
+
return entries.sort((a, b) => a - b);
|
|
647
|
+
}
|
|
648
|
+
if (remaining_zones.length > 0) {
|
|
649
|
+
let new_range = remaining_zones[0];
|
|
650
|
+
let entries2 = [];
|
|
651
|
+
let x = 0;
|
|
652
|
+
if (new_range) {
|
|
653
|
+
while (entries2.length < this.risk_reward + 1) {
|
|
654
|
+
if (kind === "long") {
|
|
655
|
+
let value2 = this.to_f(new_range[1] - spread * x);
|
|
656
|
+
if (value2 <= current_price) {
|
|
657
|
+
entries2.push(value2);
|
|
658
|
+
}
|
|
659
|
+
} else {
|
|
660
|
+
let value2 = this.to_f(new_range[1] * Math.pow(1 + percent_change, x));
|
|
661
|
+
if (value2 >= current_price) {
|
|
662
|
+
entries2.push(value2);
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
x += 1;
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
return entries2.sort((a, b) => a - b);
|
|
669
|
+
}
|
|
670
|
+
if (remaining_zones.length === 0 && this.to_f(current_price) <= Math.min(...entries)) {
|
|
671
|
+
const next_focus = margin_range[0] * Math.pow(1 + this.percent_change, -1);
|
|
672
|
+
let entries2 = [];
|
|
673
|
+
let x = 0;
|
|
674
|
+
while (entries2.length < this.risk_reward + 1) {
|
|
675
|
+
if (kind === "long") {
|
|
676
|
+
let value2 = this.to_f(next_focus - spread * x);
|
|
677
|
+
if (value2 <= this.to_f(current_price)) {
|
|
678
|
+
entries2.push(value2);
|
|
679
|
+
}
|
|
680
|
+
} else {
|
|
681
|
+
let value2 = this.to_f(next_focus * Math.pow(1 + percent_change, x));
|
|
682
|
+
if (value2 >= this.to_f(current_price)) {
|
|
683
|
+
entries2.push(value2);
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
x += 1;
|
|
687
|
+
}
|
|
688
|
+
return entries2.sort((a, b) => a - b);
|
|
689
|
+
}
|
|
690
|
+
return entries.sort((a, b) => a - b);
|
|
691
|
+
}
|
|
692
|
+
return [];
|
|
693
|
+
}
|
|
694
|
+
to_f(value2, places) {
|
|
695
|
+
return to_f2(value2, places || this.price_places);
|
|
696
|
+
}
|
|
697
|
+
get_margin_zones({
|
|
698
|
+
current_price,
|
|
699
|
+
kind = "long"
|
|
700
|
+
}) {
|
|
701
|
+
if (this.support && kind === "long") {
|
|
702
|
+
let result = [];
|
|
703
|
+
let start = current_price;
|
|
704
|
+
let counter = 0;
|
|
705
|
+
while (start > this.support) {
|
|
706
|
+
let v = this.get_margin_range(start);
|
|
707
|
+
if (v) {
|
|
708
|
+
result.push(v);
|
|
709
|
+
start = v[0] - this.min_price;
|
|
710
|
+
counter += 1;
|
|
711
|
+
}
|
|
712
|
+
if (counter > 10) {
|
|
713
|
+
break;
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
return result;
|
|
717
|
+
}
|
|
718
|
+
if (this.resistance) {
|
|
719
|
+
let result = [];
|
|
720
|
+
let start = current_price;
|
|
721
|
+
let counter = 0;
|
|
722
|
+
while (start < this.resistance) {
|
|
723
|
+
let v = this.get_margin_range(start);
|
|
724
|
+
if (v) {
|
|
725
|
+
result.push(v);
|
|
726
|
+
start = v[1] + this.min_price;
|
|
727
|
+
}
|
|
728
|
+
if (counter > 10) {
|
|
729
|
+
break;
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
return result;
|
|
733
|
+
}
|
|
734
|
+
return [this.get_margin_range(current_price)];
|
|
735
|
+
}
|
|
736
|
+
get_margin_range(current_price, kind = "long") {
|
|
737
|
+
const diff = -this.min_price;
|
|
738
|
+
const zones = _get_zones({
|
|
739
|
+
current_price: current_price + diff,
|
|
740
|
+
focus: this.focus,
|
|
741
|
+
percent_change: this.percent_change,
|
|
742
|
+
places: this.price_places
|
|
743
|
+
}) || [];
|
|
744
|
+
const top_zones = [];
|
|
745
|
+
for (const i of zones) {
|
|
746
|
+
if (i < 0.00000001) {
|
|
747
|
+
break;
|
|
748
|
+
}
|
|
749
|
+
top_zones.push(this.to_f(i));
|
|
750
|
+
}
|
|
751
|
+
if (top_zones.length > 0) {
|
|
752
|
+
const result = top_zones[top_zones.length - 1];
|
|
753
|
+
return [this.to_f(result), this.to_f(result * (1 + this.percent_change))];
|
|
754
|
+
}
|
|
755
|
+
return null;
|
|
756
|
+
}
|
|
757
|
+
process_orders({
|
|
758
|
+
current_price,
|
|
759
|
+
stop_loss,
|
|
760
|
+
trade_zones,
|
|
761
|
+
kind = "long"
|
|
762
|
+
}) {
|
|
763
|
+
const number_of_orders = trade_zones.slice(1).length;
|
|
764
|
+
let take_profit = stop_loss * (1 + 2 * this.percent_change);
|
|
765
|
+
if (kind === "short") {
|
|
766
|
+
take_profit = stop_loss * Math.pow(1 + 2 * this.percent_change, -1);
|
|
767
|
+
}
|
|
768
|
+
if (this.take_profit) {
|
|
769
|
+
take_profit = this.take_profit;
|
|
770
|
+
}
|
|
771
|
+
if (number_of_orders > 0) {
|
|
772
|
+
const risk_per_trade = this.get_risk_per_trade(number_of_orders);
|
|
773
|
+
let limit_orders = trade_zones.slice(1).filter((x) => x <= this.to_f(current_price));
|
|
774
|
+
let market_orders = trade_zones.slice(1).filter((x) => x > this.to_f(current_price));
|
|
775
|
+
if (kind === "short") {
|
|
776
|
+
limit_orders = trade_zones.slice(1).filter((x) => x >= this.to_f(current_price));
|
|
777
|
+
market_orders = trade_zones.slice(1).filter((x) => x < this.to_f(current_price));
|
|
778
|
+
}
|
|
779
|
+
if (market_orders.length === 1) {
|
|
780
|
+
limit_orders = limit_orders.concat(market_orders);
|
|
781
|
+
market_orders = [];
|
|
782
|
+
}
|
|
783
|
+
const increase_position = Boolean(this.support) && this.increase_position;
|
|
784
|
+
const market_trades = limit_orders.length > 0 ? market_orders.map((x, i) => {
|
|
785
|
+
const defaultStopLoss = i === 0 ? limit_orders[limit_orders.length - 1] : market_orders[i - 1];
|
|
786
|
+
const y = this.build_trade_dict({
|
|
787
|
+
entry: x,
|
|
788
|
+
stop: increase_position ? this.support : defaultStopLoss,
|
|
789
|
+
risk: risk_per_trade,
|
|
790
|
+
arr: market_orders,
|
|
791
|
+
index: i,
|
|
792
|
+
kind,
|
|
793
|
+
start: market_orders.length + limit_orders.length,
|
|
794
|
+
take_profit
|
|
795
|
+
});
|
|
796
|
+
return y;
|
|
797
|
+
}).filter((y) => y) : [];
|
|
798
|
+
let total_incurred_market_fees = 0;
|
|
799
|
+
if (market_trades.length > 0) {
|
|
800
|
+
let first = market_trades[0];
|
|
801
|
+
if (first) {
|
|
802
|
+
total_incurred_market_fees += first.incurred;
|
|
803
|
+
total_incurred_market_fees += first.fee;
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
const default_gap = this.gap;
|
|
807
|
+
const gap_pairs = createGapPairs(limit_orders, default_gap);
|
|
808
|
+
const limit_trades = (limit_orders.map((x, i) => {
|
|
809
|
+
let _base = limit_orders[i - 1];
|
|
810
|
+
let _stops = gap_pairs.find((o) => o[0] === x);
|
|
811
|
+
if (!_stops) {
|
|
812
|
+
return;
|
|
813
|
+
}
|
|
814
|
+
if (_stops) {
|
|
815
|
+
_base = _stops[1];
|
|
816
|
+
}
|
|
817
|
+
const defaultStopLoss = i === 0 ? stop_loss : _base;
|
|
818
|
+
const new_stop = kind === "long" ? this.support : stop_loss;
|
|
819
|
+
const y = this.build_trade_dict({
|
|
820
|
+
entry: x,
|
|
821
|
+
stop: (this.increase_position ? new_stop : defaultStopLoss) || defaultStopLoss,
|
|
822
|
+
risk: risk_per_trade,
|
|
823
|
+
arr: limit_orders,
|
|
824
|
+
index: i,
|
|
825
|
+
new_fees: total_incurred_market_fees,
|
|
826
|
+
kind,
|
|
827
|
+
start: market_orders.length + limit_orders.length,
|
|
828
|
+
take_profit
|
|
829
|
+
});
|
|
830
|
+
if (y) {
|
|
831
|
+
y.new_stop = defaultStopLoss;
|
|
832
|
+
}
|
|
833
|
+
return y !== null ? y : undefined;
|
|
834
|
+
}) || []).filter((y) => y !== undefined).filter((y) => {
|
|
835
|
+
const min_options = [0.001, 0.002, 0.003];
|
|
836
|
+
if (min_options.includes(this.minimum_size)) {
|
|
837
|
+
return y.quantity <= 0.03;
|
|
838
|
+
}
|
|
839
|
+
return true;
|
|
840
|
+
});
|
|
841
|
+
let total_orders = limit_trades.concat(market_trades);
|
|
842
|
+
if (kind === "short") {
|
|
843
|
+
}
|
|
844
|
+
if (this.minimum_size && total_orders.length > 0) {
|
|
845
|
+
let payload = total_orders;
|
|
846
|
+
let greater_than_min_size = total_orders.filter((o) => o ? o.quantity >= this.minimum_size : true);
|
|
847
|
+
let less_than_min_size = total_orders.filter((o) => o ? o.quantity < this.minimum_size : true) || total_orders;
|
|
848
|
+
less_than_min_size = groupIntoPairsWithSumLessThan(less_than_min_size, this.minimum_size, "quantity", this.first_order_size);
|
|
849
|
+
less_than_min_size = less_than_min_size.map((q, i) => {
|
|
850
|
+
let avg_entry = determine_average_entry_and_size(q.map((o) => ({
|
|
851
|
+
price: o.entry,
|
|
852
|
+
quantity: o.quantity
|
|
853
|
+
})), this.decimal_places, this.price_places);
|
|
854
|
+
let candidate = q[0];
|
|
855
|
+
candidate.entry = avg_entry.price;
|
|
856
|
+
candidate.quantity = avg_entry.quantity;
|
|
857
|
+
return candidate;
|
|
858
|
+
});
|
|
859
|
+
less_than_min_size = less_than_min_size.map((q, i) => {
|
|
860
|
+
let new_stop = q.new_stop;
|
|
861
|
+
if (i > 0) {
|
|
862
|
+
new_stop = less_than_min_size[i - 1].entry;
|
|
863
|
+
}
|
|
864
|
+
return {
|
|
865
|
+
...q,
|
|
866
|
+
new_stop
|
|
867
|
+
};
|
|
868
|
+
});
|
|
869
|
+
if (greater_than_min_size.length !== less_than_min_size.length) {
|
|
870
|
+
payload = greater_than_min_size.concat(less_than_min_size);
|
|
871
|
+
}
|
|
872
|
+
return payload;
|
|
873
|
+
}
|
|
874
|
+
return total_orders;
|
|
875
|
+
}
|
|
876
|
+
return [];
|
|
877
|
+
}
|
|
878
|
+
get_risk_per_trade(number_of_orders) {
|
|
879
|
+
if (this.risk_per_trade) {
|
|
880
|
+
return this.risk_per_trade;
|
|
881
|
+
}
|
|
882
|
+
return this.zone_risk / number_of_orders;
|
|
883
|
+
}
|
|
884
|
+
build_trade_dict({
|
|
885
|
+
entry,
|
|
886
|
+
stop,
|
|
887
|
+
risk,
|
|
888
|
+
arr,
|
|
889
|
+
index,
|
|
890
|
+
new_fees = 0,
|
|
891
|
+
kind = "long",
|
|
892
|
+
start = 0,
|
|
893
|
+
take_profit
|
|
894
|
+
}) {
|
|
895
|
+
const considered = arr.map((x, i) => i).filter((i) => i > index);
|
|
896
|
+
const with_quantity = considered.map((x) => {
|
|
897
|
+
const q = determine_position_size2({
|
|
898
|
+
entry: arr[x],
|
|
899
|
+
stop: arr[x - 1],
|
|
900
|
+
budget: risk,
|
|
901
|
+
places: this.decimal_places
|
|
902
|
+
});
|
|
903
|
+
if (!q) {
|
|
904
|
+
return;
|
|
905
|
+
}
|
|
906
|
+
if (this.minimum_size) {
|
|
907
|
+
if (q < this.minimum_size) {
|
|
908
|
+
return;
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
return { quantity: q, entry: arr[x] };
|
|
912
|
+
}).filter((x) => x);
|
|
913
|
+
if (this.increase_size) {
|
|
914
|
+
const arr_length = with_quantity.length;
|
|
915
|
+
with_quantity.forEach((x, i) => {
|
|
916
|
+
if (x) {
|
|
917
|
+
x.quantity = x.quantity * (arr_length - i);
|
|
918
|
+
}
|
|
919
|
+
});
|
|
920
|
+
}
|
|
921
|
+
const fees = with_quantity.map((x) => {
|
|
922
|
+
return this.to_df(this.fee * x.quantity * x.entry);
|
|
923
|
+
});
|
|
924
|
+
const previous_risks = with_quantity.map((x) => {
|
|
925
|
+
return this.to_df(risk);
|
|
926
|
+
});
|
|
927
|
+
const multiplier = start - index;
|
|
928
|
+
const incurred_fees = fees.reduce((a, b) => a + b, 0) + previous_risks.reduce((a, b) => a + b, 0);
|
|
929
|
+
if (index === 0) {
|
|
930
|
+
}
|
|
931
|
+
let quantity = determine_position_size2({
|
|
932
|
+
entry,
|
|
933
|
+
stop,
|
|
934
|
+
budget: risk,
|
|
935
|
+
places: this.decimal_places,
|
|
936
|
+
min_size: this.minimum_size
|
|
937
|
+
});
|
|
938
|
+
if (!quantity) {
|
|
939
|
+
return;
|
|
940
|
+
}
|
|
941
|
+
if (this.increase_size) {
|
|
942
|
+
quantity = quantity * multiplier;
|
|
943
|
+
const new_risk = determine_pnl2(entry, stop, quantity, kind);
|
|
944
|
+
risk = Math.abs(new_risk);
|
|
945
|
+
}
|
|
946
|
+
const fee = this.to_df(this.fee * quantity * entry);
|
|
947
|
+
const increment = Math.abs(arr.length - (index + 1));
|
|
948
|
+
let pnl = this.to_df(risk) * (this.risk_reward + increment);
|
|
949
|
+
if (this.minimum_pnl) {
|
|
950
|
+
pnl = this.minimum_pnl + fee;
|
|
951
|
+
}
|
|
952
|
+
let sell_price = determine_close_price2({ entry, pnl, quantity, kind });
|
|
953
|
+
if (take_profit && !this.minimum_pnl) {
|
|
954
|
+
sell_price = take_profit;
|
|
955
|
+
pnl = this.to_df(determine_pnl2(entry, sell_price, quantity, kind));
|
|
956
|
+
pnl = pnl + fee;
|
|
957
|
+
sell_price = determine_close_price2({ entry, pnl, quantity, kind });
|
|
958
|
+
}
|
|
959
|
+
let risk_sell = sell_price;
|
|
960
|
+
return {
|
|
961
|
+
entry,
|
|
962
|
+
risk: this.to_df(risk),
|
|
963
|
+
quantity,
|
|
964
|
+
sell_price: this.to_f(sell_price),
|
|
965
|
+
risk_sell: this.to_f(risk_sell),
|
|
966
|
+
stop,
|
|
967
|
+
pnl,
|
|
968
|
+
fee,
|
|
969
|
+
net: this.to_df(pnl - fee),
|
|
970
|
+
incurred: this.to_df(incurred_fees + new_fees),
|
|
971
|
+
stop_percent: this.to_df(Math.abs(entry - stop) / entry)
|
|
972
|
+
};
|
|
973
|
+
}
|
|
974
|
+
to_df(currentPrice, places = "%.3f") {
|
|
975
|
+
return to_f2(currentPrice, places);
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
// src/helpers/shared.ts
|
|
980
|
+
function buildConfig(app_config, {
|
|
981
|
+
take_profit,
|
|
982
|
+
entry,
|
|
983
|
+
stop,
|
|
984
|
+
raw_instance,
|
|
985
|
+
risk,
|
|
986
|
+
no_of_trades,
|
|
987
|
+
min_profit = 0,
|
|
988
|
+
risk_reward,
|
|
989
|
+
kind,
|
|
990
|
+
increase,
|
|
991
|
+
gap,
|
|
992
|
+
rr = 1,
|
|
993
|
+
price_places = "%.1f",
|
|
994
|
+
decimal_places = "%.3f"
|
|
995
|
+
}) {
|
|
996
|
+
let fee = app_config.fee / 100;
|
|
997
|
+
let working_risk = risk || app_config.risk_per_trade;
|
|
998
|
+
let trade_no = no_of_trades || app_config.risk_reward;
|
|
999
|
+
const config = {
|
|
1000
|
+
focus: app_config.focus,
|
|
1001
|
+
fee,
|
|
1002
|
+
budget: app_config.budget,
|
|
1003
|
+
risk_reward: risk_reward || trade_no,
|
|
1004
|
+
support: app_config.support,
|
|
1005
|
+
resistance: app_config.resistance,
|
|
1006
|
+
price_places: app_config.price_places || price_places,
|
|
1007
|
+
decimal_places,
|
|
1008
|
+
percent_change: app_config.percent_change / app_config.tradeSplit,
|
|
1009
|
+
risk_per_trade: working_risk,
|
|
1010
|
+
take_profit: take_profit || app_config.take_profit,
|
|
1011
|
+
increase_position: increase,
|
|
1012
|
+
minimum_size: app_config.min_size,
|
|
1013
|
+
entry,
|
|
1014
|
+
stop,
|
|
1015
|
+
kind: app_config.kind,
|
|
1016
|
+
gap,
|
|
1017
|
+
min_profit: min_profit || app_config.min_profit,
|
|
1018
|
+
rr: rr || 1
|
|
1019
|
+
};
|
|
1020
|
+
const instance = new Signal(config);
|
|
1021
|
+
if (raw_instance) {
|
|
1022
|
+
return instance;
|
|
1023
|
+
}
|
|
1024
|
+
if (!stop) {
|
|
1025
|
+
return [];
|
|
1026
|
+
}
|
|
1027
|
+
const condition = (kind === "long" ? entry > app_config.support : entry >= app_config.support) && stop >= app_config.support * 0.999;
|
|
1028
|
+
if (kind === "short") {
|
|
1029
|
+
console.log("condition", condition, entry === stop);
|
|
1030
|
+
}
|
|
1031
|
+
console.log({ entry, support: app_config.support, stop });
|
|
1032
|
+
const result = entry === stop ? [] : condition ? instance.build_entry({
|
|
1033
|
+
current_price: entry,
|
|
1034
|
+
stop_loss: stop,
|
|
1035
|
+
risk: working_risk,
|
|
1036
|
+
kind: kind || app_config.kind,
|
|
1037
|
+
no_of_trades: trade_no
|
|
1038
|
+
}) || [] : [];
|
|
1039
|
+
return computeTotalAverageForEachTrade(result, config);
|
|
1040
|
+
}
|
|
1041
|
+
function buildAvg({
|
|
1042
|
+
_trades,
|
|
1043
|
+
kind
|
|
1044
|
+
}) {
|
|
1045
|
+
let avg = determine_average_entry_and_size(_trades?.map((r) => ({
|
|
1046
|
+
price: r.entry,
|
|
1047
|
+
quantity: r.quantity
|
|
1048
|
+
})) || []);
|
|
1049
|
+
const stop_prices = _trades.map((o) => o.stop);
|
|
1050
|
+
const stop_loss = kind === "long" ? Math.min(...stop_prices) : Math.max(...stop_prices);
|
|
1051
|
+
avg.pnl = pnl_default.determine_pnl(avg.price, stop_loss, avg.quantity, kind);
|
|
1052
|
+
return avg;
|
|
1053
|
+
}
|
|
1054
|
+
function sortedBuildConfig(app_config, options) {
|
|
1055
|
+
const sorted = buildConfig(app_config, options).sort((a, b) => app_config.kind === "long" ? a.entry - b.entry : b.entry - b.entry).filter((x) => {
|
|
1056
|
+
if (app_config.symbol === "BTCUSDT") {
|
|
1057
|
+
return x.quantity <= 0.03;
|
|
1058
|
+
}
|
|
1059
|
+
return true;
|
|
1060
|
+
});
|
|
1061
|
+
return sorted.map((k, i) => {
|
|
1062
|
+
const arrSet = sorted.slice(0, i + 1);
|
|
1063
|
+
const avg_values = determine_average_entry_and_size(arrSet.map((u) => ({ price: u.entry, quantity: u.quantity })), app_config.decimal_places, app_config.price_places);
|
|
1064
|
+
return {
|
|
1065
|
+
...k,
|
|
1066
|
+
reverse_avg_entry: avg_values.price,
|
|
1067
|
+
reverse_avg_quantity: avg_values.quantity
|
|
1068
|
+
};
|
|
1069
|
+
});
|
|
1070
|
+
}
|
|
1071
|
+
function get_app_config_and_max_size(config, payload) {
|
|
1072
|
+
const app_config = {
|
|
1073
|
+
kind: payload.kind,
|
|
1074
|
+
entry: payload.entry,
|
|
1075
|
+
stop: payload.stop,
|
|
1076
|
+
risk_per_trade: config.risk,
|
|
1077
|
+
risk_reward: config.risk_reward || 199,
|
|
1078
|
+
support: to_f2(config.support, config.price_places),
|
|
1079
|
+
resistance: to_f2(config.resistance, config.price_places),
|
|
1080
|
+
focus: payload.entry,
|
|
1081
|
+
fee: 0,
|
|
1082
|
+
percent_change: config.stop_percent / 100,
|
|
1083
|
+
tradeSplit: 1,
|
|
1084
|
+
gap: 1,
|
|
1085
|
+
min_size: config.min_size,
|
|
1086
|
+
budget: 0,
|
|
1087
|
+
price_places: config.price_places,
|
|
1088
|
+
decimal_places: config.decimal_places,
|
|
1089
|
+
min_profit: config.profit_percent * config.profit / 100
|
|
1090
|
+
};
|
|
1091
|
+
const initialResult = sortedBuildConfig(app_config, {
|
|
1092
|
+
entry: app_config.entry,
|
|
1093
|
+
stop: app_config.stop,
|
|
1094
|
+
kind: app_config.kind,
|
|
1095
|
+
risk: app_config.risk_per_trade,
|
|
1096
|
+
risk_reward: app_config.risk_reward,
|
|
1097
|
+
increase: true,
|
|
1098
|
+
gap: app_config.gap,
|
|
1099
|
+
price_places: app_config.price_places,
|
|
1100
|
+
decimal_places: app_config.decimal_places
|
|
1101
|
+
});
|
|
1102
|
+
const max_size = initialResult[0]?.avg_size;
|
|
1103
|
+
const last_value = initialResult[0];
|
|
1104
|
+
const entries = initialResult.map((x) => ({
|
|
1105
|
+
entry: x.entry,
|
|
1106
|
+
avg_entry: x.avg_entry,
|
|
1107
|
+
avg_size: x.avg_size,
|
|
1108
|
+
neg_pnl: x.neg_pnl,
|
|
1109
|
+
quantity: x.quantity
|
|
1110
|
+
}));
|
|
1111
|
+
return {
|
|
1112
|
+
app_config,
|
|
1113
|
+
max_size,
|
|
1114
|
+
last_value,
|
|
1115
|
+
entries
|
|
1116
|
+
};
|
|
1117
|
+
}
|
|
1118
|
+
function buildAppConfig(config, payload) {
|
|
1119
|
+
const { app_config, max_size, last_value, entries } = get_app_config_and_max_size({
|
|
1120
|
+
...config,
|
|
1121
|
+
risk: payload.risk,
|
|
1122
|
+
profit: payload.profit || 500,
|
|
1123
|
+
risk_reward: payload.risk_reward,
|
|
1124
|
+
accounts: [],
|
|
1125
|
+
reduce_percent: 90,
|
|
1126
|
+
reverse_factor: 1,
|
|
1127
|
+
profit_percent: 0,
|
|
1128
|
+
kind: payload.entry > payload.stop ? "long" : "short",
|
|
1129
|
+
symbol: payload.symbol
|
|
1130
|
+
}, {
|
|
1131
|
+
entry: payload.entry,
|
|
1132
|
+
stop: payload.stop,
|
|
1133
|
+
kind: payload.entry > payload.stop ? "long" : "short"
|
|
1134
|
+
});
|
|
1135
|
+
app_config.max_size = max_size;
|
|
1136
|
+
app_config.entry = payload.entry || app_config.entry;
|
|
1137
|
+
app_config.stop = payload.stop || app_config.stop;
|
|
1138
|
+
app_config.last_value = last_value;
|
|
1139
|
+
app_config.entries = entries;
|
|
1140
|
+
return app_config;
|
|
1141
|
+
}
|
|
1142
|
+
function getOptimumStopAndRisk(app_config, params) {
|
|
1143
|
+
const { max_size, target_stop } = params;
|
|
1144
|
+
const isLong = app_config.kind === "long";
|
|
1145
|
+
const stopRange = Math.abs(app_config.entry - target_stop) * 0.5;
|
|
1146
|
+
let low_stop = isLong ? target_stop - stopRange : Math.max(target_stop - stopRange, app_config.entry);
|
|
1147
|
+
let high_stop = isLong ? Math.min(target_stop + stopRange, app_config.entry) : target_stop + stopRange;
|
|
1148
|
+
let optimal_stop = target_stop;
|
|
1149
|
+
let best_stop_result = null;
|
|
1150
|
+
let best_entry_diff = Infinity;
|
|
1151
|
+
console.log(`Finding optimal stop for ${isLong ? "LONG" : "SHORT"} position. Target: ${target_stop}, Search range: ${low_stop} to ${high_stop}`);
|
|
1152
|
+
let iterations = 0;
|
|
1153
|
+
const MAX_ITERATIONS = 50;
|
|
1154
|
+
while (Math.abs(high_stop - low_stop) > 0.1 && iterations < MAX_ITERATIONS) {
|
|
1155
|
+
iterations++;
|
|
1156
|
+
const mid_stop = (low_stop + high_stop) / 2;
|
|
1157
|
+
const result = sortedBuildConfig(app_config, {
|
|
1158
|
+
entry: app_config.entry,
|
|
1159
|
+
stop: mid_stop,
|
|
1160
|
+
kind: app_config.kind,
|
|
1161
|
+
risk: app_config.risk_per_trade,
|
|
1162
|
+
risk_reward: app_config.risk_reward,
|
|
1163
|
+
increase: true,
|
|
1164
|
+
gap: app_config.gap,
|
|
1165
|
+
price_places: app_config.price_places,
|
|
1166
|
+
decimal_places: app_config.decimal_places
|
|
1167
|
+
});
|
|
1168
|
+
if (result.length === 0) {
|
|
1169
|
+
if (isLong) {
|
|
1170
|
+
low_stop = mid_stop;
|
|
1171
|
+
} else {
|
|
1172
|
+
high_stop = mid_stop;
|
|
1173
|
+
}
|
|
1174
|
+
continue;
|
|
1175
|
+
}
|
|
1176
|
+
const first_entry = result[0].entry;
|
|
1177
|
+
const entry_diff = Math.abs(first_entry - target_stop);
|
|
1178
|
+
console.log(`Stop: ${mid_stop.toFixed(2)}, First Entry: ${first_entry.toFixed(2)}, Diff: ${entry_diff.toFixed(2)}`);
|
|
1179
|
+
if (entry_diff < best_entry_diff) {
|
|
1180
|
+
best_entry_diff = entry_diff;
|
|
1181
|
+
optimal_stop = mid_stop;
|
|
1182
|
+
best_stop_result = result;
|
|
1183
|
+
}
|
|
1184
|
+
if (first_entry < target_stop) {
|
|
1185
|
+
if (isLong) {
|
|
1186
|
+
low_stop = mid_stop;
|
|
1187
|
+
} else {
|
|
1188
|
+
low_stop = mid_stop;
|
|
1189
|
+
}
|
|
1190
|
+
} else {
|
|
1191
|
+
if (isLong) {
|
|
1192
|
+
high_stop = mid_stop;
|
|
1193
|
+
} else {
|
|
1194
|
+
high_stop = mid_stop;
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
if (entry_diff < 1)
|
|
1198
|
+
break;
|
|
1199
|
+
}
|
|
1200
|
+
console.log(`Found optimal stop: ${optimal_stop.toFixed(2)} that gives first_entry: ${best_stop_result?.[0]?.entry?.toFixed(2)}, difference: ${best_entry_diff.toFixed(2)} after ${iterations} iterations`);
|
|
1201
|
+
let low_risk = 10;
|
|
1202
|
+
let high_risk = params.highest_risk || 2000;
|
|
1203
|
+
let optimal_risk = app_config.risk_per_trade;
|
|
1204
|
+
let best_size_result = best_stop_result;
|
|
1205
|
+
let best_size_diff = Infinity;
|
|
1206
|
+
console.log(`Finding optimal risk_per_trade for size target: ${max_size} (never exceeding it)`);
|
|
1207
|
+
iterations = 0;
|
|
1208
|
+
while (Math.abs(high_risk - low_risk) > 0.1 && iterations < MAX_ITERATIONS) {
|
|
1209
|
+
iterations++;
|
|
1210
|
+
const mid_risk = (low_risk + high_risk) / 2;
|
|
1211
|
+
const result = sortedBuildConfig(app_config, {
|
|
1212
|
+
entry: app_config.entry,
|
|
1213
|
+
stop: optimal_stop,
|
|
1214
|
+
kind: app_config.kind,
|
|
1215
|
+
risk: mid_risk,
|
|
1216
|
+
risk_reward: app_config.risk_reward,
|
|
1217
|
+
increase: true,
|
|
1218
|
+
gap: app_config.gap,
|
|
1219
|
+
price_places: app_config.price_places,
|
|
1220
|
+
decimal_places: app_config.decimal_places
|
|
1221
|
+
});
|
|
1222
|
+
if (result.length === 0) {
|
|
1223
|
+
high_risk = mid_risk;
|
|
1224
|
+
continue;
|
|
1225
|
+
}
|
|
1226
|
+
const first_entry = result[0];
|
|
1227
|
+
const avg_size = first_entry.avg_size;
|
|
1228
|
+
console.log(`Risk: ${mid_risk.toFixed(2)}, Avg Size: ${avg_size.toFixed(4)}, Target: ${max_size}`);
|
|
1229
|
+
if (avg_size <= max_size) {
|
|
1230
|
+
const size_diff = max_size - avg_size;
|
|
1231
|
+
if (size_diff < best_size_diff) {
|
|
1232
|
+
best_size_diff = size_diff;
|
|
1233
|
+
optimal_risk = mid_risk;
|
|
1234
|
+
best_size_result = result;
|
|
1235
|
+
}
|
|
1236
|
+
low_risk = mid_risk;
|
|
1237
|
+
} else {
|
|
1238
|
+
high_risk = mid_risk;
|
|
1239
|
+
}
|
|
1240
|
+
if (best_size_diff < 0.001)
|
|
1241
|
+
break;
|
|
1242
|
+
}
|
|
1243
|
+
console.log(`Found optimal risk_per_trade: ${optimal_risk.toFixed(2)} that gives avg_size: ${best_size_result?.[0]?.avg_size?.toFixed(4)}, difference: ${best_size_diff.toFixed(4)} after ${iterations} iterations`);
|
|
1244
|
+
let final_risk = optimal_risk;
|
|
1245
|
+
let final_result = best_size_result;
|
|
1246
|
+
if (best_size_result?.[0]?.avg_size < max_size) {
|
|
1247
|
+
console.log(`Current avg_size (${best_size_result?.[0].avg_size.toFixed(4)}) is less than target (${max_size}). Increasing risk to reach target...`);
|
|
1248
|
+
let current_risk = optimal_risk;
|
|
1249
|
+
const risk_increment = 5;
|
|
1250
|
+
iterations = 0;
|
|
1251
|
+
while (iterations < MAX_ITERATIONS) {
|
|
1252
|
+
iterations++;
|
|
1253
|
+
current_risk += risk_increment;
|
|
1254
|
+
if (current_risk > 5000)
|
|
1255
|
+
break;
|
|
1256
|
+
const result = sortedBuildConfig(app_config, {
|
|
1257
|
+
entry: app_config.entry,
|
|
1258
|
+
stop: optimal_stop,
|
|
1259
|
+
kind: app_config.kind,
|
|
1260
|
+
risk: current_risk,
|
|
1261
|
+
risk_reward: app_config.risk_reward,
|
|
1262
|
+
increase: true,
|
|
1263
|
+
gap: app_config.gap,
|
|
1264
|
+
price_places: app_config.price_places,
|
|
1265
|
+
decimal_places: app_config.decimal_places
|
|
1266
|
+
});
|
|
1267
|
+
if (result.length === 0)
|
|
1268
|
+
continue;
|
|
1269
|
+
const avg_size = result[0].avg_size;
|
|
1270
|
+
console.log(`Increased Risk: ${current_risk.toFixed(2)}, New Avg Size: ${avg_size.toFixed(4)}`);
|
|
1271
|
+
if (avg_size >= max_size) {
|
|
1272
|
+
final_risk = current_risk;
|
|
1273
|
+
final_result = result;
|
|
1274
|
+
console.log(`Target size reached! Final risk: ${final_risk.toFixed(2)}, Final avg_size: ${avg_size.toFixed(4)}`);
|
|
1275
|
+
break;
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
return {
|
|
1280
|
+
optimal_stop: to_f2(optimal_stop, app_config.price_places),
|
|
1281
|
+
optimal_risk: to_f2(final_risk, app_config.price_places),
|
|
1282
|
+
avg_size: final_result?.[0]?.avg_size || 0,
|
|
1283
|
+
avg_entry: final_result?.[0]?.avg_entry || 0,
|
|
1284
|
+
result: final_result,
|
|
1285
|
+
first_entry: final_result?.[0]?.entry || 0,
|
|
1286
|
+
neg_pnl: final_result?.[0]?.neg_pnl || 0,
|
|
1287
|
+
risk_reward: app_config.risk_reward,
|
|
1288
|
+
size_diff: Math.abs((final_result?.[0]?.avg_size || 0) - max_size),
|
|
1289
|
+
entry_diff: best_entry_diff
|
|
1290
|
+
};
|
|
1291
|
+
}
|
|
1292
|
+
function generate_config_params(app_config, payload) {
|
|
1293
|
+
const { result, ...optimum } = getOptimumStopAndRisk(app_config, {
|
|
1294
|
+
max_size: app_config.max_size,
|
|
1295
|
+
highest_risk: payload.risk * 2,
|
|
1296
|
+
target_stop: app_config.stop
|
|
1297
|
+
});
|
|
1298
|
+
return {
|
|
1299
|
+
entry: payload.entry,
|
|
1300
|
+
stop: optimum.optimal_stop,
|
|
1301
|
+
avg_size: optimum.avg_size,
|
|
1302
|
+
avg_entry: optimum.avg_entry,
|
|
1303
|
+
risk_reward: payload.risk_reward,
|
|
1304
|
+
neg_pnl: optimum.neg_pnl,
|
|
1305
|
+
risk: payload.risk
|
|
1306
|
+
};
|
|
1307
|
+
}
|
|
1308
|
+
export {
|
|
1309
|
+
sortedBuildConfig,
|
|
1310
|
+
get_app_config_and_max_size,
|
|
1311
|
+
getOptimumStopAndRisk,
|
|
1312
|
+
generate_config_params,
|
|
1313
|
+
determine_average_entry_and_size,
|
|
1314
|
+
createArray,
|
|
1315
|
+
buildConfig,
|
|
1316
|
+
buildAvg,
|
|
1317
|
+
buildAppConfig
|
|
1318
|
+
};
|