| 
									
										
										
										
											2024-11-26 18:16:21 +00:00
										 |  |  | #!/usr/bin/env python | 
					
						
							| 
									
										
										
										
											2024-12-07 13:13:12 +00:00
										 |  |  | from decimal import ( | 
					
						
							|  |  |  |     Decimal, | 
					
						
							|  |  |  | ) | 
					
						
							| 
									
										
										
										
											2024-11-26 18:16:21 +00:00
										 |  |  | import trio | 
					
						
							|  |  |  | import tractor | 
					
						
							| 
									
										
										
										
											2024-12-07 13:13:12 +00:00
										 |  |  | from pprint import pformat | 
					
						
							| 
									
										
										
										
											2024-11-26 18:16:21 +00:00
										 |  |  | from piker.brokers.deribit.api import ( | 
					
						
							| 
									
										
										
										
											2024-12-07 13:13:12 +00:00
										 |  |  |     get_client, | 
					
						
							| 
									
										
										
										
											2024-11-26 18:16:21 +00:00
										 |  |  |     maybe_open_oi_feed, | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | async def max_pain_daemon( | 
					
						
							|  |  |  | ) -> None: | 
					
						
							| 
									
										
										
										
											2024-12-07 13:13:12 +00:00
										 |  |  |     expiry_date: str = '13DEC24' | 
					
						
							|  |  |  |     instruments: list[Symbol] = [] | 
					
						
							|  |  |  |     oi_by_strikes: dict[str, dict[str, Decimal]] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def check_if_complete( | 
					
						
							|  |  |  |         oi: dict[str, dict[str, Decimal | None]], | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |         ) -> bool: | 
					
						
							|  |  |  |             for strike in oi: | 
					
						
							|  |  |  |                 if ( | 
					
						
							|  |  |  |                     oi[strike]['C'] == None  | 
					
						
							|  |  |  |                     or | 
					
						
							|  |  |  |                     oi[strike]['P'] == None | 
					
						
							|  |  |  |                 ): | 
					
						
							|  |  |  |                     return False | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |             return True | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def get_max_pain( | 
					
						
							|  |  |  |         oi_by_strikes: dict[str, dict[str, Decimal]] | 
					
						
							|  |  |  |     ) -> dict[str, str | Decimal]: | 
					
						
							|  |  |  |         '''
 | 
					
						
							|  |  |  |         This method requires only the strike_prices and oi for call | 
					
						
							|  |  |  |         and puts, the closes list are the same as the strike_prices | 
					
						
							|  |  |  |         the idea is to sum all the calls and puts cash for each strike | 
					
						
							|  |  |  |         and the ITM strikes from that strike, the lowest value is what we  | 
					
						
							|  |  |  |         are looking for the intrinsic value. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         '''
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         nonlocal timestamp | 
					
						
							|  |  |  |         # We meed to find the lowest value, so we start at  | 
					
						
							|  |  |  |         # infinity to ensure that, and the max_pain must be  | 
					
						
							|  |  |  |         # an amount greater than zero. | 
					
						
							|  |  |  |         total_intrinsic_value: Decimal = Decimal('Infinity') | 
					
						
							|  |  |  |         max_pain: Decimal = Decimal(0) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         intrinsic_values: dict[str, dict[str, Decimal]] = {} | 
					
						
							|  |  |  |         closes: list[str] = sorted(oi_by_strikes.keys()) | 
					
						
							|  |  |  |         for strike in oi_by_strikes: | 
					
						
							|  |  |  |             s: Decimal = Decimal(f'{strike}') | 
					
						
							|  |  |  |             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[strike] = { | 
					
						
							|  |  |  |                 'C': call_cash, | 
					
						
							|  |  |  |                 'P': put_cash, | 
					
						
							|  |  |  |                 'total': call_cash + put_cash, | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         for strike in intrinsic_values: | 
					
						
							|  |  |  |             if intrinsic_values[f'{strike}']['total'] < total_intrinsic_value: | 
					
						
							|  |  |  |                 total_intrinsic_value = intrinsic_values[f'{strike}']['total'] | 
					
						
							|  |  |  |                 max_pain = strike\ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return { | 
					
						
							|  |  |  |             'timestamp': timestamp, | 
					
						
							|  |  |  |             'expiry_date': expiry_date, | 
					
						
							|  |  |  |             'total_intrinsic_value': total_intrinsic_value, | 
					
						
							|  |  |  |             'max_pain': max_pain, | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                  | 
					
						
							|  |  |  |     async with get_client( | 
					
						
							|  |  |  |     ) as client: | 
					
						
							|  |  |  |         instruments = await client.get_instruments( | 
					
						
							|  |  |  |             expiry_date=expiry_date, | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  |         oi_by_strikes = client.get_strikes_dict(instruments) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     async with maybe_open_oi_feed( | 
					
						
							|  |  |  |         instruments, | 
					
						
							|  |  |  |     ) as oi_feed: | 
					
						
							|  |  |  |         async for msg in oi_feed: | 
					
						
							|  |  |  |             if 'oi' == msg[0]: | 
					
						
							|  |  |  |                 timestamp = msg[1]['timestamp'] | 
					
						
							|  |  |  |                 strike_price = msg[1]['strike_price'] | 
					
						
							|  |  |  |                 option_type = msg[1]['option_type'] | 
					
						
							|  |  |  |                 open_interest = msg[1]['open_interest'] | 
					
						
							|  |  |  |                 oi_by_strikes[f'{strike_price}'][f'{option_type}'] = open_interest | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             if check_if_complete(oi_by_strikes): | 
					
						
							|  |  |  |                 max_pain = get_max_pain(oi_by_strikes) | 
					
						
							|  |  |  |                 print('-----------------------------------------------') | 
					
						
							|  |  |  |                 print(f'timestamp:             {max_pain['timestamp']}') | 
					
						
							|  |  |  |                 print(f'expiry_date:           {max_pain['expiry_date']}') | 
					
						
							|  |  |  |                 print(f'max_pain:              {max_pain['max_pain']}') | 
					
						
							|  |  |  |                 print(f'total intrinsic value: {max_pain['total_intrinsic_value']}') | 
					
						
							|  |  |  |                 print('-----------------------------------------------') | 
					
						
							| 
									
										
										
										
											2024-11-26 18:16:21 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | async def main(): | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     async with tractor.open_nursery() as n: | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |         p: tractor.Portal = await n.start_actor( | 
					
						
							|  |  |  |             'max_pain_daemon', | 
					
						
							|  |  |  |             enable_modules=[__name__], | 
					
						
							|  |  |  |             infect_asyncio=True, | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  |         await p.run(max_pain_daemon) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | if __name__ == '__main__': | 
					
						
							|  |  |  |     trio.run(main) |