# 2023-09-27 14:25:01
# Note: this file is to be used within profitview.net/trading/bots
# pylint: disable=locally-disabled, import-self, import-error, missing-class-docstring, invalid-name, consider-using-dict-items

from profitview import Link, http, logger

import json
import numpy as np
import pandas as pd
import scipy
import talib
from scipy.interpolate import interp1d
from talib import RSI
import asyncio
import time

class Trading(Link):
  UPDATE_SECONDS = 60
  MULTIPLIER = 1
  SHUT_IT_DOWN = False
  GRACEFUL_SHUTDOWN = True
  GRID_BIDS = (40, 30, 20)
  GRID_ASKS = (60, 70, 80)
  SRC = 'bitmex'                         # exchange name as in the Glossary
  VENUES = {
      'account1': {
        'XBTUSDT' : {
          'sym': 'XBTUSDT',
          'grid_size': 400000,                    # min is 1000 == 0.001 XBT
          'candles': {},
          'tob': (np.nan, np.nan),
          'max_risk': 400000*10,                  # Means max 10 times the base order size
          'current_risk': 0,
          'price_precision': 0.5,
          'price_decimals': 1,
          'direction': 'FLAT'
        },
        'XBTUSD' : {
          'sym': 'XBTUSD',
          'grid_size': 500,
          'candles': {},
          'tob': (np.nan, np.nan),
          'max_risk': 80000,
          'current_risk': 0,
          'price_precision': 0.5,
          'price_decimals': 1,
          'direction': 'FLAT'
        },
        'XBTU23' : {
          'sym': 'XBTU23',
          'grid_size': 400,
          'candles': {},
          'tob': (np.nan, np.nan),
          'max_risk': 8000,
          'current_risk': 0,
          'price_precision': 0.5,
          'price_decimals': 1,
          'direction': 'FLAT'
        },
        'XBTZ23' : {
          'sym': 'XBTZ23',
          'grid_size': 400,
          'candles': {},
          'tob': (np.nan, np.nan),
          'max_risk': 8000,
          'current_risk': 0,
          'price_precision': 0.5,
          'price_decimals': 1,
          'direction': 'FLAT'
        },
        'ETHUSD' : {
          'sym': 'ETHUSD',
          'grid_size': 20,                    # min is 1 Contract =~ 1.5 mXBT
          'candles': {},
          'tob': (np.nan, np.nan),
          'max_risk': 20*20,                  # Means max 20 times the base order size
          'current_risk': 0,
          'price_precision': 0.05,
          'price_decimals': 2,
          'direction': 'FLAT'
        },
        'LINKUSD' : {
          'sym': 'LINKUSD',
          'grid_size': 200,
          'candles': {},
          'tob': (np.nan, np.nan),
          'max_risk': 4000,
          'current_risk': 0,
          'price_precision': 0.001,
          'price_decimals': 3,
          'direction': 'FLAT'
        },
        'XRPUSD': {
          'sym': 'XRPUSD',
          'grid_size': 100,
          'candles': {},
          'tob': (np.nan, np.nan),
          'max_risk': 2000,
          'current_risk': 0,
          'price_precision': 0.0001,
          'price_decimals': 4,
          'direction': 'FLAT'
        },
        'SOLUSDT' : {
          'sym': 'SOLUSDT',
          'grid_size': 500000,                    # min is 1000 == 0.1 SOL
          'candles': {},
          'tob': (np.nan, np.nan),
          'max_risk': 500000*20,
          'current_risk': 0,
          'price_precision': 0.01,
          'price_decimals': 2,
          'direction': 'FLAT'
        },
        'ETHUSDT' : {
          'sym': 'ETHUSDT',
          'grid_size': 200000,
          'candles': {},
          'tob': (np.nan, np.nan),
          'max_risk': 2000000,
          'current_risk': 0,
          'price_precision': 0.05,
          'price_decimals': 2,
          'direction': 'FLAT'
        }
      },
      'account2': {
        'XBTUSD' : {
          'sym': 'XBTUSD',
          'grid_size': 400,
          'candles': {},
          'tob': (np.nan, np.nan),
          'max_risk': 300000,
          'current_risk': 0,
          'price_precision': 0.5,
          'price_decimals': 1,
          'direction': 'LONG'
        },
        'XBTU23' : {
          'sym': 'XBTU23',
          'grid_size': 400,
          'candles': {},
          'tob': (np.nan, np.nan),
          'max_risk': 30000,
          'current_risk': 0,
          'price_precision': 0.5,
          'price_decimals': 1,
          'direction': 'LONG'
        }
      }
    }

  def __init__(self):
    super().__init__()
    self.on_start()

  def on_start(self):
    for venue in self.VENUES:
      for sym in self.VENUES[venue]:
        candles = self.fetch_candles(venue, sym, level='1m')
        self.VENUES[venue][sym]['candles'] = {x['time']: x['close'] for x in candles['data']} | self.VENUES[venue][sym]['candles']
    asyncio.run(self.minutely_update())

  def hypo_rsi(self, closes, ret):
    return RSI(np.append(closes, [closes[-1] * (1 + ret)]))[-1]

  async def minutely_update(self):
    while True :
      self.fetch_current_risk()
      await self.update_limit_orders()
      await asyncio.sleep(self.UPDATE_SECONDS)

  def log_current_risk(self, venue):
    current_risk = 0
    current_risk_type = 'XBT'

    for sym in self.VENUES[venue]:
      if sym.endswith('USD') :
        current_risk = self.VENUES[venue][sym]['current_risk']
        current_risk_type = 'XBT'
      else:
        current_risk = self.VENUES[venue][sym]['current_risk']
        current_risk_type = 'USDT'

      logger.info(f'\n{venue} - {sym} current risk: {current_risk} {current_risk_type}')

  def fetch_current_risk(self):
    for venue in self.VENUES:
      positions=self.fetch_positions(venue)
      if positions :
        for x in positions['data']:
          if x['sym'] in self.VENUES[venue]:
            self.VENUES[venue][x['sym']]['current_risk'] = x['pos_size']
      # self.log_current_risk(venue)

  def remove_duplicates(self, arr):
    unique_items = list(set(arr))
    return unique_items

  def round_value(self, x, tick, decimals=0):
    return np.round(tick * np.round(x / tick), decimals)

  def orders_intent(self, sym):
    tob_bid, tob_ask = sym['tob']
    times, closes = zip(*sorted(sym['candles'].items())[-100:])
    closes = list(filter(None,closes))
    X = np.linspace(-0.2, 0.2, 100)
    Y = [self.hypo_rsi(closes, x) for x in X]
    func = interp1d(Y, X, kind='cubic', fill_value='extrapolate')
    #logger.info('\n' + json.dumps({
    #	'sym': sym['sym'],
    #	'total': 0.5 * round(closes[-1] * (1 + float(func(60))) / 0.5),
    #	'close': closes[-1],
    #	'closeLength': len(closes),
    #	'func': (1 + float(func(60))),
    #	'tob': tob_ask,
    #	'final_closest_bid': np.max([tob_ask, 0.5 * round(closes[-1] * (1 + float(func(60))) / 0.5)]),
    #	'current_risk': sym['current_risk']
    #}))

    orders = {
      'bids': [np.min([tob_bid, self.round_value(0.5 * round(closes[-1] * (1 + float(func(x))) / 0.5,4),sym['price_precision'], sym['price_decimals'])]) for x in self.GRID_BIDS],
      'asks': [np.max([tob_ask, self.round_value(0.5 * round(closes[-1] * (1 + float(func(x))) / 0.5,4),sym['price_precision'], sym['price_decimals'])]) for x in self.GRID_ASKS]
    }
    orders['bids'] = self.remove_duplicates(orders['bids'])
    orders['asks'] = self.remove_duplicates(orders['asks'])

    logger.info('sym:' + sym['sym'] + json.dumps(orders))
    return orders

  async def update_limit_orders(self):
    for venue in self.VENUES:
      for sym in self.VENUES[venue]:
        tob_bid, tob_ask =  self.VENUES[venue][sym]['tob']
        if(np.isnan(tob_bid) or np.isnan(tob_ask)):
          continue
        intent = self.orders_intent(self.VENUES[venue][sym])
        bids = intent['bids']
        asks = intent['asks']

        #cancel all current orders
        self.cancel_order(venue, sym=sym)

        # Buy orders
        if (abs(self.VENUES[venue][sym]['current_risk']) <  self.VENUES[venue][sym]['max_risk']) or (self.VENUES[venue][sym]['current_risk'] <= 0):
          if self.SHUT_IT_DOWN :
            if self.GRACEFUL_SHUTDOWN :
              bid = tob_bid
              execInst = 'Close,ParticipateDoNotInitiate'
            else :
              bid = tob_bid
              execInst = 'Close'

            try:
              self.call_endpoint(
                venue,
                'order',
                'private',
                method='POST', params={
                  'symbol': sym,
                  'side': 'Buy',
                  'price': bid,
                  'ordType': 'Limit',
                  'execInst': execInst,
                  'text': 'Sent from ProfitView.net'
                })
            except Exception as e:
              logger.error(e)
          else :
            # If I have a current open position in the opposite direction, double the order size
            multiplier = 1
            if(self.VENUES[venue][sym]['current_risk'] <= 0):
              multiplier = self.MULTIPLIER
            if(self.VENUES[venue][sym]['direction'] == 'SHORT'):
              execInst = 'ParticipateDoNotInitiate,ReduceOnly'
            else:
              execInst = 'ParticipateDoNotInitiate'
            for bid in bids:
              try:
                self.call_endpoint(
                  venue,
                  'order',
                  'private',
                  method = 'POST',
                  params = {
                    'symbol': sym,
                    'side': 'Buy',
                    'orderQty': self.VENUES[venue][sym]['grid_size'] * multiplier,
                    'price': bid,
                    'ordType': 'Limit',
                    'execInst': execInst,
                    'text': 'Sent from ProfitView.net'
                  }
                )
              except Exception as e:
                logger.error(e)

        # Sell orders
        if (abs(self.VENUES[venue][sym]['current_risk']) <  self.VENUES[venue][sym]['max_risk']) or (self.VENUES[venue][sym]['current_risk'] >= 0):
          if self.SHUT_IT_DOWN :
            if self.GRACEFUL_SHUTDOWN :
              bid = tob_ask
              execInst = 'Close,ParticipateDoNotInitiate'
            else :
              bid = tob_ask
              execInst = 'Close'

            try:
              self.call_endpoint(
                venue,
                'order',
                'private',
                method='POST', params={
                  'symbol': sym,
                  'side': 'Sell',
                  'price': bid,
                  'ordType': 'Limit',
                  'execInst': execInst,
                  'text': 'Sent from ProfitView.net'
                })
            except Exception as e:
              logger.error(e)
          else :
            # If I have a current open position in the opposite direction, double the order size
            multiplier = 1
            if(self.VENUES[venue][sym]['current_risk'] >= 0):
              multiplier = self.MULTIPLIER
            if(self.VENUES[venue][sym]['direction'] == 'LONG'):
              execInst = 'ParticipateDoNotInitiate,ReduceOnly'
            else:
              execInst = 'ParticipateDoNotInitiate'
            for ask in asks:
              try:
                self.call_endpoint(
                  venue,
                  'order',
                  'private',
                  method='POST', params={
                    'symbol': sym,
                    'side': 'Sell',
                    'orderQty': self.VENUES[venue][sym]['grid_size'] * multiplier,
                    'price': ask,
                    'ordType': 'Limit',
                    'execInst': execInst,
                    'text': 'Sent from ProfitView.net'
                  })
              except Exception as e:
                logger.error(e)

        await asyncio.sleep(3)

  def trade_update(self, src, sym, data):
    for venue in self.VENUES:
      if sym in self.VENUES[venue]:
        self.VENUES[venue][sym]['candles'][self.candle_bin(data['time'], '1m')] = data['price']

  def quote_update(self, src, sym, data):
    # logger.info('\n' + json.dumps(data))
    for venue in self.VENUES:
      if sym in self.VENUES[venue]:
        self.VENUES[venue][sym]['tob'] = (data['bid'][0], data['ask'][0])

src/grid-bots/multi-vendor-multi-contract.py
profitviews/bitmex-algo-academy
# Note: this file is to be used within profitview.net/trading/bots

from profitview import Link, http, logger

import json
import numpy as np
import pandas as pd
import scipy
import talib
import threading
import time


TIME_LOOKUP = {
    '1m':  60_000,
    '15m': 60_000 * 15,
    '1h':  60_000 * 60,
    '1d':  60_000 * 60 * 24,
}

# GARCH parameters
OMEGA = 1.605025e-08
ALPHA = 0.193613
BETA = 0.786155


def debounce(wait):
    """Postpone a function execution until after wait seconds
    have elapsed since the last time it was invoked.
    source: https://gist.github.com/walkermatt/2871026"""
    def decorator(func):
        def debounced(*args, **kwargs):
            def call_func():
                debounced.last_call = time.time()
                func(*args, **kwargs)

            if hasattr(debounced, 'timer'):
                debounced.timer.cancel()

            if time.time() - getattr(debounced, 'last_call', 0) > wait:
                call_func()
            else:
                debounced.timer = threading.Timer(wait, call_func)
                debounced.timer.start()

        return debounced
    return decorator



class Trading(Link):

    def __init__(self):
        super().__init__()
        # ALGO PARAMS
        self.src = 'bitmex'                         # exchange name
        self.venue = 'BitMEX - Mr Bath'                         # API key name
        self.sym = 'XBTUSDT'                          # symbol we will trade
        self.level = '1m'                             # OHLC candle granularity
        self.lookback = 150                         # lookback period of close prices
        self.time_step = TIME_LOOKUP[self.level]     # time step in milliseconds

        # ALGO STRATEGY STATE
        self.closes = dict()                         # time bin -> close price
        self.macd = dict(hist=np.nan, slope=np.nan) # MACD histogram and slope value
        self.var_t1 = np.nan                         # t-1 variance forecast
        self.tob = (np.nan, np.nan)                 # top of book [bid, ask] price
        self.mid = np.nan                             # current mid price
        self.risk = 0                                 # current position risk for sym
        self.orders = dict(bid={}, ask={})            # current open limit orders

        # ALGO PARAMS
        self.skew_damp = 2                            # skew dampening
        self.max_risk = 1600000                         # max position risk limit
        self.min_spread = 1e-4
        self.sharpe_target = 1.2                        # target sharpe ratio
        self.fee_cost = -0.0002                       # cost of entering and exiting position

		self.size_precision = 1000					# min step for size
		self.price_precision = 0.5					# min step for price
		self.price_decimals = 1						# decimals to round of for price


        # RUN ON STARTUP
        self.on_startup()

    @property
    def time_bin_now(self):
        return self.candle_bin(self.epoch_now, self.level)

    # MARKET DATA STATE
    def on_startup(self):
		self.fetch_positions(self.venue)
        self.fetch_latest_closes()
        self.fetch_current_risk()
        self.init_garch_var()
        self.minutely_update()

    def fetch_latest_closes(self):
        for x in self.fetch_candles(self.venue, sym=self.sym, level=self.level)['data']:
            if x['time'] not in self.closes:
                self.closes[x['time']] = x['close']

    def fetch_current_risk(self):
        for x in self.fetch_open_orders(self.venue)['data']:
            if x['sym'] == self.sym:
                key = 'bid' if x['side'] == 'Buy' else 'ask'
                self.orders[key][x['order_id']] = x

        for x in self.fetch_positions(self.venue)['data']:
            if x['sym'] == self.sym:
                sign = 1 if x['side'] == 'Buy' else -1
                self.risk = sign * x['pos_size']

    def init_garch_var(self):
        closes = self.last_closes
        shifted = np.append([np.nan], closes[1:])
        returns = np.log(closes / shifted)
        returns = returns[~np.isnan(returns)]
        self.var_t1 = np.std(returns) ** 2

    def garch_var(self, lookback=0):
        p_1, p_0, *_ = self.last_closes[-2-lookback:]
        y2 = np.log(p_0 / p_1) ** 2
        return OMEGA + ALPHA * y2 + BETA * self.var_t1

    @property
    def spread(self):
        norm_std = np.exp(np.sqrt(self.garch_var())) - 1
        return np.max([self.min_spread, self.sharpe_target * norm_std + self.fee_cost])

    def minutely_update(self):
        # update close prices every one second after each minute
        if self.time_bin_now not in self.closes:
            self.closes[self.time_bin_now] = self.last_closes[-1]
            self.update_signal()

        # update var_t1 using (t-2, t-1) closes
        self.var_t1 = self.garch_var(lookback=1)

        threading.Timer(61 - self.second, self.minutely_update).start()

    @property
    def last_closes(self):
        start_time = self.time_bin_now - self.lookback * self.time_step
        times = [start_time + (i + 1) * self.time_step for i in range(self.lookback)]
        closes = [self.closes.get(x, np.nan) for x in times]
        return np.array(pd.Series(closes).ffill())

    def update_signal(self):
        closes = self.last_closes
        if not any(np.isnan(closes)):
            macd, signal, hist = talib.MACD(self.last_closes)
            N = 10 # less points required to interpolate accurately
            try:
                cubic = scipy.interpolate.CubicSpline(range(N), hist[-N:])
                self.macd['hist'] = hist[-1]
                self.macd['slope'] = float(cubic(N-1, 1))

            except Exception as e:
                logger.error(f'unable to update signal - {e}', exc_info=True)

        else:
            logger.info(f'unable to compute signal as closes not initialized')

    def update_close(self, data):
        time_bin = self.candle_bin(data['time'], self.level)
        self.closes[time_bin] = data['price']

    def round_value(self, x, tick):
        return tick * np.round(x / tick)

    @property
    def orders_intent(self):
        tob_bid, tob_ask = self.tob
        half_spread = 0.5 * self.mid * self.spread
        skew = np.clip(self.macd['slope'] / self.skew_damp, -1, 1)
        bid = self.mid - half_spread * (1 - skew)
        ask = self.mid + half_spread * (1 + skew)
        bid = np.min([tob_bid, self.round_value(bid, 0.5)])
        ask = np.max([tob_ask, self.round_value(ask, 0.5)])
        bsize = np.clip(self.max_risk - self.risk, 0, self.max_risk) if self.risk < self.max_risk else 0
        asize = np.clip(self.max_risk + self.risk, 0, self.max_risk) if self.risk < self.max_risk else self.max_risk
		bsize = self.round_value(bsize, self.size_precision)
		asize = self.round_value(asize, self.size_precision)
		logger.info('\n' + json.dumps({'bid': (bid, bsize), 'ask': (ask, asize)}))
        return {'bid': (bid, bsize), 'ask': (ask, asize)}

    @debounce(1)
    def update_limit_orders(self):
        intent = self.orders_intent
        bid, bsize = intent['bid']
        ask, asize = intent['ask']

        log_msg = {
            'hist': np.round(self.macd['hist'], 4),
            'slope': np.round(self.macd['slope'], 2),
            'spread': np.round(1e4 * self.spread, 1),
            'bid': [bid, bsize],
            'ask': [ask, asize]
        }

        if not any(np.isnan((bid, ask, asize, bsize))):
            cancels, updates, inserts = [], [], []

            # call update or insert API for closest side first
            shift = int(np.log(self.mid / bid) > np.log(ask / self.mid))

            for key in np.roll(['bid', 'ask'], shift):
                sort_key = lambda x: (1 if key == 'ask' else -1) * x['order_price']
                orders = sorted(self.orders[key].values(), key=sort_key)

                price, size = intent[key]
                side = 'Sell' if key == 'ask' else 'Buy'

                if len(orders) > 0:
                    update = dict()
                    top, *remain = orders

                    if price != top['order_price']: update['price'] = price
                    if size != top['remain_size']: update['size'] = size

                    if len(update) > 0:
                        update['order_id'] = top['order_id']
                        cancels.append(update['order_id'])
						inserts.append({'sym': self.sym, 'side': side, 'size': size, 'price': price})

                    cancels.extend([x['order_id'] for x in remain])

                elif size > 0:
                    inserts.append({'sym': self.sym, 'side': side, 'size': size, 'price': price})

            for order_id in cancels:
                for x in self.cancel_order(self.venue, order_id=order_id)['data']:
                    key = 'bid' if x['side'] == 'Buy' else 'ask'
                    self.orders[key].pop(x['order_id'], None)

            for update in updates:
				try:
					data = self.amend_order(self.venue, **update)['data']
					key = 'bid' if data['side'] == 'Buy' else 'ask'
					self.orders[key][data['order_id']] = data
				except:
					logger.info("cannot amend order "+ json.dumps(update))

            for insert in inserts:
                data = self.create_limit_order(self.venue, **insert)['data']
                key = 'bid' if data['side'] == 'Buy' else 'ask'
                self.orders[key][data['order_id']] = data

        logger.info('\n' + json.dumps(log_msg))

    # TRADING EVENTS
    def order_update(self, src, sym, data):
        if sym == self.sym:
            if data.get('remain_size') == 0:
                key = 'bid' if data['side'] == 'Buy' else 'ask'
                self.orders[key].pop(data['order_id'], None)

    def fill_update(self, src, sym, data):
        if sym == self.sym:
            sign = 1 if data['side'] == 'Buy' else -1
            self.risk = round(self.risk + sign * data['fill_size'])
            self.update_limit_orders() # update limit orders on risk change

    def quote_update(self, src, sym, data):
        if sym == self.sym:
            self.tob = (data['bid'][0], data['ask'][0])
            if (mid := np.mean(self.tob)) != self.mid:
                self.mid = mid
                self.update_limit_orders()

    def trade_update(self, src, sym, data):
        if sym == self.sym:
            self.update_close(data)
            self.update_signal()

    # WEBHOOKS
    @http.route
    def get_state(self, data):
        return {k: getattr(self, k, None) for k in data.get('keys', [])}

    @http.route
    def post_state(self, data):
        for k, v in data.items():
            if hasattr(self, k):
                setattr(self, k, v)
        return self.get_state({'keys': list(data)})

src/mm-bots/macd-xbtusdt.py
profitviews/bitmex-algo-academy
# Note: this file is to be used within profitview.net/trading/bots

from profitview import Link, http, logger

import json
import numpy as np
import pandas as pd
import scipy
import talib
from scipy.interpolate import interp1d
from talib import RSI
import threading
import time


def debounce(wait):
    """Postpone a function execution until after wait seconds
    have elapsed since the last time it was invoked.
    source: https://gist.github.com/walkermatt/2871026"""
    def decorator(func):
        def debounced(*args, **kwargs):
            def call_func():
                debounced.last_call = time.time()
                func(*args, **kwargs)

            if hasattr(debounced, 'timer'):
                debounced.timer.cancel()

            if time.time() - getattr(debounced, 'last_call', 0) > wait:
                call_func()
            else:
                debounced.timer = threading.Timer(wait, call_func)
                debounced.timer.start()

        return debounced
    return decorator



class Trading(Link):

    def __init__(self):
        super().__init__()
        # ALGO PARAMS
        self.src = 'BitMEX'
		self.venue =  'BitMEX'

		self.XBTUSDT = {
			'sym': 'XBTUSDT',
			'order_size': 380000,
			'tob': (np.nan, np.nan),
			'current_risk': 0,
			'price_precision': 0.5,
			'price_decimals': 1
		}
		self.XBTUSD = {
			'sym': 'XBTUSD',
			'order_size': 10000,
			'tob': (np.nan, np.nan),
			'current_risk': 0,
			'price_precision': 0.5,
			'price_decimals': 1
		}
        self.on_start()

	def on_start(self):
    	while (True):
			self.fetch_current_risk()
			self.update_limit_orders()
			time.sleep(2)

    def fetch_current_risk(self):
		self.XBTUSDT['current_risk'] = 0
		self.XBTUSD['current_risk'] = 0
		for x in self.fetch_positions(self.venue)['data']:
			if x['sym'] == 'XBTUSDT':
				self.XBTUSDT['current_risk'] = x['pos_size']
			if x['sym'] == 'XBTUSD':
				self.XBTUSD['current_risk'] = x['pos_size']



	def round_value(self, x, tick, decimals=0):
		return np.round(tick * np.round(x / tick), decimals)



    def update_limit_orders(self):
		xbttob_bid, xbttob_ask = self.XBTUSD['tob']
		usdttob_bid, usdttob_ask = self.XBTUSDT['tob']
		if(np.isnan(usdttob_bid) or np.isnan(usdttob_ask) or np.isnan(xbttob_bid) or np.isnan(xbttob_ask)):
			return

		# check if I have an open position on XBTUSDT and XBTUSD, If I don't have an XBTUSDT position but do have a XBTUSD position, I need to close this XBTUSD position out asap
		self.cancel_order(self.venue, sym='XBTUSDT')
		self.cancel_order(self.venue, sym='XBTUSD')
		logger.info(json.dumps({
			'XBTUSDT': self.XBTUSDT['current_risk'],
			'XBTUSD': self.XBTUSD['current_risk']
		}))
		if(self.XBTUSDT['current_risk'] == 0 and self.XBTUSD['current_risk'] != 0):
			tob_bid, tob_ask = self.XBTUSD['tob']
			# check the side
			side = 'Buy'
			if(self.XBTUSD['current_risk'] > 0):
				side = 'Sell'
			self.call_endpoint(
				self.venue,
				'order',
				'private',
				method='POST',
				params={
					'symbol': 'XBTUSD',
					'side': side,
					'orderQty': abs(self.XBTUSD['current_risk']),
					'ordType': 'Market',
					'text': 'Sent from ProfitView.net'
				}
			)
	   #If I have a position on XBTUSDT and I dont have a position on XBTUSD, then I need to open a position on XBTUSD asap
		elif(self.XBTUSD['current_risk'] == 0 and self.XBTUSDT['current_risk'] != 0):
			tob_bid, tob_ask = self.XBTUSD['tob']
			side = 'Buy'
			if(self.XBTUSDT['current_risk'] > 0):
				side = 'Sell'

			self.call_endpoint(
				self.venue,
				'order',
				'private',
				method='POST',
				params={
					'symbol': 'XBTUSD',
					'side': side,
					'orderQty': self.round_value(abs(self.XBTUSDT['current_risk']) / 38, self.XBTUSD['price_precision'], self.XBTUSD['price_decimals']),
					'ordType': 'Market',
					'text': 'Sent from ProfitView.net'
				}
			)
		#If I have a position on both XBTUSDT and XBTUSD, I need to place a top of book limit order for XBTUSDT to try and close out the posiiton
		elif(self.XBTUSD['current_risk'] != 0 and self.XBTUSDT['current_risk'] != 0):
			tob_bid, tob_ask = self.XBTUSDT['tob']
			side = 'Buy'
			price = tob_bid
			if(self.XBTUSDT['current_risk'] > 0):
				side = 'Sell'
				price = tob_ask
			self.call_endpoint(
				self.venue,
				'order',
				'private',
				method='POST',
				params={
					'symbol': 'XBTUSDT',
					'side': side,
					'orderQty': abs(self.XBTUSDT['current_risk']),
					'price': price,
					'ordType': 'Limit',
					'execInst': 'ParticipateDoNotInitiate',
					'text': 'Sent from ProfitView.net'
				}
			)
		#If I don't have a position on either XBTUSDT or XBTUSD, open two orders at the top of the books for XBTUSDT
		elif(self.XBTUSD['current_risk'] == 0 and self.XBTUSDT['current_risk'] == 0):
			tob_bid, tob_ask = self.XBTUSDT['tob']
			self.call_endpoint(
				self.venue,
				'order',
				'private',
				method='POST',
				params={
					'symbol': 'XBTUSDT',
					'side': 'Buy',
					'orderQty': self.XBTUSDT['order_size'],
					'price': tob_bid,
					'ordType': 'Limit',
					'execInst': 'ParticipateDoNotInitiate',
					'text': 'Sent from ProfitView.net'
				}
			)
			self.call_endpoint(
				self.venue,
				'order',
				'private',
				method='POST',
				params={
					'symbol': 'XBTUSDT',
					'side': 'Sell',
					'orderQty': self.XBTUSDT['order_size'],
					'price': tob_ask,
					'ordType': 'Limit',
					'execInst': 'ParticipateDoNotInitiate',
					'text': 'Sent from ProfitView.net'
				}
			)

	def quote_update(self, src, sym, data):
		if sym == 'XBTUSDT':
			self.XBTUSDT['tob'] = (data['bid'][0], data['ask'][0])
		if sym == 'XBTUSD':
			self.XBTUSD['tob'] = (data['bid'][0], data['ask'][0])



src/volume-bots/xbt.py
profitviews/bitmex-algo-academy