def main(): # noqa: E302 global marketTable if (len(sys.argv) > 1): with open(sys.argv[1]) as f: config = BaseExchange.extend({}, baseConfig, json.load(f)) else: config = BaseExchange.extend({}, baseConfig) marketTable = MarketTable(config['marketTable']) for id in config['exchanges'].keys(): exchange = config['exchanges'][id] exConf = BaseExchange.extend( {}, config['exchangeDefaults'], exchange['options'] if 'options' in exchange else {}) ex = getattr(ccxt, id)({ 'apiKey': exConf['apiKey'] if 'apiKey' in exConf else '', 'secret': exConf['apiSecret'] if 'apiSecret' in exConf else '', 'enableRateLimit': True, 'verbose': exConf['verbose'] if 'verbose' in exConf else False, }) for symbol in exchange['symbols'].keys(): marketTable.addMarket(ex.id, symbol) asyncio.ensure_future(subscribe(ex, exchange['symbols'], config['symbolDefaults']), loop=loop)
def __init__(self): super(CcxtApi, self).__init__() self.api_key = "" self.api_secret = "" self.exchange_symbol_dict = {} self._contracts: typing.Dict[str, ContractData] = {} self._root_symbol_dict = {} self._exchange = Exchange() self._limit_context = LimitedQueryContext(self.N_RATE_LIMIT, self.PERIOD_LIMIT) self._local_order_manager = LocalOrderManager() self._callback: typing.Callable = print
def __init__(self): Exchange.__init__(self) client = MaxClient(MAX_KEY, MAX_SECRET) self.client = client self.websocket_uri = None self.fees = { 'trading': { 'tierBased': False, 'percentage': True, 'taker': 0.0015, 'maker': 0.001, } }
async def subscribe(exchange, symbols, defaultConfig): # noqa: E302 @exchange.on('err') def websocket_error(err, conxid): # pylint: disable=W0612 print(err) try: exchange.websocketClose(conxid) except ccxt.BaseError: pass for symbol in symbols.keys(): marketTable.marketError(exchange.id, symbol, err) marketTable.print() @exchange.on('ob') def websocket_ob(market, ob): # pylint: disable=W0612 marketTable.updateMarket(exchange.id, market, ob) marketTable.print() try: await exchange.load_markets() for symbol in symbols.keys(): sys.stdout.flush() config = BaseExchange.extend({}, defaultConfig, symbols[symbol]) try: await exchange.websocket_subscribe('ob', symbol, config) except Exception as ex: print(ex) sys.stdout.flush() marketTable.marketError(exchange.id, symbol, ex) marketTable.print() except Exception as ex: print(ex) sys.stdout.flush() for symbol in symbols.keys(): marketTable.marketError(exchange.id, symbol, ex) marketTable.print()
def fetch_ticker(self, symbol): ohlcv = self._ohlcvs.get(symbol) if ohlcv is None: raise BadSymbol('ExchangeBackend: no prices for {}'.format(symbol)) current_date = self._timeframe.date().floor('1T') row = ohlcv.loc[current_date] timestamp = int(current_date.value / 10**6) return { 'symbol': symbol, 'timestamp': timestamp, 'datetime': Exchange.iso8601(timestamp), 'high': row['high'], 'low': row['low'], 'bid': None, 'bidVolume': None, 'ask': None, 'askVolume': None, 'vwap': None, 'open': row['open'], 'close': row['close'], 'last': None, 'previousClose': None, 'change': None, 'percentage': None, 'average': None, 'baseVolume': None, 'quoteVolume': None, 'info': {}, }
def update_market(market: Market, exchange: Exchange = None) -> Market: """ Update Market Order Keyword arguments: market -- market model exchange -- exchange client, preload all markets to reduce requests return -> Market, updated market """ if not exchange: exchange = get_client(exchange_id=market.exchange) market_exchange: dict = exchange.market(market.symbol) return get_or_create_market(response=market_exchange, exchange_id=market.exchange)
def __init__(self, options): self.options = BaseExchange.extend({}, { "marketsByRow": 2, "maxLimit": 10, "marketColumnWidth": 50, }, options) self.markets = {} self.grid = [] self.newEmptyLine = ' ' * (self.options['marketColumnWidth'] * self.options['marketsByRow']) self.hr = "-" * (self.options['marketColumnWidth']) self.bidsAdksSeparator = "." * (self.options['marketColumnWidth']) self.emptyCell = " " * (self.options['marketColumnWidth']) self.amountColumn = self.options['marketColumnWidth'] // 2 self.height = 2 + self.options['maxLimit'] + 1 + self.options[ 'maxLimit'] + 1
def fetch_ohlcv_dataframe(self, symbol, timeframe='1m', since=None, limit=None, params={}): # Exchanges in the real world have different behaviour, when there is # no since parameter provided. (some use data from the beginning, # some from the end) # We return data from the beginning, because this is most likely not # what the user wants, so this will force the user to provide the # parameters, which will work with every exchange. This is a bug # prevention mechanism. ohlcv = self._ohlcvs.get(symbol) if ohlcv is None: raise BadSymbol('ExchangeBackend: no prices for {}'.format(symbol)) pd_current_date = self._timeframe.date().floor('1T') if limit is None: limit = 5 timeframe_sec = Exchange.parse_timeframe(timeframe) pd_timeframe = pandas.Timedelta(timeframe_sec, unit='s') ohlcv_start_date = ohlcv.index[0] if since is None: pd_since = ohlcv_start_date else: pd_since = pandas.Timestamp(since, unit='ms', tz='UTC') pd_since = pd_since.ceil(pd_timeframe) if pd_since < ohlcv_start_date: raise BadRequest('ExchangeBackend: fetch_ohlcv: no date availabe ' 'at since') pd_until = pd_since + limit * pd_timeframe - pandas.Timedelta('1m') if pd_until >= pd_current_date + pd_timeframe: raise BadRequest('ExchangeBackend: fetch_ohlcv:' ' since.ceil(timeframe) + limit * timeframe' ' needs to be in the past') pd_until = min(pd_until, pd_current_date) data = ohlcv[pd_since:pd_until] return data.resample(pd_timeframe).agg({ 'open': 'first', 'high': 'max', 'low': 'min', 'close': 'last', 'volume': 'sum' })
def parse_params_and_execute_algorithm(AlgorithmClass): parser = argparse.ArgumentParser( epilog=HELP_EPILOG, formatter_class=argparse.RawTextHelpFormatter) parser.add_argument('--start-date', default='', help='Date to start backtesting, ignored in live mode') parser.add_argument('--end-date', default='2009-01-01', help='Date to end backtesting') parser.add_argument('--interval', default='1m', help='Timedelta between each iteration') parser.add_argument('--exchanges', default='', help='Exchange ids comma separated to load ohlcv') parser.add_argument('--symbols', default='', help='Symbols (comma separated) to load ohlcv ' 'per exchange') parser.add_argument('--data-directory', default=USER_DATA_DIR, help='directory where data is stored' ' (e.g. ohlcv data') parser.add_argument('--config-directory', default=USER_CONFIG_DIR, help='directory where config is stored' ' (e.g. exchange parameters') parser.add_argument('--auth-aliases', default='{}', help='Auth aliases for different exchange' ' config files') parser.add_argument('--live', action='store_true', help='Trade live on exchanges') parser.add_argument('--start-balances', default='{}', help='Balance at start (json): ' '{"exchange": {"BTC": 3}}') AlgorithmClass.configure_argparser(parser) args = parser.parse_args() logger = logging.getLogger(__package__) def split_parameters(p): if p == '': return [] return p.split(',') exchange_names = split_parameters(args.exchanges) symbols = split_parameters(args.symbols) if not args.live: if len(exchange_names) == 0: logger.warning('No exchanges specified, do not load ohlcv') if len(symbols) == 0: logger.warning('No symbols specified, load all ohlcvs per each ' 'exchange. This can lead to long start times') try: pd_interval = pandas.Timedelta( Exchange.parse_timeframe(args.interval), unit='s') except (NotSupported, ValueError): raise ValueError('Interval is not valid') auth_aliases = {} if args.live: if args.start_date != '': raise ValueError('Start date cannot be set in live mode') if args.start_balances != '{}': raise ValueError('Start balance cannot be set in live mode') pd_start_date = pandas.Timestamp.now(tz='UTC').floor(pd_interval) start_balances = None auth_aliases = json.loads(args.auth_aliases) else: pd_start_date = pandas.to_datetime(args.start_date, utc=True) start_balances = json.loads(args.start_balances) pd_end_date = pandas.to_datetime(args.end_date, utc=True) if pandas.isnull(pd_start_date): raise ValueError('Start date is not valid') if pandas.isnull(pd_end_date): raise ValueError('End date is not valid') return execute_algorithm(exchange_names=exchange_names, symbols=symbols, pd_start_date=pd_start_date, pd_end_date=pd_end_date, pd_interval=pd_interval, conf_dir=args.config_directory, data_dir=args.data_directory, AlgorithmClass=AlgorithmClass, args=args, auth_aliases=auth_aliases, live=args.live, start_balances=start_balances)
def iso8601(value): return ccxtExchange.iso8601(value)
def safe_value(dictionary, key, default_value=None): return ccxtExchange.safe_value(dictionary, key, default_value)
def nonce(self): return Exchange.milliseconds()
def safe_float(self, dictionary, key, default_value): return ccxtExchange.safe_float(dictionary, key, default_value)
class CcxtApi(Registered): FULL_NAME = '' STATUS_MAP_REVERSE = dict() STATUS_MAP_REVERSE['open'] = EnumOrderStatus.PENDING STATUS_MAP_REVERSE['closed'] = EnumOrderStatus.FILLED STATUS_MAP_REVERSE['canceled'] = EnumOrderStatus.CANCELLED DIRECTION_MAP = dict() DIRECTION_MAP[EnumOrderDirection.BUY] = 'buy' DIRECTION_MAP[EnumOrderDirection.SELL] = 'sell' DIRECTION_MAP_RESERVE = {v: k for k, v in DIRECTION_MAP.items()} ORDER_TYPE_MAP = dict() ORDER_TYPE_MAP[EnumOrderType.MARKET] = 'market' ORDER_TYPE_MAP[EnumOrderType.LIMIT] = 'limit' ORDER_TYPE_MAP_REVERSE = {v: k for k, v in ORDER_TYPE_MAP.items()} TAG = '' N_RATE_LIMIT = 300 PERIOD_LIMIT = '1m' N_LIMIT_ORDER = 300 N_LIMIT_TRADE = 300 N_LIMIT_BAR = 500 N_LIMIT_MARKET_TRADE = 300 @property def contracts(self): return self._contracts @property def exchange(self): return self._exchange @property def oms(self): return self._local_order_manager def __init__(self): super(CcxtApi, self).__init__() self.api_key = "" self.api_secret = "" self.exchange_symbol_dict = {} self._contracts: typing.Dict[str, ContractData] = {} self._root_symbol_dict = {} self._exchange = Exchange() self._limit_context = LimitedQueryContext(self.N_RATE_LIMIT, self.PERIOD_LIMIT) self._local_order_manager = LocalOrderManager() self._callback: typing.Callable = print def set_callback(self, callback): self._callback = callback def throttle(self): with self._limit_context: pass def on_order(self, order: OrderData): self._local_order_manager.on_order(order) self._callback(order) def on_trade(self, trade: TradeData): self._callback(trade) def on_bar(self, bar: BarData): self._callback(bar) def on_depth(self, depth: DepthData): self._callback(depth) def connect(self, api_key='', api_secret=''): self.api_key = api_key self.api_secret = api_secret config = { 'apiKey': api_key, 'secret': api_secret, 'enableRateLimit': False, } exchange = self.FULL_NAME api_class = getattr(ccxt, exchange) self._exchange = api_class(config) self.fetch_contract() for symbol, contract in self.contracts.items(): exchange_symbol = contract.symbol_exchange self.exchange_symbol_dict[exchange_symbol] = symbol # --------------------- trading interface --------------------- def create_order(self, order: OrderData, params: dict = None): self.throttle() self._local_order_manager.on_order(order) order = copy.copy(order) contract = self._contracts[order.symbol] symbol_root = contract.symbol_root if params is None: params = {} try: price = order.price if order.order_type == EnumOrderType.MARKET: price = None logger.debug('%s sending order %s', self, order.client_order_id) data = self._exchange.create_order(symbol_root, self.ORDER_TYPE_MAP[order.order_type], self.DIRECTION_MAP[order.direction], order.volume, price, params) if 'status' in data: status = self.STATUS_MAP_REVERSE[data['status']] else: status = EnumOrderStatus.PENDING order.status = status order.order_id = data['id'] logger.debug('%s sent order %s successfully with order id %s and status %s ', self, order.client_order_id, order.order_id, order.status) except ccxt.InsufficientFunds as e: order.status = EnumOrderStatus.REJECTED balance_df = self.fetch_balance() base_amount = balance_df.loc[contract.symbol_base] quote_amount = balance_df.loc[contract.symbol_quote] logger.warn( "Insufficient balance for order: %s\n%s\n%s", e.args, order, f'There was only {contract.symbol_base} \n' f'{base_amount} \n' f'and {quote_amount} \n' f'{contract.symbol_quote} \n' f'in the balance. ' ) except ccxt.InvalidOrder as e: order.status = EnumOrderStatus.REJECTED logger.warn("InvalidOrder error: %s\n%s", e.args, order) except ccxt.ExchangeError as e: logger.warn("Exchange error: %s\n%s", e.args, order) order.status = EnumOrderStatus.REJECTED except ccxt.NetworkError as e: logger.warn("Network error: %s\n%s", e.args, order) order.status = EnumOrderStatus.ERROR except requests.HTTPError as e: logger.warn("Http error: %s\n%s", e.args, order) order.status = EnumOrderStatus.ERROR self._local_order_manager.on_order(order) self.on_order(order) return order def cancel_order(self, order_cancel: OrderData): self.throttle() self._local_order_manager.on_order(order_cancel) order_id = order_cancel.order_id contract = self._contracts[order_cancel.symbol] symbol_root = contract.symbol_root logger.debug('%s cancel order %s', self, order_cancel.client_order_id) try: self._exchange.cancel_order(order_id, symbol_root) logger.debug('%s canceled order %s successfully', self, order_cancel.client_order_id) order_cancel.status = EnumOrderStatus.CANCELLED except ccxt.errors.OrderNotFound as e: logger.info("Order already been closed or cancelled before: %s\n%s", e.args, order_cancel) order_status = self.fetch_order_status(order_cancel) order_cancel.on_order(order_status) except ccxt.errors.NetworkError as e: logger.info("Network error: %s\n%s", e.args, order_cancel) order_cancel.status = EnumOrderStatus.CANCEL_ERROR self._local_order_manager.on_order(order_cancel) self.on_order(order_cancel) return order_cancel def fetch_order_status(self, order: OrderData): self.throttle() logger.debug('fetch status for order %s', order) symbol = order.symbol symbol_root = self.contracts[symbol].symbol_root order_id = order.order_id data = self._exchange.fetch_order(order_id, symbol_root) order_status = copy.copy(order) order_status.status = self.STATUS_MAP_REVERSE[data['status']] order_status.executed_volume = data['filled'] return order_status def fetch_order_trades(self, order: OrderData) -> typing.List[TradeData]: symbol_root = self.contracts[order.symbol].symbol_root result = self._exchange.fetch_order_trades(order.order_id, symbol_root) trade_list = [] for data in result: trade = self._parse_trade(data) trade.strategy_id = order.strategy_id trade.client_order_id = order.client_order_id trade_list.append(trade) return trade_list def _parse_trade(self, data): trade = TradeData() trade.symbol = self._root_symbol_dict[data['symbol']] trade.order_id = data['order'] trade.trade_id = data['id'] trade.volume = data['amount'] trade.price = data['price'] trade.direction = self.DIRECTION_MAP_RESERVE[data['side']] trade.commission = data['fee']['cost'] trade.commission_asset = '.'.join([data['fee']['currency'], self.TAG]) trade.datetime = datetime.datetime.utcfromtimestamp(data['timestamp'] / 1000.0) return trade def fetch_my_trades(self, symbol, since=DEFAULT_START_TIME, params=None): self.throttle() if params is None: params = {} if symbol is None: symbol_root = None else: symbol_root = self.contracts[symbol].symbol_root start_timestamp = int(calendar.timegm(since.timetuple()) * 1000) data_list = self._exchange.fetch_my_trades(symbol_root, since=start_timestamp, params=params) trade_list = [] for data in data_list: trade_list.append(self._parse_trade(data)) return trade_list def _fetch_order_impl(self, function, symbol, since, params): self.throttle() if symbol is None: symbol_root = None else: symbol_root = self.contracts[symbol].symbol_root if params is None: params = {} data_list = [] start_timestamp = int(calendar.timegm(since.timetuple()) * 1000) limit = self.N_LIMIT_ORDER while True: orders = function(symbol_root, since=start_timestamp, limit=limit, params=params) if orders is None: break if len(orders): data_list.extend(orders) if len(orders) == limit: start_timestamp = orders[-1]['timestamp'] else: break order_list = [] order_id_set = set() for data in data_list: order_id = data['id'] if order_id not in order_id_set: order_id_set.add(order_id) order = OrderData() order.order_id = order_id order.symbol = self._root_symbol_dict[data['symbol']] order.datetime = datetime.datetime.utcfromtimestamp(data['timestamp'] / 1000.0) order.volume = data['amount'] order.executed_volume = data['filled'] order.executed_notional = data['cost'] order.price = data['price'] order.status = self.STATUS_MAP_REVERSE[data['status']] order.direction = self.DIRECTION_MAP_RESERVE[data['side']] order.order_type = self.ORDER_TYPE_MAP_REVERSE[data['type']] order_list.append(order) return order_list def fetch_open_orders(self, symbol, since=DEFAULT_START_TIME, params=None): return self._fetch_order_impl(self._exchange.fetch_open_orders, symbol, since, params) def fetch_close_orders(self, symbol, since=DEFAULT_START_TIME, params=None): return self._fetch_order_impl(self._exchange.fetch_closed_orders, symbol, since, params) def fetch_orders(self, symbol, since=DEFAULT_START_TIME, params=None): return self._fetch_order_impl(self._exchange.fetch_orders, symbol, since, params) def fetch_balance(self): self.throttle() try: # noinspection PyUnresolvedReferences data = self._exchange.fetch_balance() except ccxt.base.errors.AuthenticationError: return pd.DataFrame() account_df = pd.DataFrame([data['free'], data['used'], data['total']], index=['free_amount', 'frozen_amount', 'total_amount']).T index = account_df.any(axis=1) account_df = account_df[index] return account_df # --------------------- market data --------------------- def fetch_ticker(self, symbol): symbol_root = self.contracts[symbol].symbol_root ticker = self._exchange.fetch_ticker(symbol_root) ticker.pop('info') return ticker def fetch_bars(self, symbol, freq, start_date, end_date=None, return_df=False): symbol_root = self.contracts[symbol].symbol_root if end_date is None: now = datetime.datetime.utcnow() end_date = start_date + TIME_INTERVAL_MAP[freq] * (self.N_LIMIT_BAR - 1) end_date = min(end_date, now - TIME_INTERVAL_MAP[freq]) start_timestamp = int(calendar.timegm(start_date.timetuple()) * 1000) end_timestamp = int(calendar.timegm(end_date.timetuple()) * 1000) time_delta = int(TIME_INTERVAL_MAP[freq].total_seconds() * 1000) ohlcv_list = [] while start_timestamp <= end_timestamp: self.throttle() ohlcv = self._exchange.fetch_ohlcv(symbol_root, freq, start_timestamp, self.N_LIMIT_BAR) if ohlcv is not None and len(ohlcv): ohlcv_list.extend(ohlcv) actual_last_timestamp = ohlcv[-1][0] logger.debug('download {symbol}:{freq} from [{start} ~~ {end}]'.format( symbol=symbol, freq=freq, start=datetime.datetime.fromtimestamp(start_timestamp / 1000), end=datetime.datetime.fromtimestamp(actual_last_timestamp / 1000), )) expect_last_timestamp = start_timestamp + (self.N_LIMIT_BAR - 1) * time_delta next_timestamp = max(actual_last_timestamp, expect_last_timestamp) + time_delta start_timestamp = next_timestamp else: break # transform into data frame: # 1. check data duplication # 2. constrain data in giver date range # 3. timestamp -> datetime bar_df = pd.DataFrame(ohlcv_list, columns=['datetime', 'open', 'high', 'low', 'close', 'volume']) bar_list = [] if len(bar_df): non_duplicate_index = ~bar_df['datetime'].duplicated() date_range_index = bar_df['datetime'] <= end_timestamp filter_index = non_duplicate_index & date_range_index bar_df = bar_df[filter_index] bar_df['datetime'] = pd.to_datetime(bar_df['datetime'], unit='ms') bar_df['frequency'] = freq bar_df['symbol'] = symbol if return_df: return bar_df ohlcv_records = bar_df.to_dict('records') for record in ohlcv_records: bar = BarData.from_dict(record) bar_list.append(bar) return bar_list def first_valid_date(self, symbol): self.throttle() symbol_root = self.contracts[symbol].symbol_root start_date = DEFAULT_START_TIME freq = '1d' start_timestamp = int(calendar.timegm(start_date.timetuple()) * 1000) ohlcv = self._exchange.fetch_ohlcv(symbol_root, freq, start_timestamp, 5) if len(ohlcv): return datetime.datetime.utcfromtimestamp(ohlcv[0][0] / 1000.0) else: return start_date def back_fill_bars(self, symbols, periods=50, freq='1m'): bar_list = [] end_date = datetime.datetime.utcnow() - TIME_INTERVAL_MAP[freq] start_date = end_date - TIME_INTERVAL_MAP[freq] * periods for symbol in symbols: bars = self.fetch_bars(symbol, freq, start_date, end_date) bar_list.extend(bars) return bar_list def fetch_depth(self, symbol): self.throttle() symbol_root = self.contracts[symbol].symbol_root data = self._exchange.fetch_l2_order_book(symbol_root) depth = DepthData() depth.symbol = symbol for n, bid in enumerate(data['bids'][:DepthData.N_DEPTH]): depth.bid_prices[n] = bid[0] depth.bid_volumes[n] = bid[1] for n, ask in enumerate(data['asks'][:DepthData.N_DEPTH]): depth.ask_prices[n] = ask[0] depth.ask_volumes[n] = ask[1] if data['timestamp']: depth.datetime = datetime.datetime.utcfromtimestamp(data['timestamp'] / 1000) else: depth.datetime = datetime.datetime.utcnow() return depth # --------------------- contract information --------------------- def fetch_contract(self, return_df=False): # codes are removed pass @staticmethod def _get_precision(number): return np.float_power(10, -number)
def toWei(amount, decimals): return Exchange.to_wei(amount, decimals)
def fromWei(amount, decimals): return Exchange.from_wei(amount, decimals)
def create_order(self, market, type, price, side, amount): self._update_orders() type_market = False type_limit = False if type == 'market': if price is not None: raise InvalidOrder( 'ExchangeAccount: market order has no price') type_market = True elif type == 'limit': price = _convert_float_or_raise(price, 'ExchangeAccount: price') type_limit = True if price <= 0: raise BadRequest('ExchangeAccount: price needs to be positive') else: raise InvalidOrder( 'ExchangeAccount: only market and limit order supported') if market is None: raise InvalidOrder('ExchangeAccount: market is None') symbol = market.get('symbol') ohlcv = self._ohlcvs.get(symbol) if ohlcv is None: raise InvalidOrder('ExchangeAccount: no prices available for {}' .format(symbol)) if side not in ['buy', 'sell']: raise InvalidOrder('ExchangeAccount: side {} not supported' .format(side)) buy = side == 'buy' amount = _convert_float_or_raise(amount, 'ExchangeAccount: amount') if amount <= 0: raise BadRequest('ExchangeAccount: amount needs to be positive') base = market.get('base') quote = market.get('quote') if base is None: raise BadRequest('ExchangeAccount: market has no base') if quote is None: raise BadRequest('ExchangeAccount: market has no quote') self._last_order_id += 1 order_id = str(self._last_order_id) date = self._timeframe.date() timestamp = int(date.value / 10e5) order = { 'info': {}, 'id': order_id, 'timestamp': timestamp, 'datetime': Exchange.iso8601(timestamp), 'lastTradeTimestamp': None, 'symbol': symbol, 'type': type, 'side': side, 'price': None, 'amount': amount, 'cost': None, 'average': None, 'filled': 0, 'remaining': amount, 'status': 'open', 'fee': {'currency': base if buy else quote, 'cost': None, 'rate': None}, 'trades': None, } if type_market: # Determinie the price of the market order # We could use the next low/high to fill the order, but then we # need to wait for the next date to fill the order, otherwise we # would introduce a possibility to see the future price # (Look-Ahead Bias) # If we wait for the next date, we would return a market order that # is pending, but this should never happen in reality # Maybe the factor should depend on the volume factor = Decimal('0.0015') if buy: price = (1 + factor) * _convert_float(ohlcv['high'][date]) else: price = (1 - factor) * _convert_float(ohlcv['low'][date]) fee_percentage = market.get('taker', 0) fee_percentage = _convert_float_or_raise(fee_percentage, 'ExchangeAccount: fee') self._update_balance(price, amount, base, quote, buy, fee_percentage) self._fill_order(order, buy, price, timestamp, fee_percentage) self._closed_orders[order_id] = order if type_limit: # TODO Probably use taker fee, if the order can be filled now fee_percentage = market.get('maker', 0) fee_percentage = _convert_float_or_raise(fee_percentage, 'ExchangeAccount: fee') if buy: self._balances[quote].change_used(price * amount) else: self._balances[base].change_used(amount) self._open_orders[order_id] = order self._private_order_info[order_id] = { 'id': order_id, 'base': base, 'quote': quote, 'price': price, 'buy': buy, 'fee_percentage': fee_percentage, 'fillable_date': self._limit_order_fillable_date( symbol, buy, price), } self._update_next_private_order_to_update() return {'id': order_id, 'info': {}}