Repository: hummingbot/hummingbot
Pull request: Refactor/ Perpetual Trading base class
**Before submitting this PR, please make sure**:

- [x] Your code builds clean without any errors or warnings
- [x] You are using approved title ("feat/", "fix/", "docs/", "refactor/")

**A description of the changes proposed in the pull request**:
- Add Perpetual Trading base class (to replace DerivativeBase class)
- Add Funding Info data class


Problem statement:
Create new PerpetualTrading base class
**Description**
Create a new connector base class PerpetualTrading based on hummingbot/connector/derivative_base.py. This new base class is to define what perpetual trading is (like an interface). Perpetual connectors will need to implement this class as well as ConnectorBase or ExchangeBase (multiple inheritance).

Hints from issue discussion:

Commit message: (refactor) add perpetual trading base class and unit test

Patch to review:
diff --git a/hummingbot/connector/perpetual_trading.py b/hummingbot/connector/perpetual_trading.py
new file mode 100644
index 00000000000..2b7f0d376ab
--- /dev/null
+++ b/hummingbot/connector/perpetual_trading.py
@@ -0,0 +1,83 @@
+from decimal import Decimal
+from typing import Dict, List
+from hummingbot.core.event.events import PositionMode, FundingInfo
+from hummingbot.connector.derivative.position import Position
+
+
+NaN = float("nan")
+s_decimal_NaN = Decimal("nan")
+s_decimal_0 = Decimal(0)
+
+
+class PerpetualTrading:
+    """
+    A base class (interface) that defines what perpetual trading is for Hummingbot.
+    """
+
+    def __init__(self):
+        super().__init__()
+        self._account_positions: List[Position] = []
+        self._position_mode: PositionMode = None
+        self._leverages: Dict[str, int] = {}
+        self._funding_info: Dict[str, FundingInfo] = {}
+        self._funding_payment_span: List[int] = [0, 0]
+
+    @property
+    def account_positions(self) -> List[Position]:
+        """
+        Returns a list current active open positions
+        """
+        return self._account_positions
+
+    @property
+    def funding_payment_span(self) -> List[int]:
+        """
+        Time span(in seconds) before and after funding period when exchanges consider active positions eligible for
+        funding payment.
+        :return: a list of seconds (before and after)
+        """
+        return self._funding_payment_span
+
+    @property
+    def position_mode(self) -> PositionMode:
+        return self._position_mode
+
+    @position_mode.setter
+    def position_mode(self, value: PositionMode):
+        """
+        Sets position mode for perpetual trading, a child class might need to override this to set position mode on
+        the exchange
+        :param value: the position mode
+        """
+        self._position_mode = value
+
+    def get_leverage(self, trading_pair: str) -> int:
+        """
+        Gets leverage level of a particular market
+        :param trading_pair: the market trading pair
+        :return: leverage level
+        """
+        return self._leverages[trading_pair]
+
+    def set_leverage(self, trading_pair: str, leverage: int = 1):
+        """
+        Sets leverage level, e.g. 2x, 10x, etc..
+        A child class may need to override this to set leverage level on the exchange
+        :param trading_pair: the market trading pair
+        :param leverage: leverage to be used
+        """
+        self._leverages[trading_pair] = leverage
+
+    def supported_position_modes(self) -> List[PositionMode]:
+        """
+        Returns a list of position modes supported by the connector
+        """
+        raise NotImplementedError
+
+    def get_funding_info(self, trading_pair: str) -> FundingInfo:
+        """
+        Returns funding information
+        :param trading_pair: the market trading pair
+        :return: funding info
+        """
+        return self._funding_info[trading_pair]
diff --git a/hummingbot/core/event/events.py b/hummingbot/core/event/events.py
index 6f141c2f810..9f2c72bc6ce 100644
--- a/hummingbot/core/event/events.py
+++ b/hummingbot/core/event/events.py
@@ -96,6 +96,14 @@ class PositionMode(Enum):
     ONEWAY = False
 
 
+class FundingInfo(NamedTuple):
+    trading_pair: str
+    index_price: Decimal
+    mark_price: Decimal
+    next_funding_utc_timestamp: int
+    rate: Decimal
+
+
 class PriceType(Enum):
     MidPrice = 1
     BestBid = 2
diff --git a/test/hummingbot/connector/test_perpetual_trading.py b/test/hummingbot/connector/test_perpetual_trading.py
new file mode 100644
index 00000000000..d00329eaf93
--- /dev/null
+++ b/test/hummingbot/connector/test_perpetual_trading.py
@@ -0,0 +1,51 @@
+import unittest
+from decimal import Decimal
+from hummingbot.connector.perpetual_trading import PerpetualTrading
+from hummingbot.core.event.events import PositionMode, FundingInfo, PositionSide
+from hummingbot.connector.derivative.position import Position
+
+
+class PerpetualTest(unittest.TestCase):
+
+    def test_init(self):
+        pt: PerpetualTrading = PerpetualTrading()
+        self.assertEqual(len(pt.account_positions), 0)
+        self.assertEqual(pt.position_mode, None)
+        self.assertEqual(pt.funding_payment_span, [0, 0])
+
+    def test_account_positions(self):
+        """
+        Test getting account positions by manually adding a position to the class member
+        """
+        pt: PerpetualTrading = PerpetualTrading()
+        aPos: Position = Position("", PositionSide.LONG, Decimal("0"), Decimal("100"), Decimal("1"), Decimal("5"))
+        pt._account_positions.append(aPos)
+        self.assertEqual(len(pt.account_positions), 1)
+        self.assertEqual(pt.account_positions[0], aPos)
+
+    def test_position_mode(self):
+        pt: PerpetualTrading = PerpetualTrading()
+        self.assertEqual(pt.position_mode, None)
+        pt.position_mode = PositionMode.ONEWAY
+        self.assertEqual(pt.position_mode, PositionMode.ONEWAY)
+
+    def test_leverage(self):
+        pt: PerpetualTrading = PerpetualTrading()
+        pt.set_leverage("pair1", 2)
+        pt.set_leverage("pair2", 3)
+        self.assertEqual(pt.get_leverage("pair1"), 2)
+        self.assertEqual(pt.get_leverage("pair2"), 3)
+
+    def test_supported_position_modes(self):
+        pt: PerpetualTrading = PerpetualTrading()
+        with self.assertRaises(NotImplementedError):
+            pt.supported_position_modes()
+
+    def test_funding_info(self):
+        """
+        Test getting funding infos by manually adding a funding info to the class member
+        """
+        pt: PerpetualTrading = PerpetualTrading()
+        fInfo: FundingInfo = FundingInfo("pair1", Decimal(1), Decimal(2), 1000, Decimal(0.1))
+        pt._funding_info["pair1"] = fInfo
+        self.assertEqual(pt.get_funding_info("pair1"), fInfo)


Code review comments:
diff_hunk: @@ -0,0 +1,83 @@
+from decimal import Decimal
+from typing import Dict, List
+from hummingbot.core.event.events import PositionMode, FundingInfo
+from hummingbot.connector.derivative.position import Position
+
+
+NaN = float("nan")
+s_decimal_NaN = Decimal("nan")
+s_decimal_0 = Decimal(0)
+
+
+class PerpetualTrading:
+    """
+    A base class (interface) that defines what perpetual trading is for Hummingbot.
+    """
+
+    def __init__(self):
+        super().__init__()
+        self._account_positions: List[Position] = []
path: hummingbot/connector/perpetual_trading.py
text: @user1:
This should be a dictionary. Connectors that support Hedge-mode are going to have issues if it's a list. i.e. it'll be difficult to manage 2 contracts on  a particular market.

@user1:
Keeping it as a list will require the `get_position` function in dydx perp for instance to take an extra parameter to filter and return position side specific result.