{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 31,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2022-11-09T02:34:34.052011Z",
     "start_time": "2022-11-09T02:34:33.441460Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "running typecheck\n",
      "Success: no issues found in 1 source file\n",
      "\n",
      "running flake8\n",
      "flake8 success\n"
     ]
    }
   ],
   "source": [
    "%%checkall\n",
    "from __future__ import annotations\n",
    "import pandas as pd\n",
    "import numpy as np\n",
    "import types\n",
    "import math\n",
    "import datetime\n",
    "from dataclasses import dataclass\n",
    "from types import SimpleNamespace\n",
    "from typing import Any\n",
    "from pyqstrat.pq_utils import assert_\n",
    "\n",
    "\n",
    "class ContractGroup:\n",
    "    '''A way to group contracts for figuring out which indicators, rules and signals to apply to a contract and for PNL reporting'''\n",
    "\n",
    "    _group_names: set[str] = set()\n",
    "    name: str\n",
    "    contracts: set[Contract]\n",
    "    contracts_by_symbol: dict[str, Contract]\n",
    "    \n",
    "    @staticmethod\n",
    "    def clear() -> None:\n",
    "        '''\n",
    "        When running Python interactively you may create a ContractGroup with a given name multiple times because you don't restart Python \n",
    "        therefore global variables are not cleared.  This function clears global ContractGroups\n",
    "        '''\n",
    "        ContractGroup._group_names = set()\n",
    "        \n",
    "    @staticmethod\n",
    "    def create(name) -> ContractGroup:\n",
    "        '''\n",
    "         Args:\n",
    "            name (str): Name of the group\n",
    "        '''\n",
    "        if name in ContractGroup._group_names:\n",
    "            raise Exception(f'Contract group: {name} already exists')\n",
    "        ContractGroup._group_names.add(name)\n",
    "        contract_group = ContractGroup()\n",
    "        contract_group.name = name\n",
    "        contract_group.contracts = set()\n",
    "        contract_group.contracts_by_symbol = {}\n",
    "        return contract_group\n",
    "        \n",
    "    def add_contract(self, contract):\n",
    "        self.contracts.add(contract)\n",
    "        self.contracts_by_symbol[contract.symbol] = contract\n",
    "        \n",
    "    def get_contract(self, symbol):\n",
    "        return self.contracts_by_symbol.get(symbol)\n",
    "        \n",
    "    def __repr__(self):\n",
    "        return self.name\n",
    "\n",
    "\n",
    "class Contract:\n",
    "    _symbol_names: set[str] = set()\n",
    "    symbol: str\n",
    "    expiry: np.datetime64 | None\n",
    "    multiplier: float\n",
    "    properties: SimpleNamespace\n",
    "    contract_group: ContractGroup\n",
    "        \n",
    "    contracts_by_symbol: dict[str, Contract]\n",
    "\n",
    "    '''A contract such as a stock, option or a future that can be traded'''\n",
    "    @staticmethod\n",
    "    def create(symbol: str, \n",
    "               contract_group: ContractGroup, \n",
    "               expiry: np.datetime64 | datetime.datetime | None = None, multiplier: float = 1., \n",
    "               properties: SimpleNamespace | None = None) -> 'Contract':\n",
    "        '''\n",
    "        Args:\n",
    "            symbol: A unique string reprenting this contract. e.g IBM or ESH9\n",
    "            contract_group: We sometimes need to group contracts for calculating PNL, for example, you may have a strategy\n",
    "                which has 3 legs, a long option, a short option and a future or equity used to hedge delta.  In this case, you will be trading\n",
    "                different symbols over time as options and futures expire, but you may want to track PNL for each leg using a contract group for each leg.\n",
    "                So you could create contract groups 'Long Option', 'Short Option' and 'Hedge' and assign contracts to these.\n",
    "            expiry: In the case of a future or option, the date and time when the \n",
    "                contract expires.  For equities and other non expiring contracts, set this to None.  Default None.\n",
    "            multiplier: If the market price convention is per unit, and the unit is not the same as contract size, \n",
    "                set the multiplier here. For example, for E-mini contracts, each contract is 50 units and the price is per unit, \n",
    "                so multiplier would be 50.  Default 1\n",
    "            properties: Any data you want to store with this contract.\n",
    "                For example, you may want to store option strike.  Default None\n",
    "        '''\n",
    "        assert_(isinstance(symbol, str) and len(symbol) > 0)\n",
    "        if symbol in Contract._symbol_names:\n",
    "            raise Exception(f'Contract with symbol: {symbol} already exists')\n",
    "        Contract._symbol_names.add(symbol)\n",
    "\n",
    "        assert_(multiplier > 0)\n",
    "\n",
    "        contract = Contract()\n",
    "        contract.symbol = symbol\n",
    "        \n",
    "        assert_(expiry is None or isinstance(expiry, datetime.datetime) or isinstance(expiry, np.datetime64))\n",
    "        \n",
    "        if expiry is not None and isinstance(expiry, datetime.datetime):\n",
    "            expiry = np.datetime64(expiry)\n",
    "            \n",
    "        contract.expiry = expiry\n",
    "        contract.multiplier = multiplier\n",
    "        \n",
    "        if properties is None:\n",
    "            properties = types.SimpleNamespace()\n",
    "        contract.properties = properties\n",
    "        \n",
    "        contract_group.add_contract(contract)\n",
    "        contract.contract_group = contract_group\n",
    "        return contract\n",
    "    \n",
    "    @staticmethod\n",
    "    def clear() -> None:\n",
    "        '''\n",
    "        When running Python interactively you may create a Contract with a given symbol multiple times because you don't restart Python \n",
    "        therefore global variables are not cleared.  This function clears global Contracts\n",
    "        '''\n",
    "        Contract._symbol_names = set()\n",
    "        \n",
    "    def __repr__(self) -> str:\n",
    "        return f'{self.symbol}' + (f' {self.multiplier}' if self.multiplier != 1 else '') + (\n",
    "            f' expiry: {self.expiry.astype(datetime.datetime):%Y-%m-%d %H:%M:%S}' if self.expiry is not None else '') + (\n",
    "            f' group: {self.contract_group.name}' if self.contract_group else '') + (\n",
    "            f' {self.properties.__dict__}' if self.properties.__dict__ else '')\n",
    "    \n",
    "\n",
    "@dataclass\n",
    "class Price:\n",
    "    '''\n",
    "    >>> price = Price(datetime.datetime(2020, 1, 1), 15.25, 15.75, 189, 300)\n",
    "    >>> print(price)\n",
    "    15.25@189/15.75@300\n",
    "    >>> price.properties = SimpleNamespace(delta = -0.3)\n",
    "    >>> price.valid = False\n",
    "    >>> print(price)\n",
    "    15.25@189/15.75@300 delta: -0.3 invalid\n",
    "    >>> print(price.mid())\n",
    "    15.5\n",
    "    '''\n",
    "    timestamp: datetime.datetime\n",
    "    bid: float\n",
    "    ask: float\n",
    "    bid_size: int\n",
    "    ask_size: int\n",
    "    valid: bool = True\n",
    "    properties: SimpleNamespace | None = None\n",
    "        \n",
    "    @staticmethod\n",
    "    def invalid() -> Price:\n",
    "        return Price(datetime.datetime(datetime.MINYEAR, 1, 1),\n",
    "                     bid=math.nan, \n",
    "                     ask=math.nan, \n",
    "                     bid_size=-1, \n",
    "                     ask_size=-1, \n",
    "                     valid=False)\n",
    "        \n",
    "    def mid(self) -> float:\n",
    "        return 0.5 * (self.bid + self.ask)\n",
    "    \n",
    "    def vw_mid(self) -> float:\n",
    "        '''\n",
    "        Volume weighted mid\n",
    "        >>> price = Price(datetime.datetime(2020, 1, 1), 15.25, 15.75, 189, 300)\n",
    "        >>> print(f'{price.vw_mid():.4f}')\n",
    "        15.4433\n",
    "        >>> price.bid_size = 0\n",
    "        >>> price.ask_size = 0\n",
    "        >>> assert math.isnan(price.vw_mid())\n",
    "        '''\n",
    "        if self.bid_size + self.ask_size == 0: return math.nan\n",
    "        return (self.bid * self.ask_size + self.ask * self.bid_size) / (self.bid_size + self.ask_size)\n",
    "    \n",
    "    def set_property(self, name: str, value: Any) -> None:\n",
    "        if self.properties is None:\n",
    "            self.properties = SimpleNamespace()\n",
    "        setattr(self.properties, name, value)\n",
    "    \n",
    "    def spread(self) -> float:\n",
    "        if self.ask < self.bid: return math.nan\n",
    "        return self.ask - self.bid\n",
    "        \n",
    "    def __repr__(self) -> str:\n",
    "        msg = f'{self.bid:.2f}@{self.bid_size}/{self.ask:.2f}@{self.ask_size}'\n",
    "        if self.properties:\n",
    "            for k, v in self.properties.__dict__.items():\n",
    "                if isinstance(v, (np.floating, float)):\n",
    "                    msg += f' {k}: {v:.5g}'\n",
    "                else:\n",
    "                    msg += f' {k}: {v}'\n",
    "        if not self.valid:\n",
    "            msg += ' invalid'\n",
    "        return msg\n",
    "\n",
    "\n",
    "class OrderStatus:\n",
    "    '''\n",
    "    Enum for order status\n",
    "    '''\n",
    "    OPEN = 'open'\n",
    "    FILLED = 'filled'\n",
    "    \n",
    "\n",
    "class ReasonCode:\n",
    "    '''A class containing constants for predefined order reason codes. Prefer these predefined reason codes if they suit\n",
    "    the reason you are creating your order.  Otherwise, use your own string.\n",
    "    '''\n",
    "    ENTER_LONG = 'enter long'\n",
    "    ENTER_SHORT = 'enter short'\n",
    "    EXIT_LONG = 'exit long'\n",
    "    EXIT_SHORT = 'exit short'\n",
    "    BACKTEST_END = 'backtest end'\n",
    "    ROLL_FUTURE = 'roll future'\n",
    "    NONE = 'none'\n",
    "    \n",
    "    # Used for plotting trades\n",
    "    MARKER_PROPERTIES = {\n",
    "        ENTER_LONG: {'symbol': 'P', 'color': 'blue', 'size': 50},\n",
    "        ENTER_SHORT: {'symbol': 'P', 'color': 'red', 'size': 50},\n",
    "        EXIT_LONG: {'symbol': 'X', 'color': 'blue', 'size': 50},\n",
    "        EXIT_SHORT: {'symbol': 'X', 'color': 'red', 'size': 50},\n",
    "        ROLL_FUTURE: {'symbol': '>', 'color': 'green', 'size': 50},\n",
    "        BACKTEST_END: {'symbol': '*', 'color': 'green', 'size': 50},\n",
    "        NONE: {'symbol': 'o', 'color': 'green', 'size': 50}\n",
    "    }\n",
    "    \n",
    "\n",
    "class Order:\n",
    "    def __init__(self) -> None:\n",
    "        self.contract: Contract | None = None\n",
    "        self.timestamp: np.datetime64 | None = None\n",
    "        self.qty: float = math.nan\n",
    "        self.reason_code: str | None = None\n",
    "        self.properties: SimpleNamespace | None = None\n",
    "        self.status: str | None = None\n",
    "\n",
    "\n",
    "class MarketOrder(Order):\n",
    "    def __init__(self, contract: Contract, \n",
    "                 timestamp: np.datetime64, \n",
    "                 qty: float, \n",
    "                 reason_code: str = ReasonCode.NONE, \n",
    "                 properties: SimpleNamespace | None = None, \n",
    "                 status: str = 'open') -> None:\n",
    "        '''\n",
    "        Args:\n",
    "            contract: The contract this order is for\n",
    "            timestamp: Time the order was placed\n",
    "            qty:  Number of contracts or shares.  Use a negative quantity for sell orders\n",
    "            reason_code: The reason this order was created.\n",
    "                Prefer a predefined constant from the ReasonCode class if it matches your reason for creating this order.\n",
    "                Default None\n",
    "            properties: Any order specific data we want to store.  Default None\n",
    "            status: Status of the order, \"open\", \"filled\", etc. Default \"open\"\n",
    "        '''\n",
    "        self.contract = contract\n",
    "        self.timestamp = timestamp\n",
    "        if not np.isfinite(qty) or math.isclose(qty, 0): raise Exception(f'order qty must be finite and nonzero: {qty}')\n",
    "        self.qty = qty\n",
    "        self.reason_code = reason_code\n",
    "        self.status = status\n",
    "        if properties is None: properties = types.SimpleNamespace()\n",
    "        self.properties = properties\n",
    "        \n",
    "    def __repr__(self):\n",
    "        timestamp = pd.Timestamp(self.timestamp).to_pydatetime()\n",
    "        return f'{self.contract.symbol} {timestamp:%Y-%m-%d %H:%M:%S} qty: {self.qty}' + (\n",
    "            '' if self.reason_code == ReasonCode.NONE else f' {self.reason_code}') + (\n",
    "            '' if not self.properties.__dict__ else f' {self.properties}') + (\n",
    "            f' {self.status}')\n",
    "            \n",
    "\n",
    "class LimitOrder(Order):\n",
    "    def __init__(self, \n",
    "                 contract: Contract,\n",
    "                 timestamp: np.datetime64,\n",
    "                 qty: float,\n",
    "                 limit_price: float,\n",
    "                 reason_code: str = ReasonCode.NONE,\n",
    "                 properties: SimpleNamespace | None = None,\n",
    "                 status: str = 'open') -> None:\n",
    "        '''\n",
    "        Args:\n",
    "            contract: The contract this order is for\n",
    "            timestamp: Time the order was placed\n",
    "            qty:  Number of contracts or shares.  Use a negative quantity for sell orders\n",
    "            limit_price: Limit price (float)\n",
    "            reason_code: The reason this order was created.\n",
    "                Prefer a predefined constant from the ReasonCode class if it matches your reason for creating this order.\n",
    "                Default None\n",
    "            properties: Any order specific data we want to store.  Default None\n",
    "            status: Status of the order, \"open\", \"filled\", etc. (default \"open\")\n",
    "        '''\n",
    "        self.contract = contract\n",
    "        self.timestamp = timestamp\n",
    "        if not np.isfinite(qty) or math.isclose(qty, 0): raise Exception(f'order qty must be finite and nonzero: {qty}')\n",
    "        self.qty = qty\n",
    "        self.reason_code = reason_code\n",
    "        self.limit_price = limit_price\n",
    "        if properties is None: properties = types.SimpleNamespace()\n",
    "        self.properties = properties\n",
    "        self.properties.limit_price = self.limit_price\n",
    "        self.status = status\n",
    "        \n",
    "    def __repr__(self) -> str:\n",
    "        timestamp = pd.Timestamp(self.timestamp).to_pydatetime()\n",
    "        symbol = self.contract.symbol if self.contract else ''\n",
    "        return f'{symbol} {timestamp:%Y-%m-%d %H:%M:%S} qty: {self.qty} lmt_prc: {self.limit_price}' + (\n",
    "            '' if self.reason_code == ReasonCode.NONE else f' {self.reason_code}') + (\n",
    "            '' if not self.properties.__dict__ else f' {self.properties}') + (\n",
    "            f' {self.status}')\n",
    "    \n",
    "\n",
    "class RollOrder(Order):\n",
    "    '''A roll order is used to roll a future from one series to the next.  It represents a sell of one future and the buying of another future.'''\n",
    "    def __init__(self, \n",
    "                 contract: Contract, \n",
    "                 timestamp: np.datetime64,\n",
    "                 close_qty: float,\n",
    "                 reopen_qty: float,\n",
    "                 reason_code: str = ReasonCode.ROLL_FUTURE,\n",
    "                 properties: SimpleNamespace | None = None, \n",
    "                 status: str = 'open') -> None:\n",
    "        '''\n",
    "        Args:\n",
    "            contract: The contract this order is for\n",
    "            timestamp: Time the order was placed\n",
    "            close_qty: Quantity of the future you are rolling\n",
    "            reopen_qty: Quantity of the future you are rolling to\n",
    "            reason_code: The reason this order was created.\n",
    "                Prefer a predefined constant from the ReasonCode class if it matches your reason for creating this order.\n",
    "                Default None\n",
    "            properties: Any order specific data we want to store.  Default None\n",
    "            status: Status of the order, \"open\", \"filled\", etc. (default \"open\")\n",
    "        '''\n",
    "        self.contract = contract\n",
    "        self.timestamp = timestamp\n",
    "        if not np.isfinite(close_qty) or math.isclose(close_qty, 0) or not np.isfinite(reopen_qty) or math.isclose(reopen_qty, 0):\n",
    "            raise Exception(f'order quantities must be non-zero and finite: {close_qty} {reopen_qty}')\n",
    "        self.close_qty = close_qty\n",
    "        self.reopen_qty = reopen_qty\n",
    "        self.reason_code = reason_code\n",
    "        self.qty = close_qty  # For display purposes when we print varying order types\n",
    "        if properties is None: properties = types.SimpleNamespace()\n",
    "        self.properties = properties\n",
    "        self.properties.close_qty = self.close_qty\n",
    "        self.properties.reopen_qty = self.reopen_qty\n",
    "        self.status = status\n",
    "        \n",
    "    def __repr__(self) -> str:\n",
    "        timestamp = pd.Timestamp(self.timestamp).to_pydatetime()\n",
    "        symbol = self.contract.symbol if self.contract else ''\n",
    "        return f'{symbol} {timestamp:%Y-%m-%d %H:%M:%S} close_qty: {self.close_qty} reopen_qty: {self.reopen_qty}' + (\n",
    "            '' if self.reason_code == ReasonCode.NONE else f' {self.reason_code}') + '' if not self.properties.__dict__ else f' {self.properties}' + (\n",
    "            f' {self.status}')\n",
    "\n",
    "  \n",
    "class StopLimitOrder(Order):\n",
    "    '''Used for stop loss or stop limit orders.  The order is triggered when price goes above or below trigger price, depending on whether this is a short\n",
    "      or long order.  Becomes either a market or limit order at that point, depending on whether you set the limit price or not.\n",
    "    '''\n",
    "    def __init__(self, \n",
    "                 contract: Contract,\n",
    "                 timestamp: np.datetime64,\n",
    "                 qty: float,\n",
    "                 trigger_price: float,\n",
    "                 limit_price: float = np.nan,\n",
    "                 reason_code: str = ReasonCode.NONE, \n",
    "                 properties: SimpleNamespace | None = None,\n",
    "                 status: str = 'open') -> None:\n",
    "        '''\n",
    "        Args:\n",
    "            contract: The contract this order is for\n",
    "            timestamp: Time the order was placed\n",
    "            qty:  Number of contracts or shares.  Use a negative quantity for sell orders\n",
    "            trigger_price: Order becomes a market or limit order if price crosses trigger_price.\n",
    "            limit_price: If not set (default), order becomes a market order when price crosses trigger price.  \n",
    "                Otherwise it becomes a limit order.  Default np.nan\n",
    "            reason_code: The reason this order was created.\n",
    "                Prefer a predefined constant from the ReasonCode class if it matches your reason for creating this order.\n",
    "                Default None\n",
    "            properties: Any order specific data we want to store.  Default None\n",
    "            status: Status of the order, \"open\", \"filled\", etc. (default \"open\")\n",
    "        '''      \n",
    "        self.contract = contract\n",
    "        self.timestamp = timestamp\n",
    "        if not np.isfinite(qty) or math.isclose(qty, 0): raise Exception(f'order qty must be finite and nonzero: {qty}')\n",
    "        self.qty = qty\n",
    "        self.trigger_price = trigger_price\n",
    "        self.limit_price = limit_price\n",
    "        self.reason_code = reason_code\n",
    "        self.triggered = False\n",
    "        if properties is None: properties = types.SimpleNamespace()\n",
    "        self.properties = properties\n",
    "        self.properties.trigger_price = trigger_price\n",
    "        self.properties.limit_price = limit_price\n",
    "        self.status = status\n",
    "        \n",
    "    def __repr__(self) -> str:\n",
    "        timestamp = pd.Timestamp(self.timestamp).to_pydatetime()\n",
    "        symbol = self.contract.symbol if self.contract else ''\n",
    "        return f'{symbol} {timestamp:%Y-%m-%d %H:%M:%S} qty: {self.qty} trigger_prc: {self.trigger_price} limit_prc: {self.limit_price}' + (\n",
    "            '' if self.reason_code == ReasonCode.NONE else f' {self.reason_code}') + ('' if not self.properties.__dict__ else f' {self.properties}') + (\n",
    "            f' {self.status}')\n",
    "    \n",
    "\n",
    "class Trade:\n",
    "    def __init__(self, contract: Contract,\n",
    "                 order: Order,\n",
    "                 timestamp: np.datetime64, \n",
    "                 qty: float, \n",
    "                 price: float, \n",
    "                 fee: float = 0., \n",
    "                 commission: float = 0., \n",
    "                 properties: SimpleNamespace | None = None) -> None:\n",
    "        '''\n",
    "        Args:\n",
    "            contract: The contract we traded\n",
    "            order: A reference to the order that created this trade. Default None\n",
    "            timestamp: Trade execution datetime\n",
    "            qty: Number of contracts or shares filled\n",
    "            price: Trade price\n",
    "            fee: Fees paid to brokers or others. Default 0\n",
    "            commision: Commission paid to brokers or others. Default 0\n",
    "            properties: Any data you want to store with this contract.\n",
    "                For example, you may want to store bid / ask prices at time of trade.  Default None\n",
    "        '''\n",
    "        # assert(isinstance(contract, Contract))\n",
    "        # assert(isinstance(order, Order))\n",
    "        assert_(np.isfinite(qty))\n",
    "        assert_(np.isfinite(price))\n",
    "        assert_(np.isfinite(fee))\n",
    "        assert_(np.isfinite(commission))\n",
    "        # assert(isinstance(timestamp, np.datetime64))\n",
    "        \n",
    "        self.contract = contract\n",
    "        self.order = order\n",
    "        self.timestamp = timestamp\n",
    "        self.qty = qty\n",
    "        self.price = price\n",
    "        self.fee = fee\n",
    "        self.commission = commission\n",
    "        \n",
    "        if properties is None:\n",
    "            properties = types.SimpleNamespace()\n",
    "        self.properties = properties\n",
    "        \n",
    "    def __repr__(self) -> str:\n",
    "        '''\n",
    "        >>> Contract.clear()\n",
    "        >>> ContractGroup.clear()\n",
    "        >>> contract = Contract.create('IBM', contract_group = ContractGroup.create('IBM'))\n",
    "        >>> order = MarketOrder(contract, np.datetime64('2019-01-01T14:59'), 100)\n",
    "        >>> print(Trade(contract, order, np.datetime64('2019-01-01 15:00'), 100, 10.2130000, 0.01))\n",
    "        IBM 2019-01-01 15:00:00 qty: 100 prc: 10.213 fee: 0.01 order: IBM 2019-01-01 14:59:00 qty: 100 open\n",
    "        '''\n",
    "        timestamp = pd.Timestamp(self.timestamp).to_pydatetime()\n",
    "        fee = f' fee: {self.fee:.6g}' if self.fee else ''\n",
    "        commission = f' commission: {self.commission:.6g}' if self.commission else ''\n",
    "        return f'{self.contract.symbol}' + (\n",
    "            f' {self.contract.properties.__dict__}' if self.contract.properties.__dict__ else '') + (\n",
    "            f' {timestamp:%Y-%m-%d %H:%M:%S} qty: {self.qty} prc: {self.price:.6g}{fee}{commission} order: {self.order}') + (\n",
    "            f' {self.properties.__dict__}' if self.properties.__dict__ else '')\n",
    "    \n",
    "\n",
    "@dataclass\n",
    "class RoundTripTrade:\n",
    "    contract: Contract\n",
    "    entry_order: Order\n",
    "    exit_order: Order\n",
    "    entry_timestamp: np.datetime64\n",
    "    exit_timestamp: np.datetime64\n",
    "    qty: int\n",
    "    entry_price: float\n",
    "    exit_price: float\n",
    "    entry_reason: str | None\n",
    "    exit_reason: str | None\n",
    "    entry_commission: float\n",
    "    exit_commission: float\n",
    "    entry_properties: SimpleNamespace | None\n",
    "    exit_properties: SimpleNamespace | None\n",
    "    net_pnl: float\n",
    "    \n",
    "    \n",
    "if __name__ == \"__main__\":\n",
    "    import doctest\n",
    "    doctest.testmod(optionflags=doctest.NORMALIZE_WHITESPACE)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.13"
  },
  "latex_envs": {
   "LaTeX_envs_menu_present": true,
   "autoclose": false,
   "autocomplete": true,
   "bibliofile": "biblio.bib",
   "cite_by": "apalike",
   "current_citInitial": 1,
   "eqLabelWithNumbers": true,
   "eqNumInitial": 1,
   "hotkeys": {
    "equation": "Ctrl-E",
    "itemize": "Ctrl-I"
   },
   "labels_anchors": false,
   "latex_user_defs": false,
   "report_style_numbering": false,
   "user_envs_cfg": false
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
