Exemple #1
0
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 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 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)
Exemple #6
0
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)
Exemple #8
0
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 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 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
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 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,
             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