def init_objects_unequal(parameters, seed, equality=1.0): """ Init object for the distribution version of the model :param parameters: :param seed: :param equality: float to change init equality 0.01 is very unequal, to inf being very equal, the default 1.0 is equal to the uniform distribution :return: """ np.random.seed(seed) random.seed(seed) traders = [] n_traders = parameters["n_traders"] weight_f = (1 - parameters['strat_share_chartists']) * ( 1 - parameters['w_random']) weight_c = parameters['strat_share_chartists'] * (1 - parameters['w_random']) f_points = int(weight_f * 100 * n_traders) c_points = int(weight_c * 100 * n_traders) r_points = int(parameters['w_random'] * 100 * n_traders) # create list of strategy points, shuffle it and divide in equal parts strat_points = ['f' for f in range(f_points)] + [ 'c' for c in range(c_points) ] + ['r' for r in range(r_points)] random.shuffle(strat_points) agent_points = np.array_split(strat_points, n_traders) max_horizon = parameters[ 'horizon'] * 2 # this is the max horizon of an agent if 100% fundamentalist historical_stock_returns = np.random.normal(0, parameters["std_fundamental"], max_horizon) init_distribution = np.random.power(equality, n_traders) # normalize init_distribution = init_distribution / init_distribution.sum() for idx in range(n_traders): weight_fundamentalist = list(agent_points[idx]).count('f') / float( len(agent_points[idx])) weight_chartist = list(agent_points[idx]).count('c') / float( len(agent_points[idx])) weight_random = list(agent_points[idx]).count('r') / float( len(agent_points[idx])) init_stocks = int(parameters["init_stocks"] * n_traders * init_distribution[idx]) init_money = parameters["init_stocks"] * parameters[ 'fundamental_value'] * n_traders * init_distribution[ idx] #TODO debug c_share_strat = div0(weight_chartist, (weight_fundamentalist + weight_chartist)) # initialize co_variance_matrix init_covariance_matrix = calculate_covariance_matrix( historical_stock_returns, parameters["std_fundamental"]) lft_vars = TraderVariablesDistribution(weight_fundamentalist, weight_chartist, weight_random, c_share_strat, init_money, init_stocks, init_covariance_matrix, parameters['fundamental_value']) # determine heterogeneous horizon and risk aversion based on individual_horizon = parameters[ 'horizon'] #np.random.randint(10, parameters['horizon']) TODO, maybe change back individual_risk_aversion = parameters[ "base_risk_aversion"] # abs(np.random.normal(parameters["base_risk_aversion"], parameters["base_risk_aversion"] / 5.0))#parameters["base_risk_aversion"] * relative_fundamentalism individual_learning_ability = parameters[ 'average_learning_ability'] #min(abs(np.random.normal(parameters['average_learning_ability'], 0.1)), 1.0) #TODO what to do with std_dev lft_params = TraderParametersDistribution(individual_horizon, individual_risk_aversion, individual_learning_ability, parameters['spread_max']) lft_expectations = TraderExpectations(parameters['fundamental_value']) traders.append(Trader(idx, lft_vars, lft_params, lft_expectations)) orderbook = LimitOrderBook(parameters['fundamental_value'], parameters["std_fundamental"], max_horizon, parameters['ticks']) # initialize order-book returns for initial variance calculations orderbook.returns = list(historical_stock_returns) return traders, orderbook
def init_objects(parameters, seed): """ Init object for the distribution version of the model :param parameters: :param seed: :return: """ np.random.seed(seed) random.seed(seed) traders = [] n_traders = parameters["n_traders"] weight_f = (1 - parameters['strat_share_chartists']) * ( 1 - parameters['w_random']) weight_c = parameters['strat_share_chartists'] * (1 - parameters['w_random']) f_points = int(weight_f * 100 * n_traders) c_points = int(weight_c * 100 * n_traders) r_points = int(parameters['w_random'] * 100 * n_traders) # create list of strategy points, shuffle it and divide in equal parts strat_points = ['f' for f in range(f_points)] + [ 'c' for c in range(c_points) ] + ['r' for r in range(r_points)] random.shuffle(strat_points) agent_points = np.array_split(strat_points, n_traders) max_horizon = parameters[ 'horizon'] * 2 # this is the max horizon of an agent if 100% fundamentalist historical_stock_returns = np.random.normal(0, parameters["std_fundamental"], max_horizon) for idx in range(n_traders): weight_fundamentalist = list(agent_points[idx]).count('f') / float( len(agent_points[idx])) weight_chartist = list(agent_points[idx]).count('c') / float( len(agent_points[idx])) weight_random = list(agent_points[idx]).count('r') / float( len(agent_points[idx])) init_stocks = int(np.random.uniform(0, parameters["init_stocks"])) init_money = np.random.uniform( 0, (parameters["init_stocks"] * parameters['fundamental_value'])) if weight_random < 1.0: c_share_strat = div0(weight_chartist, (weight_fundamentalist + weight_chartist)) else: c_share_strat = 0.0 # initialize co_variance_matrix init_covariance_matrix = calculate_covariance_matrix( historical_stock_returns, parameters["std_fundamental"]) lft_vars = TraderVariables(weight_fundamentalist, weight_chartist, weight_random, c_share_strat, init_money, init_stocks, init_covariance_matrix, parameters['fundamental_value']) # determine heterogeneous horizon and risk aversion based on individual_horizon = np.random.randint(10, parameters['horizon']) individual_risk_aversion = abs( np.random.normal(parameters["base_risk_aversion"], parameters["base_risk_aversion"] / 5.0) ) #parameters["base_risk_aversion"] * relative_fundamentalism individual_learning_ability = min( abs(np.random.normal(parameters['average_learning_ability'], 0.1)), 1.0) #TODO what to do with std_dev lft_params = TraderParameters(individual_horizon, individual_risk_aversion, individual_learning_ability, parameters['spread_max']) lft_expectations = TraderExpectations(parameters['fundamental_value']) traders.append(Trader(idx, lft_vars, lft_params, lft_expectations)) # Add market maker with 100k money and 1k stocks mm_tradervariables = TraderVariables( weight_fundamentalist=0, weight_chartist=0, weight_random=0, c_share_strat=0, money=100000, stocks=1000, covariance_matrix=0, init_price=parameters['fundamental_value']) mm_traderparams = TraderParameters(ref_horizon=0, risk_aversion=0, learning_ability=0.0, max_spread=10000) mm_traderexp = TraderExpectations(parameters['fundamental_value']) market_maker = Trader(0, mm_tradervariables, mm_traderparams, mm_traderexp) orderbook = LimitOrderBook(parameters['fundamental_value'], parameters["std_fundamental"], max_horizon, parameters['ticks']) # initialize order-book returns for initial variance calculations orderbook.returns = list(historical_stock_returns) return traders, orderbook, market_maker
def pb_distr_model(traders, orderbook, parameters, seed=1): """ The main model function of distribution model where trader stocks are tracked. :param traders: list of Agent objects :param orderbook: object Order book :param parameters: dictionary of parameters :param seed: integer seed to initialise the random number generators :return: list of simulated Agent objects, object simulated Order book """ random.seed(seed) np.random.seed(seed) fundamental = [parameters["fundamental_value"]] orderbook.tick_close_price.append(fundamental[-1]) traders_by_wealth = [t for t in traders] for tick in range(parameters['horizon'] + 1, parameters["ticks"] + parameters['horizon'] + 1): # for init history if tick == parameters['horizon'] + 1: print('Start of simulation ', seed) # update money and stocks history for agents for trader in traders: trader.var.money.append(trader.var.money[-1]) trader.var.stocks.append(trader.var.stocks[-1]) trader.var.wealth.append( trader.var.money[-1] + trader.var.stocks[-1] * orderbook.tick_close_price[-1]) # TODO debug trader.var.weight_fundamentalist.append( trader.var.weight_fundamentalist[-1]) trader.var.weight_chartist.append(trader.var.weight_chartist[-1]) trader.var.weight_random.append(trader.var.weight_random[-1]) # sort the traders by wealth to TODo debug traders_by_wealth.sort(key=lambda x: x.var.wealth[-1], reverse=True) # evolve the fundamental value via random walk process fundamental.append( max( fundamental[-1] + parameters["std_fundamental"] * np.random.randn(), 0.1)) # allow for multiple trades in one day for turn in range(parameters["trades_per_tick"]): # select random sample of active traders active_traders = random.sample( traders, int((parameters['trader_sample_size']))) mid_price = np.mean( [orderbook.highest_bid_price, orderbook.lowest_ask_price]) #TODO debug fundamental_component = np.log(fundamental[-1] / mid_price) orderbook.returns[-1] = ( mid_price - orderbook.tick_close_price[-2] ) / orderbook.tick_close_price[-2] #TODO debug chartist_component = np.cumsum( orderbook.returns[:-len(orderbook.returns) - 1:-1]) / np.arange( 1., float(len(orderbook.returns) + 1)) for trader in active_traders: # Cancel any active orders if trader.var.active_orders: for order in trader.var.active_orders: orderbook.cancel_order(order) trader.var.active_orders = [] def evolve(probability): return random.random() < probability # Evolve an expectations parameter by learning from a successful trader or mutate at random if evolve(trader.par.learning_ability): wealthy_trader = traders_by_wealth[random.randint( 0, parameters['trader_sample_size'])] trader.var.c_share_strat = np.mean([ trader.var.c_share_strat, wealthy_trader.var.c_share_strat ]) else: trader.var.c_share_strat = min( max( trader.var.c_share_strat * (1 + parameters['mutation_intensity'] * np.random.randn()), 0.01), 0.99) # update fundamentalist & chartist weights total_strat_weight = trader.var.weight_fundamentalist[ -1] + trader.var.weight_chartist[-1] trader.var.weight_chartist[ -1] = trader.var.c_share_strat * total_strat_weight trader.var.weight_fundamentalist[-1] = ( 1 - trader.var.c_share_strat) * total_strat_weight # record sentiment in orderbook orderbook.sentiment.append( np.array([ trader.var.weight_fundamentalist[-1], trader.var.weight_chartist[-1], trader.var.weight_random[-1] ])) # Update trader specific expectations noise_component = parameters['std_noise'] * np.random.randn() # Expectation formation trader.exp.returns['stocks'] = ( trader.var.weight_fundamentalist[-1] * np.divide( 1, float(trader.par.horizon) * parameters["fundamentalist_horizon_multiplier"]) * fundamental_component + trader.var.weight_chartist[-1] * chartist_component[trader.par.horizon - 1] + trader.var.weight_random[-1] * noise_component) fcast_price = mid_price * np.exp(trader.exp.returns['stocks']) trader.var.covariance_matrix = calculate_covariance_matrix( orderbook.returns[-trader.par.horizon:], parameters["std_fundamental"]) # employ portfolio optimization algo ideal_trader_weights = portfolio_optimization(trader, tick) # Determine price and volume trader_price = np.random.normal(fcast_price, trader.par.spread) position_change = (ideal_trader_weights['stocks'] * (trader.var.stocks[-1] * trader_price + trader.var.money[-1])) - ( trader.var.stocks[-1] * trader_price) volume = int(div0(position_change, trader_price)) # Trade: if volume > 0: bid = orderbook.add_bid(trader_price, volume, trader) trader.var.active_orders.append(bid) elif volume < 0: ask = orderbook.add_ask(trader_price, -volume, trader) trader.var.active_orders.append(ask) # Match orders in the order-book while True: matched_orders = orderbook.match_orders() if matched_orders is None: break # execute trade matched_orders[3].owner.sell( matched_orders[1], matched_orders[0] * matched_orders[1]) matched_orders[2].owner.buy( matched_orders[1], matched_orders[0] * matched_orders[1]) # Clear and update order-book history orderbook.cleanse_book() orderbook.fundamental = fundamental return traders, orderbook
def exuberance_inequality_model(traders, orderbook, parameters, seed=1): """ The main model function of distribution model where trader stocks are tracked. :param traders: list of Agent objects :param orderbook: object Order book :param parameters: dictionary of parameters :param seed: integer seed to initialise the random number generators :return: list of simulated Agent objects, object simulated Order book """ random.seed(seed) np.random.seed(seed) fundamental = [parameters["fundamental_value"]] orderbook.tick_close_price.append(fundamental[-1]) traders_by_wealth = [t for t in traders] for tick in range(parameters['horizon'] + 1, parameters["ticks"] + parameters['horizon'] + 1): # for init history if tick == parameters['horizon'] + 1: print('Start of simulation ', seed) # update money and stocks history for agents for trader in traders: trader.var.money.append(trader.var.money[-1]) trader.var.stocks.append(trader.var.stocks[-1]) trader.var.wealth.append(trader.var.money[-1] + trader.var.stocks[-1] * orderbook.tick_close_price[-1]) trader.var.real_wealth.append(trader.var.money[-1] + trader.var.stocks[-1] * fundamental[-1]) # sort the traders by wealth to traders_by_wealth.sort(key=lambda x: x.var.wealth[-1], reverse=True) # For simplicity, the fundamental value does not change. fundamental.append(fundamental[-1]) # select random sample of active traders active_traders = random.sample(traders, int((parameters['trader_sample_size']))) mid_price = np.mean([orderbook.highest_bid_price, orderbook.lowest_ask_price]) fundamental_component = np.log(fundamental[-1] / mid_price) orderbook.returns[-1] = (mid_price - orderbook.tick_close_price[-2]) / orderbook.tick_close_price[-2] chartist_component = np.cumsum(orderbook.returns[:-len(orderbook.returns) - 1:-1] ) / np.arange(1., float(len(orderbook.returns) + 1)) for trader in active_traders: # Cancel any active orders if trader.var.active_orders: for order in trader.var.active_orders: orderbook.cancel_order(order) trader.var.active_orders = [] # Update trader specific expectations noise_component = parameters['std_noise'] * np.random.randn() # Expectation formation trader.exp.returns['stocks'] = ( trader.var.weight_fundamentalist[-1] * np.divide(1, float(trader.par.horizon) * parameters["fundamentalist_horizon_multiplier"]) * fundamental_component + trader.var.weight_chartist[-1] * chartist_component[trader.par.horizon - 1] + trader.var.weight_random[-1] * noise_component) fcast_price = mid_price * np.exp(trader.exp.returns['stocks']) trader.var.covariance_matrix = calculate_covariance_matrix(orderbook.returns[-trader.par.horizon:], parameters["std_noise"]) # employ portfolio optimization algo ideal_trader_weights = portfolio_optimization(trader, tick) # Determine price and volume trader_price = np.random.normal(fcast_price, trader.par.spread) position_change = (ideal_trader_weights['stocks'] * (trader.var.stocks[-1] * trader_price + trader.var.money[-1]) ) - (trader.var.stocks[-1] * trader_price) volume = int(div0(position_change, trader_price)) # Trade: if volume > 0: bid = orderbook.add_bid(trader_price, volume, trader) trader.var.active_orders.append(bid) elif volume < 0: ask = orderbook.add_ask(trader_price, -volume, trader) trader.var.active_orders.append(ask) # Match orders in the order-book while True: matched_orders = orderbook.match_orders() if matched_orders is None: break # execute trade matched_orders[3].owner.sell(matched_orders[1], matched_orders[0] * matched_orders[1]) matched_orders[2].owner.buy(matched_orders[1], matched_orders[0] * matched_orders[1]) # Clear and update order-book history orderbook.cleanse_book() orderbook.fundamental = fundamental print('last mid-price was: ', mid_price) return traders, orderbook
def init_objects(parameters, seed): """ Init object for the distribution version of the model :param parameters: :param seed: :return: """ np.random.seed(seed) random.seed(seed) traders = [] n_traders = parameters["n_traders"] weight_f = (1 - parameters['strat_share_chartists']) * ( 1 - parameters['w_random']) weight_c = parameters['strat_share_chartists'] * (1 - parameters['w_random']) f_points = int(weight_f * 100 * n_traders) c_points = int(weight_c * 100 * n_traders) r_points = int(parameters['w_random'] * 100 * n_traders) # create list of strategy points, shuffle it and divide in equal parts strat_points = ['f' for f in range(f_points)] + [ 'c' for c in range(c_points) ] + ['r' for r in range(r_points)] random.shuffle(strat_points) agent_points = np.array_split(strat_points, n_traders) max_horizon = parameters[ 'horizon'] * 2 # this is the max horizon of an agent if 100% fundamentalist historical_stock_returns = list( np.random.normal(0, parameters["std_fundamental"], max_horizon)) total_stocks = 0 for idx in range(n_traders): weight_fundamentalist = list(agent_points[idx]).count('f') / float( len(agent_points[idx])) weight_chartist = list(agent_points[idx]).count('c') / float( len(agent_points[idx])) weight_random = list(agent_points[idx]).count('r') / float( len(agent_points[idx])) init_stocks = int(np.random.uniform(0, parameters["init_assets"])) total_stocks += init_stocks init_money = np.random.uniform( 0, (init_stocks * parameters['fundamental_value'] * parameters['money_multiplier'])) c_share_strat = div0(weight_chartist, (weight_fundamentalist + weight_chartist)) # initialize co_variance_matrix init_covariance_matrix = calculate_covariance_matrix( historical_stock_returns) init_active_orders = [] lft_vars = TraderVariables(weight_fundamentalist, weight_chartist, weight_random, c_share_strat, init_money, init_stocks, init_covariance_matrix, parameters['fundamental_value'], init_active_orders) # determine heterogeneous horizon and risk aversion individual_horizon = np.random.randint(10, parameters['horizon']) individual_risk_aversion = abs( np.random.normal(parameters["base_risk_aversion"], parameters["base_risk_aversion"] / 5.0)) individual_learning_ability = min( abs(np.random.normal(parameters['average_learning_ability'], 0.1)), 1.0) lft_params = TraderParameters(individual_horizon, individual_risk_aversion, individual_learning_ability, parameters['spread_max']) exp_returns = {'risky_asset': 0.0, 'money': 0.0} lft_expectations = TraderExpectations(parameters['fundamental_value'], exp_returns) traders.append(Trader(idx, lft_vars, lft_params, lft_expectations)) # initialize central bank with assets at target asset_target_cb = int(parameters["qe_perc_size"] * total_stocks) asset_target = [asset_target_cb for t in range(parameters['ticks'])] ## TODO new # Determine QE volume if parameters["qe_end"] - parameters["qe_start"] > 0: QE_periods = parameters["qe_end"] - parameters["qe_start"] total_QE_volume = parameters["qe_perc_size"] * total_stocks period_volume = int(total_QE_volume / QE_periods) #asset_target = [0 for t in range(parameters['ticks'])] for t in range(parameters['ticks']): if t in range(parameters["qe_start"], parameters["qe_end"]): asset_target[t] = asset_target[t - 1] + period_volume elif t >= parameters["qe_end"]: asset_target[t] = asset_target[t - 1] ## cb_assets = [asset_target_cb for t in range(parameters['ticks'])] currency = np.zeros(parameters["ticks"]) currency -= np.array(cb_assets) * parameters["fundamental_value"] asset_demand = 0 init_active_orders_cb = [] cb_pars = CBParameters(0.0) cb_vars = CBVariables(cb_assets, currency, asset_demand, asset_target, init_active_orders_cb) central_bank = CentralBank(cb_vars, cb_pars) # initialize the order book order_book = LimitOrderBook(parameters['fundamental_value'], parameters["std_fundamental"], max_horizon, parameters['ticks']) order_book.returns = list(historical_stock_returns) order_book.qe_period = [False for t in range(parameters['ticks'])] order_book.qt_period = [False for t in range(parameters['ticks'])] return traders, central_bank, order_book
def qe_model(traders, central_bank, orderbook, parameters, scenario=None, seed=1): """ The main model function of distribution model where trader stocks are tracked. :param traders: list of Agent objects :param orderbook: object Order book :param parameters: dictionary of parameters :param scenario: can be the following strings: BUSTQE, BUSTQT, BOOMQE, BOOMQT :param seed: integer seed to initialise the random number generators :return: list of simulated Agent objects, object simulated Order book """ random.seed(seed) np.random.seed(seed) fundamentals = [parameters["fundamental_value"]] orderbook.tick_close_price.append(fundamentals[-1]) traders_by_wealth = [t for t in traders] for tick in range(parameters['horizon'] + 1, parameters["ticks"] + parameters['horizon'] + 1): qe_tick = tick - parameters[ 'horizon'] - 1 # correcting for the memory period necessary for traders if tick == parameters['horizon'] + 1: print('Start of simulation ', seed) print(tick) # if tick == 419: # print('debug') # update money and stocks history for agents for trader in traders: trader.var.money.append(trader.var.money[-1]) trader.var.assets.append(trader.var.assets[-1]) #TODO debug trader.var.wealth.append(trader.var.money[-1] + trader.var.assets[-1] * orderbook.tick_close_price[-1]) trader.var.weight_fundamentalist.append( trader.var.weight_fundamentalist[-1]) trader.var.weight_chartist.append(trader.var.weight_chartist[-1]) trader.var.weight_random.append(trader.var.weight_random[-1]) # update money and assets for central bank central_bank.var.assets[qe_tick] = central_bank.var.assets[qe_tick - 1] if scenario in ['BUSTQE', 'BUSTQT', 'BOOMQE', 'BOOMQT', 'BLR']: central_bank.var.asset_target[ qe_tick] = central_bank.var.asset_target[qe_tick - 1] central_bank.var.currency[qe_tick] = central_bank.var.currency[qe_tick - 1] # sort the traders by wealth to traders_by_wealth.sort(key=lambda x: x.var.wealth[-1], reverse=True) # evolve the fundamental value via random walk process if True: #TODO decide what to use. fundamentals.append( max( ornstein_uhlenbeck_evolve( parameters["fundamental_value"], fundamentals[-1], parameters["std_fundamental"], parameters['bond_mean_reversion'], seed), 0.1)) else: fundamentals.append( max( fundamentals[-1] + parameters["std_fundamentals"] * np.random.randn(), 0.1)) # allow for multiple trades in one day for turn in range(parameters["trades_per_tick"]): # Allow the central bank to do Quantitative Easing #################################################### if central_bank.var.active_orders: for order in central_bank.var.active_orders: orderbook.cancel_order(order) central_bank.var.active_orders = [] # calculate PF ratio mid_price = np.mean( [orderbook.highest_bid_price, orderbook.lowest_ask_price]) pf = mid_price / fundamentals[-1] # if pf is too high, decrease asset target otherwise increase asset target quantity_available = int(central_bank.var.assets[qe_tick] * parameters['cb_size']) # TODO debug if scenario == 'BUSTQE': if pf < 1 - parameters['cb_pf_range']: # buy assets central_bank.var.asset_target[ qe_tick] += quantity_available elif scenario == 'BUSTQT': if pf < 1 - parameters['cb_pf_range']: # sell assets central_bank.var.asset_target[ qe_tick] -= quantity_available central_bank.var.asset_target[qe_tick] = max( central_bank.var.asset_target[qe_tick], 0) elif scenario == 'BOOMQE': if pf > 1 + parameters['cb_pf_range']: # buy assets central_bank.var.asset_target[ qe_tick] += quantity_available elif scenario == 'BOOMQT': if pf > 1 + parameters['cb_pf_range']: # sell assets central_bank.var.asset_target[ qe_tick] -= quantity_available central_bank.var.asset_target[qe_tick] = max( central_bank.var.asset_target[qe_tick], 0) elif scenario == 'BLR': if pf > 1 + parameters['cb_pf_range']: # sell assets central_bank.var.asset_target[ qe_tick] -= quantity_available central_bank.var.asset_target[qe_tick] = max( central_bank.var.asset_target[qe_tick], 0) elif pf < 1 - parameters['cb_pf_range']: # buy assets central_bank.var.asset_target[ qe_tick] += quantity_available # determine demand cb_demand = int(central_bank.var.asset_target[qe_tick] - central_bank.var.assets[qe_tick]) # TODO debug # Submit QE orders: if cb_demand > 0: bid = orderbook.add_bid(orderbook.lowest_ask_price, cb_demand, central_bank) central_bank.var.active_orders.append(bid) print('cb QE') orderbook.qe_period[qe_tick] = True elif cb_demand < 0: ask = orderbook.add_ask(orderbook.highest_bid_price, -cb_demand, central_bank) central_bank.var.active_orders.append(ask) print('cb QT') orderbook.qt_period[qe_tick] = True # END QE ############################################################################################## # select random sample of active traders active_traders = random.sample( traders, int((parameters['trader_sample_size']))) orderbook.returns[-1] = ( mid_price - orderbook.tick_close_price[-2] ) / orderbook.tick_close_price[-2] #todo is this correct fundamental_component = np.log(fundamentals[-1] / mid_price) chartist_component = np.cumsum( orderbook.returns[:-len(orderbook.returns) - 1:-1]) / np.arange( 1., float(len(orderbook.returns) + 1)) for trader in active_traders: # Cancel any active orders if trader.var.active_orders: for order in trader.var.active_orders: orderbook.cancel_order(order) trader.var.active_orders = [] def evolve(probability): return random.random() < probability # Evolve an expectations parameter by learning from a successful trader or mutate at random if evolve(trader.par.learning_ability): wealthy_trader = traders_by_wealth[random.randint( 0, parameters['trader_sample_size'])] trader.var.c_share_strat = np.mean([ trader.var.c_share_strat, wealthy_trader.var.c_share_strat ]) else: trader.var.c_share_strat = min( max( trader.var.c_share_strat * (1 + parameters['mutation_intensity'] * np.random.randn()), 0.01), 0.99) # update fundamentalist & chartist weights total_strat_weight = trader.var.weight_fundamentalist[ -1] + trader.var.weight_chartist[-1] trader.var.weight_chartist[ -1] = trader.var.c_share_strat * total_strat_weight trader.var.weight_fundamentalist[-1] = ( 1 - trader.var.c_share_strat) * total_strat_weight # record sentiment in orderbook orderbook.sentiment.append( np.array([ trader.var.weight_fundamentalist[-1], trader.var.weight_chartist[-1], trader.var.weight_random[-1] ])) # Update trader specific expectations noise_component = parameters['std_noise'] * np.random.randn() # Expectation formation #TODO fix below exp returns per stock asset trader.exp.returns['risky_asset'] = ( trader.var.weight_fundamentalist[-1] * np.divide( 1, float(trader.par.horizon) * parameters["fundamentalist_horizon_multiplier"]) * fundamental_component + trader.var.weight_chartist[-1] * chartist_component[trader.par.horizon - 1] + trader.var.weight_random[-1] * noise_component) fcast_price = mid_price * np.exp( trader.exp.returns['risky_asset']) obs_rets = orderbook.returns[-trader.par.horizon:] if sum(np.abs(obs_rets)) > 0: observed_returns = obs_rets else: observed_returns = np.diff( fundamentals)[-trader.par.horizon:] trader.var.covariance_matrix = calculate_covariance_matrix( observed_returns) #TODo debug, does this work as intended? # employ portfolio optimization algo TODO if observed returns 0 (replace with stdev fundamental? ideal_trader_weights = portfolio_optimization( trader, tick) #TODO debug, does this still work as intended # Determine price and volume trader_price = np.random.normal(fcast_price, trader.par.spread) position_change = (ideal_trader_weights['risky_asset'] * (trader.var.assets[-1] * trader_price + trader.var.money[-1])) - ( trader.var.assets[-1] * trader_price) volume = int(div0(position_change, trader_price)) # Trade: if volume > 0: volume = min( volume, int(div0(trader.var.money[-1], trader_price)) ) # the trader can only buy new stocks with money bid = orderbook.add_bid(trader_price, volume, trader) trader.var.active_orders.append(bid) elif volume < 0: volume = max( volume, -trader.var.assets[-1] ) # the trader can only sell as much assets as it owns ask = orderbook.add_ask(trader_price, -volume, trader) trader.var.active_orders.append(ask) # Match orders in the order-book while True: matched_orders = orderbook.match_orders() if matched_orders is None: break # execute trade matched_orders[3].owner.sell( matched_orders[1], matched_orders[0] * matched_orders[1], qe_tick) matched_orders[2].owner.buy( matched_orders[1], matched_orders[0] * matched_orders[1], qe_tick) # Clear and update order-book history orderbook.cleanse_book() orderbook.fundamental = fundamentals return traders, central_bank, orderbook
def ABM_model(traders, orderbook, market_maker, parameters, seed=1): """ The main model function of distribution model where trader stocks are tracked. :param traders: list of Agent objects :param orderbook: object Order book :param parameters: dictionary of parameters :param seed: integer seed to initialise the random number generators :return: list of simulated Agent objects, object simulated Order book """ random.seed(seed) np.random.seed(seed) fundamental = [parameters["fundamental_value"]] orderbook.tick_close_price.append(fundamental[-1]) traders_by_wealth = [t for t in traders] initial_mm_wealth = market_maker.var.wealth[0] for tick in range(parameters['horizon'] + 1, parameters["ticks"] + parameters['horizon'] + 1): # for init history if tick == parameters['horizon'] + 1: print('Start of simulation ', seed) # update money and stocks history for agents for trader in traders: trader.var.money.append(trader.var.money[-1]) trader.var.stocks.append(trader.var.stocks[-1]) trader.var.wealth.append(trader.var.money[-1] + trader.var.stocks[-1] * orderbook.tick_close_price[-1]) #TODO Jakob and / or Adrien update variables of the market maker here (similar to above) # sort the traders by wealth to traders_by_wealth.sort(key=lambda x: x.var.wealth[-1], reverse=True) # evolve the fundamental value via random walk process fundamental.append(max( ornstein_uhlenbeck_evolve(parameters["fundamental_value"], fundamental[-1], parameters["std_fundamental"], parameters['mean_reversion'], seed), 0.1)) # allow for multiple trades in one day for turn in range(parameters["trades_per_tick"]): # select random sample of active traders active_traders = random.sample(traders, int((parameters['trader_sample_size']))) mid_price = np.mean([orderbook.highest_bid_price, orderbook.lowest_ask_price]) fundamental_component = np.log(fundamental[-1] / mid_price) orderbook.returns[-1] = (mid_price - orderbook.tick_close_price[-2]) / orderbook.tick_close_price[-2] chartist_component = np.cumsum(orderbook.returns[:-len(orderbook.returns) - 1:-1] ) / np.arange(1., float(len(orderbook.returns) + 1)) # Market maker quotes best ask and bid whenever money/inventory permits # TODO Jakob / Adrien make the market maker use stock / money / wealth data over time (see traders) inventory = market_maker.var.stocks[0] inventory_value = mid_price*inventory cash = market_maker.var.money[0] wealth = cash + inventory_value print(f"Market maker has money {cash} and stocks {inventory}") print(f"Stocks are worth {inventory_value} and thus MM's wealth is {wealth}") print(f"Profit margin thus far is {round(wealth/initial_mm_wealth-1,2)*100}%") if cash > 0: bid = orderbook.add_bid(orderbook.highest_bid_price, 1, market_maker) market_maker.var.active_orders.append(bid) if inventory > 0: ask = orderbook.add_ask(orderbook.lowest_ask_price, 1, market_maker) market_maker.var.active_orders.append(ask) for trader in active_traders: # Cancel any active orders if trader.var.active_orders: for order in trader.var.active_orders: orderbook.cancel_order(order) trader.var.active_orders = [] # Update trader specific expectations noise_component = parameters['std_noise'] * np.random.randn() # Expectation formation trader.exp.returns['stocks'] = ( trader.var.weight_fundamentalist[-1] * np.divide(1, float(trader.par.horizon) * parameters["fundamentalist_horizon_multiplier"]) * fundamental_component + trader.var.weight_chartist[-1] * chartist_component[trader.par.horizon - 1] + trader.var.weight_random[-1] * noise_component) fcast_price = mid_price * np.exp(trader.exp.returns['stocks']) trader.var.covariance_matrix = calculate_covariance_matrix(orderbook.returns[-trader.par.horizon:], parameters["std_fundamental"]) # employ portfolio optimization algo ideal_trader_weights = portfolio_optimization(trader, tick) # Determine price and volume trader_price = np.random.normal(fcast_price, trader.par.spread) position_change = (ideal_trader_weights['stocks'] * (trader.var.stocks[-1] * trader_price + trader.var.money[-1]) ) - (trader.var.stocks[-1] * trader_price) volume = int(div0(position_change, trader_price)) # Trade: if volume > 0: bid = orderbook.add_bid(trader_price, volume, trader) trader.var.active_orders.append(bid) elif volume < 0: ask = orderbook.add_ask(trader_price, -volume, trader) trader.var.active_orders.append(ask) # Match orders in the order-book while True: matched_orders = orderbook.match_orders() if matched_orders is None: break # execute trade matched_orders[3].owner.sell(matched_orders[1], matched_orders[0] * matched_orders[1]) matched_orders[2].owner.buy(matched_orders[1], matched_orders[0] * matched_orders[1]) # Clear and update order-book history orderbook.cleanse_book() orderbook.fundamental = fundamental return traders, orderbook, market_maker
def init_objects(parameters, seed): """ Init object for the distribution version of the model :param parameters: :param seed: :return: """ np.random.seed(seed) random.seed(seed) traders = [] n_traders = parameters["n_traders"] weight_f = (1 - parameters['strat_share_chartists']) * ( 1 - parameters['w_random']) weight_c = parameters['strat_share_chartists'] * (1 - parameters['w_random']) f_points = int(weight_f * 100 * n_traders) c_points = int(weight_c * 100 * n_traders) r_points = int(parameters['w_random'] * 100 * n_traders) # create list of strategy points, shuffle it and divide in equal parts strat_points = ['f' for f in range(f_points)] + [ 'c' for c in range(c_points) ] + ['r' for r in range(r_points)] random.shuffle(strat_points) agent_points = np.array_split(strat_points, n_traders) max_horizon = int( parameters['horizon'] * parameters['fundamentalist_horizon_multiplier'] ) + 1 # to offset rounding historical_stock_returns = np.random.normal(0, parameters["std_noise"], max_horizon) for idx in range(n_traders): weights = [] for typ in ['f', 'c', 'r']: weights.append( list(agent_points[idx]).count(typ) / float(len(agent_points[idx]))) init_stocks = int(np.random.uniform(0, parameters["init_stocks"])) init_money = np.random.uniform( 0, (parameters["init_stocks"] * parameters['fundamental_value'])) # If there are chartists (c) & fundamentalists (f) in the model, keep track of the fraction between c & f. if weights[2] < 1.0: c_share_strat = div0(weights[1], (weights[0] + weights[1])) else: c_share_strat = 0.0 init_covariance_matrix = calculate_covariance_matrix( historical_stock_returns, parameters["std_noise"]) trader_vars = TraderVariables(weights[0], weights[1], weights[2], c_share_strat, init_money, init_stocks, init_covariance_matrix, parameters['fundamental_value']) individual_horizon = np.random.randint(10, parameters['horizon']) individual_risk_aversion = abs( np.random.normal(parameters["base_risk_aversion"], parameters["base_risk_aversion"] / 5.0) ) #parameters["base_risk_aversion"] * relative_fundamentalism trader_params = TraderParameters(individual_horizon, individual_risk_aversion, parameters['spread_max']) trader_expectations = TraderExpectations( parameters['fundamental_value']) traders.append( Trader(idx, trader_vars, trader_params, trader_expectations)) order_book = LimitOrderBook(parameters['fundamental_value'], parameters["std_noise"], max_horizon, parameters['ticks']) # initialize order-book returns for initial variance calculations order_book.returns = list(historical_stock_returns) return traders, order_book