max_pain_chart #23
			
				
			
		
		
		
	| 
						 | 
					@ -10,6 +10,11 @@ from piker.brokers.deribit.api import (
 | 
				
			||||||
    get_client,
 | 
					    get_client,
 | 
				
			||||||
    maybe_open_oi_feed,
 | 
					    maybe_open_oi_feed,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					import sys
 | 
				
			||||||
 | 
					import pyqtgraph as pg
 | 
				
			||||||
 | 
					from PyQt6 import QtCore
 | 
				
			||||||
 | 
					from pyqtgraph import ScatterPlotItem, InfiniteLine
 | 
				
			||||||
 | 
					from PyQt6.QtWidgets import QApplication
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def check_if_complete(
 | 
					def check_if_complete(
 | 
				
			||||||
        oi: dict[str, dict[str, Decimal | None]]
 | 
					        oi: dict[str, dict[str, Decimal | None]]
 | 
				
			||||||
| 
						 | 
					@ -49,6 +54,103 @@ async def max_pain_daemon(
 | 
				
			||||||
        oi_by_strikes = client.get_strikes_dict(instruments)
 | 
					        oi_by_strikes = client.get_strikes_dict(instruments)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_total_intrinsic_values(
 | 
				
			||||||
 | 
					        oi_by_strikes: dict[str, dict[str, Decimal]]
 | 
				
			||||||
 | 
					    ) -> dict[str, dict[str, Decimal]]:
 | 
				
			||||||
 | 
					        call_cash: Decimal = Decimal(0)
 | 
				
			||||||
 | 
					        put_cash: Decimal = Decimal(0)
 | 
				
			||||||
 | 
					        intrinsic_values: dict[str, dict[str, Decimal]] = {}
 | 
				
			||||||
 | 
					        closes: list = sorted(Decimal(close) for close in oi_by_strikes)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for strike, oi in oi_by_strikes.items():
 | 
				
			||||||
 | 
					            s = Decimal(strike)
 | 
				
			||||||
 | 
					            call_cash = sum(max(0, (s - c) * oi_by_strikes[str(c)]['C']) for c in closes)
 | 
				
			||||||
 | 
					            put_cash = sum(max(0, (c - s) * oi_by_strikes[str(c)]['P']) for c in closes)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            intrinsic_values[strike] = {
 | 
				
			||||||
 | 
					                'C': call_cash,
 | 
				
			||||||
 | 
					                'P': put_cash,
 | 
				
			||||||
 | 
					                'total': call_cash + put_cash,
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return intrinsic_values
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_intrinsic_value_and_max_pain(
 | 
				
			||||||
 | 
					        intrinsic_values: dict[str, dict[str, Decimal]]
 | 
				
			||||||
 | 
					        ):
 | 
				
			||||||
 | 
					        # 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)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for strike, oi in oi_by_strikes.items():
 | 
				
			||||||
 | 
					            s = Decimal(strike)
 | 
				
			||||||
 | 
					            if intrinsic_values[strike]['total'] < total_intrinsic_value:
 | 
				
			||||||
 | 
					                total_intrinsic_value = intrinsic_values[strike]['total']
 | 
				
			||||||
 | 
					                max_pain = s
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return total_intrinsic_value, max_pain
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def plot_graph(
 | 
				
			||||||
 | 
					        oi_by_strikes: dict[str, dict[str, Decimal]],
 | 
				
			||||||
 | 
					        plot,
 | 
				
			||||||
 | 
					    ):
 | 
				
			||||||
 | 
					        """Update the bar graph with new open interest data."""
 | 
				
			||||||
 | 
					        plot.clear()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        intrinsic_values = get_total_intrinsic_values(oi_by_strikes)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for strike_str in sorted(oi_by_strikes, key=lambda x: int(x)):
 | 
				
			||||||
 | 
					            strike = int(strike_str)
 | 
				
			||||||
 | 
					            calls_val = float(oi_by_strikes[strike_str]['C'])
 | 
				
			||||||
 | 
					            puts_val  = float(oi_by_strikes[strike_str]['P'])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            bar_c = pg.BarGraphItem(
 | 
				
			||||||
 | 
					                x=[strike - 100],
 | 
				
			||||||
 | 
					                height=[calls_val],
 | 
				
			||||||
 | 
					                width=200,
 | 
				
			||||||
 | 
					                pen='w',
 | 
				
			||||||
 | 
					                brush=(0, 0, 255, 150)
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            plot.addItem(bar_c)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            bar_p = pg.BarGraphItem(
 | 
				
			||||||
 | 
					                x=[strike + 100],
 | 
				
			||||||
 | 
					                height=[puts_val],
 | 
				
			||||||
 | 
					                width=200,
 | 
				
			||||||
 | 
					                pen='w',
 | 
				
			||||||
 | 
					                brush=(255, 0, 0, 150)
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            plot.addItem(bar_p)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            total_val = float(intrinsic_values[strike_str]['total']) / 100000
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            scatter_iv = ScatterPlotItem(
 | 
				
			||||||
 | 
					                x=[strike],
 | 
				
			||||||
 | 
					                y=[total_val],
 | 
				
			||||||
 | 
					                pen=pg.mkPen(color=(0, 255, 0), width=2),
 | 
				
			||||||
 | 
					                brush=pg.mkBrush(0, 255, 0, 150),
 | 
				
			||||||
 | 
					                size=3,
 | 
				
			||||||
 | 
					                symbol='o'
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            plot.addItem(scatter_iv)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        _, max_pain = get_intrinsic_value_and_max_pain(intrinsic_values)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        vertical_line = InfiniteLine(
 | 
				
			||||||
 | 
					            pos=max_pain,
 | 
				
			||||||
 | 
					            angle=90,
 | 
				
			||||||
 | 
					            pen=pg.mkPen(color='yellow', width=1, style=QtCore.Qt.PenStyle.DotLine),
 | 
				
			||||||
 | 
					            label=f'Max pain: {max_pain:,.0f}',
 | 
				
			||||||
 | 
					            labelOpts={
 | 
				
			||||||
 | 
					                'position': 0.85,
 | 
				
			||||||
 | 
					                'color': 'yellow',
 | 
				
			||||||
 | 
					                'movable': True
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        plot.addItem(vertical_line)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def update_oi_by_strikes(msg: tuple):
 | 
					    def update_oi_by_strikes(msg: tuple):
 | 
				
			||||||
        nonlocal oi_by_strikes
 | 
					        nonlocal oi_by_strikes
 | 
				
			||||||
        if 'oi' == msg[0]:
 | 
					        if 'oi' == msg[0]:
 | 
				
			||||||
| 
						 | 
					@ -74,30 +176,10 @@ async def max_pain_daemon(
 | 
				
			||||||
        '''
 | 
					        '''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        nonlocal timestamp
 | 
					        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)
 | 
					 | 
				
			||||||
        call_cash: Decimal = Decimal(0)
 | 
					 | 
				
			||||||
        put_cash: Decimal = Decimal(0)
 | 
					 | 
				
			||||||
        intrinsic_values: dict[str, dict[str, Decimal]] = {}
 | 
					 | 
				
			||||||
        closes: list = sorted(Decimal(close) for close in oi_by_strikes)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        for strike, oi in oi_by_strikes.items():
 | 
					        intrinsic_values = get_total_intrinsic_values(oi_by_strikes)
 | 
				
			||||||
            s = Decimal(strike)
 | 
					 | 
				
			||||||
            call_cash = sum(max(0, (s - c) * oi_by_strikes[str(c)]['C']) for c in closes)
 | 
					 | 
				
			||||||
            put_cash = sum(max(0, (c - s) * oi_by_strikes[str(c)]['P']) for c in closes)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            intrinsic_values[strike] = {
 | 
					        total_intrinsic_value, max_pain = get_intrinsic_value_and_max_pain(intrinsic_values)
 | 
				
			||||||
                'C': call_cash,
 | 
					 | 
				
			||||||
                'P': put_cash,
 | 
					 | 
				
			||||||
                'total': call_cash + put_cash,
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if intrinsic_values[strike]['total'] < total_intrinsic_value:
 | 
					 | 
				
			||||||
                total_intrinsic_value = intrinsic_values[strike]['total']
 | 
					 | 
				
			||||||
                max_pain = s
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return {
 | 
					        return {
 | 
				
			||||||
            'timestamp': timestamp,
 | 
					            'timestamp': timestamp,
 | 
				
			||||||
| 
						 | 
					@ -109,6 +191,16 @@ async def max_pain_daemon(
 | 
				
			||||||
    async with maybe_open_oi_feed(
 | 
					    async with maybe_open_oi_feed(
 | 
				
			||||||
        instruments,
 | 
					        instruments,
 | 
				
			||||||
    ) as oi_feed:
 | 
					    ) as oi_feed:
 | 
				
			||||||
 | 
					        # Initialize QApplication
 | 
				
			||||||
 | 
					        app = QApplication(sys.argv)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        win = pg.GraphicsLayoutWidget(show=True)
 | 
				
			||||||
 | 
					        win.setWindowTitle('Calls (blue) vs Puts (red)')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        plot = win.addPlot(title='OI by Strikes')
 | 
				
			||||||
 | 
					        plot.showGrid(x=True, y=True)
 | 
				
			||||||
 | 
					        print('Plot initialized...')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        async for msg in oi_feed:
 | 
					        async for msg in oi_feed:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            update_oi_by_strikes(msg)
 | 
					            update_oi_by_strikes(msg)
 | 
				
			||||||
| 
						 | 
					@ -116,13 +208,21 @@ async def max_pain_daemon(
 | 
				
			||||||
                if 'oi' == msg[0]:
 | 
					                if 'oi' == msg[0]:
 | 
				
			||||||
                    timestamp = msg[1]['timestamp']
 | 
					                    timestamp = msg[1]['timestamp']
 | 
				
			||||||
                    max_pain = get_max_pain(oi_by_strikes)
 | 
					                    max_pain = get_max_pain(oi_by_strikes)
 | 
				
			||||||
 | 
					                    intrinsic_values = get_total_intrinsic_values(oi_by_strikes)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    # graph here
 | 
				
			||||||
 | 
					                    plot_graph(oi_by_strikes, plot)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    print('-----------------------------------------------')
 | 
					                    print('-----------------------------------------------')
 | 
				
			||||||
                    print(f'timestamp:             {datetime.fromtimestamp(max_pain['timestamp'])}')
 | 
					                    print(f'timestamp:             {datetime.fromtimestamp(max_pain['timestamp'])}')
 | 
				
			||||||
                    print(f'expiry_date:           {max_pain['expiry_date']}')
 | 
					                    print(f'expiry_date:           {max_pain['expiry_date']}')
 | 
				
			||||||
                    print(f'max_pain:              {max_pain['max_pain']}')
 | 
					                    print(f'max_pain:              {max_pain['max_pain']:,.0f}')
 | 
				
			||||||
                    print(f'total intrinsic value: {max_pain['total_intrinsic_value']}')
 | 
					                    print(f'total intrinsic value: {max_pain['total_intrinsic_value']:,.0f}')
 | 
				
			||||||
                    print('-----------------------------------------------')
 | 
					                    print('-----------------------------------------------')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # Process GUI events to keep the window responsive
 | 
				
			||||||
 | 
					            app.processEvents()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async def main():
 | 
					async def main():
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue