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