@gbozee/ultimate 0.0.2-45 → 0.0.2-46
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/frontend-index.d.ts +73 -9
- package/dist/frontend-index.js +508 -316
- package/package.json +1 -1
package/dist/frontend-index.d.ts
CHANGED
|
@@ -1,5 +1,78 @@
|
|
|
1
1
|
// Generated by dts-bundle-generator v9.5.1
|
|
2
2
|
|
|
3
|
+
export declare function profitHelper(longPosition: any, shortPosition: any, config?: any, contract_size?: number, balance?: number): {
|
|
4
|
+
long: any;
|
|
5
|
+
short: any;
|
|
6
|
+
};
|
|
7
|
+
export declare function getParamForField(self: any, configs: any[], field: string, isGroup?: string): any | any[];
|
|
8
|
+
export declare function getTradeEntries(entry: number, min_size: number, kind: "long" | "short", size: number, spread?: number): {
|
|
9
|
+
entry: number;
|
|
10
|
+
size: number;
|
|
11
|
+
}[];
|
|
12
|
+
export declare function extractValue(_param: any, condition: boolean): any;
|
|
13
|
+
export declare function asCoins(symbol: string): string;
|
|
14
|
+
export declare const SpecialCoins: string[];
|
|
15
|
+
export declare function allCoins(symbols: string[]): string[];
|
|
16
|
+
export declare function formatPrice(value: number, opts?: {
|
|
17
|
+
locale?: string;
|
|
18
|
+
currency?: string;
|
|
19
|
+
}): string;
|
|
20
|
+
export declare function to_f(value: string | number, places?: string): number;
|
|
21
|
+
export declare function determine_stop_and_size(entry: number, pnl: number, take_profit: number, kind?: string): number;
|
|
22
|
+
export declare const range: (start: number, stop: number, step?: number) => number[];
|
|
23
|
+
export declare function determine_amount_to_sell(entry: number, quantity: number, sell_price: number, pnl: number, kind: "long" | "short", places?: string): number;
|
|
24
|
+
export declare function determine_position_size({ entry, stop, budget, percent, min_size, notional_value, as_coin, places, }: {
|
|
25
|
+
entry: number;
|
|
26
|
+
stop?: number;
|
|
27
|
+
budget?: number;
|
|
28
|
+
percent?: number;
|
|
29
|
+
notional_value?: number;
|
|
30
|
+
min_size?: number;
|
|
31
|
+
as_coin?: boolean;
|
|
32
|
+
places?: string;
|
|
33
|
+
}): number;
|
|
34
|
+
export declare function determine_remaining_entry({ risk, max_size, stop_loss, kind, position, }: {
|
|
35
|
+
risk: number;
|
|
36
|
+
max_size: number;
|
|
37
|
+
stop_loss: number;
|
|
38
|
+
kind: string;
|
|
39
|
+
position: {
|
|
40
|
+
quantity: number;
|
|
41
|
+
entry: number;
|
|
42
|
+
};
|
|
43
|
+
}): number;
|
|
44
|
+
export declare function determine_average_entry_and_size(orders: Array<{
|
|
45
|
+
price: number;
|
|
46
|
+
quantity: number;
|
|
47
|
+
}>, places?: string, price_places?: string): {
|
|
48
|
+
entry: number;
|
|
49
|
+
price: number;
|
|
50
|
+
quantity: number;
|
|
51
|
+
};
|
|
52
|
+
export declare const createArray: (start: number, stop: number, step: number) => number[];
|
|
53
|
+
export declare const groupBy: (xs: any[], key: string) => any;
|
|
54
|
+
export declare function fibonacci_analysis({ support, resistance, kind, trend, places, }: {
|
|
55
|
+
support: number;
|
|
56
|
+
resistance: number;
|
|
57
|
+
kind?: string;
|
|
58
|
+
trend?: string;
|
|
59
|
+
places?: string;
|
|
60
|
+
}): number[];
|
|
61
|
+
export declare const groupIntoPairs: (arr: any[], size: number) => any[][];
|
|
62
|
+
export declare const groupIntoPairsWithSumLessThan: (arr: any[], targetSum: number, key?: string, firstSize?: number) => any[][];
|
|
63
|
+
/**
|
|
64
|
+
* This function computes the cummulative entry and size for each trade
|
|
65
|
+
* @param trades The trades for which the cummulative entry and size will be computed
|
|
66
|
+
* @param config The current config
|
|
67
|
+
* @returns An array of trades with the cummulative entry and size
|
|
68
|
+
*/
|
|
69
|
+
export declare const computeTotalAverageForEachTrade: (trades: any[], config: any) => any[];
|
|
70
|
+
export declare function getDecimalPlaces(numberString: string | number): number;
|
|
71
|
+
export declare function createGapPairs<T>(arr: T[], gap: number, item?: T): [
|
|
72
|
+
T,
|
|
73
|
+
T
|
|
74
|
+
][];
|
|
75
|
+
export declare function logWithLineNumber(...args: any[]): void;
|
|
3
76
|
export type SignalConfigType = {
|
|
4
77
|
focus: number;
|
|
5
78
|
budget: number;
|
|
@@ -149,15 +222,6 @@ export type GlobalConfig = {
|
|
|
149
222
|
risk_reward: number;
|
|
150
223
|
reverse_factor: number;
|
|
151
224
|
};
|
|
152
|
-
export declare function determine_average_entry_and_size(orders: Array<{
|
|
153
|
-
price: number;
|
|
154
|
-
quantity: number;
|
|
155
|
-
}>, places?: string, price_places?: string): {
|
|
156
|
-
entry: number;
|
|
157
|
-
price: number;
|
|
158
|
-
quantity: number;
|
|
159
|
-
};
|
|
160
|
-
export declare const createArray: (start: number, stop: number, step: number) => number[];
|
|
161
225
|
export type AppConfig = {
|
|
162
226
|
fee: number;
|
|
163
227
|
risk_per_trade: number;
|
package/dist/frontend-index.js
CHANGED
|
@@ -1,261 +1,5 @@
|
|
|
1
|
-
// src/helpers/pnl.ts
|
|
2
|
-
function determine_position_size(entry, stop, budget) {
|
|
3
|
-
let stop_percent = Math.abs(entry - stop) / entry;
|
|
4
|
-
let size = budget / stop_percent / entry;
|
|
5
|
-
return size;
|
|
6
|
-
}
|
|
7
|
-
function determine_risk(entry, stop, quantity) {
|
|
8
|
-
let stop_percent = Math.abs(entry - stop) / entry;
|
|
9
|
-
let risk = quantity * stop_percent * entry;
|
|
10
|
-
return risk;
|
|
11
|
-
}
|
|
12
|
-
function determine_close_price(entry, pnl, quantity, kind, single = false, leverage = 1) {
|
|
13
|
-
const dollar_value = entry / leverage;
|
|
14
|
-
const position = dollar_value * quantity;
|
|
15
|
-
if (position) {
|
|
16
|
-
let percent = pnl / position;
|
|
17
|
-
let difference = position * percent / quantity;
|
|
18
|
-
let result;
|
|
19
|
-
if (kind === "long") {
|
|
20
|
-
result = difference + entry;
|
|
21
|
-
} else {
|
|
22
|
-
result = entry - difference;
|
|
23
|
-
}
|
|
24
|
-
if (single) {
|
|
25
|
-
return result;
|
|
26
|
-
}
|
|
27
|
-
return result;
|
|
28
|
-
}
|
|
29
|
-
return 0;
|
|
30
|
-
}
|
|
31
|
-
function determine_amount_to_sell(entry, quantity, sell_price, pnl, kind, places = "%.3f") {
|
|
32
|
-
const _pnl = determine_pnl(entry, sell_price, quantity, kind);
|
|
33
|
-
const ratio = pnl / to_f(Math.abs(_pnl), places);
|
|
34
|
-
quantity = quantity * ratio;
|
|
35
|
-
return to_f(quantity, places);
|
|
36
|
-
}
|
|
37
|
-
function determine_pnl(entry, close_price, quantity, kind, contract_size, places = "%.2f") {
|
|
38
|
-
if (contract_size) {
|
|
39
|
-
const direction = kind === "long" ? 1 : -1;
|
|
40
|
-
return quantity * contract_size * direction * (1 / entry - 1 / close_price);
|
|
41
|
-
}
|
|
42
|
-
let difference = entry - close_price;
|
|
43
|
-
if (kind === "long") {
|
|
44
|
-
difference = close_price - entry;
|
|
45
|
-
}
|
|
46
|
-
return to_f(difference * quantity, places);
|
|
47
|
-
}
|
|
48
|
-
function position(entry, quantity, kind, leverage = 1) {
|
|
49
|
-
const direction = { long: 1, short: -1 };
|
|
50
|
-
return parseFloat((direction[kind] * quantity * (entry / leverage)).toFixed(3));
|
|
51
|
-
}
|
|
52
|
-
function to_f(value, places) {
|
|
53
|
-
if (value) {
|
|
54
|
-
let pp = parseInt(places.replace("%.", "").replace("f", ""));
|
|
55
|
-
return parseFloat(value.toFixed(pp));
|
|
56
|
-
}
|
|
57
|
-
return value;
|
|
58
|
-
}
|
|
59
|
-
var value = {
|
|
60
|
-
determine_risk,
|
|
61
|
-
determine_position_size,
|
|
62
|
-
determine_close_price,
|
|
63
|
-
determine_pnl,
|
|
64
|
-
position,
|
|
65
|
-
determine_amount_to_sell,
|
|
66
|
-
to_f
|
|
67
|
-
};
|
|
68
|
-
var pnl_default = value;
|
|
69
|
-
|
|
70
|
-
// src/helpers/trade_utils.ts
|
|
71
|
-
function to_f2(value2, places = "%.1f") {
|
|
72
|
-
let v = typeof value2 === "string" ? parseFloat(value2) : value2;
|
|
73
|
-
const formattedValue = places.replace("%.", "").replace("f", "");
|
|
74
|
-
return parseFloat(v.toFixed(parseInt(formattedValue)));
|
|
75
|
-
}
|
|
76
|
-
function determine_position_size2({
|
|
77
|
-
entry,
|
|
78
|
-
stop,
|
|
79
|
-
budget,
|
|
80
|
-
percent,
|
|
81
|
-
min_size,
|
|
82
|
-
notional_value,
|
|
83
|
-
as_coin = true,
|
|
84
|
-
places = "%.3f"
|
|
85
|
-
}) {
|
|
86
|
-
let stop_percent = stop ? Math.abs(entry - stop) / entry : percent;
|
|
87
|
-
if (stop_percent && budget) {
|
|
88
|
-
let size = budget / stop_percent;
|
|
89
|
-
let notion_value = size * entry;
|
|
90
|
-
if (notional_value && notional_value > notion_value) {
|
|
91
|
-
size = notional_value / entry;
|
|
92
|
-
}
|
|
93
|
-
if (as_coin) {
|
|
94
|
-
size = size / entry;
|
|
95
|
-
if (min_size && min_size === 1) {
|
|
96
|
-
return to_f2(Math.round(size), places);
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
return to_f2(size, places);
|
|
100
|
-
}
|
|
101
|
-
return;
|
|
102
|
-
}
|
|
103
|
-
function determine_average_entry_and_size(orders, places = "%.3f", price_places = "%.1f") {
|
|
104
|
-
const sum_values = orders.reduce((sum, order) => sum + order.price * order.quantity, 0);
|
|
105
|
-
const total_quantity = orders.reduce((sum, order) => sum + order.quantity, 0);
|
|
106
|
-
const avg_price = total_quantity ? to_f2(sum_values / total_quantity, price_places) : 0;
|
|
107
|
-
return {
|
|
108
|
-
entry: avg_price,
|
|
109
|
-
price: avg_price,
|
|
110
|
-
quantity: to_f2(total_quantity, places)
|
|
111
|
-
};
|
|
112
|
-
}
|
|
113
|
-
var createArray = (start, stop, step) => {
|
|
114
|
-
const result = [];
|
|
115
|
-
let current = start;
|
|
116
|
-
while (current <= stop) {
|
|
117
|
-
result.push(current);
|
|
118
|
-
current += step;
|
|
119
|
-
}
|
|
120
|
-
return result;
|
|
121
|
-
};
|
|
122
|
-
var groupIntoPairsWithSumLessThan = (arr, targetSum, key = "quantity", firstSize = 0) => {
|
|
123
|
-
if (firstSize) {
|
|
124
|
-
const totalSize = arr.reduce((sum, order) => sum + order[key], 0);
|
|
125
|
-
const remainingSize = totalSize - firstSize;
|
|
126
|
-
let newSum = 0;
|
|
127
|
-
let newArray = [];
|
|
128
|
-
let lastIndex = 0;
|
|
129
|
-
for (let i = 0;i < arr.length; i++) {
|
|
130
|
-
if (newSum < remainingSize) {
|
|
131
|
-
newSum += arr[i][key];
|
|
132
|
-
newArray.push(arr[i]);
|
|
133
|
-
lastIndex = i;
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
const lastGroup = arr.slice(lastIndex + 1);
|
|
137
|
-
const previousPair = groupInPairs(newArray, key, targetSum);
|
|
138
|
-
if (lastGroup.length > 0) {
|
|
139
|
-
previousPair.push(lastGroup);
|
|
140
|
-
}
|
|
141
|
-
return previousPair;
|
|
142
|
-
}
|
|
143
|
-
return groupInPairs(arr, key, targetSum);
|
|
144
|
-
};
|
|
145
|
-
function groupInPairs(_arr, key, targetSum) {
|
|
146
|
-
const result = [];
|
|
147
|
-
let currentSum = 0;
|
|
148
|
-
let currentGroup = [];
|
|
149
|
-
for (let i = 0;i < _arr.length; i++) {
|
|
150
|
-
currentSum += _arr[i][key];
|
|
151
|
-
currentGroup.push(_arr[i]);
|
|
152
|
-
if (currentSum >= targetSum) {
|
|
153
|
-
result.push(currentGroup);
|
|
154
|
-
currentGroup = [];
|
|
155
|
-
currentSum = 0;
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
return result;
|
|
159
|
-
}
|
|
160
|
-
var computeTotalAverageForEachTrade = (trades, config) => {
|
|
161
|
-
let _take_profit = config.take_profit;
|
|
162
|
-
let kind = config.kind;
|
|
163
|
-
let entryToUse = kind === "short" ? Math.min(config.entry, config.stop) : Math.max(config.entry, config.stop);
|
|
164
|
-
let _currentEntry = config.currentEntry || entryToUse;
|
|
165
|
-
let less = trades.filter((p) => kind === "long" ? p.entry <= _currentEntry : p.entry >= _currentEntry);
|
|
166
|
-
let rrr = trades.map((r, i) => {
|
|
167
|
-
let considered = [];
|
|
168
|
-
if (kind === "long") {
|
|
169
|
-
considered = trades.filter((p) => p.entry > _currentEntry);
|
|
170
|
-
} else {
|
|
171
|
-
considered = trades.filter((p) => p.entry < _currentEntry);
|
|
172
|
-
}
|
|
173
|
-
const x_pnl = 0;
|
|
174
|
-
const remaining = less.filter((o) => {
|
|
175
|
-
if (kind === "long") {
|
|
176
|
-
return o.entry >= r.entry;
|
|
177
|
-
}
|
|
178
|
-
return o.entry <= r.entry;
|
|
179
|
-
});
|
|
180
|
-
if (remaining.length === 0) {
|
|
181
|
-
return { ...r, pnl: x_pnl };
|
|
182
|
-
}
|
|
183
|
-
const start = kind === "long" ? Math.max(...remaining.map((o) => o.entry)) : Math.min(...remaining.map((o) => o.entry));
|
|
184
|
-
considered = considered.map((o) => ({ ...o, entry: start }));
|
|
185
|
-
considered = considered.concat(remaining);
|
|
186
|
-
let avg_entry = determine_average_entry_and_size([
|
|
187
|
-
...considered.map((o) => ({
|
|
188
|
-
price: o.entry,
|
|
189
|
-
quantity: o.quantity
|
|
190
|
-
})),
|
|
191
|
-
{
|
|
192
|
-
price: _currentEntry,
|
|
193
|
-
quantity: config.currentQty || 0
|
|
194
|
-
}
|
|
195
|
-
], config.decimal_places, config.price_places);
|
|
196
|
-
let _pnl = r.pnl;
|
|
197
|
-
let sell_price = r.sell_price;
|
|
198
|
-
let entry_pnl = r.pnl;
|
|
199
|
-
if (_take_profit) {
|
|
200
|
-
_pnl = pnl_default.determine_pnl(avg_entry.price, _take_profit, avg_entry.quantity, kind);
|
|
201
|
-
sell_price = _take_profit;
|
|
202
|
-
entry_pnl = pnl_default.determine_pnl(r.entry, _take_profit, avg_entry.quantity, kind);
|
|
203
|
-
}
|
|
204
|
-
const loss = pnl_default.determine_pnl(avg_entry.price, r.stop, avg_entry.quantity, kind);
|
|
205
|
-
let new_stop = r.new_stop;
|
|
206
|
-
const entry_loss = pnl_default.determine_pnl(r.entry, new_stop, avg_entry.quantity, kind);
|
|
207
|
-
let min_profit = 0;
|
|
208
|
-
let min_entry_profit = 0;
|
|
209
|
-
if (config.min_profit) {
|
|
210
|
-
min_profit = pnl_default.determine_close_price(avg_entry.price, config.min_profit, avg_entry.quantity, kind);
|
|
211
|
-
min_entry_profit = pnl_default.determine_close_price(r.entry, config.min_profit, avg_entry.quantity, kind);
|
|
212
|
-
}
|
|
213
|
-
let x_fee = r.fee;
|
|
214
|
-
if (config.fee) {
|
|
215
|
-
x_fee = config.fee * r.stop * avg_entry.quantity;
|
|
216
|
-
}
|
|
217
|
-
let tp_close = pnl_default.determine_close_price(r.entry, Math.abs(entry_loss) * (config.rr || 1) + x_fee, avg_entry.quantity, kind);
|
|
218
|
-
return {
|
|
219
|
-
...r,
|
|
220
|
-
x_fee: to_f2(x_fee, "%.2f"),
|
|
221
|
-
avg_entry: avg_entry.price,
|
|
222
|
-
avg_size: avg_entry.quantity,
|
|
223
|
-
entry_pnl: to_f2(entry_pnl, "%.2f"),
|
|
224
|
-
entry_loss: to_f2(entry_loss, "%.2f"),
|
|
225
|
-
min_entry_pnl: to_f2(min_entry_profit, "%.2f"),
|
|
226
|
-
pnl: _pnl,
|
|
227
|
-
neg_pnl: to_f2(loss, "%.2f"),
|
|
228
|
-
sell_price,
|
|
229
|
-
close_p: to_f2(tp_close, "%.2f"),
|
|
230
|
-
min_pnl: to_f2(min_profit, "%.2f"),
|
|
231
|
-
new_stop
|
|
232
|
-
};
|
|
233
|
-
});
|
|
234
|
-
return rrr;
|
|
235
|
-
};
|
|
236
|
-
function createGapPairs(arr, gap, item) {
|
|
237
|
-
if (arr.length === 0) {
|
|
238
|
-
return [];
|
|
239
|
-
}
|
|
240
|
-
const result = [];
|
|
241
|
-
const firstElement = arr[0];
|
|
242
|
-
for (let i = arr.length - 1;i >= 0; i--) {
|
|
243
|
-
const current = arr[i];
|
|
244
|
-
const gapIndex = i - gap;
|
|
245
|
-
const pairedElement = gapIndex < 0 ? firstElement : arr[gapIndex];
|
|
246
|
-
if (current !== pairedElement) {
|
|
247
|
-
result.push([current, pairedElement]);
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
if (item) {
|
|
251
|
-
let r = result.find((o) => o[0] === item);
|
|
252
|
-
return r ? [r] : [];
|
|
253
|
-
}
|
|
254
|
-
return result;
|
|
255
|
-
}
|
|
256
|
-
|
|
257
1
|
// src/helpers/trade_signal.ts
|
|
258
|
-
function
|
|
2
|
+
function determine_close_price({
|
|
259
3
|
entry,
|
|
260
4
|
pnl,
|
|
261
5
|
quantity,
|
|
@@ -263,16 +7,16 @@ function determine_close_price2({
|
|
|
263
7
|
kind = "long"
|
|
264
8
|
}) {
|
|
265
9
|
const dollar_value = entry / leverage;
|
|
266
|
-
const
|
|
267
|
-
if (
|
|
268
|
-
const percent = pnl /
|
|
269
|
-
const difference =
|
|
10
|
+
const position = dollar_value * quantity;
|
|
11
|
+
if (position) {
|
|
12
|
+
const percent = pnl / position;
|
|
13
|
+
const difference = position * percent / quantity;
|
|
270
14
|
const result = kind === "long" ? difference + entry : entry - difference;
|
|
271
15
|
return result;
|
|
272
16
|
}
|
|
273
17
|
return 0;
|
|
274
18
|
}
|
|
275
|
-
function
|
|
19
|
+
function determine_pnl(entry, close_price, quantity, kind = "long", contract_size) {
|
|
276
20
|
if (contract_size) {
|
|
277
21
|
const direction = kind === "long" ? 1 : -1;
|
|
278
22
|
return quantity * contract_size * direction * (1 / entry - 1 / close_price);
|
|
@@ -291,7 +35,7 @@ function* _get_zones({
|
|
|
291
35
|
let focus_low = last * Math.pow(1 + percent_change, -1);
|
|
292
36
|
if (focus_high > current_price) {
|
|
293
37
|
while (focus_high > current_price) {
|
|
294
|
-
yield
|
|
38
|
+
yield to_f(last, places);
|
|
295
39
|
focus_high = last;
|
|
296
40
|
last = focus_high * Math.pow(1 + percent_change, -1);
|
|
297
41
|
focus_low = last * Math.pow(1 + percent_change, -1);
|
|
@@ -299,14 +43,14 @@ function* _get_zones({
|
|
|
299
43
|
} else {
|
|
300
44
|
if (focus_high <= current_price) {
|
|
301
45
|
while (focus_high <= current_price) {
|
|
302
|
-
yield
|
|
46
|
+
yield to_f(focus_high, places);
|
|
303
47
|
focus_low = focus_high;
|
|
304
48
|
last = focus_low * (1 + percent_change);
|
|
305
49
|
focus_high = last * (1 + percent_change);
|
|
306
50
|
}
|
|
307
51
|
} else {
|
|
308
52
|
while (focus_low <= current_price) {
|
|
309
|
-
yield
|
|
53
|
+
yield to_f(focus_high, places);
|
|
310
54
|
focus_low = focus_high;
|
|
311
55
|
last = focus_low * (1 + percent_change);
|
|
312
56
|
focus_high = last * (1 + percent_change);
|
|
@@ -570,7 +314,7 @@ class Signal {
|
|
|
570
314
|
} else {
|
|
571
315
|
i["risk_sell"] = Math.min(...potentials.slice(0, max_index));
|
|
572
316
|
}
|
|
573
|
-
i["pnl"] = this.to_df(
|
|
317
|
+
i["pnl"] = this.to_df(determine_pnl(i["entry"], i["risk_sell"], i["quantity"], kind));
|
|
574
318
|
}
|
|
575
319
|
}
|
|
576
320
|
}
|
|
@@ -634,13 +378,13 @@ class Signal {
|
|
|
634
378
|
let remaining_zones = margin_zones.filter((x) => JSON.stringify(x) != JSON.stringify(margin_range));
|
|
635
379
|
if (margin_range) {
|
|
636
380
|
const difference = Math.abs(margin_range[0] - margin_range[1]);
|
|
637
|
-
const spread =
|
|
381
|
+
const spread = to_f(difference / this.risk_reward, this.price_places);
|
|
638
382
|
let entries;
|
|
639
383
|
const percent_change = this.percent_change / this.risk_reward;
|
|
640
384
|
if (kind === "long") {
|
|
641
|
-
entries = Array.from({ length: Math.floor(this.risk_reward) + 1 }, (_, x) =>
|
|
385
|
+
entries = Array.from({ length: Math.floor(this.risk_reward) + 1 }, (_, x) => to_f(margin_range[1] - spread * x, this.price_places));
|
|
642
386
|
} else {
|
|
643
|
-
entries = Array.from({ length: Math.floor(this.risk_reward) + 1 }, (_, x) =>
|
|
387
|
+
entries = Array.from({ length: Math.floor(this.risk_reward) + 1 }, (_, x) => to_f(margin_range[1] * Math.pow(1 + percent_change, x), this.price_places));
|
|
644
388
|
}
|
|
645
389
|
if (Math.min(...entries) < this.to_f(current_price) && this.to_f(current_price) < Math.max(...entries)) {
|
|
646
390
|
return entries.sort((a, b) => a - b);
|
|
@@ -652,14 +396,14 @@ class Signal {
|
|
|
652
396
|
if (new_range) {
|
|
653
397
|
while (entries2.length < this.risk_reward + 1) {
|
|
654
398
|
if (kind === "long") {
|
|
655
|
-
let
|
|
656
|
-
if (
|
|
657
|
-
entries2.push(
|
|
399
|
+
let value = this.to_f(new_range[1] - spread * x);
|
|
400
|
+
if (value <= current_price) {
|
|
401
|
+
entries2.push(value);
|
|
658
402
|
}
|
|
659
403
|
} else {
|
|
660
|
-
let
|
|
661
|
-
if (
|
|
662
|
-
entries2.push(
|
|
404
|
+
let value = this.to_f(new_range[1] * Math.pow(1 + percent_change, x));
|
|
405
|
+
if (value >= current_price) {
|
|
406
|
+
entries2.push(value);
|
|
663
407
|
}
|
|
664
408
|
}
|
|
665
409
|
x += 1;
|
|
@@ -673,14 +417,14 @@ class Signal {
|
|
|
673
417
|
let x = 0;
|
|
674
418
|
while (entries2.length < this.risk_reward + 1) {
|
|
675
419
|
if (kind === "long") {
|
|
676
|
-
let
|
|
677
|
-
if (
|
|
678
|
-
entries2.push(
|
|
420
|
+
let value = this.to_f(next_focus - spread * x);
|
|
421
|
+
if (value <= this.to_f(current_price)) {
|
|
422
|
+
entries2.push(value);
|
|
679
423
|
}
|
|
680
424
|
} else {
|
|
681
|
-
let
|
|
682
|
-
if (
|
|
683
|
-
entries2.push(
|
|
425
|
+
let value = this.to_f(next_focus * Math.pow(1 + percent_change, x));
|
|
426
|
+
if (value >= this.to_f(current_price)) {
|
|
427
|
+
entries2.push(value);
|
|
684
428
|
}
|
|
685
429
|
}
|
|
686
430
|
x += 1;
|
|
@@ -691,8 +435,8 @@ class Signal {
|
|
|
691
435
|
}
|
|
692
436
|
return [];
|
|
693
437
|
}
|
|
694
|
-
to_f(
|
|
695
|
-
return
|
|
438
|
+
to_f(value, places) {
|
|
439
|
+
return to_f(value, places || this.price_places);
|
|
696
440
|
}
|
|
697
441
|
get_margin_zones({
|
|
698
442
|
current_price,
|
|
@@ -894,7 +638,7 @@ class Signal {
|
|
|
894
638
|
}) {
|
|
895
639
|
const considered = arr.map((x, i) => i).filter((i) => i > index);
|
|
896
640
|
const with_quantity = considered.map((x) => {
|
|
897
|
-
const q =
|
|
641
|
+
const q = determine_position_size({
|
|
898
642
|
entry: arr[x],
|
|
899
643
|
stop: arr[x - 1],
|
|
900
644
|
budget: risk,
|
|
@@ -928,7 +672,7 @@ class Signal {
|
|
|
928
672
|
const incurred_fees = fees.reduce((a, b) => a + b, 0) + previous_risks.reduce((a, b) => a + b, 0);
|
|
929
673
|
if (index === 0) {
|
|
930
674
|
}
|
|
931
|
-
let quantity =
|
|
675
|
+
let quantity = determine_position_size({
|
|
932
676
|
entry,
|
|
933
677
|
stop,
|
|
934
678
|
budget: risk,
|
|
@@ -940,7 +684,7 @@ class Signal {
|
|
|
940
684
|
}
|
|
941
685
|
if (this.increase_size) {
|
|
942
686
|
quantity = quantity * multiplier;
|
|
943
|
-
const new_risk =
|
|
687
|
+
const new_risk = determine_pnl(entry, stop, quantity, kind);
|
|
944
688
|
risk = Math.abs(new_risk);
|
|
945
689
|
}
|
|
946
690
|
const fee = this.to_df(this.fee * quantity * entry);
|
|
@@ -949,12 +693,12 @@ class Signal {
|
|
|
949
693
|
if (this.minimum_pnl) {
|
|
950
694
|
pnl = this.minimum_pnl + fee;
|
|
951
695
|
}
|
|
952
|
-
let sell_price =
|
|
696
|
+
let sell_price = determine_close_price({ entry, pnl, quantity, kind });
|
|
953
697
|
if (take_profit && !this.minimum_pnl) {
|
|
954
698
|
sell_price = take_profit;
|
|
955
|
-
pnl = this.to_df(
|
|
699
|
+
pnl = this.to_df(determine_pnl(entry, sell_price, quantity, kind));
|
|
956
700
|
pnl = pnl + fee;
|
|
957
|
-
sell_price =
|
|
701
|
+
sell_price = determine_close_price({ entry, pnl, quantity, kind });
|
|
958
702
|
}
|
|
959
703
|
let risk_sell = sell_price;
|
|
960
704
|
return {
|
|
@@ -972,29 +716,455 @@ class Signal {
|
|
|
972
716
|
};
|
|
973
717
|
}
|
|
974
718
|
to_df(currentPrice, places = "%.3f") {
|
|
975
|
-
return
|
|
719
|
+
return to_f(currentPrice, places);
|
|
976
720
|
}
|
|
977
721
|
}
|
|
978
722
|
|
|
979
|
-
// src/helpers/
|
|
980
|
-
function
|
|
981
|
-
|
|
982
|
-
entry
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
723
|
+
// src/helpers/pnl.ts
|
|
724
|
+
function determine_position_size2(entry, stop, budget) {
|
|
725
|
+
let stop_percent = Math.abs(entry - stop) / entry;
|
|
726
|
+
let size = budget / stop_percent / entry;
|
|
727
|
+
return size;
|
|
728
|
+
}
|
|
729
|
+
function determine_risk(entry, stop, quantity) {
|
|
730
|
+
let stop_percent = Math.abs(entry - stop) / entry;
|
|
731
|
+
let risk = quantity * stop_percent * entry;
|
|
732
|
+
return risk;
|
|
733
|
+
}
|
|
734
|
+
function determine_close_price2(entry, pnl, quantity, kind, single = false, leverage = 1) {
|
|
735
|
+
const dollar_value = entry / leverage;
|
|
736
|
+
const position = dollar_value * quantity;
|
|
737
|
+
if (position) {
|
|
738
|
+
let percent = pnl / position;
|
|
739
|
+
let difference = position * percent / quantity;
|
|
740
|
+
let result;
|
|
741
|
+
if (kind === "long") {
|
|
742
|
+
result = difference + entry;
|
|
743
|
+
} else {
|
|
744
|
+
result = entry - difference;
|
|
745
|
+
}
|
|
746
|
+
if (single) {
|
|
747
|
+
return result;
|
|
748
|
+
}
|
|
749
|
+
return result;
|
|
750
|
+
}
|
|
751
|
+
return 0;
|
|
752
|
+
}
|
|
753
|
+
function determine_amount_to_sell(entry, quantity, sell_price, pnl, kind, places = "%.3f") {
|
|
754
|
+
const _pnl = determine_pnl2(entry, sell_price, quantity, kind);
|
|
755
|
+
const ratio = pnl / to_f2(Math.abs(_pnl), places);
|
|
756
|
+
quantity = quantity * ratio;
|
|
757
|
+
return to_f2(quantity, places);
|
|
758
|
+
}
|
|
759
|
+
function determine_pnl2(entry, close_price, quantity, kind, contract_size, places = "%.2f") {
|
|
760
|
+
if (contract_size) {
|
|
761
|
+
const direction = kind === "long" ? 1 : -1;
|
|
762
|
+
return quantity * contract_size * direction * (1 / entry - 1 / close_price);
|
|
763
|
+
}
|
|
764
|
+
let difference = entry - close_price;
|
|
765
|
+
if (kind === "long") {
|
|
766
|
+
difference = close_price - entry;
|
|
767
|
+
}
|
|
768
|
+
return to_f2(difference * quantity, places);
|
|
769
|
+
}
|
|
770
|
+
function position(entry, quantity, kind, leverage = 1) {
|
|
771
|
+
const direction = { long: 1, short: -1 };
|
|
772
|
+
return parseFloat((direction[kind] * quantity * (entry / leverage)).toFixed(3));
|
|
773
|
+
}
|
|
774
|
+
function to_f2(value, places) {
|
|
775
|
+
if (value) {
|
|
776
|
+
let pp = parseInt(places.replace("%.", "").replace("f", ""));
|
|
777
|
+
return parseFloat(value.toFixed(pp));
|
|
778
|
+
}
|
|
779
|
+
return value;
|
|
780
|
+
}
|
|
781
|
+
var value = {
|
|
782
|
+
determine_risk,
|
|
783
|
+
determine_position_size: determine_position_size2,
|
|
784
|
+
determine_close_price: determine_close_price2,
|
|
785
|
+
determine_pnl: determine_pnl2,
|
|
786
|
+
position,
|
|
787
|
+
determine_amount_to_sell,
|
|
788
|
+
to_f: to_f2
|
|
789
|
+
};
|
|
790
|
+
var pnl_default = value;
|
|
791
|
+
|
|
792
|
+
// src/helpers/trade_utils.ts
|
|
793
|
+
function profitHelper(longPosition, shortPosition, config, contract_size, balance = 0) {
|
|
794
|
+
let long = { takeProfit: 0, quantity: 0, pnl: 0 };
|
|
795
|
+
let short = { takeProfit: 0, quantity: 0, pnl: 0 };
|
|
796
|
+
if (longPosition) {
|
|
797
|
+
long = config?.getSize2(longPosition, contract_size, balance) || null;
|
|
798
|
+
}
|
|
799
|
+
if (shortPosition) {
|
|
800
|
+
short = config?.getSize2(shortPosition, contract_size, balance) || null;
|
|
801
|
+
}
|
|
802
|
+
return { long, short };
|
|
803
|
+
}
|
|
804
|
+
function getParamForField(self, configs, field, isGroup) {
|
|
805
|
+
if (isGroup === "group" && field === "checkbox") {
|
|
806
|
+
return configs.filter((o) => o.kind === field && o.group === true).map((o) => {
|
|
807
|
+
let _self = self;
|
|
808
|
+
let value2 = _self[o.name];
|
|
809
|
+
return { ...o, value: value2 };
|
|
810
|
+
});
|
|
811
|
+
}
|
|
812
|
+
let r = configs.find((o) => o.name == field);
|
|
813
|
+
if (r) {
|
|
814
|
+
let oo = self;
|
|
815
|
+
let tt = oo[r.name] || "";
|
|
816
|
+
r.value = tt;
|
|
817
|
+
}
|
|
818
|
+
return r;
|
|
819
|
+
}
|
|
820
|
+
function getTradeEntries(entry, min_size, kind, size, spread = 0) {
|
|
821
|
+
let result = [];
|
|
822
|
+
let index = 0;
|
|
823
|
+
let no_of_trades = size > min_size ? Math.round(size / min_size) : 1;
|
|
824
|
+
while (index < no_of_trades) {
|
|
825
|
+
if (kind === "long") {
|
|
826
|
+
result.push({ entry: entry - index * spread, size: min_size });
|
|
827
|
+
} else {
|
|
828
|
+
result.push({ entry: entry + index * spread, size: min_size });
|
|
829
|
+
}
|
|
830
|
+
index = index + 1;
|
|
831
|
+
}
|
|
832
|
+
return result;
|
|
833
|
+
}
|
|
834
|
+
function extractValue(_param, condition) {
|
|
835
|
+
let param;
|
|
836
|
+
if (condition) {
|
|
837
|
+
try {
|
|
838
|
+
let value2 = JSON.parse(_param || "[]");
|
|
839
|
+
param = value2.map((o) => parseFloat(o));
|
|
840
|
+
} catch (error) {
|
|
841
|
+
}
|
|
842
|
+
} else {
|
|
843
|
+
param = parseFloat(_param);
|
|
844
|
+
}
|
|
845
|
+
return param;
|
|
846
|
+
}
|
|
847
|
+
function asCoins(symbol) {
|
|
848
|
+
let _type = symbol.toLowerCase().includes("usdt") ? "usdt" : "coin";
|
|
849
|
+
if (symbol.toLowerCase() == "btcusdt") {
|
|
850
|
+
_type = "usdt";
|
|
851
|
+
}
|
|
852
|
+
let result = _type === "usdt" ? symbol.toLowerCase().includes("usdt") ? "USDT" : "BUSD" : symbol.toUpperCase().split("USD_")[0];
|
|
853
|
+
if (symbol.toLowerCase().includes("-")) {
|
|
854
|
+
result = result.split("-")[0];
|
|
855
|
+
}
|
|
856
|
+
if (symbol.toLowerCase() == "usdt-usd") {
|
|
857
|
+
}
|
|
858
|
+
let result2 = _type == "usdt" ? symbol.split(result)[0] : result;
|
|
859
|
+
if (result.includes("-")) {
|
|
860
|
+
result2 = result;
|
|
861
|
+
}
|
|
862
|
+
return result2;
|
|
863
|
+
}
|
|
864
|
+
var SpecialCoins = ["NGN", "USDT", "BUSD", "PAX", "USDC", "EUR"];
|
|
865
|
+
function allCoins(symbols) {
|
|
866
|
+
let r = symbols.map((o, i) => asCoins(o));
|
|
867
|
+
return [...new Set(r), ...SpecialCoins];
|
|
868
|
+
}
|
|
869
|
+
function formatPrice(value2, opts = {}) {
|
|
870
|
+
const { locale = "en-US", currency = "USD" } = opts;
|
|
871
|
+
const formatter = new Intl.NumberFormat(locale, {
|
|
872
|
+
currency,
|
|
873
|
+
style: "currency",
|
|
874
|
+
maximumFractionDigits: 2
|
|
875
|
+
});
|
|
876
|
+
return formatter.format(value2);
|
|
877
|
+
}
|
|
878
|
+
function to_f(value2, places = "%.1f") {
|
|
879
|
+
let v = typeof value2 === "string" ? parseFloat(value2) : value2;
|
|
880
|
+
const formattedValue = places.replace("%.", "").replace("f", "");
|
|
881
|
+
return parseFloat(v.toFixed(parseInt(formattedValue)));
|
|
882
|
+
}
|
|
883
|
+
function determine_stop_and_size(entry, pnl, take_profit, kind = "long") {
|
|
884
|
+
const difference = kind === "long" ? take_profit - entry : entry - take_profit;
|
|
885
|
+
const quantity = pnl / difference;
|
|
886
|
+
return Math.abs(quantity);
|
|
887
|
+
}
|
|
888
|
+
var range = (start, stop, step = 1) => Array.from({ length: (stop - start) / step + 1 }, (_, i) => start + i * step);
|
|
889
|
+
function determine_amount_to_sell2(entry, quantity, sell_price, pnl, kind, places = "%.3f") {
|
|
890
|
+
const _pnl = determine_pnl(entry, sell_price, quantity, kind);
|
|
891
|
+
const ratio = pnl / to_f(Math.abs(_pnl), places);
|
|
892
|
+
quantity = quantity * ratio;
|
|
893
|
+
return to_f(quantity, places);
|
|
894
|
+
}
|
|
895
|
+
function determine_position_size({
|
|
896
|
+
entry,
|
|
897
|
+
stop,
|
|
898
|
+
budget,
|
|
899
|
+
percent,
|
|
900
|
+
min_size,
|
|
901
|
+
notional_value,
|
|
902
|
+
as_coin = true,
|
|
903
|
+
places = "%.3f"
|
|
904
|
+
}) {
|
|
905
|
+
let stop_percent = stop ? Math.abs(entry - stop) / entry : percent;
|
|
906
|
+
if (stop_percent && budget) {
|
|
907
|
+
let size = budget / stop_percent;
|
|
908
|
+
let notion_value = size * entry;
|
|
909
|
+
if (notional_value && notional_value > notion_value) {
|
|
910
|
+
size = notional_value / entry;
|
|
911
|
+
}
|
|
912
|
+
if (as_coin) {
|
|
913
|
+
size = size / entry;
|
|
914
|
+
if (min_size && min_size === 1) {
|
|
915
|
+
return to_f(Math.round(size), places);
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
return to_f(size, places);
|
|
919
|
+
}
|
|
920
|
+
return;
|
|
921
|
+
}
|
|
922
|
+
function determine_remaining_entry({
|
|
923
|
+
risk,
|
|
924
|
+
max_size,
|
|
925
|
+
stop_loss,
|
|
926
|
+
kind,
|
|
927
|
+
position: position2
|
|
928
|
+
}) {
|
|
929
|
+
const avg_entry = determine_avg_entry_based_on_max_size({
|
|
930
|
+
risk,
|
|
931
|
+
max_size,
|
|
932
|
+
stop_loss,
|
|
933
|
+
kind
|
|
934
|
+
});
|
|
935
|
+
const result = avg_entry * max_size - position2.quantity * position2.entry;
|
|
936
|
+
return result / (max_size - position2.quantity);
|
|
937
|
+
}
|
|
938
|
+
function determine_avg_entry_based_on_max_size({
|
|
939
|
+
risk,
|
|
940
|
+
max_size,
|
|
941
|
+
stop_loss,
|
|
942
|
+
kind = "long"
|
|
943
|
+
}) {
|
|
944
|
+
const diff = max_size * stop_loss;
|
|
945
|
+
if (kind === "long") {
|
|
946
|
+
return (risk + diff) / max_size;
|
|
947
|
+
}
|
|
948
|
+
return (risk - diff) / max_size;
|
|
949
|
+
}
|
|
950
|
+
function determine_average_entry_and_size(orders, places = "%.3f", price_places = "%.1f") {
|
|
951
|
+
const sum_values = orders.reduce((sum, order) => sum + order.price * order.quantity, 0);
|
|
952
|
+
const total_quantity = orders.reduce((sum, order) => sum + order.quantity, 0);
|
|
953
|
+
const avg_price = total_quantity ? to_f(sum_values / total_quantity, price_places) : 0;
|
|
954
|
+
return {
|
|
955
|
+
entry: avg_price,
|
|
956
|
+
price: avg_price,
|
|
957
|
+
quantity: to_f(total_quantity, places)
|
|
958
|
+
};
|
|
959
|
+
}
|
|
960
|
+
var createArray = (start, stop, step) => {
|
|
961
|
+
const result = [];
|
|
962
|
+
let current = start;
|
|
963
|
+
while (current <= stop) {
|
|
964
|
+
result.push(current);
|
|
965
|
+
current += step;
|
|
966
|
+
}
|
|
967
|
+
return result;
|
|
968
|
+
};
|
|
969
|
+
var groupBy = (xs, key) => {
|
|
970
|
+
return xs.reduce((rv, x) => {
|
|
971
|
+
(rv[x[key]] = rv[x[key]] || []).push(x);
|
|
972
|
+
return rv;
|
|
973
|
+
}, {});
|
|
974
|
+
};
|
|
975
|
+
function fibonacci_analysis({
|
|
976
|
+
support,
|
|
977
|
+
resistance,
|
|
978
|
+
kind = "long",
|
|
979
|
+
trend = "long",
|
|
980
|
+
places = "%.1f"
|
|
981
|
+
}) {
|
|
982
|
+
const swing_high = trend === "long" ? resistance : support;
|
|
983
|
+
const swing_low = trend === "long" ? support : resistance;
|
|
984
|
+
const ranges = [0, 0.236, 0.382, 0.5, 0.618, 0.789, 1, 1.272, 1.414, 1.618];
|
|
985
|
+
const fib_calc = (p, h, l) => p * (h - l) + l;
|
|
986
|
+
const fib_values = ranges.map((x) => fib_calc(x, swing_high, swing_low)).map((x) => to_f(x, places));
|
|
987
|
+
if (kind === "short") {
|
|
988
|
+
return trend === "long" ? fib_values.reverse() : fib_values;
|
|
989
|
+
} else {
|
|
990
|
+
return trend === "short" ? fib_values.reverse() : fib_values;
|
|
991
|
+
}
|
|
992
|
+
return fib_values;
|
|
993
|
+
}
|
|
994
|
+
var groupIntoPairs = (arr, size) => {
|
|
995
|
+
const result = [];
|
|
996
|
+
for (let i = 0;i < arr.length; i += size) {
|
|
997
|
+
result.push(arr.slice(i, i + size));
|
|
998
|
+
}
|
|
999
|
+
return result;
|
|
1000
|
+
};
|
|
1001
|
+
var groupIntoPairsWithSumLessThan = (arr, targetSum, key = "quantity", firstSize = 0) => {
|
|
1002
|
+
if (firstSize) {
|
|
1003
|
+
const totalSize = arr.reduce((sum, order) => sum + order[key], 0);
|
|
1004
|
+
const remainingSize = totalSize - firstSize;
|
|
1005
|
+
let newSum = 0;
|
|
1006
|
+
let newArray = [];
|
|
1007
|
+
let lastIndex = 0;
|
|
1008
|
+
for (let i = 0;i < arr.length; i++) {
|
|
1009
|
+
if (newSum < remainingSize) {
|
|
1010
|
+
newSum += arr[i][key];
|
|
1011
|
+
newArray.push(arr[i]);
|
|
1012
|
+
lastIndex = i;
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
const lastGroup = arr.slice(lastIndex + 1);
|
|
1016
|
+
const previousPair = groupInPairs(newArray, key, targetSum);
|
|
1017
|
+
if (lastGroup.length > 0) {
|
|
1018
|
+
previousPair.push(lastGroup);
|
|
1019
|
+
}
|
|
1020
|
+
return previousPair;
|
|
1021
|
+
}
|
|
1022
|
+
return groupInPairs(arr, key, targetSum);
|
|
1023
|
+
};
|
|
1024
|
+
function groupInPairs(_arr, key, targetSum) {
|
|
1025
|
+
const result = [];
|
|
1026
|
+
let currentSum = 0;
|
|
1027
|
+
let currentGroup = [];
|
|
1028
|
+
for (let i = 0;i < _arr.length; i++) {
|
|
1029
|
+
currentSum += _arr[i][key];
|
|
1030
|
+
currentGroup.push(_arr[i]);
|
|
1031
|
+
if (currentSum >= targetSum) {
|
|
1032
|
+
result.push(currentGroup);
|
|
1033
|
+
currentGroup = [];
|
|
1034
|
+
currentSum = 0;
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
return result;
|
|
1038
|
+
}
|
|
1039
|
+
var computeTotalAverageForEachTrade = (trades, config) => {
|
|
1040
|
+
let _take_profit = config.take_profit;
|
|
1041
|
+
let kind = config.kind;
|
|
1042
|
+
let entryToUse = kind === "short" ? Math.min(config.entry, config.stop) : Math.max(config.entry, config.stop);
|
|
1043
|
+
let _currentEntry = config.currentEntry || entryToUse;
|
|
1044
|
+
let less = trades.filter((p) => kind === "long" ? p.entry <= _currentEntry : p.entry >= _currentEntry);
|
|
1045
|
+
let rrr = trades.map((r, i) => {
|
|
1046
|
+
let considered = [];
|
|
1047
|
+
if (kind === "long") {
|
|
1048
|
+
considered = trades.filter((p) => p.entry > _currentEntry);
|
|
1049
|
+
} else {
|
|
1050
|
+
considered = trades.filter((p) => p.entry < _currentEntry);
|
|
1051
|
+
}
|
|
1052
|
+
const x_pnl = 0;
|
|
1053
|
+
const remaining = less.filter((o) => {
|
|
1054
|
+
if (kind === "long") {
|
|
1055
|
+
return o.entry >= r.entry;
|
|
1056
|
+
}
|
|
1057
|
+
return o.entry <= r.entry;
|
|
1058
|
+
});
|
|
1059
|
+
if (remaining.length === 0) {
|
|
1060
|
+
return { ...r, pnl: x_pnl };
|
|
1061
|
+
}
|
|
1062
|
+
const start = kind === "long" ? Math.max(...remaining.map((o) => o.entry)) : Math.min(...remaining.map((o) => o.entry));
|
|
1063
|
+
considered = considered.map((o) => ({ ...o, entry: start }));
|
|
1064
|
+
considered = considered.concat(remaining);
|
|
1065
|
+
let avg_entry = determine_average_entry_and_size([
|
|
1066
|
+
...considered.map((o) => ({
|
|
1067
|
+
price: o.entry,
|
|
1068
|
+
quantity: o.quantity
|
|
1069
|
+
})),
|
|
1070
|
+
{
|
|
1071
|
+
price: _currentEntry,
|
|
1072
|
+
quantity: config.currentQty || 0
|
|
1073
|
+
}
|
|
1074
|
+
], config.decimal_places, config.price_places);
|
|
1075
|
+
let _pnl = r.pnl;
|
|
1076
|
+
let sell_price = r.sell_price;
|
|
1077
|
+
let entry_pnl = r.pnl;
|
|
1078
|
+
if (_take_profit) {
|
|
1079
|
+
_pnl = pnl_default.determine_pnl(avg_entry.price, _take_profit, avg_entry.quantity, kind);
|
|
1080
|
+
sell_price = _take_profit;
|
|
1081
|
+
entry_pnl = pnl_default.determine_pnl(r.entry, _take_profit, avg_entry.quantity, kind);
|
|
1082
|
+
}
|
|
1083
|
+
const loss = pnl_default.determine_pnl(avg_entry.price, r.stop, avg_entry.quantity, kind);
|
|
1084
|
+
let new_stop = r.new_stop;
|
|
1085
|
+
const entry_loss = pnl_default.determine_pnl(r.entry, new_stop, avg_entry.quantity, kind);
|
|
1086
|
+
let min_profit = 0;
|
|
1087
|
+
let min_entry_profit = 0;
|
|
1088
|
+
if (config.min_profit) {
|
|
1089
|
+
min_profit = pnl_default.determine_close_price(avg_entry.price, config.min_profit, avg_entry.quantity, kind);
|
|
1090
|
+
min_entry_profit = pnl_default.determine_close_price(r.entry, config.min_profit, avg_entry.quantity, kind);
|
|
1091
|
+
}
|
|
1092
|
+
let x_fee = r.fee;
|
|
1093
|
+
if (config.fee) {
|
|
1094
|
+
x_fee = config.fee * r.stop * avg_entry.quantity;
|
|
1095
|
+
}
|
|
1096
|
+
let tp_close = pnl_default.determine_close_price(r.entry, Math.abs(entry_loss) * (config.rr || 1) + x_fee, avg_entry.quantity, kind);
|
|
1097
|
+
return {
|
|
1098
|
+
...r,
|
|
1099
|
+
x_fee: to_f(x_fee, "%.2f"),
|
|
1100
|
+
avg_entry: avg_entry.price,
|
|
1101
|
+
avg_size: avg_entry.quantity,
|
|
1102
|
+
entry_pnl: to_f(entry_pnl, "%.2f"),
|
|
1103
|
+
entry_loss: to_f(entry_loss, "%.2f"),
|
|
1104
|
+
min_entry_pnl: to_f(min_entry_profit, "%.2f"),
|
|
1105
|
+
pnl: _pnl,
|
|
1106
|
+
neg_pnl: to_f(loss, "%.2f"),
|
|
1107
|
+
sell_price,
|
|
1108
|
+
close_p: to_f(tp_close, "%.2f"),
|
|
1109
|
+
min_pnl: to_f(min_profit, "%.2f"),
|
|
1110
|
+
new_stop
|
|
1111
|
+
};
|
|
1112
|
+
});
|
|
1113
|
+
return rrr;
|
|
1114
|
+
};
|
|
1115
|
+
function getDecimalPlaces(numberString) {
|
|
1116
|
+
let parts = numberString.toString().split(".");
|
|
1117
|
+
if (parts.length == 2) {
|
|
1118
|
+
return parts[1].length;
|
|
1119
|
+
}
|
|
1120
|
+
return 0;
|
|
1121
|
+
}
|
|
1122
|
+
function createGapPairs(arr, gap, item) {
|
|
1123
|
+
if (arr.length === 0) {
|
|
1124
|
+
return [];
|
|
1125
|
+
}
|
|
1126
|
+
const result = [];
|
|
1127
|
+
const firstElement = arr[0];
|
|
1128
|
+
for (let i = arr.length - 1;i >= 0; i--) {
|
|
1129
|
+
const current = arr[i];
|
|
1130
|
+
const gapIndex = i - gap;
|
|
1131
|
+
const pairedElement = gapIndex < 0 ? firstElement : arr[gapIndex];
|
|
1132
|
+
if (current !== pairedElement) {
|
|
1133
|
+
result.push([current, pairedElement]);
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
if (item) {
|
|
1137
|
+
let r = result.find((o) => o[0] === item);
|
|
1138
|
+
return r ? [r] : [];
|
|
1139
|
+
}
|
|
1140
|
+
return result;
|
|
1141
|
+
}
|
|
1142
|
+
function logWithLineNumber(...args) {
|
|
1143
|
+
const stack = new Error().stack;
|
|
1144
|
+
const lines = stack?.split(`
|
|
1145
|
+
`).slice(2).map((line) => line.trim());
|
|
1146
|
+
const lineNumber = lines?.[0]?.split(":").pop();
|
|
1147
|
+
console.log(`${lineNumber}:`, ...args);
|
|
1148
|
+
}
|
|
1149
|
+
// src/helpers/shared.ts
|
|
1150
|
+
function buildConfig(app_config, {
|
|
1151
|
+
take_profit,
|
|
1152
|
+
entry,
|
|
1153
|
+
stop,
|
|
1154
|
+
raw_instance,
|
|
1155
|
+
risk,
|
|
1156
|
+
no_of_trades,
|
|
1157
|
+
min_profit = 0,
|
|
1158
|
+
risk_reward,
|
|
1159
|
+
kind,
|
|
1160
|
+
increase,
|
|
1161
|
+
gap,
|
|
1162
|
+
rr = 1,
|
|
1163
|
+
price_places = "%.1f",
|
|
1164
|
+
decimal_places = "%.3f"
|
|
1165
|
+
}) {
|
|
1166
|
+
let fee = app_config.fee / 100;
|
|
1167
|
+
let working_risk = risk || app_config.risk_per_trade;
|
|
998
1168
|
let trade_no = no_of_trades || app_config.risk_reward;
|
|
999
1169
|
const config = {
|
|
1000
1170
|
focus: app_config.focus,
|
|
@@ -1075,8 +1245,8 @@ function get_app_config_and_max_size(config, payload) {
|
|
|
1075
1245
|
stop: payload.stop,
|
|
1076
1246
|
risk_per_trade: config.risk,
|
|
1077
1247
|
risk_reward: config.risk_reward || 199,
|
|
1078
|
-
support:
|
|
1079
|
-
resistance:
|
|
1248
|
+
support: to_f(config.support, config.price_places),
|
|
1249
|
+
resistance: to_f(config.resistance, config.price_places),
|
|
1080
1250
|
focus: payload.entry,
|
|
1081
1251
|
fee: 0,
|
|
1082
1252
|
percent_change: config.stop_percent / 100,
|
|
@@ -1277,8 +1447,8 @@ function getOptimumStopAndRisk(app_config, params) {
|
|
|
1277
1447
|
}
|
|
1278
1448
|
}
|
|
1279
1449
|
return {
|
|
1280
|
-
optimal_stop:
|
|
1281
|
-
optimal_risk:
|
|
1450
|
+
optimal_stop: to_f(optimal_stop, app_config.price_places),
|
|
1451
|
+
optimal_risk: to_f(final_risk, app_config.price_places),
|
|
1282
1452
|
avg_size: final_result?.[0]?.avg_size || 0,
|
|
1283
1453
|
avg_entry: final_result?.[0]?.avg_entry || 0,
|
|
1284
1454
|
result: final_result,
|
|
@@ -1326,7 +1496,7 @@ function determine_amount_to_buy(payload) {
|
|
|
1326
1496
|
existingOrders
|
|
1327
1497
|
} = payload;
|
|
1328
1498
|
const totalQuantity = orders.reduce((sum, order) => sum + (order.quantity || 0), 0);
|
|
1329
|
-
let runningTotal =
|
|
1499
|
+
let runningTotal = to_f(totalQuantity, decimal_places);
|
|
1330
1500
|
let sortedOrders = [...orders].sort((a, b) => (a.entry || 0) - (b.entry || 0));
|
|
1331
1501
|
if (kind === "short") {
|
|
1332
1502
|
sortedOrders.reverse();
|
|
@@ -1338,7 +1508,7 @@ function determine_amount_to_buy(payload) {
|
|
|
1338
1508
|
cumulative_quantity: runningTotal
|
|
1339
1509
|
});
|
|
1340
1510
|
runningTotal -= order.quantity;
|
|
1341
|
-
runningTotal =
|
|
1511
|
+
runningTotal = to_f(runningTotal, decimal_places);
|
|
1342
1512
|
}
|
|
1343
1513
|
let filteredOrders = withCumulative.filter((order) => (order.cumulative_quantity || 0) > position2?.quantity).map((order) => ({
|
|
1344
1514
|
...order,
|
|
@@ -1350,15 +1520,37 @@ function determine_amount_to_buy(payload) {
|
|
|
1350
1520
|
return filteredOrders;
|
|
1351
1521
|
}
|
|
1352
1522
|
export {
|
|
1523
|
+
to_f,
|
|
1353
1524
|
sortedBuildConfig,
|
|
1525
|
+
range,
|
|
1526
|
+
profitHelper,
|
|
1527
|
+
logWithLineNumber,
|
|
1528
|
+
groupIntoPairsWithSumLessThan,
|
|
1529
|
+
groupIntoPairs,
|
|
1530
|
+
groupBy,
|
|
1354
1531
|
get_app_config_and_max_size,
|
|
1532
|
+
getTradeEntries,
|
|
1533
|
+
getParamForField,
|
|
1355
1534
|
getOptimumStopAndRisk,
|
|
1535
|
+
getDecimalPlaces,
|
|
1356
1536
|
generate_config_params,
|
|
1537
|
+
formatPrice,
|
|
1538
|
+
fibonacci_analysis,
|
|
1539
|
+
extractValue,
|
|
1540
|
+
determine_stop_and_size,
|
|
1541
|
+
determine_remaining_entry,
|
|
1542
|
+
determine_position_size,
|
|
1357
1543
|
determine_break_even_price,
|
|
1358
1544
|
determine_average_entry_and_size,
|
|
1545
|
+
determine_amount_to_sell2 as determine_amount_to_sell,
|
|
1359
1546
|
determine_amount_to_buy,
|
|
1547
|
+
createGapPairs,
|
|
1360
1548
|
createArray,
|
|
1549
|
+
computeTotalAverageForEachTrade,
|
|
1361
1550
|
buildConfig,
|
|
1362
1551
|
buildAvg,
|
|
1363
|
-
buildAppConfig
|
|
1552
|
+
buildAppConfig,
|
|
1553
|
+
asCoins,
|
|
1554
|
+
allCoins,
|
|
1555
|
+
SpecialCoins
|
|
1364
1556
|
};
|