class QSM(Process, Dictable): def __init__(self, name: str, msg_list: list = None): super().__init__() self.is_exitting = False self.name = name self.mappings = {} self.setup_states() self.msg_list = msg_list if msg_list is not None else [] if 'all' not in self.msg_list: self.msg_list.append('all') self.setup_msg_mappings(self.msg_list) self.handler = MessageHandler(self.name, self.msg_list) # Because otherwise we can't join from 'final' self.states = Queue() self.append_state('init') @property def dict(self) -> dict: print('Converting qsm to dict') d = {'name': self.name, 'msg_list': self.msg_list, 'handler': self.handler.dict} return d @staticmethod def from_dict(d: dict) -> object: sm = QSM(d['name'], d['msg_list']) sm.handler = MessageHandler.from_dict(d['handler']) sm.append_state('init') return sm def join(self, timeout: Optional[float] = -1) -> None: self.append_state('exit') super().join(timeout=timeout) self.handler.join() def run(self) -> None: # sys.stdin = self.cclient while not self.is_exitting: try: s = self.states.get_nowait() # print('Going to {}'.format(s['state'])) if s['payload'] is not None: self.mappings[s['state']](s['payload']) else: self.mappings[s['state']]() except Empty as _: self.mappings['idle']() def setup_msg_mappings(self, msg_list: list): """Sets up the self.msg_map dictionary, mapping message titles to state names, by default, appends '_msg' to msg_list entries""" for m in msg_list: # print('Setting up message state for {} to {}'.format(m, eval('self.' + m + '_msg'))) self.mappings[m] = eval('self.' + m + '_msg') def all_msg(self, msg: Message): if msg.msg == 'save': self.handler.send(Message('json_update', self.name, {'dtype': str(type(self).__name__), 'package': inspect.getmodulename( inspect.getfile(self.__class__)), 'path': str(inspect.getfile(self.__class__)), 'data': self.dict})) def setup_states(self): """Sets up the self.mappings dictionary, mapping state names to state methods, by default 'init', 'idle', 'exit', and 'final' states are set up.""" self.mappings['init'] = self.initial_state self.mappings['idle'] = self.idle_state self.mappings['exit'] = self.exit_state self.mappings['final'] = self.final_state def append_state(self, state: str, payload: object = None): try: # print('Appending {}'.format(state)) self.states.put_nowait({'state': state, 'payload': payload}) except Full as _: print('{} state queue is full, skipping {}'.format(self.name, state)) def append_states(self, states: list, payloads: list = None): for i, s in enumerate(states): p = payloads[i] if payloads is not None else None self.append_state(s, p) def initial_state(self): """This is the first state to execute, always""" pass def idle_state(self): """This is the state the is triggered when the state machine has nowhere to go to.""" m = self.handler.receive(block=False) if m is not None: print('Received message {}'.format(m)) self.append_state(m.title, m) def exit_state(self): """This stateis triggered when the qsm should exit, enqueues the 'final' state""" # print('Exitting') self.append_state('final') def final_state(self): """This is the final state to execute, always""" self.is_exitting = True
time.sleep(self.interval) class MarketTimer(Timer): """A Normal Timer class that automatically pauses during after hours.""" def idle_state(self): dow = dt.datetime.now().isoweekday() hour = dt.datetime.now().hour minute = dt.datetime.now().minute # print('The date is {} @ {}:{}'.format(dow, hour, minute)) if not self.paused and ((dow == 6 or dow == 7) or ((hour >= 16 or hour < 9) or (hour == 9 and minute < 30))): print('Pausing Timer') self.paused = True elif self.paused and ((dow != 6 and dow != 7) and ((9 < hour < 16) or (hour == 9 and minute >= 30))): print('Unpausing Timer') self.paused = False super().idle_state() if __name__ == "__main__": from tradebot.messaging.message import MessageHandler t = Timer('timer1') h = MessageHandler('timer_rx', ['timer']) t.start() while True: if h.receive() is not None: print('Timer triggered')
from tradebot.messaging.qsm import QSM from tradebot.messaging.message import Message class TimerRelay(QSM): def __init__(self, name: str, target_msg: Message): super().__init__(name, ['timer']) self.target = target_msg def timer_msg(self, msg: Message): print('Relay {} triggered'.format(self.name)) self.handler.send(self.target) if __name__ == '__main__': from tradebot.controllers.timer import Timer from tradebot.messaging.message import MessageHandler t = Timer('timer1', 1) tr = TimerRelay('relay', Message('something')) rx = MessageHandler('receiver', ['something']) tr.start() t.start() while True: if rx.receive() is not None: print('Relay fired')
class CLITest(unittest.TestCase): def setUp(self) -> None: self.handler = MessageHandler('test_handler', ['trade_request', 'monitor_config', 'vault_request']) def tearDown(self) -> None: self.handler.join() cli.cli_handler = None def test_add(self): parse_command('add stock AAPL shares=10; add stock AAPL 1 3.0; add stock AAPL shares=10 price=4.50') msg = self.handler.receive() self.assertEqual(msg.title, 'vault_request', 'Add command should produce vault_request message') self.assertEqual(msg.msg, 'add_stock', 'Add command should produce an add_stock message') self.assertTrue(msg.payload == ManagedStock('AAPL', shares=10, last_price=-1)) msg = self.handler.receive() self.assertTrue(msg.payload == ManagedStock('AAPL', shares=1, last_price=3)) msg = self.handler.receive() self.assertTrue(msg.payload == ManagedStock('AAPL', shares=10, last_price=4.5)) def test_remove(self): parse_command('remove stock 123; remove stock 123 10') msg = self.handler.receive() self.assertEqual(msg.title, 'vault_request', 'Remove command should produce vault_request message') self.assertEqual(msg.msg, 'remove_stock', 'Remove command should produce a remove_stock message') self.assertTrue(msg.payload == ManagedStock('None', table_id=123, shares=-1)) msg = self.handler.receive() self.assertTrue(msg.payload == ManagedStock('None', table_id=123, shares=10)) def test_list(self): parse_command('list; list acronym AAPL') # msg = self.handler.receive() # self.assertEqual(msg.title, 'vault_request', 'List command should produce vault_request message') # self.assertEqual(msg.msg, 'get_stock_names', 'List command should produce a get_stock_names message') def test_limit(self): parse_command('add limit 123; add limit 123 % 1.05 0.95; add limit 123 0.95 0.85') msg = self.handler.receive() self.assertEqual(msg.title, 'monitor_config', 'List command should produce monitor_config message') self.assertEqual(msg.msg, 'limit', 'List command should produce a limit message') self.assertTrue(msg.payload == LimitDescriptor(123)) msg = self.handler.receive() self.assertTrue(msg.payload == LimitDescriptor(123)) msg = self.handler.receive() self.assertTrue(msg.payload == LimitDescriptor(123, upper=0.95, lower=0.85)) def test_buy(self): parse_command('buy AAPL; buy AAPL 10; buy AAPL 10 3.75') msg = self.handler.receive() self.assertEqual(msg.title, 'trade_request', 'Buy command should produce a trade_request message') self.assertEqual(msg.msg, 'buy', 'Buy command should produce a buy message') self.assertTrue(msg.payload == ManagedStock('AAPL', last_price=-1)) msg = self.handler.receive() self.assertTrue(msg.payload == ManagedStock('AAPL', shares=10, last_price=-1)) msg = self.handler.receive() self.assertTrue(msg.payload == ManagedStock('AAPL', shares=10, last_price=3.75)) def test_sell(self): parse_command('sell 123; sell 123 10') msg = self.handler.receive() self.assertEqual(msg.title, 'trade_request', 'Sell command should produce a trade_request message') self.assertEqual(msg.msg, 'sell', 'Sell command should produce a sell message') self.assertEqual(msg.payload['id'], 123) self.assertEqual(msg.payload['shares'], -1) msg = self.handler.receive() self.assertEqual(msg.payload['id'], 123) self.assertEqual(msg.payload['shares'], 10) def test_update(self): parse_command('update') msg = self.handler.receive() self.assertEqual(msg.title, 'monitor_config', 'Update command should produce a monitor_config message') self.assertEqual(msg.msg, 'update', 'Update command should produce an update message') def test_export(self): parse_command('export') msg = self.handler.receive() self.assertEqual(msg.title, 'all', 'Export command should produce a global message') self.assertEqual(msg.msg, 'save', 'Export command should produce an save message') def test_import(self): parse_command('import') msg = self.handler.receive() self.assertEqual(msg.title, 'all', 'Import command should produce a global message') self.assertEqual(msg.msg, 'load', 'Import command should produce an load message')