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,316 @@
/*
* SPDX-License-Identifier: MIT
*/
package ta4jexamples.datasources.json;
import com.google.gson.*;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.ta4j.core.BarSeries;
import org.ta4j.core.BaseBarSeries;
import org.ta4j.core.BaseBarSeriesBuilder;
import java.io.IOException;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
/**
* A TypeAdapter implementation for deserializing JSON data into BarSeries
* objects. This adapter supports multiple JSON formats by detecting the
* structure of the input data. It currently handles two formats:
* <ul>
* <li><strong>Coinbase format:</strong> identified by the presence of a
* "candles" array. The "start" field represents the start of the candle period,
* and the end time is calculated as start + duration. This adapter can be used
* for both Coinbase API responses (where the interval is known) and Coinbase
* JSON files (where the interval can be inferred from the data).</li>
* <li><strong>Binance format:</strong> identified by the presence of an "ohlc"
* array</li>
* </ul>
* <p>
* The adapter parses the JSON input and converts it into a BaseBarSeries
* instance populated with the appropriate bar data. The bar data is sorted by
* timestamp for the Coinbase format to ensure chronological order.
* <p>
* For Coinbase format, the adapter provides a static helper method
* {@link #parseCoinbaseFormat(JsonObject, String, Duration)} that can be used
* directly when parsing Coinbase data, allowing you to specify a known interval
* (for API responses) or let it be inferred (for JSON files).
* <p>
* Write operations are not supported by this adapter and will throw an
* exception.
* <p>
* The adapter uses internal lightweight data classes to temporarily hold the
* parsed JSON data before converting it into the final BarSeries format.
* <p>
* Logging is implemented to track which format is being parsed during
* deserialization.
*
* @since 0.19
*/
public class AdaptiveBarSeriesTypeAdapter extends TypeAdapter<BarSeries> {
private static final Logger LOG = LogManager.getLogger(AdaptiveBarSeriesTypeAdapter.class);
@Override
public void write(JsonWriter out, BarSeries value) throws IOException {
// Not implemented for this use case
throw new UnsupportedOperationException("Write operation not supported");
}
/**
* Reads a BarSeries from the provided JsonReader by detecting the format based
* on available fields. The method parses the JSON input and delegates to
* appropriate parsing methods depending on whether the root object contains
* "candles" or "ohlc" fields.
*
* @param in the JsonReader to read the JSON data from
* @return the parsed BarSeries object
* @throws JsonParseException if the JSON format is unknown or neither "candles"
* nor "ohlc" fields are found
* @since 0.19
*/
@Override
public BarSeries read(JsonReader in) {
JsonElement json = JsonParser.parseReader(in);
JsonObject root = json.getAsJsonObject();
// Detect format based on available fields
if (root.has("candles")) {
return parseCoinbaseFormat(root);
} else if (root.has("ohlc")) {
return parseBinanceFormat(root);
} else {
throw new JsonParseException("Unknown format - neither 'candles' nor 'ohlc' found");
}
}
/**
* Parses a JSON object in Coinbase format into a BarSeries. The input JSON is
* expected to contain a "candles" array where each element represents a bar
* with start time, open, high, low, close, and volume values.
* <p>
* Note: Coinbase API's "start" field represents the start of the time interval
* for the candle. The end time is calculated as start + duration. This method
* infers the duration from the data. For cases where the interval is known, use
* {@link #parseCoinbaseFormat(JsonObject, String, Duration)} instead.
*
* @param root the JsonObject representing the root of the JSON data in Coinbase
* format
* @return a BarSeries populated with data parsed from the Coinbase format JSON
*/
private BarSeries parseCoinbaseFormat(JsonObject root) {
return parseCoinbaseFormat(root, "CoinbaseData", null);
}
/**
* Parses a JSON object in Coinbase format into a BarSeries with a known
* interval and series name. The input JSON is expected to contain a "candles"
* array where each element represents a bar with start time, open, high, low,
* close, and volume values.
* <p>
* Note: Coinbase API's "start" field represents the start of the time interval
* for the candle. The end time is calculated as start + duration. This method
* uses the provided interval directly instead of inferring it from the data.
* <p>
* This method can be used for both API responses (where the interval is known)
* and JSON files (where the interval can be inferred or provided).
*
* @param root the JsonObject representing the root of the JSON data in
* Coinbase format
* @param seriesName the name to use for the BarSeries
* @param knownInterval the known bar interval (if null, will be inferred from
* the data by calculating the difference between
* consecutive start times)
* @return a BarSeries populated with data parsed from the Coinbase format JSON
*/
public static BarSeries parseCoinbaseFormat(JsonObject root, String seriesName, Duration knownInterval) {
LOG.trace("Parsing Coinbase format");
JsonArray candles = root.getAsJsonArray("candles");
if (candles == null || candles.isEmpty()) {
return new BaseBarSeriesBuilder().withName(seriesName).build();
}
List<CoinbaseBar> barList = new ArrayList<>();
for (JsonElement candle : candles) {
JsonObject candleObj = candle.getAsJsonObject();
// Skip candles with null or missing required fields
if (candleObj.get("start") == null || candleObj.get("start").isJsonNull() || candleObj.get("open") == null
|| candleObj.get("open").isJsonNull() || candleObj.get("high") == null
|| candleObj.get("high").isJsonNull() || candleObj.get("low") == null
|| candleObj.get("low").isJsonNull() || candleObj.get("close") == null
|| candleObj.get("close").isJsonNull()) {
continue;
}
// Handle null volume by defaulting to "0"
String volume = "0";
if (candleObj.get("volume") != null && !candleObj.get("volume").isJsonNull()) {
volume = candleObj.get("volume").getAsString();
}
// Validate timestamp format before creating CoinbaseBar
String startStr = candleObj.get("start").getAsString();
try {
Long.parseLong(startStr);
} catch (NumberFormatException nfe) {
LOG.warn("Invalid timestamp format in Coinbase data, skipping candle: {}", startStr, nfe);
continue;
}
barList.add(
new CoinbaseBar(startStr, candleObj.get("open").getAsString(), candleObj.get("high").getAsString(),
candleObj.get("low").getAsString(), candleObj.get("close").getAsString(), volume));
}
// Sort by timestamp (ascending order)
barList.sort(Comparator.comparingLong(CoinbaseBar::getStartTime));
// Build series
BaseBarSeries series = new BaseBarSeriesBuilder().withName(seriesName).build();
Duration lastDuration = knownInterval;
for (int i = 0; i < barList.size(); i++) {
CoinbaseBar bar = barList.get(i);
// Coinbase "start" field is the start of the candle period
Instant startTime = bar.getStartInstant();
Duration duration;
if (knownInterval != null) {
duration = knownInterval;
} else {
// Infer duration from data by calculating the difference between consecutive
// start times
Instant previousStart = i > 0 ? barList.get(i - 1).getStartInstant() : null;
Instant currentStart = bar.getStartInstant();
Instant nextStart = i + 1 < barList.size() ? barList.get(i + 1).getStartInstant() : null;
duration = inferDuration(previousStart, currentStart, nextStart, lastDuration);
lastDuration = duration;
}
// End time is start time + duration
Instant endTime = startTime.plus(duration);
bar.addToSeries(series, endTime, duration);
}
return series;
}
/**
* Parses a JSON object in Binance format into a BarSeries. The input JSON is
* expected to contain an "ohlc" array where each element represents a bar with
* end time, open price, high price, low price, close price, volume, and amount
* values.
*
* @param root the JsonObject representing the root of the JSON data in Binance
* format
* @return a BarSeries populated with data parsed from the Binance format JSON
*/
private BarSeries parseBinanceFormat(JsonObject root) {
LOG.trace("Parsing Binance format");
JsonArray ohlc = root.getAsJsonArray("ohlc");
String seriesName = root.has("name") ? root.get("name").getAsString() : "BinanceData";
BaseBarSeries series = new BaseBarSeriesBuilder().withName(seriesName).build();
List<BinanceBar> bars = new ArrayList<>();
for (JsonElement barElement : ohlc) {
JsonObject barObj = barElement.getAsJsonObject();
BinanceBar bar = new BinanceBar(barObj.get("endTime").getAsLong(), barObj.get("openPrice").getAsNumber(),
barObj.get("highPrice").getAsNumber(), barObj.get("lowPrice").getAsNumber(),
barObj.get("closePrice").getAsNumber(), barObj.get("volume").getAsNumber(),
barObj.get("amount").getAsNumber());
bars.add(bar);
}
bars.sort(Comparator.comparingLong(BinanceBar::endTime));
Duration lastDuration = null;
for (int i = 0; i < bars.size(); i++) {
BinanceBar bar = bars.get(i);
Instant previousEnd = i > 0 ? bars.get(i - 1).getEndInstant() : null;
Instant currentEnd = bar.getEndInstant();
Instant nextEnd = i + 1 < bars.size() ? bars.get(i + 1).getEndInstant() : null;
Duration duration = inferDuration(previousEnd, currentEnd, nextEnd, lastDuration);
lastDuration = duration;
bar.addToSeries(series, currentEnd, duration);
}
return series;
}
private static Duration inferDuration(Instant previous, Instant current, Instant next, Duration fallback) {
Duration candidate = null;
if (next != null) {
candidate = Duration.between(current, next);
} else if (previous != null) {
candidate = Duration.between(previous, current);
}
if (candidate != null) {
if (candidate.isNegative()) {
candidate = candidate.negated();
}
if (!candidate.isZero()) {
return candidate;
}
}
if (fallback != null && !fallback.isZero() && !fallback.isNegative()) {
return fallback;
}
return Duration.ofSeconds(1);
}
// Lightweight data classes for internal use only
private record CoinbaseBar(String start, String open, String high, String low, String close, String volume) {
public long getStartTime() {
return Long.parseLong(start);
}
public Instant getStartInstant() {
return Instant.ofEpochSecond(getStartTime());
}
public void addToSeries(BaseBarSeries series, Instant endTime, Duration timePeriod) {
series.barBuilder()
.timePeriod(timePeriod)
.endTime(endTime)
.openPrice(open)
.highPrice(high)
.lowPrice(low)
.closePrice(close)
.volume(volume)
.add();
}
}
private record BinanceBar(long endTime, Number openPrice, Number highPrice, Number lowPrice, Number closePrice,
Number volume, Number amount) {
public Instant getEndInstant() {
return Instant.ofEpochMilli(endTime);
}
public void addToSeries(BaseBarSeries series, Instant endTimeInstant, Duration timePeriod) {
series.barBuilder()
.timePeriod(timePeriod)
.endTime(endTimeInstant)
.openPrice(openPrice)
.highPrice(highPrice)
.lowPrice(lowPrice)
.closePrice(closePrice)
.volume(volume)
.amount(amount)
.add();
}
}
}
@@ -0,0 +1,59 @@
/*
* SPDX-License-Identifier: MIT
*/
package ta4jexamples.datasources.json;
import org.ta4j.core.Bar;
import org.ta4j.core.BaseBarSeries;
import org.ta4j.core.utils.DeprecationNotifier;
import java.time.Duration;
import java.time.Instant;
@Deprecated(since = "0.19", forRemoval = true)
public class GsonBarData {
private long endTime;
private Number openPrice;
private Number highPrice;
private Number lowPrice;
private Number closePrice;
private Number volume;
private Number amount;
public GsonBarData() {
DeprecationNotifier.warnOnce(GsonBarData.class, "ta4jexamples.datasources.json.JsonFileBarSeriesDataSource",
"0.24.0");
}
@Deprecated(since = "0.19", forRemoval = true)
public static GsonBarData from(Bar bar) {
DeprecationNotifier.warnOnce(GsonBarData.class, "ta4jexamples.datasources.json.JsonFileBarSeriesDataSource",
"0.24.0");
var result = new GsonBarData();
result.endTime = bar.getEndTime().toEpochMilli();
result.openPrice = bar.getOpenPrice().getDelegate();
result.highPrice = bar.getHighPrice().getDelegate();
result.lowPrice = bar.getLowPrice().getDelegate();
result.closePrice = bar.getClosePrice().getDelegate();
result.volume = bar.getVolume().getDelegate();
result.amount = bar.getAmount().getDelegate();
return result;
}
@Deprecated(since = "0.19", forRemoval = true)
public void addTo(BaseBarSeries barSeries) {
DeprecationNotifier.warnOnce(GsonBarData.class, "ta4jexamples.datasources.json.JsonFileBarSeriesDataSource",
"0.24.0");
var endTimeInstant = Instant.ofEpochMilli(endTime);
barSeries.barBuilder()
.timePeriod(Duration.ofDays(1))
.endTime(endTimeInstant)
.openPrice(openPrice)
.highPrice(highPrice)
.lowPrice(lowPrice)
.closePrice(closePrice)
.volume(volume)
.amount(amount)
.add();
}
}
@@ -0,0 +1,50 @@
/*
* SPDX-License-Identifier: MIT
*/
package ta4jexamples.datasources.json;
import org.ta4j.core.Bar;
import org.ta4j.core.BarSeries;
import org.ta4j.core.BaseBarSeries;
import org.ta4j.core.BaseBarSeriesBuilder;
import org.ta4j.core.utils.DeprecationNotifier;
import java.util.LinkedList;
import java.util.List;
@Deprecated(since = "0.19", forRemoval = true)
public class GsonBarSeries {
private String name;
private List<GsonBarData> ohlc = new LinkedList<>();
public GsonBarSeries() {
DeprecationNotifier.warnOnce(GsonBarSeries.class, "ta4jexamples.datasources.json.JsonFileBarSeriesDataSource",
"0.24.0");
}
@Deprecated(since = "0.19", forRemoval = true)
public static GsonBarSeries from(BarSeries series) {
DeprecationNotifier.warnOnce(GsonBarSeries.class, "ta4jexamples.datasources.json.JsonFileBarSeriesDataSource",
"0.24.0");
GsonBarSeries result = new GsonBarSeries();
result.name = series.getName();
List<Bar> barData = series.getBarData();
for (Bar bar : barData) {
GsonBarData exportableBarData = GsonBarData.from(bar);
result.ohlc.add(exportableBarData);
}
return result;
}
@Deprecated(since = "0.19", forRemoval = true)
public BarSeries toBarSeries() {
DeprecationNotifier.warnOnce(GsonBarSeries.class, "ta4jexamples.datasources.json.JsonFileBarSeriesDataSource",
"0.24.0");
BaseBarSeries result = new BaseBarSeriesBuilder().withName(this.name).build();
for (GsonBarData data : ohlc) {
data.addTo(result);
}
return result;
}
}
@@ -0,0 +1,135 @@
/*
* SPDX-License-Identifier: MIT
*/
package ta4jexamples.datasources.json;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.ta4j.core.BarSeries;
import org.ta4j.core.utils.DeprecationNotifier;
import java.io.*;
/**
* @deprecated
* <p>
* Use {@link JsonFileBarSeriesDataSource} instead. Scheduled for
* removal in 0.24.0.
* </p>
*/
@Deprecated(since = "0.19", forRemoval = true)
public class JsonBarsSerializer {
private static final Logger LOG = LogManager.getLogger(JsonBarsSerializer.class);
public JsonBarsSerializer() {
warnDeprecatedUse();
}
private static void warnDeprecatedUse() {
DeprecationNotifier.warnOnce(JsonBarsSerializer.class,
"ta4jexamples.datasources.json.JsonFileBarSeriesDataSource", "0.24.0");
}
@Deprecated(since = "0.19", forRemoval = true)
public static void persistSeries(BarSeries series, String filename) {
warnDeprecatedUse();
GsonBarSeries exportableSeries = GsonBarSeries.from(series);
Gson gson = new GsonBuilder().setPrettyPrinting().create();
FileWriter writer = null;
try {
writer = new FileWriter(filename);
gson.toJson(exportableSeries, writer);
LOG.debug("Bar series '{}' successfully saved to '{}'", series.getName(), filename);
} catch (IOException e) {
LOG.error("Unable to store bars in JSON", e);
} finally {
if (writer != null) {
try {
writer.flush();
writer.close();
} catch (IOException e) {
LOG.warn("Unable to flush and/or close file writer", e);
}
}
}
}
@Deprecated(since = "0.19", forRemoval = true)
public static BarSeries loadSeries(String filename) {
warnDeprecatedUse();
Gson gson = new Gson();
FileReader reader = null;
BarSeries result = null;
try {
reader = new FileReader(filename);
GsonBarSeries loadedSeries = gson.fromJson(reader, GsonBarSeries.class);
result = loadedSeries.toBarSeries();
LOG.debug("Bar series '" + result.getName() + "' successfully loaded. #Entries: " + result.getBarCount());
} catch (FileNotFoundException e) {
LOG.error("Unable to load bars from JSON", e);
} finally {
try {
if (reader != null)
reader.close();
} catch (IOException e) {
LOG.warn("Unable to close file reader", e);
}
}
return result;
}
/**
* Loads a BarSeries from the provided InputStream containing JSON data. The
* method parses the JSON content using Gson library and converts it to a
* BarSeries object. If the input stream is null or parsing fails, appropriate
* warning or error messages are logged and null is returned.
*
* @deprecated
* <p>
* Use {@link JsonFileBarSeriesDataSource#loadSeries(String)}
* instead.
*
* @param inputStream the input stream containing JSON data to be parsed into a
* BarSeries
* @return the loaded BarSeries object, or null if loading fails or input stream
* is null
*/
@Deprecated(since = "0.19", forRemoval = true)
public static BarSeries loadSeries(InputStream inputStream) {
warnDeprecatedUse();
if (inputStream == null) {
LOG.warn("Input stream is null, returning null");
return null;
}
Gson gson = new Gson();
InputStreamReader reader = null;
BarSeries result = null;
try {
reader = new InputStreamReader(inputStream);
GsonBarSeries loadedSeries = gson.fromJson(reader, GsonBarSeries.class);
if (loadedSeries == null) {
LOG.warn("Failed to parse JSON, loadedSeries is null");
return null;
}
result = loadedSeries.toBarSeries();
LOG.debug("Bar series '" + result.getName() + "' successfully loaded. #Entries: " + result.getBarCount());
} catch (Exception e) {
LOG.error("Unable to load bars from JSON", e);
} finally {
try {
if (reader != null)
reader.close();
} catch (IOException e) {
LOG.warn("Error closing input stream reader", e);
}
}
return result;
}
}