예제 #1
0
def save_symbols(symbols: SyncedList, start: dt.datetime, end: dt.datetime,
                 listener: Listener, dir: str):
    """
    Saves data from the range given by <start>-<end> for each symbol in <symbols>, placing output in the folder
    given by <dir>.

    For each symbol in <symbols>, fetches as much price data as possible within the given range, and saves it in a file
    named <symbol>.csv, in the folder given by <dir>.
    Args:
        symbols: a SyncedList of stock symbols that should be saved
        start: a datetime object representing the beginning of the range to try and get data from
        end:
        listener:
        dir:

    Returns:

    """
    msg = Message()

    symbol = symbols.pop()
    while symbol is not None:
        msg.reset()
        msg.add_line("Saving " + symbol + "...")
        listener.send(msg)
        save(symbol, start, end, listener, dir)
        symbol = symbols.pop()
예제 #2
0
def _analyze_thread(symbols: SyncedList, tracker: IndicatorResultTracker,
                    days: int, listener: Listener):
    symbol = symbols.pop()
    while symbol is not None:
        msg = Message()
        msg.add_line(f"Thread {threading.currentThread()} analyzing {symbol}")
        listener.send(msg)
        analyze(symbol, tracker, days, listener)
        symbol = symbols.pop()
예제 #3
0
def MACD_signal(symbol: str, listener: Listener, start: datetime.datetime, end: datetime.datetime, local=False,
                dir="") -> \
        List[datetime.datetime]:
    """
    Look for the MACD of the given stock to cross above its signal line WHILE price is also above the 200-day EMA.
    Return a list of all days that are the endpoints of such crosses. That is, the list of all days between <start> and
    <end> where MACD was above its signal line having been below it the day before.

    If <local>=True, assumes that <dir> is a string containing a path to a directory containing stock data.
    Files in this directory should be .csv's and have filename equal to the stock symbol whose data they are holding.
    This method assumes that the file <symbol>.csv exists in <dir>, if <local>=True.

    Prints a message to the console using <listener> and returns an empty list if not enough data could be found to
    compute the necessary values.
    """
    try:
        macd = MACD(symbol, start, end, local=local, dir=dir)
    except NotEnoughDataError as e:
        msg = Message()
        msg.add_line(str(e))
        listener.send(msg)

        return []

    # Get a 200-day EMA of price
    data = get_data(symbol, start, end, local=local, dir=dir)
    if data is None:
        return []

    try:
        EMA(data, "Adj Close", "EMA", 200)
    except NotEnoughDataError as e:
        msg = Message()
        msg.add_line(str(e))
        listener.send(msg)

    signals = []
    for i in range(1, len(macd.index)):
        day_before = macd.index[i - 1]
        day = macd.index[i]
        if macd["MACD"][day] > macd["Signal"][day] and macd["MACD"][day_before] <= macd["Signal"][day_before] and \
                data["Adj Close"][day] > data["EMA"][day]:
            signals.append(day)

    return signals
예제 #4
0
def save(symbol: str, start: dt.datetime, end: dt.datetime, listener: Listener,
         dir: str) -> None:
    """
    Saves all price data associated with the given symbol as a .csv file in the directory given by <dir>
    The .csv file shares the same name as the symbol, and if a file already exists with that name it will be overwritten
    """
    df = get_data(symbol, start, end)

    if df is None:
        msg = Message()
        msg.add_line("*****************************")
        msg.add_line(f"KeyError fetching data for {symbol}")
        msg.add_line("*****************************")
        listener.send(msg)
        return

    f = open(dir + "/" + symbol + ".csv", "w")
    df.to_csv(f)
예제 #5
0
def check_for_crosses(lst: SyncedList, listener: Listener, start: datetime.datetime, end: datetime.datetime):
    """
    Check for crosses of the <short>-day moving average above the <long>-day moving average in the last five days of
    trading. <lst> should be a SyncedList of stock symbols, and <listener> is what the threads spawned to accomplish the
    task will use to communicate with the console. 
    """
    msg = Message()
    symbol = lst.pop()
    while symbol is not None:
        msg.add_line("Analyzing symbol...")
        listener.send(msg)
        msg.reset()

        crosses = golden_cross(symbol, listener, SHORT_MOVING_AVERAGE, LONG_MOVING_AVERAGE, start, end)
        for cross in crosses:
            msg.add_line("=========================================")
            msg.add_line(f"Cross above for {symbol} on {cross}")
            listener.send(msg)
            msg.reset()

        symbol = lst.pop()
예제 #6
0
def golden_cross(symbol: str, listener: Listener, short: int, long: int, start: datetime.datetime,
                 end: datetime.datetime, local=False, dir="") -> List[datetime.datetime]:
    """
    Check for crosses by the <short>-day moving average above the <long>-day moving average some time between <start>
    and <end>. The returned list contains all days on which the short-term average closed ABOVE the long-term average,
    having been below the long-term average on the previous day.

    If <local>=True, assumes that <dir> is a string containing a path to a directory containing stock data.
    Files in this directory should be .csv's and have filename equal to the stock symbol whose data they are holding.
    This method assumes that the file <symbol>.csv exists in <dir>, if <local>=True.
    """
    msg = Message()

    try:
        short_average = moving_average(symbol, start, end, short, local=local, dir=dir)
        long_average = moving_average(symbol, start, end, long, local=local, dir=dir)
    except NotEnoughDataError as e:
        msg.add_line(str(e))
        listener.send(msg)

        return []

    if short_average is not None and long_average is not None:

        days = short_average.index

        crosses = []
        for i in range(len(days) - 1):
            if (short_average["Average"][days[i]] <= long_average["Average"][days[i]]
                    and short_average["Average"][days[i + 1]] > long_average["Average"][days[i + 1]]):
                crosses.append(days[i + 1])

        return crosses
    else:
        msg.add_line("******************************************************")
        msg.add_line(f"Couldn't get data for {symbol}")
        msg.add_line("******************************************************")
        listener.send(msg)

        return []
예제 #7
0
def check_overbought_oversold(symbols: SyncedList, start: datetime.datetime,
                              end: datetime.datetime, period: int,
                              listener: Listener):
    symbol = symbols.pop()
    while symbol is not None:
        msg = Message()
        msg.add_line("Checking " + symbol + "...")
        listener.send(msg)

        df = rsi(symbol, start, end, period)
        if df is not None:
            for date in df.index:
                if df["RSI"][date] >= 80:
                    msg = Message()
                    msg.add_line("=========================")
                    msg.add_line(symbol + " was overbought on " + str(date))
                    msg.add_line("=========================")
                    listener.send(msg)
                elif df["RSI"][date] <= 20:
                    msg = Message()
                    msg.add_line("=========================")
                    msg.add_line(symbol + " was oversold on " + str(date))
                    msg.add_line("=========================")
                    listener.send(msg)
        symbol = symbols.pop()
예제 #8
0
def analyze(symbol: str, tracker: IndicatorResultTracker, days: int,
            listener: Listener):
    """
    Analyzes the past 10 years of price data of the given stock symbol. Computes the standard 14-day period RSI for the
    interval ranging from January 1st, 2010 to December 31, 2019. Then, searches for periods where the stock was
    "oversold"; for our purposes, that means an RSI of 20 or less. Searches for periods containing some number of
    consecutive days on which the RSI remained below 20, followed immediately by a day on which the RSI was above 20.
    Examines the closing prices of the 10 days following this latter day, to see how frequently price increases follow
    a period of "oversold"-ness, and what the average price change is 1,2,3,... and 10 days after a period of
    "oversold"-ness.

    Uses the above class, IndicatorResultTracker, to keep track of the relevant data.
    """
    msg = Message()
    # msg.add_line(f"Analyzing {symbol}")
    # listener.send(msg)

    df_rsi = rsi(symbol,
                 START_DATE,
                 END_DATE,
                 RSI_PERIOD,
                 local=True,
                 dir="../data")

    last_was_oversold = False
    if df_rsi is not None:
        for i in range(len(df_rsi.index)):
            date = df_rsi.index[i]
            if df_rsi["RSI"][date] <= 20:
                if not last_was_oversold:
                    last_was_oversold = True

                msg.add_line("=====================================")
                msg.add_line(f"{symbol} was oversold on {date}")
                msg.add_line("=====================================")
            else:
                if last_was_oversold:
                    listener.send(msg)
                    msg.reset()
                    last_was_oversold = False

                    tracker.record_occurrence()

                    listener.send(msg)
                    msg.reset()
                    j = i + 1
                    while j <= i + days and j < len(df_rsi.index):
                        change = (df_rsi["Adj Close"][df_rsi.index[j]] -
                                  df_rsi["Adj Close"][df_rsi.index[i]]
                                  ) / df_rsi["Adj Close"][df_rsi.index[i]]
                        msg.add_line(
                            f"Recording change for {symbol} of {format(change, '.5f')} on day {j-i}"
                        )
                        tracker.record_change(change, j - i)
                        j += 1

                    listener.send(msg)
                    msg.reset()
예제 #9
0
def analyze_symbols(symbols: SyncedList, days: int):
    threads = []
    tracker = IndicatorResultTracker(days)
    listener = Listener()

    for i in range(6):
        x = threading.Thread(target=_analyze_thread,
                             args=(symbols, tracker, days, listener))
        x.start()
        threads.append(x)

    for thread in threads:
        thread.join()

    tracker.summarize()
예제 #10
0
def check_for_MACD_signal_crosses(lst: SyncedList, listener: Listener, start: datetime.datetime,
                                  end: datetime.datetime):
    """
    Check each stock symbol in the given SyncedList for crosses of the MACD above its signal line while price is above
    its 200-day EMA during the period from <start> to <end>. Print a message to the console for each such cross found.
    """
    symbol = lst.pop()
    msg = Message()

    while symbol is not None:
        msg.add_line(f"Analyzing {symbol}...")
        listener.send(msg)
        msg.reset()
        signals = MACD_signal(symbol, listener, start, end, local=True, dir="../data")

        for signal in signals:
            msg.add_line("======================================")
            msg.add_line(f"Signal on {signal} for {symbol}")
            msg.add_line("======================================")
            listener.send(msg)
            signal_output.save(msg)
            msg.reset()

        symbol = lst.pop()
예제 #11
0
def analyze_symbols(lst: SyncedList, start: datetime.datetime, end: datetime.datetime,
                    func: Callable[[SyncedList, Listener, datetime.datetime, datetime.datetime], None]) -> \
        List[threading.Thread]:
    """
    Spawns NUM_THREADS threads to analyze the given list of symbols. <func> is the method that each thread will run.
    It's signature should match the above. It should take a SyncedList of symbols to analyze, a Listener object with
    which to communicate with the console, and two dates: a start date for analysis and an end date for analysis. It
    should return nothing.
    """
    listener = Listener()

    threads = []
    # Start NUM_THREADS different threads, each drawing from the same central list of symbols, to look for golden
    # crosses
    for i in range(NUM_THREADS):
        x = threading.Thread(target=func, args=(lst, listener, start, end))
        x.start()
        threads.append(x)

    return threads
예제 #12
0
    except FileExistsError:
        print(f"Folder \"{sys.argv[1]}\" already exists. Continuing...")

    try:
        source = open(sys.argv[2])
    except OSError:
        print(f"Couldn't open source file {sys.argv[2]}")
        sys.exit(1)

    symbols = []
    for symbol in source:
        symbol = symbol.strip()
        if symbol.isalpha():
            symbols.append(symbol)

    synced = SyncedList(symbols)

    threads = []
    for i in range(2):
        x = threading.Thread(target=save_symbols,
                             args=(synced, dt.datetime(2010, 1,
                                                       1), dt.datetime.today(),
                                   Listener(), sys.argv[1]))
        x.start()
        threads.append(x)

    for thread in threads:
        thread.join()

    end = time.time()
    print("Time taken: " + str(end - start))
예제 #13
0
        listener.send(msg)

        df = rsi(symbol, start, end, period)
        if df is not None:
            for date in df.index:
                if df["RSI"][date] >= 80:
                    msg = Message()
                    msg.add_line("=========================")
                    msg.add_line(symbol + " was overbought on " + str(date))
                    msg.add_line("=========================")
                    listener.send(msg)
                elif df["RSI"][date] <= 20:
                    msg = Message()
                    msg.add_line("=========================")
                    msg.add_line(symbol + " was oversold on " + str(date))
                    msg.add_line("=========================")
                    listener.send(msg)
        symbol = symbols.pop()


if __name__ == '__main__':
    symbols = []
    f = open("../s&p500_symbols.txt")
    for line in f:
        line = line.strip()
        symbols.append(line)

    lst = SyncedList(symbols)
    check_overbought_oversold(lst, datetime.datetime(2010, 1, 1),
                              datetime.datetime(2019, 12, 31), 14, Listener())