goldenChat base source add

This commit is contained in:
aidev
2026-05-23 15:11:48 +09:00
commit a4ea7762b5
2081 changed files with 1155760 additions and 0 deletions
@@ -0,0 +1,695 @@
/*
* SPDX-License-Identifier: MIT
*/
package ta4jexamples.doc;
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.drawdown.MaximumDrawdownCriterion;
import org.ta4j.core.criteria.pnl.NetProfitLossCriterion;
import org.ta4j.core.indicators.RSIIndicator;
import org.ta4j.core.indicators.MACDIndicator;
import org.ta4j.core.indicators.averages.EMAIndicator;
import org.ta4j.core.indicators.averages.SMAIndicator;
import org.ta4j.core.indicators.helpers.ClosePriceIndicator;
import org.ta4j.core.rules.*;
import org.ta4j.core.serialization.ComponentSerialization;
import org.ta4j.core.serialization.RuleSerialization;
import ta4jexamples.charting.workflow.ChartWorkflow;
import ta4jexamples.datasources.BitStampCsvTradesFileBarSeriesDataSource;
import java.awt.GraphicsEnvironment;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Utility class to manage README content including chart images, code snippets,
* and documentation.
*
* <p>
* This class serves as the single source of truth for README content. It
* generates chart images, extracts code snippets from source code using special
* comment markers, and synchronizes the README with the latest code examples.
* </p>
*
* <p>
* <strong>How it works:</strong>
* <ul>
* <li>Code snippets are marked with {@code // START_SNIPPET: snippet-id} and
* {@code // END_SNIPPET: snippet-id} comments in the chart generation
* methods</li>
* <li>The README.md file contains corresponding HTML comment markers:
* {@code <!-- START_SNIPPET: snippet-id -->} and
* {@code <!-- END_SNIPPET: snippet-id -->}</li>
* <li>Run with argument "update-readme" to automatically extract snippets and
* update the README:
* {@code mvn -pl ta4j-examples exec:java -Dexec.mainClass=ta4jexamples.doc.ReadmeContentManager -Dexec.args=update-readme}</li>
* </ul>
* </p>
*
* <p>
* <strong>Usage:</strong>
* <ul>
* <li>Generate charts and update README (default):
* {@code mvn -pl ta4j-examples exec:java -Dexec.mainClass=ta4jexamples.doc.ReadmeContentManager}</li>
* <li>Update README snippets only:
* {@code mvn -pl ta4j-examples exec:java -Dexec.mainClass=ta4jexamples.doc.ReadmeContentManager -Dexec.args=update-readme}</li>
* <li>View snippets only:
* {@code mvn -pl ta4j-examples exec:java -Dexec.mainClass=ta4jexamples.doc.ReadmeContentManager -Dexec.args=snippets}</li>
* </ul>
* </p>
*/
public class ReadmeContentManager {
private static final Logger LOG = LogManager.getLogger(ReadmeContentManager.class);
/**
* Generates the EMA Crossover chart matching the README quick start example.
*
* @param outputDir the directory to save the chart image (e.g., "docs/charts"
* or ".")
* @return the path to the saved chart image, or empty if saving failed
*/
public static Optional<Path> generateEmaCrossoverChart(String outputDir) {
LOG.info("Generating EMA Crossover chart for README...");
// Load historical price data
BarSeries fullSeries = BitStampCsvTradesFileBarSeriesDataSource.loadBitstampSeries();
LOG.info("Loaded {} bars from Bitstamp series", fullSeries.getBarCount());
// Use a smaller subset for a cleaner chart (last ~150 bars)
int totalBars = fullSeries.getBarCount();
int startIndex = Math.max(0, totalBars - 150);
BarSeries series = fullSeries.getSubSeries(startIndex, totalBars);
LOG.info("Using subset: {} bars (indices {} to {})", series.getBarCount(), startIndex, totalBars - 1);
// Create indicators: calculate moving averages from close prices
ClosePriceIndicator close = new ClosePriceIndicator(series);
EMAIndicator fastEma = new EMAIndicator(close, 12); // 12-period EMA
EMAIndicator slowEma = new EMAIndicator(close, 26); // 26-period EMA
// Define entry rule: buy when fast EMA crosses above slow EMA (golden cross)
Rule entry = new CrossedUpIndicatorRule(fastEma, slowEma);
// Define exit rule: sell when price gains 3% OR loses 1.5%
Rule exit = new StopGainRule(close, 3.0) // take profit at +3%
.or(new StopLossRule(close, 1.5)); // or cut losses at -1.5%
// Combine rules into a strategy
Strategy strategy = new BaseStrategy("EMA Crossover", entry, exit);
// Run the strategy on historical data
BarSeriesManager manager = new BarSeriesManager(series);
TradingRecord record = manager.run(strategy);
LOG.info("Strategy executed: {} positions", record.getPositionCount());
// START_SNIPPET: ema-crossover
// Generate simplified chart - just price, indicators, and signals (no subchart)
ChartWorkflow chartWorkflow = new ChartWorkflow();
JFreeChart chart = chartWorkflow.builder()
.withTitle("EMA Crossover Strategy")
.withSeries(series) // Price bars (candlesticks)
.withIndicatorOverlay(fastEma) // Overlay indicators on price chart
.withIndicatorOverlay(slowEma)
.withTradingRecordOverlay(record) // Mark entry/exit points with arrows
.toChart();
chartWorkflow.saveChartImage(chart, series, "ema-crossover-strategy", "output/charts"); // Save as image
// END_SNIPPET: ema-crossover
// Display chart in interactive window (only in non-headless environments)
if (!GraphicsEnvironment.isHeadless()) {
chartWorkflow.displayChart(chart);
}
// Save chart image
Optional<Path> savedPath = chartWorkflow.saveChartImage(chart, series, "ema-crossover-readme", outputDir);
if (savedPath.isPresent()) {
Path relativePath = getRelativePath(savedPath.get());
LOG.info("Chart saved to: {}", relativePath);
} else {
LOG.warn("Failed to save ema-crossover-readme.jpg chart image");
}
return savedPath;
}
/**
* Generates an RSI Strategy chart with RSI indicator in a subchart.
* Demonstrates using subcharts for indicators with different scales.
*
* @param outputDir the directory to save the chart image
* @return the path to the saved chart image, or empty if saving failed
*/
public static Optional<Path> generateRsiStrategyChart(String outputDir) {
LOG.info("Generating RSI Strategy chart with subchart for README...");
// Load historical price data
BarSeries fullSeries = BitStampCsvTradesFileBarSeriesDataSource.loadBitstampSeries();
LOG.info("Loaded {} bars from Bitstamp series", fullSeries.getBarCount());
// Use a smaller subset for a cleaner chart (last ~150 bars)
int totalBars = fullSeries.getBarCount();
int startIndex = Math.max(0, totalBars - 150);
BarSeries series = fullSeries.getSubSeries(startIndex, totalBars);
LOG.info("Using subset: {} bars (indices {} to {})", series.getBarCount(), startIndex, totalBars - 1);
// START_SNIPPET: rsi-strategy
// Create indicators
ClosePriceIndicator close = new ClosePriceIndicator(series);
RSIIndicator rsi = new RSIIndicator(close, 14);
// RSI strategy: buy when RSI crosses below 30 (oversold), sell when RSI crosses
// above 70 (overbought)
Rule entry = new CrossedDownIndicatorRule(rsi, 30);
Rule exit = new CrossedUpIndicatorRule(rsi, 70);
Strategy strategy = new BaseStrategy("RSI Strategy", entry, exit);
TradingRecord record = new BarSeriesManager(series).run(strategy);
ChartWorkflow chartWorkflow = new ChartWorkflow();
JFreeChart chart = chartWorkflow.builder()
.withTitle("RSI Strategy with Subchart")
.withSeries(series) // Price bars (candlesticks)
.withTradingRecordOverlay(record) // Mark entry/exit points
.withSubChart(rsi) // RSI indicator in separate subchart panel
.toChart();
// END_SNIPPET: rsi-strategy
// Save chart image
Optional<Path> savedPath = chartWorkflow.saveChartImage(chart, series, "rsi-strategy-readme", outputDir);
if (savedPath.isPresent()) {
Path relativePath = getRelativePath(savedPath.get());
LOG.info("Chart saved to: {}", relativePath);
} else {
LOG.warn("Failed to save rsi-strategy-readme.jpg chart image");
}
return savedPath;
}
/**
* Generates a strategy chart with performance metrics subchart. Demonstrates
* visualizing performance analysis criteria over time.
*
* @param outputDir the directory to save the chart image
* @return the path to the saved chart image, or empty if saving failed
*/
public static Optional<Path> generateStrategyPerformanceChart(String outputDir) {
LOG.info("Generating Strategy Performance chart with metrics subchart for README...");
// Load historical price data
BarSeries fullSeries = BitStampCsvTradesFileBarSeriesDataSource.loadBitstampSeries();
LOG.info("Loaded {} bars from Bitstamp series", fullSeries.getBarCount());
// Use a smaller subset for a cleaner chart (last ~150 bars)
int totalBars = fullSeries.getBarCount();
int startIndex = Math.max(0, totalBars - 150);
BarSeries series = fullSeries.getSubSeries(startIndex, totalBars);
LOG.info("Using subset: {} bars (indices {} to {})", series.getBarCount(), startIndex, totalBars - 1);
// START_SNIPPET: strategy-performance
// Create indicators: multiple moving averages
ClosePriceIndicator close = new ClosePriceIndicator(series);
SMAIndicator sma20 = new SMAIndicator(close, 20);
EMAIndicator ema12 = new EMAIndicator(close, 12);
// Strategy: buy when EMA crosses above SMA, sell when EMA crosses below SMA
Rule entry = new CrossedUpIndicatorRule(ema12, sma20);
Rule exit = new CrossedDownIndicatorRule(ema12, sma20);
Strategy strategy = new BaseStrategy("EMA/SMA Crossover", entry, exit);
TradingRecord record = new BarSeriesManager(series).run(strategy);
ChartWorkflow chartWorkflow = new ChartWorkflow();
JFreeChart chart = chartWorkflow.builder()
.withTitle("Strategy Performance Analysis")
.withSeries(series) // Price bars (candlesticks)
.withIndicatorOverlay(sma20) // Overlay SMA on price chart
.withIndicatorOverlay(ema12) // Overlay EMA on price chart
.withTradingRecordOverlay(record) // Mark entry/exit points
.withSubChart(new MaximumDrawdownCriterion(), record) // Performance metric in subchart
.toChart();
// END_SNIPPET: strategy-performance
// Save chart image
Optional<Path> savedPath = chartWorkflow.saveChartImage(chart, series, "strategy-performance-readme",
outputDir);
if (savedPath.isPresent()) {
Path relativePath = getRelativePath(savedPath.get());
LOG.info("Chart saved to: {}", relativePath);
} else {
LOG.warn("Failed to save strategy-performance-readme.jpg chart image");
}
return savedPath;
}
/**
* Generates an advanced multi-indicator strategy chart with multiple subcharts.
* Demonstrates full charting capabilities with multiple analysis layers.
*
* @param outputDir the directory to save the chart image
* @return the path to the saved chart image, or empty if saving failed
*/
public static Optional<Path> generateAdvancedStrategyChart(String outputDir) {
LOG.info("Generating Advanced Multi-Indicator Strategy chart for README...");
// Load historical price data
BarSeries fullSeries = BitStampCsvTradesFileBarSeriesDataSource.loadBitstampSeries();
LOG.info("Loaded {} bars from Bitstamp series", fullSeries.getBarCount());
// Use a smaller subset for a cleaner chart (last ~150 bars)
int totalBars = fullSeries.getBarCount();
int startIndex = Math.max(0, totalBars - 150);
BarSeries series = fullSeries.getSubSeries(startIndex, totalBars);
LOG.info("Using subset: {} bars (indices {} to {})", series.getBarCount(), startIndex, totalBars - 1);
// START_SNIPPET: advanced-strategy
// Create indicators
ClosePriceIndicator close = new ClosePriceIndicator(series);
SMAIndicator sma50 = new SMAIndicator(close, 50);
EMAIndicator ema12 = new EMAIndicator(close, 12);
MACDIndicator macd = new MACDIndicator(close, 12, 26);
RSIIndicator rsi = new RSIIndicator(close, 14);
// Strategy: buy when EMA crosses above SMA and RSI > 50, sell when EMA crosses
// below SMA
Rule entry = new CrossedUpIndicatorRule(ema12, sma50).and(new OverIndicatorRule(rsi, 50));
Rule exit = new CrossedDownIndicatorRule(ema12, sma50);
Strategy strategy = new BaseStrategy("Advanced Multi-Indicator Strategy", entry, exit);
TradingRecord record = new BarSeriesManager(series).run(strategy);
ChartWorkflow chartWorkflow = new ChartWorkflow();
JFreeChart chart = chartWorkflow.builder()
.withTitle("Advanced Multi-Indicator Strategy")
.withSeries(series) // Price bars (candlesticks)
.withIndicatorOverlay(sma50) // Overlay SMA on price chart
.withIndicatorOverlay(ema12) // Overlay EMA on price chart
.withTradingRecordOverlay(record) // Mark entry/exit points
.withSubChart(macd) // MACD indicator in subchart
.withSubChart(rsi) // RSI indicator in subchart
.withSubChart(new NetProfitLossCriterion(), record) // Net profit/loss performance metric
.toChart();
// END_SNIPPET: advanced-strategy
// Save chart image
Optional<Path> savedPath = chartWorkflow.saveChartImage(chart, series, "advanced-strategy-readme", outputDir);
if (savedPath.isPresent()) {
Path relativePath = getRelativePath(savedPath.get());
LOG.info("Chart saved to: {}", relativePath);
} else {
LOG.warn("Failed to save advanced-strategy-readme.jpg chart image");
}
return savedPath;
}
/**
* Finds the project root directory by searching for README.md file. Starts from
* the current working directory and walks up the directory tree.
*
* @return the project root path, or current directory if not found
*/
private static Path findProjectRoot() {
Path current = Paths.get("").toAbsolutePath().normalize();
Path root = current.getRoot();
// Walk up the directory tree looking for README.md
while (current != null && !current.equals(root)) {
Path readmePath = current.resolve("README.md");
if (Files.exists(readmePath)) {
return current;
}
current = current.getParent();
}
// If not found, return current working directory as fallback
return Paths.get("").toAbsolutePath().normalize();
}
/**
* Converts an absolute path to a relative path from the project root.
*
* @param absolutePath the absolute path to convert
* @return the relative path, or the original path if conversion fails
*/
private static Path getRelativePath(Path absolutePath) {
try {
Path projectRoot = findProjectRoot();
Path normalized = absolutePath.normalize();
if (normalized.startsWith(projectRoot)) {
return projectRoot.relativize(normalized);
}
return normalized;
} catch (Exception e) {
LOG.warn("Failed to convert path to relative: {}", e.getMessage());
return absolutePath;
}
}
/**
* Generates serialization examples for the README. This method demonstrates
* serializing indicators, rules, and strategies to JSON. The examples are
* extracted via snippet markers for inclusion in the README.
*
* @param series the bar series to use for examples
*/
@SuppressWarnings("unused")
public static void generateSerializationExamples(BarSeries series) {
// START_SNIPPET: serialize-indicator
// Serialize an indicator (RSI) to JSON
ClosePriceIndicator close = new ClosePriceIndicator(series);
RSIIndicator rsi = new RSIIndicator(close, 14);
String rsiJson = rsi.toJson();
LOG.info("Output: {}", rsiJson);
// Output:
// {"type":"RSIIndicator","parameters":{"barCount":14},"components":[{"type":"ClosePriceIndicator"}]}
// END_SNIPPET: serialize-indicator
// START_SNIPPET: serialize-rule
// Serialize a rule (AndRule) to JSON
Rule rule1 = new OverIndicatorRule(rsi, 50);
Rule rule2 = new UnderIndicatorRule(rsi, 80);
Rule andRule = new AndRule(rule1, rule2);
String ruleJson = ComponentSerialization.toJson(RuleSerialization.describe(andRule));
LOG.info("Output: {}", ruleJson);
// Output:
// {"type":"AndRule","label":"AndRule","components":[{"type":"OverIndicatorRule","label":"OverIndicatorRule","components":[{"type":"RSIIndicator","parameters":{"barCount":14},"components":[{"type":"ClosePriceIndicator"}]}],"parameters":{"threshold":50.0}},{"type":"UnderIndicatorRule","label":"UnderIndicatorRule","components":[{"type":"RSIIndicator","parameters":{"barCount":14},"components":[{"type":"ClosePriceIndicator"}]}],"parameters":{"threshold":80.0}}]}
// END_SNIPPET: serialize-rule
// START_SNIPPET: serialize-strategy
// Serialize a strategy (EMA Crossover) to JSON
EMAIndicator fastEma = new EMAIndicator(close, 12);
EMAIndicator slowEma = new EMAIndicator(close, 26);
Rule entry = new CrossedUpIndicatorRule(fastEma, slowEma);
Rule exit = new CrossedDownIndicatorRule(fastEma, slowEma);
Strategy strategy = new BaseStrategy("EMA Crossover", entry, exit);
String strategyJson = strategy.toJson();
LOG.info("Output: {}", strategyJson);
// Output: {"type":"BaseStrategy","label":"EMA
// Crossover","parameters":{"unstableBars":0},"rules":[{"type":"CrossedUpIndicatorRule","label":"entry","components":[{"type":"EMAIndicator","parameters":{"barCount":12},"components":[{"type":"ClosePriceIndicator"}]},{"type":"EMAIndicator","parameters":{"barCount":26},"components":[{"type":"ClosePriceIndicator"}]}]},{"type":"CrossedDownIndicatorRule","label":"exit","components":[{"type":"EMAIndicator","parameters":{"barCount":12},"components":[{"type":"ClosePriceIndicator"}]},{"type":"EMAIndicator","parameters":{"barCount":26},"components":[{"type":"ClosePriceIndicator"}]}]}]}
// END_SNIPPET: serialize-strategy
}
/**
* Extracts code between START_SNIPPET and END_SNIPPET markers from the source
* file.
*
* @param sourceFile the Java source file to read
* @param snippetId the snippet identifier (e.g., "ema-crossover")
* @return the extracted code snippet, or empty if not found
*/
public static Optional<String> extractCodeSnippet(Path sourceFile, String snippetId) {
try {
String content = Files.readString(sourceFile, StandardCharsets.UTF_8);
String startMarker = "// START_SNIPPET: " + snippetId;
String endMarker = "// END_SNIPPET: " + snippetId;
int startIndex = content.indexOf(startMarker);
if (startIndex == -1) {
LOG.warn("Start marker not found for snippet: {}", snippetId);
return Optional.empty();
}
int endIndex = content.indexOf(endMarker, startIndex);
if (endIndex == -1) {
LOG.warn("End marker not found for snippet: {}", snippetId);
return Optional.empty();
}
// Extract code between markers (excluding the markers themselves)
int newlineIndex = content.indexOf('\n', startIndex);
if (newlineIndex == -1) {
LOG.warn("No newline found after start marker for snippet: {}", snippetId);
return Optional.empty();
}
int codeStart = newlineIndex + 1;
// Don't trim here - we need to preserve the exact indentation structure
// We'll trim only trailing whitespace from the entire snippet after processing
String snippet = content.substring(codeStart, endIndex);
// Remove leading indentation (find minimum indentation and remove it)
// Strategy: Find the minimum indentation among lines that have indentation > 0,
// but if all lines have 0 indentation, use 0. This handles cases where the
// first
// line (e.g., a comment) has no indentation but subsequent lines do.
String[] lines = snippet.split("\n");
int minIndent = Integer.MAX_VALUE;
int minIndentIncludingZero = Integer.MAX_VALUE;
boolean hasIndentedLines = false;
for (String line : lines) {
if (!line.trim().isEmpty()) {
int indent = 0;
for (char c : line.toCharArray()) {
if (c == ' ') {
indent++;
} else {
break;
}
}
minIndentIncludingZero = Math.min(minIndentIncludingZero, indent);
if (indent > 0) {
minIndent = Math.min(minIndent, indent);
hasIndentedLines = true;
}
}
}
// Use the minimum indentation of indented lines if any exist, otherwise use 0
int indentToRemove = hasIndentedLines ? minIndent
: (minIndentIncludingZero == Integer.MAX_VALUE ? 0 : minIndentIncludingZero);
// Remove minimum indentation from all lines
StringBuilder result = new StringBuilder();
for (String line : lines) {
if (line.trim().isEmpty()) {
result.append("\n");
} else {
// Ensure we don't go beyond the line length
int indentToRemoveForLine = Math.min(indentToRemove, line.length());
result.append(line.substring(indentToRemoveForLine)).append("\n");
}
}
// Trim only trailing whitespace/newlines, preserve leading structure
String resultStr = result.toString();
// Remove trailing newlines and whitespace
while (resultStr.endsWith("\n") || resultStr.endsWith(" ") || resultStr.endsWith("\r")) {
resultStr = resultStr.substring(0, resultStr.length() - 1);
}
return Optional.of(resultStr);
} catch (IOException e) {
LOG.error("Error reading source file: {}", e.getMessage());
return Optional.empty();
}
}
/**
* Updates the README file by replacing code snippets between HTML comment
* markers.
*
* @param readmePath path to the README.md file
* @param sourceFile path to the ReadmeContentManager.java file
* @return true if update was successful
*/
public static boolean updateReadmeSnippets(Path readmePath, Path sourceFile) {
try {
String readmeContent = Files.readString(readmePath, StandardCharsets.UTF_8);
String lineSeparator = detectPreferredLineSeparator(readmeContent);
String[] snippetIds = { "ema-crossover", "rsi-strategy", "strategy-performance", "advanced-strategy",
"serialize-indicator", "serialize-rule", "serialize-strategy" };
boolean updated = false;
for (String snippetId : snippetIds) {
Optional<String> snippet = extractCodeSnippet(sourceFile, snippetId);
if (snippet.isEmpty()) {
LOG.warn("Could not extract snippet: {}", snippetId);
continue;
}
String startMarker = "<!-- START_SNIPPET: " + snippetId + " -->";
String endMarker = "<!-- END_SNIPPET: " + snippetId + " -->";
// Find the code block between markers
Pattern pattern = Pattern.compile(Pattern.quote(startMarker) + ".*?" + Pattern.quote(endMarker),
Pattern.DOTALL);
String normalizedSnippet = normalizeLineSeparators(snippet.get(), lineSeparator);
String replacement = startMarker + lineSeparator + "```java" + lineSeparator + normalizedSnippet
+ lineSeparator + "```" + lineSeparator + endMarker;
String before = readmeContent;
readmeContent = pattern.matcher(readmeContent).replaceAll(Matcher.quoteReplacement(replacement));
if (!readmeContent.equals(before)) {
updated = true;
LOG.info("Updated snippet in README: {}", snippetId);
} else {
LOG.warn("Snippet markers not found in README for: {}", snippetId);
}
}
if (updated) {
Files.writeString(readmePath, readmeContent, StandardCharsets.UTF_8);
LOG.info("README updated successfully");
return true;
} else {
LOG.warn("No updates made to README");
return false;
}
} catch (IOException e) {
LOG.error("Error updating README: {}", e.getMessage());
return false;
}
}
private static String detectPreferredLineSeparator(String content) {
int crlfCount = 0;
int lfCount = 0;
for (int i = 0; i < content.length(); i++) {
if (content.charAt(i) == '\n') {
if (i > 0 && content.charAt(i - 1) == '\r') {
crlfCount++;
} else {
lfCount++;
}
}
}
if (crlfCount >= lfCount && crlfCount > 0) {
return "\r\n";
}
if (lfCount > 0) {
return "\n";
}
return System.lineSeparator();
}
private static String normalizeLineSeparators(String content, String lineSeparator) {
String normalized = content.replace("\r\n", "\n").replace("\r", "\n");
if ("\n".equals(lineSeparator)) {
return normalized;
}
return normalized.replace("\n", lineSeparator);
}
/**
* Main method to generate all README charts and automatically update README
* code snippets.
*
* <p>
* By default, this method generates all chart images and automatically updates
* the README with code snippets extracted from the chart generation methods to
* keep them in sync.
* </p>
*
* @param args optional arguments: - "update-readme": Only updates README.md
* with code snippets (no chart generation) - "snippets": Only
* prints all code snippets to log (no chart generation) -
* Otherwise: output directory for charts (defaults to
* "ta4j-examples/docs/img")
*/
public static void main(String[] args) {
if (args.length > 0 && "update-readme".equals(args[0])) {
LOG.info("=== Updating README with code snippets ===");
Path projectRoot = findProjectRoot();
Path sourceFile = projectRoot
.resolve("ta4j-examples/src/main/java/ta4jexamples/doc/ReadmeContentManager.java")
.normalize();
Path readmePath = projectRoot.resolve("README.md").normalize();
if (!Files.exists(sourceFile)) {
LOG.error("Source file not found: {}", sourceFile.toAbsolutePath());
return;
}
if (!Files.exists(readmePath)) {
LOG.error("README file not found: {}", readmePath.toAbsolutePath());
return;
}
boolean success = updateReadmeSnippets(readmePath, sourceFile);
if (success) {
LOG.info("=== README update complete ===");
} else {
LOG.warn("=== README update failed or no changes made ===");
}
return;
}
if (args.length > 0 && "snippets".equals(args[0])) {
LOG.info("=== Extracting code snippets ===");
Path projectRoot = findProjectRoot();
Path sourceFile = projectRoot
.resolve("ta4j-examples/src/main/java/ta4jexamples/doc/ReadmeContentManager.java")
.normalize();
String[] snippetIds = { "ema-crossover", "rsi-strategy", "strategy-performance", "advanced-strategy",
"serialize-indicator", "serialize-rule", "serialize-strategy" };
for (String snippetId : snippetIds) {
Optional<String> snippet = extractCodeSnippet(sourceFile, snippetId);
if (snippet.isPresent()) {
LOG.info("\n=== {} Code Snippet ===", snippetId);
LOG.info("\n{}", snippet.get());
} else {
LOG.warn("Could not extract snippet: {}", snippetId);
}
}
return;
}
// Chart generation requires a display (non-headless environment)
if (GraphicsEnvironment.isHeadless()) {
LOG.error("Cannot generate charts in headless environment. Chart generation requires a display.");
LOG.error("Use 'update-readme' or 'snippets' arguments for headless operations.");
System.exit(1);
}
String outputDir = args.length > 0 ? args[0] : "ta4j-examples/docs/img";
LOG.info("=== README Content Manager ===");
LOG.info("Output directory: {}", outputDir);
// Generate all charts
generateEmaCrossoverChart(outputDir);
generateRsiStrategyChart(outputDir);
generateStrategyPerformanceChart(outputDir);
generateAdvancedStrategyChart(outputDir);
LOG.info("=== Chart generation complete ===");
// Automatically update README with code snippets to keep them in sync
LOG.info("=== Updating README with code snippets ===");
Path projectRoot = findProjectRoot();
Path sourceFile = projectRoot.resolve("ta4j-examples/src/main/java/ta4jexamples/doc/ReadmeContentManager.java")
.normalize();
Path readmePath = projectRoot.resolve("README.md").normalize();
if (Files.exists(sourceFile) && Files.exists(readmePath)) {
boolean success = updateReadmeSnippets(readmePath, sourceFile);
if (success) {
LOG.info("=== README updated successfully ===");
} else {
LOG.warn("=== README update completed (no changes or warnings occurred) ===");
}
} else {
LOG.warn("Could not update README: source file or README not found");
LOG.warn("Source: {}, exists: {}", sourceFile.toAbsolutePath(), Files.exists(sourceFile));
LOG.warn("README: {}, exists: {}", readmePath.toAbsolutePath(), Files.exists(readmePath));
}
LOG.info("To view code snippets only, run with argument 'snippets'");
}
}