feat(data): expose CandleReader (CSV) natively in all 10 languages (#311)

Add the data-layer CSV candle reader to every binding so loading OHLCV
candles from a CSV no longer needs a per-language CSV/dataframe dependency.

- C ABI: wickra_candle_reader_new(bytes, len) / _count / _read / _free over
  an opaque CandleReader handle (parse the whole buffer up front, then drain).
- Native: Node/WASM CandleReader.read() -> Candle[], Python read() -> list[tuple].
- C-ABI languages: Go Read() []Candle, C# Candle[] Read(), Java Candle[] read(),
  R read() S3 generic (n x 6 matrix); C / C++ call the C ABI directly.
- Cross-language golden testdata/golden/data_csv*.csv pins the parsed candles
  bit-for-bit across every binding.

Verified locally across Rust (test+clippy+fmt), Node, WASM, Python, C#, Go,
Java, R, and the C/C++ cmake parity suite.
This commit is contained in:
kingchenc
2026-06-16 00:10:58 +02:00
committed by GitHub
parent cb6da4d737
commit d362ae26a3
31 changed files with 867 additions and 6 deletions
+10
View File
@@ -8,6 +8,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Added
- **CSV candle reading in all 10 languages (data layer).** The `CandleReader`
parses a `timestamp,open,high,low,close,volume` CSV buffer (a leading UTF-8 BOM
and field whitespace are tolerated) into candles: construct it from a CSV string
and call `read()` for every candle in file order. Exposed natively (Node.js /
WASM `read(): Candle[]`, Python `read() -> list[tuple]`) and over the C ABI as Go
`Read() []Candle`, C# `Candle[] Read()`, Java `Candle[] read()`, and the R
`read()` S3 generic (an `n×6` matrix); C / C++ call `wickra_candle_reader_new` /
`_count` / `_read` directly. A cross-language golden
(`testdata/golden/data_csv*.csv`) pins the parsed candles identically across
every binding. This makes CSV backtest loading dependency-free in every binding.
- **Candle resampling in all 10 languages (data layer).** The `Resampler`
aggregates candles into a higher timeframe (e.g. 1m → 5m): `update(open, high,
low, close, volume, timestamp)` returns the completed higher-timeframe candle on
+12
View File
@@ -128,6 +128,8 @@ typedef struct CalmarRatio CalmarRatio;
typedef struct Camarilla Camarilla;
typedef struct CandleReader CandleReader;
typedef struct CandleVolume CandleVolume;
typedef struct Cci Cci;
@@ -13736,6 +13738,16 @@ bool wickra_resampler_flush(struct Resampler *handle, struct WickraCandle *out);
void wickra_resampler_free(struct Resampler *handle);
struct CandleReader *wickra_candle_reader_new(const uint8_t *data, uintptr_t len);
uintptr_t wickra_candle_reader_count(const struct CandleReader *handle);
uintptr_t wickra_candle_reader_read(struct CandleReader *handle,
struct WickraCandle *out,
uintptr_t cap);
void wickra_candle_reader_free(struct CandleReader *handle);
#ifdef __cplusplus
} // extern "C"
#endif // __cplusplus
+93
View File
@@ -68286,6 +68286,99 @@ pub unsafe extern "C" fn wickra_resampler_free(handle: *mut Resampler) {
}
}
/// Opaque CSV candle reader: parses an entire `timestamp,open,high,low,close,volume`
/// CSV buffer up front and hands the candles out in drain order. Named
/// `CandleReader` (the public C-ABI handle); the inner `wickra-data` reader is
/// reached through its full path to avoid the name clash.
#[derive(Debug)]
pub struct CandleReader {
candles: Vec<Candle>,
pos: usize,
}
/// Parse an OHLCV CSV buffer (`len` bytes at `data`) into candles. The first line
/// must be a header naming `timestamp,open,high,low,close,volume` (a leading UTF-8
/// BOM and field whitespace are tolerated). Returns `NULL` on a `NULL` pointer or a
/// malformed CSV (missing column, unparseable row, or an OHLC relation the core
/// rejects). Read the candles with `wickra_candle_reader_read` and release with
/// `wickra_candle_reader_free`.
///
/// # Safety
/// `data` must point to `len` readable bytes, or be `NULL`.
#[no_mangle]
pub unsafe extern "C" fn wickra_candle_reader_new(
data: *const u8,
len: usize,
) -> *mut CandleReader {
if data.is_null() {
return ptr::null_mut();
}
let bytes = slice::from_raw_parts(data, len);
let Ok(mut reader) = wickra_data::csv::CandleReader::from_reader(bytes) else {
return ptr::null_mut();
};
match reader.read_all() {
Ok(candles) => Box::into_raw(Box::new(CandleReader { candles, pos: 0 })),
Err(_) => ptr::null_mut(),
}
}
/// Number of candles not yet read from the reader. Returns `0` on a `NULL` handle.
///
/// # Safety
/// `handle` must be valid (from `wickra_candle_reader_new`, not freed), or `NULL`.
#[no_mangle]
pub unsafe extern "C" fn wickra_candle_reader_count(handle: *const CandleReader) -> usize {
match handle.as_ref() {
Some(reader) => reader.candles.len() - reader.pos,
None => 0,
}
}
/// Copy up to `cap` not-yet-read candles into `out`, advance past them, and return
/// the number written. Returns `0` on a `NULL` handle / `out`. Call with `cap` equal
/// to `wickra_candle_reader_count` to drain every candle in one call.
///
/// # Safety
/// `handle` (from `wickra_candle_reader_new`, not freed) and `out` must be valid or
/// `NULL`; when non-`NULL`, `out` must cover `cap` `WickraCandle` elements.
#[no_mangle]
pub unsafe extern "C" fn wickra_candle_reader_read(
handle: *mut CandleReader,
out: *mut WickraCandle,
cap: usize,
) -> usize {
let Some(reader) = handle.as_mut() else {
return 0;
};
if out.is_null() {
return 0;
}
let count = (reader.candles.len() - reader.pos).min(cap);
let slots = slice::from_raw_parts_mut(out, count);
for (slot, candle) in slots
.iter_mut()
.zip(&reader.candles[reader.pos..reader.pos + count])
{
*slot = candle_to_c(*candle);
}
reader.pos += count;
count
}
/// Destroy a candle reader created by `wickra_candle_reader_new`. No-op if `handle`
/// is `NULL`.
///
/// # Safety
/// `handle` must have been returned by `wickra_candle_reader_new` and not previously
/// freed, or `NULL`.
#[no_mangle]
pub unsafe extern "C" fn wickra_candle_reader_free(handle: *mut CandleReader) {
if !handle.is_null() {
drop(Box::from_raw(handle));
}
}
#[cfg(test)]
mod tests {
use super::*;
@@ -59,6 +59,26 @@ public class DataLayerTests
}
}
[Fact]
public void CandleReaderMatchesGolden()
{
var csv = File.ReadAllText(Path.Combine(GoldenDir(), "data_csv.csv"));
using var reader = new CandleReader(csv);
var candles = reader.Read();
var want = Read("data_csv_candles");
Assert.Equal(want.Length, candles.Length);
for (var i = 0; i < candles.Length; i++)
{
var c = candles[i];
var got = new[] { c.Open, c.High, c.Low, c.Close, c.Volume, (double)c.Timestamp };
for (var j = 0; j < 6; j++)
{
var tol = 1e-9 * Math.Max(1, Math.Abs(want[i][j]));
Assert.True(Math.Abs(got[j] - want[i][j]) <= tol, $"candle reader row {i} col {j}: {got[j]} vs {want[i][j]}");
}
}
}
[Theory]
[InlineData(false, "data_candles")]
[InlineData(true, "data_candles_gap")]
@@ -4981,6 +4981,60 @@ public sealed class Camarilla : IDisposable
public void Dispose() => _handle.Dispose();
}
public sealed class CandleReader : IDisposable
{
private readonly WickraHandle _handle;
/// <summary>Parse a timestamp,open,high,low,close,volume CSV string (a
/// leading UTF-8 BOM and field whitespace are tolerated).</summary>
public CandleReader(string csv)
{
ArgumentNullException.ThrowIfNull(csv);
var bytes = System.Text.Encoding.UTF8.GetBytes(csv);
nint ptr;
unsafe
{
fixed (byte* data = bytes)
{
ptr = NativeMethods.wickra_candle_reader_new(data, (nuint)bytes.Length);
}
}
if (ptr == nint.Zero)
{
throw new ArgumentException("invalid CandleReader CSV");
}
_handle = new WickraHandle(ptr, NativeMethods.wickra_candle_reader_free);
}
/// <summary>Every candle parsed from the CSV, in file order.</summary>
public Candle[] Read()
{
var count = (long)NativeMethods.wickra_candle_reader_count(_handle.DangerousGetHandle());
GC.KeepAlive(_handle);
if (count <= 0)
{
return Array.Empty<Candle>();
}
var buffer = new WickraCandle[count];
unsafe
{
fixed (WickraCandle* ptr = buffer)
{
NativeMethods.wickra_candle_reader_read(_handle.DangerousGetHandle(), ptr, (nuint)count);
}
}
GC.KeepAlive(_handle);
var result = new Candle[count];
for (var i = 0; i < count; i++)
{
result[i] = new Candle(buffer[i].open, buffer[i].high, buffer[i].low, buffer[i].close, buffer[i].volume, buffer[i].timestamp);
}
return result;
}
public void Dispose() => _handle.Dispose();
}
public sealed class CandleVolume : IDisposable
{
private readonly WickraHandle _handle;
@@ -12430,6 +12430,18 @@ internal static partial class NativeMethods
[LibraryImport(WickraNative.LibraryName)]
internal static partial void wickra_resampler_free(nint handle);
[LibraryImport(WickraNative.LibraryName)]
internal static unsafe partial nint wickra_candle_reader_new(byte* data, nuint len);
[LibraryImport(WickraNative.LibraryName)]
internal static partial nuint wickra_candle_reader_count(nint handle);
[LibraryImport(WickraNative.LibraryName)]
internal static unsafe partial nuint wickra_candle_reader_read(nint handle, WickraCandle* @out, nuint cap);
[LibraryImport(WickraNative.LibraryName)]
internal static partial void wickra_candle_reader_free(nint handle);
}
[StructLayout(LayoutKind.Sequential)]
+30
View File
@@ -2,6 +2,7 @@ package wickra
import (
"math"
"os"
"strconv"
"testing"
)
@@ -51,6 +52,35 @@ func TestResamplerGolden(t *testing.T) {
}
}
func TestCandleReaderGolden(t *testing.T) {
csv, err := os.ReadFile("../../testdata/golden/data_csv.csv")
if err != nil {
t.Fatalf("read data_csv.csv: %v", err)
}
r, err := NewCandleReader(string(csv))
if err != nil {
t.Fatalf("new: %v", err)
}
defer r.Close()
candles := r.Read()
got := make([][6]float64, len(candles))
for i, c := range candles {
got[i] = [6]float64{c.Open, c.High, c.Low, c.Close, c.Volume, float64(c.Timestamp)}
}
want := readGolden(t, "data_csv_candles")
if len(got) != len(want) {
t.Fatalf("candle reader: %d candles vs %d", len(got), len(want))
}
for i := range got {
for j := 0; j < 6; j++ {
w := dataParseF(t, want[i][j])
if math.Abs(got[i][j]-w) > 1e-9*math.Max(1, math.Abs(w)) {
t.Errorf("candle reader row %d col %d: %v vs %v", i, j, got[i][j], w)
}
}
}
}
func TestTickAggregatorGolden(t *testing.T) {
ticks := readGolden(t, "data_ticks")
cases := []struct {
+12
View File
@@ -128,6 +128,8 @@ typedef struct CalmarRatio CalmarRatio;
typedef struct Camarilla Camarilla;
typedef struct CandleReader CandleReader;
typedef struct CandleVolume CandleVolume;
typedef struct Cci Cci;
@@ -13736,6 +13738,16 @@ bool wickra_resampler_flush(struct Resampler *handle, struct WickraCandle *out);
void wickra_resampler_free(struct Resampler *handle);
struct CandleReader *wickra_candle_reader_new(const uint8_t *data, uintptr_t len);
uintptr_t wickra_candle_reader_count(const struct CandleReader *handle);
uintptr_t wickra_candle_reader_read(struct CandleReader *handle,
struct WickraCandle *out,
uintptr_t cap);
void wickra_candle_reader_free(struct CandleReader *handle);
#ifdef __cplusplus
} // extern "C"
#endif // __cplusplus
+51
View File
@@ -5746,6 +5746,57 @@ func (ind *Camarilla) Close() {
}
}
// CandleReader parses an OHLCV CSV buffer into candles over the Wickra C ABI.
type CandleReader struct {
handle *C.struct_CandleReader
}
// NewCandleReader parses a timestamp,open,high,low,close,volume CSV string (a
// leading UTF-8 BOM and field whitespace are tolerated). It returns
// ErrInvalidParams when the header or a row is malformed.
func NewCandleReader(csv string) (*CandleReader, error) {
data := []byte(csv)
var ptr *C.struct_CandleReader
if len(data) == 0 {
ptr = C.wickra_candle_reader_new(nil, 0)
} else {
ptr = C.wickra_candle_reader_new((*C.uint8_t)(unsafe.Pointer(&data[0])), C.uintptr_t(len(data)))
}
if ptr == nil {
return nil, ErrInvalidParams
}
obj := &CandleReader{handle: ptr}
runtime.SetFinalizer(obj, (*CandleReader).Close)
return obj, nil
}
// Read returns every candle parsed from the CSV, in file order.
func (ind *CandleReader) Read() []Candle {
n := int(C.wickra_candle_reader_count(ind.handle))
runtime.KeepAlive(ind)
if n <= 0 {
return nil
}
buf := make([]C.struct_WickraCandle, n)
C.wickra_candle_reader_read(ind.handle, &buf[0], C.uintptr_t(n))
runtime.KeepAlive(ind)
out := make([]Candle, n)
for i := 0; i < n; i++ {
out[i] = Candle{float64(buf[i].open), float64(buf[i].high), float64(buf[i].low), float64(buf[i].close), float64(buf[i].volume), int64(buf[i].timestamp)}
}
return out
}
// Close frees the native handle. It is idempotent and safe to call
// alongside the finalizer.
func (ind *CandleReader) Close() {
if ind.handle != nil {
C.wickra_candle_reader_free(ind.handle)
ind.handle = nil
runtime.SetFinalizer(ind, nil)
}
}
// CandleVolume wraps the CandleVolume indicator over the Wickra C ABI.
type CandleVolume struct {
handle *C.struct_CandleVolume
@@ -0,0 +1,69 @@
// Generated from bindings/c/include/wickra.h. Do not edit by hand.
package org.wickra;
import org.wickra.internal.NativeMethods;
import org.wickra.internal.WickraNative;
import java.lang.foreign.Arena;
import java.lang.foreign.MemorySegment;
import java.lang.ref.Cleaner;
import static java.lang.foreign.ValueLayout.*;
/** CSV candle reader over the Wickra C ABI. Not thread-safe; close when done. */
public final class CandleReader implements AutoCloseable {
private final MemorySegment handle;
private final Cleaner.Cleanable cleanable;
/** Parse a timestamp,open,high,low,close,volume CSV string (a leading
* UTF-8 BOM and field whitespace are tolerated). */
public CandleReader(String csv) {
if (csv == null) {
throw new NullPointerException("csv");
}
byte[] bytes = csv.getBytes(java.nio.charset.StandardCharsets.UTF_8);
MemorySegment h;
try (Arena a = Arena.ofConfined()) {
MemorySegment data = a.allocate(Math.max(1L, bytes.length));
MemorySegment.copy(bytes, 0, data, JAVA_BYTE, 0L, bytes.length);
h = (MemorySegment) NativeMethods.WICKRA_CANDLE_READER_NEW.invokeExact(data, (long) bytes.length);
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
if (h.address() == 0L) {
throw new IllegalArgumentException("invalid CandleReader CSV");
}
this.handle = h;
this.cleanable = WickraNative.register(this, h, NativeMethods.WICKRA_CANDLE_READER_FREE);
}
/** Every candle parsed from the CSV, in file order. */
public Candle[] read() {
try {
long n = (long) NativeMethods.WICKRA_CANDLE_READER_COUNT.invokeExact(handle);
if (n <= 0) {
return new Candle[0];
}
try (Arena a = Arena.ofConfined()) {
MemorySegment out = a.allocate(48L * n);
long w = (long) NativeMethods.WICKRA_CANDLE_READER_READ.invokeExact(handle, out, n);
Candle[] result = new Candle[(int) w];
for (int i = 0; i < w; i++) {
long b = (long) i * 48L;
result[i] = new Candle(
out.get(JAVA_DOUBLE, b + 0L),
out.get(JAVA_DOUBLE, b + 8L),
out.get(JAVA_DOUBLE, b + 16L),
out.get(JAVA_DOUBLE, b + 24L),
out.get(JAVA_DOUBLE, b + 32L),
(double) out.get(JAVA_LONG, b + 40L));
}
return result;
}
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
@Override public void close() {
cleanable.clean();
}
}
@@ -3955,6 +3955,10 @@ public final class NativeMethods {
public static MethodHandle WICKRA_RESAMPLER_UPDATE;
public static MethodHandle WICKRA_RESAMPLER_FLUSH;
public static MethodHandle WICKRA_RESAMPLER_FREE;
public static MethodHandle WICKRA_CANDLE_READER_NEW;
public static MethodHandle WICKRA_CANDLE_READER_COUNT;
public static MethodHandle WICKRA_CANDLE_READER_READ;
public static MethodHandle WICKRA_CANDLE_READER_FREE;
static {
init0();
@@ -8031,6 +8035,10 @@ public final class NativeMethods {
WICKRA_RESAMPLER_UPDATE = h("wickra_resampler_update", FunctionDescriptor.of(JAVA_BYTE, ADDRESS, JAVA_DOUBLE, JAVA_DOUBLE, JAVA_DOUBLE, JAVA_DOUBLE, JAVA_DOUBLE, JAVA_LONG, ADDRESS));
WICKRA_RESAMPLER_FLUSH = h("wickra_resampler_flush", FunctionDescriptor.of(JAVA_BYTE, ADDRESS, ADDRESS));
WICKRA_RESAMPLER_FREE = h("wickra_resampler_free", FunctionDescriptor.ofVoid(ADDRESS));
WICKRA_CANDLE_READER_NEW = h("wickra_candle_reader_new", FunctionDescriptor.of(ADDRESS, ADDRESS, JAVA_LONG));
WICKRA_CANDLE_READER_COUNT = h("wickra_candle_reader_count", FunctionDescriptor.of(JAVA_LONG, ADDRESS));
WICKRA_CANDLE_READER_READ = h("wickra_candle_reader_read", FunctionDescriptor.of(JAVA_LONG, ADDRESS, ADDRESS, JAVA_LONG));
WICKRA_CANDLE_READER_FREE = h("wickra_candle_reader_free", FunctionDescriptor.ofVoid(ADDRESS));
}
}
@@ -73,6 +73,25 @@ class DataLayerTest {
}
}
@Test
void candleReaderMatchesGolden() throws IOException {
String csv = Files.readString(goldenDir().resolve("data_csv.csv"));
try (CandleReader reader = new CandleReader(csv)) {
Candle[] candles = reader.read();
double[][] want = read("data_csv_candles");
assertEquals(want.length, candles.length, "candle reader count");
for (int i = 0; i < candles.length; i++) {
Candle k = candles[i];
double[] got = {k.open(), k.high(), k.low(), k.close(), k.volume(), (double) k.timestamp()};
for (int j = 0; j < 6; j++) {
double w = want[i][j];
assertTrue(Math.abs(got[j] - w) <= 1e-9 * Math.max(1, Math.abs(w)),
"candle reader row " + i + " col " + j + ": " + got[j] + " vs " + w);
}
}
}
}
@Test
void tickAggregatorMatchesGolden() throws IOException {
double[][] ticks = read("data_ticks");
+5 -4
View File
@@ -27,10 +27,11 @@ const BAR_BUILDERS = new Set([
'ThreeLineBreakBars',
]);
// Data-layer types (tick aggregator, resampler) are not `Indicator`s: they
// transform raw market data into candles and have their own update/flush shape,
// so they are excluded from the streaming-indicator completeness contract.
const DATA_LAYER = new Set(['TickAggregator', 'Resampler']);
// Data-layer types (tick aggregator, resampler, CSV candle reader) are not
// `Indicator`s: they transform raw market data into candles and have their own
// update/flush/read shape, so they are excluded from the streaming-indicator
// completeness contract.
const DATA_LAYER = new Set(['TickAggregator', 'Resampler', 'CandleReader']);
// An "indicator class" is an exported constructor whose prototype carries the
// streaming `update` method. This excludes `version` (a plain function), the bar
+8 -1
View File
@@ -8,7 +8,7 @@ const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const { TickAggregator, Resampler } = require('..');
const { TickAggregator, Resampler, CandleReader } = require('..');
const GOLDEN = path.resolve(__dirname, '..', '..', '..', 'testdata', 'golden');
@@ -52,6 +52,13 @@ test('tick aggregator gap-fill matches the golden candles', () => {
assertCandles(run(true), readCsv('data_candles_gap'), 'gap');
});
test('candle reader matches the golden candles', () => {
const csv = fs.readFileSync(path.join(GOLDEN, 'data_csv.csv'), 'utf8');
const reader = new CandleReader(csv);
const got = reader.read().map((c) => [c.open, c.high, c.low, c.close, c.volume, c.timestamp]);
assertCandles(got, readCsv('data_csv_candles'), 'candle-reader');
});
const INPUT = readCsv('input'); // open,high,low,close,volume (timestamp = row index)
function runResample() {
+11
View File
@@ -5973,3 +5973,14 @@ export declare class Resampler {
/** Emit the final, still-open candle (or `null` if none is pending). */
flush(): CandleValue | null
}
export type CandleReaderNode = CandleReader
/**
* Parse OHLCV candles from a CSV string (header `timestamp,open,high,low,close,
* volume`; a leading UTF-8 BOM is stripped).
*/
export declare class CandleReader {
/** Parse the whole CSV up front; throws on a malformed header or row. */
constructor(csv: string)
/** Return every parsed candle as `{ open, high, low, close, volume, timestamp }`. */
read(): Array<CandleValue>
}
File diff suppressed because one or more lines are too long
+27
View File
@@ -22000,3 +22000,30 @@ impl ResamplerNode {
.map(candle_to_value))
}
}
// ===== Data layer: CSV candle reader =====
/// Parse OHLCV candles from a CSV string (header `timestamp,open,high,low,close,
/// volume`; a leading UTF-8 BOM is stripped).
#[napi(js_name = "CandleReader")]
pub struct CandleReaderNode {
candles: Vec<wc::Candle>,
}
#[napi]
impl CandleReaderNode {
/// Parse the whole CSV up front; throws on a malformed header or row.
#[napi(constructor)]
pub fn new(csv: String) -> napi::Result<Self> {
let mut reader =
wickra_data::csv::CandleReader::from_reader(csv.as_bytes()).map_err(map_data_err)?;
let candles = reader.read_all().map_err(map_data_err)?;
Ok(Self { candles })
}
/// Return every parsed candle as `{ open, high, low, close, volume, timestamp }`.
#[napi]
pub fn read(&self) -> Vec<CandleValue> {
self.candles.iter().map(|&c| candle_to_value(c)).collect()
}
}
@@ -361,6 +361,7 @@ from ._wickra import (
# Data layer
TickAggregator,
Resampler,
CandleReader,
# Market Profile
CompositeProfile,
HighLowVolumeNodes,
@@ -908,6 +909,7 @@ __all__ = [
# Data layer
"TickAggregator",
"Resampler",
"CandleReader",
# Market Profile
"CompositeProfile",
"HighLowVolumeNodes",
+30
View File
@@ -28288,6 +28288,7 @@ fn _wickra(_py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> {
// Data layer.
m.add_class::<PyTickAggregator>()?;
m.add_class::<PyResampler>()?;
m.add_class::<PyCandleReader>()?;
// Candlestick patterns.
m.add_class::<PyDoji>()?;
m.add_class::<PyHammer>()?;
@@ -28666,3 +28667,32 @@ impl PyResampler {
.map(|c| (c.open, c.high, c.low, c.close, c.volume, c.timestamp)))
}
}
// ===== Data layer: CSV candle reader =====
/// Parse OHLCV candles from a CSV string (header `timestamp,open,high,low,close,
/// volume`; a leading UTF-8 BOM is stripped).
#[pyclass(name = "CandleReader", module = "wickra._wickra", skip_from_py_object)]
#[derive(Clone)]
struct PyCandleReader {
candles: Vec<wc::Candle>,
}
#[pymethods]
impl PyCandleReader {
#[new]
fn new(csv: &str) -> PyResult<Self> {
let mut reader =
wickra_data::csv::CandleReader::from_reader(csv.as_bytes()).map_err(map_data_err)?;
let candles = reader.read_all().map_err(map_data_err)?;
Ok(Self { candles })
}
/// Return every parsed candle as `(open, high, low, close, volume, timestamp)`.
fn read(&self) -> Vec<CandleTuple> {
self.candles
.iter()
.map(|c| (c.open, c.high, c.low, c.close, c.volume, c.timestamp))
.collect()
}
}
+12
View File
@@ -44,6 +44,18 @@ def test_tick_aggregator_matches_golden(gap_fill, fixture):
assert abs(g[j] - w[j]) <= tol, f"row {i} col {j}: {g[j]} vs {w[j]}"
def test_candle_reader_matches_golden():
with open(os.path.join(GOLDEN, "data_csv.csv")) as f:
text = f.read()
got = ta.CandleReader(text).read()
want = _read("data_csv_candles")
assert len(got) == len(want)
for i, (g, w) in enumerate(zip(got, want)):
for j in range(6):
tol = 1e-9 * max(1.0, abs(w[j]))
assert abs(g[j] - w[j]) <= tol, f"row {i} col {j}: {g[j]} vs {w[j]}"
INPUT = _read("input") # open,high,low,close,volume (timestamp = row index)
+3
View File
@@ -5,6 +5,7 @@ S3method(flush,wickra_indicator)
S3method(is_ready,wickra_indicator)
S3method(name,wickra_indicator)
S3method(push,wickra_indicator)
S3method(read,wickra_indicator)
S3method(reset,wickra_indicator)
S3method(update,wickra_indicator)
S3method(warmup_period,wickra_indicator)
@@ -67,6 +68,7 @@ export(Butterfly)
export(CalendarSpread)
export(CalmarRatio)
export(Camarilla)
export(CandleReader)
export(CandleVolume)
export(Cci)
export(CenterOfGravity)
@@ -528,6 +530,7 @@ export(batch)
export(is_ready)
export(name)
export(push)
export(read)
export(reset)
export(warmup_period)
importFrom(stats,update)
+8
View File
@@ -479,6 +479,14 @@ Camarilla <- function() {
.wk_obj("camarilla", ptr, "Camarilla")
}
#' CandleReader: parse OHLCV candles from a CSV string
#' @keywords internal
#' @export
CandleReader <- function(csv) {
ptr <- .Call("wk_candle_reader_new", csv, PACKAGE = "wickra")
.wk_obj("candle_reader", ptr, "CandleReader")
}
#' CandleVolume indicator
#' @keywords internal
#' @export
+23
View File
@@ -189,3 +189,26 @@ flush.wickra_indicator <- function(con) {
out <- .Call(paste0("wk_", con$prefix, "_flush"), con$ptr, PACKAGE = "wickra")
out
}
#' Read every candle parsed by a CSV candle reader
#'
#' Returns all the candles a [CandleReader()] parsed from its CSV, as a numeric
#' matrix with columns `open`, `high`, `low`, `close`, `volume`, `timestamp`.
#'
#' @param object A `wickra_indicator` created by [CandleReader()].
#' @return A numeric matrix with six named columns (zero rows for an empty CSV).
#' @examples
#' r <- CandleReader("timestamp,open,high,low,close,volume\n0,100,101,99,100.5,10\n")
#' read(r)
#' @export
read <- function(object) {
UseMethod("read")
}
#' @rdname read
#' @export
read.wickra_indicator <- function(object) {
out <- .Call(paste0("wk_", object$prefix, "_read"), object$ptr, PACKAGE = "wickra")
colnames(out) <- c("open", "high", "low", "close", "volume", "timestamp")
out
}
+36
View File
@@ -2603,6 +2603,40 @@ SEXP wk_camarilla_reset(SEXP e) {
return R_NilValue;
}
static void candle_reader_fin(SEXP e) {
struct CandleReader *h = (struct CandleReader *)R_ExternalPtrAddr(e);
if (h) wickra_candle_reader_free(h);
R_ClearExternalPtr(e);
}
SEXP wk_candle_reader_new(SEXP csv) {
SEXP s = STRING_ELT(csv, 0);
const char *str = CHAR(s);
struct CandleReader *h = wickra_candle_reader_new((const uint8_t *)str, (uintptr_t)LENGTH(s));
if (!h) Rf_error("invalid CandleReader CSV");
SEXP e = PROTECT(R_MakeExternalPtr(h, R_NilValue, R_NilValue));
R_RegisterCFinalizerEx(e, candle_reader_fin, TRUE);
UNPROTECT(1);
return e;
}
SEXP wk_candle_reader_read(SEXP e) {
struct CandleReader *h = (struct CandleReader *)R_ExternalPtrAddr(e);
uintptr_t n = wickra_candle_reader_count(h);
if (n == 0) return Rf_allocMatrix(REALSXP, 0, 6);
struct WickraCandle *buf = (struct WickraCandle *)R_alloc(n, sizeof(struct WickraCandle));
uintptr_t w = wickra_candle_reader_read(h, buf, n);
SEXP r = PROTECT(Rf_allocMatrix(REALSXP, (int)w, 6));
for (uintptr_t i = 0; i < w; i++) {
REAL(r)[i + w * 0] = buf[i].open;
REAL(r)[i + w * 1] = buf[i].high;
REAL(r)[i + w * 2] = buf[i].low;
REAL(r)[i + w * 3] = buf[i].close;
REAL(r)[i + w * 4] = buf[i].volume;
REAL(r)[i + w * 5] = (double)buf[i].timestamp;
}
UNPROTECT(1);
return r;
}
static void candle_volume_fin(SEXP e) {
struct CandleVolume *h = (struct CandleVolume *)R_ExternalPtrAddr(e);
if (h) wickra_candle_volume_free(h);
@@ -23021,6 +23055,8 @@ static const R_CallMethodDef CallEntries[] = {
{"wk_camarilla_is_ready", (DL_FUNC)&wk_camarilla_is_ready, 1},
{"wk_camarilla_name", (DL_FUNC)&wk_camarilla_name, 1},
{"wk_camarilla_reset", (DL_FUNC)&wk_camarilla_reset, 1},
{"wk_candle_reader_new", (DL_FUNC)&wk_candle_reader_new, 1},
{"wk_candle_reader_read", (DL_FUNC)&wk_candle_reader_read, 1},
{"wk_candle_volume_new", (DL_FUNC)&wk_candle_volume_new, 1},
{"wk_candle_volume_update", (DL_FUNC)&wk_candle_volume_update, 7},
{"wk_candle_volume_warmup_period", (DL_FUNC)&wk_candle_volume_warmup_period, 1},
@@ -51,6 +51,24 @@ test_that("tick aggregator matches the golden candles", {
}
})
test_that("candle reader matches the golden candles", {
gdir <- find_data_golden_dir()
skip_if(is.null(gdir), "golden fixtures not bundled with the package")
read_mat <- function(name) {
lines <- readLines(file.path(gdir, paste0(name, ".csv")))[-1]
lines <- lines[nzchar(lines)]
do.call(rbind, lapply(lines, function(l) as.numeric(strsplit(l, ",")[[1]])))
}
csv <- paste(readLines(file.path(gdir, "data_csv.csv")), collapse = "\n")
reader <- CandleReader(csv)
got <- unname(read(reader))
want <- unname(read_mat("data_csv_candles"))
expect_equal(nrow(got), nrow(want))
expect_equal(got, want, tolerance = 1e-9)
})
test_that("resampler matches the golden candles", {
gdir <- find_data_golden_dir()
skip_if(is.null(gdir), "golden fixtures not bundled with the package")
+31
View File
@@ -16101,3 +16101,34 @@ impl WasmResampler {
}
}
}
// ===== Data layer: CSV candle reader =====
/// Parse OHLCV candles from a CSV string (header `timestamp,open,high,low,close,
/// volume`; a leading UTF-8 BOM is stripped).
#[wasm_bindgen(js_name = CandleReader)]
pub struct WasmCandleReader {
candles: Vec<wc::Candle>,
}
#[wasm_bindgen(js_class = CandleReader)]
impl WasmCandleReader {
/// Parse the whole CSV up front; throws on a malformed header or row.
#[wasm_bindgen(constructor)]
pub fn new(csv: &str) -> Result<WasmCandleReader, JsError> {
let mut reader =
wickra_data::csv::CandleReader::from_reader(csv.as_bytes()).map_err(map_data_err)?;
let candles = reader.read_all().map_err(map_data_err)?;
Ok(Self { candles })
}
/// Return every parsed candle as a `{ open, high, low, close, volume,
/// timestamp }` object.
pub fn read(&self) -> Array {
let arr = Array::new();
for &c in &self.candles {
arr.push(&candle_object(c));
}
arr
}
}
+7
View File
@@ -50,6 +50,13 @@ test('wasm tick aggregator gap-fill matches the golden candles', () => {
assertCandles(run(true), readCsv('data_candles_gap'), 'gap');
});
test('wasm candle reader matches the golden candles', () => {
const csv = fs.readFileSync(path.join(GOLDEN, 'data_csv.csv'), 'utf8');
const reader = new W.CandleReader(csv);
const got = reader.read().map((c) => [c.open, c.high, c.low, c.close, c.volume, c.timestamp]);
assertCandles(got, readCsv('data_csv_candles'), 'candle-reader');
});
const INPUT = readCsv('input'); // open,high,low,close,volume (timestamp = row index)
function runResample() {
+44
View File
@@ -147,6 +147,49 @@ static int check_resample(void) {
return fails;
}
static int check_reader(void) {
char path[1024];
snprintf(path, sizeof(path), "%s/data_csv.csv", GDIR);
FILE *f = fopen(path, "rb");
if (!f) {
printf("FAIL reader: cannot open %s\n", path);
return 1;
}
static char buf[1 << 16];
size_t len = fread(buf, 1, sizeof(buf), f);
fclose(f);
struct CandleReader *r = wickra_candle_reader_new((const uint8_t *)buf, len);
if (!r) {
printf("FAIL reader: new returned NULL\n");
return 1;
}
double want[MAXROWS * 6];
int nw = read_csv("data_csv_candles", want, 6);
uintptr_t n = wickra_candle_reader_count(r);
struct WickraCandle cands[MAXROWS];
uintptr_t got = wickra_candle_reader_read(r, cands, n);
wickra_candle_reader_free(r);
if ((int)got != nw) {
printf("FAIL reader: %d candles vs %d\n", (int)got, nw);
return 1;
}
int fails = 0;
for (uintptr_t i = 0; i < got; i++) {
double row[6] = {cands[i].open, cands[i].high, cands[i].low,
cands[i].close, cands[i].volume, (double)cands[i].timestamp};
for (int j = 0; j < 6; j++) {
double w = want[i * 6 + j];
double tol = 1e-9 * fmax(1.0, fabs(w));
if (fabs(row[j] - w) > tol) {
printf("FAIL reader row %d col %d: %g vs %g\n", (int)i, j, row[j], w);
fails++;
}
}
}
return fails;
}
int main(int argc, char **argv) {
GDIR = (argc > 1) ? argv[1] : "testdata/golden";
double ticks[MAXROWS * 3];
@@ -154,6 +197,7 @@ int main(int argc, char **argv) {
int fails = check("data_candles", false, ticks, nt);
fails += check("data_candles_gap", true, ticks, nt);
fails += check_resample();
fails += check_reader();
if (fails == 0) {
printf("C/C++ data layer: OK (%d ticks)\n", nt);
}
+48
View File
@@ -187,9 +187,57 @@ fn main() {
emit_bars(dir, &candles);
emit_data_layer(dir);
emit_resampler(dir, &candles);
emit_candle_reader_csv(dir, &candles);
println!("golden fixtures written to {}", dir.display());
}
/// Data layer: the CSV candle reader. Writes a source CSV in the reader's required
/// `timestamp,open,high,low,close,volume` layout, plus the reference candles the
/// reader parses out of it. Every binding parses the same bytes and checks the
/// candles match — pinning column mapping and numeric round-tripping across the
/// FFI.
fn emit_candle_reader_csv(dir: &Path, candles: &[Candle]) {
let mut src = Vec::with_capacity(candles.len());
for c in candles {
src.push(format!(
"{},{},{},{},{},{}",
c.timestamp, c.open, c.high, c.low, c.close, c.volume
));
}
write_csv(
dir,
"data_csv",
"timestamp,open,high,low,close,volume",
&src,
);
// Reference parse: feed the same bytes back through the reader so the fixture
// is exactly what wickra-data's parser yields, not just the input echoed.
let mut bytes = String::from("timestamp,open,high,low,close,volume\n");
for row in &src {
bytes.push_str(row);
bytes.push('\n');
}
let mut reader =
wickra_data::csv::CandleReader::from_reader(bytes.as_bytes()).expect("valid candle reader");
let parsed = reader.read_all().expect("valid csv parse");
let rows: Vec<String> = parsed
.iter()
.map(|c| {
format!(
"{},{},{},{},{},{}",
c.open, c.high, c.low, c.close, c.volume, c.timestamp
)
})
.collect();
write_csv(
dir,
"data_csv_candles",
"open,high,low,close,volume,timestamp",
&rows,
);
}
/// Data layer: the resampler. Resamples the shared input candles (timestamp =
/// row index) into 5-unit buckets; the final partial bucket comes out of flush.
fn emit_resampler(dir: &Path, candles: &[Candle]) {
+81
View File
@@ -0,0 +1,81 @@
timestamp,open,high,low,close,volume
0,99,101,98,100,1000
1,102.57761950472302,104.77731091023225,101.25551066110417,103.4552020666134,1019.8669330795061
2,106.10612242808222,108.13914959894458,104.61339756308799,106.64642473395035,1038.941834230865
3,109.26253189460714,110.76487377959927,107.8309272112827,109.33326909627483,1056.4642473395036
4,111.7365376962194,112.90403177129735,110.1528967845943,111.32039085967226,1071.7356090899523
5,113.27609348158748,114.45148509543229,111.29955825219574,112.47494986604055,1084.1470984807897
6,113.7284688053824,115.1642566915892,111.30268842257516,112.73847630878196,1093.2039085967226
7,113.06855035377954,114.55977666009171,110.64086736017657,112.13209366648874,1098.544972998846
8,111.40827542637513,112.72390874531129,109.43899848657534,110.75463180551151,1099.9573603041506
9,108.98459460176909,109.99300155201126,107.76539185209613,108.7737988023383,1097.3847630878195
10,106.12753789513545,107.73969337995807,104.79904459577605,106.41120008059868,1090.929742682568
11,103.21387328427625,105.41662717550601,101.71978916733775,103.92254305856751,1080.849640381959
12,100.61462528040111,103.00209502109563,99.18732582635697,101.57479556705148,1067.546318055115
13,98.64575078243223,100.78188758933494,97.48620160125755,99.62233840816026,1051.5501371821465
14,97.53034002152081,99.46748184049008,96.34710045689485,98.28424227586412,1033.4988150155905
15,97.378063505514,99.16454670333486,95.93821562552817,97.72469882334903,1014.1120008059867
16,98.18385394545021,99.67344281002588,96.54876504706593,98.0383539116416,1005.837414342758
17,99.8438650794075,101.15293363552601,97.93278462060415,99.24185317672267,1025.5541102026832
18,102.1834853863248,103.20029690993537,100.25554360082955,101.27235512444012,1044.2520443294852
19,104.99031673022002,106.32510161131832,102.65835969292533,103.99314457402363,1061.185789094272
20,108.04491654708718,109.54022022493461,105.7105413401633,107.20584501801073,1075.680249530793
21,111.14367593283949,112.56654934841096,109.24526558927202,110.6681390048435,1087.157577241359
22,114.11098793714572,115.26697281350663,112.95942875877287,114.11541363513378,1095.1602073889517
23,116.80109288512898,118.47543335247398,115.61005717653698,117.28439764388199,1099.3691003633464
24,119.09282467975903,121.38046215528227,117.64904116296829,119.93667863849153,1099.616460883584
25,120.88220148856881,123.36781277048146,119.39438848583472,121.87999976774739,1095.8924274663138
26,122.07798667229586,124.2878498649492,120.77557026109272,122.98543345374605,1088.3454655720152
27,122.60397744514097,124.22410945235427,121.57876610123756,123.19889810845086,1077.2764487555987
28,122.40925186267498,123.88697089091687,121.06827005264091,122.5459890808828,1063.126663787232
29,121.48461656761053,122.98099977052848,119.63330909790388,121.12969230082183,1046.4602179413757
30,119.8808727652764,121.29920058454442,117.70285703314954,119.12118485241757,1027.9415498198925
31,117.72299769788953,118.8665240235534,115.60101790940676,116.74454423507063,1008.3089402817496
32,115.21439166809358,116.4131695096543,113.05795434620948,114.2567321877702,1011.6549204850494
33,112.6270381197495,114.07463180365934,110.47704737833695,111.92464106224679,1031.154136351338
34,110.27641646211617,111.76231568498811,108.51535390119264,110.00125312406458,1049.4113351138608
35,108.48360243707184,109.99872116521585,107.18792367213928,108.7030424002833,1065.6986598718788
36,107.530320991091,109.22424173559783,106.49671695482826,108.19063769933508,1079.3667863849153
37,107.61494922421186,109.90155645208623,106.26786689008574,108.55447411796011,1089.8708095811626
38,108.81801012516657,111.30403713029216,107.32068773822765,109.80671474335324,1096.7919672031487
39,111.08434919099572,113.29382811110736,109.67068524069803,111.88016416080967,1099.8543345374605
40,114.22618875818226,115.76972371414958,113.09073586402833,114.63427081999565,1098.9358246623383
41,117.94724565362051,119.15370942824079,116.6612183117217,117.86768208634197,1094.0730556679773
42,121.88395973243564,123.33523703654073,119.88495316810628,121.33623047221137,1085.459890808828
43,125.65653611891825,127.14038418498515,123.29089632531003,124.77474439137693,1073.4397097874114
44,128.92069597346688,130.20955349568973,126.63187762484937,127.92073514707224,1058.4917192891762
45,131.41114890560974,132.45313613345562,129.49585703767033,130.5378442655162,1041.2118485241756
46,132.96978971477444,134.3228744433646,131.08387196585088,132.43695669444105,1022.2889914100246
47,133.5549991814736,135.053120145851,131.99497292310176,133.49309388747918,1002.4775425453357
48,133.2323987581558,135.065460892756,131.8235156308926,133.65657776549278,1017.4326781222982
49,132.1510588175171,134.08480947785137,131.0237176510951,132.95746831142935,1036.647912925193
50,130.5116755897077,132.71696973631924,129.29758425495962,131.50287840157117,1054.402111088937
51,128.53409061924222,130.9222390672229,127.07925728332546,129.46740573130614,1069.9874687593544
52,126.4306172006658,128.5591966352313,124.94895708842891,127.07753652299444,1082.7826469085653
53,124.38927906587094,125.87336879788326,123.1073244542458,124.59141418625812,1092.2775421612807
54,122.56791894474918,123.61827749324543,121.2254215875191,122.27578013601534,1098.093623006649
55,121.09703354508844,122.45602084147426,119.02315927992295,120.38214657630877,1099.9990206550704
56,120.08693553049852,121.58571423995242,117.62555095473105,119.12432966418496,1097.9177729151318
57,119.63399522490424,121.03798192673773,117.25536324735009,118.65934994918358,1091.9328525664675
58,119.82146372498268,120.94065716085713,117.95421275941922,119.07340619529367,1082.2828594968707
59,120.7124947130592,121.93415307848436,119.152517136663,120.37417550208815,1069.3525084777123
60,122.33587608239566,123.94838830624106,120.87761530843784,122.49012753228324,1053.6572918000436
61,124.66785812762632,126.75625008370868,123.18852214765032,125.27691410373268,1035.8229282236828
62,127.61552102482923,129.80523536741393,126.34054904004905,128.53026338263376,1016.5604175448309
63,131.00775897051096,133.06294250948164,129.94904333909741,132.0042268780681,1003.3623047221139
64,134.59892592769242,136.79793765689536,133.234137558996,135.43314928819893,1023.1509825101539
65,138.08766685694894,140.0546941334019,136.58837142074304,138.555398697196,1042.016703682664
66,141.1500141222941,142.5489901806554,139.73776131670974,141.13673737507105,1059.2073514707224
67,143.48228026040917,144.5932922665057,141.8802335303824,142.99124553647894,1074.037588995245
68,144.84649927621132,146.0756620034572,142.76876627418082,143.9979290014267,1085.9161814856498
69,145.1098734724699,146.57143212166807,142.649968595823,144.11152724502116,1094.3695669444105
70,144.27024859045207,145.7471249168318,141.88968005898082,143.36655638536055,1099.060735569487
71,142.46200877506277,143.72992039092952,140.60631434563564,141.8742259615024,1099.8026652716362
72,139.9404686061768,141.00752471999962,138.7454488027266,139.81250491654941,1096.5657776549278
73,137.04703875358243,138.78071046739933,135.67655244818158,137.41022416199849,1089.4791172140503
74,134.16122174198705,136.42630679247307,132.66155074344638,134.9266357939324,1078.8252067375315
75,131.6480122345848,134.02210748888697,130.25415962109273,132.6282548753949,1065.0287840157116
76,129.80997879551026,131.86785162975886,128.70717960530897,130.76505243955756,1048.6398688853799
77,128.85205701193928,130.78472862032595,127.6154547134165,129.54812632180318,1030.3118356745701
78,128.8642014864336,130.59557044803222,127.39947545719488,129.13084441879352,1010.7753652299442
79,129.82321172551153,131.29749357237068,128.12086294416926,129.59514479102842,1009.1906850227682
1 timestamp open high low close volume
2 0 99 101 98 100 1000
3 1 102.57761950472302 104.77731091023225 101.25551066110417 103.4552020666134 1019.8669330795061
4 2 106.10612242808222 108.13914959894458 104.61339756308799 106.64642473395035 1038.941834230865
5 3 109.26253189460714 110.76487377959927 107.8309272112827 109.33326909627483 1056.4642473395036
6 4 111.7365376962194 112.90403177129735 110.1528967845943 111.32039085967226 1071.7356090899523
7 5 113.27609348158748 114.45148509543229 111.29955825219574 112.47494986604055 1084.1470984807897
8 6 113.7284688053824 115.1642566915892 111.30268842257516 112.73847630878196 1093.2039085967226
9 7 113.06855035377954 114.55977666009171 110.64086736017657 112.13209366648874 1098.544972998846
10 8 111.40827542637513 112.72390874531129 109.43899848657534 110.75463180551151 1099.9573603041506
11 9 108.98459460176909 109.99300155201126 107.76539185209613 108.7737988023383 1097.3847630878195
12 10 106.12753789513545 107.73969337995807 104.79904459577605 106.41120008059868 1090.929742682568
13 11 103.21387328427625 105.41662717550601 101.71978916733775 103.92254305856751 1080.849640381959
14 12 100.61462528040111 103.00209502109563 99.18732582635697 101.57479556705148 1067.546318055115
15 13 98.64575078243223 100.78188758933494 97.48620160125755 99.62233840816026 1051.5501371821465
16 14 97.53034002152081 99.46748184049008 96.34710045689485 98.28424227586412 1033.4988150155905
17 15 97.378063505514 99.16454670333486 95.93821562552817 97.72469882334903 1014.1120008059867
18 16 98.18385394545021 99.67344281002588 96.54876504706593 98.0383539116416 1005.837414342758
19 17 99.8438650794075 101.15293363552601 97.93278462060415 99.24185317672267 1025.5541102026832
20 18 102.1834853863248 103.20029690993537 100.25554360082955 101.27235512444012 1044.2520443294852
21 19 104.99031673022002 106.32510161131832 102.65835969292533 103.99314457402363 1061.185789094272
22 20 108.04491654708718 109.54022022493461 105.7105413401633 107.20584501801073 1075.680249530793
23 21 111.14367593283949 112.56654934841096 109.24526558927202 110.6681390048435 1087.157577241359
24 22 114.11098793714572 115.26697281350663 112.95942875877287 114.11541363513378 1095.1602073889517
25 23 116.80109288512898 118.47543335247398 115.61005717653698 117.28439764388199 1099.3691003633464
26 24 119.09282467975903 121.38046215528227 117.64904116296829 119.93667863849153 1099.616460883584
27 25 120.88220148856881 123.36781277048146 119.39438848583472 121.87999976774739 1095.8924274663138
28 26 122.07798667229586 124.2878498649492 120.77557026109272 122.98543345374605 1088.3454655720152
29 27 122.60397744514097 124.22410945235427 121.57876610123756 123.19889810845086 1077.2764487555987
30 28 122.40925186267498 123.88697089091687 121.06827005264091 122.5459890808828 1063.126663787232
31 29 121.48461656761053 122.98099977052848 119.63330909790388 121.12969230082183 1046.4602179413757
32 30 119.8808727652764 121.29920058454442 117.70285703314954 119.12118485241757 1027.9415498198925
33 31 117.72299769788953 118.8665240235534 115.60101790940676 116.74454423507063 1008.3089402817496
34 32 115.21439166809358 116.4131695096543 113.05795434620948 114.2567321877702 1011.6549204850494
35 33 112.6270381197495 114.07463180365934 110.47704737833695 111.92464106224679 1031.154136351338
36 34 110.27641646211617 111.76231568498811 108.51535390119264 110.00125312406458 1049.4113351138608
37 35 108.48360243707184 109.99872116521585 107.18792367213928 108.7030424002833 1065.6986598718788
38 36 107.530320991091 109.22424173559783 106.49671695482826 108.19063769933508 1079.3667863849153
39 37 107.61494922421186 109.90155645208623 106.26786689008574 108.55447411796011 1089.8708095811626
40 38 108.81801012516657 111.30403713029216 107.32068773822765 109.80671474335324 1096.7919672031487
41 39 111.08434919099572 113.29382811110736 109.67068524069803 111.88016416080967 1099.8543345374605
42 40 114.22618875818226 115.76972371414958 113.09073586402833 114.63427081999565 1098.9358246623383
43 41 117.94724565362051 119.15370942824079 116.6612183117217 117.86768208634197 1094.0730556679773
44 42 121.88395973243564 123.33523703654073 119.88495316810628 121.33623047221137 1085.459890808828
45 43 125.65653611891825 127.14038418498515 123.29089632531003 124.77474439137693 1073.4397097874114
46 44 128.92069597346688 130.20955349568973 126.63187762484937 127.92073514707224 1058.4917192891762
47 45 131.41114890560974 132.45313613345562 129.49585703767033 130.5378442655162 1041.2118485241756
48 46 132.96978971477444 134.3228744433646 131.08387196585088 132.43695669444105 1022.2889914100246
49 47 133.5549991814736 135.053120145851 131.99497292310176 133.49309388747918 1002.4775425453357
50 48 133.2323987581558 135.065460892756 131.8235156308926 133.65657776549278 1017.4326781222982
51 49 132.1510588175171 134.08480947785137 131.0237176510951 132.95746831142935 1036.647912925193
52 50 130.5116755897077 132.71696973631924 129.29758425495962 131.50287840157117 1054.402111088937
53 51 128.53409061924222 130.9222390672229 127.07925728332546 129.46740573130614 1069.9874687593544
54 52 126.4306172006658 128.5591966352313 124.94895708842891 127.07753652299444 1082.7826469085653
55 53 124.38927906587094 125.87336879788326 123.1073244542458 124.59141418625812 1092.2775421612807
56 54 122.56791894474918 123.61827749324543 121.2254215875191 122.27578013601534 1098.093623006649
57 55 121.09703354508844 122.45602084147426 119.02315927992295 120.38214657630877 1099.9990206550704
58 56 120.08693553049852 121.58571423995242 117.62555095473105 119.12432966418496 1097.9177729151318
59 57 119.63399522490424 121.03798192673773 117.25536324735009 118.65934994918358 1091.9328525664675
60 58 119.82146372498268 120.94065716085713 117.95421275941922 119.07340619529367 1082.2828594968707
61 59 120.7124947130592 121.93415307848436 119.152517136663 120.37417550208815 1069.3525084777123
62 60 122.33587608239566 123.94838830624106 120.87761530843784 122.49012753228324 1053.6572918000436
63 61 124.66785812762632 126.75625008370868 123.18852214765032 125.27691410373268 1035.8229282236828
64 62 127.61552102482923 129.80523536741393 126.34054904004905 128.53026338263376 1016.5604175448309
65 63 131.00775897051096 133.06294250948164 129.94904333909741 132.0042268780681 1003.3623047221139
66 64 134.59892592769242 136.79793765689536 133.234137558996 135.43314928819893 1023.1509825101539
67 65 138.08766685694894 140.0546941334019 136.58837142074304 138.555398697196 1042.016703682664
68 66 141.1500141222941 142.5489901806554 139.73776131670974 141.13673737507105 1059.2073514707224
69 67 143.48228026040917 144.5932922665057 141.8802335303824 142.99124553647894 1074.037588995245
70 68 144.84649927621132 146.0756620034572 142.76876627418082 143.9979290014267 1085.9161814856498
71 69 145.1098734724699 146.57143212166807 142.649968595823 144.11152724502116 1094.3695669444105
72 70 144.27024859045207 145.7471249168318 141.88968005898082 143.36655638536055 1099.060735569487
73 71 142.46200877506277 143.72992039092952 140.60631434563564 141.8742259615024 1099.8026652716362
74 72 139.9404686061768 141.00752471999962 138.7454488027266 139.81250491654941 1096.5657776549278
75 73 137.04703875358243 138.78071046739933 135.67655244818158 137.41022416199849 1089.4791172140503
76 74 134.16122174198705 136.42630679247307 132.66155074344638 134.9266357939324 1078.8252067375315
77 75 131.6480122345848 134.02210748888697 130.25415962109273 132.6282548753949 1065.0287840157116
78 76 129.80997879551026 131.86785162975886 128.70717960530897 130.76505243955756 1048.6398688853799
79 77 128.85205701193928 130.78472862032595 127.6154547134165 129.54812632180318 1030.3118356745701
80 78 128.8642014864336 130.59557044803222 127.39947545719488 129.13084441879352 1010.7753652299442
81 79 129.82321172551153 131.29749357237068 128.12086294416926 129.59514479102842 1009.1906850227682
+81
View File
@@ -0,0 +1,81 @@
open,high,low,close,volume,timestamp
99,101,98,100,1000,0
102.57761950472302,104.77731091023225,101.25551066110417,103.4552020666134,1019.8669330795061,1
106.10612242808222,108.13914959894458,104.61339756308799,106.64642473395035,1038.941834230865,2
109.26253189460714,110.76487377959927,107.8309272112827,109.33326909627483,1056.4642473395036,3
111.7365376962194,112.90403177129735,110.1528967845943,111.32039085967226,1071.7356090899523,4
113.27609348158748,114.45148509543229,111.29955825219574,112.47494986604055,1084.1470984807897,5
113.7284688053824,115.1642566915892,111.30268842257516,112.73847630878196,1093.2039085967226,6
113.06855035377954,114.55977666009171,110.64086736017657,112.13209366648874,1098.544972998846,7
111.40827542637513,112.72390874531129,109.43899848657534,110.75463180551151,1099.9573603041506,8
108.98459460176909,109.99300155201126,107.76539185209613,108.7737988023383,1097.3847630878195,9
106.12753789513545,107.73969337995807,104.79904459577605,106.41120008059868,1090.929742682568,10
103.21387328427625,105.41662717550601,101.71978916733775,103.92254305856751,1080.849640381959,11
100.61462528040111,103.00209502109563,99.18732582635697,101.57479556705148,1067.546318055115,12
98.64575078243223,100.78188758933494,97.48620160125755,99.62233840816026,1051.5501371821465,13
97.53034002152081,99.46748184049008,96.34710045689485,98.28424227586412,1033.4988150155905,14
97.378063505514,99.16454670333486,95.93821562552817,97.72469882334903,1014.1120008059867,15
98.18385394545021,99.67344281002588,96.54876504706593,98.0383539116416,1005.837414342758,16
99.8438650794075,101.15293363552601,97.93278462060415,99.24185317672267,1025.5541102026832,17
102.1834853863248,103.20029690993537,100.25554360082955,101.27235512444012,1044.2520443294852,18
104.99031673022002,106.32510161131832,102.65835969292533,103.99314457402363,1061.185789094272,19
108.04491654708718,109.54022022493461,105.7105413401633,107.20584501801073,1075.680249530793,20
111.14367593283949,112.56654934841096,109.24526558927202,110.6681390048435,1087.157577241359,21
114.11098793714572,115.26697281350663,112.95942875877287,114.11541363513378,1095.1602073889517,22
116.80109288512898,118.47543335247398,115.61005717653698,117.28439764388199,1099.3691003633464,23
119.09282467975903,121.38046215528227,117.64904116296829,119.93667863849153,1099.616460883584,24
120.88220148856881,123.36781277048146,119.39438848583472,121.87999976774739,1095.8924274663138,25
122.07798667229586,124.2878498649492,120.77557026109272,122.98543345374605,1088.3454655720152,26
122.60397744514097,124.22410945235427,121.57876610123756,123.19889810845086,1077.2764487555987,27
122.40925186267498,123.88697089091687,121.06827005264091,122.5459890808828,1063.126663787232,28
121.48461656761053,122.98099977052848,119.63330909790388,121.12969230082183,1046.4602179413757,29
119.8808727652764,121.29920058454442,117.70285703314954,119.12118485241757,1027.9415498198925,30
117.72299769788953,118.8665240235534,115.60101790940676,116.74454423507063,1008.3089402817496,31
115.21439166809358,116.4131695096543,113.05795434620948,114.2567321877702,1011.6549204850494,32
112.6270381197495,114.07463180365934,110.47704737833695,111.92464106224679,1031.154136351338,33
110.27641646211617,111.76231568498811,108.51535390119264,110.00125312406458,1049.4113351138608,34
108.48360243707184,109.99872116521585,107.18792367213928,108.7030424002833,1065.6986598718788,35
107.530320991091,109.22424173559783,106.49671695482826,108.19063769933508,1079.3667863849153,36
107.61494922421186,109.90155645208623,106.26786689008574,108.55447411796011,1089.8708095811626,37
108.81801012516657,111.30403713029216,107.32068773822765,109.80671474335324,1096.7919672031487,38
111.08434919099572,113.29382811110736,109.67068524069803,111.88016416080967,1099.8543345374605,39
114.22618875818226,115.76972371414958,113.09073586402833,114.63427081999565,1098.9358246623383,40
117.94724565362051,119.15370942824079,116.6612183117217,117.86768208634197,1094.0730556679773,41
121.88395973243564,123.33523703654073,119.88495316810628,121.33623047221137,1085.459890808828,42
125.65653611891825,127.14038418498515,123.29089632531003,124.77474439137693,1073.4397097874114,43
128.92069597346688,130.20955349568973,126.63187762484937,127.92073514707224,1058.4917192891762,44
131.41114890560974,132.45313613345562,129.49585703767033,130.5378442655162,1041.2118485241756,45
132.96978971477444,134.3228744433646,131.08387196585088,132.43695669444105,1022.2889914100246,46
133.5549991814736,135.053120145851,131.99497292310176,133.49309388747918,1002.4775425453357,47
133.2323987581558,135.065460892756,131.8235156308926,133.65657776549278,1017.4326781222982,48
132.1510588175171,134.08480947785137,131.0237176510951,132.95746831142935,1036.647912925193,49
130.5116755897077,132.71696973631924,129.29758425495962,131.50287840157117,1054.402111088937,50
128.53409061924222,130.9222390672229,127.07925728332546,129.46740573130614,1069.9874687593544,51
126.4306172006658,128.5591966352313,124.94895708842891,127.07753652299444,1082.7826469085653,52
124.38927906587094,125.87336879788326,123.1073244542458,124.59141418625812,1092.2775421612807,53
122.56791894474918,123.61827749324543,121.2254215875191,122.27578013601534,1098.093623006649,54
121.09703354508844,122.45602084147426,119.02315927992295,120.38214657630877,1099.9990206550704,55
120.08693553049852,121.58571423995242,117.62555095473105,119.12432966418496,1097.9177729151318,56
119.63399522490424,121.03798192673773,117.25536324735009,118.65934994918358,1091.9328525664675,57
119.82146372498268,120.94065716085713,117.95421275941922,119.07340619529367,1082.2828594968707,58
120.7124947130592,121.93415307848436,119.152517136663,120.37417550208815,1069.3525084777123,59
122.33587608239566,123.94838830624106,120.87761530843784,122.49012753228324,1053.6572918000436,60
124.66785812762632,126.75625008370868,123.18852214765032,125.27691410373268,1035.8229282236828,61
127.61552102482923,129.80523536741393,126.34054904004905,128.53026338263376,1016.5604175448309,62
131.00775897051096,133.06294250948164,129.94904333909741,132.0042268780681,1003.3623047221139,63
134.59892592769242,136.79793765689536,133.234137558996,135.43314928819893,1023.1509825101539,64
138.08766685694894,140.0546941334019,136.58837142074304,138.555398697196,1042.016703682664,65
141.1500141222941,142.5489901806554,139.73776131670974,141.13673737507105,1059.2073514707224,66
143.48228026040917,144.5932922665057,141.8802335303824,142.99124553647894,1074.037588995245,67
144.84649927621132,146.0756620034572,142.76876627418082,143.9979290014267,1085.9161814856498,68
145.1098734724699,146.57143212166807,142.649968595823,144.11152724502116,1094.3695669444105,69
144.27024859045207,145.7471249168318,141.88968005898082,143.36655638536055,1099.060735569487,70
142.46200877506277,143.72992039092952,140.60631434563564,141.8742259615024,1099.8026652716362,71
139.9404686061768,141.00752471999962,138.7454488027266,139.81250491654941,1096.5657776549278,72
137.04703875358243,138.78071046739933,135.67655244818158,137.41022416199849,1089.4791172140503,73
134.16122174198705,136.42630679247307,132.66155074344638,134.9266357939324,1078.8252067375315,74
131.6480122345848,134.02210748888697,130.25415962109273,132.6282548753949,1065.0287840157116,75
129.80997879551026,131.86785162975886,128.70717960530897,130.76505243955756,1048.6398688853799,76
128.85205701193928,130.78472862032595,127.6154547134165,129.54812632180318,1030.3118356745701,77
128.8642014864336,130.59557044803222,127.39947545719488,129.13084441879352,1010.7753652299442,78
129.82321172551153,131.29749357237068,128.12086294416926,129.59514479102842,1009.1906850227682,79
1 open high low close volume timestamp
2 99 101 98 100 1000 0
3 102.57761950472302 104.77731091023225 101.25551066110417 103.4552020666134 1019.8669330795061 1
4 106.10612242808222 108.13914959894458 104.61339756308799 106.64642473395035 1038.941834230865 2
5 109.26253189460714 110.76487377959927 107.8309272112827 109.33326909627483 1056.4642473395036 3
6 111.7365376962194 112.90403177129735 110.1528967845943 111.32039085967226 1071.7356090899523 4
7 113.27609348158748 114.45148509543229 111.29955825219574 112.47494986604055 1084.1470984807897 5
8 113.7284688053824 115.1642566915892 111.30268842257516 112.73847630878196 1093.2039085967226 6
9 113.06855035377954 114.55977666009171 110.64086736017657 112.13209366648874 1098.544972998846 7
10 111.40827542637513 112.72390874531129 109.43899848657534 110.75463180551151 1099.9573603041506 8
11 108.98459460176909 109.99300155201126 107.76539185209613 108.7737988023383 1097.3847630878195 9
12 106.12753789513545 107.73969337995807 104.79904459577605 106.41120008059868 1090.929742682568 10
13 103.21387328427625 105.41662717550601 101.71978916733775 103.92254305856751 1080.849640381959 11
14 100.61462528040111 103.00209502109563 99.18732582635697 101.57479556705148 1067.546318055115 12
15 98.64575078243223 100.78188758933494 97.48620160125755 99.62233840816026 1051.5501371821465 13
16 97.53034002152081 99.46748184049008 96.34710045689485 98.28424227586412 1033.4988150155905 14
17 97.378063505514 99.16454670333486 95.93821562552817 97.72469882334903 1014.1120008059867 15
18 98.18385394545021 99.67344281002588 96.54876504706593 98.0383539116416 1005.837414342758 16
19 99.8438650794075 101.15293363552601 97.93278462060415 99.24185317672267 1025.5541102026832 17
20 102.1834853863248 103.20029690993537 100.25554360082955 101.27235512444012 1044.2520443294852 18
21 104.99031673022002 106.32510161131832 102.65835969292533 103.99314457402363 1061.185789094272 19
22 108.04491654708718 109.54022022493461 105.7105413401633 107.20584501801073 1075.680249530793 20
23 111.14367593283949 112.56654934841096 109.24526558927202 110.6681390048435 1087.157577241359 21
24 114.11098793714572 115.26697281350663 112.95942875877287 114.11541363513378 1095.1602073889517 22
25 116.80109288512898 118.47543335247398 115.61005717653698 117.28439764388199 1099.3691003633464 23
26 119.09282467975903 121.38046215528227 117.64904116296829 119.93667863849153 1099.616460883584 24
27 120.88220148856881 123.36781277048146 119.39438848583472 121.87999976774739 1095.8924274663138 25
28 122.07798667229586 124.2878498649492 120.77557026109272 122.98543345374605 1088.3454655720152 26
29 122.60397744514097 124.22410945235427 121.57876610123756 123.19889810845086 1077.2764487555987 27
30 122.40925186267498 123.88697089091687 121.06827005264091 122.5459890808828 1063.126663787232 28
31 121.48461656761053 122.98099977052848 119.63330909790388 121.12969230082183 1046.4602179413757 29
32 119.8808727652764 121.29920058454442 117.70285703314954 119.12118485241757 1027.9415498198925 30
33 117.72299769788953 118.8665240235534 115.60101790940676 116.74454423507063 1008.3089402817496 31
34 115.21439166809358 116.4131695096543 113.05795434620948 114.2567321877702 1011.6549204850494 32
35 112.6270381197495 114.07463180365934 110.47704737833695 111.92464106224679 1031.154136351338 33
36 110.27641646211617 111.76231568498811 108.51535390119264 110.00125312406458 1049.4113351138608 34
37 108.48360243707184 109.99872116521585 107.18792367213928 108.7030424002833 1065.6986598718788 35
38 107.530320991091 109.22424173559783 106.49671695482826 108.19063769933508 1079.3667863849153 36
39 107.61494922421186 109.90155645208623 106.26786689008574 108.55447411796011 1089.8708095811626 37
40 108.81801012516657 111.30403713029216 107.32068773822765 109.80671474335324 1096.7919672031487 38
41 111.08434919099572 113.29382811110736 109.67068524069803 111.88016416080967 1099.8543345374605 39
42 114.22618875818226 115.76972371414958 113.09073586402833 114.63427081999565 1098.9358246623383 40
43 117.94724565362051 119.15370942824079 116.6612183117217 117.86768208634197 1094.0730556679773 41
44 121.88395973243564 123.33523703654073 119.88495316810628 121.33623047221137 1085.459890808828 42
45 125.65653611891825 127.14038418498515 123.29089632531003 124.77474439137693 1073.4397097874114 43
46 128.92069597346688 130.20955349568973 126.63187762484937 127.92073514707224 1058.4917192891762 44
47 131.41114890560974 132.45313613345562 129.49585703767033 130.5378442655162 1041.2118485241756 45
48 132.96978971477444 134.3228744433646 131.08387196585088 132.43695669444105 1022.2889914100246 46
49 133.5549991814736 135.053120145851 131.99497292310176 133.49309388747918 1002.4775425453357 47
50 133.2323987581558 135.065460892756 131.8235156308926 133.65657776549278 1017.4326781222982 48
51 132.1510588175171 134.08480947785137 131.0237176510951 132.95746831142935 1036.647912925193 49
52 130.5116755897077 132.71696973631924 129.29758425495962 131.50287840157117 1054.402111088937 50
53 128.53409061924222 130.9222390672229 127.07925728332546 129.46740573130614 1069.9874687593544 51
54 126.4306172006658 128.5591966352313 124.94895708842891 127.07753652299444 1082.7826469085653 52
55 124.38927906587094 125.87336879788326 123.1073244542458 124.59141418625812 1092.2775421612807 53
56 122.56791894474918 123.61827749324543 121.2254215875191 122.27578013601534 1098.093623006649 54
57 121.09703354508844 122.45602084147426 119.02315927992295 120.38214657630877 1099.9990206550704 55
58 120.08693553049852 121.58571423995242 117.62555095473105 119.12432966418496 1097.9177729151318 56
59 119.63399522490424 121.03798192673773 117.25536324735009 118.65934994918358 1091.9328525664675 57
60 119.82146372498268 120.94065716085713 117.95421275941922 119.07340619529367 1082.2828594968707 58
61 120.7124947130592 121.93415307848436 119.152517136663 120.37417550208815 1069.3525084777123 59
62 122.33587608239566 123.94838830624106 120.87761530843784 122.49012753228324 1053.6572918000436 60
63 124.66785812762632 126.75625008370868 123.18852214765032 125.27691410373268 1035.8229282236828 61
64 127.61552102482923 129.80523536741393 126.34054904004905 128.53026338263376 1016.5604175448309 62
65 131.00775897051096 133.06294250948164 129.94904333909741 132.0042268780681 1003.3623047221139 63
66 134.59892592769242 136.79793765689536 133.234137558996 135.43314928819893 1023.1509825101539 64
67 138.08766685694894 140.0546941334019 136.58837142074304 138.555398697196 1042.016703682664 65
68 141.1500141222941 142.5489901806554 139.73776131670974 141.13673737507105 1059.2073514707224 66
69 143.48228026040917 144.5932922665057 141.8802335303824 142.99124553647894 1074.037588995245 67
70 144.84649927621132 146.0756620034572 142.76876627418082 143.9979290014267 1085.9161814856498 68
71 145.1098734724699 146.57143212166807 142.649968595823 144.11152724502116 1094.3695669444105 69
72 144.27024859045207 145.7471249168318 141.88968005898082 143.36655638536055 1099.060735569487 70
73 142.46200877506277 143.72992039092952 140.60631434563564 141.8742259615024 1099.8026652716362 71
74 139.9404686061768 141.00752471999962 138.7454488027266 139.81250491654941 1096.5657776549278 72
75 137.04703875358243 138.78071046739933 135.67655244818158 137.41022416199849 1089.4791172140503 73
76 134.16122174198705 136.42630679247307 132.66155074344638 134.9266357939324 1078.8252067375315 74
77 131.6480122345848 134.02210748888697 130.25415962109273 132.6282548753949 1065.0287840157116 75
78 129.80997879551026 131.86785162975886 128.70717960530897 130.76505243955756 1048.6398688853799 76
79 128.85205701193928 130.78472862032595 127.6154547134165 129.54812632180318 1030.3118356745701 77
80 128.8642014864336 130.59557044803222 127.39947545719488 129.13084441879352 1010.7753652299442 78
81 129.82321172551153 131.29749357237068 128.12086294416926 129.59514479102842 1009.1906850227682 79