text: from helpers import *
  import yfinance as yf
  import numpy as np
  from datetime import date, timedelta
  DT_FORMAT= "%Y-%m-%d"
  
  class Simulation:
      
      def __init__(self,date_start, date_end, initial_cash = 1000, percent_slip=0.0005, trade_fee=1, benchmark="SPY"):
          self.initial_cash = initial_cash
          self.cash = self.initial_cash
          self.slippage = percent_slip/100
          self.trade_fee = trade_fee
          self.n_symbols = None
          self.symbols = []
          self._portfolio = {}
          self.trades = {}
          self.weights = None
          self.start_date = date_convert(date_start)
          self.end_date = date_convert(date_end)
          self.simulation_length = self.end_date-self.start_date
          self.simulation_length = self.simulation_length.days
          self._check_dates()
          spyData = yf.Ticker(benchmark)
          spyDf = spyData.history(start=self.start_date, end=self.end_date)
          #spyDf["Date"] = date_convert(spyDf.index)
          self.spy = spyDf
          
      def set_symbols(self, *args):
          if len(args)==1:
              # list of symbols given
              self.symbols=args[0]
          else:
              for arg in args:
                  self.symbols.append(arg)
          self.n_symbols = len(self.symbols)
          print(self.symbols)
          if len(self.symbols)==0:
              print("No symbols specified, use set_symbols()")
          for symbol in self.symbols:
              tickerData = yf.Ticker(symbol)
              tickerDf = tickerData.history(start=self.start_date, end=self.end_date)
              tickerDf["Date"] = date_convert(tickerDf.index)
              self._portfolio.update({symbol:tickerDf})
          
      def set_custom_trade_signals(self, trades: dict):
          pass
      
      def set_default_trade_signals(self, weights):
          self.weight_vec = weights
          self.trades = pd.DataFrame(columns=["Symbol", "Date", "Signal", "Shares"])
          assert len(weights)==self.n_symbols, "wrong number of weights given"
          if sum(self.weight_vec) != 1:
              self.weight_vec /= sum(self.weight_vec)
          for i, symbol in enumerate(self.symbols):
              num_shares = weights[i]*self.initial_cash/self._portfolio[symbol].Open.iloc[0]
              self.trades = self.trades.append({"Symbol": symbol, "Date":self.start_date+timedelta(13) ,"Signal": "Buy", "Shares":num_shares}, ignore_index=True)
              self.trades = self.trades.append({"Symbol": symbol, "Date":self.end_date-timedelta(4) ,"Signal": "Exit", "Shares":0}, ignore_index=True)
          self.trades = self.trades.sort_values(by="Date")
          self.n_trades = len(self.trades)
          
      def get_trade_signal_format(self):
          trades_sample = pd.DataFrame(columns=["Symbol", "Date", "Signal", "Shares"])
          trades_sample = self.trades.append({"Symbol": "Symbol to trade", "Date":"Y-m-d" ,"Signal": "Buy, Sell or Exit", "Shares":"float"}, ignore_index=True)
          print(trades_sample.head())
          
      def _check_dates(self):
          while self.end_date.weekday()>4:
              self.end_date -= timedelta(1)
              print("Updated end date to a weekday: "+date_convert(self.end_date))
          while self.start_date.weekday()>4:
              self.start_date -= timedelta(1)
              print("Updated start date to a weekday: "+date_convert(self.start_date))
              
      def _order(self, portfolio, i, order):
          date = self.trades.Date.iloc[i]
          open_price = self._portfolio[self.trades.Symbol.iloc[i]].Open[date]*(1+self.slippage)
          close_price = self._portfolio[self.trades.Symbol.iloc[i]].Close[date]*(1-self.slippage)
          if order == "Buy":
              portfolio.Position_Obj.loc[(portfolio.Symbol==self.trades.Symbol.iloc[i])].values[0].buy(date, open_price, self.trades.Shares.iloc[i])
              self.cash -= (open_price+self.trade_fee)*self.trades.Shares.iloc[i]
          elif order == "Exit":
              self.cash += close_price*portfolio.Position_Obj.loc[(portfolio.Symbol==self.trades.Symbol.iloc[i])].values[0].shares
              portfolio.Position_Obj.loc[(portfolio.Symbol==self.trades.Symbol.iloc[i])].values[0].exit(date, open_price)
          elif order == "Sell":
              portfolio.Position_Obj.loc[(portfolio.Symbol==self.trades.Symbol.iloc[i])].values[0].sell(date, open_price, self.trades.Shares.iloc[i])
              self.cash += open_price*self.trades.Shares.iloc[i]
              
      def years_elapsed(self, series):
          return (series.index[-1]-series.index[0]).days/365.25
  
      def cagr(self, series):
          _cagr = (series.iloc[-1]/series.iloc[0])**(1/years_elapsed(series))-1
          return _cagr
      
      def a_volatility(self, series):
          years = self.years_elapsed(series)
          yearly_entries = series.shape[0]/years
          vol = series.std()*np.sqrt(yearly_entries)
          return vol
      
      def sharpe(self, series, benchmark=0.):
          _cagr = self.cagr(series)
          returns = self.return_series(series)
          vol = self.a_volatility(returns)
          sharpe = (_cagr-benchmark)/vol
          return sharpe
          
      def return_series(self, series):
          return pd.Series(np.log(series/series.shift(1, axis=0)))
      
      def start(self): 
          print("Simulating between "+date_convert(self.start_date)+" and "+date_convert(self.end_date))
          #print(self.trades)
          portfolio = pd.DataFrame(columns=["Symbol","Position_Obj"])
          equity = pd.DataFrame(self.initial_cash, columns=["Equity"], index=self.spy.index)
          self.value_frame = pd.DataFrame(0, columns=self.symbols, index=self.spy.index)
  
          self.cash_series = pd.DataFrame(self.initial_cash, index=self.spy.index,columns=["Cash"])
          # create empty positions   
          for symbol in self.symbols:
              portfolio = portfolio.append({"Symbol": symbol, "Position_Obj": Position(symbol, self.start_date, 0, 0)}, ignore_index=True)
          n_open_positions = len(portfolio)
          for i in range(self.n_trades):
              shares = portfolio.Position_Obj.loc[(portfolio.Symbol==self.trades.Symbol.iloc[i])].values[0].shares
              date = self.trades.Date.iloc[i]
              price = self._portfolio[portfolio.Position_Obj.loc[(portfolio.Symbol==self.trades.Symbol.iloc[i])].values[0].symbol]
              open_price = self._portfolio[self.trades.Symbol.iloc[i]].Open[date]
              close_price = self._portfolio[self.trades.Symbol.iloc[i]].Close[date]
              if self.trades.Signal.iloc[i]=="Buy":
                  self._order(portfolio, i, "Buy")
                  #equity["Equity"].loc[equity.index>=date] += price.Open.loc[equity.index>=date]*self.trades.Shares.iloc[i]-self.cash
                  self.value_frame[self.trades.Symbol.iloc[i]].loc[equity.index>=date] = price.Open.loc[equity.index>=date]*self.trades.Shares.iloc[i]
                  self.cash_series["Cash"].loc[self.cash_series.index>=date] =self.cash
              elif self.trades.Signal.iloc[i]=="Exit":      
                  self._order(portfolio, i, "Exit")
                  self.value_frame[self.trades.Symbol.iloc[i]].loc[equity.index>=date] = 0
                  #equity["Equity"].loc[equity.index>=date] = self.cash #- price.Close.loc[equity.index==date].values[0]*shares
                  self.cash_series["Cash"].loc[self.cash_series.index>=date] =self.cash
              elif self.trades.Signal.iloc[i]=="Sell":
                  self._order(portfolio, i, "Sell")
                  self.value_frame[self.trades.Symbol.iloc[i]].loc[equity.index>=date] = price.Close.loc[equity.index>=date]*(shares-self.trades.Shares.iloc[i])
                  #equity["Equity"].loc[equity.index>=date] += -price.Close.loc[equity.index>=date]*self.trades.Shares.iloc[i]+ self.cash
                  self.cash_series["Cash"].loc[self.cash_series.index>=date] = self.cash
              
              
          self.equity = self.value_frame.sum(axis=1)+self.cash_series["Cash"]
          self.equity_series = self.equity#self.equity.T.iloc[0, :]
          self.set_sharpe()
          
      def set_sharpe(self):
          self.sharpe_ratio = self.sharpe(self.equity_series)
          
      def get_sharpe(self):
          return self.sharpe(self.equity_series) 
                  
  
                      
      
          
      