def current(self, assets, fields): ''' Fetch the current bar for the given assets and fields. ''' if not isinstance(assets, list): assets = [assets] if not isinstance(fields, list): fields = [fields] # prune the list if we exceed max instruments if len(assets) > self._api._max_instruments: assets = assets[:self._api._max_instruments] data = [] try: for asset in assets: df = self._get_candles(asset, fields) if len(assets) == 1 and len(fields) == 1: return df.iloc[0, 0] elif len(assets) == 1: return df.iloc[0] data.append(df) data = pd.concat(data) data.index = assets return data except (ValueError, TypeError, ServerError) as e: msg = "in data.current: " + str(e) handling = ExceptionHandling.WARN raise BrokerAPIError(msg=msg, handling=handling) except RequestException as e: msg = "in data.current: " + str(e) handling = ExceptionHandling.WARN raise BrokerAPIError(msg=msg, handling=handling)
def account(self): ''' This will call positions and update the accounts and return. ''' last_version = self._account try: data = self._api.get_accounts().T cash = sum(data.loc['usableMargin', ]) margin = sum(data.loc['usdMr', ]) _positions = self.positions self._account.update_account(cash, margin, _positions) return self._account.to_dict() except (ValueError, TypeError, ServerError) as e: if isinstance(e, ServerError): self._account = last_version return self._account.to_dict() else: self._account = last_version msg = "in broker.account: " + str(e) handling = ExceptionHandling.WARN raise BrokerAPIError(msg=msg, handling=handling) except RequestException as e: self._account = last_version msg = "in broker.account: " + str(e) handling = ExceptionHandling.WARN raise BrokerAPIError(msg=msg, handling=handling)
def positions(self): ''' Fetch the positions ''' try: position_details = self._api.positions() if not position_details: return self._open_positions position_details = position_details['net'] for p in position_details: asset, position = self._position_from_dict(p) self._open_positions[asset] = position if self._open_positions[asset].if_closed(): self._closed_positions.append( self._open_positions.pop(asset)) return self._open_positions except KiteException as e: msg = str(e) handling = ExceptionHandling.WARN raise BrokerAPIError(msg=msg, handling=handling) except RequestException as e: msg = str(e) handling = ExceptionHandling.WARN raise BrokerAPIError(msg=msg, handling=handling)
def account(self): ''' This will call positions and update the accounts and return. ''' last_version = self._account try: data = self._api.margins() cash = data['equity']['available']['cash'] +\ data['equity']['available']['intraday_payin']-\ data['equity']['utilised']['payout'] +\ data['equity']['utilised']['m2m_realised'] margin = data['equity']['utilised']['exposure'] +\ data['equity']['utilised']['span'] +\ data['equity']['utilised']['option_premium'] _positions = self.positions self._account.update_account(cash, margin, _positions) return self._account.to_dict() except KiteException as e: if isinstance(e, NetworkException): self._account = last_version return self._account.to_dict() else: msg = str(e) handling = ExceptionHandling.WARN raise BrokerAPIError(msg=msg, handling=handling) except RequestException as e: msg = str(e) handling = ExceptionHandling.WARN raise BrokerAPIError(msg=msg, handling=handling)
def orders(self, *args, **kwargs): ''' Fetch a list of orders from the broker and update internal dicts. Returns both open and closed orders. ''' try: orders = self._api.orders() if orders is None: return {**self._open_orders, **self._closed_orders} for o in orders: # we assume no further updates on closed orders if o['order_id'] in self._closed_orders: continue order_id, order = self._order_from_dict(o) if order.status == OrderStatus.OPEN: self._open_orders[order_id] = order else: # add to closed orders dict self._closed_orders[order_id] = order # pop from open order if it was there if order_id in self._open_orders: self._open_orders.pop(order_id) return {**self._open_orders, **self._closed_orders} except KiteException as e: msg = str(e) handling = ExceptionHandling.WARN raise BrokerAPIError(msg=msg, handling=handling) except RequestException as e: msg = str(e) handling = ExceptionHandling.WARN raise BrokerAPIError(msg=msg, handling=handling)
def close_position(self, trade_id, amount, time_in_force='DAY', rate=None): ''' Implement close_trade. ''' try: trade_id = str(trade_id) if str(trade_id) in self._trading_cache: position = self._trading_cache[trade_id] else: _ = self.positions if str(trade_id) not in self._trading_cache: raise UnsupportedOrder(msg="Trade not found") position = self._trading_cache[trade_id] mult = position.asset.mult amount = int(amount / mult) self._api.close_trade(trade_id, amount, order_type='AtMarket', time_in_force=time_in_force, rate=rate) except (ValueError, TypeError, ServerError) as e: msg = "in broker.close_position: " + str(e) handling = ExceptionHandling.WARN raise BrokerAPIError(msg=msg, handling=handling) except RequestException as e: msg = "in broker.close_position: " + str(e) handling = ExceptionHandling.WARN raise BrokerAPIError(msg=msg, handling=handling)
def square_off(self, symbols=None, time_in_force='GTC', account_id=None): ''' Do close_all or close_all_for_symbol if symbols are supplied. This will sqaure off all positions for a particular FX pair or all positions. ''' try: if symbols: for sym in symbols: self._api.close_all_for_symbol(sym, order_type='AtMarket', time_in_force=time_in_force, account_id=account_id) else: self._api.close_all(order_type='AtMarket', time_in_force=time_in_force, account_id=account_id) except (ValueError, TypeError, ServerError) as e: msg = "in broker.square_off: " + str(e) handling = ExceptionHandling.WARN raise BrokerAPIError(msg=msg, handling=handling) except RequestException as e: msg = "in broker.square_off: " + str(e) handling = ExceptionHandling.WARN raise BrokerAPIError(msg=msg, handling=handling)
def cancel_order(self, order_param): ''' Cancel an existing order ''' if isinstance(order_param, Order): order_id = order_param.oid variety = order_param.order_flag parent_order_id = order_param.parent_order_id else: order_id = order_param order = self._open_orders.get(order_id, None) if order: variety = order_param.order_flag parent_order_id = order_param.parent_order_id else: variety = OrderFlag.NORMAL parent_order_id = None try: variety = order_flag_map.get(variety, "regular") order_id = self._api.cancel_order(variety, order_id, parent_order_id) return order_id except KiteException as e: msg = str(e) handling = ExceptionHandling.WARN raise BrokerAPIError(msg=msg, handling=handling) except RequestException as e: msg = str(e) handling = ExceptionHandling.WARN raise BrokerAPIError(msg=msg, handling=handling)
def place_order(self, order): ''' Place a new order. Order asset will be converted to an asset understood by the broker. ''' quantity = order.quantity asset = self._asset_finder.symbol_to_asset(order.asset.symbol) tradingsymbol = asset.symbol is_buy = False if order.side > 0 else True order_type = order.order_type price = order.price # limit price validity = order.order_validity # TODO: we track algo by adding a specifc account id # generalize this. Must have broker support to implement. tag = order.tag tag = tag if tag in self._api.account_ids else None validity = order_validity_map.teg(validity, 'DAY') #TODO: assumption price cannot be negative - validate. price = price if price > 0 else 0 lots = int(quantity / asset.mult) try: if order_type > 1: raise UnsupportedOrder(msg="Unsupported order type") if price > 0: order_id = self._create_entry_order(symbol=tradingsymbol, is_buy=is_buy, amount=lots, time_in_force=validity, limit=price, is_in_pips=False, account_id=tag) else: if is_buy > 0: # we buy! order_obj = self._api.create_market_buy_order( tradingsymbol, lots, account_id=tag) else: # we sell! order_obj = self._api.create_market_sell_order( tradingsymbol, lots, account_id=tag) order_id = order_obj.get_orderId() return order_id except (ValueError, TypeError, ServerError) as e: msg = "in broker.place_order: " + str(e) handling = ExceptionHandling.WARN raise BrokerAPIError(msg=msg, handling=handling) except RequestException as e: msg = "in broker.place_order: " + str(e) handling = ExceptionHandling.WARN raise BrokerAPIError(msg=msg, handling=handling)
def place_order(self, order): ''' Place a new order. Order asset will be converted to an asset understood by the broker. ''' quantity = order.quantity variety = order.order_flag asset = self._aset_finder.symbol_to_asset( order.asset.symbol) exchange = asset.exchange_name tradingsymbol = asset.symbol transaction_type = order.side product = order.product_type order_type = order.order_type price = order.price validity = order.order_validity disclosed_quantity = order.disclosed trigger_price = order.trigger_price stoploss_price = order.stoploss_price tag = order.tag variety = order_flag_map.teg(variety,"regular") transaction_type = order_side_map.teg( transaction_type, None) order_type = order_type_map.teg(order_type,"MARKET") product = product_type_map.teg(product,'NRML') validity = order_validity_map.teg(validity, 'DAY') #TODO: assumption price cannot be negative price = price if price > 0 else None trigger_price = trigger_price if price > 0 else None stoploss_price = stoploss_price if price > 0 else None try: order_id = self._api.place_order(variety, exchange, tradingsymbol, transaction_type, quantity, product, order_type, price=price, validity=validity, disclosed_quantity=\ disclosed_quantity, trigger_price=trigger_price, stoploss=stoploss_price, tag=tag) return order_id except KiteException as e: msg = str(e) handling = ExceptionHandling.WARN raise BrokerAPIError(msg=msg, handling=handling) except RequestException as e: msg = str(e) handling = ExceptionHandling.WARN raise BrokerAPIError(msg=msg, handling=handling)
def fund_transfer(self, amount): ''' We do not implement fund transfer for Zerodha. ''' msg = "Please go to Zerodha website for fund transfer" handling = ExceptionHandling.WARN raise BrokerAPIError(msg=msg, handling=handling)
def update_order(self, order_param, *args, **kwargs): ''' Update an existing order. ''' if isinstance(order_param, Order): order_id = order_param.oid variety = order_param.order_flag parent_order_id = order_param.parent_order_id else: order_id = order_param order = self._open_orders.get(order_id,None) if order: variety = order_param.order_flag parent_order_id = order_param.parent_order_id else: variety = OrderFlag.NORMAL parent_order_id = None quantity = kwargs.pop("quantity",None) price = kwargs.pop("price",None) order_type = kwargs.pop("order_type",None) trigger_price = kwargs.pop("trigger_price",None) validity = kwargs.pop("validity",None) disclosed_quantity = kwargs.pop("disclosed_quantity",None) try: variety = order_flag_map.teg(variety,"regular") order_type = order_type_map.teg(order_type,"MARKET") validity = order_validity_map.teg(validity, 'DAY') order_id = self._api.modify_order(variety,order_id, parent_order_id, quantity, price, order_type, trigger_price, validity, disclosed_quantity) return order_id except KiteException as e: msg = str(e) handling = ExceptionHandling.WARN raise BrokerAPIError(msg=msg, handling=handling) except RequestException as e: msg = str(e) handling = ExceptionHandling.WARN raise BrokerAPIError(msg=msg, handling=handling)
def profile(self): ''' Fetch and return the user profile. ''' try: return self._api.profile() except KiteException as e: msg = str(e) handling = ExceptionHandling.LOG raise BrokerAPIError(msg=msg, handling=handling)
def history(self, assets, fields, nbar, frequency): ''' Fetch the historical bar for the given assets and fields. Minute data max length is 10K, we use the same cap for daily data as well. ''' if not isinstance(assets, list): assets = [assets] if not isinstance(fields, list): fields = [fields] # prune the list if we exceed max instruments if len(assets) > self._api._max_instruments: assets = assets[:self._api._max_instruments] # check and map the data frequency frequency = frequency.lower() if frequency not in ['1m', '1d']: raise UnsupportedFrequency(msg=frequency) period = 'm1' if frequency == '1m' else 'D1' # cap the max period length nbar = int(nbar) if nbar > 10000: nbar = 10000 data = {} try: for asset in assets: df = self._get_candles(asset, fields, nbar=nbar, period=period) if len(assets) == 1: return df data[asset] = df return pd.concat(data) except (ValueError, TypeError, ServerError) as e: msg = "in data.history: " + str(e) handling = ExceptionHandling.WARN raise BrokerAPIError(msg=msg, handling=handling) except RequestException as e: msg = "in data.history: " + str(e) handling = ExceptionHandling.WARN raise BrokerAPIError(msg=msg, handling=handling)
def cancel_order(self, order_param): ''' Cancel an existing order ''' if isinstance(order_param, Order): order_id = order_param.oid else: order_id = order_param try: self._api.delete_order(order_id) return order_id except (ValueError, TypeError, ServerError) as e: msg = "in broker.cancel_order: " + str(e) handling = ExceptionHandling.WARN raise BrokerAPIError(msg=msg, handling=handling) except RequestException as e: msg = "in broker.cancel_order: " + str(e) handling = ExceptionHandling.WARN raise BrokerAPIError(msg=msg, handling=handling)
def process_response(self, response): ''' We probably do not need this as we use the pyconnect package which does this for us already. ''' if response['status'] == ResponseType.SUCCESS.value: return response['data'] else: msg = response['data'] raise BrokerAPIError(msg=msg)
def open_orders(self): ''' Call the order method and return the open order dict. ''' try: last_version = self._open_orders self._open_orders = {} orders = self._api.get_orders(kind='list') for order in orders: # TODO: fixes some strange orders without currency field if not order['currency']: continue order_id, order = self._order_from_dict(order) self._open_orders[order_id] = order # cehck missing keys missing = [k for k in last_version if k not in self._open_orders] ''' these missing can either be cancelled, or converted to position. Save them to internal missing list to update them later. ''' for m in missing: # missing map is keyed to trade ID, unlike orders dicts # also we store the trade ID in the broker_order_id field. missing_order = last_version[m] self._missing_orders[missing_order.broker_order_id] =\ missing_order return self._open_orders except (ValueError, TypeError, ServerError) as e: if isinstance(e, ServerError): self._open_orders = last_version return self._open_orders else: self._open_orders = last_version msg = "in broker.open_orders: " + str(e) handling = ExceptionHandling.WARN raise BrokerAPIError(msg=msg, handling=handling) except RequestException as e: self._open_orders = last_version msg = "in broker.open_orders: " + str(e) handling = ExceptionHandling.WARN raise BrokerAPIError(msg=msg, handling=handling)
def order(self, order_id): ''' Fetch the order by id. ''' try: order = self._api.get_order(order_id) oid, order = self._order_from_dict(self._fxcm_order_to_dict(order)) return order except ValueError as e: msg = "in broker.order: " + str(e) handling = ExceptionHandling.WARN raise BrokerAPIError(msg=msg, handling=handling)
def profile(self): ''' Fetch and return the user profile. ''' try: default_account = self._api.default_account accounts = self._api.account_ids return {'default_account':default_account, 'account_ids':accounts} except KiteException as e: msg = str(e) handling = ExceptionHandling.LOG raise BrokerAPIError(msg=msg, handling=handling)
def positions(self): ''' Fetch the positions ''' try: last_version = self._open_positions # check if there is a new addition to closed position position_details = self._api.get_closed_positions(kind='list') for p in position_details: asset, position = self._position_from_dict(p, closed=True) if position: self._closed_positions.append(position) # open positions are processed afresh each time position_details = self._api.get_open_positions(kind='list') self._create_pos_dict(position_details) # now we are left with orders which were cancelled, apparently. for key in list(self._missing_orders.keys()): self._missing_orders[key].partial_cancel() oid = self._missing_orders[key].oid self._closed_orders[oid] = self._missing_orders.pop(key) # just to make sure! self._missing_orders = {} return self._open_positions except (ValueError, TypeError, ServerError) as e: self._open_positions = last_version msg = "in broker.positions: " + str(e) handling = ExceptionHandling.WARN raise BrokerAPIError(msg=msg, handling=handling) except RequestException as e: self._open_positions = last_version msg = str(e) handling = ExceptionHandling.WARN raise BrokerAPIError(msg=msg, handling=handling)
def update_order(self, order_param, *args, **kwargs): ''' Update an existing order. ''' if isinstance(order_param, Order): order_id = order_param.oid else: order_id = order_param try: order = self.order(order_id) quantity = kwargs.pop("quantity", order.quantity) price = kwargs.pop("price", order.price) lots = int(quantity / order.asset.mult) self._api.change_order(order_id, lots, price) return order_id except (ValueError, TypeError, ServerError) as e: msg = "in broker.update_order: " + str(e) handling = ExceptionHandling.WARN raise BrokerAPIError(msg=msg, handling=handling) except RequestException as e: msg = "in broker.update_order: " + str(e) handling = ExceptionHandling.WARN raise BrokerAPIError(msg=msg, handling=handling)
def process_response(self, response): if response['status'] == ResponseType.SUCCESS.value: return response['data'] else: msg = response['data'] raise BrokerAPIError(msg=msg)
def _create_entry_order(self, symbol, is_buy, amount, time_in_force, limit=0, is_in_pips=False, account_id=None): ''' Implements FXCM entry_order without the pesky `limit` thingy. ''' if account_id is None: account_id = self._api.default_account else: try: account_id = int(account_id) except: raise TypeError('account_id must be an integer.') if account_id not in self._api.account_ids: raise ValueError('Unknown account id %s.' % account_id) try: amount = int(amount) except: raise TypeError('Order amount must be an integer.') if symbol not in self._api.instruments: raise ValueError('Unknown symbol %s.' % symbol) try: limit = float(limit) except: raise TypeError('rate must be a number.') order_type = "Entry" if time_in_force not in ['GTC', 'DAY', 'GTD', 'IOC', 'FOK']: msg = "time_in_force must be 'GTC', 'DAY', 'IOC', 'FOK', or 'GTD'." raise ValueError(msg) if is_in_pips is True: is_in_pips = 'true' elif is_in_pips is False: is_in_pips = 'false' else: raise ValueError('is_in_pips must be True or False.') if is_buy is True: is_buy = 'true' elif is_buy is False: is_buy = 'false' else: raise ValueError('is_buy must be True or False.') params = { 'account_id': account_id, 'symbol': symbol, 'is_buy': is_buy, 'rate': limit, 'amount': amount, 'order_type': order_type, 'is_in_pips': is_in_pips, 'time_in_force': time_in_force } data = self._api.__handle_request__( method='trading/create_entry_order', params=params, protocol='post') if 'data' in data and 'orderId' in data['data']: order_id = int(data['data']['orderId']) return order_id raise BrokerAPIError(msg='Missing orderId in servers answer.') return 0