@gbozee/ultimate 0.0.2-43 → 0.0.2-46
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-index.d.ts +82 -9
- package/dist/frontend-index.js +539 -314
- package/dist/index.d.ts +10 -0
- package/dist/index.js +27824 -8704
- package/package.json +1 -1
package/dist/frontend-index.js
CHANGED
|
@@ -1,261 +1,5 @@
|
|
|
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
1
|
// src/helpers/trade_signal.ts
|
|
258
|
-
function
|
|
2
|
+
function determine_close_price({
|
|
259
3
|
entry,
|
|
260
4
|
pnl,
|
|
261
5
|
quantity,
|
|
@@ -263,16 +7,16 @@ function determine_close_price2({
|
|
|
263
7
|
kind = "long"
|
|
264
8
|
}) {
|
|
265
9
|
const dollar_value = entry / leverage;
|
|
266
|
-
const
|
|
267
|
-
if (
|
|
268
|
-
const percent = pnl /
|
|
269
|
-
const difference =
|
|
10
|
+
const position = dollar_value * quantity;
|
|
11
|
+
if (position) {
|
|
12
|
+
const percent = pnl / position;
|
|
13
|
+
const difference = position * percent / quantity;
|
|
270
14
|
const result = kind === "long" ? difference + entry : entry - difference;
|
|
271
15
|
return result;
|
|
272
16
|
}
|
|
273
17
|
return 0;
|
|
274
18
|
}
|
|
275
|
-
function
|
|
19
|
+
function determine_pnl(entry, close_price, quantity, kind = "long", contract_size) {
|
|
276
20
|
if (contract_size) {
|
|
277
21
|
const direction = kind === "long" ? 1 : -1;
|
|
278
22
|
return quantity * contract_size * direction * (1 / entry - 1 / close_price);
|
|
@@ -291,7 +35,7 @@ function* _get_zones({
|
|
|
291
35
|
let focus_low = last * Math.pow(1 + percent_change, -1);
|
|
292
36
|
if (focus_high > current_price) {
|
|
293
37
|
while (focus_high > current_price) {
|
|
294
|
-
yield
|
|
38
|
+
yield to_f(last, places);
|
|
295
39
|
focus_high = last;
|
|
296
40
|
last = focus_high * Math.pow(1 + percent_change, -1);
|
|
297
41
|
focus_low = last * Math.pow(1 + percent_change, -1);
|
|
@@ -299,14 +43,14 @@ function* _get_zones({
|
|
|
299
43
|
} else {
|
|
300
44
|
if (focus_high <= current_price) {
|
|
301
45
|
while (focus_high <= current_price) {
|
|
302
|
-
yield
|
|
46
|
+
yield to_f(focus_high, places);
|
|
303
47
|
focus_low = focus_high;
|
|
304
48
|
last = focus_low * (1 + percent_change);
|
|
305
49
|
focus_high = last * (1 + percent_change);
|
|
306
50
|
}
|
|
307
51
|
} else {
|
|
308
52
|
while (focus_low <= current_price) {
|
|
309
|
-
yield
|
|
53
|
+
yield to_f(focus_high, places);
|
|
310
54
|
focus_low = focus_high;
|
|
311
55
|
last = focus_low * (1 + percent_change);
|
|
312
56
|
focus_high = last * (1 + percent_change);
|
|
@@ -570,7 +314,7 @@ class Signal {
|
|
|
570
314
|
} else {
|
|
571
315
|
i["risk_sell"] = Math.min(...potentials.slice(0, max_index));
|
|
572
316
|
}
|
|
573
|
-
i["pnl"] = this.to_df(
|
|
317
|
+
i["pnl"] = this.to_df(determine_pnl(i["entry"], i["risk_sell"], i["quantity"], kind));
|
|
574
318
|
}
|
|
575
319
|
}
|
|
576
320
|
}
|
|
@@ -634,13 +378,13 @@ class Signal {
|
|
|
634
378
|
let remaining_zones = margin_zones.filter((x) => JSON.stringify(x) != JSON.stringify(margin_range));
|
|
635
379
|
if (margin_range) {
|
|
636
380
|
const difference = Math.abs(margin_range[0] - margin_range[1]);
|
|
637
|
-
const spread =
|
|
381
|
+
const spread = to_f(difference / this.risk_reward, this.price_places);
|
|
638
382
|
let entries;
|
|
639
383
|
const percent_change = this.percent_change / this.risk_reward;
|
|
640
384
|
if (kind === "long") {
|
|
641
|
-
entries = Array.from({ length: Math.floor(this.risk_reward) + 1 }, (_, x) =>
|
|
385
|
+
entries = Array.from({ length: Math.floor(this.risk_reward) + 1 }, (_, x) => to_f(margin_range[1] - spread * x, this.price_places));
|
|
642
386
|
} else {
|
|
643
|
-
entries = Array.from({ length: Math.floor(this.risk_reward) + 1 }, (_, x) =>
|
|
387
|
+
entries = Array.from({ length: Math.floor(this.risk_reward) + 1 }, (_, x) => to_f(margin_range[1] * Math.pow(1 + percent_change, x), this.price_places));
|
|
644
388
|
}
|
|
645
389
|
if (Math.min(...entries) < this.to_f(current_price) && this.to_f(current_price) < Math.max(...entries)) {
|
|
646
390
|
return entries.sort((a, b) => a - b);
|
|
@@ -652,14 +396,14 @@ class Signal {
|
|
|
652
396
|
if (new_range) {
|
|
653
397
|
while (entries2.length < this.risk_reward + 1) {
|
|
654
398
|
if (kind === "long") {
|
|
655
|
-
let
|
|
656
|
-
if (
|
|
657
|
-
entries2.push(
|
|
399
|
+
let value = this.to_f(new_range[1] - spread * x);
|
|
400
|
+
if (value <= current_price) {
|
|
401
|
+
entries2.push(value);
|
|
658
402
|
}
|
|
659
403
|
} else {
|
|
660
|
-
let
|
|
661
|
-
if (
|
|
662
|
-
entries2.push(
|
|
404
|
+
let value = this.to_f(new_range[1] * Math.pow(1 + percent_change, x));
|
|
405
|
+
if (value >= current_price) {
|
|
406
|
+
entries2.push(value);
|
|
663
407
|
}
|
|
664
408
|
}
|
|
665
409
|
x += 1;
|
|
@@ -673,14 +417,14 @@ class Signal {
|
|
|
673
417
|
let x = 0;
|
|
674
418
|
while (entries2.length < this.risk_reward + 1) {
|
|
675
419
|
if (kind === "long") {
|
|
676
|
-
let
|
|
677
|
-
if (
|
|
678
|
-
entries2.push(
|
|
420
|
+
let value = this.to_f(next_focus - spread * x);
|
|
421
|
+
if (value <= this.to_f(current_price)) {
|
|
422
|
+
entries2.push(value);
|
|
679
423
|
}
|
|
680
424
|
} else {
|
|
681
|
-
let
|
|
682
|
-
if (
|
|
683
|
-
entries2.push(
|
|
425
|
+
let value = this.to_f(next_focus * Math.pow(1 + percent_change, x));
|
|
426
|
+
if (value >= this.to_f(current_price)) {
|
|
427
|
+
entries2.push(value);
|
|
684
428
|
}
|
|
685
429
|
}
|
|
686
430
|
x += 1;
|
|
@@ -691,8 +435,8 @@ class Signal {
|
|
|
691
435
|
}
|
|
692
436
|
return [];
|
|
693
437
|
}
|
|
694
|
-
to_f(
|
|
695
|
-
return
|
|
438
|
+
to_f(value, places) {
|
|
439
|
+
return to_f(value, places || this.price_places);
|
|
696
440
|
}
|
|
697
441
|
get_margin_zones({
|
|
698
442
|
current_price,
|
|
@@ -894,7 +638,7 @@ class Signal {
|
|
|
894
638
|
}) {
|
|
895
639
|
const considered = arr.map((x, i) => i).filter((i) => i > index);
|
|
896
640
|
const with_quantity = considered.map((x) => {
|
|
897
|
-
const q =
|
|
641
|
+
const q = determine_position_size({
|
|
898
642
|
entry: arr[x],
|
|
899
643
|
stop: arr[x - 1],
|
|
900
644
|
budget: risk,
|
|
@@ -928,7 +672,7 @@ class Signal {
|
|
|
928
672
|
const incurred_fees = fees.reduce((a, b) => a + b, 0) + previous_risks.reduce((a, b) => a + b, 0);
|
|
929
673
|
if (index === 0) {
|
|
930
674
|
}
|
|
931
|
-
let quantity =
|
|
675
|
+
let quantity = determine_position_size({
|
|
932
676
|
entry,
|
|
933
677
|
stop,
|
|
934
678
|
budget: risk,
|
|
@@ -940,7 +684,7 @@ class Signal {
|
|
|
940
684
|
}
|
|
941
685
|
if (this.increase_size) {
|
|
942
686
|
quantity = quantity * multiplier;
|
|
943
|
-
const new_risk =
|
|
687
|
+
const new_risk = determine_pnl(entry, stop, quantity, kind);
|
|
944
688
|
risk = Math.abs(new_risk);
|
|
945
689
|
}
|
|
946
690
|
const fee = this.to_df(this.fee * quantity * entry);
|
|
@@ -949,12 +693,12 @@ class Signal {
|
|
|
949
693
|
if (this.minimum_pnl) {
|
|
950
694
|
pnl = this.minimum_pnl + fee;
|
|
951
695
|
}
|
|
952
|
-
let sell_price =
|
|
696
|
+
let sell_price = determine_close_price({ entry, pnl, quantity, kind });
|
|
953
697
|
if (take_profit && !this.minimum_pnl) {
|
|
954
698
|
sell_price = take_profit;
|
|
955
|
-
pnl = this.to_df(
|
|
699
|
+
pnl = this.to_df(determine_pnl(entry, sell_price, quantity, kind));
|
|
956
700
|
pnl = pnl + fee;
|
|
957
|
-
sell_price =
|
|
701
|
+
sell_price = determine_close_price({ entry, pnl, quantity, kind });
|
|
958
702
|
}
|
|
959
703
|
let risk_sell = sell_price;
|
|
960
704
|
return {
|
|
@@ -972,29 +716,455 @@ class Signal {
|
|
|
972
716
|
};
|
|
973
717
|
}
|
|
974
718
|
to_df(currentPrice, places = "%.3f") {
|
|
975
|
-
return
|
|
719
|
+
return to_f(currentPrice, places);
|
|
976
720
|
}
|
|
977
721
|
}
|
|
978
722
|
|
|
979
|
-
// src/helpers/
|
|
980
|
-
function
|
|
981
|
-
|
|
982
|
-
entry
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
723
|
+
// src/helpers/pnl.ts
|
|
724
|
+
function determine_position_size2(entry, stop, budget) {
|
|
725
|
+
let stop_percent = Math.abs(entry - stop) / entry;
|
|
726
|
+
let size = budget / stop_percent / entry;
|
|
727
|
+
return size;
|
|
728
|
+
}
|
|
729
|
+
function determine_risk(entry, stop, quantity) {
|
|
730
|
+
let stop_percent = Math.abs(entry - stop) / entry;
|
|
731
|
+
let risk = quantity * stop_percent * entry;
|
|
732
|
+
return risk;
|
|
733
|
+
}
|
|
734
|
+
function determine_close_price2(entry, pnl, quantity, kind, single = false, leverage = 1) {
|
|
735
|
+
const dollar_value = entry / leverage;
|
|
736
|
+
const position = dollar_value * quantity;
|
|
737
|
+
if (position) {
|
|
738
|
+
let percent = pnl / position;
|
|
739
|
+
let difference = position * percent / quantity;
|
|
740
|
+
let result;
|
|
741
|
+
if (kind === "long") {
|
|
742
|
+
result = difference + entry;
|
|
743
|
+
} else {
|
|
744
|
+
result = entry - difference;
|
|
745
|
+
}
|
|
746
|
+
if (single) {
|
|
747
|
+
return result;
|
|
748
|
+
}
|
|
749
|
+
return result;
|
|
750
|
+
}
|
|
751
|
+
return 0;
|
|
752
|
+
}
|
|
753
|
+
function determine_amount_to_sell(entry, quantity, sell_price, pnl, kind, places = "%.3f") {
|
|
754
|
+
const _pnl = determine_pnl2(entry, sell_price, quantity, kind);
|
|
755
|
+
const ratio = pnl / to_f2(Math.abs(_pnl), places);
|
|
756
|
+
quantity = quantity * ratio;
|
|
757
|
+
return to_f2(quantity, places);
|
|
758
|
+
}
|
|
759
|
+
function determine_pnl2(entry, close_price, quantity, kind, contract_size, places = "%.2f") {
|
|
760
|
+
if (contract_size) {
|
|
761
|
+
const direction = kind === "long" ? 1 : -1;
|
|
762
|
+
return quantity * contract_size * direction * (1 / entry - 1 / close_price);
|
|
763
|
+
}
|
|
764
|
+
let difference = entry - close_price;
|
|
765
|
+
if (kind === "long") {
|
|
766
|
+
difference = close_price - entry;
|
|
767
|
+
}
|
|
768
|
+
return to_f2(difference * quantity, places);
|
|
769
|
+
}
|
|
770
|
+
function position(entry, quantity, kind, leverage = 1) {
|
|
771
|
+
const direction = { long: 1, short: -1 };
|
|
772
|
+
return parseFloat((direction[kind] * quantity * (entry / leverage)).toFixed(3));
|
|
773
|
+
}
|
|
774
|
+
function to_f2(value, places) {
|
|
775
|
+
if (value) {
|
|
776
|
+
let pp = parseInt(places.replace("%.", "").replace("f", ""));
|
|
777
|
+
return parseFloat(value.toFixed(pp));
|
|
778
|
+
}
|
|
779
|
+
return value;
|
|
780
|
+
}
|
|
781
|
+
var value = {
|
|
782
|
+
determine_risk,
|
|
783
|
+
determine_position_size: determine_position_size2,
|
|
784
|
+
determine_close_price: determine_close_price2,
|
|
785
|
+
determine_pnl: determine_pnl2,
|
|
786
|
+
position,
|
|
787
|
+
determine_amount_to_sell,
|
|
788
|
+
to_f: to_f2
|
|
789
|
+
};
|
|
790
|
+
var pnl_default = value;
|
|
791
|
+
|
|
792
|
+
// src/helpers/trade_utils.ts
|
|
793
|
+
function profitHelper(longPosition, shortPosition, config, contract_size, balance = 0) {
|
|
794
|
+
let long = { takeProfit: 0, quantity: 0, pnl: 0 };
|
|
795
|
+
let short = { takeProfit: 0, quantity: 0, pnl: 0 };
|
|
796
|
+
if (longPosition) {
|
|
797
|
+
long = config?.getSize2(longPosition, contract_size, balance) || null;
|
|
798
|
+
}
|
|
799
|
+
if (shortPosition) {
|
|
800
|
+
short = config?.getSize2(shortPosition, contract_size, balance) || null;
|
|
801
|
+
}
|
|
802
|
+
return { long, short };
|
|
803
|
+
}
|
|
804
|
+
function getParamForField(self, configs, field, isGroup) {
|
|
805
|
+
if (isGroup === "group" && field === "checkbox") {
|
|
806
|
+
return configs.filter((o) => o.kind === field && o.group === true).map((o) => {
|
|
807
|
+
let _self = self;
|
|
808
|
+
let value2 = _self[o.name];
|
|
809
|
+
return { ...o, value: value2 };
|
|
810
|
+
});
|
|
811
|
+
}
|
|
812
|
+
let r = configs.find((o) => o.name == field);
|
|
813
|
+
if (r) {
|
|
814
|
+
let oo = self;
|
|
815
|
+
let tt = oo[r.name] || "";
|
|
816
|
+
r.value = tt;
|
|
817
|
+
}
|
|
818
|
+
return r;
|
|
819
|
+
}
|
|
820
|
+
function getTradeEntries(entry, min_size, kind, size, spread = 0) {
|
|
821
|
+
let result = [];
|
|
822
|
+
let index = 0;
|
|
823
|
+
let no_of_trades = size > min_size ? Math.round(size / min_size) : 1;
|
|
824
|
+
while (index < no_of_trades) {
|
|
825
|
+
if (kind === "long") {
|
|
826
|
+
result.push({ entry: entry - index * spread, size: min_size });
|
|
827
|
+
} else {
|
|
828
|
+
result.push({ entry: entry + index * spread, size: min_size });
|
|
829
|
+
}
|
|
830
|
+
index = index + 1;
|
|
831
|
+
}
|
|
832
|
+
return result;
|
|
833
|
+
}
|
|
834
|
+
function extractValue(_param, condition) {
|
|
835
|
+
let param;
|
|
836
|
+
if (condition) {
|
|
837
|
+
try {
|
|
838
|
+
let value2 = JSON.parse(_param || "[]");
|
|
839
|
+
param = value2.map((o) => parseFloat(o));
|
|
840
|
+
} catch (error) {
|
|
841
|
+
}
|
|
842
|
+
} else {
|
|
843
|
+
param = parseFloat(_param);
|
|
844
|
+
}
|
|
845
|
+
return param;
|
|
846
|
+
}
|
|
847
|
+
function asCoins(symbol) {
|
|
848
|
+
let _type = symbol.toLowerCase().includes("usdt") ? "usdt" : "coin";
|
|
849
|
+
if (symbol.toLowerCase() == "btcusdt") {
|
|
850
|
+
_type = "usdt";
|
|
851
|
+
}
|
|
852
|
+
let result = _type === "usdt" ? symbol.toLowerCase().includes("usdt") ? "USDT" : "BUSD" : symbol.toUpperCase().split("USD_")[0];
|
|
853
|
+
if (symbol.toLowerCase().includes("-")) {
|
|
854
|
+
result = result.split("-")[0];
|
|
855
|
+
}
|
|
856
|
+
if (symbol.toLowerCase() == "usdt-usd") {
|
|
857
|
+
}
|
|
858
|
+
let result2 = _type == "usdt" ? symbol.split(result)[0] : result;
|
|
859
|
+
if (result.includes("-")) {
|
|
860
|
+
result2 = result;
|
|
861
|
+
}
|
|
862
|
+
return result2;
|
|
863
|
+
}
|
|
864
|
+
var SpecialCoins = ["NGN", "USDT", "BUSD", "PAX", "USDC", "EUR"];
|
|
865
|
+
function allCoins(symbols) {
|
|
866
|
+
let r = symbols.map((o, i) => asCoins(o));
|
|
867
|
+
return [...new Set(r), ...SpecialCoins];
|
|
868
|
+
}
|
|
869
|
+
function formatPrice(value2, opts = {}) {
|
|
870
|
+
const { locale = "en-US", currency = "USD" } = opts;
|
|
871
|
+
const formatter = new Intl.NumberFormat(locale, {
|
|
872
|
+
currency,
|
|
873
|
+
style: "currency",
|
|
874
|
+
maximumFractionDigits: 2
|
|
875
|
+
});
|
|
876
|
+
return formatter.format(value2);
|
|
877
|
+
}
|
|
878
|
+
function to_f(value2, places = "%.1f") {
|
|
879
|
+
let v = typeof value2 === "string" ? parseFloat(value2) : value2;
|
|
880
|
+
const formattedValue = places.replace("%.", "").replace("f", "");
|
|
881
|
+
return parseFloat(v.toFixed(parseInt(formattedValue)));
|
|
882
|
+
}
|
|
883
|
+
function determine_stop_and_size(entry, pnl, take_profit, kind = "long") {
|
|
884
|
+
const difference = kind === "long" ? take_profit - entry : entry - take_profit;
|
|
885
|
+
const quantity = pnl / difference;
|
|
886
|
+
return Math.abs(quantity);
|
|
887
|
+
}
|
|
888
|
+
var range = (start, stop, step = 1) => Array.from({ length: (stop - start) / step + 1 }, (_, i) => start + i * step);
|
|
889
|
+
function determine_amount_to_sell2(entry, quantity, sell_price, pnl, kind, places = "%.3f") {
|
|
890
|
+
const _pnl = determine_pnl(entry, sell_price, quantity, kind);
|
|
891
|
+
const ratio = pnl / to_f(Math.abs(_pnl), places);
|
|
892
|
+
quantity = quantity * ratio;
|
|
893
|
+
return to_f(quantity, places);
|
|
894
|
+
}
|
|
895
|
+
function determine_position_size({
|
|
896
|
+
entry,
|
|
897
|
+
stop,
|
|
898
|
+
budget,
|
|
899
|
+
percent,
|
|
900
|
+
min_size,
|
|
901
|
+
notional_value,
|
|
902
|
+
as_coin = true,
|
|
903
|
+
places = "%.3f"
|
|
904
|
+
}) {
|
|
905
|
+
let stop_percent = stop ? Math.abs(entry - stop) / entry : percent;
|
|
906
|
+
if (stop_percent && budget) {
|
|
907
|
+
let size = budget / stop_percent;
|
|
908
|
+
let notion_value = size * entry;
|
|
909
|
+
if (notional_value && notional_value > notion_value) {
|
|
910
|
+
size = notional_value / entry;
|
|
911
|
+
}
|
|
912
|
+
if (as_coin) {
|
|
913
|
+
size = size / entry;
|
|
914
|
+
if (min_size && min_size === 1) {
|
|
915
|
+
return to_f(Math.round(size), places);
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
return to_f(size, places);
|
|
919
|
+
}
|
|
920
|
+
return;
|
|
921
|
+
}
|
|
922
|
+
function determine_remaining_entry({
|
|
923
|
+
risk,
|
|
924
|
+
max_size,
|
|
925
|
+
stop_loss,
|
|
926
|
+
kind,
|
|
927
|
+
position: position2
|
|
928
|
+
}) {
|
|
929
|
+
const avg_entry = determine_avg_entry_based_on_max_size({
|
|
930
|
+
risk,
|
|
931
|
+
max_size,
|
|
932
|
+
stop_loss,
|
|
933
|
+
kind
|
|
934
|
+
});
|
|
935
|
+
const result = avg_entry * max_size - position2.quantity * position2.entry;
|
|
936
|
+
return result / (max_size - position2.quantity);
|
|
937
|
+
}
|
|
938
|
+
function determine_avg_entry_based_on_max_size({
|
|
939
|
+
risk,
|
|
940
|
+
max_size,
|
|
941
|
+
stop_loss,
|
|
942
|
+
kind = "long"
|
|
943
|
+
}) {
|
|
944
|
+
const diff = max_size * stop_loss;
|
|
945
|
+
if (kind === "long") {
|
|
946
|
+
return (risk + diff) / max_size;
|
|
947
|
+
}
|
|
948
|
+
return (risk - diff) / max_size;
|
|
949
|
+
}
|
|
950
|
+
function determine_average_entry_and_size(orders, places = "%.3f", price_places = "%.1f") {
|
|
951
|
+
const sum_values = orders.reduce((sum, order) => sum + order.price * order.quantity, 0);
|
|
952
|
+
const total_quantity = orders.reduce((sum, order) => sum + order.quantity, 0);
|
|
953
|
+
const avg_price = total_quantity ? to_f(sum_values / total_quantity, price_places) : 0;
|
|
954
|
+
return {
|
|
955
|
+
entry: avg_price,
|
|
956
|
+
price: avg_price,
|
|
957
|
+
quantity: to_f(total_quantity, places)
|
|
958
|
+
};
|
|
959
|
+
}
|
|
960
|
+
var createArray = (start, stop, step) => {
|
|
961
|
+
const result = [];
|
|
962
|
+
let current = start;
|
|
963
|
+
while (current <= stop) {
|
|
964
|
+
result.push(current);
|
|
965
|
+
current += step;
|
|
966
|
+
}
|
|
967
|
+
return result;
|
|
968
|
+
};
|
|
969
|
+
var groupBy = (xs, key) => {
|
|
970
|
+
return xs.reduce((rv, x) => {
|
|
971
|
+
(rv[x[key]] = rv[x[key]] || []).push(x);
|
|
972
|
+
return rv;
|
|
973
|
+
}, {});
|
|
974
|
+
};
|
|
975
|
+
function fibonacci_analysis({
|
|
976
|
+
support,
|
|
977
|
+
resistance,
|
|
978
|
+
kind = "long",
|
|
979
|
+
trend = "long",
|
|
980
|
+
places = "%.1f"
|
|
981
|
+
}) {
|
|
982
|
+
const swing_high = trend === "long" ? resistance : support;
|
|
983
|
+
const swing_low = trend === "long" ? support : resistance;
|
|
984
|
+
const ranges = [0, 0.236, 0.382, 0.5, 0.618, 0.789, 1, 1.272, 1.414, 1.618];
|
|
985
|
+
const fib_calc = (p, h, l) => p * (h - l) + l;
|
|
986
|
+
const fib_values = ranges.map((x) => fib_calc(x, swing_high, swing_low)).map((x) => to_f(x, places));
|
|
987
|
+
if (kind === "short") {
|
|
988
|
+
return trend === "long" ? fib_values.reverse() : fib_values;
|
|
989
|
+
} else {
|
|
990
|
+
return trend === "short" ? fib_values.reverse() : fib_values;
|
|
991
|
+
}
|
|
992
|
+
return fib_values;
|
|
993
|
+
}
|
|
994
|
+
var groupIntoPairs = (arr, size) => {
|
|
995
|
+
const result = [];
|
|
996
|
+
for (let i = 0;i < arr.length; i += size) {
|
|
997
|
+
result.push(arr.slice(i, i + size));
|
|
998
|
+
}
|
|
999
|
+
return result;
|
|
1000
|
+
};
|
|
1001
|
+
var groupIntoPairsWithSumLessThan = (arr, targetSum, key = "quantity", firstSize = 0) => {
|
|
1002
|
+
if (firstSize) {
|
|
1003
|
+
const totalSize = arr.reduce((sum, order) => sum + order[key], 0);
|
|
1004
|
+
const remainingSize = totalSize - firstSize;
|
|
1005
|
+
let newSum = 0;
|
|
1006
|
+
let newArray = [];
|
|
1007
|
+
let lastIndex = 0;
|
|
1008
|
+
for (let i = 0;i < arr.length; i++) {
|
|
1009
|
+
if (newSum < remainingSize) {
|
|
1010
|
+
newSum += arr[i][key];
|
|
1011
|
+
newArray.push(arr[i]);
|
|
1012
|
+
lastIndex = i;
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
const lastGroup = arr.slice(lastIndex + 1);
|
|
1016
|
+
const previousPair = groupInPairs(newArray, key, targetSum);
|
|
1017
|
+
if (lastGroup.length > 0) {
|
|
1018
|
+
previousPair.push(lastGroup);
|
|
1019
|
+
}
|
|
1020
|
+
return previousPair;
|
|
1021
|
+
}
|
|
1022
|
+
return groupInPairs(arr, key, targetSum);
|
|
1023
|
+
};
|
|
1024
|
+
function groupInPairs(_arr, key, targetSum) {
|
|
1025
|
+
const result = [];
|
|
1026
|
+
let currentSum = 0;
|
|
1027
|
+
let currentGroup = [];
|
|
1028
|
+
for (let i = 0;i < _arr.length; i++) {
|
|
1029
|
+
currentSum += _arr[i][key];
|
|
1030
|
+
currentGroup.push(_arr[i]);
|
|
1031
|
+
if (currentSum >= targetSum) {
|
|
1032
|
+
result.push(currentGroup);
|
|
1033
|
+
currentGroup = [];
|
|
1034
|
+
currentSum = 0;
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
return result;
|
|
1038
|
+
}
|
|
1039
|
+
var computeTotalAverageForEachTrade = (trades, config) => {
|
|
1040
|
+
let _take_profit = config.take_profit;
|
|
1041
|
+
let kind = config.kind;
|
|
1042
|
+
let entryToUse = kind === "short" ? Math.min(config.entry, config.stop) : Math.max(config.entry, config.stop);
|
|
1043
|
+
let _currentEntry = config.currentEntry || entryToUse;
|
|
1044
|
+
let less = trades.filter((p) => kind === "long" ? p.entry <= _currentEntry : p.entry >= _currentEntry);
|
|
1045
|
+
let rrr = trades.map((r, i) => {
|
|
1046
|
+
let considered = [];
|
|
1047
|
+
if (kind === "long") {
|
|
1048
|
+
considered = trades.filter((p) => p.entry > _currentEntry);
|
|
1049
|
+
} else {
|
|
1050
|
+
considered = trades.filter((p) => p.entry < _currentEntry);
|
|
1051
|
+
}
|
|
1052
|
+
const x_pnl = 0;
|
|
1053
|
+
const remaining = less.filter((o) => {
|
|
1054
|
+
if (kind === "long") {
|
|
1055
|
+
return o.entry >= r.entry;
|
|
1056
|
+
}
|
|
1057
|
+
return o.entry <= r.entry;
|
|
1058
|
+
});
|
|
1059
|
+
if (remaining.length === 0) {
|
|
1060
|
+
return { ...r, pnl: x_pnl };
|
|
1061
|
+
}
|
|
1062
|
+
const start = kind === "long" ? Math.max(...remaining.map((o) => o.entry)) : Math.min(...remaining.map((o) => o.entry));
|
|
1063
|
+
considered = considered.map((o) => ({ ...o, entry: start }));
|
|
1064
|
+
considered = considered.concat(remaining);
|
|
1065
|
+
let avg_entry = determine_average_entry_and_size([
|
|
1066
|
+
...considered.map((o) => ({
|
|
1067
|
+
price: o.entry,
|
|
1068
|
+
quantity: o.quantity
|
|
1069
|
+
})),
|
|
1070
|
+
{
|
|
1071
|
+
price: _currentEntry,
|
|
1072
|
+
quantity: config.currentQty || 0
|
|
1073
|
+
}
|
|
1074
|
+
], config.decimal_places, config.price_places);
|
|
1075
|
+
let _pnl = r.pnl;
|
|
1076
|
+
let sell_price = r.sell_price;
|
|
1077
|
+
let entry_pnl = r.pnl;
|
|
1078
|
+
if (_take_profit) {
|
|
1079
|
+
_pnl = pnl_default.determine_pnl(avg_entry.price, _take_profit, avg_entry.quantity, kind);
|
|
1080
|
+
sell_price = _take_profit;
|
|
1081
|
+
entry_pnl = pnl_default.determine_pnl(r.entry, _take_profit, avg_entry.quantity, kind);
|
|
1082
|
+
}
|
|
1083
|
+
const loss = pnl_default.determine_pnl(avg_entry.price, r.stop, avg_entry.quantity, kind);
|
|
1084
|
+
let new_stop = r.new_stop;
|
|
1085
|
+
const entry_loss = pnl_default.determine_pnl(r.entry, new_stop, avg_entry.quantity, kind);
|
|
1086
|
+
let min_profit = 0;
|
|
1087
|
+
let min_entry_profit = 0;
|
|
1088
|
+
if (config.min_profit) {
|
|
1089
|
+
min_profit = pnl_default.determine_close_price(avg_entry.price, config.min_profit, avg_entry.quantity, kind);
|
|
1090
|
+
min_entry_profit = pnl_default.determine_close_price(r.entry, config.min_profit, avg_entry.quantity, kind);
|
|
1091
|
+
}
|
|
1092
|
+
let x_fee = r.fee;
|
|
1093
|
+
if (config.fee) {
|
|
1094
|
+
x_fee = config.fee * r.stop * avg_entry.quantity;
|
|
1095
|
+
}
|
|
1096
|
+
let tp_close = pnl_default.determine_close_price(r.entry, Math.abs(entry_loss) * (config.rr || 1) + x_fee, avg_entry.quantity, kind);
|
|
1097
|
+
return {
|
|
1098
|
+
...r,
|
|
1099
|
+
x_fee: to_f(x_fee, "%.2f"),
|
|
1100
|
+
avg_entry: avg_entry.price,
|
|
1101
|
+
avg_size: avg_entry.quantity,
|
|
1102
|
+
entry_pnl: to_f(entry_pnl, "%.2f"),
|
|
1103
|
+
entry_loss: to_f(entry_loss, "%.2f"),
|
|
1104
|
+
min_entry_pnl: to_f(min_entry_profit, "%.2f"),
|
|
1105
|
+
pnl: _pnl,
|
|
1106
|
+
neg_pnl: to_f(loss, "%.2f"),
|
|
1107
|
+
sell_price,
|
|
1108
|
+
close_p: to_f(tp_close, "%.2f"),
|
|
1109
|
+
min_pnl: to_f(min_profit, "%.2f"),
|
|
1110
|
+
new_stop
|
|
1111
|
+
};
|
|
1112
|
+
});
|
|
1113
|
+
return rrr;
|
|
1114
|
+
};
|
|
1115
|
+
function getDecimalPlaces(numberString) {
|
|
1116
|
+
let parts = numberString.toString().split(".");
|
|
1117
|
+
if (parts.length == 2) {
|
|
1118
|
+
return parts[1].length;
|
|
1119
|
+
}
|
|
1120
|
+
return 0;
|
|
1121
|
+
}
|
|
1122
|
+
function createGapPairs(arr, gap, item) {
|
|
1123
|
+
if (arr.length === 0) {
|
|
1124
|
+
return [];
|
|
1125
|
+
}
|
|
1126
|
+
const result = [];
|
|
1127
|
+
const firstElement = arr[0];
|
|
1128
|
+
for (let i = arr.length - 1;i >= 0; i--) {
|
|
1129
|
+
const current = arr[i];
|
|
1130
|
+
const gapIndex = i - gap;
|
|
1131
|
+
const pairedElement = gapIndex < 0 ? firstElement : arr[gapIndex];
|
|
1132
|
+
if (current !== pairedElement) {
|
|
1133
|
+
result.push([current, pairedElement]);
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
if (item) {
|
|
1137
|
+
let r = result.find((o) => o[0] === item);
|
|
1138
|
+
return r ? [r] : [];
|
|
1139
|
+
}
|
|
1140
|
+
return result;
|
|
1141
|
+
}
|
|
1142
|
+
function logWithLineNumber(...args) {
|
|
1143
|
+
const stack = new Error().stack;
|
|
1144
|
+
const lines = stack?.split(`
|
|
1145
|
+
`).slice(2).map((line) => line.trim());
|
|
1146
|
+
const lineNumber = lines?.[0]?.split(":").pop();
|
|
1147
|
+
console.log(`${lineNumber}:`, ...args);
|
|
1148
|
+
}
|
|
1149
|
+
// src/helpers/shared.ts
|
|
1150
|
+
function buildConfig(app_config, {
|
|
1151
|
+
take_profit,
|
|
1152
|
+
entry,
|
|
1153
|
+
stop,
|
|
1154
|
+
raw_instance,
|
|
1155
|
+
risk,
|
|
1156
|
+
no_of_trades,
|
|
1157
|
+
min_profit = 0,
|
|
1158
|
+
risk_reward,
|
|
1159
|
+
kind,
|
|
1160
|
+
increase,
|
|
1161
|
+
gap,
|
|
1162
|
+
rr = 1,
|
|
1163
|
+
price_places = "%.1f",
|
|
1164
|
+
decimal_places = "%.3f"
|
|
1165
|
+
}) {
|
|
1166
|
+
let fee = app_config.fee / 100;
|
|
1167
|
+
let working_risk = risk || app_config.risk_per_trade;
|
|
998
1168
|
let trade_no = no_of_trades || app_config.risk_reward;
|
|
999
1169
|
const config = {
|
|
1000
1170
|
focus: app_config.focus,
|
|
@@ -1075,8 +1245,8 @@ function get_app_config_and_max_size(config, payload) {
|
|
|
1075
1245
|
stop: payload.stop,
|
|
1076
1246
|
risk_per_trade: config.risk,
|
|
1077
1247
|
risk_reward: config.risk_reward || 199,
|
|
1078
|
-
support:
|
|
1079
|
-
resistance:
|
|
1248
|
+
support: to_f(config.support, config.price_places),
|
|
1249
|
+
resistance: to_f(config.resistance, config.price_places),
|
|
1080
1250
|
focus: payload.entry,
|
|
1081
1251
|
fee: 0,
|
|
1082
1252
|
percent_change: config.stop_percent / 100,
|
|
@@ -1277,8 +1447,8 @@ function getOptimumStopAndRisk(app_config, params) {
|
|
|
1277
1447
|
}
|
|
1278
1448
|
}
|
|
1279
1449
|
return {
|
|
1280
|
-
optimal_stop:
|
|
1281
|
-
optimal_risk:
|
|
1450
|
+
optimal_stop: to_f(optimal_stop, app_config.price_places),
|
|
1451
|
+
optimal_risk: to_f(final_risk, app_config.price_places),
|
|
1282
1452
|
avg_size: final_result?.[0]?.avg_size || 0,
|
|
1283
1453
|
avg_entry: final_result?.[0]?.avg_entry || 0,
|
|
1284
1454
|
result: final_result,
|
|
@@ -1317,15 +1487,70 @@ function determine_break_even_price(payload) {
|
|
|
1317
1487
|
direction: net_quantity > 0 ? "long" : "short"
|
|
1318
1488
|
};
|
|
1319
1489
|
}
|
|
1490
|
+
function determine_amount_to_buy(payload) {
|
|
1491
|
+
const {
|
|
1492
|
+
orders,
|
|
1493
|
+
kind,
|
|
1494
|
+
decimal_places = "%.3f",
|
|
1495
|
+
position: position2,
|
|
1496
|
+
existingOrders
|
|
1497
|
+
} = payload;
|
|
1498
|
+
const totalQuantity = orders.reduce((sum, order) => sum + (order.quantity || 0), 0);
|
|
1499
|
+
let runningTotal = to_f(totalQuantity, decimal_places);
|
|
1500
|
+
let sortedOrders = [...orders].sort((a, b) => (a.entry || 0) - (b.entry || 0));
|
|
1501
|
+
if (kind === "short") {
|
|
1502
|
+
sortedOrders.reverse();
|
|
1503
|
+
}
|
|
1504
|
+
const withCumulative = [];
|
|
1505
|
+
for (const order of sortedOrders) {
|
|
1506
|
+
withCumulative.push({
|
|
1507
|
+
...order,
|
|
1508
|
+
cumulative_quantity: runningTotal
|
|
1509
|
+
});
|
|
1510
|
+
runningTotal -= order.quantity;
|
|
1511
|
+
runningTotal = to_f(runningTotal, decimal_places);
|
|
1512
|
+
}
|
|
1513
|
+
let filteredOrders = withCumulative.filter((order) => (order.cumulative_quantity || 0) > position2?.quantity).map((order) => ({
|
|
1514
|
+
...order,
|
|
1515
|
+
price: order.entry,
|
|
1516
|
+
kind,
|
|
1517
|
+
side: kind.toLowerCase() === "long" ? "buy" : "sell"
|
|
1518
|
+
}));
|
|
1519
|
+
filteredOrders = filteredOrders.filter((k) => !existingOrders.map((j) => j.price).includes(k.price));
|
|
1520
|
+
return filteredOrders;
|
|
1521
|
+
}
|
|
1320
1522
|
export {
|
|
1523
|
+
to_f,
|
|
1321
1524
|
sortedBuildConfig,
|
|
1525
|
+
range,
|
|
1526
|
+
profitHelper,
|
|
1527
|
+
logWithLineNumber,
|
|
1528
|
+
groupIntoPairsWithSumLessThan,
|
|
1529
|
+
groupIntoPairs,
|
|
1530
|
+
groupBy,
|
|
1322
1531
|
get_app_config_and_max_size,
|
|
1532
|
+
getTradeEntries,
|
|
1533
|
+
getParamForField,
|
|
1323
1534
|
getOptimumStopAndRisk,
|
|
1535
|
+
getDecimalPlaces,
|
|
1324
1536
|
generate_config_params,
|
|
1537
|
+
formatPrice,
|
|
1538
|
+
fibonacci_analysis,
|
|
1539
|
+
extractValue,
|
|
1540
|
+
determine_stop_and_size,
|
|
1541
|
+
determine_remaining_entry,
|
|
1542
|
+
determine_position_size,
|
|
1325
1543
|
determine_break_even_price,
|
|
1326
1544
|
determine_average_entry_and_size,
|
|
1545
|
+
determine_amount_to_sell2 as determine_amount_to_sell,
|
|
1546
|
+
determine_amount_to_buy,
|
|
1547
|
+
createGapPairs,
|
|
1327
1548
|
createArray,
|
|
1549
|
+
computeTotalAverageForEachTrade,
|
|
1328
1550
|
buildConfig,
|
|
1329
1551
|
buildAvg,
|
|
1330
|
-
buildAppConfig
|
|
1552
|
+
buildAppConfig,
|
|
1553
|
+
asCoins,
|
|
1554
|
+
allCoins,
|
|
1555
|
+
SpecialCoins
|
|
1331
1556
|
};
|