@gbozee/ultimate 0.0.2-next.4 → 0.0.2-next.41
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 +3 -2
- package/dist/frontend-index.js +47 -14
- package/dist/index.cjs +18207 -5659
- package/dist/index.d.ts +4377 -25
- package/dist/index.js +18519 -5971
- package/dist/mcp-server.cjs +1972 -114
- package/dist/mcp-server.js +1972 -114
- package/package.json +15 -12
package/dist/mcp-server.cjs
CHANGED
|
@@ -63321,12 +63321,18 @@ async function initPocketBaseClient(proxy_credentials) {
|
|
|
63321
63321
|
|
|
63322
63322
|
class AppDatabase {
|
|
63323
63323
|
pb;
|
|
63324
|
+
drizzlePb;
|
|
63324
63325
|
email;
|
|
63325
63326
|
salt;
|
|
63326
|
-
|
|
63327
|
+
root_password;
|
|
63328
|
+
root_email;
|
|
63329
|
+
constructor(pb, payload, drizzlePb) {
|
|
63327
63330
|
this.pb = pb;
|
|
63328
63331
|
this.email = payload.email;
|
|
63329
63332
|
this.salt = payload.salt;
|
|
63333
|
+
this.root_password = payload.db?.password;
|
|
63334
|
+
this.root_email = payload.db?.email;
|
|
63335
|
+
this.drizzlePb = drizzlePb;
|
|
63330
63336
|
const customInspectSymbol = Symbol.for("nodejs.util.inspect.custom");
|
|
63331
63337
|
this[customInspectSymbol] = function() {
|
|
63332
63338
|
return `AppDatabase { pb: [PocketBase Instance - Sensitive Data Masked] }`;
|
|
@@ -63506,6 +63512,13 @@ class AppDatabase {
|
|
|
63506
63512
|
return Array.from(new Set(positions.map((p) => p.symbol)));
|
|
63507
63513
|
}
|
|
63508
63514
|
async createOrUpdateLiveExchangeInstance(payload) {
|
|
63515
|
+
if (this.drizzlePb) {
|
|
63516
|
+
await this.drizzlePb?.createOrUpdateLiveExchangeInstance(payload);
|
|
63517
|
+
return this.getLiveExchangeInstance({
|
|
63518
|
+
account: payload.account,
|
|
63519
|
+
symbol: payload.symbol
|
|
63520
|
+
});
|
|
63521
|
+
}
|
|
63509
63522
|
const result = await this.getLiveExchangeInstance(payload);
|
|
63510
63523
|
if (result) {
|
|
63511
63524
|
return await this.pb.collection("live_exchange_details").update(result.id, {
|
|
@@ -63521,6 +63534,9 @@ class AppDatabase {
|
|
|
63521
63534
|
}
|
|
63522
63535
|
}
|
|
63523
63536
|
async getLiveExchangeInstance(payload) {
|
|
63537
|
+
if (this.drizzlePb) {
|
|
63538
|
+
return this.drizzlePb.getLiveExchangeInstance(payload);
|
|
63539
|
+
}
|
|
63524
63540
|
const result = await this.pb.collection("live_exchange_details").getFullList({
|
|
63525
63541
|
filter: `account.owner:lower="${payload.account.owner.toLowerCase()}" && account.exchange:lower="${payload.account.exchange.toLowerCase()}" && symbol:lower="${payload.symbol.toLowerCase()}"`,
|
|
63526
63542
|
expand: "account"
|
|
@@ -63563,6 +63579,9 @@ class AppDatabase {
|
|
|
63563
63579
|
});
|
|
63564
63580
|
}
|
|
63565
63581
|
async get_exchange_db_instance(account) {
|
|
63582
|
+
if (this.drizzlePb) {
|
|
63583
|
+
return this.drizzlePb.get_exchange_db_instance(account);
|
|
63584
|
+
}
|
|
63566
63585
|
let filter = `owner="${account.owner}" && exchange="${account.exchange}"`;
|
|
63567
63586
|
if (this.email) {
|
|
63568
63587
|
const user = await this.getUserByEmail();
|
|
@@ -63586,11 +63605,18 @@ class AppDatabase {
|
|
|
63586
63605
|
"resistance",
|
|
63587
63606
|
"symbol_config",
|
|
63588
63607
|
"anchor",
|
|
63589
|
-
"watch_instance"
|
|
63608
|
+
"watch_instance",
|
|
63609
|
+
"short_compound_strategy.tracking_account.account"
|
|
63590
63610
|
];
|
|
63591
63611
|
return fields.join(", ");
|
|
63592
63612
|
}
|
|
63593
63613
|
async fetchCentralPositions(payload) {
|
|
63614
|
+
if (this.drizzlePb) {
|
|
63615
|
+
return this.drizzlePb.fetchCentralPositions({
|
|
63616
|
+
...payload,
|
|
63617
|
+
email: this.email
|
|
63618
|
+
});
|
|
63619
|
+
}
|
|
63594
63620
|
const { asset: assetName, symbol, customFilter = "" } = payload;
|
|
63595
63621
|
const pb = this.pb;
|
|
63596
63622
|
let symbolFilter = assetName ? `symbol ~ "${assetName}"` : `symbol = "${symbol}"`;
|
|
@@ -63604,6 +63630,9 @@ class AppDatabase {
|
|
|
63604
63630
|
return positionsWithAssetName;
|
|
63605
63631
|
}
|
|
63606
63632
|
async getPositions(options) {
|
|
63633
|
+
if (this.drizzlePb) {
|
|
63634
|
+
return this.drizzlePb.getPositions(options);
|
|
63635
|
+
}
|
|
63607
63636
|
const { symbol, as_view = false, account, custom_filter } = options;
|
|
63608
63637
|
let default_params = as_view ? {
|
|
63609
63638
|
table: "positions_view",
|
|
@@ -63642,6 +63671,9 @@ class AppDatabase {
|
|
|
63642
63671
|
account,
|
|
63643
63672
|
symbol
|
|
63644
63673
|
});
|
|
63674
|
+
if (this.drizzlePb) {
|
|
63675
|
+
return db_positions;
|
|
63676
|
+
}
|
|
63645
63677
|
const exchange_db_instance = await this.get_exchange_db_instance(account);
|
|
63646
63678
|
const long_exists = db_positions.find((x) => x.kind === "long");
|
|
63647
63679
|
const short_exists = db_positions.find((x) => x.kind === "short");
|
|
@@ -63664,9 +63696,15 @@ class AppDatabase {
|
|
|
63664
63696
|
return db_positions;
|
|
63665
63697
|
}
|
|
63666
63698
|
async update_db_position(position, payload) {
|
|
63699
|
+
if (this.drizzlePb) {
|
|
63700
|
+
return this.drizzlePb.update_db_position(position, payload);
|
|
63701
|
+
}
|
|
63667
63702
|
return await this.pb.collection("positions").update(position.id, payload);
|
|
63668
63703
|
}
|
|
63669
63704
|
async getSymbolConfigFromDB(symbol) {
|
|
63705
|
+
if (this.drizzlePb) {
|
|
63706
|
+
return this.drizzlePb.getSymbolConfigFromDB(symbol);
|
|
63707
|
+
}
|
|
63670
63708
|
const item = await this.pb.collection("symbol_configs").getFullList({
|
|
63671
63709
|
filter: `symbol:lower="${symbol.toLowerCase()}"`
|
|
63672
63710
|
});
|
|
@@ -63676,6 +63714,9 @@ class AppDatabase {
|
|
|
63676
63714
|
return null;
|
|
63677
63715
|
}
|
|
63678
63716
|
async getOrders(account, options) {
|
|
63717
|
+
if (this.drizzlePb) {
|
|
63718
|
+
return this.drizzlePb.getOrders(account, options);
|
|
63719
|
+
}
|
|
63679
63720
|
const { symbol, kind } = options;
|
|
63680
63721
|
const db_orders = await this.pb.collection("orders").getFullList({
|
|
63681
63722
|
filter: `symbol:lower="${symbol.toLowerCase()}" && account.owner:lower="${account.owner.toLowerCase()}" && account.exchange:lower="${account.exchange.toLowerCase()}" && kind="${kind}"`,
|
|
@@ -63742,20 +63783,99 @@ class AppDatabase {
|
|
|
63742
63783
|
});
|
|
63743
63784
|
}
|
|
63744
63785
|
async deleteAndBulCreateAllOrders(payload) {
|
|
63786
|
+
if (this.drizzlePb) {
|
|
63787
|
+
return this.drizzlePb.getOrders(payload.account, {
|
|
63788
|
+
symbol: payload.symbol,
|
|
63789
|
+
kind: "long"
|
|
63790
|
+
});
|
|
63791
|
+
}
|
|
63745
63792
|
const { account, symbol, all_orders } = payload;
|
|
63746
|
-
console.log(`Fetching existing DB orders for ${account.owner}/${account.exchange} - ${symbol}
|
|
63747
|
-
let
|
|
63793
|
+
console.log(`Fetching existing DB orders for ${account.owner}/${account.exchange} - ${symbol} for deduplication...`);
|
|
63794
|
+
let existing_orders = [];
|
|
63748
63795
|
try {
|
|
63749
|
-
|
|
63796
|
+
existing_orders = await this.pb.collection("orders").getFullList({
|
|
63750
63797
|
filter: `symbol:lower="${symbol.toLowerCase()}" && account.owner:lower="${account.owner.toLowerCase()}" && account.exchange:lower="${account.exchange.toLowerCase()}"`
|
|
63751
63798
|
});
|
|
63752
|
-
|
|
63753
|
-
console.log(`Found ${pocketbase_ids_to_delete.length} existing DB orders to delete.`);
|
|
63799
|
+
console.log(`Found ${existing_orders.length} existing DB orders.`);
|
|
63754
63800
|
} catch (error) {
|
|
63755
|
-
console.error("Error fetching DB orders
|
|
63801
|
+
console.error("Error fetching existing DB orders:", error);
|
|
63802
|
+
existing_orders = [];
|
|
63803
|
+
}
|
|
63804
|
+
const existing_signatures = new Set;
|
|
63805
|
+
const existing_ids_to_delete = new Set;
|
|
63806
|
+
for (const existing_order of existing_orders) {
|
|
63807
|
+
const signature = [
|
|
63808
|
+
existing_order.symbol?.toLowerCase() || "",
|
|
63809
|
+
existing_order.kind || "",
|
|
63810
|
+
existing_order.price?.toString() || "",
|
|
63811
|
+
existing_order.quantity?.toString() || "",
|
|
63812
|
+
existing_order.side?.toLowerCase() || "",
|
|
63813
|
+
existing_order.stop?.toString() || "0",
|
|
63814
|
+
existing_order.order_id?.toString() || "",
|
|
63815
|
+
existing_order.client_order_id?.toString() || "",
|
|
63816
|
+
account.id
|
|
63817
|
+
].join("|");
|
|
63818
|
+
existing_signatures.add(signature);
|
|
63819
|
+
existing_ids_to_delete.add(existing_order.id);
|
|
63820
|
+
}
|
|
63821
|
+
const unique_orders = [];
|
|
63822
|
+
const new_signatures = new Set;
|
|
63823
|
+
for (const order of all_orders) {
|
|
63824
|
+
const signature = [
|
|
63825
|
+
symbol.toLowerCase(),
|
|
63826
|
+
order.kind || "",
|
|
63827
|
+
order.price?.toString() || "",
|
|
63828
|
+
order.quantity?.toString() || "",
|
|
63829
|
+
order.side?.toLowerCase() || "",
|
|
63830
|
+
(order.stop || order.triggerPrice || 0).toString(),
|
|
63831
|
+
order.order_id?.toString() || "",
|
|
63832
|
+
(order.clientOrderId || "").toString(),
|
|
63833
|
+
account.id
|
|
63834
|
+
].join("|");
|
|
63835
|
+
if (!existing_signatures.has(signature) && !new_signatures.has(signature)) {
|
|
63836
|
+
unique_orders.push(order);
|
|
63837
|
+
new_signatures.add(signature);
|
|
63838
|
+
} else {
|
|
63839
|
+
console.log(`Skipping duplicate order: ${order.order_id} for ${symbol}`);
|
|
63840
|
+
}
|
|
63841
|
+
}
|
|
63842
|
+
console.log(`After deduplication: ${unique_orders.length} unique orders out of ${all_orders.length} total orders`);
|
|
63843
|
+
const orders_to_keep = new Set;
|
|
63844
|
+
for (const order of all_orders) {
|
|
63845
|
+
const signature = [
|
|
63846
|
+
symbol.toLowerCase(),
|
|
63847
|
+
order.kind || "",
|
|
63848
|
+
order.price?.toString() || "",
|
|
63849
|
+
order.quantity?.toString() || "",
|
|
63850
|
+
order.side?.toLowerCase() || "",
|
|
63851
|
+
(order.stop || order.triggerPrice || 0).toString(),
|
|
63852
|
+
order.order_id?.toString() || "",
|
|
63853
|
+
(order.clientOrderId || "").toString(),
|
|
63854
|
+
account.id
|
|
63855
|
+
].join("|");
|
|
63856
|
+
if (existing_signatures.has(signature)) {
|
|
63857
|
+
orders_to_keep.add(signature);
|
|
63858
|
+
}
|
|
63859
|
+
}
|
|
63860
|
+
const pocketbase_ids_to_delete = [];
|
|
63861
|
+
for (const existing_order of existing_orders) {
|
|
63862
|
+
const signature = [
|
|
63863
|
+
existing_order.symbol?.toLowerCase() || "",
|
|
63864
|
+
existing_order.kind || "",
|
|
63865
|
+
existing_order.price?.toString() || "",
|
|
63866
|
+
existing_order.quantity?.toString() || "",
|
|
63867
|
+
existing_order.side?.toLowerCase() || "",
|
|
63868
|
+
existing_order.stop?.toString() || "0",
|
|
63869
|
+
existing_order.order_id?.toString() || "",
|
|
63870
|
+
existing_order.client_order_id?.toString() || "",
|
|
63871
|
+
account.id
|
|
63872
|
+
].join("|");
|
|
63873
|
+
if (!orders_to_keep.has(signature)) {
|
|
63874
|
+
pocketbase_ids_to_delete.push(existing_order.id);
|
|
63875
|
+
}
|
|
63756
63876
|
}
|
|
63757
63877
|
if (pocketbase_ids_to_delete.length > 0) {
|
|
63758
|
-
console.log(`Deleting ${pocketbase_ids_to_delete.length}
|
|
63878
|
+
console.log(`Deleting ${pocketbase_ids_to_delete.length} obsolete orders from PocketBase...`);
|
|
63759
63879
|
for (let i2 = 0;i2 < pocketbase_ids_to_delete.length; i2 += 5) {
|
|
63760
63880
|
const batch_to_delete = pocketbase_ids_to_delete.slice(i2, i2 + 5);
|
|
63761
63881
|
try {
|
|
@@ -63764,13 +63884,12 @@ class AppDatabase {
|
|
|
63764
63884
|
console.error(`Error deleting batch starting at index ${i2}:`, error, "Batch IDs:", batch_to_delete);
|
|
63765
63885
|
}
|
|
63766
63886
|
}
|
|
63767
|
-
console.log("Finished deleting
|
|
63887
|
+
console.log("Finished deleting obsolete DB orders.");
|
|
63768
63888
|
}
|
|
63769
|
-
|
|
63770
|
-
|
|
63771
|
-
|
|
63772
|
-
|
|
63773
|
-
const orderChunk = all_orders.slice(i2, i2 + 100);
|
|
63889
|
+
if (unique_orders.length > 0) {
|
|
63890
|
+
console.log(`Creating ${unique_orders.length} new orders in PocketBase...`);
|
|
63891
|
+
for (let i2 = 0;i2 < unique_orders.length; i2 += 100) {
|
|
63892
|
+
const orderChunk = unique_orders.slice(i2, i2 + 100);
|
|
63774
63893
|
const batch2 = this.pb.createBatch();
|
|
63775
63894
|
for (const order of orderChunk) {
|
|
63776
63895
|
const order_data = {
|
|
@@ -63792,8 +63911,9 @@ class AppDatabase {
|
|
|
63792
63911
|
console.error(`Error creating batch starting at index ${i2}:`, error);
|
|
63793
63912
|
}
|
|
63794
63913
|
}
|
|
63795
|
-
console.log("Finished
|
|
63914
|
+
console.log("Finished creating new orders in PocketBase.");
|
|
63796
63915
|
}
|
|
63916
|
+
console.log(`Order synchronization complete. Created ${unique_orders.length} new orders, deleted ${pocketbase_ids_to_delete.length} obsolete orders.`);
|
|
63797
63917
|
}
|
|
63798
63918
|
async cancelLimitOrders(payload) {
|
|
63799
63919
|
const { symbol, kind, account, cancelExchangeOrders, raw } = payload;
|
|
@@ -63808,7 +63928,7 @@ class AppDatabase {
|
|
|
63808
63928
|
const stop_limit_orders = await this.pb.collection("orders").getFullList({
|
|
63809
63929
|
filter: `symbol:lower="${symbol.toLowerCase()}" && account.owner:lower="${account.owner.toLowerCase()}" && account.exchange:lower="${account.exchange.toLowerCase()}" && kind="${kind}" && side:lower="${side}" && stop > 0`
|
|
63810
63930
|
});
|
|
63811
|
-
const exchange_order_ids = orders.concat(existing_stop_orders).concat(stop_limit_orders).map((o) => account.exchange === "bybit" ? o.order_id : account.exchange
|
|
63931
|
+
const exchange_order_ids = orders.concat(existing_stop_orders).concat(stop_limit_orders).map((o) => account.exchange === "bybit" ? o.order_id : ["binance", "paper"].includes(account.exchange) && o.client_order_id ? o.client_order_id : parseInt(o.order_id, 10));
|
|
63812
63932
|
if (raw) {
|
|
63813
63933
|
return exchange_order_ids;
|
|
63814
63934
|
}
|
|
@@ -63882,6 +64002,9 @@ class AppDatabase {
|
|
|
63882
64002
|
return result;
|
|
63883
64003
|
}
|
|
63884
64004
|
async updateScheduledTrade(id, payload) {
|
|
64005
|
+
if (this.drizzlePb) {
|
|
64006
|
+
return this.drizzlePb.updateScheduledTrade(id, payload);
|
|
64007
|
+
}
|
|
63885
64008
|
return await this.pb.collection("scheduled_trades").update(id, payload);
|
|
63886
64009
|
}
|
|
63887
64010
|
async getPositionsToAutoFollow() {
|
|
@@ -64135,6 +64258,9 @@ class AppDatabase {
|
|
|
64135
64258
|
return { updated_bullish, moved_to_winding };
|
|
64136
64259
|
}
|
|
64137
64260
|
async updateSymbolConfigs(payload) {
|
|
64261
|
+
if (this.drizzlePb) {
|
|
64262
|
+
return this.drizzlePb.updateSymbolConfigs(payload);
|
|
64263
|
+
}
|
|
64138
64264
|
if (!payload || !payload.configs) {
|
|
64139
64265
|
console.log("No payload provided. Fetching all symbol configs...");
|
|
64140
64266
|
try {
|
|
@@ -64452,8 +64578,7 @@ function getEntries(params) {
|
|
|
64452
64578
|
margin_range,
|
|
64453
64579
|
risk_reward,
|
|
64454
64580
|
kind,
|
|
64455
|
-
price_places
|
|
64456
|
-
percent_change: distribution_params?.curveFactor
|
|
64581
|
+
price_places
|
|
64457
64582
|
});
|
|
64458
64583
|
break;
|
|
64459
64584
|
case "normal":
|
|
@@ -64483,12 +64608,37 @@ function getEntries(params) {
|
|
|
64483
64608
|
curveFactor: distribution_params?.curveFactor
|
|
64484
64609
|
});
|
|
64485
64610
|
break;
|
|
64611
|
+
case "lognormal":
|
|
64612
|
+
entries = generateLognormal({
|
|
64613
|
+
margin_range,
|
|
64614
|
+
risk_reward,
|
|
64615
|
+
kind,
|
|
64616
|
+
price_places,
|
|
64617
|
+
stdDevFactor: distribution_params?.stdDevFactor
|
|
64618
|
+
});
|
|
64619
|
+
break;
|
|
64486
64620
|
default:
|
|
64487
64621
|
throw new Error(`Unknown distribution type: ${distribution}`);
|
|
64488
64622
|
}
|
|
64489
64623
|
return entries.sort((a, b) => a - b);
|
|
64490
64624
|
}
|
|
64491
64625
|
var distributions_default = getEntries;
|
|
64626
|
+
function generateLognormal(payload) {
|
|
64627
|
+
const { margin_range, risk_reward, kind, price_places = "%.1f", stdDevFactor = 6 } = payload;
|
|
64628
|
+
const logMin = Math.log(margin_range[0]);
|
|
64629
|
+
const logMax = Math.log(margin_range[1]);
|
|
64630
|
+
const mean = (logMin + logMax) / 2;
|
|
64631
|
+
const stdDev = Math.abs(logMax - logMin) / stdDevFactor;
|
|
64632
|
+
const entries = Array.from({ length: risk_reward + 1 }, (_, i2) => {
|
|
64633
|
+
const p = (i2 + 0.5) / (risk_reward + 1);
|
|
64634
|
+
const z2 = approximateInverseNormal(p);
|
|
64635
|
+
let logPrice = mean + stdDev * z2;
|
|
64636
|
+
logPrice = Math.max(logMin, Math.min(logMax, logPrice));
|
|
64637
|
+
const price = Math.exp(logPrice);
|
|
64638
|
+
return to_f(kind === "long" ? Math.min(price, margin_range[1]) : Math.max(price, margin_range[0]), price_places);
|
|
64639
|
+
});
|
|
64640
|
+
return entries.sort((a, b) => a - b);
|
|
64641
|
+
}
|
|
64492
64642
|
|
|
64493
64643
|
// src/helpers/optimizations.ts
|
|
64494
64644
|
function calculateTheoreticalKelly({
|
|
@@ -64862,7 +65012,7 @@ class Signal {
|
|
|
64862
65012
|
const simple_support = Math.min(current_price, stop_loss);
|
|
64863
65013
|
const simple_resistance = Math.max(current_price, stop_loss);
|
|
64864
65014
|
const risk_per_trade = risk / this.risk_reward;
|
|
64865
|
-
const use_progressive = distribution_params
|
|
65015
|
+
const use_progressive = distribution_params?.use_progressive || this.use_progressive_risk;
|
|
64866
65016
|
const risk_distribution = use_progressive ? {
|
|
64867
65017
|
enabled: true,
|
|
64868
65018
|
total_risk_budget: risk,
|
|
@@ -65331,7 +65481,6 @@ class Signal {
|
|
|
65331
65481
|
const defaultStopLoss = i2 === 0 ? stop_loss : _base;
|
|
65332
65482
|
const new_stop = kind === "long" ? this.support : stop_loss;
|
|
65333
65483
|
let risk_to_use = this.getZoneRisk(i2, limit_orders.length, this);
|
|
65334
|
-
console.log("index: ", i2, " risk: ", risk_to_use);
|
|
65335
65484
|
if (this.use_kelly) {
|
|
65336
65485
|
const func = this.kelly_func === "theoretical" ? calculateTheoreticalKelly : this.kelly_func === "position_based" ? calculatePositionBasedKelly : calculateTheoreticalKellyFixed;
|
|
65337
65486
|
const theoretical_kelly = func({
|
|
@@ -65575,6 +65724,9 @@ var pnl_default = value;
|
|
|
65575
65724
|
|
|
65576
65725
|
// src/helpers/trade_utils.ts
|
|
65577
65726
|
function to_f(value2, places = "%.1f") {
|
|
65727
|
+
if (!value2) {
|
|
65728
|
+
return null;
|
|
65729
|
+
}
|
|
65578
65730
|
let v = typeof value2 === "string" ? parseFloat(value2) : value2;
|
|
65579
65731
|
const formattedValue = places.replace("%.", "").replace("f", "");
|
|
65580
65732
|
return parseFloat(v.toFixed(parseInt(formattedValue)));
|
|
@@ -65805,7 +65957,7 @@ function buildConfig(app_config, {
|
|
|
65805
65957
|
min_avg_size = 0,
|
|
65806
65958
|
distribution,
|
|
65807
65959
|
distribution_params,
|
|
65808
|
-
use_progressive_risk
|
|
65960
|
+
use_progressive_risk = false
|
|
65809
65961
|
}) {
|
|
65810
65962
|
let fee = app_config.fee / 100;
|
|
65811
65963
|
let working_risk = risk || app_config.risk_per_trade;
|
|
@@ -65847,7 +65999,7 @@ function buildConfig(app_config, {
|
|
|
65847
65999
|
if (!stop) {
|
|
65848
66000
|
return [];
|
|
65849
66001
|
}
|
|
65850
|
-
const condition =
|
|
66002
|
+
const condition = true;
|
|
65851
66003
|
if (kind === "short") {}
|
|
65852
66004
|
const result = entry === stop ? [] : condition ? instance.build_entry({
|
|
65853
66005
|
current_price: entry,
|
|
@@ -66317,7 +66469,7 @@ function determineOptimumReward(payload) {
|
|
|
66317
66469
|
const criterion = app_config.strategy || "quantity";
|
|
66318
66470
|
const risk_rewards = createArray(low_range, high_range, 1);
|
|
66319
66471
|
let func = risk_rewards.map((trade_no) => {
|
|
66320
|
-
|
|
66472
|
+
const pp = {
|
|
66321
66473
|
take_profit: app_config.take_profit,
|
|
66322
66474
|
entry: app_config.entry,
|
|
66323
66475
|
stop: app_config.stop,
|
|
@@ -66329,7 +66481,8 @@ function determineOptimumReward(payload) {
|
|
|
66329
66481
|
decimal_places: app_config.decimal_places,
|
|
66330
66482
|
distribution,
|
|
66331
66483
|
distribution_params: payload.distribution_params
|
|
66332
|
-
}
|
|
66484
|
+
};
|
|
66485
|
+
let trades = sortedBuildConfig(app_config, pp);
|
|
66333
66486
|
let total = 0;
|
|
66334
66487
|
let max = -Infinity;
|
|
66335
66488
|
let min = Infinity;
|
|
@@ -66420,7 +66573,6 @@ function findIndexByCondition(lst, kind, condition, defaultKey = "neg_pnl") {
|
|
|
66420
66573
|
return b.net_diff - a.net_diff;
|
|
66421
66574
|
}
|
|
66422
66575
|
});
|
|
66423
|
-
console.log("found", sortedFound);
|
|
66424
66576
|
if (defaultKey === "quantity") {
|
|
66425
66577
|
return sortedFound[0].index;
|
|
66426
66578
|
}
|
|
@@ -67527,10 +67679,14 @@ function helperFuncToBuildTrades({
|
|
|
67527
67679
|
symbol_config,
|
|
67528
67680
|
app_config_kind,
|
|
67529
67681
|
appConfig,
|
|
67530
|
-
force_exact_risk = true
|
|
67682
|
+
force_exact_risk = true,
|
|
67683
|
+
use_default = false
|
|
67531
67684
|
}) {
|
|
67532
67685
|
const risk = custom_b_config.risk * (custom_b_config.risk_factor || 1);
|
|
67533
|
-
let result =
|
|
67686
|
+
let result = use_default ? {
|
|
67687
|
+
risk,
|
|
67688
|
+
risk_reward: custom_b_config.risk_reward
|
|
67689
|
+
} : getRiskReward({
|
|
67534
67690
|
entry: custom_b_config.entry,
|
|
67535
67691
|
stop: custom_b_config.stop,
|
|
67536
67692
|
risk,
|
|
@@ -67579,7 +67735,8 @@ function buildWithOptimumReward({
|
|
|
67579
67735
|
config: config2,
|
|
67580
67736
|
settings,
|
|
67581
67737
|
global_config,
|
|
67582
|
-
force_exact
|
|
67738
|
+
force_exact,
|
|
67739
|
+
use_default = false
|
|
67583
67740
|
}) {
|
|
67584
67741
|
const kind = config2.entry > config2.stop ? "long" : "short";
|
|
67585
67742
|
let stop = settings.stop;
|
|
@@ -67593,7 +67750,8 @@ function buildWithOptimumReward({
|
|
|
67593
67750
|
stop,
|
|
67594
67751
|
risk,
|
|
67595
67752
|
distribution,
|
|
67596
|
-
distribution_params
|
|
67753
|
+
distribution_params,
|
|
67754
|
+
risk_reward: settings.risk_reward || config2?.risk_reward
|
|
67597
67755
|
};
|
|
67598
67756
|
const appConfig = constructAppConfig2({
|
|
67599
67757
|
config: config2,
|
|
@@ -67604,7 +67762,8 @@ function buildWithOptimumReward({
|
|
|
67604
67762
|
app_config_kind: kind,
|
|
67605
67763
|
appConfig,
|
|
67606
67764
|
symbol_config: global_config,
|
|
67607
|
-
force_exact_risk: force_exact
|
|
67765
|
+
force_exact_risk: force_exact,
|
|
67766
|
+
use_default
|
|
67608
67767
|
});
|
|
67609
67768
|
const adjusted_size = summary.quantity;
|
|
67610
67769
|
const symbol_config = global_config;
|
|
@@ -67954,6 +68113,7 @@ class BaseExchange {
|
|
|
67954
68113
|
});
|
|
67955
68114
|
}
|
|
67956
68115
|
}
|
|
68116
|
+
async placeSpotLimitOrders(_payload) {}
|
|
67957
68117
|
async bulkPlaceLimitOrders(payload) {
|
|
67958
68118
|
const {
|
|
67959
68119
|
orders,
|
|
@@ -68540,8 +68700,25 @@ var ORDERS_PER_MINUTE = 2000;
|
|
|
68540
68700
|
var ORDERS_PER_SECOND = Math.floor(ORDERS_PER_MINUTE / 60);
|
|
68541
68701
|
var BATCH_SIZE = 5;
|
|
68542
68702
|
var CANCEL_BATCH_SIZE = 10;
|
|
68543
|
-
async function createLimitPurchaseOrdersParallel(client, symbol, priceFormat, quantityFormat,
|
|
68703
|
+
async function createLimitPurchaseOrdersParallel(client, symbol, priceFormat, quantityFormat, _orders, currentPrice, workingType = "last", realClose = false) {
|
|
68544
68704
|
const workingTypeValue = workingType === "mark" ? "MARK_PRICE" : "CONTRACT_PRICE";
|
|
68705
|
+
const orders = _orders.map((u) => {
|
|
68706
|
+
if (currentPrice && !u.stop) {
|
|
68707
|
+
if (u.kind === "long" && u.price > currentPrice && u.side === "buy") {
|
|
68708
|
+
return {
|
|
68709
|
+
...u,
|
|
68710
|
+
force_market: true
|
|
68711
|
+
};
|
|
68712
|
+
}
|
|
68713
|
+
if (u.kind === "short" && u.price < currentPrice && u.side === "sell") {
|
|
68714
|
+
return {
|
|
68715
|
+
...u,
|
|
68716
|
+
force_market: true
|
|
68717
|
+
};
|
|
68718
|
+
}
|
|
68719
|
+
}
|
|
68720
|
+
return u;
|
|
68721
|
+
});
|
|
68545
68722
|
const splitOrders = (inputOrders) => {
|
|
68546
68723
|
const result = [];
|
|
68547
68724
|
for (const o of inputOrders) {
|
|
@@ -68624,6 +68801,137 @@ async function createLimitPurchaseOrdersParallel(client, symbol, priceFormat, qu
|
|
|
68624
68801
|
})));
|
|
68625
68802
|
return results;
|
|
68626
68803
|
}
|
|
68804
|
+
async function createSpotLimitOrdersParallel(client, symbol, priceFormat, quantityFormat, orders, currentPrice) {
|
|
68805
|
+
const createSpotOrder = (x) => {
|
|
68806
|
+
const v = {
|
|
68807
|
+
symbol: symbol.toUpperCase(),
|
|
68808
|
+
price: x.price ? to_f(x.price, priceFormat) : undefined,
|
|
68809
|
+
quantity: x.quantity ? to_f(x.quantity, quantityFormat) : 0,
|
|
68810
|
+
side: x.side?.toUpperCase() || "",
|
|
68811
|
+
type: "LIMIT",
|
|
68812
|
+
timeInForce: "GTC"
|
|
68813
|
+
};
|
|
68814
|
+
if (x.stop && x.stop > 0) {
|
|
68815
|
+
v.type = "STOP_LOSS_LIMIT";
|
|
68816
|
+
v.stopPrice = to_f(x.stop, priceFormat);
|
|
68817
|
+
v.timeInForce = "GTC";
|
|
68818
|
+
}
|
|
68819
|
+
if (x.force_market || x.is_market) {
|
|
68820
|
+
delete v.price;
|
|
68821
|
+
delete v.timeInForce;
|
|
68822
|
+
v.type = "MARKET";
|
|
68823
|
+
}
|
|
68824
|
+
if (x.timeInForce) {
|
|
68825
|
+
v.timeInForce = x.timeInForce;
|
|
68826
|
+
}
|
|
68827
|
+
return v;
|
|
68828
|
+
};
|
|
68829
|
+
const newOrders = orders.map(createSpotOrder);
|
|
68830
|
+
const batches = [];
|
|
68831
|
+
for (let i2 = 0;i2 < newOrders.length; i2 += BATCH_SIZE) {
|
|
68832
|
+
batches.push(newOrders.slice(i2, i2 + BATCH_SIZE));
|
|
68833
|
+
}
|
|
68834
|
+
const limit = import_p_limit.default(ORDERS_PER_SECOND);
|
|
68835
|
+
const results = await Promise.all(batches.map((batch2) => limit(async () => {
|
|
68836
|
+
try {
|
|
68837
|
+
const batchResults = await Promise.all(batch2.map(async (orderPayload) => {
|
|
68838
|
+
const cleanPayload = Object.fromEntries(Object.entries(orderPayload).filter(([, value2]) => value2 !== undefined));
|
|
68839
|
+
return await client.submitNewOrder(cleanPayload);
|
|
68840
|
+
}));
|
|
68841
|
+
console.log("Spot batch result:", batchResults);
|
|
68842
|
+
return batchResults;
|
|
68843
|
+
} catch (error) {
|
|
68844
|
+
console.error("Error processing spot batch:", error);
|
|
68845
|
+
throw error;
|
|
68846
|
+
}
|
|
68847
|
+
})));
|
|
68848
|
+
return results.flat();
|
|
68849
|
+
}
|
|
68850
|
+
async function placeSpotLimitOrders(client, payload) {
|
|
68851
|
+
const { price_places = "%.8f", decimal_places = "%.8f", symbol } = payload;
|
|
68852
|
+
if (payload.cancel) {
|
|
68853
|
+
await cancelSpotOrders(client, symbol);
|
|
68854
|
+
}
|
|
68855
|
+
if (payload.orders && payload.orders.length > 0) {
|
|
68856
|
+
const orders = payload.orders.map((x) => ({
|
|
68857
|
+
side: x.side,
|
|
68858
|
+
price: x.price,
|
|
68859
|
+
quantity: x.quantity
|
|
68860
|
+
}));
|
|
68861
|
+
return await createSpotLimitOrdersParallel(client, symbol, price_places, decimal_places, orders);
|
|
68862
|
+
}
|
|
68863
|
+
}
|
|
68864
|
+
async function cancelSpotOrders(client, symbol, orders) {
|
|
68865
|
+
if (orders && orders.length > 0) {
|
|
68866
|
+
for (const order of orders) {
|
|
68867
|
+
try {
|
|
68868
|
+
if (order.orderId) {
|
|
68869
|
+
await client.cancelOrder({
|
|
68870
|
+
symbol,
|
|
68871
|
+
orderId: order.orderId
|
|
68872
|
+
});
|
|
68873
|
+
} else if (order.origClientOrderId) {
|
|
68874
|
+
await client.cancelOrder({
|
|
68875
|
+
symbol,
|
|
68876
|
+
origClientOrderId: order.origClientOrderId
|
|
68877
|
+
});
|
|
68878
|
+
}
|
|
68879
|
+
} catch (error) {
|
|
68880
|
+
console.error("Error cancelling spot order:", error);
|
|
68881
|
+
}
|
|
68882
|
+
}
|
|
68883
|
+
} else {
|
|
68884
|
+
try {
|
|
68885
|
+
const openOrders = await client.getOpenOrders({ symbol });
|
|
68886
|
+
for (const order of openOrders) {
|
|
68887
|
+
await client.cancelOrder({
|
|
68888
|
+
symbol,
|
|
68889
|
+
orderId: order.orderId
|
|
68890
|
+
});
|
|
68891
|
+
}
|
|
68892
|
+
} catch (error) {
|
|
68893
|
+
console.error("Error cancelling all spot orders:", error);
|
|
68894
|
+
}
|
|
68895
|
+
}
|
|
68896
|
+
}
|
|
68897
|
+
async function getSpotOpenOrders(client, symbol) {
|
|
68898
|
+
try {
|
|
68899
|
+
const response = await client.getOpenOrders({
|
|
68900
|
+
symbol
|
|
68901
|
+
});
|
|
68902
|
+
return (response || []).map((order) => ({
|
|
68903
|
+
...order,
|
|
68904
|
+
id: order.orderId,
|
|
68905
|
+
order_id: order.orderId,
|
|
68906
|
+
price: parseFloat(order.price),
|
|
68907
|
+
quantity: parseFloat(order.origQty),
|
|
68908
|
+
side: order.side.toLowerCase(),
|
|
68909
|
+
type: order.type.toLowerCase(),
|
|
68910
|
+
status: order.status.toLowerCase()
|
|
68911
|
+
}));
|
|
68912
|
+
} catch (error) {
|
|
68913
|
+
console.error("Error fetching spot open orders:", error);
|
|
68914
|
+
return [];
|
|
68915
|
+
}
|
|
68916
|
+
}
|
|
68917
|
+
async function getSpotBalances(client, assets) {
|
|
68918
|
+
try {
|
|
68919
|
+
const response = await client.getAccountInformation();
|
|
68920
|
+
let balances = response.balances.map((balance) => ({
|
|
68921
|
+
asset: balance.asset,
|
|
68922
|
+
free: parseFloat(balance.free),
|
|
68923
|
+
locked: parseFloat(balance.locked),
|
|
68924
|
+
total: parseFloat(balance.free) + parseFloat(balance.locked)
|
|
68925
|
+
}));
|
|
68926
|
+
if (assets && assets.length > 0) {
|
|
68927
|
+
balances = balances.filter((balance) => assets.some((asset) => asset.toUpperCase() === balance.asset.toUpperCase()));
|
|
68928
|
+
}
|
|
68929
|
+
return balances.filter((balance) => balance.total > 0);
|
|
68930
|
+
} catch (error) {
|
|
68931
|
+
console.error("Error fetching spot balances:", error);
|
|
68932
|
+
return [];
|
|
68933
|
+
}
|
|
68934
|
+
}
|
|
68627
68935
|
async function getPositionInfo(client, symbol) {
|
|
68628
68936
|
const response = await client.getPositionsV3({
|
|
68629
68937
|
symbol
|
|
@@ -69140,7 +69448,8 @@ class BinanceExchange extends BaseExchange {
|
|
|
69140
69448
|
return await cancelAllOrders(this.client, symbol, payload);
|
|
69141
69449
|
}
|
|
69142
69450
|
async _createLimitPurchaseOrders(payload) {
|
|
69143
|
-
|
|
69451
|
+
const current_price = await this.getCurrentPrice(payload.symbol);
|
|
69452
|
+
return await createLimitPurchaseOrdersParallel(this.client, payload.symbol, payload.price_places, payload.decimal_places, payload.orders, current_price);
|
|
69144
69453
|
}
|
|
69145
69454
|
async analyzeCharts(payload) {
|
|
69146
69455
|
return await analyzeCharts({
|
|
@@ -69475,6 +69784,51 @@ class BinanceExchange extends BaseExchange {
|
|
|
69475
69784
|
};
|
|
69476
69785
|
}
|
|
69477
69786
|
}
|
|
69787
|
+
async createSpotLimitOrders(payload) {
|
|
69788
|
+
if (!this.main_client) {
|
|
69789
|
+
throw new Error("Main client not available for spot trading");
|
|
69790
|
+
}
|
|
69791
|
+
return await createSpotLimitOrdersParallel(this.main_client, payload.symbol, payload.price_places || "%.8f", payload.decimal_places || "%.8f", payload.orders);
|
|
69792
|
+
}
|
|
69793
|
+
async placeSpotLimitOrders(payload) {
|
|
69794
|
+
if (!this.main_client) {
|
|
69795
|
+
throw new Error("Main client not available for spot trading");
|
|
69796
|
+
}
|
|
69797
|
+
return await placeSpotLimitOrders(this.main_client, payload);
|
|
69798
|
+
}
|
|
69799
|
+
async cancelSpotOrders(symbol, orders) {
|
|
69800
|
+
if (!this.main_client) {
|
|
69801
|
+
throw new Error("Main client not available for spot trading");
|
|
69802
|
+
}
|
|
69803
|
+
return await cancelSpotOrders(this.main_client, symbol, orders);
|
|
69804
|
+
}
|
|
69805
|
+
async getSpotOpenOrders(symbol) {
|
|
69806
|
+
if (!this.main_client) {
|
|
69807
|
+
throw new Error("Main client not available for spot trading");
|
|
69808
|
+
}
|
|
69809
|
+
return await getSpotOpenOrders(this.main_client, symbol);
|
|
69810
|
+
}
|
|
69811
|
+
async getSpotBalances(assets) {
|
|
69812
|
+
if (!this.main_client) {
|
|
69813
|
+
throw new Error("Main client not available for spot trading");
|
|
69814
|
+
}
|
|
69815
|
+
return await getSpotBalances(this.main_client, assets);
|
|
69816
|
+
}
|
|
69817
|
+
async getSpotCurrentPrice(symbol) {
|
|
69818
|
+
if (!this.main_client) {
|
|
69819
|
+
throw new Error("Main client not available for spot trading");
|
|
69820
|
+
}
|
|
69821
|
+
try {
|
|
69822
|
+
const response = await this.main_client.getSymbolPriceTicker({
|
|
69823
|
+
symbol: symbol.toUpperCase()
|
|
69824
|
+
});
|
|
69825
|
+
const price = Array.isArray(response) ? response[0]?.price : response.price;
|
|
69826
|
+
return parseFloat(price);
|
|
69827
|
+
} catch (error) {
|
|
69828
|
+
console.error("Error fetching spot current price:", error);
|
|
69829
|
+
throw error;
|
|
69830
|
+
}
|
|
69831
|
+
}
|
|
69478
69832
|
}
|
|
69479
69833
|
function getPricePlaces(target) {
|
|
69480
69834
|
const numStr = target.toString();
|
|
@@ -70813,7 +71167,7 @@ class ExchangePosition {
|
|
|
70813
71167
|
return compound_instance;
|
|
70814
71168
|
}
|
|
70815
71169
|
async getProxyForAccount() {
|
|
70816
|
-
if (this.instance
|
|
71170
|
+
if (this.instance?.expand?.proxy) {
|
|
70817
71171
|
const result = this.instance.expand.proxy;
|
|
70818
71172
|
const { type, ip_address } = result;
|
|
70819
71173
|
console.log("exchange", {
|
|
@@ -70882,11 +71236,21 @@ class ExchangePosition {
|
|
|
70882
71236
|
if (this.instance.expand?.b_config) {
|
|
70883
71237
|
return this.instance.expand.b_config;
|
|
70884
71238
|
}
|
|
70885
|
-
|
|
71239
|
+
const result = await this.app_db.getPositionConfig({
|
|
70886
71240
|
symbol: this.symbol,
|
|
70887
71241
|
kind: this.kind,
|
|
70888
71242
|
account: this.account
|
|
70889
71243
|
});
|
|
71244
|
+
if (!result) {
|
|
71245
|
+
const entry = this.kind === "long" ? this.symbol_config.resistance : this.symbol_config.support;
|
|
71246
|
+
const stop = this.kind === "long" ? this.symbol_config.support : this.symbol_config.resistance;
|
|
71247
|
+
const params = {
|
|
71248
|
+
entry,
|
|
71249
|
+
stop
|
|
71250
|
+
};
|
|
71251
|
+
return this.getConfig({ params });
|
|
71252
|
+
}
|
|
71253
|
+
return result;
|
|
70890
71254
|
}
|
|
70891
71255
|
async updateTargetPnl() {
|
|
70892
71256
|
const position2 = this.instance;
|
|
@@ -70967,7 +71331,6 @@ class ExchangePosition {
|
|
|
70967
71331
|
kelly_confidence_factor: config2.kelly?.kelly_confidence_factor,
|
|
70968
71332
|
kelly_minimum_risk: config2.kelly?.kelly_minimum_risk,
|
|
70969
71333
|
kelly_prediction_model: config2.kelly?.kelly_prediction_model,
|
|
70970
|
-
kelly_func: config2.kelly?.kelly_func,
|
|
70971
71334
|
distribution: payload.distribution || config2.distribution,
|
|
70972
71335
|
distribution_params: payload.distribution_params || config2.distribution_params
|
|
70973
71336
|
});
|
|
@@ -70985,7 +71348,6 @@ class ExchangePosition {
|
|
|
70985
71348
|
kelly_confidence_factor: config2.kelly?.kelly_confidence_factor,
|
|
70986
71349
|
kelly_minimum_risk: config2.kelly?.kelly_minimum_risk,
|
|
70987
71350
|
kelly_prediction_model: config2.kelly?.kelly_prediction_model,
|
|
70988
|
-
kelly_func: config2.kelly?.kelly_func,
|
|
70989
71351
|
distribution: config2.distribution,
|
|
70990
71352
|
distribution_params: payload.distribution_params || config2.distribution_params
|
|
70991
71353
|
}, false);
|
|
@@ -71550,8 +71912,8 @@ class ExchangePosition {
|
|
|
71550
71912
|
stop,
|
|
71551
71913
|
risk_reward,
|
|
71552
71914
|
risk,
|
|
71553
|
-
distribution: db_config
|
|
71554
|
-
distribution_params: db_config
|
|
71915
|
+
distribution: db_config?.distribution,
|
|
71916
|
+
distribution_params: db_config?.distribution_params
|
|
71555
71917
|
});
|
|
71556
71918
|
let config2 = generate_config_params(app_config, {
|
|
71557
71919
|
entry,
|
|
@@ -71663,19 +72025,21 @@ class ExchangePosition {
|
|
|
71663
72025
|
async buildTrades(payload) {
|
|
71664
72026
|
const { risk, use_progressive_risk } = payload;
|
|
71665
72027
|
const config2 = await this.getConfig();
|
|
72028
|
+
const entry = this.kind === "long" ? payload.resistance : payload.support;
|
|
72029
|
+
const stop = this.kind === "long" ? payload.support : payload.resistance;
|
|
71666
72030
|
const app_config = await this.buildAppConfig({
|
|
71667
|
-
entry: config2.entry,
|
|
71668
|
-
stop: config2.stop,
|
|
71669
|
-
risk_reward: config2.risk_reward,
|
|
72031
|
+
entry: entry || config2.entry,
|
|
72032
|
+
stop: stop || config2.stop,
|
|
72033
|
+
risk_reward: payload.risk_reward || config2.risk_reward,
|
|
71670
72034
|
risk: risk || config2.risk,
|
|
71671
72035
|
distribution: config2.distribution,
|
|
71672
72036
|
distribution_params: config2.distribution_params,
|
|
71673
72037
|
use_progressive_risk
|
|
71674
72038
|
});
|
|
71675
72039
|
const { trades } = await this.placeConfigOrders(app_config, {
|
|
71676
|
-
risk_reward: config2.risk_reward,
|
|
71677
|
-
entry: config2.entry,
|
|
71678
|
-
stop: config2.stop,
|
|
72040
|
+
risk_reward: payload.risk_reward || config2.risk_reward,
|
|
72041
|
+
entry: entry || config2.entry,
|
|
72042
|
+
stop: stop || config2.stop,
|
|
71679
72043
|
risk_per_trade: risk || config2.risk,
|
|
71680
72044
|
avg_size: 0,
|
|
71681
72045
|
neg_pnl: 0,
|
|
@@ -71684,7 +72048,6 @@ class ExchangePosition {
|
|
|
71684
72048
|
kelly_confidence_factor: config2.kelly?.kelly_confidence_factor,
|
|
71685
72049
|
kelly_minimum_risk: config2.kelly?.kelly_minimum_risk,
|
|
71686
72050
|
kelly_prediction_model: config2.kelly?.kelly_prediction_model,
|
|
71687
|
-
kelly_func: config2.kelly?.kelly_func,
|
|
71688
72051
|
distribution: config2.distribution,
|
|
71689
72052
|
distribution_params: config2.distribution_params,
|
|
71690
72053
|
use_progressive_risk
|
|
@@ -71718,9 +72081,11 @@ class ExchangePosition {
|
|
|
71718
72081
|
async tradeConfig(payload) {
|
|
71719
72082
|
const { override = {} } = payload;
|
|
71720
72083
|
const config2 = await this.getConfig();
|
|
72084
|
+
const entry = this.kind === "long" ? payload.resistance : payload.support;
|
|
72085
|
+
const stop = this.kind === "long" ? payload.support : payload.resistance;
|
|
71721
72086
|
const app_config = await this.buildAppConfig({
|
|
71722
|
-
entry: config2.entry,
|
|
71723
|
-
stop: config2.stop,
|
|
72087
|
+
entry: entry || config2.entry,
|
|
72088
|
+
stop: stop || config2.stop,
|
|
71724
72089
|
risk_reward: config2.risk_reward,
|
|
71725
72090
|
risk: config2.risk,
|
|
71726
72091
|
symbol: this.symbol,
|
|
@@ -71728,7 +72093,6 @@ class ExchangePosition {
|
|
|
71728
72093
|
kelly_confidence_factor: config2.kelly?.kelly_confidence_factor,
|
|
71729
72094
|
kelly_minimum_risk: config2.kelly?.kelly_minimum_risk,
|
|
71730
72095
|
kelly_prediction_model: config2.kelly?.kelly_prediction_model,
|
|
71731
|
-
kelly_func: config2.kelly?.kelly_func,
|
|
71732
72096
|
distribution: config2.distribution,
|
|
71733
72097
|
distribution_params: config2.distribution_params,
|
|
71734
72098
|
...override
|
|
@@ -71766,8 +72130,9 @@ class ExchangePosition {
|
|
|
71766
72130
|
ratio: payload.ratio
|
|
71767
72131
|
});
|
|
71768
72132
|
}
|
|
71769
|
-
async getOptimumRiskReward() {
|
|
71770
|
-
const app_config = await this.tradeConfig({});
|
|
72133
|
+
async getOptimumRiskReward(payload) {
|
|
72134
|
+
const app_config = await this.tradeConfig(payload || {});
|
|
72135
|
+
console.log("app_config", app_config);
|
|
71771
72136
|
const risk_reward = computeRiskReward({
|
|
71772
72137
|
app_config,
|
|
71773
72138
|
entry: app_config.entry,
|
|
@@ -71943,68 +72308,1549 @@ function convert_to_exchange_order(order) {
|
|
|
71943
72308
|
};
|
|
71944
72309
|
}
|
|
71945
72310
|
|
|
71946
|
-
// src/
|
|
71947
|
-
|
|
71948
|
-
|
|
71949
|
-
|
|
71950
|
-
|
|
71951
|
-
|
|
71952
|
-
|
|
71953
|
-
|
|
71954
|
-
|
|
71955
|
-
|
|
71956
|
-
|
|
71957
|
-
|
|
71958
|
-
this.app_db = options.app_db;
|
|
71959
|
-
this.main_exchange = options.main_exchange;
|
|
71960
|
-
}
|
|
71961
|
-
getDBInstance() {
|
|
71962
|
-
return this.app_db;
|
|
72311
|
+
// src/exchanges/paper/index.ts
|
|
72312
|
+
var import_https_proxy_agent3 = __toESM(require_dist2());
|
|
72313
|
+
var import_socks_proxy_agent3 = __toESM(require_dist3());
|
|
72314
|
+
|
|
72315
|
+
// src/exchanges/paper/engine-class.ts
|
|
72316
|
+
function generateSummary2({
|
|
72317
|
+
trades = [],
|
|
72318
|
+
fee_percent = 0.05,
|
|
72319
|
+
anchor
|
|
72320
|
+
}) {
|
|
72321
|
+
if (trades.length === 0) {
|
|
72322
|
+
return {};
|
|
71963
72323
|
}
|
|
71964
|
-
|
|
71965
|
-
|
|
71966
|
-
|
|
71967
|
-
|
|
71968
|
-
|
|
71969
|
-
|
|
71970
|
-
|
|
71971
|
-
|
|
71972
|
-
|
|
71973
|
-
|
|
71974
|
-
|
|
71975
|
-
|
|
71976
|
-
|
|
71977
|
-
|
|
71978
|
-
|
|
71979
|
-
|
|
71980
|
-
|
|
71981
|
-
|
|
71982
|
-
|
|
71983
|
-
|
|
71984
|
-
|
|
71985
|
-
|
|
71986
|
-
|
|
71987
|
-
|
|
71988
|
-
|
|
71989
|
-
|
|
71990
|
-
|
|
72324
|
+
const avg_entry = trades[0].avg_entry;
|
|
72325
|
+
const avg_size = trades[0].avg_size;
|
|
72326
|
+
const expected_fee = avg_entry * avg_size * fee_percent / 100;
|
|
72327
|
+
return {
|
|
72328
|
+
first_entry: trades.at(-1).entry,
|
|
72329
|
+
last_entry: trades[0].entry,
|
|
72330
|
+
quantity: avg_size,
|
|
72331
|
+
entry: avg_entry,
|
|
72332
|
+
loss: trades[0].neg_pnl,
|
|
72333
|
+
number_of_trades: trades.length,
|
|
72334
|
+
fee: to_f(expected_fee, "%.2f"),
|
|
72335
|
+
anchor_pnl: anchor?.target_pnl
|
|
72336
|
+
};
|
|
72337
|
+
}
|
|
72338
|
+
function cumulativeHelper({
|
|
72339
|
+
tradesList,
|
|
72340
|
+
pos = {
|
|
72341
|
+
quantity: 0,
|
|
72342
|
+
entry: 0
|
|
72343
|
+
},
|
|
72344
|
+
decimal_places = "%.3f"
|
|
72345
|
+
}) {
|
|
72346
|
+
let cumulativeQty = pos.quantity || 0;
|
|
72347
|
+
const reversedTrades = [...tradesList].reverse();
|
|
72348
|
+
const processedTrades = reversedTrades.map((trade) => {
|
|
72349
|
+
cumulativeQty += trade.quantity;
|
|
72350
|
+
const avg_size = to_f(cumulativeQty, decimal_places);
|
|
72351
|
+
return {
|
|
72352
|
+
...trade,
|
|
72353
|
+
cumulative_size: avg_size
|
|
72354
|
+
};
|
|
72355
|
+
});
|
|
72356
|
+
return processedTrades.reverse();
|
|
72357
|
+
}
|
|
72358
|
+
|
|
72359
|
+
class PositionState {
|
|
72360
|
+
position;
|
|
72361
|
+
trades;
|
|
72362
|
+
take_profit = null;
|
|
72363
|
+
stop_loss = null;
|
|
72364
|
+
entry_fees;
|
|
72365
|
+
fee_percent;
|
|
72366
|
+
global_config;
|
|
72367
|
+
constructor({
|
|
72368
|
+
position: position2,
|
|
72369
|
+
trades,
|
|
72370
|
+
fee_percent = 0,
|
|
72371
|
+
entry_fees = 0,
|
|
72372
|
+
global_config
|
|
72373
|
+
}) {
|
|
72374
|
+
this.position = position2;
|
|
72375
|
+
this.trades = trades ?? [];
|
|
72376
|
+
this.entry_fees = entry_fees ?? 0;
|
|
72377
|
+
this.fee_percent = fee_percent ?? 0;
|
|
72378
|
+
this.global_config = global_config;
|
|
72379
|
+
}
|
|
72380
|
+
get newTrades() {
|
|
72381
|
+
const tradesList = this.trades;
|
|
72382
|
+
const pos = this.position;
|
|
72383
|
+
if (!tradesList || tradesList.length === 0) {
|
|
72384
|
+
return [];
|
|
71991
72385
|
}
|
|
71992
|
-
|
|
71993
|
-
|
|
71994
|
-
|
|
71995
|
-
const
|
|
71996
|
-
|
|
71997
|
-
|
|
71998
|
-
|
|
72386
|
+
let cumulativeQty = pos.quantity || 0;
|
|
72387
|
+
let cumulativeValue = cumulativeQty * (pos.entry || 0);
|
|
72388
|
+
const reversedTrades = [...tradesList].reverse();
|
|
72389
|
+
const processedTrades = reversedTrades.map((trade) => {
|
|
72390
|
+
cumulativeQty += trade.quantity;
|
|
72391
|
+
cumulativeValue += trade.quantity * trade.entry;
|
|
72392
|
+
const avg_entry = cumulativeQty > 0 ? to_f(cumulativeValue / cumulativeQty, this.global_config.price_places) : 0;
|
|
72393
|
+
const avg_size = to_f(cumulativeQty, this.global_config.decimal_places);
|
|
72394
|
+
const stop = trade.stop || pos.kind === "long" ? Math.min(...tradesList.map((o) => o.entry)) : Math.max(...tradesList.map((o) => o.entry));
|
|
72395
|
+
const neg_pnl = to_f(Math.abs(avg_entry - stop) * avg_size, this.global_config.price_places);
|
|
72396
|
+
return {
|
|
72397
|
+
...trade,
|
|
72398
|
+
avg_entry,
|
|
72399
|
+
avg_size,
|
|
72400
|
+
neg_pnl
|
|
72401
|
+
};
|
|
71999
72402
|
});
|
|
72000
|
-
|
|
72001
|
-
|
|
72002
|
-
|
|
72403
|
+
return processedTrades.reverse();
|
|
72404
|
+
}
|
|
72405
|
+
generateSummary(fee_percentOverride) {
|
|
72406
|
+
const trades = this.newTrades;
|
|
72407
|
+
const fee_percent = fee_percentOverride ?? this.fee_percent;
|
|
72408
|
+
return {
|
|
72409
|
+
...generateSummary2({
|
|
72410
|
+
trades,
|
|
72411
|
+
fee_percent
|
|
72412
|
+
}),
|
|
72413
|
+
entry_fees: to_f(this.entry_fees, "%.2f")
|
|
72414
|
+
};
|
|
72415
|
+
}
|
|
72416
|
+
get summary() {
|
|
72417
|
+
return this.generateSummary();
|
|
72418
|
+
}
|
|
72419
|
+
positionAt(price) {
|
|
72420
|
+
const trades = this.newTrades;
|
|
72421
|
+
const kind = this.position.kind;
|
|
72422
|
+
const filtered = trades.filter((trade) => {
|
|
72423
|
+
if (kind === "long") {
|
|
72424
|
+
return trade.entry >= price;
|
|
72425
|
+
}
|
|
72426
|
+
return trade.entry <= price;
|
|
72427
|
+
});
|
|
72428
|
+
return filtered[0];
|
|
72429
|
+
}
|
|
72430
|
+
newPositionState(price) {
|
|
72431
|
+
const position2 = this.position;
|
|
72432
|
+
const trades = this.newTrades;
|
|
72433
|
+
const instance = this.positionAt(price);
|
|
72434
|
+
if (!instance) {
|
|
72435
|
+
return new PositionState({
|
|
72436
|
+
position: { ...position2 },
|
|
72437
|
+
trades,
|
|
72438
|
+
fee_percent: this.fee_percent,
|
|
72439
|
+
entry_fees: this.entry_fees,
|
|
72440
|
+
global_config: this.global_config
|
|
72441
|
+
});
|
|
72442
|
+
}
|
|
72443
|
+
return new PositionState({
|
|
72444
|
+
position: {
|
|
72445
|
+
...position2,
|
|
72446
|
+
entry: instance.avg_entry,
|
|
72447
|
+
quantity: instance.avg_size
|
|
72448
|
+
},
|
|
72449
|
+
trades,
|
|
72450
|
+
fee_percent: this.fee_percent,
|
|
72451
|
+
entry_fees: this.entry_fees,
|
|
72452
|
+
global_config: this.global_config
|
|
72453
|
+
});
|
|
72454
|
+
}
|
|
72455
|
+
updateTakeProfit({ entry, quantity }) {
|
|
72456
|
+
this.take_profit = {
|
|
72457
|
+
price: entry,
|
|
72458
|
+
quantity: quantity ?? this.position.quantity
|
|
72459
|
+
};
|
|
72460
|
+
}
|
|
72461
|
+
updateStopLoss(payload) {
|
|
72462
|
+
const { stop, quantity } = payload ?? {
|
|
72463
|
+
stop: this.newTrades[0].entry,
|
|
72464
|
+
quantity: this.summary.quantity
|
|
72465
|
+
};
|
|
72466
|
+
this.stop_loss = {
|
|
72467
|
+
price: stop,
|
|
72468
|
+
quantity
|
|
72469
|
+
};
|
|
72470
|
+
}
|
|
72471
|
+
}
|
|
72472
|
+
function determineNewPosition({
|
|
72473
|
+
position: position2,
|
|
72474
|
+
price,
|
|
72475
|
+
trades,
|
|
72476
|
+
global_config
|
|
72477
|
+
}) {
|
|
72478
|
+
const kind = position2.kind;
|
|
72479
|
+
const placed = trades.filter((t2) => {
|
|
72480
|
+
let _price = t2.entry || t2.price;
|
|
72481
|
+
let condition1 = kind === "long" ? _price >= price : _price <= price;
|
|
72482
|
+
return condition1;
|
|
72483
|
+
}).map((t2) => {
|
|
72484
|
+
let _price = t2.entry || t2.price;
|
|
72485
|
+
let condition2 = kind === "long" ? _price >= position2.entry : _price <= position2.entry;
|
|
72486
|
+
if (condition2) {
|
|
72487
|
+
return {
|
|
72488
|
+
...t2,
|
|
72489
|
+
price: position2.entry,
|
|
72490
|
+
entry: position2.entry
|
|
72491
|
+
};
|
|
72492
|
+
}
|
|
72493
|
+
return t2;
|
|
72494
|
+
});
|
|
72495
|
+
const placeQty = placed.reduce((acc, trade) => acc + trade.quantity, 0);
|
|
72496
|
+
if (position2.quantity > 0) {
|
|
72497
|
+
const placed_less_than_entry = placed.filter((t2) => {
|
|
72498
|
+
let condition1 = kind === "long" ? t2.entry <= position2.entry : t2.entry >= position2.entry;
|
|
72499
|
+
return condition1;
|
|
72500
|
+
});
|
|
72501
|
+
const avg = determine_average_entry_and_size(placed_less_than_entry.map((o) => ({
|
|
72502
|
+
price: o.entry || o.price,
|
|
72503
|
+
quantity: o.quantity
|
|
72504
|
+
})).concat([{ price: position2.entry, quantity: position2.quantity }]), global_config.decimal_places, global_config.price_places);
|
|
72505
|
+
position2.entry = avg.price;
|
|
72506
|
+
position2.quantity = avg.quantity;
|
|
72507
|
+
return position2;
|
|
72508
|
+
}
|
|
72509
|
+
return {
|
|
72510
|
+
...position2,
|
|
72511
|
+
entry: price,
|
|
72512
|
+
quantity: placeQty
|
|
72513
|
+
};
|
|
72514
|
+
}
|
|
72515
|
+
function positionAt({
|
|
72516
|
+
price,
|
|
72517
|
+
position: position2,
|
|
72518
|
+
trades = [],
|
|
72519
|
+
as_state = true,
|
|
72520
|
+
global_config
|
|
72521
|
+
}) {
|
|
72522
|
+
const kind = position2.kind;
|
|
72523
|
+
const fee_rate = {
|
|
72524
|
+
maker: 0.02,
|
|
72525
|
+
taker: 0.05
|
|
72526
|
+
};
|
|
72527
|
+
const placed = trades.filter((t2) => {
|
|
72528
|
+
let _price = t2.entry || t2.price;
|
|
72529
|
+
let condition1 = kind === "long" ? _price >= price : _price <= price;
|
|
72530
|
+
return condition1;
|
|
72531
|
+
});
|
|
72532
|
+
const not_placed = trades.filter((t2) => {
|
|
72533
|
+
let _price = t2.entry || t2.price;
|
|
72534
|
+
let condition1 = kind === "long" ? _price < price : _price > price;
|
|
72535
|
+
return condition1;
|
|
72536
|
+
});
|
|
72537
|
+
const placeQty = placed.reduce((acc, trade) => acc + trade.quantity, 0);
|
|
72538
|
+
const takerFee = price * placeQty * fee_rate.taker / 100;
|
|
72539
|
+
const makerFee = not_placed.reduce((acc, trade) => acc + trade.entry * trade.quantity * fee_rate.maker / 100, 0);
|
|
72540
|
+
const fee = to_f(takerFee + makerFee, "%.2f");
|
|
72541
|
+
const result = {
|
|
72542
|
+
trades: not_placed,
|
|
72543
|
+
position: determineNewPosition({
|
|
72544
|
+
global_config,
|
|
72545
|
+
position: { ...position2, kind },
|
|
72546
|
+
price,
|
|
72547
|
+
trades
|
|
72548
|
+
}),
|
|
72549
|
+
entry_fees: fee,
|
|
72550
|
+
fee_percent: fee_rate.maker,
|
|
72551
|
+
global_config
|
|
72552
|
+
};
|
|
72553
|
+
if (as_state) {
|
|
72554
|
+
const derivedState = new PositionState({
|
|
72555
|
+
position: result.position,
|
|
72556
|
+
trades: result.trades,
|
|
72557
|
+
fee_percent: result.fee_percent,
|
|
72558
|
+
entry_fees: parseFloat(result.entry_fees) || 0,
|
|
72559
|
+
global_config
|
|
72560
|
+
});
|
|
72561
|
+
return derivedState;
|
|
72562
|
+
}
|
|
72563
|
+
return result;
|
|
72564
|
+
}
|
|
72565
|
+
|
|
72566
|
+
class TradeEngine {
|
|
72567
|
+
trade_details = null;
|
|
72568
|
+
position;
|
|
72569
|
+
global_config;
|
|
72570
|
+
constructor({
|
|
72571
|
+
global_config,
|
|
72572
|
+
position: position2 = { entry: 0, quantity: 0, kind: "long" }
|
|
72573
|
+
}) {
|
|
72574
|
+
this.global_config = global_config;
|
|
72575
|
+
this.position = position2;
|
|
72576
|
+
}
|
|
72577
|
+
generateShortTrades(config2) {
|
|
72578
|
+
const result = compoundAPI.buildWithOptimumReward({
|
|
72579
|
+
global_config: this.global_config,
|
|
72580
|
+
config: config2,
|
|
72581
|
+
settings: config2,
|
|
72582
|
+
use_default: true
|
|
72583
|
+
});
|
|
72584
|
+
this.trade_details = result;
|
|
72585
|
+
}
|
|
72586
|
+
positionAt({
|
|
72587
|
+
price,
|
|
72588
|
+
dangerous,
|
|
72589
|
+
trades: existingTrades
|
|
72590
|
+
}) {
|
|
72591
|
+
const trades = existingTrades || this.trade_details?.trades || [];
|
|
72592
|
+
if (dangerous) {
|
|
72593
|
+
const kind = this.position.kind;
|
|
72594
|
+
const naive_assumptions = positionAt({
|
|
72595
|
+
price,
|
|
72596
|
+
position: {
|
|
72597
|
+
kind,
|
|
72598
|
+
entry: 0,
|
|
72599
|
+
quantity: 0
|
|
72600
|
+
},
|
|
72601
|
+
global_config: this.global_config,
|
|
72602
|
+
trades,
|
|
72603
|
+
as_state: false
|
|
72604
|
+
});
|
|
72605
|
+
const avg_position = determine_average_entry_and_size([
|
|
72606
|
+
{
|
|
72607
|
+
price: this.position.entry,
|
|
72608
|
+
quantity: this.position.quantity
|
|
72609
|
+
},
|
|
72610
|
+
{
|
|
72611
|
+
price: naive_assumptions.position.entry,
|
|
72612
|
+
quantity: naive_assumptions.position.quantity
|
|
72613
|
+
}
|
|
72614
|
+
], this.global_config.decimal_places, this.global_config.price_places);
|
|
72615
|
+
const new_position = dangerous ? {
|
|
72616
|
+
...this.position,
|
|
72617
|
+
entry: avg_position.entry,
|
|
72618
|
+
quantity: avg_position.quantity
|
|
72619
|
+
} : this.position;
|
|
72620
|
+
return new PositionState({
|
|
72621
|
+
...naive_assumptions,
|
|
72622
|
+
trades: naive_assumptions.trades,
|
|
72623
|
+
position: new_position,
|
|
72624
|
+
global_config: this.global_config
|
|
72625
|
+
});
|
|
72626
|
+
}
|
|
72627
|
+
const _trades = cumulativeHelper({
|
|
72628
|
+
tradesList: [...trades].concat({
|
|
72629
|
+
entry: this.position.entry,
|
|
72630
|
+
quantity: this.position.quantity
|
|
72631
|
+
}),
|
|
72632
|
+
decimal_places: this.global_config.decimal_places
|
|
72633
|
+
}).filter((o) => {
|
|
72634
|
+
const numeric = Number(o.cumulative_size);
|
|
72635
|
+
return numeric > this.position.quantity;
|
|
72636
|
+
});
|
|
72637
|
+
return positionAt({
|
|
72638
|
+
price,
|
|
72639
|
+
position: this.position,
|
|
72640
|
+
trades: _trades,
|
|
72641
|
+
global_config: this.global_config
|
|
72642
|
+
});
|
|
72643
|
+
}
|
|
72644
|
+
initializeEngine({
|
|
72645
|
+
price,
|
|
72646
|
+
dangerous,
|
|
72647
|
+
trades,
|
|
72648
|
+
config: config2
|
|
72649
|
+
}) {
|
|
72650
|
+
if (config2) {
|
|
72651
|
+
this.generateShortTrades(config2);
|
|
72652
|
+
}
|
|
72653
|
+
return this.positionAt({
|
|
72654
|
+
price,
|
|
72655
|
+
dangerous,
|
|
72656
|
+
trades
|
|
72657
|
+
});
|
|
72658
|
+
}
|
|
72659
|
+
}
|
|
72660
|
+
function transformTradesToExchangeType({
|
|
72661
|
+
trades,
|
|
72662
|
+
take_profit,
|
|
72663
|
+
stop_loss,
|
|
72664
|
+
kind
|
|
72665
|
+
}) {
|
|
72666
|
+
const currentTime = Date.now();
|
|
72667
|
+
const result = [];
|
|
72668
|
+
trades.forEach((trade, index) => {
|
|
72669
|
+
const side = kind === "long" ? "buy" : "sell";
|
|
72670
|
+
const positionSide = kind.toUpperCase();
|
|
72671
|
+
result.push({
|
|
72672
|
+
avgPrice: "0",
|
|
72673
|
+
clientOrderId: `${index}-${kind}-${side}`.toString(),
|
|
72674
|
+
closePosition: false,
|
|
72675
|
+
cumQuote: "0.0000",
|
|
72676
|
+
executedQty: 0,
|
|
72677
|
+
goodTillDate: 0,
|
|
72678
|
+
id: index,
|
|
72679
|
+
isStop: false,
|
|
72680
|
+
kind,
|
|
72681
|
+
orderId: index,
|
|
72682
|
+
order_id: index,
|
|
72683
|
+
order_type: "future",
|
|
72684
|
+
origQty: trade.quantity,
|
|
72685
|
+
origType: "LIMIT",
|
|
72686
|
+
positionSide,
|
|
72687
|
+
price: trade.entry,
|
|
72688
|
+
priceMatch: "NONE",
|
|
72689
|
+
priceProtect: false,
|
|
72690
|
+
qty: trade.quantity,
|
|
72691
|
+
quantity: trade.quantity,
|
|
72692
|
+
reduceOnly: false,
|
|
72693
|
+
selfTradePreventionMode: "EXPIRE_MAKER",
|
|
72694
|
+
side,
|
|
72695
|
+
status: "NEW",
|
|
72696
|
+
stop: trade.stop || 0,
|
|
72697
|
+
stopPrice: 0,
|
|
72698
|
+
symbol: undefined,
|
|
72699
|
+
time: currentTime,
|
|
72700
|
+
timeInForce: "GTC",
|
|
72701
|
+
triggerPrice: 0,
|
|
72702
|
+
type: "LIMIT",
|
|
72703
|
+
updateTime: currentTime,
|
|
72704
|
+
workingType: "CONTRACT_PRICE"
|
|
72705
|
+
});
|
|
72706
|
+
});
|
|
72707
|
+
let nextOrderId = trades.length;
|
|
72708
|
+
if (take_profit && take_profit.quantity > 0) {
|
|
72709
|
+
const tpSide = kind === "long" ? "sell" : "buy";
|
|
72710
|
+
const tpPositionSide = kind.toUpperCase();
|
|
72711
|
+
result.push({
|
|
72712
|
+
avgPrice: "0",
|
|
72713
|
+
clientOrderId: `tp_${nextOrderId}`,
|
|
72714
|
+
closePosition: false,
|
|
72715
|
+
cumQuote: "0.0000",
|
|
72716
|
+
executedQty: 0,
|
|
72717
|
+
goodTillDate: 0,
|
|
72718
|
+
id: nextOrderId,
|
|
72719
|
+
isStop: false,
|
|
72720
|
+
kind,
|
|
72721
|
+
orderId: nextOrderId,
|
|
72722
|
+
order_id: nextOrderId,
|
|
72723
|
+
order_type: "future",
|
|
72724
|
+
origQty: take_profit.quantity,
|
|
72725
|
+
origType: "LIMIT",
|
|
72726
|
+
positionSide: tpPositionSide,
|
|
72727
|
+
price: take_profit.price,
|
|
72728
|
+
priceMatch: "NONE",
|
|
72729
|
+
priceProtect: false,
|
|
72730
|
+
qty: take_profit.quantity,
|
|
72731
|
+
quantity: take_profit.quantity,
|
|
72732
|
+
reduceOnly: true,
|
|
72733
|
+
selfTradePreventionMode: "EXPIRE_MAKER",
|
|
72734
|
+
side: tpSide,
|
|
72735
|
+
status: "NEW",
|
|
72736
|
+
stop: 0,
|
|
72737
|
+
stopPrice: 0,
|
|
72738
|
+
symbol: undefined,
|
|
72739
|
+
time: currentTime,
|
|
72740
|
+
timeInForce: "GTC",
|
|
72741
|
+
triggerPrice: 0,
|
|
72742
|
+
type: "LIMIT",
|
|
72743
|
+
updateTime: currentTime,
|
|
72744
|
+
workingType: "CONTRACT_PRICE"
|
|
72745
|
+
});
|
|
72746
|
+
nextOrderId++;
|
|
72747
|
+
}
|
|
72748
|
+
if (stop_loss && stop_loss.quantity > 0) {
|
|
72749
|
+
const slSide = kind === "long" ? "sell" : "buy";
|
|
72750
|
+
const slPositionSide = kind.toUpperCase();
|
|
72751
|
+
result.push({
|
|
72752
|
+
avgPrice: "0",
|
|
72753
|
+
clientOrderId: `sl_${nextOrderId}`,
|
|
72754
|
+
closePosition: false,
|
|
72755
|
+
cumQuote: "0.0000",
|
|
72756
|
+
executedQty: 0,
|
|
72757
|
+
goodTillDate: 0,
|
|
72758
|
+
id: nextOrderId,
|
|
72759
|
+
isStop: true,
|
|
72760
|
+
kind,
|
|
72761
|
+
orderId: nextOrderId,
|
|
72762
|
+
order_id: nextOrderId,
|
|
72763
|
+
order_type: "future",
|
|
72764
|
+
origQty: stop_loss.quantity,
|
|
72765
|
+
origType: "LIMIT",
|
|
72766
|
+
positionSide: slPositionSide,
|
|
72767
|
+
price: stop_loss.price,
|
|
72768
|
+
priceMatch: "NONE",
|
|
72769
|
+
priceProtect: false,
|
|
72770
|
+
qty: stop_loss.quantity,
|
|
72771
|
+
quantity: stop_loss.quantity,
|
|
72772
|
+
reduceOnly: true,
|
|
72773
|
+
selfTradePreventionMode: "EXPIRE_MAKER",
|
|
72774
|
+
side: slSide,
|
|
72775
|
+
status: "NEW",
|
|
72776
|
+
stop: stop_loss.price,
|
|
72777
|
+
stopPrice: stop_loss.price,
|
|
72778
|
+
symbol: undefined,
|
|
72779
|
+
time: currentTime,
|
|
72780
|
+
timeInForce: "GTC",
|
|
72781
|
+
triggerPrice: stop_loss.price,
|
|
72782
|
+
type: "LIMIT",
|
|
72783
|
+
updateTime: currentTime,
|
|
72784
|
+
workingType: "CONTRACT_PRICE"
|
|
72785
|
+
});
|
|
72786
|
+
}
|
|
72787
|
+
return result;
|
|
72788
|
+
}
|
|
72789
|
+
function isStopLossOrder(order) {
|
|
72790
|
+
return order.kind === "long" && order.side === "sell" && order.stop > 0 || order.kind === "short" && order.side === "buy" && order.stop > 0;
|
|
72791
|
+
}
|
|
72792
|
+
function isTakeProfitOrder(order) {
|
|
72793
|
+
return order.kind === "long" && order.side === "sell" && order.stop === 0 || order.kind === "short" && order.side === "buy" && order.stop === 0;
|
|
72794
|
+
}
|
|
72795
|
+
function transformExchangeOrderToTradeWithValidation(orders, options) {
|
|
72796
|
+
const {
|
|
72797
|
+
validatePrice = true,
|
|
72798
|
+
validateQuantity = true,
|
|
72799
|
+
defaultStop = 0
|
|
72800
|
+
} = options || {};
|
|
72801
|
+
const trades = [];
|
|
72802
|
+
let take_profit;
|
|
72803
|
+
let stop_loss;
|
|
72804
|
+
orders.forEach((order, index) => {
|
|
72805
|
+
if (validatePrice && (typeof order.price !== "number" || order.price <= 0)) {
|
|
72806
|
+
throw new Error(`Invalid price at index ${index}: ${order.price}. Price must be a positive number`);
|
|
72807
|
+
}
|
|
72808
|
+
if (validateQuantity && (typeof order.quantity !== "number" || order.quantity <= 0)) {
|
|
72809
|
+
throw new Error(`Invalid quantity at index ${index}: ${order.quantity}. Quantity must be a positive number`);
|
|
72810
|
+
}
|
|
72811
|
+
if (order.status !== "NEW") {
|
|
72812
|
+
console.warn(`Order at index ${index} has status "${order.status}" - may not be suitable for trade conversion`);
|
|
72813
|
+
}
|
|
72814
|
+
if (isTakeProfitOrder(order) && !take_profit) {
|
|
72815
|
+
take_profit = {
|
|
72816
|
+
price: order.price,
|
|
72817
|
+
quantity: order.quantity
|
|
72818
|
+
};
|
|
72819
|
+
return;
|
|
72820
|
+
}
|
|
72821
|
+
if (isStopLossOrder(order) && !stop_loss) {
|
|
72822
|
+
stop_loss = {
|
|
72823
|
+
price: order.price,
|
|
72824
|
+
quantity: order.quantity
|
|
72825
|
+
};
|
|
72826
|
+
return;
|
|
72827
|
+
}
|
|
72828
|
+
trades.push({
|
|
72829
|
+
entry: order.price,
|
|
72830
|
+
quantity: order.quantity,
|
|
72831
|
+
avg_size: order.quantity,
|
|
72832
|
+
neg_pnl: 0,
|
|
72833
|
+
avg_entry: order.price,
|
|
72834
|
+
stop: stop_loss?.price || order.stop || defaultStop,
|
|
72835
|
+
reverse_avg_entry: order.price,
|
|
72836
|
+
reverse_avg_quantity: order.quantity,
|
|
72837
|
+
fee: 0,
|
|
72838
|
+
sell_price: undefined
|
|
72839
|
+
});
|
|
72840
|
+
});
|
|
72841
|
+
return {
|
|
72842
|
+
trades,
|
|
72843
|
+
take_profit,
|
|
72844
|
+
stop_loss
|
|
72845
|
+
};
|
|
72846
|
+
}
|
|
72847
|
+
function updatePositionBasedOffTpOrSl({
|
|
72848
|
+
kind,
|
|
72849
|
+
orders,
|
|
72850
|
+
positions,
|
|
72851
|
+
current_price
|
|
72852
|
+
}) {
|
|
72853
|
+
const position2 = positions[kind];
|
|
72854
|
+
const side_orders = orders.filter((o) => o.positionSide.toLowerCase() === kind);
|
|
72855
|
+
const { trades, take_profit, stop_loss } = transformExchangeOrderToTradeWithValidation(side_orders, {
|
|
72856
|
+
validatePrice: false,
|
|
72857
|
+
validateQuantity: false,
|
|
72858
|
+
defaultStop: 0
|
|
72859
|
+
});
|
|
72860
|
+
if (position2.quantity === 0) {
|
|
72861
|
+
return {
|
|
72862
|
+
trades,
|
|
72863
|
+
position: position2,
|
|
72864
|
+
orders: side_orders,
|
|
72865
|
+
changed: null
|
|
72866
|
+
};
|
|
72867
|
+
}
|
|
72868
|
+
if (take_profit && take_profit.quantity) {
|
|
72869
|
+
const should_close = kind === "long" ? current_price >= take_profit.price : current_price <= take_profit.price;
|
|
72870
|
+
if (should_close) {
|
|
72871
|
+
position2.quantity = Math.max(0, position2.quantity - take_profit.quantity);
|
|
72872
|
+
if (position2.quantity === 0) {
|
|
72873
|
+
position2.entry = 0;
|
|
72874
|
+
}
|
|
72875
|
+
return {
|
|
72876
|
+
trades,
|
|
72877
|
+
position: position2,
|
|
72878
|
+
orders: transformTradesToExchangeType({
|
|
72879
|
+
trades,
|
|
72880
|
+
kind,
|
|
72881
|
+
stop_loss
|
|
72882
|
+
}),
|
|
72883
|
+
changed: "tp"
|
|
72884
|
+
};
|
|
72885
|
+
}
|
|
72886
|
+
}
|
|
72887
|
+
if (stop_loss && stop_loss.quantity) {
|
|
72888
|
+
const should_close = kind === "long" ? current_price <= stop_loss.price : current_price >= stop_loss.price;
|
|
72889
|
+
if (should_close) {
|
|
72890
|
+
position2.quantity = Math.max(0, position2.quantity - stop_loss.quantity);
|
|
72891
|
+
if (position2.quantity === 0) {
|
|
72892
|
+
position2.entry = 0;
|
|
72893
|
+
}
|
|
72894
|
+
return {
|
|
72895
|
+
position: position2,
|
|
72896
|
+
orders: transformTradesToExchangeType({
|
|
72897
|
+
trades: trades.filter((t2) => kind === "long" ? t2.entry < stop_loss.price : t2.entry > stop_loss.price),
|
|
72898
|
+
kind,
|
|
72899
|
+
take_profit
|
|
72900
|
+
}),
|
|
72901
|
+
changed: "sl",
|
|
72902
|
+
trades
|
|
72903
|
+
};
|
|
72904
|
+
}
|
|
72905
|
+
}
|
|
72906
|
+
return {
|
|
72907
|
+
trades,
|
|
72908
|
+
position: position2,
|
|
72909
|
+
orders: side_orders,
|
|
72910
|
+
changed: null
|
|
72911
|
+
};
|
|
72912
|
+
}
|
|
72913
|
+
|
|
72914
|
+
// src/exchanges/paper/index.ts
|
|
72915
|
+
async function initClient3(credentials, options) {
|
|
72916
|
+
try {
|
|
72917
|
+
const [account, exchange] = credentials.api_key.split(",");
|
|
72918
|
+
const email = options.db?.email;
|
|
72919
|
+
const password = options.db?.password;
|
|
72920
|
+
async function initializeAppDb() {
|
|
72921
|
+
const db = await initPocketBaseClient({
|
|
72922
|
+
host: options.db?.host,
|
|
72923
|
+
email,
|
|
72924
|
+
password
|
|
72925
|
+
});
|
|
72926
|
+
const app_db2 = new AppDatabase(db, {
|
|
72927
|
+
email: options.db?.email,
|
|
72928
|
+
salt: undefined
|
|
72929
|
+
});
|
|
72930
|
+
return app_db2;
|
|
72931
|
+
}
|
|
72932
|
+
const app_db = await initializeAppDb();
|
|
72933
|
+
const exchange_credentials = await app_db.getCredentials({
|
|
72934
|
+
password
|
|
72935
|
+
});
|
|
72936
|
+
const account_detail = await app_db.get_exchange_db_instance({
|
|
72937
|
+
owner: account,
|
|
72938
|
+
exchange
|
|
72939
|
+
});
|
|
72940
|
+
const { type, ip_address } = account_detail.expand.proxy;
|
|
72941
|
+
const proxyAgent = type === "http" ? new import_https_proxy_agent3.HttpsProxyAgent(`http://${ip_address}`) : new import_socks_proxy_agent3.SocksProxyAgent(`socks5://${ip_address}`);
|
|
72942
|
+
const credential = exchange_credentials.find((c) => c.name === account && c.exchange === exchange);
|
|
72943
|
+
if (!credential) {
|
|
72944
|
+
throw new Error(`Missing API Key or Secret for account '${account}' in .env file. Please check your environment variables.`);
|
|
72945
|
+
}
|
|
72946
|
+
const clientFunc = initClient;
|
|
72947
|
+
const client = await clientFunc(credential, {
|
|
72948
|
+
type: "future",
|
|
72949
|
+
proxyAgent
|
|
72950
|
+
});
|
|
72951
|
+
return {
|
|
72952
|
+
client,
|
|
72953
|
+
app_db,
|
|
72954
|
+
initializeAppDb,
|
|
72955
|
+
owner: account
|
|
72956
|
+
};
|
|
72957
|
+
} catch (e2) {
|
|
72958
|
+
console.log(e2);
|
|
72959
|
+
}
|
|
72960
|
+
}
|
|
72961
|
+
function buildPosition3(position2, orders, options) {
|
|
72962
|
+
const { price_places = "%.1f", decimal_places = "%.3f" } = options;
|
|
72963
|
+
const entry = position2.entryPrice;
|
|
72964
|
+
let quantity = Math.abs(position2.positionAmt);
|
|
72965
|
+
const kind = position2.kind;
|
|
72966
|
+
if (Number.isNaN(quantity)) {
|
|
72967
|
+
quantity = 0;
|
|
72968
|
+
}
|
|
72969
|
+
const limitOrders = orders.filter((x) => x.kind === kind && !x.isStop).filter((o) => {
|
|
72970
|
+
return kind === "long" ? o.side === "buy" : o.side === "sell";
|
|
72971
|
+
});
|
|
72972
|
+
const avg = determine_average_entry_and_size([
|
|
72973
|
+
{ price: entry, quantity },
|
|
72974
|
+
...limitOrders.map((o) => ({ price: o.price, quantity: o.qty }))
|
|
72975
|
+
], decimal_places, price_places);
|
|
72976
|
+
const stopOrders = orders.filter((x) => x.kind === kind && x.isStop)[0];
|
|
72977
|
+
const tpOrders = orders.filter((x) => x.kind === kind && !x.isStop).filter((x) => x.kind === "long" ? x.side === "sell" : x.side === "buy")[0];
|
|
72978
|
+
const avg_entry = avg.entry;
|
|
72979
|
+
const stop_loss = stopOrders?.triggerPrice || 0;
|
|
72980
|
+
const take_profit = tpOrders?.price || 0;
|
|
72981
|
+
const tp_quantity = tpOrders?.qty || 0;
|
|
72982
|
+
const stop_quantity = stopOrders?.qty || 0;
|
|
72983
|
+
return {
|
|
72984
|
+
id: 0,
|
|
72985
|
+
kind,
|
|
72986
|
+
entry,
|
|
72987
|
+
symbol: position2.symbol,
|
|
72988
|
+
quantity,
|
|
72989
|
+
avg_entry,
|
|
72990
|
+
config_id: 0,
|
|
72991
|
+
stop_loss,
|
|
72992
|
+
take_profit,
|
|
72993
|
+
tp_quantity,
|
|
72994
|
+
stop_quantity,
|
|
72995
|
+
liquidation: position2.liquidationPrice ? to_f(position2.liquidationPrice, price_places) : 0
|
|
72996
|
+
};
|
|
72997
|
+
}
|
|
72998
|
+
var emptyPosition3 = {
|
|
72999
|
+
id: 0,
|
|
73000
|
+
kind: "long",
|
|
73001
|
+
entry: 0,
|
|
73002
|
+
symbol: "",
|
|
73003
|
+
quantity: 0,
|
|
73004
|
+
avg_entry: 0,
|
|
73005
|
+
config_id: 0,
|
|
73006
|
+
stop_loss: 0,
|
|
73007
|
+
take_profit: 0,
|
|
73008
|
+
tp_quantity: 0,
|
|
73009
|
+
stop_quantity: 0
|
|
73010
|
+
};
|
|
73011
|
+
async function getOpenOrders3(client, symbol, type) {
|
|
73012
|
+
const { orders } = await fetchDBExchangeData(client, symbol);
|
|
73013
|
+
if (type === "tp") {
|
|
73014
|
+
const tpOrders = orders.filter((x) => {
|
|
73015
|
+
const isLong = x.positionSide.toLowerCase() === "long";
|
|
73016
|
+
const isStop = x.stopPrice > 0;
|
|
73017
|
+
if (isLong) {
|
|
73018
|
+
return !isStop && x.side.toLowerCase() === "sell" && x.positionSide.toLowerCase() === "long";
|
|
73019
|
+
} else {
|
|
73020
|
+
return !isStop && x.side.toLowerCase() === "buy" && x.positionSide.toLowerCase() === "short";
|
|
73021
|
+
}
|
|
73022
|
+
});
|
|
73023
|
+
return tpOrders.map((x) => ({
|
|
73024
|
+
...x,
|
|
73025
|
+
price: x.price,
|
|
73026
|
+
qty: x.origQty,
|
|
73027
|
+
kind: x.positionSide.toLowerCase(),
|
|
73028
|
+
side: x.side.toLowerCase(),
|
|
73029
|
+
quantity: x.origQty
|
|
73030
|
+
}));
|
|
73031
|
+
}
|
|
73032
|
+
if (type === "limit") {
|
|
73033
|
+
const limitOrders = orders.filter((x) => {
|
|
73034
|
+
const isLong = x.positionSide.toLowerCase() === "long";
|
|
73035
|
+
const isStop = x.stopPrice > 0;
|
|
73036
|
+
if (isLong) {
|
|
73037
|
+
return !isStop && x.side.toLowerCase() === "buy" && x.positionSide.toLowerCase() === "long";
|
|
73038
|
+
} else {
|
|
73039
|
+
return !isStop && x.side.toLowerCase() === "sell" && x.positionSide.toLowerCase() === "short";
|
|
73040
|
+
}
|
|
73041
|
+
});
|
|
73042
|
+
return limitOrders.map((x) => ({
|
|
73043
|
+
...x,
|
|
73044
|
+
price: x.price,
|
|
73045
|
+
qty: x.origQty,
|
|
73046
|
+
kind: x.positionSide.toLowerCase(),
|
|
73047
|
+
side: x.side.toLowerCase(),
|
|
73048
|
+
quantity: x.origQty,
|
|
73049
|
+
id: x.orderId,
|
|
73050
|
+
order_id: x.orderId
|
|
73051
|
+
}));
|
|
73052
|
+
}
|
|
73053
|
+
if (type === "stop") {
|
|
73054
|
+
const stopOrders = orders.filter((x) => {
|
|
73055
|
+
const isLong = x.positionSide.toLowerCase() === "long";
|
|
73056
|
+
const isStop = x.stopPrice > 0;
|
|
73057
|
+
if (isLong) {
|
|
73058
|
+
return isStop && x.side.toLowerCase() === "sell" && x.positionSide.toLowerCase() === "long";
|
|
73059
|
+
} else {
|
|
73060
|
+
return isStop && x.side.toLowerCase() === "buy" && x.positionSide.toLowerCase() === "short";
|
|
73061
|
+
}
|
|
73062
|
+
});
|
|
73063
|
+
return stopOrders.map((x) => ({
|
|
73064
|
+
...x,
|
|
73065
|
+
price: x.price,
|
|
73066
|
+
triggerPrice: x.stopPrice,
|
|
73067
|
+
qty: x.origQty,
|
|
73068
|
+
kind: x.positionSide.toLowerCase(),
|
|
73069
|
+
side: x.side.toLowerCase(),
|
|
73070
|
+
quantity: x.origQty
|
|
73071
|
+
}));
|
|
73072
|
+
}
|
|
73073
|
+
return orders.map((x) => ({
|
|
73074
|
+
...x,
|
|
73075
|
+
price: x.price,
|
|
73076
|
+
qty: x.origQty,
|
|
73077
|
+
kind: x.positionSide.toLowerCase(),
|
|
73078
|
+
side: x.side.toLowerCase(),
|
|
73079
|
+
isStop: x.stopPrice > 0,
|
|
73080
|
+
order_type: "future",
|
|
73081
|
+
quantity: x.origQty,
|
|
73082
|
+
triggerPrice: x.stopPrice,
|
|
73083
|
+
stop: x.stopPrice,
|
|
73084
|
+
id: x.orderId,
|
|
73085
|
+
order_id: x.orderId
|
|
73086
|
+
}));
|
|
73087
|
+
}
|
|
73088
|
+
async function getWalletBalance3(client, asset = "USDT") {
|
|
73089
|
+
const response = await client.getBalanceV3();
|
|
73090
|
+
const balance = response.find((x) => x.asset.toLowerCase() === asset.toLowerCase())?.balance;
|
|
73091
|
+
return balance ? to_f(balance, "%.2f") : 0;
|
|
73092
|
+
}
|
|
73093
|
+
async function allWalletBalances3(client) {
|
|
73094
|
+
const response = await client.getBalanceV3();
|
|
73095
|
+
return response.map((x) => ({
|
|
73096
|
+
type: "future",
|
|
73097
|
+
asset: x.asset,
|
|
73098
|
+
balance: to_f(x.balance, "%.8f")
|
|
73099
|
+
}));
|
|
73100
|
+
}
|
|
73101
|
+
async function getLeverage2(client, symbol) {
|
|
73102
|
+
const response = await client.getPositions({
|
|
73103
|
+
symbol
|
|
73104
|
+
});
|
|
73105
|
+
const leverage = response[0]?.leverage;
|
|
73106
|
+
return leverage ? to_f(leverage, "%0f") : 0;
|
|
73107
|
+
}
|
|
73108
|
+
async function updateDbDetails(client, payload, current_account_info, new_payload) {
|
|
73109
|
+
const position2 = payload.kind === "long" ? current_account_info.long_position : current_account_info.short_position;
|
|
73110
|
+
const take_profit = new_payload.take_profit || (position2.tp_quantity > 0 ? {
|
|
73111
|
+
price: position2.take_profit,
|
|
73112
|
+
quantity: position2.tp_quantity
|
|
73113
|
+
} : undefined);
|
|
73114
|
+
const stop_loss = new_payload.stop_loss || (position2.stop_quantity > 0 ? {
|
|
73115
|
+
price: position2.stop_loss,
|
|
73116
|
+
quantity: position2.stop_quantity
|
|
73117
|
+
} : undefined);
|
|
73118
|
+
const as_exchange_orders = new_payload.raw ? new_payload.orders : transformTradesToExchangeType({
|
|
73119
|
+
trades: new_payload.orders,
|
|
73120
|
+
kind: payload.kind,
|
|
73121
|
+
take_profit,
|
|
73122
|
+
stop_loss
|
|
73123
|
+
});
|
|
73124
|
+
const reverse_kind = payload.kind === "long" ? "short" : "long";
|
|
73125
|
+
const reverse_orders = current_account_info.config.trades.exchange_info.open_orders.filter((o) => {
|
|
73126
|
+
return o.positionSide.toLowerCase() === reverse_kind;
|
|
73127
|
+
});
|
|
73128
|
+
current_account_info.config.trades.exchange_info.open_orders = as_exchange_orders.concat(reverse_orders);
|
|
73129
|
+
current_account_info.config.trades.exchange_info.positions[payload.kind] = {
|
|
73130
|
+
...position2,
|
|
73131
|
+
...new_payload.position
|
|
73132
|
+
};
|
|
73133
|
+
const app_db = await client.initializeAppDb();
|
|
73134
|
+
await app_db.createOrUpdateLiveExchangeInstance({
|
|
73135
|
+
account: {
|
|
73136
|
+
owner: client.owner,
|
|
73137
|
+
exchange: "paper"
|
|
73138
|
+
},
|
|
73139
|
+
symbol: payload.symbol,
|
|
73140
|
+
data: current_account_info
|
|
73141
|
+
});
|
|
73142
|
+
return current_account_info;
|
|
73143
|
+
}
|
|
73144
|
+
async function savePaperDetails(client, payload, new_payload) {
|
|
73145
|
+
const current_account_info = await fetchBinanceAccount2(client, {
|
|
73146
|
+
symbol: payload.symbol
|
|
73147
|
+
}, {
|
|
73148
|
+
price_places: payload.price_places,
|
|
73149
|
+
decimal_places: payload.decimal_places
|
|
73150
|
+
});
|
|
73151
|
+
return await updateDbDetails(client, payload, current_account_info, new_payload);
|
|
73152
|
+
}
|
|
73153
|
+
async function fetchDBExchangeData(client, symbol) {
|
|
73154
|
+
const current_price = await getCurrentPrice3(client.client, symbol);
|
|
73155
|
+
let orders = [];
|
|
73156
|
+
let trades = { long: [], short: [] };
|
|
73157
|
+
let positions = {
|
|
73158
|
+
long: null,
|
|
73159
|
+
short: null
|
|
73160
|
+
};
|
|
73161
|
+
let config2 = {
|
|
73162
|
+
long: null,
|
|
73163
|
+
short: null
|
|
73164
|
+
};
|
|
73165
|
+
const app_db = await client.initializeAppDb();
|
|
73166
|
+
const live_exchange_details = await app_db.getLiveExchangeInstance({
|
|
73167
|
+
account: {
|
|
73168
|
+
owner: client.owner,
|
|
73169
|
+
exchange: "paper"
|
|
73170
|
+
},
|
|
73171
|
+
symbol
|
|
73172
|
+
});
|
|
73173
|
+
const symbol_config = await app_db.getSymbolConfigFromDB(symbol);
|
|
73174
|
+
if (live_exchange_details) {
|
|
73175
|
+
const raw_data = live_exchange_details.data;
|
|
73176
|
+
orders = raw_data.config.trades.exchange_info.open_orders || [];
|
|
73177
|
+
positions = raw_data.config.trades.exchange_info.positions;
|
|
73178
|
+
const long_result = updatePositionBasedOffTpOrSl({
|
|
73179
|
+
current_price,
|
|
73180
|
+
orders,
|
|
73181
|
+
positions,
|
|
73182
|
+
kind: "long"
|
|
73183
|
+
});
|
|
73184
|
+
const short_result = updatePositionBasedOffTpOrSl({
|
|
73185
|
+
current_price,
|
|
73186
|
+
orders,
|
|
73187
|
+
positions,
|
|
73188
|
+
kind: "short"
|
|
73189
|
+
});
|
|
73190
|
+
orders = long_result.orders.concat(short_result.orders);
|
|
73191
|
+
positions.long = long_result.position;
|
|
73192
|
+
positions.short = short_result.position;
|
|
73193
|
+
live_exchange_details.data.config.trades.exchange_info.open_orders = orders;
|
|
73194
|
+
live_exchange_details.data.config.trades.exchange_info.positions = positions;
|
|
73195
|
+
trades = {
|
|
73196
|
+
long: long_result.trades,
|
|
73197
|
+
short: short_result.trades
|
|
73198
|
+
};
|
|
73199
|
+
}
|
|
73200
|
+
return {
|
|
73201
|
+
orders,
|
|
73202
|
+
symbol_config,
|
|
73203
|
+
positions,
|
|
73204
|
+
current_price,
|
|
73205
|
+
config: config2,
|
|
73206
|
+
trades,
|
|
73207
|
+
raw_data: live_exchange_details?.data
|
|
73208
|
+
};
|
|
73209
|
+
}
|
|
73210
|
+
async function getPositionInfo3(client, symbol) {
|
|
73211
|
+
const { positions, symbol_config, current_price, raw_data, trades } = await fetchDBExchangeData(client, symbol);
|
|
73212
|
+
const long_generator = new TradeEngine({
|
|
73213
|
+
position: {
|
|
73214
|
+
kind: "long",
|
|
73215
|
+
entry: positions.long?.entry || 0,
|
|
73216
|
+
quantity: positions.long?.quantity || 0
|
|
73217
|
+
},
|
|
73218
|
+
global_config: symbol_config
|
|
73219
|
+
});
|
|
73220
|
+
const short_generator = new TradeEngine({
|
|
73221
|
+
position: {
|
|
73222
|
+
kind: "short",
|
|
73223
|
+
entry: positions.short?.entry || 0,
|
|
73224
|
+
quantity: positions.short?.quantity || 0
|
|
73225
|
+
},
|
|
73226
|
+
global_config: symbol_config
|
|
73227
|
+
});
|
|
73228
|
+
const long_state = long_generator.initializeEngine({
|
|
73229
|
+
trades: trades.long,
|
|
73230
|
+
price: current_price
|
|
73231
|
+
});
|
|
73232
|
+
const short_state = short_generator.initializeEngine({
|
|
73233
|
+
trades: trades.short,
|
|
73234
|
+
price: current_price
|
|
73235
|
+
});
|
|
73236
|
+
if (raw_data) {
|
|
73237
|
+
const long_state_trades = long_state.newTrades;
|
|
73238
|
+
const short_state_trades = short_state.newTrades;
|
|
73239
|
+
if (trades.long.length !== long_state_trades.length && positions.long.quantity !== long_state.position.quantity) {
|
|
73240
|
+
console.log({
|
|
73241
|
+
old: trades.long.length,
|
|
73242
|
+
new: long_state_trades.length,
|
|
73243
|
+
position: long_state.position
|
|
73244
|
+
});
|
|
73245
|
+
let _position = {
|
|
73246
|
+
entry: long_state.position.entry,
|
|
73247
|
+
quantity: long_state.position.quantity
|
|
73248
|
+
};
|
|
73249
|
+
let tp = undefined;
|
|
73250
|
+
if (long_state_trades.length === 0 && positions.long.quantity === 0) {
|
|
73251
|
+
_position.entry = 0;
|
|
73252
|
+
_position.quantity = 0;
|
|
73253
|
+
long_state.position.entry = 0;
|
|
73254
|
+
long_state.position.quantity = 0;
|
|
73255
|
+
tp = {
|
|
73256
|
+
price: 0,
|
|
73257
|
+
quantity: 0
|
|
73258
|
+
};
|
|
73259
|
+
}
|
|
73260
|
+
await updateDbDetails(client, {
|
|
73261
|
+
kind: "long",
|
|
73262
|
+
symbol
|
|
73263
|
+
}, raw_data, {
|
|
73264
|
+
take_profit: tp,
|
|
73265
|
+
position: _position,
|
|
73266
|
+
orders: long_state_trades
|
|
73267
|
+
});
|
|
73268
|
+
}
|
|
73269
|
+
if (trades.short.length !== short_state_trades.length && positions.short.quantity !== short_state.position.quantity) {
|
|
73270
|
+
let _position = {
|
|
73271
|
+
entry: short_state.position.entry,
|
|
73272
|
+
quantity: short_state.position.quantity
|
|
73273
|
+
};
|
|
73274
|
+
let tp = undefined;
|
|
73275
|
+
if (short_state_trades.length === 0 && positions.short.quantity === 0) {
|
|
73276
|
+
_position.entry = 0;
|
|
73277
|
+
_position.quantity = 0;
|
|
73278
|
+
short_state.position.entry = 0;
|
|
73279
|
+
short_state.position.quantity = 0;
|
|
73280
|
+
tp = {
|
|
73281
|
+
price: 0,
|
|
73282
|
+
quantity: 0
|
|
73283
|
+
};
|
|
73284
|
+
}
|
|
73285
|
+
await updateDbDetails(client, {
|
|
73286
|
+
kind: "short",
|
|
73287
|
+
symbol
|
|
73288
|
+
}, raw_data, {
|
|
73289
|
+
position: _position,
|
|
73290
|
+
orders: short_state_trades,
|
|
73291
|
+
take_profit: tp
|
|
73292
|
+
});
|
|
73293
|
+
}
|
|
73294
|
+
}
|
|
73295
|
+
const long_position = long_state.position;
|
|
73296
|
+
const short_position = short_state.position;
|
|
73297
|
+
const long_quantity = long_position.quantity;
|
|
73298
|
+
const short_quantity = short_position.quantity;
|
|
73299
|
+
const long = {
|
|
73300
|
+
kind: "long",
|
|
73301
|
+
size: long_quantity,
|
|
73302
|
+
positionAmt: long_quantity,
|
|
73303
|
+
entryPrice: long_quantity === 0 ? 0 : long_position.entry,
|
|
73304
|
+
symbol
|
|
73305
|
+
};
|
|
73306
|
+
const short = {
|
|
73307
|
+
kind: "short",
|
|
73308
|
+
size: short_quantity,
|
|
73309
|
+
positionAmt: short_quantity,
|
|
73310
|
+
entryPrice: short_quantity === 0 ? 0 : short_position.entry,
|
|
73311
|
+
symbol
|
|
73312
|
+
};
|
|
73313
|
+
return { long, short };
|
|
73314
|
+
}
|
|
73315
|
+
async function fetchBinanceAccount2(client, json, options) {
|
|
73316
|
+
const [position2, balance, current_price, all_balances, leverage] = await Promise.all([
|
|
73317
|
+
getPositionInfo3(client, json.symbol),
|
|
73318
|
+
getWalletBalance3(client.client, json.symbol.toLowerCase().endsWith("usdt") ? "USDT" : "USDC"),
|
|
73319
|
+
getCurrentPrice3(client.client, json.symbol),
|
|
73320
|
+
allWalletBalances3(client.client),
|
|
73321
|
+
getLeverage2(client.client, json.symbol)
|
|
73322
|
+
]);
|
|
73323
|
+
const orders = await getOpenOrders3(client, json.symbol);
|
|
73324
|
+
const limitOrders = orders.filter((x) => !x.isStop).filter((o) => {
|
|
73325
|
+
return o.kind === "long" ? o.side === "buy" : o.side === "sell";
|
|
73326
|
+
});
|
|
73327
|
+
const stopOrders = orders.filter((x) => x.isStop);
|
|
73328
|
+
const long_position = buildPosition3(position2.long || {
|
|
73329
|
+
...emptyPosition3,
|
|
73330
|
+
kind: "long"
|
|
73331
|
+
}, orders, options);
|
|
73332
|
+
const short_position = buildPosition3(position2.short || {
|
|
73333
|
+
...emptyPosition3,
|
|
73334
|
+
kind: "short"
|
|
73335
|
+
}, orders, options);
|
|
73336
|
+
const result = {
|
|
73337
|
+
id: `${client.owner}-${json.symbol}-paper`,
|
|
73338
|
+
owner: client.owner,
|
|
73339
|
+
config: {
|
|
73340
|
+
symbol: json.symbol,
|
|
73341
|
+
id: 0,
|
|
73342
|
+
price_places: options.price_places,
|
|
73343
|
+
decimal_places: options.decimal_places,
|
|
73344
|
+
trades: {}
|
|
73345
|
+
},
|
|
73346
|
+
exchange: "paper",
|
|
73347
|
+
settings: {
|
|
73348
|
+
default_take_profit: 0,
|
|
73349
|
+
stop_buying: false
|
|
73350
|
+
},
|
|
73351
|
+
balance,
|
|
73352
|
+
long_position,
|
|
73353
|
+
short_position,
|
|
73354
|
+
current_price,
|
|
73355
|
+
orders: limitOrders,
|
|
73356
|
+
entries: [],
|
|
73357
|
+
stop_orders: stopOrders,
|
|
73358
|
+
symbol: json.symbol,
|
|
73359
|
+
leverage
|
|
73360
|
+
};
|
|
73361
|
+
result.config.trades = {
|
|
73362
|
+
exchange_info: {
|
|
73363
|
+
positions: {
|
|
73364
|
+
both: null,
|
|
73365
|
+
long: long_position,
|
|
73366
|
+
short: short_position
|
|
73367
|
+
},
|
|
73368
|
+
account_balances: all_balances,
|
|
73369
|
+
open_orders: orders
|
|
73370
|
+
}
|
|
73371
|
+
};
|
|
73372
|
+
return result;
|
|
73373
|
+
}
|
|
73374
|
+
async function getCurrentPrice3(client, symbol) {
|
|
73375
|
+
const response = await client.getSymbolPriceTickerV2({
|
|
73376
|
+
symbol
|
|
73377
|
+
});
|
|
73378
|
+
return response.price;
|
|
73379
|
+
}
|
|
73380
|
+
async function placeStopOrder3(client, payload) {
|
|
73381
|
+
const {
|
|
73382
|
+
price_places = "%.1f",
|
|
73383
|
+
decimal_places = "%.3f",
|
|
73384
|
+
symbol,
|
|
73385
|
+
current_price,
|
|
73386
|
+
kind,
|
|
73387
|
+
stop,
|
|
73388
|
+
quantity,
|
|
73389
|
+
cancel
|
|
73390
|
+
} = payload;
|
|
73391
|
+
if (kind === "long" && current_price < stop || kind === "short" && current_price > stop) {
|
|
73392
|
+
console.log(`Diverting to TP order for ${symbol} (${kind}). Current: ${current_price}, Stop: ${stop}`);
|
|
73393
|
+
return placeTpOrder3(client, {
|
|
73394
|
+
symbol,
|
|
73395
|
+
tp: stop,
|
|
73396
|
+
quantity,
|
|
73397
|
+
kind,
|
|
73398
|
+
cancel,
|
|
73399
|
+
price_places,
|
|
73400
|
+
decimal_places
|
|
73401
|
+
});
|
|
73402
|
+
}
|
|
73403
|
+
if (payload.cancel) {
|
|
73404
|
+
await cancelAllOrders3(client, symbol, {
|
|
73405
|
+
type: "stop",
|
|
73406
|
+
kind: payload.kind
|
|
73407
|
+
});
|
|
73408
|
+
if (payload.hedge) {
|
|
73409
|
+
const r_kind = payload.kind === "long" ? "short" : "long";
|
|
73410
|
+
await cancelAllOrders3(client, symbol, {
|
|
73411
|
+
type: "stop",
|
|
73412
|
+
kind: r_kind
|
|
73413
|
+
});
|
|
73414
|
+
}
|
|
73415
|
+
}
|
|
73416
|
+
const spread = 1.00005;
|
|
73417
|
+
const order = {
|
|
73418
|
+
kind: payload.kind,
|
|
73419
|
+
side: payload.kind === "long" ? "sell" : "buy",
|
|
73420
|
+
price: payload.kind === "long" ? payload.stop * spread ** -1 : payload.stop * spread,
|
|
73421
|
+
quantity: payload.quantity,
|
|
73422
|
+
stop: payload.final_stop,
|
|
73423
|
+
is_market: !payload.is_limit
|
|
73424
|
+
};
|
|
73425
|
+
if (payload.hedge) {
|
|
73426
|
+
let reverse_kind = payload.kind === "long" ? "short" : "long";
|
|
73427
|
+
order.kind = reverse_kind;
|
|
73428
|
+
order.is_market = false;
|
|
73429
|
+
}
|
|
73430
|
+
const current_account_info = await fetchBinanceAccount2(client, {
|
|
73431
|
+
symbol: payload.symbol
|
|
73432
|
+
}, {
|
|
73433
|
+
price_places: payload.price_places,
|
|
73434
|
+
decimal_places: payload.decimal_places
|
|
73435
|
+
});
|
|
73436
|
+
const position2 = order.kind === "long" ? current_account_info.long_position : current_account_info.short_position;
|
|
73437
|
+
position2.stop_loss = order.stop;
|
|
73438
|
+
position2.stop_quantity = order.quantity;
|
|
73439
|
+
const allOrders = await getOpenOrders3(client, symbol);
|
|
73440
|
+
const sameKindOrders = allOrders.filter((o) => o.positionSide.toLowerCase() === order.kind);
|
|
73441
|
+
const stop_loss = {
|
|
73442
|
+
price: order.stop,
|
|
73443
|
+
quantity: order.quantity
|
|
73444
|
+
};
|
|
73445
|
+
const { trades } = transformExchangeOrderToTradeWithValidation(sameKindOrders, {
|
|
73446
|
+
validateQuantity: false,
|
|
73447
|
+
validatePrice: false
|
|
73448
|
+
});
|
|
73449
|
+
return savePaperDetails(client, {
|
|
73450
|
+
kind: order.kind,
|
|
73451
|
+
symbol: payload.symbol,
|
|
73452
|
+
decimal_places: payload.decimal_places,
|
|
73453
|
+
price_places: payload.price_places
|
|
73454
|
+
}, {
|
|
73455
|
+
position: {},
|
|
73456
|
+
orders: trades,
|
|
73457
|
+
stop_loss
|
|
73458
|
+
});
|
|
73459
|
+
}
|
|
73460
|
+
async function placeTpOrder3(client, payload) {
|
|
73461
|
+
const {
|
|
73462
|
+
symbol,
|
|
73463
|
+
kind,
|
|
73464
|
+
price_places = "%.1f",
|
|
73465
|
+
decimal_places = "%.3f"
|
|
73466
|
+
} = payload;
|
|
73467
|
+
if (payload.cancel) {
|
|
73468
|
+
await cancelAllOrders3(client, symbol, {
|
|
73469
|
+
type: "tp",
|
|
73470
|
+
kind
|
|
73471
|
+
});
|
|
73472
|
+
}
|
|
73473
|
+
let _quantity = payload.quantity;
|
|
73474
|
+
if (!_quantity) {
|
|
73475
|
+
const positionInfo = await getPositionInfo3(client, symbol);
|
|
73476
|
+
_quantity = positionInfo[payload.kind]?.size || 0;
|
|
73477
|
+
}
|
|
73478
|
+
if (_quantity) {
|
|
73479
|
+
const order = {
|
|
73480
|
+
kind: payload.kind,
|
|
73481
|
+
side: payload.kind === "long" ? "sell" : "buy",
|
|
73482
|
+
price: payload.tp,
|
|
73483
|
+
quantity: _quantity
|
|
73484
|
+
};
|
|
73485
|
+
const current_account_info = await fetchBinanceAccount2(client, {
|
|
73486
|
+
symbol: payload.symbol
|
|
73487
|
+
}, {
|
|
73488
|
+
price_places: payload.price_places,
|
|
73489
|
+
decimal_places: payload.decimal_places
|
|
73490
|
+
});
|
|
73491
|
+
const position2 = order.kind === "long" ? current_account_info.long_position : current_account_info.short_position;
|
|
73492
|
+
position2.take_profit = order.price;
|
|
73493
|
+
position2.tp_quantity = order.quantity;
|
|
73494
|
+
const allOrders = await getOpenOrders3(client, symbol);
|
|
73495
|
+
const sameKindOrders = allOrders.filter((o) => o.positionSide.toLowerCase() === order.kind);
|
|
73496
|
+
const take_profit = {
|
|
73497
|
+
price: order.price,
|
|
73498
|
+
quantity: order.quantity
|
|
73499
|
+
};
|
|
73500
|
+
const { trades } = transformExchangeOrderToTradeWithValidation(sameKindOrders, {
|
|
73501
|
+
validateQuantity: false,
|
|
73502
|
+
validatePrice: false
|
|
73503
|
+
});
|
|
73504
|
+
return savePaperDetails(client, {
|
|
73505
|
+
kind: order.kind,
|
|
73506
|
+
symbol: payload.symbol,
|
|
73507
|
+
decimal_places,
|
|
73508
|
+
price_places
|
|
73509
|
+
}, {
|
|
73510
|
+
position: {},
|
|
73511
|
+
orders: trades,
|
|
73512
|
+
take_profit
|
|
73513
|
+
});
|
|
73514
|
+
}
|
|
73515
|
+
return { error: "No quantity" };
|
|
73516
|
+
}
|
|
73517
|
+
async function cancelAllOrders3(client, symbol, payload) {
|
|
73518
|
+
const { type, side, kind } = payload;
|
|
73519
|
+
const {
|
|
73520
|
+
orders: fullOrders,
|
|
73521
|
+
symbol_config,
|
|
73522
|
+
positions
|
|
73523
|
+
} = await fetchDBExchangeData(client, symbol);
|
|
73524
|
+
const allOrders = await getOpenOrders3(client, symbol, type);
|
|
73525
|
+
const sameKindOrders = fullOrders.filter((o) => o.positionSide.toLowerCase() === kind);
|
|
73526
|
+
const ordersToCancel = allOrders.filter((x) => side ? x.side === side : true).filter((x) => kind ? x.kind === kind : true);
|
|
73527
|
+
const excludeFromSameKind = sameKindOrders.filter((o) => !ordersToCancel.map((u) => u.orderId).includes(o.orderId));
|
|
73528
|
+
await savePaperDetails(client, {
|
|
73529
|
+
kind,
|
|
73530
|
+
symbol,
|
|
73531
|
+
decimal_places: symbol_config.decimal_places,
|
|
73532
|
+
price_places: symbol_config.price_places
|
|
73533
|
+
}, {
|
|
73534
|
+
position: positions[kind] || {},
|
|
73535
|
+
orders: excludeFromSameKind,
|
|
73536
|
+
raw: true,
|
|
73537
|
+
stop_loss: {
|
|
73538
|
+
price: 0,
|
|
73539
|
+
quantity: 0
|
|
73540
|
+
}
|
|
73541
|
+
});
|
|
73542
|
+
}
|
|
73543
|
+
async function cancelOrdersParallel2(payload) {
|
|
73544
|
+
const client = payload.custom_client;
|
|
73545
|
+
const { orders: fullOrders, symbol_config } = await fetchDBExchangeData(client, payload.symbol);
|
|
73546
|
+
const excludedOrders = fullOrders.filter((o) => !payload.orders.map((p) => p.clientOrderId || p.orderId).includes(o.clientOrderId));
|
|
73547
|
+
const long_orders = excludedOrders.filter((o) => o.positionSide.toLowerCase() === "long");
|
|
73548
|
+
const short_orders = excludedOrders.filter((o) => o.positionSide.toLowerCase() === "short");
|
|
73549
|
+
await savePaperDetails(client, {
|
|
73550
|
+
kind: "long",
|
|
73551
|
+
symbol: payload.symbol,
|
|
73552
|
+
decimal_places: symbol_config.decimal_places,
|
|
73553
|
+
price_places: symbol_config.price_places
|
|
73554
|
+
}, {
|
|
73555
|
+
position: {},
|
|
73556
|
+
raw: true,
|
|
73557
|
+
orders: long_orders
|
|
73558
|
+
});
|
|
73559
|
+
await savePaperDetails(client, {
|
|
73560
|
+
kind: "short",
|
|
73561
|
+
symbol: payload.symbol,
|
|
73562
|
+
decimal_places: symbol_config.decimal_places,
|
|
73563
|
+
price_places: symbol_config.price_places
|
|
73564
|
+
}, {
|
|
73565
|
+
position: {},
|
|
73566
|
+
raw: true,
|
|
73567
|
+
orders: short_orders
|
|
73568
|
+
});
|
|
73569
|
+
}
|
|
73570
|
+
async function cancelOrders3(payload) {
|
|
73571
|
+
return await cancelOrdersParallel2({
|
|
73572
|
+
symbol: payload.symbol,
|
|
73573
|
+
orders: payload.orders,
|
|
73574
|
+
custom_client: payload.custom_client
|
|
73575
|
+
});
|
|
73576
|
+
}
|
|
73577
|
+
async function createLimitPurchaseOrders2(client, payload) {
|
|
73578
|
+
const { positions, symbol_config, current_price, raw_data } = await fetchDBExchangeData(client, payload.symbol);
|
|
73579
|
+
const kind = payload.orders[0].kind || payload.kind;
|
|
73580
|
+
const params = {
|
|
73581
|
+
position: {
|
|
73582
|
+
kind,
|
|
73583
|
+
entry: positions[kind]?.entry || 0,
|
|
73584
|
+
quantity: positions[kind]?.quantity || 0
|
|
73585
|
+
},
|
|
73586
|
+
global_config: symbol_config
|
|
73587
|
+
};
|
|
73588
|
+
const generator = new TradeEngine(params);
|
|
73589
|
+
const state = generator.initializeEngine({
|
|
73590
|
+
trades: payload.orders,
|
|
73591
|
+
price: current_price
|
|
73592
|
+
});
|
|
73593
|
+
const current_position_state = {
|
|
73594
|
+
entry: state.position.entry,
|
|
73595
|
+
quantity: state.position.quantity
|
|
73596
|
+
};
|
|
73597
|
+
const remaining_orders = state.newTrades;
|
|
73598
|
+
console.log({
|
|
73599
|
+
length: remaining_orders.length,
|
|
73600
|
+
position: current_position_state,
|
|
73601
|
+
last: remaining_orders[remaining_orders.length - 1]
|
|
73602
|
+
});
|
|
73603
|
+
return await updateDbDetails(client, {
|
|
73604
|
+
kind,
|
|
73605
|
+
symbol: payload.symbol
|
|
73606
|
+
}, raw_data, {
|
|
73607
|
+
position: current_position_state,
|
|
73608
|
+
orders: remaining_orders
|
|
73609
|
+
});
|
|
73610
|
+
return await savePaperDetails(client, {
|
|
73611
|
+
kind,
|
|
73612
|
+
symbol: payload.symbol,
|
|
73613
|
+
price_places: payload.price_places,
|
|
73614
|
+
decimal_places: payload.decimal_places
|
|
73615
|
+
}, {
|
|
73616
|
+
position: current_position_state,
|
|
73617
|
+
orders: remaining_orders
|
|
73618
|
+
});
|
|
73619
|
+
}
|
|
73620
|
+
|
|
73621
|
+
class PaperBinanceExchange extends BaseExchange {
|
|
73622
|
+
constructor(client) {
|
|
73623
|
+
super(client);
|
|
73624
|
+
this.name = "paper";
|
|
73625
|
+
this.client = client;
|
|
73626
|
+
}
|
|
73627
|
+
async getPositionInfo(symbol) {
|
|
73628
|
+
return await getPositionInfo3(this.client, symbol);
|
|
73629
|
+
}
|
|
73630
|
+
async getCurrentPrice(symbol) {
|
|
73631
|
+
return await getCurrentPrice3(this.client.client, symbol);
|
|
73632
|
+
}
|
|
73633
|
+
async cancelAllOrders(symbol, payload) {
|
|
73634
|
+
return await cancelAllOrders3(this.client, symbol, payload);
|
|
73635
|
+
}
|
|
73636
|
+
async _createLimitPurchaseOrders(payload) {
|
|
73637
|
+
return await createLimitPurchaseOrders2(this.client, payload);
|
|
73638
|
+
}
|
|
73639
|
+
async analyzeCharts(_payload) {}
|
|
73640
|
+
async getExchangeInfo(options) {
|
|
73641
|
+
return await fetchBinanceAccount2(this.client, {
|
|
73642
|
+
symbol: options.symbol
|
|
73643
|
+
}, {
|
|
73644
|
+
price_places: options.price_places,
|
|
73645
|
+
decimal_places: options.decimal_places
|
|
73646
|
+
});
|
|
73647
|
+
}
|
|
73648
|
+
async _cancelOrders(payload) {
|
|
73649
|
+
return await cancelOrders3({
|
|
73650
|
+
symbol: payload.symbol,
|
|
73651
|
+
orders: payload.orders,
|
|
73652
|
+
custom_client: this.client
|
|
73653
|
+
});
|
|
73654
|
+
}
|
|
73655
|
+
async _placeTpOrder(payload) {
|
|
73656
|
+
return await placeTpOrder3(this.client, payload);
|
|
73657
|
+
}
|
|
73658
|
+
async placeLimitOrders(payload) {
|
|
73659
|
+
const { price_places = "%.1f", decimal_places = "%.3f", symbol } = payload;
|
|
73660
|
+
if (payload.cancel) {
|
|
73661
|
+
await cancelAllOrders3(this.client, symbol, {
|
|
73662
|
+
type: "limit",
|
|
73663
|
+
kind: payload.kind
|
|
73664
|
+
});
|
|
73665
|
+
}
|
|
73666
|
+
if (payload.orders) {
|
|
73667
|
+
const orders = payload.orders.map((x) => ({
|
|
73668
|
+
side: payload.kind === "long" ? "buy" : "sell",
|
|
73669
|
+
price: x.entry,
|
|
73670
|
+
quantity: x.quantity,
|
|
73671
|
+
kind: payload.kind
|
|
73672
|
+
}));
|
|
73673
|
+
return await createLimitPurchaseOrders2(this.client, {
|
|
73674
|
+
symbol,
|
|
73675
|
+
kind: payload.kind,
|
|
73676
|
+
price_places,
|
|
73677
|
+
decimal_places,
|
|
73678
|
+
orders
|
|
73679
|
+
});
|
|
73680
|
+
}
|
|
73681
|
+
}
|
|
73682
|
+
async _placeStopOrder(payload) {
|
|
73683
|
+
return await placeStopOrder3(this.client, payload);
|
|
73684
|
+
}
|
|
73685
|
+
async setLeverage(payload) {
|
|
73686
|
+
return payload.leverage;
|
|
73687
|
+
}
|
|
73688
|
+
async generateConfig(_payload) {
|
|
73689
|
+
return {};
|
|
73690
|
+
}
|
|
73691
|
+
async checkDelistedMovers(payload) {
|
|
73692
|
+
return { movers: [], delisted: [] };
|
|
73693
|
+
}
|
|
73694
|
+
async getAllOpenOrders() {
|
|
73695
|
+
return [];
|
|
73696
|
+
}
|
|
73697
|
+
async getDelistedSpotSymbols() {
|
|
73698
|
+
return [];
|
|
73699
|
+
}
|
|
73700
|
+
async getOpenPositions() {
|
|
73701
|
+
const response = [];
|
|
73702
|
+
return Array.from(new Set(response.map((x) => x.symbol)));
|
|
73703
|
+
}
|
|
73704
|
+
async getOpenOrders(payload) {
|
|
73705
|
+
return await getOpenOrders3(this.client, payload.symbol);
|
|
73706
|
+
}
|
|
73707
|
+
async createLimitPurchaseOrders(payload) {
|
|
73708
|
+
const {
|
|
73709
|
+
orders,
|
|
73710
|
+
kind,
|
|
73711
|
+
decimal_places = "%.3f",
|
|
73712
|
+
price_places = "%.1f",
|
|
73713
|
+
symbol
|
|
73714
|
+
} = payload;
|
|
73715
|
+
const order_payload = orders.map((order) => ({
|
|
73716
|
+
...order,
|
|
73717
|
+
price: order.entry,
|
|
73718
|
+
kind,
|
|
73719
|
+
side: kind.toLowerCase() === "long" ? "buy" : "sell",
|
|
73720
|
+
stop: undefined
|
|
73721
|
+
}));
|
|
73722
|
+
return await this._createLimitPurchaseOrders({
|
|
73723
|
+
symbol,
|
|
73724
|
+
price_places,
|
|
73725
|
+
decimal_places,
|
|
73726
|
+
orders: order_payload,
|
|
73727
|
+
kind
|
|
73728
|
+
});
|
|
73729
|
+
}
|
|
73730
|
+
async forceClosePosition(symbol, options) {
|
|
73731
|
+
return await forceClosePosition2(this.client, symbol, options);
|
|
73732
|
+
}
|
|
73733
|
+
async getObservableState(_payload) {
|
|
73734
|
+
const { kind } = _payload;
|
|
73735
|
+
const {
|
|
73736
|
+
positions,
|
|
73737
|
+
symbol_config,
|
|
73738
|
+
current_price,
|
|
73739
|
+
orders: _orders,
|
|
73740
|
+
trades,
|
|
73741
|
+
config: config2
|
|
73742
|
+
} = await fetchDBExchangeData(this.client, _payload.symbol);
|
|
73743
|
+
const generator = new TradeEngine({
|
|
73744
|
+
position: {
|
|
73745
|
+
kind,
|
|
73746
|
+
entry: positions[kind]?.entry || 0,
|
|
73747
|
+
quantity: positions[kind]?.quantity || 0
|
|
73748
|
+
},
|
|
73749
|
+
global_config: symbol_config
|
|
73750
|
+
});
|
|
73751
|
+
const long_state = generator.initializeEngine({
|
|
73752
|
+
config: config2[kind],
|
|
73753
|
+
price: current_price,
|
|
73754
|
+
trades: trades.long
|
|
73755
|
+
});
|
|
73756
|
+
return long_state;
|
|
73757
|
+
}
|
|
73758
|
+
async crossAccountTransfer(payload) {}
|
|
73759
|
+
async placeMarketOrder(payload) {
|
|
73760
|
+
return this.forceClosePosition(payload.symbol, {
|
|
73761
|
+
kind: payload.kind,
|
|
73762
|
+
price_places: payload.price_places,
|
|
73763
|
+
decimal_places: payload.decimal_places
|
|
73764
|
+
});
|
|
73765
|
+
}
|
|
73766
|
+
}
|
|
73767
|
+
async function forceClosePosition2(client, symbol, options) {
|
|
73768
|
+
const { price_places = "%.1f", decimal_places = "%.3f" } = options || {};
|
|
73769
|
+
await savePaperDetails(client, {
|
|
73770
|
+
symbol,
|
|
73771
|
+
kind: options.kind,
|
|
73772
|
+
decimal_places,
|
|
73773
|
+
price_places
|
|
73774
|
+
}, {
|
|
73775
|
+
raw: true,
|
|
73776
|
+
position: {
|
|
73777
|
+
entry: 0,
|
|
73778
|
+
quantity: 0
|
|
73779
|
+
},
|
|
73780
|
+
take_profit: {
|
|
73781
|
+
price: 0,
|
|
73782
|
+
quantity: 0
|
|
73783
|
+
},
|
|
73784
|
+
stop_loss: {
|
|
73785
|
+
price: 0,
|
|
73786
|
+
quantity: 0
|
|
73787
|
+
},
|
|
73788
|
+
orders: []
|
|
73789
|
+
});
|
|
73790
|
+
}
|
|
73791
|
+
|
|
73792
|
+
// src/exchange-account.ts
|
|
73793
|
+
class ExchangeAccount {
|
|
73794
|
+
instance;
|
|
73795
|
+
exchange;
|
|
73796
|
+
main_exchange;
|
|
73797
|
+
app_db;
|
|
73798
|
+
long_position;
|
|
73799
|
+
short_position;
|
|
73800
|
+
raw_positions;
|
|
73801
|
+
constructor(payload, options) {
|
|
73802
|
+
this.instance = payload;
|
|
73803
|
+
this.exchange = options.exchange;
|
|
73804
|
+
this.app_db = options.app_db;
|
|
73805
|
+
this.main_exchange = options.main_exchange;
|
|
73806
|
+
}
|
|
73807
|
+
getDBInstance() {
|
|
73808
|
+
return this.app_db;
|
|
73809
|
+
}
|
|
73810
|
+
async getLiveExchangeInstance(payload) {
|
|
73811
|
+
const symbol_config = await this.recomputeSymbolConfig({
|
|
73812
|
+
symbol: payload.symbol,
|
|
73813
|
+
refresh: payload.refresh_symbol_config
|
|
73814
|
+
});
|
|
73815
|
+
const result = await this.app_db.getLiveExchangeInstance({
|
|
73816
|
+
account: this.instance,
|
|
73817
|
+
symbol: payload.symbol
|
|
73818
|
+
});
|
|
73819
|
+
if (payload.refresh || !result) {
|
|
73820
|
+
const data = await this.exchange.getExchangeAccountInfo({
|
|
73821
|
+
account: this.instance,
|
|
73822
|
+
symbol: payload.symbol,
|
|
73823
|
+
price_places: symbol_config?.price_places,
|
|
73824
|
+
decimal_places: symbol_config?.decimal_places
|
|
73825
|
+
});
|
|
73826
|
+
if (symbol_config?.price_places) {
|
|
73827
|
+
data.config.price_places = symbol_config.price_places;
|
|
73828
|
+
}
|
|
73829
|
+
if (symbol_config?.decimal_places) {
|
|
73830
|
+
data.config.decimal_places = symbol_config.decimal_places;
|
|
73831
|
+
}
|
|
73832
|
+
return await this.app_db.createOrUpdateLiveExchangeInstance({
|
|
73833
|
+
account: this.instance,
|
|
73834
|
+
symbol: payload.symbol,
|
|
73835
|
+
data
|
|
73836
|
+
});
|
|
73837
|
+
}
|
|
73838
|
+
return result;
|
|
73839
|
+
}
|
|
73840
|
+
async initializePositions(payload) {
|
|
73841
|
+
const raw_positions = await this.syncAccount({
|
|
73842
|
+
update: payload.update,
|
|
73843
|
+
symbol: payload.symbol,
|
|
73844
|
+
live_refresh: payload.update
|
|
73845
|
+
});
|
|
73846
|
+
const positions = await this.syncAccount({
|
|
73847
|
+
symbol: payload.symbol,
|
|
73848
|
+
as_view: true
|
|
72003
73849
|
});
|
|
72004
73850
|
const active_account = await this.getActiveAccount({
|
|
72005
73851
|
symbol: payload.symbol
|
|
72006
73852
|
});
|
|
72007
|
-
console.log("
|
|
73853
|
+
console.log("positions", raw_positions);
|
|
72008
73854
|
const long_position = positions.find((x) => x.kind === "long");
|
|
72009
73855
|
const short_position = positions.find((x) => x.kind === "short");
|
|
72010
73856
|
this.long_position = new ExchangePosition({
|
|
@@ -72030,7 +73876,7 @@ class ExchangeAccount {
|
|
|
72030
73876
|
}
|
|
72031
73877
|
async getActiveAccount(payload) {
|
|
72032
73878
|
const { symbol, full = false, refresh = false } = payload;
|
|
72033
|
-
|
|
73879
|
+
let symbol_config = await this.recomputeSymbolConfig({
|
|
72034
73880
|
symbol
|
|
72035
73881
|
});
|
|
72036
73882
|
const live_exchange_instance = await this.getLiveExchangeInstance({
|
|
@@ -72038,6 +73884,8 @@ class ExchangeAccount {
|
|
|
72038
73884
|
refresh
|
|
72039
73885
|
});
|
|
72040
73886
|
const raw_active_account = live_exchange_instance.data;
|
|
73887
|
+
console.log("raw_active", raw_active_account);
|
|
73888
|
+
console.log("symbol_config", symbol_config);
|
|
72041
73889
|
const _all = get_active_accounts({
|
|
72042
73890
|
active_account: raw_active_account,
|
|
72043
73891
|
symbol_config
|
|
@@ -72053,6 +73901,7 @@ class ExchangeAccount {
|
|
|
72053
73901
|
refresh: live_refresh,
|
|
72054
73902
|
symbol
|
|
72055
73903
|
});
|
|
73904
|
+
console.log("active_accounts", active_account, live_refresh);
|
|
72056
73905
|
if (leverage) {
|
|
72057
73906
|
this.exchange.setLeverage({ symbol, leverage });
|
|
72058
73907
|
}
|
|
@@ -72305,7 +74154,7 @@ class ExchangeAccount {
|
|
|
72305
74154
|
kind: "short"
|
|
72306
74155
|
});
|
|
72307
74156
|
const long_config = await long_position.getConfig();
|
|
72308
|
-
const short_config = short_position.
|
|
74157
|
+
const short_config = await short_position.getConfig();
|
|
72309
74158
|
const config2 = build_reduce_config({
|
|
72310
74159
|
account: this.instance,
|
|
72311
74160
|
symbol: payload.symbol,
|
|
@@ -73667,9 +75516,9 @@ class ExchangeAccount {
|
|
|
73667
75516
|
return profiles;
|
|
73668
75517
|
}
|
|
73669
75518
|
}
|
|
73670
|
-
function getExchangeKlass(exchange) {
|
|
73671
|
-
const func = exchange === "binance" ? BinanceExchange : BybitExchange;
|
|
73672
|
-
const clientFunc = exchange === "binance" ? initClient : initClient2;
|
|
75519
|
+
function getExchangeKlass(exchange, dbCredentials) {
|
|
75520
|
+
const func = exchange === "binance" ? BinanceExchange : exchange === "paper" ? PaperBinanceExchange : BybitExchange;
|
|
75521
|
+
const clientFunc = exchange === "binance" ? initClient : exchange === "paper" ? initClient3 : initClient2;
|
|
73673
75522
|
return async (payload) => {
|
|
73674
75523
|
const credentials = await payload.getCredentials({
|
|
73675
75524
|
account: payload.account,
|
|
@@ -73677,7 +75526,8 @@ function getExchangeKlass(exchange) {
|
|
|
73677
75526
|
});
|
|
73678
75527
|
const client = await clientFunc(credentials, {
|
|
73679
75528
|
type: "future",
|
|
73680
|
-
proxyAgent: payload.proxyAgent
|
|
75529
|
+
proxyAgent: payload.proxyAgent,
|
|
75530
|
+
db: dbCredentials
|
|
73681
75531
|
});
|
|
73682
75532
|
let main_client = null;
|
|
73683
75533
|
if (exchange === "binance") {
|
|
@@ -73703,7 +75553,11 @@ async function getExchangeAccount(payload) {
|
|
|
73703
75553
|
const { account, app_db, proxyOptions = {}, canWithdraw = false } = payload;
|
|
73704
75554
|
const _proxyAgent = await app_db.getProxyForAccount(account);
|
|
73705
75555
|
const proxyAgent = proxyOptions?.ignore_proxy ? null : proxyOptions?.proxy || _proxyAgent;
|
|
73706
|
-
const exchange_instance = await getExchangeKlass(account.exchange
|
|
75556
|
+
const exchange_instance = await getExchangeKlass(account.exchange, {
|
|
75557
|
+
host: app_db.pb.baseURL,
|
|
75558
|
+
email: app_db.root_email,
|
|
75559
|
+
password: app_db.root_password
|
|
75560
|
+
})({
|
|
73707
75561
|
account: account.owner,
|
|
73708
75562
|
getCredentials: async (_payload) => {
|
|
73709
75563
|
return await payload.getCredentials({ ..._payload, app_db });
|
|
@@ -74113,7 +75967,11 @@ async function initApp(payload) {
|
|
|
74113
75967
|
const pb = await initPocketBaseClient(payload.db);
|
|
74114
75968
|
const app_db = new AppDatabase(pb, {
|
|
74115
75969
|
email: payload.email,
|
|
74116
|
-
salt: payload.salt
|
|
75970
|
+
salt: payload.salt,
|
|
75971
|
+
db: {
|
|
75972
|
+
email: payload.db.email,
|
|
75973
|
+
password: payload.db.password
|
|
75974
|
+
}
|
|
74117
75975
|
});
|
|
74118
75976
|
let _getCredentials = payload.getCredentials;
|
|
74119
75977
|
if (payload.password) {
|