class_docstr: A stock trading environment for OpenAI gym
  This environment penalizes the model for not maintaining a reserve of cash.
  This enables the model to manage cash reserves in addition to performing trading procedures.
  Reward at any step is given as follows
      r_i = (sum(cash, asset_value) - initial_cash - max(0, sum(cash, asset_value)*cash_penalty_proportion-cash))/(days_elapsed)
      This reward function takes into account a liquidity requirement, as well as long-term accrued rewards.
  Parameters:
      df (pandas.DataFrame): Dataframe containing data
      buy_cost_pct (float): cost for buying shares
      sell_cost_pct (float): cost for selling shares
      hmax (int, array): maximum cash to be traded in each trade per asset. If an array is provided, then each index correspond to each asset
      discrete_actions (bool): option to choose whether perform dicretization on actions space or not
      shares_increment (int): multiples number of shares can be bought in each trade. Only applicable if discrete_actions=True
      turbulence_threshold (float): Maximum turbulence allowed in market for purchases to occur. If exceeded, positions are liquidated
      print_verbosity(int): When iterating (step), how often to print stats about state of env
      initial_amount: (int, float): Amount of cash initially available
      daily_information_columns (list(str)): Columns to use when building state space from the dataframe. It could be OHLC columns or any other variables such as technical indicators and turbulence index
      cash_penalty_proportion (int, float): Penalty to apply if the algorithm runs out of cash
      patient (bool): option to choose whether end the cycle when we're running out of cash or just don't buy anything until we got additional cash
  
  RL Inputs and Outputs
      action space: [<n_assets>,] in range {-1, 1}
      state space: {start_cash, [shares_i for in in assets], [[indicator_j for j in indicators] for i in assets]]}
  TODO:
      Organize functions
      Write README
      Document tests
code: def step(self, actions):
      # let's just log what we're doing in terms of max actions at each step.
      self.sum_trades += np.sum(np.abs(actions))
      self.log_header()
      # print if it's time.
      if (self.current_step + 1) % self.print_verbosity == 0:
          self.log_step(reason="update")
      # if we're at the end
      if self.date_index == len(self.dates) - 1:
          # if we hit the end, set reward to total gains (or losses)
          return self.return_terminal(reward=self.get_reward())
      else:
          """
          First, we need to compute values of holdings, save these, and log everything.
          Then we can reward our model for its earnings.
          """
          # compute value of cash + assets
          begin_cash = self.cash_on_hand
          assert min(self.holdings) >= 0
          asset_value = np.dot(self.holdings, self.closings)
          # log the values of cash, assets, and total assets
          self.account_information["cash"].append(begin_cash)
          self.account_information["asset_value"].append(asset_value)
          self.account_information["total_assets"].append(
              begin_cash + asset_value
          )
  
          # compute reward once we've computed the value of things!
          reward = self.get_reward()
          self.account_information["reward"].append(reward)
  
          # Now, let's get down to business at hand.
          transactions = self.get_transactions(actions)
  
          # compute our proceeds from sells, and add to cash
          sells = -np.clip(transactions, -np.inf, 0)
          proceeds = np.dot(sells, self.closings)
          costs = proceeds * self.sell_cost_pct
          coh = begin_cash + proceeds
          # compute the cost of our buys
          buys = np.clip(transactions, 0, np.inf)
          spend = np.dot(buys, self.closings)
          costs += spend * self.buy_cost_pct
          # if we run out of cash...
          if (spend + costs) > coh:
              if self.patient:
                  # ... just don't buy anything until we got additional cash
                  self.log_step(reason="CASH SHORTAGE")
                  transactions = np.where(
                      transactions > 0, 0, transactions
                  )
                  spend = 0
                  costs = 0
              else:
                  # ... end the cycle and penalize
                  return self.return_terminal(
                      reason="CASH SHORTAGE", reward=self.get_reward()
                  )
          self.transaction_memory.append(
              transactions
          )  # capture what the model's could do
          # verify we didn't do anything impossible here
          assert (spend + costs) <= coh
          # update our holdings
          coh = coh - spend - costs
          holdings_updated = self.holdings + transactions
          self.date_index += 1
          if self.turbulence_threshold is not None:
              self.turbulence = self.get_date_vector(
                  self.date_index, cols=["turbulence"]
              )[0]
          # Update State
          state = (
              [coh]
              + list(holdings_updated)
              + self.get_date_vector(self.date_index)
          )
          self.state_memory.append(state)
          return state, reward, False, {}
  
imports: from __future__ import annotations
  import random
  import time
  from copy import deepcopy
  import gym
  import matplotlib
  import numpy
  import pandas
  from gym import spaces
  from stable_baselines3.common import logger
  from stable_baselines3.common.vec_env import DummyVecEnv
  from stable_baselines3.common.vec_env import SubprocVecEnv
label_desc: Use image node_id 14 for calling the StockTradingEnvCashpenalty obj's underlying member method code with example usage: obj.step(actions) and returns: self, state,  reward,  False,  dict, self