/* * 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. * *

* 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. * *

* The strategy name is automatically generated as * {@code "HourOfDayStrategy__"} (e.g., * "HourOfDayStrategy_9_17"). * *

* 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. * *

* 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. * *

* 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 buildAllStrategyPermutations(BarSeries series) { List 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] : ""; String exit = params.length > 1 ? params[1] : ""; 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); } } }