"""Local execution broker for Flask Broker.""" import argparse from collections import namedtuple import struct import logging from eventlet import GreenPool from eventlet.green import zmq from pedlar.utils import calc_profit # Designed to run locally only if __name__ != "__main__": raise RuntimeError("Can only run as stand-alone script.") # Setup Arguments logger = logging.getLogger(__name__) parser = argparse.ArgumentParser(description=__doc__, fromfile_prefix_chars='@') parser.add_argument("-t", "--ticker", default="tcp://127.0.0.1:7000", help="Ticker URL") parser.add_argument("-b", "--broker_host", default="tcp://127.0.0.1:7100", help="Broker serve URL") parser.add_argument("-i", "--order_id", default=1, type=int, help="Initial order id") parser.add_argument("-l", "--leverage", default=100, type=int, help="Account leverage") ARGS = parser.parse_args() # Context are thread safe already, # we'll create one global one for all sockets context = zmq.Context() Order = namedtuple('Order', ['id', 'price', 'volume', 'type']) # Globals BID, ASK = 0.0, 0.0 ORDERS = dict() # Orders indexed using order id def handle_tick(): """Listen to incoming tick updates.""" socket = context.socket(zmq.SUB) # Set topic filter, this is a binary prefix # to check for each incoming message # set from server as uchar topic = X # We'll subsribe to only tick updates for now socket.setsockopt(zmq.SUBSCRIBE, bytes.fromhex('00')) logger.info("Connecting to ticker: %s", ARGS.ticker) socket.connect(ARGS.ticker) while True: raw = socket.recv() # unpack bytes https://docs.python.org/3/library/struct.html bid, ask = struct.unpack_from('dd', raw, 1) # offset topic logger.debug("Tick: %f %f", bid, ask) # We'll use global to pass tick data between green threads # since only 1 actually run at a time global BID, ASK # pylint: disable=global-statement BID, ASK = bid, ask # socket will be cleaned up at garbarge collection def handle_broker(): """Listen to incoming broker requests.""" socket = context.socket(zmq.REP) socket.bind(ARGS.broker_host) logger.info("Broker listening on: %s", ARGS.broker_host) nextid = ARGS.order_id while True: raw = socket.recv() # Prepare request: ulong order_id, double volume, uchar action order_id, volume, action = struct.unpack('LdB', raw) # Prepare response: ulong order_id, double price, double profit, uint retcode resp = (order_id, 0.0, 0.0, 1) # Assume failure if action == 1 and order_id in ORDERS: # Close order # BIG ASSUMPTION, account currency is the same as base currency # Ex. GBP account trading on GBPUSD since we don't have other # exchange rates streaming to us to handle conversion order = ORDERS.pop(order_id) closep, profit = calc_profit(order, BID, ASK, leverage=ARGS.leverage) resp = (order_id, closep, profit, 0) logger.info("CLOSING: %s", resp) elif action in (2, 3): # Buy - Sell oprice = ASK if action == 2 else BID order = Order(id=nextid, price=oprice, volume=volume, type="buy" if action == 2 else "sell") ORDERS[nextid] = order logger.info("ORDER: %s", order) resp = (nextid, oprice, 0.0, 0) nextid += 1 # Unknown action otherwise # Pack and send response resp = struct.pack('LddI', *resp) socket.send(resp) # Spawn green threads logging.basicConfig(level=logging.INFO) pool = GreenPool() try: pool.spawn_n(handle_tick) pool.spawn_n(handle_broker) pool.waitall() # Loops forever finally: # There might some orphan orders left over print("ORPHANS:", ORDERS)