def generate_order_and_txns(self, sid, order_amount, fill_amounts): asset1 = self.asset_finder.retrieve_asset(sid) # one order order = Order(dt=None, asset=asset1, amount=order_amount) # three fills txn1 = Transaction(asset=asset1, amount=fill_amounts[0], dt=None, price=100, order_id=order.id) txn2 = Transaction(asset=asset1, amount=fill_amounts[1], dt=None, price=101, order_id=order.id) txn3 = Transaction(asset=asset1, amount=fill_amounts[2], dt=None, price=102, order_id=order.id) return order, [txn1, txn2, txn3]
def simulate(self, data, asset, orders_for_asset): self._volume_for_bar = 0 price = data.current(asset, 'close') dt = data.current_dt for order in orders_for_asset: if order.open_amount == 0: continue order.check_triggers(price, dt) if not order.triggered: log.debug('order has not reached the trigger at current ' 'price {}'.format(price)) continue execution_price, execution_volume = self.process_order(data, order) transaction = Transaction(asset=order.asset, amount=abs(execution_volume), dt=dt, price=execution_price, order_id=order.id) self._volume_for_bar += abs(transaction.amount) yield order, transaction
def create_txn(asset, price, amount, datetime, order_id): return Transaction( asset=asset, price=price, amount=amount, dt=datetime, order_id=order_id, )
def process_order(self, order): # TODO: move to parent class after tracking features in the parent if not self.api.hasFetchMyTrades: return self._process_order_fallback(order) try: all_trades = self.get_trades(order.asset) except ExchangeRequestError as e: log.warn( 'unable to fetch account trades, trying an alternate ' 'method to find executed order {} / {}: {}'.format( order.id, order.asset.symbol, e ) ) return self._process_order_fallback(order) transactions = [] trades = [t for t in all_trades if t['order'] == order.id] if not trades: log.debug( 'order {} / {} not found in trades'.format( order.id, order.asset.symbol ) ) return transactions trades.sort(key=lambda t: t['timestamp'], reverse=False) order.filled = 0 order.commission = 0 for trade in trades: # status property will update automatically filled = trade['amount'] * order.direction order.filled += filled commission = 0 if 'fee' in trade and 'cost' in trade['fee']: commission = trade['fee']['cost'] order.commission += commission order.check_triggers( price=trade['price'], dt=pd.to_datetime(trade['timestamp'], unit='ms', utc=True), ) transaction = Transaction( asset=order.asset, amount=filled, dt=pd.Timestamp.utcnow(), price=trade['price'], order_id=order.id, commission=commission ) transactions.append(transaction) order.broker_order_id = ', '.join([t['id'] for t in trades]) return transactions
def test_transaction_repr(self): dt = pd.Timestamp('2017-01-01') asset = Equity(1, exchange='test') txn = Transaction(asset, amount=100, dt=dt, price=10, order_id=0) expected = ( "Transaction(asset=Equity(1), dt=2017-01-01 00:00:00," " amount=100, price=10)" ) self.assertEqual(repr(txn), expected)
def _process_order_fallback(self, order): """ Fallback method for exchanges which do not play nice with fetch-my-trades. Apparently, about 60% of exchanges will return the correct executed values with this method. Others will support fetch-my-trades. Parameters ---------- order: Order Returns ------- float """ exc_order, price = self.get_order( order.id, order.asset, return_price=True, params={'type': order.direction_type}) order.status = exc_order.status order.commission = exc_order.commission order.filled = exc_order.filled order.matched_price = price order.dt = exc_order.dt transactions = [] if exc_order.status == ORDER_STATUS.FILLED or \ (exc_order.status == ORDER_STATUS.CANCELLED and exc_order.filled != 0): if order.amount != exc_order.amount: log.warn('order({}): executed amount {} differs ' 'from original {}'.format(order.id, exc_order.amount, order.amount)) order.check_triggers( price=price, dt=exc_order.dt, ) transaction = Transaction( asset=order.asset, amount=exc_order.filled, dt=pd.Timestamp.utcnow(), price=price, order_id=order.id, commission=order.commission, ) transactions.append(transaction) return transactions
def check_open_orders(self): """ Loop through the list of open orders in the Portfolio object. For each executed order found, create a transaction and apply to the Portfolio. Returns ------- list[Transaction] """ for asset in self.open_orders: exchange = self.exchanges[asset.exchange] for order in self.open_orders[asset]: log.debug('found open order: {}'.format(order.id)) new_order, executed_price = exchange.get_order(order.id, asset) log.debug('got updated order {} {}'.format( new_order, executed_price)) order.status = new_order.status if order.status == ORDER_STATUS.FILLED: order.commission = new_order.commission if order.amount != new_order.amount: log.warn('executed order amount {} differs ' 'from original'.format( new_order.amount, order.amount)) order.amount = new_order.amount transaction = Transaction(asset=order.asset, amount=order.amount, dt=pd.Timestamp.utcnow(), price=executed_price, order_id=order.id, commission=order.commission) yield order, transaction elif order.status == ORDER_STATUS.CANCELLED: yield order, None else: delta = pd.Timestamp.utcnow() - order.dt log.info( 'order {order_id} still open after {delta}'.format( order_id=order.id, delta=delta))
def _process_order_fallback(self, order): """ Fallback method for exchanges which do not play nice with fetch-my-trades. Apparently, about 60% of exchanges will return the correct executed values with this method. Others will support fetch-my-trades. Parameters ---------- order: Order Returns ------- float """ exc_order, price = self.get_order( order.id, order.asset, return_price=True ) order.status = exc_order.status order.commission = exc_order.commission if order.amount != exc_order.amount: log.warn( 'executed order amount {} differs ' 'from original'.format( exc_order.amount, order.amount ) ) order.amount = exc_order.amount if order.status == ORDER_STATUS.FILLED: transaction = Transaction( asset=order.asset, amount=order.amount, dt=pd.Timestamp.utcnow(), price=price, order_id=order.id, commission=order.commission ) return [transaction]
def maybe_create_close_position_transaction(self, asset, dt, data_portal): if not self.positions.get(asset): return None amount = self.positions.get(asset).amount price = data_portal.get_spot_value( asset, 'price', dt, self.data_frequency) # Get the last traded price if price is no longer available if isnan(price): price = self.positions.get(asset).last_sale_price txn = Transaction( asset=asset, amount=(-1 * amount), dt=dt, price=price, commission=0, order_id=None, ) return txn
def check_open_orders(self): """ Loop through the list of open orders in the Portfolio object. For each executed order found, create a transaction and apply to the Portfolio. Returns ------- list[Transaction] """ transactions = list() if self.portfolio.open_orders: for order_id in list(self.portfolio.open_orders): log.debug('found open order: {}'.format(order_id)) order, executed_price = self.get_order(order_id) log.debug('got updated order {} {}'.format( order, executed_price)) if order.status == ORDER_STATUS.FILLED: transaction = Transaction(asset=order.asset, amount=order.amount, dt=pd.Timestamp.utcnow(), price=executed_price, order_id=order.id, commission=order.commission) transactions.append(transaction) self.portfolio.execute_order(order, transaction) elif order.status == ORDER_STATUS.CANCELLED: self.portfolio.remove_order(order) else: delta = pd.Timestamp.utcnow() - order.dt log.info( 'order {order_id} still open after {delta}'.format( order_id=order_id, delta=delta)) return transactions
def check_open_orders(self): """ Need to override this function for Poloniex: Loop through the list of open orders in the Portfolio object. Check if any transactions have been executed: If so, create a transaction and apply to the Portfolio. Check if the order is still open: If not, remove it from open orders :return: transactions: Transaction[] """ transactions = list() if self.portfolio.open_orders: for order_id in list(self.portfolio.open_orders): order = self._portfolio.open_orders[order_id] log.debug('found open order: {}'.format(order_id)) try: order_open = self.get_order(order_id) except Exception as e: raise ExchangeRequestError(error=e) if (order_open): delta = pd.Timestamp.utcnow() - order.dt log.info( 'order {order_id} still open after {delta}'.format( order_id=order_id, delta=delta)) try: response = self.api.returnordertrades(order_id) except Exception as e: raise ExchangeRequestError(error=e) if ('error' in response): if (not order_open): raise OrphanOrderReverseError(order_id=order_id, exchange=self.name) else: for tx in response: """ We maintain a list of dictionaries of transactions that correspond to partially filled orders, indexed by order_id. Every time we query executed transactions from the exchange, we check if we had that transaction for that order already. If not, we process it. When an order if fully filled, we flush the dict of transactions associated with that order. """ if (not filter( lambda item: item['order_id'] == tx['tradeID'], self.transactions[order_id])): log.debug( 'Got new transaction for order {}: amount {}, ' 'price {}'.format(order_id, tx['amount'], tx['rate'])) tx['amount'] = float(tx['amount']) if (tx['type'] == 'sell'): tx['amount'] = -tx['amount'] transaction = Transaction( asset=order.asset, amount=tx['amount'], dt=pd.to_datetime(tx['date'], utc=True), price=float(tx['rate']), order_id=tx['tradeID'], # it's a misnomer, but keep for compatibility commission=float(tx['fee'])) self.transactions[order_id].append(transaction) self.portfolio.execute_transaction(transaction) transactions.append(transaction) if (not order_open): """ Since transactions have been executed individually the only thing left to do is remove them from list of open_orders """ del self.portfolio.open_orders[order_id] del self.transactions[order_id] return transactions