@gbozee/ultimate 0.0.2-99 → 0.0.2-next.0

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.
@@ -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,20 @@ 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
+ max_quantity = 0.03;
83
407
  constructor({
84
408
  focus,
409
+ symbol,
85
410
  budget,
86
411
  percent_change = 0.02,
87
412
  price_places = "%.5f",
@@ -100,8 +425,21 @@ class Signal {
100
425
  minimum_size = 0,
101
426
  first_order_size = 0,
102
427
  gap = 10,
103
- max_size = 0
428
+ max_size = 0,
429
+ use_kelly = false,
430
+ kelly_prediction_model = "exponential",
431
+ kelly_confidence_factor = 0.6,
432
+ kelly_minimum_risk = 0.2,
433
+ kelly_func = "theoretical",
434
+ full_distribution,
435
+ max_quantity = 0.03,
436
+ distribution_params = {}
104
437
  }) {
438
+ if (full_distribution) {
439
+ this.distribution = full_distribution;
440
+ }
441
+ this.distribution_params = distribution_params;
442
+ this.symbol = symbol;
105
443
  this.minimum_size = minimum_size;
106
444
  this.first_order_size = first_order_size;
107
445
  this.focus = focus;
@@ -122,6 +460,12 @@ class Signal {
122
460
  this.increase_position = increase_position;
123
461
  this.gap = gap;
124
462
  this.max_size = max_size;
463
+ this.use_kelly = use_kelly;
464
+ this.kelly_prediction_model = kelly_prediction_model;
465
+ this.kelly_confidence_factor = kelly_confidence_factor;
466
+ this.kelly_minimum_risk = kelly_minimum_risk;
467
+ this.kelly_func = kelly_func;
468
+ this.max_quantity = max_quantity;
125
469
  }
126
470
  build_entry({
127
471
  current_price,
@@ -131,7 +475,9 @@ class Signal {
131
475
  kind = "long",
132
476
  risk,
133
477
  no_of_trades = 1,
134
- take_profit
478
+ take_profit,
479
+ distribution,
480
+ distribution_params = {}
135
481
  }) {
136
482
  let _stop_loss = stop_loss;
137
483
  if (!_stop_loss && stop_percent) {
@@ -140,20 +486,26 @@ class Signal {
140
486
  const percent_change = _stop_loss ? Math.max(current_price, _stop_loss) / Math.min(current_price, _stop_loss) - 1 : this.percent_change;
141
487
  const _no_of_trades = no_of_trades || this.risk_reward;
142
488
  let _resistance = current_price * Math.pow(1 + percent_change, 1);
489
+ const simple_support = Math.min(current_price, stop_loss);
490
+ const simple_resistance = Math.max(current_price, stop_loss);
143
491
  const derivedConfig = {
144
492
  ...this,
145
493
  percent_change,
146
494
  focus: current_price,
147
- resistance: _resistance,
495
+ resistance: distribution ? simple_resistance : _resistance,
148
496
  risk_per_trade: risk / this.risk_reward,
149
497
  minimum_pnl: pnl,
150
498
  risk_reward: _no_of_trades,
151
499
  take_profit: take_profit || this.take_profit,
152
- support: kind === "long" ? _stop_loss : this.support
500
+ support: distribution ? simple_support : kind === "long" ? _stop_loss : this.support,
501
+ full_distribution: distribution ? {
502
+ ...this.distribution,
503
+ [kind]: distribution
504
+ } : undefined,
505
+ distribution_params
153
506
  };
154
507
  const instance = new Signal(derivedConfig);
155
- if (kind === "short") {
156
- }
508
+ if (kind === "short") {}
157
509
  let result = instance.get_bulk_trade_zones({ current_price, kind });
158
510
  return result;
159
511
  return result?.filter((x) => {
@@ -309,7 +661,6 @@ class Signal {
309
661
  let potentials = new_result.filter((x) => condition(x["entry"], i["risk_sell"])).map((x) => x["entry"]);
310
662
  if (potentials.length && max_index) {
311
663
  if (kind === "long") {
312
- console.log("slice: ", potentials.slice(0, max_index));
313
664
  i["risk_sell"] = Math.max(...potentials.slice(0, max_index));
314
665
  } else {
315
666
  i["risk_sell"] = Math.min(...potentials.slice(0, max_index));
@@ -366,13 +717,31 @@ class Signal {
366
717
  }
367
718
  this.zone_risk = original;
368
719
  }
720
+ get_future_zones_simple({
721
+ current_price,
722
+ kind = "long",
723
+ raw
724
+ }) {
725
+ const margin_zones = [this.support, this.resistance];
726
+ const distribution = this.distribution ? this.distribution[kind] : "geometric";
727
+ let _kind = distribution === "inverse-exponential" ? kind === "long" ? "short" : "long" : kind;
728
+ const entries = distributions_default({
729
+ margin_range: margin_zones,
730
+ kind: _kind,
731
+ distribution,
732
+ risk_reward: this.risk_reward,
733
+ price_places: this.price_places,
734
+ distribution_params: this.distribution_params
735
+ });
736
+ return entries.sort((a, b) => a - b);
737
+ }
369
738
  get_future_zones({
370
739
  current_price,
371
740
  kind = "long",
372
741
  raw
373
742
  }) {
374
- if (raw) {
375
- }
743
+ if (raw) {}
744
+ return this.get_future_zones_simple({ current_price, raw, kind });
376
745
  const margin_range = this.get_margin_range(current_price, kind);
377
746
  let margin_zones = this.get_margin_zones({ current_price });
378
747
  let remaining_zones = margin_zones.filter((x) => JSON.stringify(x) != JSON.stringify(margin_range));
@@ -560,10 +929,25 @@ class Signal {
560
929
  }
561
930
  const defaultStopLoss = i === 0 ? stop_loss : _base;
562
931
  const new_stop = kind === "long" ? this.support : stop_loss;
932
+ let risk_to_use = risk_per_trade;
933
+ if (this.use_kelly) {
934
+ const func = this.kelly_func === "theoretical" ? calculateTheoreticalKelly : this.kelly_func === "position_based" ? calculatePositionBasedKelly : calculateTheoreticalKellyFixed;
935
+ const theoretical_kelly = func({
936
+ current_entry: x,
937
+ zone_prices: limit_orders,
938
+ kind,
939
+ config: {
940
+ price_prediction_model: this.kelly_prediction_model,
941
+ confidence_factor: this.kelly_confidence_factor,
942
+ volatility_adjustment: true
943
+ }
944
+ });
945
+ risk_to_use = theoretical_kelly * risk_per_trade / this.kelly_minimum_risk;
946
+ }
563
947
  const y = this.build_trade_dict({
564
948
  entry: x,
565
949
  stop: (this.increase_position ? new_stop : defaultStopLoss) || defaultStopLoss,
566
- risk: risk_per_trade,
950
+ risk: risk_to_use,
567
951
  arr: limit_orders,
568
952
  index: i,
569
953
  new_fees: total_incurred_market_fees,
@@ -577,14 +961,13 @@ class Signal {
577
961
  return y !== null ? y : undefined;
578
962
  }) || []).filter((y) => y !== undefined).filter((y) => {
579
963
  const min_options = [0.001, 0.002, 0.003];
580
- if (min_options.includes(this.minimum_size)) {
581
- return y.quantity <= 0.03;
964
+ if (min_options.includes(this.minimum_size) && this.symbol.toUpperCase().startsWith("BTC")) {
965
+ return y.quantity <= this.max_quantity;
582
966
  }
583
967
  return true;
584
968
  });
585
969
  let total_orders = limit_trades.concat(market_trades);
586
- if (kind === "short") {
587
- }
970
+ if (kind === "short") {}
588
971
  if (this.minimum_size && total_orders.length > 0) {
589
972
  let payload = total_orders;
590
973
  let greater_than_min_size = total_orders.filter((o) => o ? o.quantity >= this.minimum_size : true);
@@ -670,8 +1053,7 @@ class Signal {
670
1053
  });
671
1054
  const multiplier = start - index;
672
1055
  const incurred_fees = fees.reduce((a, b) => a + b, 0) + previous_risks.reduce((a, b) => a + b, 0);
673
- if (index === 0) {
674
- }
1056
+ if (index === 0) {}
675
1057
  let quantity = determine_position_size({
676
1058
  entry,
677
1059
  stop,
@@ -837,8 +1219,7 @@ function extractValue(_param, condition) {
837
1219
  try {
838
1220
  let value2 = JSON.parse(_param || "[]");
839
1221
  param = value2.map((o) => parseFloat(o));
840
- } catch (error) {
841
- }
1222
+ } catch (error) {}
842
1223
  } else {
843
1224
  param = parseFloat(_param);
844
1225
  }
@@ -853,8 +1234,7 @@ function asCoins(symbol) {
853
1234
  if (symbol.toLowerCase().includes("-")) {
854
1235
  result = result.split("-")[0];
855
1236
  }
856
- if (symbol.toLowerCase() == "usdt-usd") {
857
- }
1237
+ if (symbol.toLowerCase() == "usdt-usd") {}
858
1238
  let result2 = _type == "usdt" ? symbol.split(result)[0] : result;
859
1239
  if (result.includes("-")) {
860
1240
  result2 = result;
@@ -1153,7 +1533,64 @@ function computeSellZones(payload) {
1153
1533
  const spread = factor - 1;
1154
1534
  return Array.from({ length: zones }, (_, i) => entry * Math.pow(1 + spread, i));
1155
1535
  }
1536
+ function determineTPSl(payload) {
1537
+ const { sell_ratio = 1, kind, positions, configs, decimal_places } = payload;
1538
+ const long_settings = configs.long;
1539
+ const short_settings = configs.short;
1540
+ const settings = kind === "long" ? long_settings : short_settings;
1541
+ const reverse_kind = kind === "long" ? "short" : "long";
1542
+ const _position = positions[kind];
1543
+ const reverse_position = positions[reverse_kind];
1544
+ const reduce_ratio = settings.reduce_ratio;
1545
+ const notion = _position.entry * _position.quantity;
1546
+ const profit_percent = settings.profit_percent;
1547
+ const places = decimal_places || reverse_position.decimal_places;
1548
+ if (profit_percent) {
1549
+ let quantity = to_f(_position.quantity * sell_ratio, places);
1550
+ const as_float = parseFloat(profit_percent) * sell_ratio;
1551
+ const pnl = to_f(as_float * notion / 100, "%.2f");
1552
+ const diff = pnl / quantity;
1553
+ const tp_price = to_f(kind === "long" ? _position.entry + diff : _position.entry - diff, _position.price_places);
1554
+ const expected_loss = to_f(parseFloat(reduce_ratio) * pnl, "%.2f");
1555
+ let reduce_quantity = 0;
1556
+ if (reverse_position.quantity > 0) {
1557
+ const total_loss = Math.abs(tp_price - reverse_position.entry) * reverse_position.quantity;
1558
+ const ratio = expected_loss / total_loss;
1559
+ reduce_quantity = to_f(reverse_position.quantity * ratio, places);
1560
+ }
1561
+ return {
1562
+ tp_price,
1563
+ pnl,
1564
+ quantity,
1565
+ reduce_quantity,
1566
+ expected_loss
1567
+ };
1568
+ }
1569
+ return {
1570
+ tp_price: 0,
1571
+ pnl: 0,
1572
+ quantity: 0,
1573
+ reduce_quantity: 0,
1574
+ expected_loss: 0
1575
+ };
1576
+ }
1156
1577
  // src/helpers/shared.ts
1578
+ function getMaxQuantity(x, app_config) {
1579
+ let max_quantity = app_config.max_quantity;
1580
+ if (max_quantity) {
1581
+ return x <= max_quantity;
1582
+ }
1583
+ if (app_config.symbol === "BTCUSDT") {
1584
+ max_quantity = 0.03;
1585
+ }
1586
+ if (app_config.symbol?.toLowerCase().startsWith("sol")) {
1587
+ max_quantity = 2;
1588
+ }
1589
+ if (!max_quantity) {
1590
+ return true;
1591
+ }
1592
+ return x <= max_quantity;
1593
+ }
1157
1594
  function buildConfig(app_config, {
1158
1595
  take_profit,
1159
1596
  entry,
@@ -1168,7 +1605,15 @@ function buildConfig(app_config, {
1168
1605
  gap,
1169
1606
  rr = 1,
1170
1607
  price_places = "%.1f",
1171
- decimal_places = "%.3f"
1608
+ decimal_places = "%.3f",
1609
+ use_kelly = false,
1610
+ kelly_confidence_factor = 0.95,
1611
+ kelly_minimum_risk = 0.2,
1612
+ kelly_prediction_model = "exponential",
1613
+ kelly_func = "theoretical",
1614
+ min_avg_size = 0,
1615
+ distribution,
1616
+ distribution_params
1172
1617
  }) {
1173
1618
  let fee = app_config.fee / 100;
1174
1619
  let working_risk = risk || app_config.risk_per_trade;
@@ -1192,7 +1637,15 @@ function buildConfig(app_config, {
1192
1637
  kind: app_config.kind,
1193
1638
  gap,
1194
1639
  min_profit: min_profit || app_config.min_profit,
1195
- rr: rr || 1
1640
+ rr: rr || 1,
1641
+ use_kelly: use_kelly || app_config.kelly?.use_kelly,
1642
+ kelly_confidence_factor: kelly_confidence_factor || app_config.kelly?.kelly_confidence_factor,
1643
+ kelly_minimum_risk: kelly_minimum_risk || app_config.kelly?.kelly_minimum_risk,
1644
+ kelly_prediction_model: kelly_prediction_model || app_config.kelly?.kelly_prediction_model,
1645
+ kelly_func: kelly_func || app_config.kelly?.kelly_func,
1646
+ symbol: app_config.symbol,
1647
+ max_quantity: app_config.max_quantity,
1648
+ distribution_params: distribution_params || app_config.distribution_params
1196
1649
  };
1197
1650
  const instance = new Signal(config);
1198
1651
  if (raw_instance) {
@@ -1202,16 +1655,19 @@ function buildConfig(app_config, {
1202
1655
  return [];
1203
1656
  }
1204
1657
  const condition = (kind === "long" ? entry > app_config.support : entry >= app_config.support) && stop >= app_config.support * 0.999;
1205
- if (kind === "short") {
1206
- }
1658
+ if (kind === "short") {}
1207
1659
  const result = entry === stop ? [] : condition ? instance.build_entry({
1208
1660
  current_price: entry,
1209
1661
  stop_loss: stop,
1210
1662
  risk: working_risk,
1211
1663
  kind: kind || app_config.kind,
1212
- no_of_trades: trade_no
1664
+ no_of_trades: trade_no,
1665
+ distribution,
1666
+ distribution_params
1213
1667
  }) || [] : [];
1214
- return computeTotalAverageForEachTrade(result, config);
1668
+ const new_trades = computeTotalAverageForEachTrade(result, config);
1669
+ let filtered = new_trades.filter((o) => o.avg_size > min_avg_size);
1670
+ return computeTotalAverageForEachTrade(filtered, config);
1215
1671
  }
1216
1672
  function buildAvg({
1217
1673
  _trades,
@@ -1228,13 +1684,7 @@ function buildAvg({
1228
1684
  }
1229
1685
  function sortedBuildConfig(app_config, options) {
1230
1686
  const sorted = buildConfig(app_config, options).sort((a, b) => app_config.kind === "long" ? a.entry - b.entry : b.entry - b.entry).filter((x) => {
1231
- if (app_config.symbol === "BTCUSDT") {
1232
- return x.quantity <= 0.03;
1233
- }
1234
- if (app_config.symbol?.toLowerCase().startsWith("sol")) {
1235
- return x.quantity <= 2;
1236
- }
1237
- return true;
1687
+ return getMaxQuantity(x.quantity, app_config);
1238
1688
  });
1239
1689
  return sorted.map((k, i) => {
1240
1690
  const arrSet = sorted.slice(0, i + 1);
@@ -1265,7 +1715,8 @@ function get_app_config_and_max_size(config, payload) {
1265
1715
  price_places: config.price_places,
1266
1716
  decimal_places: config.decimal_places,
1267
1717
  min_profit: config.profit_percent * config.profit / 100,
1268
- symbol: config.symbol
1718
+ symbol: config.symbol,
1719
+ max_quantity: config.max_quantity
1269
1720
  };
1270
1721
  const initialResult = sortedBuildConfig(app_config, {
1271
1722
  entry: app_config.entry,
@@ -1276,7 +1727,14 @@ function get_app_config_and_max_size(config, payload) {
1276
1727
  increase: true,
1277
1728
  gap: app_config.gap,
1278
1729
  price_places: app_config.price_places,
1279
- decimal_places: app_config.decimal_places
1730
+ decimal_places: app_config.decimal_places,
1731
+ use_kelly: payload.use_kelly,
1732
+ kelly_confidence_factor: payload.kelly_confidence_factor,
1733
+ kelly_minimum_risk: payload.kelly_minimum_risk,
1734
+ kelly_prediction_model: payload.kelly_prediction_model,
1735
+ kelly_func: payload.kelly_func,
1736
+ distribution: payload.distribution,
1737
+ distribution_params: payload.distribution_params
1280
1738
  });
1281
1739
  const max_size = initialResult[0]?.avg_size;
1282
1740
  const last_value = initialResult[0];
@@ -1305,21 +1763,37 @@ function buildAppConfig(config, payload) {
1305
1763
  reverse_factor: 1,
1306
1764
  profit_percent: 0,
1307
1765
  kind: payload.entry > payload.stop ? "long" : "short",
1308
- symbol: payload.symbol
1766
+ symbol: payload.symbol || config.symbol
1309
1767
  }, {
1310
1768
  entry: payload.entry,
1311
1769
  stop: payload.stop,
1312
- kind: payload.entry > payload.stop ? "long" : "short"
1770
+ kind: payload.entry > payload.stop ? "long" : "short",
1771
+ use_kelly: payload.use_kelly,
1772
+ kelly_confidence_factor: payload.kelly_confidence_factor,
1773
+ kelly_minimum_risk: payload.kelly_minimum_risk,
1774
+ kelly_prediction_model: payload.kelly_prediction_model,
1775
+ kelly_func: payload.kelly_func,
1776
+ distribution: payload.distribution,
1777
+ distribution_params: payload.distribution_params
1313
1778
  });
1314
1779
  app_config.max_size = max_size;
1315
1780
  app_config.entry = payload.entry || app_config.entry;
1316
1781
  app_config.stop = payload.stop || app_config.stop;
1317
1782
  app_config.last_value = last_value;
1318
1783
  app_config.entries = entries;
1784
+ app_config.kelly = {
1785
+ use_kelly: payload.use_kelly,
1786
+ kelly_confidence_factor: payload.kelly_confidence_factor,
1787
+ kelly_minimum_risk: payload.kelly_minimum_risk,
1788
+ kelly_prediction_model: payload.kelly_prediction_model,
1789
+ kelly_func: payload.kelly_func
1790
+ };
1791
+ app_config.distribution = payload.distribution;
1792
+ app_config.distribution_params = payload.distribution_params;
1319
1793
  return app_config;
1320
1794
  }
1321
1795
  function getOptimumStopAndRisk(app_config, params) {
1322
- const { max_size, target_stop } = params;
1796
+ const { max_size, target_stop, distribution, distribution_params: _distribution_params } = params;
1323
1797
  const isLong = app_config.kind === "long";
1324
1798
  const stopRange = Math.abs(app_config.entry - target_stop) * 0.5;
1325
1799
  let low_stop = isLong ? target_stop - stopRange : Math.max(target_stop - stopRange, app_config.entry);
@@ -1342,7 +1816,9 @@ function getOptimumStopAndRisk(app_config, params) {
1342
1816
  increase: true,
1343
1817
  gap: app_config.gap,
1344
1818
  price_places: app_config.price_places,
1345
- decimal_places: app_config.decimal_places
1819
+ decimal_places: app_config.decimal_places,
1820
+ distribution,
1821
+ distribution_params: _distribution_params
1346
1822
  });
1347
1823
  if (result.length === 0) {
1348
1824
  if (isLong) {
@@ -1396,7 +1872,9 @@ function getOptimumStopAndRisk(app_config, params) {
1396
1872
  increase: true,
1397
1873
  gap: app_config.gap,
1398
1874
  price_places: app_config.price_places,
1399
- decimal_places: app_config.decimal_places
1875
+ decimal_places: app_config.decimal_places,
1876
+ distribution,
1877
+ distribution_params: _distribution_params
1400
1878
  });
1401
1879
  if (result.length === 0) {
1402
1880
  high_risk = mid_risk;
@@ -1441,7 +1919,9 @@ function getOptimumStopAndRisk(app_config, params) {
1441
1919
  increase: true,
1442
1920
  gap: app_config.gap,
1443
1921
  price_places: app_config.price_places,
1444
- decimal_places: app_config.decimal_places
1922
+ decimal_places: app_config.decimal_places,
1923
+ distribution,
1924
+ distribution_params: _distribution_params
1445
1925
  });
1446
1926
  if (result.length === 0)
1447
1927
  continue;
@@ -1564,7 +2044,8 @@ function generateOptimumAppConfig(config, payload, position2) {
1564
2044
  }, {
1565
2045
  entry: payload.entry,
1566
2046
  stop: payload.stop,
1567
- kind: position2.kind
2047
+ kind: position2.kind,
2048
+ distribution: payload.distribution
1568
2049
  });
1569
2050
  current_app_config.max_size = max_size;
1570
2051
  current_app_config.last_value = last_value;
@@ -1579,7 +2060,8 @@ function generateOptimumAppConfig(config, payload, position2) {
1579
2060
  increase: true,
1580
2061
  gap: current_app_config.gap,
1581
2062
  price_places: current_app_config.price_places,
1582
- decimal_places: current_app_config.decimal_places
2063
+ decimal_places: current_app_config.decimal_places,
2064
+ distribution: payload.distribution
1583
2065
  });
1584
2066
  if (full_trades.length === 0) {
1585
2067
  high_risk = mid_risk;
@@ -1637,7 +2119,16 @@ function generateOptimumAppConfig(config, payload, position2) {
1637
2119
  });
1638
2120
  return best_app_config;
1639
2121
  }
1640
- function determineOptimumReward(app_config, increase = true, low_range = 30, high_range = 199) {
2122
+ function determineOptimumReward(payload) {
2123
+ const {
2124
+ app_config,
2125
+ increase = true,
2126
+ low_range = 1,
2127
+ high_range = 199,
2128
+ target_loss,
2129
+ distribution,
2130
+ max_size
2131
+ } = payload;
1641
2132
  const criterion = app_config.strategy || "quantity";
1642
2133
  const risk_rewards = createArray(low_range, high_range, 1);
1643
2134
  let func = risk_rewards.map((trade_no) => {
@@ -1650,13 +2141,16 @@ function determineOptimumReward(app_config, increase = true, low_range = 30, hig
1650
2141
  increase,
1651
2142
  kind: app_config.kind,
1652
2143
  gap: app_config.gap,
1653
- decimal_places: app_config.decimal_places
2144
+ decimal_places: app_config.decimal_places,
2145
+ distribution,
2146
+ distribution_params: payload.distribution_params
1654
2147
  });
1655
2148
  let total = 0;
1656
2149
  let max = -Infinity;
1657
2150
  let min = Infinity;
1658
2151
  let neg_pnl = trades[0]?.neg_pnl || 0;
1659
2152
  let entry = trades.at(-1)?.entry;
2153
+ let avg_size = trades[0]?.avg_size || 0;
1660
2154
  if (!entry) {
1661
2155
  return null;
1662
2156
  }
@@ -1673,14 +2167,41 @@ function determineOptimumReward(app_config, increase = true, low_range = 30, hig
1673
2167
  risk_per_trade: app_config.risk_per_trade,
1674
2168
  max,
1675
2169
  min,
2170
+ avg_size,
1676
2171
  neg_pnl,
1677
2172
  entry
1678
2173
  };
1679
2174
  });
1680
- func = func.filter((r) => Boolean(r)).filter((r) => {
1681
- let foundIndex = r?.result.findIndex((e) => e.quantity === r.max);
1682
- return criterion === "quantity" ? foundIndex === 0 : true;
1683
- });
2175
+ func = func.filter((r) => Boolean(r));
2176
+ if (max_size !== undefined && max_size > 0) {
2177
+ func = func.filter((r) => r.avg_size <= max_size);
2178
+ }
2179
+ if (target_loss === undefined) {
2180
+ func = func.filter((r) => {
2181
+ let foundIndex = r?.result.findIndex((e) => e.quantity === r.max);
2182
+ return criterion === "quantity" ? foundIndex === 0 : true;
2183
+ });
2184
+ }
2185
+ if (target_loss !== undefined) {
2186
+ const validResults = func.filter((r) => Math.abs(r.neg_pnl) <= target_loss);
2187
+ if (validResults.length > 0) {
2188
+ validResults.sort((a, b) => {
2189
+ const diffA = target_loss - Math.abs(a.neg_pnl);
2190
+ const diffB = target_loss - Math.abs(b.neg_pnl);
2191
+ return diffA - diffB;
2192
+ });
2193
+ if (app_config.raw) {
2194
+ return validResults[0];
2195
+ }
2196
+ return validResults[0]?.value;
2197
+ } else {
2198
+ func.sort((a, b) => Math.abs(a.neg_pnl) - Math.abs(b.neg_pnl));
2199
+ if (app_config.raw) {
2200
+ return func[0];
2201
+ }
2202
+ return func[0]?.value;
2203
+ }
2204
+ }
1684
2205
  const highest = criterion === "quantity" ? Math.max(...func.map((o) => o.max)) : Math.min(...func.map((o) => o.entry));
1685
2206
  const key = criterion === "quantity" ? "max" : "entry";
1686
2207
  const index = findIndexByCondition(func, app_config.kind, (x) => x[key] == highest, criterion);
@@ -1714,6 +2235,7 @@ function findIndexByCondition(lst, kind, condition, defaultKey = "neg_pnl") {
1714
2235
  return b.net_diff - a.net_diff;
1715
2236
  }
1716
2237
  });
2238
+ console.log("found", sortedFound);
1717
2239
  if (defaultKey === "quantity") {
1718
2240
  return sortedFound[0].index;
1719
2241
  }
@@ -1734,58 +2256,154 @@ function findIndexByCondition(lst, kind, condition, defaultKey = "neg_pnl") {
1734
2256
  }, found[0]);
1735
2257
  return maximum;
1736
2258
  }
2259
+ function determineOptimumRisk(config, payload, params) {
2260
+ const { highest_risk, tolerance = 0.01, max_iterations = 200 } = params;
2261
+ let low_risk = 1;
2262
+ let high_risk = Math.max(highest_risk * 5, payload.risk * 10);
2263
+ let best_risk = payload.risk;
2264
+ let best_neg_pnl = 0;
2265
+ let best_diff = Infinity;
2266
+ console.log(`Finding optimal risk for target neg_pnl: ${highest_risk}`);
2267
+ let iterations = 0;
2268
+ while (iterations < max_iterations && high_risk - low_risk > tolerance) {
2269
+ iterations++;
2270
+ const mid_risk = (low_risk + high_risk) / 2;
2271
+ const test_payload = {
2272
+ ...payload,
2273
+ risk: mid_risk
2274
+ };
2275
+ const { last_value } = buildAppConfig(config, test_payload);
2276
+ if (!last_value || !last_value.neg_pnl) {
2277
+ high_risk = mid_risk;
2278
+ continue;
2279
+ }
2280
+ const current_neg_pnl = Math.abs(last_value.neg_pnl);
2281
+ const diff = Math.abs(current_neg_pnl - highest_risk);
2282
+ console.log(`Iteration ${iterations}: Risk=${mid_risk.toFixed(2)}, neg_pnl=${current_neg_pnl.toFixed(2)}, diff=${diff.toFixed(2)}`);
2283
+ if (diff < best_diff) {
2284
+ best_diff = diff;
2285
+ best_risk = mid_risk;
2286
+ best_neg_pnl = current_neg_pnl;
2287
+ }
2288
+ if (diff <= tolerance) {
2289
+ console.log(`Converged! Optimal risk: ${mid_risk.toFixed(2)}`);
2290
+ break;
2291
+ }
2292
+ if (current_neg_pnl < highest_risk) {
2293
+ low_risk = mid_risk;
2294
+ } else {
2295
+ high_risk = mid_risk;
2296
+ }
2297
+ }
2298
+ const final_payload = {
2299
+ ...payload,
2300
+ risk: best_risk
2301
+ };
2302
+ const final_result = buildAppConfig(config, final_payload);
2303
+ return {
2304
+ optimal_risk: to_f(best_risk, "%.2f"),
2305
+ achieved_neg_pnl: to_f(best_neg_pnl, "%.2f"),
2306
+ target_neg_pnl: to_f(highest_risk, "%.2f"),
2307
+ difference: to_f(best_diff, "%.2f"),
2308
+ iterations,
2309
+ converged: best_diff <= tolerance,
2310
+ last_value: final_result.last_value,
2311
+ entries: final_result.entries,
2312
+ app_config: final_result
2313
+ };
2314
+ }
1737
2315
  function computeRiskReward(payload) {
1738
- const { app_config, entry, stop, risk_per_trade } = payload;
2316
+ const {
2317
+ app_config,
2318
+ entry,
2319
+ stop,
2320
+ risk_per_trade,
2321
+ target_loss,
2322
+ distribution,
2323
+ high_range,
2324
+ max_size
2325
+ } = payload;
1739
2326
  const kind = entry > stop ? "long" : "short";
1740
2327
  app_config.kind = kind;
1741
2328
  app_config.entry = entry;
1742
2329
  app_config.stop = stop;
1743
2330
  app_config.risk_per_trade = risk_per_trade;
1744
- const result = determineOptimumReward(app_config);
2331
+ const result = determineOptimumReward({
2332
+ app_config,
2333
+ target_loss,
2334
+ distribution,
2335
+ distribution_params: payload.distribution_params,
2336
+ high_range,
2337
+ max_size
2338
+ });
1745
2339
  return result;
1746
2340
  }
1747
2341
  function getRiskReward(payload) {
1748
- const { entry, stop, risk, global_config } = payload;
2342
+ const {
2343
+ high_range,
2344
+ max_size,
2345
+ entry,
2346
+ stop,
2347
+ risk,
2348
+ global_config,
2349
+ force_exact_risk = false,
2350
+ target_loss,
2351
+ distribution,
2352
+ risk_factor = 1
2353
+ } = payload;
1749
2354
  const { entries, last_value, ...app_config } = buildAppConfig(global_config, {
1750
2355
  entry,
1751
2356
  stop,
1752
2357
  risk_reward: 30,
1753
2358
  risk,
1754
- symbol: global_config.symbol
2359
+ symbol: global_config.symbol,
2360
+ distribution,
2361
+ distribution_params: payload.distribution_params
1755
2362
  });
1756
2363
  const risk_reward = computeRiskReward({
1757
2364
  app_config,
1758
2365
  entry,
1759
2366
  stop,
1760
- risk_per_trade: risk
2367
+ risk_per_trade: risk,
2368
+ high_range,
2369
+ target_loss,
2370
+ distribution,
2371
+ distribution_params: payload.distribution_params,
2372
+ max_size
1761
2373
  });
2374
+ if (force_exact_risk) {
2375
+ const new_risk_per_trade = determineOptimumRisk(global_config, {
2376
+ entry,
2377
+ stop,
2378
+ risk_reward,
2379
+ risk,
2380
+ symbol: global_config.symbol,
2381
+ distribution,
2382
+ distribution_params: payload.distribution_params
2383
+ }, {
2384
+ highest_risk: risk * risk_factor
2385
+ }).optimal_risk;
2386
+ return { risk: new_risk_per_trade, risk_reward };
2387
+ }
1762
2388
  return risk_reward;
1763
2389
  }
1764
2390
  function computeProfitDetail(payload) {
1765
2391
  const {
1766
2392
  focus_position,
1767
2393
  strategy,
2394
+ pnl,
1768
2395
  price_places = "%.1f",
1769
2396
  reduce_position,
1770
2397
  decimal_places,
1771
- reverse_position
2398
+ reverse_position,
2399
+ full_ratio = 1
1772
2400
  } = payload;
1773
- let reward_factor = strategy.reward_factor;
1774
- let risk = strategy.risk;
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");
2401
+ let reward_factor = strategy?.reward_factor || 1;
2402
+ const profit_percent = to_f(pnl * 100 / (focus_position.avg_price * focus_position.avg_qty), "%.4f");
1786
2403
  const diff = pnl / focus_position.quantity;
1787
2404
  const sell_price = to_f(focus_position.kind === "long" ? focus_position.entry + diff : focus_position.entry - diff, price_places);
1788
2405
  let loss = 0;
2406
+ let full_loss = 0;
1789
2407
  let expected_loss = 0;
1790
2408
  let quantity = 0;
1791
2409
  let new_pnl = pnl;
@@ -1793,6 +2411,8 @@ function computeProfitDetail(payload) {
1793
2411
  loss = Math.abs(reduce_position.entry - sell_price) * reduce_position.quantity;
1794
2412
  const ratio = pnl / loss;
1795
2413
  quantity = to_f(reduce_position.quantity * ratio, decimal_places);
2414
+ expected_loss = to_f(Math.abs(reduce_position.entry - sell_price) * quantity, "%.2f");
2415
+ full_loss = Math.abs(reduce_position.avg_price - sell_price) * reduce_position.avg_qty * full_ratio;
1796
2416
  }
1797
2417
  if (reverse_position) {
1798
2418
  expected_loss = Math.abs(reverse_position.avg_price - sell_price) * reverse_position.avg_qty;
@@ -1801,12 +2421,13 @@ function computeProfitDetail(payload) {
1801
2421
  return {
1802
2422
  pnl: new_pnl,
1803
2423
  loss: to_f(expected_loss, "%.2f"),
2424
+ full_loss: to_f(full_loss, "%.2f"),
1804
2425
  original_pnl: pnl,
1805
2426
  reward_factor,
1806
2427
  profit_percent,
1807
2428
  kind: focus_position.kind,
1808
2429
  sell_price: to_f(sell_price, price_places),
1809
- quantity,
2430
+ quantity: quantity * full_ratio,
1810
2431
  price_places,
1811
2432
  decimal_places
1812
2433
  };
@@ -1815,10 +2436,26 @@ function generateGapTp(payload) {
1815
2436
  const {
1816
2437
  long,
1817
2438
  short,
1818
- factor = 1,
2439
+ factor: factor_value = 0,
2440
+ risk: desired_risk,
2441
+ sell_factor = 1,
2442
+ kind,
1819
2443
  price_places = "%.1f",
1820
2444
  decimal_places = "%.3f"
1821
2445
  } = payload;
2446
+ if (!factor_value && !desired_risk) {
2447
+ throw new Error("Either factor or risk must be provided");
2448
+ }
2449
+ if (desired_risk && !kind) {
2450
+ throw new Error("Kind must be provided when risk is provided");
2451
+ }
2452
+ let factor = factor_value || calculate_factor({
2453
+ long,
2454
+ short,
2455
+ risk: desired_risk,
2456
+ kind,
2457
+ sell_factor
2458
+ });
1822
2459
  const gap = Math.abs(long.entry - short.entry);
1823
2460
  const max_quantity = Math.max(long.quantity, short.quantity);
1824
2461
  const gapLoss = gap * max_quantity;
@@ -1828,14 +2465,36 @@ function generateGapTp(payload) {
1828
2465
  const shortTp = to_f((1 + shortPercent) ** -1 * short.entry, price_places);
1829
2466
  const shortToReduce = to_f(Math.abs(longTp - long.entry) * long.quantity, "%.1f");
1830
2467
  const longToReduce = to_f(Math.abs(shortTp - short.entry) * short.quantity, "%.1f");
1831
- const short_quantity_to_sell = determine_amount_to_sell2(short.entry, short.quantity, longTp, shortToReduce, "short", decimal_places);
1832
- const long_quantity_to_sell = determine_amount_to_sell2(long.entry, long.quantity, shortTp, longToReduce, "long", decimal_places);
2468
+ const actualShortReduce = to_f(shortToReduce * sell_factor, "%.1f");
2469
+ const actualLongReduce = to_f(longToReduce * sell_factor, "%.1f");
2470
+ const short_quantity_to_sell = determine_amount_to_sell2(short.entry, short.quantity, longTp, actualShortReduce, "short", decimal_places);
2471
+ const long_quantity_to_sell = determine_amount_to_sell2(long.entry, long.quantity, shortTp, actualLongReduce, "long", decimal_places);
2472
+ const risk_amount_short = to_f(shortToReduce - actualShortReduce, "%.2f");
2473
+ const risk_amount_long = to_f(longToReduce - actualLongReduce, "%.2f");
2474
+ const profit_percent_long = to_f(shortToReduce * 100 / (long.entry * long.quantity), "%.4f");
2475
+ const profit_percent_short = to_f(longToReduce * 100 / (short.entry * short.quantity), "%.4f");
1833
2476
  return {
1834
- long_tp: longTp,
1835
- short_tp: shortTp,
1836
- short_to_reduce: shortToReduce,
1837
- long_to_reduce: longToReduce,
1838
- to_sell: {
2477
+ profit_percent: {
2478
+ long: profit_percent_long,
2479
+ short: profit_percent_short
2480
+ },
2481
+ risk: {
2482
+ short: risk_amount_short,
2483
+ long: risk_amount_long
2484
+ },
2485
+ take_profit: {
2486
+ long: longTp,
2487
+ short: shortTp
2488
+ },
2489
+ to_reduce: {
2490
+ short: actualShortReduce,
2491
+ long: actualLongReduce
2492
+ },
2493
+ full_reduce: {
2494
+ short: shortToReduce,
2495
+ long: longToReduce
2496
+ },
2497
+ sell_quantity: {
1839
2498
  short: short_quantity_to_sell,
1840
2499
  long: long_quantity_to_sell
1841
2500
  },
@@ -1843,6 +2502,370 @@ function generateGapTp(payload) {
1843
2502
  gap_loss: to_f(gapLoss, "%.2f")
1844
2503
  };
1845
2504
  }
2505
+ function calculate_factor(payload) {
2506
+ const {
2507
+ long,
2508
+ short,
2509
+ risk: desired_risk,
2510
+ kind,
2511
+ sell_factor,
2512
+ places = "%.4f"
2513
+ } = payload;
2514
+ const gap = Math.abs(long.entry - short.entry);
2515
+ const max_quantity = Math.max(long.quantity, short.quantity);
2516
+ const gapLoss = gap * max_quantity;
2517
+ let calculated_factor;
2518
+ const long_notional = long.entry * long.quantity;
2519
+ const short_notional = short.entry * short.quantity;
2520
+ const target_to_reduce = desired_risk / (1 - sell_factor);
2521
+ if (kind === "short") {
2522
+ const calculate_longPercent = target_to_reduce / long_notional;
2523
+ calculated_factor = calculate_longPercent * short_notional / gapLoss;
2524
+ } else {
2525
+ const calculated_shortPercent = target_to_reduce / (short_notional - target_to_reduce);
2526
+ calculated_factor = calculated_shortPercent * long_notional / gapLoss;
2527
+ }
2528
+ calculated_factor = to_f(calculated_factor, places);
2529
+ return calculated_factor;
2530
+ }
2531
+ function calculateFactorFromTakeProfit(payload) {
2532
+ const { long, short, knownTp, tpType, price_places = "%.4f" } = payload;
2533
+ const gap = Math.abs(long.entry - short.entry);
2534
+ const max_quantity = Math.max(long.quantity, short.quantity);
2535
+ const gapLoss = gap * max_quantity;
2536
+ if (gapLoss === 0) {
2537
+ return 0;
2538
+ }
2539
+ let factor;
2540
+ if (tpType === "long") {
2541
+ const longPercent = knownTp / long.entry - 1;
2542
+ factor = longPercent * short.entry * short.quantity / gapLoss;
2543
+ } else {
2544
+ const shortPercent = short.entry / knownTp - 1;
2545
+ factor = shortPercent * long.entry * long.quantity / gapLoss;
2546
+ }
2547
+ return to_f(factor, price_places);
2548
+ }
2549
+ function calculateFactorFromSellQuantity(payload) {
2550
+ const {
2551
+ long,
2552
+ short,
2553
+ knownSellQuantity,
2554
+ sellType,
2555
+ sell_factor = 1,
2556
+ price_places = "%.4f"
2557
+ } = payload;
2558
+ if (knownSellQuantity < 0.00001) {
2559
+ return 0;
2560
+ }
2561
+ const gap = Math.abs(long.entry - short.entry);
2562
+ const max_quantity = Math.max(long.quantity, short.quantity);
2563
+ const gapLoss = gap * max_quantity;
2564
+ if (gapLoss === 0) {
2565
+ return 0;
2566
+ }
2567
+ let low_factor = 0.001;
2568
+ let high_factor = 1;
2569
+ let best_factor = 0;
2570
+ let best_diff = Infinity;
2571
+ const tolerance = 0.00001;
2572
+ const max_iterations = 150;
2573
+ let expansions = 0;
2574
+ const max_expansions = 15;
2575
+ let iterations = 0;
2576
+ while (iterations < max_iterations) {
2577
+ iterations++;
2578
+ const mid_factor = (low_factor + high_factor) / 2;
2579
+ const testResult = generateGapTp({
2580
+ long,
2581
+ short,
2582
+ factor: mid_factor,
2583
+ sell_factor,
2584
+ price_places: "%.8f",
2585
+ decimal_places: "%.8f"
2586
+ });
2587
+ const testSellQty = sellType === "long" ? testResult.sell_quantity.long : testResult.sell_quantity.short;
2588
+ const diff = Math.abs(testSellQty - knownSellQuantity);
2589
+ if (diff < best_diff) {
2590
+ best_diff = diff;
2591
+ best_factor = mid_factor;
2592
+ }
2593
+ if (diff < tolerance) {
2594
+ return to_f(mid_factor, price_places);
2595
+ }
2596
+ if (testSellQty < knownSellQuantity) {
2597
+ low_factor = mid_factor;
2598
+ if (mid_factor > high_factor * 0.9 && expansions < max_expansions) {
2599
+ const ratio = knownSellQuantity / testSellQty;
2600
+ if (ratio > 2) {
2601
+ high_factor = high_factor * Math.min(ratio, 10);
2602
+ } else {
2603
+ high_factor = high_factor * 2;
2604
+ }
2605
+ expansions++;
2606
+ continue;
2607
+ }
2608
+ } else {
2609
+ high_factor = mid_factor;
2610
+ }
2611
+ if (Math.abs(high_factor - low_factor) < 0.00001 && diff > tolerance) {
2612
+ if (testSellQty < knownSellQuantity && expansions < max_expansions) {
2613
+ high_factor = high_factor * 1.1;
2614
+ expansions++;
2615
+ continue;
2616
+ } else {
2617
+ break;
2618
+ }
2619
+ }
2620
+ }
2621
+ return to_f(best_factor, price_places);
2622
+ }
2623
+ function determineRewardFactor(payload) {
2624
+ const { quantity, avg_qty, minimum_pnl, risk } = payload;
2625
+ const reward_factor = minimum_pnl / risk;
2626
+ const quantity_ratio = quantity / avg_qty;
2627
+ return to_f(reward_factor / quantity_ratio, "%.4f");
2628
+ }
2629
+ function getHedgeZone(payload) {
2630
+ const {
2631
+ reward_factor: _reward_factor,
2632
+ symbol_config,
2633
+ risk,
2634
+ position: position2,
2635
+ risk_factor = 1,
2636
+ support
2637
+ } = payload;
2638
+ const kind = position2.kind;
2639
+ let reward_factor = _reward_factor;
2640
+ if (support) {
2641
+ const _result = getOptimumHedgeFactor({
2642
+ target_support: support,
2643
+ symbol_config,
2644
+ risk,
2645
+ position: position2
2646
+ });
2647
+ reward_factor = Number(_result.reward_factor);
2648
+ }
2649
+ const take_profit = position2.tp?.price;
2650
+ const tp_diff = Math.abs(take_profit - position2.entry);
2651
+ const quantity = position2.quantity;
2652
+ const diff = risk / quantity;
2653
+ let new_take_profit = kind === "long" ? to_f(position2.entry + diff, symbol_config.price_places) : to_f(position2.entry - diff, symbol_config.price_places);
2654
+ let base_factor = to_f(Math.max(tp_diff, diff) / (Math.min(tp_diff, diff) || 1), "%.3f");
2655
+ let factor = reward_factor || base_factor;
2656
+ const new_risk = risk * factor * risk_factor;
2657
+ const stop_loss_diff = new_risk / quantity;
2658
+ 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);
2659
+ 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);
2660
+ const profit_percent = new_risk * 100 / (position2.entry * position2.quantity);
2661
+ return {
2662
+ support: Math.min(new_take_profit, stop_loss),
2663
+ resistance: Math.max(new_take_profit, stop_loss),
2664
+ risk: to_f(new_risk, "%.2f"),
2665
+ profit_percent: to_f(profit_percent, "%.2f")
2666
+ };
2667
+ }
2668
+ function getOptimumHedgeFactor(payload) {
2669
+ const {
2670
+ target_support,
2671
+ max_iterations = 50,
2672
+ min_factor = 0.1,
2673
+ max_factor = 20,
2674
+ symbol_config,
2675
+ risk,
2676
+ position: position2
2677
+ } = payload;
2678
+ const current_price = position2.entry;
2679
+ const tolerance = current_price > 100 ? 0.5 : current_price > 1 ? 0.01 : 0.001;
2680
+ let low = min_factor;
2681
+ let high = max_factor;
2682
+ let best_factor = low;
2683
+ let best_diff = Infinity;
2684
+ for (let iteration = 0;iteration < max_iterations; iteration++) {
2685
+ const mid_factor = (low + high) / 2;
2686
+ const hedge_zone = getHedgeZone({
2687
+ reward_factor: mid_factor,
2688
+ symbol_config,
2689
+ risk,
2690
+ position: position2
2691
+ });
2692
+ const current_support = hedge_zone.support;
2693
+ const diff = Math.abs(current_support - target_support);
2694
+ if (diff < best_diff) {
2695
+ best_diff = diff;
2696
+ best_factor = mid_factor;
2697
+ }
2698
+ if (diff <= tolerance) {
2699
+ return {
2700
+ reward_factor: to_f(mid_factor, "%.4f"),
2701
+ achieved_support: to_f(current_support, symbol_config.price_places),
2702
+ target_support: to_f(target_support, symbol_config.price_places),
2703
+ difference: to_f(diff, symbol_config.price_places),
2704
+ iterations: iteration + 1
2705
+ };
2706
+ }
2707
+ if (current_support > target_support) {
2708
+ low = mid_factor;
2709
+ } else {
2710
+ high = mid_factor;
2711
+ }
2712
+ if (Math.abs(high - low) < 0.0001) {
2713
+ break;
2714
+ }
2715
+ }
2716
+ const final_hedge_zone = getHedgeZone({
2717
+ symbol_config,
2718
+ risk,
2719
+ position: position2,
2720
+ reward_factor: best_factor
2721
+ });
2722
+ return {
2723
+ reward_factor: to_f(best_factor, "%.4f"),
2724
+ achieved_support: to_f(final_hedge_zone.support, symbol_config.price_places),
2725
+ target_support: to_f(target_support, symbol_config.price_places),
2726
+ difference: to_f(best_diff, symbol_config.price_places),
2727
+ iterations: max_iterations,
2728
+ converged: best_diff <= tolerance
2729
+ };
2730
+ }
2731
+ function determineCompoundLongTrade(payload) {
2732
+ const {
2733
+ focus_short_position,
2734
+ focus_long_position,
2735
+ shortConfig,
2736
+ global_config,
2737
+ rr = 1
2738
+ } = payload;
2739
+ const short_app_config = buildAppConfig(global_config, {
2740
+ entry: shortConfig.entry,
2741
+ stop: shortConfig.stop,
2742
+ risk_reward: shortConfig.risk_reward,
2743
+ risk: shortConfig.risk,
2744
+ symbol: shortConfig.symbol
2745
+ });
2746
+ const short_max_size = short_app_config.last_value.avg_size;
2747
+ const start_risk = Math.abs(short_app_config.last_value.neg_pnl) * rr;
2748
+ const short_profit = short_app_config.last_value.avg_size * short_app_config.last_value.avg_entry * shortConfig.profit_percent / 100;
2749
+ const diff = short_profit * rr / short_app_config.last_value.avg_size;
2750
+ const support = Math.abs(short_app_config.last_value.avg_entry - diff);
2751
+ const resistance = focus_short_position.next_order || focus_long_position.take_profit;
2752
+ console.log({ support, resistance, short_profit });
2753
+ const result = getRiskReward({
2754
+ entry: resistance,
2755
+ stop: support,
2756
+ risk: start_risk,
2757
+ global_config,
2758
+ force_exact_risk: true
2759
+ });
2760
+ const long_app_config = buildAppConfig(global_config, {
2761
+ entry: resistance,
2762
+ stop: support,
2763
+ risk_reward: result.risk_reward,
2764
+ risk: result.risk,
2765
+ symbol: shortConfig.symbol
2766
+ });
2767
+ const long_profit_percent = start_risk * 2 * 100 / (long_app_config.last_value.avg_size * long_app_config.last_value.avg_entry);
2768
+ return {
2769
+ start_risk,
2770
+ short_profit,
2771
+ support: to_f(support, global_config.price_places),
2772
+ resistance: to_f(resistance, global_config.price_places),
2773
+ long_v: long_app_config.last_value,
2774
+ profit_percent: to_f(long_profit_percent, "%.3f"),
2775
+ result,
2776
+ short_max_size
2777
+ };
2778
+ }
2779
+ function generateOppositeTradeConfig(payload) {
2780
+ const {
2781
+ kind,
2782
+ entry,
2783
+ quantity,
2784
+ target_pnl,
2785
+ ratio = 0.5,
2786
+ global_config
2787
+ } = payload;
2788
+ const diff = target_pnl / quantity;
2789
+ const tp = kind === "long" ? entry + diff : entry - diff;
2790
+ const stop = kind === "long" ? entry - diff : entry + diff;
2791
+ const opposite_pnl = target_pnl * ratio;
2792
+ const app_config = constructAppConfig({
2793
+ account: {
2794
+ expand: {
2795
+ b_config: {
2796
+ entry,
2797
+ stop,
2798
+ symbol: global_config.symbol,
2799
+ risk: target_pnl
2800
+ }
2801
+ }
2802
+ },
2803
+ global_config,
2804
+ distribution_config: {}
2805
+ });
2806
+ const risk_reward = computeRiskReward({
2807
+ app_config,
2808
+ entry: stop,
2809
+ stop: tp,
2810
+ risk_per_trade: opposite_pnl,
2811
+ target_loss: opposite_pnl
2812
+ });
2813
+ return {
2814
+ entry: to_f(stop, global_config.price_places),
2815
+ stop: to_f(tp, global_config.price_places),
2816
+ risk: to_f(opposite_pnl, "%.2f"),
2817
+ risk_reward
2818
+ };
2819
+ }
2820
+ function constructAppConfig(payload) {
2821
+ const { account, global_config, kelly_config, distribution_config } = payload;
2822
+ const config = account.expand?.b_config;
2823
+ if (!config) {
2824
+ return null;
2825
+ }
2826
+ const kelly = config.kelly;
2827
+ const options = {
2828
+ entry: config?.entry,
2829
+ stop: config?.stop,
2830
+ risk_reward: config?.risk_reward,
2831
+ risk: config?.risk,
2832
+ symbol: account.symbol,
2833
+ use_kelly: kelly_config?.use_kelly ?? kelly?.use_kelly,
2834
+ kelly_confidence_factor: kelly_config?.kelly_confidence_factor ?? kelly?.kelly_confidence_factor,
2835
+ kelly_minimum_risk: kelly_config?.kelly_minimum_risk ?? kelly?.kelly_minimum_risk,
2836
+ kelly_prediction_model: kelly_config?.kelly_prediction_model ?? kelly?.kelly_prediction_model,
2837
+ distribution: distribution_config?.distribution ?? config?.distribution,
2838
+ distribution_params: distribution_config?.distribution_params ?? config?.distribution_params
2839
+ };
2840
+ const { entries: _entries, ...appConfig } = buildAppConfig(global_config, options);
2841
+ return appConfig;
2842
+ }
2843
+ function generateDangerousConfig(payload) {
2844
+ const { account, global_config, config } = payload;
2845
+ const app_config = constructAppConfig({
2846
+ account,
2847
+ global_config,
2848
+ kelly_config: {},
2849
+ distribution_config: {}
2850
+ });
2851
+ const { optimal_risk, optimal_stop } = getOptimumStopAndRisk(app_config, {
2852
+ max_size: config.quantity,
2853
+ target_stop: config.stop
2854
+ });
2855
+ const optimumRiskReward = computeRiskReward({
2856
+ app_config,
2857
+ entry: config.entry,
2858
+ stop: optimal_stop,
2859
+ risk_per_trade: optimal_risk,
2860
+ target_loss: optimal_risk
2861
+ });
2862
+ return {
2863
+ entry: config.entry,
2864
+ risk: optimal_risk,
2865
+ stop: optimal_stop,
2866
+ risk_reward: optimumRiskReward
2867
+ };
2868
+ }
1846
2869
  // src/helpers/strategy.ts
1847
2870
  class Strategy {
1848
2871
  position;
@@ -2245,11 +3268,14 @@ class Strategy {
2245
3268
  return { ...app_config, avg, loss: -expected_loss, profit_percent };
2246
3269
  }
2247
3270
  identifyGapConfig(payload) {
2248
- const { factor = 1 } = payload;
3271
+ const { factor, sell_factor = 1, kind, risk } = payload;
2249
3272
  return generateGapTp({
2250
3273
  long: this.position.long,
2251
3274
  short: this.position.short,
2252
3275
  factor,
3276
+ sell_factor,
3277
+ kind,
3278
+ risk,
2253
3279
  decimal_places: this.config.global_config.decimal_places,
2254
3280
  price_places: this.config.global_config.price_places
2255
3281
  });
@@ -2265,6 +3291,7 @@ class Strategy {
2265
3291
  avg_price: focus_position.avg_price,
2266
3292
  avg_qty: focus_position.avg_qty
2267
3293
  },
3294
+ pnl: this.pnl(kind),
2268
3295
  strategy: {
2269
3296
  reward_factor,
2270
3297
  max_reward_factor,
@@ -2273,7 +3300,535 @@ class Strategy {
2273
3300
  });
2274
3301
  return result;
2275
3302
  }
3303
+ simulateGapReduction(payload) {
3304
+ const {
3305
+ factor,
3306
+ direction,
3307
+ sell_factor = 1,
3308
+ iterations = 10,
3309
+ risk: desired_risk,
3310
+ kind
3311
+ } = payload;
3312
+ const results = [];
3313
+ let params = {
3314
+ long: this.position.long,
3315
+ short: this.position.short,
3316
+ config: this.config
3317
+ };
3318
+ for (let i = 0;i < iterations; i++) {
3319
+ const instance = new Strategy(params);
3320
+ const { profit_percent, risk, take_profit, sell_quantity, gap_loss } = instance.identifyGapConfig({
3321
+ factor,
3322
+ sell_factor,
3323
+ kind,
3324
+ risk: desired_risk
3325
+ });
3326
+ const to_add = {
3327
+ profit_percent,
3328
+ risk,
3329
+ take_profit,
3330
+ sell_quantity,
3331
+ gap_loss,
3332
+ position: {
3333
+ long: {
3334
+ entry: this.to_f(params.long.entry),
3335
+ quantity: this.to_df(params.long.quantity)
3336
+ },
3337
+ short: {
3338
+ entry: this.to_f(params.short.entry),
3339
+ quantity: this.to_df(params.short.quantity)
3340
+ }
3341
+ }
3342
+ };
3343
+ const sell_kind = direction == "long" ? "short" : "long";
3344
+ if (sell_quantity[sell_kind] <= 0) {
3345
+ break;
3346
+ }
3347
+ results.push(to_add);
3348
+ const remaining = this.to_df(params[sell_kind].quantity - sell_quantity[sell_kind]);
3349
+ if (remaining <= 0) {
3350
+ break;
3351
+ }
3352
+ params[sell_kind].quantity = remaining;
3353
+ params[direction] = {
3354
+ entry: take_profit[direction],
3355
+ quantity: remaining
3356
+ };
3357
+ }
3358
+ const last_gap_loss = results.at(-1)?.gap_loss;
3359
+ const last_tp = results.at(-1)?.take_profit[direction];
3360
+ const entry = this.position[direction].entry;
3361
+ const quantity = this.to_df(Math.abs(last_tp - entry) / last_gap_loss);
3362
+ return {
3363
+ results,
3364
+ quantity
3365
+ };
3366
+ }
3367
+ }
3368
+ // src/helpers/compound.ts
3369
+ function buildTrades(payload) {
3370
+ const { appConfig, settings, kind } = payload;
3371
+ const kelly_config = settings.kelly;
3372
+ const distribution_params = settings.distribution_params;
3373
+ const current_app_config = { ...appConfig[kind] };
3374
+ const entryNum = parseFloat(settings.entry);
3375
+ const stopNum = parseFloat(settings.stop);
3376
+ current_app_config.entry = entryNum;
3377
+ current_app_config.stop = stopNum;
3378
+ current_app_config.risk_per_trade = parseFloat(settings.risk);
3379
+ current_app_config.risk_reward = parseFloat(settings.risk_reward);
3380
+ current_app_config.kind = kind;
3381
+ current_app_config.kelly = kelly_config;
3382
+ current_app_config.distribution_params = distribution_params;
3383
+ const options = {
3384
+ take_profit: null,
3385
+ entry: current_app_config.entry,
3386
+ stop: current_app_config.stop,
3387
+ raw_instance: null,
3388
+ risk: current_app_config.risk_per_trade,
3389
+ no_of_trades: undefined,
3390
+ risk_reward: current_app_config.risk_reward,
3391
+ kind: current_app_config.kind,
3392
+ increase: true,
3393
+ gap: current_app_config.gap,
3394
+ rr: current_app_config.rr,
3395
+ price_places: current_app_config.price_places,
3396
+ decimal_places: current_app_config.decimal_places,
3397
+ use_kelly: kelly_config?.use_kelly,
3398
+ kelly_confidence_factor: kelly_config?.kelly_confidence_factor,
3399
+ kelly_minimum_risk: kelly_config?.kelly_minimum_risk,
3400
+ kelly_prediction_model: kelly_config?.kelly_prediction_model,
3401
+ kelly_func: kelly_config?.kelly_func,
3402
+ distribution: settings.distribution,
3403
+ distribution_params: settings.distribution_params
3404
+ };
3405
+ if (kind === "long" && entryNum <= stopNum) {
3406
+ return [];
3407
+ }
3408
+ if (kind === "short" && entryNum >= stopNum) {
3409
+ return [];
3410
+ }
3411
+ try {
3412
+ const generatedTrades = sortedBuildConfig(current_app_config, options);
3413
+ return generatedTrades ?? [];
3414
+ } catch (error) {
3415
+ console.error("Error generating orders:", error);
3416
+ return [];
3417
+ }
3418
+ }
3419
+ function generateSummary({
3420
+ trades,
3421
+ fee_percent = 0.05,
3422
+ anchor
3423
+ }) {
3424
+ const avg_entry = trades[0].avg_entry;
3425
+ const avg_size = trades[0].avg_size;
3426
+ const expected_fee = avg_entry * avg_size * fee_percent / 100;
3427
+ return {
3428
+ first_entry: trades.at(-1).entry,
3429
+ last_entry: trades[0].entry,
3430
+ quantity: avg_size,
3431
+ entry: avg_entry,
3432
+ loss: trades[0].neg_pnl,
3433
+ number_of_trades: trades.length,
3434
+ fee: to_f(expected_fee, "%.2f"),
3435
+ anchor_pnl: anchor?.target_pnl
3436
+ };
3437
+ }
3438
+ function helperFuncToBuildTrades({
3439
+ custom_b_config,
3440
+ symbol_config,
3441
+ app_config_kind,
3442
+ appConfig,
3443
+ force_exact_risk = true
3444
+ }) {
3445
+ const risk = custom_b_config.risk * (custom_b_config.risk_factor || 1);
3446
+ let result = getRiskReward({
3447
+ entry: custom_b_config.entry,
3448
+ stop: custom_b_config.stop,
3449
+ risk,
3450
+ global_config: symbol_config,
3451
+ force_exact_risk,
3452
+ target_loss: custom_b_config.risk * (custom_b_config.risk_factor || 1),
3453
+ distribution: custom_b_config.distribution,
3454
+ distribution_params: custom_b_config.distribution_params
3455
+ });
3456
+ if (!force_exact_risk) {
3457
+ result = {
3458
+ risk_reward: result,
3459
+ risk
3460
+ };
3461
+ }
3462
+ const trades = result.risk_reward ? buildTrades({
3463
+ appConfig: { [app_config_kind]: appConfig },
3464
+ kind: app_config_kind,
3465
+ settings: {
3466
+ entry: custom_b_config.entry,
3467
+ stop: custom_b_config.stop,
3468
+ risk: result.risk || custom_b_config.risk,
3469
+ risk_reward: result.risk_reward,
3470
+ distribution: custom_b_config.distribution,
3471
+ distribution_params: custom_b_config.distribution_params
3472
+ }
3473
+ }) : [];
3474
+ const summary = trades.length > 0 ? generateSummary({ trades }) : {};
3475
+ return { trades, result, summary };
3476
+ }
3477
+ function constructAppConfig2({
3478
+ config,
3479
+ global_config
3480
+ }) {
3481
+ const options = {
3482
+ entry: config?.entry,
3483
+ stop: config?.stop,
3484
+ risk_reward: config?.risk_reward,
3485
+ risk: config?.risk,
3486
+ symbol: config.symbol
3487
+ };
3488
+ const { entries: _entries, ...appConfig } = buildAppConfig(global_config, options);
3489
+ return appConfig;
3490
+ }
3491
+ function buildWithOptimumReward({
3492
+ config,
3493
+ settings,
3494
+ global_config,
3495
+ force_exact
3496
+ }) {
3497
+ const kind = config.entry > config.stop ? "long" : "short";
3498
+ let stop = settings.stop;
3499
+ let entry = settings.entry;
3500
+ const risk = settings.risk;
3501
+ const stop_ratio = settings.stop_ratio || 1;
3502
+ const distribution = settings.distribution || config?.distribution;
3503
+ const distribution_params = settings.distribution_params || config?.distribution_params;
3504
+ const custom_b_config = {
3505
+ entry,
3506
+ stop,
3507
+ risk,
3508
+ distribution,
3509
+ distribution_params
3510
+ };
3511
+ const appConfig = constructAppConfig2({
3512
+ config,
3513
+ global_config
3514
+ });
3515
+ const { trades, summary, result } = helperFuncToBuildTrades({
3516
+ custom_b_config,
3517
+ app_config_kind: kind,
3518
+ appConfig,
3519
+ symbol_config: global_config,
3520
+ force_exact_risk: force_exact
3521
+ });
3522
+ const adjusted_size = summary.quantity;
3523
+ const symbol_config = global_config;
3524
+ const entryDetails = {
3525
+ entry: to_f(custom_b_config.entry, symbol_config.price_places),
3526
+ stop: to_f(custom_b_config.stop, symbol_config.price_places),
3527
+ risk: to_f(result.risk, "%.2f"),
3528
+ risk_reward: result.risk_reward,
3529
+ avg_entry: to_f(summary.entry, symbol_config.price_places),
3530
+ avg_size: to_f(adjusted_size, symbol_config.decimal_places),
3531
+ first_entry: to_f(summary.first_entry, symbol_config.price_places),
3532
+ pnl: to_f(custom_b_config.risk, "%.2f"),
3533
+ fee: to_f(summary.fee, "%.2f"),
3534
+ loss: to_f(summary.loss, "%.2f"),
3535
+ last_entry: to_f(summary.last_entry, symbol_config.price_places),
3536
+ margin: to_f(summary.entry * adjusted_size / symbol_config.leverage, "%.2f")
3537
+ };
3538
+ return {
3539
+ trades,
3540
+ summary: entryDetails,
3541
+ config: {
3542
+ ...custom_b_config,
3543
+ ...result,
3544
+ stop_ratio
3545
+ },
3546
+ stop_order: {
3547
+ quantity: entryDetails.avg_size * stop_ratio,
3548
+ price: entryDetails.stop
3549
+ },
3550
+ kind
3551
+ };
2276
3552
  }
3553
+ function generateOppositeOptimum({
3554
+ config,
3555
+ global_config,
3556
+ settings,
3557
+ ratio = 1,
3558
+ distribution,
3559
+ distribution_params,
3560
+ risk_factor = 1
3561
+ }) {
3562
+ const configKind = config.entry > config.stop ? "long" : "short";
3563
+ if (configKind === "long" && config.entry > config.stop) {
3564
+ if (settings.stop <= settings.entry) {
3565
+ throw new Error("Invalid input: For long config positions, opposite settings must have stop > entry");
3566
+ }
3567
+ } else if (configKind === "short" && config.entry < config.stop) {
3568
+ if (settings.stop >= settings.entry) {
3569
+ throw new Error("Invalid input: For short config positions, opposite settings must have stop < entry");
3570
+ }
3571
+ }
3572
+ const kind = config.entry > config.stop ? "long" : "short";
3573
+ const app_config_kind = kind === "long" ? "short" : "long";
3574
+ let risk = settings.risk;
3575
+ const custom_b_config = {
3576
+ entry: settings.entry,
3577
+ stop: settings.stop,
3578
+ risk: risk * ratio,
3579
+ distribution: distribution || "inverse-exponential",
3580
+ distribution_params: distribution_params || config?.distribution_params,
3581
+ risk_factor
3582
+ };
3583
+ const appConfig = constructAppConfig2({
3584
+ config: {
3585
+ ...config,
3586
+ ...custom_b_config
3587
+ },
3588
+ global_config
3589
+ });
3590
+ const { result, trades, summary } = helperFuncToBuildTrades({
3591
+ custom_b_config,
3592
+ symbol_config: global_config,
3593
+ app_config_kind,
3594
+ appConfig
3595
+ });
3596
+ if (Object.keys(summary).length === 0) {
3597
+ return {
3598
+ trades,
3599
+ summary,
3600
+ config: custom_b_config,
3601
+ kind: app_config_kind
3602
+ };
3603
+ }
3604
+ const symbol_config = global_config;
3605
+ const entryDetails = {
3606
+ entry: to_f(custom_b_config.entry, symbol_config.price_places),
3607
+ stop: to_f(custom_b_config.stop, symbol_config.price_places),
3608
+ risk: to_f(result.risk, "%.2f"),
3609
+ risk_reward: result.risk_reward,
3610
+ avg_entry: to_f(summary.entry, symbol_config.price_places),
3611
+ avg_size: to_f(summary.quantity, symbol_config.decimal_places),
3612
+ first_entry: to_f(summary.first_entry, symbol_config.price_places),
3613
+ pnl: to_f(custom_b_config.risk, "%.2f"),
3614
+ fee: to_f(summary.fee, "%.2f"),
3615
+ loss: to_f(summary.loss, "%.2f"),
3616
+ last_entry: to_f(summary.last_entry, symbol_config.price_places),
3617
+ defaultEntry: settings.entry ? to_f(settings.entry, symbol_config.price_places) : null
3618
+ };
3619
+ return {
3620
+ trades,
3621
+ summary: entryDetails,
3622
+ config: {
3623
+ ...custom_b_config,
3624
+ ...result
3625
+ },
3626
+ kind: app_config_kind
3627
+ };
3628
+ }
3629
+ function defaultTradeFromCurrentState({
3630
+ config,
3631
+ global_config
3632
+ }) {
3633
+ const kind = config.entry > config.stop ? "long" : "short";
3634
+ const settings = {
3635
+ entry: config?.entry,
3636
+ stop: config?.stop,
3637
+ risk: config?.risk,
3638
+ distribution: config?.distribution,
3639
+ risk_reward: config?.risk_reward
3640
+ };
3641
+ const appConfig = constructAppConfig2({
3642
+ config,
3643
+ global_config
3644
+ });
3645
+ const trades = buildTrades({
3646
+ appConfig: { [kind]: appConfig },
3647
+ kind,
3648
+ settings
3649
+ });
3650
+ return {
3651
+ trades,
3652
+ summary: generateSummary({
3653
+ trades,
3654
+ fee_percent: global_config.fee_percent
3655
+ })
3656
+ };
3657
+ }
3658
+ function increaseTradeHelper({
3659
+ increase_qty,
3660
+ stop,
3661
+ config,
3662
+ global_config,
3663
+ style,
3664
+ entry,
3665
+ position: position2,
3666
+ stop_ratio = 1,
3667
+ distribution: default_distribution,
3668
+ distribution_params: default_distribution_params
3669
+ }) {
3670
+ const symbol_config = global_config;
3671
+ const kind = config.entry > config.stop ? "long" : "short";
3672
+ const distribution = default_distribution || config.distribution || "inverse-exponential";
3673
+ const distribution_params = default_distribution_params || config.distribution_params;
3674
+ const appConfig = constructAppConfig2({
3675
+ config,
3676
+ global_config
3677
+ });
3678
+ const currentState = defaultTradeFromCurrentState({
3679
+ config,
3680
+ global_config
3681
+ });
3682
+ const { optimal_risk, neg_pnl } = getOptimumStopAndRisk(appConfig, {
3683
+ max_size: increase_qty,
3684
+ target_stop: stop,
3685
+ distribution
3686
+ });
3687
+ if (neg_pnl === 0) {
3688
+ return {
3689
+ trades: [],
3690
+ summary: {},
3691
+ config: {},
3692
+ kind,
3693
+ current: currentState
3694
+ };
3695
+ }
3696
+ const custom_b_config = {
3697
+ entry,
3698
+ stop,
3699
+ risk: style === "minimum" ? Math.abs(neg_pnl) : optimal_risk,
3700
+ distribution,
3701
+ distribution_params
3702
+ };
3703
+ const { result, trades, summary } = helperFuncToBuildTrades({
3704
+ custom_b_config,
3705
+ symbol_config,
3706
+ appConfig,
3707
+ app_config_kind: kind
3708
+ });
3709
+ if (Object.keys(summary).length === 0) {
3710
+ return {
3711
+ trades,
3712
+ summary,
3713
+ config: {
3714
+ ...custom_b_config,
3715
+ ...result
3716
+ },
3717
+ kind,
3718
+ current: currentState
3719
+ };
3720
+ }
3721
+ const new_avg_values = determine_average_entry_and_size([
3722
+ {
3723
+ price: position2.entry,
3724
+ quantity: position2.quantity
3725
+ },
3726
+ {
3727
+ price: summary?.entry,
3728
+ quantity: summary?.quantity
3729
+ }
3730
+ ], symbol_config.decimal_places, symbol_config.price_places);
3731
+ summary.entry = new_avg_values.entry;
3732
+ summary.quantity = new_avg_values.quantity;
3733
+ const loss = Math.abs(summary.entry - custom_b_config.stop) * summary.quantity;
3734
+ const entryDetails = {
3735
+ entry: to_f(custom_b_config.entry, symbol_config.price_places),
3736
+ stop: to_f(custom_b_config.stop, symbol_config.price_places),
3737
+ risk: to_f(result.risk, symbol_config.price_places),
3738
+ risk_reward: result.risk_reward,
3739
+ avg_entry: to_f(summary.entry, symbol_config.price_places),
3740
+ avg_size: to_f(summary.quantity, symbol_config.decimal_places),
3741
+ first_entry: to_f(summary.first_entry, symbol_config.price_places),
3742
+ pnl: to_f(custom_b_config.risk, "%.2f"),
3743
+ fee: to_f(summary.fee, "%.2f"),
3744
+ loss: to_f(loss, "%.2f"),
3745
+ last_entry: to_f(summary.last_entry, symbol_config.price_places),
3746
+ margin: to_f(summary.entry * summary.quantity / global_config.leverage, "%.2f")
3747
+ };
3748
+ return {
3749
+ trades,
3750
+ summary: entryDetails,
3751
+ stop_order: {
3752
+ quantity: entryDetails.avg_size * stop_ratio,
3753
+ price: entryDetails.stop
3754
+ },
3755
+ config: {
3756
+ ...custom_b_config,
3757
+ ...result
3758
+ },
3759
+ kind,
3760
+ current: currentState
3761
+ };
3762
+ }
3763
+ function generatePositionIncreaseTrade({
3764
+ account,
3765
+ zoneAccount,
3766
+ ratio = 0.1,
3767
+ config,
3768
+ global_config,
3769
+ style = "minimum",
3770
+ distribution = "inverse-exponential",
3771
+ distribution_params
3772
+ }) {
3773
+ const kind = config.entry > config.stop ? "long" : "short";
3774
+ const target_max_quantity = kind === "long" ? account.short.quantity : account.long.quantity;
3775
+ const increase_qty = target_max_quantity * ratio;
3776
+ const entry = zoneAccount.entry;
3777
+ const stop = zoneAccount.stop;
3778
+ return increaseTradeHelper({
3779
+ config,
3780
+ position: account[kind],
3781
+ global_config,
3782
+ entry,
3783
+ stop,
3784
+ style,
3785
+ increase_qty,
3786
+ distribution,
3787
+ distribution_params
3788
+ });
3789
+ }
3790
+ function determineHedgeTradeToPlace({
3791
+ position: position2,
3792
+ config,
3793
+ global_config,
3794
+ profit_risk = 200,
3795
+ allowable_loss = 1000
3796
+ }) {
3797
+ const diff = profit_risk / position2.quantity;
3798
+ const kind = position2.kind === "long" ? "short" : "long";
3799
+ const tp_price = position2.kind === "long" ? diff + position2.entry : position2.entry - diff;
3800
+ const loss_diff = allowable_loss / position2.quantity;
3801
+ const loss_price = position2.kind === "long" ? position2.entry - loss_diff : position2.entry + loss_diff;
3802
+ const entry = kind === "short" ? loss_price : tp_price;
3803
+ const stop = kind === "short" ? tp_price : loss_price;
3804
+ const result = buildWithOptimumReward({
3805
+ config: {
3806
+ ...config,
3807
+ entry,
3808
+ stop
3809
+ },
3810
+ global_config,
3811
+ force_exact: true,
3812
+ settings: {
3813
+ entry,
3814
+ stop,
3815
+ risk: profit_risk,
3816
+ distribution: config.distribution
3817
+ }
3818
+ });
3819
+ return {
3820
+ opposite: result,
3821
+ take_profit: to_f(tp_price, global_config.price_places)
3822
+ };
3823
+ }
3824
+ var compoundAPI = {
3825
+ determineHedgeTradeToPlace,
3826
+ buildWithOptimumReward,
3827
+ constructAppConfig: constructAppConfig2,
3828
+ generateOppositeOptimum,
3829
+ increaseTradeHelper,
3830
+ generatePositionIncreaseTrade
3831
+ };
2277
3832
  export {
2278
3833
  to_f,
2279
3834
  sortedBuildConfig,
@@ -2288,10 +3843,14 @@ export {
2288
3843
  getRiskReward,
2289
3844
  getParamForField,
2290
3845
  getOptimumStopAndRisk,
3846
+ getOptimumHedgeFactor,
3847
+ getHedgeZone,
2291
3848
  getDecimalPlaces,
2292
3849
  generate_config_params,
2293
3850
  generateOptimumAppConfig,
3851
+ generateOppositeTradeConfig,
2294
3852
  generateGapTp,
3853
+ generateDangerousConfig,
2295
3854
  formatPrice,
2296
3855
  fibonacci_analysis,
2297
3856
  extractValue,
@@ -2302,13 +3861,21 @@ export {
2302
3861
  determine_average_entry_and_size,
2303
3862
  determine_amount_to_sell2 as determine_amount_to_sell,
2304
3863
  determine_amount_to_buy,
3864
+ determineTPSl,
3865
+ determineRewardFactor,
3866
+ determineOptimumRisk,
2305
3867
  determineOptimumReward,
3868
+ determineCompoundLongTrade,
2306
3869
  createGapPairs,
2307
3870
  createArray,
3871
+ constructAppConfig,
2308
3872
  computeTotalAverageForEachTrade,
2309
3873
  computeSellZones,
2310
3874
  computeRiskReward,
2311
3875
  computeProfitDetail,
3876
+ compoundAPI,
3877
+ calculateFactorFromTakeProfit,
3878
+ calculateFactorFromSellQuantity,
2312
3879
  buildConfig,
2313
3880
  buildAvg,
2314
3881
  buildAppConfig,