def buy(bot, update, args): """Add a new asset to the portfolio.""" try: quantity = int(args[0]) code = str(args[1]).upper() price = float(args[2]) # If we cannot find the file for this client, we create a new portfolio try: with open(f'users/{update.message.chat_id}.p', 'r+b') as data_file: portfolio = pickle.load(data_file) except FileNotFoundError: portfolio = Portfolio(client_id=update.message.chat_id) portfolio.buy_stock(code=code, quantity=quantity, price=price) # Save data to pickle with open(f'users/{portfolio.client_id}.p', 'wb') as data_file: pickle.dump(portfolio, data_file) # Send the updated portfolio bot.send_message( chat_id=update.message.chat_id, parse_mode=ParseMode.MARKDOWN, text=f"*{code}* :: {quantity} :: {price:.2f} adicionada!\n\n" f"TOTAL: " f"{portfolio.stocks[code].quantity} :: " f"{portfolio.stocks[code].code} :: " f"*R${portfolio.stocks[code].avg_price:.2f}*") except (ValueError, IndexError): bot.send_message( chat_id=update.message.chat_id, parse_mode=ParseMode.MARKDOWN, text= "Não foi possível adicionar o ativo a sua carteira! Exemplo de uso: */buy 100 BBAS3 34.00*" ) except StockNotFound as error: bot.send_message(chat_id=update.message.chat_id, text=str(error))
class StockData(object): # I need to re-factor this code. def __init__(self,stockdata_csv='rawdata/csv/allStocks.csv'): self.data = pd.read_csv(stockdata_csv, index_col=0) self.data.set_index(pd.to_datetime(self.data.index), inplace=True) self.portfolio = Portfolio(100000, pct_equity_risk=0.01) def graph_data(self,symbol='MMM'): fig = plt.figure() ax = self.data.plot(y=[symbol]) ax.set_ylabel('Open') plt.show() def calc_ewma_manual(self, symbol='MMM', l=0.1): timeSeries = self.data[symbol] for i in range(timeSeries.count()): if not np.isnan(timeSeries[i]): if i == 0: ewma = [timeSeries[i]] else: ewma.append(timeSeries[i]*l + (1-l)*ewma[i-1]) else: if i == 0: ewma = [np.min(timeSeries[:500])] else: ewma.append(ewma[i-1]) print(timeSeries[i], ewma[i]) return ewma def calc_ewma(self,symbol='MMM',alpha=0.1): com = (1-alpha)/alpha self.data[symbol + '_ewma'] = pd.ewma(self.data[symbol],com=com) def calc_ewma_residuals(self,symbol='MMM'): if not symbol + "_ewma" in self.data.columns: raise Exception("Error: Call .calc_ewma first.") self.data[symbol + "_ewma_res"] = self.data[symbol] - self.data[symbol + "_ewma"] window_size = 30 self.data[symbol + "_minus_ewma_res"] = self.data[symbol] - map(lambda x: 3*abs(x), pd.rolling_mean(self.data[symbol + '_ewma_res'],window_size, min_periods=0)) self.data[symbol + "_minus_ewma_res"] = pd.rolling_mean(self.data[symbol + "_minus_ewma_res"],window_size,min_periods=0) #def _movingaverage(self, interval, window_size): # window = np.ones(int(window_size)) / float(window_size) # return np.convolve(interval, window, 'same') #def calc_movingaverage(self,symbol='MMM',window_size=20): # self.data[symbol + "_ma"] = self._movingaverage(self.data[symbol],window_size) def calc_ma(self, symbol='MMM', window_size=20): self.data[symbol + "_ma_" + str(window_size)] = pd.rolling_mean(self.data[symbol], window_size, min_periods=0) def calc_moving_range(self,symbol='MMM',window_size=40): timeSeries = self.data[symbol] output = np.ones(window_size)*np.min(timeSeries[:500]) # set first 20 values equal to the min of the first 500 to avoid issues with nan for i in range(window_size-1,timeSeries.shape[0]-1): if not np.isnan(np.min(timeSeries[i-window_size:i])): output = np.append(output,np.max(timeSeries[i-window_size:i])-np.min(timeSeries[i-window_size:i])) else: output = np.append(output,output[i-1]) output2 = pd.Series(output, index=timeSeries.index) self.data[symbol + "_mrange"] = output2 window_size = 30 self.data[symbol + "_minus_mrange"] = pd.rolling_mean(self.data[symbol] - self.data[symbol + '_mrange'], window_size,min_periods=0) return output2 def calc_ma_stats(self,symbol='MMM',near_ma=20,far_ma=60): near_col_name = symbol + "_ma_" + str(near_ma) far_col_name = symbol + "_ma_" + str(far_ma) if not near_col_name in self.data.columns: self.calc_ma(symbol, near_ma) if not far_col_name in self.data.columns: self.calc_ma(symbol, far_ma) delta_col_name = symbol + "_ma_" + str(near_ma) + "-ma_" + str(far_ma) delta = self.data[near_col_name] - self.data[far_col_name] self.data[delta_col_name] = delta.round(1) for col_name in [near_col_name,far_col_name]: self.data[col_name + '_grad'] = pd.rolling_mean(np.gradient(self.data[col_name],100),10) def plot_crossovers(self,symbol='MMM',near_ma=20,far_ma=60): if not symbol in self.data.columns: raise Exception(symbol + " not found within the dataset") near_col_name = symbol + "_ma_" + str(near_ma) far_col_name = symbol + "_ma_" + str(far_ma) delta_col_name = symbol + "_ma_" + str(near_ma) + "-ma_" + str(far_ma) crossOverPts = self.data[delta_col_name][self.data[delta_col_name] == 0] ax = self.data[[symbol,near_col_name,far_col_name]].plot() for crossOverPoint in crossOverPts.index: ax.axvline(crossOverPoint,color='grey') # Plot the Gradient #self.data[col_name + '_grad'].plot(secondary_y=True) #Buy when the gradient of the near term moving average is positive and when it is crossing the long term average entry_points = self.data[symbol][(self.data[col_name+'_grad'] > 0) & (self.data[delta_col_name] == 0)] for entry_point in entry_points.index: ax.axvline(entry_point, color='red',linewidth=3) #self.data[symbol + '_entry'] = entry_points*np.ones(entry_points.shape[0]) plt.show() return crossOverPts def trade(self, symbol='MMM',near_ma=20,far_ma=60): if not symbol + '_ma_' + str(near_ma) in self.data.columns: raise Exception("Please run .calc_ma_stats first") #timeSeries = self.data[symbol].dropna() col_name = symbol + '_ma_' + str(near_ma) delta_col_name = symbol + "_ma_" + str(near_ma) + "-ma_" + str(far_ma) order_history = [] self.recent_max_list = [] self.stop_loss_list = [] holding_stock = False recent_max = 0 for i in range(self.data[symbol].shape[0]): entry_criteria = self.data[col_name +'_grad'][i] > 0 and self.data[delta_col_name][i] == 0 if recent_max < self.data[symbol][i] and not np.isnan(self.data[symbol][i]): recent_max = self.data[symbol][i] if not holding_stock: if entry_criteria and not np.isnan(self.data[symbol][i]): holding_stock = True purchase_price = self.data[symbol][i] purchase_date = self.data.index[i] recent_max = purchase_price delta_to_max = np.std(self.data[symbol + '_ewma_res'][i-50:i])*3 initial_stop_loss = purchase_price - delta_to_max # initial stop loss is minus_ewma_res self.stop_loss_list.append(initial_stop_loss) self.portfolio.buy_stock(symbol,purchase_price, initial_stop_loss) entry_criteria = False print(self.portfolio.current_holdings) #print("PURCHASE: "+ purchase_date.strftime('%m/%d/%y') + " " + str(purchase_price)) else: self.stop_loss_list.append(np.nan) else: if np.std(self.data[symbol + '_ewma_res'][i-50:i])*3 < delta_to_max: delta_to_max = np.std(self.data[symbol + '_ewma_res'][i-50:i])*3 # allow the delta from the max to get smaller not larger stop_loss = recent_max - delta_to_max exit_criteria = self.data[symbol][i] < stop_loss self.stop_loss_list.append(stop_loss) #exit_criteria = self.data[symbol][i] < self.data[symbol + '_minus_ewma_res'][i-1] #print(self.data.index[i].strftime('%m/%d/%y') + " " + str(self.data[symbol][i]) + " max: " + str(recent_max) + " Range: " + str(self.data[symbol + '_mrange'][i])) if exit_criteria and not np.isnan(self.data[symbol][i]): holding_stock = False #SELL! sell_price = self.data[symbol][i] sell_date = self.data.index[i] num_shares = self.portfolio.current_holdings[symbol] self.portfolio.sell_stock(symbol, sell_price) order_history.append((purchase_date, purchase_price, sell_date, sell_price, initial_stop_loss, num_shares, self.portfolio.cash_equity)) #print(" SELL: " + sell_date.strftime('%m/%d/%y') + " Price:" + str(sell_price) + " MAX:" + str(recent_max) + " Range:" + str(self.data[symbol + '_mrange'][i]) + " Delta:" + str(recent_max - self.data[symbol + '_mrange'][i])) purchase_price = np.nan sell_price = np.nan print(self.portfolio.current_holdings) exit_criteria = False #ax.axvline(self.data.index[i], color='blue') self.recent_max_list.append(recent_max) self.data[symbol + '_stop_loss'] = pd.Series(self.stop_loss_list, index=self.data.index) self.data[symbol + '_recent_max'] = pd.Series(self.recent_max_list,index=self.data.index) #self.data[symbol + '_recent_max_minus_mrange'] = self.data[symbol + '_recent_max'] - self.data[symbol + '_mrange'] order_history_df = pd.DataFrame(order_history, columns=['Purchase Date','Purchase Price','Sell Date','Sell Price','Stop Loss', 'Number of Shares','Total Cash Equity']) order_history_df['Symbol'] = symbol order_history_df['Profit_Loss_share'] = order_history_df['Sell Price'] - order_history_df['Purchase Price'] order_history_df['Risk'] = order_history_df['Purchase Price'] - order_history_df['Stop Loss'] order_history_df['Rmultiple'] = order_history_df['Profit_Loss_share'] / order_history_df['Risk'] if hasattr(self,'order_history'): self.order_history = pd.concat([self.order_history, order_history_df],ignore_index=True) else: self.order_history = order_history_df def calc_factors_trade(self, symbol='MMM'): self.calc_ma_stats(symbol, self.settings['near_ma'], self.settings['far_ma']) self.calc_ewma(symbol) self.calc_ewma_residuals(symbol) #output2 = self.calc_moving_range(symbol) self.trade(symbol, self.settings['near_ma'], self.settings['far_ma']) # Updates order history def backtest(self, symbol_list=['MMM','ACT'], near_ma=10, far_ma=90, save=False): self.symbol_list = symbol_list self.settings = {'near_ma': near_ma, 'far_ma': far_ma} for symbol in symbol_list: print("BackTesting " + symbol) self.calc_factors_trade(symbol) if save: try: self.order_history.to_csv('Order_History.csv') # Add numbering except IOError: print("IOError: Order History Not saved.") return self.calc_score() def show_symbol_columns(self,symbol='MMM'): for col in self.data.columns: if symbol in col: print(col) def plot_results(self, symbol_list): near_ma = self.settings['near_ma'] far_ma = self.settings['far_ma'] # ax = self.data[[symbol, symbol + '_minus_ewma_res',symbol + '_recent_max', symbol + '_stop_loss']].plot() for symbol in symbol_list: print("Plotting for " + symbol) ax = self.data[[symbol, symbol + '_ma_' + str(near_ma), symbol + '_ma_' + str(far_ma), symbol + '_stop_loss']].plot() symbol_order_history = self.order_history[self.order_history['Symbol']==symbol] for row in symbol_order_history.iterrows(): try: ax.axvline(row[1]['Purchase Date'], color='red', linewidth=2) ax.axvline(row[1]['Sell Date'], color='green', linewidth=2) except KeyError: print("KeyError: No entries found for item.") print(symbol_order_history) # fig = plt.figure() def calc_score(self,plot_histo=True): # symbol = self.sd.order_history_symbol if plot_histo and self.order_history.shape[0] >= 0: # Need to have at least one row plt.hist(self.order_history['Rmultiple']) expectancy = self.order_history['Rmultiple'].mean() RstdDev = self.order_history['Rmultiple'].std() SQN = expectancy / RstdDev scoresNumbers = list(map(lambda x: round(x, 3), [expectancy, RstdDev, SQN])) near_ma = self.settings['near_ma'] far_ma = self.settings['far_ma'] scores = ['-'.join(self.symbol_list), near_ma, far_ma, scoresNumbers[0], scoresNumbers[1], scoresNumbers[2]] columns = ['Symbols', 'near_ma', 'far_ma', 'Expectancy', 'R-StdDev', 'SQN'] a = zip(columns, scores) # scoresDict = {} # for i in range(len(columns)): # scoresDict[columns[i]] = scores[i] scoresDict = {k: v for k, v in a} self.score = pd.DataFrame(data=scoresDict, index=[0]) return self.score
class Menu: #user interface to interact that guides interaction with portfolio def __init__(self, market: Market, initial_balance: float): self.portfolio = Portfolio(market, initial_balance) self.choices = [{ "name": "exit", "action": self.exit }, { "name": "check current balance", "action": self.portfolio.print_balance }, { "name": "add or withdraw cash", "action": self.cash_interface }, { "name": "view all available stocks on the market", "action": self.portfolio.market.display_stocks }, { "name": "view your current portfolio", "action": self.portfolio.print_portfolio }, { "name": "buy and sell stocks", "action": self.stock_interface }] def display_greeting(self): print("\n\n*********************") print( "Welcome to Python Portfolio, your personal interface with our stimulated Stock Market!" ) def display_menu(self): print("\nHow would you like to proceed?\n\n") for i in range(len(self.choices)): print("to {0}, enter {1}\n".format(self.choices[i]["name"], i)) def run(self): self.display_greeting() while True: self.display_menu() choice = input("Enter selection: ") print("\n") try: action = self.choices[int(choice)]["action"] except: print( "Invalid selection. Please enter a number between 0 and {0}" .format(len(self.choices))) break action() def cash_interface(self): choice = input( "Do you wish to deposit or withdraw cash? (enter 0 for main menu) " ) if choice == 0 or ("deposit" not in choice.lower() and "withdraw" not in choice.lower()): return deposit = True if "deposit" in choice.lower() else False cash = input("How much cash do you wish to {0}? ".format( "deposit" if deposit else "withdraw")) if cash == 0: return if deposit: self.portfolio.add_cash(float(cash)) else: self.portfolio.withdraw_cash(float(cash)) print("Transaction complete") def stock_interface(self): choice = input( "Which stock do you wish to trade? (enter 0 for main menu) ") if choice == 0: return if choice not in self.portfolio.market.stocks: print("Couldn't locate stock") return buy_input = input("Do you wish to buy or sell {0}? ".format(choice)) if "buy" not in buy_input.lower() and "sell" not in buy_input.lower(): return buy = True if "buy" in buy_input.lower() else False if not buy and choice not in self.portfolio.portfolio: print("You do not own any shares of {0}".format(choice)) return quantity = input("How many shares do you wish to {0}? ".format( "buy" if buy else "sell")) if buy: self.portfolio.buy_stock(choice, quantity) else: self.portfolio.sell_stock(choice, quantity) def exit(self): print("Thank you for visiting Python Portfolio") sys.exit(0)