@gbozee/ultimate 0.0.2-next.8 → 0.0.2-next.80
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 +509 -19
- package/dist/index.cjs +31996 -10734
- package/dist/index.d.ts +4658 -42
- package/dist/index.js +31977 -10729
- package/dist/mcp-client.cjs +63 -42
- package/dist/mcp-client.js +51 -33
- package/dist/mcp-server.cjs +16085 -5518
- package/dist/mcp-server.js +16139 -5586
- 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
|
}
|
|
@@ -112,6 +146,16 @@ function getEntries(params) {
|
|
|
112
146
|
lambda: distribution_params?.lambda
|
|
113
147
|
});
|
|
114
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;
|
|
115
159
|
case "inverse-exponential":
|
|
116
160
|
entries = generateInverseExponential({
|
|
117
161
|
margin_range,
|
|
@@ -121,12 +165,53 @@ function getEntries(params) {
|
|
|
121
165
|
curveFactor: distribution_params?.curveFactor
|
|
122
166
|
});
|
|
123
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;
|
|
124
187
|
default:
|
|
125
188
|
throw new Error(`Unknown distribution type: ${distribution}`);
|
|
126
189
|
}
|
|
127
190
|
return entries.sort((a, b) => a - b);
|
|
128
191
|
}
|
|
129
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
|
+
}
|
|
130
215
|
|
|
131
216
|
// src/helpers/optimizations.ts
|
|
132
217
|
function calculateTheoreticalKelly({
|
|
@@ -500,7 +585,7 @@ class Signal {
|
|
|
500
585
|
const simple_support = Math.min(current_price, stop_loss);
|
|
501
586
|
const simple_resistance = Math.max(current_price, stop_loss);
|
|
502
587
|
const risk_per_trade = risk / this.risk_reward;
|
|
503
|
-
const use_progressive = distribution_params
|
|
588
|
+
const use_progressive = distribution_params?.use_progressive || this.use_progressive_risk;
|
|
504
589
|
const risk_distribution = use_progressive ? {
|
|
505
590
|
enabled: true,
|
|
506
591
|
total_risk_budget: risk,
|
|
@@ -1295,6 +1380,9 @@ function formatPrice(value2, opts = {}) {
|
|
|
1295
1380
|
return formatter.format(value2);
|
|
1296
1381
|
}
|
|
1297
1382
|
function to_f(value2, places = "%.1f") {
|
|
1383
|
+
if (!value2) {
|
|
1384
|
+
return null;
|
|
1385
|
+
}
|
|
1298
1386
|
let v = typeof value2 === "string" ? parseFloat(value2) : value2;
|
|
1299
1387
|
const formattedValue = places.replace("%.", "").replace("f", "");
|
|
1300
1388
|
return parseFloat(v.toFixed(parseInt(formattedValue)));
|
|
@@ -1653,7 +1741,7 @@ function buildConfig(app_config, {
|
|
|
1653
1741
|
min_avg_size = 0,
|
|
1654
1742
|
distribution,
|
|
1655
1743
|
distribution_params,
|
|
1656
|
-
use_progressive_risk
|
|
1744
|
+
use_progressive_risk = false
|
|
1657
1745
|
}) {
|
|
1658
1746
|
let fee = app_config.fee / 100;
|
|
1659
1747
|
let working_risk = risk || app_config.risk_per_trade;
|
|
@@ -1695,7 +1783,7 @@ function buildConfig(app_config, {
|
|
|
1695
1783
|
if (!stop) {
|
|
1696
1784
|
return [];
|
|
1697
1785
|
}
|
|
1698
|
-
const condition =
|
|
1786
|
+
const condition = true;
|
|
1699
1787
|
if (kind === "short") {}
|
|
1700
1788
|
const result = entry === stop ? [] : condition ? instance.build_entry({
|
|
1701
1789
|
current_price: entry,
|
|
@@ -2178,7 +2266,7 @@ function determineOptimumReward(payload) {
|
|
|
2178
2266
|
const criterion = app_config.strategy || "quantity";
|
|
2179
2267
|
const risk_rewards = createArray(low_range, high_range, 1);
|
|
2180
2268
|
let func = risk_rewards.map((trade_no) => {
|
|
2181
|
-
|
|
2269
|
+
const pp = {
|
|
2182
2270
|
take_profit: app_config.take_profit,
|
|
2183
2271
|
entry: app_config.entry,
|
|
2184
2272
|
stop: app_config.stop,
|
|
@@ -2190,7 +2278,8 @@ function determineOptimumReward(payload) {
|
|
|
2190
2278
|
decimal_places: app_config.decimal_places,
|
|
2191
2279
|
distribution,
|
|
2192
2280
|
distribution_params: payload.distribution_params
|
|
2193
|
-
}
|
|
2281
|
+
};
|
|
2282
|
+
let trades = sortedBuildConfig(app_config, pp);
|
|
2194
2283
|
let total = 0;
|
|
2195
2284
|
let max = -Infinity;
|
|
2196
2285
|
let min = Infinity;
|
|
@@ -2281,7 +2370,6 @@ function findIndexByCondition(lst, kind, condition, defaultKey = "neg_pnl") {
|
|
|
2281
2370
|
return b.net_diff - a.net_diff;
|
|
2282
2371
|
}
|
|
2283
2372
|
});
|
|
2284
|
-
console.log("found", sortedFound);
|
|
2285
2373
|
if (defaultKey === "quantity") {
|
|
2286
2374
|
return sortedFound[0].index;
|
|
2287
2375
|
}
|
|
@@ -2912,6 +3000,52 @@ function generateDangerousConfig(payload) {
|
|
|
2912
3000
|
risk_reward: optimumRiskReward
|
|
2913
3001
|
};
|
|
2914
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
|
+
}
|
|
2915
3049
|
// src/helpers/strategy.ts
|
|
2916
3050
|
class Strategy {
|
|
2917
3051
|
position;
|
|
@@ -3486,10 +3620,14 @@ function helperFuncToBuildTrades({
|
|
|
3486
3620
|
symbol_config,
|
|
3487
3621
|
app_config_kind,
|
|
3488
3622
|
appConfig,
|
|
3489
|
-
force_exact_risk = true
|
|
3623
|
+
force_exact_risk = true,
|
|
3624
|
+
use_default = false
|
|
3490
3625
|
}) {
|
|
3491
3626
|
const risk = custom_b_config.risk * (custom_b_config.risk_factor || 1);
|
|
3492
|
-
let result =
|
|
3627
|
+
let result = use_default ? {
|
|
3628
|
+
risk,
|
|
3629
|
+
risk_reward: custom_b_config.risk_reward
|
|
3630
|
+
} : getRiskReward({
|
|
3493
3631
|
entry: custom_b_config.entry,
|
|
3494
3632
|
stop: custom_b_config.stop,
|
|
3495
3633
|
risk,
|
|
@@ -3538,7 +3676,8 @@ function buildWithOptimumReward({
|
|
|
3538
3676
|
config,
|
|
3539
3677
|
settings,
|
|
3540
3678
|
global_config,
|
|
3541
|
-
force_exact
|
|
3679
|
+
force_exact,
|
|
3680
|
+
use_default = false
|
|
3542
3681
|
}) {
|
|
3543
3682
|
const kind = config.entry > config.stop ? "long" : "short";
|
|
3544
3683
|
let stop = settings.stop;
|
|
@@ -3552,7 +3691,8 @@ function buildWithOptimumReward({
|
|
|
3552
3691
|
stop,
|
|
3553
3692
|
risk,
|
|
3554
3693
|
distribution,
|
|
3555
|
-
distribution_params
|
|
3694
|
+
distribution_params,
|
|
3695
|
+
risk_reward: settings.risk_reward || config?.risk_reward
|
|
3556
3696
|
};
|
|
3557
3697
|
const appConfig = constructAppConfig2({
|
|
3558
3698
|
config,
|
|
@@ -3563,7 +3703,8 @@ function buildWithOptimumReward({
|
|
|
3563
3703
|
app_config_kind: kind,
|
|
3564
3704
|
appConfig,
|
|
3565
3705
|
symbol_config: global_config,
|
|
3566
|
-
force_exact_risk: force_exact
|
|
3706
|
+
force_exact_risk: force_exact,
|
|
3707
|
+
use_default
|
|
3567
3708
|
});
|
|
3568
3709
|
const adjusted_size = summary.quantity;
|
|
3569
3710
|
const symbol_config = global_config;
|
|
@@ -3875,6 +4016,351 @@ var compoundAPI = {
|
|
|
3875
4016
|
increaseTradeHelper,
|
|
3876
4017
|
generatePositionIncreaseTrade
|
|
3877
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
|
+
}
|
|
3878
4364
|
export {
|
|
3879
4365
|
to_f,
|
|
3880
4366
|
sortedBuildConfig,
|
|
@@ -3903,6 +4389,7 @@ export {
|
|
|
3903
4389
|
determine_stop_and_size,
|
|
3904
4390
|
determine_remaining_entry,
|
|
3905
4391
|
determine_position_size,
|
|
4392
|
+
determine_pnl,
|
|
3906
4393
|
determine_break_even_price,
|
|
3907
4394
|
determine_average_entry_and_size,
|
|
3908
4395
|
determine_amount_to_sell2 as determine_amount_to_sell,
|
|
@@ -3919,6 +4406,7 @@ export {
|
|
|
3919
4406
|
computeSellZones,
|
|
3920
4407
|
computeRiskReward,
|
|
3921
4408
|
computeProfitDetail,
|
|
4409
|
+
computeMarginProtection,
|
|
3922
4410
|
compoundAPI,
|
|
3923
4411
|
calculateFactorFromTakeProfit,
|
|
3924
4412
|
calculateFactorFromSellQuantity,
|
|
@@ -3927,6 +4415,8 @@ export {
|
|
|
3927
4415
|
buildAppConfig,
|
|
3928
4416
|
asCoins,
|
|
3929
4417
|
allCoins,
|
|
4418
|
+
TradeEngine,
|
|
3930
4419
|
Strategy,
|
|
3931
|
-
SpecialCoins
|
|
4420
|
+
SpecialCoins,
|
|
4421
|
+
Signal
|
|
3932
4422
|
};
|