/* * The MIT License (MIT) * * Copyright (c) 2017-2025 Ta4j Organization & respective * authors (see AUTHORS) * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in * the Software without restriction, including without limitation the rights to * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of * the Software, and to permit persons to whom the Software is furnished to do so, * subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ package ta4jexamples.backtesting; import java.awt.GraphicsEnvironment; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.jfree.chart.JFreeChart; import org.ta4j.core.AnalysisCriterion; import org.ta4j.core.BarSeries; import org.ta4j.core.BaseStrategy; import org.ta4j.core.Rule; import org.ta4j.core.Strategy; import org.ta4j.core.TradingRecord; import org.ta4j.core.backtest.BarSeriesManager; import org.ta4j.core.criteria.LinearTransactionCostCriterion; import org.ta4j.core.num.Num; import org.ta4j.core.criteria.ValueAtRiskCriterion; import org.ta4j.core.criteria.pnl.GrossProfitLossCriterion; import org.ta4j.core.criteria.pnl.GrossReturnCriterion; import org.ta4j.core.criteria.pnl.NetProfitLossCriterion; import org.ta4j.core.criteria.pnl.NetReturnCriterion; import org.ta4j.core.criteria.pnl.NetAverageProfitCriterion; import org.ta4j.core.criteria.pnl.NetAverageLossCriterion; import org.ta4j.core.criteria.pnl.MaxConsecutiveLossCriterion; import org.ta4j.core.criteria.pnl.MaxConsecutiveProfitCriterion; import org.ta4j.core.indicators.MACDIndicator; import org.ta4j.core.indicators.averages.EMAIndicator; import org.ta4j.core.indicators.helpers.ClosePriceIndicator; import org.ta4j.core.indicators.keltner.KeltnerChannelFacade; import org.ta4j.core.indicators.volume.MoneyFlowIndexIndicator; import org.ta4j.core.indicators.volume.VWAPIndicator; import org.ta4j.core.rules.CrossedDownIndicatorRule; import org.ta4j.core.rules.CrossedUpIndicatorRule; import org.ta4j.core.rules.OverIndicatorRule; import org.ta4j.core.rules.StopGainRule; import org.ta4j.core.rules.TrailingStopLossRule; import org.ta4j.core.rules.UnderIndicatorRule; import ta4jexamples.charting.workflow.ChartWorkflow; import ta4jexamples.datasources.CoinbaseHttpBarSeriesDataSource; /** * Coinbase Data Source Backtest - Advanced Risk Management & Transaction Costs *

* This example demonstrates advanced ta4j features focused on real-world * trading considerations: *

*

* Strategy Concept: A trend-following strategy using MACD * crossovers with Keltner Channel confirmation, VWAP for entry timing, and * sophisticated risk management with trailing stops and take-profit targets. *

* Data Source: This example uses Coinbase's public market data * API to fetch real cryptocurrency data. No API key is required, but be aware * of rate limits (350 candles per request, automatically paginated). *

* Key Learning: This example emphasizes the critical * importance of transaction costs in real trading. Notice how gross returns * differ significantly from net returns after accounting for fees! */ public class CoinbaseBacktest { private static final Logger LOG = LogManager.getLogger(CoinbaseBacktest.class); public static void main(String[] args) { System.out.println("╔══════════════════════════════════════════════════════════════╗"); System.out.println("║ Coinbase Data Source - Advanced Risk Management ║"); System.out.println("╚══════════════════════════════════════════════════════════════╝"); System.out.println(); // Step 1: Load historical price data from Coinbase System.out.println("[1/8] Loading historical price data from Coinbase..."); System.out.println(" Fetching 1 year of daily data for Bitcoin (BTC-USD)..."); System.out.println(" (Crypto markets are 24/7 - perfect for trend strategies)"); // Load 1 year of daily data CoinbaseHttpBarSeriesDataSource dataSource = new CoinbaseHttpBarSeriesDataSource(true); BarSeries series = dataSource.loadSeriesInstance("BTC-USD", CoinbaseHttpBarSeriesDataSource.CoinbaseInterval.ONE_DAY, 365); // Alternative methods you can try: // BarSeries series = dataSource.loadSeriesInstance("ETH-USD", // CoinbaseInterval.ONE_DAY, 500); // 500 bars // BarSeries series = dataSource.loadSeriesInstance("BTC-USD", // CoinbaseInterval.FOUR_HOUR, 1000); // 4-hour data // BarSeries series = dataSource.loadSeriesInstance("ETH-USD", // CoinbaseInterval.ONE_DAY, // Instant.parse("2023-01-01T00:00:00Z"), // Instant.parse("2023-12-31T23:59:59Z")); // Date range if (series == null || series.getBarCount() == 0) { System.err.println(" [ERROR] Failed to load data from Coinbase"); System.err.println(" [TIP] Check your internet connection and try again"); System.err.println(" [TIP] Coinbase API may have rate limits - wait a few minutes and retry"); return; } System.out.printf(" [OK] Loaded %d bars of price data%n", series.getBarCount()); System.out.printf(" [INFO] Date range: %s to %s%n", series.getFirstBar().getEndTime(), series.getLastBar().getEndTime()); System.out.println(); // Step 2: Create advanced indicators System.out.println("[2/8] Creating advanced technical indicators..."); ClosePriceIndicator closePrice = new ClosePriceIndicator(series); // MACD: Trend-following momentum indicator // MACD = 12-period EMA - 26-period EMA // Signal = 9-period EMA of MACD // Histogram = MACD - Signal MACDIndicator macd = new MACDIndicator(closePrice, 12, 26); EMAIndicator macdSignal = macd.getSignalLine(9); // Histogram: difference between MACD and signal line // Positive histogram = bullish momentum, negative = bearish org.ta4j.core.indicators.numeric.NumericIndicator macdHistogram = macd.getHistogram(9); // Keltner Channels: Volatility-based channels using ATR // Similar to Bollinger Bands but uses ATR instead of standard deviation // More responsive to volatility changes KeltnerChannelFacade keltner = new KeltnerChannelFacade(series, 20, 10, 2.0); // keltner.middle() = 20-period EMA // keltner.upper() = middle + (2.0 * ATR) // keltner.lower() = middle - (2.0 * ATR) // VWAP: Volume-Weighted Average Price // Critical for crypto trading - shows institutional price levels // Often acts as support/resistance // Using all available bars for VWAP calculation VWAPIndicator vwap = new VWAPIndicator(series, series.getBarCount()); // Money Flow Index: Volume-weighted RSI (0-100) // > 80 = overbought, < 20 = oversold // More reliable than RSI because it includes volume MoneyFlowIndexIndicator mfi = new MoneyFlowIndexIndicator(series, 14); System.out.println(" [OK] Created MACD (12, 26, 9) with signal line and histogram"); System.out.println(" [OK] Created Keltner Channels (20 EMA, 10 ATR, 2.0 multiplier)"); System.out.println(" [OK] Created VWAP (Volume-Weighted Average Price)"); System.out.println(" [OK] Created Money Flow Index (14-period)"); System.out.println(); // Step 3: Build trend-following strategy with risk management System.out.println("[3/8] Building trend-following strategy with advanced risk management..."); System.out.println(" Strategy: MACD crossover with Keltner Channel confirmation"); // Entry rule: Buy when MACD crosses above signal line (bullish crossover) // AND price is above VWAP (institutional support) // AND price is above Keltner middle (uptrend) // AND MFI is not overbought (< 80) Rule macdBullishCrossover = new CrossedUpIndicatorRule(macd, macdSignal); Rule priceAboveVWAP = new OverIndicatorRule(closePrice, vwap); Rule priceAboveKeltnerMiddle = new OverIndicatorRule(closePrice, keltner.middle()); Rule mfiNotOverbought = new UnderIndicatorRule(mfi, series.numFactory().numOf(80)); Rule buyingRule = macdBullishCrossover.and(priceAboveVWAP).and(priceAboveKeltnerMiddle).and(mfiNotOverbought); // Exit rule: Sell when MACD crosses below signal line (bearish crossover) // OR trailing stop loss triggers (protects profits) // OR stop gain triggers (take profit at 15%) // OR price falls below Keltner lower band (breakdown) Rule macdBearishCrossover = new CrossedDownIndicatorRule(macd, macdSignal); // Trailing stop: 5% trailing stop loss (follows price upward) // This protects profits by moving the stop loss up as price rises TrailingStopLossRule trailingStop = new TrailingStopLossRule(closePrice, series.numFactory().numOf(5.0)); // Stop gain: Take profit at 15% gain StopGainRule stopGain = new StopGainRule(closePrice, series.numFactory().numOf(15.0)); Rule priceBelowKeltnerLower = new UnderIndicatorRule(closePrice, keltner.lower()); Rule sellingRule = macdBearishCrossover.or(trailingStop).or(stopGain).or(priceBelowKeltnerLower); Strategy strategy = new BaseStrategy("MACD Trend Following (Risk Managed)", buyingRule, sellingRule); System.out.println(" [OK] Entry: MACD bullish crossover + Price > VWAP + Price > Keltner middle + MFI < 80"); System.out.println( " [OK] Exit: MACD bearish crossover OR 5% trailing stop OR 15% stop gain OR Price < Keltner lower"); System.out.println(); // Step 4: Run backtest System.out.println("[4/8] Running backtest on historical data..."); BarSeriesManager seriesManager = new BarSeriesManager(series); TradingRecord tradingRecord = seriesManager.run(strategy); System.out.printf(" [OK] Backtest complete: %d positions executed%n", tradingRecord.getPositionCount()); System.out.println(); // Step 5: Performance analysis with transaction costs System.out.println("[5/8] Performance Analysis (WITH Transaction Costs)"); System.out.println(" ──────────────────────────────────────────"); // Transaction cost parameters (typical for crypto exchanges) // Coinbase Advanced Trade: 0.4% maker fee, 0.6% taker fee // Using 0.5% average (0.005) per trade (entry + exit = 1% total per round trip) double initialAmount = 10000.0; // $10,000 starting capital double feePercentage = 0.005; // 0.5% per trade // Gross metrics (before transaction costs) AnalysisCriterion grossReturn = new GrossReturnCriterion(); AnalysisCriterion grossProfitLoss = new GrossProfitLossCriterion(); Num grossReturnValue = grossReturn.calculate(series, tradingRecord); Num grossProfitLossValue = grossProfitLoss.calculate(series, tradingRecord); // Net metrics (after transaction costs) AnalysisCriterion netReturn = new NetReturnCriterion(); AnalysisCriterion netProfitLoss = new NetProfitLossCriterion(); Num netReturnValue = netReturn.calculate(series, tradingRecord); Num netProfitLossValue = netProfitLoss.calculate(series, tradingRecord); // Transaction costs LinearTransactionCostCriterion transactionCosts = new LinearTransactionCostCriterion(initialAmount, feePercentage); Num totalCosts = transactionCosts.calculate(series, tradingRecord); // Average profit/loss per trade AnalysisCriterion avgProfit = new NetAverageProfitCriterion(); AnalysisCriterion avgLoss = new NetAverageLossCriterion(); Num avgProfitValue = avgProfit.calculate(series, tradingRecord); Num avgLossValue = avgLoss.calculate(series, tradingRecord); // Consecutive streaks AnalysisCriterion maxConsecutiveProfit = new MaxConsecutiveProfitCriterion(); AnalysisCriterion maxConsecutiveLoss = new MaxConsecutiveLossCriterion(); Num maxConsecutiveProfitValue = maxConsecutiveProfit.calculate(series, tradingRecord); Num maxConsecutiveLossValue = maxConsecutiveLoss.calculate(series, tradingRecord); // Risk metrics ValueAtRiskCriterion var95 = new ValueAtRiskCriterion(0.95); // 95% confidence level Num var95Value = var95.calculate(series, tradingRecord); // Display comprehensive results System.out.println(" Gross Performance (Before Fees):"); System.out.printf(" Gross Return: %.2f%%%n", grossReturnValue.multipliedBy(series.numFactory().numOf(100)).doubleValue()); System.out.printf(" Gross Profit/Loss: $%.2f%n", grossProfitLossValue.doubleValue()); System.out.println(); System.out.println(" Transaction Costs:"); System.out.printf(" Total Fees Paid: $%.2f (%.2f%% of initial capital)%n", totalCosts.doubleValue(), totalCosts.dividedBy(series.numFactory().numOf(initialAmount)) .multipliedBy(series.numFactory().numOf(100)) .doubleValue()); System.out.printf(" Fee per Trade: %.2f%% (entry + exit)%n", feePercentage * 200); System.out.println(); System.out.println(" Net Performance (After Fees):"); System.out.printf(" Net Return: %.2f%%%n", netReturnValue.multipliedBy(series.numFactory().numOf(100)).doubleValue()); System.out.printf(" Net Profit/Loss: $%.2f%n", netProfitLossValue.doubleValue()); System.out.printf(" Impact of Fees: %.2f%% (difference between gross and net)%n", grossReturnValue.minus(netReturnValue).multipliedBy(series.numFactory().numOf(100)).doubleValue()); System.out.println(); System.out.println(" Trade Statistics:"); System.out.printf(" Average Profit: $%.2f%n", avgProfitValue.doubleValue()); System.out.printf(" Average Loss: $%.2f%n", avgLossValue.doubleValue()); System.out.printf(" Max Consecutive Wins: %d%n", maxConsecutiveProfitValue.intValue()); System.out.printf(" Max Consecutive Losses: %d%n", maxConsecutiveLossValue.intValue()); System.out.println(); System.out.println(" Risk Metrics:"); System.out.printf(" Value at Risk (95%%): %.2f%% (worst expected loss)%n", var95Value.multipliedBy(series.numFactory().numOf(100)).doubleValue()); System.out.println(); // Step 6: Compare strategies (with and without trailing stop) System.out.println("[6/8] Strategy Comparison: With vs Without Trailing Stop"); System.out.println(" ──────────────────────────────────────────"); // Strategy without trailing stop (only MACD crossover) Rule simpleBuyingRule = macdBullishCrossover.and(priceAboveVWAP); Rule simpleSellingRule = macdBearishCrossover.or(stopGain).or(priceBelowKeltnerLower); Strategy simpleStrategy = new BaseStrategy("MACD Simple (No Trailing Stop)", simpleBuyingRule, simpleSellingRule); TradingRecord simpleRecord = seriesManager.run(simpleStrategy); Num simpleNetReturn = netReturn.calculate(series, simpleRecord); Num strategyNetReturn = netReturn.calculate(series, tradingRecord); System.out.printf(" Simple Strategy (no trailing stop): %.2f%% net return%n", simpleNetReturn.multipliedBy(series.numFactory().numOf(100)).doubleValue()); System.out.printf(" Risk-Managed Strategy (with trailing stop): %.2f%% net return%n", strategyNetReturn.multipliedBy(series.numFactory().numOf(100)).doubleValue()); System.out.printf(" Difference: %.2f%%%n", strategyNetReturn.minus(simpleNetReturn).multipliedBy(series.numFactory().numOf(100)).doubleValue()); System.out.println(); // Step 7: Visualize the strategy System.out.println("[7/8] Generating comprehensive strategy visualization..."); boolean isHeadless = GraphicsEnvironment.isHeadless(); if (isHeadless) { System.out.println(" [WARN] Headless environment detected - skipping chart display"); System.out.println(" [TIP] Run in a GUI environment to see interactive charts!"); } else { try { ChartWorkflow chartWorkflow = new ChartWorkflow(); JFreeChart chart = chartWorkflow.builder() .withTitle("MACD Trend Following Strategy - Coinbase Data (BTC-USD)") .withSeries(series) // Price bars (candlesticks) .withTradingRecordOverlay(tradingRecord) // Trading positions marked on price chart .withIndicatorOverlay(keltner.middle()) // Keltner middle band .withIndicatorOverlay(keltner.upper()) // Keltner upper band .withIndicatorOverlay(keltner.lower()) // Keltner lower band .withIndicatorOverlay(vwap) // VWAP overlay .withSubChart(macd) // MACD in first subchart .withIndicatorOverlay(macdSignal) // MACD signal line .withSubChart(macdHistogram) // MACD histogram in second subchart .withSubChart(mfi) // Money Flow Index in third subchart .withSubChart(new NetProfitLossCriterion(), tradingRecord) // Net P&L in fourth subchart .toChart(); chartWorkflow.displayChart(chart, "ta4j Coinbase Backtest - MACD Trend Following with Risk Management"); System.out.println(" [OK] Multi-subchart displayed in new window"); System.out.println(" [TIP] Chart shows: Price with Keltner Channels & VWAP, MACD, MFI, and P&L"); } catch (Exception ex) { LOG.warn("Failed to display chart: {}", ex.getMessage(), ex); System.out.println(" [WARN] Could not display chart: " + ex.getMessage()); } } System.out.println(); // Step 8: Explain advanced concepts System.out.println("[8/8] Advanced Concepts Demonstrated"); System.out.println(" ──────────────────────────────────────────"); System.out.println(" ✓ MACD: Trend-following momentum indicator"); System.out.println(" - Bullish crossover: MACD crosses above signal line"); System.out.println(" - Histogram shows momentum strength"); System.out.println(); System.out.println(" ✓ Keltner Channels: Volatility-based price channels"); System.out.println(" - Uses ATR instead of standard deviation (more responsive)"); System.out.println(" - Upper/lower bands adapt to market volatility"); System.out.println(); System.out.println(" ✓ VWAP: Volume-Weighted Average Price"); System.out.println(" - Critical for crypto trading (institutional levels)"); System.out.println(" - Price above VWAP = bullish, below = bearish"); System.out.println(); System.out.println(" ✓ Trailing Stop Loss: Dynamic risk management"); System.out.println(" - Follows price upward, protecting profits"); System.out.println(" - More effective than fixed stop loss in trending markets"); System.out.println(); System.out.println(" ✓ Stop Gain: Take-profit targets"); System.out.println(" - Locks in profits at predetermined levels"); System.out.println(" - Prevents giving back gains in volatile markets"); System.out.println(); System.out.println(" ✓ Transaction Costs: Real-world impact"); System.out.println(" - Gross returns vs Net returns (after fees)"); System.out.println(" - Fees can significantly impact profitability"); System.out.println(" - Always account for costs in backtesting!"); System.out.println(); System.out.println(" ✓ Value at Risk (VaR): Risk quantification"); System.out.println(" - Measures worst expected loss at confidence level"); System.out.println(" - Helps understand downside risk"); System.out.println(); System.out.println(" ✓ Gross vs Net Metrics: Understanding the difference"); System.out.println(" - Gross: Before transaction costs"); System.out.println(" - Net: After transaction costs (real-world)"); System.out.println(" - Always use net metrics for real trading decisions"); System.out.println(); // Summary System.out.println("╔══════════════════════════════════════════════════════════════╗"); System.out.println("║ Summary ║"); System.out.println("╚══════════════════════════════════════════════════════════════╝"); System.out.println(); System.out.println("What just happened?"); System.out.println(); System.out.println(" 1. Loaded 1 year of daily OHLCV data for BTC-USD from Coinbase"); System.out.println(" 2. Created advanced indicators:"); System.out.println(" - MACD with signal line and histogram (trend momentum)"); System.out.println(" - Keltner Channels (volatility-based channels)"); System.out.println(" - VWAP (institutional price levels)"); System.out.println(" - Money Flow Index (volume-weighted momentum)"); System.out.println(" 3. Built a trend-following strategy with risk management:"); System.out.println(" - Entry: MACD bullish crossover + Price > VWAP + Price > Keltner middle + MFI < 80"); System.out.println( " - Exit: MACD bearish crossover OR 5% trailing stop OR 15% stop gain OR Price < Keltner lower"); System.out.println(" 4. Backtested with transaction costs (0.5% per trade)"); System.out.println(" 5. Analyzed gross vs net performance (impact of fees)"); System.out.println(" 6. Compared strategies (with/without trailing stop)"); System.out.println(" 7. Calculated risk metrics (Value at Risk)"); if (!isHeadless) { System.out.println(" 8. Visualized with multi-subchart (Price, MACD, MFI, P&L)"); } System.out.println(); System.out.println("Advanced Features Demonstrated:"); System.out.println(" ✓ MACD with signal line and histogram"); System.out.println(" ✓ Keltner Channels (ATR-based volatility bands)"); System.out.println(" ✓ VWAP for institutional price levels"); System.out.println(" ✓ Money Flow Index (volume-weighted RSI)"); System.out.println(" ✓ Trailing Stop Loss (dynamic profit protection)"); System.out.println(" ✓ Stop Gain (take-profit targets)"); System.out.println(" ✓ Transaction cost analysis (real-world impact)"); System.out.println(" ✓ Value at Risk (risk quantification)"); System.out.println(" ✓ Gross vs Net metrics comparison"); System.out.println(" ✓ Multiple strategy comparison"); System.out.println(); System.out.println("Coinbase Data Source Features:"); System.out.println(" - Load data by number of days: loadSeries(\"BTC-USD\", 365)"); System.out.println(" - Load data by bar count: loadSeries(\"BTC-USD\", ONE_DAY, 500)"); System.out.println(" - Load data by date range: loadSeries(\"BTC-USD\", ONE_DAY, start, end)"); System.out.println(" - Supports multiple intervals: 1m, 5m, 15m, 30m, 1h, 2h, 4h, 6h, 1d"); System.out.println(" - Works with all Coinbase trading pairs (BTC-USD, ETH-USD, etc.)"); System.out.println(" - Automatic pagination for large date ranges (350 candles per request)"); System.out.println(); System.out.println("Key Takeaways:"); System.out.println(" • Transaction costs matter! Always use net returns for decisions."); System.out.println(" • Trailing stops protect profits better than fixed stops."); System.out.println(" • VWAP is critical for crypto trading (institutional levels)."); System.out.println(" • Risk metrics (VaR) help quantify downside risk."); System.out.println(" • Gross vs Net metrics show the real impact of fees."); System.out.println(); System.out.println("Next Steps - Experiment with:"); System.out.println(" - Different cryptocurrencies: \"ETH-USD\", \"SOL-USD\", \"ADA-USD\""); System.out.println(" - Adjust MACD periods (try 8/21 or 19/39)"); System.out.println(" - Modify trailing stop percentage (try 3% or 7%)"); System.out.println(" - Change stop gain targets (try 10% or 20%)"); System.out.println(" - Test different fee structures (0.1%, 0.25%, 1.0%)"); System.out.println(" - Try different intervals: FOUR_HOUR, SIX_HOUR for shorter timeframes"); System.out.println(" - Explore other examples in ta4j-examples"); System.out.println(" - Check out the wiki: https://ta4j.github.io/ta4j-wiki/"); System.out.println(); System.out.println("Your turn! Modify this code and see how transaction costs affect profitability."); System.out.println(); } }