def __init__(self, prices_file, balance, strategy): """ :param prices_file: The file that has the pair prices. :param balance: The initial balance to start with. :param strategy: The instance of the strategy to backtest (subclass of AbstractStrategy) """ self.prices_file = prices_file self.account = Account(balance) self.strategy = strategy
def test_create_trade(self): account = Account(100) trade_id, trade = account.open_trade(('a', 'b'), 1, Trade.TYPE_BUY, 60, 2, 0.5) self.assertEqual(40, account.balance) self.assertEqual(1, len(account.trades_list)) self.assertIn(trade, account.trades_list) self.assertEqual(1, trade_id) self.assertEqual(('a', 'b'), trade.pair) self.assertEqual(1, trade.price) self.assertEqual(60, trade.amount) self.assertEqual(2, trade.take_profit_price) self.assertEqual(0.5, trade.stop_loss_price)
class Backtest: def __init__(self, prices_file, balance, strategy): """ :param prices_file: The file that has the pair prices. :param balance: The initial balance to start with. :param strategy: The instance of the strategy to backtest (subclass of AbstractStrategy) """ self._prices_file = prices_file self._account = Account(balance) self._strategy = strategy @property def account(self): return self._account @staticmethod def parse_entry(entry): parts = entry.strip().split(';') timestamp = parts[0] values = [0] * (len(parts) - 1) for i in range(1, len(parts)): values[i - 1] = float(parts[i]) return timestamp, values def start(self): """Starts the replay of historical data against the user-defined strategy""" pairs = cfg.pairs() with open(self._prices_file) as file: while True: current_line = file.readline() if not current_line: break timestamp, prices = self.__class__.parse_entry(current_line) if len(prices) != len(pairs): raise RuntimeError( 'The number of prices in this entry ({}) does not match the number of pairs' .format(timestamp)) prices_dict = {pairs[i]: prices[i] for i in range(len(pairs))} results = self._account.auto_close_trades(prices_dict) for trade_id, outcome in results: logging.debug('{} Closed t{} with {}'.format( timestamp, trade_id, outcome)) self._strategy.execute(self._account, timestamp, prices_dict) logging.info('Result: %s' % self._account.net_balance_summary(prices_dict))
def test_close_trade(self): account = Account(100) account.open_trade(('a', 'b'), 1, Trade.TYPE_BUY, 60) self.assertEqual(40, account.balance) self.assertEqual(Account.OUTCOME_LOSS, account.close_trade(1, 0.5)) self.assertEqual(70, account.balance) account.open_trade(('a', 'b'), 2, Trade.TYPE_SELL, 50) self.assertEqual(20, account.balance) self.assertEqual(Account.OUTCOME_PROFIT, account.close_trade(2, 1)) self.assertEqual(120, account.balance)
def test_net_balance_summary(self): account = Account(100) account.open_trade(('a', 'b'), 1, Trade.TYPE_BUY, 40) account.open_trade(('c', 'd'), 1, Trade.TYPE_SELL, 40) self.assertEqual('90 units - trades [open [1, 2], 2 total, 0 wins, 0 losses]', account.net_balance_summary({('a', 'b'): 1.25, ('c', 'd'): 2})) self.assertEqual('80 units - trades [open [1, 2], 2 total, 0 wins, 0 losses]', account.net_balance_summary({('a', 'b'): 0.5, ('c', 'd'): 1})) account.close_trade(2, 4) self.assertEqual('70 units - trades [open [1], 2 total, 0 wins, 1 losses]', account.net_balance_summary({('a', 'b'): 1})) account.close_trade(1, 1.5) self.assertEqual('90 units - trades [open [], 2 total, 1 wins, 1 losses]', account.net_balance_summary({}))
def test_auto_close_trade(self): account = Account(100) account.open_trade(('a', 'b'), 1, Trade.TYPE_BUY, 30) account.open_trade(('a', 'b'), 1, Trade.TYPE_BUY, 30, 1.25, 0.75) account.open_trade(('c', 'd'), 1, Trade.TYPE_SELL, 30, 0.5, 1.5) self.assertEqual([], account.auto_close_trades({('a', 'b'): 1, ('c', 'd'): 1})) self.assertEqual(3, len(account.trades_list)) self.assertEqual([], account.auto_close_trades({('a', 'b'): 1.2, ('c', 'd'): 0.7})) self.assertEqual(3, len(account.trades_list)) self.assertEqual([(2, 'profit'), (3, 'loss')], account.auto_close_trades({('a', 'b'): 1.3, ('c', 'd'): 1.75})) self.assertEqual(1, len(account.trades_list))
def test_not_enough_balance(self): account = Account(100) with self.assertRaises(RuntimeError): account.open_trade(('a', 'b'), 1, Trade.TYPE_BUY, 200) self.assertEqual(100, account.balance) self.assertEqual(0, len(account.trades_list))