/ Strategies / src / print.jl
print.jl
  1  using .Data: closelast
  2  using .Instances: pnl, MarginInstance, NoMarginInstance, value, ohlcv, trades
  3  using .OrderTypes:
  4      LiquidationTrade, LongLiquidationTrade, ShortLiquidationTrade, LongTrade, ShortTrade
  5  
  6  @doc """ Updates the minimum and maximum holdings based on the provided value.
  7  
  8  $(TYPEDSIGNATURES)
  9  
 10  Given an asset instance, a value, and the current minimum and maximum holdings, this function updates the minimum and maximum holdings if the provided value is less than the current minimum or greater than the current maximum. It returns the updated minimum and maximum holdings.
 11  """
 12  _mmh(ai, val, min_hold, max_hold) = begin
 13      if val > max_hold[2]
 14          max_hold = (ai.asset.bc, val)
 15      end
 16      if 0 < val < min_hold[2]
 17          min_hold = (ai.asset.bc, val)
 18      end
 19      (min_hold, max_hold)
 20  end
 21  
 22  @doc """ Calculates the asset value for both long and short positions.
 23  
 24  $(TYPEDSIGNATURES)
 25  
 26  This function iterates over both long and short positions. If the asset instance for a position is not zero, it increments the number of holdings and calculates the value of the asset for the position at the current price. It then updates the minimum and maximum holdings using the `_mmh` function. The function returns the updated number of holdings, minimum holdings, and maximum holdings.
 27  """
 28  function _assetval(ai::MarginInstance, n_holdings, min_hold, max_hold; price)
 29      for p in (Long(), Short())
 30          iszero(ai, p) && continue
 31          n_holdings += 1
 32          val = value(ai, p; current_price=price)
 33          min_hold, max_hold = _mmh(ai, val, min_hold, max_hold)
 34      end
 35      (n_holdings, min_hold, max_hold)
 36  end
 37  @doc """ Calculates the asset value for a NoMarginInstance.
 38  
 39  $(TYPEDSIGNATURES)
 40  
 41  This function checks if the cash of the NoMarginInstance is not zero. If it's not, it increments the number of holdings and calculates the value of the asset at the current price. It then updates the minimum and maximum holdings using the `_mmh` function. The function returns the updated number of holdings, minimum holdings, and maximum holdings.
 42  """
 43  function _assetval(ai::NoMarginInstance, n_holdings, min_hold, max_hold; price)
 44      iszero(cash(ai)) || begin
 45          n_holdings += 1
 46          val = cash(ai) * price
 47          min_hold, max_hold = _mmh(ai, val, min_hold, max_hold)
 48      end
 49      (n_holdings, min_hold, max_hold)
 50  end
 51  
 52  @doc """ Calculates the minimum and maximum holdings for a strategy.
 53  
 54  $(TYPEDSIGNATURES)
 55  
 56  This function iterates over the holdings of a strategy. For each holding, it calculates the current price and updates the number of holdings, minimum holdings, and maximum holdings using the `_assetval` function. The function returns the minimum holdings, maximum holdings, and the count of holdings.
 57  """
 58  function minmax_holdings(s::Strategy)
 59      n_holdings = 0
 60      max_hold = (nameof(s.cash), 0.0)
 61      min_hold = (nameof(s.cash), Inf)
 62      datef = lasttrade_func(s)
 63      for ai in s.holdings
 64          iszero(ai) && continue
 65          df = ohlcv(ai)
 66          price = try
 67              closeat(df, datef(df.timestamp))
 68          catch
 69              close = df.close
 70              if isempty(close)
 71                  NaN
 72              else
 73                  last(close)
 74              end
 75          end
 76          (n_holdings, min_hold, max_hold) = _assetval(
 77              ai, n_holdings, min_hold, max_hold; price
 78          )
 79      end
 80      (min=min_hold, max=max_hold, count=n_holdings)
 81  end
 82  
 83  @doc "All trades recorded in the strategy universe (includes liquidations)."
 84  trades_count(s::Strategy) = begin
 85      n_trades = 0
 86      for ai in universe(s)
 87          n_trades += length(ai.history)
 88      end
 89      n_trades
 90  end
 91  
 92  @doc """ Counts all trades recorded in the strategy universe.
 93  
 94  $(TYPEDSIGNATURES)
 95  
 96  This function iterates over the universe of a strategy. For each asset instance in the universe, it increments a counter by the length of the asset instance's history. The function returns the total count of trades.
 97  """
 98  function trades_count(s::Strategy, ::Val{:liquidations})
 99      trades = 0
100      liquidations = 0
101      foreach(universe(s)) do ai
102          this_trades = trades(ai)
103          asset_liquidations = count((x -> x isa LiquidationTrade), this_trades)
104          trades += length(this_trades) - asset_liquidations
105          liquidations += asset_liquidations
106      end
107      (; trades, liquidations)
108  end
109  
110  function _count_trades(ai::AssetInstance; long=0, short=0, long_liq=0, short_liq=0)
111      foreach(trades(ai)) do t
112          if t isa LongTrade
113              long += 1
114          elseif t isa ShortTrade
115              short += 1
116          elseif t isa LongLiquidationTrade
117              long_liq += 1
118          elseif t isa ShortLiquidationTrade
119              short_liq += 1
120          end
121      end
122      return long, short, long_liq, short_liq
123  end
124  
125  @doc """ Counts the number of long, short, and liquidation trades in the strategy universe.
126  
127  $(TYPEDSIGNATURES)
128  
129  This function iterates over the universe of a strategy. For each asset instance in the universe, it counts the number of long trades, short trades, and liquidation trades. The function returns the total count of long trades, short trades, and liquidation trades.
130  """
131  function trades_count(s::Strategy, ::Val{:positions})
132      long = 0
133      short = 0
134      long_liq = 0
135      short_liq = 0
136      for ai in universe(s)
137          long, short, long_liq, short_liq = _count_trades(
138              ai; long, short, long_liq, short_liq
139          )
140      end
141      (; long, short, liquidations=long_liq + short_liq)
142  end
143  
144  orders(s::Strategy, ::Type{Buy}) = s.buyorders
145  orders(s::Strategy, ::Type{Sell}) = s.sellorders
146  
147  @doc """ Counts the number of orders for a given order side in a strategy.
148  
149  $(TYPEDSIGNATURES)
150  
151  This function iterates over the orders of a given side (Buy or Sell) in a strategy. It increments a counter by the length of the orders. The function returns the total count of orders.
152  """
153  function Base.count(s::Strategy, side::Type{<:OrderSide})
154      n = 0
155      for ords in values(orders(s, side))
156          n += length(ords)
157      end
158      n
159  end
160  
161  _ascash((val, sym)) = Cash(val, sym)
162  
163  Base.print(io::IO, ::Isolated) = write(io, "isolated")
164  Base.string(io::IO, ::Cross) = write(io, "cross")
165  Base.string(io::IO, ::NoMargin) = write(io, "nomargin")
166  
167  @nospecialize
168  function Base.print(out::IO, s::Strategy; price_func=lasttrade_price_func)
169      exc = exchange(s)
170      write(out, "Name: $(nameof(s)) ($(s |> execmode |> typeof |> nameof)) ")
171      if islive(s)
172          if issandbox(exc)
173              write(out, "[Sandbox]")
174          else
175              write(out, "[Wallet!]")
176          end
177      end
178      let t = attr(s, :run_task, nothing)
179          if !(isnothing(t)) && !istaskdone(t) && istaskstarted(t)
180              write(out, "(Running)")
181          end
182      end
183      cur = nameof(s.cash)
184      write(
185          out,
186          "\nConfig: $(string(s.margin)), $(s.config.min_size)($cur)(Base Size), $(s.config.initial_cash)($(cur))(Initial Cash)\n",
187      )
188      n_inst = nrow(universe(s).data)
189      n_exc = length(unique(universe(s).data.exchange))
190      write(out, "Universe: $n_inst instances, $n_exc exchanges")
191      write(out, "\n")
192      mmh = minmax_holdings(s)
193      long, short, liquidations = trades_count(s, Val(:positions))
194      trades = long + short
195      trades_msg = if marginmode(s) isa NoMargin
196          "Trades: $(trades) ~ $long\n"
197      else
198          "Trades: $(trades) ~ $long(longs) ~ $short(shorts) ~ $(liquidations)(liquidations)\n"
199      end
200      write(out, trades_msg)
201      write(out, "Holdings: $(mmh.count)")
202      if mmh.min[1] != cur
203          write(out, " ~ min $(Cash(mmh.min...))($cur)")
204      end
205      if mmh.max[1] != cur && mmh.max[1] != mmh.min[1]
206          write(out, " ~ max $(Cash(mmh.max...))($cur)\n")
207      else
208          write(out, "\n")
209      end
210      write(out, "Pending buys: $(count(s, Buy))\n")
211      write(out, "Pending sells: $(count(s, Sell))\n")
212      write(out, "$(s.cash) (Cash)\n")
213      t = nameof(s.cash)
214      try
215          tot = current_total(s; price_func, local_bal=true)
216          write(out, "$(t): $(tot) (Total)")
217      catch
218          @debug_backtrace()
219          write(out, "$(t): Error! (use JULIA_DEBUG=Strategies) (Total)")
220      end
221  end
222  Base.display(s::Strategy; kwargs...) = print(s)
223  Base.show(out::IO, ::MIME"text/plain", s::Strategy; kwargs...) = print(out, s; kwargs...)
224  Base.show(out::IO, s::Strategy; kwargs...) = print(out, ":", nameof(s))
225  
226  @specialize