Add draft `pydantic`-`QWidget` ORM system
Move all the ``pydantic`` finagling to an `_orm.py` and just keep an `Allocator` as the backing model for our pp controls in the position module. This all needs to be tied together in some sane with with facility for multiple symbols/streams per chart for when we get to charting-trading aggregate feeds.fsp_feeds
							parent
							
								
									2d1deb7ab7
								
							
						
					
					
						commit
						c982634839
					
				|  | @ -0,0 +1,129 @@ | ||||||
|  | # piker: trading gear for hackers | ||||||
|  | # Copyright (C) Tyler Goodlet | ||||||
|  | 
 | ||||||
|  | # This program is free software: you can redistribute it and/or modify | ||||||
|  | # it under the terms of the GNU Affero General Public License as published by | ||||||
|  | # the Free Software Foundation, either version 3 of the License, or | ||||||
|  | # (at your option) any later version. | ||||||
|  | 
 | ||||||
|  | # This program is distributed in the hope that it will be useful, | ||||||
|  | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | # GNU Affero General Public License for more details. | ||||||
|  | 
 | ||||||
|  | # You should have received a copy of the GNU Affero General Public License | ||||||
|  | # along with this program.  If not, see <https://www.gnu.org/licenses/>. | ||||||
|  | 
 | ||||||
|  | """ | ||||||
|  | micro-ORM for coupling ``pydantic`` models with Qt input/output widgets. | ||||||
|  | 
 | ||||||
|  | """ | ||||||
|  | from __future__ import annotations | ||||||
|  | from typing import ( | ||||||
|  |     Optional, Generic, | ||||||
|  |     TypeVar, Callable, | ||||||
|  |     Literal, | ||||||
|  | ) | ||||||
|  | import enum | ||||||
|  | import sys | ||||||
|  | 
 | ||||||
|  | from pydantic import BaseModel, validator | ||||||
|  | from pydantic.generics import GenericModel | ||||||
|  | from PyQt5.QtWidgets import ( | ||||||
|  |     QWidget, | ||||||
|  |     QComboBox, | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | from ._forms import ( | ||||||
|  |     # FontScaledDelegate, | ||||||
|  |     FontAndChartAwareLineEdit, | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | DataType = TypeVar('DataType') | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Field(GenericModel, Generic[DataType]): | ||||||
|  |     widget_factory: Optional[ | ||||||
|  |         Callable[ | ||||||
|  |             [QWidget, 'Field'], | ||||||
|  |             QWidget | ||||||
|  |         ] | ||||||
|  |     ] | ||||||
|  |     value: Optional[DataType] = None | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Selection(Field[DataType], Generic[DataType]): | ||||||
|  |     '''Model which maps to a finite set of drop down entries declared as | ||||||
|  |     a ``dict[str, DataType]``. | ||||||
|  | 
 | ||||||
|  |     ''' | ||||||
|  |     widget_factory = QComboBox | ||||||
|  |     options: dict[str, DataType] | ||||||
|  |     # value: DataType = None | ||||||
|  | 
 | ||||||
|  |     @validator('value')  # , always=True) | ||||||
|  |     def set_value_first( | ||||||
|  |         cls, | ||||||
|  | 
 | ||||||
|  |         v: DataType, | ||||||
|  |         values: dict[str, DataType], | ||||||
|  | 
 | ||||||
|  |     ) -> DataType: | ||||||
|  |         '''If no initial value is set, use the first in | ||||||
|  |         the ``options`` dict. | ||||||
|  | 
 | ||||||
|  |         ''' | ||||||
|  |         # breakpoint() | ||||||
|  |         options = values['options'] | ||||||
|  |         if v is None: | ||||||
|  |             return next(options.values()) | ||||||
|  |         else: | ||||||
|  |             assert v in options, f'{v} is not in {options}' | ||||||
|  |             return v | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # class SizeUnit(Enum): | ||||||
|  | 
 | ||||||
|  | #     currency = '$ size' | ||||||
|  | #     percent_of_port = '% of port' | ||||||
|  | #     shares = '# shares' | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # class Weighter(str, Enum): | ||||||
|  | #     uniform = 'uniform' | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Edit(Field[DataType], Generic[DataType]): | ||||||
|  |     '''An edit field which takes a number. | ||||||
|  |     ''' | ||||||
|  |     widget_factory = FontAndChartAwareLineEdit | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class AllocatorPane(BaseModel): | ||||||
|  | 
 | ||||||
|  |     account = Selection[str]( | ||||||
|  |         options=dict.fromkeys( | ||||||
|  |             ['paper', 'ib.paper', 'ib.margin'], | ||||||
|  |             'paper', | ||||||
|  |         ), | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  |     allocate = Selection[str]( | ||||||
|  |         # options=list(Size), | ||||||
|  |         options={ | ||||||
|  |             '$ size': 'currency', | ||||||
|  |             '% of port': 'percent_of_port', | ||||||
|  |             '# shares': 'shares', | ||||||
|  |         }, | ||||||
|  |         # TODO: save/load from config and/or last session | ||||||
|  |         # value='currency' | ||||||
|  |     ) | ||||||
|  |     weight = Selection[str]( | ||||||
|  |         options={ | ||||||
|  |             'uniform': 'uniform', | ||||||
|  |         }, | ||||||
|  |         # value='uniform', | ||||||
|  |     ) | ||||||
|  |     size = Edit[float](value=1000) | ||||||
|  |     slots = Edit[int](value=4) | ||||||
|  | @ -18,12 +18,17 @@ | ||||||
| Position info and display | Position info and display | ||||||
| 
 | 
 | ||||||
| """ | """ | ||||||
| from typing import Optional | from __future__ import annotations | ||||||
|  | import enum | ||||||
| from functools import partial | from functools import partial | ||||||
| from math import floor | from math import floor | ||||||
|  | import sys | ||||||
|  | from typing import Optional | ||||||
| 
 | 
 | ||||||
|  | from bidict import bidict | ||||||
| from pyqtgraph import functions as fn | from pyqtgraph import functions as fn | ||||||
| from pydantic import BaseModel | from pydantic import BaseModel, validator | ||||||
|  | # from pydantic.generics import GenericModel | ||||||
| # from PyQt5.QtCore import QPointF | # from PyQt5.QtCore import QPointF | ||||||
| # from PyQt5.QtGui import QGraphicsPathItem | # from PyQt5.QtGui import QGraphicsPathItem | ||||||
| 
 | 
 | ||||||
|  | @ -56,6 +61,70 @@ class Position(BaseModel): | ||||||
|     fills: list[Status] = [] |     fills: list[Status] = [] | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | def mk_pp_alloc( | ||||||
|  | 
 | ||||||
|  |     accounts: dict[str, Optional[str]] = { | ||||||
|  |         'paper': None, | ||||||
|  |         'ib.paper': 'DU1435481', | ||||||
|  |         'ib.margin': 'U10983%', | ||||||
|  |     }, | ||||||
|  | 
 | ||||||
|  | ) -> Allocator:  # noqa | ||||||
|  | 
 | ||||||
|  |     # lol we have to do this module patching bc ``pydantic`` | ||||||
|  |     # needs types to exist at module level: | ||||||
|  |     # https://pydantic-docs.helpmanual.io/usage/postponed_annotations/ | ||||||
|  |     mod = sys.modules[__name__] | ||||||
|  | 
 | ||||||
|  |     accounts = bidict(accounts) | ||||||
|  |     Account = mod.Account = enum.Enum('Account', accounts) | ||||||
|  | 
 | ||||||
|  |     size_units = bidict({ | ||||||
|  |         '$ size': 'currency', | ||||||
|  |         '% of port': 'percent_of_port', | ||||||
|  |         '# shares': 'shares', | ||||||
|  |     }) | ||||||
|  |     SizeUnit = mod.SizeUnit = enum.Enum( | ||||||
|  |         'SizeUnit', | ||||||
|  |         size_units.inverse | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  |     class Allocator(BaseModel): | ||||||
|  | 
 | ||||||
|  |         account: Account = None | ||||||
|  |         _accounts: dict[str, Optional[str]] = accounts | ||||||
|  | 
 | ||||||
|  |         size_unit: SizeUnit = 'currency' | ||||||
|  |         _size_units: dict[str, Optional[str]] = size_units | ||||||
|  | 
 | ||||||
|  |         disti_weight: str = 'uniform' | ||||||
|  | 
 | ||||||
|  |         size: float | ||||||
|  |         slots: int | ||||||
|  | 
 | ||||||
|  |         _position: Position = None | ||||||
|  | 
 | ||||||
|  |         def get_order_info( | ||||||
|  |             self, | ||||||
|  |             price: float, | ||||||
|  | 
 | ||||||
|  |         ) -> dict: | ||||||
|  |             units, r = divmod( | ||||||
|  |                 round((self.size / self.slots)), | ||||||
|  |                 price, | ||||||
|  |             ) | ||||||
|  |             print(f'# shares: {units}, r: {r}') | ||||||
|  | 
 | ||||||
|  |     # Allocator.update_forward_refs() | ||||||
|  | 
 | ||||||
|  |     return Allocator( | ||||||
|  |         account=None, | ||||||
|  |         size_unit=size_units.inverse['currency'], | ||||||
|  |         size=2000, | ||||||
|  |         slots=4, | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| class PositionTracker: | class PositionTracker: | ||||||
|     '''Track and display a real-time position for a single symbol |     '''Track and display a real-time position for a single symbol | ||||||
|     on a chart. |     on a chart. | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue