@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.mjs CHANGED
@@ -1,13 +1,12 @@
1
- import { registerSimpleDialog, useModal, SimpleDialog, Flex, Text, toast, Badge, Divider, CloseIcon, Button, ThrottledButton, useScreen, ArrowDownShortIcon, ArrowUpShortIcon, Grid, Statistic, ExclamationFillIcon, modal, Tooltip, DataTable, cn, ListView, SimpleSheet, usePagination, DataFilter, formatAddress, Box, HoverCard, ArrowLeftRightIcon, capitalizeFirstLetter, ShareIcon, EditIcon, Input, inputFormatter, Select, PopoverRoot, PopoverTrigger, PopoverContent, Slider } from '@orderly.network/ui';
2
- import React2, { createContext, useMemo, useCallback, useContext, useState, useEffect, useRef } from 'react';
1
+ import { registerSimpleDialog, useModal, SimpleDialog, Flex, Text, Badge, Divider, CloseIcon, Button, ThrottledButton, useScreen, ArrowDownShortIcon, ArrowUpShortIcon, Grid, Statistic, ExclamationFillIcon, modal, Tooltip, DataTable, cn, ListView, SimpleSheet, usePagination, DataFilter, toast, formatAddress, Box, HoverCard, ArrowLeftRightIcon, capitalizeFirstLetter, ShareIcon, EditIcon, Input, inputFormatter, Select, PopoverRoot, PopoverTrigger, PopoverContent, Slider } from '@orderly.network/ui';
2
+ import React2, { createContext, useMemo, useState, useCallback, useContext, useEffect, useRef } from 'react';
3
3
  import { i18n, useTranslation } from '@orderly.network/i18n';
4
- import { OrderSide, OrderType, EMPTY_LIST, AccountStatusEnum, TrackerEventName, PositionType, AlgoOrderType } from '@orderly.network/types';
4
+ import { OrderSide, OrderType, EMPTY_LIST, AccountStatusEnum, TrackerEventName, OrderStatus, AlgoOrderRootType, PositionType, AlgoOrderType } from '@orderly.network/types';
5
5
  import { commifyOptional, Decimal, formatNum, getTimestamp, commify } from '@orderly.network/utils';
6
6
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
7
- import { useSymbolsInfo, useMarkPrice, useMaxQty, useSubAccountMutation, useLocalStorage, usePrivateInfiniteQuery, useBoolean, useSessionStorage, useAccount, usePrivateQuery, useTrack, usePositionStream, usePositionClose, useSWR, fetcher, useGetRwaSymbolOpenStatus, useTpslPriceChecker, useConfig, useReferralInfo, useAccountInfo, useLeverageBySymbol, utils, useMaxLeverage } from '@orderly.network/hooks';
8
- import { produce } from 'immer';
9
- import { positions, account } from '@orderly.network/perp';
7
+ import { useSymbolsInfo, useMarkPrice, useSubAccountMutation, useLocalStorage, usePrivateInfiniteQuery, useBoolean, useSessionStorage, useAccount, usePrivateQuery, useTrack, usePositionStream, usePositionClose, useSWR, fetcher, useEventEmitter, useDebouncedCallback, useGetRwaSymbolOpenStatus, useTpslPriceChecker, useConfig, findPositionTPSLFromOrders, findTPSLFromOrder, useReferralInfo, useAccountInfo, useLeverageBySymbol, utils, useMaxLeverage } from '@orderly.network/hooks';
10
8
  import { useDataTap, useOrderEntryFormErrorMsg } from '@orderly.network/react-app';
9
+ import { positions, account } from '@orderly.network/perp';
11
10
  import { AuthGuardDataTable } from '@orderly.network/ui-connector';
12
11
  import { SymbolLeverageDialogId, SymbolLeverageSheetId } from '@orderly.network/ui-leverage';
13
12
  import { SharePnLDialogId, SharePnLBottomSheetId } from '@orderly.network/ui-share';
@@ -593,6 +592,102 @@ var FundingFeeButton = ({ fee, symbol, start_t, end_t }) => {
593
592
  )
594
593
  ] });
595
594
  };
595
+ var calculatePositions = (positions2, symbolsInfo, accountInfo, tpslOrders) => {
596
+ return positions2.map((item) => {
597
+ const info = symbolsInfo[item.symbol];
598
+ const notional = positions.notional(item.position_qty, item.mark_price);
599
+ const account2 = accountInfo.find(
600
+ (acc) => acc.account_id === item.account_id
601
+ );
602
+ const baseMMR = info?.("base_mmr");
603
+ const baseIMR = info?.("base_imr");
604
+ if (!baseMMR || !baseIMR) {
605
+ return item;
606
+ }
607
+ const MMR = positions.MMR({
608
+ baseMMR,
609
+ baseIMR,
610
+ IMRFactor: account2?.imr_factor[item.symbol] ?? 0,
611
+ positionNotional: notional,
612
+ IMR_factor_power: 4 / 5
613
+ });
614
+ const mm = positions.maintenanceMargin({
615
+ positionQty: item.position_qty,
616
+ markPrice: item.mark_price,
617
+ MMR
618
+ });
619
+ const unrealPnl = positions.unrealizedPnL({
620
+ qty: item.position_qty,
621
+ openPrice: item?.average_open_price,
622
+ markPrice: item.mark_price
623
+ });
624
+ const maxLeverage = item.leverage || 1;
625
+ const imr = account.IMR({
626
+ maxLeverage,
627
+ baseIMR,
628
+ IMR_Factor: account2?.imr_factor[item.symbol] ?? 0,
629
+ positionNotional: notional,
630
+ ordersNotional: 0,
631
+ IMR_factor_power: 4 / 5
632
+ });
633
+ const unrealPnlROI = positions.unrealizedPnLROI({
634
+ positionQty: item.position_qty,
635
+ openPrice: item.average_open_price,
636
+ IMR: imr,
637
+ unrealizedPnL: unrealPnl
638
+ });
639
+ let unrealPnl_index = 0;
640
+ let unrealPnlROI_index = 0;
641
+ if (item.index_price) {
642
+ unrealPnl_index = positions.unrealizedPnL({
643
+ qty: item.position_qty,
644
+ openPrice: item?.average_open_price,
645
+ markPrice: item.index_price
646
+ });
647
+ unrealPnlROI_index = positions.unrealizedPnLROI({
648
+ positionQty: item.position_qty,
649
+ openPrice: item.average_open_price,
650
+ IMR: imr,
651
+ unrealizedPnL: unrealPnl_index
652
+ });
653
+ }
654
+ const filteredTPSLOrders = tpslOrders.filter(
655
+ (tpslOrder) => tpslOrder.account_id === item.account_id
656
+ );
657
+ const tpsl = formatTPSL(filteredTPSLOrders, item.symbol);
658
+ return {
659
+ ...item,
660
+ ...tpsl,
661
+ mmr: MMR,
662
+ mm,
663
+ notional,
664
+ unrealized_pnl: unrealPnl,
665
+ unrealized_pnl_ROI: unrealPnlROI,
666
+ unrealized_pnl_ROI_index: unrealPnlROI_index
667
+ };
668
+ });
669
+ };
670
+ function formatTPSL(tpslOrders, symbol) {
671
+ if (Array.isArray(tpslOrders) && tpslOrders.length) {
672
+ const { fullPositionOrder, partialPositionOrders } = findPositionTPSLFromOrders(tpslOrders, symbol);
673
+ const full_tp_sl = fullPositionOrder ? findTPSLFromOrder(fullPositionOrder) : void 0;
674
+ const partialPossitionOrder = partialPositionOrders && partialPositionOrders.length ? partialPositionOrders[0] : void 0;
675
+ const partial_tp_sl = partialPossitionOrder ? findTPSLFromOrder(partialPossitionOrder) : void 0;
676
+ return {
677
+ full_tp_sl: {
678
+ tp_trigger_price: full_tp_sl?.tp_trigger_price,
679
+ sl_trigger_price: full_tp_sl?.sl_trigger_price,
680
+ algo_order: fullPositionOrder
681
+ },
682
+ partial_tp_sl: {
683
+ order_num: partialPositionOrders?.length ?? 0,
684
+ tp_trigger_price: partial_tp_sl?.tp_trigger_price,
685
+ sl_trigger_price: partial_tp_sl?.sl_trigger_price,
686
+ algo_order: partialPossitionOrder
687
+ }
688
+ };
689
+ }
690
+ }
596
691
  var signatureMiddleware = (account2, accountId) => {
597
692
  const apiBaseUrl = useConfig("apiBaseUrl");
598
693
  return (useSWRNext) => {
@@ -642,6 +737,48 @@ var useSubAccountQuery = (query, options) => {
642
737
  }
643
738
  );
644
739
  };
740
+ function useSubAccountTPSL(subAccountIds) {
741
+ const ee = useEventEmitter();
742
+ const { data: algoOrdersResponse, mutate: mutateTPSLOrders } = useSubAccountQuery(
743
+ `/v1/algo/orders?size=100&page=1&status=${OrderStatus.INCOMPLETE}`,
744
+ {
745
+ accountId: subAccountIds,
746
+ formatter: (data) => data,
747
+ revalidateOnFocus: false
748
+ }
749
+ );
750
+ const tpslOrders = useMemo(() => {
751
+ if (!Array.isArray(algoOrdersResponse)) {
752
+ return [];
753
+ }
754
+ const algoOrders = algoOrdersResponse?.map(
755
+ (item, index) => item.rows.map((order) => ({
756
+ ...order,
757
+ account_id: subAccountIds[index]
758
+ }))
759
+ )?.flat();
760
+ return algoOrders?.filter(
761
+ (order) => [AlgoOrderRootType.POSITIONAL_TP_SL, AlgoOrderRootType.TP_SL].includes(
762
+ order.algo_type
763
+ )
764
+ );
765
+ }, [algoOrdersResponse, subAccountIds]);
766
+ const refresh = useDebouncedCallback(() => {
767
+ mutateTPSLOrders();
768
+ }, 200);
769
+ useEffect(() => {
770
+ const handler = (position) => {
771
+ if (position.account_id) {
772
+ refresh();
773
+ }
774
+ };
775
+ ee.on("tpsl:updateOrder", handler);
776
+ return () => {
777
+ ee.off("tpsl:updateOrder", handler);
778
+ };
779
+ }, [ee]);
780
+ return { tpslOrders, mutateTPSLOrders };
781
+ }
645
782
 
646
783
  // src/components/positions/combinePositions.script.ts
647
784
  var useCombinePositionsScript = (props) => {
@@ -655,12 +792,9 @@ var useCombinePositionsScript = (props) => {
655
792
  selectedAccount
656
793
  } = props;
657
794
  const { pagination, setPage } = usePagination({ pageSize: 50 });
658
- useEffect(() => {
659
- setPage(1);
660
- }, [symbol]);
661
795
  const symbolsInfo = useSymbolsInfo();
662
796
  const { state } = useAccount();
663
- const [oldPositions, , { isLoading }] = usePositionStream(symbol, {
797
+ const [mainAccountPositions, , { isLoading }] = usePositionStream(symbol, {
664
798
  calcMode,
665
799
  includedPendingOrder
666
800
  });
@@ -669,90 +803,37 @@ var useCombinePositionsScript = (props) => {
669
803
  isLoading: isPositionLoading,
670
804
  mutate: mutatePositions
671
805
  } = usePrivateQuery("/v1/client/aggregate/positions", {
672
- // formatter: (data) => data,
673
806
  errorRetryCount: 3
674
807
  });
808
+ const { allAccountIds, subAccountIds } = useMemo(() => {
809
+ const uniqueIds = new Set(
810
+ newPositions.filter((item) => item.account_id).map((item) => item.account_id).filter(Boolean)
811
+ );
812
+ const allAccountIds2 = Array.from(uniqueIds);
813
+ const subAccountIds2 = allAccountIds2.filter(
814
+ (item) => item !== state.mainAccountId
815
+ );
816
+ return { allAccountIds: allAccountIds2, subAccountIds: subAccountIds2 };
817
+ }, [newPositions, state.mainAccountId]);
675
818
  const { data: accountInfo = [], isLoading: isAccountInfoLoading } = useSubAccountQuery("/v1/client/info", {
676
- accountId: newPositions.map((item) => item.account_id),
819
+ accountId: allAccountIds,
677
820
  revalidateOnFocus: false
678
821
  });
679
- const processPositions = produce(
680
- newPositions.filter((acc) => acc.account_id !== state.mainAccountId),
681
- (draft) => {
682
- for (const item of draft) {
683
- const info = symbolsInfo[item.symbol];
684
- const notional = positions.notional(item.position_qty, item.mark_price);
685
- const account2 = accountInfo.find(
686
- (acc) => acc.account_id === item.account_id
687
- );
688
- const baseMMR = info?.("base_mmr");
689
- const baseIMR = info?.("base_imr");
690
- if (!baseMMR || !baseIMR) {
691
- continue;
692
- }
693
- const MMR = positions.MMR({
694
- baseMMR,
695
- baseIMR,
696
- IMRFactor: account2?.imr_factor[item.symbol] ?? 0,
697
- positionNotional: notional,
698
- IMR_factor_power: 4 / 5
699
- });
700
- const mm = positions.maintenanceMargin({
701
- positionQty: item.position_qty,
702
- markPrice: item.mark_price,
703
- MMR
704
- });
705
- const unrealPnl = positions.unrealizedPnL({
706
- qty: item.position_qty,
707
- openPrice: item?.average_open_price,
708
- // markPrice: unRealizedPrice,
709
- markPrice: item.mark_price
710
- });
711
- const maxLeverage = item.leverage || 1;
712
- const imr = account.IMR({
713
- maxLeverage,
714
- baseIMR,
715
- IMR_Factor: account2?.imr_factor[item.symbol] ?? 0,
716
- positionNotional: notional,
717
- ordersNotional: 0,
718
- IMR_factor_power: 4 / 5
719
- });
720
- const unrealPnlROI = positions.unrealizedPnLROI({
721
- positionQty: item.position_qty,
722
- openPrice: item.average_open_price,
723
- IMR: imr,
724
- unrealizedPnL: unrealPnl
725
- });
726
- let unrealPnl_index = 0;
727
- let unrealPnlROI_index = 0;
728
- if (item.index_price) {
729
- unrealPnl_index = positions.unrealizedPnL({
730
- qty: item.position_qty,
731
- openPrice: item?.average_open_price,
732
- // markPrice: unRealizedPrice,
733
- markPrice: item.index_price
734
- });
735
- unrealPnlROI_index = positions.unrealizedPnLROI({
736
- positionQty: item.position_qty,
737
- openPrice: item.average_open_price,
738
- IMR: imr,
739
- unrealizedPnL: unrealPnl_index
740
- });
741
- }
742
- item.mmr = MMR;
743
- item.mm = mm;
744
- item.notional = notional;
745
- item.unrealized_pnl = unrealPnl;
746
- item.unrealized_pnl_ROI = unrealPnlROI;
747
- item.unrealized_pnl_ROI_index = unrealPnlROI_index;
748
- }
749
- }
750
- );
751
- const dataSource = useDataTap(
752
- [...oldPositions?.rows, ...processPositions].filter(
753
- (acc) => acc.position_qty !== 0
754
- )
755
- ) ?? [];
822
+ const { tpslOrders, mutateTPSLOrders } = useSubAccountTPSL(subAccountIds);
823
+ const subAccountPositions = useMemo(() => {
824
+ return calculatePositions(
825
+ newPositions.filter((item) => item.account_id !== state.mainAccountId),
826
+ symbolsInfo,
827
+ accountInfo,
828
+ tpslOrders
829
+ );
830
+ }, [newPositions, symbolsInfo, accountInfo, state.mainAccountId, tpslOrders]);
831
+ const allPositions = useMemo(() => {
832
+ return [...mainAccountPositions?.rows, ...subAccountPositions].filter(
833
+ (item) => item.position_qty !== 0
834
+ );
835
+ }, [mainAccountPositions, subAccountPositions]);
836
+ const dataSource = useDataTap(allPositions) ?? [];
756
837
  const filtered = useMemo(() => {
757
838
  if (!selectedAccount || selectedAccount === "All accounts" /* ALL */) {
758
839
  return dataSource;
@@ -771,18 +852,23 @@ var useCombinePositionsScript = (props) => {
771
852
  subAccounts: state.subAccounts
772
853
  });
773
854
  }, [filtered, state.mainAccountId, state.subAccounts]);
774
- const mergedLoading = useMemo(() => {
775
- return isLoading || isPositionLoading || isAccountInfoLoading;
776
- }, [isLoading, isPositionLoading, isAccountInfoLoading]);
855
+ const loading = isLoading || isPositionLoading || isAccountInfoLoading;
856
+ useEffect(() => {
857
+ setPage(1);
858
+ }, [symbol]);
859
+ const mutateList = useCallback(() => {
860
+ mutatePositions();
861
+ mutateTPSLOrders();
862
+ }, []);
777
863
  return {
778
864
  tableData: groupDataSource,
779
- isLoading: mergedLoading,
865
+ isLoading: loading,
780
866
  pnlNotionalDecimalPrecision,
781
867
  sharePnLConfig,
782
868
  symbol,
783
869
  onSymbolChange,
784
870
  pagination,
785
- mutatePositions
871
+ mutatePositions: mutateList
786
872
  };
787
873
  };
788
874
  var groupDataByAccount = (data, options) => {
@@ -790,12 +876,12 @@ var groupDataByAccount = (data, options) => {
790
876
  const map = /* @__PURE__ */ new Map();
791
877
  for (const item of data) {
792
878
  const accountId = item.account_id || mainAccountId;
793
- const findSubAccount = subAccounts.find((acc) => acc.id === accountId);
879
+ const findSubAccount = subAccounts.find((item2) => item2.id === accountId);
794
880
  if (map.has(accountId)) {
795
881
  map.get(accountId)?.children?.push(item);
796
882
  } else {
797
883
  map.set(accountId, {
798
- id: accountId,
884
+ account_id: accountId,
799
885
  description: accountId === mainAccountId ? i18n.t("common.mainAccount") : findSubAccount?.description || formatAddress(findSubAccount?.id || ""),
800
886
  children: [item]
801
887
  });
@@ -956,7 +1042,14 @@ var OrderInfoCard = ({
956
1042
  );
957
1043
  };
958
1044
  var ReversePosition = (props) => {
959
- const { displayInfo, className, style, onConfirm, onCancel } = props;
1045
+ const {
1046
+ displayInfo,
1047
+ validationError,
1048
+ className,
1049
+ style,
1050
+ onConfirm,
1051
+ onCancel
1052
+ } = props;
960
1053
  const { t } = useTranslation();
961
1054
  if (!displayInfo) {
962
1055
  return null;
@@ -983,6 +1076,7 @@ var ReversePosition = (props) => {
983
1076
  /* @__PURE__ */ jsx(Text.numeral, { dp: quoteDp, size: "sm", intensity: 80, children: markPrice }),
984
1077
  /* @__PURE__ */ jsx(Text, { size: "sm", intensity: 36, children: quote })
985
1078
  ] });
1079
+ const showBelowMinError = validationError === "belowMin";
986
1080
  return /* @__PURE__ */ jsxs(
987
1081
  Flex,
988
1082
  {
@@ -1042,11 +1136,12 @@ var ReversePosition = (props) => {
1042
1136
  baseDp
1043
1137
  }
1044
1138
  ),
1045
- /* @__PURE__ */ jsx(Text, { size: "2xs", color: "warning", weight: "semibold", children: t("positions.reverse.description") })
1139
+ showBelowMinError ? /* @__PURE__ */ jsx(Text, { size: "2xs", color: "danger", weight: "semibold", children: t("positions.reverse.error.belowMin") }) : /* @__PURE__ */ jsx(Text, { size: "2xs", color: "warning", weight: "semibold", children: t("positions.reverse.description") })
1046
1140
  ]
1047
1141
  }
1048
1142
  );
1049
1143
  };
1144
+ var MAX_BATCH_ORDER_SIZE = 20;
1050
1145
  var useReversePositionEnabled = () => {
1051
1146
  const { isMobile, isDesktop } = useScreen();
1052
1147
  const [desktopEnabled, setDesktopEnabled] = useLocalStorage(
@@ -1083,12 +1178,26 @@ var useReversePositionEnabled = () => {
1083
1178
  };
1084
1179
  var useReversePositionScript = (options) => {
1085
1180
  const { position, onSuccess, onError } = options || {};
1086
- const { t } = useTranslation();
1087
1181
  const { isEnabled, setEnabled } = useReversePositionEnabled();
1088
1182
  const symbolsInfo = useSymbolsInfo();
1089
1183
  const symbol = position?.symbol || "";
1090
1184
  const { data: symbolMarketPrice } = useMarkPrice(symbol);
1091
1185
  const symbolInfo = symbolsInfo?.[symbol];
1186
+ const baseMin = useMemo(() => {
1187
+ if (!symbolInfo)
1188
+ return 0;
1189
+ return symbolInfo("base_min") || 0;
1190
+ }, [symbolInfo]);
1191
+ const baseMax = useMemo(() => {
1192
+ if (!symbolInfo)
1193
+ return 0;
1194
+ return symbolInfo("base_max") || 0;
1195
+ }, [symbolInfo]);
1196
+ const baseDp = useMemo(() => {
1197
+ if (!symbolInfo)
1198
+ return 6;
1199
+ return symbolInfo("base_dp") || 6;
1200
+ }, [symbolInfo]);
1092
1201
  const positionQty = useMemo(() => {
1093
1202
  if (!position)
1094
1203
  return 0;
@@ -1099,21 +1208,62 @@ var useReversePositionScript = (options) => {
1099
1208
  return false;
1100
1209
  return position.position_qty > 0;
1101
1210
  }, [position]);
1102
- const oppositeSide = useMemo(() => {
1103
- return isLong ? OrderSide.SELL : OrderSide.BUY;
1104
- }, [isLong]);
1105
- const maxOpenQty = useMaxQty(symbol, oppositeSide, false);
1106
- const reverseQty = useMemo(() => {
1107
- if (!position)
1108
- return 0;
1109
- const desiredQty = positionQty;
1110
- if (desiredQty > maxOpenQty && maxOpenQty > 0) {
1111
- return maxOpenQty;
1211
+ const reverseQty = positionQty;
1212
+ const validationError = useMemo(() => {
1213
+ if (!position || !symbolInfo)
1214
+ return null;
1215
+ if (baseMin > 0 && reverseQty < baseMin) {
1216
+ return "belowMin";
1112
1217
  }
1113
- return desiredQty;
1114
- }, [positionQty, maxOpenQty]);
1115
- const [doCreateOrder, { isMutating }] = useSubAccountMutation(
1116
- "/v1/order",
1218
+ return null;
1219
+ }, [position, symbolInfo, reverseQty, baseMin]);
1220
+ const splitOrders = useMemo(() => {
1221
+ if (!position || !symbolInfo || baseMax <= 0 || reverseQty <= 0) {
1222
+ return { needsSplit: false, orders: [] };
1223
+ }
1224
+ const buildOrder = (qty, side, reduceOnly) => {
1225
+ return {
1226
+ symbol: position.symbol,
1227
+ order_type: OrderType.MARKET,
1228
+ side,
1229
+ order_quantity: new Decimal(qty).todp(baseDp).toString(),
1230
+ reduce_only: reduceOnly
1231
+ };
1232
+ };
1233
+ const closeSide = isLong ? OrderSide.SELL : OrderSide.BUY;
1234
+ const openSide = isLong ? OrderSide.SELL : OrderSide.BUY;
1235
+ if (reverseQty <= baseMax) {
1236
+ return {
1237
+ needsSplit: false,
1238
+ orders: [
1239
+ buildOrder(reverseQty, closeSide, true),
1240
+ buildOrder(reverseQty, openSide, false)
1241
+ ]
1242
+ };
1243
+ }
1244
+ const orders = [];
1245
+ const perOrderQty = baseMax;
1246
+ const numOrders = Math.ceil(reverseQty / baseMax);
1247
+ for (let i = 0; i < numOrders - 1; i++) {
1248
+ orders.push(buildOrder(perOrderQty, closeSide, true));
1249
+ }
1250
+ orders.push(
1251
+ buildOrder(reverseQty - perOrderQty * (numOrders - 1), closeSide, true)
1252
+ );
1253
+ for (let i = 0; i < numOrders - 1; i++) {
1254
+ orders.push(buildOrder(perOrderQty, openSide, false));
1255
+ }
1256
+ orders.push(
1257
+ buildOrder(reverseQty - perOrderQty * (numOrders - 1), openSide, false)
1258
+ );
1259
+ return {
1260
+ needsSplit: true,
1261
+ orders
1262
+ };
1263
+ }, [position, symbolInfo, reverseQty, baseMax, baseDp]);
1264
+ const [isReversing, setIsReversing] = useState(false);
1265
+ const [doBatchCreateOrder] = useSubAccountMutation(
1266
+ "/v1/batch-order",
1117
1267
  "POST",
1118
1268
  {
1119
1269
  accountId: position?.account_id
@@ -1121,55 +1271,74 @@ var useReversePositionScript = (options) => {
1121
1271
  );
1122
1272
  const reversePosition = useCallback(async () => {
1123
1273
  if (!position || positionQty === 0) {
1124
- toast.error("No position to reverse");
1125
1274
  return false;
1126
1275
  }
1276
+ if (validationError) {
1277
+ return false;
1278
+ }
1279
+ setIsReversing(true);
1127
1280
  try {
1128
- const closeSide = isLong ? OrderSide.SELL : OrderSide.BUY;
1129
- const closeOrder = await doCreateOrder({
1130
- symbol: position.symbol,
1131
- order_type: OrderType.MARKET,
1132
- side: closeSide,
1133
- order_quantity: new Decimal(positionQty).toString(),
1134
- reduce_only: true
1135
- });
1136
- const openSide = isLong ? OrderSide.SELL : OrderSide.BUY;
1137
- const openOrder = await doCreateOrder({
1138
- symbol: position.symbol,
1139
- order_type: OrderType.MARKET,
1140
- side: openSide,
1141
- order_quantity: new Decimal(reverseQty).toString(),
1142
- reduce_only: false
1143
- });
1144
- if (!openOrder.success) {
1145
- throw new Error(
1146
- openOrder.message || "Failed to open opposite position"
1147
- );
1281
+ const ordersArray = splitOrders.orders;
1282
+ if (ordersArray.length > MAX_BATCH_ORDER_SIZE) {
1283
+ for (let i = 0; i < ordersArray.length; i += MAX_BATCH_ORDER_SIZE) {
1284
+ const batch = ordersArray.slice(i, i + MAX_BATCH_ORDER_SIZE);
1285
+ const result = await doBatchCreateOrder({
1286
+ orders: batch,
1287
+ symbol: position.symbol
1288
+ });
1289
+ await new Promise(
1290
+ (resolve) => setTimeout(resolve, batch.length * 110)
1291
+ );
1292
+ if (!result || result.error) {
1293
+ throw result?.error || new Error("Batch order failed");
1294
+ }
1295
+ }
1296
+ } else {
1297
+ const result = await doBatchCreateOrder({
1298
+ orders: ordersArray,
1299
+ symbol: position.symbol
1300
+ });
1301
+ if (!result || result.error) {
1302
+ throw result?.error || new Error("Batch order failed");
1303
+ }
1148
1304
  }
1149
1305
  onSuccess?.();
1150
1306
  return true;
1151
1307
  } catch (error) {
1152
1308
  onError?.(error);
1153
1309
  return false;
1310
+ } finally {
1311
+ setIsReversing(false);
1154
1312
  }
1155
- }, [position, positionQty, reverseQty, isLong, doCreateOrder, t, onSuccess]);
1313
+ }, [
1314
+ position,
1315
+ positionQty,
1316
+ reverseQty,
1317
+ isLong,
1318
+ doBatchCreateOrder,
1319
+ splitOrders,
1320
+ symbolInfo,
1321
+ validationError,
1322
+ onSuccess,
1323
+ onError
1324
+ ]);
1156
1325
  const displayInfo = useMemo(() => {
1157
1326
  if (!position || !symbolInfo) {
1158
1327
  return null;
1159
1328
  }
1160
1329
  const base = symbolInfo("base");
1161
1330
  const quote = symbolInfo("quote");
1162
- const baseDp = symbolInfo("base_dp");
1331
+ const baseDp2 = symbolInfo("base_dp");
1163
1332
  const quoteDp = symbolInfo("quote_dp");
1164
1333
  const leverage = position.leverage || 1;
1165
1334
  return {
1166
1335
  symbol: position.symbol,
1167
1336
  base,
1168
1337
  quote,
1169
- baseDp,
1338
+ baseDp: baseDp2,
1170
1339
  quoteDp,
1171
- positionQty: new Decimal(positionQty).todp(baseDp).toString(),
1172
- reverseQty: new Decimal(reverseQty).todp(baseDp).toString(),
1340
+ positionQty: new Decimal(positionQty).todp(baseDp2).toString(),
1341
+ reverseQty: new Decimal(reverseQty).todp(baseDp2).toString(),
1173
1342
  markPrice: symbolMarketPrice ? new Decimal(symbolMarketPrice).todp(quoteDp).toString() : "--",
1174
1343
  leverage,
1175
1344
  isLong
@@ -1186,11 +1355,13 @@ var useReversePositionScript = (options) => {
1186
1355
  isEnabled,
1187
1356
  setEnabled,
1188
1357
  reversePosition,
1189
- isReversing: isMutating,
1358
+ isReversing,
1190
1359
  displayInfo,
1191
1360
  positionQty,
1192
1361
  reverseQty,
1193
- isLong
1362
+ isLong,
1363
+ validationError,
1364
+ splitOrders
1194
1365
  };
1195
1366
  };
1196
1367
  var ReversePositionWidget = (props) => {
@@ -1209,6 +1380,7 @@ var ReversePositionWidget = (props) => {
1209
1380
  }
1210
1381
  });
1211
1382
  const actions = useMemo(() => {
1383
+ const hasValidationError = !!state.validationError;
1212
1384
  return {
1213
1385
  primary: {
1214
1386
  label: t("common.confirm"),
@@ -1229,10 +1401,11 @@ var ReversePositionWidget = (props) => {
1229
1401
  }
1230
1402
  },
1231
1403
  loading: state.isReversing,
1232
- disabled: state.isReversing || !state.displayInfo
1404
+ disabled: state.isReversing || !state.displayInfo || hasValidationError
1233
1405
  },
1234
1406
  secondary: {
1235
1407
  label: t("common.cancel"),
1408
+ disabled: state.isReversing,
1236
1409
  onClick: async () => {
1237
1410
  reject?.("cancel");
1238
1411
  hide();
@@ -1252,6 +1425,7 @@ var ReversePositionWidget = (props) => {
1252
1425
  content: "oui-border oui-border-line-6"
1253
1426
  },
1254
1427
  actions,
1428
+ closable: !state.isReversing,
1255
1429
  children: /* @__PURE__ */ jsx(ReversePosition, { ...state })
1256
1430
  }
1257
1431
  );
@@ -2014,12 +2188,12 @@ var TPSLEditIcon = () => {
2014
2188
  };
2015
2189
  var AddIcon = (props) => {
2016
2190
  const { position, baseDp, quoteDp, tpslOrder } = usePositionsRowContext();
2017
- const [needConfirm] = useLocalStorage("orderly_order_confirm", true);
2018
2191
  const { t } = useTranslation();
2019
2192
  const { isMobile } = useScreen();
2020
2193
  const onAdd = () => {
2021
2194
  const dialogId = isMobile ? TPSLSheetId : TPSLDialogId;
2022
2195
  const modalParams = {
2196
+ position,
2023
2197
  symbol: position.symbol,
2024
2198
  baseDP: baseDp,
2025
2199
  quoteDP: quoteDp,
@@ -3429,7 +3603,7 @@ var CombinePositions = (props) => {
3429
3603
  dataSource,
3430
3604
  expanded: true,
3431
3605
  getSubRows: (row) => row.children,
3432
- generatedRowKey: (record) => record.id,
3606
+ generatedRowKey: (record) => `${record.account_id}${record.symbol || ""}`,
3433
3607
  onCell: (column, record) => {
3434
3608
  const isGroup = (record.children ?? []).length > 0;
3435
3609
  if (isGroup) {
@@ -3453,6 +3627,9 @@ var CombinePositions = (props) => {
3453
3627
  },
3454
3628
  manualPagination: false,
3455
3629
  pagination,
3630
+ classNames: {
3631
+ scroll: "oui-pb-10"
3632
+ },
3456
3633
  testIds: {
3457
3634
  body: "oui-testid-dataList-position-tab-body"
3458
3635
  }