def getExternalMarket(self): try: bitstamp_book = yield self.bitstamp.getOrderBook('BTC/USD') btcusd_bid = bitstamp_book['bids'][0]['price'] btcusd_ask = bitstamp_book['asks'][0]['price'] except Exception as e: # Unable to get markets, just exit print "unable to get external market data from bitstamp: %s" % e raise e for ticker, market in self.markets.iteritems(): new_ask = None new_bid = None if ticker not in self.factory.ignore_contracts: if market['contract_type'] == "cash_pair": if ticker == "BTC/USD": new_bid = btcusd_bid new_ask = btcusd_ask else: currency = market['denominated_contract_ticker'] try: # Get Yahoo quote yahoo_book = yield self.yahoo.getOrderBook('USD/%s' % currency) bid = yahoo_book['bids'][0]['price'] ask = yahoo_book['asks'][0]['price'] except Exception as e: # Unable to get markets, just exit print "unable to get external market data from Yahoo: %s" % e continue new_bid = btcusd_bid * bid new_ask = btcusd_ask * ask elif market['contract_type'] == "futures": got_spot = False if ticker.startswith("USDBTC"): # Ignore BTC and USD interest rates new_bid_spot = 10000/btcusd_ask new_ask_spot = 10000/btcusd_bid # Assume 10bps USD risk-free rate base_rate = 0.0010 got_spot = True elif ticker.startswith("LTCBTC"): kraken_book = yield self.kraken.getOrderBook('BTC/LTC') btcltc_bid = kraken_book['bids'][0]['price'] btcltc_ask = kraken_book['asks'][0]['price'] new_bid_spot = 10000/btcltc_ask new_ask_spot = 10000/btcltc_bid # Assume 10% LTC risk-free rate base_rate = 0.10 got_spot = True if got_spot: from datetime import datetime import util timedelta_to_expiry = util.timestamp_to_dt(market['expiration']) - datetime.utcnow() time_to_expiry = timedelta_to_expiry.total_seconds() / (365.25*24*60*60) # Assume 5% BTC risk-free rate btc_rate = 0.0500 import math forward_factor = Decimal(math.exp((base_rate - btc_rate) * time_to_expiry)) new_bid = new_bid_spot * forward_factor new_ask = new_ask_spot * forward_factor if new_ask is not None and new_bid is not None: logging.info("%s: %f/%f" % (ticker, new_bid, new_ask)) # Make sure that the marketwe are making isn't crossed if new_bid > new_ask: tmp = new_bid new_bid = new_ask new_ask = tmp # If it's matched or crossed make a spread just because if self.price_to_wire(ticker, new_bid) >= self.price_to_wire(ticker, new_ask): new_bid = min(new_bid, new_ask) - self.price_from_wire(ticker, self.markets[ticker]['tick_size']) new_ask = max(new_bid, new_ask) + self.price_from_wire(ticker, self.markets[ticker]['tick_size']) if ticker in self.external_markets: if new_bid != self.external_markets[ticker]['bid']: self.external_markets[ticker]['bid'] = new_bid self.replaceBidAsk(ticker, new_bid, 'BUY') if new_ask != self.external_markets[ticker]['ask']: self.external_markets[ticker]['ask'] = new_ask self.replaceBidAsk(ticker, new_ask, 'SELL') else: self.external_markets[ticker] = {'bid': new_bid, 'ask': new_ask} self.replaceBidAsk(ticker, new_ask, 'SELL') self.replaceBidAsk(ticker, new_bid, 'BUY')
def getExternalMarket(self): try: bitstamp_book = yield self.bitstamp.getOrderBook('BTC/USD') btcusd_bid = bitstamp_book['bids'][0]['price'] btcusd_ask = bitstamp_book['asks'][0]['price'] except Exception as e: # Unable to get markets, just exit print "unable to get external market data from bitstamp: %s" % e raise e for ticker, market in self.markets.iteritems(): new_ask = None new_bid = None if ticker not in self.factory.ignore_contracts: if market['contract_type'] == "cash_pair": if ticker == "BTC/USD": new_bid = btcusd_bid new_ask = btcusd_ask else: currency = market['denominated_contract_ticker'] try: # Get Yahoo quote yahoo_book = yield self.yahoo.getOrderBook( 'USD/%s' % currency) bid = yahoo_book['bids'][0]['price'] ask = yahoo_book['asks'][0]['price'] except Exception as e: # Unable to get markets, just exit print "unable to get external market data from Yahoo: %s" % e continue new_bid = btcusd_bid * bid new_ask = btcusd_ask * ask elif market['contract_type'] == "futures": got_spot = False if ticker.startswith("USDBTC"): # Ignore BTC and USD interest rates new_bid_spot = 10000 / btcusd_ask new_ask_spot = 10000 / btcusd_bid # Assume 10bps USD risk-free rate base_rate = 0.0010 got_spot = True elif ticker.startswith("LTCBTC"): kraken_book = yield self.kraken.getOrderBook('BTC/LTC') btcltc_bid = kraken_book['bids'][0]['price'] btcltc_ask = kraken_book['asks'][0]['price'] new_bid_spot = 10000 / btcltc_ask new_ask_spot = 10000 / btcltc_bid # Assume 10% LTC risk-free rate base_rate = 0.10 got_spot = True if got_spot: from datetime import datetime import util timedelta_to_expiry = util.timestamp_to_dt( market['expiration']) - datetime.utcnow() time_to_expiry = timedelta_to_expiry.total_seconds( ) / (365.25 * 24 * 60 * 60) # Assume 5% BTC risk-free rate btc_rate = 0.0500 import math forward_factor = Decimal( math.exp((base_rate - btc_rate) * time_to_expiry)) new_bid = new_bid_spot * forward_factor new_ask = new_ask_spot * forward_factor if new_ask is not None and new_bid is not None: logging.info("%s: %f/%f" % (ticker, new_bid, new_ask)) # Make sure that the marketwe are making isn't crossed if new_bid > new_ask: tmp = new_bid new_bid = new_ask new_ask = tmp # If it's matched or crossed make a spread just because if self.price_to_wire(ticker, new_bid) >= self.price_to_wire( ticker, new_ask): new_bid = min(new_bid, new_ask) - self.price_from_wire( ticker, self.markets[ticker]['tick_size']) new_ask = max(new_bid, new_ask) + self.price_from_wire( ticker, self.markets[ticker]['tick_size']) if ticker in self.external_markets: if new_bid != self.external_markets[ticker]['bid']: self.external_markets[ticker]['bid'] = new_bid self.replaceBidAsk(ticker, new_bid, 'BUY') if new_ask != self.external_markets[ticker]['ask']: self.external_markets[ticker]['ask'] = new_ask self.replaceBidAsk(ticker, new_ask, 'SELL') else: self.external_markets[ticker] = { 'bid': new_bid, 'ask': new_ask } self.replaceBidAsk(ticker, new_ask, 'SELL') self.replaceBidAsk(ticker, new_bid, 'BUY')
def atomic_commit(self, postings): start = time.time() log.msg("atomic commit called for %s at %f" % (postings, start)) try: # sanity check if len(postings) == 0: raise INTERNAL_ERROR types = [posting["type"] for posting in postings] counts = [posting["count"] for posting in postings] if not all(type == types[0] for type in types): raise TYPE_MISMATCH if not all(count == counts[0] for count in counts): raise COUNT_MISMATCH # balance check debitsum = defaultdict(int) creditsum = defaultdict(int) log.msg("auditing postings at %f" % (time.time() - start)) for posting in postings: if posting["direction"] == "debit": debitsum[posting["contract"]] += posting["quantity"] if posting["direction"] == "credit": creditsum[posting["contract"]] += posting["quantity"] for ticker in debitsum: if debitsum[ticker] - creditsum[ticker] is not 0: raise QUANTITY_MISMATCH # create the journal and postings # The journal is created separately from the postings but this is ok because # all the postings are created at once. If the posting commit fails then we'll # just end up with an empty journal which won't break anything # TODO: Create the journal and postings together log.msg("creating the journal at %f" % (time.time() - start)) ins = Journal.__table__.insert() result = self.execute(ins, type=types[0], timestamp=datetime.datetime.utcnow()) journal_id = result.inserted_primary_key[0] log.msg("creating the db postings at %f" % (time.time() - start)) db_postings = [] for posting in postings: contract_table = Contract.__table__ s = select([contract_table.c.id], contract_table.c.ticker == posting["contract"]) result = self.execute(s) contract_id = result.first()[0] user_table = User.__table__ s = select([user_table.c.type], user_table.c.username == posting["username"]) result = self.execute(s) user_type = result.first()[0] username = posting["username"] quantity = posting["quantity"] direction = posting["direction"] note = posting["note"] if posting["timestamp"] is not None: timestamp = util.timestamp_to_dt(posting["timestamp"]) else: timestamp = None if direction == 'debit': if user_type == 'Asset': sign = 1 else: sign = -1 else: if user_type == 'Asset': sign = -1 else: sign = 1 posting = { 'username': username, 'contract_id': contract_id, 'quantity': sign * quantity, 'note': note, 'timestamp': timestamp, 'journal_id': journal_id } db_postings.append(posting) log.msg("done making posting at %f: %s" % (time.time() - start, posting)) ins = Posting.__table__.insert() result = self.execute(ins, db_postings) log.msg("Inserted %d rows of %d postings" % (result.rowcount, len(db_postings))) log.msg("Done committing postings at %f" % (time.time() - start)) return True except Exception, e: log.err("Caught exception trying to commit. Postings were:") for posting in postings: log.err(str(posting)) log.err("Stack trace follows:") log.err() if isinstance(e, SQLAlchemyError): raise DATABASE_ERROR raise e
def atomic_commit(self, postings): start = time.time() log.msg("atomic commit called for %s at %f" % (postings, start)) try: # sanity check if len(postings) == 0: raise INTERNAL_ERROR types = [posting["type"] for posting in postings] counts = [posting["count"] for posting in postings] if not all(type == types[0] for type in types): raise TYPE_MISMATCH if not all(count == counts[0] for count in counts): raise COUNT_MISMATCH # balance check debitsum = defaultdict(int) creditsum = defaultdict(int) log.msg("auditing postings at %f" % (time.time() - start)) for posting in postings: if posting["direction"] == "debit": debitsum[posting["contract"]] += posting["quantity"] if posting["direction"] == "credit": creditsum[posting["contract"]] += posting["quantity"] for ticker in debitsum: if debitsum[ticker] - creditsum[ticker] is not 0: raise QUANTITY_MISMATCH # create the journal and postings # The journal is created separately from the postings but this is ok because # all the postings are created at once. If the posting commit fails then we'll # just end up with an empty journal which won't break anything # TODO: Create the journal and postings together log.msg("creating the journal at %f" % (time.time() - start)) ins = Journal.__table__.insert() result = self.execute(ins, type=types[0], timestamp=datetime.datetime.utcnow()) journal_id = result.inserted_primary_key[0] log.msg("creating the db postings at %f" % (time.time() - start)) db_postings = [] for posting in postings: contract_table = Contract.__table__ s = select([contract_table.c.id], contract_table.c.ticker==posting["contract"]) result = self.execute(s) contract_id = result.first()[0] user_table = User.__table__ s = select([user_table.c.type], user_table.c.username==posting["username"]) result = self.execute(s) user_type = result.first()[0] username = posting["username"] quantity = posting["quantity"] direction = posting["direction"] note = posting["note"] if posting["timestamp"] is not None: timestamp = util.timestamp_to_dt(posting["timestamp"]) else: timestamp = None if direction == 'debit': if user_type == 'Asset': sign = 1 else: sign = -1 else: if user_type == 'Asset': sign = -1 else: sign = 1 posting = {'username': username, 'contract_id': contract_id, 'quantity': sign * quantity, 'note': note, 'timestamp': timestamp, 'journal_id': journal_id } db_postings.append(posting) log.msg("done making posting at %f: %s" % (time.time() - start, posting)) ins = Posting.__table__.insert() result = self.execute(ins, db_postings) log.msg("Inserted %d rows of %d postings" % (result.rowcount, len(db_postings))) log.msg("Done committing postings at %f" % (time.time() - start)) return True except Exception, e: log.err("Caught exception trying to commit. Postings were:") for posting in postings: log.err(str(posting)) log.err("Stack trace follows:") log.err() if isinstance(e, SQLAlchemyError): raise DATABASE_ERROR raise e