forked from ta4j/ta4j
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathYahooFinanceBacktest.java
More file actions
365 lines (332 loc) · 22.1 KB
/
Copy pathYahooFinanceBacktest.java
File metadata and controls
365 lines (332 loc) · 22.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
/*
* SPDX-License-Identifier: MIT
*/
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.AnalysisCriterion.PositionFilter;
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.ExpectancyCriterion;
import org.ta4j.core.criteria.PositionsRatioCriterion;
import org.ta4j.core.criteria.SqnCriterion;
import org.ta4j.core.criteria.drawdown.MaximumDrawdownCriterion;
import org.ta4j.core.criteria.drawdown.ReturnOverMaxDrawdownCriterion;
import org.ta4j.core.criteria.VersusEnterAndHoldCriterion;
import org.ta4j.core.criteria.pnl.NetProfitLossCriterion;
import org.ta4j.core.criteria.pnl.NetReturnCriterion;
import org.ta4j.core.indicators.RSIIndicator;
import org.ta4j.core.indicators.UltimateOscillatorIndicator;
import org.ta4j.core.indicators.VortexIndicator;
import org.ta4j.core.indicators.averages.SMAIndicator;
import org.ta4j.core.indicators.bollinger.BollingerBandsLowerIndicator;
import org.ta4j.core.indicators.bollinger.BollingerBandsMiddleIndicator;
import org.ta4j.core.indicators.bollinger.BollingerBandsUpperIndicator;
import org.ta4j.core.indicators.helpers.ClosePriceIndicator;
import org.ta4j.core.indicators.helpers.PreviousValueIndicator;
import org.ta4j.core.indicators.statistics.StandardDeviationIndicator;
import org.ta4j.core.indicators.volume.OnBalanceVolumeIndicator;
import org.ta4j.core.num.Num;
import org.ta4j.core.rules.AverageTrueRangeStopLossRule;
import org.ta4j.core.rules.CrossedDownIndicatorRule;
import org.ta4j.core.rules.CrossedUpIndicatorRule;
import org.ta4j.core.rules.OverIndicatorRule;
import org.ta4j.core.rules.UnderIndicatorRule;
import ta4jexamples.charting.workflow.ChartWorkflow;
import ta4jexamples.datasources.YahooFinanceHttpBarSeriesDataSource;
/**
* Yahoo Finance Data Source Backtest - Advanced Multi-Indicator Strategy
* <p>
* This example demonstrates advanced ta4j features beyond the Quickstart:
* <ul>
* <li>Loading historical OHLCV data from Yahoo Finance API</li>
* <li>Bollinger Bands for mean reversion signals</li>
* <li>ATR-based dynamic stop-loss (adapts to market volatility)</li>
* <li>RSI for momentum confirmation</li>
* <li>Volume analysis with On-Balance Volume (OBV)</li>
* <li>Trend confirmation with Vortex and Ultimate Oscillator</li>
* <li>Indicator composition using BinaryOperationIndicator</li>
* <li>Advanced performance metrics (Expectancy, SQN, Maximum Drawdown)</li>
* <li>Multi-subchart visualization</li>
* </ul>
* <p>
* <strong>Strategy Concept:</strong> A mean reversion strategy that buys when
* price touches the lower Bollinger Band (oversold) with RSI confirmation and
* volume support, using ATR-based stops that adapt to market volatility.
* <p>
* <strong>Data Source:</strong> This example uses Yahoo Finance's public API to
* fetch real market data. No API key is required, but be aware of rate limits
* (~2000 requests/hour per IP).
* <p>
* Run this example to see an advanced trading strategy backtested on real
* market data with comprehensive analysis!
*/
public class YahooFinanceBacktest {
private static final Logger LOG = LogManager.getLogger(YahooFinanceBacktest.class);
public static void main(String[] args) {
System.out.println("╔══════════════════════════════════════════════════════════════╗");
System.out.println("║ Yahoo Finance Data Source - Backtesting Example ║");
System.out.println("╚══════════════════════════════════════════════════════════════╝");
System.out.println();
// Step 1: Load historical price data from Yahoo Finance
System.out.println("[1/7] Loading historical price data from Yahoo Finance...");
System.out.println(" Fetching 2 years of daily data for Apple Inc. (AAPL)...");
System.out.println(" (More data = better indicator calculations)");
// Load 2 years of data for better indicator stability
YahooFinanceHttpBarSeriesDataSource dataSource = new YahooFinanceHttpBarSeriesDataSource(true);
BarSeries series = dataSource.loadSeriesInstance("AAPL",
YahooFinanceHttpBarSeriesDataSource.YahooFinanceInterval.DAY_1, 730);
// Alternative methods you can try:
// BarSeries series = dataSource.loadSeriesInstance("AAPL",
// YahooFinanceInterval.DAY_1, 500); // 500 bars
// BarSeries series = dataSource.loadSeriesInstance("MSFT",
// YahooFinanceInterval.HOUR_1, 1000); // Hourly data
// BarSeries series = dataSource.loadSeriesInstance("BTC-USD",
// YahooFinanceInterval.DAY_1,
// 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 Yahoo Finance");
System.err.println(" [TIP] Check your internet connection and try again");
System.err.println(" [TIP] Yahoo Finance 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 base indicators
System.out.println("[2/7] Creating technical indicators...");
ClosePriceIndicator closePrice = new ClosePriceIndicator(series);
// Bollinger Bands: Mean reversion indicator
// Uses 20-period SMA with 2 standard deviations
int bbPeriod = 20;
double bbMultiplier = 2.0;
SMAIndicator bbSma = new SMAIndicator(closePrice, bbPeriod);
StandardDeviationIndicator bbStdDev = new StandardDeviationIndicator(closePrice, bbPeriod);
BollingerBandsMiddleIndicator bbMiddle = new BollingerBandsMiddleIndicator(bbSma);
BollingerBandsUpperIndicator bbUpper = new BollingerBandsUpperIndicator(bbMiddle, bbStdDev,
series.numFactory().numOf(bbMultiplier));
BollingerBandsLowerIndicator bbLower = new BollingerBandsLowerIndicator(bbMiddle, bbStdDev,
series.numFactory().numOf(bbMultiplier));
// RSI: Momentum oscillator (14-period)
RSIIndicator rsi = new RSIIndicator(closePrice, 14);
// On-Balance Volume: Volume-based trend indicator
OnBalanceVolumeIndicator obv = new OnBalanceVolumeIndicator(series);
// Trend confirmation indicators
VortexIndicator vortex = new VortexIndicator(series, 14);
UltimateOscillatorIndicator ultimateOscillator = new UltimateOscillatorIndicator(series);
// Note: ATR is used in the AverageTrueRangeStopLossRule below
// Advanced: You can also create custom indicators using
// BinaryOperationIndicator
// Example: Calculate distance from price to middle band as a percentage
// BinaryOperationIndicator priceToMiddleRatio =
// BinaryOperationIndicator.quotient(
// BinaryOperationIndicator.difference(closePrice, bbMiddle), bbMiddle);
System.out.println(" [OK] Created Bollinger Bands (20-period, 2 std dev)");
System.out.println(" [OK] Created RSI (14-period)");
System.out.println(" [OK] Created ATR (14-period) for dynamic stops");
System.out.println(" [OK] Created On-Balance Volume indicator");
System.out.println(" [OK] Created Vortex oscillator (14-period) for trend direction");
System.out.println(" [OK] Created Ultimate Oscillator (7/14/28) for trend strength");
System.out.println(" [OK] Created custom price-to-middle-band ratio indicator");
System.out.println();
// Step 3: Build advanced trading rules
System.out.println("[3/7] Building advanced trading strategy rules...");
System.out.println(" Strategy: Mean reversion with multiple confirmations");
// Entry rule: Buy when price is at or below lower Bollinger Band (oversold)
// AND RSI is below 45 (oversold confirmation - less strict than 40)
// Price touching lower BB OR crossing below it
Rule priceAtLowerBB = new UnderIndicatorRule(closePrice, bbLower)
.or(new CrossedDownIndicatorRule(closePrice, bbLower));
Rule rsiOversold = new UnderIndicatorRule(rsi, series.numFactory().numOf(45));
// Optional: OBV rising provides additional confirmation (but not required)
// This makes the strategy more tradeable while still using volume analysis
Rule obvRising = new OverIndicatorRule(obv, new PreviousValueIndicator(obv, 1));
Rule vortexBullish = new OverIndicatorRule(vortex, series.numFactory().zero());
Rule ultimateBullish = new OverIndicatorRule(ultimateOscillator, series.numFactory().numOf(50));
// Entry: Price at lower BB + RSI oversold + (OBV rising OR price below middle
// band) + trend confirmation from Vortex and Ultimate Oscillator
// This allows entries when either volume confirms OR price is clearly oversold
Rule priceBelowMiddle = new UnderIndicatorRule(closePrice, bbMiddle);
Rule buyingRule = priceAtLowerBB.and(rsiOversold)
.and(obvRising.or(priceBelowMiddle))
.and(vortexBullish)
.and(ultimateBullish);
// Exit rule: Sell when price reaches upper Bollinger Band (overbought)
// OR RSI crosses above 65 (overbought - less strict than 70 for more exits)
// OR ATR-based stop loss triggers (dynamic, adapts to volatility)
// OR Vortex turns bearish
Rule exitCondition1 = new CrossedUpIndicatorRule(closePrice, bbUpper)
.or(new OverIndicatorRule(closePrice, bbUpper));
Rule exitCondition2 = new OverIndicatorRule(rsi, series.numFactory().numOf(65));
// ATR-based stop: 2.5x ATR below entry price (allows for some volatility)
Rule exitCondition3 = new AverageTrueRangeStopLossRule(series, 14, 2.5);
Rule exitCondition4 = new UnderIndicatorRule(vortex, series.numFactory().zero());
Rule sellingRule = exitCondition1.or(exitCondition2).or(exitCondition3).or(exitCondition4);
Strategy strategy = new BaseStrategy("Bollinger Bands Mean Reversion (Trend-Confirmed)", buyingRule,
sellingRule);
System.out.println(
" [OK] Entry: Price at/below lower BB + RSI < 45 + (OBV rising OR price below middle) + Vortex > 0 + Ultimate > 50");
System.out.println(" [OK] Exit: Price at/above upper BB OR RSI > 65 OR ATR stop (2.5x ATR) OR Vortex < 0");
System.out.println();
// Step 4: Run backtest
System.out.println("[4/7] 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: Advanced performance analysis
System.out.println("[5/7] Advanced Performance Analysis");
System.out.println(" ──────────────────────────────────────────");
// Basic metrics
AnalysisCriterion netReturn = new NetReturnCriterion();
AnalysisCriterion winningPositionsRatio = new PositionsRatioCriterion(PositionFilter.PROFIT);
Num netReturnValue = netReturn.calculate(series, tradingRecord);
Num winRate = winningPositionsRatio.calculate(series, tradingRecord);
// Advanced risk-adjusted metrics
AnalysisCriterion romad = new ReturnOverMaxDrawdownCriterion();
AnalysisCriterion maxDrawdown = new MaximumDrawdownCriterion();
AnalysisCriterion expectancy = new ExpectancyCriterion();
AnalysisCriterion sqn = new SqnCriterion(); // System Quality Number (higher = better)
AnalysisCriterion versusEnterAndHoldCriterion = new VersusEnterAndHoldCriterion(new NetReturnCriterion());
Num romadValue = romad.calculate(series, tradingRecord);
Num maxDrawdownValue = maxDrawdown.calculate(series, tradingRecord);
Num expectancyValue = expectancy.calculate(series, tradingRecord);
Num sqnValue = sqn.calculate(series, tradingRecord);
Num vsBuyHold = versusEnterAndHoldCriterion.calculate(series, tradingRecord);
// Display comprehensive results
System.out.println(" Basic Metrics:");
System.out.printf(" Total Positions: %d%n", tradingRecord.getPositionCount());
System.out.printf(" Net Return: %.2f%%%n",
netReturnValue.multipliedBy(series.numFactory().numOf(100)).doubleValue());
System.out.printf(" Win Rate: %.1f%%%n",
winRate.multipliedBy(series.numFactory().numOf(100)).doubleValue());
System.out.println();
System.out.println(" Risk Metrics:");
System.out.printf(" Maximum Drawdown: %.2f%%%n",
maxDrawdownValue.multipliedBy(series.numFactory().numOf(100)).doubleValue());
System.out.printf(" Return/Max Drawdown: %.2f%n", romadValue.doubleValue());
System.out.println();
System.out.println(" Advanced Metrics:");
System.out.printf(" Expectancy: %.4f (avg profit per trade)%n", expectancyValue.doubleValue());
System.out.printf(" SQN (System Quality): %.2f (higher = better)%n", sqnValue.doubleValue());
System.out.printf(" vs Buy & Hold: %.2f%%%n",
vsBuyHold.multipliedBy(series.numFactory().numOf(100)).doubleValue());
System.out.println();
// Step 6: Visualize the strategy with multiple subcharts
System.out.println("[6/7] 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("Bollinger Bands Mean Reversion Strategy - Yahoo Finance Data (AAPL)")
.withSeries(series) // Price bars (candlesticks)
.withTradingRecordOverlay(tradingRecord) // Trading positions marked on price chart
.withIndicatorOverlay(bbMiddle) // Middle band overlay
.withIndicatorOverlay(bbUpper) // Upper band overlay
.withIndicatorOverlay(bbLower) // Lower band overlay
.withSubChart(rsi) // RSI in first subchart
.withSubChart(obv) // OBV in second subchart
.withSubChart(vortex) // Vortex oscillator in third subchart
.withSubChart(ultimateOscillator) // Ultimate Oscillator in fourth subchart
.withSubChart(new NetProfitLossCriterion(), tradingRecord) // Net profit/loss in fifth subchart
.toChart();
chartWorkflow.displayChart(chart, "ta4j Yahoo Finance Backtest - Advanced Mean Reversion Strategy");
System.out.println(" [OK] Multi-subchart displayed in new window");
System.out.println(" [TIP] Chart shows: Price with BB bands, RSI, OBV, Vortex, Ultimate, 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 7: Explain advanced concepts
System.out.println("[7/7] Advanced Concepts Demonstrated");
System.out.println(" ──────────────────────────────────────────");
System.out.println(" ✓ Bollinger Bands: Mean reversion indicator");
System.out.println(" - Price tends to revert to the middle band");
System.out.println(" - Lower band = oversold, Upper band = overbought");
System.out.println();
System.out.println(" ✓ ATR-based Stop Loss: Dynamic risk management");
System.out.println(" - Adapts to market volatility automatically");
System.out.println(" - Tighter stops in calm markets, wider in volatile markets");
System.out.println();
System.out.println(" ✓ Multi-Indicator Confirmation: Reduces false signals");
System.out.println(" - RSI confirms oversold/overbought conditions");
System.out.println(" - OBV confirms volume support for price moves");
System.out.println(" - Vortex confirms directional trend bias (+VI vs -VI)");
System.out.println(" - Ultimate Oscillator confirms multi-timeframe buying pressure");
System.out.println();
System.out.println(" ✓ Advanced Metrics: Deeper performance insights");
System.out.println(" - Expectancy: Average profit per trade");
System.out.println(" - SQN: System Quality Number (risk-adjusted performance)");
System.out.println(" - Maximum Drawdown: Largest peak-to-trough decline");
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 2 years of daily OHLCV data for AAPL from Yahoo Finance");
System.out.println(" 2. Created advanced indicators:");
System.out.println(" - Bollinger Bands (mean reversion)");
System.out.println(" - RSI (momentum confirmation)");
System.out.println(" - ATR (volatility for dynamic stops)");
System.out.println(" - OBV (volume trend confirmation)");
System.out.println(" - Vortex oscillator (trend direction confirmation)");
System.out.println(" - Ultimate Oscillator (trend strength confirmation)");
System.out.println(" - Custom price-to-middle-band ratio (indicator composition)");
System.out.println(" 3. Built a sophisticated mean reversion strategy:");
System.out.println(
" - Entry: Price at/below lower BB + RSI < 45 + (OBV rising OR price below middle) + Vortex > 0 + Ultimate > 50");
System.out.println(" - Exit: Price at/above upper BB OR RSI > 65 OR ATR stop (2.5x ATR) OR Vortex < 0");
System.out.println(" 4. Backtested with ATR-based dynamic stop-loss (adapts to volatility)");
System.out.println(" 5. Analyzed with advanced metrics (Expectancy, SQN, Max Drawdown)");
if (!isHeadless) {
System.out.println(" 6. Visualized with multi-subchart (Price, RSI, OBV, P&L)");
}
System.out.println();
System.out.println("Advanced Features Demonstrated:");
System.out.println(" ✓ Bollinger Bands for mean reversion trading");
System.out.println(" ✓ ATR-based dynamic stop-loss (better than fixed %)");
System.out.println(" ✓ Multi-indicator trend confirmation (RSI, OBV, Vortex, Ultimate)");
System.out.println(" ✓ Indicator composition (BinaryOperationIndicator)");
System.out.println(" ✓ Advanced performance metrics (Expectancy, SQN)");
System.out.println(" ✓ Multi-subchart visualization");
System.out.println();
System.out.println("Yahoo Finance Data Source Features:");
System.out.println(" - Load data by number of days: loadSeries(\"AAPL\", 730)");
System.out.println(" - Load data by bar count: loadSeries(\"AAPL\", DAY_1, 500)");
System.out.println(" - Load data by date range: loadSeries(\"AAPL\", DAY_1, start, end)");
System.out.println(" - Supports multiple intervals: 1m, 5m, 15m, 30m, 1h, 4h, 1d, 1wk, 1mo");
System.out.println(" - Works with stocks, ETFs, and cryptocurrencies");
System.out.println(" - Automatic pagination for large date ranges");
System.out.println();
System.out.println("Next Steps - Experiment with:");
System.out.println(" - Different tickers: \"MSFT\", \"GOOGL\", \"BTC-USD\", \"ETH-USD\"");
System.out.println(" - Adjust BB period (try 10, 30) and multiplier (try 1.5, 2.5)");
System.out.println(" - Modify RSI thresholds (try 30/70 or 35/65)");
System.out.println(" - Change ATR multiplier for stops (try 1.5x or 3.0x)");
System.out.println(" - Add MACD or Stochastic for additional confirmation");
System.out.println(" - Try different intervals: HOUR_1, WEEK_1 for different 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 it affects performance.");
System.out.println();
}
}