
This isn’t another technical analysis strategy, but a long-term investment framework based on Bitcoin’s 4-year halving cycles. Backtesting data reveals: strict execution at halving timepoints can yield over 2000% returns per cycle. But don’t get excited yet - this strategy demands iron discipline and massive risk tolerance.
Core logic is brutally simple: buy at halving, take profits 40-80 weeks later, restart DCA after 135 weeks. Sounds easy, requires steel willpower to execute.
Phase 1: Halving Entry Period (0-40 weeks) Enter immediately after halving events - this is the strategy’s core entry point. Historical data shows the 40 weeks post-halving represent optimal accumulation periods, when markets haven’t fully priced in supply reduction impacts.
Phase 2: Profit-Taking Window (40-80 weeks) Weeks 40-80 post-halving historically mark Bitcoin’s explosive price discovery periods. 2016 halving delivered 3000%+ gains by week 78, 2020 halving showed similar patterns. This timing isn’t speculation - it’s mathematical derivation from supply-demand fundamentals.
Phase 3: Bear Market DCA Period (135+ weeks) Week 135 post-halving typically enters deep bear territory, triggering DCA activation. This timing beats blind dollar-cost averaging by avoiding inefficient capital deployment during bull market peaks.
Primary Risk: Execution Failure Strategy’s biggest enemy isn’t market volatility - it’s human nature. Halving entries require contrarian positioning during pessimistic periods, profit-taking demands composure during euphoria. Historical evidence shows 90% of participants fail complete execution.
Capital Management Requirements Recommend maximum 20% portfolio allocation per cycle, as individual cycles may face 80%+ drawdowns. 2018 bear market crashed from \(20k to \)3.2k - even “correct” timing involves massive unrealized losses.
Market Evolution Risk Strategy relies on 3 complete historical cycles, but Bitcoin markets are maturing rapidly. Institutional inflows, ETF approvals, and regulatory clarity may disrupt traditional cycle patterns. Past performance doesn’t guarantee future returns - this isn’t legal boilerplate.
40-Week Profit Start: Calculated from historical post-halving supply-demand equilibrium points. Earlier exits risk missing primary uptrends, later exits risk peak exposure.
80-Week Profit End: Historical data identifies week 80 post-halving as high-probability peak zones. Must initiate position reduction here - don’t chase final rally segments.
135-Week DCA Activation: Statistical optimization for bear market bottom zones, offering optimal risk-adjusted entry timing for accumulation phases.
This strategy suits capital with 5+ year investment horizons, unsuitable for emergency funds or low risk tolerance profiles. Individual cycles require enduring 2-3 years of unrealized losses with massive psychological pressure.
Strategy edge isn’t short-term price prediction - it’s capturing long-term supply cycle dynamics. Bitcoin halving represents deterministic events, but price reaction timing and magnitude remain uncertain variables.
Critical Warning: This constitutes high-risk investment strategy with potential total capital loss. Historical backtesting doesn’t guarantee future performance. Thoroughly assess personal risk tolerance before implementation.
/*backtest
start: 2017-08-17 08:00:00
end: 2025-10-07 08:00:00
period: 1d
basePeriod: 1d
exchanges: [{"eid":"Binance","currency":"BTC_USDT","balance":500000}]
*/
//@version=6
strategy(title='Bitcoin Halving Cycle Profit - Backtesting', shorttitle='BTC Halv', overlay=true, default_qty_type=strategy.percent_of_equity, default_qty_value=100, initial_capital=10000, commission_type=strategy.commission.percent, commission_value=0.1)
// ════════════════════════════════════════════════════════════════════════════════════════════════
// CONFIGURATION & INPUTS
// ════════════════════════════════════════════════════════════════════════════════════════════════
// Backtesting Settings
enableBacktesting = input.bool(true, "Enable Backtesting", group="Backtesting Settings")
enableShortTrades = input.bool(true, "Enable Short Trades", group="Backtesting Settings")
positionSize = input.float(100, "Position Size (%)", minval=10, maxval=100, group="Backtesting Settings")
slippage = input.float(0.05, "Slippage (%)", minval=0, maxval=1, group="Backtesting Settings")
// Main Settings
showHalvingLines = input.bool(true, "Show Halving Lines", group="Display Options")
showProfitZones = input.bool(true, "Show Profit Zones", group="Display Options")
showBackgroundGradient = input.bool(true, "Show Background Gradient", group="Display Options")
showLabels = input.bool(true, "Show Labels", group="Display Options")
showDCAZone = input.bool(true, "Show DCA Zone", group="Display Options")
showInfoTable = input.bool(true, "Show Info Table", group="Display Options")
showTradeSignals = input.bool(true, "Show Trade Signals", group="Display Options")
// Table Settings
tablePosition = input.string("Top Right", "Table Position", options=["Top Left", "Top Right", "Bottom Left", "Bottom Right"], group="Table Settings")
tableSize = input.string("Normal", "Table Size", options=["Small", "Normal", "Large"], group="Table Settings")
tableTransparency = input.int(10, "Table Transparency", minval=0, maxval=50, group="Table Settings")
// Professional Dark Theme Color Scheme
colorHalving = input.color(color.new(#ff6b35, 0), "Halving Line Color", group="Colors")
colorProfitStart = input.color(color.new(#4ecdc4, 0), "Profit Start Color", group="Colors")
colorProfitEnd = input.color(color.new(#ff6b6b, 0), "Profit End Color", group="Colors")
colorDCA = input.color(color.new(#ffd93d, 0), "DCA Color", group="Colors")
colorBackground = input.color(color.new(#4ecdc4, 92), "Background Color", group="Colors")
// Timing Settings
profitStartWeeks = input.int(40, "Profit Start (Weeks)", minval=1, group="Timing")
profitEndWeeks = input.int(80, "Profit End (Weeks)", minval=1, group="Timing")
dcaStartWeeks = input.int(135, "DCA Start (Weeks)", minval=1, group="Timing")
// ════════════════════════════════════════════════════════════════════════════════════════════════
// HELPER FUNCTIONS
// ════════════════════════════════════════════════════════════════════════════════════════════════
// Get table position
getTablePosition() =>
switch tablePosition
"Top Left" => position.top_left
"Top Right" => position.top_right
"Bottom Left" => position.bottom_left
"Bottom Right" => position.bottom_right
=> position.top_right
// Get table text size
getTableTextSize() =>
switch tableSize
"Small" => size.tiny
"Normal" => size.small
"Large" => size.normal
=> size.small
// Get table header text size
getTableHeaderSize() =>
switch tableSize
"Small" => size.small
"Normal" => size.normal
"Large" => size.large
=> size.normal
// Calculate weeks from halving date
weeksFromHalving(halvingTimestamp) =>
(time - halvingTimestamp) / (7 * 24 * 60 * 60 * 1000)
// Check if current time is within profit zone
inProfitZone(halvingTimestamp) =>
weeks = weeksFromHalving(halvingTimestamp)
weeks >= profitStartWeeks and weeks <= profitEndWeeks
// Check if current time is within DCA zone
inDCAZone(halvingTimestamp) =>
weeks = weeksFromHalving(halvingTimestamp)
weeks >= dcaStartWeeks
// ════════════════════════════════════════════════════════════════════════════════════════════════
// HALVING DATES & DATA
// ════════════════════════════════════════════════════════════════════════════════════════════════
// Historical halving dates
halving1 = timestamp(2012, 11, 28)
halving2 = timestamp(2016, 7, 9)
halving3 = timestamp(2020, 5, 11)
halving4 = timestamp(2024, 4, 19)
// Store halving data
type HalvingData
float timestamp
string label
string emoji
color lineColor
halvings = array.new<HalvingData>()
array.push(halvings, HalvingData.new(halving1, "1st Halving\n2012", "⛏️", colorHalving))
array.push(halvings, HalvingData.new(halving2, "2nd Halving\n2016", "⛏️⛏️", colorHalving))
array.push(halvings, HalvingData.new(halving3, "3rd Halving\n2020", "⛏️⛏️⛏️", colorHalving))
array.push(halvings, HalvingData.new(halving4, "4th Halving\n2024", "⛏️⛏️⛏️⛏️", colorHalving))
// Get current cycle status
getCurrentCycleStatus() =>
var string result = "⏳ Pre-Halving Phase"
for i = array.size(halvings) - 1 to 0 by 1
halvingData = array.get(halvings, i)
if time >= halvingData.timestamp
weeks = weeksFromHalving(halvingData.timestamp)
if weeks <= profitStartWeeks
result := "🔶 Accumulation Phase"
break
else if weeks <= profitEndWeeks
result := "🟢 Profit Taking Phase"
break
else if weeks <= dcaStartWeeks
result := "⚠️ Bear Market Phase"
break
else
result := "🟡 DCA Phase"
break
result
// Get weeks until next phase
getWeeksUntilNextPhase() =>
var float result = na
for i = array.size(halvings) - 1 to 0 by 1
halvingData = array.get(halvings, i)
if time >= halvingData.timestamp
weeks = weeksFromHalving(halvingData.timestamp)
if weeks <= profitStartWeeks
result := profitStartWeeks - weeks
break
else if weeks <= profitEndWeeks
result := profitEndWeeks - weeks
break
else if weeks <= dcaStartWeeks
result := dcaStartWeeks - weeks
break
else
result := na
break
result
// Get next phase date
getNextPhaseDate() =>
var float result = na
for i = array.size(halvings) - 1 to 0 by 1
halvingData = array.get(halvings, i)
if time >= halvingData.timestamp
weeks = weeksFromHalving(halvingData.timestamp)
if weeks <= profitStartWeeks
result := halvingData.timestamp + (profitStartWeeks * 7 * 24 * 60 * 60 * 1000)
break
else if weeks <= profitEndWeeks
result := halvingData.timestamp + (profitEndWeeks * 7 * 24 * 60 * 60 * 1000)
break
else if weeks <= dcaStartWeeks
result := halvingData.timestamp + (dcaStartWeeks * 7 * 24 * 60 * 60 * 1000)
break
else
result := na
break
result
// Get current phase name
getCurrentPhaseName() =>
var string result = "Pre-Halving"
for i = array.size(halvings) - 1 to 0 by 1
halvingData = array.get(halvings, i)
if time >= halvingData.timestamp
weeks = weeksFromHalving(halvingData.timestamp)
if weeks <= profitStartWeeks
result := "Accumulation"
break
else if weeks <= profitEndWeeks
result := "Profit Taking"
break
else if weeks <= dcaStartWeeks
result := "Bear Market"
break
else
result := "DCA"
break
result
// Get next phase name
getNextPhaseName() =>
var string result = "Accumulation"
for i = array.size(halvings) - 1 to 0 by 1
halvingData = array.get(halvings, i)
if time >= halvingData.timestamp
weeks = weeksFromHalving(halvingData.timestamp)
if weeks <= profitStartWeeks
result := "Profit Taking"
break
else if weeks <= profitEndWeeks
result := "Bear Market"
break
else if weeks <= dcaStartWeeks
result := "DCA"
break
else
result := "Next Halving"
break
result
// Get phase countdown variables
getPhaseCountdown() =>
var float currentHalvingTimestamp = na
var float profitStartWeeksLeft = na
var float profitEndWeeksLeft = na
var float dcaStartWeeksLeft = na
var string profitStartDateText = "N/A"
var string profitEndDateText = "N/A"
var string dcaStartDateText = "N/A"
var string nextPhaseName = "N/A"
var string nextPhaseDateText = "N/A"
for i = array.size(halvings) - 1 to 0 by 1
halvingData = array.get(halvings, i)
if time >= halvingData.timestamp
currentHalvingTimestamp := halvingData.timestamp
weeks = weeksFromHalving(halvingData.timestamp)
// Calculate countdowns
profitStartWeeksLeft := profitStartWeeks - weeks
profitEndWeeksLeft := profitEndWeeks - weeks
dcaStartWeeksLeft := dcaStartWeeks - weeks
// Calculate dates
profitStartDate = halvingData.timestamp + (profitStartWeeks * 7 * 24 * 60 * 60 * 1000)
profitEndDate = halvingData.timestamp + (profitEndWeeks * 7 * 24 * 60 * 60 * 1000)
dcaStartDate = halvingData.timestamp + (dcaStartWeeks * 7 * 24 * 60 * 60 * 1000)
profitStartDateText := str.format("{0,date,yyyy-MM-dd}", profitStartDate)
profitEndDateText := str.format("{0,date,yyyy-MM-dd}", profitEndDate)
dcaStartDateText := str.format("{0,date,yyyy-MM-dd}", dcaStartDate)
// Get next phase
if weeks <= profitStartWeeks
nextPhaseName := "Profit Taking"
nextPhaseDateText := profitStartDateText
break
else if weeks <= profitEndWeeks
nextPhaseName := "Bear Market"
nextPhaseDateText := profitEndDateText
break
else if weeks <= dcaStartWeeks
nextPhaseName := "DCA"
nextPhaseDateText := dcaStartDateText
break
else
nextPhaseName := "Next Halving"
nextPhaseDateText := "N/A"
break
[profitStartWeeksLeft, profitEndWeeksLeft, dcaStartWeeksLeft, profitStartDateText, profitEndDateText, dcaStartDateText, nextPhaseName, nextPhaseDateText]
// ════════════════════════════════════════════════════════════════════════════════════════════════
// BACKTESTING LOGIC
// ════════════════════════════════════════════════════════════════════════════════════════════════
// Variables for tracking signals
var bool longSignal = false
var bool shortSignal = false
var bool buyAtHalving = false
var bool buyAtDCA = false
var bool sellAtProfitEnd = false
var bool shortAtProfitEnd = false
var bool coverAtDCA = false
// Reset signals
longSignal := false
shortSignal := false
buyAtHalving := false
buyAtDCA := false
sellAtProfitEnd := false
shortAtProfitEnd := false
coverAtDCA := false
// Check for buy signals (Halving and DCA zones)
for i = 0 to array.size(halvings) - 1
halvingData = array.get(halvings, i)
weeks = weeksFromHalving(halvingData.timestamp)
// Buy at halving (within 1 week of halving)
if math.abs(weeks) < 1 and weeks >= 0
buyAtHalving := true
longSignal := true
// Buy at DCA start
if math.abs(weeks - dcaStartWeeks) < 0.5
buyAtDCA := true
longSignal := true
// Sell at profit end
if math.abs(weeks - profitEndWeeks) < 0.5
sellAtProfitEnd := true
if enableShortTrades
shortAtProfitEnd := true
shortSignal := true
// Cover short at DCA (same time as long entry)
if math.abs(weeks - dcaStartWeeks) < 0.5 and enableShortTrades
coverAtDCA := true
// Execute trades
if enableBacktesting
// Long entries
if longSignal and (buyAtHalving or buyAtDCA)
strategy.close("SHORT", comment="Cover Short")
strategy.entry("LONG", strategy.long, qty=positionSize/100 * strategy.equity/close, comment=buyAtHalving ? "Buy at Halving" : "Buy at DCA")
// Long exit and short entry
if sellAtProfitEnd and strategy.position_size > 0
strategy.close("LONG", comment="Sell at Profit End")
if enableShortTrades and shortAtProfitEnd
strategy.entry("SHORT", strategy.short, qty=positionSize/100 * strategy.equity/close, comment="Short at Profit End")
// Short cover (already handled above with long entry)
// ════════════════════════════════════════════════════════════════════════════════════════════════
// VISUAL ELEMENTS
// ════════════════════════════════════════════════════════════════════════════════════════════════
// Trade signals visualization
if showTradeSignals
if longSignal and buyAtHalving
label.new(bar_index, low, "🟢 BUY\nHALVING", style=label.style_label_up, color=color.new(color.green, 0), textcolor=color.white, size=size.normal)
if longSignal and buyAtDCA
label.new(bar_index, low, "🟢 BUY\nDCA", style=label.style_label_up, color=color.new(color.green, 0), textcolor=color.white, size=size.normal)
if sellAtProfitEnd
label.new(bar_index, high, "🔴 SELL\nPROFIT END", style=label.style_label_down, color=color.new(color.red, 0), textcolor=color.white, size=size.normal)
if shortAtProfitEnd and enableShortTrades
label.new(bar_index, high, "🔴 SHORT\nPROFIT END", style=label.style_label_down, color=color.new(color.orange, 0), textcolor=color.white, size=size.normal)
// Background gradient for profit zones
var bool showBgGradient = false
if showBackgroundGradient
for i = 0 to array.size(halvings) - 1
halvingData = array.get(halvings, i)
if inProfitZone(halvingData.timestamp)
showBgGradient := true
break
else
showBgGradient := false
bgcolor(showBackgroundGradient and showBgGradient ? colorBackground : na)
// ════════════════════════════════════════════════════════════════════════════════════════════════
// PROFESSIONAL DARK THEME TABLE - ALWAYS VISIBLE
// ════════════════════════════════════════════════════════════════════════════════════════════════
// Get position variables
var string currentPosition = "FLAT"
var color positionColor = color.new(#cccccc, 0)
var string positionEmoji = "⚪"
// Update position variables
currentPosition := strategy.position_size > 0 ? "LONG" : strategy.position_size < 0 ? "SHORT" : "FLAT"
positionColor := strategy.position_size > 0 ? color.new(#00ff88, 0) : strategy.position_size < 0 ? color.new(#ff4444, 0) : color.new(#cccccc, 0)
positionEmoji := strategy.position_size > 0 ? "🟢" : strategy.position_size < 0 ? "🔴" : "⚪"
// Get phase countdown data
[profitStartWeeksLeft, profitEndWeeksLeft, dcaStartWeeksLeft, profitStartDateText, profitEndDateText, dcaStartDateText, nextPhaseName, nextPhaseDateText] = getPhaseCountdown()
// ════════════════════════════════════════════════════════════════════════════════════════════════
// ALERTS
// ════════════════════════════════════════════════════════════════════════════════════════════════
// Enhanced alerts with trade signals
for i = 0 to array.size(halvings) - 1
halvingData = array.get(halvings, i)
weeks = weeksFromHalving(halvingData.timestamp)
if math.abs(weeks) < 0.1 and weeks >= 0
alert("🟢 Bitcoin Halving Cycle: BUY SIGNAL at halving event!", alert.freq_once_per_bar)
if math.abs(weeks - profitEndWeeks) < 0.1
alert("🔴 Bitcoin Halving Cycle: SELL SIGNAL - Last call for profit taking! (" + str.tostring(profitEndWeeks) + " weeks post-halving)", alert.freq_once_per_bar)
if math.abs(weeks - dcaStartWeeks) < 0.1
alert("🟡 Bitcoin Halving Cycle: BUY SIGNAL - DCA accumulation phase has begun! (" + str.tostring(dcaStartWeeks) + " weeks post-halving)", alert.freq_once_per_bar)