Repository: Drakkar-Software/OctoBot-Trading (language: python)

Issue:
[FundingManager] Add predicted_funding_rate

[Future][Portfolio] Add assets



Discussion / hints:

Fix patch:
diff --git a/octobot_trading/enums.py b/octobot_trading/enums.py
index 3c40e10cd..b630d5354 100644
--- a/octobot_trading/enums.py
+++ b/octobot_trading/enums.py
@@ -287,7 +287,9 @@ class ExchangeConstantsPositionColumns(enum.Enum):
     UNREALISED_PNL = "unrealised_pnl"
     REALISED_PNL = "realised_pnl"
     QUANTITY = "quantity"
+    SIZE = "size"
     VALUE = "value"
+    INITIAL_MARGIN = "initial_margin"
     MARGIN = "margin"
     LEVERAGE = "leverage"
     MARGIN_TYPE = "margin_type"
diff --git a/octobot_trading/exchanges/implementations/future_exchange_simulator.py b/octobot_trading/exchanges/implementations/future_exchange_simulator.py
index 77c74f871..90d0e0ae4 100644
--- a/octobot_trading/exchanges/implementations/future_exchange_simulator.py
+++ b/octobot_trading/exchanges/implementations/future_exchange_simulator.py
@@ -13,6 +13,8 @@
 #
 #  You should have received a copy of the GNU Lesser General Public
 #  License along with this library.
+import decimal
+
 import octobot_trading.constants as constants
 import octobot_trading.enums as enums
 import octobot_trading.exchanges.connectors as exchange_connectors
@@ -23,6 +25,7 @@ class FutureExchangeSimulator(exchanges_types.FutureExchange):
     DEFAULT_SYMBOL_LEVERAGE = constants.ONE
     DEFAULT_SYMBOL_MARGIN_TYPE = enums.MarginType.ISOLATED
     DEFAULT_SYMBOL_CONTRACT_TYPE = enums.FutureContractType.PERPETUAL
+    DEFAULT_SYMBOL_FUNDING_RATE = decimal.Decimal(0.0001)
 
     def __init__(self, config, exchange_manager, backtesting):
         super().__init__(config, exchange_manager)
@@ -104,3 +107,6 @@ async def get_margin_type(self, symbol: str):
 
     async def get_contract_type(self, symbol: str):
         return self.DEFAULT_SYMBOL_CONTRACT_TYPE
+
+    async def get_funding_rate(self, symbol: str, **kwargs: dict):
+        return self.DEFAULT_SYMBOL_FUNDING_RATE
diff --git a/octobot_trading/personal_data/__init__.py b/octobot_trading/personal_data/__init__.py
index 64bdbddf7..8409b96ed 100644
--- a/octobot_trading/personal_data/__init__.py
+++ b/octobot_trading/personal_data/__init__.py
@@ -112,9 +112,8 @@
     PositionsChannel,
     PositionsUpdaterSimulator,
     Position,
-    parse_position_type,
-    CrossPosition,
-    IsolatedPosition,
+    LinearPosition,
+    InversePosition,
     PositionsUpdater,
     PositionsManager,
     create_position_instance_from_raw,
@@ -169,8 +168,10 @@
 }
 
 TraderPositionTypeClasses = {
-    octobot_trading.enums.TraderPositionType.CROSS: CrossPosition,
-    octobot_trading.enums.TraderPositionType.ISOLATED: IsolatedPosition,
+    octobot_trading.enums.FutureContractType.PERPETUAL: LinearPosition,
+    octobot_trading.enums.FutureContractType.INVERSE_PERPETUAL: InversePosition,
+    octobot_trading.enums.FutureContractType.EXPIRABLE: LinearPosition,
+    octobot_trading.enums.FutureContractType.INVERSE_EXPIRABLE: InversePosition,
 }
 
 __all__ = [
@@ -258,9 +259,8 @@
     "portfolio_to_float",
     "PositionsUpdaterSimulator",
     "Position",
-    "parse_position_type",
-    "CrossPosition",
-    "IsolatedPosition",
+    "LinearPosition",
+    "InversePosition",
     "PositionState",
     "LiquidatePositionState",
     "OpenPositionState",
diff --git a/octobot_trading/personal_data/exchange_personal_data.py b/octobot_trading/personal_data/exchange_personal_data.py
index 854d2fd89..bf8af5405 100644
--- a/octobot_trading/personal_data/exchange_personal_data.py
+++ b/octobot_trading/personal_data/exchange_personal_data.py
@@ -85,6 +85,21 @@ async def handle_portfolio_update_from_order(self, order,
             self.logger.exception(e, True, f"Failed to update balance : {e}")
             return False
 
+    async def handle_portfolio_update_from_position(self, position,
+                                                    require_exchange_update: bool = True,
+                                                    is_liquidated: bool = False,
+                                                    should_notify: bool = True) -> bool:
+        try:
+            changed: bool = await self.portfolio_manager.handle_balance_update_from_position(
+                position=position, require_exchange_update=require_exchange_update)
+            if should_notify:
+                await exchange_channel.get_chan(octobot_trading.constants.BALANCE_CHANNEL, self.exchange_manager.id). \
+                    get_internal_producer().send(self.portfolio_manager.portfolio.portfolio)
+            return changed
+        except AttributeError as e:
+            self.logger.exception(e, True, f"Failed to update balance : {e}")
+            return False
+
     async def handle_portfolio_profitability_update(self, balance, mark_price, symbol, should_notify: bool = True):
         try:
             portfolio_profitability = self.portfolio_manager.portfolio_profitability
@@ -222,13 +237,34 @@ async def handle_position_update(self, symbol, position_id, position, should_not
                     .send(cryptocurrency=self.exchange_manager.exchange.get_pair_cryptocurrency(symbol),
                           symbol=symbol,
                           position=position_instance.to_dict(),
-                          is_updated=changed,
-                          is_liquidated=position_instance.is_liquidated())
+                          is_updated=changed)
             return changed
         except Exception as e:
             self.logger.exception(e, True, f"Failed to update position : {e}")
             return False
 
+    async def update_position_from_filled_order(self, symbol, order, should_notify: bool = True):
+        try:
+            # Don't update if order filled quantity is null
+            if order.filled_quantity == 0:
+                return False
+
+            position_instance = self.positions_manager.get_symbol_position(symbol)
+            await position_instance.update(
+                update_quantity=order.filled_quantity if order.is_long() else -order.filled_quantity)
+
+            if should_notify:
+                await exchange_channel.get_chan(octobot_trading.constants.POSITIONS_CHANNEL,
+                                                self.exchange_manager.id).get_internal_producer() \
+                    .send(cryptocurrency=self.exchange_manager.exchange.get_pair_cryptocurrency(symbol),
+                          symbol=symbol,
+                          position=position_instance.to_dict(),
+                          is_updated=True)
+            return True
+        except Exception as e:
+            self.logger.exception(e, True, f"Failed to update position : {e}")
+            return False
+
     async def handle_position_instance_update(self, position, should_notify: bool = True):
         try:
             changed: bool = self.positions_manager.upsert_position_instance(position)
@@ -238,13 +274,26 @@ async def handle_position_instance_update(self, position, should_notify: bool =
                     .send(cryptocurrency=self.exchange_manager.exchange.get_pair_cryptocurrency(position.symbol),
                           symbol=position.symbol,
                           position=position,
-                          is_updated=changed,
-                          is_liquidated=position.is_liquidated())
+                          is_updated=changed)
             return changed
         except Exception as e:
             self.logger.exception(e, True, f"Failed to update position instance : {e}")
             return False
 
+    async def handle_position_update_notification(self, position):
+        """
+        Notify Positions channel for Position update
+        :param position: the updated position
+        """
+        try:
+            await exchange_channel.get_chan(octobot_trading.constants.POSITIONS_CHANNEL,
+                                            self.exchange_manager.id).get_internal_producer() \
+                .send(cryptocurrency=self.exchange_manager.exchange.get_pair_cryptocurrency(position.symbol),
+                      symbol=position.symbol,
+                      position=position)
+        except ValueError as e:
+            self.logger.error(f"Failed to send position update notification : {e}")
+
     def get_order_portfolio(self, order):
         return order.linked_portfolio if order.linked_portfolio is not None else self.portfolio_manager.portfolio
 
diff --git a/octobot_trading/personal_data/positions/__init__.pxd b/octobot_trading/personal_data/positions/__init__.pxd
index ac8cd43a0..2435cb11b 100644
--- a/octobot_trading/personal_data/positions/__init__.pxd
+++ b/octobot_trading/personal_data/positions/__init__.pxd
@@ -22,13 +22,12 @@ from octobot_trading.personal_data.positions.position_state cimport (
 from octobot_trading.personal_data.positions cimport position
 from octobot_trading.personal_data.positions.position cimport (
     Position,
-    parse_position_type,
 )
 
 from octobot_trading.personal_data.positions cimport types
 from octobot_trading.personal_data.positions.types cimport (
-    CrossPosition,
-    IsolatedPosition,
+    LinearPosition,
+    InversePosition,
 )
 
 from octobot_trading.personal_data.positions cimport states
@@ -68,9 +67,8 @@ __all__ = [
     "PositionsChannel",
     "PositionsUpdaterSimulator",
     "Position",
-    "parse_position_type",
-    "CrossPosition",
-    "IsolatedPosition",
+    "LinearPosition",
+    "InversePosition",
     "PositionsUpdater",
     "PositionsManager",
     "create_position_instance_from_raw",
diff --git a/octobot_trading/personal_data/positions/__init__.py b/octobot_trading/personal_data/positions/__init__.py
index f89e5afee..aae9bedb7 100644
--- a/octobot_trading/personal_data/positions/__init__.py
+++ b/octobot_trading/personal_data/positions/__init__.py
@@ -22,13 +22,12 @@
 from octobot_trading.personal_data.positions import position
 from octobot_trading.personal_data.positions.position import (
     Position,
-    parse_position_type,
 )
 
 from octobot_trading.personal_data.positions import types
 from octobot_trading.personal_data.positions.types import (
-    CrossPosition,
-    IsolatedPosition,
+    LinearPosition,
+    InversePosition,
 )
 
 from octobot_trading.personal_data.positions import states
@@ -69,9 +68,8 @@
     "PositionsChannel",
     "PositionsUpdaterSimulator",
     "Position",
-    "parse_position_type",
-    "CrossPosition",
-    "IsolatedPosition",
+    "LinearPosition",
+    "InversePosition",
     "PositionsUpdater",
     "PositionsManager",
     "create_position_instance_from_raw",
diff --git a/octobot_trading/personal_data/positions/channel/positions.py b/octobot_trading/personal_data/positions/channel/positions.py
index f5098d96c..9d3750109 100644
--- a/octobot_trading/personal_data/positions/channel/positions.py
+++ b/octobot_trading/personal_data/positions/channel/positions.py
@@ -22,10 +22,10 @@
 
 
 class PositionsProducer(exchanges_channel.ExchangeChannelProducer):
-    async def push(self, positions, is_liquidated=False):
-        await self.perform(positions, is_liquidated=is_liquidated)
+    async def push(self, positions):
+        await self.perform(positions)
 
-    async def perform(self, positions, is_liquidated=False):
+    async def perform(self, positions):
         try:
             for position in positions:
                 if not position:
@@ -46,14 +46,13 @@ async def perform(self, positions, is_liquidated=False):
                                         get_pair_cryptocurrency(symbol),
                                         symbol=symbol,
                                         position=position,
-                                        is_updated=changed,
-                                        is_liquidated=is_liquidated)
+                                        is_updated=changed)
         except asyncio.CancelledError:
             self.logger.info("Update tasks cancelled.")
         except Exception as e:
             self.logger.exception(e, True, f"Exception when triggering update: {e}")
 
-    async def send(self, cryptocurrency, symbol, position, is_updated=False, is_liquidated=False):
+    async def send(self, cryptocurrency, symbol, position, is_updated=False):
         for consumer in self.channel.get_filtered_consumers(symbol=symbol):
             await consumer.queue.put({
                 "exchange": self.channel.exchange_manager.exchange_name,
@@ -61,8 +60,7 @@ async def send(self, cryptocurrency, symbol, position, is_updated=False, is_liqu
                 "cryptocurrency": cryptocurrency,
                 "symbol": symbol,
                 "position": position,
-                "is_updated": is_updated,
-                "is_liquidated": is_liquidated
+                "is_updated": is_updated
             })
 
 
diff --git a/octobot_trading/personal_data/positions/channel/positions_updater.py b/octobot_trading/personal_data/positions/channel/positions_updater.py
index 659b8480b..3bac4cfc2 100644
--- a/octobot_trading/personal_data/positions/channel/positions_updater.py
+++ b/octobot_trading/personal_data/positions/channel/positions_updater.py
@@ -147,7 +147,7 @@ async def fetch_open_positions(self):
             await self._push_open_positions(open_positions)
 
     async def _push_open_positions(self, open_positions):
-        await self.push(positions=open_positions, is_liquidated=False)
+        await self.push(positions=open_positions)
 
         if self._should_push_mark_price():
             for position in open_positions:
diff --git a/octobot_trading/personal_data/positions/channel/positions_updater_simulator.py b/octobot_trading/personal_data/positions/channel/positions_updater_simulator.py
index 166d2e977..be800c9bc 100644
--- a/octobot_trading/personal_data/positions/channel/positions_updater_simulator.py
+++ b/octobot_trading/personal_data/positions/channel/positions_updater_simulator.py
@@ -13,13 +13,11 @@
 #
 #  You should have received a copy of the GNU Lesser General Public
 #  License along with this library.
-import copy
+import decimal
 
 import octobot_commons.logging as logging
-
 import octobot_trading.constants as constants
 import octobot_trading.exchange_channel as exchanges_channel
-import octobot_trading.personal_data.positions as positions
 import octobot_trading.personal_data.positions.channel.positions_updater as positions_updater
 
 
@@ -45,38 +43,8 @@ async def handle_mark_price(self, exchange: str, exchange_id: str, cryptocurrenc
         MarkPrice channel consumer callback
         """
         try:
-            await self._update_positions_status(cryptocurrency=cryptocurrency, symbol=symbol, mark_price=mark_price)
+            symbol_position = self.channel.exchange_manager.exchange_personal_data.positions_manager \
+                .get_symbol_position(symbol=symbol)
+            await symbol_position.update(mark_price=decimal.Decimal(str(mark_price)))
         except Exception as e:
             self.logger.exception(e, True, f"Fail to handle mark price : {e}")
-
-    async def _update_positions_status(self, cryptocurrency: str, symbol: str, mark_price):
-        """
-        Ask positions to check their status
-        Ask liquidation and P&L update process if required
-        """
-        for position in copy.copy(
-                self.channel.exchange_manager.exchange_personal_data.positions_manager.get_open_positions(
-                    symbol=symbol)):
-            await self._update_position_status(position, mark_price)
-
-            if position.is_liquidated():
-                await exchanges_channel.get_chan(constants.POSITIONS_CHANNEL, self.channel.exchange_manager.id) \
-                    .get_internal_producer().send(cryptocurrency=cryptocurrency,
-                                                  symbol=position.symbol,
-                                                  order=position.to_dict(),
-                                                  is_liquidated=position.is_liquidated(),
-                                                  is_updated=False)
-
-    async def _update_position_status(self, position: positions.Position, mark_price):
-        """
-        Call position status update
-        """
-        try:
-            position.mark_price = mark_price
-            await position.update_position_status()
-
-            if position.is_liquidated():
-                self.logger.debug(f"{position.symbol} (ID : {position.position_id})"
-                                  f" liquidated on {self.channel.exchange.name} at {position.mark_price} ")
-        except Exception as e:
-            self.logger.exception(e, True, f"Fail to update position status : {e} (concerned position : {position}).")
diff --git a/octobot_trading/personal_data/positions/position.pxd b/octobot_trading/personal_data/positions/position.pxd
index 0772681be..c58d54357 100644
--- a/octobot_trading/personal_data/positions/position.pxd
+++ b/octobot_trading/personal_data/positions/position.pxd
@@ -19,8 +19,8 @@ cimport octobot_trading.personal_data.positions.position_state as positions_stat
 cimport octobot_trading.util as util
 
 cdef class Position(util.Initializable):
-    cdef object trader
-    cdef object exchange_manager
+    cdef public object trader
+    cdef public object exchange_manager
 
     cdef public bint simulated
 
@@ -28,23 +28,27 @@ cdef class Position(util.Initializable):
     cdef public str currency
     cdef public str market
     cdef public str position_id
+    cdef public str logger_name
 
     cdef public object status # PositionStatus
     cdef public object margin_type # MarginType
     cdef public object side # PositionSide
+    cdef public object symbol_contract # FutureContract
 
     cdef public positions_states.PositionState state
 
-    cdef public int leverage
-
+    cdef public object leverage
     cdef public object entry_price
     cdef public object mark_price
     cdef public object liquidation_price
     cdef public object quantity
+    cdef public object size
     cdef public object value
     cdef public object margin
+    cdef public object initial_margin
     cdef public object unrealised_pnl
     cdef public object realised_pnl
+    cdef public object fee_to_close
 
     cdef public double timestamp
     cdef public double creation_time
@@ -61,26 +65,50 @@ cdef class Position(util.Initializable):
                       object mark_price,
                       object liquidation_price,
                       object quantity,
+                      object size,
                       object value,
                       object margin,
                       object unrealised_pnl,
                       object realised_pnl,
-                      int leverage,
+                      object leverage,
                       object margin_type,
                       object status=*,
                       object side=*)
-    cdef bint _check_for_liquidation(self)
-    cdef bint _switch_side_if_necessary(self)
     cdef bint _should_change(self, object original_value, object new_value)
+    cdef void _update_quantity_and_mark_price(self, object update_quantity=*, object mark_price=*)
+    cdef void _update_mark_price(self, object mark_price)
+    cdef void _update_entry_price_if_necessary(self, object mark_price)
+    cdef void _update_quantity_or_size_if_necessary(self)
+    cdef void _update_quantity(self, object update_size)
+    cdef void _update_size(self)
+    cdef void _update_margin(self)
+    cdef void _update_side(self)
 
-    cpdef dict to_dict(self)
-
+    cpdef void update_value(self)
+    cpdef void update_pnl(self)
+    cpdef void update_initial_margin(self)
+    cpdef object get_maintenance_margin_rate(self)
+    cpdef object get_initial_margin_rate(self)
+    cpdef object calculate_maintenance_margin(self)
     cpdef bint update_from_raw(self, dict raw_position)
+    cpdef void update_liquidation_price(self)
+    cpdef void update_cross_liquidation_price(self)
+    cpdef void update_isolated_liquidation_price(self)
+    cpdef object get_bankruptcy_price(self, bint with_mark_price=*)
+    cpdef object get_maker_fee(self)
+    cpdef object get_taker_fee(self)
+    cpdef object get_two_way_taker_fee(self)
+    cpdef object get_order_cost(self)
+    cpdef object get_fee_to_open(self)
+    cpdef void update_fee_to_close(self)
+    cpdef bint is_open(self)
     cpdef bint is_liquidated(self)
+    cpdef bint is_refreshing(self)
     cpdef bint is_long(self)
     cpdef bint is_short(self)
-    cpdef bint is_open(self)
+    cpdef bint is_idle(self)
+    cpdef object get_unrealised_pnl_percent(self)
     cpdef str to_string(self)
+    cpdef dict to_dict(self)
     cpdef void clear(self)
-
-cpdef object parse_position_type(dict raw_position)
+    cpdef str get_logger_name(self)
diff --git a/octobot_trading/personal_data/positions/position.py b/octobot_trading/personal_data/positions/position.py
index 07d26ebf3..fcc096095 100644
--- a/octobot_trading/personal_data/positions/position.py
+++ b/octobot_trading/personal_data/positions/position.py
@@ -24,26 +24,37 @@
 
 
 class Position(util.Initializable):
-    def __init__(self, trader):
+    def __init__(self, trader, symbol_contract):
         super().__init__()
         self.trader = trader
         self.exchange_manager = trader.exchange_manager
         self.simulated = trader.simulate
 
+        self.logger_name = None
         self.position_id = None
         self.timestamp = 0
         self.symbol = None
         self.currency, self.market = None, None
+        self.status = enums.PositionStatus.OPEN
+        self.side = enums.PositionSide.UNKNOWN
+
+        # Contract
+        self.symbol_contract = symbol_contract
+        self.leverage = symbol_contract.current_leverage if symbol_contract.current_leverage else constants.ONE
+        self.margin_type = None
+
+        # Prices
         self.entry_price = constants.ZERO
         self.mark_price = constants.ZERO
+        self.liquidation_price = constants.ZERO
+        self.fee_to_close = constants.ZERO
+
+        # Size
         self.quantity = constants.ZERO
+        self.size = constants.ZERO
         self.value = constants.ZERO
+        self.initial_margin = constants.ZERO
         self.margin = constants.ZERO
-        self.liquidation_price = constants.ZERO
-        self.leverage = 0
-        self.margin_type = None
-        self.status = enums.PositionStatus.OPEN
-        self.side = enums.PositionSide.UNKNOWN
 
         # PNL
         self.unrealised_pnl = constants.ZERO
@@ -59,22 +70,19 @@ def __init__(self, trader):
     def get_name(cls):
         return cls.__name__
 
-    async def initialize_impl(self, **kwargs):
+    def get_logger_name(self):
         """
-        Initialize position status update tasks
+        :return: The position logger name
         """
-        await positions_states.create_position_state(self, **kwargs)
-        await self.update_position_status()
+        if self.logger_name is None:
+            self.logger_name = f"{self.get_name()} | {self.position_id}"
+        return self.logger_name
 
-    async def update_position_status(self, force_refresh=False):
+    async def initialize_impl(self, **kwargs):
         """
-        update_position_status will define the rules for a simulated position to be liquidated
-        Should be called after updating position.mark_price
+        Initialize position status update tasks
         """
-        raise NotImplementedError("update_position_status not implemented")
-
-    def is_open(self):
-        return self.state is None or self.state.is_open()
+        await positions_states.create_position_state(self, **kwargs)
 
     async def on_open(self, force_open=False, is_from_exchange_data=False):
         self.state = positions_states.OpenPositionState(self, is_from_exchange_data=is_from_exchange_data)
@@ -90,7 +98,7 @@ def _should_change(self, original_value, new_value):
 
     def _update(self, position_id, symbol, currency, market, timestamp,
                 entry_price, mark_price, liquidation_price,
-                quantity, value, margin,
+                quantity, size, value, margin,
                 unrealised_pnl, realised_pnl,
                 leverage, margin_type, status=None, side=None):
         changed: bool = False
@@ -113,7 +121,10 @@ def _update(self, position_id, symbol, currency, market, timestamp,
 
         if self._should_change(self.quantity, quantity):
             self.quantity = quantity
-            self._switch_side_if_necessary()
+            changed = True
+
+        if self._should_change(self.size, size):
+            self.size = size
             changed = True
 
         if self._should_change(self.value, value):
@@ -132,8 +143,8 @@ def _update(self, position_id, symbol, currency, market, timestamp,
             self.realised_pnl = realised_pnl
             changed = True
 
-        if self._should_change(self.leverage, int(leverage)):
-            self.leverage = int(leverage)
+        if self._should_change(self.leverage, leverage):
+            self.leverage = leverage
             changed = True
 
         if self._should_change(self.margin_type, margin_type):
@@ -159,13 +170,180 @@ def _update(self, position_id, symbol, currency, market, timestamp,
         if self._should_change(self.side.value, side):
             self.side = enums.PositionSide(side)
 
-        if self.side is enums.PositionSide.UNKNOWN and self.quantity:
-            self._switch_side_if_necessary()
-
+        self._update_side()
+        self._update_quantity_or_size_if_necessary()
+        self._update_entry_price_if_necessary(mark_price)
+        self.update_pnl()
         return changed
 
+    async def update(self, update_quantity=None, mark_price=None):
+        self._update_quantity_and_mark_price(update_quantity=update_quantity, mark_price=mark_price)
+        if not self.is_idle():
+            await self._check_for_liquidation()
+
+    def _update_quantity_and_mark_price(self, update_quantity=None, mark_price=None):
+        """
+        Updates position mark price and / or size
+        :param update_quantity: the update quantity
+        :param mark_price: the update mark_price
+        """
+        if mark_price is not None:
+            self._update_mark_price(mark_price)
+        # TODO Check for liquidation before updating size ?
+        if update_quantity is not None:
+            self._update_quantity(update_quantity)
+
+    def _update_mark_price(self, mark_price):
+        """
+        Updates position mark_price and triggers size related attributes update
+        :param mark_price: the update mark_price
+        """
+        self.mark_price = mark_price
+        self._update_entry_price_if_necessary(mark_price)
+        if not self.is_idle():
+            self.update_value()
+            self.update_pnl()
+
+    def _update_entry_price_if_necessary(self, mark_price):
+        """
+        Update the position entry price when entry price is 0 or when the position is new
+        :param mark_price: the position mark_price
+        """
+        if self.entry_price == constants.ZERO:
+            self.entry_price = mark_price
+
+    def _update_quantity(self, update_quantity):
+        """
+        Updates position size and triggers quantity related attributes update
+        :param update_quantity: the update quantity
+        """
+        self.quantity += update_quantity
+        self._update_size()
+        self._update_side()
+        self.update_initial_margin()
+        self.update_fee_to_close()
+        self.update_liquidation_price()
+        self.update_value()
+        self.update_pnl()
+
+    def _update_quantity_or_size_if_necessary(self):
+        """
+        Set quantity from size if quantity is not set and size is set or update size
+        """
+        if self.quantity == constants.ZERO and self.size != constants.ZERO:
+            self.quantity = self.size / self.leverage
+        elif self.size == constants.ZERO and self.quantity != constants.ZERO:
+            self._update_size()
+
+    def _update_size(self):
+        """
+        Update position size from position quantity
+        """
+        self.size = self.quantity * self.leverage
+
+    def update_value(self):
+        raise NotImplementedError("update_value not implemented")
+
+    def update_pnl(self):
+        raise NotImplementedError("update_pnl not implemented")
+
+    def update_initial_margin(self):
+        raise NotImplementedError("update_initial_margin not implemented")
+
+    def get_maintenance_margin_rate(self):
+        """
+        :return: Position symbol funding rate
+        """
+        return self.exchange_manager.exchange_symbols_data.\
+            get_exchange_symbol_data(self.symbol).funding_manager.funding_rate
+
+    def get_initial_margin_rate(self):
+        """
+        :return: Initial Margin Rate = 1 / Leverage
+        """
+        try:
+            return constants.ONE / self.leverage
+        except decimal.DivisionByZero:
+            return constants.ZERO
+
+    def calculate_maintenance_margin(self):
+        raise NotImplementedError("calculate_maintenance_margin not implemented")
+
+    def update_liquidation_price(self):
+        """
+        Updates position liquidation price
+        Should call _update_fee_to_close() at the end of the implementation
+        """
+        if self.symbol_contract.is_isolated():
+            self.update_isolated_liquidation_price()
+        else:
+            self.update_cross_liquidation_price()
+
+    def update_cross_liquidation_price(self):
+        raise NotImplementedError("update_cross_liquidation_price not implemented")
+
+    def update_isolated_liquidation_price(self):
+        raise NotImplementedError("update_isolated_liquidation_price not implemented")
+
+    def get_bankruptcy_price(self, with_mark_price=False):
+        """
+        The bankruptcy price refers to the price at which the initial margin of all positions is lost.
+        :param with_mark_price: if price should be mark price instead of entry price
+        :return: the bankruptcy price
+        """
+        raise NotImplementedError("get_bankruptcy_price not implemented")
+
+    def get_maker_fee(self):
+        """
+        :return: Position maker fee
+        """
+        try:
+            symbol_fees = self.exchange_manager.exchange.get_fees(self.symbol)
+            return decimal.Decimal(
+                f"{symbol_fees[enums.ExchangeConstantsMarketPropertyColumns.MAKER.value]}") / constants.ONE_HUNDRED
+        except decimal.DivisionByZero:
+            return constants.ZERO
+
+    def get_taker_fee(self):
+        """
+        :return: Position taker fee
+        """
+        try:
+            symbol_fees = self.exchange_manager.exchange.get_fees(self.symbol)
+            return decimal.Decimal(
+                f"{symbol_fees[enums.ExchangeConstantsMarketPropertyColumns.TAKER.value]}") / constants.ONE_HUNDRED
+        except decimal.DivisionByZero:
+            return constants.ZERO
+
+    def get_two_way_taker_fee(self):
+        """
+        :return: 2-way taker fee = fee to open + fee to close
+        """
+        return self.get_fee_to_open() + self.fee_to_close
+
+    def get_order_cost(self):
+        raise NotImplementedError("get_order_cost not implemented")
+
+    def get_fee_to_open(self):
+        raise NotImplementedError("get_fee_to_open not implemented")
+
+    def update_fee_to_close(self):
+        raise NotImplementedError("update_fee_to_close not implemented")
+
+    def _update_margin(self):
+        """
+        Updates position margin = Initial margin + Fee to close
+        """
+        self.margin = self.initial_margin + self.fee_to_close
+
+    def is_open(self):
+        return self.state is None or self.state.is_open()
+
     def is_liquidated(self):
-        return self.status is enums.PositionStatus.LIQUIDATING
+        return self.state is not None and self.state.is_liquidated()
+
+    def is_refreshing(self):
+        return self.state is not None and self.state.is_refreshing()
 
     def is_long(self):
         return self.side is enums.PositionSide.LONG
@@ -173,12 +351,18 @@ def is_long(self):
     def is_short(self):
         return self.side is enums.PositionSide.SHORT
 
+    def is_idle(self):
+        return self.quantity == constants.ZERO
+
+    def get_unrealised_pnl_percent(self):
+        """
+        :return: Unrealized P&L% = [ Position's unrealized P&L / Position Margin ] x 100%
+        """
+        return (self.unrealised_pnl / self.margin) * 100
+
     async def recreate(self):
         self.exchange_manager.exchange_personal_data.positions_manager.recreate_position(self)
 
-    async def update_from_filled_order(self, order):
-        self.quantity = order.filled_quantity if order.is_long() else -order.filled_quantity
-
     def update_from_raw(self, raw_position):
         symbol = str(raw_position.get(enums.ExchangeConstantsPositionColumns.SYMBOL.value, None))
         currency, market = self.exchange_manager.get_exchange_quote_and_base(symbol)
@@ -186,17 +370,27 @@ def update_from_raw(self, raw_position):
             "symbol": symbol,
             "currency": currency,
             "market": market,
-            "entry_price": decimal.Decimal(str(raw_position.get(enums.ExchangeConstantsPositionColumns.ENTRY_PRICE.value, 0.0))),
-            "mark_price": decimal.Decimal(str(raw_position.get(enums.ExchangeConstantsPositionColumns.MARK_PRICE.value, 0.0))),
-            "liquidation_price": decimal.Decimal(str(raw_position.get(enums.ExchangeConstantsPositionColumns.LIQUIDATION_PRICE.value, 0.0))),
-            "quantity": decimal.Decimal(str(raw_position.get(enums.ExchangeConstantsPositionColumns.QUANTITY.value, 0.0))),
-            "value": decimal.Decimal(str(raw_position.get(enums.ExchangeConstantsPositionColumns.VALUE.value, 0.0))),
-            "margin": decimal.Decimal(str(raw_position.get(enums.ExchangeConstantsPositionColumns.MARGIN.value, 0.0))),
+            "entry_price": decimal.Decimal(
+                str(raw_position.get(enums.ExchangeConstantsPositionColumns.ENTRY_PRICE.value, constants.ZERO))),
+            "mark_price": decimal.Decimal(
+                str(raw_position.get(enums.ExchangeConstantsPositionColumns.MARK_PRICE.value, constants.ZERO))),
+            "liquidation_price": decimal.Decimal(
+                str(raw_position.get(enums.ExchangeConstantsPositionColumns.LIQUIDATION_PRICE.value, constants.ZERO))),
+            "quantity": decimal.Decimal(
+                str(raw_position.get(enums.ExchangeConstantsPositionColumns.QUANTITY.value, constants.ZERO))),
+            "size": decimal.Decimal(
+                str(raw_position.get(enums.ExchangeConstantsPositionColumns.SIZE.value, constants.ZERO))),
+            "value": decimal.Decimal(
+                str(raw_position.get(enums.ExchangeConstantsPositionColumns.VALUE.value, constants.ZERO))),
+            "margin": decimal.Decimal(
+                str(raw_position.get(enums.ExchangeConstantsPositionColumns.MARGIN.value, constants.ZERO))),
             "position_id": str(raw_position.get(enums.ExchangeConstantsPositionColumns.ID.value, symbol)),
-            "timestamp": raw_position.get(enums.ExchangeConstantsPositionColumns.TIMESTAMP.value, 0.0),
-            "unrealised_pnl": decimal.Decimal(str(raw_position.get(enums.ExchangeConstantsPositionColumns.UNREALISED_PNL.value, 0.0))),
-            "realised_pnl": decimal.Decimal(str(raw_position.get(enums.ExchangeConstantsPositionColumns.REALISED_PNL.value, 0.0))),
-            "leverage": raw_position.get(enums.ExchangeConstantsPositionColumns.LEVERAGE.value, 0),
+            "timestamp": raw_position.get(enums.ExchangeConstantsPositionColumns.TIMESTAMP.value, 0),
+            "unrealised_pnl": decimal.Decimal(
+                str(raw_position.get(enums.ExchangeConstantsPositionColumns.UNREALISED_PNL.value, constants.ZERO))),
+            "realised_pnl": decimal.Decimal(
+                str(raw_position.get(enums.ExchangeConstantsPositionColumns.REALISED_PNL.value, constants.ZERO))),
+            "leverage": raw_position.get(enums.ExchangeConstantsPositionColumns.LEVERAGE.value, None),
             "margin_type": raw_position.get(enums.ExchangeConstantsPositionColumns.MARGIN_TYPE.value,
                                             enums.TraderPositionType.ISOLATED),
             "status": position_util.parse_position_status(raw_position),
@@ -211,7 +405,9 @@ def to_dict(self):
             enums.ExchangeConstantsPositionColumns.TIMESTAMP.value: self.timestamp,
             enums.ExchangeConstantsPositionColumns.SIDE.value: self.side.value,
             enums.ExchangeConstantsPositionColumns.QUANTITY.value: self.quantity,
+            enums.ExchangeConstantsPositionColumns.SIZE.value: self.size,
             enums.ExchangeConstantsPositionColumns.VALUE.value: self.value,
+            enums.ExchangeConstantsPositionColumns.INITIAL_MARGIN.value: self.initial_margin,
             enums.ExchangeConstantsPositionColumns.MARGIN.value: self.margin,
             enums.ExchangeConstantsPositionColumns.ENTRY_PRICE.value: self.entry_price,
             enums.ExchangeConstantsPositionColumns.MARK_PRICE.value: self.mark_price,
@@ -222,29 +418,30 @@ def to_dict(self):
             enums.ExchangeConstantsPositionColumns.MARGIN_TYPE.value: self.margin_type,
         }
 
-    def _check_for_liquidation(self):
+    async def _check_for_liquidation(self):
         """
         _check_for_liquidation will defines rules for a simulated position to be liquidated
         :return: True if the position is being liquidated else False
         """
         if self.is_short():
-            if self.mark_price >= self.liquidation_price:
+            if self.mark_price >= self.liquidation_price > constants.ZERO:
                 self.status = enums.PositionStatus.LIQUIDATING
-                return True
+                await positions_states.create_position_state(self)
         if self.is_long():
-            if self.mark_price <= self.liquidation_price:
+            if self.mark_price <= self.liquidation_price > constants.ZERO:
                 self.status = enums.PositionStatus.LIQUIDATING
-                return True
-        return False
+                await positions_states.create_position_state(self)
 
-    def _switch_side_if_necessary(self):
+    def _update_side(self):
         """
         check if self.side still represents the position side
         """
-        if self.quantity >= 0 and self.is_short():
+        if self.quantity >= constants.ZERO:
             self.side = enums.PositionSide.LONG
-        else:
+        elif self.quantity < constants.ZERO:
             self.side = enums.PositionSide.SHORT
+        else:
+            self.side = enums.PositionSide.UNKNOWN
 
     def __str__(self):
         return self.to_string()
@@ -256,6 +453,26 @@ def to_string(self):
                 f"State : {self.state.state.value if self.state is not None else 'Unknown'} | "
                 f"id : {self.position_id}")
 
+    async def reset(self):
+        """
+        Reset position attributes
+        """
+        self.entry_price = constants.ZERO
+        self.mark_price = constants.ZERO
+        self.quantity = constants.ZERO
+        self.size = constants.ZERO
+        self.value = constants.ZERO
+        self.initial_margin = constants.ZERO
+        self.margin = constants.ZERO
+        self.liquidation_price = constants.ZERO
+        self.fee_to_close = constants.ZERO
+        self.status = enums.PositionStatus.OPEN
+        self.side = enums.PositionSide.UNKNOWN
+        self.unrealised_pnl = constants.ZERO
+        self.realised_pnl = constants.ZERO
+        self.creation_time = constants.ZERO
+        await self.on_open()
+
     def clear(self):
         """
         Clear position references
@@ -263,15 +480,3 @@ def clear(self):
         self.state.clear()
         self.trader = None
         self.exchange_manager = None
-
-
-def parse_position_type(raw_position):
-    """
-    Parse the raw position type to match a enums.TraderPositionType value
-    :param raw_position: the raw position dict
-    :return: the enums.TraderPositionType value
-    """
-    try:
-        return enums.TraderPositionType(raw_position[enums.ExchangeConstantsPositionColumns.MARGIN_TYPE.value])
-    except (KeyError, ValueError):
-        return enums.TraderPositionType.ISOLATED
diff --git a/octobot_trading/personal_data/positions/position_factory.py b/octobot_trading/personal_data/positions/position_factory.py
index 3660cd683..e29cd85f2 100644
--- a/octobot_trading/personal_data/positions/position_factory.py
+++ b/octobot_trading/personal_data/positions/position_factory.py
@@ -24,20 +24,21 @@ def create_position_instance_from_raw(trader, raw_position):
     :param raw_position: the raw position dictionary
     :return: the created position
     """
-    position_type = personal_data.parse_position_type(raw_position)
-    position = create_position_from_type(trader, position_type)
+    position_symbol_contract = trader.exchange_manager.exchange.get_pair_future_contract(
+        raw_position.get(enums.ExchangeConstantsPositionColumns.SYMBOL.value))
+    position = create_position_from_type(trader, position_symbol_contract)
     position.update_from_raw(raw_position)
     return position
 
 
-def create_position_from_type(trader, position_type):
+def create_position_from_type(trader, symbol_contract):
     """
     Creates a position instance from a position type
     :param trader: the trader instance
-    :param position_type: the position type, a enums.TraderPositionType value
+    :param symbol_contract: the position symbol contract
     :return: the created position
     """
-    return personal_data.TraderPositionTypeClasses[position_type](trader)
+    return personal_data.TraderPositionTypeClasses[symbol_contract.contract_type](trader, symbol_contract)
 
 
 def create_symbol_position(trader, symbol):
diff --git a/octobot_trading/personal_data/positions/position_state.pxd b/octobot_trading/personal_data/positions/position_state.pxd
index 77ce48118..91bc286bf 100644
--- a/octobot_trading/personal_data/positions/position_state.pxd
+++ b/octobot_trading/personal_data/positions/position_state.pxd
@@ -18,3 +18,5 @@ cimport octobot_trading.personal_data.state as state_class
 
 cdef class PositionState(state_class.State):
     cdef public object position  # instance of Position
+
+    cpdef bint is_liquidated(self)
diff --git a/octobot_trading/personal_data/positions/position_state.py b/octobot_trading/personal_data/positions/position_state.py
index be94ea6e3..7b634db0b 100644
--- a/octobot_trading/personal_data/positions/position_state.py
+++ b/octobot_trading/personal_data/positions/position_state.py
@@ -28,6 +28,12 @@ def __init__(self, position, is_from_exchange_data):
         # related position
         self.position = position
 
+    def is_liquidated(self) -> bool:
+        """
+        :return: True if the instance is considered as liquidated
+        """
+        return False
+
     def log_event_message(self, state_message, error=None):
         """
         Log a position state event
diff --git a/octobot_trading/personal_data/positions/states/liquidate_position_state.py b/octobot_trading/personal_data/positions/states/liquidate_position_state.py
index 990b97b8e..09eb5ced8 100644
--- a/octobot_trading/personal_data/positions/states/liquidate_position_state.py
+++ b/octobot_trading/personal_data/positions/states/liquidate_position_state.py
@@ -13,6 +13,8 @@
 #
 #  You should have received a copy of the GNU Lesser General Public
 #  License along with this library.
+import octobot_commons.logging as logging
+
 import octobot_trading.enums as enums
 import octobot_trading.personal_data.positions.position_state as position_state
 
@@ -34,6 +36,9 @@ def is_pending(self) -> bool:
     def is_closed(self) -> bool:
         return self.state is enums.PositionStates.LIQUIDATED
 
+    def is_liquidated(self) -> bool:
+        return True
+
     async def on_refresh_successful(self):
         """
         Verify the position is properly closed
@@ -44,6 +49,19 @@ async def on_refresh_successful(self):
 
     async def terminate(self):
         """
-        Handle position to trade conversion
+        Handle position liquidation process
         """
-        # TODO
+        logging.get_logger(self.position.get_logger_name()).warning(f"{self.position.position_id} is being liquidated")
+
+        # update portfolio with liquidated position
+        async with self.position.exchange_manager.exchange_personal_data.portfolio_manager.portfolio.lock:
+            await self.position.exchange_manager.exchange_personal_data.\
+                handle_portfolio_update_from_position(self.position)
+
+        # notify position liquidated
+        await self.position.exchange_manager.exchange_personal_data.handle_position_update_notification(self.position)
+
+        # reset position to initial state (open)
+        await self.position.reset()
+
+        logging.get_logger(self.position.get_logger_name()).warning(f"{self.position.position_id} has been liquidated")
diff --git a/octobot_trading/personal_data/positions/types/__init__.pxd b/octobot_trading/personal_data/positions/types/__init__.pxd
index ca9ba2cc3..bdb1ca4e1 100644
--- a/octobot_trading/personal_data/positions/types/__init__.pxd
+++ b/octobot_trading/personal_data/positions/types/__init__.pxd
@@ -14,18 +14,18 @@
 #  You should have received a copy of the GNU Lesser General Public
 #  License along with this library.
 
-from octobot_trading.personal_data.positions.types cimport cross_position
-from octobot_trading.personal_data.positions.types cimport isolated_position
+from octobot_trading.personal_data.positions.types cimport linear_position
+from octobot_trading.personal_data.positions.types cimport inverse_position
 
-from octobot_trading.personal_data.positions.types.cross_position cimport (
-    CrossPosition,
+from octobot_trading.personal_data.positions.types.linear_position cimport (
+    LinearPosition,
 )
 
-from octobot_trading.personal_data.positions.types.isolated_position cimport (
-    IsolatedPosition,
+from octobot_trading.personal_data.positions.types.inverse_position cimport (
+    InversePosition,
 )
 
 __all__ = [
-    "CrossPosition",
-    "IsolatedPosition",
+    "LinearPosition",
+    "InversePosition",
 ]
diff --git a/octobot_trading/personal_data/positions/types/__init__.py b/octobot_trading/personal_data/positions/types/__init__.py
index 3084e197b..04e2d7575 100644
--- a/octobot_trading/personal_data/positions/types/__init__.py
+++ b/octobot_trading/personal_data/positions/types/__init__.py
@@ -14,18 +14,18 @@
 #  You should have received a copy of the GNU Lesser General Public
 #  License along with this library.
 
-from octobot_trading.personal_data.positions.types import cross_position
-from octobot_trading.personal_data.positions.types import isolated_position
+from octobot_trading.personal_data.positions.types import linear_position
+from octobot_trading.personal_data.positions.types import inverse_position
 
-from octobot_trading.personal_data.positions.types.cross_position import (
-    CrossPosition,
+from octobot_trading.personal_data.positions.types.linear_position import (
+    LinearPosition,
 )
 
-from octobot_trading.personal_data.positions.types.isolated_position import (
-    IsolatedPosition,
+from octobot_trading.personal_data.positions.types.inverse_position import (
+    InversePosition,
 )
 
 __all__ = [
-    "CrossPosition",
-    "IsolatedPosition",
+    "LinearPosition",
+    "InversePosition",
 ]
diff --git a/octobot_trading/personal_data/positions/types/isolated_position.pxd b/octobot_trading/personal_data/positions/types/inverse_position.pxd
similarity index 93%
rename from octobot_trading/personal_data/positions/types/isolated_position.pxd
rename to octobot_trading/personal_data/positions/types/inverse_position.pxd
index 50480c44c..1b00c5c73 100644
--- a/octobot_trading/personal_data/positions/types/isolated_position.pxd
+++ b/octobot_trading/personal_data/positions/types/inverse_position.pxd
@@ -16,5 +16,5 @@
 #  License along with this library.
 cimport octobot_trading.personal_data.positions.position as position_class
 
-cdef class IsolatedPosition(position_class.Position):
+cdef class InversePosition(position_class.Position):
     pass
diff --git a/octobot_trading/personal_data/positions/types/inverse_position.py b/octobot_trading/personal_data/positions/types/inverse_position.py
new file mode 100644
index 000000000..c0316e5b8
--- /dev/null
+++ b/octobot_trading/personal_data/positions/types/inverse_position.py
@@ -0,0 +1,130 @@
+#  Drakkar-Software OctoBot-Trading
+#  Copyright (c) Drakkar-Software, All rights reserved.
+#
+#  This library is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU Lesser General Public
+#  License as published by the Free Software Foundation; either
+#  version 3.0 of the License, or (at your option) any later version.
+#
+#  This library is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+#  Lesser General Public License for more details.
+#
+#  You should have received a copy of the GNU Lesser General Public
+#  License along with this library.
+import decimal
+
+import octobot_trading.constants as constants
+import octobot_trading.personal_data.positions.position as position_class
+
+
+class InversePosition(position_class.Position):
+    def update_value(self):
+        """
+        Notional value = CONTRACT_QUANTITY / MARK_PRICE
+        """
+        try:
+            self.value = self.quantity / self.mark_price
+        except (decimal.DivisionByZero, decimal.InvalidOperation):
+            self.value = constants.ZERO
+
+    def update_pnl(self):
+        """
+        LONG_PNL = CONTRACT_QUANTITY x [(1 / ENTRY_PRICE) - (1 / MARK_PRICE)]
+        SHORT_PNL = CONTRACT_QUANTITY x [(1 / MARK_PRICE) - (1 / ENTRY_PRICE)]
+        """
+        try:
+            if self.is_long():
+                self.unrealised_pnl = self.quantity * ((constants.ONE / self.entry_price) -
+                                                       (constants.ONE / self.mark_price))
+            elif self.is_short():
+                self.unrealised_pnl = self.quantity * ((constants.ONE / self.mark_price) -
+                                                       (constants.ONE / self.entry_price))
+            else:
+                self.unrealised_pnl = constants.ZERO
+        except (decimal.DivisionByZero, decimal.InvalidOperation):
+            self.unrealised_pnl = constants.ZERO
+
+    def update_initial_margin(self):
+        """
+        Updates position initial margin = Position quantity / (entry price x leverage)
+        """
+        try:
+            self.initial_margin = self.quantity / (self.entry_price * self.leverage)
+            self._update_margin()
+        except (decimal.DivisionByZero, decimal.InvalidOperation):
+            self.initial_margin = constants.ZERO
+
+    def calculate_maintenance_margin(self):
+        """
+        :return: Maintenance margin = (Position quantity / entry price) x Maintenance margin rate
+        """
+        try:
+            return (self.quantity / self.entry_price) * self.get_maintenance_margin_rate()
+        except (decimal.DivisionByZero, decimal.InvalidOperation):
+            return constants.ZERO
+
+    def update_isolated_liquidation_price(self):
+        """
+        Updates isolated position liquidation price
+        LONG LIQUIDATION PRICE = (ENTRY_PRICE x LEVERAGE) / (LEVERAGE + 1 - (MAINTENANCE_MARGIN_RATE x LEVERAGE))
+        SHORT LIQUIDATION PRICE = (ENTRY_PRICE x LEVERAGE) / (LEVERAGE - 1 + (MAINTENANCE_MARGIN_RATE x LEVERAGE))
+        """
+        try:
+            if self.is_long():
+                self.liquidation_price = (self.entry_price * self.leverage) / \
+                                         (self.leverage + constants.ONE - (
+                                                 self.get_maintenance_margin_rate() * self.leverage))
+            elif self.is_short():
+                self.liquidation_price = (self.entry_price * self.leverage) / \
+                                         (self.leverage - constants.ONE + (
+                                                 self.get_maintenance_margin_rate() * self.leverage))
+            else:
+                self.liquidation_price = constants.ZERO
+            self.update_fee_to_close()
+        except (decimal.DivisionByZero, decimal.InvalidOperation):
+            self.liquidation_price = constants.ZERO
+
+    def get_bankruptcy_price(self, with_mark_price=False):
+        """
+        :param with_mark_price: if price should be mark price instead of entry price
+        :return: Bankruptcy Price for
+        Long position = (Entry Price x Leverage) / (Leverage + 1)
+        Short position = (Entry Price x Leverage) / (Leverage - 1)
+        """
+        try:
+            if self.is_long():
+                return (self.mark_price if with_mark_price else self.entry_price * self.leverage) \
+                       / (self.leverage + constants.ONE)
+            elif self.is_short():
+                return (self.mark_price if with_mark_price else self.entry_price * self.leverage) \
+                       / (self.leverage - constants.ONE)
+            return constants.ZERO
+        except (decimal.DivisionByZero, decimal.InvalidOperation):
+            return constants.ZERO
+
+    def get_order_cost(self):
+        """
+        :return: Order Cost = Initial margin + 2-way taker fee (fee to open + fee to close)
+        """
+        return self.initial_margin + self.get_two_way_taker_fee()
+
+    def get_fee_to_open(self):
+        """
+        :return: Fee to open = (Quantity / Mark price ) x taker fee
+        """
+        try:
+            return (self.quantity / self.mark_price) * self.get_taker_fee()
+        except (decimal.DivisionByZero, decimal.InvalidOperation):
+            return constants.ZERO
+
+    def update_fee_to_close(self):
+        """
+        :return: Fee to close = (Quantity / Bankruptcy Price derived from mark price) x taker fee
+        """
+        try:
+            self.fee_to_close = (self.quantity / self.get_bankruptcy_price(with_mark_price=True)) * self.get_taker_fee()
+            self._update_margin()
+        except (decimal.DivisionByZero, decimal.InvalidOperation):
+            self.fee_to_close = constants.ZERO
diff --git a/octobot_trading/personal_data/positions/types/cross_position.pxd b/octobot_trading/personal_data/positions/types/linear_position.pxd
similarity index 94%
rename from octobot_trading/personal_data/positions/types/cross_position.pxd
rename to octobot_trading/personal_data/positions/types/linear_position.pxd
index cb09c218b..5b2ed0ba0 100644
--- a/octobot_trading/personal_data/positions/types/cross_position.pxd
+++ b/octobot_trading/personal_data/positions/types/linear_position.pxd
@@ -16,5 +16,5 @@
 #  License along with this library.
 cimport octobot_trading.personal_data.positions.position as position_class
 
-cdef class CrossPosition(position_class.Position):
+cdef class LinearPosition(position_class.Position):
     pass
diff --git a/octobot_trading/personal_data/positions/types/linear_position.py b/octobot_trading/personal_data/positions/types/linear_position.py
new file mode 100644
index 000000000..394d7cd79
--- /dev/null
+++ b/octobot_trading/personal_data/positions/types/linear_position.py
@@ -0,0 +1,113 @@
+#  Drakkar-Software OctoBot-Trading
+#  Copyright (c) Drakkar-Software, All rights reserved.
+#
+#  This library is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU Lesser General Public
+#  License as published by the Free Software Foundation; either
+#  version 3.0 of the License, or (at your option) any later version.
+#
+#  This library is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+#  Lesser General Public License for more details.
+#
+#  You should have received a copy of the GNU Lesser General Public
+#  License along with this library.
+import decimal
+
+import octobot_trading.constants as constants
+import octobot_trading.personal_data.positions.position as position_class
+
+
+class LinearPosition(position_class.Position):
+    def update_value(self):
+        """
+        Notional value = CONTRACT_QUANTITY * MARK_PRICE
+        """
+        self.value = self.quantity * self.mark_price
+
+    def update_pnl(self):
+        """
+        LONG_PNL = CONTRACT_QUANTITY x [MARK_PRICE - ENTRY_PRICE]
+        SHORT_PNL = CONTRACT_QUANTITY x [ENTRY_PRICE - MARK_PRICE]
+        """
+        try:
+            if self.is_long():
+                self.unrealised_pnl = self.quantity * (self.mark_price - self.entry_price)
+            elif self.is_short():
+                self.unrealised_pnl = self.quantity * (self.entry_price - self.mark_price)
+            else:
+                self.unrealised_pnl = constants.ZERO
+        except (decimal.DivisionByZero, decimal.InvalidOperation):
+            self.unrealised_pnl = constants.ZERO
+
+    def update_initial_margin(self):
+        """
+        Updates position initial margin = (Position quantity x entry price) / leverage
+        """
+        try:
+            self.initial_margin = (self.quantity * self.entry_price) / self.leverage
+            self._update_margin()
+        except (decimal.DivisionByZero, decimal.InvalidOperation):
+            self.initial_margin = constants.ZERO
+
+    def calculate_maintenance_margin(self):
+        """
+        :return: Maintenance margin = Position quantity x entry price x Maintenance margin rate
+        """
+        return self.quantity * self.entry_price * self.get_maintenance_margin_rate()
+
+    def update_isolated_liquidation_price(self):
+        """
+        Updates isolated position liquidation price
+        LONG LIQUIDATION PRICE = ENTRY_PRICE * (1 - Initial Margin Rate + MAINTENANCE_MARGIN_RATE)
+        SHORT LIQUIDATION PRICE = ENTRY_PRICE * (1 + Initial Margin Rate - MAINTENANCE_MARGIN_RATE)
+        TODO : support extra margin ->
+        - Long : - Extra Margin Added/ Contract Size
+        - Short : + Extra Margin Added/ Contract Size
+        """
+        try:
+            if self.is_long():
+                self.liquidation_price = self.entry_price * (
+                        constants.ONE - self.get_initial_margin_rate() + self.calculate_maintenance_margin())
+            elif self.is_short():
+                self.liquidation_price = self.entry_price * (
+                        constants.ONE + self.get_initial_margin_rate() - self.calculate_maintenance_margin())
+            else:
+                self.liquidation_price = constants.ZERO
+            self.update_fee_to_close()
+        except (decimal.DivisionByZero, decimal.InvalidOperation):
+            self.liquidation_price = constants.ZERO
+
+    def get_bankruptcy_price(self, with_mark_price=False):
+        """
+        :param with_mark_price: if price should be mark price instead of entry price
+        :return: Bankruptcy Price
+        Long position = Entry Price x (1 - Initial Margin Rate)
+        Short position = Entry Price × (1 + Initial Margin Rate)
+        """
+        if self.is_long():
+            return self.mark_price \
+                if with_mark_price else self.entry_price * (constants.ONE - self.get_initial_margin_rate())
+        elif self.is_short():
+            return self.mark_price \
+                if with_mark_price else self.entry_price * (constants.ONE + self.get_initial_margin_rate())
+        return constants.ZERO
+
+    def get_fee_to_open(self):
+        """
+        :return: Fee to open = (Quantity * Mark Price) x Taker fee
+        """
+        return self.quantity * self.mark_price * self.get_taker_fee()
+
+    def get_order_cost(self):
+        """
+        :return: Order Cost = Initial Margin + Two-Way Taker Fee
+        """
+        return self.initial_margin + self.get_two_way_taker_fee()
+
+    def update_fee_to_close(self):
+        """
+        :return: Fee to close = (Quantity * Bankruptcy Price derived from mark price) x Taker fee
+        """
+        self.fee_to_close = self.quantity * self.get_bankruptcy_price(with_mark_price=True) * self.get_taker_fee()
diff --git a/setup.py b/setup.py
index 2938e7322..f8ed72a68 100644
--- a/setup.py
+++ b/setup.py
@@ -141,8 +141,8 @@ def build_ext(*args, **kwargs):
     "octobot_trading.personal_data.positions.channel.positions",
     "octobot_trading.personal_data.positions.channel.positions_updater",
     "octobot_trading.personal_data.positions.channel.positions_updater_simulator",
-    "octobot_trading.personal_data.positions.types.cross_position",
-    "octobot_trading.personal_data.positions.types.isolated_position",
+    "octobot_trading.personal_data.positions.types.inverse_position",
+    "octobot_trading.personal_data.positions.types.linear_position",
     "octobot_trading.personal_data.positions.states.liquidate_position_state",
     "octobot_trading.personal_data.positions.states.open_position_state",
     "octobot_trading.personal_data.positions.states.position_state_factory",


Test patch:
diff --git a/tests/exchanges/__init__.py b/tests/exchanges/__init__.py
index 433f5ac0f..038d5a578 100644
--- a/tests/exchanges/__init__.py
+++ b/tests/exchanges/__init__.py
@@ -14,9 +14,8 @@
 #  You should have received a copy of the GNU Lesser General Public
 #  License along with this library.
 import os
-from shutil import copyfile
-
 import pytest
+
 import octobot_commons.constants as commons_constants
 from octobot_backtesting.backtesting import Backtesting
 from octobot_backtesting.constants import CONFIG_BACKTESTING
@@ -24,8 +23,7 @@
 from octobot_commons.asyncio_tools import wait_asyncio_next_cycle
 from octobot_commons.enums import TimeFrames
 
-from octobot_commons.tests.test_config import load_test_config, TEST_CONFIG_FOLDER
-from octobot_tentacles_manager.constants import USER_REFERENCE_TENTACLE_CONFIG_PATH
+from octobot_commons.tests.test_config import load_test_config
 from octobot_trading.api.exchange import create_exchange_builder, cancel_ccxt_throttle_task
 from octobot_trading.exchanges.exchange_manager import ExchangeManager
 from octobot_trading.exchanges.traders.trader_simulator import TraderSimulator
@@ -38,19 +36,63 @@
 
 
 @pytest.fixture
-async def exchange_manager(request):
-    config = None
-    exchange_name = DEFAULT_EXCHANGE_NAME
-    is_spot = True
-    is_margin = False
-    is_future = False
-    if hasattr(request, "param"):
-        config, exchange_name, is_spot, is_margin, is_future = request.param
+async def exchange_manager():
+    exchange_manager_instance = ExchangeManager(load_test_config(), DEFAULT_EXCHANGE_NAME)
+    exchange_manager_instance.is_spot_only = True
+    exchange_manager_instance.is_simulated = False
+    await exchange_manager_instance.initialize()
+    yield exchange_manager_instance
+    cancel_ccxt_throttle_task()
+    await exchange_manager_instance.stop()
+    # let updaters gracefully shutdown
+    await wait_asyncio_next_cycle()
 
-    exchange_manager_instance = ExchangeManager(config if config is not None else load_test_config(), exchange_name)
-    exchange_manager_instance.is_spot_only = is_spot
-    exchange_manager_instance.is_margin = is_margin
-    exchange_manager_instance.is_future = is_future
+
+@pytest.fixture
+async def simulated_exchange_manager():
+    exchange_manager_instance = ExchangeManager(load_test_config(), DEFAULT_EXCHANGE_NAME)
+    exchange_manager_instance.is_spot_only = True
+    exchange_manager_instance.is_simulated = True
+    await exchange_manager_instance.initialize()
+    yield exchange_manager_instance
+    cancel_ccxt_throttle_task()
+    await exchange_manager_instance.stop()
+    # let updaters gracefully shutdown
+    await wait_asyncio_next_cycle()
+
+
+@pytest.fixture
+async def margin_exchange_manager():
+    exchange_manager_instance = ExchangeManager(load_test_config(), DEFAULT_EXCHANGE_NAME)
+    exchange_manager_instance.is_spot_only = False
+    exchange_manager_instance.is_margin = True
+    await exchange_manager_instance.initialize()
+    yield exchange_manager_instance
+    cancel_ccxt_throttle_task()
+    await exchange_manager_instance.stop()
+    # let updaters gracefully shutdown
+    await wait_asyncio_next_cycle()
+
+
+@pytest.fixture
+async def margin_simulated_exchange_manager():
+    exchange_manager_instance = ExchangeManager(load_test_config(), DEFAULT_EXCHANGE_NAME)
+    exchange_manager_instance.is_spot_only = False
+    exchange_manager_instance.is_simulated = True
+    exchange_manager_instance.is_margin = True
+    await exchange_manager_instance.initialize()
+    yield exchange_manager_instance
+    cancel_ccxt_throttle_task()
+    await exchange_manager_instance.stop()
+    # let updaters gracefully shutdown
+    await wait_asyncio_next_cycle()
+
+
+@pytest.fixture
+async def future_exchange_manager():
+    exchange_manager_instance = ExchangeManager(load_test_config(), DEFAULT_EXCHANGE_NAME)
+    exchange_manager_instance.is_spot_only = False
+    exchange_manager_instance.is_future = True
     await exchange_manager_instance.initialize()
     yield exchange_manager_instance
     cancel_ccxt_throttle_task()
@@ -58,6 +100,21 @@ async def exchange_manager(request):
     # let updaters gracefully shutdown
     await wait_asyncio_next_cycle()
 
+
+@pytest.fixture
+async def future_simulated_exchange_manager():
+    exchange_manager_instance = ExchangeManager(load_test_config(), DEFAULT_EXCHANGE_NAME)
+    exchange_manager_instance.is_spot_only = False
+    exchange_manager_instance.is_simulated = True
+    exchange_manager_instance.is_future = True
+    await exchange_manager_instance.initialize()
+    yield exchange_manager_instance
+    cancel_ccxt_throttle_task()
+    await exchange_manager_instance.stop()
+    # let updaters gracefully shutdown
+    await wait_asyncio_next_cycle()
+
+
 @pytest.fixture
 async def exchange_builder(request):
     config = None
@@ -87,29 +144,6 @@ async def backtesting_config():
     return config
 
 
-@pytest.fixture
-async def simulated_exchange_manager(request):
-    config = load_test_config()
-    exchange_name = DEFAULT_EXCHANGE_NAME
-    is_spot = True
-    is_margin = False
-    is_future = False
-    if hasattr(request, "param"):
-        config, exchange_name, is_spot, is_margin, is_future = request.param
-
-    exchange_manager_instance = ExchangeManager(config, exchange_name)
-    exchange_manager_instance.is_simulated = True
-    exchange_manager_instance.is_spot_only = is_spot
-    exchange_manager_instance.is_margin = is_margin
-    exchange_manager_instance.is_future = is_future
-    await exchange_manager_instance.initialize()
-    yield exchange_manager_instance
-    cancel_ccxt_throttle_task()
-    await exchange_manager_instance.stop()
-    # let updaters gracefully shutdown
-    await wait_asyncio_next_cycle()
-
-
 @pytest.fixture
 async def fake_backtesting(backtesting_config):
     return Backtesting(config=backtesting_config,
diff --git a/tests/exchanges/traders/__init__.py b/tests/exchanges/traders/__init__.py
index f0b8311c4..956c57d0c 100644
--- a/tests/exchanges/traders/__init__.py
+++ b/tests/exchanges/traders/__init__.py
@@ -13,27 +13,63 @@
 #
 #  You should have received a copy of the GNU Lesser General Public
 #  License along with this library.
+import decimal
+
 import pytest
 
 import octobot_commons.constants as commons_constants
 from octobot_commons.tests.test_config import load_test_config
+import octobot_trading.constants as constants
+import octobot_trading.enums as enums
+import octobot_trading.exchange_data.contracts as contracts
 from octobot_trading.exchanges.traders.trader_simulator import TraderSimulator
 from octobot_trading.exchanges.traders.trader import Trader
 
 
-@pytest.fixture
-async def trader(exchange_manager):
+async def create_trader_from_exchange_manager(exchange_manager, simulated=False):
     config = load_test_config()
-    config[commons_constants.CONFIG_TRADER][commons_constants.CONFIG_ENABLED_OPTION] = True
-    trader_inst = Trader(load_test_config(), exchange_manager)
+    if simulated:
+        config[commons_constants.CONFIG_SIMULATOR][commons_constants.CONFIG_ENABLED_OPTION] = True
+        trader_inst = TraderSimulator(load_test_config(), exchange_manager)
+    else:
+        config[commons_constants.CONFIG_TRADER][commons_constants.CONFIG_ENABLED_OPTION] = True
+        trader_inst = Trader(load_test_config(), exchange_manager)
     await trader_inst.initialize()
     return config, exchange_manager, trader_inst
 
 
 @pytest.fixture
-async def trader_simulator(exchange_manager):
-    config = load_test_config()
-    config[commons_constants.CONFIG_SIMULATOR][commons_constants.CONFIG_ENABLED_OPTION] = True
-    trader_inst = TraderSimulator(load_test_config(), exchange_manager)
-    await trader_inst.initialize()
-    return config, exchange_manager, trader_inst
+async def trader(exchange_manager):
+    return await create_trader_from_exchange_manager(exchange_manager)
+
+
+@pytest.fixture
+async def margin_trader(margin_exchange_manager):
+    return await create_trader_from_exchange_manager(margin_exchange_manager)
+
+
+@pytest.fixture
+async def future_trader(future_exchange_manager):
+    return await create_trader_from_exchange_manager(future_exchange_manager)
+
+
+@pytest.fixture
+async def trader_simulator(simulated_exchange_manager):
+    return await create_trader_from_exchange_manager(simulated_exchange_manager, simulated=True)
+
+
+@pytest.fixture
+async def margin_trader_simulator(margin_simulated_exchange_manager):
+    return await create_trader_from_exchange_manager(margin_simulated_exchange_manager, simulated=True)
+
+DEFAULT_FUTURE_SYMBOL = "BTC/USDT"
+DEFAULT_FUTURE_FUNDING_RATE = decimal.Decimal(0.01)
+DEFAULT_FUTURE_SYMBOL_CONTRACT = contracts.FutureContract(DEFAULT_FUTURE_SYMBOL)
+
+@pytest.fixture
+async def future_trader_simulator(future_simulated_exchange_manager):
+    DEFAULT_FUTURE_SYMBOL_CONTRACT.current_leverage = constants.ONE
+    DEFAULT_FUTURE_SYMBOL_CONTRACT.margin_type = enums.MarginType.ISOLATED
+    future_simulated_exchange_manager.exchange.set_pair_future_contract(DEFAULT_FUTURE_SYMBOL,
+                                                                        DEFAULT_FUTURE_SYMBOL_CONTRACT)
+    return await create_trader_from_exchange_manager(future_simulated_exchange_manager, simulated=True)
diff --git a/tests/personal_data/orders/test_order.py b/tests/personal_data/orders/test_order.py
index 3d6ba3438..3d0413515 100644
--- a/tests/personal_data/orders/test_order.py
+++ b/tests/personal_data/orders/test_order.py
@@ -19,7 +19,7 @@
 import octobot_trading.personal_data as personal_data
 
 from tests import event_loop
-from tests.exchanges import exchange_manager
+from tests.exchanges import exchange_manager, simulated_exchange_manager
 from tests.exchanges.traders import trader_simulator
 from tests.exchanges.traders import trader
 from tests.test_utils.random_numbers import random_price
diff --git a/octobot_trading/personal_data/positions/types/cross_position.py b/tests/personal_data/positions/channel/__init__.py
similarity index 78%
rename from octobot_trading/personal_data/positions/types/cross_position.py
rename to tests/personal_data/positions/channel/__init__.py
index db8637efc..adbc2745b 100644
--- a/octobot_trading/personal_data/positions/types/cross_position.py
+++ b/tests/personal_data/positions/channel/__init__.py
@@ -13,9 +13,3 @@
 #
 #  You should have received a copy of the GNU Lesser General Public
 #  License along with this library.
-import octobot_trading.personal_data.positions.position as position_class
-
-
-class CrossPosition(position_class.Position):
-    async def update_position_status(self, force_refresh=False):
-        pass
diff --git a/octobot_trading/personal_data/positions/types/isolated_position.py b/tests/personal_data/positions/states/__init__.py
similarity index 77%
rename from octobot_trading/personal_data/positions/types/isolated_position.py
rename to tests/personal_data/positions/states/__init__.py
index 53b978c3a..adbc2745b 100644
--- a/octobot_trading/personal_data/positions/types/isolated_position.py
+++ b/tests/personal_data/positions/states/__init__.py
@@ -13,9 +13,3 @@
 #
 #  You should have received a copy of the GNU Lesser General Public
 #  License along with this library.
-import octobot_trading.personal_data.positions.position as position_class
-
-
-class IsolatedPosition(position_class.Position):
-    async def update_position_status(self, force_refresh=False):
-        pass
diff --git a/tests/personal_data/positions/test_position.py b/tests/personal_data/positions/test_position.py
new file mode 100644
index 000000000..a46e1d44b
--- /dev/null
+++ b/tests/personal_data/positions/test_position.py
@@ -0,0 +1,52 @@
+#  Drakkar-Software OctoBot-Trading
+#  Copyright (c) Drakkar-Software, All rights reserved.
+#
+#  This library is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU Lesser General Public
+#  License as published by the Free Software Foundation; either
+#  version 3.0 of the License, or (at your option) any later version.
+#
+#  This library is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+#  Lesser General Public License for more details.
+#
+#  You should have received a copy of the GNU Lesser General Public
+#  License along with this library.
+import pytest
+import octobot_trading.constants as constants
+import octobot_trading.personal_data as personal_data
+
+from tests import event_loop
+from tests.exchanges import future_simulated_exchange_manager
+from tests.exchanges.traders import future_trader_simulator, DEFAULT_FUTURE_SYMBOL_CONTRACT
+from tests.test_utils.random_numbers import decimal_random_price, decimal_random_quantity
+
+# All test coroutines will be treated as marked.
+pytestmark = pytest.mark.asyncio
+
+
+async def test_update_entry_price(future_trader_simulator):
+    config, exchange_manager_inst, trader_inst = future_trader_simulator
+
+    position_inst = personal_data.LinearPosition(trader_inst, DEFAULT_FUTURE_SYMBOL_CONTRACT)
+
+    assert position_inst.entry_price == constants.ZERO
+    assert position_inst.mark_price == constants.ZERO
+
+    mark_price = decimal_random_price(1)
+    await position_inst.update(mark_price=mark_price)
+    assert position_inst.entry_price == mark_price
+    assert position_inst.mark_price == mark_price
+
+
+async def test_update_update_quantity(future_trader_simulator):
+    config, exchange_manager_inst, trader_inst = future_trader_simulator
+
+    position_inst = personal_data.LinearPosition(trader_inst, DEFAULT_FUTURE_SYMBOL_CONTRACT)
+
+    assert position_inst.quantity == constants.ZERO
+
+    quantity = decimal_random_quantity(1)
+    await position_inst.update(update_quantity=quantity)
+    assert position_inst.quantity == quantity
diff --git a/tests/personal_data/positions/test_position_factory.py b/tests/personal_data/positions/test_position_factory.py
index 365df059a..52b9c7568 100644
--- a/tests/personal_data/positions/test_position_factory.py
+++ b/tests/personal_data/positions/test_position_factory.py
@@ -13,50 +13,47 @@
 #
 #  You should have received a copy of the GNU Lesser General Public
 #  License along with this library.
-import decimal
 import pytest
 import octobot_trading.enums as enums
 import octobot_trading.personal_data as personal_data
 
 from tests import event_loop
-from tests.exchanges import exchange_manager
-from tests.exchanges.traders import trader_simulator
-from tests.exchanges.traders import trader
-from tests.test_utils.random_numbers import random_int, random_quantity, decimal_random_quantity
+from tests.exchanges import future_simulated_exchange_manager
+from tests.exchanges.traders import future_trader, future_trader_simulator, DEFAULT_FUTURE_SYMBOL
+from tests.test_utils.random_numbers import decimal_random_int, decimal_random_quantity
 
 # All test coroutines will be treated as marked.
 pytestmark = pytest.mark.asyncio
 
 
-async def test_create_position_instance_from_raw(trader_simulator):
-    config, exchange_manager_inst, trader_inst = trader_simulator
+async def test_create_position_instance_from_raw(future_trader_simulator):
+    config, exchange_manager_inst, trader_inst = future_trader_simulator
 
     raw_position = {
-        enums.ExchangeConstantsPositionColumns.SYMBOL.value: "BTC/USDT",
+        enums.ExchangeConstantsPositionColumns.SYMBOL.value: DEFAULT_FUTURE_SYMBOL,
         enums.ExchangeConstantsPositionColumns.STATUS.value: enums.PositionStatus.ADL.value
     }
     position = personal_data.create_position_instance_from_raw(trader_inst, raw_position)
-    position_leverage = random_int(max_value=200)
+    position_leverage = decimal_random_int(min_value=2, max_value=200)
     position_quantity = decimal_random_quantity(max_value=1000)
-    cross_position_open = personal_data.create_position_instance_from_raw(trader_inst, {
-        enums.ExchangeConstantsPositionColumns.SYMBOL.value: "BTC/USDT",
+    linear_position_open = personal_data.create_position_instance_from_raw(trader_inst, {
+        enums.ExchangeConstantsPositionColumns.SYMBOL.value: DEFAULT_FUTURE_SYMBOL,
         enums.ExchangeConstantsPositionColumns.LEVERAGE.value: position_leverage,
-        enums.ExchangeConstantsPositionColumns.QUANTITY.value: position_quantity,
-        enums.ExchangeConstantsPositionColumns.MARGIN_TYPE.value: enums.TraderPositionType.CROSS.value
+        enums.ExchangeConstantsPositionColumns.QUANTITY.value: position_quantity
     })
-    assert position.symbol == "BTC/USDT"
+    assert position.symbol == DEFAULT_FUTURE_SYMBOL
     assert position.market == "USDT"
     assert position.currency == "BTC"
     assert position.status == enums.PositionStatus.ADL
-    assert isinstance(position, personal_data.IsolatedPosition)
+    assert isinstance(position, personal_data.InversePosition)
 
-    assert cross_position_open.status == enums.PositionStatus.OPEN
-    assert cross_position_open.leverage == position_leverage
-    assert cross_position_open.quantity == position_quantity
-    assert isinstance(cross_position_open, personal_data.CrossPosition)
+    assert linear_position_open.status == enums.PositionStatus.OPEN
+    assert linear_position_open.leverage == position_leverage
+    assert linear_position_open.quantity == position_quantity
+    assert isinstance(linear_position_open, personal_data.InversePosition)
 
 
-async def test_create_symbol_position(trader_simulator):
-    config, exchange_manager_inst, trader_inst = trader_simulator
-    position = personal_data.create_symbol_position(trader_inst, "BTC/USDT")
-    assert position.symbol == "BTC/USDT"
+async def test_create_symbol_position(future_trader_simulator):
+    config, exchange_manager_inst, trader_inst = future_trader_simulator
+    position = personal_data.create_symbol_position(trader_inst, DEFAULT_FUTURE_SYMBOL)
+    assert position.symbol == DEFAULT_FUTURE_SYMBOL
diff --git a/tests/personal_data/positions/types/__init__.py b/tests/personal_data/positions/types/__init__.py
new file mode 100644
index 000000000..adbc2745b
--- /dev/null
+++ b/tests/personal_data/positions/types/__init__.py
@@ -0,0 +1,15 @@
+#  Drakkar-Software OctoBot-Trading
+#  Copyright (c) Drakkar-Software, All rights reserved.
+#
+#  This library is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU Lesser General Public
+#  License as published by the Free Software Foundation; either
+#  version 3.0 of the License, or (at your option) any later version.
+#
+#  This library is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+#  Lesser General Public License for more details.
+#
+#  You should have received a copy of the GNU Lesser General Public
+#  License along with this library.
diff --git a/tests/personal_data/positions/types/test_inverse_position.py b/tests/personal_data/positions/types/test_inverse_position.py
new file mode 100644
index 000000000..290bf7962
--- /dev/null
+++ b/tests/personal_data/positions/types/test_inverse_position.py
@@ -0,0 +1,196 @@
+#  Drakkar-Software OctoBot-Trading
+#  Copyright (c) Drakkar-Software, All rights reserved.
+#
+#  This library is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU Lesser General Public
+#  License as published by the Free Software Foundation; either
+#  version 3.0 of the License, or (at your option) any later version.
+#
+#  This library is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+#  Lesser General Public License for more details.
+#
+#  You should have received a copy of the GNU Lesser General Public
+#  License along with this library.
+import decimal
+
+import pytest
+import octobot_trading.constants as constants
+import octobot_trading.personal_data as personal_data
+
+from tests import event_loop
+from tests.exchanges import future_simulated_exchange_manager
+from tests.exchanges.traders import future_trader_simulator, DEFAULT_FUTURE_SYMBOL_CONTRACT, \
+    DEFAULT_FUTURE_SYMBOL, DEFAULT_FUTURE_FUNDING_RATE
+from tests.test_utils.random_numbers import decimal_random_price, decimal_random_quantity
+
+# All test coroutines will be treated as marked.
+pytestmark = pytest.mark.asyncio
+
+
+async def test_update_value(future_trader_simulator):
+    config, exchange_manager_inst, trader_inst = future_trader_simulator
+    position_inst = personal_data.InversePosition(trader_inst, DEFAULT_FUTURE_SYMBOL_CONTRACT)
+    await position_inst.update(update_quantity=constants.ZERO)
+    position_inst.update_value()
+    assert position_inst.value == constants.ZERO
+    await position_inst.update(update_quantity=constants.ONE_HUNDRED)
+    position_inst.update_value()
+    assert position_inst.value == constants.ZERO
+    await position_inst.update(mark_price=constants.ONE_HUNDRED)
+    position_inst.update_value()
+    assert position_inst.value == constants.ONE
+
+
+async def test_update_pnl(future_trader_simulator):
+    config, exchange_manager_inst, trader_inst = future_trader_simulator
+
+    # long test
+    position_inst = personal_data.InversePosition(trader_inst, DEFAULT_FUTURE_SYMBOL_CONTRACT)
+    position_inst.entry_price = constants.ONE_HUNDRED
+    await position_inst.update(update_quantity=constants.ONE_HUNDRED, mark_price=constants.ONE_HUNDRED)
+    position_inst.update_pnl()
+    assert position_inst.unrealised_pnl == constants.ZERO
+    await position_inst.update(update_quantity=constants.ONE_HUNDRED,
+                               mark_price=decimal.Decimal(2) * constants.ONE_HUNDRED)
+    position_inst.update_pnl()
+    assert position_inst.unrealised_pnl == constants.ONE
+
+    # short test
+    position_inst = personal_data.InversePosition(trader_inst, DEFAULT_FUTURE_SYMBOL_CONTRACT)
+    position_inst.entry_price = constants.ONE_HUNDRED
+    await position_inst.update(update_quantity=-constants.ONE_HUNDRED, mark_price=constants.ONE_HUNDRED)
+    position_inst.update_pnl()
+    assert position_inst.unrealised_pnl == constants.ZERO
+    await position_inst.update(update_quantity=-constants.ONE_HUNDRED,
+                               mark_price=constants.ONE_HUNDRED / decimal.Decimal(10))
+    position_inst.update_pnl()
+    assert position_inst.unrealised_pnl == decimal.Decimal("-18.00")
+
+
+async def test_update_initial_margin(future_trader_simulator):
+    config, exchange_manager_inst, trader_inst = future_trader_simulator
+    position_inst = personal_data.InversePosition(trader_inst, DEFAULT_FUTURE_SYMBOL_CONTRACT)
+
+    await position_inst.update(update_quantity=constants.ZERO, mark_price=constants.ZERO)
+    position_inst.update_initial_margin()
+    assert position_inst.initial_margin == constants.ZERO
+    await position_inst.update(update_quantity=constants.ONE_HUNDRED, mark_price=constants.ONE_HUNDRED)
+    position_inst.update_initial_margin()
+    assert position_inst.initial_margin == constants.ONE
+    position_inst.leverage = constants.ONE_HUNDRED
+    await position_inst.update(update_quantity=constants.ONE_HUNDRED, mark_price=constants.ONE_HUNDRED)
+    position_inst.update_initial_margin()
+    assert position_inst.initial_margin == decimal.Decimal("0.02")
+
+
+async def test_calculate_maintenance_margin(future_trader_simulator):
+    config, exchange_manager_inst, trader_inst = future_trader_simulator
+    position_inst = personal_data.InversePosition(trader_inst, DEFAULT_FUTURE_SYMBOL_CONTRACT)
+
+    position_inst.symbol = DEFAULT_FUTURE_SYMBOL
+    await position_inst.update(update_quantity=constants.ZERO, mark_price=constants.ZERO)
+    assert position_inst.calculate_maintenance_margin() == constants.ZERO
+    await position_inst.update(update_quantity=constants.ONE_HUNDRED, mark_price=constants.ONE_HUNDRED)
+    assert position_inst.calculate_maintenance_margin() == constants.ZERO
+    exchange_manager_inst.exchange_symbols_data.get_exchange_symbol_data(
+        DEFAULT_FUTURE_SYMBOL).funding_manager.funding_rate = decimal.Decimal(DEFAULT_FUTURE_FUNDING_RATE)
+    await position_inst.update(update_quantity=constants.ONE_HUNDRED, mark_price=constants.ONE_HUNDRED)
+    assert position_inst.calculate_maintenance_margin() == decimal.Decimal("0.02000000000000000041633363423")
+
+
+async def test_update_isolated_liquidation_price(future_trader_simulator):
+    config, exchange_manager_inst, trader_inst = future_trader_simulator
+    exchange_manager_inst.exchange_symbols_data.get_exchange_symbol_data(
+        DEFAULT_FUTURE_SYMBOL).funding_manager.funding_rate = decimal.Decimal(DEFAULT_FUTURE_FUNDING_RATE)
+
+    # long test
+    position_inst = personal_data.InversePosition(trader_inst, DEFAULT_FUTURE_SYMBOL_CONTRACT)
+    position_inst.symbol = DEFAULT_FUTURE_SYMBOL
+    position_inst.entry_price = constants.ONE_HUNDRED
+    await position_inst.update(update_quantity=constants.ONE_HUNDRED, mark_price=constants.ONE_HUNDRED)
+    position_inst.update_isolated_liquidation_price()
+    assert position_inst.liquidation_price == decimal.Decimal("50.25125628140703518113600205")
+    position_inst.leverage = constants.ONE_HUNDRED
+    await position_inst.update(update_quantity=constants.ONE_HUNDRED,
+                               mark_price=decimal.Decimal(2) * constants.ONE_HUNDRED)
+    position_inst.update_isolated_liquidation_price()
+    assert position_inst.liquidation_price == decimal.Decimal("100.0000000000000000208166817")
+
+    # short test
+    position_inst = personal_data.InversePosition(trader_inst, DEFAULT_FUTURE_SYMBOL_CONTRACT)
+    position_inst.symbol = DEFAULT_FUTURE_SYMBOL
+    position_inst.entry_price = constants.ONE_HUNDRED
+    await position_inst.update(update_quantity=-constants.ONE_HUNDRED, mark_price=constants.ONE_HUNDRED)
+    position_inst.update_isolated_liquidation_price()
+    assert position_inst.liquidation_price == decimal.Decimal("9999.999999999999791833182880")
+    position_inst.leverage = constants.ONE_HUNDRED
+    await position_inst.update(update_quantity=-constants.ONE_HUNDRED,
+                               mark_price=constants.ONE_HUNDRED / decimal.Decimal(10))
+    position_inst.update_isolated_liquidation_price()
+    assert position_inst.liquidation_price == decimal.Decimal("99.99999999999999997918331830")
+
+
+async def test_get_bankruptcy_price(future_trader_simulator):
+    config, exchange_manager_inst, trader_inst = future_trader_simulator
+
+    # long test
+    position_inst = personal_data.InversePosition(trader_inst, DEFAULT_FUTURE_SYMBOL_CONTRACT)
+    position_inst.entry_price = constants.ONE_HUNDRED
+    await position_inst.update(update_quantity=constants.ONE_HUNDRED, mark_price=constants.ONE_HUNDRED)
+    assert position_inst.get_bankruptcy_price() == decimal.Decimal(50)
+    assert position_inst.get_bankruptcy_price(with_mark_price=True) == decimal.Decimal(50)
+    position_inst.leverage = constants.ONE_HUNDRED
+    assert position_inst.get_bankruptcy_price() == decimal.Decimal("99.00990099009900990099009901")
+    assert position_inst.get_bankruptcy_price(with_mark_price=True) == decimal.Decimal("0.9900990099009900990099009901")
+    await position_inst.update(update_quantity=constants.ONE_HUNDRED,
+                               mark_price=decimal.Decimal(2) * constants.ONE_HUNDRED)
+    assert position_inst.get_bankruptcy_price() == decimal.Decimal("99.00990099009900990099009901")
+    assert position_inst.get_bankruptcy_price(with_mark_price=True) == decimal.Decimal("1.980198019801980198019801980")
+
+    # short test
+    position_inst = personal_data.InversePosition(trader_inst, DEFAULT_FUTURE_SYMBOL_CONTRACT)
+    position_inst.entry_price = constants.ONE_HUNDRED
+    await position_inst.update(update_quantity=-constants.ONE_HUNDRED, mark_price=constants.ONE_HUNDRED)
+    assert position_inst.get_bankruptcy_price() == constants.ZERO
+    assert position_inst.get_bankruptcy_price(with_mark_price=True) == constants.ZERO
+    position_inst.leverage = constants.ONE_HUNDRED
+    assert position_inst.get_bankruptcy_price() == decimal.Decimal("101.0101010101010101010101010")
+    assert position_inst.get_bankruptcy_price(with_mark_price=True) == decimal.Decimal("1.010101010101010101010101010")
+    await position_inst.update(update_quantity=-constants.ONE_HUNDRED,
+                               mark_price=decimal.Decimal(2) * constants.ONE_HUNDRED)
+    assert position_inst.get_bankruptcy_price() == constants.ZERO
+    assert position_inst.get_bankruptcy_price(with_mark_price=True) == constants.ZERO
+
+
+async def test_get_order_cost(future_trader_simulator):
+    config, exchange_manager_inst, trader_inst = future_trader_simulator
+    position_inst = personal_data.InversePosition(trader_inst, DEFAULT_FUTURE_SYMBOL_CONTRACT)
+
+    await position_inst.update(update_quantity=constants.ZERO, mark_price=constants.ZERO)
+    assert position_inst.get_order_cost() == constants.ZERO
+    await position_inst.update(update_quantity=constants.ONE_HUNDRED, mark_price=constants.ONE_HUNDRED)
+    assert position_inst.get_order_cost() == decimal.Decimal("1.003")
+
+
+async def test_get_fee_to_open(future_trader_simulator):
+    config, exchange_manager_inst, trader_inst = future_trader_simulator
+    position_inst = personal_data.InversePosition(trader_inst, DEFAULT_FUTURE_SYMBOL_CONTRACT)
+
+    await position_inst.update(update_quantity=constants.ZERO, mark_price=constants.ZERO)
+    assert position_inst.get_fee_to_open() == constants.ZERO
+    await position_inst.update(update_quantity=constants.ONE_HUNDRED, mark_price=constants.ONE_HUNDRED)
+    assert position_inst.get_fee_to_open() == decimal.Decimal("0.001")
+
+
+async def test_update_fee_to_close(future_trader_simulator):
+    config, exchange_manager_inst, trader_inst = future_trader_simulator
+    position_inst = personal_data.InversePosition(trader_inst, DEFAULT_FUTURE_SYMBOL_CONTRACT)
+
+    await position_inst.update(update_quantity=constants.ZERO, mark_price=constants.ZERO)
+    position_inst.update_fee_to_close()
+    assert position_inst.fee_to_close == constants.ZERO
+    await position_inst.update(update_quantity=constants.ONE_HUNDRED, mark_price=constants.ONE_HUNDRED)
+    position_inst.update_fee_to_close()
+    assert position_inst.fee_to_close == decimal.Decimal("0.002")
diff --git a/tests/personal_data/positions/types/test_linear_position.py b/tests/personal_data/positions/types/test_linear_position.py
new file mode 100644
index 000000000..d6ceec925
--- /dev/null
+++ b/tests/personal_data/positions/types/test_linear_position.py
@@ -0,0 +1,197 @@
+#  Drakkar-Software OctoBot-Trading
+#  Copyright (c) Drakkar-Software, All rights reserved.
+#
+#  This library is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU Lesser General Public
+#  License as published by the Free Software Foundation; either
+#  version 3.0 of the License, or (at your option) any later version.
+#
+#  This library is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+#  Lesser General Public License for more details.
+#
+#  You should have received a copy of the GNU Lesser General Public
+#  License along with this library.
+
+import decimal
+
+import pytest
+import octobot_trading.constants as constants
+import octobot_trading.personal_data as personal_data
+
+from tests import event_loop
+from tests.exchanges import future_simulated_exchange_manager
+from tests.exchanges.traders import future_trader_simulator, DEFAULT_FUTURE_SYMBOL_CONTRACT, \
+    DEFAULT_FUTURE_SYMBOL, DEFAULT_FUTURE_FUNDING_RATE
+from tests.test_utils.random_numbers import decimal_random_price, decimal_random_quantity
+
+# All test coroutines will be treated as marked.
+pytestmark = pytest.mark.asyncio
+
+
+async def test_update_value(future_trader_simulator):
+    config, exchange_manager_inst, trader_inst = future_trader_simulator
+    position_inst = personal_data.LinearPosition(trader_inst, DEFAULT_FUTURE_SYMBOL_CONTRACT)
+    await position_inst.update(update_quantity=constants.ZERO)
+    position_inst.update_value()
+    assert position_inst.value == constants.ZERO
+    await position_inst.update(update_quantity=constants.ONE_HUNDRED)
+    position_inst.update_value()
+    assert position_inst.value == constants.ZERO
+    await position_inst.update(mark_price=constants.ONE_HUNDRED)
+    position_inst.update_value()
+    assert position_inst.value == decimal.Decimal("10000")
+
+
+async def test_update_pnl(future_trader_simulator):
+    config, exchange_manager_inst, trader_inst = future_trader_simulator
+
+    # long test
+    position_inst = personal_data.LinearPosition(trader_inst, DEFAULT_FUTURE_SYMBOL_CONTRACT)
+    position_inst.entry_price = constants.ONE_HUNDRED
+    await position_inst.update(update_quantity=constants.ONE_HUNDRED, mark_price=constants.ONE_HUNDRED)
+    position_inst.update_pnl()
+    assert position_inst.unrealised_pnl == constants.ZERO
+    await position_inst.update(update_quantity=constants.ONE_HUNDRED,
+                               mark_price=decimal.Decimal(2) * constants.ONE_HUNDRED)
+    position_inst.update_pnl()
+    assert position_inst.unrealised_pnl == decimal.Decimal("20000")
+
+    # short test
+    position_inst = personal_data.LinearPosition(trader_inst, DEFAULT_FUTURE_SYMBOL_CONTRACT)
+    position_inst.entry_price = constants.ONE_HUNDRED
+    await position_inst.update(update_quantity=-constants.ONE_HUNDRED, mark_price=constants.ONE_HUNDRED)
+    position_inst.update_pnl()
+    assert position_inst.unrealised_pnl == constants.ZERO
+    await position_inst.update(update_quantity=-constants.ONE_HUNDRED,
+                               mark_price=constants.ONE_HUNDRED / decimal.Decimal(10))
+    position_inst.update_pnl()
+    assert position_inst.unrealised_pnl == decimal.Decimal("-18000")
+
+
+async def test_update_initial_margin(future_trader_simulator):
+    config, exchange_manager_inst, trader_inst = future_trader_simulator
+    position_inst = personal_data.LinearPosition(trader_inst, DEFAULT_FUTURE_SYMBOL_CONTRACT)
+
+    await position_inst.update(update_quantity=constants.ZERO, mark_price=constants.ZERO)
+    position_inst.update_initial_margin()
+    assert position_inst.initial_margin == constants.ZERO
+    await position_inst.update(update_quantity=constants.ONE_HUNDRED, mark_price=constants.ONE_HUNDRED)
+    position_inst.update_initial_margin()
+    assert position_inst.initial_margin == decimal.Decimal("10000")
+    position_inst.leverage = constants.ONE_HUNDRED
+    await position_inst.update(update_quantity=constants.ONE_HUNDRED, mark_price=constants.ONE_HUNDRED)
+    position_inst.update_initial_margin()
+    assert position_inst.initial_margin == decimal.Decimal("200")
+
+
+async def test_calculate_maintenance_margin(future_trader_simulator):
+    config, exchange_manager_inst, trader_inst = future_trader_simulator
+    position_inst = personal_data.LinearPosition(trader_inst, DEFAULT_FUTURE_SYMBOL_CONTRACT)
+
+    position_inst.symbol = DEFAULT_FUTURE_SYMBOL
+    await position_inst.update(update_quantity=constants.ZERO, mark_price=constants.ZERO)
+    assert position_inst.calculate_maintenance_margin() == constants.ZERO
+    await position_inst.update(update_quantity=constants.ONE_HUNDRED, mark_price=constants.ONE_HUNDRED)
+    assert position_inst.calculate_maintenance_margin() == constants.ZERO
+    exchange_manager_inst.exchange_symbols_data.get_exchange_symbol_data(
+        DEFAULT_FUTURE_SYMBOL).funding_manager.funding_rate = decimal.Decimal(DEFAULT_FUTURE_FUNDING_RATE)
+    await position_inst.update(update_quantity=constants.ONE_HUNDRED, mark_price=constants.ONE_HUNDRED)
+    assert position_inst.calculate_maintenance_margin() == decimal.Decimal("0E-59")
+
+
+async def test_update_isolated_liquidation_price(future_trader_simulator):
+    config, exchange_manager_inst, trader_inst = future_trader_simulator
+    exchange_manager_inst.exchange_symbols_data.get_exchange_symbol_data(
+        DEFAULT_FUTURE_SYMBOL).funding_manager.funding_rate = decimal.Decimal(DEFAULT_FUTURE_FUNDING_RATE)
+
+    # long test
+    position_inst = personal_data.LinearPosition(trader_inst, DEFAULT_FUTURE_SYMBOL_CONTRACT)
+    position_inst.symbol = DEFAULT_FUTURE_SYMBOL
+    position_inst.entry_price = constants.ONE_HUNDRED
+    await position_inst.update(update_quantity=constants.ONE_HUNDRED, mark_price=constants.ONE_HUNDRED)
+    position_inst.update_isolated_liquidation_price()
+    assert position_inst.liquidation_price == constants.ZERO
+    position_inst.leverage = constants.ONE_HUNDRED
+    await position_inst.update(update_quantity=constants.ONE_HUNDRED,
+                               mark_price=decimal.Decimal(2) * constants.ONE_HUNDRED)
+    position_inst.update_isolated_liquidation_price()
+    assert position_inst.liquidation_price == constants.ZERO
+
+    # short test
+    position_inst = personal_data.LinearPosition(trader_inst, DEFAULT_FUTURE_SYMBOL_CONTRACT)
+    position_inst.symbol = DEFAULT_FUTURE_SYMBOL
+    position_inst.entry_price = constants.ONE_HUNDRED
+    await position_inst.update(update_quantity=-constants.ONE_HUNDRED, mark_price=constants.ONE_HUNDRED)
+    position_inst.update_isolated_liquidation_price()
+    assert position_inst.liquidation_price == decimal.Decimal("10200.00000000000020816681712")
+    position_inst.leverage = constants.ONE_HUNDRED
+    await position_inst.update(update_quantity=-constants.ONE_HUNDRED,
+                               mark_price=constants.ONE_HUNDRED / decimal.Decimal(10))
+    position_inst.update_isolated_liquidation_price()
+    assert position_inst.liquidation_price == decimal.Decimal("20101.00000000000041633363423")
+
+
+async def test_get_bankruptcy_price(future_trader_simulator):
+    config, exchange_manager_inst, trader_inst = future_trader_simulator
+
+    # long test
+    position_inst = personal_data.LinearPosition(trader_inst, DEFAULT_FUTURE_SYMBOL_CONTRACT)
+    position_inst.entry_price = constants.ONE_HUNDRED
+    await position_inst.update(update_quantity=constants.ONE_HUNDRED, mark_price=constants.ONE_HUNDRED)
+    assert position_inst.get_bankruptcy_price() == constants.ZERO
+    assert position_inst.get_bankruptcy_price(with_mark_price=True) == constants.ONE_HUNDRED
+    position_inst.leverage = constants.ONE_HUNDRED
+    assert position_inst.get_bankruptcy_price() == decimal.Decimal("99.00")
+    assert position_inst.get_bankruptcy_price(with_mark_price=True) == decimal.Decimal("100")
+    await position_inst.update(update_quantity=constants.ONE_HUNDRED,
+                               mark_price=decimal.Decimal(2) * constants.ONE_HUNDRED)
+    assert position_inst.get_bankruptcy_price() == decimal.Decimal("99.00")
+    assert position_inst.get_bankruptcy_price(with_mark_price=True) == decimal.Decimal("200")
+
+    # short test
+    position_inst = personal_data.LinearPosition(trader_inst, DEFAULT_FUTURE_SYMBOL_CONTRACT)
+    position_inst.entry_price = constants.ONE_HUNDRED
+    await position_inst.update(update_quantity=-constants.ONE_HUNDRED, mark_price=constants.ONE_HUNDRED)
+    assert position_inst.get_bankruptcy_price() == decimal.Decimal("200")
+    assert position_inst.get_bankruptcy_price(with_mark_price=True) == decimal.Decimal("100")
+    position_inst.leverage = constants.ONE_HUNDRED
+    assert position_inst.get_bankruptcy_price() == decimal.Decimal('101.00')
+    assert position_inst.get_bankruptcy_price(with_mark_price=True) == decimal.Decimal('100')
+    await position_inst.update(update_quantity=-constants.ONE_HUNDRED,
+                               mark_price=decimal.Decimal(2) * constants.ONE_HUNDRED)
+    assert position_inst.get_bankruptcy_price() == constants.ZERO
+    assert position_inst.get_bankruptcy_price(with_mark_price=True) == constants.ZERO
+
+
+async def test_get_order_cost(future_trader_simulator):
+    config, exchange_manager_inst, trader_inst = future_trader_simulator
+    position_inst = personal_data.LinearPosition(trader_inst, DEFAULT_FUTURE_SYMBOL_CONTRACT)
+
+    await position_inst.update(update_quantity=constants.ZERO, mark_price=constants.ZERO)
+    assert position_inst.get_order_cost() == constants.ZERO
+    await position_inst.update(update_quantity=constants.ONE_HUNDRED, mark_price=constants.ONE_HUNDRED)
+    assert position_inst.get_order_cost() == decimal.Decimal("10020.000")
+
+
+async def test_get_fee_to_open(future_trader_simulator):
+    config, exchange_manager_inst, trader_inst = future_trader_simulator
+    position_inst = personal_data.LinearPosition(trader_inst, DEFAULT_FUTURE_SYMBOL_CONTRACT)
+
+    await position_inst.update(update_quantity=constants.ZERO, mark_price=constants.ZERO)
+    assert position_inst.get_fee_to_open() == constants.ZERO
+    await position_inst.update(update_quantity=constants.ONE_HUNDRED, mark_price=constants.ONE_HUNDRED)
+    assert position_inst.get_fee_to_open() == decimal.Decimal("10.000")
+
+
+async def test_update_fee_to_close(future_trader_simulator):
+    config, exchange_manager_inst, trader_inst = future_trader_simulator
+    position_inst = personal_data.LinearPosition(trader_inst, DEFAULT_FUTURE_SYMBOL_CONTRACT)
+
+    await position_inst.update(update_quantity=constants.ZERO, mark_price=constants.ZERO)
+    position_inst.update_fee_to_close()
+    assert position_inst.fee_to_close == constants.ZERO
+    await position_inst.update(update_quantity=constants.ONE_HUNDRED, mark_price=constants.ONE_HUNDRED)
+    position_inst.update_fee_to_close()
+    assert position_inst.fee_to_close == decimal.Decimal("10")
diff --git a/tests/test_utils/random_numbers.py b/tests/test_utils/random_numbers.py
index c9d448dff..b853b7e56 100644
--- a/tests/test_utils/random_numbers.py
+++ b/tests/test_utils/random_numbers.py
@@ -45,6 +45,10 @@ def random_int(min_value: int = 0, max_value: int = None) -> int:
     return randrange(min_value, max_value if max_value is not None else MAX_INT)
 
 
+def decimal_random_int(min_value: int = 0, max_value: int = None) -> decimal.Decimal:
+    return decimal.Decimal(str(random_int(min_value=min_value, max_value=max_value)))
+
+
 def random_prices(min_value: float = 0, count: int = 2) -> list:
     return [random_price(min_value=min_value) for _ in range(count)]