Adjust `.ui` modules to new set-style "optional" annots

log_linearized_curve_overlays
Tyler Goodlet 2023-02-21 09:14:26 -05:00
parent 54ecb0990f
commit 753e991dae
21 changed files with 127 additions and 130 deletions

View File

@ -18,7 +18,7 @@
Annotations for ur faces.
"""
from typing import Callable, Optional
from typing import Callable
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import QPointF, QRectF
@ -105,7 +105,7 @@ class LevelMarker(QGraphicsPathItem):
get_level: Callable[..., float],
size: float = 20,
keep_in_view: bool = True,
on_paint: Optional[Callable] = None,
on_paint: Callable | None = None,
) -> None:

View File

@ -20,7 +20,7 @@ Chart axes graphics and behavior.
"""
from __future__ import annotations
from functools import lru_cache
from typing import Optional, Callable
from typing import Callable
from math import floor
import numpy as np
@ -60,7 +60,8 @@ class Axis(pg.AxisItem):
**kwargs
)
# XXX: pretty sure this makes things slower
# XXX: pretty sure this makes things slower!
# no idea why given we only move labels for the most part?
# self.setCacheMode(QtWidgets.QGraphicsItem.DeviceCoordinateCache)
self.pi = plotitem
@ -190,7 +191,7 @@ class PriceAxis(Axis):
*args,
min_tick: int = 2,
title: str = '',
formatter: Optional[Callable[[float], str]] = None,
formatter: Callable[[float], str] | None = None,
**kwargs
) -> None:
@ -202,8 +203,8 @@ class PriceAxis(Axis):
def set_title(
self,
title: str,
view: Optional[ChartView] = None,
color: Optional[str] = None,
view: ChartView | None = None,
color: str | None = None,
) -> Label:
'''

View File

@ -21,7 +21,6 @@ High level chart-widget apis.
from __future__ import annotations
from typing import (
Iterator,
Optional,
TYPE_CHECKING,
)
@ -102,7 +101,7 @@ class GodWidget(QWidget):
super().__init__(parent)
self.search: Optional[SearchWidget] = None
self.search: SearchWidget | None = None
self.hbox = QHBoxLayout(self)
self.hbox.setContentsMargins(0, 0, 0, 0)
@ -121,9 +120,9 @@ class GodWidget(QWidget):
tuple[LinkedSplits, LinkedSplits],
] = {}
self.hist_linked: Optional[LinkedSplits] = None
self.rt_linked: Optional[LinkedSplits] = None
self._active_cursor: Optional[Cursor] = None
self.hist_linked: LinkedSplits | None = None
self.rt_linked: LinkedSplits | None = None
self._active_cursor: Cursor | None = None
# assigned in the startup func `_async_main()`
self._root_n: trio.Nursery = None
@ -367,7 +366,7 @@ class ChartnPane(QFrame):
'''
sidepane: FieldsForm | SearchWidget
hbox: QHBoxLayout
chart: Optional[ChartPlotWidget] = None
chart: ChartPlotWidget | None = None
def __init__(
self,
@ -445,7 +444,7 @@ class LinkedSplits(QWidget):
# chart-local graphics state that can be passed to
# a ``graphic_update_cycle()`` call by any task wishing to
# update the UI for a given "chart instance".
self.display_state: Optional[DisplayState] = None
self.display_state: DisplayState | None = None
self._symbol: Symbol = None
@ -475,7 +474,7 @@ class LinkedSplits(QWidget):
def set_split_sizes(
self,
prop: Optional[float] = None,
prop: float | None = None,
) -> None:
'''
@ -569,11 +568,11 @@ class LinkedSplits(QWidget):
shm: ShmArray,
flume: Flume,
array_key: Optional[str] = None,
array_key: str | None = None,
style: str = 'line',
_is_main: bool = False,
sidepane: Optional[QWidget] = None,
sidepane: QWidget | None = None,
draw_kwargs: dict = {},
**cpw_kwargs,
@ -789,7 +788,7 @@ class LinkedSplits(QWidget):
def resize_sidepanes(
self,
from_linked: Optional[LinkedSplits] = None,
from_linked: LinkedSplits | None = None,
) -> None:
'''
@ -857,7 +856,7 @@ class ChartPlotWidget(pg.PlotWidget):
# TODO: load from config
use_open_gl: bool = False,
static_yrange: Optional[tuple[float, float]] = None,
static_yrange: tuple[float, float] | None = None,
parent=None,
**kwargs,
@ -872,7 +871,7 @@ class ChartPlotWidget(pg.PlotWidget):
# NOTE: must be set bfore calling ``.mk_vb()``
self.linked = linkedsplits
self.sidepane: Optional[FieldsForm] = None
self.sidepane: FieldsForm | None = None
# source of our custom interactions
self.cv = self.mk_vb(name)
@ -1035,7 +1034,7 @@ class ChartPlotWidget(pg.PlotWidget):
def increment_view(
self,
datums: int = 1,
vb: Optional[ChartView] = None,
vb: ChartView | None = None,
) -> None:
'''
@ -1066,8 +1065,8 @@ class ChartPlotWidget(pg.PlotWidget):
def overlay_plotitem(
self,
name: str,
index: Optional[int] = None,
axis_title: Optional[str] = None,
index: int | None = None,
axis_title: str | None = None,
axis_side: str = 'right',
axis_kwargs: dict = {},
@ -1140,11 +1139,11 @@ class ChartPlotWidget(pg.PlotWidget):
shm: ShmArray,
flume: Flume,
array_key: Optional[str] = None,
array_key: str | None = None,
overlay: bool = False,
color: Optional[str] = None,
color: str | None = None,
add_label: bool = True,
pi: Optional[pg.PlotItem] = None,
pi: pg.PlotItem | None = None,
step_mode: bool = False,
is_ohlc: bool = False,
add_sticky: None | str = 'right',
@ -1277,7 +1276,7 @@ class ChartPlotWidget(pg.PlotWidget):
shm: ShmArray,
flume: Flume,
array_key: Optional[str] = None,
array_key: str | None = None,
**draw_curve_kwargs,
) -> Viz:
@ -1308,10 +1307,10 @@ class ChartPlotWidget(pg.PlotWidget):
def maxmin(
self,
name: Optional[str] = None,
bars_range: Optional[tuple[
name: str | None = None,
bars_range: tuple[
int, int, int, int, int, int
]] = None,
] | None = None,
) -> tuple[float, float]:
'''

View File

@ -21,7 +21,6 @@ Mouse interaction graphics
from __future__ import annotations
from functools import partial
from typing import (
Optional,
Callable,
TYPE_CHECKING,
)
@ -38,7 +37,10 @@ from ._style import (
_font_small,
_font,
)
from ._axes import YAxisLabel, XAxisLabel
from ._axes import (
YAxisLabel,
XAxisLabel,
)
from ..log import get_logger
if TYPE_CHECKING:
@ -167,7 +169,7 @@ class ContentsLabel(pg.LabelItem):
anchor_at: str = ('top', 'right'),
justify_text: str = 'left',
font_size: Optional[int] = None,
font_size: int | None = None,
) -> None:
@ -338,7 +340,7 @@ class Cursor(pg.GraphicsObject):
self.linked = linkedsplits
self.graphics: dict[str, pg.GraphicsObject] = {}
self.xaxis_label: Optional[XAxisLabel] = None
self.xaxis_label: XAxisLabel | None = None
self.always_show_xlabel: bool = True
self.plots: list['PlotChartWidget'] = [] # type: ignore # noqa
self.active_plot = None

View File

@ -19,7 +19,7 @@ Fast, smooth, sexy curves.
"""
from contextlib import contextmanager as cm
from typing import Optional, Callable
from typing import Callable
import numpy as np
import pyqtgraph as pg
@ -86,7 +86,7 @@ class FlowGraphic(pg.GraphicsObject):
# line styling
color: str = 'bracket',
last_step_color: str | None = None,
fill_color: Optional[str] = None,
fill_color: str | None = None,
style: str = 'solid',
**kwargs
@ -191,14 +191,14 @@ class Curve(FlowGraphic):
'''
# TODO: can we remove this?
# sub_br: Optional[Callable] = None
# sub_br: Callable | None = None
def __init__(
self,
*args,
# color: str = 'default_lightest',
# fill_color: Optional[str] = None,
# fill_color: str | None = None,
# style: str = 'solid',
**kwargs

View File

@ -25,7 +25,6 @@ from math import (
floor,
)
from typing import (
Optional,
Literal,
TYPE_CHECKING,
)
@ -249,7 +248,7 @@ class Viz(msgspec.Struct): # , frozen=True):
# in some cases a viz may want to change its
# graphical "type" or, "form" when downsampling, to
# start this is only ever an interpolation line.
ds_graphics: Optional[Curve] = None
ds_graphics: Curve | None = None
is_ohlc: bool = False
render: bool = True # toggle for display loop
@ -576,7 +575,7 @@ class Viz(msgspec.Struct): # , frozen=True):
def read(
self,
array_field: Optional[str] = None,
array_field: str | None = None,
index_field: str | None = None,
profiler: None | Profiler = None,

View File

@ -26,7 +26,6 @@ import itertools
from math import floor
import time
from typing import (
Optional,
Any,
TYPE_CHECKING,
)
@ -205,8 +204,8 @@ class DisplayState(Struct):
globalz: None | dict[str, Any] = None
vlm_chart: Optional[ChartPlotWidget] = None
vlm_sticky: Optional[YAxisLabel] = None
vlm_chart: ChartPlotWidget | None = None
vlm_sticky: YAxisLabel | None = None
wap_in_history: bool = False
@ -494,7 +493,7 @@ def graphics_update_cycle(
wap_in_history: bool = False,
trigger_all: bool = False, # flag used by prepend history updates
prepend_update_index: Optional[int] = None,
prepend_update_index: int | None = None,
) -> None:

View File

@ -21,7 +21,6 @@ Higher level annotation editors.
from __future__ import annotations
from collections import defaultdict
from typing import (
Optional,
TYPE_CHECKING
)
@ -67,7 +66,7 @@ class ArrowEditor(Struct):
x: float,
y: float,
color='default',
pointing: Optional[str] = None,
pointing: str | None = None,
) -> pg.ArrowItem:
'''
@ -221,7 +220,7 @@ class LineEditor(Struct):
line: LevelLine = None,
uuid: str = None,
) -> Optional[LevelLine]:
) -> LevelLine | None:
'''Remove a line by refernce or uuid.
If no lines or ids are provided remove all lines under the

View File

@ -23,7 +23,9 @@ from contextlib import asynccontextmanager
from functools import partial
from math import floor
from typing import (
Optional, Any, Callable, Awaitable
Any,
Callable,
Awaitable,
)
import trio
@ -263,7 +265,7 @@ class Selection(QComboBox):
def set_icon(
self,
key: str,
icon_name: Optional[str],
icon_name: str | None,
) -> None:
self.setItemIcon(
@ -344,7 +346,7 @@ class FieldsForm(QWidget):
name: str,
font_size: Optional[int] = None,
font_size: int | None = None,
font_color: str = 'default_lightest',
) -> QtGui.QLabel:
@ -469,7 +471,7 @@ def mk_form(
parent: QWidget,
fields_schema: dict,
font_size: Optional[int] = None,
font_size: int | None = None,
) -> FieldsForm:
@ -628,7 +630,7 @@ def mk_fill_status_bar(
parent_pane: QWidget,
form: FieldsForm,
pane_vbox: QVBoxLayout,
label_font_size: Optional[int] = None,
label_font_size: int | None = None,
) -> (
# TODO: turn this into a composite?
@ -738,7 +740,7 @@ def mk_fill_status_bar(
def mk_order_pane_layout(
parent: QWidget,
# accounts: dict[str, Optional[str]],
# accounts: dict[str, str | None],
) -> FieldsForm:

View File

@ -24,7 +24,10 @@ from contextlib import asynccontextmanager as acm
from functools import partial
import inspect
from itertools import cycle
from typing import Optional, AsyncGenerator, Any
from typing import (
AsyncGenerator,
Any,
)
import numpy as np
import msgspec
@ -80,7 +83,7 @@ def has_vlm(ohlcv: ShmArray) -> bool:
def update_fsp_chart(
viz,
graphics_name: str,
array_key: Optional[str],
array_key: str | None,
**kwargs,
) -> None:
@ -476,7 +479,7 @@ class FspAdmin:
target: Fsp,
conf: dict[str, dict[str, Any]],
worker_name: Optional[str] = None,
worker_name: str | None = None,
loglevel: str = 'info',
) -> (Flume, trio.Event):

View File

@ -26,7 +26,6 @@ from math import (
import time
from typing import (
Any,
Optional,
Callable,
TYPE_CHECKING,
)
@ -93,7 +92,7 @@ async def handle_viewmode_kb_inputs(
last = time.time()
action: str
on_next_release: Optional[Callable] = None
on_next_release: Callable | None = None
# for quick key sequence-combo pattern matching
# we have a min_tap period and these should not
@ -379,7 +378,7 @@ class ChartView(ViewBox):
name: str,
parent: pg.PlotItem = None,
static_yrange: Optional[tuple[float, float]] = None,
static_yrange: tuple[float, float] | None = None,
**kwargs,
):
@ -595,7 +594,7 @@ class ChartView(ViewBox):
def mouseDragEvent(
self,
ev,
axis: Optional[int] = None,
axis: int | None = None,
) -> None:
pos = ev.pos()
@ -753,19 +752,19 @@ class ChartView(ViewBox):
self,
*,
yrange: Optional[tuple[float, float]] = None,
yrange: tuple[float, float] | None = None,
viz: Viz | None = None,
# NOTE: this value pairs (more or less) with L1 label text
# height offset from from the bid/ask lines.
range_margin: float | None = 0.09,
bars_range: Optional[tuple[int, int, int, int]] = None,
bars_range: tuple[int, int, int, int] | None = None,
# flag to prevent triggering sibling charts from the same linked
# set from recursion errors.
autoscale_linked_plots: bool = False,
name: Optional[str] = None,
name: str | None = None,
) -> None:
'''
@ -871,7 +870,7 @@ class ChartView(ViewBox):
def enable_auto_yrange(
self,
viz: Viz,
src_vb: Optional[ChartView] = None,
src_vb: ChartView | None = None,
) -> None:
'''

View File

@ -19,7 +19,10 @@ Non-shitty labels that don't re-invent the wheel.
"""
from inspect import isfunction
from typing import Callable, Optional, Any
from typing import (
Callable,
Any,
)
import pyqtgraph as pg
from PyQt5 import QtGui, QtWidgets
@ -70,9 +73,7 @@ class Label:
self._fmt_str = fmt_str
self._view_xy = QPointF(0, 0)
self.scene_anchor: Optional[
Callable[..., QPointF]
] = None
self.scene_anchor: Callable[..., QPointF] | None = None
self._x_offset = x_offset
@ -164,7 +165,7 @@ class Label:
self,
y: float,
x: Optional[float] = None,
x: float | None = None,
) -> None:

View File

@ -22,7 +22,6 @@ from __future__ import annotations
from functools import partial
from math import floor
from typing import (
Optional,
Callable,
TYPE_CHECKING,
)
@ -32,7 +31,7 @@ from pyqtgraph import Point, functions as fn
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import QPointF
from ._annotate import qgo_draw_markers, LevelMarker
from ._annotate import LevelMarker
from ._anchors import (
vbr_left,
right_axis,
@ -295,7 +294,7 @@ class LevelLine(pg.InfiniteLine):
# show y-crosshair again
cursor.show_xhair()
def get_cursor(self) -> Optional[Cursor]:
def get_cursor(self) -> Cursor | None:
chart = self._chart
cur = chart.linked.cursor
@ -610,11 +609,11 @@ def order_line(
chart,
level: float,
action: Optional[str] = 'buy', # buy or sell
action: str | None = 'buy', # buy or sell
marker_style: Optional[str] = None,
level_digits: Optional[float] = 3,
size: Optional[int] = 1,
marker_style: str | None = None,
level_digits: float | None = 3,
size: int | None = 1,
size_digits: int = 1,
show_markers: bool = False,
submit_price: float = None,

View File

@ -21,7 +21,6 @@ Notifications utils.
import os
import platform
import subprocess
from typing import Optional
import trio
@ -33,7 +32,7 @@ from ..clearing._messages import (
log = get_logger(__name__)
_dbus_uid: Optional[str] = ''
_dbus_uid: str | None = ''
async def notify_from_ems_status_msg(

View File

@ -20,8 +20,9 @@ micro-ORM for coupling ``pydantic`` models with Qt input/output widgets.
"""
from __future__ import annotations
from typing import (
Optional, Generic,
TypeVar, Callable,
Generic,
TypeVar,
Callable,
)
# from pydantic import BaseModel, validator
@ -42,13 +43,11 @@ DataType = TypeVar('DataType')
class Field(GenericModel, Generic[DataType]):
widget_factory: Optional[
Callable[
widget_factory: Callable[
[QWidget, 'Field'],
QWidget
]
]
value: Optional[DataType] = None
] | None = None
value: DataType | None = None
class Selection(Field[DataType], Generic[DataType]):

View File

@ -22,8 +22,6 @@ Generally, our does not require "scentific precision" for pixel perfect
view transforms.
"""
from typing import Optional
import pyqtgraph as pg
from ._axes import Axis
@ -47,9 +45,10 @@ def invertQTransform(tr):
def _do_overrides() -> None:
"""Dooo eeet.
'''
Dooo eeet.
"""
'''
# we don't care about potential fp issues inside Qt
pg.functions.invertQTransform = invertQTransform
pg.PlotItem = PlotItem
@ -119,7 +118,7 @@ class PlotItem(pg.PlotItem):
name: str,
unlink: bool = True,
) -> Optional[pg.AxisItem]:
) -> pg.AxisItem | None:
"""
Remove an axis from the contained axis items
by ```name: str```.
@ -169,14 +168,14 @@ class PlotItem(pg.PlotItem):
def setAxisItems(
self,
# XXX: yeah yeah, i know we can't use type annots like this yet.
axisItems: Optional[dict[str, pg.AxisItem]] = None,
axisItems: dict[str, pg.AxisItem] | None = None,
add_to_layout: bool = True,
default_axes: list[str] = ['left', 'bottom'],
):
"""
Override axis item setting to only
'''
Override axis item setting to only what is passed in.
"""
'''
axisItems = axisItems or {}
# XXX: wth is is this even saying?!?

View File

@ -25,7 +25,6 @@ from functools import partial
from math import floor, copysign
from typing import (
Callable,
Optional,
TYPE_CHECKING,
)
@ -170,12 +169,12 @@ class SettingsPane:
limit_label: QLabel
# encompasing high level namespace
order_mode: Optional['OrderMode'] = None # typing: ignore # noqa
order_mode: OrderMode | None = None # typing: ignore # noqa
def set_accounts(
self,
names: list[str],
sizes: Optional[list[float]] = None,
sizes: list[float] | None = None,
) -> None:
combo = self.form.fields['account']
@ -540,8 +539,8 @@ class Nav(Struct):
charts: dict[int, ChartPlotWidget]
pp_labels: dict[str, Label] = {}
size_labels: dict[str, Label] = {}
lines: dict[str, Optional[LevelLine]] = {}
level_markers: dict[str, Optional[LevelMarker]] = {}
lines: dict[str, LevelLine | None] = {}
level_markers: dict[str, LevelMarker | None] = {}
color: str = 'default_lightest'
def update_ui(
@ -550,7 +549,7 @@ class Nav(Struct):
price: float,
size: float,
slots_used: float,
size_digits: Optional[int] = None,
size_digits: int | None = None,
) -> None:
'''
@ -847,7 +846,7 @@ class PositionTracker:
def update_from_pp(
self,
position: Optional[Position] = None,
position: Position | None = None,
set_as_startup: bool = False,
) -> None:

View File

@ -35,7 +35,6 @@ from collections import defaultdict
from contextlib import asynccontextmanager
from functools import partial
from typing import (
Optional,
Callable,
Awaitable,
Sequence,
@ -178,8 +177,8 @@ class CompleterView(QTreeView):
def resize_to_results(
self,
w: Optional[float] = 0,
h: Optional[float] = None,
w: float | None = 0,
h: float | None = None,
) -> None:
model = self.model()
@ -380,7 +379,7 @@ class CompleterView(QTreeView):
self,
section: str,
) -> Optional[QModelIndex]:
) -> QModelIndex | None:
'''
Find the *first* depth = 1 section matching ``section`` in
the tree and return its index.
@ -504,7 +503,7 @@ class CompleterView(QTreeView):
def show_matches(
self,
wh: Optional[tuple[float, float]] = None,
wh: tuple[float, float] | None = None,
) -> None:
@ -529,7 +528,7 @@ class SearchBar(Edit):
self,
parent: QWidget,
godwidget: QWidget,
view: Optional[CompleterView] = None,
view: CompleterView | None = None,
**kwargs,
) -> None:
@ -708,7 +707,7 @@ class SearchWidget(QtWidgets.QWidget):
self,
clear_to_cache: bool = True,
) -> Optional[str]:
) -> str | None:
'''
Attempt to load and switch the current selected
completion result to the affiliated chart app.
@ -1167,7 +1166,7 @@ async def register_symbol_search(
provider_name: str,
search_routine: Callable,
pause_period: Optional[float] = None,
pause_period: float | None = None,
) -> AsyncIterator[dict]:

View File

@ -18,7 +18,7 @@
Qt UI styling.
'''
from typing import Optional, Dict
from typing import Dict
import math
import pyqtgraph as pg
@ -52,7 +52,7 @@ class DpiAwareFont:
# TODO: move to config
name: str = 'Hack',
font_size: str = 'default',
# size_in_inches: Optional[float] = None,
) -> None:
self.name = name
self._qfont = QtGui.QFont(name)
@ -91,13 +91,14 @@ class DpiAwareFont:
def px_size(self) -> int:
return self._qfont.pixelSize()
def configure_to_dpi(self, screen: Optional[QtGui.QScreen] = None):
"""Set an appropriately sized font size depending on the screen DPI.
def configure_to_dpi(self, screen: QtGui.QScreen | None = None):
'''
Set an appropriately sized font size depending on the screen DPI.
If we end up needing to generalize this more here there are resources
listed in the script in ``snippets/qt_screen_info.py``.
"""
'''
if screen is None:
screen = self.screen

View File

@ -23,7 +23,6 @@ import signal
import time
from typing import (
Callable,
Optional,
Union,
)
import uuid
@ -64,9 +63,9 @@ class MultiStatus:
self,
msg: str,
final_msg: Optional[str] = None,
final_msg: str | None = None,
clear_on_next: bool = False,
group_key: Optional[Union[bool, str]] = False,
group_key: Union[bool, str] | None = False,
) -> Union[Callable[..., None], str]:
'''
@ -178,11 +177,11 @@ class MainWindow(QMainWindow):
self.setWindowTitle(self.title)
# set by runtime after `trio` is engaged.
self.godwidget: Optional[GodWidget] = None
self.godwidget: GodWidget | None = None
self._status_bar: QStatusBar = None
self._status_label: QLabel = None
self._size: Optional[tuple[int, int]] = None
self._size: tuple[int, int] | None = None
@property
def mode_label(self) -> QLabel:
@ -289,7 +288,7 @@ class MainWindow(QMainWindow):
def configure_to_desktop(
self,
size: Optional[tuple[int, int]] = None,
size: tuple[int, int] | None = None,
) -> None:
'''

View File

@ -25,7 +25,6 @@ from functools import partial
from pprint import pformat
import time
from typing import (
Optional,
Callable,
Any,
TYPE_CHECKING,
@ -129,7 +128,7 @@ class OrderMode:
trackers: dict[str, PositionTracker]
# switched state, the current position
current_pp: Optional[PositionTracker] = None
current_pp: PositionTracker | None = None
active: bool = False
name: str = 'order'
dialogs: dict[str, Dialog] = field(default_factory=dict)
@ -139,7 +138,7 @@ class OrderMode:
'buy': 'buy_green',
'sell': 'sell_red',
}
_staged_order: Optional[Order] = None
_staged_order: Order | None = None
def on_level_change_update_next_order_info(
self,
@ -180,7 +179,7 @@ class OrderMode:
def new_line_from_order(
self,
order: Order,
chart: Optional[ChartPlotWidget] = None,
chart: ChartPlotWidget | None = None,
**line_kwargs,
) -> LevelLine:
@ -340,7 +339,7 @@ class OrderMode:
def submit_order(
self,
send_msg: bool = True,
order: Optional[Order] = None,
order: Order | None = None,
) -> Dialog:
'''
@ -452,7 +451,7 @@ class OrderMode:
def on_submit(
self,
uuid: str,
order: Optional[Order] = None,
order: Order | None = None,
) -> Dialog:
'''
@ -496,7 +495,7 @@ class OrderMode:
price: float,
time_s: float,
pointing: Optional[str] = None,
pointing: str | None = None,
) -> None:
'''