Beispiel #1
0
def test_update_portfolio_home(home_currency: str, pair: Pair, exec_price: str,
        units: str, equity: str, exp_balance: str, exp_upl: str,
        exp_equity: str, TickerMock1: Ticker, TickerMock2: Ticker) -> None:
    port = Portfolio(TickerMock1, Queue(), Queue(), home_currency,
        ["GBPUSD", "EURUSD", "USDJPY"], Decimal(equity))
    port.execute_fill(FillEvent(
        ref='ref123',
        pair=pair,
        time=pd.Timestamp('2020-07-08 21:56:00'),
        units=Decimal(units),
        price=Decimal(exec_price),
        status='filled'
    ))
    
    for _pair in ['USDJPY']:
        bid = TickerMock2.prices[_pair]['bid']
        ask = TickerMock2.prices[_pair]['ask']

        TickerMock1.prices[_pair]["bid"] = bid
        TickerMock1.prices[_pair]["ask"] = ask

        # Create decimalised prices for inverted pair
        inv_pair, inv_bid, inv_ask = TickerMock2.invert_prices(_pair, bid, ask)
        TickerMock1.prices[inv_pair]["bid"] = inv_bid
        TickerMock1.prices[inv_pair]["ask"] = inv_ask

    port.update_portfolio(TickEvent(pair, pd.Timestamp('2020-07-08 21:56:00'),
        Decimal('111'), Decimal('222')))
    
    assert port.balance == Decimal(exp_balance)
    assert port.upl == Decimal(exp_upl)
    assert port.equity == Decimal(exp_equity)
Beispiel #2
0
def test_update_ticker() -> None:
    pairs = ['GBPUSD', 'USDJPY']
    time = pd.Timestamp('2020-07-09 12:23:10')
    ticker = Ticker(pairs)

    # pair, ask, bid, GBPUSD(ask, bid), USDGBP(ask, bid),
    # USDJPY(ask, bid), JPYUSD(ask, bid)
    tick1 = [
        'GBPUSD', '1.50349', '1.30328', '1.50349', '1.30328', '0.76729483',
        '0.66511916', '0', '0', '0', '0'
    ]
    tick2 = [
        'USDJPY', '110.863', '105.774', '1.50349', '1.30328', '0.76729483',
        '0.66511916', '110.863', '105.774', '0.00945412', '0.00902014'
    ]
    tick3 = [
        'GBPUSD', '1.2543', '1.2541', '1.2543', '1.2541', '0.79738458',
        '0.79725743', '110.863', '105.774', '0.00945412', '0.00902014'
    ]
    tick4 = [
        'USDJPY', '107.8', '107.25', '1.2543', '1.2541', '0.79738458',
        '0.79725743', '107.8', '107.25', '0.00932401', '0.00927644'
    ]
    tick = [tick1, tick2, tick3, tick4]

    for t in tick:
        event = TickEvent(t[0], time, Decimal(t[2]), Decimal(t[1]))
        ticker.update_ticker(event)
        result = []
        for p in ['GBPUSD', 'USDGBP', 'USDJPY', 'JPYUSD']:
            for q in ['ask', 'bid']:
                result.append(ticker.prices[p][q])
        assert result == list(map(Decimal, t[3:]))
Beispiel #3
0
 def __init__(self, engine: engine_params, datafeed: datafeed_params,
              execution: execution_params, strategy: strategy_params,
              result: result_params):
     """
     Initializes the backtest.
     """
     self.logger = getLogger(__name__)
     self.pairs = engine['pairs']
     self.home_currency = engine['home_currency']
     self.equity = engine['equity']
     self.isBacktest = engine['isBacktest']
     self.max_iters = engine['max_iters']
     self.heartbeat = engine['heart_beat']
     self.event_q = Queue()
     self.feed_q = Queue()
     self.exec_q = Queue()
     self.result_q = Queue()
     self.iters = 0
     self.datafeed = self._setup_datafeed(datafeed)
     self.execution = self._setup_execution(execution)
     self.strategy = self._setup_strategy(strategy)
     self.result = self._setup_result(result)
     self.ticker = Ticker(self.pairs)
     self.portfolio = Portfolio(ticker=self.ticker,
                                event_q=self.event_q,
                                result_q=self.result_q,
                                home_currency=self.home_currency,
                                pairs=self.pairs,
                                equity=self.equity)
     self.toContinue = True
     initializeDecimalContext()
Beispiel #4
0
def test_invert_prices(pair: str, bid: Decimal, ask: Decimal, return_pair: str,
                       return_bid: Decimal, return_ask: Decimal) -> None:
    """ invert_prices should return inverted pair, bid and ask prices"""
    pairs = ['USDJPY', 'GBPUSD']
    ticker = Ticker(pairs)
    inv_pair, inv_bid, inv_ask = ticker.invert_prices(pair, bid, ask)
    assert inv_pair == return_pair
    assert inv_bid == return_bid
    assert inv_ask == return_ask
Beispiel #5
0
def test_set_up_prices_dict() -> None:
    """
    _set_up_prices_dict should return both prices dict and
    inv-prices dict
    """
    pairs = ['USDJPY', 'GBPUSD']
    ticker = Ticker(pairs)
    prices_dict = ticker._set_up_prices_dict()
    assert sorted(list(prices_dict.keys())) == sorted(
        ["USDJPY", "JPYUSD", "GBPUSD", "USDGBP"])
    for v in prices_dict.values():
        assert v == {
            'bid': Decimal(0),
            'ask': Decimal(0),
            'time': pd.Timestamp(0)
        }
Beispiel #6
0
def TickerMock2() -> Ticker:
    _pairs = ["GBPUSD", "EURUSD", "USDJPY"]
    _prices = {
        "GBPUSD": {"bid": Decimal("1.2541"), "ask": Decimal("1.2543")},
        "USDJPY": {"bid": Decimal("107.25"), "ask": Decimal("107.80")},
    }
    tm = Ticker(_pairs)
    # Create decimalaised prices for trade pair
    for pair, price in _prices.items():
        _bid, _ask = price['bid'], price['ask']
        tm.prices[pair]["bid"] = _bid
        tm.prices[pair]["ask"] = _ask
        # Create decimalised prices for inverted pair
        inv_pair, inv_bid, inv_ask = tm.invert_prices(pair, _bid, _ask)
        tm.prices[inv_pair]["bid"] = inv_bid
        tm.prices[inv_pair]["ask"] = inv_ask
    return tm
Beispiel #7
0
def setupDataFeeder() -> Tuple[Ticker, DataFeeder]:
    pairs = ["USDJPY", "GBPUSD"]
    ticker = Ticker(pairs)
    datafeeder = HistoricCSVDataFeeder(pairs, Queue(), './tests/datafeed')
    return ticker, datafeeder
Beispiel #8
0
class Engine(object):
    """
    Enscapsulates the settings and components for carrying out
    an event-driven trading/backtest on the foreign exchange markets.
    """
    logger: Logger
    pairs: List[Pair]
    datafeed: DataFeeder
    execution: ExecutionHandler
    strategy: Strategy
    result: ResultHandler
    portfolio: Portfolio
    ticker: Ticker
    equity: Decimal
    home_currency: str
    heartbeat: float
    max_iters: int
    iters: int
    event_q: 'Queue[Event]'
    feed_q: 'Queue[Event]'
    exec_q: 'Queue[Event]'
    result_q: 'Queue[Result]'

    def __init__(self, engine: engine_params, datafeed: datafeed_params,
                 execution: execution_params, strategy: strategy_params,
                 result: result_params):
        """
        Initializes the backtest.
        """
        self.logger = getLogger(__name__)
        self.pairs = engine['pairs']
        self.home_currency = engine['home_currency']
        self.equity = engine['equity']
        self.isBacktest = engine['isBacktest']
        self.max_iters = engine['max_iters']
        self.heartbeat = engine['heart_beat']
        self.event_q = Queue()
        self.feed_q = Queue()
        self.exec_q = Queue()
        self.result_q = Queue()
        self.iters = 0
        self.datafeed = self._setup_datafeed(datafeed)
        self.execution = self._setup_execution(execution)
        self.strategy = self._setup_strategy(strategy)
        self.result = self._setup_result(result)
        self.ticker = Ticker(self.pairs)
        self.portfolio = Portfolio(ticker=self.ticker,
                                   event_q=self.event_q,
                                   result_q=self.result_q,
                                   home_currency=self.home_currency,
                                   pairs=self.pairs,
                                   equity=self.equity)
        self.toContinue = True
        initializeDecimalContext()

    def _setup_datafeed(self, datafeed: datafeed_params) -> DataFeeder:
        _module = import_module('savoia.datafeed.datafeed')

        _params = datafeed['params']
        _params['pairs'] = self.pairs
        _params['feed_q'] = self.feed_q

        df = getattr(_module, datafeed['module_name'])
        return df(**_params)

    def _setup_execution(self, execution: execution_params) \
            -> ExecutionHandler:
        _module = import_module('savoia.execution.execution')

        _params = execution['params']
        _params['event_q'] = self.event_q
        _params['exec_q'] = self.exec_q

        exe = getattr(_module, execution['module_name'])
        return exe(**_params)

    def _setup_strategy(self, strategy: strategy_params) \
            -> ExecutionHandler:
        _module = import_module('savoia.strategy.strategy')

        _params = strategy['params']
        _params['pairs'] = self.pairs
        _params['event_q'] = self.event_q

        exe = getattr(_module, strategy['module_name'])
        return exe(**_params)

    def _setup_result(self, result: result_params) -> ResultHandler:
        _module = import_module('savoia.result.result')

        _params = result['params']
        _params['pairs'] = self.pairs
        _params['result_q'] = self.result_q

        exe = getattr(_module, result['module_name'])
        return exe(**_params)

    def _run_engine(self) -> None:
        """
        Carries out an infinite while loop that polls the
        event_q queue and directs each event to either the
        strategy component of the execution handler. The
        loop will then pause for "heartbeat" seconds and
        continue until the maximum number of iterations is
        exceeded.
        """
        _wait: bool = False
        self.logger.info("Running engine...")
        while self.iters < self.max_iters and self.toContinue:
            try:
                event = self.event_q.get(_wait)
            except Empty:
                try:
                    tick_event = self.feed_q.get(False)
                except Empty:
                    pass
                else:
                    if tick_event is None:
                        self.logger.info('Acknowledged the end of datafeed.')
                        self.toContinue = False
                    elif tick_event.type != 'TICK':
                        raise Exception
                    else:
                        self.logger.debug('Process TICK -%s' % tick_event)
                        self.ticker.update_ticker(tick_event)
                        self.portfolio.update_portfolio(tick_event)
                        self.strategy.calculate_signals(tick_event)
            else:
                _wait = False
                if event is not None:
                    if event.type == 'SIGNAL':
                        self.logger.info("Process SIGNAL -%s" % event)
                        self.portfolio.execute_signal(event)
                    elif event.type == 'ORDER':
                        self.logger.info("Process ORDER -%s" % event)
                        self.exec_q.put(event)
                        _wait = self.isBacktest
                    elif event.type == 'FILL':
                        self.logger.info("Process FILL -%s" % event)
                        self.portfolio.execute_fill(event)
                    else:
                        raise Exception
            time.sleep(self.heartbeat)
            self.iters += 1
        self.exec_q.put(None)
        return

    def _output_performance(self) -> None:
        """
        Outputs the strategy performance from the backtest.
        """
        self.logger.info("Calculating Performance Metrics...")
        self.portfolio.output_results()

    def _run(self) -> None:
        _result = threading.Thread(target=self.result.run)
        _datafeed = threading.Thread(target=self.datafeed.run)
        _execution = threading.Thread(target=self.execution.run)
        _engine = threading.Thread(target=self._run_engine)

        _result.start()
        _execution.start()
        _engine.start()
        _datafeed.start()

        _datafeed.join()
        _engine.join()
        _execution.join()
        self.result_q.put(None)
        _result.join()

    def run(self) -> None:
        """
        Runs the engine.
        """
        if self.isBacktest:
            _start = time.time()
            self.logger.info('Start Backtesting.')
            self._run()
            # self._output_performance()
            self.logger.info("Backtest complete. Elapsed Time[Sec]: " +
                             f'{time.time() - _start}')
        else:
            self.logger.info('Start Live trading.')
            self._run()
            self.logger.info("Trading complete.")