def job(): for func in [ema, sma, macd, rsi, bb]: buy_list = [] sell_list = [] for symbol in sp.get_sp500(): # TODO: needs a refresh func.generate_signals(symbol, start_date=utils.add_business_days( datetime.date.today(), -200), end_date=datetime.date.today()) df = pd.read_csv(utils.get_file_path(config.ta_data_path, func.table_filename, symbol=symbol), index_col="Date", parse_dates=["Date"]) if df.index[-1].date() != datetime.date.today(): print( "Last date in {} file does not equal todays date. Is today a weekend or a holiday?" .format(symbol)) if df[func.get_signal_name()][-1] == ta.buy_signal: buy_list.append(symbol) if df[func.get_signal_name()][-1] == ta.sell_signal: sell_list.append(symbol) df = pd.DataFrame({ "Buy": pd.Series(buy_list), "Sell": pd.Series(sell_list) }) df.to_csv(utils.get_file_path(config.ta_data_path, func.get_signal_name() + table_filename, dated=True, start_date="", end_date=datetime.date.today()), index=False)
def sell(self, symbol, date, sell_size=0): """Simulates selling a stock Parameters: symbol : str date : datetime sell_size : float, optional How much to sell. If 0, sell all partial_shares : bool Whether partial shares are supported. If True, the amount sold will always equal amount, even if that number isn't reachable in a number of whole shares short_sell : bool """ if symbol in self.portfolio: if self.portfolio[symbol] > 0: # sell at close price, or next day's open price? if sell_size == 0: # sell all self.cash += self.portfolio[symbol] * self.get_price_on_date(symbol, utils.add_business_days(date, self.slippage), time="Close" if self.slippage == 0 else "Open") self.cash -= self.calculate_commission(self.portfolio[symbol]) self.log.loc[date][actions_column_name] = self.log.loc[date][actions_column_name] + "{} {} {} Shares at {} totalling {:.2f} ".format(ta.sell_signal, symbol, self.portfolio[symbol], self.get_price_on_date(symbol, utils.add_business_days(date, self.slippage), time="Close" if self.slippage == 0 else "Open"), self.portfolio[symbol] * self.get_price_on_date(symbol, utils.add_business_days(date, self.slippage), time="Close" if self.slippage == 0 else "Open")) self.portfolio.pop(symbol) else: shares = (sell_size // self.get_price_on_date(symbol, utils.add_business_days(date, self.slippage), time="Close" if self.slippage == 0 else "Open")) if not self.partial_shares else (sell_size / self.get_price_on_date(symbol, utils.add_business_days(date, self.slippage), time="Close" if self.slippage == 0 else "Open")) shares = shares if shares < self.portfolio[symbol] else self.portfolio[symbol] if shares < 0: print("Amount {} Price {} Shares {}".format(sell_size, self.get_price_on_date(symbol, utils.add_business_days(date, self.slippage), time="Close" if self.slippage == 0 else "Open"), shares), flush=True) raise Exception if shares != 0: self.cash += shares * self.get_price_on_date(symbol, utils.add_business_days(date, self.slippage), time="Close" if self.slippage == 0 else "Open") self.cash -= self.calculate_commission(shares) self.log.loc[date][actions_column_name] = self.log.loc[date][actions_column_name] + "{} {} {} Shares at {} totalling {:.2f} ".format(ta.sell_signal, symbol, shares, self.get_price_on_date(symbol, utils.add_business_days(date, self.slippage), time="Close" if self.slippage == 0 else "Open"), shares * self.get_price_on_date(symbol, utils.add_business_days(date, self.slippage), time="Close" if self.slippage == 0 else "Open")) self.portfolio[symbol] -= shares if self.portfolio[symbol] < 0: print("Amount {} Price {} Shares {} Shares in portfolio {}".format(sell_size, self.get_price_on_date(symbol, utils.add_business_days(date, self.slippage), time="Close" if self.slippage == 0 else "Open"), shares, self.portfolio[symbol]), flush=True) raise Exception if self.portfolio[symbol] == 0: self.portfolio.pop(symbol) self.set_stop_loss(symbol, date, Operation.Sell) self.update_winners_losers(symbol, date, Operation.Sell) else: # short sell more pass else: if self.short_sell: pass
def update_winners_losers(self, symbol, date, func): """Updates the cost basis when buying and updates winners/losers when selling Parameters: symbol : str date : datetime func : str """ if func == Operation.Buy: self.cost_basis[symbol] = self.get_price_on_date(symbol, utils.add_business_days(date, self.slippage), time="Close" if self.slippage == 0 else "Open") if func == Operation.Sell: if self.cost_basis[symbol] < self.get_price_on_date(symbol, utils.add_business_days(date, self.slippage), time="Close" if self.slippage == 0 else "Open"): self.winners_losers["Winners"] += 1 else: self.winners_losers["Losers"] += 1 try: self.cost_basis.pop(symbol) # not actually nessecary since next time we buy the stock it will overwrite the cost basis except KeyError: print(symbol) print(self.cost_basis) raise
def set_stop_loss(self, symbol, date, func): """Sets initial values in the stop loss dictionary when buying and removes entries when selling Parameters: symbol : str date : datetime func : str """ if func == Operation.Buy: self.stop_loss[symbol] = self.get_price_on_date(symbol, utils.add_business_days(date, self.slippage), time="Close" if self.slippage == 0 else "Open") if func == Operation.Sell: self.stop_loss.pop(symbol)
def buy(self, symbol, date, purchase_size): """Simulates buying a stock Parameters: symbol : str date : datetime purchase_size : float, optional How much to buy. If 0, buy the default amount partial_shares : bool Whether partial shares are supported. If True, the amount bought will always equal amount, even if that number isn't reachable in a number of whole shares """ if purchase_size == 0: purchase_size = self.purchase_size if self.cash < purchase_size: self.log.loc[date][actions_column_name] = self.log.loc[date][actions_column_name] + "Unable to buy {} ".format(symbol) # purchase_size = self.cash elif symbol not in self.portfolio: # buy at close price, or next day's open price? shares = (purchase_size // self.get_price_on_date(symbol, utils.add_business_days(date, self.slippage), time="Close" if self.slippage == 0 else "Open")) if not self.partial_shares else (purchase_size / self.get_price_on_date(symbol, utils.add_business_days(date, self.slippage), time="Close" if self.slippage == 0 else "Open")) if shares < 0: # Sometimes shares is -1. When variables are printed, math does not add up to -1?? print("Symbol {} self.purchase_size {} Purchase size {} Price {} Shares {}".format(symbol, self.purchase_size, purchase_size, self.get_price_on_date(symbol, utils.add_business_days(date, self.slippage), time="Close" if self.slippage == 0 else "Open"), shares), flush=True) raise Exception if shares != 0: self.portfolio[symbol] = shares self.cash -= shares * self.get_price_on_date(symbol, utils.add_business_days(date, self.slippage), time="Close" if self.slippage == 0 else "Open") self.cash -= self.calculate_commission(shares) self.log.loc[date][actions_column_name] = self.log.loc[date][actions_column_name] + "{} {} {} Shares at {} totaling {:.2f} ".format(ta.buy_signal, symbol, shares, self.get_price_on_date(symbol, utils.add_business_days(date, self.slippage), time="Close" if self.slippage == 0 else "Open"), shares * self.get_price_on_date(symbol, utils.add_business_days(date, self.slippage), time="Close" if self.slippage == 0 else "Open")) self.set_stop_loss(symbol, date, Operation.Buy) self.update_winners_losers(symbol, date, Operation.Buy) else: if self.short_sell and self.portfolio[symbol] < 0: pass elif self.portfolio[symbol] > 0: # buy more pass
def get_price_on_date(self, symbol, date, time="Close"): """Gets the price of the given symbol on the given date Parameters: symbol : str date : datetime time : str Which column to use to determine price. Valid times are "Open" and "Close" Returns: float The price of the given symbol on the given date """ start_time = timer() if symbol in self.price_files: df = self.price_files[symbol] else: if utils.refresh(utils.get_file_path(config.prices_data_path, prices.price_table_filename, symbol=symbol), refresh=False): prices.download_data_from_yahoo(symbol, start_date=self.start_date, end_date=self.end_date) df = pd.read_csv(utils.get_file_path(config.prices_data_path, prices.price_table_filename, symbol=symbol), index_col="Date", parse_dates=["Date"])[self.start_date:self.end_date] self.price_files[symbol] = df price = df.loc[date][time] if date in df.index else self.get_price_on_date(symbol, utils.add_business_days(date, -1), time=time) self.times[get_price_time] = self.times[get_price_time] + timer() - start_time return price
"RMO", "VINC", "PRCH", "ARKO", "OPEN", "AVCT", "PAE", "VRT", "RPAY", "WTRH" ] # list of spacs with missing data data_issues_list = ["AGLY", "CLVR", "HUNTF", "KLDI", "LAZY", "SONG"] spac_data = pd.read_csv(utils.get_file_path(config.spac_data_path, "SPAC.csv", symbol=""), index_col=False, parse_dates=["Completion Date"]) pre_and_post_merger_prices = [] failed_count = 0 failed_symbols = [] for index, row in spac_data.iterrows(): symbol = row["Post-SPAC Ticker Symbol"] start_date = utils.add_business_days(row["Completion Date"], days_before_merger) end_date = utils.add_business_days(row["Completion Date"], days_after_merger) if symbol not in data_issues_list: # Never refresh for spacs, Yahoo consistently missing data around mergers df = pd.read_csv(utils.get_file_path(config.spac_data_path, prices.price_table_filename, symbol=symbol), index_col="Date", parse_dates=["Date"]) try: # Preferably we would use the below line and also filter dfs for [start_date:end_date] # but there are issues where the dates are not as expected due to holdidays. Also OPEN is missing a day randomly pre_and_post_merger_prices.append( (df.loc[start_date]["Close"], df.loc[end_date]["Close"]))