def doTrade(self, portfolio: Portfolio, current_portfolio_value: float, stock_market_data: StockMarketData) -> OrderList: """ Generate action to be taken on the "stock market" Args: portfolio : current Portfolio of this trader current_portfolio_value : value of Portfolio at given Momemnt stock_market_data : StockMarketData for evaluation Returns: A OrderList instance, may be empty never None """ if self.bought_stocks: return OrderList() else: self.bought_stocks = True order_list = OrderList() # Calculate how many cash to spend per company available_cash_per_stock = portfolio.cash / stock_market_data.get_number_of_companies() # Invest (100 // `len(companies)`)% of cash into each stock for company in list(CompanyEnum): most_recent_price = stock_market_data.get_most_recent_price(company) if most_recent_price is not None: amount_to_buy = available_cash_per_stock // most_recent_price order_list.buy(company, amount_to_buy) return order_list
def doTrade(self, portfolio: Portfolio, current_portfolio_value: float, stock_market_data: StockMarketData) -> OrderList: """ Generate action to be taken on the "stock market" Args: portfolio : current Portfolio of this trader current_portfolio_value : value of Portfolio at given Momemnt stock_market_data : StockMarketData for evaluation Returns: A OrderList instance, may be empty never None """ y_a = stock_market_data.get_most_recent_price(CompanyEnum.COMPANY_A) y_b = stock_market_data.get_most_recent_price(CompanyEnum.COMPANY_B) p_a = self.stock_a_predictor.doPredict( stock_market_data[CompanyEnum.COMPANY_A]) p_b = self.stock_b_predictor.doPredict( stock_market_data[CompanyEnum.COMPANY_B]) r_a = (p_a - y_a) / y_a * 100 r_b = (p_b - y_b) / y_b * 100 result = OrderList() if (r_a < 0 and portfolio.get_amount(CompanyEnum.COMPANY_A) > 0): result.sell(CompanyEnum.COMPANY_A, portfolio.get_amount(CompanyEnum.COMPANY_A)) if (r_b < 0 and portfolio.get_amount(CompanyEnum.COMPANY_B) > 0): result.sell(CompanyEnum.COMPANY_B, portfolio.get_amount(CompanyEnum.COMPANY_B)) if (r_a <= 0 and r_b <= 0): return result company = CompanyEnum.COMPANY_A if (r_b > r_a): company = CompanyEnum.COMPANY_B buy_amount = math.floor( portfolio.cash / stock_market_data.get_most_recent_price(company)) if (portfolio.cash > 0 and buy_amount > 0): result.buy(company, buy_amount) print("Portfolio: " + str( portfolio.total_value( stock_market_data.get_most_recent_trade_day(), stock_market_data))) # TODO: implement trading logic return result
def doTrade(self, portfolio: Portfolio, current_portfolio_value: float, stock_market_data: StockMarketData) -> OrderList: """ Generate action to be taken on the "stock market" Args: portfolio : current Portfolio of this trader current_portfolio_value : value of Portfolio at given Momemnt stock_market_data : StockMarketData for evaluation Returns: A OrderList instance, may be empty never None """ result = OrderList() predictions = { CompanyEnum.COMPANY_A: self.stock_a_predictor.doPredict(stock_market_data[CompanyEnum.COMPANY_A]), CompanyEnum.COMPANY_B: self.stock_b_predictor.doPredict(stock_market_data[CompanyEnum.COMPANY_B]) } zuwachs = {} # sell companies which get worse for company in CompanyEnum: prediction = predictions[company] anzahl = portfolio.get_amount(company) current = stock_market_data.get_most_recent_price(company) zuwachs[company] = prediction / current # sell if getting worse if anzahl > 0: if current > prediction: result.sell(company, anzahl) best = sorted(zuwachs, key=zuwachs.__getitem__)[::-1] currentCash = portfolio.cash for b in best: prediction = predictions[b] current = stock_market_data.get_most_recent_price(b) wachstum = (prediction / current) vola = self.isVolatile(stock_market_data[b].get_values()) if wachstum > 1: if vola or wachstum > 1.001: count = math.floor(currentCash / current) result.buy(b, count) currentCash -= count * current else: result.sell(b, portfolio.get_amount(b)) return result
def doTrade(self, portfolio: Portfolio, current_portfolio_value: float, stock_market_data: StockMarketData) -> OrderList: """ Generate action to be taken on the "stock market" Args: portfolio : current Portfolio of this trader current_portfolio_value : value of Portfolio at given Momemnt stock_market_data : StockMarketData for evaluation Returns: A OrderList instance, may be empty never None """ result = OrderList() predictions = { CompanyEnum.COMPANY_A: self.stock_a_predictor.doPredict(stock_market_data[CompanyEnum.COMPANY_A]), CompanyEnum.COMPANY_B: self.stock_b_predictor.doPredict(stock_market_data[CompanyEnum.COMPANY_B]) } zuwachs = {} # sell companies which get worse for company in CompanyEnum: prediction = predictions[company] anzahl = portfolio.get_amount(company) current = stock_market_data.get_most_recent_price(company) zuwachs[company] = prediction / current # sell if getting worse if anzahl > 0: if current > prediction: result.sell(company, anzahl) bestCompany = None bestZuwachs = 1 for company in CompanyEnum: if zuwachs[company] > bestZuwachs: bestZuwachs = zuwachs[company] bestCompany = company if bestCompany: current = stock_market_data.get_most_recent_price(company) count = math.floor(portfolio.cash / current) result.buy(bestCompany, count) # TODO: implement trading logic return result
def doTrade(self, portfolio: Portfolio, current_portfolio_value: float, stock_market_data: StockMarketData) -> OrderList: """ Generate action to be taken on the "stock market" Args: portfolio : current Portfolio of this trader current_portfolio_value : value of Portfolio at given Momemnt stock_market_data : StockMarketData for evaluation Returns: A OrderList instance, may be empty never None """ order_list = OrderList() pred_a_value = self.stock_a_predictor.doPredict( stock_market_data[CompanyEnum.COMPANY_A]) pred_b_value = self.stock_b_predictor.doPredict( stock_market_data[CompanyEnum.COMPANY_B]) stock_a = portfolio.get_amount(CompanyEnum.COMPANY_A) stock_a_value = stock_market_data.get_most_recent_price( CompanyEnum.COMPANY_A) stock_b = portfolio.get_amount(CompanyEnum.COMPANY_B) stock_b_value = stock_market_data.get_most_recent_price( CompanyEnum.COMPANY_B) increase_a = pred_a_value - stock_a_value increase_b = pred_b_value - stock_b_value new_cash = 0.0 if stock_a > 0 and increase_a < 0.0: order_list.sell(CompanyEnum.COMPANY_A, stock_a) if stock_b > 0 and increase_b < 0.0: order_list.sell(CompanyEnum.COMPANY_B, stock_b) if increase_a > increase_b and increase_a > 0.0: count_a = portfolio.cash / stock_a_value if count_a > 0: order_list.buy(CompanyEnum.COMPANY_A, int(count_a)) elif increase_b > increase_a and increase_b > 0.0: count_b = portfolio.cash / stock_b_value if count_b > 0: order_list.buy(CompanyEnum.COMPANY_B, int(count_b)) # TODO: implement trading logic return order_list
def test_update__sufficient_cash_reserve(self): """ Tests: Portfolio#update Flavour: Enough cash in the portfolio, so the trades should be applied Creates a portfolio, a stock market data object and a arbitrary `OrderList` and executes these orders on the portfolio. Checks if those are applied correctly """ cash_reserve = 20000.0 data = StockData([(date(2017, 1, 1), 150.0)]) stock_market_data = StockMarketData({CompanyEnum.COMPANY_A: data}) portfolio = Portfolio(cash_reserve, [SharesOfCompany(CompanyEnum.COMPANY_A, 200)]) order_list = OrderList() order_list.buy(CompanyEnum.COMPANY_A, 100) updated_portfolio = portfolio.update(stock_market_data, order_list) # Current cash reserve is sufficient for trade volume. Trade should happen assert updated_portfolio.cash < cash_reserve assert updated_portfolio.cash < portfolio.cash assert updated_portfolio.shares[ 0].company_enum == CompanyEnum.COMPANY_A assert updated_portfolio.shares[0].amount == 300
def test_update__do_not_drop_below_cash_0(self): """ Tests: Portfolio#update Flavour: When receiving two BUY orders the `#update` method should regard the available cash and NEVER drop below 0 Creates a portfolio, a stock market data object and a arbitrary `OrderList` and executes these orders on the portfolio. Checks if those are applied correctly """ cash_reserve = 16000.0 data = StockData([(date(2017, 1, 1), 150.0)]) stock_market_data = StockMarketData({CompanyEnum.COMPANY_A: data}) portfolio = Portfolio(cash_reserve, []) # Create a order list whose individual actions are within the limit but in sum are over the limit # Stock price: 150.0, quantity: 100 -> trade volume: 15000.0; cash: 16000.0 order_list = OrderList() order_list.buy(CompanyEnum.COMPANY_A, 100) order_list.buy(CompanyEnum.COMPANY_A, 100) updated_portfolio = portfolio.update(stock_market_data, order_list) assert updated_portfolio.cash >= 0
def read_stock_market_data(stocks: StockList, periods: PeriodList) -> StockMarketData: """ Reads the "cross product" from `stocks` and `periods` from CSV files and creates a `StockMarketData` object from this. For each defined stock in `stocks` the corresponding value from `CompanyEnum` is used as logical name. If there are `periods` provided those are each read. Args: stocks: The company names for which to read the stock data. *Important:* These values need to be stated in `CompanyEnum` periods: The periods to read. If not empty each period is appended to the filename like this: `[stock_name]_[period].csv` Returns: The created `StockMarketData` object Examples: * Preface: Provided stock names are supposed to be part to `CompanyEnum`. They are stated plaintext-ish here to show the point: * `(['stock_a', 'stock_b'], ['1962-2011', '2012-2017'])` reads: * 'stock_a_1962-2011.csv' * 'stock_a_2012-2015.csv' * 'stock_b_1962-2011.csv' * 'stock_b_2012-2015.csv' into a dict with keys `CompanyEnum.COMPANY_A` and `CompanyEnum.COMPANY_B` respectively * `(['stock_a'], ['1962-2011', '2012-2017'])` reads: * 'stock_a_1962-2011.csv' * 'stock_a_2012-2015.csv' into a dict with a key `CompanyEnum.COMPANY_A` * `(['stock_a', 'stock_b'], ['1962-2011'])` reads: * 'stock_a_1962-2011.csv' * 'stock_b_1962-2011.csv' into a dict with keys `CompanyEnum.COMPANY_A` and `CompanyEnum.COMPANY_B` respectively * `(['stock_a', 'stock_b'], [])` reads: * 'stock_a.csv' * 'stock_b.csv' into a dict with keys `CompanyEnum.COMPANY_A` and `CompanyEnum.COMPANY_B` respectively """ data = dict() # Read *all* available data for stock in stocks: filename = stock.value if len(periods) is 0: data[stock] = StockData( __read_stock_market_data([[stock, filename]])[stock]) else: period_data = list() for period in periods: period_data.append( __read_stock_market_data( [[stock, ('%s_%s' % (filename, period))]])) data[stock] = StockData([ item for period_dict in period_data if period_dict is not None for item in period_dict[stock] ]) return StockMarketData(data)
def doTrade(self, portfolio: Portfolio, current_portfolio_value: float, stock_market_data: StockMarketData) -> OrderList: orders = OrderList() cheapest_company = min( list(CompanyEnum), key=lambda c: stock_market_data.get_most_recent_price(c)) cash_per_comp = portfolio.cash / stock_market_data.get_number_of_companies( ) price = stock_market_data.get_most_recent_price(cheapest_company) num_shares_to_buy = cash_per_comp // price if num_shares_to_buy > 0: orders.buy(cheapest_company, num_shares_to_buy) logger.info("Bought {0} shares.".format(num_shares_to_buy)) return orders
def get_data_up_to_offset(stock_market_data: StockMarketData, offset: int): """ Removes all data items *behind* the given `offset` - this emulates going through history in a list of date->price items Args: stock_market_data: The `market_data` to step through offset: The offset to apply Returns: A copied `StockMarketData` object which only reaches from start to `offset` """ if offset == 0: return stock_market_data offset_data = {} for company in stock_market_data.get_companies(): offset_data[company] = stock_market_data[company].copy_to_offset(offset) return StockMarketData(offset_data)
def create_order_list(self, action_a: float, action_b: float, portfolio: Portfolio, stock_market_data: StockMarketData) -> OrderList: """ Take two floats between -1.0 and +1.0 (one for stock A and one for stock B) and convert them into corresponding orders. Args: action_a: float between -1.0 and 1.0, representing buy(positive) / sell(negative) for Company A action_b: float between -1.0 and 1.0, representing buy(positive) / sell(negative) for Company B portfolio: current portfolio of this trader stock_market_data: current stock market data Returns: List of corresponding orders """ assert -1.0 <= action_a <= +1.0 and -1.0 <= action_b <= +1.0 assert portfolio is not None and stock_market_data is not None order_list = OrderList() # Create orders for stock A owned_amount_a = portfolio.get_amount(CompanyEnum.COMPANY_A) if action_a > 0.0: current_price = stock_market_data.get_most_recent_price(CompanyEnum.COMPANY_A) amount_to_buy = int(action_a * (portfolio.cash // current_price)) order_list.buy(CompanyEnum.COMPANY_A, amount_to_buy) if action_a < 0.0 and owned_amount_a > 0: amount_to_sell = int(abs(action_a) * owned_amount_a) order_list.sell(CompanyEnum.COMPANY_A, amount_to_sell) # Create orders for stock B owned_amount_b = portfolio.get_amount(CompanyEnum.COMPANY_B) if action_b > 0.0: current_price = stock_market_data.get_most_recent_price(CompanyEnum.COMPANY_B) amount_to_buy = int(action_b * (portfolio.cash // current_price)) order_list.buy(CompanyEnum.COMPANY_B, amount_to_buy) if action_b < 0.0 and owned_amount_b > 0: amount_to_sell = int(abs(action_b) * owned_amount_b) order_list.sell(CompanyEnum.COMPANY_B, amount_to_sell) return order_list
def doTrade(self, portfolio: Portfolio, current_portfolio_value: float, stock_market_data: StockMarketData) -> OrderList: """ Generate action to be taken on the "stock market" Args: portfolio : current Portfolio of this trader current_portfolio_value : value of Portfolio at given moment stock_market_data : StockMarketData for evaluation Returns: A OrderList instance, may be empty never None """ current_price_a = stock_market_data.get_most_recent_price(CompanyEnum.COMPANY_A) current_price_b = stock_market_data.get_most_recent_price(CompanyEnum.COMPANY_B) predicted_price_a = self.stock_a_predictor.doPredict(stock_market_data[CompanyEnum.COMPANY_A]) predicted_price_b = self.stock_b_predictor.doPredict(stock_market_data[CompanyEnum.COMPANY_B]) portfolio_value = portfolio.total_value( stock_market_data.get_most_recent_trade_day(), stock_market_data) state = State(portfolio_value, current_price_a, predicted_price_a, current_price_b, predicted_price_b) if self.last_state and self.train_while_trading: self.train_model(state, self.last_state) action = self.decide_action(state, self.last_state) orders = self.create_orders(action, portfolio, stock_market_data) self.last_state = state # log_orders(orders) return orders
def doTrade(self, portfolio: Portfolio, current_portfolio_value: float, stock_market_data: StockMarketData) -> OrderList: """ Generate action to be taken on the "stock market" Args: portfolio : current Portfolio of this trader current_portfolio_value : value of Portfolio at given moment stock_market_data : StockMarketData for evaluation Returns: A OrderList instance, may be empty never None """ # build current state object stock_a_data = stock_market_data[CompanyEnum.COMPANY_A] stock_b_data = stock_market_data[CompanyEnum.COMPANY_B] predicted_stock_a = self.stock_a_predictor.doPredict(stock_a_data) predicted_stock_b = self.stock_b_predictor.doPredict(stock_b_data) current_state = State(portfolio.cash, portfolio.get_amount(CompanyEnum.COMPANY_A), portfolio.get_amount(CompanyEnum.COMPANY_B), stock_market_data.get_most_recent_price(CompanyEnum.COMPANY_A), stock_market_data.get_most_recent_price(CompanyEnum.COMPANY_B), predicted_stock_a, predicted_stock_b) logger.debug(f"DQL Trader: Current state: {current_state}") # Store experience and train the neural network only if doTrade was called before at least once if self.train_while_trading and self.last_state is not None: reward = self.calculate_reward(self.last_portfolio_value, current_portfolio_value) memory_tuple = (self.last_state, self.last_action_a, self.last_action_b, reward, current_state) self.memory.append(memory_tuple) if len(self.memory) > self.batch_size + self.min_size_of_memory_before_training: self.train_model() # Create actions for current state and decrease epsilon for fewer random actions (action_a, action_b) = self.get_action(current_state) self.epsilon = max([self.epsilon_min, self.epsilon * self.epsilon_decay]) logger.debug(f"DQL Trader: Computed orders {action_a} and {action_b} with epsilon {self.epsilon}") # Save created state, actions and portfolio value for the next call of doTrade self.last_state, self.last_action_a, self.last_action_b = current_state, action_a, action_b self.last_portfolio_value = current_portfolio_value return self.create_order_list(action_a, action_b, portfolio, stock_market_data)
def test_update__action_order_does_not_matter(self): """ Tests: Portfolio#update Flavour: It shouldn't matter which order the orders are in, the result should always look the same. In this case the portfolio's cash reserve is too low to execute a BUY action. However, it shouldn't matter if we execute a SELL action first, because the updated cash reserve after a SELL action shouldn't affect the available cash reserve for a subsequent BUY action Creates a portfolio, a stock market data object and a arbitrary `OrderList` and executes these orders on the portfolio. Checks if those are applied correctly """ cash_reserve = 10.0 data = StockData([(date(2017, 1, 1), 150.0)]) stock_market_data = StockMarketData({CompanyEnum.COMPANY_A: data}) # Create two equal designed portfolios portfolio1 = Portfolio(cash_reserve, [SharesOfCompany(CompanyEnum.COMPANY_A, 200)]) portfolio2 = Portfolio(cash_reserve, [SharesOfCompany(CompanyEnum.COMPANY_A, 200)]) assert portfolio1 == portfolio2 # Create two order lists with the same entries, however in different order order_list_1 = OrderList() order_list_1.buy(CompanyEnum.COMPANY_A, 100) order_list_1.sell(CompanyEnum.COMPANY_A, 100) order_list_2 = OrderList() order_list_2.sell(CompanyEnum.COMPANY_A, 100) order_list_2.buy(CompanyEnum.COMPANY_A, 100) # Execute the trade action lists on the two portfolios updated_portfolio_order1 = portfolio1.update(stock_market_data, order_list_1) updated_portfolio_order2 = portfolio2.update(stock_market_data, order_list_2) # The portfolios should still be equal after applying the actions assert updated_portfolio_order1 == updated_portfolio_order2
def test_inspect__date_offset(self): """ Tests: Evaluator#inspect_over_time Flavour: Test with an date offset """ data = StockData([(date(2017, 1, 1), 150.0), (date(2017, 1, 2), 200.0), (date(2017, 1, 3), 250.0)]) stock_market_data = StockMarketData({CompanyEnum.COMPANY_A: data}) portfolio = Portfolio(20000, [SharesOfCompany(CompanyEnum.COMPANY_A, 200)]) evaluator = PortfolioEvaluator( [SimpleTrader(RandomPredictor(), RandomPredictor())]) portfolio_over_time: dict = \ evaluator.inspect_over_time(stock_market_data, [portfolio], date_offset=date(2017, 1, 2))['nameless'] assert date(2016, 12, 31) not in portfolio_over_time.keys() assert date(2017, 1, 1) in portfolio_over_time.keys() assert date(2017, 1, 2) in portfolio_over_time.keys() assert date(2017, 1, 3) not in portfolio_over_time.keys()
def doTrade(self, portfolio: Portfolio, current_portfolio_value: float, stock_market_data: StockMarketData) -> OrderList: """ Generate action to be taken on the "stock market" Args: portfolio : current Portfolio of this trader current_portfolio_value : value of Portfolio at given moment stock_market_data : StockMarketData for evaluation Returns: A OrderList instance, may be empty never None """ # TODO: Store experience and train the neural network only if doTrade was called before at least once # TODO: Create actions for current state and decrease epsilon for fewer random actions # TODO: Save created state, actions and portfolio value for the next call of doTrade deltaA = self.stock_a_predictor.doPredict( stock_market_data[CompanyEnum.COMPANY_A]) / stock_market_data.get_most_recent_price(CompanyEnum.COMPANY_A) deltaB = self.stock_b_predictor.doPredict( stock_market_data[CompanyEnum.COMPANY_B]) / stock_market_data.get_most_recent_price(CompanyEnum.COMPANY_B) INPUT = numpy.asarray([[ (deltaA - 1.0) / 0.04, (deltaB - 1.0) / 0.04, ]]) qualities = self.model.predict(INPUT)[0] qmax = max(qualities[0], qualities[1], qualities[2]) currentValue = portfolio.total_value(stock_market_data.get_most_recent_trade_day(), stock_market_data) if self.lastValue and self.train_while_trading: lastReward = min(1, max(-1, (currentValue / self.lastValue - 1) / 0.04)) shouldBeQ = lastReward + GAMMA * qmax self.lastOutput[self.lastAmax] = shouldBeQ xtrain = [self.lastInput[0]] ytrain = [self.lastOutput] for m in self.memory: xtrain.append(m[0][0]) qs = self.model.predict(m[0])[0] qs[m[1]] = m[2] + GAMMA * qs[m[1]] ytrain.append(qs) self.model.fit(numpy.asarray(xtrain), numpy.asarray(ytrain)) self.memory.append([self.lastInput, self.lastAmax, lastReward]) if len(self.memory) > MEMOMRY_SIZE: self.memory.pop(0) self.lastValue = currentValue self.lastInput = INPUT self.lastOutput = qualities result = OrderList() actions = ["BUY_A__SELL_B", "BUY_A", "BUY_B__SELL_A", "BUY_B", "SELL_ALL"] nextAction = None if random.random() < self.epsilon and self.train_while_trading: nextAction = actions[random.randint(0, self.action_size - 1)] else: i = 0 if qualities[0] > qualities[1] else 1 i = 2 if qualities[2] > qualities[i] else i i = 3 if qualities[3] > qualities[i] else i i = 4 if qualities[4] > qualities[i] else i nextAction = actions[i] self.epsilon = max(self.epsilon_decay * self.epsilon, self.epsilon_min) if nextAction == "BUY_A__SELL_B": result.sell(CompanyEnum.COMPANY_B, portfolio.get_amount(CompanyEnum.COMPANY_B)) count = math.floor(portfolio.cash / stock_market_data.get_most_recent_price(CompanyEnum.COMPANY_A)) result.buy(CompanyEnum.COMPANY_A, count) self.lastAmax = 0 elif nextAction == "BUY_A": count = math.floor(portfolio.cash / stock_market_data.get_most_recent_price(CompanyEnum.COMPANY_A)) result.buy(CompanyEnum.COMPANY_A, count) self.lastAmax = 1 elif nextAction == "BUY_B__SELL_A": result.sell(CompanyEnum.COMPANY_A, portfolio.get_amount(CompanyEnum.COMPANY_A)) count = math.floor(portfolio.cash / stock_market_data.get_most_recent_price(CompanyEnum.COMPANY_B)) result.buy(CompanyEnum.COMPANY_B, count) self.lastAmax = 2 elif nextAction == "BUY_B": count = math.floor(portfolio.cash / stock_market_data.get_most_recent_price(CompanyEnum.COMPANY_B)) result.buy(CompanyEnum.COMPANY_B, count) self.lastAmax = 3 elif nextAction == "SELL_ALL": result.sell(CompanyEnum.COMPANY_A, portfolio.get_amount(CompanyEnum.COMPANY_A)) result.sell(CompanyEnum.COMPANY_B, portfolio.get_amount(CompanyEnum.COMPANY_B)) self.lastAmax = 4 return result
def inspect_over_time_with_mapping( self, market_data: StockMarketData, portfolio_trader_mapping: PortfolioTraderMappingList, evaluation_offset: int = -1, date_offset: datetime.date = None): """ Behaves exactly as `#inspect_over_time *except* for the parameter `portfolio_trader_mapping`: While `#inspect_over_time` uses the traders provided to the constructor of this class, this method uses the provided list of trader-portfolio mappings. This allows for an fixed association between portfolios and their traders Args: market_data: portfolio_trader_mapping: A mapping between portfolios and traders. Structure: `List[Tuple[Portfolio, ITrader]]` evaluation_offset: date_offset: Returns: """ # Map that holds all portfolios in the course of time. Structure: {portfolio_name => {date => portfolio}} all_portfolios = {} # Cache that holds the latest object of each portfolio. Structure: {portfolio_name => portfolio} portfolio_cache = {} # Map that holds the drawing colors for each portfolio colors = {} if not market_data.check_data_length(): # Checks whether all data series are of the same length (i.e. have an equal count of date->price items) return if evaluation_offset == -1 and date_offset is None: # `evaluation_offset` has the 'disabled' value, so we calculate it based on the underlying data evaluation_offset = market_data.get_row_count() if date_offset is not None: # `date_offset` is set, so the `evaluation_offset` is calculated based on the given date first_company = next(iter(market_data.get_companies())) market_data_for_company = market_data[first_company] index = market_data_for_company.get_dates().index(date_offset) evaluation_offset = market_data.get_row_count() - index # Reading should start one day later, because we also save the initial portfolio value in our return data. # Therefore the return data contains `evaluation_offset` rows which includes `evaluation_offset`-1 trades evaluation_offset = evaluation_offset - 1 # And now the clock ticks # We start at -`evaluation_offset` and roll through the `market_data` in forward direction until the # second-to-last item for current_tick in range(-evaluation_offset, 0): # Retrieve the stock market data up the current day, i.e. move one tick further in `market_data` current_market_data = get_data_up_to_offset( market_data, current_tick) # Retrieve the current date current_date = current_market_data.get_most_recent_trade_day() portfolio_list = [p_t[0] for p_t in portfolio_trader_mapping] logger.debug( f"Start updating portfolios {portfolio_list} on {current_date} (tick {current_tick})" ) for portfolio, trader, color in portfolio_trader_mapping: if current_tick == -evaluation_offset: # Save the starting state of this portfolio yesterday = current_date - datetime.timedelta(days=1) all_portfolios.update( {portfolio.name: { yesterday: portfolio }}) portfolio_cache.update({portfolio.name: portfolio}) # Retrieve latest portfolio object from cache portfolio_to_update = portfolio_cache[portfolio.name] # Determine the total portfolio value at this time current_total_portfolio_value = portfolio_to_update.total_value( current_date, current_market_data) # Ask the trader for its action update = trader.doTrade(portfolio_to_update, current_total_portfolio_value, current_market_data) # Update the portfolio that is saved at ILSE - The InnovationLab Stock Exchange ;-) updated_portfolio = portfolio_to_update.update( current_market_data, update) # Save the updated portfolio in our dict under the current date as key all_portfolios[ updated_portfolio.name][current_date] = updated_portfolio portfolio_cache.update({portfolio.name: updated_portfolio}) colors[portfolio.name] = color logger.debug( f"End updating portfolios {portfolio_list} on {current_date} (tick {current_tick})\n" ) # Draw a diagram of the portfolios' changes over time - if we're not unit testing if self.draw_results: draw(all_portfolios, market_data, colors) return all_portfolios
def doTrade(self, portfolio: Portfolio, current_portfolio_value: float, stock_market_data: StockMarketData) -> OrderList: """ Generate action to be taken on the "stock market" Args: portfolio : current Portfolio of this trader current_portfolio_value : value of Portfolio at given moment stock_market_data : StockMarketData for evaluation Returns: A OrderList instance, may be empty never None """ # TODO: Build and store current state object s_a_current = stock_market_data.get_most_recent_price(CompanyEnum.COMPANY_A) s_b_current = stock_market_data.get_most_recent_price(CompanyEnum.COMPANY_B) pct_a = round((s_a_current * portfolio.get_amount(CompanyEnum.COMPANY_A))/portfolio.total_value(stock_market_data.get_most_recent_trade_day(), stock_market_data), 2) pct_b = round((s_b_current * portfolio.get_amount(CompanyEnum.COMPANY_B))/portfolio.total_value(stock_market_data.get_most_recent_trade_day(), stock_market_data), 2) pct_cash = 1 - pct_a - pct_b; s_a_next = self.stock_a_predictor.doPredict(stock_market_data[CompanyEnum.COMPANY_A]); s_b_next = self.stock_b_predictor.doPredict(stock_market_data[CompanyEnum.COMPANY_B]); pct_a_diff = round((s_a_next - s_a_current) / s_a_current, 2); pct_b_diff = round((s_b_next - s_b_current) / s_b_current, 2); portfolio_value = portfolio.total_value(stock_market_data.get_most_recent_trade_day(), stock_market_data); portfolio_diff = round((self.portfolio_value_prev - portfolio_value) / self.portfolio_value_prev, 2); #r = -1; #if (portfolio_diff < 0): # r = 1; #elif (portfolio_diff == 0): # r = 0; if (portfolio_diff < 0): self.y_prev[0][self.idx_prev] = 1; elif (portfolio_diff >= 0): self.y_prev[0][self.idx_prev] = -1; # TODO: Store experience and train the neural network only if doTrade was called before at least once if (self.loop_value > 0): self.model.fit(np.array([[self.pct_a_prev, self.pct_b_prev, self.pct_cash_prev, self.diff_a_prev, self.diff_b_prev]]), self.y_prev, epochs=1, batch_size=1) # TODO: Create actions for current state and decrease epsilon for fewer random actions res = self.model.predict(np.array([[pct_a, pct_b, pct_cash, pct_a_diff, pct_b_diff]])) # [0, 0, 1, 0] idx = np.argmax(res); ret = OrderList() if (idx == 0): ret.buy(CompanyEnum.COMPANY_A, math.floor(portfolio.cash / stock_market_data.get_most_recent_price(CompanyEnum.COMPANY_A))) elif (idx == 1): ret.buy(CompanyEnum.COMPANY_B, math.floor(portfolio.cash / stock_market_data.get_most_recent_price(CompanyEnum.COMPANY_B))) elif (idx == 2): ret.sell(CompanyEnum.COMPANY_A, portfolio.get_amount(CompanyEnum.COMPANY_A)) else: ret.sell(CompanyEnum.COMPANY_B, portfolio.get_amount(CompanyEnum.COMPANY_B)) # TODO: Save created state, actions and portfolio value for the next call of doTrade self.portfolio_value_prev = portfolio_value; self.pct_a_prev = pct_a; self.pct_b_prev = pct_b; self.pct_cash_prev = pct_cash; self.diff_a_prev = pct_a_diff; self.diff_b_prev = pct_a_diff; self.idx_prev = idx; self.y_prev = res; self.loop_value = self.loop_value + 1; return ret
def doTrade(self, portfolio: Portfolio, current_portfolio_value: float, stock_market_data: StockMarketData) -> OrderList: """ Generate action to be taken on the "stock market" Args: portfolio : current Portfolio of this trader current_portfolio_value : value of Portfolio at given moment stock_market_data : StockMarketData for evaluation Returns: A OrderList instance, may be empty never None """ # TODO: Build and store current state object ## cash %, portfolio a value %, portfolio b %, pred a, pred b account_value = portfolio.cash + current_portfolio_value a_value = portfolio.get_amount(CompanyEnum.COMPANY_A) * stock_market_data.get_most_recent_price(CompanyEnum.COMPANY_A) b_value = portfolio.get_amount(CompanyEnum.COMPANY_B) * stock_market_data.get_most_recent_price(CompanyEnum.COMPANY_B) a_value_percent = a_value / account_value b_value_percent = b_value / account_value cash_percent = portfolio.cash / account_value pred_a_value = self.stock_a_predictor.doPredict(stock_market_data[CompanyEnum.COMPANY_A]) pred_b_value = self.stock_b_predictor.doPredict(stock_market_data[CompanyEnum.COMPANY_B]) stock_a_value = stock_market_data.get_most_recent_price(CompanyEnum.COMPANY_A) stock_b_value = stock_market_data.get_most_recent_price(CompanyEnum.COMPANY_B) increase_a = (pred_a_value - stock_a_value) / stock_a_value increase_b = (pred_b_value - stock_b_value) / stock_b_value current_status = [[cash_percent, a_value_percent, b_value_percent, increase_a, increase_b]] np_current_status = np.array(current_status) # TODO: Store experience and train the neural network only if doTrade was called before at least once ## calc rewards = was cash + portfolio - (old cash + old portfolio), map auf 1 0 -1, now simple: if self.stored_action >= 0: if current_portfolio_value > self.stored_portfolio_value: reward = 1.0 elif current_portfolio_value < self.stored_portfolio_value: reward = -1.0 else: reward = 0 reward_array = [0, 0, 0] reward_array[self.stored_action] = reward np_reward_array = np.array([reward_array]) ## train again # print(np_reward_array) if self.train_while_trading: self.model.fit(self.np_previous_status, np_reward_array, epochs=1, batch_size=1, verbose=0) # TODO: Create actions for current state and decrease epsilon for fewer random actions action = -1 random_value = uniform(0.0, 1.0) if random_value > self.epsilon: action = randint(0, 2) else: pred = self.model.predict(np_current_status) prediction = pred[0] if len(prediction) != 3: print("komische prediction") action = 2 max = prediction[action] for i in range(3): if prediction[i] > max: action = i self.count[action] = self.count[action] + 1 # print("actions") # print(self.count) self.epsilon = self.epsilon * self.epsilon_decay if self.epsilon < self.epsilon_min: self.epsilon = self.epsilon_min order_list = OrderList() if action < 0 or action > 2: print("komische action") print(action) else: stock_a = portfolio.get_amount(CompanyEnum.COMPANY_A) stock_b = portfolio.get_amount(CompanyEnum.COMPANY_B) if action == 0: # sell a if stock_a > 0: order_list.sell(CompanyEnum.COMPANY_A, stock_a) # buy b count_b = portfolio.cash / stock_b_value if count_b > 0: order_list.buy(CompanyEnum.COMPANY_B, int(count_b)) if action == 1: # sell b if stock_b > 0: order_list.sell(CompanyEnum.COMPANY_B, stock_b) # boy a count_a = portfolio.cash / stock_a_value if count_a > 0: order_list.buy(CompanyEnum.COMPANY_A, int(count_a)) # TODO: Save created state, actions and portfolio value for the next call of self.stored_action = action self.stored_portfolio_value = current_portfolio_value self.np_previous_status = np_current_status ## save current state as laststate, current portfolio and cash return order_list