Create and "EMS" module for order execution/routing actor(s)
							parent
							
								
									80d48e5ece
								
							
						
					
					
						commit
						a55d72f8d6
					
				|  | @ -0,0 +1,91 @@ | ||||||
|  | # piker: trading gear for hackers | ||||||
|  | # Copyright (C) 2018-present Tyler Goodlet (in stewardship for piker0) | ||||||
|  | 
 | ||||||
|  | # 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/>. | ||||||
|  | 
 | ||||||
|  | """ | ||||||
|  | In suit parlance: "Execution management systems" | ||||||
|  | 
 | ||||||
|  | """ | ||||||
|  | import trio | ||||||
|  | from trio_typing import TaskStatus | ||||||
|  | import tractor | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | _to_router: trio.abc.SendChannel = None | ||||||
|  | _from_ui: trio.abc.ReceiveChannel = None | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # TODO: make this a ``tractor.msg.pub`` | ||||||
|  | async def stream_orders(): | ||||||
|  |     """Order streaming task: deliver orders transmitted from UI | ||||||
|  |     to downstream consumers. | ||||||
|  | 
 | ||||||
|  |     This is run in the UI actor (usually the one running Qt). | ||||||
|  |     The UI simply delivers order messages to the above ``_to_router`` | ||||||
|  |     send channel (from sync code using ``.send_nowait()``), these values | ||||||
|  |     are pulled from the channel here and send to any consumer(s). | ||||||
|  | 
 | ||||||
|  |     """ | ||||||
|  |     global _from_ui | ||||||
|  | 
 | ||||||
|  |     async for order in _from_ui: | ||||||
|  |         yield order | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | async def stream_and_route(ui_name): | ||||||
|  |     """Order router (sub)actor entrypoint. | ||||||
|  | 
 | ||||||
|  |     """ | ||||||
|  |     actor = tractor.current_actor() | ||||||
|  | 
 | ||||||
|  |     # new router entry point | ||||||
|  |     async with tractor.wait_for_actor(ui_name) as portal: | ||||||
|  | 
 | ||||||
|  |         async for order in await portal.run(stream_orders): | ||||||
|  |             print(f'order {order} received in {actor.uid}') | ||||||
|  | 
 | ||||||
|  |             # push order back to parent as an "alert" | ||||||
|  |             # (mocking for eg. a "fill") | ||||||
|  |             yield order | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | async def spawn_router_stream_alerts( | ||||||
|  |     ident: str, | ||||||
|  |     task_status: TaskStatus[str] = trio.TASK_STATUS_IGNORED, | ||||||
|  | ) -> None: | ||||||
|  | 
 | ||||||
|  |     # setup local ui event streaming channels | ||||||
|  |     global _from_ui, _to_router | ||||||
|  |     _to_router, _from_ui = trio.open_memory_channel(100) | ||||||
|  | 
 | ||||||
|  |     actor = tractor.current_actor() | ||||||
|  |     subactor_name = ident + '.router' | ||||||
|  | 
 | ||||||
|  |     async with tractor.open_nursery() as n: | ||||||
|  | 
 | ||||||
|  |         portal = await n.start_actor( | ||||||
|  |             subactor_name, | ||||||
|  |             rpc_module_paths=[__name__], | ||||||
|  |         ) | ||||||
|  |         stream = await portal.run( | ||||||
|  |             stream_and_route, | ||||||
|  |             ui_name=actor.name | ||||||
|  |         ) | ||||||
|  |         async with tractor.wait_for_actor(subactor_name): | ||||||
|  |             # let parent task continue | ||||||
|  |             task_status.started(_to_router) | ||||||
|  | 
 | ||||||
|  |         async for alert in stream: | ||||||
|  |             print(f'alert {alert} received in {actor.uid}') | ||||||
|  | @ -16,6 +16,7 @@ | ||||||
| 
 | 
 | ||||||
| """ | """ | ||||||
| High level Qt chart widgets. | High level Qt chart widgets. | ||||||
|  | 
 | ||||||
| """ | """ | ||||||
| from typing import Tuple, Dict, Any, Optional, Callable | from typing import Tuple, Dict, Any, Optional, Callable | ||||||
| from functools import partial | from functools import partial | ||||||
|  | @ -25,7 +26,6 @@ import numpy as np | ||||||
| import pyqtgraph as pg | import pyqtgraph as pg | ||||||
| import tractor | import tractor | ||||||
| import trio | import trio | ||||||
| from trio_typing import TaskStatus |  | ||||||
| 
 | 
 | ||||||
| from ._axes import ( | from ._axes import ( | ||||||
|     DynamicDateAxis, |     DynamicDateAxis, | ||||||
|  | @ -59,6 +59,7 @@ from ..log import get_logger | ||||||
| from ._exec import run_qtractor, current_screen | from ._exec import run_qtractor, current_screen | ||||||
| from ._interaction import ChartView | from ._interaction import ChartView | ||||||
| from .. import fsp | from .. import fsp | ||||||
|  | from .._ems import spawn_router_stream_alerts, _to_router | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| log = get_logger(__name__) | log = get_logger(__name__) | ||||||
|  | @ -818,73 +819,6 @@ class ChartPlotWidget(pg.PlotWidget): | ||||||
|         self.scene().leaveEvent(ev) |         self.scene().leaveEvent(ev) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| _to_router: trio.abc.SendChannel = None |  | ||||||
| _from_ui: trio.abc.ReceiveChannel = None |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| # TODO: make this a ``tractor.msg.pub`` |  | ||||||
| async def stream_orders(): |  | ||||||
|     """Order streaming task: deliver orders transmitted from UI |  | ||||||
|     to downstream consumers. |  | ||||||
| 
 |  | ||||||
|     This is run in the UI actor (usually the one running Qt). |  | ||||||
|     The UI simply delivers order messages to the above ``_to_router`` |  | ||||||
|     send channel (from sync code using ``.send_nowait()``), these values |  | ||||||
|     are pulled from the channel here and send to any consumer(s). |  | ||||||
| 
 |  | ||||||
|     """ |  | ||||||
|     global _from_ui |  | ||||||
| 
 |  | ||||||
|     async for order in _from_ui: |  | ||||||
|         yield order |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| async def stream_and_route(ui_name): |  | ||||||
|     """Order router actor entrypoint. |  | ||||||
| 
 |  | ||||||
|     """ |  | ||||||
|     actor = tractor.current_actor() |  | ||||||
| 
 |  | ||||||
|     # new router entry point |  | ||||||
|     async with tractor.wait_for_actor(ui_name) as portal: |  | ||||||
|         async for order in await portal.run(stream_orders): |  | ||||||
|             print(f'order {order} received in {actor.uid}') |  | ||||||
| 
 |  | ||||||
|             # push order back to parent as an "alert" |  | ||||||
|             # (mocking for eg. a "fill") |  | ||||||
|             yield order |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| async def spawn_router_stream_alerts( |  | ||||||
|     ident: str, |  | ||||||
|     task_status: TaskStatus[str] = trio.TASK_STATUS_IGNORED, |  | ||||||
| ) -> None: |  | ||||||
| 
 |  | ||||||
|     # setup local ui event streaming channels |  | ||||||
|     global _from_ui, _to_router |  | ||||||
|     _to_router, _from_ui = trio.open_memory_channel(100) |  | ||||||
| 
 |  | ||||||
|     actor = tractor.current_actor() |  | ||||||
|     subactor_name = ident + '.router' |  | ||||||
| 
 |  | ||||||
|     async with tractor.open_nursery() as n: |  | ||||||
| 
 |  | ||||||
|         portal = await n.start_actor( |  | ||||||
|             subactor_name, |  | ||||||
|             rpc_module_paths=[__name__], |  | ||||||
|         ) |  | ||||||
|         stream = await portal.run( |  | ||||||
|             stream_and_route, |  | ||||||
|             ui_name=actor.name |  | ||||||
|         ) |  | ||||||
| 
 |  | ||||||
|         # let parent task continue |  | ||||||
|         task_status.started(subactor_name) |  | ||||||
| 
 |  | ||||||
|         async for alert in stream: |  | ||||||
|             print(f'alert {alert} received in {actor.uid}') |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| async def _async_main( | async def _async_main( | ||||||
|     sym: str, |     sym: str, | ||||||
|     brokername: str, |     brokername: str, | ||||||
|  | @ -970,16 +904,6 @@ async def _async_main( | ||||||
| 
 | 
 | ||||||
|         async with trio.open_nursery() as n: |         async with trio.open_nursery() as n: | ||||||
| 
 | 
 | ||||||
|             router_name = await n.start( |  | ||||||
|                 spawn_router_stream_alerts, |  | ||||||
|                 sym, |  | ||||||
|             ) |  | ||||||
| 
 |  | ||||||
|             # wait for router to come up before setting |  | ||||||
|             # enabling send channel on chart |  | ||||||
|             async with tractor.wait_for_actor(router_name): |  | ||||||
|                 global _to_router |  | ||||||
|                 linked_charts._to_router = _to_router |  | ||||||
| 
 | 
 | ||||||
|             # load initial fsp chain (otherwise known as "indicators") |             # load initial fsp chain (otherwise known as "indicators") | ||||||
|             n.start_soon( |             n.start_soon( | ||||||
|  | @ -1001,8 +925,18 @@ async def _async_main( | ||||||
|                 wap_in_history, |                 wap_in_history, | ||||||
|             ) |             ) | ||||||
| 
 | 
 | ||||||
|  |             router_send_chan = await n.start( | ||||||
|  |                 spawn_router_stream_alerts, | ||||||
|  |                 sym, | ||||||
|  |             ) | ||||||
|  | 
 | ||||||
|  |             # wait for router to come up before setting | ||||||
|  |             # enabling send channel on chart | ||||||
|  |             linked_charts._to_router = router_send_chan | ||||||
|  | 
 | ||||||
|             # wait for a first quote before we start any update tasks |             # wait for a first quote before we start any update tasks | ||||||
|             quote = await feed.receive() |             quote = await feed.receive() | ||||||
|  | 
 | ||||||
|             log.info(f'Received first quote {quote}') |             log.info(f'Received first quote {quote}') | ||||||
| 
 | 
 | ||||||
|             n.start_soon( |             n.start_soon( | ||||||
|  |  | ||||||
|  | @ -150,6 +150,6 @@ def chart(config, symbol, date, rate, test, profile): | ||||||
|         tractor_kwargs={ |         tractor_kwargs={ | ||||||
|             'debug_mode': True, |             'debug_mode': True, | ||||||
|             'loglevel': tractorloglevel, |             'loglevel': tractorloglevel, | ||||||
|             'rpc_module_paths': ['piker.ui._chart'], |             'rpc_module_paths': ['piker._ems'], | ||||||
|         }, |         }, | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue