Files
goldenChart/ta4j-master/ta4j-examples/src/main/java/ta4jexamples/analysis/elliottwave/ElliottWavePresetDemo.java
T
2026-05-23 15:11:48 +09:00

255 lines
9.2 KiB
Java

/*
* SPDX-License-Identifier: MIT
*/
package ta4jexamples.analysis.elliottwave;
import java.nio.file.Path;
import java.time.Duration;
import java.time.Instant;
import java.util.Locale;
import java.util.Objects;
import java.util.Optional;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.ta4j.core.BarSeries;
import org.ta4j.core.indicators.elliott.ElliottDegree;
import ta4jexamples.analysis.elliottwave.backtest.ElliottWaveBtcMacroCycleDemo;
/**
* Consolidated preset launcher for Elliott Wave demos.
*
* <p>
* This entry point replaces multiple thin wrappers by supporting:
* <ul>
* <li>Ossified presets mapped to bundled datasets</li>
* <li>Live runs where users can specify any ticker</li>
* </ul>
*
* <p>
* Usage:
* <ul>
* <li>{@code ossified <btc|eth|sp500>}</li>
* <li>{@code live <Coinbase|YahooFinance> <ticker> [barDuration] [lookbackDays] [degree]}</li>
* </ul>
*
* <p>
* Examples:
* <ul>
* <li>{@code ossified btc}</li>
* <li>{@code live Coinbase BTC-USD PT1D 1825}</li>
* <li>{@code live YahooFinance AAPL PT1D 1460 PRIMARY}</li>
* </ul>
*
* @since 0.22.4
*/
public class ElliottWavePresetDemo {
private static final Logger LOG = LogManager.getLogger(ElliottWavePresetDemo.class);
private static final String DEFAULT_BAR_DURATION = "PT1D";
private static final long DEFAULT_LOOKBACK_DAYS = 1825L;
private static final Path DEFAULT_BTC_MACRO_CHART_DIRECTORY = Path.of("temp", "charts");
/**
* Runs a preset Elliott Wave demo.
*
* @param args command-line arguments; see class-level usage documentation
*/
public static void main(String[] args) {
if (args == null || args.length == 0) {
logUsage();
return;
}
final String mode = normalize(args[0]);
switch (mode) {
case "ossified":
runOssified(args);
return;
case "live":
runLive(args);
return;
default:
LOG.error("Unknown mode '{}'. Expected 'ossified' or 'live'.", args[0]);
logUsage();
}
}
private static void runOssified(String[] args) {
if (args.length < 2) {
LOG.error("Missing ossified preset. Expected one of: btc, eth, sp500");
logUsage();
return;
}
Optional<OssifiedPreset> preset = OssifiedPreset.fromToken(args[1]);
if (preset.isEmpty()) {
LOG.error("Unknown ossified preset '{}'. Expected one of: btc, eth, sp500", args[1]);
logUsage();
return;
}
OssifiedPreset selected = preset.orElseThrow();
ElliottWaveIndicatorSuiteDemo.runOssifiedResource(ElliottWavePresetDemo.class, selected.resource(),
selected.seriesName(), selected.degreeOverride().orElse(null));
}
private static void runLive(String[] args) {
if (args.length < 3) {
LOG.error("Live mode expects at least dataSource and ticker");
logUsage();
return;
}
final String dataSource = Objects.requireNonNull(args[1], "dataSource");
final String ticker = Objects.requireNonNull(args[2], "ticker");
final String barDuration = args.length > 3 ? args[3] : DEFAULT_BAR_DURATION;
long lookbackDays = DEFAULT_LOOKBACK_DAYS;
if (args.length > 4) {
try {
lookbackDays = Long.parseLong(args[4]);
} catch (NumberFormatException ex) {
LOG.error("Invalid lookbackDays '{}': expected a positive integer", args[4]);
return;
}
}
if (lookbackDays <= 0) {
LOG.error("Invalid lookbackDays '{}': must be > 0", lookbackDays);
return;
}
ElliottDegree degree = null;
if (args.length > 5) {
try {
degree = ElliottDegree.valueOf(args[5].trim().toUpperCase(Locale.ROOT));
} catch (IllegalArgumentException ex) {
LOG.error("Invalid degree '{}'", args[5]);
return;
}
}
if (shouldUseBtcMacroPreset(ticker, barDuration)) {
if (degree != null) {
LOG.info(
"Ignoring explicit degree '{}' for BTC daily macro preset; the validated macro profile selects its own structural interpretation.",
degree);
}
Instant endTime = Instant.now();
Instant startTime = endTime.minus(Duration.ofDays(lookbackDays));
Duration parsedDuration = parseBarDuration(barDuration);
BarSeries series = ElliottWaveIndicatorSuiteDemo.loadSeriesFromDataSource(dataSource, ticker,
parsedDuration, startTime, endTime);
if (series == null || series.isEmpty()) {
LOG.error("Unable to load live BTC series for macro preset from {} {} {}", dataSource, ticker,
barDuration);
return;
}
ElliottWaveBtcMacroCycleDemo.runLivePreset(series, DEFAULT_BTC_MACRO_CHART_DIRECTORY);
return;
}
String[] suiteArgs = buildLiveSuiteArgs(dataSource, ticker, barDuration, lookbackDays, Instant.now(), degree);
ElliottWaveIndicatorSuiteDemo.main(suiteArgs);
}
static boolean shouldUseBtcMacroPreset(String ticker, String barDuration) {
if (ticker == null || barDuration == null) {
return false;
}
String normalizedTicker = ticker.trim().toUpperCase(Locale.ROOT);
String normalizedDuration = barDuration.trim().toUpperCase(Locale.ROOT);
return "BTC-USD".equals(normalizedTicker)
&& ("PT1D".equals(normalizedDuration) || "PT24H".equals(normalizedDuration));
}
static String[] buildLiveSuiteArgs(String dataSource, String ticker, String barDuration, long lookbackDays,
Instant endTime, ElliottDegree degree) {
Objects.requireNonNull(dataSource, "dataSource");
Objects.requireNonNull(ticker, "ticker");
Objects.requireNonNull(barDuration, "barDuration");
Objects.requireNonNull(endTime, "endTime");
if (lookbackDays <= 0) {
throw new IllegalArgumentException("lookbackDays must be > 0");
}
Instant startTime = endTime.minus(Duration.ofDays(lookbackDays));
if (degree == null) {
return new String[] { dataSource, ticker, barDuration, Long.toString(startTime.getEpochSecond()),
Long.toString(endTime.getEpochSecond()) };
}
return new String[] { dataSource, ticker, barDuration, degree.name(), Long.toString(startTime.getEpochSecond()),
Long.toString(endTime.getEpochSecond()) };
}
private static void logUsage() {
LOG.info("Usage:");
LOG.info(" ossified <btc|eth|sp500>");
LOG.info(" live <Coinbase|YahooFinance> <ticker> [barDuration] [lookbackDays] [degree]");
}
private static Duration parseBarDuration(String barDuration) {
Objects.requireNonNull(barDuration, "barDuration");
String normalized = barDuration.trim().toUpperCase(Locale.ROOT);
if (normalized.startsWith("PT") && normalized.endsWith("D")) {
final String dayString = normalized.substring(2, normalized.length() - 1);
try {
final int days = Integer.parseInt(dayString);
return Duration.ofHours(days * 24L);
} catch (NumberFormatException e) {
throw new IllegalArgumentException("Invalid day count in barDuration: " + barDuration, e);
}
}
return Duration.parse(normalized);
}
private static String normalize(String value) {
if (value == null) {
return "";
}
return value.trim().toLowerCase(Locale.ROOT);
}
private enum OssifiedPreset {
BTC("Coinbase-BTC-USD-PT1D-20230616_20231011.json", "BTC-USD_PT1D@Coinbase (ossified)", Optional.empty()),
ETH("Coinbase-ETH-USD-PT1D-20241105_20251020.json", "ETH-USD_PT1D@Coinbase (ossified)", Optional.empty()),
SP500("YahooFinance-SP500-PT1D-20230616_20231011.json", "^GSPC_PT1D@YahooFinance (ossified)", Optional.empty());
private final String resource;
private final String seriesName;
private final Optional<ElliottDegree> degreeOverride;
OssifiedPreset(String resource, String seriesName, Optional<ElliottDegree> degreeOverride) {
this.resource = resource;
this.seriesName = seriesName;
this.degreeOverride = degreeOverride;
}
static Optional<OssifiedPreset> fromToken(String token) {
if (token == null) {
return Optional.empty();
}
String normalized = token.trim().toLowerCase(Locale.ROOT);
return switch (normalized) {
case "btc" -> Optional.of(BTC);
case "eth" -> Optional.of(ETH);
case "sp500", "s&p500", "gspc", "^gspc" -> Optional.of(SP500);
default -> Optional.empty();
};
}
String resource() {
return resource;
}
String seriesName() {
return seriesName;
}
Optional<ElliottDegree> degreeOverride() {
return degreeOverride;
}
}
}