def crossing_averages(n,m,stock_prices,available_capital,fees,portfolio,ledger = 'ledger_crossing_averages.txt'): ''' The relationship between the moving average curve determines whether to buy or sell Input: n(int): SMA period m(int): FMA period available_capital(float): Budget for each stock purchased each time fees(float): Cost per transaction portfolio ledger(str): File to store purchase information Output: purchase point ''' for stocks in range(len(stock_prices[0])): # Calculate the FMA and SMA of each stock FMA = indi.moving_average(stock_prices[:,stocks], m) SMA = indi.moving_average(stock_prices[:,stocks], n) # Initiate the event list of purchase time and sales buy_point = [] sell_point = [] final = [] # Find the point where the two curves intersect, according to the size of the stock price on the left and right for x in range(1,len(stock_prices)-1): if FMA[x-1] < SMA[x-1] and FMA[x+1] > SMA [x+1]: buy_point.append(x) elif FMA[x-1] > SMA[x-1] and FMA[x+1] < SMA [x+1]: sell_point.append(x) for day in range(len(stock_prices)): if day+1 in buy_point: proc.buy(day,stocks,available_capital,stock_prices,fees,portfolio,ledger) elif day+1 in sell_point: proc.sell(day,stocks,stock_prices,fees,portfolio,ledger) final.append([buy_point,sell_point]) return final
def momentum(stock_prices, cool_down_period=3, period=7, osc_type='stochastic', amount=5000, fees=20, ledger='ledger_momentum.txt'): ''' Oscillators can help us guess if the price of a share is currently overvalued (overbought) or undervalued (oversold). Generally: the price is considered overvalued when the oscillator is above a threshold of 0.7 to 0.8 (good time to sell). the price is considered undervalued when the oscillator is below a threshold of 0.2 to 0.3 (good time to buy). ''' day = period stock_list = stock_prices[0, ] portfolio = proc.create_portfolio([5000] * len(stock_list), stock_prices, fees) valued = np.array([ oscillator(stock_prices[:, i], period, osc_type) for i in range(len(stock_list)) ]) while day < (stock_prices.shape[0] - period): if day != 0 and (portfolio == np.zeros(len(portfolio))).all(): break for stock in range(len(stock_list)): # When the FMA crosses the SMA from below,buy if valued[stock][day] > 0.2 and valued[stock][day] < 0.3: proc.buy(day, stock, amount, stock_prices, fees, portfolio, ledger) elif valued[stock][day] > 0.7 and valued[stock][day] < 0.8: proc.sell(day, stock, stock_prices, fees, portfolio, ledger) day += cool_down_period
def momentum(stock_prices, osc_type='RSI', mom_period=7, cooldown_period=7, thresholds=(0.25, 0.75), amount=5000, fees=20, ledger='ledger_mom.txt'): ''' Algorithmic trading strategy based on the use of oscillators. Input: stock_prices (ndarray): the stock price data osc_type (str, default RSI): oscillator to use (RSI or stochastic) mom_period (int, default 7): number of days used to calculate oscillator cooldown_period (int, default 7): number of days to wait between actions thresholds (tuple (len2), deafault (0.25, 0.75)): thresholds used to determine buying and selling days amount (float, default 5000): how much we spend on each purchase (must cover fees) fees (float, default 20): transaction fees ledger (str): path to the ledger file Output: None ''' # Number of stocks simulated N = int(stock_prices.shape[1]) # Create day 0 portfolio portfolio = proc.create_portfolio(np.repeat(amount, N), stock_prices, fees, ledger) for stock_id in range(N): # Get oscillator values oscillator = indi.oscillator(stock_prices[:, stock_id], n=mom_period, osc_type=osc_type) # Set NaNs to 0 nans_locs = np.isnan(oscillator) oscillator[nans_locs] = 0 # Find buy days and sell days buy_days = np.where(oscillator < thresholds[0])[0] sell_days = np.where(oscillator > thresholds[1])[0] # Oscillator values are only valid when day >= mom_period day = mom_period # Perform transactions with cooldown while day < len(stock_prices[:, stock_id]): if day in buy_days: proc.buy(day, stock_id, amount, stock_prices, fees, portfolio, ledger) day += cooldown_period elif day in sell_days: proc.sell(day, stock_id, stock_prices, fees, portfolio, ledger) day += cooldown_period else: day += 1 # Sell all stock on final day sell_all_stock(stock_prices, fees, portfolio, ledger)
def crossing_averages(stock_prices, sma_window=200, fma_window=50, amount=5000, fees=20, ledger='ledger_cross.txt'): ''' Algorithmic trading strategy based on the crossing of the slow moving average (SMA) and fast moving average (FMA). - When the FMA crosses the SMA from below we buy shares. - When the FMA crosses the SMA from above we sell shares Input: stock_prices (ndarray): the stock price data sma_period (int, default 200): days used to calculate SMA fma_period (int, default 50): days used to calculate FMA amount (float, default 5000): how much we spend on each purchase (must cover fees) fees (float, default 20): transaction fees ledger (str): path to the ledger file Output: None ''' # Number of stocks simulated N = int(stock_prices.shape[1]) # Create day 0 portfolio portfolio = proc.create_portfolio(np.repeat(amount, N), stock_prices, fees, ledger) assert fma_window < sma_window, "Your SMA period must be less than your FMA period" for stock_id in range(N): # SMA calculated from day = sma_period sma = indi.moving_average( stock_prices[:, stock_id], window_size=sma_window) # size: N - sma_period # FMA calculated from day = fma_period fma = indi.moving_average( stock_prices[:, stock_id], window_size=fma_window)[sma_window - fma_window:] # size: N - sma_period # Comparison from day = sma_period - fma_period deltas = fma - sma # Find buying and selling days cross_days = np.where((np.diff(np.sign(deltas)) != 0))[0] + 1 for cross_day in cross_days: if (fma[cross_day - 1] > sma[cross_day - 1]): proc.sell(cross_day + sma_window - 1, stock_id, stock_prices, fees, portfolio, ledger) else: proc.buy(cross_day + sma_window - 1, stock_id, amount, stock_prices, fees, portfolio, ledger) # Sell final day stock sell_all_stock(stock_prices, fees, portfolio, ledger)
def random(stock_prices, period = 7, amount = 5000, fees = 20, ledger = 'random_ledger.txt'): ''' Randomly decide, every period, which stocks to purchase, do nothing, or sell (with equal probability). Spend a maximum of amount on every purchase. Records transaction data in given ledger. Input: stock_prices (ndarray): the stock price data period (int, default 7): how often we buy/sell (days) amount (float, default 5000): how much we spend on each purchase (must cover fees) fees (float, default 20): transaction fees ledger (str, default 'ledger_random.txt'): path to the ledger file Output: None Example: Perform random strategy with period 8-days on a given portfolio of stock. Returns None >>> random(stock_price_data, 8) ''' # get number of stocks and number of days number_of_days, N = stock_prices.shape # initialize portfolio portfolio = proc.create_portfolio(N * [amount], stock_prices, fees, ledger) # set default random number generator rng = np.random.default_rng() # loop over each period, we buy on first day so start from 'periodth' day for day in range(period, number_of_days, period): # draw integers for each stock, 1 is buy, -1 is sell, 0 do nothing random_array = rng.integers(-1, 2, size = N) # decide which stocks to buy and sell stocks_to_buy = np.where(random_array == 1)[0] stocks_to_sell = np.where(random_array == -1)[0] # if there are stocks to buy, buy them if len(np.atleast_1d(stocks_to_buy)) > 0: for stock in stocks_to_buy: proc.buy(day, stock, amount, stock_prices, fees, portfolio, ledger) # if there are stocks to sell, sell them if len(np.atleast_1d(stocks_to_sell)) > 0: for stock in stocks_to_sell: # only sell if we have them if portfolio[stock] > 0: proc.sell(day, stock, stock_prices, fees, portfolio, ledger) # if number is equal to 2 we do nothing and go to next period and sell our portfolio at the end for stock_number in range(N): proc.sell(number_of_days - 1, stock_number, stock_prices, fees, portfolio, ledger)
def sell_all_stock(stock_prices, fees, portfolio, ledger): ''' Sell all stock on the final day. Input: stock_prices (ndarray): the stock price data fees (float, default 20): transaction fees portfolio (list): our current portfolio ledger (str): path to the ledger file Output: None ''' # Number of stocks simulated N = int(stock_prices.shape[1]) # Finish with selling all stock on final day for stock_id in range(N): proc.sell(stock_prices.shape[0] - 1, stock_id, stock_prices, fees, portfolio, ledger)
def crossing_averages(stock_prices, n=50, m=200, amount=5000, fees=20, ledger='ledger_crossing_averages.txt'): ''' When the FMA crosses the SMA from below, then the share price is starting to rise significantly, and it's a good time to buy shares. When the FMA crosses the SMA from above, then the share price is starting to lower significantly, and it's a good time to sell shares before the price gets too low. Input: stock_prices (ndarray): the stock price data n (int, default 50): FMA with period � m (int, default 200): SMA with period m amount (float, default 5000): how much we spend on each purchase (must cover fees) fees (float, default 20): transaction fees ledger (str): path to the ledger file Output: None ''' day = m stock_list = stock_prices[0, ] portfolio = proc.create_portfolio([5000] * len(stock_list), stock_prices, fees) SMA = np.array([ moving_average(stock_prices[:, i], m) for i in range(len(stock_list)) ]) FMA = np.array([ moving_average(stock_prices[:, i], n) for i in range(len(stock_list)) ]) while day < (stock_prices.shape[0] - m): if day != 0 and (portfolio == np.zeros(len(portfolio))).all(): break for stock in range(len(stock_list)): #When the FMA crosses the SMA from below,buy if FMA[stock][day] < SMA[stock][day]: proc.buy(day, stock, amount, stock_prices, fees, portfolio, ledger) else: proc.sell(day, stock, stock_prices, fees, portfolio, ledger) day += 1
def random(stock_prices, period=7, amount=5000, fees=20, ledger='ledger_random.txt'): ''' Randomly decide, every period, which stocks to purchase, do nothing, or sell (with equal probability). Spend a maximum of amount on every purchase. Input: stock_prices (ndarray): the stock price data period (int, default 7): how often we buy/sell (days) amount (float, default 5000): how much we spend on each purchase (must cover fees) fees (float, default 20): transaction fees ledger (str): path to the ledger file Output: None ''' # Number of stock to simulate N = int(stock_prices.shape[1]) # Create day 0 portfolio portfolio = proc.create_portfolio(np.repeat(amount, N), stock_prices, fees, ledger) # Determine dates on which we act action_days = np.arange(1, stock_prices.shape[0] + 1, period) for action_day in action_days: for stock_id in range(N): # Randomly sample action from discrete unif dist. action = np.random.choice([0, 1, 2]) if action == 1: # Buying proc.buy(action_day, stock_id, amount, stock_prices, fees, portfolio, ledger) elif action == 2: # Selling proc.sell(action_day, stock_id, stock_prices, fees, portfolio, ledger) # Finish with selling all stock on final day sell_all_stock(stock_prices, fees, portfolio, ledger)
def random(stock_prices, period=7, amount=5000, fees=20, ledger='ledger_random.txt'): ''' Randomly decide, every period, which stocks to purchase, do nothing, or sell (with equal probability). Spend a maximum of amount on every purchase. Input: stock_prices (ndarray): the stock price data period (int, default 7): how often we buy/sell (days) amount (float, default 5000): how much we spend on each purchase (must cover fees) fees (float, default 20): transaction fees ledger (str): path to the ledger file Output: None ''' decision = ['purchase', 'nothing', 'sell'] stock_list = stock_prices[0, ] portfolio = proc.create_portfolio([5000] * len(stock_list), stock_prices, fees) day = 0 while day < stock_prices.shape[0]: if day != 0 and (portfolio == np.zeros(len(portfolio))).all(): break action = np.random.choice(decision) if action == "purchase": for stock in range(len(stock_list)): proc.buy(day, stock, amount, stock_prices, fees, portfolio, ledger) elif action == "nothing": continue elif action == "sell": for stock in range(len(stock_list)): proc.sell(day, stock, stock_prices, fees, portfolio, ledger) day += 1
def momentum(stock_prices,available_capital,fees,portfolio,n=7,osc_type='stochastic',ledger = 'ledger_momentum.txt'): ''' OSC decides whether to buy or sell Input: stock_prices(array): All prices of all stocks available_capital(float): Budget for each stock purchased each time fees(float): Cost per transaction fees(float): Cost per transaction portfolio ledger(str): File to store purchase information Output: None ''' for stocks in range(len(stock_prices[0])): # Calculate OSC result = indi.oscillator(stock_prices[:,stocks], n=n, osc_type='stochastic').tolist() # Initiate the event list of purchase time and sales buy_point = [] sell_point = [] for i in range(0, len(result)): # Judging whether to buy or sell by threshold, the cooling off period for each transaction is 10 days if result[i] > 0.2 and result[i] < 0.3: if len(buy_point) > 0 and len(sell_point) > 0: if i > max(buy_point[-1],sell_point[-1]) + 5: buy_point.append(i) else: buy_point.append(i) elif result[i] > 0.7 and result[i] < 0.8: if len(buy_point) > 0 and len(sell_point) > 0: if i > max(buy_point[-1],sell_point[-1]) + 5: sell_point.append(i) else: sell_point.append(i) # Buy and sell stocks according to the time just set for day in range(len(stock_prices)): if day+1 in buy_point: proc.buy(day,stocks,available_capital,stock_prices,fees,portfolio,ledger) elif day+1 in sell_point: proc.sell(day,stocks,stock_prices,fees,portfolio,ledger)
def random(stock_prices,portfolio,period=7, amount=5000, fees=20, ledger='ledger_random.txt'): ''' Randomly decide, every period, which stocks to purchase, do nothing, or sell (with equal probability). Spend a maximum of amount on every purchase. Input: stock_prices (ndarray): the stock price data period (int, default 7): how often we buy/sell (days) amount (float, default 5000): how much we spend on each purchase (must cover fees) fees (float, default 20): transaction fees ledger (str): path to the ledger file Output: None ''' # Calculate effective length length = (len(stock_prices[:,0]) - 1) - (len(stock_prices[:,0]) - 1) % period # Calculate the number of types of stock stocks = len(stock_prices[0]) for day in range(1, length, period): choice = ['buy','sell','nothing'] # Random select behavior rng = np.random.default_rng() action = rng.choice(choice) if action == 'buy': # for loop traverse each stock for stock in range(0,stocks): # buy stock proc.buy(day, stock, amount, stock_prices, fees, portfolio, ledger) elif action == 'sell': # for loop traverse each stock for stock in range(0, stocks): # sell all stock proc.sell(day,stock,stock_prices,fees,portfolio,ledger) else: pass
def random(stock_prices, period=7, amount=5000, fees=20, ledger='ledger_random.txt'): ''' Randomly decide, every period, which stocks to purchase, do nothing, or sell (with equal probability). Spend a maximum of amount on every purchase. Input: stock_prices (ndarray): the stock price data period (int, default 7): how often we buy/sell (days) amount (float, default 5000): how much we spend on each purchase (must cover fees) fees (float, default 20): transaction fees ledger (str): path to the ledger file Output: None ''' #important: when price of a stock is nan, then sell all the shares of that stock at price 0 rng = np.random.default_rng() num_stock = stock_prices.shape[1] days = stock_prices.shape[0] #create portfolio at day 0 portfolio = proc.create_portfolio([amount] * num_stock, stock_prices, fees, ledger=ledger) num_transaction = math.floor(days / period) for i in range(1, num_transaction + 1): # 0: buy 1: do nothing 2:sell for j in range(num_stock): if np.isnan(stock_prices[i, j]) == True: # sell at price 0 proc.sell(period * i, j, np.zeros((days, num_stock)), fees, portfolio, ledger) break else: action = rng.choice([0, 1, 2], 1, p=[ 1 / 3, 1 / 3, 1 / 3 ]) #determine the action with equal probability if action == 0: # buy stock j proc.buy( period * i, j, amount, stock_prices, fees, portfolio, ledger ) # price is the price on the last day in each period elif action == 2: #sell stock j proc.sell(period * i, j, stock_prices, fees, portfolio, ledger) #then, if there are some remaining stocks, we sell them in the last period for k in range(num_stock): if portfolio[k] != 0: proc.sell(days - 1, k, stock_prices, fees, portfolio, ledger) return None
def random(stock_prices, period=7, amount=5000, fees=20, ledger='ledger_random.txt'): ''' Randomly decide, every period, which stocks to purchase, do nothing, or sell (with equal probability). Spend a maximum of amount on every purchase. Input: stock_prices (ndarray): the stock price data period (int, default 7): how often we buy/sell (days) amount (float, default 5000): how much we spend on each purchase (must cover fees) fees (float, default 20): transaction fees ledger (str): path to the ledger file Output: None ''' portfolio = proc.create_portfolio([amount] * stock_prices.shape[1], stock_prices, fees, ledger) for i in range(period, len(stock_prices), period): for stock_no, share in enumerate(portfolio): # if share is -1, it means the company is bankruptcy if share == -1: continue elif stock_prices[i, stock_no] is None: # It defines that when element in portfolio is -1, it means never buy this company's stock. portfolio[stock_no] = -1 continue else: # generate day 0 action list. 0 represent do nothing, 1 represent buy and -1 represent sell. if i == len(stock_prices) - 1: proc.sell(i, stock_no, stock_prices, fees, portfolio, ledger_file=ledger) else: action = np.random.randint(-1, 2, size=1) if action == 1: proc.buy(i, stock_no, amount, stock_prices, fees, portfolio, ledger_file=ledger) elif action == -1: proc.sell(i, stock_no, stock_prices, fees, portfolio, ledger_file=ledger) # if at the last day, it remains stock, just sell them out last_day = len(stock_prices) - 1 for stock_no, share in enumerate(portfolio): if share > 0 and stock_prices[last_day, stock_no] is not None: proc.sell(last_day, stock_no, stock_prices, fees, portfolio, ledger_file=ledger)
def momentum(stock_prices, period=7, low_threshold=0.3, up_threshold=0.7, amount=5000, fees=20, osc_type="stochastic", ledger='ledger_random.txt', cool_down_period=4): """ using oscillator indicator to get portfolio. Input: stock_prices(ndarray): stock prices period: n of FMA low_threshold: the down threshold of indicator up_threshold: the up threshold of indicator amount: input amount osc_type: oscillator type fees: fees of transaction ledger: ledger file name cool_down_period: time to cool down Output: osc: osc result """ osc = np.full_like(stock_prices, None) cool_down_table = [0] * stock_prices.shape[1] for stock_no in range(stock_prices.shape[1]): osc[period - 1:, stock_no] = indic.oscillator(stock_prices[:, stock_no], period, osc_type) portfolio = proc.create_portfolio([amount] * stock_prices.shape[1], stock_prices, fees, ledger) for i in range(1, len(stock_prices)): for stock_no, share in enumerate(portfolio): if cool_down_table[stock_no] > 0: cool_down_table[stock_no] -= 1 continue # if share is -1, it means the company is bankruptcy if share == -1: continue elif stock_prices[i, stock_no] is None or np.isnan( stock_prices[i, stock_no]): # It defines that when element in portfolio is -1, it means never buy this company's stock. portfolio[stock_no] = -1 continue else: if osc[i, stock_no] > up_threshold: proc.buy(i, stock_no, amount, stock_prices, fees, portfolio, ledger) elif osc[i, stock_no] < low_threshold: proc.sell(i, stock_no, stock_prices, fees, portfolio, ledger) cool_down_table[stock_no] = cool_down_period last_day = len(stock_prices) - 1 for stock_no, share in enumerate(portfolio): if share > 0 and stock_prices[last_day, stock_no] is not None: proc.sell(last_day, stock_no, stock_prices, fees, portfolio, ledger_file=ledger) return osc
def crossing_averages(stock_prices, amount = 5000, cool_down_period = 5, n = 200, m = 50, n_weights = [], m_weights = [], plot = False, fees = 20, ledger = 'crossing_average_ledger.txt'): ''' Decide to buy shares when the m-day moving average crosses the n-day moving average from below, and decide to sell shares when the m-day moving average crosses the n-day mving average from above. Records transactions in ledger. Input: stock_prices (ndarray): the stock price data amount (float, default 5000): how much we spend on each purchase (must cover fees) cool_down_period (int, default = 5): how long to wait before making another trade (nb days after a trade is made) n (int, default 200): period in days for the slow moving average (n > m) m (int, default 50): period in days for the fast moving average (m < n) n_weights (list, default []): must be of length n if specified. Indicates the weights to use for the weighted average. If empty, return a non-weighted average. m_weights (list, default []): must be of length m if specified. Indicates the weights to use for the weighted average. If empty, return a non-weighted average. plot (boolean, default False): Plots moving averages if True. fees (float, default 20): transaction fees ledger (str, default 'crossing_average_ledger.txt'): path to the ledger file Output: None Example: Perform crossing average strategy with FMA period of 50 days and SMA period of 200 days with no weights and no cool_down period on a given portfolio. Returns None. >>> crossing_average(stock_price_data, cool_period = 0, n = 200, m = 50) ''' # get number of stocks and number of days total_days, N = stock_prices.shape # initialize portfolio portfolio = proc.create_portfolio(N * [amount], stock_prices, fees, ledger) # initialize MA arrays, both must have same size to match up the dates m_day_MA = np.zeros(stock_prices.shape) n_day_MA = np.zeros(stock_prices.shape) # initialize cool down matrix which indicates a cool down period cool_down_matrix = np.zeros(stock_prices.shape) # get n_day MA n_day_MA = ind.moving_average(stock_prices, n, n_weights) # get m_day MA m_day_MA = ind.moving_average(stock_prices, m, m_weights) # now loop through each day starting from day n + 1 (index n) and buy, sell as appropriate for day in range(n, total_days): # find stocks that cross from below and check that they are out of cool down period stocks_to_buy = np.where((m_day_MA[day - 1] < n_day_MA[day - 1]) & (m_day_MA[day] > n_day_MA[day]) & (cool_down_matrix[(day - cool_down_period - 1)] != 1))[0] # if there are stocks to buy, buy them if np.any(stocks_to_buy): for stock in stocks_to_buy: proc.buy(day, stock, amount, stock_prices, fees, portfolio, ledger) # indicate not to buy during cool down period cool_down_matrix[(day - cool_down_period) : day, stocks_to_buy] = 1 # if it crosses from above, we sell # find stocks that cross from above and check that they are out of cool down period stocks_to_sell = np.where((m_day_MA[day - 1] > n_day_MA[day - 1]) & (m_day_MA[day] < n_day_MA[day]) & (cool_down_matrix[(day - cool_down_period - 1)] != 1))[0] # if there are stocks to sell, buy them if np.any(stocks_to_sell): for stock in stocks_to_sell: proc.sell(day, stock, stock_prices, fees, portfolio, ledger) # indicate not to buy during cool down period cool_down_matrix[(day - cool_down_period) : day, stocks_to_sell] = 1 # sell portfolio at end for stock_number in range(N): proc.sell(total_days - 1, stock_number, stock_prices, fees, portfolio, ledger) # option to see plot if plot == True: for i in range(N): plt.plot(stock_prices[:, i], label = f'Stock Price {i}') plt.plot(n_day_MA[:, i], label = f'Stock {i} {n}-day MA') plt.plot(m_day_MA[:, i], label = f'Stock {i} {m}-day MA') plt.legend() plt.xlabel('Time (days)') plt.ylabel('Price ($)') plt.title(f'{n}-day MA vs {m}-day MA') plt.grid() plt.show()
def crossing_averages(stock_prices, fast_n=50, slow_n=200, amount=5000, fees=20, ledger='ledger_random.txt'): """ Decide by crossing average method. Input: stock_prices(ndarray): stock prices fast_n: n of FMA slow_n: n of SMA amount: input amount fees: fees of transaction ledger: ledger file name Output: (FMA,SMA):(ndarray,ndarray) the ma indicator of this price. """ FMA = np.full_like(stock_prices, fill_value=None) SMA = np.full_like(stock_prices, fill_value=None) for stock_no in range(stock_prices.shape[1]): FMA[fast_n - 1:, stock_no] = indic.moving_average(stock_prices[:, stock_no], fast_n) SMA[slow_n - 1:, stock_no] = indic.moving_average(stock_prices[:, stock_no], slow_n) actions_buy = np.where(FMA < SMA, 1, 0) actions_sell = np.where(FMA > SMA, -1, 0) portfolio = proc.create_portfolio([amount] * stock_prices.shape[1], stock_prices, fees, ledger) for i in range(1, len(stock_prices)): for stock_no, share in enumerate(portfolio): # if share is -1, it means the company is bankruptcy if share == -1: continue elif stock_prices[i, stock_no] is None: # It defines that when element in portfolio is -1, it means never buy this company's stock. portfolio[stock_no] = -1 continue else: if actions_buy[i, stock_no] == 1 and stock_prices[ i, stock_no] > stock_prices[i - 1, stock_no]: proc.buy(i, stock_no, amount, stock_prices, fees, portfolio, ledger) elif actions_sell[i, stock_no] == 1 and stock_prices[ i, stock_no] < stock_prices[i - 1, stock_no]: proc.sell(i, stock_no, stock_prices, fees, portfolio, ledger) # if at the last day, it remains stock, just sell them out last_day = len(stock_prices) - 1 for stock_no, share in enumerate(portfolio): if share > 0 and stock_prices[last_day, stock_no] is not None: proc.sell(last_day, stock_no, stock_prices, fees, portfolio, ledger_file=ledger) return FMA, SMA
def momentum(stock_prices, osc_type = 'stochastic', lower = 0.25, upper = 0.75, n = 7, wait_time = 3, plot = False, smoothing_period = False, amount = 5000, fees = 20, ledger = 'momentum_ledger.txt'): ''' Decide to sell shares in a portfolio when chosen oscillator is above upper threshold and buy when below lower threshold. Only buys/sells after wait_time (days) and only buys/sells once every time threshold is crossed. Input: stock_prices (ndarray): the stock price data osc_type (str, default 'stochastic'): either 'stochastic' or 'RSI' to choose an oscillator. lower (float, default 0.25): lower threshold upper (float, default 0.75): upper threshold n (int, default 7): period of the oscillator (in days). wait_time (int, default 10): period (in days) to wait before buying/selling stock if price remains below/above threshold. plot (boolean, default False): shows plot of oscillator if True. smoothing_period (int, default = False): period of moving average to be applied to the oscillator. amount (float, default 5000): how much we spend on each purchase (must cover fees) fees (float, default 20): transaction fees ledger (str, default 'crossing_average_ledger.txt'): path to the ledger file Output: None Example: Perform momentum strategy with 7-day RSI with thresholds 0.2 and 0.8, 4-day wait time and no smoothing period on a given portfolio. Returns None >>> momentum(stock_price_data, osc_type = 'RSI', lower = 0.2, upper = 0.8, wait_time = 4) ''' # get number of stocks and number of days total_days, N = stock_prices.shape # initialize portfolio portfolio = proc.create_portfolio(N * [amount], stock_prices, fees, ledger) # initialize oscillator array oscillator = np.zeros(stock_prices.shape) # get the oscillator for each stock oscillator = ind.oscillator(stock_prices, n, osc_type, smoothing_period) # get starting day of trading if smoothing_period != False and smoothing_period != 0: day_1 = n + smoothing_period - 1 else: day_1 = n - 1 if wait_time > 0: # initialize indicator matrix which indicates we can buy after waiting time indicator_matrix = np.zeros(oscillator.shape) # now loop through each day to decide whether to buy or sell and implement the wait time for day in range(day_1, total_days): # check if oscillator is below lower threshold and this is first time we cross threshold stocks_to_buy_later = np.where((oscillator[day] < lower) & (oscillator[(day - 1)] >= lower))[0] # indicate to buy later indicator_matrix[day, stocks_to_buy_later] = 1 # check indicator matrix, if 1 and stock remained below threshold we buy stocks_to_buy_now = np.where((indicator_matrix[(day - wait_time)] == 1) & (np.all(oscillator[(day - wait_time) : (day + 1)] < lower, axis = 0)))[0] # if there are stocks to buy, buy them if np.any(stocks_to_buy_now): for stock in stocks_to_buy_now: proc.buy(day, stock, amount, stock_prices, fees, portfolio, ledger) # check if oscillator is above upper threshold and this is first time we cross threshold stocks_to_sell_later = np.where((oscillator[day] > upper) & (oscillator[(day - 1)] <= upper))[0] # indicate to sell later indicator_matrix[day, stocks_to_sell_later] = 1 # check indicator matrix, if 1 and stock remained below threshold we buy stocks_to_sell_now = np.where((indicator_matrix[(day - wait_time)] == 1) & (np.all(oscillator[(day - wait_time) : (day + 1)] > upper, axis = 0)))[0] # if there are stocks to sell, sell them if np.any(stocks_to_sell_now) != 0: for stock in stocks_to_sell_now: proc.sell(day, stock, stock_prices, fees, portfolio, ledger) else: # now loop through each day to decide whether to buy or sell and implement the wait time for day in range(day_1, total_days): # check if oscillator is below lower threshold and this is first time we cross threshold stocks_to_buy_now = np.where((oscillator[day] < lower) & (oscillator[(day - 1)] >= lower ))[0] # if there are stocks to buy, buy them if np.any(stocks_to_buy_now): for stock in stocks_to_buy_now: proc.buy(day, stock, amount, stock_prices, fees, portfolio, ledger) # check if oscillator is above upper threshold and this is first time we cross threshold stocks_to_sell_now = np.where((oscillator[day] > upper) & (oscillator[(day - 1)] <= upper))[0] # if there are stocks to sell, sell them if np.any(stocks_to_sell_now) != 0: for stock in stocks_to_sell_now: proc.sell(day, stock, stock_prices, fees, portfolio, ledger) # sell portfolio at the end for stock_number in range(N): proc.sell(total_days - 1, stock_number, stock_prices, fees, portfolio, ledger) # option to plot oscillator with thresholds if plot == True: for i in range(N): plt.plot(oscillator[:, i], label = f'Stock {i}') plt.hlines(upper, n, total_days, colors = 'r', linestyles = '--', label = 'Upper') plt.hlines(lower, n, total_days, colors = 'r', linestyles = '--', label = 'Lower') plt.xlabel('Time (days)') plt.ylabel('Value') plt.legend() plt.title(f'{osc_type} Oscillator, Upper Threshold = {upper}, Lower Threshold = {lower}') plt.grid()
def crossing_averages(stock_prices, n, m, amount=5000, fees=20, ledger='ledger_crossing_average.txt'): ''' Input: stock_prices (ndarray): the stock price data period (int, default 7): how often we buy/sell (days) amount (float, default 5000): how much we spend on each purchase (must cover fees) fees (float, default 20): transaction fees ledger (str): path to the ledger file Output: None ''' num_stock = stock_prices.shape[1] days = stock_prices.shape[0] #create portfolio at day 0 portfolio = proc.create_portfolio([amount] * num_stock, stock_prices, fees, ledger=ledger) #loop for each stock for i in range(num_stock): #to better compare the values of FMA and SMA, I force the length of them to be the number of days by adding n-1 "0"s in #the beginning of the FMA and SMA. FMA = np.hstack(([0] * (n - 1), ind.moving_average(stock_prices[:, i], n, weights=[]))) SMA = np.hstack(([0] * (m - 1), ind.moving_average(stock_prices[:, i], m, weights=[]))) for j in range(m, days - 1): if np.isnan( FMA[j] ) == False: # FMA is not nan, then SMA is not nan (i.e. price is not nan) if (FMA[j] - SMA[j]) * (FMA[j + 1] - SMA[j + 1]) < 0: # a crossing occurs if FMA[j + 1] > SMA[ j + 1]: # FMA crosses SMA from below, buy stock i at day j proc.buy(j, i, amount, stock_prices, fees, portfolio, ledger) elif FMA[j + 1] < SMA[ j + 1]: # FMA crosses SMA from above, sell stock i at day j proc.sell(j, i, stock_prices, fees, portfolio, ledger) else: portfolio = portfolio else: #sell at price 0 proc.sell(j, i, np.zeros((days, num_stock)), fees, portfolio, ledger) break #then, if there are some remaining stocks, we sell them on the last day for k in range(num_stock): if portfolio[k] != 0: proc.sell(days - 1, k, stock_prices, fees, portfolio, ledger) return None
def momentum(stock_prices, threshold_high, threshold_low, amount=5000, fees=20, n=7, m=5, cool_period=7, osc_type='stochastic', ledger='ledger_momentum.txt'): ''' Input: stock_prices (ndarray): the stock price data period (int, default 7): how often we buy/sell (days) amount (float, default 5000): how much we spend on each purchase (must cover fees) fees (float, default 20): transaction fees ledger (str): path to the ledger file Output: None general thought: buy only if it's stayed below the threshold for a number of consecutive m days, sell only if it's stayed above the threshold for a number of consecutive m days. ''' num_stock = stock_prices.shape[1] days = stock_prices.shape[0] #create portfolio at day 0 portfolio = proc.create_portfolio([amount] * num_stock, stock_prices, fees, ledger=ledger) for i in range(num_stock): # to create a osc array with length of days, we add a array of all elements equal 0.5 to original osc.As 0.5<0.7 # and 0.5>0.3 osc = np.hstack(([0.5] * (n - 1), ind.oscillator(stock_price=stock_prices[:, i], n=n, osc_type=osc_type))) flag = 0 # count for cool_down period for j in range(days - m + 2): # check from day 0 to day days-m+1 if flag > 0: flag -= 1 continue if np.all(np.isnan(osc[j:j + m]) == False) == True: # there is no nan # if from day j to day j+m-1, all osc> threshold_high, then sell the stock at day j+m-1 if np.all(osc[j:j + m] > threshold_high) == True: proc.sell(j + m - 1, i, stock_prices, fees, portfolio, ledger) flag = cool_period # if from day j to day j+m-1, all osc< threshold_low, then buy the stock at day j+m-1 elif np.all(osc[j:j + m] < threshold_low) == True: #buy proc.buy(j + m - 1, i, amount, stock_prices, fees, portfolio, ledger) flag = cool_period else: portfolio = portfolio else: # sell at priice 0 proc.sell(j, i, np.zeros((days, num_stock)), fees, portfolio, ledger) break for k in range(num_stock): if portfolio[k] != 0: proc.sell(days - 1, k, stock_prices, fees, portfolio, ledger) return None