From c8d8ab524ea799f95dd6242345f0ebff57dfd260 Mon Sep 17 00:00:00 2001
From: Nelson Torres <nelson.torres.alvarado1@gmail.com>
Date: Thu, 5 Dec 2024 23:31:36 -0300
Subject: [PATCH] oi max_pain major refactor

Now the max_pain is calculated taking into account all strike prices and all close prices to find the intrinsic value as deribit.
---
 piker/brokers/deribit/api.py | 125 ++++++++++++-----------------------
 1 file changed, 44 insertions(+), 81 deletions(-)

diff --git a/piker/brokers/deribit/api.py b/piker/brokers/deribit/api.py
index f39815ec..2158a39d 100644
--- a/piker/brokers/deribit/api.py
+++ b/piker/brokers/deribit/api.py
@@ -827,13 +827,15 @@ async def maybe_open_price_feed(
 async def aio_open_interest_feed_relay(
     fh: FeedHandler,
     instruments: list,
-    open_interests: dict[str, dict[str, dict[str, list[dict[str, Decimal]]]]],
-    losses_cache: dict[str, Decimal],
-    max_losses: Decimal,
-    max_pain: Decimal,
+    intrinsic_values: dict[str, Decimal],
+    oi_by_strikes: dict[str, dict[str, Decimal]],
     from_trio: asyncio.Queue,
     to_trio: trio.abc.SendChannel,
 ) -> None:
+    
+    max_losses: Decimal = Decimal('Infinity')
+    max_pain: Decimal = Decimal(0)
+
     async def _trade(
         trade: Trade,  # cryptofeed, NOT ours from `.venues`!
         receipt_timestamp: int,
@@ -855,13 +857,13 @@ async def aio_open_interest_feed_relay(
         Proxy-thru `cryptofeed.FeedHandler` "oi" to `piker`-side.
 
         '''
-        nonlocal losses_cache
+        nonlocal intrinsic_values
+        nonlocal oi_by_strikes
         nonlocal max_losses
         nonlocal max_pain
-        nonlocal open_interests
+
         symbol: Symbol = str_to_cb_sym(oi.symbol)
         piker_sym: str = cb_sym_to_deribit_inst(symbol)
-        data: dict = oi.raw['params']['data']
         (
             base,
             expiry_date,
@@ -870,69 +872,37 @@ async def aio_open_interest_feed_relay(
         ) = tuple(
             piker_sym.split('-')
         )
-        if not f'{expiry_date}' in open_interests:
-            open_interests[f'{expiry_date}'] = {
-                f'{strike_price}': {
-                    'C': [],
-                    'P': [],
-                    'strike_losses': Decimal(0),
-                }
-            }
-        if not f'{strike_price}' in open_interests[f'{expiry_date}']:
-            open_interests[f'{expiry_date}'][f'{strike_price}'] = {
-                'C': [],
-                'P': [],
-                'strike_losses': Decimal(0),
-            }
-
-        index_price: Decimal = data['index_price']
         open_interest: Decimal = oi.open_interest
-        price_delta: Decimal
-        if f'{option_type}' == 'C':
-            price_delta = index_price - Decimal(strike_price)
+        oi_by_strikes[f'{strike_price}'][f'{option_type}'] = open_interest
 
-        elif f'{option_type}' == 'P':
-            price_delta = Decimal(strike_price) - index_price
+        is_ready = check_if_complete(oi_by_strikes)
+        if is_ready:
+            for strike in oi_by_strikes:
+                s: Decimal = Decimal(f'{strike}')
+                close_losses = Decimal(0)
+                closes: list[str] = sorted(oi_by_strikes.keys())
+                call_cash: Decimal = Decimal(0)
+                put_cash: Decimal = Decimal(0)
+                for close in closes:
+                    c: Decimal = Decimal(f'{close}')
+                    call_cash += max(0, (s - c) * oi_by_strikes[f'{close}']['C'])
+                    put_cash += max(0, (c - s) * oi_by_strikes[f'{close}']['P'])
+    
+                intrinsic_values[f'{strike}'] = {}
+                intrinsic_values[f'{strike}']['C'] = call_cash
+                intrinsic_values[f'{strike}']['P'] = put_cash
+                intrinsic_values[f'{strike}']['total'] = (call_cash + put_cash)
 
-        notional_value = open_interest * index_price
-        losses: Decimal = max(0, price_delta * open_interest)
-
-        if not f'{strike_price}' in losses_cache:
-            losses_cache[f'{strike_price}'] = {
-                'C': Decimal(0),
-                'P': Decimal(0),
-            }
-
-        losses_cache[f'{strike_price}'][f'{option_type}'] = losses
-
-        strike_losses: Decimal = (
-            losses_cache[f'{strike_price}']['C']
-            +
-            losses_cache[f'{strike_price}']['P']
-        )
-
-        open_interests[f'{expiry_date}'][f'{strike_price}'][f'{option_type}'] = {
-            'date': oi.timestamp,
-            'open_interest': open_interest,
-            'index_price': index_price,
-            'strike_price': strike_price,
-            'price_delta': price_delta,
-            'notional_value': notional_value,
-            'losses': losses, # this updates the global value call_losses and put_losses
-        }
-        # calculate with latest values stored in call_losses and put_losses global cache.
-        open_interests[f'{expiry_date}'][f'{strike_price}']['strike_losses'] = strike_losses
-
-        for strike in open_interests[f'{expiry_date}']:
-            if strike_losses > max_losses:
-                max_losses = open_interests[f'{expiry_date}'][strike]['strike_losses']
-                max_pain = strike
-                print(f'>>>> Open Interest...')
-                print(f'expiration: {expiry_date}')
-                print(f'>>>> max_pain: {max_pain}')
-                print(f'max_losses: {max_losses}')
-                print(f'{pformat(open_interests[f'{expiry_date}'][max_pain])}')
-                print('-----------------------------------------------')
+            for strike in intrinsic_values:
+                if intrinsic_values[f'{strike}']['total'] < max_losses:
+                    max_losses = intrinsic_values[f'{strike}']['total']
+                    max_pain = strike
+            
+            print('-----------------------------------------------')
+            print(f'time:   {oi.timestamp}')
+            print(f'max_pain:   {max_pain}')
+            print(f'max_losses: {max_losses}')
+            print('-----------------------------------------------')
 
 
     channels = [TRADES, OPEN_INTEREST]
@@ -964,21 +934,16 @@ async def open_oi_feed(
 
     expiry_date: str = '20DEC24'
     instruments: list[Symbol] = []
+    intrinsic_values: dict[str, dict[str, Decimal]] = {}
+    oi_by_strikes: dict[str, dict[str, Decimal]]
+
     async with get_client(
     ) as client:
-        # to get all currencies available in deribit
-        # currencies = await client.get_currencies()
         instruments = await client.get_instruments(
             expiry_date=expiry_date,
         )
-    losses_cache: dict[str, Decimal] = { # {'<strike_price>': <value>}
-        'C': Decimal(0),
-        'P': Decimal(0),
-    }
-#    max_losses: Decimal = Decimal('Infinity')
-    max_losses: Decimal = Decimal(0)
-    max_pain: Decimal = Decimal(0)
-    open_interests: dict[str, dict[str, dict[str, list[dict]]]] = {}
+        oi_by_strikes = client.get_strikes_dict(instruments)
+
     fh: FeedHandler
     first: None
     chan: to_asyncio.LinkedTaskChannel
@@ -989,10 +954,8 @@ async def open_oi_feed(
                 aio_open_interest_feed_relay,
                 fh,
                 instruments,
-                open_interests,
-                losses_cache,
-                max_losses,
-                max_pain,
+                intrinsic_values,
+                oi_by_strikes,
             )
         ) as (first, chan)
     ):