goldenChat base source add
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
package ta4jexamples.strategies;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.jfree.chart.JFreeChart;
|
||||
import org.ta4j.core.*;
|
||||
import org.ta4j.core.backtest.BarSeriesManager;
|
||||
import org.ta4j.core.criteria.pnl.GrossReturnCriterion;
|
||||
import org.ta4j.core.indicators.adx.ADXIndicator;
|
||||
import org.ta4j.core.indicators.adx.MinusDIIndicator;
|
||||
import org.ta4j.core.indicators.adx.PlusDIIndicator;
|
||||
import org.ta4j.core.indicators.averages.SMAIndicator;
|
||||
import org.ta4j.core.indicators.helpers.ClosePriceIndicator;
|
||||
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.BitStampCsvTradesFileBarSeriesDataSource;
|
||||
|
||||
/**
|
||||
* ADX indicator based strategy
|
||||
*
|
||||
* @see <a href=
|
||||
* "http://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:average_directional_index_adx">
|
||||
* http://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:average_directional_index_adx</a>
|
||||
*/
|
||||
public class ADXStrategy {
|
||||
|
||||
private static final Logger LOG = LogManager.getLogger(ADXStrategy.class);
|
||||
|
||||
/**
|
||||
* @param series a bar series
|
||||
* @return an adx indicator based strategy
|
||||
*/
|
||||
public static Strategy buildStrategy(BarSeries series) {
|
||||
if (series == null) {
|
||||
throw new IllegalArgumentException("Series cannot be null");
|
||||
}
|
||||
|
||||
final ClosePriceIndicator closePriceIndicator = new ClosePriceIndicator(series);
|
||||
final SMAIndicator smaIndicator = new SMAIndicator(closePriceIndicator, 50);
|
||||
|
||||
final int adxBarCount = 14;
|
||||
final ADXIndicator adxIndicator = new ADXIndicator(series, adxBarCount);
|
||||
final OverIndicatorRule adxOver20Rule = new OverIndicatorRule(adxIndicator, 20);
|
||||
|
||||
final PlusDIIndicator plusDIIndicator = new PlusDIIndicator(series, adxBarCount);
|
||||
final MinusDIIndicator minusDIIndicator = new MinusDIIndicator(series, adxBarCount);
|
||||
|
||||
final Rule plusDICrossedUpMinusDI = new CrossedUpIndicatorRule(plusDIIndicator, minusDIIndicator);
|
||||
final Rule plusDICrossedDownMinusDI = new CrossedDownIndicatorRule(plusDIIndicator, minusDIIndicator);
|
||||
final OverIndicatorRule closePriceOverSma = new OverIndicatorRule(closePriceIndicator, smaIndicator);
|
||||
final Rule entryRule = adxOver20Rule.and(plusDICrossedUpMinusDI).and(closePriceOverSma);
|
||||
|
||||
final UnderIndicatorRule closePriceUnderSma = new UnderIndicatorRule(closePriceIndicator, smaIndicator);
|
||||
final Rule exitRule = adxOver20Rule.and(plusDICrossedDownMinusDI).and(closePriceUnderSma);
|
||||
|
||||
return new BaseStrategy("ADXStrategy", entryRule, exitRule, adxBarCount);
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
||||
// Getting the bar series
|
||||
BarSeries series = BitStampCsvTradesFileBarSeriesDataSource.loadBitstampSeries();
|
||||
|
||||
// Building the trading strategy
|
||||
Strategy strategy = buildStrategy(series);
|
||||
|
||||
// Running the strategy
|
||||
BarSeriesManager seriesManager = new BarSeriesManager(series);
|
||||
TradingRecord tradingRecord = seriesManager.run(strategy);
|
||||
LOG.debug(() -> strategy.toJson());
|
||||
LOG.debug("{}'s number of positions: {}", strategy.getName(), tradingRecord.getPositionCount());
|
||||
|
||||
// Analysis
|
||||
var grossReturn = new GrossReturnCriterion().calculate(series, tradingRecord);
|
||||
LOG.debug("{}'s gross return: {}", strategy.getName(), grossReturn);
|
||||
|
||||
ClosePriceIndicator closePriceIndicator = new ClosePriceIndicator(series);
|
||||
SMAIndicator smaIndicator = new SMAIndicator(closePriceIndicator, 50);
|
||||
int adxBarCount = 14;
|
||||
ADXIndicator adxIndicator = new ADXIndicator(series, adxBarCount);
|
||||
PlusDIIndicator plusDIIndicator = new PlusDIIndicator(series, adxBarCount);
|
||||
MinusDIIndicator minusDIIndicator = new MinusDIIndicator(series, adxBarCount);
|
||||
|
||||
// Charting
|
||||
ChartWorkflow chartWorkflow = new ChartWorkflow();
|
||||
JFreeChart chart = chartWorkflow.builder()
|
||||
.withSeries(series)
|
||||
.withTradingRecordOverlay(tradingRecord)
|
||||
.withIndicatorOverlay(smaIndicator)
|
||||
.withSubChart(adxIndicator)
|
||||
.withIndicatorOverlay(plusDIIndicator)
|
||||
.withIndicatorOverlay(minusDIIndicator)
|
||||
.withSubChart(new GrossReturnCriterion(), tradingRecord)
|
||||
.toChart();
|
||||
chartWorkflow.displayChart(chart);
|
||||
chartWorkflow.saveChartImage(chart, series, "adx-strategy", "temp/charts");
|
||||
}
|
||||
}
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
package ta4jexamples.strategies;
|
||||
|
||||
import java.awt.Color;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.jfree.chart.JFreeChart;
|
||||
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.pnl.GrossReturnCriterion;
|
||||
import org.ta4j.core.indicators.CCIIndicator;
|
||||
import org.ta4j.core.num.Num;
|
||||
import org.ta4j.core.rules.OverIndicatorRule;
|
||||
import org.ta4j.core.rules.UnderIndicatorRule;
|
||||
|
||||
import ta4jexamples.charting.workflow.ChartWorkflow;
|
||||
import ta4jexamples.datasources.BitStampCsvTradesFileBarSeriesDataSource;
|
||||
|
||||
/**
|
||||
* CCI Correction Strategy
|
||||
*
|
||||
* @see <a href=
|
||||
* "http://stockcharts.com/school/doku.php?id=chart_school:trading_strategies:cci_correction">
|
||||
* http://stockcharts.com/school/doku.php?id=chart_school:trading_strategies:cci_correction</a>
|
||||
*/
|
||||
public class CCICorrectionStrategy {
|
||||
|
||||
private static final Logger LOG = LogManager.getLogger(CCICorrectionStrategy.class);
|
||||
|
||||
/**
|
||||
* @param series a bar series
|
||||
* @return a CCI correction strategy
|
||||
*/
|
||||
public static Strategy buildStrategy(BarSeries series) {
|
||||
if (series == null) {
|
||||
throw new IllegalArgumentException("Series cannot be null");
|
||||
}
|
||||
|
||||
CCIIndicator longCci = new CCIIndicator(series, 200);
|
||||
CCIIndicator shortCci = new CCIIndicator(series, 5);
|
||||
Num plus100 = series.numFactory().hundred();
|
||||
Num minus100 = series.numFactory().numOf(-100);
|
||||
|
||||
Rule entryRule = new OverIndicatorRule(longCci, plus100) // Bull trend
|
||||
.and(new UnderIndicatorRule(shortCci, minus100)); // Signal
|
||||
|
||||
Rule exitRule = new UnderIndicatorRule(longCci, minus100) // Bear trend
|
||||
.and(new OverIndicatorRule(shortCci, plus100)); // Signal
|
||||
|
||||
String strategyName = "CCICorrectionStrategy";
|
||||
Strategy strategy = new BaseStrategy(strategyName, entryRule, exitRule);
|
||||
strategy.setUnstableBars(5);
|
||||
return strategy;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
||||
// Getting the bar series
|
||||
BarSeries series = BitStampCsvTradesFileBarSeriesDataSource.loadBitstampSeries();
|
||||
|
||||
// Building the trading strategy
|
||||
Strategy strategy = buildStrategy(series);
|
||||
|
||||
// Running the strategy
|
||||
BarSeriesManager seriesManager = new BarSeriesManager(series);
|
||||
TradingRecord tradingRecord = seriesManager.run(strategy);
|
||||
LOG.debug(() -> strategy.toJson());
|
||||
LOG.debug("{}'s number of positions: {}", strategy.getName(), tradingRecord.getPositionCount());
|
||||
|
||||
// Analysis
|
||||
var grossReturn = new GrossReturnCriterion().calculate(series, tradingRecord);
|
||||
LOG.debug("{}'s gross return: {}", strategy.getName(), grossReturn);
|
||||
|
||||
CCIIndicator longCci = new CCIIndicator(series, 200);
|
||||
CCIIndicator shortCci = new CCIIndicator(series, 5);
|
||||
|
||||
// Charting
|
||||
ChartWorkflow chartWorkflow = new ChartWorkflow();
|
||||
JFreeChart chart = chartWorkflow.builder()
|
||||
.withSeries(series)
|
||||
.withTradingRecordOverlay(tradingRecord)
|
||||
.withSubChart(longCci)
|
||||
.withIndicatorOverlay(shortCci)
|
||||
.withLineColor(Color.ORANGE)
|
||||
.withSubChart(new GrossReturnCriterion(), tradingRecord)
|
||||
.toChart();
|
||||
chartWorkflow.displayChart(chart);
|
||||
chartWorkflow.saveChartImage(chart, series, "cci-correction-strategy", "temp/charts");
|
||||
}
|
||||
}
|
||||
+141
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
package ta4jexamples.strategies;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.ta4j.core.BarSeries;
|
||||
import org.ta4j.core.Strategy;
|
||||
import org.ta4j.core.indicators.helpers.DateTimeIndicator;
|
||||
import org.ta4j.core.rules.DayOfWeekRule;
|
||||
import org.ta4j.core.strategy.named.NamedStrategy;
|
||||
|
||||
import java.time.DayOfWeek;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A trading strategy that enters and exits positions based on specific days of
|
||||
* the week.
|
||||
*
|
||||
* <p>
|
||||
* This strategy uses {@link DayOfWeekRule} to determine entry and exit signals
|
||||
* based on the day of the week of each bar in the series. The strategy will
|
||||
* enter a position when the bar's day of the week matches the specified entry
|
||||
* day, and exit when it matches the specified exit day.
|
||||
*
|
||||
* <p>
|
||||
* The strategy name is automatically generated as
|
||||
* {@code "DayOfWeekStrategy_<entryDay>_<exitDay>"} (e.g.,
|
||||
* "DayOfWeekStrategy_MONDAY_FRIDAY").
|
||||
*
|
||||
* <p>
|
||||
* This strategy is useful for testing day-of-week effects in trading, such as
|
||||
* the "Monday effect" or "Friday effect" observed in some markets.
|
||||
*
|
||||
* @since 0.19
|
||||
*/
|
||||
public class DayOfWeekStrategy extends NamedStrategy {
|
||||
|
||||
static {
|
||||
registerImplementation(DayOfWeekStrategy.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new DayOfWeekStrategy with the specified entry and exit days.
|
||||
*
|
||||
* @param series the bar series to analyze
|
||||
* @param entryDayOfWeek the day of the week to enter positions
|
||||
* @param exitDayOfWeek the day of the week to exit positions
|
||||
* @throws IllegalArgumentException if series is null or if entryDayOfWeek
|
||||
* equals exitDayOfWeek
|
||||
*/
|
||||
public DayOfWeekStrategy(BarSeries series, DayOfWeek entryDayOfWeek, DayOfWeek exitDayOfWeek) {
|
||||
super(NamedStrategy.buildLabel(DayOfWeekStrategy.class, entryDayOfWeek.name(), exitDayOfWeek.name()),
|
||||
new DayOfWeekRule(new DateTimeIndicator(series), entryDayOfWeek),
|
||||
new DayOfWeekRule(new DateTimeIndicator(series), exitDayOfWeek));
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new DayOfWeekStrategy from string parameters.
|
||||
*
|
||||
* <p>
|
||||
* The parameters should be two strings representing the entry and exit days of
|
||||
* the week. Valid values are: MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY,
|
||||
* SATURDAY, SUNDAY (case-sensitive).
|
||||
*
|
||||
* @param series the bar series to analyze
|
||||
* @param params array containing [entryDayOfWeek, exitDayOfWeek] as strings
|
||||
* @throws IllegalArgumentException if params is null, has fewer than 2
|
||||
* elements, or contains invalid day names
|
||||
*/
|
||||
public DayOfWeekStrategy(BarSeries series, String... params) {
|
||||
this(series, parseEntryDayOfWeek(params), parseExitDayOfWeek(params));
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds all possible strategy permutations for all combinations of entry and
|
||||
* exit days.
|
||||
*
|
||||
* <p>
|
||||
* This method generates strategies for all pairs of different days of the week
|
||||
* (7 * 6 = 42 total strategies). Strategies where the entry day equals the exit
|
||||
* day are excluded. If any strategy construction fails, a warning is logged and
|
||||
* that strategy is skipped.
|
||||
*
|
||||
* @param series the bar series to analyze
|
||||
* @return a list of all valid DayOfWeekStrategy permutations
|
||||
*/
|
||||
public static List<Strategy> buildAllStrategyPermutations(BarSeries series) {
|
||||
List<String[]> permutations = new ArrayList<>();
|
||||
|
||||
for (DayOfWeek entryDay : DayOfWeek.values()) {
|
||||
for (DayOfWeek exitDay : DayOfWeek.values()) {
|
||||
if (entryDay != exitDay) {
|
||||
permutations.add(new String[] { entryDay.name(), exitDay.name() });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return NamedStrategy.buildAllStrategyPermutations(series, permutations, DayOfWeekStrategy::new,
|
||||
(params, error) -> {
|
||||
String entry = params.length > 0 ? params[0] : "<missing>";
|
||||
String exit = params.length > 1 ? params[1] : "<missing>";
|
||||
LogManager.getLogger()
|
||||
.warn("Failed to build strategy for entry day {} and exit day {} - {}", entry, exit,
|
||||
error.getMessage());
|
||||
});
|
||||
}
|
||||
|
||||
private static DayOfWeek parseEntryDayOfWeek(String... params) {
|
||||
if (params == null) {
|
||||
throw new IllegalArgumentException("Params cannot be null");
|
||||
}
|
||||
if (params.length < 1) {
|
||||
throw new IllegalArgumentException(
|
||||
"At least 2 parameters required (entryDayOfWeek, exitDayOfWeek), but got " + params.length);
|
||||
}
|
||||
try {
|
||||
return DayOfWeek.valueOf(params[0]);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new IllegalArgumentException("Invalid entry DayOfWeek value: '" + params[0]
|
||||
+ "'. Valid values are: MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static DayOfWeek parseExitDayOfWeek(String... params) {
|
||||
if (params == null) {
|
||||
throw new IllegalArgumentException("Params cannot be null");
|
||||
}
|
||||
if (params.length < 2) {
|
||||
throw new IllegalArgumentException(
|
||||
"At least 2 parameters required (entryDayOfWeek, exitDayOfWeek), but got " + params.length);
|
||||
}
|
||||
try {
|
||||
return DayOfWeek.valueOf(params[1]);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new IllegalArgumentException("Invalid exit DayOfWeek value: '" + params[1]
|
||||
+ "'. Valid values are: MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
package ta4jexamples.strategies;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.jfree.chart.JFreeChart;
|
||||
import org.ta4j.core.BarSeries;
|
||||
import org.ta4j.core.BaseStrategy;
|
||||
import org.ta4j.core.Strategy;
|
||||
import org.ta4j.core.backtest.BarSeriesManager;
|
||||
import org.ta4j.core.criteria.pnl.GrossReturnCriterion;
|
||||
import org.ta4j.core.criteria.pnl.NetProfitCriterion;
|
||||
import org.ta4j.core.indicators.helpers.ClosePriceIndicator;
|
||||
import org.ta4j.core.indicators.helpers.HighPriceIndicator;
|
||||
import org.ta4j.core.indicators.helpers.HighestValueIndicator;
|
||||
import org.ta4j.core.indicators.helpers.LowPriceIndicator;
|
||||
import org.ta4j.core.indicators.helpers.LowestValueIndicator;
|
||||
import org.ta4j.core.indicators.numeric.BinaryOperationIndicator;
|
||||
import org.ta4j.core.rules.OverIndicatorRule;
|
||||
import org.ta4j.core.rules.UnderIndicatorRule;
|
||||
import ta4jexamples.charting.workflow.ChartWorkflow;
|
||||
import ta4jexamples.datasources.BitStampCsvTradesFileBarSeriesDataSource;
|
||||
|
||||
/**
|
||||
* Strategies which compares current price to global extrema over a week.
|
||||
*/
|
||||
public class GlobalExtremaStrategy {
|
||||
|
||||
private static final Logger LOG = LogManager.getLogger(GlobalExtremaStrategy.class);
|
||||
|
||||
// We assume that there were at least one position every 5 minutes during the
|
||||
// whole
|
||||
// week
|
||||
private static final int NB_BARS_PER_WEEK = 12 * 24 * 7;
|
||||
|
||||
/**
|
||||
* @param series the bar series
|
||||
* @return the global extrema strategy
|
||||
*/
|
||||
public static Strategy buildStrategy(final BarSeries series) {
|
||||
if (series == null) {
|
||||
throw new IllegalArgumentException("Series cannot be null");
|
||||
}
|
||||
|
||||
final var closePrices = new ClosePriceIndicator(series);
|
||||
|
||||
// Getting the high price over the past week
|
||||
final var highPrices = new HighPriceIndicator(series);
|
||||
final var weekHighPrice = new HighestValueIndicator(highPrices, NB_BARS_PER_WEEK);
|
||||
// Getting the low price over the past week
|
||||
final var lowPrices = new LowPriceIndicator(series);
|
||||
final var weekLowPrice = new LowestValueIndicator(lowPrices, NB_BARS_PER_WEEK);
|
||||
|
||||
// Going long if the close price goes below the low price
|
||||
final var downWeek = BinaryOperationIndicator.product(weekLowPrice, 1.004);
|
||||
final var buyingRule = new UnderIndicatorRule(closePrices, downWeek);
|
||||
|
||||
// Going short if the close price goes above the high price
|
||||
final var upWeek = BinaryOperationIndicator.product(weekHighPrice, 0.996);
|
||||
final var sellingRule = new OverIndicatorRule(closePrices, upWeek);
|
||||
|
||||
String strategyName = "GlobalExtremaStrategy";
|
||||
return new BaseStrategy(strategyName, buyingRule, sellingRule);
|
||||
}
|
||||
|
||||
public static void main(final String[] args) {
|
||||
|
||||
// Getting the bar series
|
||||
final var series = BitStampCsvTradesFileBarSeriesDataSource.loadBitstampSeries();
|
||||
|
||||
// Building the trading strategy
|
||||
final var strategy = buildStrategy(series);
|
||||
|
||||
// Running the strategy
|
||||
final var seriesManager = new BarSeriesManager(series);
|
||||
final var tradingRecord = seriesManager.run(strategy);
|
||||
LOG.debug(() -> strategy.toJson());
|
||||
LOG.debug("{}'s number of positions: {}", strategy.getName(), tradingRecord.getPositionCount());
|
||||
|
||||
// Analysis
|
||||
final var grossReturn = new GrossReturnCriterion().calculate(series, tradingRecord);
|
||||
LOG.debug("{}'s gross return: {}", strategy.getName(), grossReturn);
|
||||
|
||||
final var highPrices = new HighPriceIndicator(series);
|
||||
final var weekHighPrice = new HighestValueIndicator(highPrices, NB_BARS_PER_WEEK);
|
||||
final var lowPrices = new LowPriceIndicator(series);
|
||||
final var weekLowPrice = new LowestValueIndicator(lowPrices, NB_BARS_PER_WEEK);
|
||||
final var downWeek = BinaryOperationIndicator.product(weekLowPrice, 1.004);
|
||||
final var upWeek = BinaryOperationIndicator.product(weekHighPrice, 0.996);
|
||||
|
||||
// Charting
|
||||
ChartWorkflow chartWorkflow = new ChartWorkflow();
|
||||
JFreeChart chart = chartWorkflow.builder()
|
||||
.withSeries(series)
|
||||
.withTradingRecordOverlay(tradingRecord)
|
||||
.withIndicatorOverlay(weekHighPrice)
|
||||
.withIndicatorOverlay(weekLowPrice)
|
||||
.withIndicatorOverlay(downWeek)
|
||||
.withIndicatorOverlay(upWeek)
|
||||
.withAnalysisCriterionOverlay(new NetProfitCriterion(), tradingRecord)
|
||||
.toChart();
|
||||
chartWorkflow.displayChart(chart);
|
||||
chartWorkflow.saveChartImage(chart, series, "global-extrema-strategy", "temp/charts");
|
||||
}
|
||||
}
|
||||
+689
@@ -0,0 +1,689 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
package ta4jexamples.strategies;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.ta4j.core.BarSeries;
|
||||
import org.ta4j.core.Indicator;
|
||||
import org.ta4j.core.Rule;
|
||||
import org.ta4j.core.indicators.CachedIndicator;
|
||||
import org.ta4j.core.indicators.RSIIndicator;
|
||||
import org.ta4j.core.indicators.averages.SMAIndicator;
|
||||
import org.ta4j.core.indicators.elliott.ElliottChannelIndicator;
|
||||
import org.ta4j.core.indicators.elliott.ElliottDegree;
|
||||
import org.ta4j.core.indicators.elliott.ElliottPhase;
|
||||
import org.ta4j.core.indicators.elliott.ElliottScenario;
|
||||
import org.ta4j.core.indicators.elliott.ElliottScenarioGenerator;
|
||||
import org.ta4j.core.indicators.elliott.ElliottScenarioSet;
|
||||
import org.ta4j.core.indicators.elliott.ElliottSwing;
|
||||
import org.ta4j.core.indicators.elliott.ElliottSwingCompressor;
|
||||
import org.ta4j.core.indicators.elliott.ElliottSwingIndicator;
|
||||
import org.ta4j.core.indicators.helpers.ClosePriceIndicator;
|
||||
import org.ta4j.core.indicators.macd.VolatilityNormalizedMACDIndicator;
|
||||
import org.ta4j.core.rules.NotRule;
|
||||
import org.ta4j.core.rules.OverIndicatorRule;
|
||||
import org.ta4j.core.rules.UnderIndicatorRule;
|
||||
import org.ta4j.core.rules.elliott.ElliottImpulsePhaseRule;
|
||||
import org.ta4j.core.rules.elliott.ElliottScenarioAlternationRule;
|
||||
import org.ta4j.core.rules.elliott.ElliottScenarioCompletionRule;
|
||||
import org.ta4j.core.rules.elliott.ElliottScenarioConfidenceRule;
|
||||
import org.ta4j.core.rules.elliott.ElliottScenarioDirectionRule;
|
||||
import org.ta4j.core.rules.elliott.ElliottScenarioInvalidationRule;
|
||||
import org.ta4j.core.rules.elliott.ElliottScenarioRiskRewardRule;
|
||||
import org.ta4j.core.rules.elliott.ElliottScenarioStopViolationRule;
|
||||
import org.ta4j.core.rules.elliott.ElliottScenarioTargetReachedRule;
|
||||
import org.ta4j.core.rules.elliott.ElliottScenarioTimeStopRule;
|
||||
import org.ta4j.core.rules.elliott.ElliottScenarioValidRule;
|
||||
import org.ta4j.core.rules.elliott.ElliottTrendBiasRule;
|
||||
import org.ta4j.core.strategy.named.NamedStrategy;
|
||||
|
||||
/**
|
||||
* High-reward Elliott Wave strategy that trades only high-confidence impulse
|
||||
* scenarios with favorable risk/reward and trend/momentum alignment.
|
||||
*
|
||||
* <p>
|
||||
* Entry criteria:
|
||||
* <ul>
|
||||
* <li>Impulse scenario in wave 3 or wave 5</li>
|
||||
* <li>Confidence above the minimum threshold</li>
|
||||
* <li>Directional trend bias alignment</li>
|
||||
* <li>Risk/reward meets the minimum threshold using the wave 2/4 stop and the
|
||||
* furthest Fibonacci target</li>
|
||||
* <li>Wave 2/4 time alternation exceeds the minimum ratio</li>
|
||||
* <li>Trend (SMA) and momentum (RSI or volatility-normalized MACD-V)
|
||||
* confirmation</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>
|
||||
* Exit criteria:
|
||||
* <ul>
|
||||
* <li>Scenario invalidation or completion</li>
|
||||
* <li>Corrective swing stop breached (wave 2/4)</li>
|
||||
* <li>Target reached</li>
|
||||
* <li>Trend/momentum breakdown</li>
|
||||
* <li>Time-based stop after an extended wave 3 duration</li>
|
||||
* </ul>
|
||||
*
|
||||
* @since 0.22.2
|
||||
*/
|
||||
public class HighRewardElliottWaveStrategy extends NamedStrategy {
|
||||
|
||||
static {
|
||||
registerImplementation(HighRewardElliottWaveStrategy.class);
|
||||
}
|
||||
|
||||
private static final SignalDirection DEFAULT_DIRECTION = SignalDirection.BULLISH;
|
||||
private static final ElliottDegree DEFAULT_DEGREE = ElliottDegree.PRIMARY;
|
||||
private static final double DEFAULT_MIN_CONFIDENCE = 0.35;
|
||||
private static final double DEFAULT_MIN_RISK_REWARD = 2.0;
|
||||
private static final double DEFAULT_MIN_ALTERNATION_RATIO = 1.50;
|
||||
private static final double DEFAULT_MIN_TREND_BIAS_STRENGTH = 0.10;
|
||||
private static final int DEFAULT_TREND_SMA_PERIOD = 100;
|
||||
private static final int DEFAULT_RSI_PERIOD = 14;
|
||||
private static final double DEFAULT_RSI_THRESHOLD = 50.0;
|
||||
private static final int DEFAULT_MACD_FAST = 12;
|
||||
private static final int DEFAULT_MACD_SLOW = 26;
|
||||
private static final int DEFAULT_MACD_SIGNAL = 9;
|
||||
|
||||
private static final int DEFAULT_ATR_PERIOD = 14;
|
||||
private static final double DEFAULT_MIN_RELATIVE_SWING = 0.10;
|
||||
private static final double ANALYZER_MIN_CONFIDENCE = 0.20;
|
||||
private static final int DEFAULT_SCENARIO_SWING_WINDOW = 5;
|
||||
|
||||
private static final double MAX_WAVE_DURATION_MULTIPLIER = 1.5;
|
||||
private static final int PARAMETER_COUNT = 12;
|
||||
|
||||
/**
|
||||
* Builds the strategy with default parameters.
|
||||
*
|
||||
* @param series bar series to analyze
|
||||
*/
|
||||
public HighRewardElliottWaveStrategy(final BarSeries series) {
|
||||
this(series, Config.defaults());
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the strategy using serialized label parameters.
|
||||
*
|
||||
* @param series bar series to analyze
|
||||
* @param params serialized parameters (see
|
||||
* {@link Config#fromParameters(String...)})
|
||||
*/
|
||||
public HighRewardElliottWaveStrategy(final BarSeries series, final String... params) {
|
||||
this(series, Config.fromParameters(params));
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the strategy using a precomputed scenario indicator.
|
||||
*
|
||||
* @param series bar series to analyze
|
||||
* @param config strategy configuration
|
||||
* @param scenarioIndicator indicator supplying scenario sets
|
||||
*/
|
||||
HighRewardElliottWaveStrategy(final BarSeries series, final Config config,
|
||||
final Indicator<ElliottScenarioSet> scenarioIndicator) {
|
||||
this(config, buildEntryRule(series, config, scenarioIndicator),
|
||||
buildExitRule(series, config, scenarioIndicator), calculateUnstableBars(config));
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the strategy with the default scenario indicator pipeline.
|
||||
*
|
||||
* @param series bar series to analyze
|
||||
* @param config strategy configuration
|
||||
*/
|
||||
private HighRewardElliottWaveStrategy(final BarSeries series, final Config config) {
|
||||
this(series, config, buildScenarioIndicator(series, config));
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal constructor that wires the prepared rules into the named strategy.
|
||||
*
|
||||
* @param config strategy configuration
|
||||
* @param entryRule precomputed entry rule
|
||||
* @param exitRule precomputed exit rule
|
||||
* @param unstableBars unstable bar count for warm-up
|
||||
*/
|
||||
private HighRewardElliottWaveStrategy(final Config config, final Rule entryRule, final Rule exitRule,
|
||||
final int unstableBars) {
|
||||
super(buildLabel(config), entryRule, exitRule, unstableBars);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the entry rule for the strategy.
|
||||
*
|
||||
* @param series bar series backing indicators
|
||||
* @param config strategy configuration
|
||||
* @param scenarioIndicator indicator supplying scenario sets
|
||||
* @return entry rule
|
||||
*/
|
||||
private static Rule buildEntryRule(final BarSeries series, final Config config,
|
||||
final Indicator<ElliottScenarioSet> scenarioIndicator) {
|
||||
Objects.requireNonNull(series, "series");
|
||||
Objects.requireNonNull(config, "config");
|
||||
Objects.requireNonNull(scenarioIndicator, "scenarioIndicator");
|
||||
validateScenarioIndicator(series, scenarioIndicator);
|
||||
|
||||
ClosePriceIndicator close = new ClosePriceIndicator(series);
|
||||
SMAIndicator trendSma = new SMAIndicator(close, config.trendSmaPeriod());
|
||||
RSIIndicator rsi = new RSIIndicator(close, config.rsiPeriod());
|
||||
VolatilityNormalizedMACDIndicator macd = new VolatilityNormalizedMACDIndicator(close, config.macdFastPeriod(),
|
||||
config.macdSlowPeriod(), DEFAULT_MACD_SIGNAL);
|
||||
|
||||
Rule trendRule = config.direction().isBullish() ? new OverIndicatorRule(close, trendSma)
|
||||
: new UnderIndicatorRule(close, trendSma);
|
||||
|
||||
Rule momentumRule = config.direction().isBullish()
|
||||
? new OverIndicatorRule(rsi, config.rsiThreshold()).or(new OverIndicatorRule(macd, 0))
|
||||
: new UnderIndicatorRule(rsi, config.rsiThreshold()).or(new UnderIndicatorRule(macd, 0));
|
||||
|
||||
Rule scenarioValidRule = new ElliottScenarioValidRule(scenarioIndicator, close);
|
||||
Rule impulsePhaseRule = new ElliottImpulsePhaseRule(scenarioIndicator, ElliottPhase.WAVE3, ElliottPhase.WAVE5);
|
||||
Rule confidenceRule = new ElliottScenarioConfidenceRule(scenarioIndicator, config.minConfidence());
|
||||
Rule directionRule = new ElliottScenarioDirectionRule(scenarioIndicator, config.direction().isBullish());
|
||||
Rule trendBiasRule = new ElliottTrendBiasRule(scenarioIndicator, config.direction().isBullish(),
|
||||
config.minTrendBiasStrength());
|
||||
Rule alternationRule = new ElliottScenarioAlternationRule(scenarioIndicator, config.minAlternationRatio());
|
||||
Rule riskRewardRule = new ElliottScenarioRiskRewardRule(scenarioIndicator, close,
|
||||
config.direction().isBullish(), config.minRiskReward());
|
||||
|
||||
Rule entryRule = scenarioValidRule.and(impulsePhaseRule)
|
||||
.and(confidenceRule)
|
||||
.and(directionRule)
|
||||
.and(trendBiasRule)
|
||||
.and(alternationRule)
|
||||
.and(riskRewardRule)
|
||||
.and(trendRule)
|
||||
.and(momentumRule);
|
||||
|
||||
return entryRule;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the exit rule for the strategy.
|
||||
*
|
||||
* @param series bar series backing indicators
|
||||
* @param config strategy configuration
|
||||
* @param scenarioIndicator indicator supplying scenario sets
|
||||
* @return exit rule
|
||||
*/
|
||||
private static Rule buildExitRule(final BarSeries series, final Config config,
|
||||
final Indicator<ElliottScenarioSet> scenarioIndicator) {
|
||||
Objects.requireNonNull(series, "series");
|
||||
Objects.requireNonNull(config, "config");
|
||||
Objects.requireNonNull(scenarioIndicator, "scenarioIndicator");
|
||||
validateScenarioIndicator(series, scenarioIndicator);
|
||||
|
||||
ClosePriceIndicator close = new ClosePriceIndicator(series);
|
||||
SMAIndicator trendSma = new SMAIndicator(close, config.trendSmaPeriod());
|
||||
RSIIndicator rsi = new RSIIndicator(close, config.rsiPeriod());
|
||||
VolatilityNormalizedMACDIndicator macd = new VolatilityNormalizedMACDIndicator(close, config.macdFastPeriod(),
|
||||
config.macdSlowPeriod(), DEFAULT_MACD_SIGNAL);
|
||||
|
||||
Rule trendRule = config.direction().isBullish() ? new OverIndicatorRule(close, trendSma)
|
||||
: new UnderIndicatorRule(close, trendSma);
|
||||
|
||||
Rule momentumRule = config.direction().isBullish()
|
||||
? new OverIndicatorRule(rsi, config.rsiThreshold()).or(new OverIndicatorRule(macd, 0))
|
||||
: new UnderIndicatorRule(rsi, config.rsiThreshold()).or(new UnderIndicatorRule(macd, 0));
|
||||
|
||||
Rule scenarioValidRule = new ElliottScenarioValidRule(scenarioIndicator, close);
|
||||
Rule directionRule = new ElliottScenarioDirectionRule(scenarioIndicator, config.direction().isBullish());
|
||||
Rule trendBiasRule = new ElliottTrendBiasRule(scenarioIndicator, config.direction().isBullish(),
|
||||
config.minTrendBiasStrength());
|
||||
|
||||
Rule exitTriggers = new NotRule(scenarioValidRule).or(new NotRule(directionRule))
|
||||
.or(new ElliottScenarioCompletionRule(scenarioIndicator))
|
||||
.or(new ElliottScenarioInvalidationRule(scenarioIndicator, close))
|
||||
.or(new ElliottScenarioTargetReachedRule(scenarioIndicator, close, config.direction().isBullish()))
|
||||
.or(new ElliottScenarioStopViolationRule(scenarioIndicator, close, config.direction().isBullish()))
|
||||
.or(new NotRule(trendBiasRule))
|
||||
.or(new NotRule(trendRule.and(momentumRule)))
|
||||
.or(new ElliottScenarioTimeStopRule(scenarioIndicator, MAX_WAVE_DURATION_MULTIPLIER));
|
||||
|
||||
return exitTriggers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that the scenario indicator is bound to the same series instance as
|
||||
* the strategy.
|
||||
*
|
||||
* @param series strategy series
|
||||
* @param scenarioIndicator scenario source indicator
|
||||
*/
|
||||
private static void validateScenarioIndicator(final BarSeries series,
|
||||
final Indicator<ElliottScenarioSet> scenarioIndicator) {
|
||||
if (scenarioIndicator.getBarSeries() != series) {
|
||||
throw new IllegalArgumentException("scenarioIndicator must use the same BarSeries instance");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the scenario indicator pipeline used by the strategy.
|
||||
*
|
||||
* @param series bar series to analyze
|
||||
* @param config strategy configuration
|
||||
* @return scenario indicator
|
||||
*/
|
||||
private static Indicator<ElliottScenarioSet> buildScenarioIndicator(final BarSeries series, final Config config) {
|
||||
ElliottSwingIndicator swingIndicator = ElliottSwingIndicator.zigZag(series, config.degree());
|
||||
ElliottChannelIndicator channelIndicator = new ElliottChannelIndicator(swingIndicator);
|
||||
double minConfidence = Math.min(config.minConfidence(), ANALYZER_MIN_CONFIDENCE);
|
||||
ElliottScenarioGenerator generator = new ElliottScenarioGenerator(series.numFactory(), minConfidence,
|
||||
ElliottScenarioGenerator.DEFAULT_MAX_SCENARIOS);
|
||||
ElliottSwingCompressor compressor = new ElliottSwingCompressor(new ClosePriceIndicator(series),
|
||||
config.minRelativeSwing(), 0);
|
||||
return new ScenarioSetIndicator(series, swingIndicator, channelIndicator, generator, compressor,
|
||||
DEFAULT_SCENARIO_SWING_WINDOW);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the named-strategy label for the configured parameters.
|
||||
*
|
||||
* @param config strategy configuration
|
||||
* @return label string
|
||||
*/
|
||||
private static String buildLabel(final Config config) {
|
||||
return NamedStrategy.buildLabel(HighRewardElliottWaveStrategy.class, config.labelParts());
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the number of unstable bars for indicator warm-up.
|
||||
*
|
||||
* @param config strategy configuration
|
||||
* @return unstable bar count
|
||||
*/
|
||||
private static int calculateUnstableBars(final Config config) {
|
||||
int unstable = Math.max(config.trendSmaPeriod(), Math.max(config.rsiPeriod(), config.macdSlowPeriod()));
|
||||
unstable = Math.max(unstable, DEFAULT_ATR_PERIOD);
|
||||
return unstable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a double for strategy labels without trailing zeros.
|
||||
*
|
||||
* @param value numeric value
|
||||
* @return formatted string
|
||||
*/
|
||||
private static String formatDouble(final double value) {
|
||||
return BigDecimal.valueOf(value).stripTrailingZeros().toPlainString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trade direction for the strategy.
|
||||
*/
|
||||
enum SignalDirection {
|
||||
BULLISH, BEARISH;
|
||||
|
||||
/**
|
||||
* @return {@code true} when the direction is bullish
|
||||
*/
|
||||
boolean isBullish() {
|
||||
return this == BULLISH;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Immutable configuration for the strategy.
|
||||
*/
|
||||
static final class Config {
|
||||
|
||||
private final SignalDirection direction;
|
||||
private final ElliottDegree degree;
|
||||
private final double minConfidence;
|
||||
private final double minRiskReward;
|
||||
private final double minAlternationRatio;
|
||||
private final double minTrendBiasStrength;
|
||||
private final int trendSmaPeriod;
|
||||
private final int rsiPeriod;
|
||||
private final double rsiThreshold;
|
||||
private final int macdFastPeriod;
|
||||
private final int macdSlowPeriod;
|
||||
private final double minRelativeSwing;
|
||||
|
||||
/**
|
||||
* Creates a configuration with the supplied parameters.
|
||||
*
|
||||
* @param direction trade direction
|
||||
* @param degree Elliott wave degree to analyze
|
||||
* @param minConfidence minimum confidence threshold
|
||||
* @param minRiskReward minimum risk/reward ratio
|
||||
* @param minAlternationRatio minimum alternation duration ratio
|
||||
* @param minTrendBiasStrength minimum trend bias strength
|
||||
* @param trendSmaPeriod SMA period for trend confirmation
|
||||
* @param rsiPeriod RSI period for momentum confirmation
|
||||
* @param rsiThreshold RSI threshold for momentum
|
||||
* @param macdFastPeriod MACD fast period
|
||||
* @param macdSlowPeriod MACD slow period
|
||||
* @param minRelativeSwing minimum relative swing magnitude
|
||||
*/
|
||||
Config(final SignalDirection direction, final ElliottDegree degree, final double minConfidence,
|
||||
final double minRiskReward, final double minAlternationRatio, final double minTrendBiasStrength,
|
||||
final int trendSmaPeriod, final int rsiPeriod, final double rsiThreshold, final int macdFastPeriod,
|
||||
final int macdSlowPeriod, final double minRelativeSwing) {
|
||||
this.direction = Objects.requireNonNull(direction, "direction");
|
||||
this.degree = Objects.requireNonNull(degree, "degree");
|
||||
if (minConfidence <= 0.0 || minConfidence > 1.0) {
|
||||
throw new IllegalArgumentException("minConfidence must be in (0.0, 1.0]");
|
||||
}
|
||||
if (minRiskReward <= 0.0) {
|
||||
throw new IllegalArgumentException("minRiskReward must be positive");
|
||||
}
|
||||
if (minAlternationRatio < 1.0) {
|
||||
throw new IllegalArgumentException("minAlternationRatio must be >= 1.0");
|
||||
}
|
||||
if (minTrendBiasStrength < 0.0 || minTrendBiasStrength > 1.0) {
|
||||
throw new IllegalArgumentException("minTrendBiasStrength must be in [0.0, 1.0]");
|
||||
}
|
||||
if (trendSmaPeriod <= 0) {
|
||||
throw new IllegalArgumentException("trendSmaPeriod must be positive");
|
||||
}
|
||||
if (rsiPeriod <= 0) {
|
||||
throw new IllegalArgumentException("rsiPeriod must be positive");
|
||||
}
|
||||
if (rsiThreshold < 0.0 || rsiThreshold > 100.0) {
|
||||
throw new IllegalArgumentException("rsiThreshold must be in [0.0, 100.0]");
|
||||
}
|
||||
if (macdFastPeriod <= 0 || macdSlowPeriod <= 0) {
|
||||
throw new IllegalArgumentException("MACD periods must be positive");
|
||||
}
|
||||
if (macdFastPeriod >= macdSlowPeriod) {
|
||||
throw new IllegalArgumentException("macdFastPeriod must be less than macdSlowPeriod");
|
||||
}
|
||||
if (minRelativeSwing <= 0.0 || minRelativeSwing > 1.0) {
|
||||
throw new IllegalArgumentException("minRelativeSwing must be in (0.0, 1.0]");
|
||||
}
|
||||
this.minConfidence = minConfidence;
|
||||
this.minRiskReward = minRiskReward;
|
||||
this.minAlternationRatio = minAlternationRatio;
|
||||
this.minTrendBiasStrength = minTrendBiasStrength;
|
||||
this.trendSmaPeriod = trendSmaPeriod;
|
||||
this.rsiPeriod = rsiPeriod;
|
||||
this.rsiThreshold = rsiThreshold;
|
||||
this.macdFastPeriod = macdFastPeriod;
|
||||
this.macdSlowPeriod = macdSlowPeriod;
|
||||
this.minRelativeSwing = minRelativeSwing;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return default configuration values
|
||||
*/
|
||||
static Config defaults() {
|
||||
return new Config(DEFAULT_DIRECTION, DEFAULT_DEGREE, DEFAULT_MIN_CONFIDENCE, DEFAULT_MIN_RISK_REWARD,
|
||||
DEFAULT_MIN_ALTERNATION_RATIO, DEFAULT_MIN_TREND_BIAS_STRENGTH, DEFAULT_TREND_SMA_PERIOD,
|
||||
DEFAULT_RSI_PERIOD, DEFAULT_RSI_THRESHOLD, DEFAULT_MACD_FAST, DEFAULT_MACD_SLOW,
|
||||
DEFAULT_MIN_RELATIVE_SWING);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a serialized parameter list into a configuration.
|
||||
*
|
||||
* @param params serialized parameters
|
||||
* @return parsed configuration
|
||||
*/
|
||||
static Config fromParameters(final String... params) {
|
||||
if (params == null) {
|
||||
throw new IllegalArgumentException("Params cannot be null");
|
||||
}
|
||||
if (params.length == 0) {
|
||||
return defaults();
|
||||
}
|
||||
if (params.length != PARAMETER_COUNT) {
|
||||
throw new IllegalArgumentException("Expected " + PARAMETER_COUNT
|
||||
+ " parameters (direction, degree, minConfidence, minRiskReward, minAlternationRatio, "
|
||||
+ "minTrendBiasStrength, trendSmaPeriod, rsiPeriod, rsiThreshold, macdFastPeriod, "
|
||||
+ "macdSlowPeriod, minRelativeSwing), but got " + params.length);
|
||||
}
|
||||
|
||||
SignalDirection direction = parseEnum(params[0], SignalDirection.class, "direction");
|
||||
ElliottDegree degree = parseEnum(params[1], ElliottDegree.class, "degree");
|
||||
double minConfidence = parseDouble(params[2], "minConfidence");
|
||||
double minRiskReward = parseDouble(params[3], "minRiskReward");
|
||||
double minAlternation = parseDouble(params[4], "minAlternationRatio");
|
||||
double minTrendBias = parseDouble(params[5], "minTrendBiasStrength");
|
||||
int trendSma = parseInt(params[6], "trendSmaPeriod");
|
||||
int rsiPeriod = parseInt(params[7], "rsiPeriod");
|
||||
double rsiThreshold = parseDouble(params[8], "rsiThreshold");
|
||||
int macdFast = parseInt(params[9], "macdFastPeriod");
|
||||
int macdSlow = parseInt(params[10], "macdSlowPeriod");
|
||||
double minRelativeSwing = parseDouble(params[11], "minRelativeSwing");
|
||||
|
||||
return new Config(direction, degree, minConfidence, minRiskReward, minAlternation, minTrendBias, trendSma,
|
||||
rsiPeriod, rsiThreshold, macdFast, macdSlow, minRelativeSwing);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return label parts used for NamedStrategy labels
|
||||
*/
|
||||
String[] labelParts() {
|
||||
return new String[] { direction.name(), degree.name(), formatDouble(minConfidence),
|
||||
formatDouble(minRiskReward), formatDouble(minAlternationRatio), formatDouble(minTrendBiasStrength),
|
||||
String.valueOf(trendSmaPeriod), String.valueOf(rsiPeriod), formatDouble(rsiThreshold),
|
||||
String.valueOf(macdFastPeriod), String.valueOf(macdSlowPeriod), formatDouble(minRelativeSwing) };
|
||||
}
|
||||
|
||||
/**
|
||||
* @return configured trade direction
|
||||
*/
|
||||
SignalDirection direction() {
|
||||
return direction;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return configured Elliott wave degree
|
||||
*/
|
||||
ElliottDegree degree() {
|
||||
return degree;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return minimum confidence threshold
|
||||
*/
|
||||
double minConfidence() {
|
||||
return minConfidence;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return minimum risk/reward ratio
|
||||
*/
|
||||
double minRiskReward() {
|
||||
return minRiskReward;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return minimum alternation ratio
|
||||
*/
|
||||
double minAlternationRatio() {
|
||||
return minAlternationRatio;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return minimum trend bias strength
|
||||
*/
|
||||
double minTrendBiasStrength() {
|
||||
return minTrendBiasStrength;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return trend SMA period
|
||||
*/
|
||||
int trendSmaPeriod() {
|
||||
return trendSmaPeriod;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return RSI period
|
||||
*/
|
||||
int rsiPeriod() {
|
||||
return rsiPeriod;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return RSI threshold
|
||||
*/
|
||||
double rsiThreshold() {
|
||||
return rsiThreshold;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return MACD fast period
|
||||
*/
|
||||
int macdFastPeriod() {
|
||||
return macdFastPeriod;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return MACD slow period
|
||||
*/
|
||||
int macdSlowPeriod() {
|
||||
return macdSlowPeriod;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return minimum relative swing magnitude
|
||||
*/
|
||||
double minRelativeSwing() {
|
||||
return minRelativeSwing;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an integer parameter.
|
||||
*
|
||||
* @param value parameter value
|
||||
* @param label parameter label
|
||||
* @return parsed integer
|
||||
*/
|
||||
private static int parseInt(final String value, final String label) {
|
||||
try {
|
||||
return Integer.parseInt(value);
|
||||
} catch (NumberFormatException ex) {
|
||||
throw new IllegalArgumentException("Invalid " + label + " value: '" + value + "'", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a double parameter.
|
||||
*
|
||||
* @param value parameter value
|
||||
* @param label parameter label
|
||||
* @return parsed double
|
||||
*/
|
||||
private static double parseDouble(final String value, final String label) {
|
||||
try {
|
||||
return Double.parseDouble(value);
|
||||
} catch (NumberFormatException ex) {
|
||||
throw new IllegalArgumentException("Invalid " + label + " value: '" + value + "'", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an enum parameter.
|
||||
*
|
||||
* @param value parameter value
|
||||
* @param type enum type
|
||||
* @param label parameter label
|
||||
* @param <E> enum type
|
||||
* @return parsed enum
|
||||
*/
|
||||
private static <E extends Enum<E>> E parseEnum(final String value, final Class<E> type, final String label) {
|
||||
try {
|
||||
return Enum.valueOf(type, value);
|
||||
} catch (IllegalArgumentException ex) {
|
||||
Set<String> allowed = enumNames(type);
|
||||
throw new IllegalArgumentException(
|
||||
"Invalid " + label + " value: '" + value + "'. Valid values are: " + String.join(", ", allowed),
|
||||
ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all allowed enum names for an error message.
|
||||
*
|
||||
* @param type enum type
|
||||
* @param <E> enum type
|
||||
* @return set of enum names
|
||||
*/
|
||||
private static <E extends Enum<E>> Set<String> enumNames(final Class<E> type) {
|
||||
if (type == null) {
|
||||
return Set.of();
|
||||
}
|
||||
return EnumSet.allOf(type).stream().map(Enum::name).collect(Collectors.toSet());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cached indicator that assembles scenario sets for each bar index.
|
||||
*/
|
||||
private static final class ScenarioSetIndicator extends CachedIndicator<ElliottScenarioSet> {
|
||||
|
||||
private final ElliottSwingIndicator swingIndicator;
|
||||
private final ElliottChannelIndicator channelIndicator;
|
||||
private final ElliottScenarioGenerator generator;
|
||||
private final ElliottSwingCompressor compressor;
|
||||
private final int scenarioSwingWindow;
|
||||
|
||||
/**
|
||||
* Creates a scenario indicator with the supplied dependencies.
|
||||
*
|
||||
* @param series bar series
|
||||
* @param swingIndicator swing detector indicator
|
||||
* @param channelIndicator channel indicator for scoring
|
||||
* @param generator scenario generator
|
||||
* @param compressor optional swing compressor
|
||||
* @param scenarioSwingWindow max number of swings to score
|
||||
*/
|
||||
private ScenarioSetIndicator(final BarSeries series, final ElliottSwingIndicator swingIndicator,
|
||||
final ElliottChannelIndicator channelIndicator, final ElliottScenarioGenerator generator,
|
||||
final ElliottSwingCompressor compressor, final int scenarioSwingWindow) {
|
||||
super(series);
|
||||
this.swingIndicator = Objects.requireNonNull(swingIndicator, "swingIndicator");
|
||||
this.channelIndicator = Objects.requireNonNull(channelIndicator, "channelIndicator");
|
||||
this.generator = Objects.requireNonNull(generator, "generator");
|
||||
this.compressor = compressor;
|
||||
this.scenarioSwingWindow = scenarioSwingWindow;
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the scenario set for the provided index.
|
||||
*
|
||||
* @param index bar index
|
||||
* @return scenario set for the index
|
||||
*/
|
||||
@Override
|
||||
protected ElliottScenarioSet calculate(final int index) {
|
||||
final BarSeries series = getBarSeries();
|
||||
if (series.isEmpty()) {
|
||||
throw new IllegalArgumentException("series cannot be empty");
|
||||
}
|
||||
int clampedIndex = Math.max(series.getBeginIndex(), Math.min(index, series.getEndIndex()));
|
||||
List<ElliottSwing> swings = swingIndicator.getValue(clampedIndex);
|
||||
if (compressor != null) {
|
||||
swings = compressor.compress(swings);
|
||||
}
|
||||
if (swings.isEmpty()) {
|
||||
return ElliottScenarioSet.empty(clampedIndex);
|
||||
}
|
||||
List<ElliottSwing> recent = swings;
|
||||
if (scenarioSwingWindow > 0 && swings.size() > scenarioSwingWindow) {
|
||||
recent = List.copyOf(swings.subList(swings.size() - scenarioSwingWindow, swings.size()));
|
||||
}
|
||||
return generator.generate(recent, swingIndicator.getDegree(), channelIndicator.getValue(clampedIndex),
|
||||
clampedIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the number of unstable bars for the underlying swing indicator
|
||||
*/
|
||||
@Override
|
||||
public int getCountOfUnstableBars() {
|
||||
return swingIndicator.getCountOfUnstableBars();
|
||||
}
|
||||
}
|
||||
}
|
||||
+155
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
package ta4jexamples.strategies;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.ta4j.core.BarSeries;
|
||||
import org.ta4j.core.Strategy;
|
||||
import org.ta4j.core.indicators.helpers.DateTimeIndicator;
|
||||
import org.ta4j.core.rules.HourOfDayRule;
|
||||
import org.ta4j.core.strategy.named.NamedStrategy;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A trading strategy that enters and exits positions based on specific hours of
|
||||
* the day.
|
||||
*
|
||||
* <p>
|
||||
* This strategy uses {@link HourOfDayRule} to determine entry and exit signals
|
||||
* based on the hour of the day (0-23) of each bar in the series. The strategy
|
||||
* will enter a position when the bar's hour of the day matches the specified
|
||||
* entry hour, and exit when it matches the specified exit hour.
|
||||
*
|
||||
* <p>
|
||||
* The strategy name is automatically generated as
|
||||
* {@code "HourOfDayStrategy_<entryHour>_<exitHour>"} (e.g.,
|
||||
* "HourOfDayStrategy_9_17").
|
||||
*
|
||||
* <p>
|
||||
* This strategy is useful for testing intraday trading patterns, such as market
|
||||
* open/close effects or specific trading hours in different time zones.
|
||||
*
|
||||
* @since 0.19
|
||||
*/
|
||||
public class HourOfDayStrategy extends NamedStrategy {
|
||||
|
||||
static {
|
||||
registerImplementation(HourOfDayStrategy.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new HourOfDayStrategy with the specified entry and exit hours.
|
||||
*
|
||||
* @param series the bar series to analyze
|
||||
* @param entryHour the hour of the day (0-23) to enter positions
|
||||
* @param exitHour the hour of the day (0-23) to exit positions
|
||||
* @throws IllegalArgumentException if series is null, if entryHour or exitHour
|
||||
* is not in range 0-23, or if entryHour equals
|
||||
* exitHour
|
||||
*/
|
||||
public HourOfDayStrategy(BarSeries series, int entryHour, int exitHour) {
|
||||
super(NamedStrategy.buildLabel(HourOfDayStrategy.class, String.valueOf(entryHour), String.valueOf(exitHour)),
|
||||
new HourOfDayRule(new DateTimeIndicator(series), entryHour),
|
||||
new HourOfDayRule(new DateTimeIndicator(series), exitHour));
|
||||
if (entryHour == exitHour) {
|
||||
throw new IllegalArgumentException(
|
||||
"Entry hour and exit hour must be different, but both were: " + entryHour);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new HourOfDayStrategy from string parameters.
|
||||
*
|
||||
* <p>
|
||||
* The parameters should be two strings representing the entry and exit hours of
|
||||
* the day. Valid values are integers in the range 0-23 (inclusive).
|
||||
*
|
||||
* @param series the bar series to analyze
|
||||
* @param params array containing [entryHour, exitHour] as strings
|
||||
* @throws IllegalArgumentException if params is null, has fewer than 2
|
||||
* elements, contains invalid hour values, or
|
||||
* if entryHour equals exitHour
|
||||
*/
|
||||
public HourOfDayStrategy(BarSeries series, String... params) {
|
||||
this(series, parseEntryHour(params), parseExitHour(params));
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds all possible strategy permutations for all combinations of entry and
|
||||
* exit hours.
|
||||
*
|
||||
* <p>
|
||||
* This method generates strategies for all pairs of different hours of the day
|
||||
* (24 * 23 = 552 total strategies). Strategies where the entry hour equals the
|
||||
* exit hour are excluded. If any strategy construction fails, a warning is
|
||||
* logged and that strategy is skipped.
|
||||
*
|
||||
* @param series the bar series to analyze
|
||||
* @return a list of all valid HourOfDayStrategy permutations
|
||||
*/
|
||||
public static List<Strategy> buildAllStrategyPermutations(BarSeries series) {
|
||||
List<String[]> permutations = new ArrayList<>();
|
||||
|
||||
for (int entryHour = 0; entryHour < 24; entryHour++) {
|
||||
for (int exitHour = 0; exitHour < 24; exitHour++) {
|
||||
if (entryHour != exitHour) {
|
||||
permutations.add(new String[] { String.valueOf(entryHour), String.valueOf(exitHour) });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return NamedStrategy.buildAllStrategyPermutations(series, permutations, HourOfDayStrategy::new,
|
||||
(params, error) -> {
|
||||
String entry = params.length > 0 ? params[0] : "<missing>";
|
||||
String exit = params.length > 1 ? params[1] : "<missing>";
|
||||
LogManager.getLogger()
|
||||
.warn("Failed to build strategy for entry hour {} and exit hour {} - {}", entry, exit,
|
||||
error.getMessage());
|
||||
});
|
||||
}
|
||||
|
||||
private static int parseEntryHour(String... params) {
|
||||
if (params == null) {
|
||||
throw new IllegalArgumentException("Params cannot be null");
|
||||
}
|
||||
if (params.length < 1) {
|
||||
throw new IllegalArgumentException(
|
||||
"At least 2 parameters required (entryHour, exitHour), but got " + params.length);
|
||||
}
|
||||
try {
|
||||
int hour = Integer.parseInt(params[0]);
|
||||
if (hour < 0 || hour > 23) {
|
||||
throw new IllegalArgumentException("Invalid entry hour value: '" + params[0]
|
||||
+ "'. Valid values are integers in the range 0-23 (inclusive)");
|
||||
}
|
||||
return hour;
|
||||
} catch (NumberFormatException e) {
|
||||
throw new IllegalArgumentException("Invalid entry hour value: '" + params[0]
|
||||
+ "'. Valid values are integers in the range 0-23 (inclusive)", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static int parseExitHour(String... params) {
|
||||
if (params == null) {
|
||||
throw new IllegalArgumentException("Params cannot be null");
|
||||
}
|
||||
if (params.length < 2) {
|
||||
throw new IllegalArgumentException(
|
||||
"At least 2 parameters required (entryHour, exitHour), but got " + params.length);
|
||||
}
|
||||
try {
|
||||
int hour = Integer.parseInt(params[1]);
|
||||
if (hour < 0 || hour > 23) {
|
||||
throw new IllegalArgumentException("Invalid exit hour value: '" + params[1]
|
||||
+ "'. Valid values are integers in the range 0-23 (inclusive)");
|
||||
}
|
||||
return hour;
|
||||
} catch (NumberFormatException e) {
|
||||
throw new IllegalArgumentException("Invalid exit hour value: '" + params[1]
|
||||
+ "'. Valid values are integers in the range 0-23 (inclusive)", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
package ta4jexamples.strategies;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.jfree.chart.JFreeChart;
|
||||
import org.ta4j.core.BarSeries;
|
||||
import org.ta4j.core.BaseStrategy;
|
||||
import org.ta4j.core.Indicator;
|
||||
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.pnl.GrossReturnCriterion;
|
||||
import org.ta4j.core.indicators.macd.MACDHistogramMode;
|
||||
import org.ta4j.core.indicators.macd.MACDVMomentumProfile;
|
||||
import org.ta4j.core.indicators.macd.MACDVMomentumState;
|
||||
import org.ta4j.core.indicators.macd.VolatilityNormalizedMACDIndicator;
|
||||
import org.ta4j.core.indicators.averages.SMAIndicator;
|
||||
import org.ta4j.core.indicators.helpers.ClosePriceIndicator;
|
||||
import org.ta4j.core.indicators.numeric.NumericIndicator;
|
||||
import org.ta4j.core.num.Num;
|
||||
import org.ta4j.core.rules.OverIndicatorRule;
|
||||
import org.ta4j.core.rules.UnderIndicatorRule;
|
||||
import ta4jexamples.charting.workflow.ChartWorkflow;
|
||||
import ta4jexamples.datasources.BitStampCsvTradesFileBarSeriesDataSource;
|
||||
|
||||
/**
|
||||
* Strategy showing the volatility-normalized MACD-V workflow:
|
||||
*
|
||||
* <ul>
|
||||
* <li>custom signal-line injection ({@link SMAIndicator})</li>
|
||||
* <li>histogram polarity configuration</li>
|
||||
* <li>momentum-state filtering via {@link MACDVMomentumProfile}</li>
|
||||
* </ul>
|
||||
*/
|
||||
public class MACDVMomentumStateStrategy {
|
||||
|
||||
private static final Logger LOG = LogManager.getLogger(MACDVMomentumStateStrategy.class);
|
||||
|
||||
private static final int FAST_EMA = 12;
|
||||
private static final int SLOW_EMA = 26;
|
||||
private static final int ATR_PERIOD = 26;
|
||||
private static final int SIGNAL_PERIOD = 9;
|
||||
|
||||
private static final MACDVMomentumProfile MOMENTUM_PROFILE = new MACDVMomentumProfile(25, 80, -25, -80);
|
||||
|
||||
/**
|
||||
* @param series bar series
|
||||
* @return strategy based on volatility-normalized MACD-V momentum-state helpers
|
||||
*/
|
||||
public static Strategy buildStrategy(BarSeries series) {
|
||||
if (series == null) {
|
||||
throw new IllegalArgumentException("Series cannot be null");
|
||||
}
|
||||
|
||||
ClosePriceIndicator closePrice = new ClosePriceIndicator(series);
|
||||
VolatilityNormalizedMACDIndicator macdV = new VolatilityNormalizedMACDIndicator(closePrice, FAST_EMA, SLOW_EMA,
|
||||
ATR_PERIOD, SIGNAL_PERIOD, 100);
|
||||
NumericIndicator histogram = macdV.getHistogram(SIGNAL_PERIOD, SMAIndicator::new,
|
||||
MACDHistogramMode.MACD_MINUS_SIGNAL);
|
||||
|
||||
Rule bullishMomentum = macdV.inMomentumState(MOMENTUM_PROFILE, MACDVMomentumState.RALLYING_OR_RETRACING)
|
||||
.or(macdV.inMomentumState(MOMENTUM_PROFILE, MACDVMomentumState.HIGH_RISK));
|
||||
Rule bearishMomentum = macdV.inMomentumState(MOMENTUM_PROFILE, MACDVMomentumState.REBOUNDING_OR_REVERSING)
|
||||
.or(macdV.inMomentumState(MOMENTUM_PROFILE, MACDVMomentumState.LOW_RISK));
|
||||
|
||||
Rule entryRule = macdV.crossedUpSignal(SIGNAL_PERIOD, SMAIndicator::new)
|
||||
.and(new OverIndicatorRule(histogram, 0))
|
||||
.and(bullishMomentum);
|
||||
Rule exitRule = macdV.crossedDownSignal(SIGNAL_PERIOD, SMAIndicator::new)
|
||||
.or(new UnderIndicatorRule(histogram, 0))
|
||||
.or(bearishMomentum);
|
||||
|
||||
return new BaseStrategy(MACDVMomentumStateStrategy.class.getSimpleName(), entryRule, exitRule,
|
||||
macdV.getCountOfUnstableBars());
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
||||
BarSeries series = BitStampCsvTradesFileBarSeriesDataSource.loadBitstampSeries();
|
||||
Strategy strategy = buildStrategy(series);
|
||||
|
||||
BarSeriesManager seriesManager = new BarSeriesManager(series);
|
||||
TradingRecord tradingRecord = seriesManager.run(strategy);
|
||||
LOG.debug(() -> strategy.toJson());
|
||||
LOG.debug("{}'s number of positions: {}", strategy.getName(), tradingRecord.getPositionCount());
|
||||
|
||||
Num grossReturn = new GrossReturnCriterion().calculate(series, tradingRecord);
|
||||
LOG.debug("{}'s gross return: {}", strategy.getName(), grossReturn);
|
||||
|
||||
VolatilityNormalizedMACDIndicator macdV = new VolatilityNormalizedMACDIndicator(series, FAST_EMA, SLOW_EMA,
|
||||
ATR_PERIOD, SIGNAL_PERIOD, 100);
|
||||
Indicator<Num> signal = macdV.getSignalLine(SIGNAL_PERIOD, SMAIndicator::new);
|
||||
NumericIndicator histogram = macdV.getHistogram(SIGNAL_PERIOD, SMAIndicator::new,
|
||||
MACDHistogramMode.MACD_MINUS_SIGNAL);
|
||||
|
||||
ChartWorkflow chartWorkflow = new ChartWorkflow();
|
||||
JFreeChart chart = chartWorkflow.builder()
|
||||
.withSeries(series)
|
||||
.withTradingRecordOverlay(tradingRecord)
|
||||
.withSubChart(macdV)
|
||||
.withIndicatorOverlay(signal)
|
||||
.withSubChart(histogram)
|
||||
.withAnalysisCriterionOverlay(new GrossReturnCriterion(), tradingRecord)
|
||||
.toChart();
|
||||
chartWorkflow.displayChart(chart);
|
||||
chartWorkflow.saveChartImage(chart, series, "macdv-momentum-state-strategy", "temp/charts");
|
||||
}
|
||||
}
|
||||
+156
@@ -0,0 +1,156 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
package ta4jexamples.strategies;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.ta4j.core.BarSeries;
|
||||
import org.ta4j.core.Strategy;
|
||||
import org.ta4j.core.indicators.helpers.DateTimeIndicator;
|
||||
import org.ta4j.core.rules.MinuteOfHourRule;
|
||||
import org.ta4j.core.strategy.named.NamedStrategy;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A trading strategy that enters and exits positions based on specific minutes
|
||||
* of the hour.
|
||||
*
|
||||
* <p>
|
||||
* This strategy uses {@link MinuteOfHourRule} to determine entry and exit
|
||||
* signals based on the minute of the hour (0-59) of each bar in the series. The
|
||||
* strategy will enter a position when the bar's minute of the hour matches the
|
||||
* specified entry minute, and exit when it matches the specified exit minute.
|
||||
*
|
||||
* <p>
|
||||
* The strategy name is automatically generated as
|
||||
* {@code "MinuteOfHourStrategy_<entryMinute>_<exitMinute>"} (e.g.,
|
||||
* "MinuteOfHourStrategy_15_45").
|
||||
*
|
||||
* <p>
|
||||
* This strategy is useful for testing intraday trading patterns at a finer
|
||||
* granularity, such as specific minute-based entry/exit points within an hour.
|
||||
*
|
||||
* @since 0.19
|
||||
*/
|
||||
public class MinuteOfHourStrategy extends NamedStrategy {
|
||||
|
||||
static {
|
||||
registerImplementation(MinuteOfHourStrategy.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new MinuteOfHourStrategy with the specified entry and exit
|
||||
* minutes.
|
||||
*
|
||||
* @param series the bar series to analyze
|
||||
* @param entryMinute the minute of the hour (0-59) to enter positions
|
||||
* @param exitMinute the minute of the hour (0-59) to exit positions
|
||||
* @throws IllegalArgumentException if series is null, if entryMinute or
|
||||
* exitMinute is not in range 0-59, or if
|
||||
* entryMinute equals exitMinute
|
||||
*/
|
||||
public MinuteOfHourStrategy(BarSeries series, int entryMinute, int exitMinute) {
|
||||
super(NamedStrategy.buildLabel(MinuteOfHourStrategy.class, String.valueOf(entryMinute),
|
||||
String.valueOf(exitMinute)), new MinuteOfHourRule(new DateTimeIndicator(series), entryMinute),
|
||||
new MinuteOfHourRule(new DateTimeIndicator(series), exitMinute));
|
||||
if (entryMinute == exitMinute) {
|
||||
throw new IllegalArgumentException(
|
||||
"Entry minute and exit minute must be different, but both were: " + entryMinute);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new MinuteOfHourStrategy from string parameters.
|
||||
*
|
||||
* <p>
|
||||
* The parameters should be two strings representing the entry and exit minutes
|
||||
* of the hour. Valid values are integers in the range 0-59 (inclusive).
|
||||
*
|
||||
* @param series the bar series to analyze
|
||||
* @param params array containing [entryMinute, exitMinute] as strings
|
||||
* @throws IllegalArgumentException if params is null, has fewer than 2
|
||||
* elements, contains invalid minute values, or
|
||||
* if entryMinute equals exitMinute
|
||||
*/
|
||||
public MinuteOfHourStrategy(BarSeries series, String... params) {
|
||||
this(series, parseEntryMinute(params), parseExitMinute(params));
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds all possible strategy permutations for all combinations of entry and
|
||||
* exit minutes.
|
||||
*
|
||||
* <p>
|
||||
* This method generates strategies for all pairs of different minutes of the
|
||||
* hour (60 * 59 = 3540 total strategies). Strategies where the entry minute
|
||||
* equals the exit minute are excluded. If any strategy construction fails, a
|
||||
* warning is logged and that strategy is skipped.
|
||||
*
|
||||
* @param series the bar series to analyze
|
||||
* @return a list of all valid MinuteOfHourStrategy permutations
|
||||
*/
|
||||
public static List<Strategy> buildAllStrategyPermutations(BarSeries series) {
|
||||
List<String[]> permutations = new ArrayList<>();
|
||||
|
||||
for (int entryMinute = 0; entryMinute < 60; entryMinute++) {
|
||||
for (int exitMinute = 0; exitMinute < 60; exitMinute++) {
|
||||
if (entryMinute != exitMinute) {
|
||||
permutations.add(new String[] { String.valueOf(entryMinute), String.valueOf(exitMinute) });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return NamedStrategy.buildAllStrategyPermutations(series, permutations, MinuteOfHourStrategy::new,
|
||||
(params, error) -> {
|
||||
String entry = params.length > 0 ? params[0] : "<missing>";
|
||||
String exit = params.length > 1 ? params[1] : "<missing>";
|
||||
LogManager.getLogger()
|
||||
.warn("Failed to build strategy for entry minute {} and exit minute {} - {}", entry, exit,
|
||||
error.getMessage());
|
||||
});
|
||||
}
|
||||
|
||||
private static int parseEntryMinute(String... params) {
|
||||
if (params == null) {
|
||||
throw new IllegalArgumentException("Params cannot be null");
|
||||
}
|
||||
if (params.length < 1) {
|
||||
throw new IllegalArgumentException(
|
||||
"At least 2 parameters required (entryMinute, exitMinute), but got " + params.length);
|
||||
}
|
||||
try {
|
||||
int minute = Integer.parseInt(params[0]);
|
||||
if (minute < 0 || minute > 59) {
|
||||
throw new IllegalArgumentException("Invalid entry minute value: '" + params[0]
|
||||
+ "'. Valid values are integers in the range 0-59 (inclusive)");
|
||||
}
|
||||
return minute;
|
||||
} catch (NumberFormatException e) {
|
||||
throw new IllegalArgumentException("Invalid entry minute value: '" + params[0]
|
||||
+ "'. Valid values are integers in the range 0-59 (inclusive)", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static int parseExitMinute(String... params) {
|
||||
if (params == null) {
|
||||
throw new IllegalArgumentException("Params cannot be null");
|
||||
}
|
||||
if (params.length < 2) {
|
||||
throw new IllegalArgumentException(
|
||||
"At least 2 parameters required (entryMinute, exitMinute), but got " + params.length);
|
||||
}
|
||||
try {
|
||||
int minute = Integer.parseInt(params[1]);
|
||||
if (minute < 0 || minute > 59) {
|
||||
throw new IllegalArgumentException("Invalid exit minute value: '" + params[1]
|
||||
+ "'. Valid values are integers in the range 0-59 (inclusive)");
|
||||
}
|
||||
return minute;
|
||||
} catch (NumberFormatException e) {
|
||||
throw new IllegalArgumentException("Invalid exit minute value: '" + params[1]
|
||||
+ "'. Valid values are integers in the range 0-59 (inclusive)", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
package ta4jexamples.strategies;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.jfree.chart.JFreeChart;
|
||||
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.pnl.GrossReturnCriterion;
|
||||
import org.ta4j.core.indicators.MACDIndicator;
|
||||
import org.ta4j.core.indicators.StochasticOscillatorKIndicator;
|
||||
import org.ta4j.core.indicators.averages.EMAIndicator;
|
||||
import org.ta4j.core.indicators.helpers.ClosePriceIndicator;
|
||||
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.BitStampCsvTradesFileBarSeriesDataSource;
|
||||
|
||||
/**
|
||||
* Moving momentum strategy.
|
||||
*
|
||||
* @see <a href=
|
||||
* "http://stockcharts.com/help/doku.php?id=chart_school:trading_strategies:moving_momentum">
|
||||
* http://stockcharts.com/help/doku.php?id=chart_school:trading_strategies:moving_momentum</a>
|
||||
*/
|
||||
public class MovingMomentumStrategy {
|
||||
|
||||
private static final Logger LOG = LogManager.getLogger(MovingMomentumStrategy.class);
|
||||
|
||||
/**
|
||||
* @param series the bar series
|
||||
* @return the moving momentum strategy
|
||||
*/
|
||||
public static Strategy buildStrategy(BarSeries series) {
|
||||
if (series == null) {
|
||||
throw new IllegalArgumentException("Series cannot be null");
|
||||
}
|
||||
|
||||
ClosePriceIndicator closePrice = new ClosePriceIndicator(series);
|
||||
|
||||
// The bias is bullish when the shorter-moving average moves above the longer
|
||||
// moving average.
|
||||
// The bias is bearish when the shorter-moving average moves below the longer
|
||||
// moving average.
|
||||
EMAIndicator shortEma = new EMAIndicator(closePrice, 9);
|
||||
EMAIndicator longEma = new EMAIndicator(closePrice, 26);
|
||||
|
||||
StochasticOscillatorKIndicator stochasticOscillK = new StochasticOscillatorKIndicator(series, 14);
|
||||
|
||||
MACDIndicator macd = new MACDIndicator(closePrice, 9, 26);
|
||||
EMAIndicator emaMacd = new EMAIndicator(macd, 18);
|
||||
|
||||
// Entry rule
|
||||
Rule entryRule = new OverIndicatorRule(shortEma, longEma) // Trend
|
||||
.and(new CrossedDownIndicatorRule(stochasticOscillK, 20)) // Signal 1
|
||||
.and(new OverIndicatorRule(macd, emaMacd)); // Signal 2
|
||||
|
||||
// Exit rule
|
||||
Rule exitRule = new UnderIndicatorRule(shortEma, longEma) // Trend
|
||||
.and(new CrossedUpIndicatorRule(stochasticOscillK, 80)) // Signal 1
|
||||
.and(new UnderIndicatorRule(macd, emaMacd)); // Signal 2
|
||||
|
||||
String strategyName = "MovingMomentumStrategy";
|
||||
return new BaseStrategy(strategyName, entryRule, exitRule);
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
||||
// Getting the bar series
|
||||
BarSeries series = BitStampCsvTradesFileBarSeriesDataSource.loadBitstampSeries();
|
||||
|
||||
// Building the trading strategy
|
||||
Strategy strategy = buildStrategy(series);
|
||||
|
||||
// Running the strategy
|
||||
BarSeriesManager seriesManager = new BarSeriesManager(series);
|
||||
TradingRecord tradingRecord = seriesManager.run(strategy);
|
||||
LOG.debug(() -> strategy.toJson());
|
||||
LOG.debug("{}'s number of positions: {}", strategy.getName(), tradingRecord.getPositionCount());
|
||||
|
||||
// Analysis
|
||||
var grossReturn = new GrossReturnCriterion().calculate(series, tradingRecord);
|
||||
LOG.debug("{}'s gross return: {}", strategy.getName(), grossReturn);
|
||||
|
||||
ClosePriceIndicator closePrice = new ClosePriceIndicator(series);
|
||||
EMAIndicator shortEma = new EMAIndicator(closePrice, 9);
|
||||
EMAIndicator longEma = new EMAIndicator(closePrice, 26);
|
||||
MACDIndicator macd = new MACDIndicator(closePrice, 9, 26);
|
||||
EMAIndicator emaMacd = new EMAIndicator(macd, 18);
|
||||
StochasticOscillatorKIndicator stochasticOscillK = new StochasticOscillatorKIndicator(series, 14);
|
||||
|
||||
// Charting
|
||||
ChartWorkflow chartWorkflow = new ChartWorkflow();
|
||||
JFreeChart chart = chartWorkflow.builder()
|
||||
.withSeries(series)
|
||||
.withTradingRecordOverlay(tradingRecord)
|
||||
.withIndicatorOverlay(shortEma)
|
||||
.withIndicatorOverlay(longEma)
|
||||
.withSubChart(macd)
|
||||
.withIndicatorOverlay(emaMacd)
|
||||
.withSubChart(stochasticOscillK)
|
||||
.toChart();
|
||||
chartWorkflow.displayChart(chart);
|
||||
chartWorkflow.saveChartImage(chart, series, "moving-momentum-strategy", "temp/charts");
|
||||
}
|
||||
}
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
package ta4jexamples.strategies;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.jfree.chart.JFreeChart;
|
||||
import org.ta4j.core.*;
|
||||
import org.ta4j.core.backtest.BarSeriesManager;
|
||||
import org.ta4j.core.criteria.pnl.NetProfitCriterion;
|
||||
import org.ta4j.core.criteria.pnl.NetProfitLossCriterion;
|
||||
import org.ta4j.core.indicators.NetMomentumIndicator;
|
||||
import org.ta4j.core.indicators.RSIIndicator;
|
||||
import org.ta4j.core.indicators.helpers.ClosePriceIndicator;
|
||||
import org.ta4j.core.rules.CrossedDownIndicatorRule;
|
||||
import org.ta4j.core.rules.CrossedUpIndicatorRule;
|
||||
import java.awt.Color;
|
||||
import ta4jexamples.charting.workflow.ChartWorkflow;
|
||||
import ta4jexamples.datasources.JsonFileBarSeriesDataSource;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Objects;
|
||||
|
||||
public class NetMomentumStrategy {
|
||||
|
||||
private static final Logger LOG = LogManager.getLogger(NetMomentumStrategy.class);
|
||||
|
||||
private static final int DEFAULT_OVERBOUGHT_THRESHOLD = 900;
|
||||
private static final int DEFAULT_MOMENTUM_TIMEFRAME = 200;
|
||||
private static final int DEFAULT_OVERSOLD_THRESHOLD = -200;
|
||||
private static final int DEFAULT_RSI_BARCOUNT = 14;
|
||||
private static final double DEFAULT_DECAY_FACTOR = 1;
|
||||
|
||||
public static void main(String[] args) {
|
||||
String jsonOhlcResourceFile = "Coinbase-ETH-USD-PT1D-20160517_20251028.json";
|
||||
|
||||
BarSeries series = null;
|
||||
try (InputStream resourceStream = NetMomentumStrategy.class.getClassLoader()
|
||||
.getResourceAsStream(jsonOhlcResourceFile)) {
|
||||
series = JsonFileBarSeriesDataSource.DEFAULT_INSTANCE.loadSeries(resourceStream);
|
||||
} catch (IOException ex) {
|
||||
LOG.error("IOException while loading resource: {} - {}", jsonOhlcResourceFile, ex.getMessage());
|
||||
}
|
||||
|
||||
Objects.requireNonNull(series, "Bar series was null");
|
||||
|
||||
// Running the strategy
|
||||
runSingleStrategy(series);
|
||||
}
|
||||
|
||||
private static void runSingleStrategy(BarSeries series) {
|
||||
BarSeriesManager seriesManager = new BarSeriesManager(series);
|
||||
|
||||
ClosePriceIndicator closePriceIndicator = new ClosePriceIndicator(series);
|
||||
RSIIndicator rsiIndicator = new RSIIndicator(closePriceIndicator, DEFAULT_RSI_BARCOUNT);
|
||||
NetMomentumIndicator rsiM = NetMomentumIndicator.forRsiWithDecay(rsiIndicator, DEFAULT_MOMENTUM_TIMEFRAME,
|
||||
DEFAULT_DECAY_FACTOR);
|
||||
Strategy strategy = createStrategy(rsiM);
|
||||
|
||||
TradingRecord tradingRecord = seriesManager.run(strategy);
|
||||
LOG.debug(() -> strategy.toJson());
|
||||
LOG.debug("{}'s number of positions: {}", strategy.getName(), tradingRecord.getPositionCount());
|
||||
|
||||
var netProfitLoss = new NetProfitLossCriterion().calculate(series, tradingRecord);
|
||||
LOG.debug("{}'s net profit/loss: {}", strategy.getName(), netProfitLoss);
|
||||
|
||||
// Charting
|
||||
ChartWorkflow chartWorkflow = new ChartWorkflow();
|
||||
JFreeChart chart = chartWorkflow.builder()
|
||||
.withSeries(series)
|
||||
.withTradingRecordOverlay(tradingRecord)
|
||||
.withAnalysisCriterionOverlay(new NetProfitCriterion(), tradingRecord)
|
||||
.withSubChart(rsiIndicator)
|
||||
.withHorizontalMarker(50)
|
||||
.withLineColor(Color.GRAY)
|
||||
.withOpacity(0.3f)
|
||||
.withHorizontalMarker(70)
|
||||
.withLineColor(Color.RED)
|
||||
.withOpacity(0.3f)
|
||||
.withHorizontalMarker(30)
|
||||
.withLineColor(Color.GREEN)
|
||||
.withOpacity(0.3f)
|
||||
.withSubChart(rsiM)
|
||||
.withHorizontalMarker(0)
|
||||
.withLineColor(Color.GRAY)
|
||||
.withOpacity(0.3f)
|
||||
.toChart();
|
||||
chartWorkflow.displayChart(chart);
|
||||
chartWorkflow.saveChartImage(chart, series, "net-momentum-strategy", "temp/charts");
|
||||
}
|
||||
|
||||
private static Strategy createStrategy(NetMomentumIndicator rsiM) {
|
||||
Rule entryRule = new CrossedUpIndicatorRule(rsiM, DEFAULT_OVERSOLD_THRESHOLD);
|
||||
Rule exitRule = new CrossedDownIndicatorRule(rsiM, DEFAULT_OVERBOUGHT_THRESHOLD);
|
||||
|
||||
return new BaseStrategy(NetMomentumStrategy.class.getSimpleName(), entryRule, exitRule);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
package ta4jexamples.strategies;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.jfree.chart.JFreeChart;
|
||||
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.pnl.GrossReturnCriterion;
|
||||
import org.ta4j.core.indicators.RSIIndicator;
|
||||
import org.ta4j.core.indicators.averages.SMAIndicator;
|
||||
import org.ta4j.core.indicators.helpers.ClosePriceIndicator;
|
||||
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.BitStampCsvTradesFileBarSeriesDataSource;
|
||||
|
||||
/**
|
||||
* 2-Period RSI Strategy
|
||||
*
|
||||
* @see <a href=
|
||||
* "http://stockcharts.com/school/doku.php?id=chart_school:trading_strategies:rsi2">
|
||||
* http://stockcharts.com/school/doku.php?id=chart_school:trading_strategies:rsi2</a>
|
||||
*/
|
||||
public class RSI2Strategy {
|
||||
|
||||
private static final Logger LOG = LogManager.getLogger(RSI2Strategy.class);
|
||||
|
||||
/**
|
||||
* @param series a bar series
|
||||
* @return a 2-period RSI strategy
|
||||
*/
|
||||
public static Strategy buildStrategy(BarSeries series) {
|
||||
if (series == null) {
|
||||
throw new IllegalArgumentException("Series cannot be null");
|
||||
}
|
||||
|
||||
ClosePriceIndicator closePrice = new ClosePriceIndicator(series);
|
||||
SMAIndicator shortSma = new SMAIndicator(closePrice, 5);
|
||||
SMAIndicator longSma = new SMAIndicator(closePrice, 200);
|
||||
|
||||
// We use a 2-period RSI indicator to identify buying
|
||||
// or selling opportunities within the bigger trend.
|
||||
RSIIndicator rsi = new RSIIndicator(closePrice, 2);
|
||||
|
||||
// Entry rule
|
||||
// The long-term trend is up when a security is above its 200-period SMA.
|
||||
Rule entryRule = new OverIndicatorRule(shortSma, longSma) // Trend
|
||||
.and(new CrossedDownIndicatorRule(rsi, 5)) // Signal 1
|
||||
.and(new OverIndicatorRule(shortSma, closePrice)); // Signal 2
|
||||
|
||||
// Exit rule
|
||||
// The long-term trend is down when a security is below its 200-period SMA.
|
||||
Rule exitRule = new UnderIndicatorRule(shortSma, longSma) // Trend
|
||||
.and(new CrossedUpIndicatorRule(rsi, 95)) // Signal 1
|
||||
.and(new UnderIndicatorRule(shortSma, closePrice)); // Signal 2
|
||||
|
||||
String strategyName = "RSI2Strategy";
|
||||
return new BaseStrategy(strategyName, entryRule, exitRule);
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
||||
// Getting the bar series
|
||||
BarSeries series = BitStampCsvTradesFileBarSeriesDataSource.loadBitstampSeries();
|
||||
|
||||
// Building the trading strategy
|
||||
Strategy strategy = buildStrategy(series);
|
||||
|
||||
// Running the strategy
|
||||
BarSeriesManager seriesManager = new BarSeriesManager(series);
|
||||
TradingRecord tradingRecord = seriesManager.run(strategy);
|
||||
LOG.debug(() -> strategy.toJson());
|
||||
LOG.debug("{}'s number of positions: {}", strategy.getName(), tradingRecord.getPositionCount());
|
||||
|
||||
// Analysis
|
||||
var grossReturn = new GrossReturnCriterion().calculate(series, tradingRecord);
|
||||
LOG.debug("{}'s gross return: {}", strategy.getName(), grossReturn);
|
||||
|
||||
ClosePriceIndicator closePrice = new ClosePriceIndicator(series);
|
||||
SMAIndicator shortSma = new SMAIndicator(closePrice, 5);
|
||||
SMAIndicator longSma = new SMAIndicator(closePrice, 200);
|
||||
RSIIndicator rsiOverlay = new RSIIndicator(closePrice, 2);
|
||||
|
||||
// Charting
|
||||
ChartWorkflow chartWorkflow = new ChartWorkflow();
|
||||
JFreeChart chart = chartWorkflow.builder()
|
||||
.withSeries(series)
|
||||
.withTradingRecordOverlay(tradingRecord)
|
||||
.withIndicatorOverlay(shortSma)
|
||||
.withIndicatorOverlay(longSma)
|
||||
.withAnalysisCriterionOverlay(new GrossReturnCriterion(), tradingRecord)
|
||||
.withSubChart(rsiOverlay)
|
||||
.toChart();
|
||||
chartWorkflow.displayChart(chart);
|
||||
chartWorkflow.saveChartImage(chart, series, "rsi2-strategy", "temp/charts");
|
||||
}
|
||||
|
||||
}
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
package ta4jexamples.strategies;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.ta4j.core.BarSeries;
|
||||
import org.ta4j.core.BaseBarSeriesBuilder;
|
||||
import org.ta4j.core.BaseStrategy;
|
||||
import org.ta4j.core.Indicator;
|
||||
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.indicators.averages.SMAIndicator;
|
||||
import org.ta4j.core.indicators.helpers.ClosePriceIndicator;
|
||||
import org.ta4j.core.indicators.helpers.UnstableIndicator;
|
||||
import org.ta4j.core.num.Num;
|
||||
import org.ta4j.core.rules.CrossedDownIndicatorRule;
|
||||
import org.ta4j.core.rules.CrossedUpIndicatorRule;
|
||||
|
||||
public class UnstableIndicatorStrategy {
|
||||
|
||||
private static final Logger LOG = LogManager.getLogger(UnstableIndicatorStrategy.class);
|
||||
|
||||
public static final Duration MINUTE = Duration.ofMinutes(1);
|
||||
|
||||
public static final Instant TIME = Instant.parse("2020-01-01T00:00:00Z");
|
||||
|
||||
public static Strategy buildStrategy(BarSeries series) {
|
||||
ClosePriceIndicator close = new ClosePriceIndicator(series);
|
||||
int smaPeriod = 3;
|
||||
Indicator<Num> sma = new UnstableIndicator(new SMAIndicator(close, smaPeriod), smaPeriod - 1);
|
||||
|
||||
Rule entryRule = new CrossedUpIndicatorRule(close, sma);
|
||||
Rule exitRule = new CrossedDownIndicatorRule(close, sma);
|
||||
|
||||
BaseStrategy strategy = new BaseStrategy(entryRule, exitRule);
|
||||
strategy.setUnstableBars(3);
|
||||
return strategy;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
inappropriateTrade();
|
||||
appropriateTrade();
|
||||
}
|
||||
|
||||
public static void inappropriateTrade() {
|
||||
// Should not trade
|
||||
test("Inappropriate trade", Stream.of(10d, 2d, 6d, 16d, 8d));
|
||||
}
|
||||
|
||||
public static void appropriateTrade() {
|
||||
// Should trade
|
||||
test("Appropriate trade", Stream.of(10d, 8d, 6d, 16d, 8d));
|
||||
}
|
||||
|
||||
public static void test(String name, Stream<Double> closePrices) {
|
||||
// Getting the bar series
|
||||
BarSeries series = new BaseBarSeriesBuilder().build();
|
||||
|
||||
Instant[] currentTime = { TIME };
|
||||
closePrices.forEach(close -> {
|
||||
series.barBuilder()
|
||||
.timePeriod(MINUTE)
|
||||
.endTime(currentTime[0])
|
||||
.openPrice(0)
|
||||
.closePrice(close)
|
||||
.highPrice(0)
|
||||
.lowPrice(0)
|
||||
.add();
|
||||
currentTime[0] = currentTime[0].plus(MINUTE);
|
||||
});
|
||||
|
||||
// Building the trading strategy
|
||||
Strategy strategy = buildStrategy(series);
|
||||
|
||||
// Running the strategy
|
||||
BarSeriesManager seriesManager = new BarSeriesManager(series);
|
||||
TradingRecord tradingRecord = seriesManager.run(strategy);
|
||||
|
||||
LOG.debug("{} {}", name, tradingRecord.getPositions());
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user