@gbozee/ultimate 0.0.2-next.7 → 0.0.2-next.70
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 +87 -3
- package/dist/frontend-index.js +510 -21
- package/dist/index.cjs +31914 -11928
- package/dist/index.d.ts +4430 -33
- package/dist/index.js +36443 -16471
- package/dist/mcp-client.cjs +63 -42
- package/dist/mcp-client.js +51 -33
- package/dist/mcp-server.cjs +14972 -5677
- package/dist/mcp-server.js +14962 -5681
- package/package.json +17 -12
- package/dist/frontend/frontend-index.js +0 -1318
- package/dist/mcp.d.ts +0 -5
package/dist/frontend-index.js
CHANGED
|
@@ -9,7 +9,13 @@ function generateArithmetic(payload) {
|
|
|
9
9
|
});
|
|
10
10
|
}
|
|
11
11
|
function generateGeometric(payload) {
|
|
12
|
-
const {
|
|
12
|
+
const {
|
|
13
|
+
margin_range,
|
|
14
|
+
risk_reward,
|
|
15
|
+
kind,
|
|
16
|
+
price_places = "%.1f",
|
|
17
|
+
percent_change
|
|
18
|
+
} = payload;
|
|
13
19
|
const effectivePercentChange = percent_change ?? Math.abs(margin_range[1] / margin_range[0] - 1) / risk_reward;
|
|
14
20
|
return Array.from({ length: risk_reward + 1 }, (_, i) => {
|
|
15
21
|
const price = kind === "long" ? margin_range[1] * Math.pow(1 - effectivePercentChange, i) : margin_range[0] * Math.pow(1 + effectivePercentChange, i);
|
|
@@ -31,7 +37,13 @@ function approximateInverseNormal(p) {
|
|
|
31
37
|
}
|
|
32
38
|
}
|
|
33
39
|
function generateNormal(payload) {
|
|
34
|
-
const {
|
|
40
|
+
const {
|
|
41
|
+
margin_range,
|
|
42
|
+
risk_reward,
|
|
43
|
+
kind,
|
|
44
|
+
price_places = "%.1f",
|
|
45
|
+
stdDevFactor = 6
|
|
46
|
+
} = payload;
|
|
35
47
|
const mean = (margin_range[0] + margin_range[1]) / 2;
|
|
36
48
|
const stdDev = Math.abs(margin_range[1] - margin_range[0]) / stdDevFactor;
|
|
37
49
|
const skew = kind === "long" ? -0.2 : 0.2;
|
|
@@ -46,23 +58,45 @@ function generateNormal(payload) {
|
|
|
46
58
|
return entries.sort((a, b) => a - b);
|
|
47
59
|
}
|
|
48
60
|
function generateExponential(payload) {
|
|
49
|
-
const {
|
|
61
|
+
const {
|
|
62
|
+
margin_range,
|
|
63
|
+
risk_reward,
|
|
64
|
+
kind,
|
|
65
|
+
price_places = "%.1f",
|
|
66
|
+
lambda,
|
|
67
|
+
reverse = false
|
|
68
|
+
} = payload;
|
|
50
69
|
const range = Math.abs(margin_range[1] - margin_range[0]);
|
|
51
70
|
const effectiveLambda = lambda || 2.5;
|
|
71
|
+
const normalizationFactor = 1 - Math.exp(-effectiveLambda);
|
|
52
72
|
return Array.from({ length: risk_reward + 1 }, (_, i) => {
|
|
53
73
|
const t = i / risk_reward;
|
|
54
|
-
const
|
|
55
|
-
const
|
|
74
|
+
const rawProgress = 1 - Math.exp(-effectiveLambda * t);
|
|
75
|
+
const exponentialProgress = rawProgress / normalizationFactor;
|
|
76
|
+
let price = kind === "long" ? margin_range[1] - range * exponentialProgress : margin_range[0] + range * exponentialProgress;
|
|
77
|
+
if (reverse) {
|
|
78
|
+
price = kind === "long" ? margin_range[0] + range * exponentialProgress : margin_range[1] - range * exponentialProgress;
|
|
79
|
+
}
|
|
56
80
|
return to_f(price, price_places);
|
|
57
81
|
});
|
|
58
82
|
}
|
|
59
83
|
function generateInverseExponential(payload) {
|
|
60
|
-
const {
|
|
84
|
+
const {
|
|
85
|
+
margin_range,
|
|
86
|
+
risk_reward,
|
|
87
|
+
kind,
|
|
88
|
+
price_places = "%.1f",
|
|
89
|
+
curveFactor = 2,
|
|
90
|
+
reverse = false
|
|
91
|
+
} = payload;
|
|
61
92
|
const range = Math.abs(margin_range[1] - margin_range[0]);
|
|
62
93
|
return Array.from({ length: risk_reward + 1 }, (_, i) => {
|
|
63
94
|
const t = i / risk_reward;
|
|
64
95
|
const progress = (Math.exp(curveFactor * t) - 1) / (Math.exp(curveFactor) - 1);
|
|
65
|
-
|
|
96
|
+
let price = kind === "long" ? margin_range[1] - range * progress : margin_range[0] + range * progress;
|
|
97
|
+
if (reverse) {
|
|
98
|
+
price = kind === "long" ? margin_range[0] + range * progress : margin_range[1] - range * progress;
|
|
99
|
+
}
|
|
66
100
|
return to_f(price, price_places);
|
|
67
101
|
});
|
|
68
102
|
}
|
|
@@ -91,8 +125,7 @@ function getEntries(params) {
|
|
|
91
125
|
margin_range,
|
|
92
126
|
risk_reward,
|
|
93
127
|
kind,
|
|
94
|
-
price_places
|
|
95
|
-
percent_change: distribution_params?.curveFactor
|
|
128
|
+
price_places
|
|
96
129
|
});
|
|
97
130
|
break;
|
|
98
131
|
case "normal":
|
|
@@ -113,6 +146,16 @@ function getEntries(params) {
|
|
|
113
146
|
lambda: distribution_params?.lambda
|
|
114
147
|
});
|
|
115
148
|
break;
|
|
149
|
+
case "reverse-exponential":
|
|
150
|
+
entries = generateExponential({
|
|
151
|
+
margin_range,
|
|
152
|
+
risk_reward,
|
|
153
|
+
kind,
|
|
154
|
+
price_places,
|
|
155
|
+
lambda: distribution_params?.lambda,
|
|
156
|
+
reverse: true
|
|
157
|
+
});
|
|
158
|
+
break;
|
|
116
159
|
case "inverse-exponential":
|
|
117
160
|
entries = generateInverseExponential({
|
|
118
161
|
margin_range,
|
|
@@ -122,12 +165,53 @@ function getEntries(params) {
|
|
|
122
165
|
curveFactor: distribution_params?.curveFactor
|
|
123
166
|
});
|
|
124
167
|
break;
|
|
168
|
+
case "reverse-inverse-exponential":
|
|
169
|
+
entries = generateInverseExponential({
|
|
170
|
+
margin_range,
|
|
171
|
+
risk_reward,
|
|
172
|
+
kind,
|
|
173
|
+
price_places,
|
|
174
|
+
curveFactor: distribution_params?.curveFactor,
|
|
175
|
+
reverse: true
|
|
176
|
+
});
|
|
177
|
+
break;
|
|
178
|
+
case "lognormal":
|
|
179
|
+
entries = generateLognormal({
|
|
180
|
+
margin_range,
|
|
181
|
+
risk_reward,
|
|
182
|
+
kind,
|
|
183
|
+
price_places,
|
|
184
|
+
stdDevFactor: distribution_params?.stdDevFactor
|
|
185
|
+
});
|
|
186
|
+
break;
|
|
125
187
|
default:
|
|
126
188
|
throw new Error(`Unknown distribution type: ${distribution}`);
|
|
127
189
|
}
|
|
128
190
|
return entries.sort((a, b) => a - b);
|
|
129
191
|
}
|
|
130
192
|
var distributions_default = getEntries;
|
|
193
|
+
function generateLognormal(payload) {
|
|
194
|
+
const {
|
|
195
|
+
margin_range,
|
|
196
|
+
risk_reward,
|
|
197
|
+
kind,
|
|
198
|
+
price_places = "%.1f",
|
|
199
|
+
stdDevFactor = 6
|
|
200
|
+
} = payload;
|
|
201
|
+
const logMin = Math.log(margin_range[0]);
|
|
202
|
+
const logMax = Math.log(margin_range[1]);
|
|
203
|
+
const mean = (logMin + logMax) / 2;
|
|
204
|
+
const stdDev = Math.abs(logMax - logMin) / stdDevFactor;
|
|
205
|
+
const entries = Array.from({ length: risk_reward + 1 }, (_, i) => {
|
|
206
|
+
const p = (i + 0.5) / (risk_reward + 1);
|
|
207
|
+
const z = approximateInverseNormal(p);
|
|
208
|
+
let logPrice = mean + stdDev * z;
|
|
209
|
+
logPrice = Math.max(logMin, Math.min(logMax, logPrice));
|
|
210
|
+
const price = Math.exp(logPrice);
|
|
211
|
+
return to_f(kind === "long" ? Math.min(price, margin_range[1]) : Math.max(price, margin_range[0]), price_places);
|
|
212
|
+
});
|
|
213
|
+
return entries.sort((a, b) => a - b);
|
|
214
|
+
}
|
|
131
215
|
|
|
132
216
|
// src/helpers/optimizations.ts
|
|
133
217
|
function calculateTheoreticalKelly({
|
|
@@ -501,7 +585,7 @@ class Signal {
|
|
|
501
585
|
const simple_support = Math.min(current_price, stop_loss);
|
|
502
586
|
const simple_resistance = Math.max(current_price, stop_loss);
|
|
503
587
|
const risk_per_trade = risk / this.risk_reward;
|
|
504
|
-
const use_progressive = distribution_params
|
|
588
|
+
const use_progressive = distribution_params?.use_progressive || this.use_progressive_risk;
|
|
505
589
|
const risk_distribution = use_progressive ? {
|
|
506
590
|
enabled: true,
|
|
507
591
|
total_risk_budget: risk,
|
|
@@ -1296,6 +1380,9 @@ function formatPrice(value2, opts = {}) {
|
|
|
1296
1380
|
return formatter.format(value2);
|
|
1297
1381
|
}
|
|
1298
1382
|
function to_f(value2, places = "%.1f") {
|
|
1383
|
+
if (!value2) {
|
|
1384
|
+
return null;
|
|
1385
|
+
}
|
|
1299
1386
|
let v = typeof value2 === "string" ? parseFloat(value2) : value2;
|
|
1300
1387
|
const formattedValue = places.replace("%.", "").replace("f", "");
|
|
1301
1388
|
return parseFloat(v.toFixed(parseInt(formattedValue)));
|
|
@@ -1654,7 +1741,7 @@ function buildConfig(app_config, {
|
|
|
1654
1741
|
min_avg_size = 0,
|
|
1655
1742
|
distribution,
|
|
1656
1743
|
distribution_params,
|
|
1657
|
-
use_progressive_risk
|
|
1744
|
+
use_progressive_risk = false
|
|
1658
1745
|
}) {
|
|
1659
1746
|
let fee = app_config.fee / 100;
|
|
1660
1747
|
let working_risk = risk || app_config.risk_per_trade;
|
|
@@ -1696,7 +1783,7 @@ function buildConfig(app_config, {
|
|
|
1696
1783
|
if (!stop) {
|
|
1697
1784
|
return [];
|
|
1698
1785
|
}
|
|
1699
|
-
const condition =
|
|
1786
|
+
const condition = true;
|
|
1700
1787
|
if (kind === "short") {}
|
|
1701
1788
|
const result = entry === stop ? [] : condition ? instance.build_entry({
|
|
1702
1789
|
current_price: entry,
|
|
@@ -2179,7 +2266,7 @@ function determineOptimumReward(payload) {
|
|
|
2179
2266
|
const criterion = app_config.strategy || "quantity";
|
|
2180
2267
|
const risk_rewards = createArray(low_range, high_range, 1);
|
|
2181
2268
|
let func = risk_rewards.map((trade_no) => {
|
|
2182
|
-
|
|
2269
|
+
const pp = {
|
|
2183
2270
|
take_profit: app_config.take_profit,
|
|
2184
2271
|
entry: app_config.entry,
|
|
2185
2272
|
stop: app_config.stop,
|
|
@@ -2191,7 +2278,8 @@ function determineOptimumReward(payload) {
|
|
|
2191
2278
|
decimal_places: app_config.decimal_places,
|
|
2192
2279
|
distribution,
|
|
2193
2280
|
distribution_params: payload.distribution_params
|
|
2194
|
-
}
|
|
2281
|
+
};
|
|
2282
|
+
let trades = sortedBuildConfig(app_config, pp);
|
|
2195
2283
|
let total = 0;
|
|
2196
2284
|
let max = -Infinity;
|
|
2197
2285
|
let min = Infinity;
|
|
@@ -2282,7 +2370,6 @@ function findIndexByCondition(lst, kind, condition, defaultKey = "neg_pnl") {
|
|
|
2282
2370
|
return b.net_diff - a.net_diff;
|
|
2283
2371
|
}
|
|
2284
2372
|
});
|
|
2285
|
-
console.log("found", sortedFound);
|
|
2286
2373
|
if (defaultKey === "quantity") {
|
|
2287
2374
|
return sortedFound[0].index;
|
|
2288
2375
|
}
|
|
@@ -2913,6 +3000,52 @@ function generateDangerousConfig(payload) {
|
|
|
2913
3000
|
risk_reward: optimumRiskReward
|
|
2914
3001
|
};
|
|
2915
3002
|
}
|
|
3003
|
+
function computeMarginProtection({
|
|
3004
|
+
entry,
|
|
3005
|
+
quantity,
|
|
3006
|
+
tp,
|
|
3007
|
+
stop_loss,
|
|
3008
|
+
avg_price,
|
|
3009
|
+
avg_qty,
|
|
3010
|
+
next_order,
|
|
3011
|
+
base_asset,
|
|
3012
|
+
symbol
|
|
3013
|
+
}) {
|
|
3014
|
+
const pnl = Math.abs(tp.price - entry) * quantity;
|
|
3015
|
+
const loss = Math.abs(avg_price - stop_loss.price) * avg_qty;
|
|
3016
|
+
const margin_position = pnl / Math.abs(next_order - tp.price);
|
|
3017
|
+
const protect_position = loss / Math.abs(stop_loss.price - tp.price);
|
|
3018
|
+
const ideal_protect_position = loss / Math.abs(next_order - stop_loss.price);
|
|
3019
|
+
const places = {
|
|
3020
|
+
BTCUSDT: "%.5f",
|
|
3021
|
+
ETHUSDT: "%.4f",
|
|
3022
|
+
BNBUSDT: "%.3f"
|
|
3023
|
+
};
|
|
3024
|
+
const price_places = {
|
|
3025
|
+
BTCUSDT: "%.2f",
|
|
3026
|
+
ETHUSDT: "%.2f",
|
|
3027
|
+
BNBUSDT: "%.2f"
|
|
3028
|
+
};
|
|
3029
|
+
let quantity_to_place = protect_position;
|
|
3030
|
+
if (base_asset > 0) {
|
|
3031
|
+
let diff = quantity_to_place - base_asset;
|
|
3032
|
+
quantity_to_place = diff;
|
|
3033
|
+
}
|
|
3034
|
+
const payload = {
|
|
3035
|
+
existing_quantity: base_asset,
|
|
3036
|
+
quantity_to_place: to_f(quantity_to_place, places[symbol]),
|
|
3037
|
+
quantity: to_f(margin_position, places[symbol]),
|
|
3038
|
+
price: tp.price,
|
|
3039
|
+
tp: next_order,
|
|
3040
|
+
risk: to_f(pnl, "%.1f"),
|
|
3041
|
+
protect_quantity: to_f(protect_position, places[symbol]),
|
|
3042
|
+
ideal_protect_quantity: to_f(ideal_protect_position, places[symbol]),
|
|
3043
|
+
hedge: to_f(loss, "%.1f"),
|
|
3044
|
+
price_place: price_places[symbol],
|
|
3045
|
+
decimal_place: places[symbol]
|
|
3046
|
+
};
|
|
3047
|
+
return payload;
|
|
3048
|
+
}
|
|
2916
3049
|
// src/helpers/strategy.ts
|
|
2917
3050
|
class Strategy {
|
|
2918
3051
|
position;
|
|
@@ -3487,10 +3620,14 @@ function helperFuncToBuildTrades({
|
|
|
3487
3620
|
symbol_config,
|
|
3488
3621
|
app_config_kind,
|
|
3489
3622
|
appConfig,
|
|
3490
|
-
force_exact_risk = true
|
|
3623
|
+
force_exact_risk = true,
|
|
3624
|
+
use_default = false
|
|
3491
3625
|
}) {
|
|
3492
3626
|
const risk = custom_b_config.risk * (custom_b_config.risk_factor || 1);
|
|
3493
|
-
let result =
|
|
3627
|
+
let result = use_default ? {
|
|
3628
|
+
risk,
|
|
3629
|
+
risk_reward: custom_b_config.risk_reward
|
|
3630
|
+
} : getRiskReward({
|
|
3494
3631
|
entry: custom_b_config.entry,
|
|
3495
3632
|
stop: custom_b_config.stop,
|
|
3496
3633
|
risk,
|
|
@@ -3539,7 +3676,8 @@ function buildWithOptimumReward({
|
|
|
3539
3676
|
config,
|
|
3540
3677
|
settings,
|
|
3541
3678
|
global_config,
|
|
3542
|
-
force_exact
|
|
3679
|
+
force_exact,
|
|
3680
|
+
use_default = false
|
|
3543
3681
|
}) {
|
|
3544
3682
|
const kind = config.entry > config.stop ? "long" : "short";
|
|
3545
3683
|
let stop = settings.stop;
|
|
@@ -3553,7 +3691,8 @@ function buildWithOptimumReward({
|
|
|
3553
3691
|
stop,
|
|
3554
3692
|
risk,
|
|
3555
3693
|
distribution,
|
|
3556
|
-
distribution_params
|
|
3694
|
+
distribution_params,
|
|
3695
|
+
risk_reward: settings.risk_reward || config?.risk_reward
|
|
3557
3696
|
};
|
|
3558
3697
|
const appConfig = constructAppConfig2({
|
|
3559
3698
|
config,
|
|
@@ -3564,7 +3703,8 @@ function buildWithOptimumReward({
|
|
|
3564
3703
|
app_config_kind: kind,
|
|
3565
3704
|
appConfig,
|
|
3566
3705
|
symbol_config: global_config,
|
|
3567
|
-
force_exact_risk: force_exact
|
|
3706
|
+
force_exact_risk: force_exact,
|
|
3707
|
+
use_default
|
|
3568
3708
|
});
|
|
3569
3709
|
const adjusted_size = summary.quantity;
|
|
3570
3710
|
const symbol_config = global_config;
|
|
@@ -3876,6 +4016,351 @@ var compoundAPI = {
|
|
|
3876
4016
|
increaseTradeHelper,
|
|
3877
4017
|
generatePositionIncreaseTrade
|
|
3878
4018
|
};
|
|
4019
|
+
// src/exchanges/paper/engine-class.ts
|
|
4020
|
+
function generateSummary2({
|
|
4021
|
+
trades = [],
|
|
4022
|
+
fee_percent = 0.05,
|
|
4023
|
+
anchor
|
|
4024
|
+
}) {
|
|
4025
|
+
if (trades.length === 0) {
|
|
4026
|
+
return {};
|
|
4027
|
+
}
|
|
4028
|
+
const avg_entry = trades[0].avg_entry;
|
|
4029
|
+
const avg_size = trades[0].avg_size;
|
|
4030
|
+
const expected_fee = avg_entry * avg_size * fee_percent / 100;
|
|
4031
|
+
return {
|
|
4032
|
+
first_entry: trades.at(-1).entry,
|
|
4033
|
+
last_entry: trades[0].entry,
|
|
4034
|
+
quantity: avg_size,
|
|
4035
|
+
entry: avg_entry,
|
|
4036
|
+
loss: trades[0].neg_pnl,
|
|
4037
|
+
number_of_trades: trades.length,
|
|
4038
|
+
fee: to_f(expected_fee, "%.2f"),
|
|
4039
|
+
anchor_pnl: anchor?.target_pnl
|
|
4040
|
+
};
|
|
4041
|
+
}
|
|
4042
|
+
function cumulativeHelper({
|
|
4043
|
+
tradesList,
|
|
4044
|
+
pos = {
|
|
4045
|
+
quantity: 0,
|
|
4046
|
+
entry: 0
|
|
4047
|
+
},
|
|
4048
|
+
decimal_places = "%.3f"
|
|
4049
|
+
}) {
|
|
4050
|
+
let cumulativeQty = pos.quantity || 0;
|
|
4051
|
+
const reversedTrades = [...tradesList].reverse();
|
|
4052
|
+
const processedTrades = reversedTrades.map((trade) => {
|
|
4053
|
+
cumulativeQty += trade.quantity;
|
|
4054
|
+
const avg_size = to_f(cumulativeQty, decimal_places);
|
|
4055
|
+
return {
|
|
4056
|
+
...trade,
|
|
4057
|
+
cumulative_size: avg_size
|
|
4058
|
+
};
|
|
4059
|
+
});
|
|
4060
|
+
return processedTrades.reverse();
|
|
4061
|
+
}
|
|
4062
|
+
|
|
4063
|
+
class PositionState {
|
|
4064
|
+
position;
|
|
4065
|
+
trades;
|
|
4066
|
+
take_profit = null;
|
|
4067
|
+
stop_loss = null;
|
|
4068
|
+
entry_fees;
|
|
4069
|
+
fee_percent;
|
|
4070
|
+
global_config;
|
|
4071
|
+
constructor({
|
|
4072
|
+
position: position2,
|
|
4073
|
+
trades,
|
|
4074
|
+
fee_percent = 0,
|
|
4075
|
+
entry_fees = 0,
|
|
4076
|
+
global_config
|
|
4077
|
+
}) {
|
|
4078
|
+
this.position = position2;
|
|
4079
|
+
this.trades = trades ?? [];
|
|
4080
|
+
this.entry_fees = entry_fees ?? 0;
|
|
4081
|
+
this.fee_percent = fee_percent ?? 0;
|
|
4082
|
+
this.global_config = global_config;
|
|
4083
|
+
}
|
|
4084
|
+
get newTrades() {
|
|
4085
|
+
const tradesList = this.trades;
|
|
4086
|
+
const pos = this.position;
|
|
4087
|
+
if (!tradesList || tradesList.length === 0) {
|
|
4088
|
+
return [];
|
|
4089
|
+
}
|
|
4090
|
+
let cumulativeQty = pos.quantity || 0;
|
|
4091
|
+
let cumulativeValue = cumulativeQty * (pos.entry || 0);
|
|
4092
|
+
const reversedTrades = [...tradesList].reverse();
|
|
4093
|
+
const processedTrades = reversedTrades.map((trade) => {
|
|
4094
|
+
cumulativeQty += trade.quantity;
|
|
4095
|
+
cumulativeValue += trade.quantity * trade.entry;
|
|
4096
|
+
const avg_entry = cumulativeQty > 0 ? to_f(cumulativeValue / cumulativeQty, this.global_config.price_places) : 0;
|
|
4097
|
+
const avg_size = to_f(cumulativeQty, this.global_config.decimal_places);
|
|
4098
|
+
const stop = trade.stop || pos.kind === "long" ? Math.min(...tradesList.map((o) => o.entry)) : Math.max(...tradesList.map((o) => o.entry));
|
|
4099
|
+
const neg_pnl = to_f(Math.abs(avg_entry - stop) * avg_size, this.global_config.price_places);
|
|
4100
|
+
return {
|
|
4101
|
+
...trade,
|
|
4102
|
+
avg_entry,
|
|
4103
|
+
avg_size,
|
|
4104
|
+
neg_pnl
|
|
4105
|
+
};
|
|
4106
|
+
});
|
|
4107
|
+
return processedTrades.reverse();
|
|
4108
|
+
}
|
|
4109
|
+
generateSummary(fee_percentOverride) {
|
|
4110
|
+
const trades = this.newTrades;
|
|
4111
|
+
const fee_percent = fee_percentOverride ?? this.fee_percent;
|
|
4112
|
+
return {
|
|
4113
|
+
...generateSummary2({
|
|
4114
|
+
trades,
|
|
4115
|
+
fee_percent
|
|
4116
|
+
}),
|
|
4117
|
+
entry_fees: to_f(this.entry_fees, "%.2f")
|
|
4118
|
+
};
|
|
4119
|
+
}
|
|
4120
|
+
get summary() {
|
|
4121
|
+
return this.generateSummary();
|
|
4122
|
+
}
|
|
4123
|
+
positionAt(price) {
|
|
4124
|
+
const trades = this.newTrades;
|
|
4125
|
+
const kind = this.position.kind;
|
|
4126
|
+
const filtered = trades.filter((trade) => {
|
|
4127
|
+
if (kind === "long") {
|
|
4128
|
+
return trade.entry >= price;
|
|
4129
|
+
}
|
|
4130
|
+
return trade.entry <= price;
|
|
4131
|
+
});
|
|
4132
|
+
return filtered[0];
|
|
4133
|
+
}
|
|
4134
|
+
newPositionState(price) {
|
|
4135
|
+
const position2 = this.position;
|
|
4136
|
+
const trades = this.newTrades;
|
|
4137
|
+
const instance = this.positionAt(price);
|
|
4138
|
+
if (!instance) {
|
|
4139
|
+
return new PositionState({
|
|
4140
|
+
position: { ...position2 },
|
|
4141
|
+
trades,
|
|
4142
|
+
fee_percent: this.fee_percent,
|
|
4143
|
+
entry_fees: this.entry_fees,
|
|
4144
|
+
global_config: this.global_config
|
|
4145
|
+
});
|
|
4146
|
+
}
|
|
4147
|
+
return new PositionState({
|
|
4148
|
+
position: {
|
|
4149
|
+
...position2,
|
|
4150
|
+
entry: instance.avg_entry,
|
|
4151
|
+
quantity: instance.avg_size
|
|
4152
|
+
},
|
|
4153
|
+
trades,
|
|
4154
|
+
fee_percent: this.fee_percent,
|
|
4155
|
+
entry_fees: this.entry_fees,
|
|
4156
|
+
global_config: this.global_config
|
|
4157
|
+
});
|
|
4158
|
+
}
|
|
4159
|
+
updateTakeProfit({ entry, quantity }) {
|
|
4160
|
+
this.take_profit = {
|
|
4161
|
+
price: entry,
|
|
4162
|
+
quantity: quantity ?? this.position.quantity
|
|
4163
|
+
};
|
|
4164
|
+
}
|
|
4165
|
+
updateStopLoss(payload) {
|
|
4166
|
+
const { stop, quantity } = payload ?? {
|
|
4167
|
+
stop: this.newTrades[0].entry,
|
|
4168
|
+
quantity: this.summary.quantity
|
|
4169
|
+
};
|
|
4170
|
+
this.stop_loss = {
|
|
4171
|
+
price: stop,
|
|
4172
|
+
quantity
|
|
4173
|
+
};
|
|
4174
|
+
}
|
|
4175
|
+
}
|
|
4176
|
+
function determineNewPosition({
|
|
4177
|
+
position: position2,
|
|
4178
|
+
price,
|
|
4179
|
+
trades,
|
|
4180
|
+
global_config
|
|
4181
|
+
}) {
|
|
4182
|
+
const kind = position2.kind;
|
|
4183
|
+
const placed = trades.filter((t) => {
|
|
4184
|
+
let _price = t.entry || t.price;
|
|
4185
|
+
let condition1 = kind === "long" ? _price >= price : _price <= price;
|
|
4186
|
+
return condition1;
|
|
4187
|
+
}).map((t) => {
|
|
4188
|
+
let _price = t.entry || t.price;
|
|
4189
|
+
let condition2 = kind === "long" ? _price >= position2.entry : _price <= position2.entry;
|
|
4190
|
+
if (condition2) {
|
|
4191
|
+
return {
|
|
4192
|
+
...t,
|
|
4193
|
+
price: position2.entry,
|
|
4194
|
+
entry: position2.entry
|
|
4195
|
+
};
|
|
4196
|
+
}
|
|
4197
|
+
return t;
|
|
4198
|
+
});
|
|
4199
|
+
const placeQty = placed.reduce((acc, trade) => acc + trade.quantity, 0);
|
|
4200
|
+
if (position2.quantity > 0) {
|
|
4201
|
+
const placed_less_than_entry = placed.filter((t) => {
|
|
4202
|
+
let condition1 = kind === "long" ? t.entry <= position2.entry : t.entry >= position2.entry;
|
|
4203
|
+
return condition1;
|
|
4204
|
+
});
|
|
4205
|
+
const avg = determine_average_entry_and_size(placed_less_than_entry.map((o) => ({
|
|
4206
|
+
price: o.entry || o.price,
|
|
4207
|
+
quantity: o.quantity
|
|
4208
|
+
})).concat([{ price: position2.entry, quantity: position2.quantity }]), global_config.decimal_places, global_config.price_places);
|
|
4209
|
+
position2.entry = avg.price;
|
|
4210
|
+
position2.quantity = avg.quantity;
|
|
4211
|
+
return position2;
|
|
4212
|
+
}
|
|
4213
|
+
return {
|
|
4214
|
+
...position2,
|
|
4215
|
+
entry: price,
|
|
4216
|
+
quantity: placeQty
|
|
4217
|
+
};
|
|
4218
|
+
}
|
|
4219
|
+
function positionAt({
|
|
4220
|
+
price,
|
|
4221
|
+
position: position2,
|
|
4222
|
+
trades = [],
|
|
4223
|
+
as_state = true,
|
|
4224
|
+
global_config
|
|
4225
|
+
}) {
|
|
4226
|
+
const kind = position2.kind;
|
|
4227
|
+
const fee_rate = {
|
|
4228
|
+
maker: 0.02,
|
|
4229
|
+
taker: 0.05
|
|
4230
|
+
};
|
|
4231
|
+
const placed = trades.filter((t) => {
|
|
4232
|
+
let _price = t.entry || t.price;
|
|
4233
|
+
let condition1 = kind === "long" ? _price >= price : _price <= price;
|
|
4234
|
+
return condition1;
|
|
4235
|
+
});
|
|
4236
|
+
const not_placed = trades.filter((t) => {
|
|
4237
|
+
let _price = t.entry || t.price;
|
|
4238
|
+
let condition1 = kind === "long" ? _price < price : _price > price;
|
|
4239
|
+
return condition1;
|
|
4240
|
+
});
|
|
4241
|
+
const placeQty = placed.reduce((acc, trade) => acc + trade.quantity, 0);
|
|
4242
|
+
const takerFee = price * placeQty * fee_rate.taker / 100;
|
|
4243
|
+
const makerFee = not_placed.reduce((acc, trade) => acc + trade.entry * trade.quantity * fee_rate.maker / 100, 0);
|
|
4244
|
+
const fee = to_f(takerFee + makerFee, "%.2f");
|
|
4245
|
+
const result = {
|
|
4246
|
+
trades: not_placed,
|
|
4247
|
+
position: determineNewPosition({
|
|
4248
|
+
global_config,
|
|
4249
|
+
position: { ...position2, kind },
|
|
4250
|
+
price,
|
|
4251
|
+
trades
|
|
4252
|
+
}),
|
|
4253
|
+
entry_fees: fee,
|
|
4254
|
+
fee_percent: fee_rate.maker,
|
|
4255
|
+
global_config
|
|
4256
|
+
};
|
|
4257
|
+
if (as_state) {
|
|
4258
|
+
const derivedState = new PositionState({
|
|
4259
|
+
position: result.position,
|
|
4260
|
+
trades: result.trades,
|
|
4261
|
+
fee_percent: result.fee_percent,
|
|
4262
|
+
entry_fees: parseFloat(result.entry_fees) || 0,
|
|
4263
|
+
global_config
|
|
4264
|
+
});
|
|
4265
|
+
return derivedState;
|
|
4266
|
+
}
|
|
4267
|
+
return result;
|
|
4268
|
+
}
|
|
4269
|
+
|
|
4270
|
+
class TradeEngine {
|
|
4271
|
+
trade_details = null;
|
|
4272
|
+
position;
|
|
4273
|
+
global_config;
|
|
4274
|
+
constructor({
|
|
4275
|
+
global_config,
|
|
4276
|
+
position: position2 = { entry: 0, quantity: 0, kind: "long" }
|
|
4277
|
+
}) {
|
|
4278
|
+
this.global_config = global_config;
|
|
4279
|
+
this.position = position2;
|
|
4280
|
+
}
|
|
4281
|
+
generateShortTrades(config) {
|
|
4282
|
+
const result = compoundAPI.buildWithOptimumReward({
|
|
4283
|
+
global_config: this.global_config,
|
|
4284
|
+
config,
|
|
4285
|
+
settings: config,
|
|
4286
|
+
use_default: true
|
|
4287
|
+
});
|
|
4288
|
+
this.trade_details = result;
|
|
4289
|
+
}
|
|
4290
|
+
positionAt({
|
|
4291
|
+
price,
|
|
4292
|
+
dangerous,
|
|
4293
|
+
trades: existingTrades
|
|
4294
|
+
}) {
|
|
4295
|
+
const trades = existingTrades || this.trade_details?.trades || [];
|
|
4296
|
+
if (dangerous) {
|
|
4297
|
+
const kind = this.position.kind;
|
|
4298
|
+
const naive_assumptions = positionAt({
|
|
4299
|
+
price,
|
|
4300
|
+
position: {
|
|
4301
|
+
kind,
|
|
4302
|
+
entry: 0,
|
|
4303
|
+
quantity: 0
|
|
4304
|
+
},
|
|
4305
|
+
global_config: this.global_config,
|
|
4306
|
+
trades,
|
|
4307
|
+
as_state: false
|
|
4308
|
+
});
|
|
4309
|
+
const avg_position = determine_average_entry_and_size([
|
|
4310
|
+
{
|
|
4311
|
+
price: this.position.entry,
|
|
4312
|
+
quantity: this.position.quantity
|
|
4313
|
+
},
|
|
4314
|
+
{
|
|
4315
|
+
price: naive_assumptions.position.entry,
|
|
4316
|
+
quantity: naive_assumptions.position.quantity
|
|
4317
|
+
}
|
|
4318
|
+
], this.global_config.decimal_places, this.global_config.price_places);
|
|
4319
|
+
const new_position = dangerous ? {
|
|
4320
|
+
...this.position,
|
|
4321
|
+
entry: avg_position.entry,
|
|
4322
|
+
quantity: avg_position.quantity
|
|
4323
|
+
} : this.position;
|
|
4324
|
+
return new PositionState({
|
|
4325
|
+
...naive_assumptions,
|
|
4326
|
+
trades: naive_assumptions.trades,
|
|
4327
|
+
position: new_position,
|
|
4328
|
+
global_config: this.global_config
|
|
4329
|
+
});
|
|
4330
|
+
}
|
|
4331
|
+
const _trades = cumulativeHelper({
|
|
4332
|
+
tradesList: [...trades].concat({
|
|
4333
|
+
entry: this.position.entry,
|
|
4334
|
+
quantity: this.position.quantity
|
|
4335
|
+
}),
|
|
4336
|
+
decimal_places: this.global_config.decimal_places
|
|
4337
|
+
}).filter((o) => {
|
|
4338
|
+
const numeric = Number(o.cumulative_size);
|
|
4339
|
+
return numeric > this.position.quantity;
|
|
4340
|
+
});
|
|
4341
|
+
return positionAt({
|
|
4342
|
+
price,
|
|
4343
|
+
position: this.position,
|
|
4344
|
+
trades: _trades,
|
|
4345
|
+
global_config: this.global_config
|
|
4346
|
+
});
|
|
4347
|
+
}
|
|
4348
|
+
initializeEngine({
|
|
4349
|
+
price,
|
|
4350
|
+
dangerous,
|
|
4351
|
+
trades,
|
|
4352
|
+
config
|
|
4353
|
+
}) {
|
|
4354
|
+
if (config) {
|
|
4355
|
+
this.generateShortTrades(config);
|
|
4356
|
+
}
|
|
4357
|
+
return this.positionAt({
|
|
4358
|
+
price,
|
|
4359
|
+
dangerous,
|
|
4360
|
+
trades
|
|
4361
|
+
});
|
|
4362
|
+
}
|
|
4363
|
+
}
|
|
3879
4364
|
export {
|
|
3880
4365
|
to_f,
|
|
3881
4366
|
sortedBuildConfig,
|
|
@@ -3904,6 +4389,7 @@ export {
|
|
|
3904
4389
|
determine_stop_and_size,
|
|
3905
4390
|
determine_remaining_entry,
|
|
3906
4391
|
determine_position_size,
|
|
4392
|
+
determine_pnl,
|
|
3907
4393
|
determine_break_even_price,
|
|
3908
4394
|
determine_average_entry_and_size,
|
|
3909
4395
|
determine_amount_to_sell2 as determine_amount_to_sell,
|
|
@@ -3920,6 +4406,7 @@ export {
|
|
|
3920
4406
|
computeSellZones,
|
|
3921
4407
|
computeRiskReward,
|
|
3922
4408
|
computeProfitDetail,
|
|
4409
|
+
computeMarginProtection,
|
|
3923
4410
|
compoundAPI,
|
|
3924
4411
|
calculateFactorFromTakeProfit,
|
|
3925
4412
|
calculateFactorFromSellQuantity,
|
|
@@ -3928,6 +4415,8 @@ export {
|
|
|
3928
4415
|
buildAppConfig,
|
|
3929
4416
|
asCoins,
|
|
3930
4417
|
allCoins,
|
|
4418
|
+
TradeEngine,
|
|
3931
4419
|
Strategy,
|
|
3932
|
-
SpecialCoins
|
|
4420
|
+
SpecialCoins,
|
|
4421
|
+
Signal
|
|
3933
4422
|
};
|