def optimize_x_and_y(self, step_size, order, coefficients, shifts, x0s_guess, y0s_guess, max_num_steps=10, min_alpha=0, verbose=False, plot=False, data_len=None): last_err, x, dat= self.get_total_err_from_x0_and_y0(step_size, order, coefficients, shifts, x0s_guess, y0s_guess, verbose=verbose, data_len=data_len) if plot: fig = plt.figure() ax = fig.add_subplot(111) ax.plot(dat) line, = ax.plot(x) plt.title('Iteration: 0, Error: ' + num2str(last_err, 4)) plt.draw() plt.pause(0.01) x0s = x0s_guess y0s = y0s_guess for i in range(0, max_num_steps): x0s_guess = x0s + (np.random.rand(len(x0s_guess)) - 0.5) y0s_guess = y0s + (np.random.rand(len(x0s_guess)) - 0.5) / len(dat) # x0s_guess, y0s_guess = self.get_next_guess_for_x0s_and_y0s(step_size, order, coefficients, shifts) err, x, dat = self.get_total_err_from_x0_and_y0(step_size, order, coefficients, shifts, x0s_guess, y0s_guess, verbose=verbose, data_len=data_len) if plot: line.set_ydata(x) plt.title('Iteration: ' + str(i + 1) + ', Error: ' + num2str(err, 4)) plt.draw() plt.pause(0.01) if not np.isnan(err): alpha = err / last_err u = np.random.random()#2*alpha# if alpha < min_alpha: break elif alpha < u: x0s = x0s_guess y0s = y0s_guess last_err = err self.reset(x0s, y0s)
def format_price_info(self, returns, price): if returns >= 0: value_sym = '+' else: value_sym = '-' return_str = value_sym + num2str(returns, 3) formated_string = '$' + num2str(price, 2) + ' (' + return_str + ')' return formated_string
def refresh(self): self.get_avg_buy_price() Label(self, text=self.sym + ' Available: ' + num2str(self.wallet.get_amnt_available('sell'), digits=self.wallet.product.base_decimal_num)).grid( row=1, column=3) Label(self, text='Average Buy Price: ' + num2str(self.avg_buy_price, digits=self.wallet.product.usd_decimal_num)).grid( row=2, column=3) self.update_plot()
def trade_action(self): prediction, order_book, bids, asks = self.predict() decision, order_std = self.strategy.determine_move( prediction, order_book, self.portfolio, bids, asks) # returns None for hold if (decision is not None): side = decision['side'] price = decision['price'] self.cancel_out_of_bound_orders(side, price, order_std) self.portfolio.update_value() available = self.portfolio.get_amnt_available(side) if available < 0.001: return None, None, None if side == 'buy': size = available * decision['size coeff'] / decision['price'] if price > self.spread_price_limits['buy']: return None, None, None else: size = available * decision['size coeff'] if price < self.spread_price_limits['sell']: return None, None, None print('Evaluating ' + side + ' of ' + num2str(size, 3) + ' ' + self.ticker + ' at $' + num2str(price, 2) + ' based on std of ' + num2str(order_std, 4)) is_maker = decision['is maker'] # TODO create function to optimize location in the order book when placing an order order_id = self.place_order(price, side, size, allow_taker=is_maker) self.order_stds[order_id] = order_std # -- this filters out prices for orders that were not placed -- if order_id is None: price = None else: self.update_spread_prices_limits(price, side) else: price = None side = None size = None return price, side, size
def place_order(self, price, side, size, coeff=1, post_only=True): if not side in ['buy', 'sell']: raise ValueError(side + ' is not a valid orderbook side') new_order_id = None price_str = num2str(price, 2) size_str = num2str(coeff * size, 4) order_info = self.auth_client.place_limit_order( product_id=self.product_id, side=side, price=price_str, size=size_str, post_only=post_only) sleep(0.5) if type(order_info) == dict: if "price" in order_info.keys(): new_order_id = order_info["id"] return new_order_id
def __init__(self, master, bot, sym, target, row, column=0): self.master = master self.bot = bot self.wallet = bot.portfolio.wallets[sym] self.sym = sym self.row = row self.column = column self.avg_buy_price = 0 self.command = lambda: target(sym) self.get_avg_buy_price() cmd_text = sym + '\nAvg Buy Price:' + num2str( self.avg_buy_price, digits=self.wallet.product.usd_decimal_num) self.button = Button(self.master, text=cmd_text, command=self.command) self.button.grid(row=row, column=column)
def place_order(self, price, side, size, coeff=1, post_only=True, time_out=False, stop_price=None): if not side in ['buy', 'sell']: raise ValueError(side + ' is not a valid orderbook side') if price * size < self.quote_order_min: print( num2str(price * size, self.usd_decimal_num) + ' is smaller than the minimum quote size') return None if size < self.base_order_min: print(num2str(size, 6) + ' is smaller than the minimum base size') return None new_order_id = None # Some orders are not placing due to order size, so the extra subtraction below is to ensure they are small enough price_str = num2str(price, self.usd_decimal_num) size_str = num2str(coeff * size, self.base_decimal_num) if stop_price: stop_str = num2str(stop_price, self.usd_decimal_num) if side == 'buy': stop_type = 'entry' else: stop_type = 'loss' if time_out and stop_price: # TODO fix stop order order_info = self.auth_client.place_order( product_id=self.product_id, side=side, price=price_str, size=size_str, post_only=post_only, time_in_force='GTT', cancel_after='hour', stop=stop_type, order_type='limit', stop_price=stop_str) elif stop_price: order_info = self.auth_client.place_order( product_id=self.product_id, side=side, price=price_str, size=size_str, post_only=post_only, stop=stop_type, order_type='limit', stop_price=stop_str) elif time_out: order_info = self.auth_client.place_limit_order( product_id=self.product_id, side=side, price=price_str, size=size_str, post_only=post_only, time_in_force='GTT', cancel_after='hour') else: order_info = self.auth_client.place_limit_order( product_id=self.product_id, side=side, price=price_str, size=size_str, post_only=post_only) sleep(PRIVATE_SLEEP) if type(order_info) == dict: if "price" in order_info.keys(): new_order_id = order_info["id"] if new_order_id is None: print(order_info) else: self.orders[side][new_order_id] = order_info return new_order_id
def trade_loop(self): # This method keeps the bot running continuously current_time = datetime.now().timestamp() last_check = 0 last_scrape = 0 last_training_time = current_time #- 10*60 order_dict = self.auth_client.get_product_order_book(self.product_id, level=2) sleep(0.4) accnt_data = self.auth_client.get_accounts() sleep(0.4) starting_price = round(float(order_dict['asks'][0][0]), 2) price, portfolio_value = self.get_portfolio_value(order_dict, accnt_data) self.initial_price = price self.initial_value = portfolio_value # This message is shown at the beginning print('Begin trading at ' + datetime.strftime(datetime.now(), '%m-%d-%Y %H:%M') + ' with current price of $' + str( starting_price) + ' per ' + self.prediction_ticker + 'and a portfolio worth $' + num2str(portfolio_value, 2)) sleep(1) err_counter = 0 check_period = 1 last_plot = 0 last_buy_msg = '' last_sell_msg = '' fmt = '%Y-%m-%d %H:%M:' portfolio_returns = 0 market_returns = 0 while 15.10 < portfolio_value: if (current_time > (last_check + check_period)) & (current_time < (last_training_time + 2 * 3600)): # Scrape price from cryptocompae try: order_dict = self.auth_client.get_product_order_book(self.product_id, level=2) sleep(0.4) accnt_data = self.auth_client.get_accounts() sleep(0.4) self.scrape_granular_price() last_check = current_time if (current_time > (last_scrape + 65)): price, portfolio_value = self.get_portfolio_value(order_dict, accnt_data) self.spread_bot_predict() last_scrape = current_time #self.order_status = 'active' #This forces the order to be reset as a stop order after 1 minute passes portfolio_returns, market_returns = self.update_returns_data(price, portfolio_value) self.timer['buy'] += 1 self.timer['sell'] += 1 err_counter = 0 except Exception as e: err_counter = self.print_err_msg('find new data', e, err_counter) continue # Plot returns try: if (current_time > (last_plot + 5*60)): self.plot_returns(portfolio_returns, portfolio_value, market_returns, price) last_plot = current_time err_counter = 0 except Exception as e: err_counter = self.print_err_msg('plot', e, err_counter) continue # Make trades try: err, fit_coeff, fit_offset, const_diff, fuzziness = self.find_fit_info() buy_msg = self.place_limit_orders(err, const_diff, fit_coeff, fuzziness, fit_offset, 'buy', order_dict, accnt_data) sell_msg = self.place_limit_orders(err, const_diff, fit_coeff, fuzziness, fit_offset, 'sell', order_dict, accnt_data) current_datetime = current_est_time() prez_fmt = '%Y-%m-%d %H:%M:%S' sell_msg = sell_msg.title() buy_msg = buy_msg.title() if (buy_msg != last_buy_msg) and (buy_msg != ''): print('\nCurrent time is ' + current_datetime.strftime(prez_fmt) + ' EST') print('Buy message: ' + buy_msg) last_buy_msg = buy_msg if (sell_msg != last_sell_msg) and (sell_msg != ''): print('\nCurrent time is ' + current_datetime.strftime(prez_fmt) + ' EST') print('Sell message: ' + sell_msg) last_sell_msg = sell_msg err_counter = 0 except Exception as e: err_counter = self.print_err_msg('trade', e, err_counter) continue # Update model training elif current_time > (last_training_time + 2*3600): try: last_scrape = 0 last_training_time = current_time self.price_model.model_actions('train', train_saved_model=True, save_model=False) self.price_model.model.save(self.save_str) # Reinitialize CoinPriceModel self.reinitialize_model() err_counter = 0 except Exception as e: err_counter = self.print_err_msg('trade', e, err_counter) continue current_time = datetime.now().timestamp() if err_counter > 12: print('Process aborted due to too many exceptions') break print('Algorithm failed either due to underperformance or due to too many exceptions. Now converting all crypto to USD') self.auth_client.cancel_all(self.product_id) accnt_data = self.auth_client.get_accounts() sleep(0.4) usd_available, crypto_available = self.get_available_wallet_contents(accnt_data) self.auth_client.place_market_order(self.product_id, side='sell', size=num2str(crypto_available, 8))
def place_limit_orders(self, err, const_diff, fit_coeff, fuzziness, fit_offset, order_type, order_dict, accnt_data): #get min, max, and current price and order_book min_future_price, max_future_price, current_price = self.find_spread_bounds(err, const_diff, fit_coeff, fuzziness, fit_offset, order_type, order_dict) if order_type == 'buy': cancel_type = 'sell' min_cancel_balance = 0.01 else: cancel_type = 'buy' min_cancel_balance = 10 is_predicted_return = min_future_price is not None if (not is_predicted_return) and (self.trade_logic[cancel_type]): self.trade_logic[order_type] = False msg = '' if self.trade_ids[order_type] != '': unused_msg = self.cancel_old_hodl_order(order_type, 0, 0) cancel_type_balance = self.get_full_wallet_contents(cancel_type, accnt_data) if (cancel_type_balance <= min_cancel_balance): self.trade_logic[order_type] = True return msg hodl = False trade_reason = 'predicted return' if is_predicted_return: current_state = 'predicted return ' else: current_state = 'No predicted return ' self.trade_logic[order_type] = True #this ensures that the last trade type predicted to be profitable is the one currently used price_for_finding_portfoloio_value, portfolio_value = self.get_portfolio_value(order_dict, accnt_data) balance = portfolio_value/price_for_finding_portfoloio_value # -- determine whether to buy or sell -- if order_type == 'buy': dict_type = 'bids' opposite_dict_type = 'asks' order_type = 'buy' stop_type = 'entry' sign = 1 usd_available, crypto_available = self.get_available_wallet_contents(accnt_data) price = self.determine_trade_price(order_type, order_dict, is_stop=True) #using is_stop for the most conservative answer available = usd_available/price trade_size_lim = 10/price else: dict_type = 'asks' opposite_dict_type = 'bids' order_type = 'sell' stop_type = 'loss' usd_available, available = self.get_available_wallet_contents(accnt_data) price = self.determine_trade_price(order_type, order_dict, is_stop=True) sign = -1 trade_size_lim = 0.001 # -- determine whether the conditions are right to trade -- jump_num, bound_bool = self.should_update_trade_price(order_type, -sign) last_trade_price = self.trade_info[cancel_type]['price'] spread = np.std(self.price[-30::])/np.mean(self.price[-30::]) if spread == 0: spread = 0.001 num_spread_trades = 3 spread_bool = (sign * price < (sign - spread) * last_trade_price) is_extreme_pressure = self.detect_trade_pressure(order_dict, opposite_dict_type, dict_type, pressure_ratio=7) if (jump_num) and (min_future_price is not None): # Buy when the price moves in a favorable direction hodl = True trade_reason = 'predicted return' current_state += 'jump detected' elif ((bound_bool or (jump_num >= 4)) and (sign*price < sign*last_trade_price)) and (last_trade_price > 0) and (order_type == 'sell'): # Wait until value is created or the situation has changed to sell hodl = True trade_reason = 'guess' # if available > (balance/num_spread_trades + trade_size_lim): # available = balance/num_spread_trades current_state += 'spread detected' elif is_extreme_pressure and (sign*price < sign*last_trade_price) and (order_type == 'sell'): hodl = True trade_reason = 'extreme opposing pressure' current_state += 'extreme opposing pressure' price = self.determine_trade_price(order_type, order_dict) stop_order_price = self.determine_trade_price(order_type, order_dict, is_stop=True) # -- place trade -- if hodl: is_favorable_pressure = self.detect_trade_pressure(order_dict, opposite_dict_type, dict_type) if (self.order_status == 'open' and is_predicted_return) or (is_favorable_pressure): freed_available = self.cancel_old_hodl_order(order_type, price, stop_order_price, force_limit_order=True) available += freed_available price_str = num2str(price, 2) if available < trade_size_lim: msg = 'insufficient funds - ' + trade_reason # if (order_type == 'buy') and (price > self.trade_info[order_type]['price']): # #This updates the price for calculating the spread # self.trade_info[order_type]['price'] = price # self.trade_info[order_type]['mean'] = np.mean(self.price[-30::]) # self.trade_info[order_type]['std'] = np.std(self.price[-30::]) # msg = 'Updating buy price for spread to $' + price_str return msg size_str = num2str(available, 8) order = self.auth_client.place_limit_order(self.product_id, order_type, price_str, size_str, time_in_force='GTT', cancel_after='hour', post_only=True) order_kind = 'limit' else: stop_price_str = num2str(price + sign * 0.01, 2) price_str = num2str(stop_order_price, 2) freed_available = self.cancel_old_hodl_order(order_type, price, stop_order_price, force_stop_limit_order=True) available += freed_available if available < trade_size_lim: msg = 'insufficient funds - ' + trade_reason return msg size_str = num2str(available, 8) order = self.auth_client.place_order(product_id=self.product_id, side=order_type, price=price_str, size=size_str, stop=stop_type, stop_price=stop_price_str, order_type='limit') order_kind = 'stop limit' if not ('id' in order.keys()): msg = str(order.values()) return msg self.trade_ids[order_type] = order['id'] self.trade_info[order_type]['price'] = price # TODO use second by second price self.trade_info[order_type]['mean'] = np.mean(self.price[-30::]) self.trade_info[order_type]['std'] = np.std(self.price[-30::]) self.should_reset_timer[order_type] = True msg = 'placing ' + order_kind + ' order at $' + price_str + ' due to ' + trade_reason return msg #unused_msg = self.cancel_old_hodl_order(order_type, 0, 0) msg = current_state return msg
def run_bot(): # -- Secret/changing variable declerations if len(sys.argv) > 2: # Definition from a shell file model_path = sys.argv[1] api_input = sys.argv[2] secret_input = sys.argv[3] passphrase_input = sys.argv[4] sandbox_bool = bool(int(sys.argv[5])) else: # Manual definition model_path = input('What is the model path? ') api_input = input('What is the api key? ') secret_input = input('What is the secret key? ') passphrase_input = input('What is the passphrase? ') sandbox_bool = bool(int(input('Is this for a sandbox? '))) print(model_path) print(api_input) print(secret_input) print(passphrase_input) print(sandbox_bool) # Setup initial variables strategy = Strategy() bot = LiveSmartBot(model_path, strategy, api_input, secret_input, passphrase_input, is_sandbox_api=sandbox_bool) portfolio_value = bot.get_full_portfolio_value() bot.update_current_price() starting_price = bot.current_price['bids'] portfolio_tracker = PortfolioTracker(bot.portfolio) bot.settings.write_setting_to_file('portfolio value offset', bot.portfolio.offset_value) # This method keeps the bot running continuously current_time = datetime.now().timestamp() # This message is shown at the beginning print('Begin trading at ' + datetime.strftime(datetime.now(), '%m-%d-%Y %H:%M') + ' with current price of $' + str(starting_price) + ' per ' + bot.ticker + 'and a portfolio worth $' + num2str(portfolio_value, 2)) sleep(1) last_check = 0 last_plot = 0 plot_period = 60 check_period = 1 err_counter = 0 while (11 < portfolio_value) and (err_counter < 10): current_time = datetime.now().timestamp() if (current_time > (last_check + check_period)): try: # Trade price, side, size = bot.trade_action() if price: print('Placed ' + side + ' order for ' + num2str(size, 3) + ' ' + bot.ticker + ' at $' + num2str(price, 2)) err_counter = 0 last_check = datetime.now().timestamp() # Update Settings bot.settings.update_settings() bot.spread_price_limits['buy'] = bot.settings.settings[ 'limit buy'] bot.spread_price_limits['sell'] = bot.settings.settings[ 'limit sell'] bot.spread = bot.settings.settings['spread'] bot.portfolio.offset_value = bot.settings.settings[ 'portfolio value offset'] if (current_time > (last_plot + plot_period)): portfolio_value = portfolio_tracker.plot_returns() last_plot = datetime.now().timestamp() except Exception as e: err_counter = print_err_msg('find new data', e, err_counter) continue print('Loop END')
for i in range(0, len(sym_list)): coeff = coeff_list[i] shift = shift_list[i] progress_printer(system_fit.propogators[0].N, i, tsk='Evaluationg Polynomials') x_fit, t_fit = system_fit.evaluate_nth_polynomial(t, psm_step_size, psm_order, n=i + 1, verbose=i==False) x_fit = x_fit[~np.isnan(x_fit)] x_raw = concat_data_list[i][train_len:train_len+2*test_len] x0 = np.mean(concat_data_list[i][train_len-10:train_len]) t_plot_raw = np.linspace(0, 2*test_len, len(x_raw)) t_plot_fit = np.linspace(0, np.max(t), len(x_fit)) true_fit = np.polyfit(t, x_raw[0:test_len], 1) true_fit_line = np.polyval(true_fit, t_plot_raw) trend = trend_line(coeff * ( x_fit - x_fit[0] )) plt.figure() # fit_len = len(x_fit) plt.plot(t_plot_raw, x_raw) # True data plt.plot(t_fit, coeff * ( x_fit - x_fit[0] ) + x_raw[0]) # Calculated fit line plt.plot(t_plot_raw, true_fit_line) # True fit line plt.plot(np.array([0, np.max(t)]), np.mean(np.diff(train_list[i][-test_len::]))*np.ones(2)*np.array([0, np.max(t)]) + x_raw[0]) # Naive fit line plt.legend(('true', 'projected fit', 'actual fit', 'naive fit')) plt.title( sym_list[i] + ' Predicted Price Vs Actual') plt.xlabel('Time (min)') plt.ylabel('Price ($)') print(sym_list[i]) print('true std: ' + num2str(np.std(np.diff(x_raw)), digits=5)) print('psm std: ' + num2str(np.std(np.diff(coeff*x_fit)), digits=5)) print('naive std: ' + num2str(np.std(np.diff(train_list[i][-100::])), digits=5)) plt.show()
def fit_system_of_odes(data, time, order, number_of_hidden_eqautions=5): # Format data to range from -1 to 1 response_poly = construct_piecewise_polynomial_for_data(data, order, t=time) inds = [entry[1] for entry in response_poly[1::]] ind_list = list(range(0, number_of_hidden_eqautions)) ind_list.reverse() for ind, next_ind in zip(inds[0:-1], inds[1::]): # Setup initial guess solution y = data[ind:next_ind] zero = np.min(y) scale = 2 / (np.max(y) - np.min(y)) y = y - zero y = scale * y y = y - 1 y0 = y t = time[ind:next_ind] force = np.zeros(len(t)) f_coeff = np.zeros(order) fit = np.zeros(len(t)) omega_guess = find_sin_freq(y, t) zeta_guess = 0 step_size = 1 polynomial_coefficients = np.polyfit(t - t[0], y, order) # Format variables with initial conditions omega_arr = [] zeta_arr = [] fit_arr = [] for iteration in range(0, 3): y = y0 for i in ind_list: print('omega: ' + num2str(omega_guess) + ', zeta: ' + num2str(zeta_guess) + ', equation: ' + str(i)) if (zeta_guess == 0) or (iteration == 0): fit_psm = np.sin(omega_guess * t) else: propogator = PSMPolynomialGenerator( polynomial_coefficients[-1], polynomial_coefficients[-2], zeta_guess, omega_guess, f_coeff) fit_psm, t_psm, _ = propogator.take_n_steps( len(t), 15, step_size) plt.plot(t_psm, fit_psm) plt.figure() # Store for next iteration if (iteration == 0): omega_arr.append(omega_guess) zeta_arr.append(zeta_guess) fit_arr.append(fit_psm) fit = fit + fit_psm y = y0 - fit else: omega_arr[i] = omega_guess zeta_arr[i] = zeta_guess fit_arr[i] = fit_psm fit = np.zeros(len(fit_psm)) fit_force = np.zeros(len(fit_psm)) for partial_fit in fit_arr[i::]: fit = fit + partial_fit for partial_fit in fit_arr[0:i]: fit_force = fit_force + partial_fit y = y0 - fit_arr[i] plt.plot(fit) plt.plot(y) plt.show() if iteration > 0: polynomial_coefficients = np.polyfit(t - t[0], y, order) F_coefficients = np.polyfit(t - t[0], fit_force, order) zeta_guess, omega_guess = calculate_coefficients_for_second_order( polynomial_coefficients, F_coefficients) if np.isnan(zeta_guess) or (iteration == 0): zeta_guess = 0 omega_guess = find_sin_freq(y, t) plt.plot(t, y0, 'b.') plt.plot(t, fit) plt.show()
plt.plot(fit_t, F[start_stop[0]:start_stop[1]], 'b') plt.plot(fit_t, F_poly_fit, 'rx') plt.show() fit_finder = ODEFit(data, F, sample_rate=0.01) fit_coeff = fit_finder.calculate_ode_coeff(15) for i in range(1, len(fit_coeff['start_ind']) - 2): zeta = fit_coeff['zeta'][i] omega = fit_coeff['omega'][i] x_0 = coeff[i + 2][0][-1] y_0 = coeff[i + 2][0][-2] ind0 = fit_coeff['start_ind'][i] indf = fit_coeff['start_ind'][i + 1] indffit = fit_coeff['start_ind'][i + 2] print('Calculated zeta: ' + num2str(zeta, 4) + ' Calculated omega: ' + num2str(omega, 4)) propogator = PSMPolynomialGenerator(x_0, y_0, zeta, omega, F_coeff[i + 2][0]) # propogator.generate_nth_order_polynomial(10) # fit = propogator.evaluate_polynomial(np.arange(0, 10, 0.01)) # t_psm = np.arange(0, 10, 0.01) fit, t_psm, _ = propogator.take_n_steps(100, 15, 0.1) # fit_decay = zeta * omega if (np.isnan(zeta)) or (zeta < 1): continue # fit_freq = omega * np.sqrt(1 - zeta ** 2) # fit = np.exp(-fit_decay*fit_t)*np.sin(fit_freq*fit_t + c) #
def update_text(self): self.get_avg_buy_price() cmd_text = self.sym + '\nAvg Buy Price:' + num2str( self.avg_buy_price, digits=self.wallet.product.usd_decimal_num) self.button.config(text=cmd_text)
t0 = time() t = 0 i = 0 print(str(int(time()))) sym_pairs = DEFAULT_SYM_PAIRS while t < 60: i += 1 t = time() - t0 for pair in sym_pairs: if 'USD' in pair: continue base = pair.split('-')[0] quote = pair.split('-')[1] if (base + '-USD') in bot.portfolio.wallets.keys(): stable = 'USD' elif (base + '-USDC') in bot.portfolio.wallets.keys(): stable = 'USDC' else: continue f, r = bot.find_arbitradge_spreads(base, quote, stable_ticker=stable) if (f > 0) or (r > 0): print(pair) print(str(int(time()))) print('Forwad value: ' + num2str(f, 3) + '%') print('Reverse value: ' + num2str(r, 3) + '%\n') else: sym_pairs = DEFAULT_SYM_PAIRS