max_pain_chart #23
			
				
			
		
		
		
	| 
						 | 
				
			
			@ -10,6 +10,11 @@ from piker.brokers.deribit.api import (
 | 
			
		|||
    get_client,
 | 
			
		||||
    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(
 | 
			
		||||
        oi: dict[str, dict[str, Decimal | None]]
 | 
			
		||||
| 
						 | 
				
			
			@ -49,6 +54,103 @@ async def max_pain_daemon(
 | 
			
		|||
        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):
 | 
			
		||||
        nonlocal oi_by_strikes
 | 
			
		||||
        if 'oi' == msg[0]:
 | 
			
		||||
| 
						 | 
				
			
			@ -74,30 +176,10 @@ async def max_pain_daemon(
 | 
			
		|||
        '''
 | 
			
		||||
 | 
			
		||||
        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():
 | 
			
		||||
            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 = get_total_intrinsic_values(oi_by_strikes)
 | 
			
		||||
 | 
			
		||||
            intrinsic_values[strike] = {
 | 
			
		||||
                '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
 | 
			
		||||
        total_intrinsic_value, max_pain = get_intrinsic_value_and_max_pain(intrinsic_values)
 | 
			
		||||
 | 
			
		||||
        return {
 | 
			
		||||
            'timestamp': timestamp,
 | 
			
		||||
| 
						 | 
				
			
			@ -109,6 +191,16 @@ async def max_pain_daemon(
 | 
			
		|||
    async with maybe_open_oi_feed(
 | 
			
		||||
        instruments,
 | 
			
		||||
    ) 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:
 | 
			
		||||
 | 
			
		||||
            update_oi_by_strikes(msg)
 | 
			
		||||
| 
						 | 
				
			
			@ -116,13 +208,21 @@ async def max_pain_daemon(
 | 
			
		|||
                if 'oi' == msg[0]:
 | 
			
		||||
                    timestamp = msg[1]['timestamp']
 | 
			
		||||
                    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(f'timestamp:             {datetime.fromtimestamp(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(f'max_pain:              {max_pain['max_pain']:,.0f}')
 | 
			
		||||
                    print(f'total intrinsic value: {max_pain['total_intrinsic_value']:,.0f}')
 | 
			
		||||
                    print('-----------------------------------------------')
 | 
			
		||||
 | 
			
		||||
            # Process GUI events to keep the window responsive
 | 
			
		||||
            app.processEvents()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def main():
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue