def get_order(self, time, countdown, lob): """ Creates a GDX trader's order :param time: Current time :param countdown: Time left in the current trading period :param lob: Current state of the limit order book :return: Order to be sent to the exchange """ if len(self.orders) < 1: self.active = False order = None else: coid = max(self.orders.keys()) self.active = True self.limit = self.orders[coid].price self.job = self.orders[coid].otype # calculate price if self.job == 'Bid': self.price = self.calc_p_bid(self.holdings - 1, self.remaining_offer_ops - 1) if self.job == 'Ask': self.price = self.calc_p_ask(self.holdings - 1, self.remaining_offer_ops - 1) order = Order(self.tid, self.job, int(self.price), self.orders[coid].qty, time, self.orders[coid].coid, self.orders[coid].toid) self.last_quote = order if self.first_turn or self.price == -1: return None return order
def get_order(self, time, countdown, lob): """ :param time: Current time :param countdown: Time until end of current market session :param lob: Limit order book :return: Trader order to be sent to exchange """ if len(self.orders) < 1: self.active = False order = None else: coid = max(self.orders.keys()) self.active = True self.limit = self.orders[coid].price self.job = self.orders[coid].otype if self.job == 'Bid': # currently a buyer (working a bid order) self.margin = self.margin_buy else: # currently a seller (working a sell order) self.margin = self.margin_sell quote_price = int(self.limit * (1 + self.margin)) self.price = quote_price order = Order(self.tid, self.job, quote_price, self.orders[coid].qty, time, self.orders[coid].coid, self.orders[coid].toid) self.last_quote = order return order
def get_order(self, time, countdown, lob): """ :param time: Current time :param countdown: Time until end of market session :param lob: Limit order book :return: Trader order to be sent to exchange """ lurk_threshold = 0.2 shave_growth_rate = 3 shave = int(1.0 / (0.01 + countdown / (shave_growth_rate * lurk_threshold))) if (len(self.orders) < 1) or (countdown > lurk_threshold): order = None else: coid = max(self.orders.keys()) limit_price = self.orders[coid].price otype = self.orders[coid].otype if otype == 'Bid': if lob['bids']['n'] > 0: quote_price = lob['bids']['best'] + shave quote_price = min(quote_price, limit_price) else: quote_price = lob['bids']['worst'] else: if lob['asks']['n'] > 0: quote_price = lob['asks']['best'] - shave quote_price = min(quote_price, limit_price) else: quote_price = lob['asks']['worst'] order = Order(self.tid, otype, quote_price, self.orders[coid].qty, time, self.orders[coid].coid, self.orders[coid].toid) self.last_quote = order return order
def get_order(self, time, countdown, lob): """ Get's Shaver trader order by shaving/adding a penny to current best bid :param time: Current time :param countdown: Countdown to end of market session :param lob: Limit order book :return: The trader order to be sent to the exchange """ if len(self.orders) < 1: order = None else: coid = max(self.orders.keys()) limit_price = self.orders[coid].price otype = self.orders[coid].otype if otype == 'Bid': if lob['bids']['n'] > 0: quote_price = lob['bids']['best'] + 1 quote_price = min(quote_price, limit_price) else: quote_price = lob['bids']['worst'] else: if lob['asks']['n'] > 0: quote_price = lob['asks']['best'] - 1 quote_price = min(quote_price, limit_price) else: quote_price = lob['asks']['worst'] order = Order(self.tid, otype, quote_price, self.orders[coid].qty, time, self.orders[coid].coid, self.orders[coid].toid) self.last_quote = order return order
def get_order(self, time, countdown, lob): """ Gets ZIC trader, limit price is randomly selected :param time: Current time :param countdown: Time until end of current market session :param lob: Limit order book :return: The trader order to be sent to the exchange """ if len(self.orders) < 1: # no orders: return NULL order = None else: coid = max(self.orders.keys()) min_price = lob['bids']['worst'] max_price = lob['asks']['worst'] limit = self.orders[coid].price otype = self.orders[coid].otype if otype == 'Bid': quote_price = random.randint(min_price, limit) else: quote_price = random.randint(limit, max_price) # NB should check it == 'Ask' and barf if not order = Order(self.tid, otype, quote_price, self.orders[coid].qty, time, self.orders[coid].coid, self.orders[coid].toid) self.last_quote = order return order
def get_order(self, time, countdown, lob): """ Get's giveaway traders order - in this case the price is just the limit price from the customer order :param time: Current time :param countdown: Time until end of session :param lob: Limit order book :return: Order to be sent to the exchange """ if len(self.orders) < 1: order = None else: coid = max(self.orders.keys()) quote_price = self.orders[coid].price order = Order(self.tid, self.orders[coid].otype, quote_price, self.orders[coid].qty, time, self.orders[coid].coid, self.orders[coid].toid) self.last_quote = order return order
def get_order(self, time, countdown, lob): """ Creates an AA trader's order :param time: Current time :param countdown: Time left in the current trading period :param lob: Current state of the limit order book :return: Order to be sent to the exchange """ if len(self.orders) < 1: self.active = False return None coid = max(self.orders.keys()) self.active = True self.limit = self.orders[coid].price self.job = self.orders[coid].otype self.calc_target() if self.prev_best_bid_p is None: o_bid = 0 else: o_bid = self.prev_best_bid_p if self.prev_best_ask_p is None: o_ask = self.market_max else: o_ask = self.prev_best_ask_p quote_price = TBSE_SYS_MIN_PRICE if self.job == 'Bid': # BUYER if self.limit <= o_bid: return None if len(self.previous_transactions ) > 0: # has been at least one transaction o_ask_plus = (1 + self.r_shout_change_relative ) * o_ask + self.r_shout_change_absolute quote_price = o_bid + ((min(self.limit, o_ask_plus) - o_bid) / self.offer_change_rate) else: if o_ask <= self.buy_target: quote_price = o_ask else: quote_price = o_bid + ( (self.buy_target - o_bid) / self.offer_change_rate) elif self.job == 'Ask': if self.limit >= o_ask: return None if len(self.previous_transactions ) > 0: # has been at least one transaction o_bid_minus = (1 - self.r_shout_change_relative ) * o_bid - self.r_shout_change_absolute quote_price = o_ask - ((o_ask - max(self.limit, o_bid_minus)) / self.offer_change_rate) else: if o_bid >= self.sell_target: quote_price = o_bid else: quote_price = o_ask - ( (o_ask - self.sell_target) / self.offer_change_rate) order = Order(self.tid, self.job, int(quote_price), self.orders[coid].qty, time, self.orders[coid].coid, self.orders[coid].toid) self.last_quote = order return order
def customer_orders(time, coid, traders, trader_stats, order_sched, pending, verbose): """ Produce and distribute customer orders to traders Mostly unaltered from original BSE code by Dave Cliff :param time: current curr_time :param coid: last used customer order ID :param traders: List of traders :param trader_stats: number of buyers and number of sellers :param order_sched: order schedule :param pending: pending orders to be distributed :param verbose: should verbose logging be printed to console :return: List containing left over pending orders, cancellations to be made and the final customer ID used """ def sys_min_check(price): """ Check if order price is below system minimum price and sets price to be minimum if it is :param price: Order price :return: new order price """ if price < TBSE_SYS_MIN_PRICE: print('WARNING: price < bse_sys_min -- clipped') price = TBSE_SYS_MIN_PRICE return price def sys_max_check(price): """ Check if order price is above system maximum price and sets price to be maximum if it is :param price: Order price :return: new order price """ if price > TBSE_SYS_MAX_PRICE: print('WARNING: price > bse_sys_max -- clipped') price = TBSE_SYS_MAX_PRICE return price def get_order_price(i, schedule, schedule_end, n, stepmode, time_of_issue): """ Calculates order price for a new customer order :param i: Index of either buyer or seller trader :param schedule: Order schedule :param schedule_end: End curr_time of order schedule :param n: number of buyers or sellers :param stepmode: Stepmode of order schedule :param time_of_issue: Time order should be issued after :return: Order price """ # pylint: disable=too-many-branches,too-many-statements if config.useInputFile: if len(schedule[0]) > 2: offset_function = schedule[0][2][0] offset_function_params = [schedule_end] + list( schedule[0][2][1]) if callable(offset_function): # same offset for min and max offset_min = offset_function(time_of_issue, offset_function_params) offset_max = offset_min else: sys.exit( 'FAIL: 3rd argument of schedule in get_order_price() should be [callable_fn [params]]' ) if len(schedule[0]) > 3: # if second offset function is specfied, that applies only to the max value offset_function = schedule[0][3][0] offset_function_params = [schedule_end] + list( schedule[0][3][1]) if callable(offset_function): # this function applies to max offset_max = offset_function(time_of_issue, offset_function_params) else: sys.exit( 'FAIL: 4th argument of schedule in get_order_price() should be [callable_fn [params]]' ) else: offset_min = 0.0 offset_max = 0.0 else: # does the first schedule range include optional dynamic offset function(s)? if len(schedule[0]) > 2: offset_function = schedule[0][2] if callable(offset_function): # same offset for min and max offset_min = offset_function(time_of_issue) offset_max = offset_min else: sys.exit( 'FAIL: 3rd argument of schedule in get_order_price() not callable' ) if len(schedule[0]) > 3: # if second offset function is specfied, that applies only to the max value offset_function = schedule[0][3] if callable(offset_function): # this function applies to max offset_max = offset_function(time_of_issue) else: sys.exit( 'FAIL: 4th argument of schedule in get_order_price() not callable' ) else: offset_min = 0.0 offset_max = 0.0 p_min = sys_min_check(offset_min + min(schedule[0][0], schedule[0][1])) p_max = sys_max_check(offset_max + max(schedule[0][0], schedule[0][1])) p_range = p_max - p_min step_size = p_range / (n - 1) half_step = round(step_size / 2.0) if stepmode == 'fixed': new_order_price = p_min + int(i * step_size) elif stepmode == 'jittered': new_order_price = p_min + int(i * step_size) + random.randint( -half_step, half_step) elif stepmode == 'random': if len(schedule) > 1: # more than one schedule: choose one equiprobably s = random.randint(0, len(schedule) - 1) p_min = sys_min_check(min(schedule[s][0], schedule[s][1])) p_max = sys_max_check(max(schedule[s][0], schedule[s][1])) new_order_price = random.randint(p_min, p_max) else: sys.exit('ERROR: Unknown stepmode in schedule') new_order_price = sys_min_check(sys_max_check(new_order_price)) return new_order_price # pylint: disable=too-many-branches def get_issue_times(n_traders, stepmode, interval, shuffle, fit_to_interval): """ Produces issue times for orders :param n_traders: The number of traders :param stepmode: :param interval: Gap between each set of orders :param shuffle: Boolean of whether it should be shuffled or not :param fit_to_interval: :return: Issue times """ interval = float(interval) if n_traders < 1: sys.exit('FAIL: n_traders < 1 in get_issue_times()') elif n_traders == 1: t_step = interval else: t_step = interval / (n_traders - 1) arr_time = 0 order_issue_times = [] for i in range(n_traders): if stepmode == 'periodic': arr_time = interval elif stepmode == 'drip-fixed': arr_time = i * t_step elif stepmode == 'drip-jitter': arr_time = i * t_step + t_step * random.random() elif stepmode == 'drip-poisson': # poisson requires a bit of extra work inter_arrival_time = random.expovariate(n_traders / interval) arr_time += inter_arrival_time else: sys.exit('FAIL: unknown t-stepmode in get_issue_times()') order_issue_times.append(arr_time) # at this point, arr_time is the last arrival i if fit_to_interval and ((arr_time > interval) or (arr_time < interval)): # generated sum of inter-arrival times longer than the interval # squish them back so that last arrival falls at t=interval for i in range(n_traders): order_issue_times[i] = interval * (order_issue_times[i] / arr_time) # optionally randomly shuffle the times if shuffle: for i in range(n_traders): i = (n_traders - 1) - i j = random.randint(0, i) tmp = order_issue_times[i] order_issue_times[i] = order_issue_times[j] order_issue_times[j] = tmp return order_issue_times def get_sched_mode(curr_time, order_schedule): """ Extract details of the order_schedule :param curr_time: Current time :param order_schedule: Order Schedule :return: The range of values the orders can take, the stepmode and the schedule end time. """ schedrange = None stepmode = None sched_end_time = None got_one = False for schedule in order_schedule: if schedule['from'] <= curr_time < schedule['to']: # within the timezone for this schedule schedrange = schedule['ranges'] stepmode = schedule['stepmode'] sched_end_time = schedule['to'] got_one = True break # jump out the loop -- so the first matching timezone has priority over any others if not got_one: sys.exit( f'Fail: t={curr_time:5.2f} not within any timezone in order_schedule={order_schedule}' ) return schedrange, stepmode, sched_end_time n_buyers = trader_stats['n_buyers'] n_sellers = trader_stats['n_sellers'] shuffle_times = True cancellations = [] if len(pending) < 1: # list of pending (to-be-issued) customer orders is empty, so generate a new one new_pending = [] # demand side (buyers) issue_times = get_issue_times(n_buyers, order_sched['timemode'], order_sched['interval'], shuffle_times, True) order_type = 'Bid' (sched, mode, sched_end) = get_sched_mode(time, order_sched['dem']) for t in range(n_buyers): issue_time = time + issue_times[t] t_name = f'B{str(t).zfill(2)}' order_price = get_order_price(t, sched, sched_end, n_buyers, mode, issue_time) order = Order(t_name, order_type, order_price, 1, issue_time, coid, -3.14) new_pending.append(order) coid += 1 # supply side (sellers) issue_times = get_issue_times(n_sellers, order_sched['timemode'], order_sched['interval'], shuffle_times, True) order_type = 'Ask' (sched, mode, sched_end) = get_sched_mode(time, order_sched['sup']) for t in range(n_sellers): issue_time = time + issue_times[t] t_name = f'S{str(t).zfill(2)}' order_price = get_order_price(t, sched, sched_end, n_sellers, mode, issue_time) order = Order(t_name, order_type, order_price, 1, issue_time, coid, -3.14) new_pending.append(order) coid += 1 else: # there are pending future orders: issue any whose timestamp is in the past new_pending = [] for order in pending: if order.time < time: # this order should have been issued by now # issue it to the trader t_name = order.tid response = traders[t_name].add_order(order, verbose) if verbose: print(f'Customer order: {response} {order}') if response == 'LOB_Cancel': cancellations.append(t_name) if verbose: print(f'Cancellations: {cancellations}') # and then don't add it to new_pending (i.e., delete it) else: # this order stays on the pending list new_pending.append(order) return [new_pending, cancellations, coid]