diff --git a/examples/max_pain.py b/examples/max_pain.py index 4fd2d4f8..d64b08e6 100644 --- a/examples/max_pain.py +++ b/examples/max_pain.py @@ -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]] @@ -87,6 +92,65 @@ async def max_pain_daemon( 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() + + 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) + + for strike_str in sorted(intrinsic_values, key=lambda x: int(x)): + strike = int(strike_str) + 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) + + total_intrinsic_value, 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]: @@ -127,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) @@ -134,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():