class TestBrokerLocal(): def __init__(self, account_balance, margin_rate, tick_source, account_currency="EUR"): self.account_id = None self.account_name = 'Local test' self.account_currency = account_currency self.margin_rate = margin_rate self.balance = account_balance self.margin_available = account_balance self.margin_used = 0.0 self.open_orders = 0 self.open_orders_list = [] self.open_trades = 0 self.open_trades_list = [] self.realized_pl = 0 self.unrealized_pl = 0 self.tick_source = Driver.init_module_config(tick_source) self.stat = Stat(account_balance) self.last_tick = {} self.logger = logging.getLogger(__name__) def __str__(self): ret = "Local test broker" ret += "account id: " + str(self.account_id) + "\n" ret += "account name: " + str(self.account_name) + "\n" ret += "account currency: " + str(self.account_currency) + "\n" ret += "balance: " + str(self.balance) + "\n" ret += "margin available: " + str(self.margin_available) + "\n" ret += "margin used: " + str(self.margin_used) + "\n" ret += "margin rate: " + str(self.margin_rate) + "\n" ret += "realized pl: " + str(self.realized_pl) + "\n" ret += "unrealized pl: " + str(self.unrealized_pl) + "\n" return ret def get_tick_data(self, instrument): for tick in self.tick_source.get_tick_data(instrument): self.stat.add_tick(tick) self.last_tick[instrument] = tick # close finished sl/tp trades self.open_trades_list = self.close_finished_trades(self.open_trades_list) yield tick def open(self, instrument, volume, order_type='market', expiry=None, **args): # price=None, lower_bound=None, upper_bound=None, sl=None, tp=None, ts=None): if order_type == 'market': if volume > 0: open_price = self.last_tick[instrument].buy elif volume < 0: open_price = self.last_tick[instrument].sell trade = Trade(instrument=instrument, volume=volume, open_price=open_price, open_datetime=self.last_tick[instrument].datetime) trade_margin = (abs(volume) * open_price) * self.margin_rate if self.margin_available - trade_margin < 0: print("Not enough money to open the trade") return None else: self.margin_available -= trade_margin self.margin_used += trade_margin self.open_trades += 1 self.open_trades_list.append(trade) return trade else: self.logger.warning("oreder_type is not 'market' - not implemented yet") def close(self, trade): if trade.volume > 0: close_price = self.last_tick[trade.instrument].sell elif trade.volume < 0: close_price = self.last_tick[trade.instrument].buy trade.close(close_price, self.last_tick[trade.instrument].datetime) trade_margin = (abs(trade.volume) * close_price) * self.margin_rate self.margin_available += trade_margin self.margin_used -= trade_margin # get profit pl = (trade.close_rate - trade.open_rate) * abs(trade.volume) self.realized_pl += pl self.balance += pl trade.set_profit(pl) self.stat.add_trade(trade) return trade def convert_currency(self, instrument, base_volume, rate=None): if rate: return base_volume * rate else: tick = self.get_tick_data(instrument) if base_volume > 0: # buy return tick.buy * base_volume else: # sell return tick.sell * abs(base_volume) def close_finished_trades(self, trades): """ Checks for sl/tp out. This method simulates what the real broker does and must be called from model after processing each new tick.. TODO: trailing stoploss Arguments: trades list of all trades the model has opened Returns: list of trades left open """ left_trades = [] for trade in trades: if trade.volume > 0: if trade.sl: if self.last_tick[trade.instrument] <= trade.sl: self.close(trade) else: left_trades.append(trade) if self.tp: if self.last_tick[trade.instrument] >= trade.tp: self.close(trade) else: left_trades.append(trade) else: if trade.sl: if self.last_tick[trade.instrument] >= trade.sl: self.close(trade) else: left_trades.append(trade) if self.tp: if self.last_tick[trade.instrument] <= trade.tp: self.close(trade) else: left_trades.append(trade) return left_trades def get_account(self): """Does nothing in local implementation """ None def get_account_information(self): """Does nothing in local implementation """ None def get_open_trades(self): return []
class OandaBroker(): def __init__(self, enviroment, username, access_token=None, tick_freq_ms=500): """ :param enviroment: the environment for oanda's REST api, either 'sandbox', 'practice', or 'live'. :param username: username for Oanda broker :param access_token: a valid access token if you have one. This is required if the environment is not sandbox. :param tick_freq_ms: how often should be the server asked to get new rates in milliseconds. Default: 500 """ # connect to the broker self.oanda = oandapy.API(environment=enviroment, access_token=access_token) # get basic account info self.get_account(username=username) self.get_account_information() # rates streaming self.rate_freq = timedelta(milliseconds=tick_freq_ms) self.last_tick_datetime = datetime.now() self.stat = Stat(self.balance) self.logger = logging.getLogger(__name__) def __str__(self): ret = "OANDA broker" ret += "account id: " + str(self.account_id) + "\n" ret += "account name: " + str(self.account_name) + "\n" ret += "account currency: " + str(self.account_currency) + "\n" ret += "balance: " + str(self.balance) + "\n" ret += "margin available: " + str(self.margin_available) + "\n" ret += "margin used: " + str(self.margin_used) + "\n" ret += "margin rate: " + str(self.margin_rate) + "\n" ret += "realized pl: " + str(self.realized_pl) + "\n" ret += "unrealized pl: " + str(self.unrealized_pl) + "\n" return ret def _str2datetime(self, str_datetime): return datetime.strptime(str_datetime, '%Y-%m-%dT%H:%M:%S.%fZ') def get_tick_data(self, instrument): while(1): now = datetime.now() if now - self.last_tick_datetime >= self.rate_freq: self.last_tick_datetime = now response = self.oanda.get_prices(instruments=instrument[0] + "_" + instrument[1]) t = response.get("prices")[0] yield Tick(self._str2datetime(t['time']), t['ask'], t['bid']) def open(self, instrument, volume, order_type='market', expiry=None, **args): # price=None, lower_bound=None, upper_bound=None, sl=None, tp=None, ts=None): if expiry is None: expiry = datetime.now() + timedelta(days=1) expiry = expiry.isoformat("T") + "Z" self.logger.debug('trade open called') response = self.oanda.create_order(self.account_id, instrument=instrument[0] + "_" + instrument[1], units=abs(volume), side='buy' if volume > 0 else 'sell', type=order_type, expiry=expiry, **{k:v for k, v in args.items() if v is not None} ) self.logger.debug('trade open carried out') if response['tradeOpened']: return Trade(instrument=instrument, volume=volume, open_price=response['price'], margin_rate=self.margin_rate, sl=response['tradeOpened']['stopLoss'], tp=response['tradeOpened']['takeProfit'], ts=response['tradeOpened']['trailingStop'], id=response['tradeOpened']['id'], open_datetime=self._str2datetime(response['time'])) elif response['tradesClosed']: self.logger.critical("When opening trade, 'tradesClosed' returned. This is not implemented, careful!!!") return None elif response['tradeReduced']: self.logger.critical("When opening trade, 'tradeReduced' returned. This is not implemented, careful!!!") return None def close(self, trade): self.logger.debug('trade close called') response = self.oanda.close_trade(self.account_id, trade.id) self.logger.info('trade close carried out') # if the trade was found if 'code' not in response: trade.close(response['price'], response['time']) trade.set_profit(response['profit']) self.balance += response['profit'] self.stat.add_trade(trade) else: # try to close order self.logger.debug('order close') response = self.oanda.close_order(self.account_id, trade.id) self.logger.info('order close carried out') trade = None return trade def convert_currency(self, instrument, base_volume, rate=None): if rate: return base_volume * rate else: tick = self.get_tick_data(instrument).next() if base_volume > 0: return tick.buy * base_volume else: return tick.sell * abs(base_volume) def get_account(self, username): response = self.oanda.get_accounts(username=username) account = response.get("accounts")[0] self.account_id = account['accountId'] self.account_name = account['accountName'] self.account_currency = account['accountCurrency'] self.margin_rate = account['marginRate'] def get_account_information(self): response = self.oanda.get_account(account_id=self.account_id) self.balance = response['balance'] self.margin_available = response['marginAvail'] self.margin_used = response['marginUsed'] self.open_orders = response['openOrders'] self.open_trades = response['openTrades'] self.realized_pl = response['realizedPl'] self.unrealized_pl = response['unrealizedPl'] def get_open_trades(self): response = self.oanda.get_trades(account_id=self.account_id) open_trades = [] trades = response.get("trades") for trade in trades: volume = -trade['units'] if trade['side'] == 'sell' else trade['units'] instrument = (trade['instrument'][:3], trade['instrument'][-3:]) open_trades.append(Trade(instrument=instrument, volume=volume, open_price=trade['price'], margin_rate=self.margin_rate, id=trade['id'], sl=trade['stopLoss'], tp=trade['takeProfit'], ts=trade['trailingStop'], open_datetime=self._str2datetime(trade['time']))) open_trades.reverse() return open_trades def get_open_orders(self): pass
class TestBrokerLocal(): def __init__(self, account_balance, margin_rate, tick_source, account_currency="EUR"): self.param_account_balance = account_balance self.param_margin_rate = margin_rate self.param_tick_source = tick_source self.param_account_currency = account_currency self.reset_broker() self.logger = logging.getLogger(__name__) def reset_broker(self): self.account_id = None self.account_name = 'Local test' self.account_currency = self.param_account_currency self.margin_rate = self.param_margin_rate self.balance = self.param_account_balance self.margin_available = self.param_account_balance self.margin_used = 0.0 self.open_orders = 0 self.open_orders_list = [] self.open_trades = 0 self.open_trades_list = [] self.trade_id = 0 self.realized_pl = 0 self.unrealized_pl = 0 self.tick_source = Driver.init_module_config(self.param_tick_source) self.stat = Stat(self.param_account_balance) self.last_tick = {} def __str__(self): ret = "Local test broker" ret += "account id: " + str(self.account_id) + "\n" ret += "account name: " + str(self.account_name) + "\n" ret += "account currency: " + str(self.account_currency) + "\n" ret += "balance: " + str(self.balance) + "\n" ret += "margin available: " + str(self.margin_available) + "\n" ret += "margin used: " + str(self.margin_used) + "\n" ret += "margin rate: " + str(self.margin_rate) + "\n" ret += "realized pl: " + str(self.realized_pl) + "\n" ret += "unrealized pl: " + str(self.unrealized_pl) + "\n" return ret def get_tick_data(self, instrument): for tick in self.tick_source.get_tick_data(instrument): self.stat.add_tick(tick) self.last_tick[instrument] = tick # close finished sl/tp trades self.open_trades_list = self.close_finished_trades(self.open_trades_list) yield tick def open(self, instrument, volume, order_type='market', expiry=None, **args): # price=None, lower_bound=None, upper_bound=None, sl=None, tp=None, ts=None): if order_type == 'market': if volume > 0: open_price = self.last_tick[instrument].buy elif volume < 0: open_price = self.last_tick[instrument].sell trade = Trade(instrument=instrument, volume=volume, open_price=open_price, open_datetime=self.last_tick[instrument].datetime, id=self.trade_id, **args) # calculate margin and convert it to the account currency if needed trade_margin = abs(trade.volume) * self.margin_rate trade_margin_converted = self.convert_to_account_currency(trade_margin, trade.instrument[0]) if self.margin_available - trade_margin_converted < 0: self.logger.warning("not enough money to open the trade") return None else: self.margin_available -= trade_margin_converted self.margin_used += trade_margin_converted self.open_trades += 1 self.open_trades_list.append(trade) self.trade_id += 1 return trade else: self.logger.warning("oreder_type is not 'market' - not implemented yet") return None def close(self, trade): if trade.volume > 0: close_price = self.last_tick[trade.instrument].sell elif trade.volume < 0: close_price = self.last_tick[trade.instrument].buy if trade in self.open_trades_list: self.open_trades_list.remove(trade) else: print(trade.id, "not in trade list!!") return None trade.close(close_price, self.last_tick[trade.instrument].datetime) # calculate margin and convert it to the account currency if needed trade_margin = abs(trade.volume) * self.margin_rate trade_margin_converted = self.convert_to_account_currency(trade_margin, trade.instrument[0]) self.margin_available += trade_margin_converted self.margin_used -= trade_margin_converted # get profit in quote currency and convert it to the base currency quote_pl = (trade.close_rate - trade.open_rate) * trade.volume pl = round(self.convert_to_account_currency(quote_pl, trade.instrument[1]), 4) self.realized_pl += pl self.balance += pl trade.set_profit(pl) self.stat.add_trade(trade) return trade def convert_from_account_currency(self, volume, target_currency): if target_currency == self.account_currency: return volume else: if self.last_tick[(self.account_currency, target_currency)]: return volume * self.last_tick[(self.account_currency, target_currency)].buy else: tick = self.get_tick_data((self.account_currency, target_currency)).__next__() return volume * tick.buy def convert_to_account_currency(self, volume, source_currency): if source_currency == self.account_currency: return volume else: if self.last_tick[(self.account_currency, source_currency)]: return volume * (1.0 /self.last_tick[(self.account_currency, source_currency)].buy) else: tick = self.get_tick_data((self.account_currency, source_currency)).__next__() return volume * (1.0 /tick.buy) def close_finished_trades(self, trades): """ Checks for sl/tp out. This method simulates what the real broker does and must be called from model after processing each new tick.. TODO: trailing stoploss Arguments: trades list of all trades the model has opened Returns: list of trades left open """ left_trades = [] for trade in trades: if trade.sl or trade.tp: close = False if trade.volume > 0: if trade.sl: if self.last_tick[trade.instrument].sell <= trade.sl: self.logger.info("trade closed with stop loss") close = True if trade.tp: if self.last_tick[trade.instrument].sell >= trade.tp: self.logger.info("trade closed with take profit") close = True else: if trade.sl: if self.last_tick[trade.instrument].buy >= trade.sl: self.logger.info("trade closed with stop loss") close = True if trade.tp: if self.last_tick[trade.instrument].buy <= trade.tp: self.logger.info("trade closed with take profit") close = True if close: result = self.close(trade) self.logger.info("profit: result.profit") else: left_trades.append(trade) else: left_trades.append(trade) return left_trades def get_account(self): """Does nothing in local implementation """ None def get_account_information(self): """Does nothing in local implementation """ None def get_open_trades(self): return copy.deepcopy(self.open_trades_list)