@orderly.network/ui-positions 2.8.8 → 2.8.9

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/index.js CHANGED
@@ -7,9 +7,8 @@ var types = require('@orderly.network/types');
7
7
  var utils = require('@orderly.network/utils');
8
8
  var jsxRuntime = require('react/jsx-runtime');
9
9
  var hooks = require('@orderly.network/hooks');
10
- var immer = require('immer');
11
- var perp = require('@orderly.network/perp');
12
10
  var reactApp = require('@orderly.network/react-app');
11
+ var perp = require('@orderly.network/perp');
13
12
  var uiConnector = require('@orderly.network/ui-connector');
14
13
  var uiLeverage = require('@orderly.network/ui-leverage');
15
14
  var uiShare = require('@orderly.network/ui-share');
@@ -599,6 +598,102 @@ var FundingFeeButton = ({ fee, symbol, start_t, end_t }) => {
599
598
  )
600
599
  ] });
601
600
  };
601
+ var calculatePositions = (positions2, symbolsInfo, accountInfo, tpslOrders) => {
602
+ return positions2.map((item) => {
603
+ const info = symbolsInfo[item.symbol];
604
+ const notional = perp.positions.notional(item.position_qty, item.mark_price);
605
+ const account2 = accountInfo.find(
606
+ (acc) => acc.account_id === item.account_id
607
+ );
608
+ const baseMMR = info?.("base_mmr");
609
+ const baseIMR = info?.("base_imr");
610
+ if (!baseMMR || !baseIMR) {
611
+ return item;
612
+ }
613
+ const MMR = perp.positions.MMR({
614
+ baseMMR,
615
+ baseIMR,
616
+ IMRFactor: account2?.imr_factor[item.symbol] ?? 0,
617
+ positionNotional: notional,
618
+ IMR_factor_power: 4 / 5
619
+ });
620
+ const mm = perp.positions.maintenanceMargin({
621
+ positionQty: item.position_qty,
622
+ markPrice: item.mark_price,
623
+ MMR
624
+ });
625
+ const unrealPnl = perp.positions.unrealizedPnL({
626
+ qty: item.position_qty,
627
+ openPrice: item?.average_open_price,
628
+ markPrice: item.mark_price
629
+ });
630
+ const maxLeverage = item.leverage || 1;
631
+ const imr = perp.account.IMR({
632
+ maxLeverage,
633
+ baseIMR,
634
+ IMR_Factor: account2?.imr_factor[item.symbol] ?? 0,
635
+ positionNotional: notional,
636
+ ordersNotional: 0,
637
+ IMR_factor_power: 4 / 5
638
+ });
639
+ const unrealPnlROI = perp.positions.unrealizedPnLROI({
640
+ positionQty: item.position_qty,
641
+ openPrice: item.average_open_price,
642
+ IMR: imr,
643
+ unrealizedPnL: unrealPnl
644
+ });
645
+ let unrealPnl_index = 0;
646
+ let unrealPnlROI_index = 0;
647
+ if (item.index_price) {
648
+ unrealPnl_index = perp.positions.unrealizedPnL({
649
+ qty: item.position_qty,
650
+ openPrice: item?.average_open_price,
651
+ markPrice: item.index_price
652
+ });
653
+ unrealPnlROI_index = perp.positions.unrealizedPnLROI({
654
+ positionQty: item.position_qty,
655
+ openPrice: item.average_open_price,
656
+ IMR: imr,
657
+ unrealizedPnL: unrealPnl_index
658
+ });
659
+ }
660
+ const filteredTPSLOrders = tpslOrders.filter(
661
+ (tpslOrder) => tpslOrder.account_id === item.account_id
662
+ );
663
+ const tpsl = formatTPSL(filteredTPSLOrders, item.symbol);
664
+ return {
665
+ ...item,
666
+ ...tpsl,
667
+ mmr: MMR,
668
+ mm,
669
+ notional,
670
+ unrealized_pnl: unrealPnl,
671
+ unrealized_pnl_ROI: unrealPnlROI,
672
+ unrealized_pnl_ROI_index: unrealPnlROI_index
673
+ };
674
+ });
675
+ };
676
+ function formatTPSL(tpslOrders, symbol) {
677
+ if (Array.isArray(tpslOrders) && tpslOrders.length) {
678
+ const { fullPositionOrder, partialPositionOrders } = hooks.findPositionTPSLFromOrders(tpslOrders, symbol);
679
+ const full_tp_sl = fullPositionOrder ? hooks.findTPSLFromOrder(fullPositionOrder) : void 0;
680
+ const partialPossitionOrder = partialPositionOrders && partialPositionOrders.length ? partialPositionOrders[0] : void 0;
681
+ const partial_tp_sl = partialPossitionOrder ? hooks.findTPSLFromOrder(partialPossitionOrder) : void 0;
682
+ return {
683
+ full_tp_sl: {
684
+ tp_trigger_price: full_tp_sl?.tp_trigger_price,
685
+ sl_trigger_price: full_tp_sl?.sl_trigger_price,
686
+ algo_order: fullPositionOrder
687
+ },
688
+ partial_tp_sl: {
689
+ order_num: partialPositionOrders?.length ?? 0,
690
+ tp_trigger_price: partial_tp_sl?.tp_trigger_price,
691
+ sl_trigger_price: partial_tp_sl?.sl_trigger_price,
692
+ algo_order: partialPossitionOrder
693
+ }
694
+ };
695
+ }
696
+ }
602
697
  var signatureMiddleware = (account2, accountId) => {
603
698
  const apiBaseUrl = hooks.useConfig("apiBaseUrl");
604
699
  return (useSWRNext) => {
@@ -648,6 +743,48 @@ var useSubAccountQuery = (query, options) => {
648
743
  }
649
744
  );
650
745
  };
746
+ function useSubAccountTPSL(subAccountIds) {
747
+ const ee = hooks.useEventEmitter();
748
+ const { data: algoOrdersResponse, mutate: mutateTPSLOrders } = useSubAccountQuery(
749
+ `/v1/algo/orders?size=100&page=1&status=${types.OrderStatus.INCOMPLETE}`,
750
+ {
751
+ accountId: subAccountIds,
752
+ formatter: (data) => data,
753
+ revalidateOnFocus: false
754
+ }
755
+ );
756
+ const tpslOrders = React2.useMemo(() => {
757
+ if (!Array.isArray(algoOrdersResponse)) {
758
+ return [];
759
+ }
760
+ const algoOrders = algoOrdersResponse?.map(
761
+ (item, index) => item.rows.map((order) => ({
762
+ ...order,
763
+ account_id: subAccountIds[index]
764
+ }))
765
+ )?.flat();
766
+ return algoOrders?.filter(
767
+ (order) => [types.AlgoOrderRootType.POSITIONAL_TP_SL, types.AlgoOrderRootType.TP_SL].includes(
768
+ order.algo_type
769
+ )
770
+ );
771
+ }, [algoOrdersResponse, subAccountIds]);
772
+ const refresh = hooks.useDebouncedCallback(() => {
773
+ mutateTPSLOrders();
774
+ }, 200);
775
+ React2.useEffect(() => {
776
+ const handler = (position) => {
777
+ if (position.account_id) {
778
+ refresh();
779
+ }
780
+ };
781
+ ee.on("tpsl:updateOrder", handler);
782
+ return () => {
783
+ ee.off("tpsl:updateOrder", handler);
784
+ };
785
+ }, [ee]);
786
+ return { tpslOrders, mutateTPSLOrders };
787
+ }
651
788
 
652
789
  // src/components/positions/combinePositions.script.ts
653
790
  var useCombinePositionsScript = (props) => {
@@ -661,12 +798,9 @@ var useCombinePositionsScript = (props) => {
661
798
  selectedAccount
662
799
  } = props;
663
800
  const { pagination, setPage } = ui.usePagination({ pageSize: 50 });
664
- React2.useEffect(() => {
665
- setPage(1);
666
- }, [symbol]);
667
801
  const symbolsInfo = hooks.useSymbolsInfo();
668
802
  const { state } = hooks.useAccount();
669
- const [oldPositions, , { isLoading }] = hooks.usePositionStream(symbol, {
803
+ const [mainAccountPositions, , { isLoading }] = hooks.usePositionStream(symbol, {
670
804
  calcMode,
671
805
  includedPendingOrder
672
806
  });
@@ -675,90 +809,37 @@ var useCombinePositionsScript = (props) => {
675
809
  isLoading: isPositionLoading,
676
810
  mutate: mutatePositions
677
811
  } = hooks.usePrivateQuery("/v1/client/aggregate/positions", {
678
- // formatter: (data) => data,
679
812
  errorRetryCount: 3
680
813
  });
814
+ const { allAccountIds, subAccountIds } = React2.useMemo(() => {
815
+ const uniqueIds = new Set(
816
+ newPositions.filter((item) => item.account_id).map((item) => item.account_id).filter(Boolean)
817
+ );
818
+ const allAccountIds2 = Array.from(uniqueIds);
819
+ const subAccountIds2 = allAccountIds2.filter(
820
+ (item) => item !== state.mainAccountId
821
+ );
822
+ return { allAccountIds: allAccountIds2, subAccountIds: subAccountIds2 };
823
+ }, [newPositions, state.mainAccountId]);
681
824
  const { data: accountInfo = [], isLoading: isAccountInfoLoading } = useSubAccountQuery("/v1/client/info", {
682
- accountId: newPositions.map((item) => item.account_id),
825
+ accountId: allAccountIds,
683
826
  revalidateOnFocus: false
684
827
  });
685
- const processPositions = immer.produce(
686
- newPositions.filter((acc) => acc.account_id !== state.mainAccountId),
687
- (draft) => {
688
- for (const item of draft) {
689
- const info = symbolsInfo[item.symbol];
690
- const notional = perp.positions.notional(item.position_qty, item.mark_price);
691
- const account2 = accountInfo.find(
692
- (acc) => acc.account_id === item.account_id
693
- );
694
- const baseMMR = info?.("base_mmr");
695
- const baseIMR = info?.("base_imr");
696
- if (!baseMMR || !baseIMR) {
697
- continue;
698
- }
699
- const MMR = perp.positions.MMR({
700
- baseMMR,
701
- baseIMR,
702
- IMRFactor: account2?.imr_factor[item.symbol] ?? 0,
703
- positionNotional: notional,
704
- IMR_factor_power: 4 / 5
705
- });
706
- const mm = perp.positions.maintenanceMargin({
707
- positionQty: item.position_qty,
708
- markPrice: item.mark_price,
709
- MMR
710
- });
711
- const unrealPnl = perp.positions.unrealizedPnL({
712
- qty: item.position_qty,
713
- openPrice: item?.average_open_price,
714
- // markPrice: unRealizedPrice,
715
- markPrice: item.mark_price
716
- });
717
- const maxLeverage = item.leverage || 1;
718
- const imr = perp.account.IMR({
719
- maxLeverage,
720
- baseIMR,
721
- IMR_Factor: account2?.imr_factor[item.symbol] ?? 0,
722
- positionNotional: notional,
723
- ordersNotional: 0,
724
- IMR_factor_power: 4 / 5
725
- });
726
- const unrealPnlROI = perp.positions.unrealizedPnLROI({
727
- positionQty: item.position_qty,
728
- openPrice: item.average_open_price,
729
- IMR: imr,
730
- unrealizedPnL: unrealPnl
731
- });
732
- let unrealPnl_index = 0;
733
- let unrealPnlROI_index = 0;
734
- if (item.index_price) {
735
- unrealPnl_index = perp.positions.unrealizedPnL({
736
- qty: item.position_qty,
737
- openPrice: item?.average_open_price,
738
- // markPrice: unRealizedPrice,
739
- markPrice: item.index_price
740
- });
741
- unrealPnlROI_index = perp.positions.unrealizedPnLROI({
742
- positionQty: item.position_qty,
743
- openPrice: item.average_open_price,
744
- IMR: imr,
745
- unrealizedPnL: unrealPnl_index
746
- });
747
- }
748
- item.mmr = MMR;
749
- item.mm = mm;
750
- item.notional = notional;
751
- item.unrealized_pnl = unrealPnl;
752
- item.unrealized_pnl_ROI = unrealPnlROI;
753
- item.unrealized_pnl_ROI_index = unrealPnlROI_index;
754
- }
755
- }
756
- );
757
- const dataSource = reactApp.useDataTap(
758
- [...oldPositions?.rows, ...processPositions].filter(
759
- (acc) => acc.position_qty !== 0
760
- )
761
- ) ?? [];
828
+ const { tpslOrders, mutateTPSLOrders } = useSubAccountTPSL(subAccountIds);
829
+ const subAccountPositions = React2.useMemo(() => {
830
+ return calculatePositions(
831
+ newPositions.filter((item) => item.account_id !== state.mainAccountId),
832
+ symbolsInfo,
833
+ accountInfo,
834
+ tpslOrders
835
+ );
836
+ }, [newPositions, symbolsInfo, accountInfo, state.mainAccountId, tpslOrders]);
837
+ const allPositions = React2.useMemo(() => {
838
+ return [...mainAccountPositions?.rows, ...subAccountPositions].filter(
839
+ (item) => item.position_qty !== 0
840
+ );
841
+ }, [mainAccountPositions, subAccountPositions]);
842
+ const dataSource = reactApp.useDataTap(allPositions) ?? [];
762
843
  const filtered = React2.useMemo(() => {
763
844
  if (!selectedAccount || selectedAccount === "All accounts" /* ALL */) {
764
845
  return dataSource;
@@ -777,18 +858,23 @@ var useCombinePositionsScript = (props) => {
777
858
  subAccounts: state.subAccounts
778
859
  });
779
860
  }, [filtered, state.mainAccountId, state.subAccounts]);
780
- const mergedLoading = React2.useMemo(() => {
781
- return isLoading || isPositionLoading || isAccountInfoLoading;
782
- }, [isLoading, isPositionLoading, isAccountInfoLoading]);
861
+ const loading = isLoading || isPositionLoading || isAccountInfoLoading;
862
+ React2.useEffect(() => {
863
+ setPage(1);
864
+ }, [symbol]);
865
+ const mutateList = React2.useCallback(() => {
866
+ mutatePositions();
867
+ mutateTPSLOrders();
868
+ }, []);
783
869
  return {
784
870
  tableData: groupDataSource,
785
- isLoading: mergedLoading,
871
+ isLoading: loading,
786
872
  pnlNotionalDecimalPrecision,
787
873
  sharePnLConfig,
788
874
  symbol,
789
875
  onSymbolChange,
790
876
  pagination,
791
- mutatePositions
877
+ mutatePositions: mutateList
792
878
  };
793
879
  };
794
880
  var groupDataByAccount = (data, options) => {
@@ -796,12 +882,12 @@ var groupDataByAccount = (data, options) => {
796
882
  const map = /* @__PURE__ */ new Map();
797
883
  for (const item of data) {
798
884
  const accountId = item.account_id || mainAccountId;
799
- const findSubAccount = subAccounts.find((acc) => acc.id === accountId);
885
+ const findSubAccount = subAccounts.find((item2) => item2.id === accountId);
800
886
  if (map.has(accountId)) {
801
887
  map.get(accountId)?.children?.push(item);
802
888
  } else {
803
889
  map.set(accountId, {
804
- id: accountId,
890
+ account_id: accountId,
805
891
  description: accountId === mainAccountId ? i18n.i18n.t("common.mainAccount") : findSubAccount?.description || ui.formatAddress(findSubAccount?.id || ""),
806
892
  children: [item]
807
893
  });
@@ -962,7 +1048,14 @@ var OrderInfoCard = ({
962
1048
  );
963
1049
  };
964
1050
  var ReversePosition = (props) => {
965
- const { displayInfo, className, style, onConfirm, onCancel } = props;
1051
+ const {
1052
+ displayInfo,
1053
+ validationError,
1054
+ className,
1055
+ style,
1056
+ onConfirm,
1057
+ onCancel
1058
+ } = props;
966
1059
  const { t } = i18n.useTranslation();
967
1060
  if (!displayInfo) {
968
1061
  return null;
@@ -989,6 +1082,7 @@ var ReversePosition = (props) => {
989
1082
  /* @__PURE__ */ jsxRuntime.jsx(ui.Text.numeral, { dp: quoteDp, size: "sm", intensity: 80, children: markPrice }),
990
1083
  /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "sm", intensity: 36, children: quote })
991
1084
  ] });
1085
+ const showBelowMinError = validationError === "belowMin";
992
1086
  return /* @__PURE__ */ jsxRuntime.jsxs(
993
1087
  ui.Flex,
994
1088
  {
@@ -1048,11 +1142,12 @@ var ReversePosition = (props) => {
1048
1142
  baseDp
1049
1143
  }
1050
1144
  ),
1051
- /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "2xs", color: "warning", weight: "semibold", children: t("positions.reverse.description") })
1145
+ showBelowMinError ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "2xs", color: "danger", weight: "semibold", children: t("positions.reverse.error.belowMin") }) : /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "2xs", color: "warning", weight: "semibold", children: t("positions.reverse.description") })
1052
1146
  ]
1053
1147
  }
1054
1148
  );
1055
1149
  };
1150
+ var MAX_BATCH_ORDER_SIZE = 20;
1056
1151
  var useReversePositionEnabled = () => {
1057
1152
  const { isMobile, isDesktop } = ui.useScreen();
1058
1153
  const [desktopEnabled, setDesktopEnabled] = hooks.useLocalStorage(
@@ -1089,12 +1184,26 @@ var useReversePositionEnabled = () => {
1089
1184
  };
1090
1185
  var useReversePositionScript = (options) => {
1091
1186
  const { position, onSuccess, onError } = options || {};
1092
- const { t } = i18n.useTranslation();
1093
1187
  const { isEnabled, setEnabled } = useReversePositionEnabled();
1094
1188
  const symbolsInfo = hooks.useSymbolsInfo();
1095
1189
  const symbol = position?.symbol || "";
1096
1190
  const { data: symbolMarketPrice } = hooks.useMarkPrice(symbol);
1097
1191
  const symbolInfo = symbolsInfo?.[symbol];
1192
+ const baseMin = React2.useMemo(() => {
1193
+ if (!symbolInfo)
1194
+ return 0;
1195
+ return symbolInfo("base_min") || 0;
1196
+ }, [symbolInfo]);
1197
+ const baseMax = React2.useMemo(() => {
1198
+ if (!symbolInfo)
1199
+ return 0;
1200
+ return symbolInfo("base_max") || 0;
1201
+ }, [symbolInfo]);
1202
+ const baseDp = React2.useMemo(() => {
1203
+ if (!symbolInfo)
1204
+ return 6;
1205
+ return symbolInfo("base_dp") || 6;
1206
+ }, [symbolInfo]);
1098
1207
  const positionQty = React2.useMemo(() => {
1099
1208
  if (!position)
1100
1209
  return 0;
@@ -1105,21 +1214,62 @@ var useReversePositionScript = (options) => {
1105
1214
  return false;
1106
1215
  return position.position_qty > 0;
1107
1216
  }, [position]);
1108
- const oppositeSide = React2.useMemo(() => {
1109
- return isLong ? types.OrderSide.SELL : types.OrderSide.BUY;
1110
- }, [isLong]);
1111
- const maxOpenQty = hooks.useMaxQty(symbol, oppositeSide, false);
1112
- const reverseQty = React2.useMemo(() => {
1113
- if (!position)
1114
- return 0;
1115
- const desiredQty = positionQty;
1116
- if (desiredQty > maxOpenQty && maxOpenQty > 0) {
1117
- return maxOpenQty;
1217
+ const reverseQty = positionQty;
1218
+ const validationError = React2.useMemo(() => {
1219
+ if (!position || !symbolInfo)
1220
+ return null;
1221
+ if (baseMin > 0 && reverseQty < baseMin) {
1222
+ return "belowMin";
1118
1223
  }
1119
- return desiredQty;
1120
- }, [positionQty, maxOpenQty]);
1121
- const [doCreateOrder, { isMutating }] = hooks.useSubAccountMutation(
1122
- "/v1/order",
1224
+ return null;
1225
+ }, [position, symbolInfo, reverseQty, baseMin]);
1226
+ const splitOrders = React2.useMemo(() => {
1227
+ if (!position || !symbolInfo || baseMax <= 0 || reverseQty <= 0) {
1228
+ return { needsSplit: false, orders: [] };
1229
+ }
1230
+ const buildOrder = (qty, side, reduceOnly) => {
1231
+ return {
1232
+ symbol: position.symbol,
1233
+ order_type: types.OrderType.MARKET,
1234
+ side,
1235
+ order_quantity: new utils.Decimal(qty).todp(baseDp).toString(),
1236
+ reduce_only: reduceOnly
1237
+ };
1238
+ };
1239
+ const closeSide = isLong ? types.OrderSide.SELL : types.OrderSide.BUY;
1240
+ const openSide = isLong ? types.OrderSide.SELL : types.OrderSide.BUY;
1241
+ if (reverseQty <= baseMax) {
1242
+ return {
1243
+ needsSplit: false,
1244
+ orders: [
1245
+ buildOrder(reverseQty, closeSide, true),
1246
+ buildOrder(reverseQty, openSide, false)
1247
+ ]
1248
+ };
1249
+ }
1250
+ const orders = [];
1251
+ const perOrderQty = baseMax;
1252
+ const numOrders = Math.ceil(reverseQty / baseMax);
1253
+ for (let i = 0; i < numOrders - 1; i++) {
1254
+ orders.push(buildOrder(perOrderQty, closeSide, true));
1255
+ }
1256
+ orders.push(
1257
+ buildOrder(reverseQty - perOrderQty * (numOrders - 1), closeSide, true)
1258
+ );
1259
+ for (let i = 0; i < numOrders - 1; i++) {
1260
+ orders.push(buildOrder(perOrderQty, openSide, false));
1261
+ }
1262
+ orders.push(
1263
+ buildOrder(reverseQty - perOrderQty * (numOrders - 1), openSide, false)
1264
+ );
1265
+ return {
1266
+ needsSplit: true,
1267
+ orders
1268
+ };
1269
+ }, [position, symbolInfo, reverseQty, baseMax, baseDp]);
1270
+ const [isReversing, setIsReversing] = React2.useState(false);
1271
+ const [doBatchCreateOrder] = hooks.useSubAccountMutation(
1272
+ "/v1/batch-order",
1123
1273
  "POST",
1124
1274
  {
1125
1275
  accountId: position?.account_id
@@ -1127,55 +1277,74 @@ var useReversePositionScript = (options) => {
1127
1277
  );
1128
1278
  const reversePosition = React2.useCallback(async () => {
1129
1279
  if (!position || positionQty === 0) {
1130
- ui.toast.error("No position to reverse");
1131
1280
  return false;
1132
1281
  }
1282
+ if (validationError) {
1283
+ return false;
1284
+ }
1285
+ setIsReversing(true);
1133
1286
  try {
1134
- const closeSide = isLong ? types.OrderSide.SELL : types.OrderSide.BUY;
1135
- const closeOrder = await doCreateOrder({
1136
- symbol: position.symbol,
1137
- order_type: types.OrderType.MARKET,
1138
- side: closeSide,
1139
- order_quantity: new utils.Decimal(positionQty).toString(),
1140
- reduce_only: true
1141
- });
1142
- const openSide = isLong ? types.OrderSide.SELL : types.OrderSide.BUY;
1143
- const openOrder = await doCreateOrder({
1144
- symbol: position.symbol,
1145
- order_type: types.OrderType.MARKET,
1146
- side: openSide,
1147
- order_quantity: new utils.Decimal(reverseQty).toString(),
1148
- reduce_only: false
1149
- });
1150
- if (!openOrder.success) {
1151
- throw new Error(
1152
- openOrder.message || "Failed to open opposite position"
1153
- );
1287
+ const ordersArray = splitOrders.orders;
1288
+ if (ordersArray.length > MAX_BATCH_ORDER_SIZE) {
1289
+ for (let i = 0; i < ordersArray.length; i += MAX_BATCH_ORDER_SIZE) {
1290
+ const batch = ordersArray.slice(i, i + MAX_BATCH_ORDER_SIZE);
1291
+ const result = await doBatchCreateOrder({
1292
+ orders: batch,
1293
+ symbol: position.symbol
1294
+ });
1295
+ await new Promise(
1296
+ (resolve) => setTimeout(resolve, batch.length * 110)
1297
+ );
1298
+ if (!result || result.error) {
1299
+ throw result?.error || new Error("Batch order failed");
1300
+ }
1301
+ }
1302
+ } else {
1303
+ const result = await doBatchCreateOrder({
1304
+ orders: ordersArray,
1305
+ symbol: position.symbol
1306
+ });
1307
+ if (!result || result.error) {
1308
+ throw result?.error || new Error("Batch order failed");
1309
+ }
1154
1310
  }
1155
1311
  onSuccess?.();
1156
1312
  return true;
1157
1313
  } catch (error) {
1158
1314
  onError?.(error);
1159
1315
  return false;
1316
+ } finally {
1317
+ setIsReversing(false);
1160
1318
  }
1161
- }, [position, positionQty, reverseQty, isLong, doCreateOrder, t, onSuccess]);
1319
+ }, [
1320
+ position,
1321
+ positionQty,
1322
+ reverseQty,
1323
+ isLong,
1324
+ doBatchCreateOrder,
1325
+ splitOrders,
1326
+ symbolInfo,
1327
+ validationError,
1328
+ onSuccess,
1329
+ onError
1330
+ ]);
1162
1331
  const displayInfo = React2.useMemo(() => {
1163
1332
  if (!position || !symbolInfo) {
1164
1333
  return null;
1165
1334
  }
1166
1335
  const base = symbolInfo("base");
1167
1336
  const quote = symbolInfo("quote");
1168
- const baseDp = symbolInfo("base_dp");
1337
+ const baseDp2 = symbolInfo("base_dp");
1169
1338
  const quoteDp = symbolInfo("quote_dp");
1170
1339
  const leverage = position.leverage || 1;
1171
1340
  return {
1172
1341
  symbol: position.symbol,
1173
1342
  base,
1174
1343
  quote,
1175
- baseDp,
1344
+ baseDp: baseDp2,
1176
1345
  quoteDp,
1177
- positionQty: new utils.Decimal(positionQty).todp(baseDp).toString(),
1178
- reverseQty: new utils.Decimal(reverseQty).todp(baseDp).toString(),
1346
+ positionQty: new utils.Decimal(positionQty).todp(baseDp2).toString(),
1347
+ reverseQty: new utils.Decimal(reverseQty).todp(baseDp2).toString(),
1179
1348
  markPrice: symbolMarketPrice ? new utils.Decimal(symbolMarketPrice).todp(quoteDp).toString() : "--",
1180
1349
  leverage,
1181
1350
  isLong
@@ -1192,11 +1361,13 @@ var useReversePositionScript = (options) => {
1192
1361
  isEnabled,
1193
1362
  setEnabled,
1194
1363
  reversePosition,
1195
- isReversing: isMutating,
1364
+ isReversing,
1196
1365
  displayInfo,
1197
1366
  positionQty,
1198
1367
  reverseQty,
1199
- isLong
1368
+ isLong,
1369
+ validationError,
1370
+ splitOrders
1200
1371
  };
1201
1372
  };
1202
1373
  var ReversePositionWidget = (props) => {
@@ -1215,6 +1386,7 @@ var ReversePositionWidget = (props) => {
1215
1386
  }
1216
1387
  });
1217
1388
  const actions = React2.useMemo(() => {
1389
+ const hasValidationError = !!state.validationError;
1218
1390
  return {
1219
1391
  primary: {
1220
1392
  label: t("common.confirm"),
@@ -1235,10 +1407,11 @@ var ReversePositionWidget = (props) => {
1235
1407
  }
1236
1408
  },
1237
1409
  loading: state.isReversing,
1238
- disabled: state.isReversing || !state.displayInfo
1410
+ disabled: state.isReversing || !state.displayInfo || hasValidationError
1239
1411
  },
1240
1412
  secondary: {
1241
1413
  label: t("common.cancel"),
1414
+ disabled: state.isReversing,
1242
1415
  onClick: async () => {
1243
1416
  reject?.("cancel");
1244
1417
  hide();
@@ -1258,6 +1431,7 @@ var ReversePositionWidget = (props) => {
1258
1431
  content: "oui-border oui-border-line-6"
1259
1432
  },
1260
1433
  actions,
1434
+ closable: !state.isReversing,
1261
1435
  children: /* @__PURE__ */ jsxRuntime.jsx(ReversePosition, { ...state })
1262
1436
  }
1263
1437
  );
@@ -2020,12 +2194,12 @@ var TPSLEditIcon = () => {
2020
2194
  };
2021
2195
  var AddIcon = (props) => {
2022
2196
  const { position, baseDp, quoteDp, tpslOrder } = usePositionsRowContext();
2023
- const [needConfirm] = hooks.useLocalStorage("orderly_order_confirm", true);
2024
2197
  const { t } = i18n.useTranslation();
2025
2198
  const { isMobile } = ui.useScreen();
2026
2199
  const onAdd = () => {
2027
2200
  const dialogId = isMobile ? uiTpsl.TPSLSheetId : uiTpsl.TPSLDialogId;
2028
2201
  const modalParams = {
2202
+ position,
2029
2203
  symbol: position.symbol,
2030
2204
  baseDP: baseDp,
2031
2205
  quoteDP: quoteDp,
@@ -3435,7 +3609,7 @@ var CombinePositions = (props) => {
3435
3609
  dataSource,
3436
3610
  expanded: true,
3437
3611
  getSubRows: (row) => row.children,
3438
- generatedRowKey: (record) => record.id,
3612
+ generatedRowKey: (record) => `${record.account_id}${record.symbol || ""}`,
3439
3613
  onCell: (column, record) => {
3440
3614
  const isGroup = (record.children ?? []).length > 0;
3441
3615
  if (isGroup) {
@@ -3459,6 +3633,9 @@ var CombinePositions = (props) => {
3459
3633
  },
3460
3634
  manualPagination: false,
3461
3635
  pagination,
3636
+ classNames: {
3637
+ scroll: "oui-pb-10"
3638
+ },
3462
3639
  testIds: {
3463
3640
  body: "oui-testid-dataList-position-tab-body"
3464
3641
  }