class BacktestBroker(Broker): def __init__(self, hub: Hub) -> None: self.hub = hub self.publisher = Publisher(hub, prefix='broker') self.subscriber = Subscriber(hub, 'broker') self.subscriber.add_sync_listener(events.AnyNewTrade, self.log_trade_timestamp) self.orders = [] self.positions = [] self._timestamp = None def submit_order(self, order: Order) -> None: order.timestamp = self.get_timestamp() exchange = order.exchange self.publisher.publish([exchange.name, 'new-order'], order) self.orders.append(order) position = self.make_position(order) self.positions.append(position) position.open(order.price) def make_position(self, order: Order) -> Position: position = Position(order.market.quote, order.amount, timestamp=order.timestamp) self.publisher.publish([str(order.market), 'new-position'], position) return position def get_timestamp(self) -> datetime: if self._timestamp is not None: return self._timestamp else: return utc_now() def log_trade_timestamp(self, key: Key, trade: Trade) -> None: self._timestamp = trade.timestamp
class MultiStrategy: """ MutliStrategy is a base class for strategies that need to take in information from multiple markets. """ pairs = [] def __init__(self, hub: Hub, broker: Broker) -> None: self.subscriber = Subscriber(hub, 'multi_strategy') for pair in self.pairs: namespace = f"{pair[0]}-{pair[1]}" bar_key = Key('*', namespace, 'new-bar') self.subscriber.add_sync_listener(bar_key, self.store_new_bar) self.subscriber.add_sync_listener(bar_key, self.on_new_bar) self.broker = broker self.bars = {} def store_new_bar(self, key: Key, bar: Bar) -> None: namespace = key[1] if namespace not in self.bars: self.bars[namespace] = [] self.bars[namespace].append(bar) def on_new_bar(self, key: Key, bar: Bar) -> None: pass
class Strategy: """ MutliStrategy is a base class for strategies that need to operate on a single market. """ exchange = None market = None def __init__(self, hub: Hub, broker: Broker) -> None: self.subscriber = Subscriber(hub, 'strategy') bar_key = Key('*', f"{self.exchange}-{self.market}", 'new-bar') self.subscriber.add_sync_listener(bar_key, self.store_new_bar) self.subscriber.add_sync_listener(bar_key, self.on_new_bar) self.position_manager = PositionManager(hub, self.market) self.broker = broker self.bars = [] def __call__(self, bar: Bar): pass def store_new_bar(self, key: Key, bar: Bar) -> None: self.bars.append(bar) def on_new_bar(self, key: Key, bar: Bar) -> None: self.__call__(bar)
class ThresholdBarGenerator: """ ThresholdBarGenerator samples price statistics and generates a new bar when predefined threshold of given statistics has been reached. """ def __init__(self, hub: Hub, threshold: int): self.subscriber = Subscriber(hub, 'threshold_bars') self.subscriber.add_sync_listener(events.AnyNewTrade, self.on_new_trade) self.publisher = Publisher(hub, prefix='threshold_bars') self.threshold = threshold self.bar = None self.value = 0 def on_new_trade(self, key: Key, trade: Trade) -> None: if not self.bar: self.bar = Bar(trade) self.bar.append(trade) if self.value >= self.threshold: namespace = key[1] self._build_new_bar(trade, namespace) self.value += self.metric(trade) def _build_new_bar(self, trade: Trade, namespace: str) -> Bar: self.value = 0 key = [namespace, 'new-bar'] self.publisher.publish(key, self.bar) self.bar = Bar(trade) return self.bar
def __init__(self, hub: Hub, broker: Broker) -> None: self.subscriber = Subscriber(hub, 'strategy') bar_key = Key('*', f"{self.exchange}-{self.market}", 'new-bar') self.subscriber.add_sync_listener(bar_key, self.store_new_bar) self.subscriber.add_sync_listener(bar_key, self.on_new_bar) self.position_manager = PositionManager(hub, self.market) self.broker = broker self.bars = []
def __init__(self, hub: Hub, market: Market): self.subscriber = Subscriber(hub, 'PositionManager') new_position = Key('*', '*', 'new-position') self.subscriber.add_sync_listener(new_position, self.on_new_position) self.market = market self.position = None self.positions = [] self.closed_positions = []
def __init__(self, hub: Hub, threshold: int): self.subscriber = Subscriber(hub, 'threshold_bars') self.subscriber.add_sync_listener(events.AnyNewTrade, self.on_new_trade) self.publisher = Publisher(hub, prefix='threshold_bars') self.threshold = threshold self.bar = None self.value = 0
def __init__(self, hub: Hub, broker: Broker) -> None: self.subscriber = Subscriber(hub, 'multi_strategy') for pair in self.pairs: namespace = f"{pair[0]}-{pair[1]}" bar_key = Key('*', namespace, 'new-bar') self.subscriber.add_sync_listener(bar_key, self.store_new_bar) self.subscriber.add_sync_listener(bar_key, self.on_new_bar) self.broker = broker self.bars = {}
def __init__(self, hub: Hub) -> None: self.hub = hub self.publisher = Publisher(hub, prefix='broker') self.subscriber = Subscriber(hub, 'broker') self.subscriber.add_sync_listener(events.AnyNewTrade, self.log_trade_timestamp) self.orders = [] self.positions = [] self._timestamp = None
def __init__(self, hub: Hub, filename: str, dump_wait: bool = True) -> None: self.filename = filename self.subscriber = Subscriber(hub, 'csv_bar_writer') self.subscriber.add_sync_listener(events.AnyNewBar, self.on_new_bar) self.subscriber.add_sync_listener(events.AnyProcessingFinished, self.on_processing_finished) self.bars = [] self.dump_wait = dump_wait
def __init__(self, hub: Hub, initial_theta: int = 50) -> None: self.subscriber = Subscriber(hub, 'tick_imbalance_bar_generator') self.subscriber.add_sync_listener(events.AnyNewTrade, self.on_new_trade) self.publisher = Publisher(hub, prefix='tick_imbalance_bar_generator') self.bar = None self.last_trade = None self.b_ts = [] self.t_vals = [] self.theta = 0 self.expected_theta = initial_theta self.initial_T = 50
class TickImbalanceBarGenerator(ImbalanceBarGenerator): """ Tick imbalance bars (TIBs) are produced more frequently when there is informed trading (asymmetric information). Attributes: last_trade (Trade): the last observed trade b_ts (List[int]): the sign of the price change between ticks theta (int): current tick imbalance expected_theta (int): expected tick imbalance """ def __init__(self, hub: Hub, initial_theta: int = 50) -> None: self.subscriber = Subscriber(hub, 'tick_imbalance_bar_generator') self.subscriber.add_sync_listener(events.AnyNewTrade, self.on_new_trade) self.publisher = Publisher(hub, prefix='tick_imbalance_bar_generator') self.bar = None self.last_trade = None self.b_ts = [] self.t_vals = [] self.theta = 0 self.expected_theta = initial_theta self.initial_T = 50 def price_change(self, trade: Trade) -> int: if self.last_trade: return np.sign(trade.price - self.last_trade.price) else: return 0 def on_new_trade(self, key: Key, trade: Trade) -> None: if not self.bar: self.bar = Bar(trade) b_t = self.price_change(trade) self.b_ts.append(b_t) self.theta += b_t self.bar.append(trade) self.last_trade = trade if np.abs(self.theta) >= np.abs(self.expected_theta): self.t_vals.append(self.bar.count) window = 5 if len(self.t_vals) > window: expected_T = ema(self.t_vals, window)[-1] else: expected_T = self.t_vals[-1] namespace = key[1] self._build_new_bar(trade, namespace) self.theta = 0 logger.info(f"Expected T: {expected_T}") # print(ema(self.b_ts, 10)) probability = ema(self.b_ts, 10)[-1] self.expected_theta = expected_T * (2 * probability - 1) logger.info(self.expected_theta)
def test_tick_bar_generation_after_threshold(): hub = Hub() publisher = Publisher(hub, 'test-publisher') generator = TickBarGenerator(hub, threshold=3) subscriber = Subscriber(hub, 'test-subscriber') bars = [] def new_bar_handler(key, bar): bars.append(bar) subscriber.add_sync_listener(events.AnyNewBar, new_bar_handler) assert len(bars) == 0 for i in range(5): trade = Trade(12345, price=1.0, amount=1.0) publisher.publish(['test-BTCUSD', 'new-trade'], trade) assert len(bars) == 1
class PositionManager: """ PositionManager handles opening, closing and book-keeping of positions on the market. """ def __init__(self, hub: Hub, market: Market): self.subscriber = Subscriber(hub, 'PositionManager') new_position = Key('*', '*', 'new-position') self.subscriber.add_sync_listener(new_position, self.on_new_position) self.market = market self.position = None self.positions = [] self.closed_positions = [] @property def is_open(self) -> bool: """ Is any position open? """ return bool(self.position) def on_new_position(self, key: Key, position: Position) -> None: self.positions.append(position) self.position = position def close_position(self, price: float) -> None: if not self.position: raise Exception("Cannot close position when no position open!") self.position.close(price) self.closed_positions.append(self.position) self.position = None def total_realized_pnl(self) -> float: """ Returns realized PnL of all past (closed) positions. """ return np.sum([pos.realized_pnl for pos in self.closed_positions])
class CSVBarWriter: def __init__(self, hub: Hub, filename: str, dump_wait: bool = True) -> None: self.filename = filename self.subscriber = Subscriber(hub, 'csv_bar_writer') self.subscriber.add_sync_listener(events.AnyNewBar, self.on_new_bar) self.subscriber.add_sync_listener(events.AnyProcessingFinished, self.on_processing_finished) self.bars = [] self.dump_wait = dump_wait def on_new_bar(self, key: Key, bar: Bar) -> None: self.bars.append(bar.to_dict()) if not self.dump_wait: self.on_processing_finished(None, None) def on_processing_finished(self, key: Key, _: None) -> None: logger.info('Saving CSV file with bars') df = pd.DataFrame(self.bars) logger.info(df.head()) df.to_csv(self.filename)
def subscriber(hub): return Subscriber(hub, 'test-subscriber')