@gbozee/ultimate 0.0.2-99 → 0.0.2-next.4
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 +1124 -24
- package/dist/frontend-index.js +1694 -79
- package/dist/index.cjs +6679 -5157
- package/dist/index.d.ts +1907 -308
- package/dist/index.js +6680 -5162
- package/dist/mcp-client.cjs +6827 -529
- package/dist/mcp-client.js +6836 -529
- package/dist/mcp-server.cjs +14595 -6432
- package/dist/mcp-server.js +14597 -6438
- package/package.json +17 -5
package/dist/frontend-index.js
CHANGED
|
@@ -1,3 +1,315 @@
|
|
|
1
|
+
// src/helpers/distributions.ts
|
|
2
|
+
function generateArithmetic(payload) {
|
|
3
|
+
const { margin_range, risk_reward, kind, price_places = "%.1f" } = payload;
|
|
4
|
+
const difference = Math.abs(margin_range[1] - margin_range[0]);
|
|
5
|
+
const spread = difference / risk_reward;
|
|
6
|
+
return Array.from({ length: risk_reward + 1 }, (_, i) => {
|
|
7
|
+
const price = kind === "long" ? margin_range[1] - spread * i : margin_range[0] + spread * i;
|
|
8
|
+
return to_f(price, price_places);
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
function generateGeometric(payload) {
|
|
12
|
+
const { margin_range, risk_reward, kind, price_places = "%.1f", percent_change } = payload;
|
|
13
|
+
const effectivePercentChange = percent_change ?? Math.abs(margin_range[1] / margin_range[0] - 1) / risk_reward;
|
|
14
|
+
return Array.from({ length: risk_reward + 1 }, (_, i) => {
|
|
15
|
+
const price = kind === "long" ? margin_range[1] * Math.pow(1 - effectivePercentChange, i) : margin_range[0] * Math.pow(1 + effectivePercentChange, i);
|
|
16
|
+
return to_f(price, price_places);
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
function approximateInverseNormal(p) {
|
|
20
|
+
p = Math.max(0.0001, Math.min(0.9999, p));
|
|
21
|
+
if (p < 0.5) {
|
|
22
|
+
const t = Math.sqrt(-2 * Math.log(p));
|
|
23
|
+
const c0 = 2.515517, c1 = 0.802853, c2 = 0.010328;
|
|
24
|
+
const d1 = 1.432788, d2 = 0.189269, d3 = 0.001308;
|
|
25
|
+
return -(t - (c0 + c1 * t + c2 * t * t) / (1 + d1 * t + d2 * t * t + d3 * t * t * t));
|
|
26
|
+
} else {
|
|
27
|
+
const t = Math.sqrt(-2 * Math.log(1 - p));
|
|
28
|
+
const c0 = 2.515517, c1 = 0.802853, c2 = 0.010328;
|
|
29
|
+
const d1 = 1.432788, d2 = 0.189269, d3 = 0.001308;
|
|
30
|
+
return t - (c0 + c1 * t + c2 * t * t) / (1 + d1 * t + d2 * t * t + d3 * t * t * t);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function generateNormal(payload) {
|
|
34
|
+
const { margin_range, risk_reward, kind, price_places = "%.1f", stdDevFactor = 6 } = payload;
|
|
35
|
+
const mean = (margin_range[0] + margin_range[1]) / 2;
|
|
36
|
+
const stdDev = Math.abs(margin_range[1] - margin_range[0]) / stdDevFactor;
|
|
37
|
+
const skew = kind === "long" ? -0.2 : 0.2;
|
|
38
|
+
const adjustedMean = mean + stdDev * skew;
|
|
39
|
+
const entries = Array.from({ length: risk_reward + 1 }, (_, i) => {
|
|
40
|
+
const p = (i + 0.5) / (risk_reward + 1);
|
|
41
|
+
const z = approximateInverseNormal(p);
|
|
42
|
+
let price = adjustedMean + stdDev * z;
|
|
43
|
+
price = Math.max(margin_range[0], Math.min(margin_range[1], price));
|
|
44
|
+
return to_f(price, price_places);
|
|
45
|
+
});
|
|
46
|
+
return entries.sort((a, b) => a - b);
|
|
47
|
+
}
|
|
48
|
+
function generateExponential(payload) {
|
|
49
|
+
const { margin_range, risk_reward, kind, price_places = "%.1f", lambda } = payload;
|
|
50
|
+
const range = Math.abs(margin_range[1] - margin_range[0]);
|
|
51
|
+
const effectiveLambda = lambda || 2.5;
|
|
52
|
+
return Array.from({ length: risk_reward + 1 }, (_, i) => {
|
|
53
|
+
const t = i / risk_reward;
|
|
54
|
+
const exponentialProgress = 1 - Math.exp(-effectiveLambda * t);
|
|
55
|
+
const price = kind === "long" ? margin_range[1] - range * exponentialProgress : margin_range[0] + range * exponentialProgress;
|
|
56
|
+
return to_f(price, price_places);
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
function generateInverseExponential(payload) {
|
|
60
|
+
const { margin_range, risk_reward, kind, price_places = "%.1f", curveFactor = 2 } = payload;
|
|
61
|
+
const range = Math.abs(margin_range[1] - margin_range[0]);
|
|
62
|
+
return Array.from({ length: risk_reward + 1 }, (_, i) => {
|
|
63
|
+
const t = i / risk_reward;
|
|
64
|
+
const progress = (Math.exp(curveFactor * t) - 1) / (Math.exp(curveFactor) - 1);
|
|
65
|
+
const price = kind === "long" ? margin_range[1] - range * progress : margin_range[0] + range * progress;
|
|
66
|
+
return to_f(price, price_places);
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
function getEntries(params) {
|
|
70
|
+
const {
|
|
71
|
+
kind,
|
|
72
|
+
distribution,
|
|
73
|
+
margin_range,
|
|
74
|
+
risk_reward,
|
|
75
|
+
price_places = "%.1f",
|
|
76
|
+
distribution_params = {}
|
|
77
|
+
} = params;
|
|
78
|
+
let entries = [];
|
|
79
|
+
switch (distribution) {
|
|
80
|
+
case "arithmetic":
|
|
81
|
+
entries = generateArithmetic({
|
|
82
|
+
margin_range,
|
|
83
|
+
risk_reward,
|
|
84
|
+
kind,
|
|
85
|
+
price_places,
|
|
86
|
+
percent_change: distribution_params?.curveFactor
|
|
87
|
+
});
|
|
88
|
+
break;
|
|
89
|
+
case "geometric":
|
|
90
|
+
entries = generateGeometric({
|
|
91
|
+
margin_range,
|
|
92
|
+
risk_reward,
|
|
93
|
+
kind,
|
|
94
|
+
price_places,
|
|
95
|
+
percent_change: distribution_params?.curveFactor
|
|
96
|
+
});
|
|
97
|
+
break;
|
|
98
|
+
case "normal":
|
|
99
|
+
entries = generateNormal({
|
|
100
|
+
margin_range,
|
|
101
|
+
risk_reward,
|
|
102
|
+
kind,
|
|
103
|
+
price_places,
|
|
104
|
+
stdDevFactor: distribution_params?.stdDevFactor
|
|
105
|
+
});
|
|
106
|
+
break;
|
|
107
|
+
case "exponential":
|
|
108
|
+
entries = generateExponential({
|
|
109
|
+
margin_range,
|
|
110
|
+
risk_reward,
|
|
111
|
+
kind,
|
|
112
|
+
price_places,
|
|
113
|
+
lambda: distribution_params?.lambda
|
|
114
|
+
});
|
|
115
|
+
break;
|
|
116
|
+
case "inverse-exponential":
|
|
117
|
+
entries = generateInverseExponential({
|
|
118
|
+
margin_range,
|
|
119
|
+
risk_reward,
|
|
120
|
+
kind,
|
|
121
|
+
price_places,
|
|
122
|
+
curveFactor: distribution_params?.curveFactor
|
|
123
|
+
});
|
|
124
|
+
break;
|
|
125
|
+
default:
|
|
126
|
+
throw new Error(`Unknown distribution type: ${distribution}`);
|
|
127
|
+
}
|
|
128
|
+
return entries.sort((a, b) => a - b);
|
|
129
|
+
}
|
|
130
|
+
var distributions_default = getEntries;
|
|
131
|
+
|
|
132
|
+
// src/helpers/optimizations.ts
|
|
133
|
+
function calculateTheoreticalKelly({
|
|
134
|
+
current_entry,
|
|
135
|
+
zone_prices,
|
|
136
|
+
kind = "long",
|
|
137
|
+
config = {}
|
|
138
|
+
}) {
|
|
139
|
+
const {
|
|
140
|
+
price_prediction_model = "uniform",
|
|
141
|
+
confidence_factor = 0.6,
|
|
142
|
+
volatility_adjustment = true
|
|
143
|
+
} = config;
|
|
144
|
+
const sorted_prices = zone_prices;
|
|
145
|
+
const current_index = sorted_prices.findIndex((price) => price === current_entry);
|
|
146
|
+
if (current_index === -1)
|
|
147
|
+
return 0.02;
|
|
148
|
+
const win_zones = kind === "long" ? sorted_prices.filter((price) => price > current_entry) : sorted_prices.filter((price) => price < current_entry);
|
|
149
|
+
const lose_zones = kind === "long" ? sorted_prices.filter((price) => price < current_entry) : sorted_prices.filter((price) => price > current_entry);
|
|
150
|
+
const { win_probability, avg_win_ratio, avg_loss_ratio } = calculateZoneProbabilities({
|
|
151
|
+
current_entry,
|
|
152
|
+
win_zones,
|
|
153
|
+
lose_zones,
|
|
154
|
+
price_prediction_model,
|
|
155
|
+
confidence_factor,
|
|
156
|
+
kind
|
|
157
|
+
});
|
|
158
|
+
const odds_ratio = avg_win_ratio / avg_loss_ratio;
|
|
159
|
+
const loss_probability = 1 - win_probability;
|
|
160
|
+
let kelly_fraction = (win_probability * odds_ratio - loss_probability) / odds_ratio;
|
|
161
|
+
if (volatility_adjustment) {
|
|
162
|
+
const zone_volatility = calculateZoneVolatility(sorted_prices);
|
|
163
|
+
const vol_adjustment = 1 / (1 + zone_volatility);
|
|
164
|
+
kelly_fraction *= vol_adjustment;
|
|
165
|
+
}
|
|
166
|
+
kelly_fraction = Math.max(0.005, Math.min(kelly_fraction, 0.5));
|
|
167
|
+
return to_f(kelly_fraction, "%.4f");
|
|
168
|
+
}
|
|
169
|
+
function calculateZoneProbabilities({
|
|
170
|
+
current_entry,
|
|
171
|
+
win_zones,
|
|
172
|
+
lose_zones,
|
|
173
|
+
price_prediction_model,
|
|
174
|
+
confidence_factor,
|
|
175
|
+
kind
|
|
176
|
+
}) {
|
|
177
|
+
if (win_zones.length === 0 && lose_zones.length === 0) {
|
|
178
|
+
return { win_probability: 0.5, avg_win_ratio: 0.02, avg_loss_ratio: 0.02 };
|
|
179
|
+
}
|
|
180
|
+
let win_probability;
|
|
181
|
+
switch (price_prediction_model) {
|
|
182
|
+
case "uniform":
|
|
183
|
+
win_probability = win_zones.length / (win_zones.length + lose_zones.length);
|
|
184
|
+
break;
|
|
185
|
+
case "normal":
|
|
186
|
+
const win_weight = win_zones.reduce((sum, _, idx) => sum + 1 / (idx + 1), 0);
|
|
187
|
+
const lose_weight = lose_zones.reduce((sum, _, idx) => sum + 1 / (idx + 1), 0);
|
|
188
|
+
win_probability = win_weight / (win_weight + lose_weight);
|
|
189
|
+
break;
|
|
190
|
+
case "exponential":
|
|
191
|
+
const exp_win_weight = win_zones.reduce((sum, _, idx) => sum + Math.exp(-idx * 0.5), 0);
|
|
192
|
+
const exp_lose_weight = lose_zones.reduce((sum, _, idx) => sum + Math.exp(-idx * 0.5), 0);
|
|
193
|
+
win_probability = exp_win_weight / (exp_win_weight + exp_lose_weight);
|
|
194
|
+
break;
|
|
195
|
+
default:
|
|
196
|
+
win_probability = 0.5;
|
|
197
|
+
}
|
|
198
|
+
win_probability = win_probability * confidence_factor + (1 - confidence_factor) * 0.5;
|
|
199
|
+
const avg_win_ratio = win_zones.length > 0 ? win_zones.reduce((sum, price) => {
|
|
200
|
+
return sum + Math.abs(price - current_entry) / current_entry;
|
|
201
|
+
}, 0) / win_zones.length : 0.02;
|
|
202
|
+
const avg_loss_ratio = lose_zones.length > 0 ? lose_zones.reduce((sum, price) => {
|
|
203
|
+
return sum + Math.abs(price - current_entry) / current_entry;
|
|
204
|
+
}, 0) / lose_zones.length : 0.02;
|
|
205
|
+
return {
|
|
206
|
+
win_probability: Math.max(0.1, Math.min(0.9, win_probability)),
|
|
207
|
+
avg_win_ratio: Math.max(0.005, avg_win_ratio),
|
|
208
|
+
avg_loss_ratio: Math.max(0.005, avg_loss_ratio)
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
function calculateZoneVolatility(zone_prices) {
|
|
212
|
+
if (zone_prices.length < 2)
|
|
213
|
+
return 0;
|
|
214
|
+
const price_changes = zone_prices.slice(1).map((price, i) => Math.abs(price - zone_prices[i]) / zone_prices[i]);
|
|
215
|
+
return price_changes.reduce((sum, change) => sum + change, 0) / price_changes.length;
|
|
216
|
+
}
|
|
217
|
+
function calculateTheoreticalKellyFixed({
|
|
218
|
+
current_entry,
|
|
219
|
+
zone_prices,
|
|
220
|
+
kind = "long",
|
|
221
|
+
config = {}
|
|
222
|
+
}) {
|
|
223
|
+
const {
|
|
224
|
+
price_prediction_model = "uniform",
|
|
225
|
+
confidence_factor = 0.6,
|
|
226
|
+
volatility_adjustment = true
|
|
227
|
+
} = config;
|
|
228
|
+
const sorted_prices = zone_prices;
|
|
229
|
+
const current_index = sorted_prices.findIndex((price) => price === current_entry);
|
|
230
|
+
if (current_index === -1)
|
|
231
|
+
return 0.02;
|
|
232
|
+
let stop_loss;
|
|
233
|
+
let target_zones;
|
|
234
|
+
if (kind === "long") {
|
|
235
|
+
stop_loss = Math.min(...zone_prices);
|
|
236
|
+
target_zones = zone_prices.filter((price) => price > current_entry);
|
|
237
|
+
} else {
|
|
238
|
+
stop_loss = Math.max(...zone_prices);
|
|
239
|
+
target_zones = zone_prices.filter((price) => price < current_entry);
|
|
240
|
+
}
|
|
241
|
+
const risk_amount = Math.abs(current_entry - stop_loss);
|
|
242
|
+
const avg_reward = target_zones.length > 0 ? target_zones.reduce((sum, price) => sum + Math.abs(price - current_entry), 0) / target_zones.length : risk_amount;
|
|
243
|
+
const risk_reward_ratio = avg_reward / risk_amount;
|
|
244
|
+
let position_quality;
|
|
245
|
+
if (kind === "long") {
|
|
246
|
+
const distance_from_stop = current_entry - stop_loss;
|
|
247
|
+
const max_distance = Math.max(...zone_prices) - stop_loss;
|
|
248
|
+
position_quality = 1 - distance_from_stop / max_distance;
|
|
249
|
+
} else {
|
|
250
|
+
const distance_from_stop = stop_loss - current_entry;
|
|
251
|
+
const max_distance = stop_loss - Math.min(...zone_prices);
|
|
252
|
+
position_quality = 1 - distance_from_stop / max_distance;
|
|
253
|
+
}
|
|
254
|
+
let base_probability = 0.5;
|
|
255
|
+
switch (price_prediction_model) {
|
|
256
|
+
case "uniform":
|
|
257
|
+
base_probability = 0.5;
|
|
258
|
+
break;
|
|
259
|
+
case "normal":
|
|
260
|
+
base_probability = 0.3 + position_quality * 0.4;
|
|
261
|
+
break;
|
|
262
|
+
case "exponential":
|
|
263
|
+
base_probability = 0.2 + Math.pow(position_quality, 0.5) * 0.6;
|
|
264
|
+
break;
|
|
265
|
+
}
|
|
266
|
+
const win_probability = base_probability * confidence_factor + (1 - confidence_factor) * 0.5;
|
|
267
|
+
const odds_ratio = Math.max(risk_reward_ratio, 0.5);
|
|
268
|
+
const loss_probability = 1 - win_probability;
|
|
269
|
+
let kelly_fraction = (win_probability * odds_ratio - loss_probability) / odds_ratio;
|
|
270
|
+
if (volatility_adjustment) {
|
|
271
|
+
const zone_volatility = calculateZoneVolatility(sorted_prices);
|
|
272
|
+
const vol_adjustment = 1 / (1 + zone_volatility);
|
|
273
|
+
kelly_fraction *= vol_adjustment;
|
|
274
|
+
}
|
|
275
|
+
kelly_fraction = Math.max(0.005, Math.min(kelly_fraction, 0.5));
|
|
276
|
+
return to_f(kelly_fraction, "%.4f");
|
|
277
|
+
}
|
|
278
|
+
function calculatePositionBasedKelly({
|
|
279
|
+
current_entry,
|
|
280
|
+
zone_prices,
|
|
281
|
+
kind = "long",
|
|
282
|
+
config = {}
|
|
283
|
+
}) {
|
|
284
|
+
const {
|
|
285
|
+
price_prediction_model = "uniform",
|
|
286
|
+
confidence_factor: _confidence_factor = 0.6
|
|
287
|
+
} = config;
|
|
288
|
+
const current_index = zone_prices.findIndex((price) => price === current_entry);
|
|
289
|
+
if (current_index === -1)
|
|
290
|
+
return 0.02;
|
|
291
|
+
const total_zones = zone_prices.length;
|
|
292
|
+
const position_score = (total_zones - current_index) / total_zones;
|
|
293
|
+
let adjusted_score;
|
|
294
|
+
switch (price_prediction_model) {
|
|
295
|
+
case "uniform":
|
|
296
|
+
adjusted_score = 0.5;
|
|
297
|
+
break;
|
|
298
|
+
case "normal":
|
|
299
|
+
adjusted_score = 0.3 + position_score * 0.4;
|
|
300
|
+
break;
|
|
301
|
+
case "exponential":
|
|
302
|
+
adjusted_score = 0.2 + Math.pow(position_score, 0.3) * 0.6;
|
|
303
|
+
break;
|
|
304
|
+
default:
|
|
305
|
+
adjusted_score = 0.5;
|
|
306
|
+
}
|
|
307
|
+
const base_kelly = 0.02;
|
|
308
|
+
const max_kelly = 0.2;
|
|
309
|
+
const kelly_fraction = base_kelly + adjusted_score * (max_kelly - base_kelly);
|
|
310
|
+
return to_f(kelly_fraction, "%.4f");
|
|
311
|
+
}
|
|
312
|
+
|
|
1
313
|
// src/helpers/trade_signal.ts
|
|
2
314
|
function determine_close_price({
|
|
3
315
|
entry,
|
|
@@ -64,6 +376,7 @@ class Signal {
|
|
|
64
376
|
budget;
|
|
65
377
|
percent_change = 0.02;
|
|
66
378
|
price_places = "%.5f";
|
|
379
|
+
distribution_params = {};
|
|
67
380
|
decimal_places = "%.0f";
|
|
68
381
|
zone_risk = 1;
|
|
69
382
|
fee = 0.08 / 100;
|
|
@@ -80,8 +393,22 @@ class Signal {
|
|
|
80
393
|
first_order_size;
|
|
81
394
|
gap = 10;
|
|
82
395
|
max_size = 0;
|
|
396
|
+
use_kelly = false;
|
|
397
|
+
kelly_prediction_model = "exponential";
|
|
398
|
+
kelly_confidence_factor = 0.6;
|
|
399
|
+
kelly_minimum_risk = 0.2;
|
|
400
|
+
kelly_func = "theoretical";
|
|
401
|
+
symbol;
|
|
402
|
+
distribution = {
|
|
403
|
+
long: "arithmetic",
|
|
404
|
+
short: "geometric"
|
|
405
|
+
};
|
|
406
|
+
use_progressive_risk;
|
|
407
|
+
risk_distribution;
|
|
408
|
+
max_quantity = 0.03;
|
|
83
409
|
constructor({
|
|
84
410
|
focus,
|
|
411
|
+
symbol,
|
|
85
412
|
budget,
|
|
86
413
|
percent_change = 0.02,
|
|
87
414
|
price_places = "%.5f",
|
|
@@ -100,8 +427,25 @@ class Signal {
|
|
|
100
427
|
minimum_size = 0,
|
|
101
428
|
first_order_size = 0,
|
|
102
429
|
gap = 10,
|
|
103
|
-
max_size = 0
|
|
430
|
+
max_size = 0,
|
|
431
|
+
use_kelly = false,
|
|
432
|
+
kelly_prediction_model = "exponential",
|
|
433
|
+
kelly_confidence_factor = 0.6,
|
|
434
|
+
kelly_minimum_risk = 0.2,
|
|
435
|
+
kelly_func = "theoretical",
|
|
436
|
+
full_distribution,
|
|
437
|
+
max_quantity = 0.03,
|
|
438
|
+
distribution_params = {},
|
|
439
|
+
use_progressive_risk,
|
|
440
|
+
risk_distribution
|
|
104
441
|
}) {
|
|
442
|
+
if (full_distribution) {
|
|
443
|
+
this.distribution = full_distribution;
|
|
444
|
+
}
|
|
445
|
+
this.distribution_params = distribution_params;
|
|
446
|
+
this.use_progressive_risk = use_progressive_risk;
|
|
447
|
+
this.risk_distribution = risk_distribution;
|
|
448
|
+
this.symbol = symbol;
|
|
105
449
|
this.minimum_size = minimum_size;
|
|
106
450
|
this.first_order_size = first_order_size;
|
|
107
451
|
this.focus = focus;
|
|
@@ -122,6 +466,12 @@ class Signal {
|
|
|
122
466
|
this.increase_position = increase_position;
|
|
123
467
|
this.gap = gap;
|
|
124
468
|
this.max_size = max_size;
|
|
469
|
+
this.use_kelly = use_kelly;
|
|
470
|
+
this.kelly_prediction_model = kelly_prediction_model;
|
|
471
|
+
this.kelly_confidence_factor = kelly_confidence_factor;
|
|
472
|
+
this.kelly_minimum_risk = kelly_minimum_risk;
|
|
473
|
+
this.kelly_func = kelly_func;
|
|
474
|
+
this.max_quantity = max_quantity;
|
|
125
475
|
}
|
|
126
476
|
build_entry({
|
|
127
477
|
current_price,
|
|
@@ -131,7 +481,15 @@ class Signal {
|
|
|
131
481
|
kind = "long",
|
|
132
482
|
risk,
|
|
133
483
|
no_of_trades = 1,
|
|
134
|
-
take_profit
|
|
484
|
+
take_profit,
|
|
485
|
+
distribution,
|
|
486
|
+
distribution_params = {
|
|
487
|
+
buckets: [
|
|
488
|
+
{ zone_percentage: 0.25, risk_percentage: 0.1 },
|
|
489
|
+
{ zone_percentage: 0.5, risk_percentage: 0.4 },
|
|
490
|
+
{ zone_percentage: 0.25, risk_percentage: 0.5 }
|
|
491
|
+
]
|
|
492
|
+
}
|
|
135
493
|
}) {
|
|
136
494
|
let _stop_loss = stop_loss;
|
|
137
495
|
if (!_stop_loss && stop_percent) {
|
|
@@ -140,20 +498,36 @@ class Signal {
|
|
|
140
498
|
const percent_change = _stop_loss ? Math.max(current_price, _stop_loss) / Math.min(current_price, _stop_loss) - 1 : this.percent_change;
|
|
141
499
|
const _no_of_trades = no_of_trades || this.risk_reward;
|
|
142
500
|
let _resistance = current_price * Math.pow(1 + percent_change, 1);
|
|
501
|
+
const simple_support = Math.min(current_price, stop_loss);
|
|
502
|
+
const simple_resistance = Math.max(current_price, stop_loss);
|
|
503
|
+
const risk_per_trade = risk / this.risk_reward;
|
|
504
|
+
const use_progressive = distribution_params.use_progressive || this.use_progressive_risk;
|
|
505
|
+
const risk_distribution = use_progressive ? {
|
|
506
|
+
enabled: true,
|
|
507
|
+
total_risk_budget: risk,
|
|
508
|
+
risk_reward: this.risk_reward,
|
|
509
|
+
buckets: distribution_params.buckets || []
|
|
510
|
+
} : undefined;
|
|
143
511
|
const derivedConfig = {
|
|
144
512
|
...this,
|
|
145
513
|
percent_change,
|
|
146
514
|
focus: current_price,
|
|
147
|
-
resistance: _resistance,
|
|
148
|
-
risk_per_trade
|
|
515
|
+
resistance: distribution ? simple_resistance : _resistance,
|
|
516
|
+
risk_per_trade,
|
|
149
517
|
minimum_pnl: pnl,
|
|
150
518
|
risk_reward: _no_of_trades,
|
|
151
519
|
take_profit: take_profit || this.take_profit,
|
|
152
|
-
support: kind === "long" ? _stop_loss : this.support
|
|
520
|
+
support: distribution ? simple_support : kind === "long" ? _stop_loss : this.support,
|
|
521
|
+
full_distribution: distribution ? {
|
|
522
|
+
...this.distribution,
|
|
523
|
+
[kind]: distribution
|
|
524
|
+
} : undefined,
|
|
525
|
+
use_progressive_risk: use_progressive,
|
|
526
|
+
distribution_params,
|
|
527
|
+
risk_distribution
|
|
153
528
|
};
|
|
154
529
|
const instance = new Signal(derivedConfig);
|
|
155
|
-
if (kind === "short") {
|
|
156
|
-
}
|
|
530
|
+
if (kind === "short") {}
|
|
157
531
|
let result = instance.get_bulk_trade_zones({ current_price, kind });
|
|
158
532
|
return result;
|
|
159
533
|
return result?.filter((x) => {
|
|
@@ -309,7 +683,6 @@ class Signal {
|
|
|
309
683
|
let potentials = new_result.filter((x) => condition(x["entry"], i["risk_sell"])).map((x) => x["entry"]);
|
|
310
684
|
if (potentials.length && max_index) {
|
|
311
685
|
if (kind === "long") {
|
|
312
|
-
console.log("slice: ", potentials.slice(0, max_index));
|
|
313
686
|
i["risk_sell"] = Math.max(...potentials.slice(0, max_index));
|
|
314
687
|
} else {
|
|
315
688
|
i["risk_sell"] = Math.min(...potentials.slice(0, max_index));
|
|
@@ -366,13 +739,31 @@ class Signal {
|
|
|
366
739
|
}
|
|
367
740
|
this.zone_risk = original;
|
|
368
741
|
}
|
|
742
|
+
get_future_zones_simple({
|
|
743
|
+
current_price,
|
|
744
|
+
kind = "long",
|
|
745
|
+
raw
|
|
746
|
+
}) {
|
|
747
|
+
const margin_zones = [this.support, this.resistance];
|
|
748
|
+
const distribution = this.distribution ? this.distribution[kind] : "geometric";
|
|
749
|
+
let _kind = distribution === "inverse-exponential" ? kind === "long" ? "short" : "long" : kind;
|
|
750
|
+
const entries = distributions_default({
|
|
751
|
+
margin_range: margin_zones,
|
|
752
|
+
kind: _kind,
|
|
753
|
+
distribution,
|
|
754
|
+
risk_reward: this.risk_reward,
|
|
755
|
+
price_places: this.price_places,
|
|
756
|
+
distribution_params: this.distribution_params
|
|
757
|
+
});
|
|
758
|
+
return entries.sort((a, b) => a - b);
|
|
759
|
+
}
|
|
369
760
|
get_future_zones({
|
|
370
761
|
current_price,
|
|
371
762
|
kind = "long",
|
|
372
763
|
raw
|
|
373
764
|
}) {
|
|
374
|
-
if (raw) {
|
|
375
|
-
}
|
|
765
|
+
if (raw) {}
|
|
766
|
+
return this.get_future_zones_simple({ current_price, raw, kind });
|
|
376
767
|
const margin_range = this.get_margin_range(current_price, kind);
|
|
377
768
|
let margin_zones = this.get_margin_zones({ current_price });
|
|
378
769
|
let remaining_zones = margin_zones.filter((x) => JSON.stringify(x) != JSON.stringify(margin_range));
|
|
@@ -498,6 +889,24 @@ class Signal {
|
|
|
498
889
|
}
|
|
499
890
|
return null;
|
|
500
891
|
}
|
|
892
|
+
getZoneRisk(index, totalZones, account) {
|
|
893
|
+
if (!account.risk_distribution?.enabled) {
|
|
894
|
+
return account.risk_per_trade;
|
|
895
|
+
}
|
|
896
|
+
const { buckets, total_risk_budget } = account.risk_distribution;
|
|
897
|
+
const reversedBuckets = [...buckets].reverse();
|
|
898
|
+
let zoneStart = 0;
|
|
899
|
+
for (const bucket of reversedBuckets) {
|
|
900
|
+
const zoneEnd = zoneStart + Math.floor(totalZones * bucket.zone_percentage);
|
|
901
|
+
if (index < zoneEnd) {
|
|
902
|
+
const zonesInBucket = zoneEnd - zoneStart;
|
|
903
|
+
const bucket_total = total_risk_budget * bucket.risk_percentage;
|
|
904
|
+
return bucket_total / zonesInBucket;
|
|
905
|
+
}
|
|
906
|
+
zoneStart = zoneEnd;
|
|
907
|
+
}
|
|
908
|
+
return account.risk_per_trade;
|
|
909
|
+
}
|
|
501
910
|
process_orders({
|
|
502
911
|
current_price,
|
|
503
912
|
stop_loss,
|
|
@@ -560,10 +969,26 @@ class Signal {
|
|
|
560
969
|
}
|
|
561
970
|
const defaultStopLoss = i === 0 ? stop_loss : _base;
|
|
562
971
|
const new_stop = kind === "long" ? this.support : stop_loss;
|
|
972
|
+
let risk_to_use = this.getZoneRisk(i, limit_orders.length, this);
|
|
973
|
+
console.log("index: ", i, " risk: ", risk_to_use);
|
|
974
|
+
if (this.use_kelly) {
|
|
975
|
+
const func = this.kelly_func === "theoretical" ? calculateTheoreticalKelly : this.kelly_func === "position_based" ? calculatePositionBasedKelly : calculateTheoreticalKellyFixed;
|
|
976
|
+
const theoretical_kelly = func({
|
|
977
|
+
current_entry: x,
|
|
978
|
+
zone_prices: limit_orders,
|
|
979
|
+
kind,
|
|
980
|
+
config: {
|
|
981
|
+
price_prediction_model: this.kelly_prediction_model,
|
|
982
|
+
confidence_factor: this.kelly_confidence_factor,
|
|
983
|
+
volatility_adjustment: true
|
|
984
|
+
}
|
|
985
|
+
});
|
|
986
|
+
risk_to_use = theoretical_kelly * risk_per_trade / this.kelly_minimum_risk;
|
|
987
|
+
}
|
|
563
988
|
const y = this.build_trade_dict({
|
|
564
989
|
entry: x,
|
|
565
990
|
stop: (this.increase_position ? new_stop : defaultStopLoss) || defaultStopLoss,
|
|
566
|
-
risk:
|
|
991
|
+
risk: risk_to_use,
|
|
567
992
|
arr: limit_orders,
|
|
568
993
|
index: i,
|
|
569
994
|
new_fees: total_incurred_market_fees,
|
|
@@ -577,14 +1002,13 @@ class Signal {
|
|
|
577
1002
|
return y !== null ? y : undefined;
|
|
578
1003
|
}) || []).filter((y) => y !== undefined).filter((y) => {
|
|
579
1004
|
const min_options = [0.001, 0.002, 0.003];
|
|
580
|
-
if (min_options.includes(this.minimum_size)) {
|
|
581
|
-
return y.quantity <=
|
|
1005
|
+
if (min_options.includes(this.minimum_size) && this.symbol.toUpperCase().startsWith("BTC")) {
|
|
1006
|
+
return y.quantity <= this.max_quantity;
|
|
582
1007
|
}
|
|
583
1008
|
return true;
|
|
584
1009
|
});
|
|
585
1010
|
let total_orders = limit_trades.concat(market_trades);
|
|
586
|
-
if (kind === "short") {
|
|
587
|
-
}
|
|
1011
|
+
if (kind === "short") {}
|
|
588
1012
|
if (this.minimum_size && total_orders.length > 0) {
|
|
589
1013
|
let payload = total_orders;
|
|
590
1014
|
let greater_than_min_size = total_orders.filter((o) => o ? o.quantity >= this.minimum_size : true);
|
|
@@ -670,8 +1094,7 @@ class Signal {
|
|
|
670
1094
|
});
|
|
671
1095
|
const multiplier = start - index;
|
|
672
1096
|
const incurred_fees = fees.reduce((a, b) => a + b, 0) + previous_risks.reduce((a, b) => a + b, 0);
|
|
673
|
-
if (index === 0) {
|
|
674
|
-
}
|
|
1097
|
+
if (index === 0) {}
|
|
675
1098
|
let quantity = determine_position_size({
|
|
676
1099
|
entry,
|
|
677
1100
|
stop,
|
|
@@ -837,8 +1260,7 @@ function extractValue(_param, condition) {
|
|
|
837
1260
|
try {
|
|
838
1261
|
let value2 = JSON.parse(_param || "[]");
|
|
839
1262
|
param = value2.map((o) => parseFloat(o));
|
|
840
|
-
} catch (error) {
|
|
841
|
-
}
|
|
1263
|
+
} catch (error) {}
|
|
842
1264
|
} else {
|
|
843
1265
|
param = parseFloat(_param);
|
|
844
1266
|
}
|
|
@@ -853,8 +1275,7 @@ function asCoins(symbol) {
|
|
|
853
1275
|
if (symbol.toLowerCase().includes("-")) {
|
|
854
1276
|
result = result.split("-")[0];
|
|
855
1277
|
}
|
|
856
|
-
if (symbol.toLowerCase() == "usdt-usd") {
|
|
857
|
-
}
|
|
1278
|
+
if (symbol.toLowerCase() == "usdt-usd") {}
|
|
858
1279
|
let result2 = _type == "usdt" ? symbol.split(result)[0] : result;
|
|
859
1280
|
if (result.includes("-")) {
|
|
860
1281
|
result2 = result;
|
|
@@ -1153,7 +1574,64 @@ function computeSellZones(payload) {
|
|
|
1153
1574
|
const spread = factor - 1;
|
|
1154
1575
|
return Array.from({ length: zones }, (_, i) => entry * Math.pow(1 + spread, i));
|
|
1155
1576
|
}
|
|
1577
|
+
function determineTPSl(payload) {
|
|
1578
|
+
const { sell_ratio = 1, kind, positions, configs, decimal_places } = payload;
|
|
1579
|
+
const long_settings = configs.long;
|
|
1580
|
+
const short_settings = configs.short;
|
|
1581
|
+
const settings = kind === "long" ? long_settings : short_settings;
|
|
1582
|
+
const reverse_kind = kind === "long" ? "short" : "long";
|
|
1583
|
+
const _position = positions[kind];
|
|
1584
|
+
const reverse_position = positions[reverse_kind];
|
|
1585
|
+
const reduce_ratio = settings.reduce_ratio;
|
|
1586
|
+
const notion = _position.entry * _position.quantity;
|
|
1587
|
+
const profit_percent = settings.profit_percent;
|
|
1588
|
+
const places = decimal_places || reverse_position.decimal_places;
|
|
1589
|
+
if (profit_percent) {
|
|
1590
|
+
let quantity = to_f(_position.quantity * sell_ratio, places);
|
|
1591
|
+
const as_float = parseFloat(profit_percent) * sell_ratio;
|
|
1592
|
+
const pnl = to_f(as_float * notion / 100, "%.2f");
|
|
1593
|
+
const diff = pnl / quantity;
|
|
1594
|
+
const tp_price = to_f(kind === "long" ? _position.entry + diff : _position.entry - diff, _position.price_places);
|
|
1595
|
+
const expected_loss = to_f(parseFloat(reduce_ratio) * pnl, "%.2f");
|
|
1596
|
+
let reduce_quantity = 0;
|
|
1597
|
+
if (reverse_position.quantity > 0) {
|
|
1598
|
+
const total_loss = Math.abs(tp_price - reverse_position.entry) * reverse_position.quantity;
|
|
1599
|
+
const ratio = expected_loss / total_loss;
|
|
1600
|
+
reduce_quantity = to_f(reverse_position.quantity * ratio, places);
|
|
1601
|
+
}
|
|
1602
|
+
return {
|
|
1603
|
+
tp_price,
|
|
1604
|
+
pnl,
|
|
1605
|
+
quantity,
|
|
1606
|
+
reduce_quantity,
|
|
1607
|
+
expected_loss
|
|
1608
|
+
};
|
|
1609
|
+
}
|
|
1610
|
+
return {
|
|
1611
|
+
tp_price: 0,
|
|
1612
|
+
pnl: 0,
|
|
1613
|
+
quantity: 0,
|
|
1614
|
+
reduce_quantity: 0,
|
|
1615
|
+
expected_loss: 0
|
|
1616
|
+
};
|
|
1617
|
+
}
|
|
1156
1618
|
// src/helpers/shared.ts
|
|
1619
|
+
function getMaxQuantity(x, app_config) {
|
|
1620
|
+
let max_quantity = app_config.max_quantity;
|
|
1621
|
+
if (max_quantity) {
|
|
1622
|
+
return x <= max_quantity;
|
|
1623
|
+
}
|
|
1624
|
+
if (app_config.symbol === "BTCUSDT") {
|
|
1625
|
+
max_quantity = 0.03;
|
|
1626
|
+
}
|
|
1627
|
+
if (app_config.symbol?.toLowerCase().startsWith("sol")) {
|
|
1628
|
+
max_quantity = 2;
|
|
1629
|
+
}
|
|
1630
|
+
if (!max_quantity) {
|
|
1631
|
+
return true;
|
|
1632
|
+
}
|
|
1633
|
+
return x <= max_quantity;
|
|
1634
|
+
}
|
|
1157
1635
|
function buildConfig(app_config, {
|
|
1158
1636
|
take_profit,
|
|
1159
1637
|
entry,
|
|
@@ -1168,7 +1646,16 @@ function buildConfig(app_config, {
|
|
|
1168
1646
|
gap,
|
|
1169
1647
|
rr = 1,
|
|
1170
1648
|
price_places = "%.1f",
|
|
1171
|
-
decimal_places = "%.3f"
|
|
1649
|
+
decimal_places = "%.3f",
|
|
1650
|
+
use_kelly = false,
|
|
1651
|
+
kelly_confidence_factor = 0.95,
|
|
1652
|
+
kelly_minimum_risk = 0.2,
|
|
1653
|
+
kelly_prediction_model = "exponential",
|
|
1654
|
+
kelly_func = "theoretical",
|
|
1655
|
+
min_avg_size = 0,
|
|
1656
|
+
distribution,
|
|
1657
|
+
distribution_params,
|
|
1658
|
+
use_progressive_risk
|
|
1172
1659
|
}) {
|
|
1173
1660
|
let fee = app_config.fee / 100;
|
|
1174
1661
|
let working_risk = risk || app_config.risk_per_trade;
|
|
@@ -1192,7 +1679,16 @@ function buildConfig(app_config, {
|
|
|
1192
1679
|
kind: app_config.kind,
|
|
1193
1680
|
gap,
|
|
1194
1681
|
min_profit: min_profit || app_config.min_profit,
|
|
1195
|
-
rr: rr || 1
|
|
1682
|
+
rr: rr || 1,
|
|
1683
|
+
use_kelly: use_kelly || app_config.kelly?.use_kelly,
|
|
1684
|
+
kelly_confidence_factor: kelly_confidence_factor || app_config.kelly?.kelly_confidence_factor,
|
|
1685
|
+
kelly_minimum_risk: kelly_minimum_risk || app_config.kelly?.kelly_minimum_risk,
|
|
1686
|
+
kelly_prediction_model: kelly_prediction_model || app_config.kelly?.kelly_prediction_model,
|
|
1687
|
+
kelly_func: kelly_func || app_config.kelly?.kelly_func,
|
|
1688
|
+
symbol: app_config.symbol,
|
|
1689
|
+
max_quantity: app_config.max_quantity,
|
|
1690
|
+
distribution_params: distribution_params || app_config.distribution_params,
|
|
1691
|
+
use_progressive_risk
|
|
1196
1692
|
};
|
|
1197
1693
|
const instance = new Signal(config);
|
|
1198
1694
|
if (raw_instance) {
|
|
@@ -1202,16 +1698,19 @@ function buildConfig(app_config, {
|
|
|
1202
1698
|
return [];
|
|
1203
1699
|
}
|
|
1204
1700
|
const condition = (kind === "long" ? entry > app_config.support : entry >= app_config.support) && stop >= app_config.support * 0.999;
|
|
1205
|
-
if (kind === "short") {
|
|
1206
|
-
}
|
|
1701
|
+
if (kind === "short") {}
|
|
1207
1702
|
const result = entry === stop ? [] : condition ? instance.build_entry({
|
|
1208
1703
|
current_price: entry,
|
|
1209
1704
|
stop_loss: stop,
|
|
1210
1705
|
risk: working_risk,
|
|
1211
1706
|
kind: kind || app_config.kind,
|
|
1212
|
-
no_of_trades: trade_no
|
|
1707
|
+
no_of_trades: trade_no,
|
|
1708
|
+
distribution,
|
|
1709
|
+
distribution_params
|
|
1213
1710
|
}) || [] : [];
|
|
1214
|
-
|
|
1711
|
+
const new_trades = computeTotalAverageForEachTrade(result, config);
|
|
1712
|
+
let filtered = new_trades.filter((o) => o.avg_size > min_avg_size);
|
|
1713
|
+
return computeTotalAverageForEachTrade(filtered, config);
|
|
1215
1714
|
}
|
|
1216
1715
|
function buildAvg({
|
|
1217
1716
|
_trades,
|
|
@@ -1228,13 +1727,7 @@ function buildAvg({
|
|
|
1228
1727
|
}
|
|
1229
1728
|
function sortedBuildConfig(app_config, options) {
|
|
1230
1729
|
const sorted = buildConfig(app_config, options).sort((a, b) => app_config.kind === "long" ? a.entry - b.entry : b.entry - b.entry).filter((x) => {
|
|
1231
|
-
|
|
1232
|
-
return x.quantity <= 0.03;
|
|
1233
|
-
}
|
|
1234
|
-
if (app_config.symbol?.toLowerCase().startsWith("sol")) {
|
|
1235
|
-
return x.quantity <= 2;
|
|
1236
|
-
}
|
|
1237
|
-
return true;
|
|
1730
|
+
return getMaxQuantity(x.quantity, app_config);
|
|
1238
1731
|
});
|
|
1239
1732
|
return sorted.map((k, i) => {
|
|
1240
1733
|
const arrSet = sorted.slice(0, i + 1);
|
|
@@ -1265,7 +1758,8 @@ function get_app_config_and_max_size(config, payload) {
|
|
|
1265
1758
|
price_places: config.price_places,
|
|
1266
1759
|
decimal_places: config.decimal_places,
|
|
1267
1760
|
min_profit: config.profit_percent * config.profit / 100,
|
|
1268
|
-
symbol: config.symbol
|
|
1761
|
+
symbol: config.symbol,
|
|
1762
|
+
max_quantity: config.max_quantity
|
|
1269
1763
|
};
|
|
1270
1764
|
const initialResult = sortedBuildConfig(app_config, {
|
|
1271
1765
|
entry: app_config.entry,
|
|
@@ -1276,7 +1770,14 @@ function get_app_config_and_max_size(config, payload) {
|
|
|
1276
1770
|
increase: true,
|
|
1277
1771
|
gap: app_config.gap,
|
|
1278
1772
|
price_places: app_config.price_places,
|
|
1279
|
-
decimal_places: app_config.decimal_places
|
|
1773
|
+
decimal_places: app_config.decimal_places,
|
|
1774
|
+
use_kelly: payload.use_kelly,
|
|
1775
|
+
kelly_confidence_factor: payload.kelly_confidence_factor,
|
|
1776
|
+
kelly_minimum_risk: payload.kelly_minimum_risk,
|
|
1777
|
+
kelly_prediction_model: payload.kelly_prediction_model,
|
|
1778
|
+
kelly_func: payload.kelly_func,
|
|
1779
|
+
distribution: payload.distribution,
|
|
1780
|
+
distribution_params: payload.distribution_params
|
|
1280
1781
|
});
|
|
1281
1782
|
const max_size = initialResult[0]?.avg_size;
|
|
1282
1783
|
const last_value = initialResult[0];
|
|
@@ -1305,21 +1806,42 @@ function buildAppConfig(config, payload) {
|
|
|
1305
1806
|
reverse_factor: 1,
|
|
1306
1807
|
profit_percent: 0,
|
|
1307
1808
|
kind: payload.entry > payload.stop ? "long" : "short",
|
|
1308
|
-
symbol: payload.symbol
|
|
1809
|
+
symbol: payload.symbol || config.symbol
|
|
1309
1810
|
}, {
|
|
1310
1811
|
entry: payload.entry,
|
|
1311
1812
|
stop: payload.stop,
|
|
1312
|
-
kind: payload.entry > payload.stop ? "long" : "short"
|
|
1813
|
+
kind: payload.entry > payload.stop ? "long" : "short",
|
|
1814
|
+
use_kelly: payload.use_kelly,
|
|
1815
|
+
kelly_confidence_factor: payload.kelly_confidence_factor,
|
|
1816
|
+
kelly_minimum_risk: payload.kelly_minimum_risk,
|
|
1817
|
+
kelly_prediction_model: payload.kelly_prediction_model,
|
|
1818
|
+
kelly_func: payload.kelly_func,
|
|
1819
|
+
distribution: payload.distribution,
|
|
1820
|
+
distribution_params: payload.distribution_params
|
|
1313
1821
|
});
|
|
1314
1822
|
app_config.max_size = max_size;
|
|
1315
1823
|
app_config.entry = payload.entry || app_config.entry;
|
|
1316
1824
|
app_config.stop = payload.stop || app_config.stop;
|
|
1317
1825
|
app_config.last_value = last_value;
|
|
1318
1826
|
app_config.entries = entries;
|
|
1827
|
+
app_config.kelly = {
|
|
1828
|
+
use_kelly: payload.use_kelly,
|
|
1829
|
+
kelly_confidence_factor: payload.kelly_confidence_factor,
|
|
1830
|
+
kelly_minimum_risk: payload.kelly_minimum_risk,
|
|
1831
|
+
kelly_prediction_model: payload.kelly_prediction_model,
|
|
1832
|
+
kelly_func: payload.kelly_func
|
|
1833
|
+
};
|
|
1834
|
+
app_config.distribution = payload.distribution;
|
|
1835
|
+
app_config.distribution_params = payload.distribution_params;
|
|
1319
1836
|
return app_config;
|
|
1320
1837
|
}
|
|
1321
1838
|
function getOptimumStopAndRisk(app_config, params) {
|
|
1322
|
-
const {
|
|
1839
|
+
const {
|
|
1840
|
+
max_size,
|
|
1841
|
+
target_stop,
|
|
1842
|
+
distribution,
|
|
1843
|
+
distribution_params: _distribution_params
|
|
1844
|
+
} = params;
|
|
1323
1845
|
const isLong = app_config.kind === "long";
|
|
1324
1846
|
const stopRange = Math.abs(app_config.entry - target_stop) * 0.5;
|
|
1325
1847
|
let low_stop = isLong ? target_stop - stopRange : Math.max(target_stop - stopRange, app_config.entry);
|
|
@@ -1342,7 +1864,9 @@ function getOptimumStopAndRisk(app_config, params) {
|
|
|
1342
1864
|
increase: true,
|
|
1343
1865
|
gap: app_config.gap,
|
|
1344
1866
|
price_places: app_config.price_places,
|
|
1345
|
-
decimal_places: app_config.decimal_places
|
|
1867
|
+
decimal_places: app_config.decimal_places,
|
|
1868
|
+
distribution,
|
|
1869
|
+
distribution_params: _distribution_params
|
|
1346
1870
|
});
|
|
1347
1871
|
if (result.length === 0) {
|
|
1348
1872
|
if (isLong) {
|
|
@@ -1396,7 +1920,9 @@ function getOptimumStopAndRisk(app_config, params) {
|
|
|
1396
1920
|
increase: true,
|
|
1397
1921
|
gap: app_config.gap,
|
|
1398
1922
|
price_places: app_config.price_places,
|
|
1399
|
-
decimal_places: app_config.decimal_places
|
|
1923
|
+
decimal_places: app_config.decimal_places,
|
|
1924
|
+
distribution,
|
|
1925
|
+
distribution_params: _distribution_params
|
|
1400
1926
|
});
|
|
1401
1927
|
if (result.length === 0) {
|
|
1402
1928
|
high_risk = mid_risk;
|
|
@@ -1441,7 +1967,9 @@ function getOptimumStopAndRisk(app_config, params) {
|
|
|
1441
1967
|
increase: true,
|
|
1442
1968
|
gap: app_config.gap,
|
|
1443
1969
|
price_places: app_config.price_places,
|
|
1444
|
-
decimal_places: app_config.decimal_places
|
|
1970
|
+
decimal_places: app_config.decimal_places,
|
|
1971
|
+
distribution,
|
|
1972
|
+
distribution_params: _distribution_params
|
|
1445
1973
|
});
|
|
1446
1974
|
if (result.length === 0)
|
|
1447
1975
|
continue;
|
|
@@ -1564,7 +2092,8 @@ function generateOptimumAppConfig(config, payload, position2) {
|
|
|
1564
2092
|
}, {
|
|
1565
2093
|
entry: payload.entry,
|
|
1566
2094
|
stop: payload.stop,
|
|
1567
|
-
kind: position2.kind
|
|
2095
|
+
kind: position2.kind,
|
|
2096
|
+
distribution: payload.distribution
|
|
1568
2097
|
});
|
|
1569
2098
|
current_app_config.max_size = max_size;
|
|
1570
2099
|
current_app_config.last_value = last_value;
|
|
@@ -1579,7 +2108,8 @@ function generateOptimumAppConfig(config, payload, position2) {
|
|
|
1579
2108
|
increase: true,
|
|
1580
2109
|
gap: current_app_config.gap,
|
|
1581
2110
|
price_places: current_app_config.price_places,
|
|
1582
|
-
decimal_places: current_app_config.decimal_places
|
|
2111
|
+
decimal_places: current_app_config.decimal_places,
|
|
2112
|
+
distribution: payload.distribution
|
|
1583
2113
|
});
|
|
1584
2114
|
if (full_trades.length === 0) {
|
|
1585
2115
|
high_risk = mid_risk;
|
|
@@ -1637,7 +2167,16 @@ function generateOptimumAppConfig(config, payload, position2) {
|
|
|
1637
2167
|
});
|
|
1638
2168
|
return best_app_config;
|
|
1639
2169
|
}
|
|
1640
|
-
function determineOptimumReward(
|
|
2170
|
+
function determineOptimumReward(payload) {
|
|
2171
|
+
const {
|
|
2172
|
+
app_config,
|
|
2173
|
+
increase = true,
|
|
2174
|
+
low_range = 1,
|
|
2175
|
+
high_range = 199,
|
|
2176
|
+
target_loss,
|
|
2177
|
+
distribution,
|
|
2178
|
+
max_size
|
|
2179
|
+
} = payload;
|
|
1641
2180
|
const criterion = app_config.strategy || "quantity";
|
|
1642
2181
|
const risk_rewards = createArray(low_range, high_range, 1);
|
|
1643
2182
|
let func = risk_rewards.map((trade_no) => {
|
|
@@ -1650,13 +2189,16 @@ function determineOptimumReward(app_config, increase = true, low_range = 30, hig
|
|
|
1650
2189
|
increase,
|
|
1651
2190
|
kind: app_config.kind,
|
|
1652
2191
|
gap: app_config.gap,
|
|
1653
|
-
decimal_places: app_config.decimal_places
|
|
2192
|
+
decimal_places: app_config.decimal_places,
|
|
2193
|
+
distribution,
|
|
2194
|
+
distribution_params: payload.distribution_params
|
|
1654
2195
|
});
|
|
1655
2196
|
let total = 0;
|
|
1656
2197
|
let max = -Infinity;
|
|
1657
2198
|
let min = Infinity;
|
|
1658
2199
|
let neg_pnl = trades[0]?.neg_pnl || 0;
|
|
1659
2200
|
let entry = trades.at(-1)?.entry;
|
|
2201
|
+
let avg_size = trades[0]?.avg_size || 0;
|
|
1660
2202
|
if (!entry) {
|
|
1661
2203
|
return null;
|
|
1662
2204
|
}
|
|
@@ -1673,14 +2215,41 @@ function determineOptimumReward(app_config, increase = true, low_range = 30, hig
|
|
|
1673
2215
|
risk_per_trade: app_config.risk_per_trade,
|
|
1674
2216
|
max,
|
|
1675
2217
|
min,
|
|
2218
|
+
avg_size,
|
|
1676
2219
|
neg_pnl,
|
|
1677
2220
|
entry
|
|
1678
2221
|
};
|
|
1679
2222
|
});
|
|
1680
|
-
func = func.filter((r) => Boolean(r))
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
}
|
|
2223
|
+
func = func.filter((r) => Boolean(r));
|
|
2224
|
+
if (max_size !== undefined && max_size > 0) {
|
|
2225
|
+
func = func.filter((r) => r.avg_size <= max_size);
|
|
2226
|
+
}
|
|
2227
|
+
if (target_loss === undefined) {
|
|
2228
|
+
func = func.filter((r) => {
|
|
2229
|
+
let foundIndex = r?.result.findIndex((e) => e.quantity === r.max);
|
|
2230
|
+
return criterion === "quantity" ? foundIndex === 0 : true;
|
|
2231
|
+
});
|
|
2232
|
+
}
|
|
2233
|
+
if (target_loss !== undefined) {
|
|
2234
|
+
const validResults = func.filter((r) => Math.abs(r.neg_pnl) <= target_loss);
|
|
2235
|
+
if (validResults.length > 0) {
|
|
2236
|
+
validResults.sort((a, b) => {
|
|
2237
|
+
const diffA = target_loss - Math.abs(a.neg_pnl);
|
|
2238
|
+
const diffB = target_loss - Math.abs(b.neg_pnl);
|
|
2239
|
+
return diffA - diffB;
|
|
2240
|
+
});
|
|
2241
|
+
if (app_config.raw) {
|
|
2242
|
+
return validResults[0];
|
|
2243
|
+
}
|
|
2244
|
+
return validResults[0]?.value;
|
|
2245
|
+
} else {
|
|
2246
|
+
func.sort((a, b) => Math.abs(a.neg_pnl) - Math.abs(b.neg_pnl));
|
|
2247
|
+
if (app_config.raw) {
|
|
2248
|
+
return func[0];
|
|
2249
|
+
}
|
|
2250
|
+
return func[0]?.value;
|
|
2251
|
+
}
|
|
2252
|
+
}
|
|
1684
2253
|
const highest = criterion === "quantity" ? Math.max(...func.map((o) => o.max)) : Math.min(...func.map((o) => o.entry));
|
|
1685
2254
|
const key = criterion === "quantity" ? "max" : "entry";
|
|
1686
2255
|
const index = findIndexByCondition(func, app_config.kind, (x) => x[key] == highest, criterion);
|
|
@@ -1714,6 +2283,7 @@ function findIndexByCondition(lst, kind, condition, defaultKey = "neg_pnl") {
|
|
|
1714
2283
|
return b.net_diff - a.net_diff;
|
|
1715
2284
|
}
|
|
1716
2285
|
});
|
|
2286
|
+
console.log("found", sortedFound);
|
|
1717
2287
|
if (defaultKey === "quantity") {
|
|
1718
2288
|
return sortedFound[0].index;
|
|
1719
2289
|
}
|
|
@@ -1734,58 +2304,154 @@ function findIndexByCondition(lst, kind, condition, defaultKey = "neg_pnl") {
|
|
|
1734
2304
|
}, found[0]);
|
|
1735
2305
|
return maximum;
|
|
1736
2306
|
}
|
|
2307
|
+
function determineOptimumRisk(config, payload, params) {
|
|
2308
|
+
const { highest_risk, tolerance = 0.01, max_iterations = 200 } = params;
|
|
2309
|
+
let low_risk = 1;
|
|
2310
|
+
let high_risk = Math.max(highest_risk * 5, payload.risk * 10);
|
|
2311
|
+
let best_risk = payload.risk;
|
|
2312
|
+
let best_neg_pnl = 0;
|
|
2313
|
+
let best_diff = Infinity;
|
|
2314
|
+
console.log(`Finding optimal risk for target neg_pnl: ${highest_risk}`);
|
|
2315
|
+
let iterations = 0;
|
|
2316
|
+
while (iterations < max_iterations && high_risk - low_risk > tolerance) {
|
|
2317
|
+
iterations++;
|
|
2318
|
+
const mid_risk = (low_risk + high_risk) / 2;
|
|
2319
|
+
const test_payload = {
|
|
2320
|
+
...payload,
|
|
2321
|
+
risk: mid_risk
|
|
2322
|
+
};
|
|
2323
|
+
const { last_value } = buildAppConfig(config, test_payload);
|
|
2324
|
+
if (!last_value || !last_value.neg_pnl) {
|
|
2325
|
+
high_risk = mid_risk;
|
|
2326
|
+
continue;
|
|
2327
|
+
}
|
|
2328
|
+
const current_neg_pnl = Math.abs(last_value.neg_pnl);
|
|
2329
|
+
const diff = Math.abs(current_neg_pnl - highest_risk);
|
|
2330
|
+
console.log(`Iteration ${iterations}: Risk=${mid_risk.toFixed(2)}, neg_pnl=${current_neg_pnl.toFixed(2)}, diff=${diff.toFixed(2)}`);
|
|
2331
|
+
if (diff < best_diff) {
|
|
2332
|
+
best_diff = diff;
|
|
2333
|
+
best_risk = mid_risk;
|
|
2334
|
+
best_neg_pnl = current_neg_pnl;
|
|
2335
|
+
}
|
|
2336
|
+
if (diff <= tolerance) {
|
|
2337
|
+
console.log(`Converged! Optimal risk: ${mid_risk.toFixed(2)}`);
|
|
2338
|
+
break;
|
|
2339
|
+
}
|
|
2340
|
+
if (current_neg_pnl < highest_risk) {
|
|
2341
|
+
low_risk = mid_risk;
|
|
2342
|
+
} else {
|
|
2343
|
+
high_risk = mid_risk;
|
|
2344
|
+
}
|
|
2345
|
+
}
|
|
2346
|
+
const final_payload = {
|
|
2347
|
+
...payload,
|
|
2348
|
+
risk: best_risk
|
|
2349
|
+
};
|
|
2350
|
+
const final_result = buildAppConfig(config, final_payload);
|
|
2351
|
+
return {
|
|
2352
|
+
optimal_risk: to_f(best_risk, "%.2f"),
|
|
2353
|
+
achieved_neg_pnl: to_f(best_neg_pnl, "%.2f"),
|
|
2354
|
+
target_neg_pnl: to_f(highest_risk, "%.2f"),
|
|
2355
|
+
difference: to_f(best_diff, "%.2f"),
|
|
2356
|
+
iterations,
|
|
2357
|
+
converged: best_diff <= tolerance,
|
|
2358
|
+
last_value: final_result.last_value,
|
|
2359
|
+
entries: final_result.entries,
|
|
2360
|
+
app_config: final_result
|
|
2361
|
+
};
|
|
2362
|
+
}
|
|
1737
2363
|
function computeRiskReward(payload) {
|
|
1738
|
-
const {
|
|
2364
|
+
const {
|
|
2365
|
+
app_config,
|
|
2366
|
+
entry,
|
|
2367
|
+
stop,
|
|
2368
|
+
risk_per_trade,
|
|
2369
|
+
target_loss,
|
|
2370
|
+
distribution,
|
|
2371
|
+
high_range,
|
|
2372
|
+
max_size
|
|
2373
|
+
} = payload;
|
|
1739
2374
|
const kind = entry > stop ? "long" : "short";
|
|
1740
2375
|
app_config.kind = kind;
|
|
1741
2376
|
app_config.entry = entry;
|
|
1742
2377
|
app_config.stop = stop;
|
|
1743
2378
|
app_config.risk_per_trade = risk_per_trade;
|
|
1744
|
-
const result = determineOptimumReward(
|
|
2379
|
+
const result = determineOptimumReward({
|
|
2380
|
+
app_config,
|
|
2381
|
+
target_loss,
|
|
2382
|
+
distribution,
|
|
2383
|
+
distribution_params: payload.distribution_params,
|
|
2384
|
+
high_range,
|
|
2385
|
+
max_size
|
|
2386
|
+
});
|
|
1745
2387
|
return result;
|
|
1746
2388
|
}
|
|
1747
2389
|
function getRiskReward(payload) {
|
|
1748
|
-
const {
|
|
2390
|
+
const {
|
|
2391
|
+
high_range,
|
|
2392
|
+
max_size,
|
|
2393
|
+
entry,
|
|
2394
|
+
stop,
|
|
2395
|
+
risk,
|
|
2396
|
+
global_config,
|
|
2397
|
+
force_exact_risk = false,
|
|
2398
|
+
target_loss,
|
|
2399
|
+
distribution,
|
|
2400
|
+
risk_factor = 1
|
|
2401
|
+
} = payload;
|
|
1749
2402
|
const { entries, last_value, ...app_config } = buildAppConfig(global_config, {
|
|
1750
2403
|
entry,
|
|
1751
2404
|
stop,
|
|
1752
2405
|
risk_reward: 30,
|
|
1753
2406
|
risk,
|
|
1754
|
-
symbol: global_config.symbol
|
|
2407
|
+
symbol: global_config.symbol,
|
|
2408
|
+
distribution,
|
|
2409
|
+
distribution_params: payload.distribution_params
|
|
1755
2410
|
});
|
|
1756
2411
|
const risk_reward = computeRiskReward({
|
|
1757
2412
|
app_config,
|
|
1758
2413
|
entry,
|
|
1759
2414
|
stop,
|
|
1760
|
-
risk_per_trade: risk
|
|
2415
|
+
risk_per_trade: risk,
|
|
2416
|
+
high_range,
|
|
2417
|
+
target_loss,
|
|
2418
|
+
distribution,
|
|
2419
|
+
distribution_params: payload.distribution_params,
|
|
2420
|
+
max_size
|
|
1761
2421
|
});
|
|
2422
|
+
if (force_exact_risk) {
|
|
2423
|
+
const new_risk_per_trade = determineOptimumRisk(global_config, {
|
|
2424
|
+
entry,
|
|
2425
|
+
stop,
|
|
2426
|
+
risk_reward,
|
|
2427
|
+
risk,
|
|
2428
|
+
symbol: global_config.symbol,
|
|
2429
|
+
distribution,
|
|
2430
|
+
distribution_params: payload.distribution_params
|
|
2431
|
+
}, {
|
|
2432
|
+
highest_risk: risk * risk_factor
|
|
2433
|
+
}).optimal_risk;
|
|
2434
|
+
return { risk: new_risk_per_trade, risk_reward };
|
|
2435
|
+
}
|
|
1762
2436
|
return risk_reward;
|
|
1763
2437
|
}
|
|
1764
2438
|
function computeProfitDetail(payload) {
|
|
1765
2439
|
const {
|
|
1766
2440
|
focus_position,
|
|
1767
2441
|
strategy,
|
|
2442
|
+
pnl,
|
|
1768
2443
|
price_places = "%.1f",
|
|
1769
2444
|
reduce_position,
|
|
1770
2445
|
decimal_places,
|
|
1771
|
-
reverse_position
|
|
2446
|
+
reverse_position,
|
|
2447
|
+
full_ratio = 1
|
|
1772
2448
|
} = payload;
|
|
1773
|
-
let reward_factor = strategy
|
|
1774
|
-
|
|
1775
|
-
if (strategy.max_reward_factor === 0) {
|
|
1776
|
-
reward_factor = strategy.reward_factor;
|
|
1777
|
-
}
|
|
1778
|
-
if (focus_position.avg_qty >= focus_position.quantity && strategy.max_reward_factor) {
|
|
1779
|
-
reward_factor = to_f(focus_position.quantity * strategy.max_reward_factor / focus_position.avg_qty, "%.4f");
|
|
1780
|
-
} else {
|
|
1781
|
-
reward_factor = strategy.reward_factor;
|
|
1782
|
-
}
|
|
1783
|
-
const full_pnl = reward_factor * risk;
|
|
1784
|
-
const profit_percent = to_f(full_pnl * 100 / (focus_position.avg_price * focus_position.avg_qty), "%.4f");
|
|
1785
|
-
const pnl = to_f(focus_position.entry * focus_position.quantity * profit_percent / 100, "%.2f");
|
|
2449
|
+
let reward_factor = strategy?.reward_factor || 1;
|
|
2450
|
+
const profit_percent = to_f(pnl * 100 / (focus_position.avg_price * focus_position.avg_qty), "%.4f");
|
|
1786
2451
|
const diff = pnl / focus_position.quantity;
|
|
1787
2452
|
const sell_price = to_f(focus_position.kind === "long" ? focus_position.entry + diff : focus_position.entry - diff, price_places);
|
|
1788
2453
|
let loss = 0;
|
|
2454
|
+
let full_loss = 0;
|
|
1789
2455
|
let expected_loss = 0;
|
|
1790
2456
|
let quantity = 0;
|
|
1791
2457
|
let new_pnl = pnl;
|
|
@@ -1793,6 +2459,8 @@ function computeProfitDetail(payload) {
|
|
|
1793
2459
|
loss = Math.abs(reduce_position.entry - sell_price) * reduce_position.quantity;
|
|
1794
2460
|
const ratio = pnl / loss;
|
|
1795
2461
|
quantity = to_f(reduce_position.quantity * ratio, decimal_places);
|
|
2462
|
+
expected_loss = to_f(Math.abs(reduce_position.entry - sell_price) * quantity, "%.2f");
|
|
2463
|
+
full_loss = Math.abs(reduce_position.avg_price - sell_price) * reduce_position.avg_qty * full_ratio;
|
|
1796
2464
|
}
|
|
1797
2465
|
if (reverse_position) {
|
|
1798
2466
|
expected_loss = Math.abs(reverse_position.avg_price - sell_price) * reverse_position.avg_qty;
|
|
@@ -1801,12 +2469,13 @@ function computeProfitDetail(payload) {
|
|
|
1801
2469
|
return {
|
|
1802
2470
|
pnl: new_pnl,
|
|
1803
2471
|
loss: to_f(expected_loss, "%.2f"),
|
|
2472
|
+
full_loss: to_f(full_loss, "%.2f"),
|
|
1804
2473
|
original_pnl: pnl,
|
|
1805
2474
|
reward_factor,
|
|
1806
2475
|
profit_percent,
|
|
1807
2476
|
kind: focus_position.kind,
|
|
1808
2477
|
sell_price: to_f(sell_price, price_places),
|
|
1809
|
-
quantity,
|
|
2478
|
+
quantity: quantity * full_ratio,
|
|
1810
2479
|
price_places,
|
|
1811
2480
|
decimal_places
|
|
1812
2481
|
};
|
|
@@ -1815,10 +2484,26 @@ function generateGapTp(payload) {
|
|
|
1815
2484
|
const {
|
|
1816
2485
|
long,
|
|
1817
2486
|
short,
|
|
1818
|
-
factor =
|
|
2487
|
+
factor: factor_value = 0,
|
|
2488
|
+
risk: desired_risk,
|
|
2489
|
+
sell_factor = 1,
|
|
2490
|
+
kind,
|
|
1819
2491
|
price_places = "%.1f",
|
|
1820
2492
|
decimal_places = "%.3f"
|
|
1821
2493
|
} = payload;
|
|
2494
|
+
if (!factor_value && !desired_risk) {
|
|
2495
|
+
throw new Error("Either factor or risk must be provided");
|
|
2496
|
+
}
|
|
2497
|
+
if (desired_risk && !kind) {
|
|
2498
|
+
throw new Error("Kind must be provided when risk is provided");
|
|
2499
|
+
}
|
|
2500
|
+
let factor = factor_value || calculate_factor({
|
|
2501
|
+
long,
|
|
2502
|
+
short,
|
|
2503
|
+
risk: desired_risk,
|
|
2504
|
+
kind,
|
|
2505
|
+
sell_factor
|
|
2506
|
+
});
|
|
1822
2507
|
const gap = Math.abs(long.entry - short.entry);
|
|
1823
2508
|
const max_quantity = Math.max(long.quantity, short.quantity);
|
|
1824
2509
|
const gapLoss = gap * max_quantity;
|
|
@@ -1828,14 +2513,36 @@ function generateGapTp(payload) {
|
|
|
1828
2513
|
const shortTp = to_f((1 + shortPercent) ** -1 * short.entry, price_places);
|
|
1829
2514
|
const shortToReduce = to_f(Math.abs(longTp - long.entry) * long.quantity, "%.1f");
|
|
1830
2515
|
const longToReduce = to_f(Math.abs(shortTp - short.entry) * short.quantity, "%.1f");
|
|
1831
|
-
const
|
|
1832
|
-
const
|
|
2516
|
+
const actualShortReduce = to_f(shortToReduce * sell_factor, "%.1f");
|
|
2517
|
+
const actualLongReduce = to_f(longToReduce * sell_factor, "%.1f");
|
|
2518
|
+
const short_quantity_to_sell = determine_amount_to_sell2(short.entry, short.quantity, longTp, actualShortReduce, "short", decimal_places);
|
|
2519
|
+
const long_quantity_to_sell = determine_amount_to_sell2(long.entry, long.quantity, shortTp, actualLongReduce, "long", decimal_places);
|
|
2520
|
+
const risk_amount_short = to_f(shortToReduce - actualShortReduce, "%.2f");
|
|
2521
|
+
const risk_amount_long = to_f(longToReduce - actualLongReduce, "%.2f");
|
|
2522
|
+
const profit_percent_long = to_f(shortToReduce * 100 / (long.entry * long.quantity), "%.4f");
|
|
2523
|
+
const profit_percent_short = to_f(longToReduce * 100 / (short.entry * short.quantity), "%.4f");
|
|
1833
2524
|
return {
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
2525
|
+
profit_percent: {
|
|
2526
|
+
long: profit_percent_long,
|
|
2527
|
+
short: profit_percent_short
|
|
2528
|
+
},
|
|
2529
|
+
risk: {
|
|
2530
|
+
short: risk_amount_short,
|
|
2531
|
+
long: risk_amount_long
|
|
2532
|
+
},
|
|
2533
|
+
take_profit: {
|
|
2534
|
+
long: longTp,
|
|
2535
|
+
short: shortTp
|
|
2536
|
+
},
|
|
2537
|
+
to_reduce: {
|
|
2538
|
+
short: actualShortReduce,
|
|
2539
|
+
long: actualLongReduce
|
|
2540
|
+
},
|
|
2541
|
+
full_reduce: {
|
|
2542
|
+
short: shortToReduce,
|
|
2543
|
+
long: longToReduce
|
|
2544
|
+
},
|
|
2545
|
+
sell_quantity: {
|
|
1839
2546
|
short: short_quantity_to_sell,
|
|
1840
2547
|
long: long_quantity_to_sell
|
|
1841
2548
|
},
|
|
@@ -1843,6 +2550,370 @@ function generateGapTp(payload) {
|
|
|
1843
2550
|
gap_loss: to_f(gapLoss, "%.2f")
|
|
1844
2551
|
};
|
|
1845
2552
|
}
|
|
2553
|
+
function calculate_factor(payload) {
|
|
2554
|
+
const {
|
|
2555
|
+
long,
|
|
2556
|
+
short,
|
|
2557
|
+
risk: desired_risk,
|
|
2558
|
+
kind,
|
|
2559
|
+
sell_factor,
|
|
2560
|
+
places = "%.4f"
|
|
2561
|
+
} = payload;
|
|
2562
|
+
const gap = Math.abs(long.entry - short.entry);
|
|
2563
|
+
const max_quantity = Math.max(long.quantity, short.quantity);
|
|
2564
|
+
const gapLoss = gap * max_quantity;
|
|
2565
|
+
let calculated_factor;
|
|
2566
|
+
const long_notional = long.entry * long.quantity;
|
|
2567
|
+
const short_notional = short.entry * short.quantity;
|
|
2568
|
+
const target_to_reduce = desired_risk / (1 - sell_factor);
|
|
2569
|
+
if (kind === "short") {
|
|
2570
|
+
const calculate_longPercent = target_to_reduce / long_notional;
|
|
2571
|
+
calculated_factor = calculate_longPercent * short_notional / gapLoss;
|
|
2572
|
+
} else {
|
|
2573
|
+
const calculated_shortPercent = target_to_reduce / (short_notional - target_to_reduce);
|
|
2574
|
+
calculated_factor = calculated_shortPercent * long_notional / gapLoss;
|
|
2575
|
+
}
|
|
2576
|
+
calculated_factor = to_f(calculated_factor, places);
|
|
2577
|
+
return calculated_factor;
|
|
2578
|
+
}
|
|
2579
|
+
function calculateFactorFromTakeProfit(payload) {
|
|
2580
|
+
const { long, short, knownTp, tpType, price_places = "%.4f" } = payload;
|
|
2581
|
+
const gap = Math.abs(long.entry - short.entry);
|
|
2582
|
+
const max_quantity = Math.max(long.quantity, short.quantity);
|
|
2583
|
+
const gapLoss = gap * max_quantity;
|
|
2584
|
+
if (gapLoss === 0) {
|
|
2585
|
+
return 0;
|
|
2586
|
+
}
|
|
2587
|
+
let factor;
|
|
2588
|
+
if (tpType === "long") {
|
|
2589
|
+
const longPercent = knownTp / long.entry - 1;
|
|
2590
|
+
factor = longPercent * short.entry * short.quantity / gapLoss;
|
|
2591
|
+
} else {
|
|
2592
|
+
const shortPercent = short.entry / knownTp - 1;
|
|
2593
|
+
factor = shortPercent * long.entry * long.quantity / gapLoss;
|
|
2594
|
+
}
|
|
2595
|
+
return to_f(factor, price_places);
|
|
2596
|
+
}
|
|
2597
|
+
function calculateFactorFromSellQuantity(payload) {
|
|
2598
|
+
const {
|
|
2599
|
+
long,
|
|
2600
|
+
short,
|
|
2601
|
+
knownSellQuantity,
|
|
2602
|
+
sellType,
|
|
2603
|
+
sell_factor = 1,
|
|
2604
|
+
price_places = "%.4f"
|
|
2605
|
+
} = payload;
|
|
2606
|
+
if (knownSellQuantity < 0.00001) {
|
|
2607
|
+
return 0;
|
|
2608
|
+
}
|
|
2609
|
+
const gap = Math.abs(long.entry - short.entry);
|
|
2610
|
+
const max_quantity = Math.max(long.quantity, short.quantity);
|
|
2611
|
+
const gapLoss = gap * max_quantity;
|
|
2612
|
+
if (gapLoss === 0) {
|
|
2613
|
+
return 0;
|
|
2614
|
+
}
|
|
2615
|
+
let low_factor = 0.001;
|
|
2616
|
+
let high_factor = 1;
|
|
2617
|
+
let best_factor = 0;
|
|
2618
|
+
let best_diff = Infinity;
|
|
2619
|
+
const tolerance = 0.00001;
|
|
2620
|
+
const max_iterations = 150;
|
|
2621
|
+
let expansions = 0;
|
|
2622
|
+
const max_expansions = 15;
|
|
2623
|
+
let iterations = 0;
|
|
2624
|
+
while (iterations < max_iterations) {
|
|
2625
|
+
iterations++;
|
|
2626
|
+
const mid_factor = (low_factor + high_factor) / 2;
|
|
2627
|
+
const testResult = generateGapTp({
|
|
2628
|
+
long,
|
|
2629
|
+
short,
|
|
2630
|
+
factor: mid_factor,
|
|
2631
|
+
sell_factor,
|
|
2632
|
+
price_places: "%.8f",
|
|
2633
|
+
decimal_places: "%.8f"
|
|
2634
|
+
});
|
|
2635
|
+
const testSellQty = sellType === "long" ? testResult.sell_quantity.long : testResult.sell_quantity.short;
|
|
2636
|
+
const diff = Math.abs(testSellQty - knownSellQuantity);
|
|
2637
|
+
if (diff < best_diff) {
|
|
2638
|
+
best_diff = diff;
|
|
2639
|
+
best_factor = mid_factor;
|
|
2640
|
+
}
|
|
2641
|
+
if (diff < tolerance) {
|
|
2642
|
+
return to_f(mid_factor, price_places);
|
|
2643
|
+
}
|
|
2644
|
+
if (testSellQty < knownSellQuantity) {
|
|
2645
|
+
low_factor = mid_factor;
|
|
2646
|
+
if (mid_factor > high_factor * 0.9 && expansions < max_expansions) {
|
|
2647
|
+
const ratio = knownSellQuantity / testSellQty;
|
|
2648
|
+
if (ratio > 2) {
|
|
2649
|
+
high_factor = high_factor * Math.min(ratio, 10);
|
|
2650
|
+
} else {
|
|
2651
|
+
high_factor = high_factor * 2;
|
|
2652
|
+
}
|
|
2653
|
+
expansions++;
|
|
2654
|
+
continue;
|
|
2655
|
+
}
|
|
2656
|
+
} else {
|
|
2657
|
+
high_factor = mid_factor;
|
|
2658
|
+
}
|
|
2659
|
+
if (Math.abs(high_factor - low_factor) < 0.00001 && diff > tolerance) {
|
|
2660
|
+
if (testSellQty < knownSellQuantity && expansions < max_expansions) {
|
|
2661
|
+
high_factor = high_factor * 1.1;
|
|
2662
|
+
expansions++;
|
|
2663
|
+
continue;
|
|
2664
|
+
} else {
|
|
2665
|
+
break;
|
|
2666
|
+
}
|
|
2667
|
+
}
|
|
2668
|
+
}
|
|
2669
|
+
return to_f(best_factor, price_places);
|
|
2670
|
+
}
|
|
2671
|
+
function determineRewardFactor(payload) {
|
|
2672
|
+
const { quantity, avg_qty, minimum_pnl, risk } = payload;
|
|
2673
|
+
const reward_factor = minimum_pnl / risk;
|
|
2674
|
+
const quantity_ratio = quantity / avg_qty;
|
|
2675
|
+
return to_f(reward_factor / quantity_ratio, "%.4f");
|
|
2676
|
+
}
|
|
2677
|
+
function getHedgeZone(payload) {
|
|
2678
|
+
const {
|
|
2679
|
+
reward_factor: _reward_factor,
|
|
2680
|
+
symbol_config,
|
|
2681
|
+
risk,
|
|
2682
|
+
position: position2,
|
|
2683
|
+
risk_factor = 1,
|
|
2684
|
+
support
|
|
2685
|
+
} = payload;
|
|
2686
|
+
const kind = position2.kind;
|
|
2687
|
+
let reward_factor = _reward_factor;
|
|
2688
|
+
if (support) {
|
|
2689
|
+
const _result = getOptimumHedgeFactor({
|
|
2690
|
+
target_support: support,
|
|
2691
|
+
symbol_config,
|
|
2692
|
+
risk,
|
|
2693
|
+
position: position2
|
|
2694
|
+
});
|
|
2695
|
+
reward_factor = Number(_result.reward_factor);
|
|
2696
|
+
}
|
|
2697
|
+
const take_profit = position2.tp?.price;
|
|
2698
|
+
const tp_diff = Math.abs(take_profit - position2.entry);
|
|
2699
|
+
const quantity = position2.quantity;
|
|
2700
|
+
const diff = risk / quantity;
|
|
2701
|
+
let new_take_profit = kind === "long" ? to_f(position2.entry + diff, symbol_config.price_places) : to_f(position2.entry - diff, symbol_config.price_places);
|
|
2702
|
+
let base_factor = to_f(Math.max(tp_diff, diff) / (Math.min(tp_diff, diff) || 1), "%.3f");
|
|
2703
|
+
let factor = reward_factor || base_factor;
|
|
2704
|
+
const new_risk = risk * factor * risk_factor;
|
|
2705
|
+
const stop_loss_diff = new_risk / quantity;
|
|
2706
|
+
new_take_profit = kind === "long" ? to_f(position2.entry + stop_loss_diff, symbol_config.price_places) : to_f(position2.entry - stop_loss_diff, symbol_config.price_places);
|
|
2707
|
+
const stop_loss = kind === "long" ? to_f(position2.entry - stop_loss_diff, symbol_config.price_places) : to_f(position2.entry + stop_loss_diff, symbol_config.price_places);
|
|
2708
|
+
const profit_percent = new_risk * 100 / (position2.entry * position2.quantity);
|
|
2709
|
+
return {
|
|
2710
|
+
support: Math.min(new_take_profit, stop_loss),
|
|
2711
|
+
resistance: Math.max(new_take_profit, stop_loss),
|
|
2712
|
+
risk: to_f(new_risk, "%.2f"),
|
|
2713
|
+
profit_percent: to_f(profit_percent, "%.2f")
|
|
2714
|
+
};
|
|
2715
|
+
}
|
|
2716
|
+
function getOptimumHedgeFactor(payload) {
|
|
2717
|
+
const {
|
|
2718
|
+
target_support,
|
|
2719
|
+
max_iterations = 50,
|
|
2720
|
+
min_factor = 0.1,
|
|
2721
|
+
max_factor = 20,
|
|
2722
|
+
symbol_config,
|
|
2723
|
+
risk,
|
|
2724
|
+
position: position2
|
|
2725
|
+
} = payload;
|
|
2726
|
+
const current_price = position2.entry;
|
|
2727
|
+
const tolerance = current_price > 100 ? 0.5 : current_price > 1 ? 0.01 : 0.001;
|
|
2728
|
+
let low = min_factor;
|
|
2729
|
+
let high = max_factor;
|
|
2730
|
+
let best_factor = low;
|
|
2731
|
+
let best_diff = Infinity;
|
|
2732
|
+
for (let iteration = 0;iteration < max_iterations; iteration++) {
|
|
2733
|
+
const mid_factor = (low + high) / 2;
|
|
2734
|
+
const hedge_zone = getHedgeZone({
|
|
2735
|
+
reward_factor: mid_factor,
|
|
2736
|
+
symbol_config,
|
|
2737
|
+
risk,
|
|
2738
|
+
position: position2
|
|
2739
|
+
});
|
|
2740
|
+
const current_support = hedge_zone.support;
|
|
2741
|
+
const diff = Math.abs(current_support - target_support);
|
|
2742
|
+
if (diff < best_diff) {
|
|
2743
|
+
best_diff = diff;
|
|
2744
|
+
best_factor = mid_factor;
|
|
2745
|
+
}
|
|
2746
|
+
if (diff <= tolerance) {
|
|
2747
|
+
return {
|
|
2748
|
+
reward_factor: to_f(mid_factor, "%.4f"),
|
|
2749
|
+
achieved_support: to_f(current_support, symbol_config.price_places),
|
|
2750
|
+
target_support: to_f(target_support, symbol_config.price_places),
|
|
2751
|
+
difference: to_f(diff, symbol_config.price_places),
|
|
2752
|
+
iterations: iteration + 1
|
|
2753
|
+
};
|
|
2754
|
+
}
|
|
2755
|
+
if (current_support > target_support) {
|
|
2756
|
+
low = mid_factor;
|
|
2757
|
+
} else {
|
|
2758
|
+
high = mid_factor;
|
|
2759
|
+
}
|
|
2760
|
+
if (Math.abs(high - low) < 0.0001) {
|
|
2761
|
+
break;
|
|
2762
|
+
}
|
|
2763
|
+
}
|
|
2764
|
+
const final_hedge_zone = getHedgeZone({
|
|
2765
|
+
symbol_config,
|
|
2766
|
+
risk,
|
|
2767
|
+
position: position2,
|
|
2768
|
+
reward_factor: best_factor
|
|
2769
|
+
});
|
|
2770
|
+
return {
|
|
2771
|
+
reward_factor: to_f(best_factor, "%.4f"),
|
|
2772
|
+
achieved_support: to_f(final_hedge_zone.support, symbol_config.price_places),
|
|
2773
|
+
target_support: to_f(target_support, symbol_config.price_places),
|
|
2774
|
+
difference: to_f(best_diff, symbol_config.price_places),
|
|
2775
|
+
iterations: max_iterations,
|
|
2776
|
+
converged: best_diff <= tolerance
|
|
2777
|
+
};
|
|
2778
|
+
}
|
|
2779
|
+
function determineCompoundLongTrade(payload) {
|
|
2780
|
+
const {
|
|
2781
|
+
focus_short_position,
|
|
2782
|
+
focus_long_position,
|
|
2783
|
+
shortConfig,
|
|
2784
|
+
global_config,
|
|
2785
|
+
rr = 1
|
|
2786
|
+
} = payload;
|
|
2787
|
+
const short_app_config = buildAppConfig(global_config, {
|
|
2788
|
+
entry: shortConfig.entry,
|
|
2789
|
+
stop: shortConfig.stop,
|
|
2790
|
+
risk_reward: shortConfig.risk_reward,
|
|
2791
|
+
risk: shortConfig.risk,
|
|
2792
|
+
symbol: shortConfig.symbol
|
|
2793
|
+
});
|
|
2794
|
+
const short_max_size = short_app_config.last_value.avg_size;
|
|
2795
|
+
const start_risk = Math.abs(short_app_config.last_value.neg_pnl) * rr;
|
|
2796
|
+
const short_profit = short_app_config.last_value.avg_size * short_app_config.last_value.avg_entry * shortConfig.profit_percent / 100;
|
|
2797
|
+
const diff = short_profit * rr / short_app_config.last_value.avg_size;
|
|
2798
|
+
const support = Math.abs(short_app_config.last_value.avg_entry - diff);
|
|
2799
|
+
const resistance = focus_short_position.next_order || focus_long_position.take_profit;
|
|
2800
|
+
console.log({ support, resistance, short_profit });
|
|
2801
|
+
const result = getRiskReward({
|
|
2802
|
+
entry: resistance,
|
|
2803
|
+
stop: support,
|
|
2804
|
+
risk: start_risk,
|
|
2805
|
+
global_config,
|
|
2806
|
+
force_exact_risk: true
|
|
2807
|
+
});
|
|
2808
|
+
const long_app_config = buildAppConfig(global_config, {
|
|
2809
|
+
entry: resistance,
|
|
2810
|
+
stop: support,
|
|
2811
|
+
risk_reward: result.risk_reward,
|
|
2812
|
+
risk: result.risk,
|
|
2813
|
+
symbol: shortConfig.symbol
|
|
2814
|
+
});
|
|
2815
|
+
const long_profit_percent = start_risk * 2 * 100 / (long_app_config.last_value.avg_size * long_app_config.last_value.avg_entry);
|
|
2816
|
+
return {
|
|
2817
|
+
start_risk,
|
|
2818
|
+
short_profit,
|
|
2819
|
+
support: to_f(support, global_config.price_places),
|
|
2820
|
+
resistance: to_f(resistance, global_config.price_places),
|
|
2821
|
+
long_v: long_app_config.last_value,
|
|
2822
|
+
profit_percent: to_f(long_profit_percent, "%.3f"),
|
|
2823
|
+
result,
|
|
2824
|
+
short_max_size
|
|
2825
|
+
};
|
|
2826
|
+
}
|
|
2827
|
+
function generateOppositeTradeConfig(payload) {
|
|
2828
|
+
const {
|
|
2829
|
+
kind,
|
|
2830
|
+
entry,
|
|
2831
|
+
quantity,
|
|
2832
|
+
target_pnl,
|
|
2833
|
+
ratio = 0.5,
|
|
2834
|
+
global_config
|
|
2835
|
+
} = payload;
|
|
2836
|
+
const diff = target_pnl / quantity;
|
|
2837
|
+
const tp = kind === "long" ? entry + diff : entry - diff;
|
|
2838
|
+
const stop = kind === "long" ? entry - diff : entry + diff;
|
|
2839
|
+
const opposite_pnl = target_pnl * ratio;
|
|
2840
|
+
const app_config = constructAppConfig({
|
|
2841
|
+
account: {
|
|
2842
|
+
expand: {
|
|
2843
|
+
b_config: {
|
|
2844
|
+
entry,
|
|
2845
|
+
stop,
|
|
2846
|
+
symbol: global_config.symbol,
|
|
2847
|
+
risk: target_pnl
|
|
2848
|
+
}
|
|
2849
|
+
}
|
|
2850
|
+
},
|
|
2851
|
+
global_config,
|
|
2852
|
+
distribution_config: {}
|
|
2853
|
+
});
|
|
2854
|
+
const risk_reward = computeRiskReward({
|
|
2855
|
+
app_config,
|
|
2856
|
+
entry: stop,
|
|
2857
|
+
stop: tp,
|
|
2858
|
+
risk_per_trade: opposite_pnl,
|
|
2859
|
+
target_loss: opposite_pnl
|
|
2860
|
+
});
|
|
2861
|
+
return {
|
|
2862
|
+
entry: to_f(stop, global_config.price_places),
|
|
2863
|
+
stop: to_f(tp, global_config.price_places),
|
|
2864
|
+
risk: to_f(opposite_pnl, "%.2f"),
|
|
2865
|
+
risk_reward
|
|
2866
|
+
};
|
|
2867
|
+
}
|
|
2868
|
+
function constructAppConfig(payload) {
|
|
2869
|
+
const { account, global_config, kelly_config, distribution_config } = payload;
|
|
2870
|
+
const config = account.expand?.b_config;
|
|
2871
|
+
if (!config) {
|
|
2872
|
+
return null;
|
|
2873
|
+
}
|
|
2874
|
+
const kelly = config.kelly;
|
|
2875
|
+
const options = {
|
|
2876
|
+
entry: config?.entry,
|
|
2877
|
+
stop: config?.stop,
|
|
2878
|
+
risk_reward: config?.risk_reward,
|
|
2879
|
+
risk: config?.risk,
|
|
2880
|
+
symbol: account.symbol,
|
|
2881
|
+
use_kelly: kelly_config?.use_kelly ?? kelly?.use_kelly,
|
|
2882
|
+
kelly_confidence_factor: kelly_config?.kelly_confidence_factor ?? kelly?.kelly_confidence_factor,
|
|
2883
|
+
kelly_minimum_risk: kelly_config?.kelly_minimum_risk ?? kelly?.kelly_minimum_risk,
|
|
2884
|
+
kelly_prediction_model: kelly_config?.kelly_prediction_model ?? kelly?.kelly_prediction_model,
|
|
2885
|
+
distribution: distribution_config?.distribution ?? config?.distribution,
|
|
2886
|
+
distribution_params: distribution_config?.distribution_params ?? config?.distribution_params
|
|
2887
|
+
};
|
|
2888
|
+
const { entries: _entries, ...appConfig } = buildAppConfig(global_config, options);
|
|
2889
|
+
return appConfig;
|
|
2890
|
+
}
|
|
2891
|
+
function generateDangerousConfig(payload) {
|
|
2892
|
+
const { account, global_config, config } = payload;
|
|
2893
|
+
const app_config = constructAppConfig({
|
|
2894
|
+
account,
|
|
2895
|
+
global_config,
|
|
2896
|
+
kelly_config: {},
|
|
2897
|
+
distribution_config: {}
|
|
2898
|
+
});
|
|
2899
|
+
const { optimal_risk, optimal_stop } = getOptimumStopAndRisk(app_config, {
|
|
2900
|
+
max_size: config.quantity,
|
|
2901
|
+
target_stop: config.stop
|
|
2902
|
+
});
|
|
2903
|
+
const optimumRiskReward = computeRiskReward({
|
|
2904
|
+
app_config,
|
|
2905
|
+
entry: config.entry,
|
|
2906
|
+
stop: optimal_stop,
|
|
2907
|
+
risk_per_trade: optimal_risk,
|
|
2908
|
+
target_loss: optimal_risk
|
|
2909
|
+
});
|
|
2910
|
+
return {
|
|
2911
|
+
entry: config.entry,
|
|
2912
|
+
risk: optimal_risk,
|
|
2913
|
+
stop: optimal_stop,
|
|
2914
|
+
risk_reward: optimumRiskReward
|
|
2915
|
+
};
|
|
2916
|
+
}
|
|
1846
2917
|
// src/helpers/strategy.ts
|
|
1847
2918
|
class Strategy {
|
|
1848
2919
|
position;
|
|
@@ -2245,11 +3316,14 @@ class Strategy {
|
|
|
2245
3316
|
return { ...app_config, avg, loss: -expected_loss, profit_percent };
|
|
2246
3317
|
}
|
|
2247
3318
|
identifyGapConfig(payload) {
|
|
2248
|
-
const { factor = 1 } = payload;
|
|
3319
|
+
const { factor, sell_factor = 1, kind, risk } = payload;
|
|
2249
3320
|
return generateGapTp({
|
|
2250
3321
|
long: this.position.long,
|
|
2251
3322
|
short: this.position.short,
|
|
2252
3323
|
factor,
|
|
3324
|
+
sell_factor,
|
|
3325
|
+
kind,
|
|
3326
|
+
risk,
|
|
2253
3327
|
decimal_places: this.config.global_config.decimal_places,
|
|
2254
3328
|
price_places: this.config.global_config.price_places
|
|
2255
3329
|
});
|
|
@@ -2265,6 +3339,7 @@ class Strategy {
|
|
|
2265
3339
|
avg_price: focus_position.avg_price,
|
|
2266
3340
|
avg_qty: focus_position.avg_qty
|
|
2267
3341
|
},
|
|
3342
|
+
pnl: this.pnl(kind),
|
|
2268
3343
|
strategy: {
|
|
2269
3344
|
reward_factor,
|
|
2270
3345
|
max_reward_factor,
|
|
@@ -2273,7 +3348,535 @@ class Strategy {
|
|
|
2273
3348
|
});
|
|
2274
3349
|
return result;
|
|
2275
3350
|
}
|
|
3351
|
+
simulateGapReduction(payload) {
|
|
3352
|
+
const {
|
|
3353
|
+
factor,
|
|
3354
|
+
direction,
|
|
3355
|
+
sell_factor = 1,
|
|
3356
|
+
iterations = 10,
|
|
3357
|
+
risk: desired_risk,
|
|
3358
|
+
kind
|
|
3359
|
+
} = payload;
|
|
3360
|
+
const results = [];
|
|
3361
|
+
let params = {
|
|
3362
|
+
long: this.position.long,
|
|
3363
|
+
short: this.position.short,
|
|
3364
|
+
config: this.config
|
|
3365
|
+
};
|
|
3366
|
+
for (let i = 0;i < iterations; i++) {
|
|
3367
|
+
const instance = new Strategy(params);
|
|
3368
|
+
const { profit_percent, risk, take_profit, sell_quantity, gap_loss } = instance.identifyGapConfig({
|
|
3369
|
+
factor,
|
|
3370
|
+
sell_factor,
|
|
3371
|
+
kind,
|
|
3372
|
+
risk: desired_risk
|
|
3373
|
+
});
|
|
3374
|
+
const to_add = {
|
|
3375
|
+
profit_percent,
|
|
3376
|
+
risk,
|
|
3377
|
+
take_profit,
|
|
3378
|
+
sell_quantity,
|
|
3379
|
+
gap_loss,
|
|
3380
|
+
position: {
|
|
3381
|
+
long: {
|
|
3382
|
+
entry: this.to_f(params.long.entry),
|
|
3383
|
+
quantity: this.to_df(params.long.quantity)
|
|
3384
|
+
},
|
|
3385
|
+
short: {
|
|
3386
|
+
entry: this.to_f(params.short.entry),
|
|
3387
|
+
quantity: this.to_df(params.short.quantity)
|
|
3388
|
+
}
|
|
3389
|
+
}
|
|
3390
|
+
};
|
|
3391
|
+
const sell_kind = direction == "long" ? "short" : "long";
|
|
3392
|
+
if (sell_quantity[sell_kind] <= 0) {
|
|
3393
|
+
break;
|
|
3394
|
+
}
|
|
3395
|
+
results.push(to_add);
|
|
3396
|
+
const remaining = this.to_df(params[sell_kind].quantity - sell_quantity[sell_kind]);
|
|
3397
|
+
if (remaining <= 0) {
|
|
3398
|
+
break;
|
|
3399
|
+
}
|
|
3400
|
+
params[sell_kind].quantity = remaining;
|
|
3401
|
+
params[direction] = {
|
|
3402
|
+
entry: take_profit[direction],
|
|
3403
|
+
quantity: remaining
|
|
3404
|
+
};
|
|
3405
|
+
}
|
|
3406
|
+
const last_gap_loss = results.at(-1)?.gap_loss;
|
|
3407
|
+
const last_tp = results.at(-1)?.take_profit[direction];
|
|
3408
|
+
const entry = this.position[direction].entry;
|
|
3409
|
+
const quantity = this.to_df(Math.abs(last_tp - entry) / last_gap_loss);
|
|
3410
|
+
return {
|
|
3411
|
+
results,
|
|
3412
|
+
quantity
|
|
3413
|
+
};
|
|
3414
|
+
}
|
|
3415
|
+
}
|
|
3416
|
+
// src/helpers/compound.ts
|
|
3417
|
+
function buildTrades(payload) {
|
|
3418
|
+
const { appConfig, settings, kind } = payload;
|
|
3419
|
+
const kelly_config = settings.kelly;
|
|
3420
|
+
const distribution_params = settings.distribution_params;
|
|
3421
|
+
const current_app_config = { ...appConfig[kind] };
|
|
3422
|
+
const entryNum = parseFloat(settings.entry);
|
|
3423
|
+
const stopNum = parseFloat(settings.stop);
|
|
3424
|
+
current_app_config.entry = entryNum;
|
|
3425
|
+
current_app_config.stop = stopNum;
|
|
3426
|
+
current_app_config.risk_per_trade = parseFloat(settings.risk);
|
|
3427
|
+
current_app_config.risk_reward = parseFloat(settings.risk_reward);
|
|
3428
|
+
current_app_config.kind = kind;
|
|
3429
|
+
current_app_config.kelly = kelly_config;
|
|
3430
|
+
current_app_config.distribution_params = distribution_params;
|
|
3431
|
+
const options = {
|
|
3432
|
+
take_profit: null,
|
|
3433
|
+
entry: current_app_config.entry,
|
|
3434
|
+
stop: current_app_config.stop,
|
|
3435
|
+
raw_instance: null,
|
|
3436
|
+
risk: current_app_config.risk_per_trade,
|
|
3437
|
+
no_of_trades: undefined,
|
|
3438
|
+
risk_reward: current_app_config.risk_reward,
|
|
3439
|
+
kind: current_app_config.kind,
|
|
3440
|
+
increase: true,
|
|
3441
|
+
gap: current_app_config.gap,
|
|
3442
|
+
rr: current_app_config.rr,
|
|
3443
|
+
price_places: current_app_config.price_places,
|
|
3444
|
+
decimal_places: current_app_config.decimal_places,
|
|
3445
|
+
use_kelly: kelly_config?.use_kelly,
|
|
3446
|
+
kelly_confidence_factor: kelly_config?.kelly_confidence_factor,
|
|
3447
|
+
kelly_minimum_risk: kelly_config?.kelly_minimum_risk,
|
|
3448
|
+
kelly_prediction_model: kelly_config?.kelly_prediction_model,
|
|
3449
|
+
kelly_func: kelly_config?.kelly_func,
|
|
3450
|
+
distribution: settings.distribution,
|
|
3451
|
+
distribution_params: settings.distribution_params
|
|
3452
|
+
};
|
|
3453
|
+
if (kind === "long" && entryNum <= stopNum) {
|
|
3454
|
+
return [];
|
|
3455
|
+
}
|
|
3456
|
+
if (kind === "short" && entryNum >= stopNum) {
|
|
3457
|
+
return [];
|
|
3458
|
+
}
|
|
3459
|
+
try {
|
|
3460
|
+
const generatedTrades = sortedBuildConfig(current_app_config, options);
|
|
3461
|
+
return generatedTrades ?? [];
|
|
3462
|
+
} catch (error) {
|
|
3463
|
+
console.error("Error generating orders:", error);
|
|
3464
|
+
return [];
|
|
3465
|
+
}
|
|
3466
|
+
}
|
|
3467
|
+
function generateSummary({
|
|
3468
|
+
trades,
|
|
3469
|
+
fee_percent = 0.05,
|
|
3470
|
+
anchor
|
|
3471
|
+
}) {
|
|
3472
|
+
const avg_entry = trades[0].avg_entry;
|
|
3473
|
+
const avg_size = trades[0].avg_size;
|
|
3474
|
+
const expected_fee = avg_entry * avg_size * fee_percent / 100;
|
|
3475
|
+
return {
|
|
3476
|
+
first_entry: trades.at(-1).entry,
|
|
3477
|
+
last_entry: trades[0].entry,
|
|
3478
|
+
quantity: avg_size,
|
|
3479
|
+
entry: avg_entry,
|
|
3480
|
+
loss: trades[0].neg_pnl,
|
|
3481
|
+
number_of_trades: trades.length,
|
|
3482
|
+
fee: to_f(expected_fee, "%.2f"),
|
|
3483
|
+
anchor_pnl: anchor?.target_pnl
|
|
3484
|
+
};
|
|
3485
|
+
}
|
|
3486
|
+
function helperFuncToBuildTrades({
|
|
3487
|
+
custom_b_config,
|
|
3488
|
+
symbol_config,
|
|
3489
|
+
app_config_kind,
|
|
3490
|
+
appConfig,
|
|
3491
|
+
force_exact_risk = true
|
|
3492
|
+
}) {
|
|
3493
|
+
const risk = custom_b_config.risk * (custom_b_config.risk_factor || 1);
|
|
3494
|
+
let result = getRiskReward({
|
|
3495
|
+
entry: custom_b_config.entry,
|
|
3496
|
+
stop: custom_b_config.stop,
|
|
3497
|
+
risk,
|
|
3498
|
+
global_config: symbol_config,
|
|
3499
|
+
force_exact_risk,
|
|
3500
|
+
target_loss: custom_b_config.risk * (custom_b_config.risk_factor || 1),
|
|
3501
|
+
distribution: custom_b_config.distribution,
|
|
3502
|
+
distribution_params: custom_b_config.distribution_params
|
|
3503
|
+
});
|
|
3504
|
+
if (!force_exact_risk) {
|
|
3505
|
+
result = {
|
|
3506
|
+
risk_reward: result,
|
|
3507
|
+
risk
|
|
3508
|
+
};
|
|
3509
|
+
}
|
|
3510
|
+
const trades = result.risk_reward ? buildTrades({
|
|
3511
|
+
appConfig: { [app_config_kind]: appConfig },
|
|
3512
|
+
kind: app_config_kind,
|
|
3513
|
+
settings: {
|
|
3514
|
+
entry: custom_b_config.entry,
|
|
3515
|
+
stop: custom_b_config.stop,
|
|
3516
|
+
risk: result.risk || custom_b_config.risk,
|
|
3517
|
+
risk_reward: result.risk_reward,
|
|
3518
|
+
distribution: custom_b_config.distribution,
|
|
3519
|
+
distribution_params: custom_b_config.distribution_params
|
|
3520
|
+
}
|
|
3521
|
+
}) : [];
|
|
3522
|
+
const summary = trades.length > 0 ? generateSummary({ trades }) : {};
|
|
3523
|
+
return { trades, result, summary };
|
|
3524
|
+
}
|
|
3525
|
+
function constructAppConfig2({
|
|
3526
|
+
config,
|
|
3527
|
+
global_config
|
|
3528
|
+
}) {
|
|
3529
|
+
const options = {
|
|
3530
|
+
entry: config?.entry,
|
|
3531
|
+
stop: config?.stop,
|
|
3532
|
+
risk_reward: config?.risk_reward,
|
|
3533
|
+
risk: config?.risk,
|
|
3534
|
+
symbol: config.symbol
|
|
3535
|
+
};
|
|
3536
|
+
const { entries: _entries, ...appConfig } = buildAppConfig(global_config, options);
|
|
3537
|
+
return appConfig;
|
|
3538
|
+
}
|
|
3539
|
+
function buildWithOptimumReward({
|
|
3540
|
+
config,
|
|
3541
|
+
settings,
|
|
3542
|
+
global_config,
|
|
3543
|
+
force_exact
|
|
3544
|
+
}) {
|
|
3545
|
+
const kind = config.entry > config.stop ? "long" : "short";
|
|
3546
|
+
let stop = settings.stop;
|
|
3547
|
+
let entry = settings.entry;
|
|
3548
|
+
const risk = settings.risk;
|
|
3549
|
+
const stop_ratio = settings.stop_ratio || 1;
|
|
3550
|
+
const distribution = settings.distribution || config?.distribution;
|
|
3551
|
+
const distribution_params = settings.distribution_params || config?.distribution_params;
|
|
3552
|
+
const custom_b_config = {
|
|
3553
|
+
entry,
|
|
3554
|
+
stop,
|
|
3555
|
+
risk,
|
|
3556
|
+
distribution,
|
|
3557
|
+
distribution_params
|
|
3558
|
+
};
|
|
3559
|
+
const appConfig = constructAppConfig2({
|
|
3560
|
+
config,
|
|
3561
|
+
global_config
|
|
3562
|
+
});
|
|
3563
|
+
const { trades, summary, result } = helperFuncToBuildTrades({
|
|
3564
|
+
custom_b_config,
|
|
3565
|
+
app_config_kind: kind,
|
|
3566
|
+
appConfig,
|
|
3567
|
+
symbol_config: global_config,
|
|
3568
|
+
force_exact_risk: force_exact
|
|
3569
|
+
});
|
|
3570
|
+
const adjusted_size = summary.quantity;
|
|
3571
|
+
const symbol_config = global_config;
|
|
3572
|
+
const entryDetails = {
|
|
3573
|
+
entry: to_f(custom_b_config.entry, symbol_config.price_places),
|
|
3574
|
+
stop: to_f(custom_b_config.stop, symbol_config.price_places),
|
|
3575
|
+
risk: to_f(result.risk, "%.2f"),
|
|
3576
|
+
risk_reward: result.risk_reward,
|
|
3577
|
+
avg_entry: to_f(summary.entry, symbol_config.price_places),
|
|
3578
|
+
avg_size: to_f(adjusted_size, symbol_config.decimal_places),
|
|
3579
|
+
first_entry: to_f(summary.first_entry, symbol_config.price_places),
|
|
3580
|
+
pnl: to_f(custom_b_config.risk, "%.2f"),
|
|
3581
|
+
fee: to_f(summary.fee, "%.2f"),
|
|
3582
|
+
loss: to_f(summary.loss, "%.2f"),
|
|
3583
|
+
last_entry: to_f(summary.last_entry, symbol_config.price_places),
|
|
3584
|
+
margin: to_f(summary.entry * adjusted_size / symbol_config.leverage, "%.2f")
|
|
3585
|
+
};
|
|
3586
|
+
return {
|
|
3587
|
+
trades,
|
|
3588
|
+
summary: entryDetails,
|
|
3589
|
+
config: {
|
|
3590
|
+
...custom_b_config,
|
|
3591
|
+
...result,
|
|
3592
|
+
stop_ratio
|
|
3593
|
+
},
|
|
3594
|
+
stop_order: {
|
|
3595
|
+
quantity: entryDetails.avg_size * stop_ratio,
|
|
3596
|
+
price: entryDetails.stop
|
|
3597
|
+
},
|
|
3598
|
+
kind
|
|
3599
|
+
};
|
|
3600
|
+
}
|
|
3601
|
+
function generateOppositeOptimum({
|
|
3602
|
+
config,
|
|
3603
|
+
global_config,
|
|
3604
|
+
settings,
|
|
3605
|
+
ratio = 1,
|
|
3606
|
+
distribution,
|
|
3607
|
+
distribution_params,
|
|
3608
|
+
risk_factor = 1
|
|
3609
|
+
}) {
|
|
3610
|
+
const configKind = config.entry > config.stop ? "long" : "short";
|
|
3611
|
+
if (configKind === "long" && config.entry > config.stop) {
|
|
3612
|
+
if (settings.stop <= settings.entry) {
|
|
3613
|
+
throw new Error("Invalid input: For long config positions, opposite settings must have stop > entry");
|
|
3614
|
+
}
|
|
3615
|
+
} else if (configKind === "short" && config.entry < config.stop) {
|
|
3616
|
+
if (settings.stop >= settings.entry) {
|
|
3617
|
+
throw new Error("Invalid input: For short config positions, opposite settings must have stop < entry");
|
|
3618
|
+
}
|
|
3619
|
+
}
|
|
3620
|
+
const kind = config.entry > config.stop ? "long" : "short";
|
|
3621
|
+
const app_config_kind = kind === "long" ? "short" : "long";
|
|
3622
|
+
let risk = settings.risk;
|
|
3623
|
+
const custom_b_config = {
|
|
3624
|
+
entry: settings.entry,
|
|
3625
|
+
stop: settings.stop,
|
|
3626
|
+
risk: risk * ratio,
|
|
3627
|
+
distribution: distribution || "inverse-exponential",
|
|
3628
|
+
distribution_params: distribution_params || config?.distribution_params,
|
|
3629
|
+
risk_factor
|
|
3630
|
+
};
|
|
3631
|
+
const appConfig = constructAppConfig2({
|
|
3632
|
+
config: {
|
|
3633
|
+
...config,
|
|
3634
|
+
...custom_b_config
|
|
3635
|
+
},
|
|
3636
|
+
global_config
|
|
3637
|
+
});
|
|
3638
|
+
const { result, trades, summary } = helperFuncToBuildTrades({
|
|
3639
|
+
custom_b_config,
|
|
3640
|
+
symbol_config: global_config,
|
|
3641
|
+
app_config_kind,
|
|
3642
|
+
appConfig
|
|
3643
|
+
});
|
|
3644
|
+
if (Object.keys(summary).length === 0) {
|
|
3645
|
+
return {
|
|
3646
|
+
trades,
|
|
3647
|
+
summary,
|
|
3648
|
+
config: custom_b_config,
|
|
3649
|
+
kind: app_config_kind
|
|
3650
|
+
};
|
|
3651
|
+
}
|
|
3652
|
+
const symbol_config = global_config;
|
|
3653
|
+
const entryDetails = {
|
|
3654
|
+
entry: to_f(custom_b_config.entry, symbol_config.price_places),
|
|
3655
|
+
stop: to_f(custom_b_config.stop, symbol_config.price_places),
|
|
3656
|
+
risk: to_f(result.risk, "%.2f"),
|
|
3657
|
+
risk_reward: result.risk_reward,
|
|
3658
|
+
avg_entry: to_f(summary.entry, symbol_config.price_places),
|
|
3659
|
+
avg_size: to_f(summary.quantity, symbol_config.decimal_places),
|
|
3660
|
+
first_entry: to_f(summary.first_entry, symbol_config.price_places),
|
|
3661
|
+
pnl: to_f(custom_b_config.risk, "%.2f"),
|
|
3662
|
+
fee: to_f(summary.fee, "%.2f"),
|
|
3663
|
+
loss: to_f(summary.loss, "%.2f"),
|
|
3664
|
+
last_entry: to_f(summary.last_entry, symbol_config.price_places),
|
|
3665
|
+
defaultEntry: settings.entry ? to_f(settings.entry, symbol_config.price_places) : null
|
|
3666
|
+
};
|
|
3667
|
+
return {
|
|
3668
|
+
trades,
|
|
3669
|
+
summary: entryDetails,
|
|
3670
|
+
config: {
|
|
3671
|
+
...custom_b_config,
|
|
3672
|
+
...result
|
|
3673
|
+
},
|
|
3674
|
+
kind: app_config_kind
|
|
3675
|
+
};
|
|
3676
|
+
}
|
|
3677
|
+
function defaultTradeFromCurrentState({
|
|
3678
|
+
config,
|
|
3679
|
+
global_config
|
|
3680
|
+
}) {
|
|
3681
|
+
const kind = config.entry > config.stop ? "long" : "short";
|
|
3682
|
+
const settings = {
|
|
3683
|
+
entry: config?.entry,
|
|
3684
|
+
stop: config?.stop,
|
|
3685
|
+
risk: config?.risk,
|
|
3686
|
+
distribution: config?.distribution,
|
|
3687
|
+
risk_reward: config?.risk_reward
|
|
3688
|
+
};
|
|
3689
|
+
const appConfig = constructAppConfig2({
|
|
3690
|
+
config,
|
|
3691
|
+
global_config
|
|
3692
|
+
});
|
|
3693
|
+
const trades = buildTrades({
|
|
3694
|
+
appConfig: { [kind]: appConfig },
|
|
3695
|
+
kind,
|
|
3696
|
+
settings
|
|
3697
|
+
});
|
|
3698
|
+
return {
|
|
3699
|
+
trades,
|
|
3700
|
+
summary: generateSummary({
|
|
3701
|
+
trades,
|
|
3702
|
+
fee_percent: global_config.fee_percent
|
|
3703
|
+
})
|
|
3704
|
+
};
|
|
3705
|
+
}
|
|
3706
|
+
function increaseTradeHelper({
|
|
3707
|
+
increase_qty,
|
|
3708
|
+
stop,
|
|
3709
|
+
config,
|
|
3710
|
+
global_config,
|
|
3711
|
+
style,
|
|
3712
|
+
entry,
|
|
3713
|
+
position: position2,
|
|
3714
|
+
stop_ratio = 1,
|
|
3715
|
+
distribution: default_distribution,
|
|
3716
|
+
distribution_params: default_distribution_params
|
|
3717
|
+
}) {
|
|
3718
|
+
const symbol_config = global_config;
|
|
3719
|
+
const kind = config.entry > config.stop ? "long" : "short";
|
|
3720
|
+
const distribution = default_distribution || config.distribution || "inverse-exponential";
|
|
3721
|
+
const distribution_params = default_distribution_params || config.distribution_params;
|
|
3722
|
+
const appConfig = constructAppConfig2({
|
|
3723
|
+
config,
|
|
3724
|
+
global_config
|
|
3725
|
+
});
|
|
3726
|
+
const currentState = defaultTradeFromCurrentState({
|
|
3727
|
+
config,
|
|
3728
|
+
global_config
|
|
3729
|
+
});
|
|
3730
|
+
const { optimal_risk, neg_pnl } = getOptimumStopAndRisk(appConfig, {
|
|
3731
|
+
max_size: increase_qty,
|
|
3732
|
+
target_stop: stop,
|
|
3733
|
+
distribution
|
|
3734
|
+
});
|
|
3735
|
+
if (neg_pnl === 0) {
|
|
3736
|
+
return {
|
|
3737
|
+
trades: [],
|
|
3738
|
+
summary: {},
|
|
3739
|
+
config: {},
|
|
3740
|
+
kind,
|
|
3741
|
+
current: currentState
|
|
3742
|
+
};
|
|
3743
|
+
}
|
|
3744
|
+
const custom_b_config = {
|
|
3745
|
+
entry,
|
|
3746
|
+
stop,
|
|
3747
|
+
risk: style === "minimum" ? Math.abs(neg_pnl) : optimal_risk,
|
|
3748
|
+
distribution,
|
|
3749
|
+
distribution_params
|
|
3750
|
+
};
|
|
3751
|
+
const { result, trades, summary } = helperFuncToBuildTrades({
|
|
3752
|
+
custom_b_config,
|
|
3753
|
+
symbol_config,
|
|
3754
|
+
appConfig,
|
|
3755
|
+
app_config_kind: kind
|
|
3756
|
+
});
|
|
3757
|
+
if (Object.keys(summary).length === 0) {
|
|
3758
|
+
return {
|
|
3759
|
+
trades,
|
|
3760
|
+
summary,
|
|
3761
|
+
config: {
|
|
3762
|
+
...custom_b_config,
|
|
3763
|
+
...result
|
|
3764
|
+
},
|
|
3765
|
+
kind,
|
|
3766
|
+
current: currentState
|
|
3767
|
+
};
|
|
3768
|
+
}
|
|
3769
|
+
const new_avg_values = determine_average_entry_and_size([
|
|
3770
|
+
{
|
|
3771
|
+
price: position2.entry,
|
|
3772
|
+
quantity: position2.quantity
|
|
3773
|
+
},
|
|
3774
|
+
{
|
|
3775
|
+
price: summary?.entry,
|
|
3776
|
+
quantity: summary?.quantity
|
|
3777
|
+
}
|
|
3778
|
+
], symbol_config.decimal_places, symbol_config.price_places);
|
|
3779
|
+
summary.entry = new_avg_values.entry;
|
|
3780
|
+
summary.quantity = new_avg_values.quantity;
|
|
3781
|
+
const loss = Math.abs(summary.entry - custom_b_config.stop) * summary.quantity;
|
|
3782
|
+
const entryDetails = {
|
|
3783
|
+
entry: to_f(custom_b_config.entry, symbol_config.price_places),
|
|
3784
|
+
stop: to_f(custom_b_config.stop, symbol_config.price_places),
|
|
3785
|
+
risk: to_f(result.risk, symbol_config.price_places),
|
|
3786
|
+
risk_reward: result.risk_reward,
|
|
3787
|
+
avg_entry: to_f(summary.entry, symbol_config.price_places),
|
|
3788
|
+
avg_size: to_f(summary.quantity, symbol_config.decimal_places),
|
|
3789
|
+
first_entry: to_f(summary.first_entry, symbol_config.price_places),
|
|
3790
|
+
pnl: to_f(custom_b_config.risk, "%.2f"),
|
|
3791
|
+
fee: to_f(summary.fee, "%.2f"),
|
|
3792
|
+
loss: to_f(loss, "%.2f"),
|
|
3793
|
+
last_entry: to_f(summary.last_entry, symbol_config.price_places),
|
|
3794
|
+
margin: to_f(summary.entry * summary.quantity / global_config.leverage, "%.2f")
|
|
3795
|
+
};
|
|
3796
|
+
return {
|
|
3797
|
+
trades,
|
|
3798
|
+
summary: entryDetails,
|
|
3799
|
+
stop_order: {
|
|
3800
|
+
quantity: entryDetails.avg_size * stop_ratio,
|
|
3801
|
+
price: entryDetails.stop
|
|
3802
|
+
},
|
|
3803
|
+
config: {
|
|
3804
|
+
...custom_b_config,
|
|
3805
|
+
...result
|
|
3806
|
+
},
|
|
3807
|
+
kind,
|
|
3808
|
+
current: currentState
|
|
3809
|
+
};
|
|
2276
3810
|
}
|
|
3811
|
+
function generatePositionIncreaseTrade({
|
|
3812
|
+
account,
|
|
3813
|
+
zoneAccount,
|
|
3814
|
+
ratio = 0.1,
|
|
3815
|
+
config,
|
|
3816
|
+
global_config,
|
|
3817
|
+
style = "minimum",
|
|
3818
|
+
distribution = "inverse-exponential",
|
|
3819
|
+
distribution_params
|
|
3820
|
+
}) {
|
|
3821
|
+
const kind = config.entry > config.stop ? "long" : "short";
|
|
3822
|
+
const target_max_quantity = kind === "long" ? account.short.quantity : account.long.quantity;
|
|
3823
|
+
const increase_qty = target_max_quantity * ratio;
|
|
3824
|
+
const entry = zoneAccount.entry;
|
|
3825
|
+
const stop = zoneAccount.stop;
|
|
3826
|
+
return increaseTradeHelper({
|
|
3827
|
+
config,
|
|
3828
|
+
position: account[kind],
|
|
3829
|
+
global_config,
|
|
3830
|
+
entry,
|
|
3831
|
+
stop,
|
|
3832
|
+
style,
|
|
3833
|
+
increase_qty,
|
|
3834
|
+
distribution,
|
|
3835
|
+
distribution_params
|
|
3836
|
+
});
|
|
3837
|
+
}
|
|
3838
|
+
function determineHedgeTradeToPlace({
|
|
3839
|
+
position: position2,
|
|
3840
|
+
config,
|
|
3841
|
+
global_config,
|
|
3842
|
+
profit_risk = 200,
|
|
3843
|
+
allowable_loss = 1000
|
|
3844
|
+
}) {
|
|
3845
|
+
const diff = profit_risk / position2.quantity;
|
|
3846
|
+
const kind = position2.kind === "long" ? "short" : "long";
|
|
3847
|
+
const tp_price = position2.kind === "long" ? diff + position2.entry : position2.entry - diff;
|
|
3848
|
+
const loss_diff = allowable_loss / position2.quantity;
|
|
3849
|
+
const loss_price = position2.kind === "long" ? position2.entry - loss_diff : position2.entry + loss_diff;
|
|
3850
|
+
const entry = kind === "short" ? loss_price : tp_price;
|
|
3851
|
+
const stop = kind === "short" ? tp_price : loss_price;
|
|
3852
|
+
const result = buildWithOptimumReward({
|
|
3853
|
+
config: {
|
|
3854
|
+
...config,
|
|
3855
|
+
entry,
|
|
3856
|
+
stop
|
|
3857
|
+
},
|
|
3858
|
+
global_config,
|
|
3859
|
+
force_exact: true,
|
|
3860
|
+
settings: {
|
|
3861
|
+
entry,
|
|
3862
|
+
stop,
|
|
3863
|
+
risk: profit_risk,
|
|
3864
|
+
distribution: config.distribution
|
|
3865
|
+
}
|
|
3866
|
+
});
|
|
3867
|
+
return {
|
|
3868
|
+
opposite: result,
|
|
3869
|
+
take_profit: to_f(tp_price, global_config.price_places)
|
|
3870
|
+
};
|
|
3871
|
+
}
|
|
3872
|
+
var compoundAPI = {
|
|
3873
|
+
determineHedgeTradeToPlace,
|
|
3874
|
+
buildWithOptimumReward,
|
|
3875
|
+
constructAppConfig: constructAppConfig2,
|
|
3876
|
+
generateOppositeOptimum,
|
|
3877
|
+
increaseTradeHelper,
|
|
3878
|
+
generatePositionIncreaseTrade
|
|
3879
|
+
};
|
|
2277
3880
|
export {
|
|
2278
3881
|
to_f,
|
|
2279
3882
|
sortedBuildConfig,
|
|
@@ -2288,10 +3891,14 @@ export {
|
|
|
2288
3891
|
getRiskReward,
|
|
2289
3892
|
getParamForField,
|
|
2290
3893
|
getOptimumStopAndRisk,
|
|
3894
|
+
getOptimumHedgeFactor,
|
|
3895
|
+
getHedgeZone,
|
|
2291
3896
|
getDecimalPlaces,
|
|
2292
3897
|
generate_config_params,
|
|
2293
3898
|
generateOptimumAppConfig,
|
|
3899
|
+
generateOppositeTradeConfig,
|
|
2294
3900
|
generateGapTp,
|
|
3901
|
+
generateDangerousConfig,
|
|
2295
3902
|
formatPrice,
|
|
2296
3903
|
fibonacci_analysis,
|
|
2297
3904
|
extractValue,
|
|
@@ -2302,13 +3909,21 @@ export {
|
|
|
2302
3909
|
determine_average_entry_and_size,
|
|
2303
3910
|
determine_amount_to_sell2 as determine_amount_to_sell,
|
|
2304
3911
|
determine_amount_to_buy,
|
|
3912
|
+
determineTPSl,
|
|
3913
|
+
determineRewardFactor,
|
|
3914
|
+
determineOptimumRisk,
|
|
2305
3915
|
determineOptimumReward,
|
|
3916
|
+
determineCompoundLongTrade,
|
|
2306
3917
|
createGapPairs,
|
|
2307
3918
|
createArray,
|
|
3919
|
+
constructAppConfig,
|
|
2308
3920
|
computeTotalAverageForEachTrade,
|
|
2309
3921
|
computeSellZones,
|
|
2310
3922
|
computeRiskReward,
|
|
2311
3923
|
computeProfitDetail,
|
|
3924
|
+
compoundAPI,
|
|
3925
|
+
calculateFactorFromTakeProfit,
|
|
3926
|
+
calculateFactorFromSellQuantity,
|
|
2312
3927
|
buildConfig,
|
|
2313
3928
|
buildAvg,
|
|
2314
3929
|
buildAppConfig,
|