Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c2c09fc8ba | |||
| 0eb52cc06a | |||
| 1e6025a968 |
@@ -16,9 +16,87 @@ env:
|
||||
|
||||
jobs:
|
||||
# ============================================================
|
||||
# Linux x86_64 (manylinux)
|
||||
# 1. 发布 chanlun 核心库至 crates.io
|
||||
# ============================================================
|
||||
publish-crates:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
version: ${{ steps.version.outputs.version }}
|
||||
exists: ${{ steps.check.outputs.exists }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: 安装 Rust 工具链
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
components: rustfmt, clippy
|
||||
|
||||
- name: 缓存依赖
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
target
|
||||
key: ${{ runner.os }}-cargo-${{ hashFiles('chanlun/Cargo.lock') }}
|
||||
|
||||
- name: 提取版本号
|
||||
id: version
|
||||
working-directory: chanlun
|
||||
run: |
|
||||
VER=$(cargo metadata --format-version 1 --no-deps 2>/dev/null \
|
||||
| jq -r '.packages[] | select(.name == "chanlun") | .version')
|
||||
echo "version=$VER" >> $GITHUB_OUTPUT
|
||||
echo "当前版本: $VER"
|
||||
|
||||
- name: 检查版本是否已存在
|
||||
id: check
|
||||
run: |
|
||||
VER="${{ steps.version.outputs.version }}"
|
||||
EXISTS=$(curl -sS "https://crates.io/api/v1/crates/chanlun" \
|
||||
| jq -r --arg v "$VER" '.versions[]?.num // empty | select(. == $v)')
|
||||
if [ -n "$EXISTS" ]; then
|
||||
echo "版本 $VER 已存在于 crates.io,跳过发布"
|
||||
echo "exists=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "版本 $VER 未发布,继续"
|
||||
echo "exists=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: 格式检查
|
||||
if: steps.check.outputs.exists == 'false'
|
||||
working-directory: chanlun
|
||||
run: cargo fmt --check
|
||||
|
||||
- name: Lint 检查
|
||||
if: steps.check.outputs.exists == 'false'
|
||||
working-directory: chanlun
|
||||
run: cargo clippy -- -D warnings
|
||||
|
||||
- name: 运行测试
|
||||
if: steps.check.outputs.exists == 'false'
|
||||
working-directory: chanlun
|
||||
run: cargo test
|
||||
|
||||
- name: 验证打包
|
||||
if: steps.check.outputs.exists == 'false'
|
||||
working-directory: chanlun
|
||||
run: cargo package --dry-run
|
||||
|
||||
- name: 登录 crates.io
|
||||
if: steps.check.outputs.exists == 'false'
|
||||
run: cargo login ${{ secrets.CARGO_TOKEN }}
|
||||
|
||||
- name: 发布 chanlun 至 crates.io
|
||||
if: steps.check.outputs.exists == 'false'
|
||||
working-directory: chanlun
|
||||
run: cargo publish
|
||||
|
||||
# ============================================================
|
||||
# 2. 构建 wheel — Linux x86_64 (manylinux)
|
||||
# ============================================================
|
||||
linux-x86_64:
|
||||
needs: [publish-crates]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -31,6 +109,10 @@ jobs:
|
||||
- name: 安装 Rust 工具链
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- name: 更新 cargo 索引(确保新版本可见)
|
||||
run: cargo update
|
||||
working-directory: chanlun-py
|
||||
|
||||
- name: 构建 wheel (manylinux)
|
||||
uses: PyO3/maturin-action@v1
|
||||
with:
|
||||
@@ -45,11 +127,11 @@ jobs:
|
||||
name: wheels-linux-x86_64
|
||||
path: chanlun-py/dist/
|
||||
|
||||
|
||||
# ============================================================
|
||||
# macOS wheels (x86_64 + arm64)
|
||||
# 3. 构建 wheel — macOS (x86_64 + arm64)
|
||||
# ============================================================
|
||||
macos:
|
||||
needs: [publish-crates]
|
||||
runs-on: macos-latest
|
||||
strategy:
|
||||
matrix:
|
||||
@@ -65,6 +147,10 @@ jobs:
|
||||
with:
|
||||
python-version: '3.12'
|
||||
|
||||
- name: 更新 cargo 索引
|
||||
run: cargo update
|
||||
working-directory: chanlun-py
|
||||
|
||||
- name: 构建 wheel
|
||||
uses: PyO3/maturin-action@v1
|
||||
with:
|
||||
@@ -79,9 +165,10 @@ jobs:
|
||||
path: chanlun-py/dist/
|
||||
|
||||
# ============================================================
|
||||
# Windows wheels (x86_64)
|
||||
# 4. 构建 wheel — Windows x86_64
|
||||
# ============================================================
|
||||
windows:
|
||||
needs: [publish-crates]
|
||||
runs-on: windows-latest
|
||||
strategy:
|
||||
matrix:
|
||||
@@ -97,6 +184,10 @@ jobs:
|
||||
with:
|
||||
python-version: '3.12'
|
||||
|
||||
- name: 更新 cargo 索引
|
||||
run: cargo update
|
||||
working-directory: chanlun-py
|
||||
|
||||
- name: 构建 wheel
|
||||
uses: PyO3/maturin-action@v1
|
||||
with:
|
||||
@@ -111,9 +202,10 @@ jobs:
|
||||
path: chanlun-py/dist/
|
||||
|
||||
# ============================================================
|
||||
# 源码分发包 (sdist)
|
||||
# 5. 源码分发包 (sdist)
|
||||
# ============================================================
|
||||
sdist:
|
||||
needs: [publish-crates]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -137,14 +229,14 @@ jobs:
|
||||
path: chanlun-py/dist/
|
||||
|
||||
# ============================================================
|
||||
# 发布至 PyPI
|
||||
# 6. 发布至 PyPI
|
||||
# ============================================================
|
||||
publish:
|
||||
needs: [linux-x86_64, macos, windows, sdist]
|
||||
needs: [publish-crates, linux-x86_64, macos, windows, sdist]
|
||||
runs-on: ubuntu-latest
|
||||
if: startsWith(github.ref, 'refs/tags/v') || github.event.inputs.publish-to-pypi == 'true'
|
||||
permissions:
|
||||
id-token: write # PyPI 信任发布(推荐)
|
||||
id-token: write
|
||||
|
||||
steps:
|
||||
- name: 下载所有产物
|
||||
|
||||
@@ -5078,6 +5078,17 @@ class 观察者:
|
||||
self.扩展线段序列_扩展线段: List[虚线] = []
|
||||
self.扩展中枢序列_扩展线段: List[中枢] = []
|
||||
|
||||
def 投喂原始数据(self, 时间戳: datetime, 开: float, 高: float, 低: float, 收: float, 量: float):
|
||||
"""便捷入口,直接从 OHLCV 创建 K线 并投喂
|
||||
:param 时间戳: 时间戳
|
||||
:param 开: 开盘价
|
||||
:param 高: 最高价
|
||||
:param 低: 最低价
|
||||
:param 收: 收盘价
|
||||
:param 量: 成交量
|
||||
"""
|
||||
self.增加原始K线(K线.创建普K(self.标识, 时间戳, 开, 高, 低, 收, 量, 0, self.周期))
|
||||
|
||||
@final
|
||||
def 增加原始K线(self, 普K: K线):
|
||||
"""核心入口 — 投喂一根原始K线,增量更新所有层级
|
||||
@@ -5224,33 +5235,32 @@ class 观察者:
|
||||
self.配置.分析线段中枢 and 中枢.分析(self.线段_线段序列, self.线段_中枢序列)
|
||||
|
||||
def 加载本地数据(self, 文件路径: str):
|
||||
"""重置基础序列后加载数据文件
|
||||
:param 文件路径: 数据文件路径 格式如: btcusd-300-1631772074-1632222374.nb
|
||||
"""
|
||||
self.重置基础序列()
|
||||
with open(文件路径, "rb") as f:
|
||||
buffer = f.read()
|
||||
size = struct.calcsize(">6d")
|
||||
for i in range(len(buffer) // size):
|
||||
k线 = K线.读取大端字节数组(buffer[i * size : i * size + size], self.周期, self.标识)
|
||||
self.增加原始K线(k线)
|
||||
时间戳, 开盘价, 最高价, 最低价, 收盘价, 成交量 = struct.unpack(">6d", buffer[i * size: i * size + size])
|
||||
self.投喂原始数据(转化为时间戳(int(时间戳)), 开盘价, 最高价, 最低价, 收盘价, 成交量)
|
||||
|
||||
@classmethod
|
||||
def 读取数据文件(cls, 文件路径: str, 配置=缠论配置()) -> Self:
|
||||
"""
|
||||
def 读取数据文件(cls,观察员:"观察者", 文件路径: str, 配置=缠论配置()) -> Self:
|
||||
""" 加载数据文件
|
||||
:param 观察员: 观察者
|
||||
:param 文件路径: 数据文件路径 格式如: btcusd-300-1631772074-1632222374.nb
|
||||
:param 配置: 缠论配置
|
||||
:return: 观察者实例
|
||||
"""
|
||||
name = Path(文件路径).name.split(".")[0]
|
||||
符号, 周期, 起始时间戳, 结束时间戳 = name.split("-")
|
||||
实例 = cls(符号=符号, 周期=int(周期), 配置=配置)
|
||||
|
||||
with open(文件路径, "rb") as f:
|
||||
buffer = f.read()
|
||||
size = struct.calcsize(">6d")
|
||||
for i in range(len(buffer) // size):
|
||||
k线 = K线.读取大端字节数组(buffer[i * size : i * size + size], int(周期), 符号)
|
||||
实例.增加原始K线(k线)
|
||||
|
||||
return 实例
|
||||
观察员.符号 = 符号
|
||||
观察员.周期 = int(周期)
|
||||
观察员.配置 = 配置
|
||||
观察员.加载本地数据(文件路径)
|
||||
return 观察员
|
||||
|
||||
|
||||
class K线合成器:
|
||||
@@ -5503,16 +5513,16 @@ class 立体分析器:
|
||||
print(f"多级别数据拆分保存完成,目录:{保存路径.resolve()}")
|
||||
|
||||
|
||||
def 测试_读取数据(配置: 缠论配置):
|
||||
def 测试_读取数据(观察员: 观察者, 配置: 缠论配置)-> Callable[[], "观察者"]:
|
||||
"""测试_读取数据
|
||||
|
||||
:param 观察员: 观察者
|
||||
:param 配置: 缠论配置
|
||||
:return: 测试函数
|
||||
"""
|
||||
|
||||
def 魔法():
|
||||
启动时间 = datetime.now()
|
||||
观察员 = 观察者.读取数据文件(配置.加载文件路径, 配置)
|
||||
观察者.读取数据文件(观察员, 配置.加载文件路径, 配置)
|
||||
消耗用时 = datetime.now() - 启动时间
|
||||
print("测试_读取数据 耗时", 消耗用时, "普K数量", len(观察员.普通K线序列))
|
||||
return 观察员
|
||||
@@ -5552,5 +5562,6 @@ def 测试_周期合成(配置: 缠论配置, 配置组: Dict[int, 缠论配置]
|
||||
if __name__ == "__main__":
|
||||
当前配置 = 缠论配置.不推送()
|
||||
当前配置.加载文件路径 = str(Path(__file__).parent / "btcusd-300-1761327300-1776327900.nb")
|
||||
测试_读取数据(当前配置)().测试_保存数据()
|
||||
测试_周期合成(当前配置)().测试_保存数据()
|
||||
观察员 = 观察者("", 0, 当前配置)
|
||||
测试_读取数据(观察员,当前配置)()# .测试_保存数据()
|
||||
# 测试_周期合成(当前配置)().测试_保存数据()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "chanlun-py"
|
||||
version = "26.5.94"
|
||||
version = "26.5.101"
|
||||
edition = "2021"
|
||||
description = "缠论技术分析库 — Rust 高性能 Python 绑定"
|
||||
authors = ["YuYuKunKun"]
|
||||
@@ -12,7 +12,7 @@ crate-type = ["cdylib"]
|
||||
name = "chanlun"
|
||||
|
||||
[dependencies]
|
||||
chanlun = "26.5.4" # { path = "../chanlun" }
|
||||
pyo3 = { version = "0.28", features = ["extension-module", "experimental-inspect"] }
|
||||
chanlun = "26.5.5" # { path = "../chanlun" }
|
||||
pyo3 = { version = "0.28", features = ["experimental-inspect"] }
|
||||
serde_json = "1"
|
||||
chrono = "0.4"
|
||||
|
||||
@@ -27,6 +27,8 @@ __all__ = [
|
||||
"转化为时间戳",
|
||||
"转化为时间戳_数字",
|
||||
"随机指标",
|
||||
"chan",
|
||||
]
|
||||
|
||||
from ._chanlun import *
|
||||
from . import chan
|
||||
|
||||
+28
-18
@@ -5078,6 +5078,17 @@ class 观察者:
|
||||
self.扩展线段序列_扩展线段: List[虚线] = []
|
||||
self.扩展中枢序列_扩展线段: List[中枢] = []
|
||||
|
||||
def 投喂原始数据(self, 时间戳: datetime, 开: float, 高: float, 低: float, 收: float, 量: float):
|
||||
"""便捷入口,直接从 OHLCV 创建 K线 并投喂
|
||||
:param 时间戳: 时间戳
|
||||
:param 开: 开盘价
|
||||
:param 高: 最高价
|
||||
:param 低: 最低价
|
||||
:param 收: 收盘价
|
||||
:param 量: 成交量
|
||||
"""
|
||||
self.增加原始K线(K线.创建普K(self.标识, 时间戳, 开, 高, 低, 收, 量, 0, self.周期))
|
||||
|
||||
@final
|
||||
def 增加原始K线(self, 普K: K线):
|
||||
"""核心入口 — 投喂一根原始K线,增量更新所有层级
|
||||
@@ -5224,33 +5235,32 @@ class 观察者:
|
||||
self.配置.分析线段中枢 and 中枢.分析(self.线段_线段序列, self.线段_中枢序列)
|
||||
|
||||
def 加载本地数据(self, 文件路径: str):
|
||||
"""重置基础序列后加载数据文件
|
||||
:param 文件路径: 数据文件路径 格式如: btcusd-300-1631772074-1632222374.nb
|
||||
"""
|
||||
self.重置基础序列()
|
||||
with open(文件路径, "rb") as f:
|
||||
buffer = f.read()
|
||||
size = struct.calcsize(">6d")
|
||||
for i in range(len(buffer) // size):
|
||||
k线 = K线.读取大端字节数组(buffer[i * size : i * size + size], self.周期, self.标识)
|
||||
self.增加原始K线(k线)
|
||||
时间戳, 开盘价, 最高价, 最低价, 收盘价, 成交量 = struct.unpack(">6d", buffer[i * size: i * size + size])
|
||||
self.投喂原始数据(转化为时间戳(int(时间戳)), 开盘价, 最高价, 最低价, 收盘价, 成交量)
|
||||
|
||||
@classmethod
|
||||
def 读取数据文件(cls, 文件路径: str, 配置=缠论配置()) -> Self:
|
||||
"""
|
||||
def 读取数据文件(cls,观察员:"观察者", 文件路径: str, 配置=缠论配置()) -> Self:
|
||||
""" 加载数据文件
|
||||
:param 观察员: 观察者
|
||||
:param 文件路径: 数据文件路径 格式如: btcusd-300-1631772074-1632222374.nb
|
||||
:param 配置: 缠论配置
|
||||
:return: 观察者实例
|
||||
"""
|
||||
name = Path(文件路径).name.split(".")[0]
|
||||
符号, 周期, 起始时间戳, 结束时间戳 = name.split("-")
|
||||
实例 = cls(符号=符号, 周期=int(周期), 配置=配置)
|
||||
|
||||
with open(文件路径, "rb") as f:
|
||||
buffer = f.read()
|
||||
size = struct.calcsize(">6d")
|
||||
for i in range(len(buffer) // size):
|
||||
k线 = K线.读取大端字节数组(buffer[i * size : i * size + size], int(周期), 符号)
|
||||
实例.增加原始K线(k线)
|
||||
|
||||
return 实例
|
||||
观察员.符号 = 符号
|
||||
观察员.周期 = int(周期)
|
||||
观察员.配置 = 配置
|
||||
观察员.加载本地数据(文件路径)
|
||||
return 观察员
|
||||
|
||||
|
||||
class K线合成器:
|
||||
@@ -5503,16 +5513,16 @@ class 立体分析器:
|
||||
print(f"多级别数据拆分保存完成,目录:{保存路径.resolve()}")
|
||||
|
||||
|
||||
def 测试_读取数据(配置: 缠论配置):
|
||||
def 测试_读取数据(观察员: 观察者, 配置: 缠论配置)-> Callable[()]:
|
||||
"""测试_读取数据
|
||||
|
||||
:param 观察员: 观察者
|
||||
:param 配置: 缠论配置
|
||||
:return: 测试函数
|
||||
"""
|
||||
|
||||
def 魔法():
|
||||
启动时间 = datetime.now()
|
||||
观察员 = 观察者.读取数据文件(配置.加载文件路径, 配置)
|
||||
观察者.读取数据文件(观察员, 配置.加载文件路径, 配置)
|
||||
消耗用时 = datetime.now() - 启动时间
|
||||
print("测试_读取数据 耗时", 消耗用时, "普K数量", len(观察员.普通K线序列))
|
||||
return 观察员
|
||||
@@ -5553,5 +5563,5 @@ if __name__ == "__main__":
|
||||
当前配置 = 缠论配置.不推送()
|
||||
当前配置.加载文件路径 = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "tests", "btcusd-300-1761327300-1776327900.nb")
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
测试_读取数据(当前配置)().测试_保存数据(tmpdir)
|
||||
测试_读取数据(观察者("", 0, 当前配置), 当前配置)().测试_保存数据(tmpdir)
|
||||
测试_周期合成(当前配置)().测试_保存数据(tmpdir)
|
||||
|
||||
@@ -4,7 +4,7 @@ build-backend = "maturin"
|
||||
|
||||
[project]
|
||||
name = "chanlun"
|
||||
version = "2605.94"
|
||||
version = "2605.101"
|
||||
description = "缠论技术分析库 — Rust 高性能实现"
|
||||
readme = { file = "README.md", content-type = "text/markdown" }
|
||||
license = { file = "LICENSE", content-type = "text/plain" }
|
||||
|
||||
@@ -1214,10 +1214,8 @@ impl 中枢Py {
|
||||
|
||||
#[getter]
|
||||
/// :return: 中枢方向(首条虚线的方向翻转)
|
||||
fn 方向(&self) -> 相对方向Py {
|
||||
相对方向Py {
|
||||
inner: self.inner.方向(),
|
||||
}
|
||||
fn 方向(&self, py: Python<'_>) -> Py<相对方向Py> {
|
||||
crate::types_py::获取相对方向单例(py, self.inner.方向())
|
||||
}
|
||||
|
||||
#[getter]
|
||||
@@ -1328,7 +1326,7 @@ impl 中枢Py {
|
||||
dict.set_item("标识", self.标识())?;
|
||||
dict.set_item("级别", self.级别())?;
|
||||
dict.set_item("图表标题", self.图表标题())?;
|
||||
dict.set_item("方向", self.方向())?;
|
||||
dict.set_item("方向", self.方向(py))?;
|
||||
dict.set_item("高", self.高())?;
|
||||
dict.set_item("低", self.低())?;
|
||||
dict.set_item("高高", self.高高())?;
|
||||
|
||||
@@ -560,30 +560,30 @@ impl 观察者Py {
|
||||
self.obs_mut().增加原始K线((*普K.borrow().inner).clone());
|
||||
}
|
||||
|
||||
/// 投喂原始数据 — 便捷入口,直接从 OHLCV 创建 K线 并投喂
|
||||
fn 投喂原始数据(
|
||||
&mut self, 时间戳: i64, 开: f64, 高: f64, 低: f64, 收: f64, 量: f64
|
||||
) {
|
||||
self.obs_mut().投喂原始数据(时间戳, 开, 高, 低, 收, 量);
|
||||
}
|
||||
|
||||
/// 加载本地数据 — 从 .nb 文件加载K线数据(先重置,再通过 Python dispatch 逐根投喂,
|
||||
/// 确保子类重写的 增加原始K线 被正确调用)。
|
||||
fn 加载本地数据(slf: &Bound<'_, Self>, 文件路径: &str) -> PyResult<()> {
|
||||
let py = slf.py();
|
||||
|
||||
// 重置基础序列
|
||||
slf.borrow_mut().obs_mut().重置基础序列();
|
||||
|
||||
// 解析文件得到 K线 列表
|
||||
let bars = slf
|
||||
.borrow()
|
||||
.obs()
|
||||
.解析本地数据(文件路径)
|
||||
.map_err(|e| pyo3::exceptions::PyValueError::new_err(e))?;
|
||||
|
||||
// 通过 Python dispatch 逐根投喂,确保子类重写生效
|
||||
for k线 in bars {
|
||||
let k线_py = Py::new(
|
||||
py,
|
||||
K线Py {
|
||||
inner: Arc::new(k线),
|
||||
},
|
||||
)?;
|
||||
slf.call_method1("增加原始K线", (k线_py,))?;
|
||||
// 读取文件,通过 Python dispatch 逐根投喂(支持子类重写 增加原始K线)
|
||||
let data = std::fs::read(文件路径)
|
||||
.map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("read file: {}", e)))?;
|
||||
let size: usize = 48;
|
||||
for i in 0..data.len() / size {
|
||||
let offset = i * size;
|
||||
if let Some((时间戳, 开, 高, 低, 收, 量)) =
|
||||
chanlun::kline::bar::K线::解析原始数据(&data[offset..offset + size])
|
||||
{
|
||||
slf.call_method1("投喂原始数据", (时间戳, 开, 高, 低, 收, 量))?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -600,10 +600,14 @@ impl 观察者Py {
|
||||
}
|
||||
|
||||
#[classmethod]
|
||||
#[pyo3(signature = (文件路径, 配置 = None))]
|
||||
#[pyo3(signature = (观察员, 文件路径, 配置 = None))]
|
||||
/// :param 观察员: 观察者实例
|
||||
/// :param 文件路径: 数据文件路径 格式如: btcusd-300-1631772074-1632222374.nb
|
||||
/// :param 配置: 缠论配置
|
||||
/// :return: 观察者实例
|
||||
fn 读取数据文件(
|
||||
cls: &Bound<'_, PyType>,
|
||||
_cls: &Bound<'_, PyType>,
|
||||
观察员: &Bound<'_, Self>,
|
||||
文件路径: &str,
|
||||
配置: Option<&Bound<'_, 缠论配置Py>>,
|
||||
py: Python<'_>,
|
||||
@@ -631,31 +635,19 @@ impl 观察者Py {
|
||||
.parse()
|
||||
.map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("parse period: {}", e)))?;
|
||||
|
||||
// 通过 cls 构造实例(支持子类化)
|
||||
let cfg_py = 缠论配置Py::from_rust_config(&config)?;
|
||||
let cfg_obj = Py::new(py, cfg_py)?;
|
||||
let obj = cls.call1((符号.clone(), 周期, cfg_obj))?;
|
||||
|
||||
// 读取文件并通过 Python 分发逐根投喂(支持子类重写 增加原始K线)
|
||||
let data = std::fs::read(文件路径)
|
||||
.map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("read file: {}", e)))?;
|
||||
let size: usize = 48;
|
||||
for i in 0..data.len() / size {
|
||||
let offset = i * size;
|
||||
if let Some(k线) =
|
||||
chanlun::kline::bar::K线::from_bytes(&data[offset..offset + size], 周期, &符号)
|
||||
{
|
||||
let k线_py = Py::new(
|
||||
py,
|
||||
K线Py {
|
||||
inner: Arc::new(k线),
|
||||
},
|
||||
)?;
|
||||
obj.call_method1("增加原始K线", (k线_py,))?;
|
||||
}
|
||||
// 设置观察员属性
|
||||
{
|
||||
let slf_ref = 观察员.borrow_mut();
|
||||
let mut obs_mut = slf_ref.obs_mut();
|
||||
obs_mut.符号 = 符号;
|
||||
obs_mut.周期 = 周期;
|
||||
obs_mut.配置 = config;
|
||||
}
|
||||
|
||||
Ok(obj.unbind())
|
||||
// 调用加载本地数据
|
||||
观察员.call_method1("加载本地数据", (文件路径,))?;
|
||||
|
||||
Ok(观察员.clone().unbind().into())
|
||||
}
|
||||
|
||||
// ---- 序列 getters ----
|
||||
|
||||
@@ -139,10 +139,8 @@ impl K线Py {
|
||||
|
||||
#[getter]
|
||||
/// :return: 相对方向.向上(开盘<收盘)或 相对方向.向下(开盘>收盘)
|
||||
fn 方向(&self) -> 相对方向Py {
|
||||
相对方向Py {
|
||||
inner: self.inner.方向(),
|
||||
}
|
||||
fn 方向(&self, py: Python<'_>) -> Py<相对方向Py> {
|
||||
crate::types_py::获取相对方向单例(py, self.inner.方向())
|
||||
}
|
||||
|
||||
#[getter]
|
||||
@@ -182,7 +180,7 @@ impl K线Py {
|
||||
dict.set_item("开盘价", self.开盘价())?;
|
||||
dict.set_item("收盘价", self.收盘价())?;
|
||||
dict.set_item("成交量", self.成交量())?;
|
||||
dict.set_item("方向", self.方向())?;
|
||||
dict.set_item("方向", self.方向(py))?;
|
||||
if let Some(v) = self.macd() {
|
||||
dict.set_item("macd", v)?;
|
||||
}
|
||||
@@ -443,10 +441,8 @@ impl 缠论K线Py {
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn 方向(&self) -> 相对方向Py {
|
||||
相对方向Py {
|
||||
inner: *self.inner.方向.read().unwrap(),
|
||||
}
|
||||
fn 方向(&self, py: Python<'_>) -> Py<相对方向Py> {
|
||||
crate::types_py::获取相对方向单例(py, *self.inner.方向.read().unwrap())
|
||||
}
|
||||
|
||||
#[getter]
|
||||
@@ -496,7 +492,7 @@ impl 缠论K线Py {
|
||||
dict.set_item("时间戳", self.时间戳())?;
|
||||
dict.set_item("高", self.高())?;
|
||||
dict.set_item("低", self.低())?;
|
||||
dict.set_item("方向", self.方向())?;
|
||||
dict.set_item("方向", self.方向(py))?;
|
||||
dict.set_item("周期", self.周期())?;
|
||||
dict.set_item("标识", self.标识())?;
|
||||
dict.set_item("分型特征值", self.分型特征值())?;
|
||||
|
||||
+30
-29
@@ -71,39 +71,40 @@ fn _chanlun(_py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::*;
|
||||
use pyo3::prelude::*;
|
||||
|
||||
#[test]
|
||||
fn test_rc_pointer_across_getters() {
|
||||
pyo3::prepare_freethreaded_python();
|
||||
Python::with_gil(|py| {
|
||||
fn test_分型模式_get_set() {
|
||||
// 手动初始化 Python 解释器(cargo test 环境下 auto-initialize 不一定生效)
|
||||
unsafe {
|
||||
if pyo3::ffi::Py_IsInitialized() == 0 {
|
||||
pyo3::ffi::Py_Initialize();
|
||||
}
|
||||
}
|
||||
pyo3::Python::try_attach(|py| {
|
||||
let module = PyModule::new(py, "test_module").unwrap();
|
||||
module.add_class::<business_py::观察者Py>().unwrap();
|
||||
module.add_class::<business_py::基础买卖点Py>().unwrap();
|
||||
module.add_class::<business_py::买卖点Py>().unwrap();
|
||||
module.add_class::<kline_py::K线Py>().unwrap();
|
||||
module.add_class::<kline_py::缠论K线Py>().unwrap();
|
||||
module.add_class::<structure_py::分型Py>().unwrap();
|
||||
module.add_class::<structure_py::虚线Py>().unwrap();
|
||||
module.add_class::<config_py::缠论配置Py>().unwrap();
|
||||
module
|
||||
.add_function(wrap_pyfunction!(get_分型模式, &module).unwrap())
|
||||
.unwrap();
|
||||
module
|
||||
.add_function(wrap_pyfunction!(set_分型模式, &module).unwrap())
|
||||
.unwrap();
|
||||
|
||||
let config = config_py::缠论配置Py::from_rust_config(&Default::default()).unwrap();
|
||||
let obs = business_py::观察者Py::new_impl("btcusd".into(), 300, config, py).unwrap();
|
||||
// 默认 true
|
||||
let getter = module.getattr("get_分型模式").unwrap();
|
||||
let result: bool = getter.call0().unwrap().extract().unwrap();
|
||||
assert!(result, "分型模式 默认应为 True");
|
||||
|
||||
// Feed one K line
|
||||
let kline = kline_py::K线Py::new_impl(
|
||||
"btcusd".into(),
|
||||
1000,
|
||||
100.0,
|
||||
105.0,
|
||||
99.0,
|
||||
103.0,
|
||||
1000.0,
|
||||
0,
|
||||
300,
|
||||
);
|
||||
let kline_ref = kline.into_ref(py);
|
||||
// ... this is too complex
|
||||
});
|
||||
// 设置为 false
|
||||
let setter = module.getattr("set_分型模式").unwrap();
|
||||
setter.call1((false,)).unwrap();
|
||||
let result: bool = getter.call0().unwrap().extract().unwrap();
|
||||
assert!(!result, "分型模式 应为 False");
|
||||
|
||||
// 恢复 true
|
||||
setter.call1((true,)).unwrap();
|
||||
let result: bool = getter.call0().unwrap().extract().unwrap();
|
||||
assert!(result, "分型模式 应为 True");
|
||||
})
|
||||
.expect("Python 解释器初始化后 attach 仍失败");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -233,12 +233,15 @@ impl 分型Py {
|
||||
|
||||
#[getter]
|
||||
/// 左、中、右三对相对方向关系
|
||||
fn 关系组(&self) -> Option<(相对方向Py, 相对方向Py, 相对方向Py)> {
|
||||
fn 关系组(
|
||||
&self,
|
||||
py: Python<'_>,
|
||||
) -> Option<(Py<相对方向Py>, Py<相对方向Py>, Py<相对方向Py>)> {
|
||||
self.inner.关系组().map(|(a, b, c)| {
|
||||
(
|
||||
相对方向Py { inner: a },
|
||||
相对方向Py { inner: b },
|
||||
相对方向Py { inner: c },
|
||||
crate::types_py::获取相对方向单例(py, a),
|
||||
crate::types_py::获取相对方向单例(py, b),
|
||||
crate::types_py::获取相对方向单例(py, c),
|
||||
)
|
||||
})
|
||||
}
|
||||
@@ -271,7 +274,7 @@ impl 分型Py {
|
||||
if let Some(v) = self.右(py) {
|
||||
dict.set_item("右", v)?;
|
||||
}
|
||||
if let Some(v) = self.关系组() {
|
||||
if let Some(v) = self.关系组(py) {
|
||||
dict.set_item("关系组", v)?;
|
||||
}
|
||||
Ok(dict.into())
|
||||
@@ -545,10 +548,8 @@ impl 虚线Py {
|
||||
|
||||
#[getter]
|
||||
/// :return: 运行方向
|
||||
fn 方向(&self) -> 相对方向Py {
|
||||
相对方向Py {
|
||||
inner: self.inner.方向(),
|
||||
}
|
||||
fn 方向(&self, py: Python<'_>) -> Py<相对方向Py> {
|
||||
crate::types_py::获取相对方向单例(py, self.inner.方向())
|
||||
}
|
||||
|
||||
#[getter]
|
||||
@@ -1036,10 +1037,8 @@ impl 线段特征Py {
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn 线段方向(&self) -> 相对方向Py {
|
||||
相对方向Py {
|
||||
inner: self.inner.线段方向,
|
||||
}
|
||||
fn 线段方向(&self, py: Python<'_>) -> Py<相对方向Py> {
|
||||
crate::types_py::获取相对方向单例(py, self.inner.线段方向)
|
||||
}
|
||||
|
||||
#[getter]
|
||||
@@ -1101,7 +1100,7 @@ impl 线段特征Py {
|
||||
let dict = PyDict::new(py);
|
||||
dict.set_item("序号", self.序号())?;
|
||||
dict.set_item("标识", self.标识())?;
|
||||
dict.set_item("线段方向", self.线段方向())?;
|
||||
dict.set_item("线段方向", self.线段方向(py))?;
|
||||
dict.set_item("图表标题", self.图表标题())?;
|
||||
Ok(dict.into())
|
||||
}
|
||||
@@ -1128,10 +1127,8 @@ impl 线段特征Py {
|
||||
|
||||
#[getter]
|
||||
/// :return: 特征序列方向(线段方向的翻转)
|
||||
fn 方向(&self) -> 相对方向Py {
|
||||
相对方向Py {
|
||||
inner: self.inner.方向(),
|
||||
}
|
||||
fn 方向(&self, py: Python<'_>) -> Py<相对方向Py> {
|
||||
crate::types_py::获取相对方向单例(py, self.inner.方向())
|
||||
}
|
||||
|
||||
#[getter]
|
||||
|
||||
@@ -61,6 +61,40 @@ pub fn 获取分型结构单例(
|
||||
result
|
||||
}
|
||||
|
||||
static 相对方向_单例缓存: Mutex<Option<HashMap<u8, Py<相对方向Py>>>> = Mutex::new(None);
|
||||
|
||||
pub fn 获取相对方向单例(
|
||||
py: Python<'_>,
|
||||
inner: chanlun::types::相对方向,
|
||||
) -> Py<相对方向Py> {
|
||||
let mut guard = 相对方向_单例缓存.lock().unwrap();
|
||||
if let Some(ref map) = *guard {
|
||||
return map[&(inner as u8)].clone_ref(py);
|
||||
}
|
||||
|
||||
// 首次访问时从类属性加载单例
|
||||
let module = py.import("chanlun._chanlun").unwrap();
|
||||
let class = module.getattr("相对方向").unwrap();
|
||||
let mut map = HashMap::new();
|
||||
for (name, variant) in &[
|
||||
("向上", chanlun::types::相对方向::向上),
|
||||
("向下", chanlun::types::相对方向::向下),
|
||||
("向上缺口", chanlun::types::相对方向::向上缺口),
|
||||
("向下缺口", chanlun::types::相对方向::向下缺口),
|
||||
("衔接向上", chanlun::types::相对方向::衔接向上),
|
||||
("衔接向下", chanlun::types::相对方向::衔接向下),
|
||||
("顺", chanlun::types::相对方向::顺),
|
||||
("逆", chanlun::types::相对方向::逆),
|
||||
("同", chanlun::types::相对方向::同),
|
||||
] {
|
||||
let instance: Py<相对方向Py> = class.getattr(*name).unwrap().extract().unwrap();
|
||||
map.insert(*variant as u8, instance);
|
||||
}
|
||||
let result = map[&(inner as u8)].clone_ref(py);
|
||||
*guard = Some(map);
|
||||
result
|
||||
}
|
||||
|
||||
// ========== 买卖点类型 ==========
|
||||
|
||||
/// 买卖点类型 — 缠论的三类买卖点及扩展类型。
|
||||
@@ -189,10 +223,8 @@ impl 相对方向Py {
|
||||
}
|
||||
|
||||
/// 返回方向的对立面(向上↔向下, 缺口↔反向缺口, 衔接↔反向衔接)。
|
||||
fn 翻转(&self) -> Self {
|
||||
Self {
|
||||
inner: self.inner.翻转(),
|
||||
}
|
||||
fn 翻转(&self, py: Python<'_>) -> Py<Self> {
|
||||
获取相对方向单例(py, self.inner.翻转())
|
||||
}
|
||||
|
||||
/// 判断是否为向上方向(向上/向上缺口/衔接向上)
|
||||
@@ -245,10 +277,11 @@ impl 相对方向Py {
|
||||
#[classmethod]
|
||||
fn 分析(
|
||||
_cls: &Bound<'_, PyType>, 前高: f64, 前低: f64, 后高: f64, 后低: f64
|
||||
) -> Self {
|
||||
Self {
|
||||
inner: chanlun::types::相对方向::分析(前高, 前低, 后高, 后低),
|
||||
}
|
||||
) -> Py<Self> {
|
||||
获取相对方向单例(
|
||||
_cls.py(),
|
||||
chanlun::types::相对方向::分析(前高, 前低, 后高, 后低),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "chanlun"
|
||||
version = "26.5.4"
|
||||
version = "26.5.5"
|
||||
edition = "2021"
|
||||
rust-version = "1.70"
|
||||
license = "MIT"
|
||||
|
||||
@@ -155,6 +155,14 @@ impl 观察者 {
|
||||
self.__处理数据(普K);
|
||||
}
|
||||
|
||||
/// 投喂原始数据 — 便捷入口,直接从 OHLCV 创建 K线 并投喂
|
||||
pub fn 投喂原始数据(
|
||||
&mut self, 时间戳: i64, 开: f64, 高: f64, 低: f64, 收: f64, 量: f64
|
||||
) {
|
||||
let 普K = K线::创建普K(&self.符号, 时间戳, 开, 高, 低, 收, 量, 0, self.周期);
|
||||
self.增加原始K线(普K);
|
||||
}
|
||||
|
||||
/// 核心数据处理管道
|
||||
fn __处理数据(&mut self, 普K: K线) {
|
||||
// Step 1: 缠论K线分析 (普K is consumed by 分析 as &mut)
|
||||
@@ -479,39 +487,26 @@ impl 观察者 {
|
||||
println!("全部数据拆分保存完成,目录:{}", 保存路径.display());
|
||||
}
|
||||
|
||||
/// 解析本地数据文件 — 从 .nb 文件读取并解析所有 K线
|
||||
pub fn 解析本地数据(&self, 文件路径: &str) -> Result<Vec<K线>, String> {
|
||||
let data = std::fs::read(文件路径).map_err(|e| format!("read file: {}", e))?;
|
||||
let mut bars = Vec::new();
|
||||
let size = 48;
|
||||
for i in 0..data.len() / size {
|
||||
let offset = i * size;
|
||||
if let Some(k线) = K线::from_bytes(&data[offset..offset + size], self.周期, &self.符号)
|
||||
{
|
||||
bars.push(k线);
|
||||
}
|
||||
}
|
||||
Ok(bars)
|
||||
}
|
||||
|
||||
/// 加载本地数据 — 从 .nb 文件加载数据到当前观察者(先重置再投喂)
|
||||
pub fn 加载本地数据(&mut self, 文件路径: &str) -> Result<(), String> {
|
||||
self.重置基础序列();
|
||||
let bars = self.解析本地数据(文件路径)?;
|
||||
for k线 in bars {
|
||||
self.增加原始K线(k线);
|
||||
let data = std::fs::read(文件路径).map_err(|e| format!("read file: {}", e))?;
|
||||
let size = 48;
|
||||
for i in 0..data.len() / size {
|
||||
let offset = i * size;
|
||||
if let Some((时间戳, 开, 高, 低, 收, 量)) =
|
||||
K线::解析原始数据(&data[offset..offset + size])
|
||||
{
|
||||
self.投喂原始数据(时间戳, 开, 高, 低, 收, 量);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 读取数据文件 — 从 .nb 文件加载数据
|
||||
/// 读取数据文件 — 更新当前观察者并加载 .nb 文件
|
||||
pub fn 读取数据文件(
|
||||
文件路径: &str,
|
||||
配置: Option<缠论配置>,
|
||||
) -> Result<Arc<RwLock<Self>>, String> {
|
||||
let 配置 = 配置.unwrap_or_default();
|
||||
|
||||
// Parse filename: btcusd-300-1631772074-1632222374.nb
|
||||
&mut self, 文件路径: &str, 配置: 缠论配置
|
||||
) -> Result<(), String> {
|
||||
let path = std::path::Path::new(文件路径);
|
||||
let name = path
|
||||
.file_stem()
|
||||
@@ -521,23 +516,12 @@ impl 观察者 {
|
||||
if parts.len() < 4 {
|
||||
return Err(format!("invalid filename format: {}", name));
|
||||
}
|
||||
let 符号 = parts[0].to_string();
|
||||
let 周期: i64 = parts[1]
|
||||
self.符号 = parts[0].to_string();
|
||||
self.周期 = parts[1]
|
||||
.parse()
|
||||
.map_err(|e| format!("parse period: {}", e))?;
|
||||
|
||||
let 实例 = Self::new(符号, 周期, 配置);
|
||||
|
||||
let data = std::fs::read(文件路径).map_err(|e| format!("read file: {}", e))?;
|
||||
let size = 48; // 6 × 8 bytes (big-endian double)
|
||||
for i in 0..data.len() / size {
|
||||
let offset = i * size;
|
||||
if let Some(k线) = K线::from_bytes(&data[offset..offset + size], 周期, "nb") {
|
||||
实例.write().unwrap().增加原始K线(k线);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(实例)
|
||||
self.配置 = 配置;
|
||||
self.加载本地数据(文件路径)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -546,12 +530,23 @@ mod tests {
|
||||
use super::*;
|
||||
use crate::config::缠论配置;
|
||||
|
||||
const TEST_DATA_PATH: &str = "/home/moscow/chanlun.rs/btcusd-300-1777649100-1778398800.nb";
|
||||
fn test_data_path() -> String {
|
||||
let manifest = std::path::Path::new(env!("CARGO_MANIFEST_DIR"));
|
||||
manifest
|
||||
.parent()
|
||||
.unwrap()
|
||||
.join("btcusd-300-1777649100-1778398800.nb")
|
||||
.to_string_lossy()
|
||||
.to_string()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_普k序列指针一致性() {
|
||||
let config = 缠论配置::default();
|
||||
let obs = 观察者::读取数据文件(TEST_DATA_PATH, Some(config)).unwrap();
|
||||
let obs = 观察者::new("btcusd".into(), 300, Default::default());
|
||||
obs.write()
|
||||
.unwrap()
|
||||
.读取数据文件(&test_data_path(), Default::default())
|
||||
.unwrap();
|
||||
let obs_ref = obs.read().unwrap();
|
||||
|
||||
for (i, bi) in obs_ref.笔序列.iter().enumerate() {
|
||||
@@ -595,7 +590,7 @@ mod tests {
|
||||
let config = 缠论配置::default();
|
||||
let obs_ref = 观察者::new("btcusd".into(), 300, config);
|
||||
|
||||
let data = std::fs::read(TEST_DATA_PATH).unwrap();
|
||||
let data = std::fs::read(&test_data_path()).unwrap();
|
||||
let size = 48;
|
||||
|
||||
for i in 0..data.len() / size {
|
||||
@@ -633,8 +628,11 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_分型到笔的文武Rc指针一致性() {
|
||||
let config = 缠论配置::default();
|
||||
let obs = 观察者::读取数据文件(TEST_DATA_PATH, Some(config)).unwrap();
|
||||
let obs = 观察者::new("btcusd".into(), 300, Default::default());
|
||||
obs.write()
|
||||
.unwrap()
|
||||
.读取数据文件(&test_data_path(), Default::default())
|
||||
.unwrap();
|
||||
let obs_ref = obs.read().unwrap();
|
||||
|
||||
// 每个笔的文/武 分型 Rc 指针必须在 分型序列 中
|
||||
@@ -663,8 +661,11 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_笔到线段的基础序列Rc指针一致性() {
|
||||
let config = 缠论配置::default();
|
||||
let obs = 观察者::读取数据文件(TEST_DATA_PATH, Some(config)).unwrap();
|
||||
let obs = 观察者::new("btcusd".into(), 300, Default::default());
|
||||
obs.write()
|
||||
.unwrap()
|
||||
.读取数据文件(&test_data_path(), Default::default())
|
||||
.unwrap();
|
||||
let obs_ref = obs.read().unwrap();
|
||||
|
||||
// 每个线段的基础序列中的笔 Rc 指针必须在 笔序列 中
|
||||
@@ -685,8 +686,11 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_中枢基础序列与笔序列Rc指针一致() {
|
||||
let config = 缠论配置::default();
|
||||
let obs = 观察者::读取数据文件(TEST_DATA_PATH, Some(config)).unwrap();
|
||||
let obs = 观察者::new("btcusd".into(), 300, Default::default());
|
||||
obs.write()
|
||||
.unwrap()
|
||||
.读取数据文件(&test_data_path(), Default::default())
|
||||
.unwrap();
|
||||
let obs_ref = obs.read().unwrap();
|
||||
|
||||
for (i, hub) in obs_ref.笔_中枢序列.iter().enumerate() {
|
||||
@@ -716,7 +720,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_重复计算后结果一致() {
|
||||
let data = std::fs::read(TEST_DATA_PATH).unwrap();
|
||||
let data = std::fs::read(&test_data_path()).unwrap();
|
||||
let size = 48;
|
||||
|
||||
let 计算 = || {
|
||||
@@ -760,7 +764,7 @@ mod tests {
|
||||
let config = 缠论配置::default();
|
||||
let obs_ref = 观察者::new("btcusd".into(), 300, config);
|
||||
|
||||
let data = std::fs::read(TEST_DATA_PATH).unwrap();
|
||||
let data = std::fs::read(&test_data_path()).unwrap();
|
||||
let size = 48;
|
||||
|
||||
for i in 0..data.len() / size {
|
||||
@@ -800,8 +804,11 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_RefCell借用安全性_连续读取不panic() {
|
||||
let config = 缠论配置::default();
|
||||
let obs = 观察者::读取数据文件(TEST_DATA_PATH, Some(config)).unwrap();
|
||||
let obs = 观察者::new("btcusd".into(), 300, Default::default());
|
||||
obs.write()
|
||||
.unwrap()
|
||||
.读取数据文件(&test_data_path(), Default::default())
|
||||
.unwrap();
|
||||
let obs_ref = obs.read().unwrap();
|
||||
|
||||
// 连续大量读取所有 RefCell 字段,不应 panic
|
||||
@@ -831,8 +838,11 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_RefCell借用安全性_交替读写不panic() {
|
||||
let config = 缠论配置::default();
|
||||
let obs = 观察者::读取数据文件(TEST_DATA_PATH, Some(config)).unwrap();
|
||||
let obs = 观察者::new("btcusd".into(), 300, Default::default());
|
||||
obs.write()
|
||||
.unwrap()
|
||||
.读取数据文件(&test_data_path(), Default::default())
|
||||
.unwrap();
|
||||
let obs_ref = obs.read().unwrap();
|
||||
|
||||
// 交替读写 RefCell 字段 — 先读再写同字段,分离 borrow 作用域
|
||||
@@ -860,8 +870,11 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_缠K到分型的Rc指针一致性() {
|
||||
let config = 缠论配置::default();
|
||||
let obs = 观察者::读取数据文件(TEST_DATA_PATH, Some(config)).unwrap();
|
||||
let obs = 观察者::new("btcusd".into(), 300, Default::default());
|
||||
obs.write()
|
||||
.unwrap()
|
||||
.读取数据文件(&test_data_path(), Default::default())
|
||||
.unwrap();
|
||||
let obs_ref = obs.read().unwrap();
|
||||
|
||||
// 每个分型的左/中/右 缠K 指针必须在 缠论K线序列 中
|
||||
|
||||
@@ -125,6 +125,21 @@ impl K线 {
|
||||
Self::from_bytes(字节组, 周期, 标识)
|
||||
}
|
||||
|
||||
/// 解析原始数据 — 只提取时间戳+OHLCV,不构造 K线
|
||||
pub fn 解析原始数据(字节组: &[u8]) -> Option<(i64, f64, f64, f64, f64, f64)> {
|
||||
if 字节组.len() < 48 {
|
||||
return None;
|
||||
}
|
||||
let mut reader = &字节组[..48];
|
||||
let 时间戳 = reader.read_f64::<BigEndian>().ok()? as i64;
|
||||
let 开 = reader.read_f64::<BigEndian>().ok()?;
|
||||
let 高 = reader.read_f64::<BigEndian>().ok()?;
|
||||
let 低 = reader.read_f64::<BigEndian>().ok()?;
|
||||
let 收 = reader.read_f64::<BigEndian>().ok()?;
|
||||
let 量 = reader.read_f64::<BigEndian>().ok()?;
|
||||
Some((时间戳, 开, 高, 低, 收, 量))
|
||||
}
|
||||
|
||||
/// 创建普通K线
|
||||
pub fn 创建普K(
|
||||
标识: &str,
|
||||
|
||||
+29
-31
@@ -77,38 +77,36 @@ fn 测试_读取数据(文件路径: &str) {
|
||||
let 启动时间 = Instant::now();
|
||||
|
||||
let 配置 = 缠论配置::default().不推送();
|
||||
match 观察者::读取数据文件(文件路径, Some(配置)) {
|
||||
Ok(观察员) => {
|
||||
let 观察员 = 观察员.read().unwrap();
|
||||
let 消耗用时 = 启动时间.elapsed();
|
||||
println!(
|
||||
"测试_读取数据 耗时 {:.2?} 普K数量 {}",
|
||||
消耗用时,
|
||||
观察员.普通K线序列.len()
|
||||
);
|
||||
println!("符号: {}", 观察员.符号);
|
||||
println!("周期: {}", 观察员.周期);
|
||||
println!("缠K数量: {}", 观察员.缠论K线序列.len());
|
||||
println!("分型数量: {}", 观察员.分型序列.len());
|
||||
println!("笔数量: {}", 观察员.笔序列.len());
|
||||
println!("笔中枢数量: {}", 观察员.笔_中枢序列.len());
|
||||
println!("线段数量: {}", 观察员.线段序列.len());
|
||||
println!("中枢数量: {}", 观察员.中枢序列.len());
|
||||
println!("扩展线段数量: {}", 观察员.扩展线段序列.len());
|
||||
println!("线段_线段序列数量: {}", 观察员.线段_线段序列.len());
|
||||
println!(
|
||||
"扩展线段_扩展线段数量: {}",
|
||||
观察员.扩展线段序列_扩展线段.len()
|
||||
);
|
||||
let 观察员 = 观察者::new("".into(), 0, 缠论配置::default());
|
||||
观察员
|
||||
.write()
|
||||
.unwrap()
|
||||
.读取数据文件(文件路径, 配置)
|
||||
.expect("读取数据文件失败");
|
||||
let 观察员 = 观察员.read().unwrap();
|
||||
let 消耗用时 = 启动时间.elapsed();
|
||||
println!(
|
||||
"测试_读取数据 耗时 {:.2?} 普K数量 {}",
|
||||
消耗用时,
|
||||
观察员.普通K线序列.len()
|
||||
);
|
||||
println!("符号: {}", 观察员.符号);
|
||||
println!("周期: {}", 观察员.周期);
|
||||
println!("缠K数量: {}", 观察员.缠论K线序列.len());
|
||||
println!("分型数量: {}", 观察员.分型序列.len());
|
||||
println!("笔数量: {}", 观察员.笔序列.len());
|
||||
println!("笔中枢数量: {}", 观察员.笔_中枢序列.len());
|
||||
println!("线段数量: {}", 观察员.线段序列.len());
|
||||
println!("中枢数量: {}", 观察员.中枢序列.len());
|
||||
println!("扩展线段数量: {}", 观察员.扩展线段序列.len());
|
||||
println!("线段_线段序列数量: {}", 观察员.线段_线段序列.len());
|
||||
println!(
|
||||
"扩展线段_扩展线段数量: {}",
|
||||
观察员.扩展线段序列_扩展线段.len()
|
||||
);
|
||||
|
||||
println!("\n===== 保存分析数据 =====\n");
|
||||
观察员.测试_保存数据(None);
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("读取失败: {}", e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
println!("\n===== 保存分析数据 =====\n");
|
||||
观察员.测试_保存数据(None);
|
||||
}
|
||||
|
||||
/// 测试_周期合成 — 多周期合成分析
|
||||
|
||||
@@ -1,3 +1,27 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2026 YuYuKunKun
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
|
||||
/// f64 原子类型 — 基于 AtomicU64 + 位转换,API 与 `Cell<f64>` 一致。
|
||||
|
||||
Reference in New Issue
Block a user