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