XTZ trading strategy for Bear months
// 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 a strategy for trading XTZ in the Bear months.
//
// It also works in Bull months but loses money versus the Buy&Hold approach
// 
import hugodanielcom/PureRebalance/3 as rebalancer
import hugodanielcom/TradingPortfolio/1 as portfolio

strategy("V3", overlay=false, calc_on_order_fills = false, initial_capital = 10000.0)
min_value = input.float(defval = 11.0,   title = "Min. Trading Fiat Value",  minval = 1.0, maxval = 100.0)
float precision = input.float(defval = 11.5,   title = "Rebalance Precision",  minval = 1.0, maxval = 100.0)
fromHour  = input.int(defval = 1,    title = "hh",       group = "From", inline="fromClock", minval = 1, maxval = 24)
fromMin   = input.int(defval = 0,    title = "mm",       group = "From", inline="fromClock", minval = 0, maxval = 60)
fromMonth = input.int(defval = 11,   title = "MM",      group = "From", inline="fromDate",  minval = 1, maxval = 12)
fromDay   = input.int(defval = 1,    title = "DD",      group = "From", inline="fromDate",  minval = 1, maxval = 31)
fromYear  = input.int(defval = 2021, title = "YYYY",    group = "From", inline="fromDate",  minval = 1970)
thruMonth = input.int(defval = 11,   title = "MM",     group = "Thru", inline="thruDate",  minval = 1, maxval = 12)
thruDay   = input.int(defval = 31,   title = "DD",     group = "Thru", inline="thruDate",  minval = 1, maxval = 31)
thruYear  = input.int(defval = 2021, title = "YYYY",    group = "Thru", inline="thruDate",  minval = 1970)
float MIN_AMOUNT = min_value / close
// === FUNCTION EXAMPLE ===
start     = timestamp(fromYear, fromMonth, fromDay, fromHour, fromMin)        // backtest start window
finish    = timestamp(thruYear, thruMonth, thruDay, 23, 59)        // backtest finish window
window()  => time >= start and time <= finish ? true : false       // create function "within window of time"


rebull_rebear_precision  = input.float(defval = 3.0,          group = "Bear/Bull Market Indicator", 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 = fromMonth // input.int(defval = 11,             group = "Bear/Bull Market Indicator", title = "Start At Month",        minval = 1, maxval = 12)
rebull_rebear_from_day   = input.int(defval = 1,              group = "Bear/Bull Market Indicator", title = "Start At Day",          minval = 1, maxval = 31)
rebull_rebear_from_year  = fromYear // input.int(defval = 2021,           group = "Bear/Bull Market Indicator", title = "Start At Year",         minval = 2020)
rebull_rebear_capital    = input.float(defval = 10000.0,      group = "Bear/Bull Market Indicator", 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.0,         group = "Bear/Bull Market Indicator", 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,         group = "Bear/Bull Market Indicator", title = "Show force")
rebull_rebear_show_opos  = input.bool(defval = true,          group = "Bear/Bull Market Indicator", 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

// The two market trend indicator portfolios 
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_force_selling_opportunity = force > 0 and ((market_value < 0 and force >= 2)  or (market_value > 0 and force >= 1))
bool is_force_buying_opportunity  = force < 0 and ((market_value < 0 and force <= -2) or (market_value > 0 and force <= -3))
bool is_buying_opportunity = is_force_buying_opportunity or (is_force_buying_opportunity[1] and close[1] >= close[0])
bool is_selling_opportunity = is_force_selling_opportunity or (is_force_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 bear/bull 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))

////////////////////////////////////////////////////////////////////////////////////
// Rebalancer oscillator
////////////////////////////////////////////////////////////////////////////////////
// Calculate the HODL money in the portfolio - this is necessary because the
// TradingView strategy tester is targeted for single position strategies, which
// is not the case of this one.
var float token_start = 0.0
if window() and token_start == 0.0
    token_start := close
var float hold = 0 
var float window_close = 0.0
if window()
    window_close := close
    hold := (strategy.initial_capital / token_start)
float hold_profit = hold * window_close - token_start * hold
plot(hold_profit, "Hold Profit", color=na)

// Arrays to keep track of previous orders (the TradingView strategy object
// works with a single position, this strategy doesn't follow pyramiding or a
// position - these arrays help to keep track of the orders being issued and
// get updated when they get matched by an requivalent upper/lower sale/buy)
var buy_orders_value = array.new_float()
var buy_orders_amount = array.new_float()
var buy_orders_fullfillment = array.new_float()
var sell_orders_value = array.new_float()
var sell_orders_amount = array.new_float()
var sell_orders_fullfillment = array.new_float()
var tmp_fullfillment_indices = array.new_int()
var tmp_fullfillment_amounts = array.new_float()

// Returns [ticker value, total amount sold, amount available]
get_past_unfulfilled_sell(int i) =>
    if i >= 0 and i < array.size(sell_orders_value) and i < array.size(sell_orders_amount) and i < array.size(sell_orders_fullfillment)
        fullfillment = array.get(sell_orders_fullfillment, i)
        if fullfillment < 1.0
            initial_amount_sold = array.get(sell_orders_amount, i)
            available_amount = initial_amount_sold * (1.0 - fullfillment)
            [array.get(sell_orders_value, i), initial_amount_sold, available_amount]
        else
            [0.0, 0.0, 0.0]
    else
        [0.0, 0.0, 0.0]

set_fullfillment_amount_in_sell(int i, float amount) =>
    [_, initial_sell_amount, sell_available_amount] = get_past_unfulfilled_sell(i)
    fullfilled_amount = initial_sell_amount - sell_available_amount
    array.set(sell_orders_fullfillment, i, (amount + fullfilled_amount) / initial_sell_amount)

fullfill_sell_at(float value, float fiat_account_balance, int at) =>
    amount_to_buy = 0.0

    [sell_close, initial_sell_amount, sell_available_amount] = get_past_unfulfilled_sell(at)
    if sell_available_amount * value < fiat_account_balance
        // Fullfill 100% immediately
        amount_to_buy += sell_available_amount
    else
        // Try to fullfill in parcels
        amount_fullfilled = 0.0
        total_parcels = 20
        for j = 0 to total_parcels-1 
            current_parcel_amount = j*sell_available_amount/total_parcels
            // Do we have fiat enough to buy a parcel this big?
            if fiat_account_balance > current_parcel_amount * value
                amount_fullfilled := current_parcel_amount
        // Fullfill this
        amount_to_buy += amount_fullfilled
    profit = sell_close * amount_to_buy - value * amount_to_buy
    [amount_to_buy, sell_close, profit]

// Tries to buy enough 
fullfill_sells_gt(float value, float fiat_account_balance) =>
    array.clear(tmp_fullfillment_indices)
    array.clear(tmp_fullfillment_amounts)
    float profit = 0.0
    float amount_to_buy = 0.0
    float tmp_fiat_balance = fiat_account_balance
    int _size = array.size(sell_orders_value)
    if _size > 0 and fiat_account_balance > 1.0
        for i = 0 to _size-1
            cur_order_value = array.get(sell_orders_value, i)
            if cur_order_value > value
                [amount_possible, sell_value, order_profit] = fullfill_sell_at(value, tmp_fiat_balance, i)
                tmp_fiat_balance -= amount_possible * sell_value
                amount_to_buy += amount_possible
                profit += order_profit
                // Mark this to be fullfilled
                array.push(tmp_fullfillment_indices, i)
                array.push(tmp_fullfillment_amounts, amount_possible)
    [amount_to_buy, profit]

fullfill_oldest_sale(float value, float fiat_account_balance) =>
    array.clear(tmp_fullfillment_indices)
    array.clear(tmp_fullfillment_amounts)
    int _size = array.size(sell_orders_value)
    float amount_to_buy = MIN_AMOUNT
    bool _is_done = false
    if _size > 0
        for i = 0 to _size-1
            [amount_possible, sell_close, _] = fullfill_sell_at(value, fiat_account_balance, i)
            if not _is_done
                // This is the percentage of the profit to take in float: 1.XXX
                price_adjustment = (1.0 - (sell_close / value)) + 1.0
                if amount_possible > 0.0 and amount_possible * price_adjustment < fiat_account_balance
                    amount_to_buy := amount_possible * price_adjustment
                    _is_done := true
                    array.push(tmp_fullfillment_indices, i)
                    array.push(tmp_fullfillment_amounts, amount_to_buy)
                    amount_to_buy
                else
                    amount_to_buy := 0.03
    else
        amount_to_buy
    
    
clear_fullfilled_sells() =>
    old_sell_orders_value = array.copy(sell_orders_value)
    old_sell_orders_amount = array.copy(sell_orders_amount)
    old_sell_orders_fullfillment = array.copy(sell_orders_fullfillment)
    array.clear(sell_orders_value)
    array.clear(sell_orders_amount)
    array.clear(sell_orders_fullfillment)
    _size = array.size(old_sell_orders_fullfillment)
    if _size > 0
        for i = 0 to _size-1
            if array.get(old_sell_orders_fullfillment, i) < 1.0
                array.push(sell_orders_fullfillment, array.get(old_sell_orders_fullfillment, i))
                array.push(sell_orders_value, array.get(old_sell_orders_value, i))
                array.push(sell_orders_amount, array.get(old_sell_orders_amount, i))

close_buy() =>
    _size = array.size(tmp_fullfillment_indices)
    if _size > 0
        for i = 0 to _size-1
            sell_index = array.get(tmp_fullfillment_indices, i)
            sell_amount = array.get(tmp_fullfillment_amounts, i)
            set_fullfillment_amount_in_sell(sell_index, sell_amount)
    clear_fullfilled_sells()
            
register_sell(value, amount) =>
    array.push(sell_orders_value, value)
    array.push(sell_orders_amount, amount)
    array.push(sell_orders_fullfillment, 0.0)


// Returns [ticker value, total amount sold, amount available]
get_past_unfulfilled_buy(int i) =>
    if i >= 0 and i < array.size(buy_orders_value) and i < array.size(buy_orders_amount) and i < array.size(buy_orders_fullfillment)
        fullfillment = array.get(buy_orders_fullfillment, i)
        if fullfillment < 1.0
            initial_amount_bought = array.get(buy_orders_amount, i)
            available_amount = initial_amount_bought * (1.0 - fullfillment)
            [array.get(buy_orders_value, i), initial_amount_bought, available_amount]
        else
            [0.0, 0.0, 0.0]
    else
        [0.0, 0.0, 0.0]

set_fullfillment_amount_in_buy(int i, float amount) =>
    [_, initial_buy_amount, buy_available_amount] = get_past_unfulfilled_buy(i)
    fullfilled_amount = initial_buy_amount - buy_available_amount
    array.set(buy_orders_fullfillment, i, (amount + fullfilled_amount) / initial_buy_amount)

fullfill_buy_at(float value, float fiat_account_balance, int at) =>
    amount_to_sell = 0.0

    [buy_close, initial_buy_amount, buy_available_amount] = get_past_unfulfilled_buy(at)
    if buy_available_amount * value < fiat_account_balance
        // Fullfill 100% immediately
        amount_to_sell += buy_available_amount
    else
        // Try to fullfill in parcels
        amount_fullfilled = 0.0
        total_parcels = 20
        for j = 0 to total_parcels-1 
            current_parcel_amount = j*buy_available_amount/total_parcels
            // Do we have fiat enough to buy a parcel this big?
            if fiat_account_balance > current_parcel_amount * value
                amount_fullfilled := current_parcel_amount
        // Fullfill this
        amount_to_sell += amount_fullfilled
    profit = buy_close * amount_to_sell - value * amount_to_sell
    [amount_to_sell, buy_close, profit]

// Tries to buy enough 
fullfill_buys_lt(float value, float fiat_account_balance) =>
    array.clear(tmp_fullfillment_indices)
    array.clear(tmp_fullfillment_amounts)
    float profit = 0.0
    float amount_to_sell = 0.0
    float tmp_fiat_balance = fiat_account_balance
    int _size = array.size(buy_orders_value)
    if _size > 0 and fiat_account_balance > 1.0
        for i = 0 to _size-1
            cur_order_value = array.get(buy_orders_value, i)
            if cur_order_value < value
                [amount_possible, buy_value, order_profit] = fullfill_buy_at(value, tmp_fiat_balance, i)
                tmp_fiat_balance -= amount_possible * buy_value
                amount_to_sell += amount_possible
                profit += order_profit
                // Mark this to be fullfilled
                array.push(tmp_fullfillment_indices, i)
                array.push(tmp_fullfillment_amounts, amount_possible)
    [amount_to_sell, profit]

fullfill_oldest_buy(float value, float fiat_account_balance) =>
    array.clear(tmp_fullfillment_indices)
    array.clear(tmp_fullfillment_amounts)
    int _size = array.size(buy_orders_value)
    float amount_to_sell = 1.0
    bool _is_done = false
    if _size > 0
        for i = 0 to _size-1
            [amount_possible, buy_close, _] = fullfill_buy_at(value, fiat_account_balance, i)
            if not _is_done
                price_adjustment = (1.0 - (buy_close / value)) + 1.0
                if amount_possible > 0.0 and amount_possible * price_adjustment < fiat_account_balance
                    amount_to_sell := amount_possible * price_adjustment
                    _is_done := true
                    array.push(tmp_fullfillment_indices, i)
                    array.push(tmp_fullfillment_amounts, amount_to_sell)
                    amount_to_sell
                else
                    amount_to_sell := 0.03
    else
        amount_to_sell
    
    
clear_fullfilled_buys() =>
    old_buy_orders_value = array.copy(buy_orders_value)
    old_buy_orders_amount = array.copy(buy_orders_amount)
    old_buy_orders_fullfillment = array.copy(buy_orders_fullfillment)
    array.clear(buy_orders_value)
    array.clear(buy_orders_amount)
    array.clear(buy_orders_fullfillment)
    _size = array.size(old_buy_orders_fullfillment)
    if _size > 0
        for i = 0 to _size-1
            if array.get(old_buy_orders_fullfillment, i) < 1.0
                array.push(buy_orders_fullfillment, array.get(old_buy_orders_fullfillment, i))
                array.push(buy_orders_value, array.get(old_buy_orders_value, i))
                array.push(buy_orders_amount, array.get(old_buy_orders_amount, i))

close_sell() =>
    _size = array.size(tmp_fullfillment_indices)
    if _size > 0
        for i = 0 to _size-1
            buy_index = array.get(tmp_fullfillment_indices, i)
            buy_amount = array.get(tmp_fullfillment_amounts, i)
            set_fullfillment_amount_in_buy(buy_index, buy_amount)
    clear_fullfilled_buys()
            
register_buy(value, amount) =>
    array.push(buy_orders_value, value)
    array.push(buy_orders_amount, amount)
    array.push(buy_orders_fullfillment, 0.0)

// Virtual Portfolio (useful to get the rebalancer buy/sell points without
// suffering interference from the real buy/sales being done)
var float[] virtual = portfolio.init()

// The Real Portfolio
var float[] balance = portfolio.init()


buy_order(float amount) =>
    strategy.order("buy", strategy.long, amount, when = window(), alert_message = str.tostring(amount))
    
sell_order(float amount) =>
    strategy.order("sell", strategy.short, amount, when = window(), alert_message = str.tostring(-amount))


// Initialization starts by buying MIN_AMOUNT token and selling it right away.
// this is to ensure proper values in the Strategy Tester that start at the
// first block. Strategy Tester by default starts at the block of the first
// order - a buy/sell order is done on the first block so that it starts at it.
var init1 = true
var init2 = false
var init3 = false
var first_full_amount = MIN_AMOUNT
var last_rebalance_at = 0
ratio_of_first_rebalance = 0.5
float capital = float(strategy.initial_capital[0])
// Start by selling enough crypto to have this
// amount of fiat 
fiat_offset = 2000
on_the_side = 0
if window() and init1
    init1 := false
    init2 := true
    strategy.order("buy", strategy.long, first_full_amount, when = window())
    init1
else if window() and init2
    init2 := false
    init3 := true
    strategy.order("sell", strategy.short, first_full_amount, when = window())
    init2
else if window() and init3
    init3 := false
    first_rebalance_amount = rebalancer.rebalance(close, 0.0, capital, ratio_of_first_rebalance)
    portfolio.set_balance(virtual, first_rebalance_amount, strategy.initial_capital - (first_rebalance_amount * close))
    // first_amount_to_buy = (strategy.initial_capital - fiat_offset) / close // 100% crypto - offset
    // Start with `offset` FIAT on the side
    initial_crypto = (strategy.initial_capital - fiat_offset) / close
    portfolio.set_balance(balance, initial_crypto, fiat_offset + on_the_side)
    // last_rebalance_at := month(time)
    // strategy.order("buy", strategy.long, first_rebalance_amount, when = window(), alert_message = str.tostring(first_rebalance_amount))
    init3

// Adjust the precision to the current token value,
// This way the amount to rebalance is adjusted to be meaningful in very high
// value tokens, or very low value tokens
float token_adjustment = 2000.0 / (strategy.initial_capital / close)


// The virtual portfolio is frequently rebalanced at 60% crypto
// this allows it to be used to output buy/sales points while the real
// portfolio gets messed up with the trading action
// amount = portfolio.get_rebalance_amount(virtual, 0.60, close)
float virtual_amount = window() ? rebalancer.rebalance(close, portfolio.crypto(virtual), portfolio.fiat(virtual), 0.60) : 0.0

color virtual_rebalance_color = na
bool is_rebalance_selling = virtual_amount * token_adjustment < -precision
bool is_rebalance_buying = virtual_amount * token_adjustment > precision
if window()
    if is_rebalance_buying
        portfolio.buy(virtual, virtual_amount, close)
        virtual_rebalance_color := color.new(color.teal, 80)
            
    if is_rebalance_selling
        portfolio.sell(virtual, math.abs(virtual_amount), close)
        virtual_rebalance_color := color.new(color.maroon, 80)

bgcolor(window() ? virtual_rebalance_color : na, title="Buy/Sell Bar")


////////////////////////////////////////////////////////////////////////////////
// MAIN - balance = portfolio.init()
////////////////////////////////////////////////////////////////////////////////
bool is_selling_opportunity_finished = (not is_selling_opportunity) and is_selling_opportunity[1]
bool is_buying_opportunity_finished = (not is_buying_opportunity) and is_buying_opportunity[1]
bool is_selling_opportunity_starting = (not is_selling_opportunity[1]) and is_selling_opportunity[0]
bool is_buying_opportunity_starting = (not is_buying_opportunity[1]) and is_buying_opportunity[0]

buy(portfolio, amount, value) =>
    [prev_sold_amount, profit_if_bought] = fullfill_sells_gt(value*1.12, portfolio.fiat(balance))
    if math.abs(amount) > MIN_AMOUNT
        if portfolio.buy(portfolio, math.abs(amount) + math.abs(prev_sold_amount), value)
            buy_order(math.abs(amount) + math.abs(prev_sold_amount))
            register_buy(value, math.abs(amount))
            close_buy()

sell(portfolio, amount, value) =>
    [prev_bought_amount, profit_if_sold] = fullfill_buys_lt(value*0.88, portfolio.fiat(balance))
    if math.abs(amount) > MIN_AMOUNT
        if portfolio.sell(portfolio, math.abs(amount) + math.abs(prev_bought_amount), value)
            sell_order(math.abs(amount) + math.abs(prev_bought_amount))
            register_sell(value, math.abs(amount))
            close_sell()


var bool has_pending_sell = false
var bool has_pending_buy = false

if is_rebalance_selling and is_selling_opportunity
    has_pending_sell := true

if is_rebalance_buying and is_buying_opportunity
    has_pending_buy := true

bool buy_now = false
bool sell_now = false
float default_ratio = market_value > 0 ? 0.8 : (market_value < 0 ? 0.6 : 0.7)
float rebalance_ratio = default_ratio

// Overall performance of this strategy
float strategy_total = portfolio.fiat(balance) + portfolio.crypto(balance) * window_close
float vs_hold = (strategy_total + portfolio.retained(balance)) - (hold * window_close)
float did_profit = (portfolio.retained(balance) + strategy_total) - strategy.initial_capital

///////////////
// Positions //
///////////////

// [amount, value]
var float[] bought = array.new_float(8, 0.0)
var float[] sold   = array.new_float(8, 0.0)
var int position_at = 0 // index
int POSITION_NO_ACTION = 0
int POSITION_BUYING = 1
int POSITION_SELLING = 2
var int position_state = POSITION_NO_ACTION
var int sell_signal_count = 0
var int buy_signal_count = 0
int NEEDED_BUY_SIGNALS = 5
int NEEDED_SELL_SIGNALS = 5
// Amount of portfolio to buy/sell at each position
float AMOUNT_PERCENTAGE = 9.0/100.0
float PROFIT_MARGIN = 8.0/100.0

///////////////
// Actions
///////////////

// Close At Start records the `close` value at the start of a market trend (bear/bull)
var float close_at_start = 0
// The maximum bull strength found:
var int max_market_value = 0
if market_value > max_market_value
    max_market_value := market_value
    
// BEAR MARKET STRATEGY:
if market_value < 0
    // Don't rebalance in Bear Market
    buy_now := false
    sell_now := false
    

    if is_buying_opportunity_starting or is_buying_opportunity_finished
        close_at_start := close
    
    ///////////////
    /// SELLING
    float margin = PROFIT_MARGIN / 2.0
    if (not is_selling_opportunity) and is_rebalance_buying and (close_at_start*(1.0 + margin)) <= close
        float sell_amount = portfolio.crypto(balance)*AMOUNT_PERCENTAGE
        sell(balance, sell_amount, close)
    
    //////////////
    /// BUYING
    if is_buying_opportunity and is_rebalance_buying and (close_at_start*(1.0 - margin)) >= close
        float buy_amount = (portfolio.fiat(balance)/close) * AMOUNT_PERCENTAGE
        buy(balance, buy_amount, close)

    
// BULL MARKET:
if market_value > 0
    // Don't rebalance in Bull Market
    buy_now := false
    sell_now := false

    if is_selling_opportunity_starting or is_selling_opportunity_finished
        close_at_start := close
        
    ///////////////
    /// SELLING
    float margin = PROFIT_MARGIN
    if is_selling_opportunity and is_rebalance_selling and (close_at_start*(1.0 + margin)) <= close
        float sell_amount = portfolio.crypto(balance)*AMOUNT_PERCENTAGE
        sell(balance, sell_amount, close)
    
    //////////////
    /// BUYING
    if (not is_selling_opportunity) and is_rebalance_buying and (close_at_start*(1.0 - margin)) >= close
        float buy_amount = (portfolio.fiat(balance)/close) * AMOUNT_PERCENTAGE
        buy(balance, buy_amount, close)


    // Buy 99% crypto when entering
    if market_value[1] == 0
        sell_now := false
        buy_now := true
        rebalance_ratio := 0.99

// NEUTRAL MARKET
if market_value == 0
    buy_now := false
    sell_now := false
    position_at := 0
    array.clear(bought)
    array.clear(sold)
    
    // Sell 70% of crypto when entering from BULL (i.e. going down)
    if market_value[1] == 1 and did_profit < 0.0 // Only sell if bleeding cash
        sell_now := true
        buy_now := false
        rebalance_ratio := 0.3    


////////////////////////////////////////
// Rebalance buy_now/sell_now signals //
////////////////////////////////////////
float unbalanced_amount = window() ? rebalancer.rebalance(close, portfolio.crypto(balance), portfolio.fiat(balance), rebalance_ratio) : 0.0
float abs_unbalanced_amount = math.abs(unbalanced_amount)
if sell_now and MIN_AMOUNT <= abs_unbalanced_amount
    // SELL NOW
    sell(balance, abs_unbalanced_amount, close)
    has_pending_sell := false


if buy_now and MIN_AMOUNT <= abs_unbalanced_amount
    // BUY NOW
    buy(balance, abs_unbalanced_amount, close)
    has_pending_buy := false

//////////////////////////////////////////////////////////////////////////////
// ANALYSIS
//////////////////////////////////////////////////////////////////////////////
plot(portfolio.fiat(balance) + portfolio.retained(balance), title = "Fiat in Balance")
plot(portfolio.crypto(balance), title = "Crypto in Balance")
plot( did_profit, title = "Did Profit? (dif. from initial capital)", color = ((portfolio.retained(balance) + strategy_total) - strategy.initial_capital) > 0 ? color.green : color.red)
plot(vs_hold, title = "Total profit vs Hold (in Fiat)", color = (vs_hold > 0 ? color.green : color.red))


