Rebalance as a Bear/Bull indicator
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © hugodanielcom
//@version=5

// 
// Welcome, this is an indicator of a simple idea to check if the market has a Bear or Bull tendency:
// 
// 1. Start with a virtual portfolio of 60/40 tokens per fiat.
// 2. Rebalance it when its ratio oscillates by a given % (first input)
// 3. Count the number of times the rebalancer buys, and sells
// 4. When the number of buys is greater than the number of sells => the market is going down
// 5. When the number of sells is greater than the number of buys => the market is going up
//
// This is shown as the "Bear/Bull Strength" columns (red when bear, green when bull)
//
// An extra rebalancer is also kept that works at each bar (regardless of the input %).
// This is used to calculate an amount of tokens beying sold/bought and used as a "market force" coefficient.
//
// Another extra: based on both the bear/bull strengh and market force an attempt is made to
// provide good buying/selling windows of analysis.
// The blue background is a buying opportunity, the red background is a sell opportunity.
// In a bear market sales are delayed, and in a bull market buys are delayed.
// 

// This code uses the following public libraries:
import hugodanielcom/TradingPortfolio/1 as portfolio 
import hugodanielcom/PureRebalance/3    as rebalancer
// These are available here: https://www.tradingview.com/u/hugodanielcom/#published-scripts

indicator("Rebalance as a Bear/Bull indicator")
rebull_rebear_precision  = input.float(defval = 3,            title = "Rebalance when portfolio differs by %",  tooltip="Bigger values make the rebalance hold more and become less sensitive to peaks/bottoms and act less often.", minval = 0.1, maxval = 99.9)
rebull_rebear_from_month = input.int(defval = 11,             title = "Start At Month",        minval = 1, maxval = 12)
rebull_rebear_from_day   = input.int(defval = 1,              title = "Start At Day",          minval = 1, maxval = 31)
rebull_rebear_from_year  = input.int(defval = 2021,           title = "Start At Year",         minval = 2020)
rebull_rebear_capital    = input.float(defval = 10000,        title = "Initial capital",   minval = 1, tooltip="The total portfolio value to start with. A higher value makes the rebalancer more sensitive to peaks/bottoms and act more often")
rebull_rebear_percentage = input.float(defval = 60,           title = "Rebalance Token %", minval = 0.01, maxval = 100.0, tooltip="Percentage of Token / Money.\n60 means the targeted balance is 60% token and 40% money")
rebull_rebear_show_force = input.bool(defval = false,         title = "Show force")
rebull_rebear_show_opos  = input.bool(defval = true,          title = "Show buy/sell windows of opportunities")

// Backtest start window
rebull_rebear_start     = timestamp(rebull_rebear_from_year, rebull_rebear_from_month, rebull_rebear_from_day)
// Function returns true when the current bar time is
// "within the window of time" for this indicator
rebull_rebear_window()  => time >= rebull_rebear_start


var float[] percentage_portfolio = portfolio.init()
var float[] signal_portfolio = portfolio.init()

// Initialization starts by doing a rebalance at the first bar within
// the window
var bool rebull_rebear_init1 = true
if rebull_rebear_window() and rebull_rebear_init1
    // Only do this once, for the first bar, set init1 to false to disallow
    // this block from running again
    rebull_rebear_init1 := false
    first_rebalance_amount = rebalancer.rebalance(close, 0.0, rebull_rebear_capital, rebull_rebear_percentage / 100)
    portfolio.set_balance(percentage_portfolio, first_rebalance_amount, rebull_rebear_capital - (first_rebalance_amount * close))
    float signal_capital = close*100.0
    signal_first_rebalance_amount = rebalancer.rebalance(close, 0.0, signal_capital, rebull_rebear_percentage / 100)
    portfolio.set_balance(signal_portfolio, signal_first_rebalance_amount, signal_capital - (signal_first_rebalance_amount * close))

var float signal = 0.0
float signal_rebalance_amount = rebalancer.rebalance(close, portfolio.crypto(signal_portfolio), portfolio.fiat(signal_portfolio), rebull_rebear_percentage / 100.0)
float signal_percentage_change = math.abs(signal_rebalance_amount) / portfolio.crypto(signal_portfolio)
bool signal_is_rebalance_needed = signal_percentage_change >= ((rebull_rebear_precision / 4.0) / 100.0)
bool signal_is_rebalance_buying  = signal_is_rebalance_needed and signal_rebalance_amount > 0
bool signal_is_rebalance_selling = signal_is_rebalance_needed and signal_rebalance_amount < 0


// This is where the rebalance gets done
float percentage_rebalance_amount = rebalancer.rebalance(close, portfolio.crypto(percentage_portfolio), portfolio.fiat(percentage_portfolio), rebull_rebear_percentage / 100.0)
// Check if the amount to rebalance is bigger than the percentage
float percentage_change = math.abs(percentage_rebalance_amount) / portfolio.crypto(percentage_portfolio)
bool percentage_is_rebalance_needed = percentage_change >= (rebull_rebear_precision / 100.0)

bool percentage_is_rebalance_buying = percentage_is_rebalance_needed and percentage_rebalance_amount > 0
bool percentage_is_rebalance_selling = percentage_is_rebalance_needed and percentage_rebalance_amount < 0

var int bear_bull_balance = 0
var int bear_balance = 0
var int bull_balance = 0
var int signal_bear_balance = 0
var int signal_bull_balance = 0

var float percentage_signal = 0.0
// Update the percentage portfolio and the signal portfolio number of operations
// and bear/bull market balance. The values calculated here will then be used
// to set the "market_value" and "signal_market_value" below, which are the
// main indicators of the bear/bull market strength.
if rebull_rebear_window()
    if percentage_is_rebalance_buying
        portfolio.buy(percentage_portfolio, percentage_rebalance_amount, close)
        bear_balance += 1
        
    if percentage_is_rebalance_selling
        portfolio.sell(percentage_portfolio, math.abs(percentage_rebalance_amount), close)
        bull_balance += 1
    
    if signal_is_rebalance_buying
        if portfolio.buy(signal_portfolio, signal_rebalance_amount, close)
            signal += signal_rebalance_amount
            signal_bear_balance += 1
    if signal_is_rebalance_selling
        if portfolio.sell(signal_portfolio, math.abs(signal_rebalance_amount), close)
            signal += signal_rebalance_amount
            signal_bull_balance += 1

market_value = bull_balance - bear_balance
signal_market_value = signal_bull_balance - signal_bear_balance
// Store the signal_market_value at the entry point of a new market_value, this
// is used to subtract to the current signal value, useful as an indicator of
// the market force within the current market_value quantity.
var market_value_start = signal_market_value
if market_value[0] != market_value[1]
    market_value_start := signal_market_value

// The colors to be used when the market is considered bear or bull
bear_down_color = color.new(color.red, 15)
bear_up_color = color.new(color.red, 15)
bull_down_color = color.new(color.green, 15)
bull_up_color = color.new(color.green, 15)
// A special color to be used when it is neither.
nothing_color = color.new(color.gray, 0)
// By default it is neither:
color market_color = nothing_color

// Look at the market_value and decide which color to use
market_color := nothing_color
bool is_signal_going_up = signal > 0
bool is_signal_going_down = not is_signal_going_up
if market_value > 0 and is_signal_going_up
    market_color := bull_up_color
if market_value > 0 and is_signal_going_down
    market_color := bull_down_color
if market_value < 0 and is_signal_going_up
    market_color := bear_up_color
if market_value < 0 and is_signal_going_down
    market_color := bear_down_color
// The market force is the second indicator that this code outputs, it represents
// the tendency of the market to go up or down within a "market_value" block.
force=signal_market_value - market_value_start

// It can be used to find buying and selling windows
bool is_selling_opportunity = force > 0 and ((market_value < 0 and force >= 3)  or (market_value > 0 and force >= 2))
bool is_buying_opportunity  = force < 0 and ((market_value < 0 and force <= -2) or (market_value > 0 and force <= -3))
is_buying_opportunity := is_buying_opportunity or (is_buying_opportunity[1] and close[1] > close[0])
is_selling_opportunity := is_selling_opportunity or (is_selling_opportunity[1] and close[1] <= close[0])
    

// Paint these extra values if they are being drawn:
color force_color = na
if is_selling_opportunity
    force_color := color.new(color.red, 80)
if is_buying_opportunity
    force_color := color.new(color.blue, 80)

// The final output
bgcolor(rebull_rebear_show_opos ? force_color : na, title="Buy/Sell opportunities")
plot(market_value, "Bear/Bull Strength", color=market_color, style=plot.style_area)
plot(force, "Force", color=rebull_rebear_show_force ? color.orange : na)
hline(0, "Middle Band", color=color.new(#787B86, 50))

