def fundamental_value(self, orderbook): depth = Money('20', 'BTC') try: fundamental_value = midpoint.get_midpoint_from_orderbook( orderbook, depth) except: # Some very illiquid exchanges don't always have 20 BTC of depth. depth = Money('1', 'BTC') fundamental_value = midpoint.get_midpoint_from_orderbook( orderbook, depth) return fundamental_value
def test_bigger_with_depth(self): result = midpoint.get_midpoint_from_orderbook( self.bigger_book(), depth=Money('20', 'BTC'), ) result.should.equal(Money('252', 'USD'))
def test_basic_with_depth(self): result = midpoint.get_midpoint_from_orderbook( self.basic_book(price_currency='BTC', vol_currency='ETH'), depth=Money('0.5', 'ETH'), ) result.should.equal(Money('250', 'BTC'))
def orderbook_strength_at_slippage_in_usd(orderbook, order_type, slippage): """ Get the amount of liquidity (bitcoin denominated) available within [slippage] dollars of the top [order_type]. Returns a decimal. """ midpoint = midpoint_lib.get_midpoint_from_orderbook( orderbook, Money('20', 'BTC')) vol = Money('0', 'BTC') if order_type == Consts.BID: top_bid = orderbook['bids'][0] price = top_bid.price - slippage vol = available_volume.volume_available_at_price( Consts.ASK, price, orderbook) else: top_ask = orderbook['asks'][0] price = top_ask.price + slippage vol = available_volume.volume_available_at_price( Consts.BID, price, orderbook) return vol.amount * midpoint.amount
def midpoint_centered_fixed_spread(orderbook, spread, quote_depth=None): """ """ midpoint = midpoint_lib.get_midpoint_from_orderbook(orderbook, depth=quote_depth) bid_price = midpoint - (midpoint * spread) ask_price = midpoint + (midpoint * spread) return bid_price, ask_price
def orderbook_strength_at_slippages_in_usd(orderbook, order_type, slippages): """ Same as orderbook_strength_at_slippage but instead of returning the levels as a bitcoin-denominated quantity, we return the levels as to the usd value of the bitcoins at that level, which we definine as level * 20btc midpoint. Returns a decimal: decimal dict. """ midpoint = midpoint_lib.get_midpoint_from_orderbook( orderbook, Money('20', 'BTC')) slippage_lookup = {} if order_type == Consts.BID: top_bid = orderbook['bids'][0] prices = [top_bid.price - slippage for slippage in slippages] slippage_lookup = {(top_bid.price - slippage).amount: slippage for slippage in slippages} levels = available_volume.volume_available_at_prices( Consts.ASK, prices, orderbook, ) else: top_ask = orderbook['asks'][0] prices = [top_ask.price + slippage for slippage in slippages] slippage_lookup = {(top_ask.price + slippage).amount: slippage for slippage in slippages} levels = available_volume.volume_available_at_prices( Consts.BID, prices, orderbook, ) levels = { slippage_lookup[price].amount: value.amount * midpoint.amount for price, value in levels.items() } return levels
def tick(self, current_orders, eaten_order_ids): self.logger.debug("--Strategy Tick--") # Desk can be our runtime mock, when operating without exchange... ob, current_orders, eaten_order_ids = self.desk.tick( current_orders=current_orders, eaten_orders=eaten_order_ids) # NOTE : we currently minimize the number of simultaneous trades, to avoid unintended tricky behavior... balance = self.primary_exchange.get_balance() midpoint = midpoint_lib.get_midpoint_from_orderbook(ob) #self.midpoints += midpoint self.logger.info("Current midpoint of orderbook : " + str(midpoint)) self.logger.info("Ephemeral position: " + str(self.position_tracker.position)) # if self.position_tracker.position != self.primary_exchange.exchange_account.position: # self.logger.warning("Exchange position: " + str(self.primary_exchange.exchange_account.position)) # Preparing to enter BET... def on_bear_trend(): # Ultimately make decision #self.logger.info("Attempting to enter a new position...") #self.position_tracker.market_enter(bid_price=midpoint, bid_volume=self.base_volume) pass # TODO : reverse trend ? or only useful for mixed currency bag as quote ?? def on_bull_trend(): if not self.position_tracker.position or ( self.position_tracker.exited and not self.position_tracker.entering): # Ultimately make decision self.logger.info("Attempting to enter at market price...") self.position_tracker.market_enter(bid_volume=self.base_volume) self.market_observer.tick(midpoint, on_bull_trend=on_bull_trend, on_bear_trend=on_bear_trend) # If we currently track a position (from this run) if self.position_tracker.position and self.position_tracker.entered and not self.position_tracker.exiting: # we can attempt an exit exit_profit_price = self.position_tracker.exit_profit_price( quote_currency=self.quote_currency, volume_currency=self.volume_currency) self.logger.info("Targetted profit price: " + str(exit_profit_price)) if exit_profit_price: # TODO # if timeout: # else: if midpoint > exit_profit_price: #if midpoint is already above, exit at market ! self.logger.info( "Attempting to exit at targetted profit price...") self.position_tracker.market_exit() else: # else exit limit self.logger.info("Attempting to exit at market price...") self.position_tracker.limit_exit(exit_profit_price) else: self.logger.error("Cannot calculate exit profit price")
def test_bigger(self): result = midpoint.get_midpoint_from_orderbook(self.basic_book()) result.should.equal(Money('250', 'USD'))
def test_basic_crypto_crypto(self): result = midpoint.get_midpoint_from_orderbook( self.basic_book(price_currency='BTC', vol_currency='ETH'), ) result.should.equal(Money('250', 'BTC'))
def test_basic_non_btc(self): result = midpoint.get_midpoint_from_orderbook( self.basic_book(vol_currency='ETH'), ) result.should.equal(Money('250', 'USD'))
def tick(self, current_orders): self.logger.debug("--Strategy Tick--") self.logger.info("Current Orders: " + str(current_orders)) # Question : Can we detect fulfilled orders ? # upon fulfilled order we can increase spread base on volatility # otherwise we should probably decrease spread to get order fulfilled.... ob = self.primary_exchange.get_orderbook() self.midpoints += midpoint_lib.get_midpoint_from_orderbook(ob) # SAFETY # if (hasattr(self, 'last_ask_price') and self.last_ask_price < self.midpoint) or (hasattr(self, 'last_bid_price') and self.last_bid_price > self.midpoint): # # spread was not high enough ! We likely lost money here -> correct quickly # self.logger.warning("HIGH VOLATILITY encountered -> adjusting spread") # self.spread *= self.spread_coef_on_loss # #TODO : maybe terminate instead, with advice to change spread ? by some amount ? # TODO : calculate local volatility... (or reuse some indicator ??? -> see TA-lib) # Then based on volatility adjust spread | base_volume | tick_sleep, within exchange/user acceptable limits... # since we cancel and reopen order, we only need local volatility. # Note we probably do not want to slow down tick_sleep to not miss trend/volatility changes. # -> We should play on spread + base_volume only # OPTIMIZATION if self.midpoints.volatility(last=1): self.volat += self.midpoints.volatility( last=1) # TODO : since last successful order... if self.volat.last()[1] < 0: self.logger.info("Price went down. skipping this tick...") # TODO : manage bear markets with shorts and leverage... elif self.volat.last( )[1] > 0: # we only want bull micro market for now # We bet on the volatility to come to be the same as the one past, print("Volatility derivative :" + str(self.volat.deriv())) # TODO : define what value derivative of volatility should be # and set the spread based on that. self.spread = self.volat.last()[1] / 2 # TODO : reduce spread if expectation failed (order not passed), to maximize likelyhood to pass order... # TODO: increase spread if successful order passed, trying to maximize profit on volatile markets # for constant tick period, we need increased spread, and reduced base_volume #self.spread = self.spread_adjust_coef * (self.volat - self.last_volat) + self.spread # adjusting spread relative to volatility self.logger.info("Volatility: " + str(self.volat)) self.logger.info("Spread: " + str(self.spread)) # TODO : take fees into account to remain profitable exchange_fees = self.primary_exchange.exchange_wrapper.fee if self.spread < exchange_fees * self.base_volume: self.logger.info("Fees would be too high : " + str(exchange_fees * self.base_volume) + " vs spread of " + str(self.spread)) else: self.logger.info("Spread : " + str(self.spread) + " is larger than expected fees : " + str(exchange_fees * self.base_volume)) # Base volume increase means increased risk (maybe more than spread decrease). # We want to increase base volume when volatility doesnt change 'much'... # TODO : self.relative_volat_change = abs(self.volat - self.last_volat) / self.last_volat # relative change of base_volume # TODO : self.base_volume = self.base_volume_adjust_coef * (1-self.relative_volat_change) * self.base_volume + self.base_volume # TODO : have a minimum volume to exchange + slowly limit to that when volatility detected. # TODO : increase volume on non volatility/'less volatile than expected'... + profit ? how to measure that here ? # TODO: use profit measurement to adjust the adjust_coefs (machine learning stuff ?)... # #Note : the speed of adjustment is critical to be in front of order fullfilment. # -> Check control theory for a converging / asymptotic optimisation of control (yet easily reversible if needed...) bid_price, ask_price = mm.midpoint_centered_fixed_spread( ob, self.spread) # TODO : improve by doing everything based on ohlcv since last check... bid_volume, ask_volume = mm.simple_position_responsive_sizing( self.base_volume, self.position, ) self.logger.info("balance: " + str(self.primary_exchange.get_balance())) self.logger.info("bid volume: " + str(bid_volume) + " price: " + str(bid_price)) self.logger.info("ask volume: " + str(ask_volume) + " price: " + str(ask_price)) placeable_bid = self.primary_exchange.get_balance().get( bid_price.currency ).amount >= bid_price.amount * bid_volume.amount placeable_ask = self.primary_exchange.get_balance().get( ask_volume.currency).amount >= ask_volume.amount # TODO : maybe we do not need to cancel everything everytime ? self.primary_exchange.cancel_all_open_orders() # Place order only if we can... if placeable_bid: self.primary_exchange.limit_order( Consts.BID, bid_volume, bid_price) # Place order only if we can... if placeable_ask: self.primary_exchange.limit_order( Consts.ASK, ask_volume, ask_price) self.last_bid_price = bid_price self.last_ask_price = ask_price