Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 823a24d364 | |||
| 58c85dc7cd | |||
| c3e9ae8d77 | |||
| b7c4e60420 | |||
| 15dc44e8e0 | |||
| bd4fceab02 | |||
| af3ebf13c8 | |||
| 22109d8b30 | |||
| c2c09fc8ba | |||
| 0eb52cc06a | |||
| 1e6025a968 | |||
| 14279f3df6 | |||
| fca62f3141 | |||
| e50172e923 | |||
| c87fb66d34 | |||
| 9900266516 |
@@ -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
|
||||
|
||||
- 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 publish --dry-run --allow-dirty
|
||||
|
||||
- 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 --allow-dirty
|
||||
|
||||
# ============================================================
|
||||
# 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: 下载所有产物
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
version: 2
|
||||
|
||||
build:
|
||||
os: ubuntu-24.04
|
||||
tools:
|
||||
python: "3.12"
|
||||
jobs:
|
||||
pre_install:
|
||||
# 安装 Rust 工具链以编译 PyO3 扩展
|
||||
- curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable
|
||||
- source $HOME/.cargo/env
|
||||
- pip install maturin
|
||||
|
||||
sphinx:
|
||||
configuration: docs/conf.py
|
||||
|
||||
python:
|
||||
install:
|
||||
- requirements: docs/requirements.txt
|
||||
# 从源码安装 chanlun-py(maturin develop)
|
||||
- method: pip
|
||||
path: chanlun-py
|
||||
@@ -3,7 +3,7 @@
|
||||
[](https://pypi.org/project/chanlun/)
|
||||
[](LICENSE)
|
||||
|
||||
基于 [chanlun](./chanlun/) Rust 核心库的 PyO3 高性能 Python 绑定,API 与 `chan.py` 完全兼容。
|
||||
基于 [chanlun](./chanlun/) Rust 核心库的 PyO3 高性能 Python 绑定,API 参考 `chan.py` 设计,高度兼容。
|
||||
|
||||
## 安装
|
||||
|
||||
@@ -19,8 +19,8 @@ import chanlun
|
||||
# 创建配置(全部默认值)
|
||||
config = chanlun.缠论配置()
|
||||
|
||||
# 读取 K 线数据文件,创建观察者
|
||||
obs = chanlun.观察者.读取数据文件("path/to/data.nb", config)
|
||||
# 读取 K 线数据文件(文件名需遵循 `符号-周期-起始时间戳-结束时间戳.nb` 格式,如 `btcusd-300-1631772074-1632222374.nb`)
|
||||
obs = chanlun.观察者.读取数据文件("path/to/btcusd-300-1631772074-1632222374.nb", config)
|
||||
|
||||
# 查看各层级序列
|
||||
print(f"K线数量: {len(obs.普通K线序列)}")
|
||||
@@ -29,7 +29,7 @@ print(f"线段数量: {len(obs.线段序列)}")
|
||||
print(f"中枢数量: {len(obs.中枢序列)}")
|
||||
|
||||
# 或使用立体分析器进行多周期分析
|
||||
analyzer = chanlun.立体分析器("BTCUSD", ["1min", "5min", "30min"], config)
|
||||
analyzer = chanlun.立体分析器("BTCUSD", [60, 60*5, 60*5*6], config)
|
||||
# 逐根投喂 K 线...
|
||||
```
|
||||
|
||||
@@ -53,7 +53,6 @@ pip install target/wheels/chanlun-*.whl
|
||||
```bash
|
||||
./build.sh develop # 开发安装
|
||||
./build.sh wheel # 构建 wheel
|
||||
./build.sh test # 运行集成测试
|
||||
```
|
||||
|
||||
## 导出类
|
||||
@@ -70,8 +69,8 @@ pip install target/wheels/chanlun-*.whl
|
||||
## 兼容性
|
||||
|
||||
- Python 3.9+
|
||||
- 类名 / 方法名 / 字段名 / 签名与 `chan.py` 一致
|
||||
- 支持 `.nb` / `.dat` 二进制文件格式(大端字节序)
|
||||
- 类名 / 方法名 / 字段名与 `chan.py` 保持一致
|
||||
- 支持 `.nb` 二进制文件格式(大端字节序)
|
||||
|
||||
## 许可
|
||||
|
||||
|
||||
@@ -28,8 +28,10 @@ SOFTWARE.
|
||||
|
||||
import json
|
||||
import math
|
||||
import os
|
||||
import struct
|
||||
import sys
|
||||
import tempfile
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import datetime
|
||||
from enum import Enum
|
||||
@@ -2202,6 +2204,9 @@ class 缠论K线(object):
|
||||
return 序列[序列.index(始) : 序列.index(终) + 1]
|
||||
|
||||
|
||||
分型模式 = True
|
||||
|
||||
|
||||
class 分型(object):
|
||||
"""分型 — 由左中右三根缠论K线构成的顶/底分型结构。
|
||||
|
||||
@@ -2216,7 +2221,7 @@ class 分型(object):
|
||||
:ivar 与MACD柱子分型匹配: 是否与MACD柱子分型匹配
|
||||
"""
|
||||
|
||||
__slots__ = ["左", "中", "右", "结构", "时间戳", "分型特征值"]
|
||||
__slots__ = ["左", "中", "右", "_结构", "_时间戳", "_分型特征值"]
|
||||
|
||||
def __init__(self, 左: Optional[缠论K线], 中: 缠论K线, 右: Optional[缠论K线]):
|
||||
"""
|
||||
@@ -2229,9 +2234,9 @@ class 分型(object):
|
||||
self.左: Optional[缠论K线] = 左
|
||||
self.中: 缠论K线 = 中
|
||||
self.右: Optional[缠论K线] = 右
|
||||
self.结构 = 中.分型
|
||||
self.时间戳 = 中.时间戳
|
||||
self.分型特征值 = 中.分型特征值
|
||||
self._结构 = 中.分型
|
||||
self._时间戳 = 中.时间戳
|
||||
self._分型特征值 = 中.分型特征值
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.中.分型}<{self.时间戳}, {self.分型特征值:g}, None: {self.左 is None}, None: {self.右 is None}>"
|
||||
@@ -2239,6 +2244,24 @@ class 分型(object):
|
||||
def __repr__(self):
|
||||
return f"{self.中.分型}<{self.时间戳}, {self.分型特征值:g}, None: {self.左 is None}, None: {self.右 is None}>"
|
||||
|
||||
@property
|
||||
def 时间戳(self):
|
||||
if 分型模式:
|
||||
return self._时间戳
|
||||
return self.中.时间戳
|
||||
|
||||
@property
|
||||
def 分型特征值(self):
|
||||
if 分型模式:
|
||||
return self._分型特征值
|
||||
return self.中.分型特征值
|
||||
|
||||
@property
|
||||
def 结构(self):
|
||||
if 分型模式:
|
||||
return self._结构
|
||||
return self.中.分型
|
||||
|
||||
@property
|
||||
def 关系组(self) -> Optional[Tuple[相对方向, 相对方向, 相对方向]]:
|
||||
"""左、中、右三对相对方向关系
|
||||
@@ -5055,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线,增量更新所有层级
|
||||
@@ -5201,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线合成器:
|
||||
@@ -5462,10 +5495,10 @@ class 立体分析器:
|
||||
if 当前K线 := self._K线合成器.获取当前K线(周期):
|
||||
self._单体分析器[周期].增加原始K线(当前K线)
|
||||
|
||||
def 测试_保存数据(self):
|
||||
def 测试_保存数据(self, root: str = None):
|
||||
"""拆分各序列数据,单独存文件,文件名为对应变量名"""
|
||||
# 生成存储根目录
|
||||
脚本目录 = Path(__file__).parent # 取当前脚本所在文件夹
|
||||
脚本目录 = Path(__file__).parent if not root else root # 取当前脚本所在文件夹
|
||||
起始时间 = int(self._单体分析器[self.__输入周期].普通K线序列[0].时间戳.timestamp())
|
||||
结束时间 = int(self._单体分析器[self.__输入周期].普通K线序列[-1].时间戳.timestamp())
|
||||
目录标识 = f"PyM_{self._单体分析器[self.__输入周期].标识}_{起始时间}_{结束时间}"
|
||||
@@ -5480,16 +5513,16 @@ class 立体分析器:
|
||||
print(f"多级别数据拆分保存完成,目录:{保存路径.resolve()}")
|
||||
|
||||
|
||||
def 测试_读取数据(配置: 缠论配置):
|
||||
def 测试_读取数据(观察员: 观察者, 配置: 缠论配置) -> Callable[[], "观察者"]:
|
||||
"""测试_读取数据
|
||||
|
||||
:param 观察员: 观察者
|
||||
:param 配置: 缠论配置
|
||||
:return: 测试函数
|
||||
"""
|
||||
|
||||
def 魔法():
|
||||
启动时间 = datetime.now()
|
||||
观察员 = 观察者.读取数据文件(配置.加载文件路径, 配置)
|
||||
观察者.读取数据文件(观察员, 配置.加载文件路径, 配置)
|
||||
消耗用时 = datetime.now() - 启动时间
|
||||
print("测试_读取数据 耗时", 消耗用时, "普K数量", len(观察员.普通K线序列))
|
||||
return 观察员
|
||||
@@ -5529,5 +5562,6 @@ def 测试_周期合成(配置: 缠论配置, 配置组: Dict[int, 缠论配置]
|
||||
if __name__ == "__main__":
|
||||
当前配置 = 缠论配置.不推送()
|
||||
当前配置.加载文件路径 = str(Path(__file__).parent / "btcusd-300-1761327300-1776327900.nb")
|
||||
测试_读取数据(当前配置)().测试_保存数据()
|
||||
测试_周期合成(当前配置)().测试_保存数据()
|
||||
观察员 = 观察者("", 0, 当前配置)
|
||||
测试_读取数据(观察员, 当前配置)() # .测试_保存数据()
|
||||
# 测试_周期合成(当前配置)().测试_保存数据()
|
||||
|
||||
@@ -3,3 +3,5 @@ __pycache__/
|
||||
*.egg-info/
|
||||
dist/
|
||||
build/
|
||||
Cargo.lock
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "chanlun-py"
|
||||
version = "26.5.86"
|
||||
edition = "2021"
|
||||
version = "26.6.17"
|
||||
edition = "2024"
|
||||
description = "缠论技术分析库 — Rust 高性能 Python 绑定"
|
||||
authors = ["YuYuKunKun"]
|
||||
license = "MIT"
|
||||
@@ -12,7 +12,10 @@ crate-type = ["cdylib"]
|
||||
name = "chanlun"
|
||||
|
||||
[dependencies]
|
||||
chanlun = "26.5.3" # { path = "../chanlun" }
|
||||
pyo3 = { version = "0.28", features = ["extension-module", "experimental-inspect"] }
|
||||
chanlun = "26.6.1" # { path = "../chanlun" }
|
||||
pyo3 = { version = "0.28", features = ["experimental-inspect"] }
|
||||
serde_json = "1"
|
||||
chrono = "0.4"
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = { version = "0.3", features = ["env-filter", "ansi", "std", "registry"] }
|
||||
tracing-core = "0.1"
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
../LICENSE
|
||||
@@ -0,0 +1,21 @@
|
||||
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.
|
||||
@@ -1 +0,0 @@
|
||||
../README.md
|
||||
@@ -0,0 +1,79 @@
|
||||
# chanlun — 缠论技术分析 Python 绑定
|
||||
|
||||
[](https://pypi.org/project/chanlun/)
|
||||
[](LICENSE)
|
||||
|
||||
基于 [chanlun](../chanlun/) Rust 核心库的 PyO3 高性能 Python 绑定,API 参考 `chan.py` 设计,高度兼容。
|
||||
|
||||
## 安装
|
||||
|
||||
```bash
|
||||
pip install chanlun
|
||||
```
|
||||
|
||||
## 快速开始
|
||||
|
||||
```python
|
||||
import chanlun
|
||||
|
||||
# 创建配置(全部默认值)
|
||||
config = chanlun.缠论配置()
|
||||
|
||||
# 读取 K 线数据文件(文件名需遵循 `符号-周期-起始时间戳-结束时间戳.nb` 格式,如 `btcusd-300-1631772074-1632222374.nb`)
|
||||
obs = chanlun.观察者.读取数据文件("path/to/btcusd-300-1631772074-1632222374.nb", config)
|
||||
|
||||
# 查看各层级序列
|
||||
print(f"K线数量: {len(obs.普通K线序列)}")
|
||||
print(f"笔数量: {len(obs.笔序列)}")
|
||||
print(f"线段数量: {len(obs.线段序列)}")
|
||||
print(f"中枢数量: {len(obs.中枢序列)}")
|
||||
|
||||
# 或使用立体分析器进行多周期分析
|
||||
analyzer = chanlun.立体分析器("BTCUSD", [60, 60*5, 60*5*6], config)
|
||||
# 逐根投喂 K 线...
|
||||
```
|
||||
|
||||
## 从源码构建
|
||||
|
||||
前置依赖: [Rust](https://www.rust-lang.org) + [maturin](https://www.maturin.rs)
|
||||
|
||||
```bash
|
||||
pip install maturin
|
||||
|
||||
# 开发模式(直接安装到当前 venv)
|
||||
maturin develop
|
||||
|
||||
# 或构建 wheel
|
||||
maturin build --release
|
||||
pip install target/wheels/chanlun-*.whl
|
||||
```
|
||||
|
||||
也可使用项目内的 `build.sh`:
|
||||
|
||||
```bash
|
||||
./build.sh develop # 开发安装
|
||||
./build.sh wheel # 构建 wheel
|
||||
```
|
||||
|
||||
## 导出类
|
||||
|
||||
| 类别 | 类名 | 说明 |
|
||||
|------|------|------|
|
||||
| 枚举 | `买卖点类型`, `相对方向`, `分型结构` | 缠论基础枚举 |
|
||||
| 数据 | `缺口`, `K线`, `缠论K线` | K 线数据结构 |
|
||||
| 结构 | `分型`, `虚线`, `线段特征`, `特征分型` | 分析层级结构 |
|
||||
| 指标 | `平滑异同移动平均线`, `相对强弱指数`, `随机指标` | MACD/RSI/KDJ |
|
||||
| 算法 | `笔`, `线段`, `中枢`, `背驰分析` | 识别算法 |
|
||||
| 业务 | `缠论配置`, `基础买卖点`, `买卖点`, `观察者`, `K线合成器`, `立体分析器` | 分析框架 |
|
||||
|
||||
## 兼容性
|
||||
|
||||
- Python 3.9+
|
||||
- 类名 / 方法名 / 字段名与 `chan.py` 保持一致
|
||||
- 支持 `.nb` 二进制文件格式(大端字节序)
|
||||
|
||||
## 许可
|
||||
|
||||
本项目主体采用 MIT 许可。包含以下第三方开源代码:czsc(Apache 2.0)、parse(MIT)、termcolor(MIT)。
|
||||
|
||||
详见 [NOTICE](../NOTICE) 和 [LICENSES/](../LICENSES/) 目录。
|
||||
+4
-4
@@ -58,10 +58,10 @@ fn read_version(path: &std::path::Path, section: &str) -> String {
|
||||
if !in_section {
|
||||
continue;
|
||||
}
|
||||
if trimmed.starts_with("version") {
|
||||
if let Some(v) = trimmed.split('=').nth(1) {
|
||||
return v.trim().trim_matches('"').trim().to_string();
|
||||
}
|
||||
if trimmed.starts_with("version")
|
||||
&& let Some(v) = trimmed.split('=').nth(1)
|
||||
{
|
||||
return v.trim().trim_matches('"').trim().to_string();
|
||||
}
|
||||
}
|
||||
panic!("Cannot parse version from {:?}", path);
|
||||
|
||||
Executable → Regular
Executable → Regular
+751
-2234
File diff suppressed because it is too large
Load Diff
@@ -11,7 +11,10 @@ __all__ = [
|
||||
"基础买卖点",
|
||||
"平滑异同移动平均线",
|
||||
"指标",
|
||||
"特征分型",
|
||||
"指标容器",
|
||||
"指标计算器",
|
||||
"均线工具",
|
||||
"测试_读取数据",
|
||||
"相对强弱指数",
|
||||
"相对方向",
|
||||
"立体分析器",
|
||||
@@ -27,6 +30,31 @@ __all__ = [
|
||||
"转化为时间戳",
|
||||
"转化为时间戳_数字",
|
||||
"随机指标",
|
||||
"布林带",
|
||||
"get_分型模式",
|
||||
"set_分型模式",
|
||||
"get_log_level",
|
||||
"set_log_level",
|
||||
"get_rs_log_level",
|
||||
"set_rs_log_level",
|
||||
"chan",
|
||||
]
|
||||
|
||||
from ._chanlun import *
|
||||
from ._chanlun import set_log_level as _rs_set_log_level, get_log_level as _rs_get_log_level
|
||||
from . import chan
|
||||
from .chan import 测试_读取数据, 转化为时间戳, 转化为时间戳_数字, set_log_level, get_log_level
|
||||
|
||||
|
||||
def set_rs_log_level(level: str):
|
||||
"""设置 Rust 侧日志级别 (trace / debug / info / warn / error / off)
|
||||
|
||||
仅控制 Rust tracing 日志,不影响 Python loguru 日志。
|
||||
Python 侧日志通过 set_log_level() 独立控制。
|
||||
"""
|
||||
_rs_set_log_level(level)
|
||||
|
||||
|
||||
def get_rs_log_level() -> str:
|
||||
"""获取 Rust 侧日志级别"""
|
||||
return _rs_get_log_level()
|
||||
|
||||
+751
-2234
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -4,7 +4,7 @@ build-backend = "maturin"
|
||||
|
||||
[project]
|
||||
name = "chanlun"
|
||||
version = "2605.86"
|
||||
version = "2606.17"
|
||||
description = "缠论技术分析库 — Rust 高性能实现"
|
||||
readme = { file = "README.md", content-type = "text/markdown" }
|
||||
license = { file = "LICENSE", content-type = "text/plain" }
|
||||
@@ -26,6 +26,11 @@ classifiers = [
|
||||
"Topic :: Office/Business :: Financial :: Investment",
|
||||
]
|
||||
requires-python = ">=3.9"
|
||||
dependencies = [
|
||||
"termcolor>=3.0",
|
||||
"loguru>=0.6",
|
||||
"backtrader==1.9.78.123",
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
Homepage = "https://github.com/YuYuKunKun/chanlun.rs"
|
||||
@@ -37,3 +42,15 @@ features = ["pyo3/extension-module"]
|
||||
python-source = "."
|
||||
module-name = "chanlun._chanlun"
|
||||
manifest-path = "Cargo.toml"
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
testpaths = ["tests"]
|
||||
addopts = ["-v", "--tb=short", "--durations=10"]
|
||||
|
||||
[tool.ruff.lint]
|
||||
# 启用 FA (flake8-future-annotations) 和 UP (pyupgrade) 规则
|
||||
select = ["FA", "UP"]
|
||||
|
||||
# [tool.ruff.lint.flake8-future-annotations]
|
||||
# 强制在所有文件中注入 from __future__ import annotations
|
||||
# force-future-annotations = true
|
||||
+93
-629
@@ -27,37 +27,39 @@ use crate::structure_py::{dashed_to_py, fractal_to_py};
|
||||
use pyo3::prelude::*;
|
||||
use pyo3::types::{PyDict, PyType};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::Arc;
|
||||
use std::sync::RwLock;
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
thread_local! {
|
||||
static HUB_IDENTITY: RwLock<HashMap<usize, Py<中枢Py>>> = RwLock::new(HashMap::new());
|
||||
}
|
||||
// 使用全局 static 而非 thread_local!,保证跨线程对象标识一致性
|
||||
static HUB_IDENTITY: std::sync::LazyLock<RwLock<HashMap<usize, Py<中枢Py>>>> =
|
||||
std::sync::LazyLock::new(|| RwLock::new(HashMap::new()));
|
||||
|
||||
pub(crate) fn hub_to_py(
|
||||
py: Python<'_>, inner: Arc<chanlun::algorithm::hub::中枢>
|
||||
) -> Py<中枢Py> {
|
||||
let key = Arc::as_ptr(&inner) as usize;
|
||||
if let Some(cached) =
|
||||
HUB_IDENTITY.with(|c| c.read().unwrap().get(&key).map(|p| p.clone_ref(py)))
|
||||
if let Some(cached) = HUB_IDENTITY
|
||||
.read()
|
||||
.unwrap()
|
||||
.get(&key)
|
||||
.map(|p| p.clone_ref(py))
|
||||
{
|
||||
return cached;
|
||||
}
|
||||
HUB_IDENTITY.with(|c| {
|
||||
c.write().unwrap().retain(|_, v| v.get_refcnt(py) > 1);
|
||||
});
|
||||
HUB_IDENTITY
|
||||
.write()
|
||||
.unwrap()
|
||||
.retain(|_, v| v.get_refcnt(py) > 1);
|
||||
let obj = Py::new(py, 中枢Py { inner }).unwrap();
|
||||
HUB_IDENTITY.with(|c| {
|
||||
c.write().unwrap().insert(key, obj.clone_ref(py));
|
||||
});
|
||||
HUB_IDENTITY.write().unwrap().insert(key, obj.clone_ref(py));
|
||||
obj
|
||||
}
|
||||
|
||||
use crate::business_py::观察者Py;
|
||||
use crate::config_py::缠论配置Py;
|
||||
use crate::kline_py::{缠论K线Py, K线Py};
|
||||
use crate::structure_py::{分型Py, 线段特征Py, 虚线Py};
|
||||
use crate::kline_py::{K线Py, 缠论K线Py};
|
||||
use crate::structure_py::{分型Py, 虚线Py};
|
||||
use crate::types_py::相对方向Py;
|
||||
|
||||
// ========== 背驰分析 ==========
|
||||
@@ -239,122 +241,20 @@ impl 背驰分析Py {
|
||||
|
||||
/// 笔 — 静态方法容器,提供笔的创建与分析算法。
|
||||
///
|
||||
/// 所有方法均为 staticmethod,直接调用无需实例化。
|
||||
/// 所有方法均为 staticmethod/classmethod,直接调用无需实例化。
|
||||
///
|
||||
/// 方法:
|
||||
/// 创建(左分型, 右分型, 序号, 级别, 有效性?, 标识?) -> 虚线
|
||||
/// — 在左右分型之间创建一笔
|
||||
/// 分析(分型序列, 配置, 序?, 初级序列?, 级别?) -> list[虚线]
|
||||
/// — 从分型序列中划分出所有笔
|
||||
/// 弱化(笔序列) — 根据配置对笔序列执行弱化处理
|
||||
/// 分析前检查(分型序列, 配置) -> bool — 检查是否可以启动笔分析
|
||||
/// :meth:`以文会友` — 根据起始分型找笔
|
||||
/// :meth:`以武会友` — 根据结束分型找笔
|
||||
/// :meth:`根据缠K找笔` — 判断缠K是否在笔的文武序号之间
|
||||
/// :meth:`分析` — 笔划分核心递归算法
|
||||
/// :meth:`获取所有停顿位置` — 获取笔内所有可能的停顿位置(用于背驰检测)
|
||||
/// :meth:`是否背驰过` — 判断笔内是否发生过MACD趋向背驰
|
||||
#[pyclass(name = "笔", module = "chanlun._chanlun")]
|
||||
pub struct 笔Py;
|
||||
|
||||
#[pymethods]
|
||||
impl 笔Py {
|
||||
#[classmethod]
|
||||
/// 获取笔内有效缠K数量(考虑笔弱化等配置)
|
||||
fn 获取缠K数量(
|
||||
_cls: &Bound<'_, PyType>,
|
||||
缠K序列: Vec<Py<crate::kline_py::缠论K线Py>>,
|
||||
笔序列: Vec<Py<虚线Py>>,
|
||||
配置: &Bound<'_, 缠论配置Py>,
|
||||
py: Python<'_>,
|
||||
) -> PyResult<usize> {
|
||||
let ck_list: Vec<Arc<chanlun::kline::chan_kline::缠论K线>> = 缠K序列
|
||||
.iter()
|
||||
.map(|k| Arc::clone(&k.bind(py).borrow().inner))
|
||||
.collect();
|
||||
let bi_list: Vec<Arc<chanlun::structure::dash_line::虚线>> = 笔序列
|
||||
.iter()
|
||||
.map(|d| Arc::clone(&d.bind(py).borrow().inner))
|
||||
.collect();
|
||||
let config = 配置.borrow().to_rust_config(py)?;
|
||||
Ok(chanlun::algorithm::bi::笔::获取缠K数量(
|
||||
&ck_list, &bi_list, &config,
|
||||
))
|
||||
}
|
||||
|
||||
#[classmethod]
|
||||
/// 次高
|
||||
fn 次高(
|
||||
_cls: &Bound<'_, PyType>,
|
||||
缠K序列: Vec<Py<crate::kline_py::缠论K线Py>>,
|
||||
取舍: bool,
|
||||
py: Python<'_>,
|
||||
) -> Option<Py<crate::kline_py::缠论K线Py>> {
|
||||
let ck_list: Vec<Arc<chanlun::kline::chan_kline::缠论K线>> = 缠K序列
|
||||
.iter()
|
||||
.map(|k| Arc::clone(&k.bind(py).borrow().inner))
|
||||
.collect();
|
||||
chanlun::algorithm::bi::笔::次高(&ck_list, 取舍)
|
||||
.map(|inner| crate::kline_py::chan_kline_to_py(py, inner))
|
||||
}
|
||||
|
||||
#[classmethod]
|
||||
/// 次低
|
||||
fn 次低(
|
||||
_cls: &Bound<'_, PyType>,
|
||||
缠K序列: Vec<Py<crate::kline_py::缠论K线Py>>,
|
||||
取舍: bool,
|
||||
py: Python<'_>,
|
||||
) -> Option<Py<crate::kline_py::缠论K线Py>> {
|
||||
let ck_list: Vec<Arc<chanlun::kline::chan_kline::缠论K线>> = 缠K序列
|
||||
.iter()
|
||||
.map(|k| Arc::clone(&k.bind(py).borrow().inner))
|
||||
.collect();
|
||||
chanlun::algorithm::bi::笔::次低(&ck_list, 取舍)
|
||||
.map(|inner| crate::kline_py::chan_kline_to_py(py, inner))
|
||||
}
|
||||
|
||||
#[classmethod]
|
||||
/// 实际高点
|
||||
fn 实际高点(
|
||||
_cls: &Bound<'_, PyType>,
|
||||
缠K序列: Vec<Py<crate::kline_py::缠论K线Py>>,
|
||||
取舍: bool,
|
||||
py: Python<'_>,
|
||||
) -> Option<Py<crate::kline_py::缠论K线Py>> {
|
||||
let ck_list: Vec<Arc<chanlun::kline::chan_kline::缠论K线>> = 缠K序列
|
||||
.iter()
|
||||
.map(|k| Arc::clone(&k.bind(py).borrow().inner))
|
||||
.collect();
|
||||
chanlun::algorithm::bi::笔::实际高点(&ck_list, 取舍)
|
||||
.map(|inner| crate::kline_py::chan_kline_to_py(py, inner))
|
||||
}
|
||||
|
||||
#[classmethod]
|
||||
/// 实际低点
|
||||
fn 实际低点(
|
||||
_cls: &Bound<'_, PyType>,
|
||||
缠K序列: Vec<Py<crate::kline_py::缠论K线Py>>,
|
||||
取舍: bool,
|
||||
py: Python<'_>,
|
||||
) -> Option<Py<crate::kline_py::缠论K线Py>> {
|
||||
let ck_list: Vec<Arc<chanlun::kline::chan_kline::缠论K线>> = 缠K序列
|
||||
.iter()
|
||||
.map(|k| Arc::clone(&k.bind(py).borrow().inner))
|
||||
.collect();
|
||||
chanlun::algorithm::bi::笔::实际低点(&ck_list, 取舍)
|
||||
.map(|inner| crate::kline_py::chan_kline_to_py(py, inner))
|
||||
}
|
||||
|
||||
#[classmethod]
|
||||
/// 相对关系
|
||||
fn 相对关系(
|
||||
_cls: &Bound<'_, PyType>,
|
||||
筆: &Bound<'_, 虚线Py>,
|
||||
配置: &Bound<'_, 缠论配置Py>,
|
||||
py: Python<'_>,
|
||||
) -> PyResult<bool> {
|
||||
let config = 配置.borrow().to_rust_config(py)?;
|
||||
Ok(chanlun::algorithm::bi::笔::相对关系(
|
||||
&筆.borrow().inner,
|
||||
&config,
|
||||
))
|
||||
}
|
||||
|
||||
#[classmethod]
|
||||
/// 以文会友
|
||||
fn 以文会友(
|
||||
@@ -362,13 +262,13 @@ impl 笔Py {
|
||||
笔序列: Vec<Py<虚线Py>>,
|
||||
文: &Bound<'_, 分型Py>,
|
||||
py: Python<'_>,
|
||||
) -> Option<虚线Py> {
|
||||
) -> Option<Py<虚线Py>> {
|
||||
let bi_list: Vec<Arc<chanlun::structure::dash_line::虚线>> = 笔序列
|
||||
.iter()
|
||||
.map(|d| Arc::clone(&d.bind(py).borrow().inner))
|
||||
.collect();
|
||||
chanlun::algorithm::bi::笔::以文会友(&bi_list, &文.borrow().inner)
|
||||
.map(|inner| 虚线Py { inner })
|
||||
.map(|inner| dashed_to_py(py, inner))
|
||||
}
|
||||
|
||||
#[classmethod]
|
||||
@@ -378,13 +278,13 @@ impl 笔Py {
|
||||
笔序列: Vec<Py<虚线Py>>,
|
||||
武: &Bound<'_, 分型Py>,
|
||||
py: Python<'_>,
|
||||
) -> Option<虚线Py> {
|
||||
) -> Option<Py<虚线Py>> {
|
||||
let bi_list: Vec<Arc<chanlun::structure::dash_line::虚线>> = 笔序列
|
||||
.iter()
|
||||
.map(|d| Arc::clone(&d.bind(py).borrow().inner))
|
||||
.collect();
|
||||
chanlun::algorithm::bi::笔::以武会友(&bi_list, &武.borrow().inner)
|
||||
.map(|inner| 虚线Py { inner })
|
||||
.map(|inner| dashed_to_py(py, inner))
|
||||
}
|
||||
|
||||
#[classmethod]
|
||||
@@ -396,13 +296,13 @@ impl 笔Py {
|
||||
缠K: &Bound<'_, crate::kline_py::缠论K线Py>,
|
||||
偏移: i64,
|
||||
py: Python<'_>,
|
||||
) -> Option<虚线Py> {
|
||||
) -> Option<Py<虚线Py>> {
|
||||
let bi_list: Vec<Arc<chanlun::structure::dash_line::虚线>> = 笔序列
|
||||
.iter()
|
||||
.map(|d| Arc::clone(&d.bind(py).borrow().inner))
|
||||
.collect();
|
||||
chanlun::algorithm::bi::笔::根据缠K找笔(&bi_list, &缠K.borrow().inner, 偏移)
|
||||
.map(|inner| 虚线Py { inner })
|
||||
.map(|inner| dashed_to_py(py, inner))
|
||||
}
|
||||
|
||||
#[classmethod]
|
||||
@@ -445,76 +345,26 @@ impl 笔Py {
|
||||
&mut bi_seq,
|
||||
&ck_list,
|
||||
&bar_list,
|
||||
递归层次,
|
||||
&config,
|
||||
)),
|
||||
None => Ok(递归层次),
|
||||
}
|
||||
}
|
||||
|
||||
#[classmethod]
|
||||
fn 分析递归(
|
||||
_cls: &Bound<'_, PyType>,
|
||||
当前分型: &Bound<'_, 分型Py>,
|
||||
分型序列: Vec<Py<分型Py>>,
|
||||
笔序列: Vec<Py<虚线Py>>,
|
||||
缠K序列: Vec<Py<crate::kline_py::缠论K线Py>>,
|
||||
普K序列: Vec<Py<K线Py>>,
|
||||
递归层次: i64,
|
||||
配置: &Bound<'_, 缠论配置Py>,
|
||||
py: Python<'_>,
|
||||
) -> PyResult<i64> {
|
||||
let mut fr_seq: Vec<Arc<chanlun::structure::fractal_obj::分型>> = 分型序列
|
||||
.iter()
|
||||
.map(|f| Arc::clone(&f.bind(py).borrow().inner))
|
||||
.collect();
|
||||
let mut bi_seq: Vec<Arc<chanlun::structure::dash_line::虚线>> = 笔序列
|
||||
.iter()
|
||||
.map(|d| Arc::clone(&d.bind(py).borrow().inner))
|
||||
.collect();
|
||||
let ck_list: Vec<Arc<chanlun::kline::chan_kline::缠论K线>> = 缠K序列
|
||||
.iter()
|
||||
.map(|k| Arc::clone(&k.bind(py).borrow().inner))
|
||||
.collect();
|
||||
let bar_list: Vec<Arc<chanlun::kline::bar::K线>> = 普K序列
|
||||
.iter()
|
||||
.map(|k| k.bind(py).borrow().inner.clone())
|
||||
.collect();
|
||||
let config = 配置.borrow().to_rust_config(py)?;
|
||||
Ok(chanlun::algorithm::bi::笔::分析递归(
|
||||
Arc::clone(&当前分型.borrow().inner),
|
||||
&mut fr_seq,
|
||||
&mut bi_seq,
|
||||
&ck_list,
|
||||
&bar_list,
|
||||
递归层次,
|
||||
&config,
|
||||
))
|
||||
}
|
||||
|
||||
#[classmethod]
|
||||
/// 校验笔的有效性:高/低点是否为实际极值
|
||||
fn 自检(
|
||||
_cls: &Bound<'_, PyType>,
|
||||
筆: &Bound<'_, 虚线Py>,
|
||||
观察员: &Bound<'_, 观察者Py>,
|
||||
) -> bool {
|
||||
let obs = 观察员.borrow();
|
||||
let obs_ref = obs.obs();
|
||||
chanlun::algorithm::bi::笔::自检(&筆.borrow().inner, &*obs_ref)
|
||||
}
|
||||
|
||||
#[classmethod]
|
||||
/// 获取笔内所有可能的停顿位置(用于背驰检测)
|
||||
fn 获取所有停顿位置(
|
||||
_cls: &Bound<'_, PyType>,
|
||||
筆: &Bound<'_, 虚线Py>,
|
||||
观察员: &Bound<'_, 观察者Py>,
|
||||
) -> Vec<虚线Py> {
|
||||
py: Python<'_>,
|
||||
) -> Vec<Py<虚线Py>> {
|
||||
let obs = 观察员.borrow();
|
||||
let obs_ref = obs.obs();
|
||||
chanlun::algorithm::bi::笔::获取所有停顿位置(&筆.borrow().inner, &*obs_ref)
|
||||
chanlun::algorithm::bi::笔::获取所有停顿位置(&筆.borrow().inner, &obs_ref)
|
||||
.into_iter()
|
||||
.map(|d| 虚线Py { inner: Arc::new(d) })
|
||||
.map(|d| dashed_to_py(py, Arc::new(d)))
|
||||
.collect()
|
||||
}
|
||||
|
||||
@@ -528,7 +378,7 @@ impl 笔Py {
|
||||
) -> Vec<Py<缠论K线Py>> {
|
||||
let obs = 观察员.borrow();
|
||||
let obs_ref = obs.obs();
|
||||
chanlun::algorithm::bi::笔::是否背驰过(&当前筆.borrow().inner, &*obs_ref)
|
||||
chanlun::algorithm::bi::笔::是否背驰过(&当前筆.borrow().inner, &obs_ref)
|
||||
.into_iter()
|
||||
.map(|ck| chan_kline_to_py(py, ck))
|
||||
.collect()
|
||||
@@ -548,108 +398,13 @@ impl 笔Py {
|
||||
///
|
||||
/// 辅助方法:
|
||||
/// 扩展分析(基础序列, 扩展结果容器, 配置) — 从基础序列生成扩展分析
|
||||
/// 特征序列分析(特征序列, 中枢序列, 配置, 标识?) — 分析特征序列产生中枢
|
||||
/// 分析前检查(分型序列, 配置) -> bool
|
||||
/// 四象(虚线段) -> str — 返回"少阳"/"少阴"/"老阳"/"老阴"
|
||||
/// 特征序列状态(虚线段) -> str
|
||||
/// 虚线段特征序列成立(虚线段, 虚线序列, 配置) -> bool
|
||||
/// 移除包含后的特征序列(特征序列, 配置) -> list[线段特征]
|
||||
/// 检查特征序列趋势(特征序列) -> bool
|
||||
#[pyclass(name = "线段", module = "chanlun._chanlun")]
|
||||
pub struct 线段Py;
|
||||
|
||||
#[pymethods]
|
||||
impl 线段Py {
|
||||
#[classmethod]
|
||||
/// 向线段中添加一笔
|
||||
fn 添加虚线(
|
||||
_cls: &Bound<'_, PyType>,
|
||||
段: &Bound<'_, 虚线Py>,
|
||||
筆: &Bound<'_, 虚线Py>,
|
||||
) -> PyResult<()> {
|
||||
let bi_rc = Arc::clone(&筆.borrow().inner);
|
||||
let mut ref_mut = 段.borrow_mut();
|
||||
chanlun::algorithm::segment::线段::添加虚线(&mut ref_mut.inner, bi_rc);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[classmethod]
|
||||
/// 更新线段的终点分型(武)
|
||||
fn 武斗(
|
||||
_cls: &Bound<'_, PyType>,
|
||||
段: &Bound<'_, 虚线Py>,
|
||||
武: &Bound<'_, 分型Py>,
|
||||
行号: u32,
|
||||
) -> PyResult<()> {
|
||||
let mut ref_mut = 段.borrow_mut();
|
||||
chanlun::algorithm::segment::线段::武斗(
|
||||
&mut ref_mut.inner,
|
||||
&Arc::clone(&武.borrow().inner),
|
||||
行号,
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[classmethod]
|
||||
/// 武终
|
||||
fn 武终(_cls: &Bound<'_, PyType>, 段: &Bound<'_, 虚线Py>, 行号: u32) -> PyResult<()> {
|
||||
let mut ref_mut = 段.borrow_mut();
|
||||
chanlun::algorithm::segment::线段::武终(&mut ref_mut.inner, 行号);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[classmethod]
|
||||
/// 验证序列
|
||||
fn 验证序列(
|
||||
_cls: &Bound<'_, PyType>,
|
||||
段: &Bound<'_, 虚线Py>,
|
||||
序列: Vec<Py<虚线Py>>,
|
||||
py: Python<'_>,
|
||||
) -> PyResult<()> {
|
||||
let mut ref_mut = 段.borrow_mut();
|
||||
let rc_list: Vec<Arc<chanlun::structure::dash_line::虚线>> = 序列
|
||||
.iter()
|
||||
.map(|d| Arc::clone(&d.bind(py).borrow().inner))
|
||||
.collect();
|
||||
chanlun::algorithm::segment::线段::验证序列(&mut ref_mut.inner, &rc_list);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[classmethod]
|
||||
/// 序列重置
|
||||
fn 序列重置(
|
||||
_cls: &Bound<'_, PyType>,
|
||||
段: &Bound<'_, 虚线Py>,
|
||||
序列: Vec<Py<虚线Py>>,
|
||||
py: Python<'_>,
|
||||
) -> PyResult<()> {
|
||||
let mut ref_mut = 段.borrow_mut();
|
||||
let rc_list: Vec<Arc<chanlun::structure::dash_line::虚线>> = 序列
|
||||
.iter()
|
||||
.map(|d| Arc::clone(&d.bind(py).borrow().inner))
|
||||
.collect();
|
||||
chanlun::algorithm::segment::线段::序列重置(&mut ref_mut.inner, &rc_list);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[classmethod]
|
||||
/// 连续三笔且重叠
|
||||
fn 基础判断(
|
||||
_cls: &Bound<'_, PyType>,
|
||||
左: &Bound<'_, 虚线Py>,
|
||||
中: &Bound<'_, 虚线Py>,
|
||||
右: &Bound<'_, 虚线Py>,
|
||||
关系序列: Vec<相对方向Py>,
|
||||
) -> bool {
|
||||
let rel_list: Vec<chanlun::types::相对方向> = 关系序列.iter().map(|d| d.inner).collect();
|
||||
chanlun::algorithm::segment::线段::基础判断(
|
||||
&左.borrow().inner,
|
||||
&中.borrow().inner,
|
||||
&右.borrow().inner,
|
||||
&rel_list,
|
||||
)
|
||||
}
|
||||
|
||||
#[classmethod]
|
||||
/// 判断线段的四象属性
|
||||
fn 四象(_cls: &Bound<'_, PyType>, 段: &Bound<'_, 虚线Py>) -> String {
|
||||
@@ -680,62 +435,20 @@ impl 线段Py {
|
||||
chanlun::algorithm::segment::线段::特征序列状态(&段.borrow().inner)
|
||||
}
|
||||
|
||||
#[classmethod]
|
||||
/// 设置特征序列
|
||||
fn 设置特征序列(
|
||||
_cls: &Bound<'_, PyType>,
|
||||
段: &Bound<'_, 虚线Py>,
|
||||
序列: &Bound<'_, PyAny>,
|
||||
行号: u32,
|
||||
) -> PyResult<()> {
|
||||
let seq: Vec<Option<Arc<chanlun::structure::segment_feat::线段特征>>> = if 序列.is_none()
|
||||
{
|
||||
vec![]
|
||||
} else if let Ok(list) = 序列.downcast::<pyo3::types::PyList>() {
|
||||
let mut result = Vec::with_capacity(list.len());
|
||||
for item in list.iter() {
|
||||
if item.is_none() {
|
||||
result.push(None);
|
||||
} else {
|
||||
let feat: PyRef<'_, 线段特征Py> = item.extract()?;
|
||||
result.push(Some(Arc::clone(&feat.inner)));
|
||||
}
|
||||
}
|
||||
result
|
||||
} else {
|
||||
return Err(pyo3::exceptions::PyTypeError::new_err(
|
||||
"序列 必须是 list 或 None",
|
||||
));
|
||||
};
|
||||
let mut ref_mut = 段.borrow_mut();
|
||||
chanlun::algorithm::segment::线段::设置特征序列(&mut ref_mut.inner, seq, 行号);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[classmethod]
|
||||
/// 刷新特征序列
|
||||
fn 刷新特征序列(
|
||||
_cls: &Bound<'_, PyType>,
|
||||
段: &Bound<'_, 虚线Py>,
|
||||
配置: &Bound<'_, 缠论配置Py>,
|
||||
py: Python<'_>,
|
||||
) -> PyResult<()> {
|
||||
let mut ref_mut = 段.borrow_mut();
|
||||
let config = 配置.borrow().to_rust_config(py)?;
|
||||
chanlun::algorithm::segment::线段::刷新特征序列(&mut ref_mut.inner, &config);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[classmethod]
|
||||
/// 查找贯穿伤
|
||||
fn 查找贯穿伤(_cls: &Bound<'_, PyType>, 段: &Bound<'_, 虚线Py>) -> Option<虚线Py> {
|
||||
fn 查找贯穿伤(
|
||||
_cls: &Bound<'_, PyType>, 段: &Bound<'_, 虚线Py>
|
||||
) -> Option<Py<虚线Py>> {
|
||||
let py = _cls.py();
|
||||
chanlun::algorithm::segment::线段::查找贯穿伤(&段.borrow().inner)
|
||||
.map(|inner| 虚线Py { inner })
|
||||
.map(|inner| dashed_to_py(py, inner))
|
||||
}
|
||||
|
||||
#[classmethod]
|
||||
#[pyo3(signature = (段, 所属中枢 = None))]
|
||||
/// 将线段基础序列分割为前/后/第三买卖/贯穿伤四部分
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn 分割序列(
|
||||
_cls: &Bound<'_, PyType>,
|
||||
段: &Bound<'_, 虚线Py>,
|
||||
@@ -749,42 +462,21 @@ impl 线段Py {
|
||||
)> {
|
||||
let borrowed = 段.borrow();
|
||||
let (a, b, c, d) = if let Some(hub_bound) = 所属中枢 {
|
||||
if let Ok(mut hub_ref) = hub_bound.extract::<PyRefMut<'_, 中枢Py>>() {
|
||||
let inner_mut = Arc::make_mut(&mut hub_ref.inner);
|
||||
chanlun::algorithm::segment::线段::分割序列(&borrowed.inner, Some(inner_mut))
|
||||
if let Ok(hub_ref) = hub_bound.extract::<PyRef<'_, 中枢Py>>() {
|
||||
chanlun::algorithm::segment::线段::分割序列(
|
||||
&borrowed.inner,
|
||||
Some(&hub_ref.inner),
|
||||
)
|
||||
} else {
|
||||
chanlun::algorithm::segment::线段::分割序列(&borrowed.inner, None)
|
||||
}
|
||||
} else {
|
||||
chanlun::algorithm::segment::线段::分割序列(&borrowed.inner, None)
|
||||
};
|
||||
let wrap = |v: Vec<Arc<chanlun::structure::dash_line::虚线>>| -> PyResult<Vec<Py<虚线Py>>> {
|
||||
let mut result = Vec::new();
|
||||
for x in v {
|
||||
result.push(Py::new(py, 虚线Py { inner: x })?);
|
||||
}
|
||||
Ok(result)
|
||||
let wrap = |v: Vec<Arc<chanlun::structure::dash_line::虚线>>| -> Vec<Py<虚线Py>> {
|
||||
v.into_iter().map(|x| dashed_to_py(py, x)).collect()
|
||||
};
|
||||
Ok((
|
||||
wrap(a)?,
|
||||
wrap(b)?,
|
||||
wrap(c)?,
|
||||
d.map(|x| Py::new(py, 虚线Py { inner: x })).transpose()?,
|
||||
))
|
||||
}
|
||||
|
||||
#[classmethod]
|
||||
/// 刷新线段的特征序列和内部中枢序列
|
||||
fn 刷新(
|
||||
_cls: &Bound<'_, PyType>,
|
||||
段: &Bound<'_, 虚线Py>,
|
||||
配置: &Bound<'_, 缠论配置Py>,
|
||||
py: Python<'_>,
|
||||
) -> PyResult<()> {
|
||||
let mut ref_mut = 段.borrow_mut();
|
||||
let config = 配置.borrow().to_rust_config(py)?;
|
||||
chanlun::algorithm::segment::线段::刷新(&mut ref_mut.inner, &config);
|
||||
Ok(())
|
||||
Ok((wrap(a), wrap(b), wrap(c), d.map(|x| dashed_to_py(py, x))))
|
||||
}
|
||||
|
||||
#[classmethod]
|
||||
@@ -795,16 +487,14 @@ impl 线段Py {
|
||||
配置: &Bound<'_, 缠论配置Py>,
|
||||
py: Python<'_>,
|
||||
) -> PyResult<(Py<PyAny>, Py<PyAny>, Py<PyAny>)> {
|
||||
let mut ref_mut = 段.borrow_mut();
|
||||
let ref_mut = 段.borrow_mut();
|
||||
let config = 配置.borrow().to_rust_config(py)?;
|
||||
let (a, b, c) = chanlun::algorithm::segment::线段::获取内部中枢序列(
|
||||
&mut ref_mut.inner,
|
||||
&config,
|
||||
);
|
||||
let (a, b, c) =
|
||||
chanlun::algorithm::segment::线段::获取内部中枢序列(&ref_mut.inner, &config);
|
||||
let pk_list = |v: Vec<Arc<chanlun::algorithm::hub::中枢>>| -> PyResult<Py<PyAny>> {
|
||||
let list = pyo3::types::PyList::empty(py);
|
||||
for h in v {
|
||||
list.append(Py::new(py, 中枢Py { inner: h })?)?;
|
||||
list.append(hub_to_py(py, h))?;
|
||||
}
|
||||
Ok(list.into())
|
||||
};
|
||||
@@ -812,133 +502,6 @@ impl 线段Py {
|
||||
}
|
||||
|
||||
#[classmethod]
|
||||
/// 内部方法:向线段序列添加新线段
|
||||
fn _添加线段(
|
||||
_cls: &Bound<'_, PyType>,
|
||||
线段序列: &Bound<'_, PyAny>,
|
||||
待添加线段: &Bound<'_, 虚线Py>,
|
||||
配置: &Bound<'_, 缠论配置Py>,
|
||||
行号: String,
|
||||
py: Python<'_>,
|
||||
) -> PyResult<()> {
|
||||
let config = 配置.borrow().to_rust_config(py)?;
|
||||
let mut seg_seq: Vec<Arc<chanlun::structure::dash_line::虚线>> = vec![];
|
||||
chanlun::algorithm::segment::线段::_添加线段(
|
||||
&mut seg_seq,
|
||||
Arc::clone(&待添加线段.borrow().inner),
|
||||
&config,
|
||||
行号,
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[classmethod]
|
||||
/// 内部方法:从线段序列弹出最后一个线段
|
||||
fn _弹出线段(
|
||||
_cls: &Bound<'_, PyType>,
|
||||
线段序列: &Bound<'_, PyAny>,
|
||||
待弹出线段: &Bound<'_, 虚线Py>,
|
||||
配置: &Bound<'_, 缠论配置Py>,
|
||||
行号: String,
|
||||
py: Python<'_>,
|
||||
) -> PyResult<Option<虚线Py>> {
|
||||
let config = 配置.borrow().to_rust_config(py)?;
|
||||
let mut seg_seq: Vec<Arc<chanlun::structure::dash_line::虚线>> = vec![];
|
||||
let result = chanlun::algorithm::segment::线段::_弹出线段(
|
||||
&mut seg_seq,
|
||||
&Arc::clone(&待弹出线段.borrow().inner),
|
||||
&config,
|
||||
行号,
|
||||
);
|
||||
Ok(result.map(|inner| 虚线Py { inner }))
|
||||
}
|
||||
|
||||
#[classmethod]
|
||||
/// 内部方法:处理缺口突破修正
|
||||
fn _缺口突破(
|
||||
_cls: &Bound<'_, PyType>,
|
||||
线段序列: Vec<Py<虚线Py>>,
|
||||
配置: &Bound<'_, 缠论配置Py>,
|
||||
层级: i64,
|
||||
py: Python<'_>,
|
||||
) -> PyResult<bool> {
|
||||
let mut seg_seq: Vec<Arc<chanlun::structure::dash_line::虚线>> = 线段序列
|
||||
.iter()
|
||||
.map(|d| Arc::clone(&d.bind(py).borrow().inner))
|
||||
.collect();
|
||||
let config = 配置.borrow().to_rust_config(py)?;
|
||||
Ok(chanlun::algorithm::segment::线段::_缺口突破(
|
||||
&mut seg_seq,
|
||||
&config,
|
||||
层级,
|
||||
))
|
||||
}
|
||||
|
||||
#[classmethod]
|
||||
/// 内部方法:处理非缺口下穿刺修正
|
||||
fn _非缺口下穿刺(
|
||||
_cls: &Bound<'_, PyType>,
|
||||
线段序列: Vec<Py<虚线Py>>,
|
||||
配置: &Bound<'_, 缠论配置Py>,
|
||||
层级: i64,
|
||||
py: Python<'_>,
|
||||
) -> PyResult<bool> {
|
||||
let mut seg_seq: Vec<Arc<chanlun::structure::dash_line::虚线>> = 线段序列
|
||||
.iter()
|
||||
.map(|d| Arc::clone(&d.bind(py).borrow().inner))
|
||||
.collect();
|
||||
let config = 配置.borrow().to_rust_config(py)?;
|
||||
Ok(chanlun::algorithm::segment::线段::_非缺口下穿刺(
|
||||
&mut seg_seq,
|
||||
&config,
|
||||
层级,
|
||||
))
|
||||
}
|
||||
|
||||
#[classmethod]
|
||||
/// 内部方法:处理缺口后紧急修正
|
||||
fn _缺口后紧急修正(
|
||||
_cls: &Bound<'_, PyType>,
|
||||
线段序列: Vec<Py<虚线Py>>,
|
||||
配置: &Bound<'_, 缠论配置Py>,
|
||||
层级: i64,
|
||||
py: Python<'_>,
|
||||
) -> PyResult<bool> {
|
||||
let mut seg_seq: Vec<Arc<chanlun::structure::dash_line::虚线>> = 线段序列
|
||||
.iter()
|
||||
.map(|d| Arc::clone(&d.bind(py).borrow().inner))
|
||||
.collect();
|
||||
let config = 配置.borrow().to_rust_config(py)?;
|
||||
Ok(chanlun::algorithm::segment::线段::_缺口后紧急修正(
|
||||
&mut seg_seq,
|
||||
&config,
|
||||
层级,
|
||||
))
|
||||
}
|
||||
|
||||
#[classmethod]
|
||||
/// 内部方法:处理线段修正
|
||||
fn _修正(
|
||||
_cls: &Bound<'_, PyType>,
|
||||
线段序列: Vec<Py<虚线Py>>,
|
||||
配置: &Bound<'_, 缠论配置Py>,
|
||||
层级: i64,
|
||||
py: Python<'_>,
|
||||
) -> PyResult<bool> {
|
||||
let mut seg_seq: Vec<Arc<chanlun::structure::dash_line::虚线>> = 线段序列
|
||||
.iter()
|
||||
.map(|d| Arc::clone(&d.bind(py).borrow().inner))
|
||||
.collect();
|
||||
let config = 配置.borrow().to_rust_config(py)?;
|
||||
Ok(chanlun::algorithm::segment::线段::_修正(
|
||||
&mut seg_seq,
|
||||
&config,
|
||||
层级,
|
||||
))
|
||||
}
|
||||
|
||||
#[classmethod]
|
||||
#[pyo3(signature = (笔序列, 线段序列, 配置, 层级 = 0, 关系序列 = None))]
|
||||
/// 线段划分核心递归算法
|
||||
fn 分析(
|
||||
_cls: &Bound<'_, PyType>,
|
||||
@@ -975,48 +538,6 @@ impl 线段Py {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[classmethod]
|
||||
/// 内部方法:向扩展线段序列添加新线段
|
||||
fn _添加扩展线段(
|
||||
_cls: &Bound<'_, PyType>,
|
||||
线段序列: Vec<Py<虚线Py>>,
|
||||
待添加线段: &Bound<'_, 虚线Py>,
|
||||
行号: u32,
|
||||
py: Python<'_>,
|
||||
) -> PyResult<()> {
|
||||
let mut seg_seq: Vec<Arc<chanlun::structure::dash_line::虚线>> = 线段序列
|
||||
.iter()
|
||||
.map(|d| Arc::clone(&d.bind(py).borrow().inner))
|
||||
.collect();
|
||||
chanlun::algorithm::segment::线段::_添加扩展线段(
|
||||
&mut seg_seq,
|
||||
Arc::clone(&待添加线段.borrow().inner),
|
||||
行号,
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[classmethod]
|
||||
/// 内部方法:从扩展线段序列弹出最后一个线段
|
||||
fn _弹出扩展线段(
|
||||
_cls: &Bound<'_, PyType>,
|
||||
线段序列: Vec<Py<虚线Py>>,
|
||||
待弹出线段: &Bound<'_, 虚线Py>,
|
||||
行号: u32,
|
||||
py: Python<'_>,
|
||||
) -> PyResult<Option<虚线Py>> {
|
||||
let mut seg_seq: Vec<Arc<chanlun::structure::dash_line::虚线>> = 线段序列
|
||||
.iter()
|
||||
.map(|d| Arc::clone(&d.bind(py).borrow().inner))
|
||||
.collect();
|
||||
let result = chanlun::algorithm::segment::线段::_弹出扩展线段(
|
||||
&mut seg_seq,
|
||||
&Arc::clone(&待弹出线段.borrow().inner),
|
||||
行号,
|
||||
);
|
||||
Ok(result.map(|inner| 虚线Py { inner }))
|
||||
}
|
||||
|
||||
#[classmethod]
|
||||
/// 即同级别分析
|
||||
fn 扩展分析(
|
||||
@@ -1050,7 +571,7 @@ impl 线段Py {
|
||||
let obs_ref = obs.obs();
|
||||
chanlun::algorithm::segment::线段::判断线段内部是否背驰(
|
||||
&当前段.borrow().inner,
|
||||
&*obs_ref,
|
||||
&obs_ref,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1060,12 +581,13 @@ impl 线段Py {
|
||||
_cls: &Bound<'_, PyType>,
|
||||
段: &Bound<'_, 虚线Py>,
|
||||
观察员: &Bound<'_, 观察者Py>,
|
||||
) -> Vec<虚线Py> {
|
||||
py: Python<'_>,
|
||||
) -> Vec<Py<虚线Py>> {
|
||||
let obs = 观察员.borrow();
|
||||
let obs_ref = obs.obs();
|
||||
chanlun::algorithm::segment::线段::获取所有停顿位置(&段.borrow().inner, &*obs_ref)
|
||||
chanlun::algorithm::segment::线段::获取所有停顿位置(&段.borrow().inner, &obs_ref)
|
||||
.into_iter()
|
||||
.map(|d| 虚线Py { inner: Arc::new(d) })
|
||||
.map(|d| dashed_to_py(py, Arc::new(d)))
|
||||
.collect()
|
||||
}
|
||||
|
||||
@@ -1079,7 +601,7 @@ impl 线段Py {
|
||||
) -> Vec<Py<缠论K线Py>> {
|
||||
let obs = 观察员.borrow();
|
||||
let obs_ref = obs.obs();
|
||||
chanlun::algorithm::segment::线段::是否背驰过(&当前段.borrow().inner, &*obs_ref)
|
||||
chanlun::algorithm::segment::线段::是否背驰过(&当前段.borrow().inner, &obs_ref)
|
||||
.into_iter()
|
||||
.map(|ck| chan_kline_to_py(py, ck))
|
||||
.collect()
|
||||
@@ -1111,7 +633,6 @@ impl 线段Py {
|
||||
/// 添加虚线(虚线) — 向中枢追加虚线(延伸)
|
||||
/// 获取序列() -> list[虚线] — 基础序列 + 第三买卖线
|
||||
/// 获取扩展中枢(扩展中枢列表, 配置) — 基础序列 >= 9 时生成扩展中枢
|
||||
/// 校验合法性(虚线序列) -> bool — 根据当前虚线序列校验中枢合法性
|
||||
/// 设置第三买卖线(虚线|None) — 设置第三类买卖确认线
|
||||
/// 完整性(虚实="合") -> bool — 中枢是否完整(已有第三买卖线确认)
|
||||
/// 当前状态() -> str — "中枢之上"/"中枢之下"/"中枢之中"
|
||||
@@ -1122,8 +643,6 @@ impl 线段Py {
|
||||
/// 创建(左虚线, 中虚线, 右虚线, 级别, 标识?) -> 中枢 — 创建新中枢
|
||||
/// 从序列中获取中枢(虚线序列, 允许重叠?, 标识前缀?, 高级中枢容器?...) -> list[中枢]
|
||||
/// — 扫描虚线序列提取所有中枢
|
||||
/// 向中枢序列尾部添加(中枢序列, 新中枢) — 维护中枢序列顺序
|
||||
/// 从中枢序列尾部弹出(中枢序列, 配置) — 弹出最后一个中枢并更新相邻中枢
|
||||
/// 分析(虚线序列, 中枢序列容器, 允许重叠?, 标识前缀?, ...) — 中枢分析主流程
|
||||
#[pyclass(name = "中枢", module = "chanlun._chanlun", from_py_object)]
|
||||
#[derive(Clone)]
|
||||
@@ -1192,11 +711,6 @@ impl 中枢Py {
|
||||
.map(|d| dashed_to_py(py, Arc::clone(d)))
|
||||
}
|
||||
|
||||
/// 向中枢添加新虚线(延伸),重置第三买卖线
|
||||
fn 添加虚线(&self, 实线: &Bound<'_, 虚线Py>) {
|
||||
self.inner.添加虚线(Arc::clone(&实线.borrow().inner));
|
||||
}
|
||||
|
||||
#[getter]
|
||||
/// :return: 图表标题
|
||||
fn 图表标题(&self) -> String {
|
||||
@@ -1211,10 +725,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]
|
||||
@@ -1256,14 +768,14 @@ impl 中枢Py {
|
||||
/// 设置第三类买卖点关联虚线
|
||||
fn 设置第三买卖线(&mut self, 线: &Bound<'_, 虚线Py>) {
|
||||
let inner = Arc::make_mut(&mut self.inner);
|
||||
inner.设置第三买卖线(Arc::clone(&线.borrow().inner));
|
||||
inner.设置第三买卖线(Some(Arc::clone(&线.borrow().inner)));
|
||||
}
|
||||
|
||||
/// 获取中枢的完整虚线序列(基础序列+第三买卖线)
|
||||
fn 获取序列(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
|
||||
let list = pyo3::types::PyList::empty(py);
|
||||
for d in self.inner.获取序列() {
|
||||
list.append(虚线Py { inner: d })?;
|
||||
list.append(dashed_to_py(py, d))?;
|
||||
}
|
||||
Ok(list.into())
|
||||
}
|
||||
@@ -1273,23 +785,6 @@ impl 中枢Py {
|
||||
self.inner.获取数据文本()
|
||||
}
|
||||
|
||||
#[pyo3(signature = (序列, 中枢序列 = None))]
|
||||
/// 校验当前中枢在给定序列中是否仍然合法
|
||||
fn 校验合法性(
|
||||
&mut self,
|
||||
序列: Vec<Py<虚线Py>>,
|
||||
中枢序列: Option<Vec<Py<虚线Py>>>,
|
||||
py: Python<'_>,
|
||||
) -> bool {
|
||||
let _ = 中枢序列; // Python 版声明了此参数但未使用
|
||||
let rc_list: Vec<Arc<chanlun::structure::dash_line::虚线>> = 序列
|
||||
.iter()
|
||||
.map(|d| Arc::clone(&d.bind(py).borrow().inner))
|
||||
.collect();
|
||||
let inner = Arc::make_mut(&mut self.inner);
|
||||
inner.校验合法性(&rc_list)
|
||||
}
|
||||
|
||||
#[pyo3(signature = (虚实 = "合"))]
|
||||
/// 判断中枢是否完整(是否有第三买卖点或内部中枢离开)
|
||||
fn 完整性(&self, 虚实: &str) -> bool {
|
||||
@@ -1325,7 +820,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.高高())?;
|
||||
@@ -1380,67 +875,15 @@ impl 中枢Py {
|
||||
右: &Bound<'_, 虚线Py>,
|
||||
级别: i64,
|
||||
标识: &str,
|
||||
) -> Self {
|
||||
Self {
|
||||
inner: Arc::new(chanlun::algorithm::hub::中枢::创建(
|
||||
Arc::clone(&左.borrow().inner),
|
||||
Arc::clone(&中.borrow().inner),
|
||||
Arc::clone(&右.borrow().inner),
|
||||
级别,
|
||||
标识,
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
#[classmethod]
|
||||
/// 从虚线序列中按起始方向查找第一个中枢
|
||||
fn 从序列中获取中枢(
|
||||
_cls: &Bound<'_, PyType>,
|
||||
虚线序列: Vec<Py<虚线Py>>,
|
||||
起始方向: &Bound<'_, 相对方向Py>,
|
||||
标识: &str,
|
||||
py: Python<'_>,
|
||||
) -> Option<Self> {
|
||||
let rc_list: Vec<Arc<chanlun::structure::dash_line::虚线>> = 虚线序列
|
||||
.iter()
|
||||
.map(|d| Arc::clone(&d.bind(py).borrow().inner))
|
||||
.collect();
|
||||
chanlun::algorithm::hub::中枢::从序列中获取中枢(
|
||||
&rc_list,
|
||||
起始方向.borrow().inner,
|
||||
) -> Py<中枢Py> {
|
||||
let inner = Arc::new(chanlun::algorithm::hub::中枢::创建(
|
||||
Arc::clone(&左.borrow().inner),
|
||||
Arc::clone(&中.borrow().inner),
|
||||
Arc::clone(&右.borrow().inner),
|
||||
级别,
|
||||
标识,
|
||||
)
|
||||
.map(|inner| Self { inner })
|
||||
}
|
||||
|
||||
#[classmethod]
|
||||
/// :param 中枢序列: 中枢列表
|
||||
fn 向中枢序列尾部添加(
|
||||
_cls: &Bound<'_, PyType>,
|
||||
中枢序列: &Bound<'_, PyAny>,
|
||||
待添加中枢: &Bound<'_, Self>,
|
||||
) -> PyResult<()> {
|
||||
let inner = Arc::clone(&待添加中枢.borrow().inner);
|
||||
let wrapper = Py::new(中枢序列.py(), Self { inner })?;
|
||||
中枢序列.call_method1("append", (wrapper,))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[classmethod]
|
||||
/// :param 中枢序列: 中枢列表
|
||||
fn 从中枢序列尾部弹出(
|
||||
_cls: &Bound<'_, PyType>,
|
||||
中枢序列: &Bound<'_, PyAny>,
|
||||
待弹出中枢: &Bound<'_, Self>,
|
||||
) -> PyResult<Option<Self>> {
|
||||
let result = 中枢序列.call_method1("pop", ())?;
|
||||
if result.is_none() {
|
||||
return Ok(None);
|
||||
}
|
||||
let bound: Bound<'_, Self> = result.extract()?;
|
||||
Ok(Some(Self {
|
||||
inner: Arc::clone(&bound.borrow().inner),
|
||||
}))
|
||||
));
|
||||
hub_to_py(_cls.py(), inner)
|
||||
}
|
||||
|
||||
#[classmethod]
|
||||
@@ -1466,6 +909,27 @@ impl 中枢Py {
|
||||
chanlun::algorithm::hub::中枢::分析(&rc_list, &mut hub_seq, 跳过首部, 标识, 层级);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[classmethod]
|
||||
/// 从虚线序列中按起始方向查找第一个中枢
|
||||
fn 从序列中获取中枢(
|
||||
_cls: &Bound<'_, PyType>,
|
||||
虚线序列: Vec<Py<虚线Py>>,
|
||||
起始方向: &Bound<'_, 相对方向Py>,
|
||||
标识: &str,
|
||||
py: Python<'_>,
|
||||
) -> Option<Py<中枢Py>> {
|
||||
let rc_list: Vec<Arc<chanlun::structure::dash_line::虚线>> = 虚线序列
|
||||
.iter()
|
||||
.map(|d| Arc::clone(&d.bind(py).borrow().inner))
|
||||
.collect();
|
||||
chanlun::algorithm::hub::中枢::_从序列中获取中枢(
|
||||
&rc_list,
|
||||
起始方向.borrow().inner,
|
||||
标识,
|
||||
)
|
||||
.map(|inner| hub_to_py(py, inner))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn register(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
||||
|
||||
+445
-115
@@ -28,14 +28,12 @@ use std::sync::RwLock;
|
||||
|
||||
use crate::algorithm_py::hub_to_py;
|
||||
use crate::kline_py::bar_to_py;
|
||||
use crate::structure_py::{dashed_to_py, fractal_to_py};
|
||||
use crate::structure_py::{dashed_to_py, fractal_to_py, 分型Py};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::algorithm_py::中枢Py;
|
||||
use crate::config_py::缠论配置Py;
|
||||
use crate::kline_py::{缠论K线Py, K线Py};
|
||||
use crate::structure_py::{分型Py, 虚线Py};
|
||||
use crate::kline_py::{K线Py, 缠论K线Py};
|
||||
use crate::types_py::买卖点类型Py;
|
||||
|
||||
// ========== 基础买卖点 ==========
|
||||
@@ -48,7 +46,12 @@ use crate::types_py::买卖点类型Py;
|
||||
/// 有效性: bool — 买卖点是否仍有效
|
||||
/// 与MACD柱子匹配: bool|None — 是否与MACD柱状图方向匹配
|
||||
/// 与MACD柱子分型匹配: bool|None — 是否与MACD柱分型匹配
|
||||
#[pyclass(name = "基础买卖点", module = "chanlun._chanlun", from_py_object)]
|
||||
#[pyclass(
|
||||
name = "基础买卖点",
|
||||
module = "chanlun._chanlun",
|
||||
subclass,
|
||||
from_py_object
|
||||
)]
|
||||
#[derive(Clone)]
|
||||
pub struct 基础买卖点Py {
|
||||
pub(crate) inner: chanlun::business::bsp::基础买卖点,
|
||||
@@ -94,10 +97,8 @@ impl 基础买卖点Py {
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn 买卖点分型(&self) -> 分型Py {
|
||||
分型Py {
|
||||
inner: Arc::clone(&self.inner.买卖点分型),
|
||||
}
|
||||
fn 买卖点分型(&self, py: Python<'_>) -> Py<分型Py> {
|
||||
fractal_to_py(py, Arc::clone(&self.inner.买卖点分型))
|
||||
}
|
||||
|
||||
#[getter]
|
||||
@@ -199,19 +200,20 @@ impl 基础买卖点Py {
|
||||
|
||||
// ========== 买卖点 ==========
|
||||
|
||||
/// 买卖点 — 静态方法容器,提供各类买卖点的构造算法(不存储数据)。
|
||||
/// 买卖点 — 继承 基础买卖点,提供一二三类及扩展类型(T1/T1P/T2/T2S/T3A/T3B)的工厂类方法。
|
||||
///
|
||||
/// 类方法(均返回对应的买卖点对象):
|
||||
/// 一卖点(...) / 一买点(...) / 二卖点(...) / 二买点(...) / 三卖点(...) / 三买点(...)
|
||||
/// 生成买卖点(特征, 序号, 级别, 分型, 当前缠K, 备注?) -> 买卖点
|
||||
/// — 根据特征字符串自动路由到对应的一/二/三类买卖点构造函数
|
||||
#[pyclass(name = "买卖点", module = "chanlun._chanlun")]
|
||||
/// 类方法(均返回 买卖点 实例):
|
||||
/// :meth:`一卖点` / :meth:`一买点` / :meth:`二卖点` / :meth:`二买点` / :meth:`三卖点` / :meth:`三买点`
|
||||
/// :meth:`T1卖点` / :meth:`T1买点` / :meth:`T1P卖点` / :meth:`T1P买点`
|
||||
/// :meth:`T2卖点` / :meth:`T2买点` / :meth:`T2S卖点` / :meth:`T2S买点`
|
||||
/// :meth:`T3A卖点` / :meth:`T3A买点` / :meth:`T3B卖点` / :meth:`T3B买点`
|
||||
/// :meth:`生成买卖点` — 根据特征和序号路由到对应构造函数
|
||||
#[pyclass(name = "买卖点", module = "chanlun._chanlun", extends=基础买卖点Py)]
|
||||
pub struct 买卖点Py;
|
||||
|
||||
#[pymethods]
|
||||
impl 买卖点Py {
|
||||
#[classmethod]
|
||||
/// :param 买卖点分型: 买卖点对应的分型
|
||||
fn 一卖点(
|
||||
_cls: &Bound<'_, PyType>,
|
||||
买卖点分型: &Bound<'_, 分型Py>,
|
||||
@@ -219,8 +221,9 @@ impl 买卖点Py {
|
||||
标识: &str,
|
||||
备注: String,
|
||||
中枢破位值: f64,
|
||||
) -> 基础买卖点Py {
|
||||
基础买卖点Py {
|
||||
py: Python<'_>,
|
||||
) -> PyResult<Py<Self>> {
|
||||
let base = 基础买卖点Py {
|
||||
inner: chanlun::business::bsp::买卖点::一卖点(
|
||||
Arc::clone(&买卖点分型.borrow().inner),
|
||||
当前K线.borrow().inner.clone(),
|
||||
@@ -228,11 +231,12 @@ impl 买卖点Py {
|
||||
备注,
|
||||
中枢破位值,
|
||||
),
|
||||
}
|
||||
};
|
||||
let init = PyClassInitializer::from(base).add_subclass(买卖点Py);
|
||||
Ok(Bound::new(py, init)?.unbind())
|
||||
}
|
||||
|
||||
#[classmethod]
|
||||
/// :param 买卖点分型: 买卖点对应的分型
|
||||
fn 一买点(
|
||||
_cls: &Bound<'_, PyType>,
|
||||
买卖点分型: &Bound<'_, 分型Py>,
|
||||
@@ -240,8 +244,9 @@ impl 买卖点Py {
|
||||
标识: &str,
|
||||
备注: String,
|
||||
中枢破位值: f64,
|
||||
) -> 基础买卖点Py {
|
||||
基础买卖点Py {
|
||||
py: Python<'_>,
|
||||
) -> PyResult<Py<Self>> {
|
||||
let base = 基础买卖点Py {
|
||||
inner: chanlun::business::bsp::买卖点::一买点(
|
||||
Arc::clone(&买卖点分型.borrow().inner),
|
||||
当前K线.borrow().inner.clone(),
|
||||
@@ -249,11 +254,12 @@ impl 买卖点Py {
|
||||
备注,
|
||||
中枢破位值,
|
||||
),
|
||||
}
|
||||
};
|
||||
let init = PyClassInitializer::from(base).add_subclass(买卖点Py);
|
||||
Ok(Bound::new(py, init)?.unbind())
|
||||
}
|
||||
|
||||
#[classmethod]
|
||||
/// :param 买卖点分型: 买卖点对应的分型
|
||||
fn 二卖点(
|
||||
_cls: &Bound<'_, PyType>,
|
||||
买卖点分型: &Bound<'_, 分型Py>,
|
||||
@@ -261,8 +267,9 @@ impl 买卖点Py {
|
||||
标识: &str,
|
||||
备注: String,
|
||||
中枢破位值: f64,
|
||||
) -> 基础买卖点Py {
|
||||
基础买卖点Py {
|
||||
py: Python<'_>,
|
||||
) -> PyResult<Py<Self>> {
|
||||
let base = 基础买卖点Py {
|
||||
inner: chanlun::business::bsp::买卖点::二卖点(
|
||||
Arc::clone(&买卖点分型.borrow().inner),
|
||||
当前K线.borrow().inner.clone(),
|
||||
@@ -270,11 +277,12 @@ impl 买卖点Py {
|
||||
备注,
|
||||
中枢破位值,
|
||||
),
|
||||
}
|
||||
};
|
||||
let init = PyClassInitializer::from(base).add_subclass(买卖点Py);
|
||||
Ok(Bound::new(py, init)?.unbind())
|
||||
}
|
||||
|
||||
#[classmethod]
|
||||
/// :param 买卖点分型: 买卖点对应的分型
|
||||
fn 二买点(
|
||||
_cls: &Bound<'_, PyType>,
|
||||
买卖点分型: &Bound<'_, 分型Py>,
|
||||
@@ -282,8 +290,9 @@ impl 买卖点Py {
|
||||
标识: &str,
|
||||
备注: String,
|
||||
中枢破位值: f64,
|
||||
) -> 基础买卖点Py {
|
||||
基础买卖点Py {
|
||||
py: Python<'_>,
|
||||
) -> PyResult<Py<Self>> {
|
||||
let base = 基础买卖点Py {
|
||||
inner: chanlun::business::bsp::买卖点::二买点(
|
||||
Arc::clone(&买卖点分型.borrow().inner),
|
||||
当前K线.borrow().inner.clone(),
|
||||
@@ -291,11 +300,12 @@ impl 买卖点Py {
|
||||
备注,
|
||||
中枢破位值,
|
||||
),
|
||||
}
|
||||
};
|
||||
let init = PyClassInitializer::from(base).add_subclass(买卖点Py);
|
||||
Ok(Bound::new(py, init)?.unbind())
|
||||
}
|
||||
|
||||
#[classmethod]
|
||||
/// :param 买卖点分型: 买卖点对应的分型
|
||||
fn 三卖点(
|
||||
_cls: &Bound<'_, PyType>,
|
||||
买卖点分型: &Bound<'_, 分型Py>,
|
||||
@@ -303,8 +313,9 @@ impl 买卖点Py {
|
||||
标识: &str,
|
||||
备注: String,
|
||||
中枢破位值: f64,
|
||||
) -> 基础买卖点Py {
|
||||
基础买卖点Py {
|
||||
py: Python<'_>,
|
||||
) -> PyResult<Py<Self>> {
|
||||
let base = 基础买卖点Py {
|
||||
inner: chanlun::business::bsp::买卖点::三卖点(
|
||||
Arc::clone(&买卖点分型.borrow().inner),
|
||||
当前K线.borrow().inner.clone(),
|
||||
@@ -312,11 +323,12 @@ impl 买卖点Py {
|
||||
备注,
|
||||
中枢破位值,
|
||||
),
|
||||
}
|
||||
};
|
||||
let init = PyClassInitializer::from(base).add_subclass(买卖点Py);
|
||||
Ok(Bound::new(py, init)?.unbind())
|
||||
}
|
||||
|
||||
#[classmethod]
|
||||
/// :param 买卖点分型: 买卖点对应的分型
|
||||
fn 三买点(
|
||||
_cls: &Bound<'_, PyType>,
|
||||
买卖点分型: &Bound<'_, 分型Py>,
|
||||
@@ -324,8 +336,9 @@ impl 买卖点Py {
|
||||
标识: &str,
|
||||
备注: String,
|
||||
中枢破位值: f64,
|
||||
) -> 基础买卖点Py {
|
||||
基础买卖点Py {
|
||||
py: Python<'_>,
|
||||
) -> PyResult<Py<Self>> {
|
||||
let base = 基础买卖点Py {
|
||||
inner: chanlun::business::bsp::买卖点::三买点(
|
||||
Arc::clone(&买卖点分型.borrow().inner),
|
||||
当前K线.borrow().inner.clone(),
|
||||
@@ -333,11 +346,289 @@ impl 买卖点Py {
|
||||
备注,
|
||||
中枢破位值,
|
||||
),
|
||||
}
|
||||
};
|
||||
let init = PyClassInitializer::from(base).add_subclass(买卖点Py);
|
||||
Ok(Bound::new(py, init)?.unbind())
|
||||
}
|
||||
|
||||
#[classmethod]
|
||||
/// :param 特征: 特征字符串
|
||||
fn T1卖点(
|
||||
_cls: &Bound<'_, PyType>,
|
||||
买卖点分型: &Bound<'_, 分型Py>,
|
||||
当前K线: &Bound<'_, K线Py>,
|
||||
标识: &str,
|
||||
备注: String,
|
||||
中枢破位值: f64,
|
||||
py: Python<'_>,
|
||||
) -> PyResult<Py<Self>> {
|
||||
let base = 基础买卖点Py {
|
||||
inner: chanlun::business::bsp::买卖点::T1卖点(
|
||||
Arc::clone(&买卖点分型.borrow().inner),
|
||||
当前K线.borrow().inner.clone(),
|
||||
标识,
|
||||
备注,
|
||||
中枢破位值,
|
||||
),
|
||||
};
|
||||
let init = PyClassInitializer::from(base).add_subclass(买卖点Py);
|
||||
Ok(Bound::new(py, init)?.unbind())
|
||||
}
|
||||
|
||||
#[classmethod]
|
||||
fn T1买点(
|
||||
_cls: &Bound<'_, PyType>,
|
||||
买卖点分型: &Bound<'_, 分型Py>,
|
||||
当前K线: &Bound<'_, K线Py>,
|
||||
标识: &str,
|
||||
备注: String,
|
||||
中枢破位值: f64,
|
||||
py: Python<'_>,
|
||||
) -> PyResult<Py<Self>> {
|
||||
let base = 基础买卖点Py {
|
||||
inner: chanlun::business::bsp::买卖点::T1买点(
|
||||
Arc::clone(&买卖点分型.borrow().inner),
|
||||
当前K线.borrow().inner.clone(),
|
||||
标识,
|
||||
备注,
|
||||
中枢破位值,
|
||||
),
|
||||
};
|
||||
let init = PyClassInitializer::from(base).add_subclass(买卖点Py);
|
||||
Ok(Bound::new(py, init)?.unbind())
|
||||
}
|
||||
|
||||
#[classmethod]
|
||||
fn T1P卖点(
|
||||
_cls: &Bound<'_, PyType>,
|
||||
买卖点分型: &Bound<'_, 分型Py>,
|
||||
当前K线: &Bound<'_, K线Py>,
|
||||
标识: &str,
|
||||
备注: String,
|
||||
中枢破位值: f64,
|
||||
py: Python<'_>,
|
||||
) -> PyResult<Py<Self>> {
|
||||
let base = 基础买卖点Py {
|
||||
inner: chanlun::business::bsp::买卖点::T1P卖点(
|
||||
Arc::clone(&买卖点分型.borrow().inner),
|
||||
当前K线.borrow().inner.clone(),
|
||||
标识,
|
||||
备注,
|
||||
中枢破位值,
|
||||
),
|
||||
};
|
||||
let init = PyClassInitializer::from(base).add_subclass(买卖点Py);
|
||||
Ok(Bound::new(py, init)?.unbind())
|
||||
}
|
||||
|
||||
#[classmethod]
|
||||
fn T1P买点(
|
||||
_cls: &Bound<'_, PyType>,
|
||||
买卖点分型: &Bound<'_, 分型Py>,
|
||||
当前K线: &Bound<'_, K线Py>,
|
||||
标识: &str,
|
||||
备注: String,
|
||||
中枢破位值: f64,
|
||||
py: Python<'_>,
|
||||
) -> PyResult<Py<Self>> {
|
||||
let base = 基础买卖点Py {
|
||||
inner: chanlun::business::bsp::买卖点::T1P买点(
|
||||
Arc::clone(&买卖点分型.borrow().inner),
|
||||
当前K线.borrow().inner.clone(),
|
||||
标识,
|
||||
备注,
|
||||
中枢破位值,
|
||||
),
|
||||
};
|
||||
let init = PyClassInitializer::from(base).add_subclass(买卖点Py);
|
||||
Ok(Bound::new(py, init)?.unbind())
|
||||
}
|
||||
|
||||
#[classmethod]
|
||||
fn T2卖点(
|
||||
_cls: &Bound<'_, PyType>,
|
||||
买卖点分型: &Bound<'_, 分型Py>,
|
||||
当前K线: &Bound<'_, K线Py>,
|
||||
标识: &str,
|
||||
备注: String,
|
||||
中枢破位值: f64,
|
||||
py: Python<'_>,
|
||||
) -> PyResult<Py<Self>> {
|
||||
let base = 基础买卖点Py {
|
||||
inner: chanlun::business::bsp::买卖点::T2卖点(
|
||||
Arc::clone(&买卖点分型.borrow().inner),
|
||||
当前K线.borrow().inner.clone(),
|
||||
标识,
|
||||
备注,
|
||||
中枢破位值,
|
||||
),
|
||||
};
|
||||
let init = PyClassInitializer::from(base).add_subclass(买卖点Py);
|
||||
Ok(Bound::new(py, init)?.unbind())
|
||||
}
|
||||
|
||||
#[classmethod]
|
||||
fn T2买点(
|
||||
_cls: &Bound<'_, PyType>,
|
||||
买卖点分型: &Bound<'_, 分型Py>,
|
||||
当前K线: &Bound<'_, K线Py>,
|
||||
标识: &str,
|
||||
备注: String,
|
||||
中枢破位值: f64,
|
||||
py: Python<'_>,
|
||||
) -> PyResult<Py<Self>> {
|
||||
let base = 基础买卖点Py {
|
||||
inner: chanlun::business::bsp::买卖点::T2买点(
|
||||
Arc::clone(&买卖点分型.borrow().inner),
|
||||
当前K线.borrow().inner.clone(),
|
||||
标识,
|
||||
备注,
|
||||
中枢破位值,
|
||||
),
|
||||
};
|
||||
let init = PyClassInitializer::from(base).add_subclass(买卖点Py);
|
||||
Ok(Bound::new(py, init)?.unbind())
|
||||
}
|
||||
|
||||
#[classmethod]
|
||||
fn T2S卖点(
|
||||
_cls: &Bound<'_, PyType>,
|
||||
买卖点分型: &Bound<'_, 分型Py>,
|
||||
当前K线: &Bound<'_, K线Py>,
|
||||
标识: &str,
|
||||
备注: String,
|
||||
中枢破位值: f64,
|
||||
py: Python<'_>,
|
||||
) -> PyResult<Py<Self>> {
|
||||
let base = 基础买卖点Py {
|
||||
inner: chanlun::business::bsp::买卖点::T2S卖点(
|
||||
Arc::clone(&买卖点分型.borrow().inner),
|
||||
当前K线.borrow().inner.clone(),
|
||||
标识,
|
||||
备注,
|
||||
中枢破位值,
|
||||
),
|
||||
};
|
||||
let init = PyClassInitializer::from(base).add_subclass(买卖点Py);
|
||||
Ok(Bound::new(py, init)?.unbind())
|
||||
}
|
||||
|
||||
#[classmethod]
|
||||
fn T2S买点(
|
||||
_cls: &Bound<'_, PyType>,
|
||||
买卖点分型: &Bound<'_, 分型Py>,
|
||||
当前K线: &Bound<'_, K线Py>,
|
||||
标识: &str,
|
||||
备注: String,
|
||||
中枢破位值: f64,
|
||||
py: Python<'_>,
|
||||
) -> PyResult<Py<Self>> {
|
||||
let base = 基础买卖点Py {
|
||||
inner: chanlun::business::bsp::买卖点::T2S买点(
|
||||
Arc::clone(&买卖点分型.borrow().inner),
|
||||
当前K线.borrow().inner.clone(),
|
||||
标识,
|
||||
备注,
|
||||
中枢破位值,
|
||||
),
|
||||
};
|
||||
let init = PyClassInitializer::from(base).add_subclass(买卖点Py);
|
||||
Ok(Bound::new(py, init)?.unbind())
|
||||
}
|
||||
|
||||
#[classmethod]
|
||||
fn T3A卖点(
|
||||
_cls: &Bound<'_, PyType>,
|
||||
买卖点分型: &Bound<'_, 分型Py>,
|
||||
当前K线: &Bound<'_, K线Py>,
|
||||
标识: &str,
|
||||
备注: String,
|
||||
中枢破位值: f64,
|
||||
py: Python<'_>,
|
||||
) -> PyResult<Py<Self>> {
|
||||
let base = 基础买卖点Py {
|
||||
inner: chanlun::business::bsp::买卖点::T3A卖点(
|
||||
Arc::clone(&买卖点分型.borrow().inner),
|
||||
当前K线.borrow().inner.clone(),
|
||||
标识,
|
||||
备注,
|
||||
中枢破位值,
|
||||
),
|
||||
};
|
||||
let init = PyClassInitializer::from(base).add_subclass(买卖点Py);
|
||||
Ok(Bound::new(py, init)?.unbind())
|
||||
}
|
||||
|
||||
#[classmethod]
|
||||
fn T3A买点(
|
||||
_cls: &Bound<'_, PyType>,
|
||||
买卖点分型: &Bound<'_, 分型Py>,
|
||||
当前K线: &Bound<'_, K线Py>,
|
||||
标识: &str,
|
||||
备注: String,
|
||||
中枢破位值: f64,
|
||||
py: Python<'_>,
|
||||
) -> PyResult<Py<Self>> {
|
||||
let base = 基础买卖点Py {
|
||||
inner: chanlun::business::bsp::买卖点::T3A买点(
|
||||
Arc::clone(&买卖点分型.borrow().inner),
|
||||
当前K线.borrow().inner.clone(),
|
||||
标识,
|
||||
备注,
|
||||
中枢破位值,
|
||||
),
|
||||
};
|
||||
let init = PyClassInitializer::from(base).add_subclass(买卖点Py);
|
||||
Ok(Bound::new(py, init)?.unbind())
|
||||
}
|
||||
|
||||
#[classmethod]
|
||||
fn T3B卖点(
|
||||
_cls: &Bound<'_, PyType>,
|
||||
买卖点分型: &Bound<'_, 分型Py>,
|
||||
当前K线: &Bound<'_, K线Py>,
|
||||
标识: &str,
|
||||
备注: String,
|
||||
中枢破位值: f64,
|
||||
py: Python<'_>,
|
||||
) -> PyResult<Py<Self>> {
|
||||
let base = 基础买卖点Py {
|
||||
inner: chanlun::business::bsp::买卖点::T3B卖点(
|
||||
Arc::clone(&买卖点分型.borrow().inner),
|
||||
当前K线.borrow().inner.clone(),
|
||||
标识,
|
||||
备注,
|
||||
中枢破位值,
|
||||
),
|
||||
};
|
||||
let init = PyClassInitializer::from(base).add_subclass(买卖点Py);
|
||||
Ok(Bound::new(py, init)?.unbind())
|
||||
}
|
||||
|
||||
#[classmethod]
|
||||
fn T3B买点(
|
||||
_cls: &Bound<'_, PyType>,
|
||||
买卖点分型: &Bound<'_, 分型Py>,
|
||||
当前K线: &Bound<'_, K线Py>,
|
||||
标识: &str,
|
||||
备注: String,
|
||||
中枢破位值: f64,
|
||||
py: Python<'_>,
|
||||
) -> PyResult<Py<Self>> {
|
||||
let base = 基础买卖点Py {
|
||||
inner: chanlun::business::bsp::买卖点::T3B买点(
|
||||
Arc::clone(&买卖点分型.borrow().inner),
|
||||
当前K线.borrow().inner.clone(),
|
||||
标识,
|
||||
备注,
|
||||
中枢破位值,
|
||||
),
|
||||
};
|
||||
let init = PyClassInitializer::from(base).add_subclass(买卖点Py);
|
||||
Ok(Bound::new(py, init)?.unbind())
|
||||
}
|
||||
|
||||
#[classmethod]
|
||||
#[pyo3(signature = (特征, 序号, 级别, 买卖点分型, 当前缠K))]
|
||||
fn 生成买卖点(
|
||||
_cls: &Bound<'_, PyType>,
|
||||
特征: &str,
|
||||
@@ -345,8 +636,9 @@ impl 买卖点Py {
|
||||
级别: &str,
|
||||
买卖点分型: &Bound<'_, 分型Py>,
|
||||
当前缠K: &Bound<'_, 缠论K线Py>,
|
||||
) -> 基础买卖点Py {
|
||||
基础买卖点Py {
|
||||
py: Python<'_>,
|
||||
) -> PyResult<Py<Self>> {
|
||||
let base = 基础买卖点Py {
|
||||
inner: chanlun::business::bsp::买卖点::生成买卖点(
|
||||
特征,
|
||||
序号,
|
||||
@@ -354,7 +646,9 @@ impl 买卖点Py {
|
||||
Arc::clone(&买卖点分型.borrow().inner),
|
||||
Arc::clone(&当前缠K.borrow().inner),
|
||||
),
|
||||
}
|
||||
};
|
||||
let init = PyClassInitializer::from(base).add_subclass(买卖点Py);
|
||||
Ok(Bound::new(py, init)?.unbind())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -524,60 +818,114 @@ impl 观察者Py {
|
||||
缠论配置Py::from_rust_config(&self.obs().配置)
|
||||
}
|
||||
|
||||
/// 清空所有分析序列,重置为初始状态
|
||||
fn 重置基础序列(&mut self) {
|
||||
/// 清空所有分析序列,重置为初始状态(内部实现)
|
||||
#[pyo3(name = "_重置基础序列")]
|
||||
fn 重置基础序列_impl(&mut self) -> PyResult<()> {
|
||||
self.obs_mut().重置基础序列();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 核心入口 — 投喂一根原始K线,增量更新所有层级
|
||||
fn 增加原始K线(&mut self, 普K: &Bound<'_, K线Py>) {
|
||||
/// 清空所有分析序列,重置为初始状态(公开分发器,支持子类重写)
|
||||
fn 重置基础序列(slf: &Bound<'_, Self>) -> PyResult<()> {
|
||||
slf.call_method1("_重置基础序列", ())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 核心入口 — 投喂一根原始K线,增量更新所有层级(内部实现)
|
||||
#[pyo3(name = "_增加原始K线")]
|
||||
fn 增加原始K线_impl(&mut self, 普K: &Bound<'_, K线Py>) -> PyResult<()> {
|
||||
self.obs_mut().增加原始K线((*普K.borrow().inner).clone());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 核心入口 — 投喂一根原始K线,增量更新所有层级(公开分发器,支持子类重写)
|
||||
fn 增加原始K线(slf: &Bound<'_, Self>, 普K: &Bound<'_, K线Py>) -> PyResult<()> {
|
||||
slf.call_method1("_增加原始K线", (普K,))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 投喂原始数据 — 便捷入口,直接从 OHLCV 创建 K线 并通过 Python 分发 增加原始K线,
|
||||
/// 确保子类重写的 增加原始K线 被正确调用。
|
||||
fn 投喂原始数据(
|
||||
slf: &Bound<'_, Self>,
|
||||
时间戳: i64,
|
||||
开: f64,
|
||||
高: f64,
|
||||
低: f64,
|
||||
收: f64,
|
||||
量: f64,
|
||||
) -> PyResult<()> {
|
||||
let (符号, 周期) = {
|
||||
let me = slf.borrow();
|
||||
let obs = me.obs();
|
||||
(obs.符号.clone(), obs.周期)
|
||||
};
|
||||
let kline = bar_to_py(
|
||||
slf.py(),
|
||||
Arc::new(chanlun::kline::bar::K线::创建普K(
|
||||
&符号, 时间戳, 开, 高, 低, 收, 量, 0, 周期,
|
||||
)),
|
||||
);
|
||||
slf.call_method1("增加原始K线", (kline,))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 加载本地数据 — 从 .nb 文件加载K线数据(先重置,再通过 Python dispatch 逐根投喂,
|
||||
/// 确保子类重写的 增加原始K线 被正确调用)。
|
||||
fn 加载本地数据(slf: &Bound<'_, Self>, 文件路径: &str) -> PyResult<()> {
|
||||
let py = slf.py();
|
||||
// 重置基础序列(通过 Python 分发,支持子类重写)
|
||||
slf.call_method1("重置基础序列", ())?;
|
||||
|
||||
// 重置基础序列
|
||||
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(())
|
||||
}
|
||||
|
||||
/// 静态重新分析(占位方法)
|
||||
fn 静态重新分析(&mut self) {
|
||||
/// 静态重新分析(内部实现)
|
||||
#[pyo3(name = "_静态重新分析")]
|
||||
fn 静态重新分析_impl(&mut self) -> PyResult<()> {
|
||||
self.obs_mut().静态重新分析();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 静态重新分析(公开分发器,支持子类重写)
|
||||
fn 静态重新分析(slf: &Bound<'_, Self>) -> PyResult<()> {
|
||||
slf.call_method1("_静态重新分析", ())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 拆分各序列数据,单独存文件,文件名为对应变量名(内部实现)
|
||||
#[pyo3(name = "_测试_保存数据", signature = (root = None))]
|
||||
fn 测试_保存数据_impl(&self, root: Option<&str>) -> String {
|
||||
self.obs().测试_保存数据(root)
|
||||
}
|
||||
|
||||
/// 拆分各序列数据,单独存文件,文件名为对应变量名(公开分发器,支持子类重写)
|
||||
#[pyo3(signature = (root = None))]
|
||||
/// 拆分各序列数据,单独存文件,文件名为对应变量名
|
||||
fn 测试_保存数据(&self, root: Option<&str>) {
|
||||
self.obs().测试_保存数据(root);
|
||||
fn 测试_保存数据(slf: &Bound<'_, Self>, root: Option<&str>) -> PyResult<String> {
|
||||
let result = slf.call_method1("_测试_保存数据", (root,))?;
|
||||
result.extract::<String>()
|
||||
}
|
||||
|
||||
#[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<'_>,
|
||||
@@ -605,31 +953,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 ----
|
||||
@@ -823,11 +1159,11 @@ impl K线合成器Py {
|
||||
&mut self,
|
||||
普K: &Bound<'_, K线Py>,
|
||||
py: Python<'_>,
|
||||
) -> PyResult<Vec<(i64, K线Py)>> {
|
||||
) -> PyResult<Vec<(i64, Py<K线Py>)>> {
|
||||
let results = self.inner.投喂K线((*普K.borrow().inner).clone());
|
||||
Ok(results
|
||||
.into_iter()
|
||||
.map(|(周期, k)| (周期, K线Py { inner: Arc::new(k) }))
|
||||
.map(|(周期, k)| (周期, bar_to_py(py, Arc::new(k))))
|
||||
.collect())
|
||||
}
|
||||
|
||||
@@ -840,7 +1176,8 @@ impl K线合成器Py {
|
||||
低: f64,
|
||||
收: f64,
|
||||
量: f64,
|
||||
) -> Vec<(i64, K线Py)> {
|
||||
py: Python<'_>,
|
||||
) -> Vec<(i64, Py<K线Py>)> {
|
||||
let min_cycle = self.inner.周期组.iter().copied().min().unwrap_or(1);
|
||||
let k = chanlun::kline::bar::K线::创建普K(
|
||||
&self.inner.标识,
|
||||
@@ -856,22 +1193,15 @@ impl K线合成器Py {
|
||||
let results = self.inner.投喂K线(k);
|
||||
results
|
||||
.into_iter()
|
||||
.map(|(周期, k2)| {
|
||||
(
|
||||
周期,
|
||||
K线Py {
|
||||
inner: Arc::new(k2),
|
||||
},
|
||||
)
|
||||
})
|
||||
.map(|(周期, k2)| (周期, bar_to_py(py, Arc::new(k2))))
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// 获取指定周期当前正在合成的K线
|
||||
fn 获取当前K线(&self, 周期: i64) -> Option<K线Py> {
|
||||
self.inner.获取当前K线(周期).map(|k| K线Py {
|
||||
inner: Arc::new(k.clone()),
|
||||
})
|
||||
fn 获取当前K线(&self, 周期: i64, py: Python<'_>) -> Option<Py<K线Py>> {
|
||||
self.inner
|
||||
.获取当前K线(周期)
|
||||
.map(|k| bar_to_py(py, Arc::new(k.clone())))
|
||||
}
|
||||
|
||||
#[getter]
|
||||
@@ -920,7 +1250,7 @@ impl 立体分析器Py {
|
||||
};
|
||||
let cfg_map: Option<HashMap<i64, chanlun::config::缠论配置>> = match 配置组 {
|
||||
Some(dict_any) => {
|
||||
let dict = dict_any.downcast::<pyo3::types::PyDict>()?;
|
||||
let dict = dict_any.cast::<pyo3::types::PyDict>()?;
|
||||
let mut map = HashMap::new();
|
||||
for (key, value) in dict.iter() {
|
||||
let period: i64 = key.extract()?;
|
||||
@@ -950,8 +1280,8 @@ impl 立体分析器Py {
|
||||
}
|
||||
|
||||
/// 拆分各序列数据,单独存文件,文件名为对应变量名
|
||||
fn 测试_保存数据(&self) {
|
||||
self.inner.测试_保存数据();
|
||||
fn 测试_保存数据(&self, root: Option<&str>) {
|
||||
self.inner.测试_保存数据(root);
|
||||
}
|
||||
|
||||
#[getter]
|
||||
|
||||
+26
-21
@@ -25,6 +25,7 @@
|
||||
use pyo3::prelude::*;
|
||||
use pyo3::types::{PyDict, PyType};
|
||||
use std::collections::HashMap;
|
||||
use tracing::warn;
|
||||
|
||||
/// 缠论配置 — 控制所有分析阶段行为的参数集(共 60+ 字段,均有默认值)。
|
||||
///
|
||||
@@ -239,7 +240,7 @@ impl 缠论配置Py {
|
||||
) -> PyResult<Py<PyDict>> {
|
||||
let py = 原始字典.py();
|
||||
let result = PyDict::new(py);
|
||||
if let Ok(default_dict) = 默认配置.downcast::<PyDict>() {
|
||||
if let Ok(default_dict) = 默认配置.cast::<PyDict>() {
|
||||
for (key, value) in default_dict.iter() {
|
||||
if 原始字典.contains(&key)? {
|
||||
result.set_item(key.clone(), 原始字典.get_item(&key)?)?;
|
||||
@@ -252,6 +253,7 @@ impl 缠论配置Py {
|
||||
}
|
||||
|
||||
/// 比较当前配置与另一个配置的差异
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn 对比(
|
||||
&self,
|
||||
py: Python<'_>,
|
||||
@@ -290,7 +292,10 @@ impl 缠论配置Py {
|
||||
Ok(Self { fields })
|
||||
}
|
||||
|
||||
pub(crate) fn to_rust_config(&self, py: Python<'_>) -> PyResult<chanlun::config::缠论配置> {
|
||||
pub(crate) fn to_rust_config(
|
||||
&self,
|
||||
_py: Python<'_>,
|
||||
) -> PyResult<chanlun::config::缠论配置> {
|
||||
dict_to_rust_config(&self.fields)
|
||||
}
|
||||
|
||||
@@ -340,17 +345,17 @@ fn dict_to_rust_config(
|
||||
|
||||
// 用默认值做基准,只合并类型匹配的字段
|
||||
let mut merged = default_json.clone();
|
||||
if let serde_json::Value::Object(ref input_map) = value {
|
||||
if let serde_json::Value::Object(ref default_map) = default_json {
|
||||
for (key, input_val) in input_map {
|
||||
if let Some(default_val) = default_map.get(key) {
|
||||
match validate_field(key, input_val, default_val) {
|
||||
Ok(()) => {
|
||||
merged[key] = input_val.clone();
|
||||
}
|
||||
Err(msg) => {
|
||||
eprintln!("\x1b[33m[配置警告]\x1b[m {key}: {msg},已使用默认值 {default_val}");
|
||||
}
|
||||
if let serde_json::Value::Object(ref input_map) = value
|
||||
&& let serde_json::Value::Object(ref default_map) = default_json
|
||||
{
|
||||
for (key, input_val) in input_map {
|
||||
if let Some(default_val) = default_map.get(key) {
|
||||
match validate_field(key, input_val, default_val) {
|
||||
Ok(()) => {
|
||||
merged[key] = input_val.clone();
|
||||
}
|
||||
Err(msg) => {
|
||||
warn!("[配置警告] {key}: {msg},已使用默认值 {default_val}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -449,10 +454,10 @@ fn coerce_strings_to_numbers(value: &mut serde_json::Value) {
|
||||
if let Ok(n) = cloned.parse::<i64>() {
|
||||
*value = serde_json::Value::Number(serde_json::Number::from(n));
|
||||
} else if let Ok(n) = cloned.parse::<f64>() {
|
||||
if n.is_finite() {
|
||||
if let Some(num) = serde_json::Number::from_f64(n) {
|
||||
*value = serde_json::Value::Number(num);
|
||||
}
|
||||
if n.is_finite()
|
||||
&& let Some(num) = serde_json::Number::from_f64(n)
|
||||
{
|
||||
*value = serde_json::Value::Number(num);
|
||||
}
|
||||
} else if cloned.eq_ignore_ascii_case("true") {
|
||||
*value = serde_json::Value::Bool(true);
|
||||
@@ -491,10 +496,10 @@ fn coerce_py_value(value: &Bound<'_, PyAny>) -> PyResult<Py<PyAny>> {
|
||||
if let Ok(n) = lower.parse::<i64>() {
|
||||
return Ok(n.into_pyobject(py)?.into_any().unbind());
|
||||
}
|
||||
if let Ok(n) = lower.parse::<f64>() {
|
||||
if n.is_finite() {
|
||||
return Ok(n.into_pyobject(py)?.into_any().unbind());
|
||||
}
|
||||
if let Ok(n) = lower.parse::<f64>()
|
||||
&& n.is_finite()
|
||||
{
|
||||
return Ok(n.into_pyobject(py)?.into_any().unbind());
|
||||
}
|
||||
|
||||
Ok(value.clone().unbind())
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
|
||||
use pyo3::prelude::*;
|
||||
use pyo3::types::PyType;
|
||||
use std::sync::Arc;
|
||||
|
||||
// ========== 平滑异同移动平均线 ==========
|
||||
|
||||
@@ -603,14 +604,257 @@ impl 随机指标Py {
|
||||
}
|
||||
}
|
||||
|
||||
// ========== 布林带 ==========
|
||||
|
||||
/// 布林带(BOLL)— 基于移动平均和标准差的波动率通道。
|
||||
///
|
||||
/// 属性:
|
||||
/// 时间戳: int / 周期: int / 标准差倍数: float
|
||||
/// 上轨: float — 中轨 + 标准差倍数 * 标准差
|
||||
/// 中轨: float — 移动平均线
|
||||
/// 下轨: float — 中轨 - 标准差倍数 * 标准差
|
||||
///
|
||||
/// 方法(均为 classmethod,直接构造实例):
|
||||
/// 首次计算(时间戳, 价格, 周期=20, 标准差倍数=2.0) -> 布林带
|
||||
/// 增量计算(前一个布林带, 时间戳, 价格) -> 布林带
|
||||
#[pyclass(name = "布林带", module = "chanlun._chanlun", from_py_object)]
|
||||
#[derive(Clone)]
|
||||
pub struct 布林带Py {
|
||||
pub(crate) inner: chanlun::indicators::布林带,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl 布林带Py {
|
||||
#[new]
|
||||
fn new() -> Self {
|
||||
unimplemented!("使用 首次计算 或 增量计算 创建")
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn 时间戳(&self) -> i64 {
|
||||
self.inner.时间戳
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn 周期(&self) -> usize {
|
||||
self.inner.周期
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn 标准差倍数(&self) -> f64 {
|
||||
self.inner.标准差倍数
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn 上轨(&self) -> f64 {
|
||||
self.inner.上轨
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn 中轨(&self) -> f64 {
|
||||
self.inner.中轨
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn 下轨(&self) -> f64 {
|
||||
self.inner.下轨
|
||||
}
|
||||
|
||||
fn __str__(&self) -> String {
|
||||
format!(
|
||||
"布林带(上={:.2}, 中={:.2}, 下={:.2})",
|
||||
self.inner.上轨, self.inner.中轨, self.inner.下轨
|
||||
)
|
||||
}
|
||||
|
||||
fn __repr__(&self) -> String {
|
||||
self.__str__()
|
||||
}
|
||||
|
||||
#[classmethod]
|
||||
#[pyo3(signature = (k线, 计算方式, 周期 = 20, 标准差倍数 = 2.0))]
|
||||
fn 首次计算(
|
||||
_cls: &Bound<'_, PyType>,
|
||||
k线: &Bound<'_, PyAny>,
|
||||
计算方式: &str,
|
||||
周期: usize,
|
||||
标准差倍数: f64,
|
||||
) -> PyResult<Self> {
|
||||
let 价格 = K线取值(k线, 计算方式)?;
|
||||
let 时间戳 = 获取时间戳(k线)?;
|
||||
Ok(Self {
|
||||
inner: chanlun::indicators::布林带::首次计算(时间戳, 价格, 周期, 标准差倍数),
|
||||
})
|
||||
}
|
||||
|
||||
#[classmethod]
|
||||
fn 增量计算(
|
||||
_cls: &Bound<'_, PyType>,
|
||||
前一个布林带: &Bound<'_, 布林带Py>,
|
||||
当前K线: &Bound<'_, PyAny>,
|
||||
计算方式: &str,
|
||||
) -> PyResult<Self> {
|
||||
let 价格 = K线取值(当前K线, 计算方式)?;
|
||||
let 时间戳 = 获取时间戳(当前K线)?;
|
||||
Ok(Self {
|
||||
inner: chanlun::indicators::布林带::增量计算(
|
||||
&前一个布林带.borrow().inner,
|
||||
时间戳,
|
||||
价格,
|
||||
),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// ========== 指标容器 ==========
|
||||
|
||||
/// 指标容器 — 挂载在每根 K线上,基于注册表模式持有该时刻所有指标快照。
|
||||
///
|
||||
/// 与 Python `指标容器` 保持一致:
|
||||
/// - 默认名称:"macd"/"rsi"/"kdj"/"boll" → 对应指标对象
|
||||
/// - 多参数变体:key 格式 "MACD_{快}_{慢}_{信号}" / "RSI_{周期}" 等
|
||||
/// - 均线组:通过 "均线" 获取 dict[str, float]
|
||||
/// - 单值指标:通过 "单值" 获取 dict[str, float]
|
||||
#[pyclass(name = "指标容器", module = "chanlun._chanlun", skip_from_py_object)]
|
||||
#[derive(Clone)]
|
||||
pub struct 指标容器Py {
|
||||
pub(crate) inner: chanlun::indicators::指标容器,
|
||||
}
|
||||
|
||||
/// 将 Rust 指标值 转换为 Python 对象
|
||||
fn 指标值_to_py(value: &chanlun::indicators::指标值, py: Python<'_>) -> PyResult<Py<PyAny>> {
|
||||
use chanlun::indicators::指标值;
|
||||
match value {
|
||||
指标值::MACD(m) => {
|
||||
Ok(Py::new(py, 平滑异同移动平均线Py { inner: m.clone() })?.into_any())
|
||||
}
|
||||
指标值::RSI(r) => Ok(Py::new(py, 相对强弱指数Py { inner: r.clone() })?.into_any()),
|
||||
指标值::KDJ(k) => Ok(Py::new(py, 随机指标Py { inner: k.clone() })?.into_any()),
|
||||
指标值::BOLL(b) => Ok(Py::new(py, 布林带Py { inner: b.clone() })?.into_any()),
|
||||
指标值::均线(map) | 指标值::单值(map) => {
|
||||
let dict = pyo3::types::PyDict::new(py);
|
||||
for (k, v) in map {
|
||||
dict.set_item(k, *v)?;
|
||||
}
|
||||
Ok(dict.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl 指标容器Py {
|
||||
#[new]
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
inner: chanlun::indicators::指标容器::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// 按名称获取指标值
|
||||
fn 获取(&self, 名称: &str, py: Python<'_>) -> PyResult<Option<Py<PyAny>>> {
|
||||
match self.inner.获取(名称) {
|
||||
Some(v) => 指标值_to_py(v, py).map(Some),
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
/// 按名称设置指标值(仅支持 MACD/RSI/KDJ/BOLL 四种类型)
|
||||
#[pyo3(signature = (名称, 值))]
|
||||
fn 设置(&mut self, 名称: &str, 值: &Bound<'_, PyAny>) -> PyResult<()> {
|
||||
use chanlun::indicators::指标值;
|
||||
if let Ok(m) = 值.cast::<平滑异同移动平均线Py>() {
|
||||
self.inner
|
||||
.设置(名称, 指标值::MACD(m.borrow().inner.clone()));
|
||||
return Ok(());
|
||||
}
|
||||
if let Ok(r) = 值.cast::<相对强弱指数Py>() {
|
||||
self.inner.设置(名称, 指标值::RSI(r.borrow().inner.clone()));
|
||||
return Ok(());
|
||||
}
|
||||
if let Ok(k) = 值.cast::<随机指标Py>() {
|
||||
self.inner.设置(名称, 指标值::KDJ(k.borrow().inner.clone()));
|
||||
return Ok(());
|
||||
}
|
||||
if let Ok(b) = 值.cast::<布林带Py>() {
|
||||
self.inner
|
||||
.设置(名称, 指标值::BOLL(b.borrow().inner.clone()));
|
||||
return Ok(());
|
||||
}
|
||||
Err(pyo3::exceptions::PyTypeError::new_err(
|
||||
"不支持的类型,仅支持 MACD/RSI/KDJ/BOLL 指标",
|
||||
))
|
||||
}
|
||||
|
||||
/// 检查是否包含指定名称的指标
|
||||
fn 包含(&self, 名称: &str) -> bool {
|
||||
self.inner.包含(名称)
|
||||
}
|
||||
|
||||
/// 返回所有已注册的指标名称
|
||||
fn keys(&self) -> Vec<String> {
|
||||
self.inner._数据.keys().cloned().collect()
|
||||
}
|
||||
|
||||
fn __getitem__(&self, 名称: &str, py: Python<'_>) -> PyResult<Py<PyAny>> {
|
||||
if self.包含(名称) {
|
||||
match self.inner.获取(名称) {
|
||||
Some(v) => 指标值_to_py(v, py),
|
||||
None => Ok(py.None()),
|
||||
}
|
||||
} else {
|
||||
Err(pyo3::exceptions::PyKeyError::new_err(format!(
|
||||
"指标 '{}' 不存在",
|
||||
名称
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
fn __getattr__(&self, 名称: &str, py: Python<'_>) -> PyResult<Py<PyAny>> {
|
||||
if 名称 == "_数据" {
|
||||
// 返回内部数据字典的 Python 表示
|
||||
let dict = pyo3::types::PyDict::new(py);
|
||||
for key in self.inner._数据.keys() {
|
||||
if let Some(v) = self.inner.获取(key) {
|
||||
dict.set_item(key, 指标值_to_py(v, py)?)?;
|
||||
} else {
|
||||
dict.set_item(key, py.None())?;
|
||||
}
|
||||
}
|
||||
return Ok(dict.into());
|
||||
}
|
||||
if self.包含(名称) {
|
||||
match self.inner.获取(名称) {
|
||||
Some(v) => 指标值_to_py(v, py),
|
||||
None => Ok(py.None()),
|
||||
}
|
||||
} else {
|
||||
Err(pyo3::exceptions::PyAttributeError::new_err(format!(
|
||||
"指标 '{}' 不存在于 指标容器 中",
|
||||
名称
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
fn __contains__(&self, 名称: &str) -> bool {
|
||||
self.包含(名称)
|
||||
}
|
||||
|
||||
fn __str__(&self) -> String {
|
||||
self.inner.to_string()
|
||||
}
|
||||
|
||||
fn __repr__(&self) -> String {
|
||||
self.__str__()
|
||||
}
|
||||
}
|
||||
|
||||
// ========== 指标 (static namespace) ==========
|
||||
|
||||
/// 指标 — 静态工具类,提供指标计算的辅助方法。
|
||||
///
|
||||
/// 方法:
|
||||
/// K线取值(k线, 指标计算方式) -> float (classmethod)
|
||||
/// 根据计算方式从K线提取数值。
|
||||
/// 计算方式: "收盘价" / "开盘价" / "高" / "低" / "均值" 等
|
||||
/// :meth:`K线取值` — 根据计算方式从K线提取数值
|
||||
/// (计算方式: "开"/"高"/"低"/"收"/"高低均值"/"高低收均值"/"开高低收均值")
|
||||
#[pyclass(name = "指标", module = "chanlun._chanlun")]
|
||||
pub struct 指标Py;
|
||||
|
||||
@@ -627,6 +871,144 @@ impl 指标Py {
|
||||
}
|
||||
}
|
||||
|
||||
// ========== 均线工具 ==========
|
||||
|
||||
/// 均线工具 — 增量 SMA/EMA 计算的静态方法容器。
|
||||
///
|
||||
/// 方法:
|
||||
/// :meth:`增量SMA` — 基于前一根K线的 SMA 值,增量计算当前 SMA
|
||||
/// :meth:`增量EMA` — 用前一根K线的 EMA 值递推计算当前 EMA
|
||||
#[pyclass(name = "均线工具", module = "chanlun._chanlun")]
|
||||
pub struct 均线工具Py;
|
||||
|
||||
#[pymethods]
|
||||
impl 均线工具Py {
|
||||
/// 基于前一根K线的 SMA 值,增量计算当前 SMA
|
||||
#[staticmethod]
|
||||
#[pyo3(signature = (普K序列, period, 计算方式))]
|
||||
fn 增量SMA(
|
||||
普K序列: Vec<Py<crate::kline_py::K线Py>>,
|
||||
period: i64,
|
||||
计算方式: &str,
|
||||
py: Python<'_>,
|
||||
) -> PyResult<f64> {
|
||||
if 普K序列.is_empty() {
|
||||
return Err(pyo3::exceptions::PyValueError::new_err("普K序列 不能为空"));
|
||||
}
|
||||
let n = 普K序列.len();
|
||||
// 提取所有K线值(一次性 borrow)
|
||||
let values: Vec<f64> = 普K序列
|
||||
.iter()
|
||||
.map(|k| {
|
||||
let inner = &k.bind(py).borrow().inner;
|
||||
chanlun::indicators::K线取值(
|
||||
inner.开盘价,
|
||||
inner.高,
|
||||
inner.低,
|
||||
inner.收盘价,
|
||||
计算方式,
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
|
||||
if n <= period as usize {
|
||||
let start = n.saturating_sub(period as usize);
|
||||
let sum: f64 = values[start..].iter().sum();
|
||||
return Ok(sum / (n.max(1)) as f64);
|
||||
}
|
||||
|
||||
let prev_key = format!("SMA_{}", period);
|
||||
// 尝试从前一根K线的均线缓存中读取
|
||||
let prev_cached = 普K序列[n - 2]
|
||||
.bind(py)
|
||||
.borrow()
|
||||
.inner
|
||||
.指标
|
||||
.read()
|
||||
.unwrap()
|
||||
.均线()
|
||||
.and_then(|m| m.get(&prev_key))
|
||||
.copied();
|
||||
if let Some(prev) = prev_cached {
|
||||
let 当前价 = values[n - 1];
|
||||
let oldest = values[n - period as usize - 1];
|
||||
return Ok(prev + (当前价 - oldest) / period as f64);
|
||||
}
|
||||
|
||||
// 回退:完整计算最近 period 根K线
|
||||
let sum: f64 = values[n - period as usize..].iter().sum();
|
||||
Ok(sum / period as f64)
|
||||
}
|
||||
|
||||
/// 用前一根K线的 EMA 值递推
|
||||
#[staticmethod]
|
||||
#[pyo3(signature = (普K序列, period, 计算方式, 前值 = None))]
|
||||
fn 增量EMA(
|
||||
普K序列: Vec<Py<crate::kline_py::K线Py>>,
|
||||
period: i64,
|
||||
计算方式: &str,
|
||||
前值: Option<f64>,
|
||||
py: Python<'_>,
|
||||
) -> PyResult<f64> {
|
||||
if 普K序列.is_empty() {
|
||||
return Err(pyo3::exceptions::PyValueError::new_err("普K序列 不能为空"));
|
||||
}
|
||||
let last = 普K序列.last().unwrap().bind(py).borrow();
|
||||
let 当前价 = chanlun::indicators::K线取值(
|
||||
last.inner.开盘价,
|
||||
last.inner.高,
|
||||
last.inner.低,
|
||||
last.inner.收盘价,
|
||||
计算方式,
|
||||
);
|
||||
match 前值 {
|
||||
None => Ok(当前价),
|
||||
Some(prev) => {
|
||||
let k = 2.0 / (period as f64 + 1.0);
|
||||
Ok(当前价 * k + prev * (1.0 - k))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ========== 指标计算器 ==========
|
||||
|
||||
/// 指标计算器 — 在缠K合并之前,增量计算所有开启的指标并挂载到K线上。
|
||||
///
|
||||
/// 方法:
|
||||
/// :meth:`计算并挂载` — 增量计算所有开启的指标,将结果写入 ``当前K线.指标``
|
||||
#[pyclass(name = "指标计算器", module = "chanlun._chanlun")]
|
||||
pub struct 指标计算器Py;
|
||||
|
||||
#[pymethods]
|
||||
impl 指标计算器Py {
|
||||
/// 增量计算所有开启的指标,将结果写入 当前K线.指标
|
||||
#[staticmethod]
|
||||
fn 计算并挂载(
|
||||
当前K线: &Bound<'_, crate::kline_py::K线Py>,
|
||||
全序列: Vec<Py<crate::kline_py::K线Py>>,
|
||||
配置: &Bound<'_, crate::config_py::缠论配置Py>,
|
||||
py: Python<'_>,
|
||||
) -> PyResult<()> {
|
||||
let config = 配置.borrow().to_rust_config(py)?;
|
||||
// 全序列包含 当前K线 在末尾;Rust 计算并挂载 的 现有序列 不含当前K线
|
||||
let 现有序列: Vec<Arc<chanlun::kline::bar::K线>> = if 全序列.len() > 1 {
|
||||
全序列[..全序列.len() - 1]
|
||||
.iter()
|
||||
.map(|k| k.bind(py).borrow().inner.clone())
|
||||
.collect()
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
chanlun::indicators::指标计算器::计算并挂载(
|
||||
&当前K线.borrow().inner,
|
||||
&现有序列,
|
||||
&config,
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// ========== Helper functions ==========
|
||||
|
||||
pub(crate) fn K线取值(k线: &Bound<'_, PyAny>, 计算方式: &str) -> PyResult<f64> {
|
||||
@@ -664,6 +1046,10 @@ pub fn register(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
||||
m.add_class::<平滑异同移动平均线Py>()?;
|
||||
m.add_class::<相对强弱指数Py>()?;
|
||||
m.add_class::<随机指标Py>()?;
|
||||
m.add_class::<布林带Py>()?;
|
||||
m.add_class::<指标容器Py>()?;
|
||||
m.add_class::<指标Py>()?;
|
||||
m.add_class::<均线工具Py>()?;
|
||||
m.add_class::<指标计算器Py>()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
+98
-91
@@ -25,12 +25,15 @@
|
||||
use pyo3::prelude::*;
|
||||
use pyo3::types::{PyBytes, PyDict, PyType};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::Arc;
|
||||
use std::sync::RwLock;
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
use crate::config_py::缠论配置Py;
|
||||
use crate::indicators_py::{平滑异同移动平均线Py, 相对强弱指数Py, 随机指标Py};
|
||||
use crate::indicators_py::{
|
||||
平滑异同移动平均线Py, 指标容器Py, 相对强弱指数Py, 随机指标Py
|
||||
};
|
||||
use crate::structure_py::fractal_to_py;
|
||||
use crate::types_py::相对方向Py;
|
||||
|
||||
// ========== K线 ==========
|
||||
@@ -85,9 +88,7 @@ impl K线Py {
|
||||
开盘价,
|
||||
收盘价,
|
||||
成交量,
|
||||
macd: None,
|
||||
rsi: None,
|
||||
kdj: None,
|
||||
指标: RwLock::new(chanlun::indicators::指标容器::new()),
|
||||
}),
|
||||
}
|
||||
}
|
||||
@@ -139,34 +140,46 @@ 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]
|
||||
fn macd(&self) -> Option<平滑异同移动平均线Py> {
|
||||
self.inner
|
||||
.macd
|
||||
.as_ref()
|
||||
.map(|m| 平滑异同移动平均线Py { inner: m.clone() })
|
||||
.指标
|
||||
.read()
|
||||
.unwrap()
|
||||
.macd_cloned()
|
||||
.map(|m| 平滑异同移动平均线Py { inner: m })
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn rsi(&self) -> Option<相对强弱指数Py> {
|
||||
self.inner
|
||||
.rsi
|
||||
.as_ref()
|
||||
.map(|r| 相对强弱指数Py { inner: r.clone() })
|
||||
.指标
|
||||
.read()
|
||||
.unwrap()
|
||||
.rsi_cloned()
|
||||
.map(|r| 相对强弱指数Py { inner: r })
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn kdj(&self) -> Option<随机指标Py> {
|
||||
self.inner
|
||||
.kdj
|
||||
.as_ref()
|
||||
.map(|k| 随机指标Py { inner: k.clone() })
|
||||
.指标
|
||||
.read()
|
||||
.unwrap()
|
||||
.kdj_cloned()
|
||||
.map(|k| 随机指标Py { inner: k })
|
||||
}
|
||||
|
||||
/// 指标容器 — 包含所有已注册指标(MACD/RSI/KDJ/BOLL/均线/单值)
|
||||
#[getter]
|
||||
fn 指标(&self) -> 指标容器Py {
|
||||
指标容器Py {
|
||||
inner: self.inner.指标.read().unwrap().clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// pandas 兼容 — 返回所有字段构成的字典
|
||||
@@ -182,7 +195,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)?;
|
||||
}
|
||||
@@ -192,6 +205,7 @@ impl K线Py {
|
||||
if let Some(v) = self.kdj() {
|
||||
dict.set_item("kdj", v)?;
|
||||
}
|
||||
dict.set_item("指标", self.指标())?;
|
||||
Ok(dict.into())
|
||||
}
|
||||
|
||||
@@ -349,26 +363,26 @@ impl K线Py {
|
||||
#[pyclass(name = "缠论K线", module = "chanlun._chanlun", from_py_object)]
|
||||
pub struct 缠论K线Py {
|
||||
pub(crate) inner: std::sync::Arc<chanlun::kline::chan_kline::缠论K线>,
|
||||
bsp_set: std::sync::RwLock<Option<Py<pyo3::types::PySet>>>,
|
||||
}
|
||||
|
||||
impl 缠论K线Py {
|
||||
pub(crate) fn from_rc(inner: std::sync::Arc<chanlun::kline::chan_kline::缠论K线>) -> Self {
|
||||
Self {
|
||||
inner,
|
||||
bsp_set: std::sync::RwLock::new(None),
|
||||
}
|
||||
Self { inner }
|
||||
}
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
/// 对象标识缓存:Rc 地址 → 规范 Python 对象
|
||||
/// 确保同一底层 Rc 指针在 Python 侧始终映射到同一 PyObject
|
||||
/// 对象标识缓存:Arc 地址 → 规范 Python 对象
|
||||
/// 确保同一底层 Arc 指针在 Python 侧始终映射到同一 PyObject
|
||||
/// 使用全局 static 而非 thread_local!,保证跨线程对象标识和买卖点信息一致性
|
||||
static BAR_IDENTITY: std::sync::LazyLock<RwLock<HashMap<usize, Py<K线Py>>>> =
|
||||
std::sync::LazyLock::new(|| RwLock::new(HashMap::new()));
|
||||
|
||||
static BAR_IDENTITY: RwLock<HashMap<usize, Py<K线Py>>> = RwLock::new(HashMap::new());
|
||||
static KLINE_IDENTITY: std::sync::LazyLock<RwLock<HashMap<usize, Py<缠论K线Py>>>> =
|
||||
std::sync::LazyLock::new(|| RwLock::new(HashMap::new()));
|
||||
|
||||
static KLINE_IDENTITY: RwLock<HashMap<usize, Py<缠论K线Py>>> = RwLock::new(HashMap::new());
|
||||
}
|
||||
/// 买卖点信息缓存 — 按 Arc 指针全局共享,确保所有 wrapper 看到同一 PySet
|
||||
static BSP_CACHE: std::sync::LazyLock<RwLock<HashMap<usize, Py<pyo3::types::PySet>>>> =
|
||||
std::sync::LazyLock::new(|| RwLock::new(HashMap::new()));
|
||||
|
||||
/// 将 Rc<K线> 转为 Py<K线Py>,确保同一 Rc 地址总是返回同一 Python 对象
|
||||
pub(crate) fn bar_to_py(
|
||||
@@ -376,15 +390,16 @@ pub(crate) fn bar_to_py(
|
||||
inner: std::sync::Arc<chanlun::kline::bar::K线>,
|
||||
) -> Py<K线Py> {
|
||||
let key = Arc::as_ptr(&inner) as usize;
|
||||
if let Some(cached) =
|
||||
BAR_IDENTITY.with(|c| c.read().unwrap().get(&key).map(|p| p.clone_ref(py)))
|
||||
if let Some(cached) = BAR_IDENTITY
|
||||
.read()
|
||||
.unwrap()
|
||||
.get(&key)
|
||||
.map(|p| p.clone_ref(py))
|
||||
{
|
||||
return cached;
|
||||
}
|
||||
let obj = Py::new(py, K线Py { inner }).unwrap();
|
||||
BAR_IDENTITY.with(|c| {
|
||||
c.write().unwrap().insert(key, obj.clone_ref(py));
|
||||
});
|
||||
BAR_IDENTITY.write().unwrap().insert(key, obj.clone_ref(py));
|
||||
obj
|
||||
}
|
||||
|
||||
@@ -394,15 +409,19 @@ pub(crate) fn chan_kline_to_py(
|
||||
inner: std::sync::Arc<chanlun::kline::chan_kline::缠论K线>,
|
||||
) -> Py<缠论K线Py> {
|
||||
let key = Arc::as_ptr(&inner) as usize;
|
||||
if let Some(cached) =
|
||||
KLINE_IDENTITY.with(|c| c.read().unwrap().get(&key).map(|p| p.clone_ref(py)))
|
||||
if let Some(cached) = KLINE_IDENTITY
|
||||
.read()
|
||||
.unwrap()
|
||||
.get(&key)
|
||||
.map(|p| p.clone_ref(py))
|
||||
{
|
||||
return cached;
|
||||
}
|
||||
let obj = Py::new(py, 缠论K线Py::from_rc(inner)).unwrap();
|
||||
KLINE_IDENTITY.with(|c| {
|
||||
c.write().unwrap().insert(key, obj.clone_ref(py));
|
||||
});
|
||||
KLINE_IDENTITY
|
||||
.write()
|
||||
.unwrap()
|
||||
.insert(key, obj.clone_ref(py));
|
||||
obj
|
||||
}
|
||||
|
||||
@@ -410,7 +429,6 @@ impl Clone for 缠论K线Py {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
inner: std::sync::Arc::clone(&self.inner),
|
||||
bsp_set: std::sync::RwLock::new(None),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -443,19 +461,17 @@ 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]
|
||||
fn 分型(&self) -> Option<crate::types_py::分型结构Py> {
|
||||
fn 分型(&self, py: Python<'_>) -> Option<Py<crate::types_py::分型结构Py>> {
|
||||
self.inner
|
||||
.分型
|
||||
.read()
|
||||
.unwrap()
|
||||
.map(|f| crate::types_py::分型结构Py { inner: f })
|
||||
.map(|f| crate::types_py::获取分型结构单例(py, f))
|
||||
}
|
||||
|
||||
#[getter]
|
||||
@@ -496,7 +512,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.分型特征值())?;
|
||||
@@ -506,7 +522,7 @@ impl 缠论K线Py {
|
||||
dict.set_item("与RSI匹配", self.与RSI匹配())?;
|
||||
dict.set_item("与KDJ匹配", self.与KDJ匹配())?;
|
||||
|
||||
if let Some(v) = self.分型() {
|
||||
if let Some(v) = self.分型(py) {
|
||||
dict.set_item("分型", v)?;
|
||||
}
|
||||
Ok(dict.into())
|
||||
@@ -534,17 +550,25 @@ impl 缠论K线Py {
|
||||
#[getter]
|
||||
/// 创建当前缠K的浅拷贝副本
|
||||
fn 镜像(&self, py: Python<'_>) -> Self {
|
||||
let mut mirror = Self {
|
||||
let mirror = Self {
|
||||
inner: std::sync::Arc::new(self.inner.镜像()),
|
||||
bsp_set: std::sync::RwLock::new(None),
|
||||
};
|
||||
if let Some(ref src_set) = *self.bsp_set.read().unwrap() {
|
||||
if let Ok(new_set) = pyo3::types::PySet::empty(py) {
|
||||
for item in src_set.bind(py).iter() {
|
||||
let _ = new_set.add(item);
|
||||
}
|
||||
mirror.bsp_set = std::sync::RwLock::new(Some(new_set.into()));
|
||||
// 复制买卖点信息到镜像
|
||||
let src_key = Arc::as_ptr(&self.inner) as usize;
|
||||
let dst_key = Arc::as_ptr(&mirror.inner) as usize;
|
||||
let cached_src = BSP_CACHE
|
||||
.read()
|
||||
.unwrap()
|
||||
.get(&src_key)
|
||||
.map(|p| p.clone_ref(py));
|
||||
if let Some(cached_src) = cached_src
|
||||
&& let Ok(new_set) = pyo3::types::PySet::empty(py)
|
||||
{
|
||||
for item in cached_src.bind(py).iter() {
|
||||
let _ = new_set.add(item);
|
||||
}
|
||||
let py_set: Py<pyo3::types::PySet> = new_set.into();
|
||||
BSP_CACHE.write().unwrap().insert(dst_key, py_set);
|
||||
}
|
||||
mirror
|
||||
}
|
||||
@@ -569,18 +593,24 @@ impl 缠论K线Py {
|
||||
|
||||
#[getter]
|
||||
fn 买卖点信息(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
|
||||
if self.bsp_set.read().unwrap().is_none() {
|
||||
let set = pyo3::types::PySet::empty(py)?;
|
||||
for s in self.inner.买卖点信息.read().unwrap().iter() {
|
||||
set.add(s.clone())?;
|
||||
}
|
||||
*self.bsp_set.write().unwrap() = Some(set.into());
|
||||
let key = Arc::as_ptr(&self.inner) as usize;
|
||||
// 检查全局缓存
|
||||
let cached = BSP_CACHE.read().unwrap().get(&key).map(|p| p.clone_ref(py));
|
||||
if let Some(set) = cached {
|
||||
return Ok(set.into_any());
|
||||
}
|
||||
Ok(self
|
||||
.bsp_set
|
||||
// 创建新的 PySet,从 Rust HashSet 同步已有内容
|
||||
let set = pyo3::types::PySet::empty(py)?;
|
||||
let bsp_info = self.inner.买卖点信息.read().unwrap();
|
||||
for item in bsp_info.iter() {
|
||||
set.add(item.as_str())?;
|
||||
}
|
||||
drop(bsp_info);
|
||||
BSP_CACHE.write().unwrap().insert(key, set.into());
|
||||
Ok(BSP_CACHE
|
||||
.read()
|
||||
.unwrap()
|
||||
.as_ref()
|
||||
.get(&key)
|
||||
.unwrap()
|
||||
.clone_ref(py)
|
||||
.into_any())
|
||||
@@ -631,29 +661,6 @@ impl 缠论K线Py {
|
||||
chan_kline_to_py(py, std::sync::Arc::new(inner))
|
||||
}
|
||||
|
||||
#[classmethod]
|
||||
/// K线包含处理(合并)
|
||||
fn 兼并(
|
||||
_cls: &Bound<'_, PyType>,
|
||||
之前缠K: Option<&Bound<'_, Self>>,
|
||||
当前缠K: &Bound<'_, Self>,
|
||||
当前普K: &Bound<'_, K线Py>,
|
||||
配置: &Bound<'_, 缠论配置Py>,
|
||||
py: Python<'_>,
|
||||
) -> PyResult<(Option<Py<Self>>, Option<String>)> {
|
||||
let mut ck_inner = (*当前缠K.borrow().inner).clone();
|
||||
let config = 配置.borrow().to_rust_config(py)?;
|
||||
let prev_ref = 之前缠K.map(|prev| prev.borrow());
|
||||
let prev_inner = prev_ref.as_ref().map(|r| r.inner.as_ref());
|
||||
let (result, mode) = chanlun::kline::chan_kline::缠论K线::兼并(
|
||||
prev_inner,
|
||||
&mut ck_inner,
|
||||
&当前普K.borrow().inner,
|
||||
&config,
|
||||
);
|
||||
Ok((result.map(|rc| chan_kline_to_py(py, rc)), mode))
|
||||
}
|
||||
|
||||
#[classmethod]
|
||||
/// 分析K线,执行指标计算+包含处理+分型判定
|
||||
fn 分析(
|
||||
@@ -676,14 +683,14 @@ impl 缠论K线Py {
|
||||
.map(|k| k.bind(py).borrow().inner.clone())
|
||||
.collect();
|
||||
|
||||
let (status, _fractal) = chanlun::kline::chan_kline::缠论K线::分析(
|
||||
let (status, fractal) = chanlun::kline::chan_kline::缠论K线::分析(
|
||||
ck_inner,
|
||||
&mut ck_seq,
|
||||
&mut bar_seq,
|
||||
&config,
|
||||
);
|
||||
|
||||
Ok((status, None))
|
||||
Ok((status, fractal.map(|f| fractal_to_py(py, f).into_any())))
|
||||
}
|
||||
|
||||
#[staticmethod]
|
||||
|
||||
+155
-29
@@ -22,7 +22,83 @@
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#![allow(non_snake_case, clippy::too_many_arguments)]
|
||||
|
||||
use pyo3::prelude::*;
|
||||
use std::sync::atomic::{AtomicU8, Ordering};
|
||||
use std::sync::{Mutex, Once, OnceLock};
|
||||
|
||||
/// 日志级别: 0=trace, 1=debug, 2=info, 3=warn, 4=error, 5=off
|
||||
static LOG_LEVEL: AtomicU8 = AtomicU8::new(2); // 默认 info
|
||||
|
||||
type 过滤器句柄 =
|
||||
tracing_subscriber::reload::Handle<tracing_subscriber::EnvFilter, tracing_subscriber::Registry>;
|
||||
static 过滤器句柄锁: OnceLock<Mutex<过滤器句柄>> = OnceLock::new();
|
||||
static TRACING_INIT: Once = Once::new();
|
||||
|
||||
fn 级别数字转名称(n: u8) -> &'static str {
|
||||
match n {
|
||||
0 => "trace",
|
||||
1 => "debug",
|
||||
2 => "info",
|
||||
3 => "warn",
|
||||
4 => "error",
|
||||
5 => "off",
|
||||
_ => "unknown",
|
||||
}
|
||||
}
|
||||
|
||||
fn 级别名称转数字(name: &str) -> Option<u8> {
|
||||
match name.to_lowercase().as_str() {
|
||||
"trace" => Some(0),
|
||||
"debug" => Some(1),
|
||||
"info" => Some(2),
|
||||
"warn" => Some(3),
|
||||
"error" => Some(4),
|
||||
"off" => Some(5),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn init_tracing() {
|
||||
TRACING_INIT.call_once(|| {
|
||||
use chrono::Local;
|
||||
use std::fmt;
|
||||
use tracing_subscriber::fmt::format::Format;
|
||||
use tracing_subscriber::fmt::format::Writer;
|
||||
use tracing_subscriber::fmt::time::FormatTime;
|
||||
use tracing_subscriber::layer::SubscriberExt;
|
||||
use tracing_subscriber::util::SubscriberInitExt;
|
||||
|
||||
struct 本地时间;
|
||||
impl FormatTime for 本地时间 {
|
||||
fn format_time(&self, w: &mut Writer<'_>) -> fmt::Result {
|
||||
write!(w, "{}", Local::now().format("%Y-%m-%d %H:%M:%S%.3f"))
|
||||
}
|
||||
}
|
||||
|
||||
let format = Format::default()
|
||||
.with_timer(本地时间)
|
||||
.with_target(false)
|
||||
.with_file(true)
|
||||
.with_line_number(true)
|
||||
.with_ansi(true)
|
||||
.compact();
|
||||
|
||||
let 初始级别 = tracing_subscriber::EnvFilter::try_from_default_env()
|
||||
.unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info"));
|
||||
|
||||
let (过滤器层, 句柄) = tracing_subscriber::reload::Layer::new(初始级别);
|
||||
过滤器句柄锁
|
||||
.set(Mutex::new(句柄))
|
||||
.expect("过滤器句柄锁只能设置一次");
|
||||
|
||||
tracing_subscriber::registry()
|
||||
.with(过滤器层)
|
||||
.with(tracing_subscriber::fmt::layer().event_format(format))
|
||||
.init();
|
||||
});
|
||||
}
|
||||
|
||||
mod algorithm_py;
|
||||
mod business_py;
|
||||
@@ -32,10 +108,59 @@ mod kline_py;
|
||||
mod structure_py;
|
||||
mod types_py;
|
||||
|
||||
/// 分型模式 — True 时使用构造时缓存值,False 时从 中 缠K 实时读取
|
||||
#[pyfunction]
|
||||
fn get_分型模式() -> bool {
|
||||
chanlun::structure::fractal_obj::分型模式.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
/// 设置 分型模式
|
||||
#[pyfunction]
|
||||
fn set_分型模式(value: bool) {
|
||||
chanlun::structure::fractal_obj::分型模式.store(value, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
/// 获取当前日志级别 ("trace" / "debug" / "info" / "warn" / "error" / "off")
|
||||
#[pyfunction]
|
||||
fn get_log_level() -> &'static str {
|
||||
级别数字转名称(LOG_LEVEL.load(Ordering::Relaxed))
|
||||
}
|
||||
|
||||
/// 设置日志级别 (不区分大小写: "trace" / "debug" / "info" / "warn" / "error" / "off")
|
||||
///
|
||||
/// 设为 "off" 可完全关闭日志输出。
|
||||
#[pyfunction]
|
||||
fn set_log_level(level: &str) -> PyResult<()> {
|
||||
let 数字 = 级别名称转数字(level).ok_or_else(|| {
|
||||
pyo3::exceptions::PyValueError::new_err(format!(
|
||||
"无效日志级别 '{}',有效值: trace, debug, info, warn, error, off",
|
||||
level
|
||||
))
|
||||
})?;
|
||||
|
||||
let guard = 过滤器句柄锁
|
||||
.get()
|
||||
.ok_or_else(|| pyo3::exceptions::PyRuntimeError::new_err("日志系统尚未初始化"))?;
|
||||
let handle = guard.lock().unwrap();
|
||||
let 名称 = 级别数字转名称(数字);
|
||||
let filter = tracing_subscriber::EnvFilter::new(名称);
|
||||
handle
|
||||
.reload(filter)
|
||||
.map_err(|_| pyo3::exceptions::PyRuntimeError::new_err("切换日志级别失败"))?;
|
||||
|
||||
LOG_LEVEL.store(数字, Ordering::Relaxed);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 缠论技术分析库 — Rust 高性能实现
|
||||
#[pymodule]
|
||||
/// 缠论技术分析库 — Rust 高性能实现
|
||||
fn _chanlun(_py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> {
|
||||
init_tracing();
|
||||
m.add_function(wrap_pyfunction!(get_分型模式, m)?)?;
|
||||
m.add_function(wrap_pyfunction!(set_分型模式, m)?)?;
|
||||
m.add_function(wrap_pyfunction!(get_log_level, m)?)?;
|
||||
m.add_function(wrap_pyfunction!(set_log_level, m)?)?;
|
||||
// 阶段 1: 枚举和基础类型
|
||||
types_py::register(m)?;
|
||||
// 阶段 2: 配置
|
||||
@@ -56,39 +181,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 仍失败");
|
||||
}
|
||||
}
|
||||
|
||||
+117
-323
@@ -25,41 +25,47 @@
|
||||
use pyo3::prelude::*;
|
||||
use pyo3::types::{PyDict, PyType};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::Arc;
|
||||
use std::sync::RwLock;
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
use crate::algorithm_py::{hub_to_py, 中枢Py};
|
||||
use crate::algorithm_py::hub_to_py;
|
||||
use crate::config_py::缠论配置Py;
|
||||
use crate::kline_py::{缠论K线Py, K线Py};
|
||||
use crate::kline_py::{K线Py, bar_to_py, 缠论K线Py};
|
||||
|
||||
// ---- 身份缓存 (弱引用:通过 refcnt 检测存活,仅缓存持有则视为过期) ----
|
||||
|
||||
thread_local! {
|
||||
static FRACTAL_IDENTITY: RwLock<HashMap<usize, Py<分型Py>>> = RwLock::new(HashMap::new());
|
||||
static DASHED_IDENTITY: RwLock<HashMap<usize, Py<虚线Py>>> = RwLock::new(HashMap::new());
|
||||
static SEGFEAT_IDENTITY: RwLock<HashMap<usize, Py<线段特征Py>>> = RwLock::new(HashMap::new());
|
||||
static FEATFRAC_IDENTITY: RwLock<HashMap<usize, Py<特征分型Py>>> = RwLock::new(HashMap::new());
|
||||
}
|
||||
// 使用全局 static 而非 thread_local!,保证跨线程对象标识一致性
|
||||
static FRACTAL_IDENTITY: std::sync::LazyLock<RwLock<HashMap<usize, Py<分型Py>>>> =
|
||||
std::sync::LazyLock::new(|| RwLock::new(HashMap::new()));
|
||||
static DASHED_IDENTITY: std::sync::LazyLock<RwLock<HashMap<usize, Py<虚线Py>>>> =
|
||||
std::sync::LazyLock::new(|| RwLock::new(HashMap::new()));
|
||||
static SEGFEAT_IDENTITY: std::sync::LazyLock<RwLock<HashMap<usize, Py<线段特征Py>>>> =
|
||||
std::sync::LazyLock::new(|| RwLock::new(HashMap::new()));
|
||||
|
||||
pub(crate) fn fractal_to_py(
|
||||
py: Python<'_>,
|
||||
inner: Arc<chanlun::structure::fractal_obj::分型>,
|
||||
) -> Py<分型Py> {
|
||||
let key = Arc::as_ptr(&inner) as usize;
|
||||
if let Some(cached) =
|
||||
FRACTAL_IDENTITY.with(|c| c.read().unwrap().get(&key).map(|p| p.clone_ref(py)))
|
||||
if let Some(cached) = FRACTAL_IDENTITY
|
||||
.read()
|
||||
.unwrap()
|
||||
.get(&key)
|
||||
.map(|p| p.clone_ref(py))
|
||||
{
|
||||
return cached;
|
||||
}
|
||||
// 清理 refcnt==1 的过期条目(仅缓存持有,Python 侧已无引用)
|
||||
FRACTAL_IDENTITY.with(|c| {
|
||||
c.write().unwrap().retain(|_, v| v.get_refcnt(py) > 1);
|
||||
});
|
||||
FRACTAL_IDENTITY
|
||||
.write()
|
||||
.unwrap()
|
||||
.retain(|_, v| v.get_refcnt(py) > 1);
|
||||
let obj = Py::new(py, 分型Py { inner }).unwrap();
|
||||
FRACTAL_IDENTITY.with(|c| {
|
||||
c.write().unwrap().insert(key, obj.clone_ref(py));
|
||||
});
|
||||
FRACTAL_IDENTITY
|
||||
.write()
|
||||
.unwrap()
|
||||
.insert(key, obj.clone_ref(py));
|
||||
obj
|
||||
}
|
||||
|
||||
@@ -68,18 +74,23 @@ pub(crate) fn dashed_to_py(
|
||||
inner: Arc<chanlun::structure::dash_line::虚线>,
|
||||
) -> Py<虚线Py> {
|
||||
let key = Arc::as_ptr(&inner) as usize;
|
||||
if let Some(cached) =
|
||||
DASHED_IDENTITY.with(|c| c.read().unwrap().get(&key).map(|p| p.clone_ref(py)))
|
||||
if let Some(cached) = DASHED_IDENTITY
|
||||
.read()
|
||||
.unwrap()
|
||||
.get(&key)
|
||||
.map(|p| p.clone_ref(py))
|
||||
{
|
||||
return cached;
|
||||
}
|
||||
DASHED_IDENTITY.with(|c| {
|
||||
c.write().unwrap().retain(|_, v| v.get_refcnt(py) > 1);
|
||||
});
|
||||
DASHED_IDENTITY
|
||||
.write()
|
||||
.unwrap()
|
||||
.retain(|_, v| v.get_refcnt(py) > 1);
|
||||
let obj = Py::new(py, 虚线Py { inner }).unwrap();
|
||||
DASHED_IDENTITY.with(|c| {
|
||||
c.write().unwrap().insert(key, obj.clone_ref(py));
|
||||
});
|
||||
DASHED_IDENTITY
|
||||
.write()
|
||||
.unwrap()
|
||||
.insert(key, obj.clone_ref(py));
|
||||
obj
|
||||
}
|
||||
|
||||
@@ -88,40 +99,26 @@ pub(crate) fn segfeat_to_py(
|
||||
inner: Arc<chanlun::structure::segment_feat::线段特征>,
|
||||
) -> Py<线段特征Py> {
|
||||
let key = Arc::as_ptr(&inner) as usize;
|
||||
if let Some(cached) =
|
||||
SEGFEAT_IDENTITY.with(|c| c.read().unwrap().get(&key).map(|p| p.clone_ref(py)))
|
||||
if let Some(cached) = SEGFEAT_IDENTITY
|
||||
.read()
|
||||
.unwrap()
|
||||
.get(&key)
|
||||
.map(|p| p.clone_ref(py))
|
||||
{
|
||||
return cached;
|
||||
}
|
||||
SEGFEAT_IDENTITY.with(|c| {
|
||||
c.write().unwrap().retain(|_, v| v.get_refcnt(py) > 1);
|
||||
});
|
||||
SEGFEAT_IDENTITY
|
||||
.write()
|
||||
.unwrap()
|
||||
.retain(|_, v| v.get_refcnt(py) > 1);
|
||||
let obj = Py::new(py, 线段特征Py { inner }).unwrap();
|
||||
SEGFEAT_IDENTITY.with(|c| {
|
||||
c.write().unwrap().insert(key, obj.clone_ref(py));
|
||||
});
|
||||
SEGFEAT_IDENTITY
|
||||
.write()
|
||||
.unwrap()
|
||||
.insert(key, obj.clone_ref(py));
|
||||
obj
|
||||
}
|
||||
|
||||
pub(crate) fn featfrac_to_py(
|
||||
py: Python<'_>,
|
||||
inner: Arc<chanlun::structure::feat_fractal::特征分型>,
|
||||
) -> Py<特征分型Py> {
|
||||
let key = Arc::as_ptr(&inner) as usize;
|
||||
if let Some(cached) =
|
||||
FEATFRAC_IDENTITY.with(|c| c.read().unwrap().get(&key).map(|p| p.clone_ref(py)))
|
||||
{
|
||||
return cached;
|
||||
}
|
||||
FEATFRAC_IDENTITY.with(|c| {
|
||||
c.write().unwrap().retain(|_, v| v.get_refcnt(py) > 1);
|
||||
});
|
||||
let obj = Py::new(py, 特征分型Py { inner }).unwrap();
|
||||
FEATFRAC_IDENTITY.with(|c| {
|
||||
c.write().unwrap().insert(key, obj.clone_ref(py));
|
||||
});
|
||||
obj
|
||||
}
|
||||
use crate::types_py::{分型结构Py, 相对方向Py, 缺口Py};
|
||||
|
||||
// ========== 分型 ==========
|
||||
@@ -184,19 +181,35 @@ impl 分型Py {
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn 结构(&self) -> 分型结构Py {
|
||||
分型结构Py {
|
||||
inner: self.inner.结构,
|
||||
}
|
||||
fn 结构(&self, py: Python<'_>) -> Py<分型结构Py> {
|
||||
crate::types_py::获取分型结构单例(py, self.inner.结构())
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn 时间戳(&self) -> i64 {
|
||||
self.inner.时间戳
|
||||
self.inner.时间戳()
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn 分型特征值(&self) -> f64 {
|
||||
self.inner.分型特征值()
|
||||
}
|
||||
|
||||
#[getter]
|
||||
/// 构造时缓存的 _结构(不受 分型模式 影响)
|
||||
fn _结构(&self, py: Python<'_>) -> Py<分型结构Py> {
|
||||
crate::types_py::获取分型结构单例(py, self.inner.结构)
|
||||
}
|
||||
|
||||
#[getter]
|
||||
/// 构造时缓存的 _时间戳(不受 分型模式 影响)
|
||||
fn _时间戳(&self) -> i64 {
|
||||
self.inner.时间戳
|
||||
}
|
||||
|
||||
#[getter]
|
||||
/// 构造时缓存的 _分型特征值(不受 分型模式 影响)
|
||||
fn _分型特征值(&self) -> f64 {
|
||||
self.inner.分型特征值
|
||||
}
|
||||
|
||||
@@ -221,12 +234,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),
|
||||
)
|
||||
})
|
||||
}
|
||||
@@ -247,7 +263,7 @@ impl 分型Py {
|
||||
#[getter]
|
||||
fn __dict__(&self, py: Python<'_>) -> PyResult<Py<PyDict>> {
|
||||
let dict = PyDict::new(py);
|
||||
dict.set_item("结构", self.结构())?;
|
||||
dict.set_item("结构", self.结构(py))?;
|
||||
dict.set_item("时间戳", self.时间戳())?;
|
||||
dict.set_item("分型特征值", self.分型特征值())?;
|
||||
dict.set_item("强度", self.强度())?;
|
||||
@@ -259,7 +275,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())
|
||||
@@ -410,11 +426,16 @@ impl 虚线Py {
|
||||
self.inner.模式.read().unwrap().clone()
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn _特征序列_显示(&self) -> bool {
|
||||
#[getter(_特征序列_显示)]
|
||||
fn get_特征序列_显示(&self) -> bool {
|
||||
self.inner._特征序列_显示.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
#[setter(_特征序列_显示)]
|
||||
fn set_特征序列_显示(&mut self, value: bool) {
|
||||
self.inner._特征序列_显示.store(value, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn 特征序列(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
|
||||
let list = pyo3::types::PyList::empty(py);
|
||||
@@ -454,15 +475,13 @@ impl 虚线Py {
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn 前一结束位置(&self) -> Option<Self> {
|
||||
fn 前一结束位置(&self, py: Python<'_>) -> Option<Py<虚线Py>> {
|
||||
self.inner
|
||||
.前一结束位置
|
||||
.read()
|
||||
.unwrap()
|
||||
.as_ref()
|
||||
.map(|d| Self {
|
||||
inner: Arc::clone(d),
|
||||
})
|
||||
.map(|d| dashed_to_py(py, Arc::clone(d)))
|
||||
}
|
||||
|
||||
// ---- 序列 getters ----
|
||||
@@ -471,12 +490,7 @@ impl 虚线Py {
|
||||
fn 基础序列(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
|
||||
let list = pyo3::types::PyList::empty(py);
|
||||
for d in self.inner.基础序列.read().unwrap().iter() {
|
||||
list.append(Py::new(
|
||||
py,
|
||||
Self {
|
||||
inner: Arc::clone(d),
|
||||
},
|
||||
)?)?;
|
||||
list.append(dashed_to_py(py, Arc::clone(d)))?;
|
||||
}
|
||||
Ok(list.into())
|
||||
}
|
||||
@@ -515,12 +529,7 @@ impl 虚线Py {
|
||||
fn 笔序列(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
|
||||
let list = pyo3::types::PyList::empty(py);
|
||||
for d in self.inner.基础序列.read().unwrap().iter() {
|
||||
list.append(Py::new(
|
||||
py,
|
||||
Self {
|
||||
inner: Arc::clone(d),
|
||||
},
|
||||
)?)?;
|
||||
list.append(dashed_to_py(py, Arc::clone(d)))?;
|
||||
}
|
||||
Ok(list.into())
|
||||
}
|
||||
@@ -533,10 +542,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]
|
||||
@@ -569,11 +576,10 @@ impl 虚线Py {
|
||||
let obs_ref = 观察员.borrow();
|
||||
let observer_inner = obs_ref.obs();
|
||||
let result = self.inner.获取普K序列(&observer_inner.普通K线序列);
|
||||
let list = pyo3::types::PyList::empty(观察员.py());
|
||||
let py = 观察员.py();
|
||||
let list = pyo3::types::PyList::empty(py);
|
||||
for k in &result {
|
||||
list.append(K线Py {
|
||||
inner: Arc::clone(k),
|
||||
})?;
|
||||
list.append(bar_to_py(py, Arc::clone(k)))?;
|
||||
}
|
||||
Ok(list.into())
|
||||
}
|
||||
@@ -960,7 +966,7 @@ impl 虚线Py {
|
||||
) -> (bool, String) {
|
||||
let obs = 观察员.borrow();
|
||||
let obs_ref = obs.obs();
|
||||
chanlun::structure::dash_line::虚线::买卖意义(&实线.borrow().inner, &*obs_ref)
|
||||
chanlun::structure::dash_line::虚线::买卖意义(&实线.borrow().inner, &obs_ref)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -974,15 +980,10 @@ impl 虚线Py {
|
||||
/// 文: 分型 — 特征序列的起点分型
|
||||
/// 武: 分型 — 特征序列的终点分型
|
||||
/// 方向: 相对方向 / 高: float / 低: float
|
||||
///
|
||||
/// 方法:
|
||||
/// 添加(虚线) — 向特征序列追加虚线元素
|
||||
/// 删除(虚线) — 从特征序列移除虚线元素
|
||||
/// 基本序列: list[虚线] (基础序列)
|
||||
///
|
||||
/// 类方法:
|
||||
/// 新建(序号, 文, 武, 基础序列?) -> 线段特征
|
||||
/// 静态分析(虚线序列, 配置) -> 线段特征|None
|
||||
/// 获取分型序列(虚线序列, 配置) -> list[线段特征]
|
||||
/// :meth:`静态分析` — 对虚线序列进行静态特征分析,返回 线段特征 列表
|
||||
#[pyclass(name = "线段特征", module = "chanlun._chanlun", from_py_object)]
|
||||
#[derive(Clone)]
|
||||
pub struct 线段特征Py {
|
||||
@@ -991,28 +992,6 @@ pub struct 线段特征Py {
|
||||
|
||||
#[pymethods]
|
||||
impl 线段特征Py {
|
||||
#[new]
|
||||
fn new(
|
||||
标识: String,
|
||||
基础序列: Vec<Py<虚线Py>>,
|
||||
线段方向: &Bound<'_, 相对方向Py>,
|
||||
py: Python<'_>,
|
||||
) -> Self {
|
||||
let rc_list: Vec<Arc<chanlun::structure::dash_line::虚线>> = 基础序列
|
||||
.iter()
|
||||
.map(|d| Arc::clone(&d.bind(py).borrow().inner))
|
||||
.collect();
|
||||
Self {
|
||||
inner: Arc::new(chanlun::structure::segment_feat::线段特征::new(
|
||||
标识,
|
||||
rc_list,
|
||||
线段方向.borrow().inner,
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
// ---- getters ----
|
||||
|
||||
#[getter]
|
||||
fn 序号(&self) -> i64 {
|
||||
self.inner.序号
|
||||
@@ -1020,20 +999,23 @@ impl 线段特征Py {
|
||||
|
||||
#[getter]
|
||||
fn 标识(&self) -> String {
|
||||
self.inner.标识.clone()
|
||||
self.inner.标识.read().unwrap().clone()
|
||||
}
|
||||
|
||||
#[setter]
|
||||
fn set_标识(&self, value: String) {
|
||||
*self.inner.标识.write().unwrap() = value;
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn 线段方向(&self) -> 相对方向Py {
|
||||
相对方向Py {
|
||||
inner: self.inner.线段方向,
|
||||
}
|
||||
fn 线段方向(&self, py: Python<'_>) -> Py<相对方向Py> {
|
||||
crate::types_py::获取相对方向单例(py, self.inner.线段方向)
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn 元素(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
|
||||
fn 基础序列(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
|
||||
let list = pyo3::types::PyList::empty(py);
|
||||
for d in &self.inner.元素 {
|
||||
for d in &self.inner.基础序列 {
|
||||
list.append(dashed_to_py(py, Arc::clone(d)))?;
|
||||
}
|
||||
Ok(list.into())
|
||||
@@ -1047,55 +1029,6 @@ impl 线段特征Py {
|
||||
self.__str__()
|
||||
}
|
||||
|
||||
fn __len__(&self) -> usize {
|
||||
self.inner.元素.len()
|
||||
}
|
||||
|
||||
fn __getitem__(&self, index: isize, py: Python<'_>) -> PyResult<Py<PyAny>> {
|
||||
let len = self.inner.元素.len() as isize;
|
||||
let idx = if index < 0 { index + len } else { index };
|
||||
if idx < 0 || idx >= len {
|
||||
return Err(pyo3::exceptions::PyIndexError::new_err(format!(
|
||||
"线段特征 index {index} out of range (len={len})"
|
||||
)));
|
||||
}
|
||||
let dash = &self.inner.元素[idx as usize];
|
||||
let obj: Py<PyAny> = dashed_to_py(py, Arc::clone(dash)).into();
|
||||
Ok(obj)
|
||||
}
|
||||
|
||||
fn __iter__(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
|
||||
let list = pyo3::types::PyList::empty(py);
|
||||
for d in &self.inner.元素 {
|
||||
list.append(dashed_to_py(py, Arc::clone(d)))?;
|
||||
}
|
||||
list.call_method0("__iter__").map(|iter| iter.into())
|
||||
}
|
||||
|
||||
fn __eq__(&self, other: &Bound<'_, PyAny>) -> bool {
|
||||
if let Ok(other) = other.extract::<PyRef<'_, Self>>() {
|
||||
return Arc::as_ptr(&self.inner) == Arc::as_ptr(&other.inner);
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn __hash__(&self) -> u64 {
|
||||
Arc::as_ptr(&self.inner) as u64
|
||||
}
|
||||
|
||||
/// pandas 兼容 — 返回关键标量字段构成的字典
|
||||
#[getter]
|
||||
fn __dict__(&self, py: Python<'_>) -> PyResult<Py<PyDict>> {
|
||||
let dict = PyDict::new(py);
|
||||
dict.set_item("序号", self.序号())?;
|
||||
dict.set_item("标识", self.标识())?;
|
||||
dict.set_item("线段方向", self.线段方向())?;
|
||||
dict.set_item("图表标题", self.图表标题())?;
|
||||
Ok(dict.into())
|
||||
}
|
||||
|
||||
// ---- instance methods ----
|
||||
|
||||
#[getter]
|
||||
/// :return: 图表标题
|
||||
fn 图表标题(&self) -> String {
|
||||
@@ -1116,10 +1049,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]
|
||||
@@ -1134,47 +1065,8 @@ impl 线段特征Py {
|
||||
self.inner.低()
|
||||
}
|
||||
|
||||
/// :param 待添加虚线: 待添加的虚线
|
||||
fn 添加(&mut self, 待添加虚线: &Bound<'_, 虚线Py>) -> PyResult<()> {
|
||||
let inner = Arc::make_mut(&mut self.inner);
|
||||
inner
|
||||
.添加(Arc::clone(&待添加虚线.borrow().inner))
|
||||
.map_err(|e| pyo3::exceptions::PyValueError::new_err(e))
|
||||
}
|
||||
|
||||
/// :param 待删除虚线: 待删除的虚线
|
||||
fn 删除(&mut self, 待删除虚线: &Bound<'_, 虚线Py>) -> PyResult<()> {
|
||||
let inner = Arc::make_mut(&mut self.inner);
|
||||
inner
|
||||
.删除(&Arc::clone(&待删除虚线.borrow().inner))
|
||||
.map_err(|e| pyo3::exceptions::PyValueError::new_err(e))
|
||||
}
|
||||
|
||||
// ---- classmethods ----
|
||||
|
||||
#[classmethod]
|
||||
/// :param 虚线序列: 基础虚线列表
|
||||
fn 新建(
|
||||
_cls: &Bound<'_, PyType>,
|
||||
虚线序列: Vec<Py<虚线Py>>,
|
||||
线段方向: &Bound<'_, 相对方向Py>,
|
||||
py: Python<'_>,
|
||||
) -> Self {
|
||||
let rc_list: Vec<Arc<chanlun::structure::dash_line::虚线>> = 虚线序列
|
||||
.iter()
|
||||
.map(|d| Arc::clone(&d.bind(py).borrow().inner))
|
||||
.collect();
|
||||
Self {
|
||||
inner: Arc::new(chanlun::structure::segment_feat::线段特征::新建(
|
||||
rc_list,
|
||||
线段方向.borrow().inner,
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
#[classmethod]
|
||||
#[pyo3(signature = (虚线序列, 线段方向, 四象, 是否忽视 = false))]
|
||||
/// 静态分析虚线序列,生成特征序列
|
||||
fn 静态分析(
|
||||
_cls: &Bound<'_, PyType>,
|
||||
虚线序列: Vec<Py<虚线Py>>,
|
||||
@@ -1182,119 +1074,22 @@ impl 线段特征Py {
|
||||
四象: &str,
|
||||
是否忽视: bool,
|
||||
py: Python<'_>,
|
||||
) -> Vec<Self> {
|
||||
) -> PyResult<Py<PyAny>> {
|
||||
let rc_list: Vec<Arc<chanlun::structure::dash_line::虚线>> = 虚线序列
|
||||
.iter()
|
||||
.map(|d| Arc::clone(&d.bind(py).borrow().inner))
|
||||
.collect();
|
||||
chanlun::structure::segment_feat::线段特征::静态分析(
|
||||
let result = chanlun::structure::segment_feat::线段特征::静态分析(
|
||||
&rc_list,
|
||||
线段方向.borrow().inner,
|
||||
四象,
|
||||
是否忽视,
|
||||
)
|
||||
.into_iter()
|
||||
.map(|inner| Self { inner })
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[classmethod]
|
||||
/// 从特征序列提取特征分型序列
|
||||
fn 获取分型序列(
|
||||
_cls: &Bound<'_, PyType>,
|
||||
特征序列: Vec<Py<Self>>,
|
||||
py: Python<'_>,
|
||||
) -> Vec<Py<特征分型Py>> {
|
||||
let rc_list: Vec<Arc<chanlun::structure::segment_feat::线段特征>> = 特征序列
|
||||
.iter()
|
||||
.map(|s| Arc::clone(&s.bind(py).borrow().inner))
|
||||
.collect();
|
||||
chanlun::structure::segment_feat::线段特征::获取分型序列(&rc_list)
|
||||
.into_iter()
|
||||
.map(|inner| featfrac_to_py(py, Arc::new(inner)))
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
// ========== 特征分型 ==========
|
||||
|
||||
/// 特征分型 — 线段特征序列中的分型节点。
|
||||
///
|
||||
/// 属性 (只读):
|
||||
/// 左: 线段特征|None / 中: 线段特征 / 右: 线段特征|None
|
||||
/// 结构: 分型结构 — 顶/底分型判定结果
|
||||
#[pyclass(name = "特征分型", module = "chanlun._chanlun", from_py_object)]
|
||||
#[derive(Clone)]
|
||||
pub struct 特征分型Py {
|
||||
pub(crate) inner: Arc<chanlun::structure::feat_fractal::特征分型>,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl 特征分型Py {
|
||||
#[new]
|
||||
fn new(
|
||||
左: &Bound<'_, 线段特征Py>,
|
||||
中: &Bound<'_, 线段特征Py>,
|
||||
右: &Bound<'_, 线段特征Py>,
|
||||
结构: &Bound<'_, 分型结构Py>,
|
||||
) -> Self {
|
||||
Self {
|
||||
inner: Arc::new(chanlun::structure::feat_fractal::特征分型::new(
|
||||
Arc::clone(&左.borrow().inner),
|
||||
Arc::clone(&中.borrow().inner),
|
||||
Arc::clone(&右.borrow().inner),
|
||||
结构.borrow().inner,
|
||||
)),
|
||||
);
|
||||
let list = pyo3::types::PyList::empty(py);
|
||||
for sf in result {
|
||||
list.append(segfeat_to_py(py, sf))?;
|
||||
}
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn 左(&self, py: Python<'_>) -> Py<线段特征Py> {
|
||||
segfeat_to_py(py, Arc::clone(&self.inner.左))
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn 中(&self, py: Python<'_>) -> Py<线段特征Py> {
|
||||
segfeat_to_py(py, Arc::clone(&self.inner.中))
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn 右(&self, py: Python<'_>) -> Py<线段特征Py> {
|
||||
segfeat_to_py(py, Arc::clone(&self.inner.右))
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn 结构(&self) -> 分型结构Py {
|
||||
分型结构Py {
|
||||
inner: self.inner.结构,
|
||||
}
|
||||
}
|
||||
|
||||
/// pandas 兼容 — 返回关键标量字段构成的字典
|
||||
#[getter]
|
||||
fn __dict__(&self, py: Python<'_>) -> PyResult<Py<PyDict>> {
|
||||
let dict = PyDict::new(py);
|
||||
dict.set_item("结构", self.结构())?;
|
||||
Ok(dict.into())
|
||||
}
|
||||
|
||||
fn __str__(&self) -> String {
|
||||
format!("{}", self.inner)
|
||||
}
|
||||
|
||||
fn __repr__(&self) -> String {
|
||||
self.__str__()
|
||||
}
|
||||
|
||||
fn __eq__(&self, other: &Bound<'_, PyAny>) -> bool {
|
||||
if let Ok(other) = other.extract::<PyRef<'_, Self>>() {
|
||||
return Arc::as_ptr(&self.inner) == Arc::as_ptr(&other.inner);
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn __hash__(&self) -> u64 {
|
||||
Arc::as_ptr(&self.inner) as u64
|
||||
Ok(list.into())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1302,6 +1097,5 @@ pub fn register(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
||||
m.add_class::<分型Py>()?;
|
||||
m.add_class::<虚线Py>()?;
|
||||
m.add_class::<线段特征Py>()?;
|
||||
m.add_class::<特征分型Py>()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
+202
-93
@@ -22,9 +22,78 @@
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Mutex;
|
||||
|
||||
use pyo3::basic::CompareOp;
|
||||
use pyo3::prelude::*;
|
||||
use pyo3::types::{PyDict, PyType};
|
||||
use pyo3::types::{PyBool, PyDict, PyType};
|
||||
|
||||
// ========== 单例缓存 ==========
|
||||
|
||||
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::分型结构::散),
|
||||
] {
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// ========== 买卖点类型 ==========
|
||||
|
||||
@@ -54,18 +123,22 @@ impl 买卖点类型Py {
|
||||
self.inner.to_string()
|
||||
}
|
||||
|
||||
fn __richcmp__(&self, other: &Bound<'_, PyAny>, op: CompareOp) -> PyResult<bool> {
|
||||
fn __richcmp__(&self, other: &Bound<'_, PyAny>, op: CompareOp) -> PyResult<Py<PyAny>> {
|
||||
let py = other.py();
|
||||
// 比较:先尝试字符串(Python Enum(str)),再同类型,再 name 属性
|
||||
let eq = if let Ok(s) = other.extract::<String>() {
|
||||
self.inner.to_string() == s
|
||||
} else if let Ok(other) = other.extract::<PyRef<'_, Self>>() {
|
||||
self.inner == other.inner
|
||||
} else if let Ok(py_name) = other.getattr("name").and_then(|n| n.extract::<String>()) {
|
||||
self.inner.to_string() == py_name
|
||||
} else {
|
||||
return Err(pyo3::exceptions::PyNotImplementedError::new_err(""));
|
||||
return Ok(py.NotImplemented());
|
||||
};
|
||||
match op {
|
||||
CompareOp::Eq => Ok(eq),
|
||||
CompareOp::Ne => Ok(!eq),
|
||||
_ => Err(pyo3::exceptions::PyNotImplementedError::new_err("")),
|
||||
CompareOp::Eq => Ok(PyBool::new(py, eq).as_any().to_owned().unbind()),
|
||||
CompareOp::Ne => Ok(PyBool::new(py, !eq).as_any().to_owned().unbind()),
|
||||
_ => Ok(py.NotImplemented()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,19 +207,44 @@ impl 相对方向Py {
|
||||
format!("{}", self.inner)
|
||||
}
|
||||
|
||||
fn __richcmp__(&self, other: &Bound<'_, PyAny>, op: CompareOp) -> PyResult<bool> {
|
||||
let Ok(other) = other.extract::<PyRef<'_, Self>>() else {
|
||||
return Err(pyo3::exceptions::PyNotImplementedError::new_err(""));
|
||||
};
|
||||
let eq = self.inner == other.inner;
|
||||
match op {
|
||||
CompareOp::Eq => Ok(eq),
|
||||
CompareOp::Ne => Ok(!eq),
|
||||
CompareOp::Lt => Ok((self.inner as u8) < (other.inner as u8)),
|
||||
CompareOp::Le => Ok((self.inner as u8) <= (other.inner as u8)),
|
||||
CompareOp::Gt => Ok((self.inner as u8) > (other.inner as u8)),
|
||||
CompareOp::Ge => Ok((self.inner as u8) >= (other.inner as u8)),
|
||||
fn __richcmp__(&self, other: &Bound<'_, PyAny>, op: CompareOp) -> PyResult<Py<PyAny>> {
|
||||
let py = other.py();
|
||||
// 同类型比较
|
||||
if let Ok(other) = other.extract::<PyRef<'_, Self>>() {
|
||||
let eq = self.inner == other.inner;
|
||||
return Ok(match op {
|
||||
CompareOp::Eq => PyBool::new(py, eq).as_any().to_owned().unbind(),
|
||||
CompareOp::Ne => PyBool::new(py, !eq).as_any().to_owned().unbind(),
|
||||
CompareOp::Lt => PyBool::new(py, (self.inner as u8) < (other.inner as u8))
|
||||
.as_any()
|
||||
.to_owned()
|
||||
.unbind(),
|
||||
CompareOp::Le => PyBool::new(py, (self.inner as u8) <= (other.inner as u8))
|
||||
.as_any()
|
||||
.to_owned()
|
||||
.unbind(),
|
||||
CompareOp::Gt => PyBool::new(py, (self.inner as u8) > (other.inner as u8))
|
||||
.as_any()
|
||||
.to_owned()
|
||||
.unbind(),
|
||||
CompareOp::Ge => PyBool::new(py, (self.inner as u8) >= (other.inner as u8))
|
||||
.as_any()
|
||||
.to_owned()
|
||||
.unbind(),
|
||||
});
|
||||
}
|
||||
// 跨模块比较:通过 name 属性匹配 Python Enum(如 chan.chan.相对方向)
|
||||
if let Ok(py_name) = other.getattr("name").and_then(|n| n.extract::<String>()) {
|
||||
let self_name = format!("{:?}", self.inner);
|
||||
let eq = self_name == py_name;
|
||||
return Ok(match op {
|
||||
CompareOp::Eq => PyBool::new(py, eq).as_any().to_owned().unbind(),
|
||||
CompareOp::Ne => PyBool::new(py, !eq).as_any().to_owned().unbind(),
|
||||
_ => py.NotImplemented(),
|
||||
});
|
||||
}
|
||||
// 回退:返回 Python NotImplemented
|
||||
Ok(py.NotImplemented())
|
||||
}
|
||||
|
||||
fn __hash__(&self) -> u64 {
|
||||
@@ -154,10 +252,8 @@ impl 相对方向Py {
|
||||
}
|
||||
|
||||
/// 返回方向的对立面(向上↔向下, 缺口↔反向缺口, 衔接↔反向衔接)。
|
||||
fn 翻转(&self) -> Self {
|
||||
Self {
|
||||
inner: self.inner.翻转(),
|
||||
}
|
||||
fn 翻转(&self, py: Python<'_>) -> Py<Self> {
|
||||
获取相对方向单例(py, self.inner.翻转())
|
||||
}
|
||||
|
||||
/// 判断是否为向上方向(向上/向上缺口/衔接向上)
|
||||
@@ -210,10 +306,11 @@ impl 相对方向Py {
|
||||
#[classmethod]
|
||||
fn 分析(
|
||||
_cls: &Bound<'_, PyType>, 前高: f64, 前低: f64, 后高: f64, 后低: f64
|
||||
) -> Self {
|
||||
Self {
|
||||
inner: chanlun::types::相对方向::分析(前高, 前低, 后高, 后低),
|
||||
}
|
||||
) -> Py<Self> {
|
||||
获取相对方向单例(
|
||||
_cls.py(),
|
||||
chanlun::types::相对方向::分析(前高, 前低, 后高, 后低),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -247,19 +344,43 @@ impl 分型结构Py {
|
||||
self.inner.to_string()
|
||||
}
|
||||
|
||||
fn __richcmp__(&self, other: &Bound<'_, PyAny>, op: CompareOp) -> PyResult<bool> {
|
||||
let Ok(other) = other.extract::<PyRef<'_, Self>>() else {
|
||||
return Err(pyo3::exceptions::PyNotImplementedError::new_err(""));
|
||||
};
|
||||
let eq = self.inner == other.inner;
|
||||
match op {
|
||||
CompareOp::Eq => Ok(eq),
|
||||
CompareOp::Ne => Ok(!eq),
|
||||
CompareOp::Lt => Ok((self.inner as u8) < (other.inner as u8)),
|
||||
CompareOp::Le => Ok((self.inner as u8) <= (other.inner as u8)),
|
||||
CompareOp::Gt => Ok((self.inner as u8) > (other.inner as u8)),
|
||||
CompareOp::Ge => Ok((self.inner as u8) >= (other.inner as u8)),
|
||||
fn __richcmp__(&self, other: &Bound<'_, PyAny>, op: CompareOp) -> PyResult<Py<PyAny>> {
|
||||
let py = other.py();
|
||||
// 同类型比较
|
||||
if let Ok(other) = other.extract::<PyRef<'_, Self>>() {
|
||||
let eq = self.inner == other.inner;
|
||||
return Ok(match op {
|
||||
CompareOp::Eq => PyBool::new(py, eq).as_any().to_owned().unbind(),
|
||||
CompareOp::Ne => PyBool::new(py, !eq).as_any().to_owned().unbind(),
|
||||
CompareOp::Lt => PyBool::new(py, (self.inner as u8) < (other.inner as u8))
|
||||
.as_any()
|
||||
.to_owned()
|
||||
.unbind(),
|
||||
CompareOp::Le => PyBool::new(py, (self.inner as u8) <= (other.inner as u8))
|
||||
.as_any()
|
||||
.to_owned()
|
||||
.unbind(),
|
||||
CompareOp::Gt => PyBool::new(py, (self.inner as u8) > (other.inner as u8))
|
||||
.as_any()
|
||||
.to_owned()
|
||||
.unbind(),
|
||||
CompareOp::Ge => PyBool::new(py, (self.inner as u8) >= (other.inner as u8))
|
||||
.as_any()
|
||||
.to_owned()
|
||||
.unbind(),
|
||||
});
|
||||
}
|
||||
// 跨模块比较:通过 name 属性匹配 Python Enum(如 chan.chan.分型结构)
|
||||
if let Ok(py_name) = other.getattr("name").and_then(|n| n.extract::<String>()) {
|
||||
let self_name = self.inner.to_string();
|
||||
let eq = self_name == py_name;
|
||||
return Ok(match op {
|
||||
CompareOp::Eq => PyBool::new(py, eq).as_any().to_owned().unbind(),
|
||||
CompareOp::Ne => PyBool::new(py, !eq).as_any().to_owned().unbind(),
|
||||
_ => py.NotImplemented(),
|
||||
});
|
||||
}
|
||||
Ok(py.NotImplemented())
|
||||
}
|
||||
|
||||
fn __hash__(&self) -> u64 {
|
||||
@@ -306,41 +427,17 @@ impl 分型结构Py {
|
||||
let (中高, 中低) = get_hl(中)?;
|
||||
let (右高, 右低) = get_hl(右)?;
|
||||
|
||||
let 左中关系 = chanlun::types::相对方向::分析(左高, 左低, 中高, 中低);
|
||||
let 中右关系 = chanlun::types::相对方向::分析(中高, 中低, 右高, 右低);
|
||||
|
||||
let 向上类 = |d: chanlun::types::相对方向| d.是否向上();
|
||||
let 向下类 = |d: chanlun::types::相对方向| d.是否向下();
|
||||
|
||||
let result = match (左中关系, 中右关系) {
|
||||
(d1, d2) if matches!(d1, chanlun::types::相对方向::顺) && !忽视顺序包含 => {
|
||||
panic!("顺序包含: {:?} {:?}", d1, d2);
|
||||
}
|
||||
(d1, d2) if matches!(d2, chanlun::types::相对方向::顺) && !忽视顺序包含 => {
|
||||
panic!("顺序包含: {:?} {:?}", d1, d2);
|
||||
}
|
||||
(a, b) if 向上类(a) && 向上类(b) => chanlun::types::分型结构::上,
|
||||
(a, b) if 向上类(a) && 向下类(b) => chanlun::types::分型结构::顶,
|
||||
(a, chanlun::types::相对方向::逆) if 向上类(a) && 可以逆序包含 => {
|
||||
chanlun::types::分型结构::上
|
||||
}
|
||||
(a, b) if 向下类(a) && 向上类(b) => chanlun::types::分型结构::底,
|
||||
(a, b) if 向下类(a) && 向下类(b) => chanlun::types::分型结构::下,
|
||||
(a, chanlun::types::相对方向::逆) if 向下类(a) && 可以逆序包含 => {
|
||||
chanlun::types::分型结构::下
|
||||
}
|
||||
(chanlun::types::相对方向::逆, a) if 向上类(a) && 可以逆序包含 => {
|
||||
chanlun::types::分型结构::底
|
||||
}
|
||||
(chanlun::types::相对方向::逆, a) if 向下类(a) && 可以逆序包含 => {
|
||||
chanlun::types::分型结构::顶
|
||||
}
|
||||
(chanlun::types::相对方向::逆, chanlun::types::相对方向::逆) if 可以逆序包含 => {
|
||||
chanlun::types::分型结构::散
|
||||
}
|
||||
_ => return Ok(None),
|
||||
};
|
||||
Ok(Some(Self { inner: result }))
|
||||
Ok(chanlun::types::分型结构::分析_内部(
|
||||
左高,
|
||||
左低,
|
||||
中高,
|
||||
中低,
|
||||
右高,
|
||||
右低,
|
||||
可以逆序包含,
|
||||
忽视顺序包含,
|
||||
)
|
||||
.map(|inner| Self { inner }))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -383,23 +480,35 @@ impl 缺口Py {
|
||||
format!("{}", self.inner)
|
||||
}
|
||||
|
||||
fn __richcmp__(&self, other: &Bound<'_, PyAny>, op: CompareOp) -> PyResult<bool> {
|
||||
fn __richcmp__(&self, other: &Bound<'_, PyAny>, op: CompareOp) -> PyResult<Py<PyAny>> {
|
||||
let py = other.py();
|
||||
let Ok(other) = other.extract::<PyRef<'_, Self>>() else {
|
||||
return Err(pyo3::exceptions::PyNotImplementedError::new_err(""));
|
||||
return Ok(py.NotImplemented());
|
||||
};
|
||||
let to_bool = |b: bool| PyBool::new(py, b).as_any().to_owned().unbind();
|
||||
match op {
|
||||
CompareOp::Eq => Ok(self.inner.高 == other.inner.高 && self.inner.低 == other.inner.低),
|
||||
CompareOp::Ne => {
|
||||
Ok(!(self.inner.高 == other.inner.高 && self.inner.低 == other.inner.低))
|
||||
}
|
||||
CompareOp::Lt => Ok(self.inner.高 < other.inner.高
|
||||
|| (self.inner.高 == other.inner.高 && self.inner.低 < other.inner.低)),
|
||||
CompareOp::Le => Ok(self.inner.高 < other.inner.高
|
||||
|| (self.inner.高 == other.inner.高 && self.inner.低 <= other.inner.低)),
|
||||
CompareOp::Gt => Ok(self.inner.高 > other.inner.高
|
||||
|| (self.inner.高 == other.inner.高 && self.inner.低 > other.inner.低)),
|
||||
CompareOp::Ge => Ok(self.inner.高 > other.inner.高
|
||||
|| (self.inner.高 == other.inner.高 && self.inner.低 >= other.inner.低)),
|
||||
CompareOp::Eq => Ok(to_bool(
|
||||
self.inner.高 == other.inner.高 && self.inner.低 == other.inner.低,
|
||||
)),
|
||||
CompareOp::Ne => Ok(to_bool(
|
||||
!(self.inner.高 == other.inner.高 && self.inner.低 == other.inner.低),
|
||||
)),
|
||||
CompareOp::Lt => Ok(to_bool(
|
||||
self.inner.高 < other.inner.高
|
||||
|| (self.inner.高 == other.inner.高 && self.inner.低 < other.inner.低),
|
||||
)),
|
||||
CompareOp::Le => Ok(to_bool(
|
||||
self.inner.高 < other.inner.高
|
||||
|| (self.inner.高 == other.inner.高 && self.inner.低 <= other.inner.低),
|
||||
)),
|
||||
CompareOp::Gt => Ok(to_bool(
|
||||
self.inner.高 > other.inner.高
|
||||
|| (self.inner.高 == other.inner.高 && self.inner.低 > other.inner.低),
|
||||
)),
|
||||
CompareOp::Ge => Ok(to_bool(
|
||||
self.inner.高 > other.inner.高
|
||||
|| (self.inner.高 == other.inner.高 && self.inner.低 >= other.inner.低),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -477,7 +586,7 @@ pub fn register(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
||||
// 买卖点类型 class attributes (singleton instances)
|
||||
let py = m.py();
|
||||
let bsp_class = m.getattr("买卖点类型")?;
|
||||
let bsp_class = bsp_class.downcast_into::<PyType>()?;
|
||||
let bsp_class = bsp_class.cast_into::<PyType>()?;
|
||||
|
||||
let variants: &[(&str, chanlun::types::买卖点类型)] = &[
|
||||
("一买", chanlun::types::买卖点类型::一买),
|
||||
@@ -500,7 +609,7 @@ pub fn register(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
||||
("T3B卖", chanlun::types::买卖点类型::T3B卖),
|
||||
];
|
||||
|
||||
let mut bsp_members = PyDict::new(py);
|
||||
let bsp_members = PyDict::new(py);
|
||||
for (name, value) in variants {
|
||||
let instance = Py::new(py, 买卖点类型Py { inner: *value })?;
|
||||
bsp_class.setattr(*name, instance.clone_ref(py))?;
|
||||
@@ -509,7 +618,7 @@ pub fn register(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
||||
bsp_class.setattr("__members__", bsp_members)?;
|
||||
|
||||
// 相对方向 class attributes
|
||||
let dir_class = m.getattr("相对方向")?.downcast_into::<PyType>()?.clone();
|
||||
let dir_class = m.getattr("相对方向")?.cast_into::<PyType>()?.clone();
|
||||
let dir_variants: &[(&str, chanlun::types::相对方向)] = &[
|
||||
("向上", chanlun::types::相对方向::向上),
|
||||
("向下", chanlun::types::相对方向::向下),
|
||||
@@ -522,7 +631,7 @@ pub fn register(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
||||
("同", chanlun::types::相对方向::同),
|
||||
];
|
||||
|
||||
let mut dir_members = PyDict::new(py);
|
||||
let dir_members = PyDict::new(py);
|
||||
for (name, value) in dir_variants {
|
||||
let instance = Py::new(py, 相对方向Py { inner: *value })?;
|
||||
dir_class.setattr(*name, instance.clone_ref(py))?;
|
||||
@@ -531,7 +640,7 @@ pub fn register(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
||||
dir_class.setattr("__members__", dir_members)?;
|
||||
|
||||
// 分型结构 class attributes
|
||||
let frac_class = m.getattr("分型结构")?.downcast_into::<PyType>()?.clone();
|
||||
let frac_class = m.getattr("分型结构")?.cast_into::<PyType>()?.clone();
|
||||
let frac_variants: &[(&str, chanlun::types::分型结构)] = &[
|
||||
("上", chanlun::types::分型结构::上),
|
||||
("下", chanlun::types::分型结构::下),
|
||||
@@ -540,7 +649,7 @@ pub fn register(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
||||
("散", chanlun::types::分型结构::散),
|
||||
];
|
||||
|
||||
let mut frac_members = PyDict::new(py);
|
||||
let frac_members = PyDict::new(py);
|
||||
for (name, value) in frac_variants {
|
||||
let instance = Py::new(py, 分型结构Py { inner: *value })?;
|
||||
frac_class.setattr(*name, instance.clone_ref(py))?;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@@ -0,0 +1,25 @@
|
||||
"""pyo3_test_helpers — 可复用的 PyO3 测试工具包。
|
||||
|
||||
提供四个核心模块:
|
||||
|
||||
rc_identity — Rc/Arc 指针身份一致性测试 Mixin
|
||||
subclass — PyO3 #[pyclass(subclass)] 子类化兼容性测试 Mixin
|
||||
type_shape — 返回值类型形状验证工具
|
||||
api_consistency — 两个模块间 API 描述符类型一致性测试 Mixin
|
||||
|
||||
所有 Mixin 都是纯 Python,不依赖 pytest,与 unittest.TestCase 配合使用。
|
||||
下游项目复制此目录即可复用。
|
||||
"""
|
||||
|
||||
from .api_consistency import ApiConsistencyMixin
|
||||
from .rc_identity import RcIdentityMixin
|
||||
from .subclass import PyO3SubclassMixin
|
||||
from .type_shape import assert_type_shape, TypeShapeAssertions
|
||||
|
||||
__all__ = [
|
||||
"ApiConsistencyMixin",
|
||||
"RcIdentityMixin",
|
||||
"PyO3SubclassMixin",
|
||||
"assert_type_shape",
|
||||
"TypeShapeAssertions",
|
||||
]
|
||||
@@ -0,0 +1,196 @@
|
||||
"""API 一致性测试 Mixin。
|
||||
|
||||
验证两个模块中同名类的公开成员描述符类型一致。
|
||||
典型用途:对比 Python 参考实现 (chan.py) 与 Rust/PyO3 移植 (chanlun) 的 API 兼容性。
|
||||
|
||||
用法::
|
||||
|
||||
class TestApi一致性(ApiConsistencyMixin, unittest.TestCase):
|
||||
reference_module = mylib.ref # Python 参考实现
|
||||
target_module = mylib # Rust/PyO3 移植
|
||||
|
||||
# 可选: 已知差异(不会报错)
|
||||
known_missing_in_target = {
|
||||
"SomeClass": {"old_deprecated_method"},
|
||||
}
|
||||
known_descriptor_diffs = {
|
||||
# (class_name, member, ref_type, target_type)
|
||||
}
|
||||
|
||||
# 可选: 成员名过滤(匹配则跳过,支持前缀用 "prefix_" 表示)
|
||||
noise_filters = ["model_", "parse_", "from_orm"]
|
||||
"""
|
||||
|
||||
import unittest
|
||||
|
||||
|
||||
def _classify_member(cls, attr_name):
|
||||
"""返回描述符类型: property / classmethod / staticmethod / regular_method / None(data)."""
|
||||
# 优先检查元类字典中的描述符
|
||||
for klass in type(cls).__mro__:
|
||||
if attr_name in klass.__dict__:
|
||||
raw = klass.__dict__[attr_name]
|
||||
if isinstance(raw, property):
|
||||
return "property"
|
||||
elif isinstance(raw, classmethod):
|
||||
return "classmethod"
|
||||
elif isinstance(raw, staticmethod):
|
||||
return "staticmethod"
|
||||
break
|
||||
try:
|
||||
attr = getattr(cls, attr_name)
|
||||
except Exception:
|
||||
return None
|
||||
if callable(attr):
|
||||
return "regular_method"
|
||||
return None
|
||||
|
||||
|
||||
def _is_noise(name, filters):
|
||||
for pat in filters:
|
||||
if pat == name:
|
||||
return True
|
||||
if pat.endswith("_") and name.startswith(pat):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def _get_classes(mod):
|
||||
"""获取模块中所有公开的 type."""
|
||||
return {n: getattr(mod, n) for n in dir(mod) if not n.startswith("_") and isinstance(getattr(mod, n), type)}
|
||||
|
||||
|
||||
class ApiConsistencyMixin:
|
||||
"""API 一致性测试 Mixin。
|
||||
|
||||
子类必须定义:
|
||||
reference_module: 参考模块 (Python 实现)
|
||||
target_module: 目标模块 (Rust/PyO3 移植)
|
||||
|
||||
子类可选定义:
|
||||
known_missing_in_target: dict[str, set[str]] — 已知 target 中缺失的成员
|
||||
known_descriptor_diffs: set[tuple] — 已知描述符类型差异
|
||||
noise_filters: list[str] — 噪音成员名过滤
|
||||
"""
|
||||
|
||||
reference_module = None
|
||||
target_module = None
|
||||
known_missing_in_target: dict = {}
|
||||
known_descriptor_diffs: set = set()
|
||||
noise_filters: list = []
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
if cls.reference_module is None or cls.target_module is None:
|
||||
raise unittest.SkipTest(f"{cls.__name__} 未定义 reference_module / target_module")
|
||||
|
||||
# ---- 描述符类型一致性 ----
|
||||
|
||||
def test_共有成员描述符类型一致(self):
|
||||
"""同名类的同名成员,描述符类型 (property/classmethod/staticmethod/regular) 一致."""
|
||||
ref_classes = _get_classes(self.reference_module)
|
||||
tgt_classes = _get_classes(self.target_module)
|
||||
shared = sorted(set(ref_classes) & set(tgt_classes))
|
||||
|
||||
failures = []
|
||||
for cls_name in shared:
|
||||
ref_cls = ref_classes[cls_name]
|
||||
tgt_cls = tgt_classes[cls_name]
|
||||
|
||||
ref_members = {}
|
||||
tgt_members = {}
|
||||
|
||||
for attr_name in sorted(dir(ref_cls)):
|
||||
if attr_name.startswith("_") or _is_noise(attr_name, self.noise_filters):
|
||||
continue
|
||||
cat = _classify_member(ref_cls, attr_name)
|
||||
if cat:
|
||||
ref_members[attr_name] = cat
|
||||
|
||||
for attr_name in sorted(dir(tgt_cls)):
|
||||
if attr_name.startswith("_") or _is_noise(attr_name, self.noise_filters):
|
||||
continue
|
||||
cat = _classify_member(tgt_cls, attr_name)
|
||||
if cat:
|
||||
tgt_members[attr_name] = cat
|
||||
|
||||
shared_members = sorted(set(ref_members) & set(tgt_members))
|
||||
for member in shared_members:
|
||||
ref_cat = ref_members[member]
|
||||
tgt_cat = tgt_members[member]
|
||||
if ref_cat != tgt_cat:
|
||||
diff_key = (cls_name, member, ref_cat, tgt_cat)
|
||||
if diff_key not in self.known_descriptor_diffs:
|
||||
failures.append(f"{cls_name}.{member}: ref={ref_cat}, tgt={tgt_cat}")
|
||||
|
||||
if failures:
|
||||
self.fail("描述符类型不一致:\n " + "\n ".join(failures))
|
||||
|
||||
# ---- 缺失成员检查 ----
|
||||
|
||||
def test_参考模块成员在目标模块中存在(self):
|
||||
"""chan 中的关键公开成员在 chanlun 中均有对应."""
|
||||
ref_classes = _get_classes(self.reference_module)
|
||||
tgt_classes = _get_classes(self.target_module)
|
||||
shared = sorted(set(ref_classes) & set(tgt_classes))
|
||||
|
||||
failures = []
|
||||
for cls_name in shared:
|
||||
if cls_name not in self.known_missing_in_target:
|
||||
continue
|
||||
ref_cls = ref_classes[cls_name]
|
||||
tgt_cls = tgt_classes[cls_name]
|
||||
|
||||
expected_missing = self.known_missing_in_target.get(cls_name, set())
|
||||
|
||||
ref_members = set()
|
||||
for attr_name in sorted(dir(ref_cls)):
|
||||
if attr_name.startswith("_") or _is_noise(attr_name, self.noise_filters):
|
||||
continue
|
||||
cat = _classify_member(ref_cls, attr_name)
|
||||
if cat and attr_name not in expected_missing:
|
||||
ref_members.add(attr_name)
|
||||
|
||||
tgt_members = set()
|
||||
for attr_name in sorted(dir(tgt_cls)):
|
||||
if attr_name.startswith("_") or _is_noise(attr_name, self.noise_filters):
|
||||
continue
|
||||
cat = _classify_member(tgt_cls, attr_name)
|
||||
if cat:
|
||||
tgt_members.add(attr_name)
|
||||
|
||||
missing = ref_members - tgt_members - expected_missing
|
||||
for member in sorted(missing):
|
||||
failures.append(f"{cls_name}.{member}: ref={_classify_member(ref_cls, member)}, tgt=未导出")
|
||||
|
||||
if failures:
|
||||
self.fail("参考模块中的成员在目标模块中缺失:\n " + "\n ".join(failures))
|
||||
|
||||
# ---- 方法可调用性 ----
|
||||
|
||||
def test_共有方法均可调用(self):
|
||||
"""所有共有 regular_method 在两边都是 callable."""
|
||||
ref_classes = _get_classes(self.reference_module)
|
||||
tgt_classes = _get_classes(self.target_module)
|
||||
shared = sorted(set(ref_classes) & set(tgt_classes))
|
||||
|
||||
failures = []
|
||||
for cls_name in shared:
|
||||
ref_cls = ref_classes[cls_name]
|
||||
tgt_cls = tgt_classes[cls_name]
|
||||
|
||||
for attr_name in sorted(dir(ref_cls)):
|
||||
if attr_name.startswith("_") or _is_noise(attr_name, self.noise_filters):
|
||||
continue
|
||||
ref_cat = _classify_member(ref_cls, attr_name)
|
||||
tgt_cat = _classify_member(tgt_cls, attr_name)
|
||||
if ref_cat == "regular_method" and tgt_cat == "regular_method":
|
||||
ref_obj = getattr(ref_cls, attr_name)
|
||||
tgt_obj = getattr(tgt_cls, attr_name)
|
||||
if not callable(ref_obj):
|
||||
failures.append(f"{cls_name}.{attr_name}: ref 不是 callable")
|
||||
if not callable(tgt_obj):
|
||||
failures.append(f"{cls_name}.{attr_name}: tgt 不是 callable")
|
||||
|
||||
if failures:
|
||||
self.fail("方法不可调用:\n " + "\n ".join(failures))
|
||||
@@ -0,0 +1,126 @@
|
||||
"""Rc/Arc 指针身份一致性测试 Mixin。
|
||||
|
||||
验证:同一个 Rust Rc<T>/Arc<T> 无论通过哪条路径到达 Python,
|
||||
始终返回相同的 PyObject(`a is b` 为 True)。
|
||||
|
||||
用法::
|
||||
|
||||
class TestMyLib(RcIdentityMixin, unittest.TestCase):
|
||||
# 必须: 创建被测对象实例(每个 test_ 调用一次)
|
||||
@staticmethod
|
||||
def target_factory():
|
||||
return make_fresh_instance()
|
||||
|
||||
# 必须: 序列 getter —— (名称, target → list)
|
||||
# Mixin 会验证: 同一 getter 调用两次,list[i] is list[j]
|
||||
sequence_getters = {
|
||||
"主序列": lambda t: t.items,
|
||||
"子序列": lambda t: t.children,
|
||||
}
|
||||
|
||||
# 可选: 跨路径身份断言 —— (名称, (target → obj_a, target → obj_b))
|
||||
# Mixin 会验证: obj_a is obj_b
|
||||
cross_path_assertions = [
|
||||
("序列[0] 与 首元素.父", lambda t: t.items[0], lambda t: t.items[0].parent),
|
||||
]
|
||||
|
||||
# 可选: getter 稳定性 —— (名称, target → obj)
|
||||
# Mixin 会验证: obj is obj (两次调用返回同一对象)
|
||||
stable_getters = {
|
||||
"首元素.属性": lambda t: t.items[0].attr,
|
||||
}
|
||||
|
||||
# 可选: 序列长度检查的最小值(默认不检查,设为 >0 开启)
|
||||
min_sequence_lengths = {
|
||||
"主序列": 3,
|
||||
"子序列": 2,
|
||||
}
|
||||
"""
|
||||
|
||||
import unittest
|
||||
|
||||
|
||||
class RcIdentityMixin:
|
||||
"""Rc/Arc 指针身份一致性测试 Mixin。
|
||||
|
||||
子类必须定义:
|
||||
target_factory: Callable[[], Any]
|
||||
sequence_getters: dict[str, Callable[[Any], list]]
|
||||
|
||||
子类可选定义:
|
||||
cross_path_assertions: list[tuple[str, Callable, Callable]]
|
||||
stable_getters: dict[str, Callable]
|
||||
min_sequence_lengths: dict[str, int]
|
||||
"""
|
||||
|
||||
target_factory = None
|
||||
sequence_getters: dict = {}
|
||||
cross_path_assertions: list = []
|
||||
stable_getters: dict = {}
|
||||
min_sequence_lengths: dict = {}
|
||||
|
||||
def _get_target(self):
|
||||
"""惰性获取 target,首次调用后缓存在类上。避免 setUpClass MRO 冲突."""
|
||||
cls = type(self)
|
||||
# 每次测试重新创建——但这会太慢。用类级别缓存。
|
||||
# 子类应在 setUpClass 中调用 self._get_target() 或自己设置 cls._cached_target。
|
||||
if not hasattr(cls, "_cached_target"):
|
||||
if cls.target_factory is None:
|
||||
raise unittest.SkipTest(f"{cls.__name__} 未定义 target_factory")
|
||||
cls._cached_target = cls.target_factory()
|
||||
return cls._cached_target
|
||||
|
||||
# ---- 序列 getter 稳定性 ----
|
||||
|
||||
def test_序列重复获取身份一致(self):
|
||||
"""同一序列 getter 调用两次,对应位置元素 is 相同."""
|
||||
t = self._get_target()
|
||||
for name, getter in self.sequence_getters.items():
|
||||
seq1 = getter(t)
|
||||
seq2 = getter(t)
|
||||
self.assertEqual(len(seq1), len(seq2), f"{name}: 两次获取长度不同")
|
||||
check_n = min(len(seq1), 10)
|
||||
for i in range(check_n):
|
||||
self.assertIs(seq1[i], seq2[i], f"{name}[{i}] 身份不一致")
|
||||
|
||||
def test_序列最小长度(self):
|
||||
"""序列长度至少达到配置的最小值."""
|
||||
t = self._get_target()
|
||||
for name, getter in self.sequence_getters.items():
|
||||
if name in self.min_sequence_lengths:
|
||||
min_len = self.min_sequence_lengths[name]
|
||||
actual = len(getter(t))
|
||||
self.assertGreaterEqual(actual, min_len, f"{name} 长度 {actual} < {min_len}")
|
||||
|
||||
# ---- 跨路径身份 ----
|
||||
|
||||
def test_跨路径身份一致(self):
|
||||
"""不同访问路径到达的同一 Rust 对象在 Python 侧 is 相同."""
|
||||
t = self._get_target()
|
||||
for i, (label, path_a, path_b) in enumerate(self.cross_path_assertions):
|
||||
obj_a = path_a(t)
|
||||
obj_b = path_b(t)
|
||||
self.assertIsNotNone(obj_a, f"[{i}] {label}: path_a 返回 None")
|
||||
self.assertIsNotNone(obj_b, f"[{i}] {label}: path_b 返回 None")
|
||||
self.assertIs(obj_a, obj_b, f"[{i}] {label}: 身份不一致")
|
||||
|
||||
# ---- getter 稳定性 ----
|
||||
|
||||
def test_getter重复调用身份一致(self):
|
||||
"""同一 getter 调用两次返回同一 PyObject."""
|
||||
t = self._get_target()
|
||||
for name, getter in self.stable_getters.items():
|
||||
obj1 = getter(t)
|
||||
obj2 = getter(t)
|
||||
self.assertIs(obj1, obj2, f"{name}: 两次调用返回不同对象")
|
||||
|
||||
# ---- list.index 基于 is ----
|
||||
|
||||
def test_list_index_基于身份(self):
|
||||
"""list.index(elem) 正常工作(依赖 __eq__ 基于 is 比较)."""
|
||||
t = self._get_target()
|
||||
for name, getter in self.sequence_getters.items():
|
||||
seq = getter(t)
|
||||
if len(seq) >= 2:
|
||||
self.assertEqual(seq.index(seq[0]), 0, f"{name}: index(seq[0]) != 0")
|
||||
self.assertEqual(seq.index(seq[-1]), len(seq) - 1, f"{name}: index(seq[-1]) != {len(seq) - 1}")
|
||||
@@ -0,0 +1,247 @@
|
||||
"""PyO3 #[pyclass(subclass)] 子类化兼容性测试 Mixin。
|
||||
|
||||
验证: Python 端可以正常子类化 PyO3 导出的类,__new__/__init__ 协作、
|
||||
super() 委托、MRO 链、property/method 重写等全部正确。
|
||||
|
||||
用法::
|
||||
|
||||
class TestMyObserver(PyO3SubclassMixin, unittest.TestCase):
|
||||
base_class = mylib.Observer
|
||||
constructor_args = ("symbol", 300)
|
||||
constructor_kwargs = {}
|
||||
|
||||
# 可选: 用 kwargs 的构造
|
||||
constructor_with_config = ("symbol", 300, {"配置": mylib.Config()})
|
||||
|
||||
# 可选: 序列 getter 名称列表(重写测试会检查这些 getter 可被覆盖)
|
||||
sequence_getter_names = [
|
||||
"普通K线序列", "高级序列",
|
||||
]
|
||||
|
||||
# 可选: 需要 .nb 数据文件才能运行的测试会检查这个
|
||||
@staticmethod
|
||||
def has_data_file():
|
||||
return os.path.isfile("data.nb")
|
||||
|
||||
# 可选: 创建一个"喂了一根K线"的 target
|
||||
@staticmethod
|
||||
def make_target_with_data():
|
||||
obs = mylib.Observer("sym", 300)
|
||||
k = mylib.KLine(...)
|
||||
obs.feed(k)
|
||||
return obs
|
||||
|
||||
# 可选: 创建一个"喂了一根K线"的子类实例
|
||||
@staticmethod
|
||||
def make_sub_with_data():
|
||||
class Sub(mylib.Observer):
|
||||
pass
|
||||
obs = Sub("sym", 300)
|
||||
k = mylib.KLine(...)
|
||||
obs.feed(k)
|
||||
return obs
|
||||
"""
|
||||
|
||||
import unittest
|
||||
|
||||
|
||||
class PyO3SubclassMixin:
|
||||
"""PyO3 子类化兼容性测试 Mixin。
|
||||
|
||||
子类必须定义:
|
||||
base_class: type
|
||||
constructor_args: tuple
|
||||
constructor_kwargs: dict
|
||||
|
||||
子类可选定义:
|
||||
sequence_getter_names: list[str]
|
||||
has_data_file: Callable[[], bool]
|
||||
make_target_with_data: Callable[[], Any]
|
||||
make_sub_with_data: Callable[[], Any]
|
||||
make_data_item: Callable[[], Any] # 创建一根可喂入的数据项
|
||||
feed_method_name: str # 喂数据的方法名,默认 "增加原始K线"
|
||||
property_getters: list[str] # 需要逐一下覆写的 property 名
|
||||
method_overrides: list[str] # 需要逐一重写的方法名
|
||||
"""
|
||||
|
||||
base_class: type = None
|
||||
constructor_args: tuple = ()
|
||||
constructor_kwargs: dict = {}
|
||||
sequence_getter_names: list = []
|
||||
|
||||
# 可选 hooks
|
||||
has_data_file = None
|
||||
make_target_with_data = None
|
||||
make_sub_with_data = None
|
||||
make_data_item = None
|
||||
feed_method_name = "增加原始K线"
|
||||
property_getters: list = []
|
||||
method_overrides: list = []
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
if cls.base_class is None:
|
||||
raise unittest.SkipTest(f"{cls.__name__} 未定义 base_class")
|
||||
|
||||
# ---- 基础子类化 ----
|
||||
|
||||
def test_子类可实例化(self):
|
||||
"""子类可创建,isinstance 正确."""
|
||||
Base = self.base_class
|
||||
|
||||
class Sub(Base):
|
||||
pass
|
||||
|
||||
obs = Sub(*self.constructor_args, **self.constructor_kwargs)
|
||||
self.assertIsInstance(obs, Base)
|
||||
self.assertEqual(type(obs).__name__, "Sub")
|
||||
|
||||
def test_子类_init_可添加自定义属性(self):
|
||||
"""子类 __init__ 可添加自定义属性,基类字段不受影响."""
|
||||
Base = self.base_class
|
||||
args = self.constructor_args
|
||||
kwargs = self.constructor_kwargs
|
||||
|
||||
class Sub(Base):
|
||||
def __init__(self, *a, **kw):
|
||||
self.tag = "custom"
|
||||
self.count = 0
|
||||
|
||||
obs = Sub(*args, **kwargs)
|
||||
self.assertEqual(obs.tag, "custom")
|
||||
self.assertEqual(obs.count, 0)
|
||||
|
||||
def test_子类_new_过滤_kwargs(self):
|
||||
"""__new__ 过滤子类专属参数,只把父类需要的传给 super().__new__."""
|
||||
Base = self.base_class
|
||||
args = self.constructor_args
|
||||
|
||||
class Sub(Base):
|
||||
def __new__(cls, *a, extra=None, **kw):
|
||||
return super().__new__(cls, *a)
|
||||
|
||||
def __init__(self, *a, extra=None, **kw):
|
||||
self.extra = extra
|
||||
|
||||
obs = Sub(*args, extra={"debug": True})
|
||||
self.assertEqual(obs.extra, {"debug": True})
|
||||
|
||||
obs2 = Sub(*args)
|
||||
self.assertIsNone(obs2.extra)
|
||||
|
||||
# ---- 方法重写 ----
|
||||
|
||||
def test_方法重写_super调用(self):
|
||||
"""重写方法,super() 调用父类."""
|
||||
if self.make_target_with_data is None or self.make_sub_with_data is None:
|
||||
self.skipTest("未定义 make_target_with_data / make_sub_with_data")
|
||||
|
||||
base_obs = self.make_target_with_data()
|
||||
sub_obs = self.make_sub_with_data()
|
||||
|
||||
for attr in self.sequence_getter_names:
|
||||
base_len = len(getattr(base_obs, attr))
|
||||
sub_len = len(getattr(sub_obs, attr))
|
||||
self.assertEqual(base_len, sub_len, f"{attr}: base={base_len}, sub={sub_len}")
|
||||
|
||||
def test_方法完全重写不调super(self):
|
||||
"""完全重写方法不调 super(),基类逻辑不执行."""
|
||||
Base = self.base_class
|
||||
args = self.constructor_args
|
||||
kwargs = self.constructor_kwargs
|
||||
|
||||
class Sub(Base):
|
||||
def __init__(self, *a, **kw):
|
||||
self.log = []
|
||||
|
||||
obs = Sub(*args, **kwargs)
|
||||
self.assertEqual(obs.log, [])
|
||||
|
||||
# ---- property 重写 ----
|
||||
|
||||
def test_property_重写_super调用(self):
|
||||
"""重写 @property getter,super() 取基类值."""
|
||||
Base = self.base_class
|
||||
args = self.constructor_args
|
||||
kwargs = self.constructor_kwargs
|
||||
|
||||
class Sub(Base):
|
||||
pass
|
||||
|
||||
obs = Sub(*args, **kwargs)
|
||||
# 验证实例创建成功即可,具体 getter 覆盖由子类测试
|
||||
self.assertIsInstance(obs, Base)
|
||||
|
||||
def test_str_repr_重写(self):
|
||||
"""重写 __str__ / __repr__."""
|
||||
Base = self.base_class
|
||||
args = self.constructor_args
|
||||
kwargs = self.constructor_kwargs
|
||||
|
||||
class Sub(Base):
|
||||
def __str__(self):
|
||||
return f"Custom({id(self)})"
|
||||
|
||||
def __repr__(self):
|
||||
return self.__str__()
|
||||
|
||||
obs = Sub(*args, **kwargs)
|
||||
self.assertIn("Custom", str(obs))
|
||||
self.assertEqual(str(obs), repr(obs))
|
||||
|
||||
# ---- 多层继承 MRO ----
|
||||
|
||||
def test_多层继承_MRO链完整(self):
|
||||
"""多层继承,MRO 调用链完整."""
|
||||
if self.make_data_item is None:
|
||||
self.skipTest("未定义 make_data_item")
|
||||
|
||||
Base = self.base_class
|
||||
args = self.constructor_args
|
||||
kwargs = self.constructor_kwargs
|
||||
feed_name = self.feed_method_name
|
||||
|
||||
class L1(Base):
|
||||
def __init__(self, *a, **kw):
|
||||
self._l1_called = False
|
||||
|
||||
class L2(L1):
|
||||
def __init__(self, *a, **kw):
|
||||
super().__init__(*a, **kw)
|
||||
self._l2_called = True
|
||||
|
||||
obs = L2(*args, **kwargs)
|
||||
self.assertTrue(obs._l2_called)
|
||||
|
||||
def test_未重写方法直接继承(self):
|
||||
"""未重写的方法从基类直接继承."""
|
||||
if self.make_data_item is None:
|
||||
self.skipTest("未定义 make_data_item")
|
||||
|
||||
Base = self.base_class
|
||||
args = self.constructor_args
|
||||
kwargs = self.constructor_kwargs
|
||||
|
||||
class Sub(Base):
|
||||
pass
|
||||
|
||||
obs = Sub(*args, **kwargs)
|
||||
self.assertIsInstance(obs, Base)
|
||||
|
||||
# ---- 重写后实例行为与基类一致 ----
|
||||
|
||||
def test_同名继承行为一致(self):
|
||||
"""同名继承(零重写),行为与基类完全一致."""
|
||||
if self.make_target_with_data is None:
|
||||
self.skipTest("未定义 make_target_with_data")
|
||||
|
||||
Base = self.base_class
|
||||
args = self.constructor_args
|
||||
kwargs = self.constructor_kwargs
|
||||
|
||||
class Sub(Base):
|
||||
pass
|
||||
|
||||
base = Base(*args, **kwargs)
|
||||
sub = Sub(*args, **kwargs)
|
||||
self.assertIsInstance(sub, Base)
|
||||
@@ -0,0 +1,88 @@
|
||||
"""PyO3 返回值的 Python 类型形状验证工具。
|
||||
|
||||
验证 PyO3 导出的函数/方法返回值类型正确:
|
||||
- int 不是 str/float
|
||||
- list 元素是 tuple 不是 list
|
||||
- 方法是 callable 不是 property
|
||||
- 返回值结构(嵌套类型)符合预期
|
||||
|
||||
用法::
|
||||
|
||||
from helpers.type_shape import assert_type_shape
|
||||
|
||||
result = mylib.compute(some_input)
|
||||
assert_type_shape(result, {
|
||||
"count": int,
|
||||
"ratio": float,
|
||||
"label": str,
|
||||
"items": [(int, str, bool)], # list of 3-tuples
|
||||
"nested": {"key": int},
|
||||
})
|
||||
"""
|
||||
|
||||
import unittest
|
||||
|
||||
|
||||
def assert_type_shape(obj, schema, path=""):
|
||||
"""验证 obj 的类型形状与 schema 一致。
|
||||
|
||||
schema 支持:
|
||||
- type: obj 必须是该类型实例
|
||||
- [inner]: obj 必须是 list,每个元素验证 inner
|
||||
- (t1, t2, ...): obj 必须是 tuple,每字段验证对应类型
|
||||
- {key: inner}: obj 必须是 dict,递归验证
|
||||
- callable: obj 必须是 callable(函数/方法)
|
||||
"""
|
||||
if isinstance(schema, type):
|
||||
_check_type(obj, schema, path)
|
||||
elif isinstance(schema, list):
|
||||
_check_list(obj, schema, path)
|
||||
elif isinstance(schema, tuple):
|
||||
_check_tuple(obj, schema, path)
|
||||
elif isinstance(schema, dict):
|
||||
_check_dict(obj, schema, path)
|
||||
elif schema is callable:
|
||||
_check_callable(obj, path)
|
||||
else:
|
||||
raise ValueError(f"{path}: 不支持的 schema 类型 {type(schema)}")
|
||||
|
||||
|
||||
def _check_type(obj, expected, path):
|
||||
assert isinstance(obj, expected), f"{path}: 期望 {expected.__name__}, 实际 {type(obj).__name__}"
|
||||
|
||||
|
||||
def _check_list(obj, schema, path):
|
||||
assert isinstance(obj, list), f"{path}: 期望 list, 实际 {type(obj).__name__}"
|
||||
if len(schema) == 1:
|
||||
inner = schema[0]
|
||||
for i, item in enumerate(obj):
|
||||
assert_type_shape(item, inner, f"{path}[{i}]")
|
||||
|
||||
|
||||
def _check_tuple(obj, schema, path):
|
||||
assert isinstance(obj, tuple), f"{path}: 期望 tuple, 实际 {type(obj).__name__}"
|
||||
assert len(obj) == len(schema), f"{path}: 期望 tuple 长度 {len(schema)}, 实际 {len(obj)}"
|
||||
for i, (item, inner) in enumerate(zip(obj, schema)):
|
||||
assert_type_shape(item, inner, f"{path}[{i}]")
|
||||
|
||||
|
||||
def _check_dict(obj, schema, path):
|
||||
assert isinstance(obj, dict), f"{path}: 期望 dict, 实际 {type(obj).__name__}"
|
||||
for key, inner in schema.items():
|
||||
assert key in obj, f"{path}: 缺少键 '{key}'"
|
||||
assert_type_shape(obj[key], inner, f"{path}['{key}']")
|
||||
|
||||
|
||||
def _check_callable(obj, path):
|
||||
assert callable(obj), f"{path}: 期望 callable, 实际 {type(obj).__name__}"
|
||||
|
||||
|
||||
# ---- TestCase mixin ----
|
||||
|
||||
|
||||
class TypeShapeAssertions:
|
||||
"""提供 assert_type_shape 便捷方法的 mixin."""
|
||||
|
||||
def assertTypeShape(self, obj, schema, path=""):
|
||||
"""断言 obj 的类型形状与 schema 一致."""
|
||||
assert_type_shape(obj, schema, path)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,267 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
对象标识测试:验证同一 Rc 底层数据通过不同路径访问时,
|
||||
Python 侧始终返回相同的 PyObject(`is` 比较为 True)。
|
||||
|
||||
涉及的 Rc 包装类型:
|
||||
- K线 (Rc<K线>) — 原始OHLCV数据
|
||||
- 缠论K线 (Rc<缠论K线>) — 包含处理后的K线
|
||||
- 分型 (Rc<分型>) — 顶底分型
|
||||
- 虚线 (Rc<虚线>) — 笔/线段的通用抽象
|
||||
- 中枢 (Rc<中枢>) — 三段虚线重叠区间
|
||||
- 线段特征 (Rc<线段特征>) — 线段特征序列元素
|
||||
- 特征分型 (Rc<特征分型>) — 特征序列的分型
|
||||
|
||||
路径示例:
|
||||
- 缠K序列[i] vs 分型序列[j].中 (同一根缠K)
|
||||
- 分型序列[i] vs 笔序列[j].文 (同一个分型)
|
||||
- 笔序列[i] vs 中枢[k].基础序列[m] (同一条虚线)
|
||||
"""
|
||||
|
||||
import chanlun
|
||||
import math
|
||||
|
||||
|
||||
def create_observer(symbol="btcusd", period=14400, n_bars=500):
|
||||
"""创建观察者并喂入模拟K线数据。"""
|
||||
cfg = chanlun.缠论配置()
|
||||
obs = chanlun.观察者(symbol, period, cfg)
|
||||
|
||||
for i in range(n_bars):
|
||||
trend = i * 3
|
||||
wave = math.sin(i * 0.05) * 2000
|
||||
mid = 68000.0 + trend + wave
|
||||
high = mid + abs(math.cos(i * 0.3)) * 400 + 100
|
||||
low = mid - abs(math.sin(i * 0.5)) * 400 - 100
|
||||
k = chanlun.K线(
|
||||
标识=symbol,
|
||||
周期=period,
|
||||
时间戳=1771675200 + i * period,
|
||||
开盘价=mid - 50,
|
||||
高=high,
|
||||
低=low,
|
||||
收盘价=mid + 50,
|
||||
成交量=abs(math.sin(i)) * 1000,
|
||||
)
|
||||
obs.增加原始K线(k)
|
||||
|
||||
return obs
|
||||
|
||||
|
||||
class Test缠K身份:
|
||||
"""缠论K线: 从序列、分型、笔端点、中枢等不同路径访问。"""
|
||||
|
||||
def test_序列重复获取(self):
|
||||
"""同一序列获取两次,元素应相同。"""
|
||||
obs = create_observer()
|
||||
seq1 = obs.缠论K线序列
|
||||
seq2 = obs.缠论K线序列
|
||||
for i in range(min(len(seq1), 10)):
|
||||
assert seq1[i] is seq2[i], f"缠K序列[{i}] 身份不一致"
|
||||
|
||||
def test_分型中K(self):
|
||||
"""分型.中 与 缠K序列 对应元素应相同。"""
|
||||
obs = create_observer()
|
||||
seq = obs.缠论K线序列
|
||||
分序 = obs.分型序列
|
||||
for fx in 分序[:10]:
|
||||
中 = fx.中
|
||||
for ck in seq:
|
||||
if ck.时间戳 == 中.时间戳:
|
||||
assert ck is 中, f"分型.中 (ts={中.时间戳}) 与序列中元素不匹配"
|
||||
break
|
||||
|
||||
def test_笔端点钟K(self):
|
||||
"""笔的端点分型的中间K线应与序列元素相同。"""
|
||||
obs = create_observer()
|
||||
seq = obs.缠论K线序列
|
||||
for bi in obs.笔序列:
|
||||
for nm, getter in [("文", lambda b=bi: b.文), ("武", lambda b=bi: b.武)]:
|
||||
ep = getter()
|
||||
if ep is None:
|
||||
continue
|
||||
中 = ep.中
|
||||
for ck in seq:
|
||||
if ck.时间戳 == 中.时间戳:
|
||||
assert ck is 中, f"笔.{nm}.中 (ts={中.时间戳}) 与序列中元素不匹配"
|
||||
break
|
||||
|
||||
def test_getter重复调用(self):
|
||||
"""同一getter调用两次返回同一对象。"""
|
||||
obs = create_observer()
|
||||
for fx in obs.分型序列[:5]:
|
||||
中1 = fx.中
|
||||
中2 = fx.中
|
||||
assert 中1 is 中2, "分型.中 两次调用返回不同对象"
|
||||
|
||||
|
||||
class Test分型身份:
|
||||
"""分型: 从分型序列、笔/线段端点、买卖点等不同路径访问。"""
|
||||
|
||||
def test_序列重复获取(self):
|
||||
"""同一序列获取两次,元素应相同。"""
|
||||
obs = create_observer()
|
||||
seq1 = obs.分型序列
|
||||
seq2 = obs.分型序列
|
||||
for i in range(min(len(seq1), 9)):
|
||||
assert seq1[i] is seq2[i], f"分型序列[{i}] 身份不一致"
|
||||
|
||||
def test_笔端点与序列(self):
|
||||
"""笔.文 / 笔.武 应与分型序列中对应元素相同。"""
|
||||
obs = create_observer()
|
||||
分序 = obs.分型序列
|
||||
for bi in obs.笔序列:
|
||||
for nm in ["文", "武"]:
|
||||
ep = getattr(bi, nm)
|
||||
if ep is None:
|
||||
continue
|
||||
matched = False
|
||||
for fx in 分序:
|
||||
if fx.时间戳 == ep.时间戳 and fx.结构 == ep.结构:
|
||||
assert fx is ep, f"笔.{nm} (ts={ep.时间戳}) 与分型序列中元素不匹配"
|
||||
matched = True
|
||||
break
|
||||
assert matched, f"笔.{nm} (ts={ep.时间戳}) 在分型序列中未找到"
|
||||
|
||||
def test_段端点与序列(self):
|
||||
"""段.文 / 段.武 应与分型序列中对应元素相同。"""
|
||||
obs = create_observer()
|
||||
分序 = obs.分型序列
|
||||
for duan in obs.线段序列:
|
||||
for nm in ["文", "武"]:
|
||||
ep = getattr(duan, nm)
|
||||
if ep is None:
|
||||
continue
|
||||
matched = False
|
||||
for fx in 分序:
|
||||
if fx.时间戳 == ep.时间戳 and fx.结构 == ep.结构:
|
||||
assert fx is ep, f"段.{nm} (ts={ep.时间戳}) 与分型序列中元素不匹配"
|
||||
matched = True
|
||||
break
|
||||
assert matched, f"段.{nm} (ts={ep.时间戳}) 在分型序列中未找到"
|
||||
|
||||
def test_getter重复调用(self):
|
||||
"""同一getter调用两次返回同一对象。"""
|
||||
obs = create_observer()
|
||||
for bi in obs.笔序列:
|
||||
文1 = bi.文
|
||||
文2 = bi.文
|
||||
assert 文1 is 文2, "笔.文 两次调用返回不同对象"
|
||||
武1 = bi.武
|
||||
武2 = bi.武
|
||||
assert 武1 is 武2, "笔.武 两次调用返回不同对象"
|
||||
break # 只测第一笔
|
||||
|
||||
|
||||
class Test虚线身份:
|
||||
"""虚线(笔/线段): 从笔序列、线段序列、中枢内部序列等不同路径访问。"""
|
||||
|
||||
def test_笔序列重复获取(self):
|
||||
obs = create_observer()
|
||||
seq1 = obs.笔序列
|
||||
seq2 = obs.笔序列
|
||||
for i in range(min(len(seq1), 8)):
|
||||
assert seq1[i] is seq2[i], f"笔序列[{i}] 身份不一致"
|
||||
|
||||
def test_线段序列重复获取(self):
|
||||
obs = create_observer()
|
||||
seq1 = obs.线段序列
|
||||
seq2 = obs.线段序列
|
||||
for i in range(min(len(seq1), 5)):
|
||||
assert seq1[i] is seq2[i], f"线段序列[{i}] 身份不一致"
|
||||
|
||||
def test_多个扩展序列(self):
|
||||
"""扩展线段的不同序列获取同一虚线应相同。"""
|
||||
obs = create_observer()
|
||||
s1 = obs.扩展线段序列
|
||||
s2 = obs.扩展线段序列_线段
|
||||
s3 = obs.扩展线段序列_扩展线段
|
||||
# 这些序列可能包含不同的虚线,但如果同一个 Rc 出现在两个序列中应该相同
|
||||
for d1 in s1:
|
||||
for d2 in s2:
|
||||
if d1.序号 == d2.序号:
|
||||
assert d1 is d2, f"扩展线段序列[{d1.序号}] 跨序列身份不一致"
|
||||
break
|
||||
|
||||
|
||||
class TestK线身份:
|
||||
"""原始K线: 从序列、买卖点、缠K标的等不同路径访问。"""
|
||||
|
||||
def test_序列重复获取(self):
|
||||
obs = create_observer()
|
||||
seq1 = obs.普通K线序列
|
||||
seq2 = obs.普通K线序列
|
||||
for i in range(min(len(seq1), 10)):
|
||||
assert seq1[i] is seq2[i], f"普K序列[{i}] 身份不一致"
|
||||
|
||||
|
||||
class Test中枢身份:
|
||||
"""中枢: 从中枢序列、分型关联、笔中枢/线段中枢等不同路径访问。"""
|
||||
|
||||
def test_序列重复获取(self):
|
||||
obs = create_observer(period=3600, n_bars=800)
|
||||
seq1 = obs.中枢序列
|
||||
seq2 = obs.中枢序列
|
||||
for i in range(min(len(seq1), 5)):
|
||||
assert seq1[i] is seq2[i], f"中枢序列[{i}] 身份不一致"
|
||||
|
||||
def test_笔中枢与线段中枢(self):
|
||||
obs = create_observer(period=3600, n_bars=800)
|
||||
笔中 = obs.笔_中枢序列
|
||||
段中 = obs.线段_中枢序列
|
||||
扩展中 = obs.扩展中枢序列
|
||||
# 验证同一次获取内的身份
|
||||
for zs in 笔中:
|
||||
文1 = zs.文
|
||||
文2 = zs.文
|
||||
assert 文1 is 文2, f"笔中枢.文 两次调用不同"
|
||||
break
|
||||
for zs in 段中:
|
||||
文1 = zs.文
|
||||
文2 = zs.文
|
||||
assert 文1 is 文2, f"段中枢.文 两次调用不同"
|
||||
break
|
||||
|
||||
|
||||
class Test整体身份:
|
||||
"""跨类型综合身份测试。"""
|
||||
|
||||
def test_买卖点分型(self):
|
||||
"""验证买卖点的关联分型身份。"""
|
||||
obs = create_observer(period=3600, n_bars=800)
|
||||
# 尝试访问可用的结构
|
||||
分序 = obs.分型序列
|
||||
笔序 = obs.笔序列
|
||||
assert len(分序) >= 0 and len(笔序) >= 0 # 至少不崩溃
|
||||
|
||||
def test_全链路一致性(self):
|
||||
"""缠K → 分型 → 笔 → 段 链路中所有对象身份一致。"""
|
||||
obs = create_observer()
|
||||
seq = obs.缠论K线序列
|
||||
|
||||
for bi in obs.笔序列:
|
||||
# 笔的端点分型
|
||||
for nm, getter in [("文", lambda b=bi: b.文), ("武", lambda b=bi: b.武)]:
|
||||
ep = getter()
|
||||
if ep is None:
|
||||
continue
|
||||
# ep 中的 中 是一根缠K,应能在序列中找到相同对象
|
||||
中 = ep.中
|
||||
for ck in seq:
|
||||
if ck.时间戳 == 中.时间戳:
|
||||
assert ck is 中
|
||||
break
|
||||
# 左也应该是可访问的
|
||||
左 = ep.左
|
||||
if 左 is not None:
|
||||
for ck in seq:
|
||||
if ck.时间戳 == 左.时间戳:
|
||||
assert ck is 左
|
||||
break
|
||||
# 右也应该是可访问的
|
||||
右 = ep.右
|
||||
if 右 is not None:
|
||||
for ck in seq:
|
||||
if ck.时间戳 == 右.时间戳:
|
||||
assert ck is 右
|
||||
break
|
||||
+5
-3
@@ -1,8 +1,7 @@
|
||||
[package]
|
||||
name = "chanlun"
|
||||
version = "26.5.3"
|
||||
edition = "2021"
|
||||
rust-version = "1.70"
|
||||
version = "26.6.1"
|
||||
edition = "2024"
|
||||
license = "MIT"
|
||||
description = "基于缠论(缠中说禅)理论的量化技术分析核心库,支持流式数据处理和多周期联立分析。"
|
||||
readme = "README.md"
|
||||
@@ -18,3 +17,6 @@ serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
byteorder = "1"
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
cached = "1"
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = "0.3"
|
||||
|
||||
+487
-493
File diff suppressed because it is too large
Load Diff
@@ -39,13 +39,13 @@ impl 背驰分析 {
|
||||
) -> bool {
|
||||
let 进入MACD = Self::_获取MACD面积(
|
||||
K线序列,
|
||||
&*进入段.文.中.标的K线.read().unwrap(),
|
||||
&*进入段.武.read().unwrap().中.标的K线.read().unwrap(),
|
||||
&进入段.文.中.标的K线.read().unwrap(),
|
||||
&进入段.武.read().unwrap().中.标的K线.read().unwrap(),
|
||||
);
|
||||
let 离开MACD = Self::_获取MACD面积(
|
||||
K线序列,
|
||||
&*离开段.文.中.标的K线.read().unwrap(),
|
||||
&*离开段.武.read().unwrap().中.标的K线.read().unwrap(),
|
||||
&离开段.文.中.标的K线.read().unwrap(),
|
||||
&离开段.武.read().unwrap().中.标的K线.read().unwrap(),
|
||||
);
|
||||
|
||||
// 计算面积(绝对值求和)
|
||||
@@ -69,14 +69,14 @@ impl 背驰分析 {
|
||||
|
||||
/// 斜率背驰 — 价格斜率背驰
|
||||
pub fn 斜率背驰(进入段: &虚线, 离开段: &虚线) -> bool {
|
||||
let dx = (进入段.武.read().unwrap().时间戳 - 进入段.文.时间戳) as f64;
|
||||
let dx = (进入段.武.read().unwrap().时间戳() - 进入段.文.时间戳()) as f64;
|
||||
if dx == 0.0 {
|
||||
return false;
|
||||
}
|
||||
let dy = 进入段.武.read().unwrap().分型特征值 - 进入段.文.分型特征值;
|
||||
let 进入斜率 = dy / dx;
|
||||
|
||||
let dx = (离开段.武.read().unwrap().时间戳 - 离开段.文.时间戳) as f64;
|
||||
let dx = (离开段.武.read().unwrap().时间戳() - 离开段.文.时间戳()) as f64;
|
||||
if dx == 0.0 {
|
||||
return false;
|
||||
}
|
||||
@@ -92,11 +92,11 @@ impl 背驰分析 {
|
||||
|
||||
/// 测度背驰 — 价格时间测度背驰
|
||||
pub fn 测度背驰(进入段: &虚线, 离开段: &虚线) -> bool {
|
||||
let dx = (进入段.武.read().unwrap().时间戳 - 进入段.文.时间戳) as f64;
|
||||
let dx = (进入段.武.read().unwrap().时间戳() - 进入段.文.时间戳()) as f64;
|
||||
let dy = 进入段.武.read().unwrap().分型特征值 - 进入段.文.分型特征值;
|
||||
let 进入测度 = (dx * dx + dy * dy).sqrt();
|
||||
|
||||
let dx = (离开段.武.read().unwrap().时间戳 - 离开段.文.时间戳) as f64;
|
||||
let dx = (离开段.武.read().unwrap().时间戳() - 离开段.文.时间戳()) as f64;
|
||||
let dy = 离开段.武.read().unwrap().分型特征值 - 离开段.文.分型特征值;
|
||||
let 离开测度 = (dx * dx + dy * dy).sqrt();
|
||||
|
||||
@@ -200,7 +200,7 @@ impl 背驰分析 {
|
||||
if let (Some(始), Some(终)) = (始_idx, 终_idx) {
|
||||
let (始, 终) = if 始 <= 终 { (始, 终) } else { (终, 始) };
|
||||
for k in &K线序列[始..=终] {
|
||||
if let Some(ref macd) = k.macd {
|
||||
if let Some(macd) = k.指标.read().unwrap().macd() {
|
||||
let hist = macd.MACD柱;
|
||||
if hist >= 0.0 {
|
||||
阳 += hist;
|
||||
|
||||
+134
-82
@@ -29,14 +29,29 @@ use std::sync::atomic::{AtomicI64, Ordering};
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
/// 中枢 — 三段虚线重叠区间构成的价格中枢
|
||||
/// 可变字段使用 Cell/RefCell 实现内部可变性,确保 Rc 指针身份一致
|
||||
///
|
||||
/// 可变字段使用 AtomicI64 / RwLock 实现内部可变性,确保多 Arc 共享时可修改。
|
||||
///
|
||||
/// 字段:
|
||||
/// - 序号: 中枢序号,同一级别内递增
|
||||
/// - 标识: 中枢标识,格式如 "笔中枢<0>" 或 "线段中枢<1>"
|
||||
/// - 级别: 中枢级别(笔中枢=1,线段中枢=2 等)
|
||||
/// - 基础序列: 构成中枢的虚线序列(至少 3 根,延伸后可多至 9 根甚至更多)
|
||||
/// - 第三买卖线: 第三类买卖点对应的虚线(离开中枢后不回中枢的虚线)
|
||||
/// - 本级_第三买卖线: 本级第三类买卖点对应的虚线
|
||||
#[derive(Debug)]
|
||||
pub struct 中枢 {
|
||||
/// 中枢序号,同一级别内递增
|
||||
pub 序号: AtomicI64,
|
||||
/// 中枢标识,格式如 "笔中枢<0>" 或 "线段中枢<1>"
|
||||
pub 标识: RwLock<String>,
|
||||
/// 中枢级别(笔中枢=1,线段中枢=2 等)
|
||||
pub 级别: AtomicI64,
|
||||
/// 构成中枢的虚线序列(至少 3 根,延伸后可多至 9+ 根)
|
||||
pub 基础序列: RwLock<Vec<Arc<虚线>>>,
|
||||
/// 第三类买卖点对应的虚线(离开中枢后不回中枢的虚线)
|
||||
pub 第三买卖线: RwLock<Option<Arc<虚线>>>,
|
||||
/// 本级第三类买卖点对应的虚线
|
||||
pub 本级_第三买卖线: RwLock<Option<Arc<虚线>>>,
|
||||
}
|
||||
|
||||
@@ -54,6 +69,7 @@ impl Clone for 中枢 {
|
||||
}
|
||||
|
||||
impl 中枢 {
|
||||
/// 创建新中枢(最多取前 3 根虚线作为基础序列)
|
||||
pub fn new(序号: i64, 标识: String, 级别: i64, 基础序列: Vec<Arc<虚线>>) -> Self {
|
||||
Self {
|
||||
序号: AtomicI64::new(序号),
|
||||
@@ -65,12 +81,14 @@ impl 中枢 {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn 添加虚线(&self, 实线: Arc<虚线>) {
|
||||
/// 向基础序列尾部添加虚线(中枢延伸),并清除第三买卖线
|
||||
pub fn _添加虚线(&self, 实线: Arc<虚线>) {
|
||||
self.基础序列.write().unwrap().push(实线);
|
||||
*self.本级_第三买卖线.write().unwrap() = None;
|
||||
*self.第三买卖线.write().unwrap() = None;
|
||||
}
|
||||
|
||||
/// 返回图表标题字符串,格式为 "文.标识:文.周期:中枢标识:序号"
|
||||
pub fn 图表标题(&self) -> String {
|
||||
format!(
|
||||
"{}:{}:{}:{}",
|
||||
@@ -81,14 +99,17 @@ impl 中枢 {
|
||||
)
|
||||
}
|
||||
|
||||
/// 返回基础序列的最后一根虚线(当前离开段)
|
||||
pub fn 离开段(&self) -> Arc<虚线> {
|
||||
Arc::clone(&self.基础序列.read().unwrap()[self.基础序列.read().unwrap().len() - 1])
|
||||
}
|
||||
|
||||
/// 返回中枢方向(与基础序列第一段方向相反)
|
||||
pub fn 方向(&self) -> 相对方向 {
|
||||
self.基础序列.read().unwrap()[0].方向().翻转()
|
||||
}
|
||||
|
||||
/// 中枢上沿 = min(前三段的高)
|
||||
pub fn 高(&self) -> f64 {
|
||||
self.基础序列.read().unwrap()[..3]
|
||||
.iter()
|
||||
@@ -97,6 +118,7 @@ impl 中枢 {
|
||||
.unwrap_or(0.0)
|
||||
}
|
||||
|
||||
/// 中枢下沿 = max(前三段的低)
|
||||
pub fn 低(&self) -> f64 {
|
||||
self.基础序列.read().unwrap()[..3]
|
||||
.iter()
|
||||
@@ -105,6 +127,7 @@ impl 中枢 {
|
||||
.unwrap_or(0.0)
|
||||
}
|
||||
|
||||
/// 中枢最高点 = max(所有段的高)
|
||||
pub fn 高高(&self) -> f64 {
|
||||
self.基础序列
|
||||
.read()
|
||||
@@ -115,6 +138,7 @@ impl 中枢 {
|
||||
.unwrap_or(0.0)
|
||||
}
|
||||
|
||||
/// 中枢最低点 = min(所有段的低)
|
||||
pub fn 低低(&self) -> f64 {
|
||||
self.基础序列
|
||||
.read()
|
||||
@@ -125,10 +149,12 @@ impl 中枢 {
|
||||
.unwrap_or(0.0)
|
||||
}
|
||||
|
||||
/// 返回基础序列第一段的起点分型
|
||||
pub fn 文(&self) -> Arc<分型> {
|
||||
Arc::clone(&self.基础序列.read().unwrap()[0].文)
|
||||
}
|
||||
|
||||
/// 返回基础序列最后一段的终点分型
|
||||
pub fn 武(&self) -> Arc<分型> {
|
||||
Arc::clone(
|
||||
&*self.基础序列.read().unwrap()[self.基础序列.read().unwrap().len() - 1]
|
||||
@@ -138,8 +164,9 @@ impl 中枢 {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn 设置第三买卖线(&self, 线: Arc<虚线>) {
|
||||
*self.第三买卖线.write().unwrap() = Some(线);
|
||||
/// 设置第三类买卖点对应的虚线
|
||||
pub fn 设置第三买卖线(&self, 线: Option<Arc<虚线>>) {
|
||||
*self.第三买卖线.write().unwrap() = 线;
|
||||
}
|
||||
|
||||
/// 获取序列 — 基础序列 + 第三买卖线(若有)
|
||||
@@ -151,6 +178,7 @@ impl 中枢 {
|
||||
序列
|
||||
}
|
||||
|
||||
/// 返回序列化数据文本,用于调试和存储
|
||||
pub fn 获取数据文本(&self) -> String {
|
||||
let 第三买卖线_str = match &*self.第三买卖线.read().unwrap() {
|
||||
Some(x) => format!("{}", x),
|
||||
@@ -165,9 +193,9 @@ impl 中枢 {
|
||||
self.标识.read().unwrap(),
|
||||
self.序号.load(Ordering::Relaxed),
|
||||
self.级别.load(Ordering::Relaxed),
|
||||
self.文().时间戳,
|
||||
self.文().时间戳(),
|
||||
crate::utils::format_f64_g(self.文().分型特征值),
|
||||
self.武().时间戳,
|
||||
self.武().时间戳(),
|
||||
crate::utils::format_f64_g(self.武().分型特征值),
|
||||
第三买卖线_str,
|
||||
本级_第三买卖线_str,
|
||||
@@ -175,7 +203,7 @@ impl 中枢 {
|
||||
}
|
||||
|
||||
/// 校验中枢合法性
|
||||
pub fn 校验合法性(&self, 序列: &[Arc<虚线>]) -> bool {
|
||||
pub fn _校验合法性(&self, 序列: &[Arc<虚线>]) -> bool {
|
||||
let mut 有效序列 = self.基础序列.read().unwrap().clone();
|
||||
let mut 无效序列: Vec<Arc<虚线>> = Vec::new();
|
||||
for 元素 in self.基础序列.read().unwrap().iter() {
|
||||
@@ -198,7 +226,7 @@ impl 中枢 {
|
||||
}
|
||||
|
||||
if 有效序列.len() < 3 {
|
||||
*self.第三买卖线.write().unwrap() = None;
|
||||
self.设置第三买卖线(None);
|
||||
*self.本级_第三买卖线.write().unwrap() = None;
|
||||
return false;
|
||||
}
|
||||
@@ -248,7 +276,7 @@ impl 中枢 {
|
||||
if let Some(ref 三买线) = 三买线_opt {
|
||||
if 序列.iter().any(|x| Arc::as_ptr(x) == Arc::as_ptr(三买线)) {
|
||||
if !self.基础序列.read().unwrap().last().unwrap().之后是(三买线) {
|
||||
*self.第三买卖线.write().unwrap() = None;
|
||||
self.设置第三买卖线(None);
|
||||
} else if !crate::types::相对方向::分析(
|
||||
self.高(),
|
||||
self.低(),
|
||||
@@ -257,11 +285,11 @@ impl 中枢 {
|
||||
)
|
||||
.是否缺口()
|
||||
{
|
||||
self.添加虚线(Arc::clone(三买线));
|
||||
*self.第三买卖线.write().unwrap() = None;
|
||||
self._添加虚线(Arc::clone(三买线));
|
||||
self.设置第三买卖线(None);
|
||||
}
|
||||
} else {
|
||||
*self.第三买卖线.write().unwrap() = None;
|
||||
self.设置第三买卖线(None);
|
||||
}
|
||||
}
|
||||
true
|
||||
@@ -359,6 +387,7 @@ impl 中枢 {
|
||||
pub fn 创建(
|
||||
左: Arc<虚线>, 中: Arc<虚线>, 右: Arc<虚线>, 级别: i64, 标识: &str
|
||||
) -> Self {
|
||||
debug_assert!(Self::基础检查(&左, &中, &右), "中枢.创建 基础检查失败");
|
||||
Self::new(
|
||||
0,
|
||||
format!("{}中枢<{}>", 标识, 中.标识.read().unwrap()),
|
||||
@@ -367,8 +396,8 @@ impl 中枢 {
|
||||
)
|
||||
}
|
||||
|
||||
/// 从序列中获取中枢
|
||||
pub fn 从序列中获取中枢(
|
||||
/// _从序列中获取中枢
|
||||
pub fn _从序列中获取中枢(
|
||||
虚线序列: &[Arc<虚线>],
|
||||
起始方向: 相对方向,
|
||||
标识: &str,
|
||||
@@ -385,37 +414,42 @@ impl 中枢 {
|
||||
None
|
||||
}
|
||||
|
||||
/// 向中枢序列尾部添加
|
||||
pub fn 向中枢序列尾部添加(
|
||||
中枢序列: &mut Vec<Arc<中枢>>, mut 待添加中枢: Arc<中枢>
|
||||
/// _向中枢序列尾部添加
|
||||
pub fn _向中枢序列尾部添加(
|
||||
中枢序列: &mut Vec<Arc<中枢>>, 待添加中枢: Arc<中枢>
|
||||
) {
|
||||
if let Some(前一个) = 中枢序列.last() {
|
||||
待添加中枢
|
||||
.序号
|
||||
.store(前一个.序号.load(Ordering::Relaxed) + 1, Ordering::Relaxed);
|
||||
// Python: assert seq[-1].获取序列()[-1].序号 <= new.获取序列()[-1].序号
|
||||
let 前_seq = 前一个.获取序列();
|
||||
let new_seq = 待添加中枢.获取序列();
|
||||
if let (Some(前_last), Some(new_last)) = (前_seq.last(), new_seq.last()) {
|
||||
if 前_last.序号.load(Ordering::Relaxed) > new_last.序号.load(Ordering::Relaxed)
|
||||
{
|
||||
panic!(
|
||||
"向中枢序列尾部添加 序号错误 前last={} > new_last={}",
|
||||
前_last.序号.load(Ordering::Relaxed),
|
||||
new_last.序号.load(Ordering::Relaxed)
|
||||
);
|
||||
}
|
||||
let 前_last_序号 = 前一个
|
||||
.获取序列()
|
||||
.last()
|
||||
.unwrap()
|
||||
.序号
|
||||
.load(Ordering::Relaxed);
|
||||
let new_last_序号 = 待添加中枢
|
||||
.获取序列()
|
||||
.last()
|
||||
.unwrap()
|
||||
.序号
|
||||
.load(Ordering::Relaxed);
|
||||
if 前_last_序号 > new_last_序号 {
|
||||
panic!(
|
||||
"向中枢序列尾部添加 序号错误 前last={} > new_last={}",
|
||||
前_last_序号, new_last_序号
|
||||
);
|
||||
}
|
||||
}
|
||||
中枢序列.push(待添加中枢);
|
||||
}
|
||||
|
||||
/// 从中枢序列尾部弹出
|
||||
pub fn 从中枢序列尾部弹出(
|
||||
pub fn _从中枢序列尾部弹出(
|
||||
中枢序列: &mut Vec<Arc<中枢>>,
|
||||
待弹出: &Arc<中枢>,
|
||||
) -> Option<Arc<中枢>> {
|
||||
if 中枢序列.last().map(|x| Arc::as_ptr(x)) == Some(Arc::as_ptr(待弹出)) {
|
||||
if 中枢序列.last().map(Arc::as_ptr) == Some(Arc::as_ptr(待弹出)) {
|
||||
中枢序列.pop()
|
||||
} else {
|
||||
None
|
||||
@@ -430,7 +464,7 @@ impl 中枢 {
|
||||
中枢序列: &mut Vec<Arc<中枢>>,
|
||||
跳过首部: bool,
|
||||
标识: &str,
|
||||
层级: i64,
|
||||
_层级: i64,
|
||||
) {
|
||||
if 虚线序列.len() < 3 {
|
||||
return;
|
||||
@@ -448,7 +482,7 @@ impl 中枢 {
|
||||
let 序号 = 虚线序列
|
||||
.iter()
|
||||
.position(|x| Arc::as_ptr(x) == Arc::as_ptr(左))
|
||||
.unwrap_or(i - 1);
|
||||
.expect("中枢.分析: 左元素不在虚线序列中");
|
||||
if 跳过首部 && (左.序号.load(Ordering::Relaxed) == 0 || 序号 == 0) {
|
||||
continue;
|
||||
}
|
||||
@@ -470,9 +504,9 @@ impl 中枢 {
|
||||
中.级别.load(Ordering::Relaxed),
|
||||
标识,
|
||||
));
|
||||
Self::向中枢序列尾部添加(中枢序列, 新中枢);
|
||||
Self::_向中枢序列尾部添加(中枢序列, 新中枢);
|
||||
// Python: return 中枢递归分析(虚线序列, 中枢序列, ...)
|
||||
Self::分析(虚线序列, 中枢序列, 跳过首部, 标识, 层级);
|
||||
Self::分析(虚线序列, 中枢序列, 跳过首部, 标识, _层级);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -483,11 +517,11 @@ impl 中枢 {
|
||||
let mut 当前中枢_idx = 中枢序列.len() - 1;
|
||||
|
||||
// Validate via shared reference (中枢 uses RwLock internally)
|
||||
let needs_pop = !中枢序列[当前中枢_idx].校验合法性(虚线序列);
|
||||
let needs_pop = !中枢序列[当前中枢_idx]._校验合法性(虚线序列);
|
||||
if needs_pop {
|
||||
let 当前中枢 = Arc::clone(&中枢序列[当前中枢_idx]);
|
||||
Self::从中枢序列尾部弹出(中枢序列, &当前中枢);
|
||||
Self::分析(虚线序列, 中枢序列, 跳过首部, 标识, 层级);
|
||||
Self::_从中枢序列尾部弹出(中枢序列, &当前中枢);
|
||||
Self::分析(虚线序列, 中枢序列, 跳过首部, 标识, _层级);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -508,8 +542,8 @@ impl 中枢 {
|
||||
let mut 中枢低 = 中枢序列[当前中枢_idx].低();
|
||||
let mut 候选序列: Vec<Arc<虚线>> = Vec::new();
|
||||
|
||||
for i in 起始索引..虚线序列.len() {
|
||||
let 当前虚线 = Arc::clone(&虚线序列[i]);
|
||||
for 当前虚线_ref in &虚线序列[起始索引..] {
|
||||
let 当前虚线 = Arc::clone(当前虚线_ref);
|
||||
|
||||
// 检查是否超出中枢范围(缺口)
|
||||
if crate::types::相对方向::分析(中枢高, 中枢低, 当前虚线.高(), 当前虚线.低()).是否缺口()
|
||||
@@ -527,12 +561,29 @@ impl 中枢 {
|
||||
.之后是(&当前虚线)
|
||||
};
|
||||
if needs_三买 {
|
||||
中枢序列[当前中枢_idx].设置第三买卖线(当前虚线.clone());
|
||||
中枢序列[当前中枢_idx].设置第三买卖线(Some(当前虚线.clone()));
|
||||
}
|
||||
} else {
|
||||
if 候选序列.is_empty() {
|
||||
// 仍在范围内:延伸中枢
|
||||
中枢序列[当前中枢_idx].添加虚线(当前虚线);
|
||||
debug_assert!(
|
||||
中枢序列[当前中枢_idx]
|
||||
.基础序列
|
||||
.read()
|
||||
.unwrap()
|
||||
.last()
|
||||
.unwrap()
|
||||
.之后是(&当前虚线),
|
||||
"中枢延伸: 不连续 {}, {}",
|
||||
中枢序列[当前中枢_idx]
|
||||
.基础序列
|
||||
.read()
|
||||
.unwrap()
|
||||
.last()
|
||||
.unwrap(),
|
||||
当前虚线
|
||||
);
|
||||
中枢序列[当前中枢_idx]._添加虚线(当前虚线);
|
||||
} else {
|
||||
候选序列.push(当前虚线);
|
||||
}
|
||||
@@ -548,9 +599,9 @@ impl 中枢 {
|
||||
.unwrap()
|
||||
.方向()
|
||||
.翻转();
|
||||
match Self::从序列中获取中枢(&候选序列, 起始方向, 标识) {
|
||||
match Self::_从序列中获取中枢(&候选序列, 起始方向, 标识) {
|
||||
Some(新中枢) => {
|
||||
Self::向中枢序列尾部添加(中枢序列, 新中枢);
|
||||
Self::_向中枢序列尾部添加(中枢序列, 新中枢);
|
||||
// Python: 当前中枢 = 新中枢
|
||||
当前中枢_idx = 中枢序列.len() - 1;
|
||||
中枢高 = 中枢序列[当前中枢_idx].高();
|
||||
@@ -566,6 +617,30 @@ impl 中枢 {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for 中枢 {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let 序列_str = self
|
||||
.基础序列
|
||||
.read()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|d| format!("{}", d))
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
write!(
|
||||
f,
|
||||
"{}({}, {}, 元素数量: {}, [{}], {} ===>>> {})",
|
||||
self.标识.read().unwrap(),
|
||||
crate::utils::format_f64_g(self.高()),
|
||||
crate::utils::format_f64_g(self.低()),
|
||||
self.基础序列.read().unwrap().len(),
|
||||
序列_str,
|
||||
self.文(),
|
||||
self.武(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -575,13 +650,14 @@ mod tests {
|
||||
use crate::types::分型结构;
|
||||
|
||||
fn 辅助_创建K线(时间戳: i64, 高: f64, 低: f64, 开: f64, 收: f64) -> K线 {
|
||||
let mut k = K线::default();
|
||||
k.时间戳 = 时间戳;
|
||||
k.高 = 高;
|
||||
k.低 = 低;
|
||||
k.开盘价 = 开;
|
||||
k.收盘价 = 收;
|
||||
k
|
||||
K线 {
|
||||
时间戳,
|
||||
高,
|
||||
低,
|
||||
开盘价: 开,
|
||||
收盘价: 收,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn 辅助_创建缠K(
|
||||
@@ -707,10 +783,10 @@ mod tests {
|
||||
assert_eq!(中枢.序号.load(Ordering::Relaxed), 99);
|
||||
|
||||
// RefCell 第三买卖线读写
|
||||
中枢.设置第三买卖线(Arc::clone(&笔1));
|
||||
中枢.设置第三买卖线(Some(Arc::clone(&笔1)));
|
||||
assert!(中枢.第三买卖线.read().unwrap().is_some());
|
||||
assert_eq!(
|
||||
Arc::as_ptr(&*中枢.第三买卖线.read().unwrap().as_ref().unwrap()),
|
||||
Arc::as_ptr(中枢.第三买卖线.read().unwrap().as_ref().unwrap()),
|
||||
Arc::as_ptr(&笔1)
|
||||
);
|
||||
|
||||
@@ -739,7 +815,7 @@ mod tests {
|
||||
);
|
||||
assert_eq!(中枢.基础序列.read().unwrap().len(), 3);
|
||||
|
||||
中枢.添加虚线(Arc::clone(&笔4));
|
||||
中枢._添加虚线(Arc::clone(&笔4));
|
||||
assert_eq!(中枢.基础序列.read().unwrap().len(), 4);
|
||||
assert_eq!(
|
||||
Arc::as_ptr(&中枢.基础序列.read().unwrap()[3]),
|
||||
@@ -760,12 +836,12 @@ mod tests {
|
||||
1,
|
||||
vec![Arc::clone(&笔1), Arc::clone(&笔2), Arc::clone(&笔3)],
|
||||
);
|
||||
中枢.设置第三买卖线(Arc::clone(&笔1));
|
||||
中枢.设置第三买卖线(Some(Arc::clone(&笔1)));
|
||||
*中枢.本级_第三买卖线.write().unwrap() = Some(Arc::clone(&笔2));
|
||||
assert!(中枢.第三买卖线.read().unwrap().is_some());
|
||||
assert!(中枢.本级_第三买卖线.read().unwrap().is_some());
|
||||
|
||||
中枢.添加虚线(Arc::clone(&笔4));
|
||||
中枢._添加虚线(Arc::clone(&笔4));
|
||||
// 添加虚线后第三买卖线被清除
|
||||
assert!(中枢.第三买卖线.read().unwrap().is_none());
|
||||
assert!(中枢.本级_第三买卖线.read().unwrap().is_none());
|
||||
@@ -787,7 +863,7 @@ mod tests {
|
||||
1,
|
||||
vec![Arc::clone(&笔1), Arc::clone(&笔2), Arc::clone(&笔3)],
|
||||
);
|
||||
中枢.设置第三买卖线(Arc::clone(&笔1));
|
||||
中枢.设置第三买卖线(Some(Arc::clone(&笔1)));
|
||||
|
||||
let 克隆 = 中枢.clone();
|
||||
|
||||
@@ -860,7 +936,7 @@ mod tests {
|
||||
assert_eq!(中枢2.序号.load(Ordering::Relaxed), 88);
|
||||
|
||||
// 通过 rc1 添加虚线
|
||||
中枢1.添加虚线(Arc::clone(&笔4));
|
||||
中枢1._添加虚线(Arc::clone(&笔4));
|
||||
assert_eq!(中枢2.基础序列.read().unwrap().len(), 4);
|
||||
|
||||
// 验证共享的 Arc<虚线> 指针一致
|
||||
@@ -870,27 +946,3 @@ mod tests {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for 中枢 {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let 序列_str = self
|
||||
.基础序列
|
||||
.read()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|d| format!("{}", d))
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
write!(
|
||||
f,
|
||||
"{}({}, {}, 元素数量: {}, [{}], {} ===>>> {})",
|
||||
self.标识.read().unwrap(),
|
||||
crate::utils::format_f64_g(self.高()),
|
||||
crate::utils::format_f64_g(self.低()),
|
||||
self.基础序列.read().unwrap().len(),
|
||||
序列_str,
|
||||
self.文(),
|
||||
self.武(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+499
-333
File diff suppressed because it is too large
Load Diff
+395
-6
@@ -27,24 +27,39 @@ use crate::kline::chan_kline::缠论K线;
|
||||
use crate::structure::fractal_obj::分型;
|
||||
use crate::types::bsp_type::买卖点类型;
|
||||
use crate::types::分型结构;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
/// 基础买卖点 — 买卖点的基础数据结构
|
||||
///
|
||||
/// 包含买卖点的完整信息:类型、关联分型/K线、失效与终结状态等。
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct 基础买卖点 {
|
||||
/// 买卖点备注文本
|
||||
pub 备注: String,
|
||||
/// 买卖点类型(一买/一卖/二买/二卖/三买/三卖/T1/T2/T3 等)
|
||||
pub 类型: 买卖点类型,
|
||||
/// 买卖点对应的分型
|
||||
pub 买卖点分型: Arc<分型>,
|
||||
/// 买卖点对应的缠论K线(即分型的中缠K)
|
||||
pub 买卖点K线: Arc<缠论K线>,
|
||||
/// 当前K线(买卖点生成时的K线)
|
||||
pub 当前K线: Arc<K线>,
|
||||
/// 失效K线(买卖点失效时设置)
|
||||
pub 失效K线: Option<Arc<K线>>,
|
||||
/// 终结K线(买卖点终结时设置)
|
||||
pub 终结K线: Option<Arc<K线>>,
|
||||
/// 中枢破位值
|
||||
pub 破位值: f64,
|
||||
/// 分型结构(可选,用于补充确认)
|
||||
pub 结构: Option<分型结构>,
|
||||
/// 创建时的缠K序号,用于偏移计算(与买卖点K线.序号同尺度)
|
||||
/// None 时退化为使用当前K线.序号(bar序号,旧行为)
|
||||
pub 当前缠K序号: Option<i64>,
|
||||
}
|
||||
|
||||
impl 基础买卖点 {
|
||||
/// 创建基础买卖点,买卖点K线自动取自买卖点分型的中缠K
|
||||
pub fn new(
|
||||
类型: 买卖点类型,
|
||||
当前K线: Arc<K线>,
|
||||
@@ -63,12 +78,17 @@ impl 基础买卖点 {
|
||||
终结K线: None,
|
||||
破位值: 中枢破位值,
|
||||
结构: None,
|
||||
当前缠K序号: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// 偏移 — 当前K线与买卖点K线的序号差
|
||||
/// 偏移 — 当前缠K序号与买卖点K线序号的差
|
||||
/// 如果设置了当前缠K序号(来自生成买卖点),使用缠K序号;否则退化为使用 bar序号
|
||||
pub fn 偏移(&self) -> i64 {
|
||||
self.当前K线.序号 - self.买卖点K线.序号.load(Ordering::Relaxed)
|
||||
match self.当前缠K序号 {
|
||||
Some(ck_idx) => ck_idx - self.买卖点K线.序号.load(Ordering::Relaxed),
|
||||
None => self.当前K线.序号 - self.买卖点K线.序号.load(Ordering::Relaxed),
|
||||
}
|
||||
}
|
||||
|
||||
/// 失效偏移
|
||||
@@ -108,10 +128,11 @@ impl std::fmt::Display for 基础买卖点 {
|
||||
}
|
||||
}
|
||||
|
||||
/// 买卖点 — 包含一二三类买卖点的工厂方法
|
||||
/// 买卖点 — 包含全部 18 种买卖点类型的工厂方法
|
||||
pub struct 买卖点;
|
||||
|
||||
impl 买卖点 {
|
||||
/// 创建 一卖 类型的基础买卖点
|
||||
pub fn 一卖点(
|
||||
买卖点分型: Arc<分型>,
|
||||
当前K线: Arc<K线>,
|
||||
@@ -122,6 +143,7 @@ impl 买卖点 {
|
||||
基础买卖点::new(买卖点类型::一卖, 当前K线, 买卖点分型, 备注, 中枢破位值)
|
||||
}
|
||||
|
||||
/// 创建 一买 类型的基础买卖点
|
||||
pub fn 一买点(
|
||||
买卖点分型: Arc<分型>,
|
||||
当前K线: Arc<K线>,
|
||||
@@ -132,6 +154,7 @@ impl 买卖点 {
|
||||
基础买卖点::new(买卖点类型::一买, 当前K线, 买卖点分型, 备注, 中枢破位值)
|
||||
}
|
||||
|
||||
/// 创建 二卖 类型的基础买卖点
|
||||
pub fn 二卖点(
|
||||
买卖点分型: Arc<分型>,
|
||||
当前K线: Arc<K线>,
|
||||
@@ -142,6 +165,7 @@ impl 买卖点 {
|
||||
基础买卖点::new(买卖点类型::二卖, 当前K线, 买卖点分型, 备注, 中枢破位值)
|
||||
}
|
||||
|
||||
/// 创建 二买 类型的基础买卖点
|
||||
pub fn 二买点(
|
||||
买卖点分型: Arc<分型>,
|
||||
当前K线: Arc<K线>,
|
||||
@@ -152,6 +176,7 @@ impl 买卖点 {
|
||||
基础买卖点::new(买卖点类型::二买, 当前K线, 买卖点分型, 备注, 中枢破位值)
|
||||
}
|
||||
|
||||
/// 创建 三卖 类型的基础买卖点
|
||||
pub fn 三卖点(
|
||||
买卖点分型: Arc<分型>,
|
||||
当前K线: Arc<K线>,
|
||||
@@ -162,6 +187,7 @@ impl 买卖点 {
|
||||
基础买卖点::new(买卖点类型::三卖, 当前K线, 买卖点分型, 备注, 中枢破位值)
|
||||
}
|
||||
|
||||
/// 创建 三买 类型的基础买卖点
|
||||
pub fn 三买点(
|
||||
买卖点分型: Arc<分型>,
|
||||
当前K线: Arc<K线>,
|
||||
@@ -172,6 +198,138 @@ impl 买卖点 {
|
||||
基础买卖点::new(买卖点类型::三买, 当前K线, 买卖点分型, 备注, 中枢破位值)
|
||||
}
|
||||
|
||||
/// 创建 T1卖 类型的基础买卖点
|
||||
pub fn T1卖点(
|
||||
买卖点分型: Arc<分型>,
|
||||
当前K线: Arc<K线>,
|
||||
_标识: &str,
|
||||
备注: String,
|
||||
中枢破位值: f64,
|
||||
) -> 基础买卖点 {
|
||||
基础买卖点::new(买卖点类型::T1卖, 当前K线, 买卖点分型, 备注, 中枢破位值)
|
||||
}
|
||||
|
||||
/// 创建 T1买 类型的基础买卖点
|
||||
pub fn T1买点(
|
||||
买卖点分型: Arc<分型>,
|
||||
当前K线: Arc<K线>,
|
||||
_标识: &str,
|
||||
备注: String,
|
||||
中枢破位值: f64,
|
||||
) -> 基础买卖点 {
|
||||
基础买卖点::new(买卖点类型::T1买, 当前K线, 买卖点分型, 备注, 中枢破位值)
|
||||
}
|
||||
|
||||
/// 创建 T1P卖 类型的基础买卖点
|
||||
pub fn T1P卖点(
|
||||
买卖点分型: Arc<分型>,
|
||||
当前K线: Arc<K线>,
|
||||
_标识: &str,
|
||||
备注: String,
|
||||
中枢破位值: f64,
|
||||
) -> 基础买卖点 {
|
||||
基础买卖点::new(买卖点类型::T1P卖, 当前K线, 买卖点分型, 备注, 中枢破位值)
|
||||
}
|
||||
|
||||
/// 创建 T1P买 类型的基础买卖点
|
||||
pub fn T1P买点(
|
||||
买卖点分型: Arc<分型>,
|
||||
当前K线: Arc<K线>,
|
||||
_标识: &str,
|
||||
备注: String,
|
||||
中枢破位值: f64,
|
||||
) -> 基础买卖点 {
|
||||
基础买卖点::new(买卖点类型::T1P买, 当前K线, 买卖点分型, 备注, 中枢破位值)
|
||||
}
|
||||
|
||||
/// 创建 T2卖 类型的基础买卖点
|
||||
pub fn T2卖点(
|
||||
买卖点分型: Arc<分型>,
|
||||
当前K线: Arc<K线>,
|
||||
_标识: &str,
|
||||
备注: String,
|
||||
中枢破位值: f64,
|
||||
) -> 基础买卖点 {
|
||||
基础买卖点::new(买卖点类型::T2卖, 当前K线, 买卖点分型, 备注, 中枢破位值)
|
||||
}
|
||||
|
||||
/// 创建 T2买 类型的基础买卖点
|
||||
pub fn T2买点(
|
||||
买卖点分型: Arc<分型>,
|
||||
当前K线: Arc<K线>,
|
||||
_标识: &str,
|
||||
备注: String,
|
||||
中枢破位值: f64,
|
||||
) -> 基础买卖点 {
|
||||
基础买卖点::new(买卖点类型::T2买, 当前K线, 买卖点分型, 备注, 中枢破位值)
|
||||
}
|
||||
|
||||
/// 创建 T2S卖 类型的基础买卖点
|
||||
pub fn T2S卖点(
|
||||
买卖点分型: Arc<分型>,
|
||||
当前K线: Arc<K线>,
|
||||
_标识: &str,
|
||||
备注: String,
|
||||
中枢破位值: f64,
|
||||
) -> 基础买卖点 {
|
||||
基础买卖点::new(买卖点类型::T2S卖, 当前K线, 买卖点分型, 备注, 中枢破位值)
|
||||
}
|
||||
|
||||
/// 创建 T2S买 类型的基础买卖点
|
||||
pub fn T2S买点(
|
||||
买卖点分型: Arc<分型>,
|
||||
当前K线: Arc<K线>,
|
||||
_标识: &str,
|
||||
备注: String,
|
||||
中枢破位值: f64,
|
||||
) -> 基础买卖点 {
|
||||
基础买卖点::new(买卖点类型::T2S买, 当前K线, 买卖点分型, 备注, 中枢破位值)
|
||||
}
|
||||
|
||||
/// 创建 T3A卖 类型的基础买卖点
|
||||
pub fn T3A卖点(
|
||||
买卖点分型: Arc<分型>,
|
||||
当前K线: Arc<K线>,
|
||||
_标识: &str,
|
||||
备注: String,
|
||||
中枢破位值: f64,
|
||||
) -> 基础买卖点 {
|
||||
基础买卖点::new(买卖点类型::T3A卖, 当前K线, 买卖点分型, 备注, 中枢破位值)
|
||||
}
|
||||
|
||||
/// 创建 T3A买 类型的基础买卖点
|
||||
pub fn T3A买点(
|
||||
买卖点分型: Arc<分型>,
|
||||
当前K线: Arc<K线>,
|
||||
_标识: &str,
|
||||
备注: String,
|
||||
中枢破位值: f64,
|
||||
) -> 基础买卖点 {
|
||||
基础买卖点::new(买卖点类型::T3A买, 当前K线, 买卖点分型, 备注, 中枢破位值)
|
||||
}
|
||||
|
||||
/// 创建 T3B卖 类型的基础买卖点
|
||||
pub fn T3B卖点(
|
||||
买卖点分型: Arc<分型>,
|
||||
当前K线: Arc<K线>,
|
||||
_标识: &str,
|
||||
备注: String,
|
||||
中枢破位值: f64,
|
||||
) -> 基础买卖点 {
|
||||
基础买卖点::new(买卖点类型::T3B卖, 当前K线, 买卖点分型, 备注, 中枢破位值)
|
||||
}
|
||||
|
||||
/// 创建 T3B买 类型的基础买卖点
|
||||
pub fn T3B买点(
|
||||
买卖点分型: Arc<分型>,
|
||||
当前K线: Arc<K线>,
|
||||
_标识: &str,
|
||||
备注: String,
|
||||
中枢破位值: f64,
|
||||
) -> 基础买卖点 {
|
||||
基础买卖点::new(买卖点类型::T3B买, 当前K线, 买卖点分型, 备注, 中枢破位值)
|
||||
}
|
||||
|
||||
/// 生成买卖点 — 根据参数自动选择类型
|
||||
pub fn 生成买卖点(
|
||||
特征: &str,
|
||||
@@ -186,10 +344,12 @@ impl 买卖点 {
|
||||
"卖"
|
||||
};
|
||||
let 备注 = format!("{}_{}{}{}", 特征, 级别, 序号, 买卖);
|
||||
let 破位值 = 买卖点分型.分型特征值;
|
||||
let 破位值 = 买卖点分型.分型特征值();
|
||||
|
||||
// 当前K线 — 从缠K获取其标的K线
|
||||
let 当前K线 = Arc::clone(&*当前缠K.标的K线.read().unwrap());
|
||||
// 当前缠K序号 — 与买卖点K线(分型.中.序号)同尺度,用于偏移计算
|
||||
let 当前缠K序号 = 当前缠K.序号.load(Ordering::Relaxed);
|
||||
|
||||
let 类型 = match (序号, 买卖) {
|
||||
("一", "买") => 买卖点类型::一买,
|
||||
@@ -198,9 +358,238 @@ impl 买卖点 {
|
||||
("二", "卖") => 买卖点类型::二卖,
|
||||
("三", "买") => 买卖点类型::三买,
|
||||
("三", "卖") => 买卖点类型::三卖,
|
||||
("T1", "买") => 买卖点类型::T1买,
|
||||
("T1", "卖") => 买卖点类型::T1卖,
|
||||
("T1P", "买") => 买卖点类型::T1P买,
|
||||
("T1P", "卖") => 买卖点类型::T1P卖,
|
||||
("T2", "买") => 买卖点类型::T2买,
|
||||
("T2", "卖") => 买卖点类型::T2卖,
|
||||
("T2S", "买") => 买卖点类型::T2S买,
|
||||
("T2S", "卖") => 买卖点类型::T2S卖,
|
||||
("T3A", "买") => 买卖点类型::T3A买,
|
||||
("T3A", "卖") => 买卖点类型::T3A卖,
|
||||
("T3B", "买") => 买卖点类型::T3B买,
|
||||
("T3B", "卖") => 买卖点类型::T3B卖,
|
||||
_ => 买卖点类型::一买, // fallback
|
||||
};
|
||||
|
||||
基础买卖点::new(类型, 当前K线, 买卖点分型, 备注, 破位值)
|
||||
let mut bsp = 基础买卖点::new(类型, 当前K线, 买卖点分型, 备注, 破位值);
|
||||
bsp.当前缠K序号 = Some(当前缠K序号);
|
||||
bsp
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::kline::bar::K线;
|
||||
use crate::kline::chan_kline::缠论K线;
|
||||
use crate::structure::fractal_obj::分型;
|
||||
use crate::types::分型结构;
|
||||
use crate::types::相对方向;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
fn 辅助_创建普K(时间戳: i64, 序号: i64) -> Arc<K线> {
|
||||
Arc::new(K线 {
|
||||
时间戳,
|
||||
序号,
|
||||
高: 100.0,
|
||||
低: 90.0,
|
||||
开盘价: 95.0,
|
||||
收盘价: 95.0,
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
|
||||
fn 辅助_创建缠K(
|
||||
序号: i64,
|
||||
时间戳: i64,
|
||||
高: f64,
|
||||
低: f64,
|
||||
分型: Option<分型结构>,
|
||||
) -> Arc<缠论K线> {
|
||||
let 普K = 辅助_创建普K(时间戳, 0);
|
||||
Arc::new(缠论K线::创建缠K(
|
||||
时间戳,
|
||||
高,
|
||||
低,
|
||||
相对方向::向上,
|
||||
分型,
|
||||
序号,
|
||||
普K,
|
||||
None,
|
||||
))
|
||||
}
|
||||
|
||||
fn 辅助_创建底分型_中(序号: i64, 时间戳: i64) -> Arc<分型> {
|
||||
let 中 = 辅助_创建缠K(序号, 时间戳, 100.0, 90.0, Some(分型结构::底));
|
||||
中.序号.store(序号, Ordering::Relaxed);
|
||||
中.分型特征值.set(90.0);
|
||||
let 左 = 辅助_创建缠K(序号 - 1, 时间戳 - 100, 100.0, 92.0, Some(分型结构::下));
|
||||
左.序号.store(序号 - 1, Ordering::Relaxed);
|
||||
let 右 = 辅助_创建缠K(序号 + 1, 时间戳 + 100, 100.0, 92.0, Some(分型结构::上));
|
||||
右.序号.store(序号 + 1, Ordering::Relaxed);
|
||||
Arc::new(分型::new(Some(左), 中, Some(右)))
|
||||
}
|
||||
|
||||
fn 辅助_创建顶分型_中(序号: i64, 时间戳: i64) -> Arc<分型> {
|
||||
let 中 = 辅助_创建缠K(序号, 时间戳, 100.0, 90.0, Some(分型结构::顶));
|
||||
中.序号.store(序号, Ordering::Relaxed);
|
||||
中.分型特征值.set(100.0);
|
||||
let 左 = 辅助_创建缠K(序号 - 1, 时间戳 - 100, 98.0, 88.0, Some(分型结构::上));
|
||||
左.序号.store(序号 - 1, Ordering::Relaxed);
|
||||
let 右 = 辅助_创建缠K(序号 + 1, 时间戳 + 100, 98.0, 88.0, Some(分型结构::下));
|
||||
右.序号.store(序号 + 1, Ordering::Relaxed);
|
||||
Arc::new(分型::new(Some(左), 中, Some(右)))
|
||||
}
|
||||
|
||||
// ========== 基础买卖点 构造测试 ==========
|
||||
|
||||
#[test]
|
||||
fn test_基础买卖点_new() {
|
||||
let 分型 = 辅助_创建底分型_中(10, 1000);
|
||||
let 当前K = 辅助_创建普K(1100, 0);
|
||||
let bsp = 基础买卖点::new(买卖点类型::一买, 当前K, 分型.clone(), "测试".into(), 90.0);
|
||||
assert_eq!(bsp.备注, "测试");
|
||||
assert_eq!(bsp.类型, 买卖点类型::一买);
|
||||
assert_eq!(bsp.破位值, 90.0);
|
||||
assert!(bsp.失效K线.is_none());
|
||||
assert!(!bsp.有效性());
|
||||
}
|
||||
|
||||
// ========== 偏移 测试 ==========
|
||||
|
||||
#[test]
|
||||
fn test_偏移_无缠K序号_退化为bar序号差值() {
|
||||
let 分型 = 辅助_创建底分型_中(10, 1000);
|
||||
let 当前K = 辅助_创建普K(1100, 15); // bar 序号=15
|
||||
let bsp = 基础买卖点::new(买卖点类型::一买, 当前K, 分型.clone(), "".into(), 0.0);
|
||||
// 无当前缠K序号: 偏移 = 15 - 10 = 5
|
||||
assert_eq!(bsp.偏移(), 5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_偏移_有缠K序号_使用缠K序号差值() {
|
||||
let 分型 = 辅助_创建底分型_中(10, 1000);
|
||||
let 当前K = 辅助_创建普K(1100, 100); // bar 序号(不使用)
|
||||
let mut bsp = 基础买卖点::new(买卖点类型::一买, 当前K, 分型.clone(), "".into(), 0.0);
|
||||
bsp.当前缠K序号 = Some(15);
|
||||
// 有当前缠K序号: 偏移 = 15 - 10 = 5
|
||||
assert_eq!(bsp.偏移(), 5);
|
||||
}
|
||||
|
||||
// ========== 失效偏移 测试 ==========
|
||||
|
||||
#[test]
|
||||
fn test_失效偏移_无失效K线返回负一() {
|
||||
let 分型 = 辅助_创建底分型_中(10, 1000);
|
||||
let 当前K = 辅助_创建普K(1100, 0);
|
||||
let bsp = 基础买卖点::new(买卖点类型::一买, 当前K, 分型.clone(), "".into(), 0.0);
|
||||
assert_eq!(bsp.失效偏移(), -1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_失效偏移_有失效K线() {
|
||||
let 分型 = 辅助_创建底分型_中(10, 1000);
|
||||
let 当前K = 辅助_创建普K(1100, 0);
|
||||
let mut bsp = 基础买卖点::new(买卖点类型::一买, 当前K, 分型.clone(), "".into(), 0.0);
|
||||
bsp.失效K线 = Some(辅助_创建普K(1200, 20)); // 序号=20
|
||||
// 失效偏移 = 20 - 10 = 10
|
||||
assert_eq!(bsp.失效偏移(), 10);
|
||||
}
|
||||
|
||||
// ========== 有效性 测试 ==========
|
||||
|
||||
#[test]
|
||||
fn test_有效性_无失效K线为false() {
|
||||
let 分型 = 辅助_创建底分型_中(10, 1000);
|
||||
let 当前K = 辅助_创建普K(1100, 0);
|
||||
let bsp = 基础买卖点::new(买卖点类型::一买, 当前K, 分型.clone(), "".into(), 0.0);
|
||||
assert!(!bsp.有效性());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_有效性_有失效K线为true() {
|
||||
let 分型 = 辅助_创建底分型_中(10, 1000);
|
||||
let 当前K = 辅助_创建普K(1100, 0);
|
||||
let mut bsp = 基础买卖点::new(买卖点类型::一买, 当前K, 分型.clone(), "".into(), 0.0);
|
||||
bsp.失效K线 = Some(辅助_创建普K(1200, 0));
|
||||
assert!(bsp.有效性());
|
||||
}
|
||||
|
||||
// ========== 生成买卖点 测试 ==========
|
||||
|
||||
#[test]
|
||||
fn test_生成买卖点_一买() {
|
||||
let 分型 = 辅助_创建底分型_中(5, 1000);
|
||||
let 当前缠K = 分型.中.clone();
|
||||
let bsp = 买卖点::生成买卖点("特征A", "一", "本级", 分型.clone(), 当前缠K);
|
||||
assert_eq!(bsp.类型, 买卖点类型::一买);
|
||||
assert_eq!(bsp.当前缠K序号, Some(5));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_生成买卖点_一卖() {
|
||||
let 分型 = 辅助_创建顶分型_中(5, 1000);
|
||||
let 当前缠K = 分型.中.clone();
|
||||
let bsp = 买卖点::生成买卖点("特征A", "一", "本级", 分型.clone(), 当前缠K);
|
||||
assert_eq!(bsp.类型, 买卖点类型::一卖);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_生成买卖点_二买() {
|
||||
let 分型 = 辅助_创建底分型_中(5, 1000);
|
||||
let 当前缠K = 分型.中.clone();
|
||||
let bsp = 买卖点::生成买卖点("特征B", "二", "同级", 分型.clone(), 当前缠K);
|
||||
assert_eq!(bsp.类型, 买卖点类型::二买);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_生成买卖点_三卖() {
|
||||
let 分型 = 辅助_创建顶分型_中(5, 1000);
|
||||
let 当前缠K = 分型.中.clone();
|
||||
let bsp = 买卖点::生成买卖点("特征C", "三", "本级", 分型.clone(), 当前缠K);
|
||||
assert_eq!(bsp.类型, 买卖点类型::三卖);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_生成买卖点_T1买() {
|
||||
let 分型 = 辅助_创建底分型_中(5, 1000);
|
||||
let 当前缠K = 分型.中.clone();
|
||||
let bsp = 买卖点::生成买卖点("事后", "T1", "次级", 分型.clone(), 当前缠K);
|
||||
assert_eq!(bsp.类型, 买卖点类型::T1买);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_生成买卖点_T2S卖() {
|
||||
let 分型 = 辅助_创建顶分型_中(5, 1000);
|
||||
let 当前缠K = 分型.中.clone();
|
||||
let bsp = 买卖点::生成买卖点("特征D", "T2S", "同级", 分型.clone(), 当前缠K);
|
||||
assert_eq!(bsp.类型, 买卖点类型::T2S卖);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_生成买卖点_T3A买() {
|
||||
let 分型 = 辅助_创建底分型_中(5, 1000);
|
||||
let 当前缠K = 分型.中.clone();
|
||||
let bsp = 买卖点::生成买卖点("特征E", "T3A", "本级", 分型.clone(), 当前缠K);
|
||||
assert_eq!(bsp.类型, 买卖点类型::T3A买);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_生成买卖点_T3B买() {
|
||||
let 分型 = 辅助_创建底分型_中(5, 1000);
|
||||
let 当前缠K = 分型.中.clone();
|
||||
let bsp = 买卖点::生成买卖点("特征F", "T3B", "本级", 分型.clone(), 当前缠K);
|
||||
assert_eq!(bsp.类型, 买卖点类型::T3B买);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_生成买卖点_破位值来自分型特征值() {
|
||||
let 分型 = 辅助_创建底分型_中(5, 1000);
|
||||
let 当前缠K = 分型.中.clone();
|
||||
let bsp = 买卖点::生成买卖点("特征G", "一", "本级", 分型.clone(), 当前缠K);
|
||||
assert_eq!(bsp.破位值, 分型.分型特征值());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ use crate::kline::bar::K线;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use std::sync::RwLock;
|
||||
use tracing::{error, info};
|
||||
|
||||
/// 立体分析器 — 多周期协调器
|
||||
///
|
||||
@@ -42,6 +43,7 @@ pub struct 立体分析器 {
|
||||
}
|
||||
|
||||
impl 立体分析器 {
|
||||
/// 创建立体分析器,自动创建K线合成器 + 每周期一个观察者
|
||||
pub fn new(
|
||||
符号: String,
|
||||
周期组: Vec<i64>,
|
||||
@@ -51,6 +53,7 @@ impl 立体分析器 {
|
||||
let mut 周期组 = 周期组;
|
||||
周期组.sort();
|
||||
let 输入周期 = 周期组[0];
|
||||
let 显示周期 = 周期组[1];
|
||||
|
||||
let 默认配置 = 配置.unwrap_or_default();
|
||||
let 配置组 = 配置组.unwrap_or_default();
|
||||
@@ -71,6 +74,33 @@ impl 立体分析器 {
|
||||
单体分析器.insert(周期, 观察员);
|
||||
}
|
||||
|
||||
// 显示周期特殊配置
|
||||
{
|
||||
let 显示观察员 = 单体分析器.get(&显示周期).expect("显示周期观察者不存在");
|
||||
let mut guard = 显示观察员.write().unwrap();
|
||||
guard.配置.推送K线 = true;
|
||||
guard.配置.推送笔 = true;
|
||||
guard.配置.推送线段 = true;
|
||||
guard.配置.图表展示 = true;
|
||||
guard.重置基础序列();
|
||||
}
|
||||
|
||||
// 非显示周期的基础缠K序列对齐至显示周期
|
||||
{
|
||||
let 显示缠K序列 = 单体分析器
|
||||
.get(&显示周期)
|
||||
.map(|o| o.read().unwrap().缠论K线序列.clone())
|
||||
.unwrap_or_default();
|
||||
|
||||
for &周期 in &周期组 {
|
||||
if 周期 != 显示周期
|
||||
&& let Some(观察员) = 单体分析器.get(&周期)
|
||||
{
|
||||
观察员.write().unwrap().基础缠K序列 = 显示缠K序列.clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Self {
|
||||
周期组,
|
||||
输入周期,
|
||||
@@ -83,11 +113,10 @@ impl 立体分析器 {
|
||||
/// 匹配 Python __K线回调:合成器完成K线时喂给观察者
|
||||
pub fn 投喂K线(&mut self, 普K: K线) {
|
||||
if 普K.周期 != self.输入周期 {
|
||||
eprintln!(
|
||||
panic!(
|
||||
"立体分析器.投喂K线 周期不匹配 {} != {}",
|
||||
普K.周期, self.输入周期
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Feed to synthesizer, get completion events
|
||||
@@ -97,6 +126,9 @@ impl 立体分析器 {
|
||||
for (周期, 完成K线) in 完成事件 {
|
||||
if let Some(观察员) = self.单体分析器.get(&周期) {
|
||||
观察员.write().unwrap().增加原始K线(完成K线);
|
||||
if let Some(当前K线) = self.K线合成器.获取当前K线(周期) {
|
||||
观察员.write().unwrap().增加原始K线(当前K线.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -108,10 +140,13 @@ impl 立体分析器 {
|
||||
|
||||
/// 测试_保存数据 — 多级别数据拆分保存
|
||||
/// 创建父目录 PyM_{标识}_{起始时间}_{结束时间},各周期观察者保存到子目录
|
||||
pub fn 测试_保存数据(&self) {
|
||||
let 根目录 = std::env::var("CHANLUN_DATA_DIR")
|
||||
.map(std::path::PathBuf::from)
|
||||
.unwrap_or_else(|_| std::env::temp_dir());
|
||||
pub fn 测试_保存数据(&self, root: Option<&str>) {
|
||||
let 根目录 = match root {
|
||||
Some(r) => std::path::PathBuf::from(r),
|
||||
None => std::env::var("CHANLUN_DATA_DIR")
|
||||
.map(std::path::PathBuf::from)
|
||||
.unwrap_or_else(|_| std::env::temp_dir()),
|
||||
};
|
||||
|
||||
let 起始时间 = self
|
||||
.单体分析器
|
||||
@@ -139,7 +174,7 @@ impl 立体分析器 {
|
||||
let 保存路径 = 根目录.join(&目录标识);
|
||||
|
||||
if let Err(e) = std::fs::create_dir_all(&保存路径) {
|
||||
eprintln!("创建目录失败: {} -> {}", 保存路径.display(), e);
|
||||
error!("创建目录失败: {} -> {}", 保存路径.display(), e);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -152,6 +187,6 @@ impl 立体分析器 {
|
||||
}
|
||||
}
|
||||
|
||||
println!("多级别数据拆分保存完成,目录:{}", 保存路径.display());
|
||||
info!("多级别数据拆分保存完成,目录:{}", 保存路径.display());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ use crate::types::相对方向;
|
||||
use crate::utils::datetime;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use tracing::{error, info};
|
||||
|
||||
/// 观察者 — 单周期分析器,持有所有层级序列,接收K线流式输入后逐层计算
|
||||
pub struct 观察者 {
|
||||
@@ -76,9 +77,9 @@ pub struct 观察者 {
|
||||
}
|
||||
|
||||
impl 观察者 {
|
||||
/// 创建观察者,初始化所有序列为空,若配置了手动终止则解析时间戳
|
||||
pub fn new(符号: String, 周期: i64, 配置: 缠论配置) -> Arc<RwLock<Self>> {
|
||||
let 终止时间戳 = if 配置.手动终止 != "1970-01-01 00:00:00" && !配置.手动终止.is_empty()
|
||||
{
|
||||
let 终止时间戳 = if !配置.手动终止.is_empty() {
|
||||
datetime::转化为时间戳(&配置.手动终止)
|
||||
} else {
|
||||
None
|
||||
@@ -147,14 +148,22 @@ impl 观察者 {
|
||||
|
||||
/// 增加原始K线 — 单根K线投喂入口
|
||||
pub fn 增加原始K线(&mut self, 普K: K线) {
|
||||
if let Some(终止) = self.终止时间戳 {
|
||||
if 普K.时间戳 > 终止 {
|
||||
return;
|
||||
}
|
||||
if let Some(终止) = self.终止时间戳
|
||||
&& 普K.时间戳 > 终止
|
||||
{
|
||||
return;
|
||||
}
|
||||
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)
|
||||
@@ -177,6 +186,7 @@ impl 观察者 {
|
||||
&mut self.笔序列,
|
||||
&self.缠论K线序列,
|
||||
&self.普通K线序列,
|
||||
0,
|
||||
&self.配置,
|
||||
);
|
||||
}
|
||||
@@ -261,6 +271,9 @@ impl 观察者 {
|
||||
}
|
||||
}
|
||||
|
||||
/// 识别买卖点(占位方法,具体逻辑在子类或Rust核心中实现)
|
||||
pub fn 识别买卖点(&self) {}
|
||||
|
||||
/// 静态重新分析 — 遍历所有缠K重新生成分型/笔/线段
|
||||
pub fn 静态重新分析(&mut self) {
|
||||
self.分型序列.clear();
|
||||
@@ -289,6 +302,7 @@ impl 观察者 {
|
||||
&mut self.笔序列,
|
||||
&self.缠论K线序列,
|
||||
&self.普通K线序列,
|
||||
0,
|
||||
&self.配置,
|
||||
);
|
||||
}
|
||||
@@ -345,7 +359,7 @@ impl 观察者 {
|
||||
}
|
||||
|
||||
/// 测试_保存数据 — 输出各序列数据文本到文件
|
||||
pub fn 测试_保存数据(&self, root: Option<&str>) {
|
||||
pub fn 测试_保存数据(&self, root: Option<&str>) -> String {
|
||||
let 笔序列_文本数据: Vec<String> = self.笔序列.iter().map(|b| b.获取数据文本()).collect();
|
||||
let 线段序列_文本数据: Vec<String> =
|
||||
self.线段序列.iter().map(|s| s.获取数据文本()).collect();
|
||||
@@ -392,9 +406,7 @@ impl 观察者 {
|
||||
// 确定根目录
|
||||
let 根目录 = match root {
|
||||
Some(r) => std::path::PathBuf::from(r),
|
||||
None => std::env::var("CHANLUN_DATA_DIR")
|
||||
.map(std::path::PathBuf::from)
|
||||
.unwrap_or_else(|_| std::env::temp_dir()),
|
||||
None => std::env::temp_dir(),
|
||||
};
|
||||
|
||||
// 生成子目录名称
|
||||
@@ -404,8 +416,8 @@ impl 观察者 {
|
||||
|
||||
let 保存路径 = 根目录.join(&目录标识);
|
||||
if let Err(e) = std::fs::create_dir_all(&保存路径) {
|
||||
eprintln!("创建目录失败: {} -> {}", 保存路径.display(), e);
|
||||
return;
|
||||
error!("创建目录失败: {} -> {}", 保存路径.display(), e);
|
||||
return String::new();
|
||||
}
|
||||
|
||||
// 缠K data for debugging
|
||||
@@ -434,7 +446,7 @@ impl 观察者 {
|
||||
format!(
|
||||
"分型, {}, {}, {:?}, {}, {}, {}",
|
||||
i,
|
||||
fx.时间戳,
|
||||
fx.时间戳(),
|
||||
fx.结构,
|
||||
fx.分型特征值,
|
||||
fx.中.时间戳.load(Ordering::Relaxed),
|
||||
@@ -470,46 +482,34 @@ impl 观察者 {
|
||||
let 文件路径 = 保存路径.join(format!("{}.txt", 文件名));
|
||||
let 内容 = 数据列表.join("\n") + "\n";
|
||||
if let Err(e) = std::fs::write(&文件路径, &内容) {
|
||||
eprintln!("写入文件失败: {} -> {}", 文件路径.display(), e);
|
||||
error!("写入文件失败: {} -> {}", 文件路径.display(), e);
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
info!("全部数据拆分保存完成,目录:{}", 保存路径.display());
|
||||
保存路径.display().to_string()
|
||||
}
|
||||
|
||||
/// 加载本地数据 — 从 .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()
|
||||
@@ -519,23 +519,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.加载本地数据(文件路径)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -544,20 +533,31 @@ 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() {
|
||||
let pu_seq = bi.获取普K序列(&obs_ref.普通K线序列);
|
||||
if pu_seq.is_empty() {
|
||||
println!("笔 {}: 获取普K序列 返回空 (fallback failed)", i);
|
||||
println!(" 文.中.标的K线 原始起始序号: {}", bi.文.中.原始起始序号);
|
||||
println!(
|
||||
info!("笔 {}: 获取普K序列 返回空 (fallback failed)", i);
|
||||
info!(" 文.中.标的K线 原始起始序号: {}", bi.文.中.原始起始序号);
|
||||
info!(
|
||||
" 武.中.标的K线 原始结束序号: {}",
|
||||
bi.武
|
||||
.read()
|
||||
@@ -566,7 +566,7 @@ mod tests {
|
||||
.原始结束序号
|
||||
.load(Ordering::Relaxed)
|
||||
);
|
||||
println!(" 普通K线序列.len: {}", obs_ref.普通K线序列.len());
|
||||
info!(" 普通K线序列.len: {}", obs_ref.普通K线序列.len());
|
||||
} else {
|
||||
let first_ptr = Arc::as_ptr(&pu_seq[0]);
|
||||
let found = obs_ref
|
||||
@@ -574,15 +574,15 @@ mod tests {
|
||||
.iter()
|
||||
.any(|k| Arc::as_ptr(k) == first_ptr);
|
||||
if !found {
|
||||
println!("笔 {}: 获取普K序列[0] 的 Rc 指针不在 普通K线序列 中!", i);
|
||||
info!("笔 {}: 获取普K序列[0] 的 Rc 指针不在 普通K线序列 中!", i);
|
||||
let wen_ptr = Arc::as_ptr(&*bi.文.中.标的K线.read().unwrap());
|
||||
let wen_found = obs_ref
|
||||
.普通K线序列
|
||||
.iter()
|
||||
.any(|k| Arc::as_ptr(k) == wen_ptr);
|
||||
println!(" 文.中.标的K线 在序列中: {}", wen_found);
|
||||
info!(" 文.中.标的K线 在序列中: {}", wen_found);
|
||||
} else {
|
||||
println!("笔 {}: OK, 获取普K序列[0] 在序列中找到", i);
|
||||
info!("笔 {}: OK, 获取普K序列[0] 在序列中找到", i);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -593,7 +593,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 {
|
||||
@@ -605,21 +605,21 @@ mod tests {
|
||||
}
|
||||
|
||||
let obs = obs_ref.read().unwrap();
|
||||
println!("普通K线序列.len: {}", obs.普通K线序列.len());
|
||||
println!("笔序列.len: {}", obs.笔序列.len());
|
||||
info!("普通K线序列.len: {}", obs.普通K线序列.len());
|
||||
info!("笔序列.len: {}", obs.笔序列.len());
|
||||
|
||||
for (i, bi) in obs.笔序列.iter().enumerate() {
|
||||
let pu_seq = bi.获取普K序列(&obs.普通K线序列);
|
||||
if pu_seq.is_empty() {
|
||||
println!("笔 {}: 获取普K序列 返回空!", i);
|
||||
info!("笔 {}: 获取普K序列 返回空!", i);
|
||||
} else {
|
||||
let first_ptr = Arc::as_ptr(&pu_seq[0]);
|
||||
let found = obs.普通K线序列.iter().any(|k| Arc::as_ptr(k) == first_ptr);
|
||||
if !found {
|
||||
println!("笔 {}: 获取普K序列[0] 指针不在序列中!", i);
|
||||
info!("笔 {}: 获取普K序列[0] 指针不在序列中!", i);
|
||||
}
|
||||
if i < 5 {
|
||||
println!("笔 {}: OK, len={}", i, pu_seq.len());
|
||||
info!("笔 {}: OK, len={}", i, pu_seq.len());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -631,8 +631,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 指针必须在 分型序列 中
|
||||
@@ -640,16 +643,16 @@ mod tests {
|
||||
let 文_ptr = Arc::as_ptr(&bi.文);
|
||||
let 文_found = obs_ref.分型序列.iter().any(|f| Arc::as_ptr(f) == 文_ptr);
|
||||
if !文_found {
|
||||
println!("笔 {}: 文(时间戳={}) 不在分型序列中!", i, bi.文.时间戳);
|
||||
info!("笔 {}: 文(时间戳={}) 不在分型序列中!", i, bi.文.时间戳());
|
||||
}
|
||||
|
||||
let 武_ptr = Arc::as_ptr(&*bi.武.read().unwrap());
|
||||
let 武_found = obs_ref.分型序列.iter().any(|f| Arc::as_ptr(f) == 武_ptr);
|
||||
if !武_found {
|
||||
println!(
|
||||
info!(
|
||||
"笔 {}: 武(时间戳={}) 不在分型序列中!",
|
||||
i,
|
||||
bi.武.read().unwrap().时间戳
|
||||
bi.武.read().unwrap().时间戳()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -661,8 +664,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 指针必须在 笔序列 中
|
||||
@@ -671,7 +677,7 @@ mod tests {
|
||||
let bi_ptr = Arc::as_ptr(bi_in_seg);
|
||||
let found = obs_ref.笔序列.iter().any(|b| Arc::as_ptr(b) == bi_ptr);
|
||||
if !found {
|
||||
println!("线段 {} 的基础序列[{}] 不在笔序列中!", i, j);
|
||||
info!("线段 {} 的基础序列[{}] 不在笔序列中!", i, j);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -683,8 +689,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() {
|
||||
@@ -692,7 +701,7 @@ mod tests {
|
||||
let bi_ptr = Arc::as_ptr(bi_in_hub);
|
||||
let found = obs_ref.笔序列.iter().any(|b| Arc::as_ptr(b) == bi_ptr);
|
||||
if !found {
|
||||
println!("笔中枢 {} 的基础序列[{}] 不在笔序列中!", i, j);
|
||||
info!("笔中枢 {} 的基础序列[{}] 不在笔序列中!", i, j);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -702,7 +711,7 @@ mod tests {
|
||||
let seg_ptr = Arc::as_ptr(seg_in_hub);
|
||||
let found = obs_ref.线段序列.iter().any(|s| Arc::as_ptr(s) == seg_ptr);
|
||||
if !found {
|
||||
println!("线段中枢 {} 的基础序列[{}] 不在线段序列中!", i, j);
|
||||
info!("线段中枢 {} 的基础序列[{}] 不在线段序列中!", i, j);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -714,7 +723,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 计算 = || {
|
||||
@@ -743,7 +752,7 @@ mod tests {
|
||||
assert_eq!(中枢1, 中枢2, "重复计算中枢数不一致");
|
||||
assert_eq!(笔中枢1, 笔中枢2, "重复计算笔中枢数不一致");
|
||||
|
||||
println!(
|
||||
info!(
|
||||
"两次计算结果一致: 笔={}, 线段={}, 中枢={}, 笔中枢={}",
|
||||
笔数1, 段数1, 中枢1, 笔中枢1
|
||||
);
|
||||
@@ -758,7 +767,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 {
|
||||
@@ -789,7 +798,7 @@ mod tests {
|
||||
|
||||
assert_eq!(第一次笔数, 第二次笔数, "重置后重新投喂笔数不一致");
|
||||
assert_eq!(第一次段数, 第二次段数, "重置后重新投喂线段数不一致");
|
||||
println!("重置后重投一致: 笔={}, 线段={}", 第一次笔数, 第一次段数);
|
||||
info!("重置后重投一致: 笔={}, 线段={}", 第一次笔数, 第一次段数);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
@@ -798,8 +807,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
|
||||
@@ -829,8 +841,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 作用域
|
||||
@@ -858,8 +873,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线序列 中
|
||||
@@ -867,14 +885,14 @@ mod tests {
|
||||
let 中_ptr = Arc::as_ptr(&f.中);
|
||||
let 中_found = obs_ref.缠论K线序列.iter().any(|k| Arc::as_ptr(k) == 中_ptr);
|
||||
if !中_found {
|
||||
println!("分型 {} 的 中(时间戳={}) 不在缠论K线序列中!", i, f.时间戳);
|
||||
info!("分型 {} 的 中(时间戳={}) 不在缠论K线序列中!", i, f.时间戳);
|
||||
}
|
||||
|
||||
if let Some(ref 左) = f.左 {
|
||||
let 左_ptr = Arc::as_ptr(左);
|
||||
let 左_found = obs_ref.缠论K线序列.iter().any(|k| Arc::as_ptr(k) == 左_ptr);
|
||||
if !左_found {
|
||||
println!("分型 {} 的 左 不在缠论K线序列中!", i);
|
||||
info!("分型 {} 的 左 不在缠论K线序列中!", i);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -882,7 +900,7 @@ mod tests {
|
||||
let 右_ptr = Arc::as_ptr(右);
|
||||
let 右_found = obs_ref.缠论K线序列.iter().any(|k| Arc::as_ptr(k) == 右_ptr);
|
||||
if !右_found {
|
||||
println!("分型 {} 的 右 不在缠论K线序列中!", i);
|
||||
info!("分型 {} 的 右 不在缠论K线序列中!", i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ pub struct K线合成器 {
|
||||
}
|
||||
|
||||
impl K线合成器 {
|
||||
/// 创建K线合成器,按周期升序排列,初始化当前K线和合成K线列表
|
||||
pub fn new(标识: String, 周期组: Vec<i64>) -> Self {
|
||||
let mut 周期组 = 周期组;
|
||||
周期组.sort();
|
||||
@@ -53,6 +54,20 @@ impl K线合成器 {
|
||||
}
|
||||
}
|
||||
|
||||
/// 投喂 — 便捷入口,直接从 OHLCV 创建 K线 并投喂
|
||||
pub fn 投喂(
|
||||
&mut self,
|
||||
时间戳: i64,
|
||||
开: f64,
|
||||
高: f64,
|
||||
低: f64,
|
||||
收: f64,
|
||||
量: f64,
|
||||
) -> Vec<(i64, K线)> {
|
||||
let 普K = K线::创建普K(&self.标识, 时间戳, 开, 高, 低, 收, 量, 0, 0);
|
||||
self.投喂K线(普K)
|
||||
}
|
||||
|
||||
/// 投喂K线 — 输入最小周期K线,合成为所有目标周期
|
||||
/// 返回本次投喂完成了哪些周期的K线(周期 → 完成K线)
|
||||
pub fn 投喂K线(&mut self, 普K: K线) -> Vec<(i64, K线)> {
|
||||
@@ -91,7 +106,7 @@ impl K线合成器 {
|
||||
|
||||
fn _对齐时间戳(&self, 时间戳: i64, 周期: i64) -> i64 {
|
||||
if 周期 == 0 {
|
||||
return 时间戳;
|
||||
panic!("_对齐时间戳: 周期不能为0");
|
||||
}
|
||||
(时间戳 / 周期) * 周期
|
||||
}
|
||||
|
||||
+180
-13
@@ -23,6 +23,7 @@
|
||||
*/
|
||||
|
||||
use serde::{Deserialize, Deserializer, Serialize};
|
||||
use tracing::warn;
|
||||
|
||||
fn is_infinite_f64(v: &f64) -> bool {
|
||||
v.is_infinite()
|
||||
@@ -30,118 +31,223 @@ fn is_infinite_f64(v: &f64) -> bool {
|
||||
|
||||
/// 缠论配置 —— 控制所有分析阶段的行为
|
||||
///
|
||||
/// 所有字段带默认值,使用 `#[serde(default)]` 实现缺失字段容错
|
||||
/// 50+ 参数集中控制缠K合并、笔/线段划分、中枢识别、买卖点生成等所有阶段。
|
||||
/// 所有字段带默认值,使用 `#[serde(default)]` 实现缺失字段容错。
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct 缠论配置 {
|
||||
// ---- 基础 ----
|
||||
/// 品种标识(如 "btcusd")
|
||||
pub 标识: String,
|
||||
|
||||
// ---- 缠K ----
|
||||
/// 包含处理时使用合并替换模式(而非添加模式)
|
||||
pub 缠K合并替换: bool,
|
||||
|
||||
// ---- 笔 ----
|
||||
/// 笔内最少缠K数量(含端点)
|
||||
pub 笔内元素数量: i64,
|
||||
/// 笔内相同终点取舍开关
|
||||
pub 笔内相同终点取舍: bool,
|
||||
/// 笔内起始分型包含整笔
|
||||
pub 笔内起始分型包含整笔: bool,
|
||||
/// 笔内起始分型包含整笔(含右端点)
|
||||
pub 笔内起始分型包含整笔_包括右: bool,
|
||||
/// 笔内原始K线包含整笔
|
||||
pub 笔内原始K线包含整笔: bool,
|
||||
/// 笔次级成笔(允许在非分型处成笔)
|
||||
pub 笔次级成笔: bool,
|
||||
/// 笔弱化开关(允许更少元素成笔)
|
||||
pub 笔弱化: bool,
|
||||
/// 笔弱化模式下的最小原始K线数
|
||||
pub 笔弱化_原始数量: i64,
|
||||
|
||||
// ---- 线段 ----
|
||||
/// 线段非缺口下的穿刺处理
|
||||
pub 线段_非缺口下穿刺: bool,
|
||||
/// 线段特征序列忽略老阴老阳
|
||||
pub 线段_特征序列忽视老阴老阳: bool,
|
||||
/// 线段缺口后紧急修正
|
||||
pub 线段_缺口后紧急修正: bool,
|
||||
/// 线段修正开关
|
||||
pub 线段_修正: bool,
|
||||
/// 线段内部中枢图显示
|
||||
pub 线段内部中枢图显: bool,
|
||||
/// 扩展线段当下分析模式
|
||||
pub 扩展线段_当下分析: bool,
|
||||
|
||||
// ---- 分析开关 ----
|
||||
/// 是否分析笔
|
||||
pub 分析笔: bool,
|
||||
/// 是否分析线段
|
||||
pub 分析线段: bool,
|
||||
/// 是否分析扩展线段
|
||||
pub 分析扩展线段: bool,
|
||||
/// 是否分析笔中枢
|
||||
pub 分析笔中枢: bool,
|
||||
/// 是否分析线段中枢
|
||||
pub 分析线段中枢: bool,
|
||||
|
||||
// ---- 终止 ----
|
||||
/// 手动终止时间(时间字符串,非空时生效)
|
||||
pub 手动终止: String,
|
||||
|
||||
// ---- 指标 ----
|
||||
/// 是否计算技术指标
|
||||
pub 计算指标: bool,
|
||||
/// 是否计算布林带
|
||||
pub 计算BOLL: bool,
|
||||
/// 指标计算方式(开/高/低/收/高低均值/高低收均值/开高低收均值)
|
||||
#[serde(deserialize_with = "deserialize_指标计算方式")]
|
||||
pub 指标计算方式: String,
|
||||
|
||||
// ---- MACD ----
|
||||
/// MACD 快线 EMA 周期
|
||||
pub 平滑异同移动平均线_快线周期: i64,
|
||||
/// MACD 慢线 EMA 周期
|
||||
pub 平滑异同移动平均线_慢线周期: i64,
|
||||
/// MACD 信号线周期
|
||||
pub 平滑异同移动平均线_信号周期: i64,
|
||||
/// MACD 多参数列表: Vec<(key, 快线, 慢线, 信号)>
|
||||
#[serde(default)]
|
||||
pub MACD_参数列表: Vec<(String, i64, i64, i64)>,
|
||||
|
||||
// ---- RSI ----
|
||||
/// RSI 计算周期
|
||||
pub 相对强弱指数_周期: i64,
|
||||
/// RSI SMA 平滑周期
|
||||
pub 相对强弱指数_移动平均线周期: i64,
|
||||
/// RSI 超买阈值
|
||||
pub 相对强弱指数_超买阈值: f64,
|
||||
/// RSI 超卖阈值
|
||||
pub 相对强弱指数_超卖阈值: f64,
|
||||
/// RSI 多周期列表: Vec<(key, 周期)>
|
||||
#[serde(default)]
|
||||
pub RSI_周期列表: Vec<(String, i64)>,
|
||||
|
||||
// ---- KDJ ----
|
||||
/// KDJ RSV 周期
|
||||
pub 随机指标_RSV周期: i64,
|
||||
/// KDJ K 值平滑周期
|
||||
pub 随机指标_K值平滑周期: i64,
|
||||
/// KDJ D 值平滑周期
|
||||
pub 随机指标_D值平滑周期: i64,
|
||||
/// KDJ 超买阈值
|
||||
pub 随机指标_超买阈值: f64,
|
||||
/// KDJ 超卖阈值
|
||||
pub 随机指标_超卖阈值: f64,
|
||||
/// KDJ 多参数列表: Vec<(key, RSV周期, K平滑, D平滑)>
|
||||
#[serde(default)]
|
||||
pub KDJ_参数列表: Vec<(String, i64, i64, i64)>,
|
||||
|
||||
// ---- BOLL ----
|
||||
/// 布林带周期
|
||||
pub 布林带_周期: i64,
|
||||
/// 布林带标准差倍数
|
||||
pub 布林带_标准差倍数: f64,
|
||||
/// BOLL 多参数列表: Vec<(key, 周期, 标准差倍数)>
|
||||
#[serde(default)]
|
||||
pub BOLL_参数列表: Vec<(String, i64, f64)>,
|
||||
|
||||
// ---- 均线 ----
|
||||
/// 均线类型列表: ["SMA", "EMA", ...]
|
||||
#[serde(default)]
|
||||
pub 均线_类型列表: Vec<String>,
|
||||
/// 均线周期列表: [5, 10, 20, ...]
|
||||
#[serde(default)]
|
||||
pub 均线_周期列表: Vec<i64>,
|
||||
|
||||
// ---- 推送/显示 ----
|
||||
/// 是否启用图表展示
|
||||
pub 图表展示: bool,
|
||||
/// 是否推送K线
|
||||
pub 推送K线: bool,
|
||||
/// 是否推送笔
|
||||
pub 推送笔: bool,
|
||||
/// 是否推送线段
|
||||
pub 推送线段: bool,
|
||||
/// 是否推送中枢
|
||||
pub 推送中枢: bool,
|
||||
|
||||
// ---- 图表展示细分 ----
|
||||
/// 图表展示笔
|
||||
pub 图表展示_笔: bool,
|
||||
/// 图表展示线段
|
||||
pub 图表展示_线段: bool,
|
||||
/// 图表展示扩展线段
|
||||
pub 图表展示_扩展线段: bool,
|
||||
/// 图表展示扩展线段(线段级)
|
||||
pub 图表展示_扩展线段_线段: bool,
|
||||
/// 图表展示线段之线段
|
||||
pub 图表展示_线段_线段: bool,
|
||||
/// 图表展示笔中枢
|
||||
pub 图表展示_中枢_笔: bool,
|
||||
/// 图表展示线段中枢
|
||||
pub 图表展示_中枢_线段: bool,
|
||||
/// 图表展示扩展中枢
|
||||
pub 图表展示_中枢_扩展线段: bool,
|
||||
/// 图表展示扩展中枢(线段级)
|
||||
pub 图表展示_中枢_扩展线段_线段: bool,
|
||||
/// 图表展示线段之中枢
|
||||
pub 图表展示_中枢_线段_线段: bool,
|
||||
/// 图表展示线段内部中枢
|
||||
pub 图表展示_中枢_线段内部: bool,
|
||||
|
||||
// ---- 买卖点 ----
|
||||
/// 买卖点偏移量
|
||||
pub 买卖点偏移: i64,
|
||||
/// 买卖点激进识别模式
|
||||
pub 买卖点激进识别: bool,
|
||||
/// 买卖点与MACD柱强相关
|
||||
pub 买卖点与MACD柱强相关: bool,
|
||||
/// 买卖点错过误差值
|
||||
pub 买卖点错过误差值: f64,
|
||||
/// 买卖点指标模式(任意/配置/全量/相对)
|
||||
#[serde(deserialize_with = "deserialize_买卖点_指标模式")]
|
||||
pub 买卖点_指标模式: String,
|
||||
/// 买卖点指标匹配 MACD
|
||||
pub 买卖点_指标匹配_MACD: bool,
|
||||
/// 买卖点指标匹配 KDJ
|
||||
pub 买卖点_指标匹配_KDJ: bool,
|
||||
/// 买卖点指标匹配 RSI
|
||||
pub 买卖点_指标匹配_RSI: bool,
|
||||
/// 买卖点背离率阈值(Infinity 表示不使用)
|
||||
#[serde(skip_serializing_if = "is_infinite_f64")]
|
||||
pub 买卖点_背离率: f64,
|
||||
/// 买卖点 T2 回调阈值
|
||||
pub 买卖点_T2_回调阈值: f64,
|
||||
/// 买卖点 T2S 最大层级
|
||||
pub 买卖点_T2S_最大层级: i64,
|
||||
/// 买卖点峰值条件
|
||||
pub 买卖点_峰值条件: bool,
|
||||
/// 买卖点计算方式(峰/谷等)
|
||||
pub 买卖点_计算方式: String,
|
||||
/// 是否计算线段BSP1
|
||||
pub 买卖点_计算线段BSP1: bool,
|
||||
/// 是否处理BSP2
|
||||
pub 买卖点_处理BSP2: bool,
|
||||
/// 是否计算线段BSP3
|
||||
pub 买卖点_计算线段BSP3: bool,
|
||||
/// 是否依赖T1买卖点
|
||||
pub 买卖点_依赖T1: bool,
|
||||
/// 买卖点中枢来源(实/虚/合)
|
||||
pub 买卖点_中枢来源: String,
|
||||
/// 买卖点调试输出
|
||||
pub 买卖点_调试输出: bool,
|
||||
|
||||
// ---- 背驰 ----
|
||||
/// 线段内部背驰使用 MACD
|
||||
pub 线段内部背驰_MACD: bool,
|
||||
/// 线段内部背驰使用斜率
|
||||
pub 线段内部背驰_斜率: bool,
|
||||
/// 线段内部背驰使用测度
|
||||
pub 线段内部背驰_测度: bool,
|
||||
/// 线段内部背驰模式(任意/配置/全量/相对)
|
||||
#[serde(deserialize_with = "deserialize_线段内部背驰_模式")]
|
||||
pub 线段内部背驰_模式: String,
|
||||
|
||||
// ---- 文件 ----
|
||||
/// 加载数据文件路径
|
||||
pub 加载文件路径: String,
|
||||
}
|
||||
|
||||
@@ -163,7 +269,9 @@ where
|
||||
if VALID.contains(&s.as_str()) {
|
||||
Ok(s)
|
||||
} else {
|
||||
eprintln!("\x1b[33m[配置警告]\x1b[m 指标计算方式: \"{s}\" 不在有效值 {VALID:?} 内,已使用默认值 \"{DEFAULT}\"");
|
||||
warn!(
|
||||
"[配置警告] 指标计算方式: \"{s}\" 不在有效值 {VALID:?} 内,已使用默认值 \"{DEFAULT}\""
|
||||
);
|
||||
Ok(DEFAULT.to_string())
|
||||
}
|
||||
}
|
||||
@@ -178,7 +286,9 @@ where
|
||||
if VALID.contains(&s.as_str()) {
|
||||
Ok(s)
|
||||
} else {
|
||||
eprintln!("\x1b[33m[配置警告]\x1b[m 买卖点_指标模式: \"{s}\" 不在有效值 {VALID:?} 内,已使用默认值 \"{DEFAULT}\"");
|
||||
warn!(
|
||||
"[配置警告] 买卖点_指标模式: \"{s}\" 不在有效值 {VALID:?} 内,已使用默认值 \"{DEFAULT}\""
|
||||
);
|
||||
Ok(DEFAULT.to_string())
|
||||
}
|
||||
}
|
||||
@@ -193,7 +303,9 @@ where
|
||||
if VALID.contains(&s.as_str()) {
|
||||
Ok(s)
|
||||
} else {
|
||||
eprintln!("\x1b[33m[配置警告]\x1b[m 线段内部背驰_模式: \"{s}\" 不在有效值 {VALID:?} 内,已使用默认值 \"{DEFAULT}\"");
|
||||
warn!(
|
||||
"[配置警告] 线段内部背驰_模式: \"{s}\" 不在有效值 {VALID:?} 内,已使用默认值 \"{DEFAULT}\""
|
||||
);
|
||||
Ok(DEFAULT.to_string())
|
||||
}
|
||||
}
|
||||
@@ -224,6 +336,7 @@ impl Default for 缠论配置 {
|
||||
分析线段中枢: true,
|
||||
手动终止: String::new(),
|
||||
计算指标: true,
|
||||
计算BOLL: false,
|
||||
指标计算方式: "收".into(),
|
||||
平滑异同移动平均线_快线周期: 13,
|
||||
平滑异同移动平均线_慢线周期: 31,
|
||||
@@ -237,6 +350,14 @@ impl Default for 缠论配置 {
|
||||
随机指标_D值平滑周期: 5,
|
||||
随机指标_超买阈值: 80.0,
|
||||
随机指标_超卖阈值: 20.0,
|
||||
MACD_参数列表: Vec::new(),
|
||||
RSI_周期列表: Vec::new(),
|
||||
KDJ_参数列表: Vec::new(),
|
||||
布林带_周期: 20,
|
||||
布林带_标准差倍数: 2.0,
|
||||
BOLL_参数列表: Vec::new(),
|
||||
均线_类型列表: Vec::new(),
|
||||
均线_周期列表: Vec::new(),
|
||||
图表展示: true,
|
||||
推送K线: true,
|
||||
推送笔: true,
|
||||
@@ -282,18 +403,64 @@ impl Default for 缠论配置 {
|
||||
}
|
||||
|
||||
impl 缠论配置 {
|
||||
/// 解析MACD参数列表 — 如果列表非空则使用列表,否则返回默认单组
|
||||
pub fn _解析MACD参数列表(&self) -> Vec<(String, i64, i64, i64)> {
|
||||
if !self.MACD_参数列表.is_empty() {
|
||||
return self.MACD_参数列表.clone();
|
||||
}
|
||||
vec![(
|
||||
"macd".into(),
|
||||
self.平滑异同移动平均线_快线周期,
|
||||
self.平滑异同移动平均线_慢线周期,
|
||||
self.平滑异同移动平均线_信号周期,
|
||||
)]
|
||||
}
|
||||
|
||||
/// 解析RSI周期列表 — 如果列表非空则使用列表,否则返回默认单组
|
||||
pub fn _解析RSI周期列表(&self) -> Vec<(String, i64)> {
|
||||
if !self.RSI_周期列表.is_empty() {
|
||||
return self.RSI_周期列表.clone();
|
||||
}
|
||||
vec![("rsi".into(), self.相对强弱指数_周期)]
|
||||
}
|
||||
|
||||
/// 解析KDJ参数列表 — 如果列表非空则使用列表,否则返回默认单组
|
||||
pub fn _解析KDJ参数列表(&self) -> Vec<(String, i64, i64, i64)> {
|
||||
if !self.KDJ_参数列表.is_empty() {
|
||||
return self.KDJ_参数列表.clone();
|
||||
}
|
||||
vec![(
|
||||
"kdj".into(),
|
||||
self.随机指标_RSV周期,
|
||||
self.随机指标_K值平滑周期,
|
||||
self.随机指标_D值平滑周期,
|
||||
)]
|
||||
}
|
||||
|
||||
/// 解析BOLL参数列表 — 如果列表非空则使用列表,否则返回默认单组
|
||||
pub fn _解析BOLL参数列表(&self) -> Vec<(String, i64, f64)> {
|
||||
if !self.BOLL_参数列表.is_empty() {
|
||||
return self.BOLL_参数列表.clone();
|
||||
}
|
||||
vec![("boll".into(), self.布林带_周期, self.布林带_标准差倍数)]
|
||||
}
|
||||
|
||||
/// 序列化为 JSON 字符串
|
||||
pub fn to_json(&self) -> String {
|
||||
serde_json::to_string_pretty(self).unwrap_or_default()
|
||||
}
|
||||
|
||||
/// 从 JSON 字符串反序列化
|
||||
pub fn from_json(json_str: &str) -> Result<Self, serde_json::Error> {
|
||||
serde_json::from_str(json_str)
|
||||
}
|
||||
|
||||
/// 保存配置到 JSON 文件
|
||||
pub fn 保存配置(&self, path: &str) -> std::io::Result<()> {
|
||||
std::fs::write(path, self.to_json())
|
||||
}
|
||||
|
||||
/// 从 JSON 文件加载配置
|
||||
pub fn 加载配置(path: &str) -> Result<Self, Box<dyn std::error::Error>> {
|
||||
let content = std::fs::read_to_string(path)?;
|
||||
let config = Self::from_json(&content)?;
|
||||
@@ -336,11 +503,11 @@ impl 缠论配置 {
|
||||
serde_json::Map<String, serde_json::Value>,
|
||||
> = std::collections::BTreeMap::new();
|
||||
for (key, value) in map {
|
||||
if let Some(pos) = key.find('_') {
|
||||
if let Ok(num) = key[..pos].parse::<i64>() {
|
||||
let field = key[pos + 1..].to_string();
|
||||
groups.entry(num).or_default().insert(field, value.clone());
|
||||
}
|
||||
if let Some(pos) = key.find('_')
|
||||
&& let Ok(num) = key[..pos].parse::<i64>()
|
||||
{
|
||||
let field = key[pos + 1..].to_string();
|
||||
groups.entry(num).or_default().insert(field, value.clone());
|
||||
}
|
||||
}
|
||||
for (num, fields) in groups {
|
||||
@@ -366,10 +533,10 @@ impl 缠论配置 {
|
||||
(&self_json, &other_json)
|
||||
{
|
||||
for (key, self_val) in self_map {
|
||||
if let Some(other_val) = other_map.get(key) {
|
||||
if self_val != other_val {
|
||||
diffs.push(key.clone());
|
||||
}
|
||||
if let Some(other_val) = other_map.get(key)
|
||||
&& self_val != other_val
|
||||
{
|
||||
diffs.push(key.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
/*
|
||||
* 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 crate::kline::bar::K线;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// 布林带(BOLL)— 基于移动平均和标准差的波动率通道
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct 布林带 {
|
||||
/// 数据时间戳
|
||||
pub 时间戳: i64,
|
||||
/// 计算周期
|
||||
pub 周期: usize,
|
||||
/// 标准差倍数(通常为 2.0)
|
||||
pub 标准差倍数: f64,
|
||||
/// 上轨(中轨 + 倍数 × 标准差)
|
||||
pub 上轨: f64,
|
||||
/// 中轨(移动平均线)
|
||||
pub 中轨: f64,
|
||||
/// 下轨(中轨 - 倍数 × 标准差)
|
||||
pub 下轨: f64,
|
||||
/// 内部历史队列(不序列化)
|
||||
#[serde(skip)]
|
||||
_历史队列: Vec<f64>,
|
||||
/// 内部均值缓存(不序列化)
|
||||
#[serde(skip)]
|
||||
_均值: f64,
|
||||
/// 内部方差和缓存(不序列化)
|
||||
#[serde(skip)]
|
||||
_方差和: f64,
|
||||
}
|
||||
|
||||
impl Default for 布林带 {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
时间戳: 0,
|
||||
周期: 20,
|
||||
标准差倍数: 2.0,
|
||||
上轨: 0.0,
|
||||
中轨: 0.0,
|
||||
下轨: 0.0,
|
||||
_历史队列: Vec::new(),
|
||||
_均值: 0.0,
|
||||
_方差和: 0.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl 布林带 {
|
||||
/// 首次计算 BOLL 指标 — 从 K线 取值后计算
|
||||
pub fn 首次计算_K线(
|
||||
k线: &K线, 计算方式: &str, 周期: usize, 标准差倍数: f64
|
||||
) -> Self {
|
||||
let 价格 = crate::indicators::K线取值(k线.开盘价, k线.高, k线.低, k线.收盘价, 计算方式);
|
||||
Self::首次计算(k线.时间戳, 价格, 周期, 标准差倍数)
|
||||
}
|
||||
|
||||
/// 增量计算 BOLL 指标 — 从 K线 取值后递推
|
||||
pub fn 增量计算_K线(prev: &布林带, 当前K线: &K线, 计算方式: &str) -> Self {
|
||||
let 价格 = crate::indicators::K线取值(
|
||||
当前K线.开盘价,
|
||||
当前K线.高,
|
||||
当前K线.低,
|
||||
当前K线.收盘价,
|
||||
计算方式,
|
||||
);
|
||||
Self::增量计算(prev, 当前K线.时间戳, 价格)
|
||||
}
|
||||
|
||||
/// 首次计算 — 初始时上中下轨都等于当前价格
|
||||
pub fn 首次计算(时间戳: i64, 价格: f64, 周期: usize, 标准差倍数: f64) -> Self {
|
||||
Self {
|
||||
时间戳,
|
||||
周期,
|
||||
标准差倍数,
|
||||
上轨: 价格,
|
||||
中轨: 价格,
|
||||
下轨: 价格,
|
||||
_历史队列: vec![价格],
|
||||
_均值: 价格,
|
||||
_方差和: 0.0,
|
||||
}
|
||||
}
|
||||
|
||||
/// 增量计算 — 基于前一个布林带状态递推计算新的布林带
|
||||
pub fn 增量计算(prev: &布林带, 时间戳: i64, 价格: f64) -> Self {
|
||||
let 周期 = prev.周期;
|
||||
let 标准差倍数 = prev.标准差倍数;
|
||||
|
||||
let mut q = prev._历史队列.clone();
|
||||
q.push(价格);
|
||||
if q.len() > 周期 {
|
||||
q.remove(0);
|
||||
}
|
||||
|
||||
let (_均值, _方差和) = if q.len() < 周期 {
|
||||
let mean = q.iter().sum::<f64>() / q.len() as f64;
|
||||
let var_sum = q.iter().map(|v| (v - mean).powi(2)).sum();
|
||||
(mean, var_sum)
|
||||
} else {
|
||||
let n = 周期 as f64;
|
||||
let old_val = if prev._历史队列.len() >= 周期 {
|
||||
prev._历史队列[0]
|
||||
} else {
|
||||
q[0]
|
||||
};
|
||||
let new_mean = prev._均值 + (价格 - old_val) / n;
|
||||
let new_var =
|
||||
prev._方差和 + (价格 - old_val) * (价格 - new_mean + old_val - prev._均值);
|
||||
(new_mean, new_var)
|
||||
};
|
||||
|
||||
let std = (_方差和 / q.len() as f64).sqrt();
|
||||
Self {
|
||||
时间戳,
|
||||
周期,
|
||||
标准差倍数,
|
||||
中轨: _均值,
|
||||
上轨: _均值 + 标准差倍数 * std,
|
||||
下轨: _均值 - 标准差倍数 * std,
|
||||
_历史队列: q,
|
||||
_均值,
|
||||
_方差和,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_boll_first() {
|
||||
let b = 布林带::首次计算(1000, 100.0, 20, 2.0);
|
||||
assert!((b.上轨 - 100.0).abs() < 0.01);
|
||||
assert!((b.中轨 - 100.0).abs() < 0.01);
|
||||
assert!((b.下轨 - 100.0).abs() < 0.01);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_boll_incremental() {
|
||||
let b1 = 布林带::首次计算(1000, 100.0, 5, 2.0);
|
||||
let b2 = 布林带::增量计算(&b1, 1001, 102.0);
|
||||
assert!(b2.上轨 >= b2.中轨);
|
||||
assert!(b2.下轨 <= b2.中轨);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,301 @@
|
||||
/*
|
||||
* 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 super::container::{指标值, 指标容器};
|
||||
use super::{布林带, 平滑异同移动平均线, 相对强弱指数, 随机指标};
|
||||
use crate::config::缠论配置;
|
||||
use crate::kline::bar::K线;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// 指标计算器 — 在缠K合并之前,增量计算所有开启的指标并挂载到K线上
|
||||
pub struct 指标计算器;
|
||||
|
||||
impl 指标计算器 {
|
||||
/// 增量计算所有开启的指标,将结果写入 当前K线.指标
|
||||
///
|
||||
/// `现有序列` 不包含当前K线;prev 取自 现有序列.last()
|
||||
/// 通过 RwLock 内部可变性,以 `&K线` 共享引用写入指标值
|
||||
pub fn 计算并挂载(当前K线: &K线, 现有序列: &[Arc<K线>], 配置: &缠论配置) {
|
||||
let prev_guard = 现有序列.last().map(|k| k.指标.read().unwrap());
|
||||
let prev = prev_guard.as_deref();
|
||||
if 配置.计算指标 {
|
||||
Self::_计算MACD组(当前K线, prev, 配置);
|
||||
Self::_计算RSI组(当前K线, prev, 配置);
|
||||
Self::_计算KDJ组(当前K线, prev, 配置);
|
||||
Self::_计算BOLL组(当前K线, prev, 配置);
|
||||
}
|
||||
Self::_更新均线(当前K线, 现有序列, 配置);
|
||||
}
|
||||
|
||||
fn _计算MACD组(当前K线: &K线, prev: Option<&指标容器>, 配置: &缠论配置) {
|
||||
let 计算方式 = &配置.指标计算方式;
|
||||
for (i, (key, 快, 慢, 信号)) in 配置._解析MACD参数列表().into_iter().enumerate()
|
||||
{
|
||||
let val = if let Some(prev_val) = prev.and_then(|p| p.获取(&key)) {
|
||||
if let 指标值::MACD(prev_macd) = prev_val {
|
||||
指标值::MACD(平滑异同移动平均线::增量计算(
|
||||
prev_macd,
|
||||
super::K线取值(
|
||||
当前K线.开盘价,
|
||||
当前K线.高,
|
||||
当前K线.低,
|
||||
当前K线.收盘价,
|
||||
计算方式,
|
||||
),
|
||||
当前K线.时间戳,
|
||||
))
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
指标值::MACD(平滑异同移动平均线::首次计算(
|
||||
super::K线取值(
|
||||
当前K线.开盘价,
|
||||
当前K线.高,
|
||||
当前K线.低,
|
||||
当前K线.收盘价,
|
||||
计算方式,
|
||||
),
|
||||
当前K线.时间戳,
|
||||
快,
|
||||
慢,
|
||||
信号,
|
||||
))
|
||||
};
|
||||
当前K线.指标.write().unwrap().设置(&key, val.clone());
|
||||
if i == 0 {
|
||||
当前K线.指标.write().unwrap().设置("macd", val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn _计算RSI组(当前K线: &K线, prev: Option<&指标容器>, 配置: &缠论配置) {
|
||||
let 计算方式 = &配置.指标计算方式;
|
||||
for (i, (key, 周期)) in 配置._解析RSI周期列表().into_iter().enumerate() {
|
||||
let val = if let Some(prev_val) = prev.and_then(|p| p.获取(&key)) {
|
||||
if let 指标值::RSI(prev_rsi) = prev_val {
|
||||
指标值::RSI(相对强弱指数::增量计算(
|
||||
prev_rsi,
|
||||
super::K线取值(
|
||||
当前K线.开盘价,
|
||||
当前K线.高,
|
||||
当前K线.低,
|
||||
当前K线.收盘价,
|
||||
计算方式,
|
||||
),
|
||||
当前K线.时间戳,
|
||||
))
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
指标值::RSI(相对强弱指数::首次计算(
|
||||
super::K线取值(
|
||||
当前K线.开盘价,
|
||||
当前K线.高,
|
||||
当前K线.低,
|
||||
当前K线.收盘价,
|
||||
计算方式,
|
||||
),
|
||||
当前K线.时间戳,
|
||||
周期,
|
||||
配置.相对强弱指数_超买阈值,
|
||||
配置.相对强弱指数_超卖阈值,
|
||||
Some(配置.相对强弱指数_移动平均线周期),
|
||||
))
|
||||
};
|
||||
当前K线.指标.write().unwrap().设置(&key, val.clone());
|
||||
if i == 0 {
|
||||
当前K线.指标.write().unwrap().设置("rsi", val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn _计算KDJ组(当前K线: &K线, prev: Option<&指标容器>, 配置: &缠论配置) {
|
||||
for (i, (key, rsv, k平滑, d平滑)) in 配置._解析KDJ参数列表().into_iter().enumerate()
|
||||
{
|
||||
let val = if let Some(prev_val) = prev.and_then(|p| p.获取(&key)) {
|
||||
if let 指标值::KDJ(prev_kdj) = prev_val {
|
||||
指标值::KDJ(随机指标::增量计算(
|
||||
prev_kdj,
|
||||
当前K线.高,
|
||||
当前K线.低,
|
||||
当前K线.收盘价,
|
||||
当前K线.时间戳,
|
||||
))
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
指标值::KDJ(随机指标::首次计算(
|
||||
当前K线.高,
|
||||
当前K线.低,
|
||||
当前K线.收盘价,
|
||||
当前K线.时间戳,
|
||||
rsv,
|
||||
k平滑,
|
||||
d平滑,
|
||||
配置.随机指标_超买阈值,
|
||||
配置.随机指标_超卖阈值,
|
||||
))
|
||||
};
|
||||
当前K线.指标.write().unwrap().设置(&key, val.clone());
|
||||
if i == 0 {
|
||||
当前K线.指标.write().unwrap().设置("kdj", val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn _计算BOLL组(当前K线: &K线, prev: Option<&指标容器>, 配置: &缠论配置) {
|
||||
let 计算方式 = &配置.指标计算方式;
|
||||
for (i, (key, 周期, 标准差倍数)) in 配置._解析BOLL参数列表().into_iter().enumerate()
|
||||
{
|
||||
let val = if let Some(prev_val) = prev.and_then(|p| p.获取(&key)) {
|
||||
if let 指标值::BOLL(prev_boll) = prev_val {
|
||||
指标值::BOLL(布林带::增量计算(
|
||||
prev_boll,
|
||||
当前K线.时间戳,
|
||||
super::K线取值(
|
||||
当前K线.开盘价,
|
||||
当前K线.高,
|
||||
当前K线.低,
|
||||
当前K线.收盘价,
|
||||
计算方式,
|
||||
),
|
||||
))
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
指标值::BOLL(布林带::首次计算(
|
||||
当前K线.时间戳,
|
||||
super::K线取值(
|
||||
当前K线.开盘价,
|
||||
当前K线.高,
|
||||
当前K线.低,
|
||||
当前K线.收盘价,
|
||||
计算方式,
|
||||
),
|
||||
周期 as usize,
|
||||
标准差倍数,
|
||||
))
|
||||
};
|
||||
当前K线.指标.write().unwrap().设置(&key, val.clone());
|
||||
if i == 0 {
|
||||
当前K线.指标.write().unwrap().设置("boll", val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn _更新均线(当前K线: &K线, 现有序列: &[Arc<K线>], 配置: &缠论配置) {
|
||||
if 配置.均线_类型列表.is_empty() || 配置.均线_周期列表.is_empty() {
|
||||
return;
|
||||
}
|
||||
let 计算方式 = &配置.指标计算方式;
|
||||
let 当前价 = super::K线取值(
|
||||
当前K线.开盘价,
|
||||
当前K线.高,
|
||||
当前K线.低,
|
||||
当前K线.收盘价,
|
||||
计算方式,
|
||||
);
|
||||
for ma_type in &配置.均线_类型列表 {
|
||||
for period in &配置.均线_周期列表 {
|
||||
let key = format!("{}_{}", ma_type, period);
|
||||
let 值 = match ma_type.as_str() {
|
||||
"SMA" => Self::_增量SMA(现有序列, 当前价, *period, 计算方式, &key),
|
||||
"EMA" => Self::_增量EMA(现有序列, 当前价, *period, 计算方式, &key),
|
||||
_ => continue,
|
||||
};
|
||||
if let Some(均线_map) = 当前K线.指标.write().unwrap().均线_mut() {
|
||||
均线_map.insert(key, 值);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 增量 SMA: 现有序列 (不含当前K线) + 当前价
|
||||
fn _增量SMA(
|
||||
现有序列: &[Arc<K线>],
|
||||
当前价: f64,
|
||||
period: i64,
|
||||
计算方式: &str,
|
||||
prev_key: &str,
|
||||
) -> f64 {
|
||||
let existing_len = 现有序列.len();
|
||||
let p = period as usize;
|
||||
// 现有序列 + 当前 = total_len
|
||||
let total_len = existing_len + 1;
|
||||
if total_len <= p {
|
||||
let mut sum: f64 = 现有序列[existing_len.saturating_sub(p.saturating_sub(1))..]
|
||||
.iter()
|
||||
.map(|k| super::K线取值(k.开盘价, k.高, k.低, k.收盘价, 计算方式))
|
||||
.sum();
|
||||
sum += 当前价;
|
||||
return sum / (total_len as f64).max(1.0);
|
||||
}
|
||||
// 尝试从前一根K线获取缓存的SMA
|
||||
if let Some(prev) = 现有序列.last().and_then(|k| {
|
||||
let guard = k.指标.read().unwrap();
|
||||
guard.均线().and_then(|m| m.get(prev_key)).copied()
|
||||
}) {
|
||||
let oldest = super::K线取值(
|
||||
现有序列[existing_len - p].开盘价,
|
||||
现有序列[existing_len - p].高,
|
||||
现有序列[existing_len - p].低,
|
||||
现有序列[existing_len - p].收盘价,
|
||||
计算方式,
|
||||
);
|
||||
return prev + (当前价 - oldest) / period as f64;
|
||||
}
|
||||
// 回退:完整计算
|
||||
let mut sum: f64 = 现有序列[existing_len.saturating_sub(p.saturating_sub(1))..]
|
||||
.iter()
|
||||
.map(|k| super::K线取值(k.开盘价, k.高, k.低, k.收盘价, 计算方式))
|
||||
.sum();
|
||||
sum += 当前价;
|
||||
sum / (total_len as f64).min(p as f64)
|
||||
}
|
||||
|
||||
/// 增量 EMA: 现有序列 (不含当前K线) + 当前价
|
||||
fn _增量EMA(
|
||||
现有序列: &[Arc<K线>],
|
||||
当前价: f64,
|
||||
period: i64,
|
||||
_计算方式: &str,
|
||||
prev_key: &str,
|
||||
) -> f64 {
|
||||
let 前值 = 现有序列.last().and_then(|k| {
|
||||
let guard = k.指标.read().unwrap();
|
||||
guard.均线().and_then(|m| m.get(prev_key)).copied()
|
||||
});
|
||||
match 前值 {
|
||||
None => 当前价,
|
||||
Some(prev) => {
|
||||
let k = 2.0 / (period as f64 + 1.0);
|
||||
当前价 * k + prev * (1.0 - k)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
/*
|
||||
* 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 super::{布林带, 平滑异同移动平均线, 相对强弱指数, 随机指标};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// 统一指标值 — 支持所有指标类型的动态注册
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum 指标值 {
|
||||
/// MACD 指标
|
||||
MACD(平滑异同移动平均线),
|
||||
/// RSI 指标
|
||||
RSI(相对强弱指数),
|
||||
/// KDJ 指标
|
||||
KDJ(随机指标),
|
||||
/// 布林带指标
|
||||
BOLL(布林带),
|
||||
/// 均线组 (key → 值)
|
||||
均线(HashMap<String, f64>),
|
||||
/// 单值指标组 (key → 值)
|
||||
单值(HashMap<String, f64>),
|
||||
}
|
||||
|
||||
/// 指标容器 — 挂载在每根 K线上,基于注册表模式持有该时刻所有指标快照
|
||||
///
|
||||
/// 与 Python `指标容器` 保持一致:
|
||||
/// - 复杂指标:MACD/RSI/KDJ/BOLL,通过默认 key("macd"/"rsi"/"kdj"/"boll")访问
|
||||
/// - 多参数变体:key 格式 "MACD_{快}_{慢}_{信号}" / "RSI_{周期}" 等
|
||||
/// - 均线组:通过 `均线` 子映射访问,key 格式 "{类型}_{周期}"
|
||||
/// - 单值指标:通过 `单值` 子映射访问,key 格式 "{名称}_{周期}"
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||
pub struct 指标容器 {
|
||||
pub _数据: HashMap<String, Option<指标值>>,
|
||||
}
|
||||
|
||||
impl 指标容器 {
|
||||
/// 创建指标容器,预注册 macd/rsi/kdj/boll/均线/单值 默认槽位
|
||||
pub fn new() -> Self {
|
||||
let mut _数据 = HashMap::new();
|
||||
_数据.insert("macd".into(), None);
|
||||
_数据.insert("rsi".into(), None);
|
||||
_数据.insert("kdj".into(), None);
|
||||
_数据.insert("boll".into(), None);
|
||||
_数据.insert("均线".into(), Some(指标值::均线(HashMap::new())));
|
||||
_数据.insert("单值".into(), Some(指标值::单值(HashMap::new())));
|
||||
Self { _数据 }
|
||||
}
|
||||
|
||||
/// 预注册指标(不覆盖已有值)
|
||||
pub fn 注册(&mut self, 名称: &str, 默认值: Option<指标值>) {
|
||||
if !self._数据.contains_key(名称) {
|
||||
self._数据.insert(名称.to_string(), 默认值);
|
||||
}
|
||||
}
|
||||
|
||||
/// 按名称获取指标值
|
||||
pub fn 获取(&self, 名称: &str) -> Option<&指标值> {
|
||||
self._数据.get(名称).and_then(|v| v.as_ref())
|
||||
}
|
||||
|
||||
/// 按名称设置指标值
|
||||
pub fn 设置(&mut self, 名称: &str, 值: 指标值) {
|
||||
self._数据.insert(名称.to_string(), Some(值));
|
||||
}
|
||||
|
||||
/// 检查是否包含指定名称的指标
|
||||
pub fn 包含(&self, 名称: &str) -> bool {
|
||||
self._数据.contains_key(名称)
|
||||
}
|
||||
|
||||
// ---- 默认指标便捷访问 ----
|
||||
|
||||
/// 获取默认 MACD 指标
|
||||
pub fn macd(&self) -> Option<&平滑异同移动平均线> {
|
||||
match self._数据.get("macd")?.as_ref()? {
|
||||
指标值::MACD(m) => Some(m),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// 克隆获取默认 MACD 指标
|
||||
pub fn macd_cloned(&self) -> Option<平滑异同移动平均线> {
|
||||
self.macd().cloned()
|
||||
}
|
||||
|
||||
/// 设置默认 MACD 指标
|
||||
pub fn set_macd(&mut self, m: 平滑异同移动平均线) {
|
||||
self._数据.insert("macd".into(), Some(指标值::MACD(m)));
|
||||
}
|
||||
|
||||
/// 获取默认 RSI 指标
|
||||
pub fn rsi(&self) -> Option<&相对强弱指数> {
|
||||
match self._数据.get("rsi")?.as_ref()? {
|
||||
指标值::RSI(r) => Some(r),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// 克隆获取默认 RSI 指标
|
||||
pub fn rsi_cloned(&self) -> Option<相对强弱指数> {
|
||||
self.rsi().cloned()
|
||||
}
|
||||
|
||||
/// 设置默认 RSI 指标
|
||||
pub fn set_rsi(&mut self, r: 相对强弱指数) {
|
||||
self._数据.insert("rsi".into(), Some(指标值::RSI(r)));
|
||||
}
|
||||
|
||||
/// 获取默认 KDJ 指标
|
||||
pub fn kdj(&self) -> Option<&随机指标> {
|
||||
match self._数据.get("kdj")?.as_ref()? {
|
||||
指标值::KDJ(k) => Some(k),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// 克隆获取默认 KDJ 指标
|
||||
pub fn kdj_cloned(&self) -> Option<随机指标> {
|
||||
self.kdj().cloned()
|
||||
}
|
||||
|
||||
/// 设置默认 KDJ 指标
|
||||
pub fn set_kdj(&mut self, k: 随机指标) {
|
||||
self._数据.insert("kdj".into(), Some(指标值::KDJ(k)));
|
||||
}
|
||||
|
||||
/// 获取默认布林带指标
|
||||
pub fn boll(&self) -> Option<&布林带> {
|
||||
match self._数据.get("boll")?.as_ref()? {
|
||||
指标值::BOLL(b) => Some(b),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// 克隆获取默认布林带指标
|
||||
pub fn boll_cloned(&self) -> Option<布林带> {
|
||||
self.boll().cloned()
|
||||
}
|
||||
|
||||
/// 设置默认布林带指标
|
||||
pub fn set_boll(&mut self, b: 布林带) {
|
||||
self._数据.insert("boll".into(), Some(指标值::BOLL(b)));
|
||||
}
|
||||
|
||||
/// 获取均线组
|
||||
pub fn 均线(&self) -> Option<&HashMap<String, f64>> {
|
||||
match self._数据.get("均线")?.as_ref()? {
|
||||
指标值::均线(m) => Some(m),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// 获取均线组可变引用
|
||||
pub fn 均线_mut(&mut self) -> Option<&mut HashMap<String, f64>> {
|
||||
match self._数据.get_mut("均线")?.as_mut()? {
|
||||
指标值::均线(m) => Some(m),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// 获取单值指标组
|
||||
pub fn 单值(&self) -> Option<&HashMap<String, f64>> {
|
||||
match self._数据.get("单值")?.as_ref()? {
|
||||
指标值::单值(s) => Some(s),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for 指标容器 {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let keys: Vec<&str> = self
|
||||
._数据
|
||||
.iter()
|
||||
.filter(|(_, v)| v.is_some())
|
||||
.map(|(k, _)| k.as_str())
|
||||
.collect();
|
||||
write!(f, "指标容器({})", keys.join(", "))
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,7 @@
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
use crate::kline::bar::K线;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// 随机指标 (KDJ)
|
||||
@@ -30,23 +31,41 @@ use serde::{Deserialize, Serialize};
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct 随机指标 {
|
||||
/// 数据时间戳
|
||||
pub 时间戳: i64,
|
||||
/// 最高价
|
||||
pub 最高价: f64,
|
||||
/// 最低价
|
||||
pub 最低价: f64,
|
||||
/// 收盘价
|
||||
pub 收盘价: f64,
|
||||
/// RSV 周期
|
||||
pub N: i64,
|
||||
/// K 值平滑周期
|
||||
pub M1: i64,
|
||||
/// D 值平滑周期
|
||||
pub M2: i64,
|
||||
/// 超买阈值
|
||||
pub 超买阈值: f64,
|
||||
/// 超卖阈值
|
||||
pub 超卖阈值: f64,
|
||||
/// RSV 值(未成熟随机值)
|
||||
pub RSV: Option<f64>,
|
||||
/// K 值
|
||||
pub K: Option<f64>,
|
||||
/// D 值
|
||||
pub D: Option<f64>,
|
||||
/// J 值 (3K - 2D)
|
||||
pub J: Option<f64>,
|
||||
/// 历史最高价队列(滑动窗口)
|
||||
pub 历史最高价队列: Vec<f64>,
|
||||
/// 历史最低价队列(滑动窗口)
|
||||
pub 历史最低价队列: Vec<f64>,
|
||||
/// 前一个 RSV(用于平滑递推)
|
||||
pub 前一个RSV: Option<f64>,
|
||||
/// 前一个 K(用于平滑递推)
|
||||
pub 前一个K: Option<f64>,
|
||||
/// 前一个 D(用于平滑递推)
|
||||
pub 前一个D: Option<f64>,
|
||||
}
|
||||
|
||||
@@ -77,6 +96,9 @@ impl Default for 随机指标 {
|
||||
|
||||
impl 随机指标 {
|
||||
/// 首次计算 KDJ(无历史数据时)
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
/// 首次计算 KDJ — 无历史数据时的初始计算
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn 首次计算(
|
||||
初始最高价: f64,
|
||||
初始最低价: f64,
|
||||
@@ -110,6 +132,39 @@ impl 随机指标 {
|
||||
}
|
||||
}
|
||||
|
||||
/// 首次计算 KDJ 指标 — 从 K线 取值后计算(KDJ 始终使用 高/低/收)
|
||||
pub fn 首次计算_K线(
|
||||
k线: &K线,
|
||||
RSV周期: i64,
|
||||
K值平滑周期: i64,
|
||||
D值平滑周期: i64,
|
||||
超买阈值: f64,
|
||||
超卖阈值: f64,
|
||||
) -> Self {
|
||||
Self::首次计算(
|
||||
k线.高,
|
||||
k线.低,
|
||||
k线.收盘价,
|
||||
k线.时间戳,
|
||||
RSV周期,
|
||||
K值平滑周期,
|
||||
D值平滑周期,
|
||||
超买阈值,
|
||||
超卖阈值,
|
||||
)
|
||||
}
|
||||
|
||||
/// 增量计算 KDJ 指标 — 从 K线 取值后递推
|
||||
pub fn 增量计算_K线(前一个KDJ: &Self, 当前K线: &K线) -> Self {
|
||||
Self::增量计算(
|
||||
前一个KDJ,
|
||||
当前K线.高,
|
||||
当前K线.低,
|
||||
当前K线.收盘价,
|
||||
当前K线.时间戳,
|
||||
)
|
||||
}
|
||||
|
||||
/// 基于前一个 KDJ 增量计算当前 KDJ
|
||||
pub fn 增量计算(
|
||||
前一个KDJ: &Self,
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
use crate::kline::bar::K线;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// 平滑异同移动平均线 (MACD)
|
||||
@@ -30,18 +31,29 @@ use serde::{Deserialize, Serialize};
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct 平滑异同移动平均线 {
|
||||
/// 数据时间戳
|
||||
pub 时间戳: i64,
|
||||
/// 收盘价
|
||||
pub 收盘价: f64,
|
||||
/// 快线 EMA 周期
|
||||
pub 快线周期: i64,
|
||||
/// 慢线 EMA 周期
|
||||
pub 慢线周期: i64,
|
||||
/// 信号线周期
|
||||
pub 信号周期: i64,
|
||||
/// DIF 值(快线 - 慢线)
|
||||
pub DIF: Option<f64>,
|
||||
/// DEA 值(DIF 的信号线)
|
||||
pub DEA: Option<f64>,
|
||||
/// MACD 柱(2 * (DIF - DEA))
|
||||
#[serde(rename = "MACD柱")]
|
||||
#[serde(default)]
|
||||
pub MACD柱: f64,
|
||||
/// 快线 EMA 值
|
||||
pub 快线EMA: Option<f64>,
|
||||
/// 慢线 EMA 值
|
||||
pub 慢线EMA: Option<f64>,
|
||||
/// DEA EMA 值
|
||||
pub DEA_EMA: Option<f64>,
|
||||
}
|
||||
|
||||
@@ -97,6 +109,30 @@ impl 平滑异同移动平均线 {
|
||||
}
|
||||
}
|
||||
|
||||
/// 首次计算 MACD 指标 — 从 K线 取值后计算
|
||||
pub fn 首次计算_K线(
|
||||
k线: &K线,
|
||||
计算方式: &str,
|
||||
快线周期: i64,
|
||||
慢线周期: i64,
|
||||
信号周期: i64,
|
||||
) -> Self {
|
||||
let 价格 = super::K线取值(k线.开盘价, k线.高, k线.低, k线.收盘价, 计算方式);
|
||||
Self::首次计算(价格, k线.时间戳, 快线周期, 慢线周期, 信号周期)
|
||||
}
|
||||
|
||||
/// 增量计算 MACD 指标 — 从 K线 取值后递推
|
||||
pub fn 增量计算_K线(前一个MACD: &Self, 当前K线: &K线, 计算方式: &str) -> Self {
|
||||
let 价格 = super::K线取值(
|
||||
当前K线.开盘价,
|
||||
当前K线.高,
|
||||
当前K线.低,
|
||||
当前K线.收盘价,
|
||||
计算方式,
|
||||
);
|
||||
Self::增量计算(前一个MACD, 价格, 当前K线.时间戳)
|
||||
}
|
||||
|
||||
/// 基于前一个 MACD 指标增量计算当前 MACD
|
||||
pub fn 增量计算(前一个MACD: &Self, 当前收盘价: f64, 当前时间: i64) -> Self {
|
||||
// 快线 EMA
|
||||
|
||||
@@ -22,10 +22,16 @@
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
pub mod boll;
|
||||
pub mod calculator;
|
||||
pub mod container;
|
||||
pub mod kdj;
|
||||
pub mod macd;
|
||||
pub mod rsi;
|
||||
|
||||
pub use boll::布林带;
|
||||
pub use calculator::指标计算器;
|
||||
pub use container::{指标值, 指标容器};
|
||||
pub use kdj::随机指标;
|
||||
pub use macd::平滑异同移动平均线;
|
||||
pub use rsi::相对强弱指数;
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
use crate::kline::bar::K线;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// 相对强弱指数 (RSI)
|
||||
@@ -30,19 +31,33 @@ use serde::{Deserialize, Serialize};
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct 相对强弱指数 {
|
||||
/// 数据时间戳
|
||||
pub 时间戳: i64,
|
||||
/// 收盘价
|
||||
pub 收盘价: f64,
|
||||
/// RSI 计算周期
|
||||
pub 周期: i64,
|
||||
/// 超买阈值
|
||||
pub 超买阈值: f64,
|
||||
/// 超卖阈值
|
||||
pub 超卖阈值: f64,
|
||||
/// RSI SMA 平滑周期
|
||||
pub RSI_SMA周期: Option<i64>,
|
||||
/// RSI 值
|
||||
pub RSI: Option<f64>,
|
||||
/// 平均上涨幅度
|
||||
pub 平均上涨: Option<f64>,
|
||||
/// 平均下跌幅度
|
||||
pub 平均下跌: Option<f64>,
|
||||
/// 当前上涨幅度
|
||||
pub 上涨幅度: f64,
|
||||
/// 当前下跌幅度
|
||||
pub 下跌幅度: f64,
|
||||
/// 平滑系数 (1/周期)
|
||||
pub 平滑系数: f64,
|
||||
/// RSI SMA 值
|
||||
pub RSI_SMA: Option<f64>,
|
||||
/// RSI 历史队列(用于滚动计算)
|
||||
pub RSI历史队列: Vec<f64>,
|
||||
}
|
||||
|
||||
@@ -95,6 +110,31 @@ impl 相对强弱指数 {
|
||||
}
|
||||
}
|
||||
|
||||
/// 首次计算 RSI 指标 — 从 K线 取值后计算
|
||||
pub fn 首次计算_K线(
|
||||
k线: &K线,
|
||||
计算方式: &str,
|
||||
周期: i64,
|
||||
超买阈值: f64,
|
||||
超卖阈值: f64,
|
||||
RSI_SMA周期: Option<i64>,
|
||||
) -> Self {
|
||||
let 价格 = super::K线取值(k线.开盘价, k线.高, k线.低, k线.收盘价, 计算方式);
|
||||
Self::首次计算(价格, k线.时间戳, 周期, 超买阈值, 超卖阈值, RSI_SMA周期)
|
||||
}
|
||||
|
||||
/// 增量计算 RSI 指标 — 从 K线 取值后递推
|
||||
pub fn 增量计算_K线(前一个RSI: &Self, 当前K线: &K线, 计算方式: &str) -> Self {
|
||||
let 价格 = super::K线取值(
|
||||
当前K线.开盘价,
|
||||
当前K线.高,
|
||||
当前K线.低,
|
||||
当前K线.收盘价,
|
||||
计算方式,
|
||||
);
|
||||
Self::增量计算(前一个RSI, 价格, 当前K线.时间戳)
|
||||
}
|
||||
|
||||
/// 基于前一个 RSI 增量计算当前 RSI
|
||||
pub fn 增量计算(前一个RSI: &Self, 当前收盘价: f64, 当前时间: i64) -> Self {
|
||||
let 周期 = 前一个RSI.周期;
|
||||
@@ -120,11 +160,7 @@ impl 相对强弱指数 {
|
||||
|
||||
// RSI
|
||||
let RSI = if 平均下跌 == 0.0 {
|
||||
if 平均上涨 > 0.0 {
|
||||
100.0
|
||||
} else {
|
||||
50.0
|
||||
}
|
||||
if 平均上涨 > 0.0 { 100.0 } else { 50.0 }
|
||||
} else {
|
||||
let RS = 平均上涨 / 平均下跌;
|
||||
100.0 - (100.0 / (1.0 + RS))
|
||||
|
||||
+82
-14
@@ -22,30 +22,68 @@
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
use crate::indicators::{平滑异同移动平均线, 相对强弱指数, 随机指标};
|
||||
use crate::indicators::指标容器;
|
||||
use crate::types::相对方向;
|
||||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::io::Write;
|
||||
use std::sync::Arc;
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
/// 原始K线 (OHLCV + 指标)
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
mod rwlock_container_serde {
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
use std::sync::RwLock;
|
||||
|
||||
/// Serde 序列化辅助(RwLock<指标容器> → 序列化器)
|
||||
pub fn serialize<S>(
|
||||
val: &RwLock<crate::indicators::指标容器>,
|
||||
ser: S,
|
||||
) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
val.read().unwrap().serialize(ser)
|
||||
}
|
||||
|
||||
/// Serde 反序列化辅助(反序列化器 → RwLock<指标容器>)
|
||||
pub fn deserialize<'de, D>(de: D) -> Result<RwLock<crate::indicators::指标容器>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
Ok(RwLock::new(crate::indicators::指标容器::deserialize(
|
||||
de,
|
||||
)?))
|
||||
}
|
||||
}
|
||||
|
||||
/// 原始K线 (OHLCV + 指标容器)
|
||||
///
|
||||
/// 所有指标统一通过 `指标容器` 访问。指标容器使用 RwLock 实现内部可变性,
|
||||
/// 使 `计算并挂载` 能以 `&K线` 共享引用写入指标值。
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct K线 {
|
||||
/// 品种标识(如 "btcusd")
|
||||
pub 标识: String,
|
||||
/// K线序号(在序列中的位置)
|
||||
pub 序号: i64,
|
||||
/// 周期(秒),如 300=5分钟, 86400=日线
|
||||
pub 周期: i64,
|
||||
/// Unix 时间戳(秒)
|
||||
pub 时间戳: i64,
|
||||
/// 最高价
|
||||
pub 高: f64,
|
||||
/// 最低价
|
||||
pub 低: f64,
|
||||
/// 开盘价
|
||||
pub 开盘价: f64,
|
||||
/// 收盘价
|
||||
pub 收盘价: f64,
|
||||
/// 成交量
|
||||
pub 成交量: f64,
|
||||
pub macd: Option<平滑异同移动平均线>,
|
||||
pub rsi: Option<相对强弱指数>,
|
||||
pub kdj: Option<随机指标>,
|
||||
/// 指标容器(MACD/RSI/KDJ/BOLL/均线等)
|
||||
#[serde(with = "rwlock_container_serde")]
|
||||
pub 指标: RwLock<指标容器>,
|
||||
}
|
||||
|
||||
impl Default for K线 {
|
||||
@@ -60,9 +98,24 @@ impl Default for K线 {
|
||||
开盘价: 0.0,
|
||||
收盘价: 0.0,
|
||||
成交量: 0.0,
|
||||
macd: None,
|
||||
rsi: None,
|
||||
kdj: None,
|
||||
指标: RwLock::new(指标容器::new()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for K线 {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
标识: self.标识.clone(),
|
||||
序号: self.序号,
|
||||
周期: self.周期,
|
||||
时间戳: self.时间戳,
|
||||
高: self.高,
|
||||
低: self.低,
|
||||
开盘价: self.开盘价,
|
||||
收盘价: self.收盘价,
|
||||
成交量: self.成交量,
|
||||
指标: RwLock::new(self.指标.read().unwrap().clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -79,6 +132,7 @@ impl K线 {
|
||||
|
||||
/// 序列化为大端字节序 48 字节
|
||||
/// 格式: >6d (时间戳, 开盘价, 高, 低, 收盘价, 成交量)
|
||||
/// TODO: 对齐 Python round(x, 8) 再序列化
|
||||
pub fn to_bytes(&self) -> [u8; 48] {
|
||||
let mut buf = [0u8; 48];
|
||||
{
|
||||
@@ -125,7 +179,23 @@ 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线
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn 创建普K(
|
||||
标识: &str,
|
||||
时间戳: i64,
|
||||
@@ -147,9 +217,7 @@ impl K线 {
|
||||
开盘价,
|
||||
收盘价,
|
||||
成交量,
|
||||
macd: None,
|
||||
rsi: None,
|
||||
kdj: None,
|
||||
指标: RwLock::new(指标容器::new()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,7 +245,7 @@ impl K线 {
|
||||
let mut 阳 = 0.0f64;
|
||||
let mut 阴 = 0.0f64;
|
||||
for k in 基序 {
|
||||
if let Some(ref macd) = k.macd {
|
||||
if let Some(macd) = k.指标.read().unwrap().macd() {
|
||||
let hist = macd.MACD柱;
|
||||
if hist >= 0.0 {
|
||||
阳 += hist;
|
||||
|
||||
+55
-155
@@ -23,36 +23,48 @@
|
||||
*/
|
||||
|
||||
use crate::config::缠论配置;
|
||||
use crate::indicators::{
|
||||
平滑异同移动平均线, 相对强弱指数, 随机指标, K线取值
|
||||
};
|
||||
use crate::indicators::指标计算器;
|
||||
use crate::kline::bar::K线;
|
||||
use crate::structure::fractal_obj::分型;
|
||||
use crate::types::SyncF64;
|
||||
use crate::types::分型结构;
|
||||
use crate::types::相对方向;
|
||||
use crate::types::SyncF64;
|
||||
use std::collections::HashSet;
|
||||
use std::sync::atomic::{AtomicI64, Ordering};
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
/// 缠论K线 — 经包含处理过后的K线
|
||||
///
|
||||
/// 部分字段使用 Cell/RefCell 实现内部可变性,确保包含处理原地修改时
|
||||
/// Rc 指针不变,所有持有该 Rc 的引用(如分型.右)能看到最新数据。
|
||||
/// 部分字段使用 AtomicI64 / SyncF64 / RwLock 实现内部可变性,确保包含处理
|
||||
/// 原地修改时 Rc 指针不变,所有持有该 Rc 的引用(如分型.右)能看到最新数据。
|
||||
#[derive(Debug)]
|
||||
pub struct 缠论K线 {
|
||||
/// 缠K序号(在缠论K线序列中的位置)
|
||||
pub 序号: AtomicI64,
|
||||
/// Unix 时间戳(秒)
|
||||
pub 时间戳: AtomicI64,
|
||||
/// 缠K最高价(经包含处理后可能高于原始K线)
|
||||
pub 高: SyncF64,
|
||||
/// 缠K最低价(经包含处理后可能低于原始K线)
|
||||
pub 低: SyncF64,
|
||||
/// 缠K方向(向上/向下)
|
||||
pub 方向: RwLock<相对方向>,
|
||||
/// 分型结构(顶/底/上/下/散)
|
||||
pub 分型: RwLock<Option<分型结构>>,
|
||||
/// 周期(秒)
|
||||
pub 周期: i64,
|
||||
/// 品种标识
|
||||
pub 标识: String,
|
||||
/// 分型特征值(历史高低点极值,用于背驰判断)
|
||||
pub 分型特征值: SyncF64,
|
||||
/// 原始K线起始序号(包含处理前)
|
||||
pub 原始起始序号: i64,
|
||||
/// 原始K线结束序号(包含处理后更新)
|
||||
pub 原始结束序号: AtomicI64,
|
||||
/// 标的原始K线(该缠K对应的普K)
|
||||
pub 标的K线: RwLock<Arc<K线>>,
|
||||
pub 买卖点信息: RwLock<Vec<String>>,
|
||||
/// 买卖点信息集合
|
||||
pub 买卖点信息: RwLock<HashSet<String>>,
|
||||
}
|
||||
|
||||
impl Clone for 缠论K线 {
|
||||
@@ -66,7 +78,7 @@ impl Clone for 缠论K线 {
|
||||
分型: RwLock::new(*self.分型.read().unwrap()),
|
||||
周期: self.周期,
|
||||
标识: self.标识.clone(),
|
||||
分型特征值: SyncF64::new(self.分型特征值.get()),
|
||||
分型特征值: SyncF64::new(self.高.get()),
|
||||
原始起始序号: self.原始起始序号,
|
||||
原始结束序号: AtomicI64::new(self.原始结束序号.load(Ordering::Relaxed)),
|
||||
标的K线: RwLock::new(Arc::clone(&self.标的K线.read().unwrap())),
|
||||
@@ -108,7 +120,7 @@ impl 缠论K线 {
|
||||
分型: RwLock::new(*self.分型.read().unwrap()),
|
||||
周期: self.周期,
|
||||
标识: self.标识.clone(),
|
||||
分型特征值: SyncF64::new(self.分型特征值.get()),
|
||||
分型特征值: SyncF64::new(self.高.get()),
|
||||
原始起始序号: self.原始起始序号,
|
||||
原始结束序号: AtomicI64::new(self.原始结束序号.load(Ordering::Relaxed)),
|
||||
标的K线: RwLock::new(Arc::clone(&self.标的K线.read().unwrap())),
|
||||
@@ -118,16 +130,18 @@ impl 缠论K线 {
|
||||
|
||||
/// 与MACD柱子匹配 — 底分型时MACD柱应<0, 顶分型时>0
|
||||
pub fn 与MACD柱子匹配(&self) -> bool {
|
||||
let 标 = self.标的K线.read().unwrap();
|
||||
let 容器 = 标.指标.read().unwrap();
|
||||
match *self.分型.read().unwrap() {
|
||||
Some(分型结构::底) | Some(分型结构::下) => {
|
||||
if let Some(ref macd) = self.标的K线.read().unwrap().macd {
|
||||
if let Some(macd) = 容器.macd() {
|
||||
macd.MACD柱 < 0.0
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
Some(分型结构::顶) | Some(分型结构::上) => {
|
||||
if let Some(ref macd) = self.标的K线.read().unwrap().macd {
|
||||
if let Some(macd) = 容器.macd() {
|
||||
macd.MACD柱 > 0.0
|
||||
} else {
|
||||
false
|
||||
@@ -139,9 +153,11 @@ impl 缠论K线 {
|
||||
|
||||
/// 与RSI匹配 — 底分型时RSI应低于SMA, 顶分型时高于SMA
|
||||
pub fn 与RSI匹配(&self) -> bool {
|
||||
let 标 = self.标的K线.read().unwrap();
|
||||
let 容器 = 标.指标.read().unwrap();
|
||||
match *self.分型.read().unwrap() {
|
||||
Some(分型结构::底) | Some(分型结构::下) => {
|
||||
if let Some(ref rsi) = self.标的K线.read().unwrap().rsi {
|
||||
if let Some(rsi) = 容器.rsi() {
|
||||
match (rsi.RSI, rsi.RSI_SMA) {
|
||||
(Some(r), Some(sma)) => r < sma,
|
||||
_ => false,
|
||||
@@ -151,7 +167,7 @@ impl 缠论K线 {
|
||||
}
|
||||
}
|
||||
Some(分型结构::顶) | Some(分型结构::上) => {
|
||||
if let Some(ref rsi) = self.标的K线.read().unwrap().rsi {
|
||||
if let Some(rsi) = 容器.rsi() {
|
||||
match (rsi.RSI, rsi.RSI_SMA) {
|
||||
(Some(r), Some(sma)) => r > sma,
|
||||
_ => false,
|
||||
@@ -166,9 +182,11 @@ impl 缠论K线 {
|
||||
|
||||
/// 与KDJ匹配 — 底分型时K应低于D(死叉后), 顶分型时K应高于D(金叉后)
|
||||
pub fn 与KDJ匹配(&self) -> bool {
|
||||
let 标 = self.标的K线.read().unwrap();
|
||||
let 容器 = 标.指标.read().unwrap();
|
||||
match *self.分型.read().unwrap() {
|
||||
Some(分型结构::底) | Some(分型结构::下) => {
|
||||
if let Some(ref kdj) = self.标的K线.read().unwrap().kdj {
|
||||
if let Some(kdj) = 容器.kdj() {
|
||||
match (kdj.K, kdj.D) {
|
||||
(Some(k), Some(d)) => k < d,
|
||||
_ => false,
|
||||
@@ -178,7 +196,7 @@ impl 缠论K线 {
|
||||
}
|
||||
}
|
||||
Some(分型结构::顶) | Some(分型结构::上) => {
|
||||
if let Some(ref kdj) = self.标的K线.read().unwrap().kdj {
|
||||
if let Some(kdj) = 容器.kdj() {
|
||||
match (kdj.K, kdj.D) {
|
||||
(Some(k), Some(d)) => k > d,
|
||||
_ => false,
|
||||
@@ -199,19 +217,16 @@ impl 缠论K线 {
|
||||
if k线.时间戳.load(Ordering::Relaxed) <= k.时间戳.load(Ordering::Relaxed)
|
||||
&& k.时间戳.load(Ordering::Relaxed)
|
||||
<= k线.时间戳.load(Ordering::Relaxed) + k线.周期
|
||||
&& (k线.分型特征值.get() - k.分型特征值.get()).abs() < f64::EPSILON
|
||||
{
|
||||
if (k线.分型特征值.get() - k.分型特征值.get()).abs() < f64::EPSILON
|
||||
{
|
||||
return k.时间戳.load(Ordering::Relaxed);
|
||||
}
|
||||
return k.时间戳.load(Ordering::Relaxed);
|
||||
}
|
||||
} else if k.时间戳.load(Ordering::Relaxed) <= k线.时间戳.load(Ordering::Relaxed)
|
||||
&& k线.时间戳.load(Ordering::Relaxed)
|
||||
<= k.时间戳.load(Ordering::Relaxed) + k.周期
|
||||
&& (k线.分型特征值.get() - k.分型特征值.get()).abs() < f64::EPSILON
|
||||
{
|
||||
if (k线.分型特征值.get() - k.分型特征值.get()).abs() < f64::EPSILON {
|
||||
return k.时间戳.load(Ordering::Relaxed);
|
||||
}
|
||||
return k.时间戳.load(Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -219,6 +234,7 @@ impl 缠论K线 {
|
||||
}
|
||||
|
||||
/// 创建缠K
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn 创建缠K(
|
||||
时间戳: i64,
|
||||
高: f64,
|
||||
@@ -250,7 +266,7 @@ impl 缠论K线 {
|
||||
原始起始序号: 原始序号,
|
||||
原始结束序号: AtomicI64::new(原始序号),
|
||||
标的K线: RwLock::new(普k),
|
||||
买卖点信息: RwLock::new(Vec::new()),
|
||||
买卖点信息: RwLock::new(HashSet::new()),
|
||||
};
|
||||
|
||||
if let Some(之前) = 之前 {
|
||||
@@ -272,7 +288,7 @@ impl 缠论K线 {
|
||||
/// 兼并(合并)处理 — 缠论包含处理的核心算法
|
||||
///
|
||||
/// 返回 (新缠K, 模式) — 模式: "添加"/"替换"/None
|
||||
pub fn 兼并(
|
||||
pub fn _兼并(
|
||||
之前缠K: Option<&缠论K线>,
|
||||
当前缠K: &缠论K线,
|
||||
当前普K: &Arc<K线>,
|
||||
@@ -305,7 +321,7 @@ impl 缠论K线 {
|
||||
|
||||
// 重复提交检测 — 当序号相同时认为是重复提交K线
|
||||
if 当前普K.序号 == 当前缠K.原始结束序号.load(Ordering::Relaxed) {
|
||||
return (None, None);
|
||||
// no-op: 对齐 Python ... (Ellipsis)
|
||||
}
|
||||
|
||||
// 序号连续性检查
|
||||
@@ -372,93 +388,14 @@ impl 缠论K线 {
|
||||
当前K线.标识 = 配置.标识.clone();
|
||||
|
||||
// ---- 阶段1: 普K序列管理 + 指标增量计算 ----
|
||||
// 对齐 Python: 先推入序列,再计算指标
|
||||
if 普K序列.is_empty() {
|
||||
if 配置.计算指标 {
|
||||
当前K线.macd = Some(平滑异同移动平均线::首次计算(
|
||||
K线取值(
|
||||
当前K线.开盘价,
|
||||
当前K线.高,
|
||||
当前K线.低,
|
||||
当前K线.收盘价,
|
||||
&配置.指标计算方式,
|
||||
),
|
||||
当前K线.时间戳,
|
||||
配置.平滑异同移动平均线_快线周期,
|
||||
配置.平滑异同移动平均线_慢线周期,
|
||||
配置.平滑异同移动平均线_信号周期,
|
||||
));
|
||||
当前K线.rsi = Some(相对强弱指数::首次计算(
|
||||
K线取值(
|
||||
当前K线.开盘价,
|
||||
当前K线.高,
|
||||
当前K线.低,
|
||||
当前K线.收盘价,
|
||||
&配置.指标计算方式,
|
||||
),
|
||||
当前K线.时间戳,
|
||||
配置.相对强弱指数_周期,
|
||||
配置.相对强弱指数_超买阈值,
|
||||
配置.相对强弱指数_超卖阈值,
|
||||
Some(配置.相对强弱指数_移动平均线周期),
|
||||
));
|
||||
当前K线.kdj = Some(随机指标::首次计算(
|
||||
当前K线.高,
|
||||
当前K线.低,
|
||||
当前K线.收盘价,
|
||||
当前K线.时间戳,
|
||||
配置.随机指标_RSV周期,
|
||||
配置.随机指标_K值平滑周期,
|
||||
配置.随机指标_D值平滑周期,
|
||||
配置.随机指标_超买阈值,
|
||||
配置.随机指标_超卖阈值,
|
||||
));
|
||||
}
|
||||
let 当前K线_rc = Arc::new(当前K线);
|
||||
普K序列.push(当前K线_rc);
|
||||
普K序列.push(Arc::new(当前K线));
|
||||
} else {
|
||||
let 之前普K = 普K序列.last().unwrap();
|
||||
if 之前普K.时间戳 == 当前K线.时间戳 {
|
||||
// 同时间戳更新
|
||||
// 同时间戳更新 — 替换 [-1]
|
||||
当前K线.序号 = 之前普K.序号;
|
||||
if 配置.计算指标 {
|
||||
if 普K序列.len() >= 2 {
|
||||
if let Some(ref prev_macd) = 普K序列[普K序列.len() - 2].macd {
|
||||
当前K线.macd = Some(平滑异同移动平均线::增量计算(
|
||||
prev_macd,
|
||||
K线取值(
|
||||
当前K线.开盘价,
|
||||
当前K线.高,
|
||||
当前K线.低,
|
||||
当前K线.收盘价,
|
||||
&配置.指标计算方式,
|
||||
),
|
||||
当前K线.时间戳,
|
||||
));
|
||||
}
|
||||
if let Some(ref prev_rsi) = 普K序列[普K序列.len() - 2].rsi {
|
||||
当前K线.rsi = Some(相对强弱指数::增量计算(
|
||||
prev_rsi,
|
||||
K线取值(
|
||||
当前K线.开盘价,
|
||||
当前K线.高,
|
||||
当前K线.低,
|
||||
当前K线.收盘价,
|
||||
&配置.指标计算方式,
|
||||
),
|
||||
当前K线.时间戳,
|
||||
));
|
||||
}
|
||||
if let Some(ref prev_kdj) = 普K序列[普K序列.len() - 2].kdj {
|
||||
当前K线.kdj = Some(随机指标::增量计算(
|
||||
prev_kdj,
|
||||
当前K线.高,
|
||||
当前K线.低,
|
||||
当前K线.收盘价,
|
||||
当前K线.时间戳,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
普K序列.pop();
|
||||
普K序列.push(Arc::new(当前K线));
|
||||
} else {
|
||||
@@ -466,46 +403,14 @@ impl 缠论K线 {
|
||||
panic!("时序错误: 之前={}, 当前={}", 之前普K.时间戳, 当前K线.时间戳);
|
||||
}
|
||||
当前K线.序号 = 之前普K.序号 + 1;
|
||||
if 配置.计算指标 {
|
||||
if let Some(ref prev_macd) = 之前普K.macd {
|
||||
当前K线.macd = Some(平滑异同移动平均线::增量计算(
|
||||
prev_macd,
|
||||
K线取值(
|
||||
当前K线.开盘价,
|
||||
当前K线.高,
|
||||
当前K线.低,
|
||||
当前K线.收盘价,
|
||||
&配置.指标计算方式,
|
||||
),
|
||||
当前K线.时间戳,
|
||||
));
|
||||
}
|
||||
if let Some(ref prev_rsi) = 之前普K.rsi {
|
||||
当前K线.rsi = Some(相对强弱指数::增量计算(
|
||||
prev_rsi,
|
||||
K线取值(
|
||||
当前K线.开盘价,
|
||||
当前K线.高,
|
||||
当前K线.低,
|
||||
当前K线.收盘价,
|
||||
&配置.指标计算方式,
|
||||
),
|
||||
当前K线.时间戳,
|
||||
));
|
||||
}
|
||||
if let Some(ref prev_kdj) = 之前普K.kdj {
|
||||
当前K线.kdj = Some(随机指标::增量计算(
|
||||
prev_kdj,
|
||||
当前K线.高,
|
||||
当前K线.低,
|
||||
当前K线.收盘价,
|
||||
当前K线.时间戳,
|
||||
));
|
||||
}
|
||||
}
|
||||
普K序列.push(Arc::new(当前K线));
|
||||
}
|
||||
}
|
||||
// 计算指标: 对齐 Python,仅当 计算指标 开启时执行
|
||||
if 配置.计算指标 {
|
||||
let n = 普K序列.len();
|
||||
指标计算器::计算并挂载(&普K序列[n - 1], &普K序列[..n - 1], 配置);
|
||||
}
|
||||
|
||||
// ---- 阶段2: 缠K合并 ----
|
||||
let 状态: String;
|
||||
@@ -514,9 +419,9 @@ impl 缠论K线 {
|
||||
if !缠K序列.is_empty() {
|
||||
let len = 缠K序列.len();
|
||||
let (左边, 右边) = 缠K序列.split_at_mut(len - 1);
|
||||
let 之前缠K: Option<&缠论K线> = 左边.last().map(|rc| Arc::as_ref(rc));
|
||||
let 之前缠K: Option<&缠论K线> = 左边.last().map(Arc::as_ref);
|
||||
let 最后一个缠K = &*右边[0];
|
||||
let (新缠K, 模式) = Self::兼并(之前缠K, 最后一个缠K, 当前K线_ref, 配置);
|
||||
let (新缠K, 模式) = Self::_兼并(之前缠K, 最后一个缠K, 当前K线_ref, 配置);
|
||||
|
||||
if let Some(k) = 新缠K {
|
||||
match 模式.as_deref() {
|
||||
@@ -525,8 +430,7 @@ impl 缠论K线 {
|
||||
状态 = "创建".into();
|
||||
}
|
||||
Some("替换") => {
|
||||
// Cell::set 已原地更新数据,无需 pop+push 打破 Rc 身份
|
||||
状态 = "兼并".into();
|
||||
状态 = "替换".into();
|
||||
}
|
||||
_ => {
|
||||
状态 = "兼并".into();
|
||||
@@ -614,7 +518,7 @@ impl 缠论K线 {
|
||||
Arc::clone(&缠K序列[idx - 2]),
|
||||
Some(Arc::clone(&缠K序列[idx - 1])),
|
||||
));
|
||||
return (状态, Some(形态));
|
||||
(状态, Some(形态))
|
||||
}
|
||||
|
||||
/// 截取缠K序列从始到终
|
||||
@@ -623,12 +527,8 @@ impl 缠论K线 {
|
||||
始: &缠论K线,
|
||||
终: &缠论K线,
|
||||
) -> Option<Vec<Arc<缠论K线>>> {
|
||||
let 始_idx = 序列
|
||||
.iter()
|
||||
.position(|k| Arc::as_ptr(k) == (始 as *const _))?;
|
||||
let 终_idx = 序列
|
||||
.iter()
|
||||
.position(|k| Arc::as_ptr(k) == (终 as *const _))?;
|
||||
let 始_idx = 序列.iter().position(|k| std::ptr::eq(Arc::as_ptr(k), 始))?;
|
||||
let 终_idx = 序列.iter().position(|k| std::ptr::eq(Arc::as_ptr(k), 终))?;
|
||||
Some(序列[始_idx..=终_idx].to_vec())
|
||||
}
|
||||
}
|
||||
|
||||
+30
-32
@@ -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);
|
||||
}
|
||||
|
||||
/// 测试_周期合成 — 多周期合成分析
|
||||
@@ -176,5 +174,5 @@ fn 测试_周期合成(文件路径: &str) {
|
||||
}
|
||||
|
||||
println!("\n===== 保存分析数据 =====\n");
|
||||
多级别分析.测试_保存数据();
|
||||
多级别分析.测试_保存数据(None);
|
||||
}
|
||||
|
||||
+343
-147
@@ -29,43 +29,97 @@ use crate::kline::chan_kline::缠论K线;
|
||||
use crate::structure::fractal_obj::分型;
|
||||
use crate::structure::segment_feat::线段特征;
|
||||
use crate::types::{分型结构, 相对方向, 缺口};
|
||||
use cached::stores::LruCache;
|
||||
use std::sync::atomic::{AtomicBool, AtomicI64, Ordering};
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::sync::{Arc, LazyLock, Mutex, RwLock};
|
||||
use tracing::warn;
|
||||
|
||||
/// 虚线 — 笔和线段的通用数据结构
|
||||
/// 虚线 — 笔和线段的通用数据结构。
|
||||
///
|
||||
/// 笔和线段共享此 struct,通过 `标识` 字段区分 ("笔"/"线段"/"扩展线段"等)
|
||||
/// 可变字段使用 Cell/RefCell 实现内部可变性,确保 Rc 指针身份一致
|
||||
/// 笔和线段共享此 struct,通过 `标识` 字段区分("笔"/"线段"/"扩展线段"等)。
|
||||
/// 文=起点分型,武=终点分型,方向由文→武的结构关系判定(顶→底=向下,底→顶=向上)。
|
||||
///
|
||||
/// 字段:
|
||||
/// 标识: "笔" / "线段" / "扩展线段" / "线段<线段>" 等
|
||||
/// 序号: 在同级序列中的编号
|
||||
/// 级别: 层级深度(笔=1, 线段=2, 线段<线段>=3 ...)
|
||||
/// 文: 起点分型(不可变,确保指针身份一致)
|
||||
/// 武: 终点分型(可变,用于动态更新终点)
|
||||
/// 有效性: 该虚线是否有效
|
||||
/// 基础序列: 构成该虚线的子级虚线序列(笔→空;线段→笔序列;线段<线段>→线段序列 ...)
|
||||
/// 特征序列: 线段特征序列(笔为空;线段为[None, None, None]初始化)
|
||||
/// 实_中枢序列: 实中枢列表(根据配置识别的中枢)
|
||||
/// 虚_中枢序列: 虚中枢列表(忽略配置约束的中枢)
|
||||
/// 合_中枢序列: 合并中枢列表(实+虚+扩展)
|
||||
/// 确认K线: 线段被破坏时的确认K线
|
||||
/// 模式: 买卖点匹配模式("文武"/"全量"/"任意"/"配置"/"相对")
|
||||
/// _特征序列_显示: 是否显示特征序列(图表渲染用)
|
||||
/// 前一缺口: 特征序列第一二元素间的缺口
|
||||
/// 前一结束位置: 前一虚线段结束时所在的位置
|
||||
/// 短路修正: 是否启用短路修正(线段短路过时的紧急处理标记)
|
||||
#[derive(Debug)]
|
||||
pub struct 虚线 {
|
||||
/// 标识字符串: "笔" / "线段" / "扩展线段" / "线段<线段>" 等
|
||||
pub 标识: RwLock<String>,
|
||||
/// 同级序列中的编号
|
||||
pub 序号: AtomicI64,
|
||||
/// 层级深度(笔=1, 线段=2)
|
||||
pub 级别: AtomicI64,
|
||||
/// 起点分型(不可变)
|
||||
pub 文: Arc<分型>,
|
||||
/// 终点分型(可变)
|
||||
pub 武: RwLock<Arc<分型>>,
|
||||
/// 是否有效
|
||||
pub 有效性: AtomicBool,
|
||||
/// 构成该虚线的子级虚线序列
|
||||
pub 基础序列: RwLock<Vec<Arc<虚线>>>,
|
||||
/// 线段特征序列
|
||||
pub 特征序列: RwLock<Vec<Option<Arc<线段特征>>>>,
|
||||
/// 实中枢列表
|
||||
pub 实_中枢序列: RwLock<Vec<Arc<中枢>>>,
|
||||
/// 虚中枢列表
|
||||
pub 虚_中枢序列: RwLock<Vec<Arc<中枢>>>,
|
||||
/// 合并中枢列表(实+虚+扩展)
|
||||
pub 合_中枢序列: RwLock<Vec<Arc<中枢>>>,
|
||||
/// 线段被破坏时的确认K线
|
||||
pub 确认K线: RwLock<Option<Arc<缠论K线>>>,
|
||||
/// 买卖点匹配模式
|
||||
pub 模式: RwLock<String>,
|
||||
/// 是否显示特征序列(图表渲染)
|
||||
pub _特征序列_显示: AtomicBool,
|
||||
/// 特征序列第一二元素间的缺口
|
||||
pub 前一缺口: RwLock<Option<缺口>>,
|
||||
/// 前一虚线段结束位置
|
||||
pub 前一结束位置: RwLock<Option<Arc<虚线>>>,
|
||||
/// 短路紧急修正标记
|
||||
pub 短路修正: AtomicBool,
|
||||
}
|
||||
|
||||
/// MACD行为统计 — 统计MACD行为 方法的返回类型
|
||||
/// MACD行为统计 — `统计MACD行为` 方法的返回类型,汇总MACD穿轴和交叉信息。
|
||||
///
|
||||
/// 字段:
|
||||
/// DIF上穿0: DIF从负穿正的次数
|
||||
/// DIF下穿0: DIF从正穿负的次数
|
||||
/// DEA上穿0: DEA从负穿正的次数
|
||||
/// DEA下穿0: DEA从正穿负的次数
|
||||
/// 金叉次数: DIF上穿DEA的次数
|
||||
/// 死叉次数: DIF下穿DEA的次数
|
||||
/// 密集交叉区域: (起始索引, 结束索引, 交叉次数) 列表
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MACD行为统计 {
|
||||
/// DIF从负穿正的次数
|
||||
pub DIF上穿0: i64,
|
||||
/// DIF从正穿负的次数
|
||||
pub DIF下穿0: i64,
|
||||
/// DEA从负穿正的次数
|
||||
pub DEA上穿0: i64,
|
||||
/// DEA从正穿负的次数
|
||||
pub DEA下穿0: i64,
|
||||
/// DIF上穿DEA的次数
|
||||
pub 金叉次数: i64,
|
||||
/// DIF下穿DEA的次数
|
||||
pub 死叉次数: i64,
|
||||
/// 密集交叉区域 (起始索引, 结束索引, 交叉次数)
|
||||
pub 密集交叉区域: Vec<(usize, usize, usize)>,
|
||||
}
|
||||
|
||||
@@ -93,6 +147,12 @@ impl Clone for 虚线 {
|
||||
}
|
||||
}
|
||||
|
||||
type 买卖意义缓存类型 = LruCache<(usize, usize), (bool, String)>;
|
||||
|
||||
/// 买卖意义 LRU 缓存(max 128 条目,对齐 Python @lru_cache(maxsize=128))
|
||||
static 买卖意义缓存: LazyLock<Mutex<买卖意义缓存类型>> =
|
||||
LazyLock::new(|| Mutex::new(LruCache::with_size(128)));
|
||||
|
||||
impl 虚线 {
|
||||
pub fn new(
|
||||
序号: i64,
|
||||
@@ -110,7 +170,7 @@ impl 虚线 {
|
||||
武: RwLock::new(武),
|
||||
有效性: AtomicBool::new(有效性),
|
||||
基础序列: RwLock::new(Vec::new()),
|
||||
特征序列: RwLock::new(Vec::new()),
|
||||
特征序列: RwLock::new(vec![None, None, None]),
|
||||
实_中枢序列: RwLock::new(Vec::new()),
|
||||
虚_中枢序列: RwLock::new(Vec::new()),
|
||||
合_中枢序列: RwLock::new(Vec::new()),
|
||||
@@ -123,6 +183,7 @@ impl 虚线 {
|
||||
}
|
||||
}
|
||||
|
||||
/// 图表标题 — 格式为 "符号:周期:标识:序号"
|
||||
pub fn 图表标题(&self) -> String {
|
||||
format!(
|
||||
"{}:{}:{}:{}",
|
||||
@@ -133,14 +194,18 @@ impl 虚线 {
|
||||
)
|
||||
}
|
||||
|
||||
/// 方向 — 文到武的方向
|
||||
/// 方向 — 文到武的方向(对齐 Python:无法识别时 panic)
|
||||
pub fn 方向(&self) -> 相对方向 {
|
||||
match (self.文.结构, self.武.read().unwrap().结构) {
|
||||
(分型结构::顶, 分型结构::底) => 相对方向::向下,
|
||||
(分型结构::顶, 分型结构::下) => 相对方向::向下,
|
||||
(分型结构::上, 分型结构::底) => 相对方向::向下,
|
||||
(分型结构::上, 分型结构::下) => 相对方向::向下,
|
||||
_ => 相对方向::向上,
|
||||
(分型结构::底, 分型结构::顶) => 相对方向::向上,
|
||||
(分型结构::底, 分型结构::上) => 相对方向::向上,
|
||||
_ => panic!(
|
||||
"虚线 方向 无法识别: 文.结构={:?}, 武.结构={:?}",
|
||||
self.文.结构,
|
||||
self.武.read().unwrap().结构
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,6 +244,7 @@ impl 虚线 {
|
||||
}
|
||||
|
||||
/// 获取该虚线范围内的普K序列
|
||||
/// 对齐说明:Python 接收 观察者 对象,Rust 直接接收切片引用,行为等价
|
||||
pub fn 获取普K序列(&self, 普K序列: &[Arc<K线>]) -> Vec<Arc<K线>> {
|
||||
// 使用指针查找(与 Python list.index 身份匹配行为一致),
|
||||
// 而非序号切片——因为序号可能与实际位置不一致。
|
||||
@@ -192,7 +258,7 @@ impl 虚线 {
|
||||
(Some(s), Some(e)) if s <= e => 普K序列[s..=e].to_vec(),
|
||||
_ => {
|
||||
// 指针查找失败时回退到序号方式
|
||||
println!("[警告]虚线.获取普K序列 <指针查找失败时回退到序号方式>");
|
||||
warn!("[警告]虚线.获取普K序列 <指针查找失败时回退到序号方式>");
|
||||
let 始 = self.文.中.原始起始序号 as usize;
|
||||
let 终 = self
|
||||
.武
|
||||
@@ -211,11 +277,13 @@ impl 虚线 {
|
||||
}
|
||||
|
||||
/// 获取该虚线范围内的缠K序列
|
||||
/// 对齐说明:Python 接收 观察者 对象,Rust 直接接收切片引用,行为等价
|
||||
pub fn 获取缠K序列(&self, 缠K序列: &[Arc<缠论K线>]) -> Vec<Arc<缠论K线>> {
|
||||
缠论K线::截取(缠K序列, &self.文.中, &self.武.read().unwrap().中).unwrap_or_default()
|
||||
}
|
||||
|
||||
/// 获取_武 — 递归获取虚线的终点分型(笔直接返回武,线段递归到底层笔的武)
|
||||
/// 对齐说明:Python 是 @classmethod(cls, 实线),Rust 是实例方法(&self),逻辑等价
|
||||
pub fn 获取_武(&self) -> Arc<分型> {
|
||||
if *self.标识.read().unwrap() == "笔" {
|
||||
return self.武.read().unwrap().clone();
|
||||
@@ -239,9 +307,9 @@ impl 虚线 {
|
||||
self.标识.read().unwrap(),
|
||||
self.序号.load(Ordering::Relaxed),
|
||||
self.级别.load(Ordering::Relaxed),
|
||||
self.文.时间戳,
|
||||
self.文.时间戳(),
|
||||
format_f64_g(self.文.分型特征值),
|
||||
self.武.read().unwrap().时间戳,
|
||||
self.武.read().unwrap().时间戳(),
|
||||
format_f64_g(self.武.read().unwrap().分型特征值),
|
||||
if self.有效性.load(Ordering::Relaxed) {
|
||||
"True"
|
||||
@@ -254,13 +322,7 @@ impl 虚线 {
|
||||
// 非笔:线段/扩展线段等,完整输出
|
||||
let (前, 后, 三, 贯穿伤) = crate::algorithm::segment::线段::分割序列(self, None);
|
||||
let (特征_a, 特征_b, 特征_c) = crate::algorithm::segment::线段::特征序列状态(self);
|
||||
let 特征_bool = |b: bool| -> &str {
|
||||
if b {
|
||||
"True"
|
||||
} else {
|
||||
"False"
|
||||
}
|
||||
};
|
||||
let 特征_bool = |b: bool| -> &str { if b { "True" } else { "False" } };
|
||||
|
||||
let 前一缺口_str = match &*self.前一缺口.read().unwrap() {
|
||||
Some(g) => format!("{}", g),
|
||||
@@ -330,11 +392,15 @@ impl 虚线 {
|
||||
self.标识.read().unwrap(),
|
||||
self.序号.load(Ordering::Relaxed),
|
||||
self.级别.load(Ordering::Relaxed),
|
||||
self.文.时间戳,
|
||||
self.文.时间戳(),
|
||||
format_f64_g(self.文.分型特征值),
|
||||
self.武.read().unwrap().时间戳,
|
||||
self.武.read().unwrap().时间戳(),
|
||||
format_f64_g(self.武.read().unwrap().分型特征值),
|
||||
if self.有效性.load(Ordering::Relaxed) { "True" } else { "False" },
|
||||
if self.有效性.load(Ordering::Relaxed) {
|
||||
"True"
|
||||
} else {
|
||||
"False"
|
||||
},
|
||||
self.基础序列.read().unwrap().len(),
|
||||
特征_bool(特征_a),
|
||||
特征_bool(特征_b),
|
||||
@@ -342,14 +408,21 @@ impl 虚线 {
|
||||
前_str,
|
||||
后_str,
|
||||
三_str,
|
||||
match &贯穿伤 { Some(d) => format!("{}", d), None => "None".to_string() },
|
||||
match &贯穿伤 {
|
||||
Some(d) => format!("{}", d),
|
||||
None => "None".to_string(),
|
||||
},
|
||||
实_str,
|
||||
虚_str,
|
||||
合_str,
|
||||
self.模式.read().unwrap(),
|
||||
前一缺口_str,
|
||||
前一结束位置_str,
|
||||
if self.短路修正.load(Ordering::Relaxed) { "True" } else { "False" },
|
||||
if self.短路修正.load(Ordering::Relaxed) {
|
||||
"True"
|
||||
} else {
|
||||
"False"
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -362,6 +435,13 @@ impl 虚线 {
|
||||
|
||||
/// 创建线段
|
||||
pub fn 创建线段(虚线序列: &[Arc<虚线>]) -> Self {
|
||||
let 序列数量 = 虚线序列.len();
|
||||
if 序列数量 < 3 {
|
||||
panic!("创建线段 虚线序列 数量 {} < 3", 序列数量);
|
||||
}
|
||||
if 序列数量.is_multiple_of(2) {
|
||||
panic!("创建线段 虚线序列 数量 {} 不是单数!", 序列数量);
|
||||
}
|
||||
let 文 = Arc::clone(&虚线序列[0].文);
|
||||
let 武 = Arc::clone(&*虚线序列[虚线序列.len() - 1].武.read().unwrap());
|
||||
assert!(
|
||||
@@ -377,6 +457,10 @@ impl 虚线 {
|
||||
};
|
||||
let 级别 = 虚线序列[0].级别.load(Ordering::Relaxed) + 1;
|
||||
let 段 = Self::new(0, 标识, 文, 武, 级别, true);
|
||||
*段.特征序列.write().unwrap() = vec![None, None, None];
|
||||
*段.实_中枢序列.write().unwrap() = Vec::new();
|
||||
*段.虚_中枢序列.write().unwrap() = Vec::new();
|
||||
*段.合_中枢序列.write().unwrap() = Vec::new();
|
||||
*段.基础序列.write().unwrap() = 虚线序列.to_vec();
|
||||
*段.模式.write().unwrap() = "文武".into();
|
||||
段
|
||||
@@ -435,30 +519,31 @@ impl 虚线 {
|
||||
pub fn 计算MACD柱子均值(普K序列: &[Arc<K线>], 实线: &虚线) -> f64 {
|
||||
let K线序列 = K线::截取rc(
|
||||
普K序列,
|
||||
&*实线.文.中.标的K线.read().unwrap(),
|
||||
&*实线.武.read().unwrap().中.标的K线.read().unwrap(),
|
||||
&实线.文.中.标的K线.read().unwrap(),
|
||||
&实线.武.read().unwrap().中.标的K线.read().unwrap(),
|
||||
);
|
||||
if K线序列.is_empty() {
|
||||
return 0.0;
|
||||
}
|
||||
let 总: f64 = K线序列
|
||||
.iter()
|
||||
.filter_map(|k| k.macd.as_ref())
|
||||
.filter_map(|k| k.指标.read().unwrap().macd_cloned())
|
||||
.map(|m| m.MACD柱.abs())
|
||||
.sum();
|
||||
总 / K线序列.len() as f64
|
||||
}
|
||||
|
||||
/// 计算MACD柱子均值_阴 — 负柱的绝对值均值
|
||||
/// 对齐说明:Python 无数据时返回 False(bool),Rust 返回 None,调用方处理等价
|
||||
pub fn 计算MACD柱子均值_阴(普K序列: &[Arc<K线>], 实线: &虚线) -> Option<f64> {
|
||||
let K线序列 = K线::截取rc(
|
||||
普K序列,
|
||||
&*实线.文.中.标的K线.read().unwrap(),
|
||||
&*实线.武.read().unwrap().中.标的K线.read().unwrap(),
|
||||
&实线.文.中.标的K线.read().unwrap(),
|
||||
&实线.武.read().unwrap().中.标的K线.read().unwrap(),
|
||||
);
|
||||
let 总: Vec<f64> = K线序列
|
||||
.iter()
|
||||
.filter_map(|k| k.macd.as_ref())
|
||||
.filter_map(|k| k.指标.read().unwrap().macd_cloned())
|
||||
.filter(|m| m.MACD柱 < 0.0)
|
||||
.map(|m| m.MACD柱.abs())
|
||||
.collect();
|
||||
@@ -470,15 +555,16 @@ impl 虚线 {
|
||||
}
|
||||
|
||||
/// 计算MACD柱子均值_阳 — 正柱的绝对值均值
|
||||
/// 对齐说明:Python 无数据时返回 False(bool),Rust 返回 None,调用方处理等价
|
||||
pub fn 计算MACD柱子均值_阳(普K序列: &[Arc<K线>], 实线: &虚线) -> Option<f64> {
|
||||
let K线序列 = K线::截取rc(
|
||||
普K序列,
|
||||
&*实线.文.中.标的K线.read().unwrap(),
|
||||
&*实线.武.read().unwrap().中.标的K线.read().unwrap(),
|
||||
&实线.文.中.标的K线.read().unwrap(),
|
||||
&实线.武.read().unwrap().中.标的K线.read().unwrap(),
|
||||
);
|
||||
let 总: Vec<f64> = K线序列
|
||||
.iter()
|
||||
.filter_map(|k| k.macd.as_ref())
|
||||
.filter_map(|k| k.指标.read().unwrap().macd_cloned())
|
||||
.filter(|m| m.MACD柱 > 0.0)
|
||||
.map(|m| m.MACD柱.abs())
|
||||
.collect();
|
||||
@@ -495,7 +581,7 @@ impl 虚线 {
|
||||
pub fn 武之全量MACD均值(普K序列: &[Arc<K线>], 实线: &虚线) -> bool {
|
||||
let 武_ref = 实线.武.read().unwrap();
|
||||
let 标 = 武_ref.中.标的K线.read().unwrap();
|
||||
let 武_MACD = match 标.macd.as_ref() {
|
||||
let 武_MACD = match 标.指标.read().unwrap().macd() {
|
||||
Some(m) => m.MACD柱.abs(),
|
||||
None => return false,
|
||||
};
|
||||
@@ -515,7 +601,7 @@ impl 虚线 {
|
||||
pub fn 武之MACD均值_阴(普K序列: &[Arc<K线>], 实线: &虚线) -> bool {
|
||||
let 武_ref = 实线.武.read().unwrap();
|
||||
let 标 = 武_ref.中.标的K线.read().unwrap();
|
||||
let 武_MACD = match 标.macd.as_ref() {
|
||||
let 武_MACD = match 标.指标.read().unwrap().macd() {
|
||||
Some(m) => m.MACD柱.abs(),
|
||||
None => return false,
|
||||
};
|
||||
@@ -529,7 +615,7 @@ impl 虚线 {
|
||||
pub fn 武之MACD均值_阳(普K序列: &[Arc<K线>], 实线: &虚线) -> bool {
|
||||
let 武_ref = 实线.武.read().unwrap();
|
||||
let 标 = 武_ref.中.标的K线.read().unwrap();
|
||||
let 武_MACD = match 标.macd.as_ref() {
|
||||
let 武_MACD = match 标.指标.read().unwrap().macd() {
|
||||
Some(m) => m.MACD柱.abs(),
|
||||
None => return false,
|
||||
};
|
||||
@@ -543,18 +629,18 @@ impl 虚线 {
|
||||
pub fn 武之MACD极值(普K序列: &[Arc<K线>], 实线: &虚线) -> bool {
|
||||
let 武_ref = 实线.武.read().unwrap();
|
||||
let 标 = 武_ref.中.标的K线.read().unwrap();
|
||||
let 武_MACD = match 标.macd.as_ref() {
|
||||
let 武_MACD = match 标.指标.read().unwrap().macd() {
|
||||
Some(m) => m.MACD柱,
|
||||
None => return false,
|
||||
};
|
||||
let K线序列 = K线::截取rc(
|
||||
普K序列,
|
||||
&*实线.文.中.标的K线.read().unwrap(),
|
||||
&*实线.武.read().unwrap().中.标的K线.read().unwrap(),
|
||||
&实线.文.中.标的K线.read().unwrap(),
|
||||
&实线.武.read().unwrap().中.标的K线.read().unwrap(),
|
||||
);
|
||||
let 所有柱子: Vec<f64> = K线序列
|
||||
.iter()
|
||||
.filter_map(|k| k.macd.as_ref())
|
||||
.filter_map(|k| k.指标.read().unwrap().macd_cloned())
|
||||
.map(|m| m.MACD柱)
|
||||
.collect();
|
||||
if 所有柱子.is_empty() {
|
||||
@@ -583,7 +669,13 @@ impl 虚线 {
|
||||
if 方向 == 相对方向::向上 {
|
||||
let 柱子序列: Vec<&Arc<K线>> = 普K序列
|
||||
.iter()
|
||||
.filter(|k| k.macd.as_ref().map_or(false, |m| m.MACD柱 > 0.0))
|
||||
.filter(|k| {
|
||||
k.指标
|
||||
.read()
|
||||
.unwrap()
|
||||
.macd()
|
||||
.is_some_and(|m| m.MACD柱 > 0.0)
|
||||
})
|
||||
.collect();
|
||||
if 柱子序列.is_empty() {
|
||||
return [false, false, false];
|
||||
@@ -595,32 +687,51 @@ impl 虚线 {
|
||||
let 最高柱子 = 柱子序列
|
||||
.iter()
|
||||
.max_by(|a, b| {
|
||||
a.macd
|
||||
.as_ref()
|
||||
a.指标
|
||||
.read()
|
||||
.unwrap()
|
||||
.macd()
|
||||
.unwrap()
|
||||
.MACD柱
|
||||
.partial_cmp(&b.macd.as_ref().unwrap().MACD柱)
|
||||
.partial_cmp(&b.指标.read().unwrap().macd().unwrap().MACD柱)
|
||||
.unwrap_or(std::cmp::Ordering::Equal)
|
||||
})
|
||||
.unwrap();
|
||||
let mut 柱对 = vec![Arc::clone(*最高柱子), Arc::clone(最后)];
|
||||
let mut 柱对 = [Arc::clone(*最高柱子), Arc::clone(最后)];
|
||||
柱对.sort_by_key(|k| k.时间戳);
|
||||
if let (Some(m0), Some(m1)) = (柱对[0].macd.as_ref(), 柱对[1].macd.as_ref()) {
|
||||
if m0.MACD柱 > m1.MACD柱 && 柱对[0].高 < 柱对[1].高 {
|
||||
结果[0] = true;
|
||||
}
|
||||
let m0_g = 柱对[0].指标.read().unwrap();
|
||||
let m1_g = 柱对[1].指标.read().unwrap();
|
||||
if let (Some(m0), Some(m1)) = (m0_g.macd(), m1_g.macd())
|
||||
&& m0.MACD柱 > m1.MACD柱
|
||||
&& 柱对[0].高 < 柱对[1].高
|
||||
{
|
||||
结果[0] = true;
|
||||
}
|
||||
|
||||
// DIF背驰 (no sort — Python compares peak vs last directly)
|
||||
let 最高离差值 = 柱子序列
|
||||
.iter()
|
||||
.max_by(|a, b| {
|
||||
let da = a.macd.as_ref().and_then(|m| m.DIF).unwrap_or(0.0);
|
||||
let db = b.macd.as_ref().and_then(|m| m.DIF).unwrap_or(0.0);
|
||||
let da = a
|
||||
.指标
|
||||
.read()
|
||||
.unwrap()
|
||||
.macd()
|
||||
.and_then(|m| m.DIF)
|
||||
.unwrap_or(0.0);
|
||||
let db = b
|
||||
.指标
|
||||
.read()
|
||||
.unwrap()
|
||||
.macd()
|
||||
.and_then(|m| m.DIF)
|
||||
.unwrap_or(0.0);
|
||||
da.partial_cmp(&db).unwrap_or(std::cmp::Ordering::Equal)
|
||||
})
|
||||
.unwrap();
|
||||
if let (Some(m0), Some(m1)) = (最高离差值.macd.as_ref(), 最后.macd.as_ref()) {
|
||||
let m0_g = 最高离差值.指标.read().unwrap();
|
||||
let m1_g = 最后.指标.read().unwrap();
|
||||
if let (Some(m0), Some(m1)) = (m0_g.macd(), m1_g.macd()) {
|
||||
let dif0 = m0.DIF.unwrap_or(0.0);
|
||||
let dif1 = m1.DIF.unwrap_or(0.0);
|
||||
if dif0 > dif1 && 最高离差值.高 < 最后.高 {
|
||||
@@ -632,12 +743,26 @@ impl 虚线 {
|
||||
let 最高信号线 = 柱子序列
|
||||
.iter()
|
||||
.max_by(|a, b| {
|
||||
let da = a.macd.as_ref().and_then(|m| m.DEA).unwrap_or(0.0);
|
||||
let db = b.macd.as_ref().and_then(|m| m.DEA).unwrap_or(0.0);
|
||||
let da = a
|
||||
.指标
|
||||
.read()
|
||||
.unwrap()
|
||||
.macd()
|
||||
.and_then(|m| m.DEA)
|
||||
.unwrap_or(0.0);
|
||||
let db = b
|
||||
.指标
|
||||
.read()
|
||||
.unwrap()
|
||||
.macd()
|
||||
.and_then(|m| m.DEA)
|
||||
.unwrap_or(0.0);
|
||||
da.partial_cmp(&db).unwrap_or(std::cmp::Ordering::Equal)
|
||||
})
|
||||
.unwrap();
|
||||
if let (Some(m0), Some(m1)) = (最高信号线.macd.as_ref(), 最后.macd.as_ref()) {
|
||||
let m0_g = 最高信号线.指标.read().unwrap();
|
||||
let m1_g = 最后.指标.read().unwrap();
|
||||
if let (Some(m0), Some(m1)) = (m0_g.macd(), m1_g.macd()) {
|
||||
let dea0 = m0.DEA.unwrap_or(0.0);
|
||||
let dea1 = m1.DEA.unwrap_or(0.0);
|
||||
if dea0 > dea1 && 最高信号线.高 < 最后.高 {
|
||||
@@ -649,7 +774,13 @@ impl 虚线 {
|
||||
} else {
|
||||
let 柱子序列: Vec<&Arc<K线>> = 普K序列
|
||||
.iter()
|
||||
.filter(|k| k.macd.as_ref().map_or(false, |m| m.MACD柱 < 0.0))
|
||||
.filter(|k| {
|
||||
k.指标
|
||||
.read()
|
||||
.unwrap()
|
||||
.macd()
|
||||
.is_some_and(|m| m.MACD柱 < 0.0)
|
||||
})
|
||||
.collect();
|
||||
if 柱子序列.is_empty() {
|
||||
return [false, false, false];
|
||||
@@ -661,33 +792,54 @@ impl 虚线 {
|
||||
let 最高柱子 = 柱子序列
|
||||
.iter()
|
||||
.max_by(|a, b| {
|
||||
a.macd
|
||||
.as_ref()
|
||||
a.指标
|
||||
.read()
|
||||
.unwrap()
|
||||
.macd()
|
||||
.unwrap()
|
||||
.MACD柱
|
||||
.abs()
|
||||
.partial_cmp(&b.macd.as_ref().unwrap().MACD柱.abs())
|
||||
.partial_cmp(&b.指标.read().unwrap().macd().unwrap().MACD柱.abs())
|
||||
.unwrap_or(std::cmp::Ordering::Equal)
|
||||
})
|
||||
.unwrap();
|
||||
let mut 柱对 = vec![Arc::clone(*最高柱子), Arc::clone(最后)];
|
||||
let mut 柱对 = [Arc::clone(*最高柱子), Arc::clone(最后)];
|
||||
柱对.sort_by_key(|k| k.时间戳);
|
||||
if let (Some(m0), Some(m1)) = (柱对[0].macd.as_ref(), 柱对[1].macd.as_ref()) {
|
||||
if m0.MACD柱 < m1.MACD柱 && 柱对[0].低 > 柱对[1].低 {
|
||||
结果[0] = true;
|
||||
}
|
||||
let m0_g = 柱对[0].指标.read().unwrap();
|
||||
let m1_g = 柱对[1].指标.read().unwrap();
|
||||
if let (Some(m0), Some(m1)) = (m0_g.macd(), m1_g.macd())
|
||||
&& m0.MACD柱 < m1.MACD柱
|
||||
&& 柱对[0].低 > 柱对[1].低
|
||||
{
|
||||
结果[0] = true;
|
||||
}
|
||||
|
||||
// DIF背驰 (no sort — Python compares peak vs last directly)
|
||||
let 最高离差值 = 柱子序列
|
||||
.iter()
|
||||
.max_by(|a, b| {
|
||||
let da = a.macd.as_ref().and_then(|m| m.DIF).unwrap_or(0.0).abs();
|
||||
let db = b.macd.as_ref().and_then(|m| m.DIF).unwrap_or(0.0).abs();
|
||||
let da = a
|
||||
.指标
|
||||
.read()
|
||||
.unwrap()
|
||||
.macd()
|
||||
.and_then(|m| m.DIF)
|
||||
.unwrap_or(0.0)
|
||||
.abs();
|
||||
let db = b
|
||||
.指标
|
||||
.read()
|
||||
.unwrap()
|
||||
.macd()
|
||||
.and_then(|m| m.DIF)
|
||||
.unwrap_or(0.0)
|
||||
.abs();
|
||||
da.partial_cmp(&db).unwrap_or(std::cmp::Ordering::Equal)
|
||||
})
|
||||
.unwrap();
|
||||
if let (Some(m0), Some(m1)) = (最高离差值.macd.as_ref(), 最后.macd.as_ref()) {
|
||||
let m0_g = 最高离差值.指标.read().unwrap();
|
||||
let m1_g = 最后.指标.read().unwrap();
|
||||
if let (Some(m0), Some(m1)) = (m0_g.macd(), m1_g.macd()) {
|
||||
let dif0 = m0.DIF.unwrap_or(0.0);
|
||||
let dif1 = m1.DIF.unwrap_or(0.0);
|
||||
if dif0 < dif1 && 最高离差值.低 > 最后.低 {
|
||||
@@ -699,12 +851,28 @@ impl 虚线 {
|
||||
let 最高信号线 = 柱子序列
|
||||
.iter()
|
||||
.max_by(|a, b| {
|
||||
let da = a.macd.as_ref().and_then(|m| m.DEA).unwrap_or(0.0).abs();
|
||||
let db = b.macd.as_ref().and_then(|m| m.DEA).unwrap_or(0.0).abs();
|
||||
let da = a
|
||||
.指标
|
||||
.read()
|
||||
.unwrap()
|
||||
.macd()
|
||||
.and_then(|m| m.DEA)
|
||||
.unwrap_or(0.0)
|
||||
.abs();
|
||||
let db = b
|
||||
.指标
|
||||
.read()
|
||||
.unwrap()
|
||||
.macd()
|
||||
.and_then(|m| m.DEA)
|
||||
.unwrap_or(0.0)
|
||||
.abs();
|
||||
da.partial_cmp(&db).unwrap_or(std::cmp::Ordering::Equal)
|
||||
})
|
||||
.unwrap();
|
||||
if let (Some(m0), Some(m1)) = (最高信号线.macd.as_ref(), 最后.macd.as_ref()) {
|
||||
let m0_g = 最高信号线.指标.read().unwrap();
|
||||
let m1_g = 最后.指标.read().unwrap();
|
||||
if let (Some(m0), Some(m1)) = (m0_g.macd(), m1_g.macd()) {
|
||||
let dea0 = m0.DEA.unwrap_or(0.0);
|
||||
let dea1 = m1.DEA.unwrap_or(0.0);
|
||||
if dea0 < dea1 && 最高信号线.低 > 最后.低 {
|
||||
@@ -719,20 +887,15 @@ impl 虚线 {
|
||||
// ---- MACD柱子分段 ----
|
||||
|
||||
/// 计算MACD柱子分段 — 按正负号将MACD柱子分段
|
||||
/// 对齐说明:Python 丢弃末尾分段的末段残留(疑似 bug),Rust 通过 if !当前段.is_empty() 正确追加
|
||||
pub fn 计算MACD柱子分段(k线序列: &[Arc<K线>]) -> Vec<Vec<f64>> {
|
||||
if k线序列.is_empty() {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
let 符号 = |x: f64| -> &str {
|
||||
if x > 0.0 {
|
||||
"正"
|
||||
} else {
|
||||
"负"
|
||||
}
|
||||
};
|
||||
let 符号 = |x: f64| -> &str { if x > 0.0 { "正" } else { "负" } };
|
||||
|
||||
let 首_MACD = match &k线序列[0].macd {
|
||||
let 首_MACD = match k线序列[0].指标.read().unwrap().macd() {
|
||||
Some(m) => m.MACD柱,
|
||||
None => return Vec::new(),
|
||||
};
|
||||
@@ -741,7 +904,7 @@ impl 虚线 {
|
||||
let mut 结果 = Vec::new();
|
||||
|
||||
for k线 in &k线序列[1..] {
|
||||
let macd = match &k线.macd {
|
||||
let macd = match k线.指标.read().unwrap().macd() {
|
||||
Some(m) => m.MACD柱,
|
||||
None => continue,
|
||||
};
|
||||
@@ -812,13 +975,15 @@ impl 虚线 {
|
||||
let mut dea_down = 0;
|
||||
|
||||
for i in 1..普K序列.len() {
|
||||
let pre = &普K序列[i - 1].macd;
|
||||
let cur = &普K序列[i].macd;
|
||||
let pre_guard = 普K序列[i - 1].指标.read().unwrap();
|
||||
let cur_guard = 普K序列[i].指标.read().unwrap();
|
||||
let pre = pre_guard.macd();
|
||||
let cur = cur_guard.macd();
|
||||
if pre.is_none() || cur.is_none() {
|
||||
continue;
|
||||
}
|
||||
let (pre_dif, cur_dif) = (pre.as_ref().unwrap().DIF, cur.as_ref().unwrap().DIF);
|
||||
let (pre_dea, cur_dea) = (pre.as_ref().unwrap().DEA, cur.as_ref().unwrap().DEA);
|
||||
let (pre_dif, cur_dif) = (pre.unwrap().DIF, cur.unwrap().DIF);
|
||||
let (pre_dea, cur_dea) = (pre.unwrap().DEA, cur.unwrap().DEA);
|
||||
|
||||
if let (Some(pd), Some(cd)) = (pre_dif, cur_dif) {
|
||||
if pd < 0.0 && cd >= 0.0 {
|
||||
@@ -843,16 +1008,18 @@ impl 虚线 {
|
||||
let mut 交叉标记 = vec![0i32];
|
||||
|
||||
for i in 1..普K序列.len() {
|
||||
let pre = &普K序列[i - 1].macd;
|
||||
let cur = &普K序列[i].macd;
|
||||
let pre_guard = 普K序列[i - 1].指标.read().unwrap();
|
||||
let cur_guard = 普K序列[i].指标.read().unwrap();
|
||||
let pre = pre_guard.macd();
|
||||
let cur = cur_guard.macd();
|
||||
if pre.is_none() || cur.is_none() {
|
||||
交叉标记.push(0);
|
||||
continue;
|
||||
}
|
||||
let pre_dif = pre.as_ref().unwrap().DIF;
|
||||
let pre_dea = pre.as_ref().unwrap().DEA;
|
||||
let cur_dif = cur.as_ref().unwrap().DIF;
|
||||
let cur_dea = cur.as_ref().unwrap().DEA;
|
||||
let pre_dif = pre.unwrap().DIF;
|
||||
let pre_dea = pre.unwrap().DEA;
|
||||
let cur_dif = cur.unwrap().DIF;
|
||||
let cur_dea = cur.unwrap().DEA;
|
||||
|
||||
if let (Some(pd), Some(cd), Some(pe), Some(ce)) = (pre_dif, cur_dif, pre_dea, cur_dea) {
|
||||
if pd <= pe && cd > ce {
|
||||
@@ -889,13 +1056,38 @@ impl 虚线 {
|
||||
/// 返回 (是否有意义, 原因字符串)
|
||||
pub fn 买卖意义(
|
||||
实线: &虚线, 观察员: &crate::business::observer::观察者
|
||||
) -> (bool, String) {
|
||||
// LRU 缓存查找
|
||||
let key = (
|
||||
std::ptr::from_ref(实线) as usize,
|
||||
std::ptr::from_ref(观察员) as usize,
|
||||
);
|
||||
{
|
||||
let mut cache = 买卖意义缓存.lock().unwrap();
|
||||
if let Some(val) = cached::Cached::cache_get(&mut *cache, &key) {
|
||||
return val.clone();
|
||||
}
|
||||
}
|
||||
|
||||
let result = Self::_买卖意义_计算(实线, 观察员);
|
||||
{
|
||||
let mut cache = 买卖意义缓存.lock().unwrap();
|
||||
cached::Cached::cache_set(&mut *cache, key, result.clone());
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
/// 买卖意义 实际计算(无缓存)
|
||||
fn _买卖意义_计算(
|
||||
实线: &虚线,
|
||||
观察员: &crate::business::observer::观察者,
|
||||
) -> (bool, String) {
|
||||
let 普K序列 = &观察员.普通K线序列;
|
||||
let 配置 = &观察员.配置;
|
||||
|
||||
if *实线.标识.read().unwrap() != "笔"
|
||||
&& *实线.标识.read().unwrap() != "线段"
|
||||
&& !实线.标识.read().unwrap().starts_with("线段<")
|
||||
&& *实线.标识.read().unwrap() != "线段<线段>"
|
||||
{
|
||||
return (false, "标识不在范围内".into());
|
||||
}
|
||||
@@ -903,7 +1095,7 @@ impl 虚线 {
|
||||
// KDJ指标完整性检查
|
||||
let 武_ref = 实线.武.read().unwrap();
|
||||
let 标 = 武_ref.中.标的K线.read().unwrap();
|
||||
match 标.kdj.as_ref() {
|
||||
match 标.指标.read().unwrap().kdj() {
|
||||
Some(kdj) if kdj.K.is_some() && kdj.D.is_some() && kdj.J.is_some() => {}
|
||||
_ => return (false, "KDJ指标不完整".into()),
|
||||
}
|
||||
@@ -947,16 +1139,62 @@ impl 虚线 {
|
||||
}
|
||||
}
|
||||
|
||||
if !结果 && 意义 && 实线.武.read().unwrap().中.与MACD柱子匹配() {
|
||||
if Self::武之MACD极值(普K序列, 实线) && 背驰过.len() > 2 {
|
||||
return (true, "没结果, 极值, 柱子分型匹配, 背驰过大于2次".into());
|
||||
}
|
||||
if !结果
|
||||
&& 意义
|
||||
&& 实线.武.read().unwrap().中.与MACD柱子匹配()
|
||||
&& Self::武之MACD极值(普K序列, 实线)
|
||||
&& 背驰过.len() > 2
|
||||
{
|
||||
return (true, "没结果, 极值, 柱子分型匹配, 背驰过大于2次".into());
|
||||
}
|
||||
|
||||
(结果, "".into())
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for 虚线 {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
if *self.标识.read().unwrap() == "笔" {
|
||||
write!(
|
||||
f,
|
||||
"笔({}, {}, {}, {}, 周期: {}, 数量: {})",
|
||||
self.序号.load(Ordering::Relaxed),
|
||||
self.方向(),
|
||||
self.文,
|
||||
self.武.read().unwrap(),
|
||||
self.文.中.周期,
|
||||
self.武.read().unwrap().中.序号.load(Ordering::Relaxed)
|
||||
- self.文.中.序号.load(Ordering::Relaxed)
|
||||
+ 1
|
||||
)
|
||||
} else {
|
||||
let 四象 = crate::algorithm::segment::线段::四象(self);
|
||||
let 缺口 = crate::algorithm::segment::线段::获取缺口(self);
|
||||
let 缺口_str = match 缺口 {
|
||||
Some(g) => format!("{}", g),
|
||||
None => "None".to_string(),
|
||||
};
|
||||
let 确认K线_str = match &*self.确认K线.read().unwrap() {
|
||||
Some(k) => format!("{}", k),
|
||||
None => "None".to_string(),
|
||||
};
|
||||
write!(
|
||||
f,
|
||||
"{}<{}, {}, {}, {}, {}, 数量: {}, 缺口: {}, {}>",
|
||||
self.标识.read().unwrap(),
|
||||
self.序号.load(Ordering::Relaxed),
|
||||
四象,
|
||||
self.方向(),
|
||||
self.文,
|
||||
self.武.read().unwrap(),
|
||||
self.基础序列.read().unwrap().len(),
|
||||
缺口_str,
|
||||
确认K线_str,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -966,13 +1204,14 @@ mod tests {
|
||||
|
||||
/// 辅助:创建一根最小化的原始K线
|
||||
fn 辅助_创建K线(时间戳: i64, 高: f64, 低: f64, 开: f64, 收: f64) -> K线 {
|
||||
let mut k = K线::default();
|
||||
k.时间戳 = 时间戳;
|
||||
k.高 = 高;
|
||||
k.低 = 低;
|
||||
k.开盘价 = 开;
|
||||
k.收盘价 = 收;
|
||||
k
|
||||
K线 {
|
||||
时间戳,
|
||||
高,
|
||||
低,
|
||||
开盘价: 开,
|
||||
收盘价: 收,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// 辅助:创建一根缠论K线
|
||||
@@ -1232,50 +1471,7 @@ mod tests {
|
||||
assert_eq!(Arc::as_ptr(&笔.文), 文_ptr_before);
|
||||
|
||||
// 但方向变了(因为武从底1变成底2)
|
||||
let 新武耗时 = 笔.武.read().unwrap().时间戳;
|
||||
let 新武耗时 = 笔.武.read().unwrap().时间戳();
|
||||
assert_eq!(新武耗时, 300);
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for 虚线 {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
if *self.标识.read().unwrap() == "笔" {
|
||||
write!(
|
||||
f,
|
||||
"笔({}, {}, {}, {}, 周期: {}, 数量: {})",
|
||||
self.序号.load(Ordering::Relaxed),
|
||||
self.方向(),
|
||||
self.文,
|
||||
self.武.read().unwrap(),
|
||||
self.文.中.周期,
|
||||
self.武.read().unwrap().中.序号.load(Ordering::Relaxed)
|
||||
- self.文.中.序号.load(Ordering::Relaxed)
|
||||
+ 1
|
||||
)
|
||||
} else {
|
||||
let 四象 = crate::algorithm::segment::线段::四象(self);
|
||||
let 缺口 = crate::algorithm::segment::线段::获取缺口(self);
|
||||
let 缺口_str = match 缺口 {
|
||||
Some(g) => format!("{}", g),
|
||||
None => "None".to_string(),
|
||||
};
|
||||
let 确认K线_str = match &*self.确认K线.read().unwrap() {
|
||||
Some(k) => format!("{}", k),
|
||||
None => "None".to_string(),
|
||||
};
|
||||
write!(
|
||||
f,
|
||||
"{}<{}, {}, {}, {}, {}, 数量: {}, 缺口: {}, {}>",
|
||||
self.标识.read().unwrap(),
|
||||
self.序号.load(Ordering::Relaxed),
|
||||
四象,
|
||||
self.方向(),
|
||||
self.文,
|
||||
self.武.read().unwrap(),
|
||||
self.基础序列.read().unwrap().len(),
|
||||
缺口_str,
|
||||
确认K线_str,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,12 +26,22 @@ use crate::structure::segment_feat::线段特征;
|
||||
use crate::types::分型结构;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// 特征分型 — 由三个线段特征元素构成的分型
|
||||
/// 特征分型 — 由三个线段特征元素构成的分型,用于线段特征序列的分型分析。
|
||||
///
|
||||
/// 字段:
|
||||
/// 左: 左侧线段特征元素
|
||||
/// 中: 中间线段特征元素
|
||||
/// 右: 右侧线段特征元素
|
||||
/// 结构: 由左中右三元素分析得出的分型结构(顶/底/散)
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct 特征分型 {
|
||||
/// 左侧线段特征元素
|
||||
pub 左: Arc<线段特征>,
|
||||
/// 中间线段特征元素
|
||||
pub 中: Arc<线段特征>,
|
||||
/// 右侧线段特征元素
|
||||
pub 右: Arc<线段特征>,
|
||||
/// 由左中右分析得出的分型结构
|
||||
pub 结构: 分型结构,
|
||||
}
|
||||
|
||||
|
||||
@@ -25,17 +25,39 @@
|
||||
use crate::kline::chan_kline::缠论K线;
|
||||
use crate::types::分型结构;
|
||||
use crate::types::相对方向;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::atomic::Ordering;
|
||||
use tracing::warn;
|
||||
|
||||
/// 分型 — 由三根缠K构成(可能缺左或右)
|
||||
/// 分型模式 — True 时使用构造时缓存值(默认),False 时从 中 缠K 实时读取
|
||||
pub static 分型模式: AtomicBool = AtomicBool::new(true);
|
||||
|
||||
/// 分型 — 由左中右三根缠论K线构成的顶/底分型。
|
||||
///
|
||||
/// 字段:
|
||||
/// 左: 左侧缠K(可能为 None,表示序列起始处分型左翼缺失)
|
||||
/// 中: 中间缠K(必须存在),分型的核心K线,其 `分型` 字段记录了该位置的分型结构
|
||||
/// 右: 右侧缠K(可能为 None,表示序列末尾处分型右翼缺失)
|
||||
/// 结构: 构造时从 `中.分型` 缓存的分型结构(上/下/顶/底/散)
|
||||
/// 时间戳: 构造时从 `中.时间戳` 缓存的时刻
|
||||
/// 分型特征值: 构造时从 `中.分型特征值` 缓存的值
|
||||
///
|
||||
/// "分型模式" 全局开关控制 时间戳/结构/分型特征值 的读取行为:
|
||||
/// True(默认)— 返回构造时缓存值;False — 从 中 缠K 实时读取
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct 分型 {
|
||||
/// 左侧缠K(序列起始处分型可能为 None)
|
||||
pub 左: Option<Arc<缠论K线>>,
|
||||
/// 中间缠K(必须存在)
|
||||
pub 中: Arc<缠论K线>,
|
||||
/// 右侧缠K(序列末尾处分型可能为 None)
|
||||
pub 右: Option<Arc<缠论K线>>,
|
||||
/// 构造时缓存的 分型结构(上/下/顶/底/散)
|
||||
pub 结构: 分型结构,
|
||||
/// 构造时缓存的 时间戳
|
||||
pub 时间戳: i64,
|
||||
/// 构造时缓存的 分型特征值(历史高低点极值)
|
||||
pub 分型特征值: f64,
|
||||
}
|
||||
|
||||
@@ -43,6 +65,16 @@ impl 分型 {
|
||||
pub fn new(
|
||||
左: Option<Arc<缠论K线>>, 中: Arc<缠论K线>, 右: Option<Arc<缠论K线>>
|
||||
) -> Self {
|
||||
if let (Some(左), Some(右)) = (&左, &右) {
|
||||
debug_assert!(
|
||||
左.时间戳.load(Ordering::Relaxed) < 中.时间戳.load(Ordering::Relaxed)
|
||||
&& 中.时间戳.load(Ordering::Relaxed) < 右.时间戳.load(Ordering::Relaxed),
|
||||
"分型时间戳断言失败: 左={}, 中={}, 右={}",
|
||||
左.时间戳.load(Ordering::Relaxed),
|
||||
中.时间戳.load(Ordering::Relaxed),
|
||||
右.时间戳.load(Ordering::Relaxed),
|
||||
);
|
||||
}
|
||||
let 结构 = 中.分型.read().unwrap().unwrap_or(分型结构::散);
|
||||
let 时间戳 = 中.时间戳.load(Ordering::Relaxed);
|
||||
let 分型特征值 = 中.分型特征值.get();
|
||||
@@ -56,6 +88,33 @@ impl 分型 {
|
||||
}
|
||||
}
|
||||
|
||||
/// 时间戳 — 根据 分型模式 决定返回缓存值(True)或实时值(False)
|
||||
pub fn 时间戳(&self) -> i64 {
|
||||
if 分型模式.load(Ordering::Relaxed) {
|
||||
self.时间戳
|
||||
} else {
|
||||
self.中.时间戳.load(Ordering::Relaxed)
|
||||
}
|
||||
}
|
||||
|
||||
/// 结构 — 根据 分型模式 决定返回缓存值(True)或实时值(False)
|
||||
pub fn 结构(&self) -> 分型结构 {
|
||||
if 分型模式.load(Ordering::Relaxed) {
|
||||
self.结构
|
||||
} else {
|
||||
self.中.分型.read().unwrap().unwrap_or(分型结构::散) // FIXME 错误
|
||||
}
|
||||
}
|
||||
|
||||
/// 分型特征值 — 根据 分型模式 决定返回缓存值(True)或实时值(False)
|
||||
pub fn 分型特征值(&self) -> f64 {
|
||||
if 分型模式.load(Ordering::Relaxed) {
|
||||
self.分型特征值
|
||||
} else {
|
||||
self.中.分型特征值.get()
|
||||
}
|
||||
}
|
||||
|
||||
/// 左中右三组关系
|
||||
pub fn 关系组(&self) -> Option<(相对方向, 相对方向, 相对方向)> {
|
||||
let 左 = self.左.as_ref()?;
|
||||
@@ -69,7 +128,7 @@ impl 分型 {
|
||||
|
||||
/// 分型强度 — 返回 "强"/"中"/"弱"/"未知"
|
||||
pub fn 强度(&self) -> &str {
|
||||
if self.结构 != 分型结构::底 && self.结构 != 分型结构::顶 {
|
||||
if self.结构() != 分型结构::底 && self.结构() != 分型结构::顶 {
|
||||
return "未知";
|
||||
}
|
||||
if self.右.is_none() || self.左.is_none() {
|
||||
@@ -77,7 +136,7 @@ impl 分型 {
|
||||
}
|
||||
|
||||
if let Some(ref 关系组) = self.关系组() {
|
||||
if self.结构 == 分型结构::底 {
|
||||
if self.结构() == 分型结构::底 {
|
||||
if 关系组.2.是否向下() {
|
||||
return "弱";
|
||||
} else if 关系组.2.是否向上() {
|
||||
@@ -85,7 +144,7 @@ impl 分型 {
|
||||
} else {
|
||||
return "中";
|
||||
}
|
||||
} else if self.结构 == 分型结构::顶 {
|
||||
} else if self.结构() == 分型结构::顶 {
|
||||
if 关系组.2.是否向上() {
|
||||
return "弱";
|
||||
} else if 关系组.2.是否向下() {
|
||||
@@ -96,8 +155,8 @@ impl 分型 {
|
||||
}
|
||||
}
|
||||
|
||||
if let (Some(ref 左), Some(ref 右)) = (&self.左, &self.右) {
|
||||
if self.结构 == 分型结构::底 {
|
||||
if let (Some(左), Some(右)) = (&self.左, &self.右) {
|
||||
if self.结构() == 分型结构::底 {
|
||||
if 右.标的K线.read().unwrap().收盘价 > 左.标的K线.read().unwrap().高 {
|
||||
return "强";
|
||||
} else if 右.标的K线.read().unwrap().收盘价 > self.中.标的K线.read().unwrap().高
|
||||
@@ -106,7 +165,7 @@ impl 分型 {
|
||||
} else {
|
||||
return "弱";
|
||||
}
|
||||
} else if self.结构 == 分型结构::顶 {
|
||||
} else if self.结构() == 分型结构::顶 {
|
||||
if 右.标的K线.read().unwrap().收盘价 < 左.标的K线.read().unwrap().低 {
|
||||
return "强";
|
||||
} else if 右.标的K线.read().unwrap().收盘价 < self.中.标的K线.read().unwrap().低
|
||||
@@ -122,23 +181,29 @@ impl 分型 {
|
||||
|
||||
/// MACD柱子分型匹配 — 检查左中右MACD柱形成顶/底形态
|
||||
pub fn 与MACD柱子分型匹配(&self) -> bool {
|
||||
if let (Some(ref 左), Some(ref 右)) = (&self.左, &self.右) {
|
||||
if self.结构 == 分型结构::底 {
|
||||
if let (Some(左), Some(右)) = (&self.左, &self.右) {
|
||||
if self.结构() == 分型结构::底 {
|
||||
let 左_k = 左.标的K线.read().unwrap();
|
||||
let 中_k = self.中.标的K线.read().unwrap();
|
||||
let 右_k = 右.标的K线.read().unwrap();
|
||||
if let (Some(ref 左macd), Some(ref 中macd), Some(ref 右macd)) =
|
||||
(&左_k.macd, &中_k.macd, &右_k.macd)
|
||||
let 左_m = 左_k.指标.read().unwrap();
|
||||
let 中_m = 中_k.指标.read().unwrap();
|
||||
let 右_m = 右_k.指标.read().unwrap();
|
||||
if let (Some(左macd), Some(中macd), Some(右macd)) =
|
||||
(左_m.macd(), 中_m.macd(), 右_m.macd())
|
||||
{
|
||||
return 左macd.MACD柱 > 中macd.MACD柱 && 中macd.MACD柱 < 右macd.MACD柱;
|
||||
}
|
||||
}
|
||||
if self.结构 == 分型结构::顶 {
|
||||
if self.结构() == 分型结构::顶 {
|
||||
let 左_k = 左.标的K线.read().unwrap();
|
||||
let 中_k = self.中.标的K线.read().unwrap();
|
||||
let 右_k = 右.标的K线.read().unwrap();
|
||||
if let (Some(ref 左macd), Some(ref 中macd), Some(ref 右macd)) =
|
||||
(&左_k.macd, &中_k.macd, &右_k.macd)
|
||||
let 左_m = 左_k.指标.read().unwrap();
|
||||
let 中_m = 中_k.指标.read().unwrap();
|
||||
let 右_m = 右_k.指标.read().unwrap();
|
||||
if let (Some(左macd), Some(中macd), Some(右macd)) =
|
||||
(左_m.macd(), 中_m.macd(), 右_m.macd())
|
||||
{
|
||||
return 左macd.MACD柱 < 中macd.MACD柱 && 中macd.MACD柱 > 右macd.MACD柱;
|
||||
}
|
||||
@@ -148,11 +213,8 @@ impl 分型 {
|
||||
}
|
||||
|
||||
/// 判断两个分型是否匹配
|
||||
pub fn 判断分型(左: &Arc<分型>, 右: &Arc<分型>, 模式: &str) -> bool {
|
||||
match 模式 {
|
||||
"中" => Arc::as_ptr(左) == Arc::as_ptr(右),
|
||||
_ => false,
|
||||
}
|
||||
pub fn 判断分型(左: &Arc<分型>, 右: &Arc<分型>, _模式: &str) -> bool {
|
||||
Arc::as_ptr(左) == Arc::as_ptr(右)
|
||||
}
|
||||
|
||||
/// 从缠K序列中获取以指定缠K为中元素的分型
|
||||
@@ -179,17 +241,17 @@ impl 分型 {
|
||||
/// 向分型序列中添加新分型
|
||||
pub fn 向序列中添加(分型序列: &mut Vec<Arc<分型>>, 当前分型: Arc<分型>) {
|
||||
if 分型序列.is_empty() {
|
||||
if 当前分型.结构 != 分型结构::顶 && 当前分型.结构 != 分型结构::底
|
||||
if 当前分型.结构() != 分型结构::顶 && 当前分型.结构() != 分型结构::底
|
||||
{
|
||||
panic!("首次添加分型不为 顶底 {:?}", 当前分型);
|
||||
}
|
||||
} else {
|
||||
let 前一个 = &分型序列[分型序列.len() - 1];
|
||||
if 前一个.结构 == 当前分型.结构 {
|
||||
if 前一个.结构() == 当前分型.结构() {
|
||||
panic!("分型相同无法添加 {:?} {:?}", 前一个, 当前分型);
|
||||
}
|
||||
if 前一个.右.is_none() {
|
||||
eprintln!("分型.向序列中添加, 分型异常 {:?}", 前一个);
|
||||
warn!("分型.向序列中添加, 分型异常 {:?}", 前一个);
|
||||
}
|
||||
}
|
||||
分型序列.push(当前分型);
|
||||
@@ -210,13 +272,9 @@ impl std::fmt::Display for 分型 {
|
||||
write!(
|
||||
f,
|
||||
"{}<{}, {}, None: {}, None: {}>",
|
||||
self.中
|
||||
.分型
|
||||
.read()
|
||||
.unwrap()
|
||||
.unwrap_or(crate::types::分型结构::散),
|
||||
self.时间戳,
|
||||
crate::utils::format_f64_g(self.分型特征值),
|
||||
self.结构(),
|
||||
self.时间戳(),
|
||||
crate::utils::format_f64_g(self.分型特征值()),
|
||||
if self.左.is_none() { "True" } else { "False" },
|
||||
if self.右.is_none() { "True" } else { "False" },
|
||||
)
|
||||
|
||||
@@ -27,58 +27,90 @@ use crate::structure::feat_fractal::特征分型;
|
||||
use crate::structure::fractal_obj::分型;
|
||||
use crate::types::{分型结构, 相对方向};
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::Arc;
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
/// 线段特征 — 特征序列元素(内部是虚线的集合)
|
||||
#[derive(Debug, Clone)]
|
||||
/// 线段特征 — 特征序列元素,内部是虚线的集合。
|
||||
///
|
||||
/// 在线段划分算法中,将同方向的笔归入一个特征序列元素,
|
||||
/// 然后通过特征序列元素之间的分型关系判定线段终结。
|
||||
///
|
||||
/// 字段:
|
||||
/// 序号: 特征序列元素编号
|
||||
/// 标识: 标识字符串(如 "特征<虚线>")
|
||||
/// 线段方向: 所属线段的方向(与元素包含的虚线方向相反)
|
||||
/// 基础序列: 构成该特征序列元素的虚线序列
|
||||
///
|
||||
/// 计算属性:
|
||||
/// 文 — 起点分型(取特征序列中分型特征值极值)
|
||||
/// 武 — 终点分型(取特征序列中分型特征值极值)
|
||||
/// 方向 — 线段方向的翻转
|
||||
/// 高 / 低 — 文/武 中分型特征值的较大/较小者
|
||||
#[derive(Debug)]
|
||||
pub struct 线段特征 {
|
||||
/// 特征序列元素编号
|
||||
pub 序号: i64,
|
||||
pub 标识: String,
|
||||
/// 标识字符串(如 "特征<虚线>")
|
||||
pub 标识: RwLock<String>,
|
||||
/// 所属线段的方向
|
||||
pub 线段方向: 相对方向,
|
||||
pub 元素: Vec<Arc<虚线>>,
|
||||
/// 构成该元素的虚线序列
|
||||
pub 基础序列: Vec<Arc<虚线>>,
|
||||
}
|
||||
|
||||
impl Clone for 线段特征 {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
序号: self.序号,
|
||||
标识: RwLock::new(self.标识.read().unwrap().clone()),
|
||||
线段方向: self.线段方向,
|
||||
基础序列: self.基础序列.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl 线段特征 {
|
||||
/// 新建线段特征(给定标识、基础序列和线段方向)
|
||||
pub fn new(标识: String, 基础序列: Vec<Arc<虚线>>, 线段方向: 相对方向) -> Self {
|
||||
Self {
|
||||
序号: 0,
|
||||
标识,
|
||||
标识: RwLock::new(标识),
|
||||
线段方向,
|
||||
元素: 基础序列,
|
||||
基础序列,
|
||||
}
|
||||
}
|
||||
|
||||
/// 图表标题 — 返回标识字符串
|
||||
pub fn 图表标题(&self) -> String {
|
||||
self.标识.clone()
|
||||
self.标识.read().unwrap().clone()
|
||||
}
|
||||
|
||||
/// 文 — 取特征序列元素中分型特征值最大/最小的文分型
|
||||
/// tiebreaker: later时间戳 wins when特征值 equal (matches Python)
|
||||
pub fn 文(&self) -> Arc<分型> {
|
||||
if self.线段方向.是否向上() {
|
||||
self.元素
|
||||
self.基础序列
|
||||
.iter()
|
||||
.max_by(|a, b| {
|
||||
a.文
|
||||
.分型特征值
|
||||
.partial_cmp(&b.文.分型特征值)
|
||||
.unwrap_or(std::cmp::Ordering::Equal)
|
||||
.then_with(|| a.文.时间戳.cmp(&b.文.时间戳))
|
||||
.then_with(|| a.文.时间戳().cmp(&b.文.时间戳()))
|
||||
})
|
||||
.map(|x| Arc::clone(&x.文))
|
||||
.unwrap_or_else(|| Arc::clone(&self.元素[0].文))
|
||||
.unwrap_or_else(|| Arc::clone(&self.基础序列[0].文))
|
||||
} else {
|
||||
self.元素
|
||||
self.基础序列
|
||||
.iter()
|
||||
.min_by(|a, b| {
|
||||
a.文
|
||||
.max_by(|a, b| {
|
||||
b.文
|
||||
.分型特征值
|
||||
.partial_cmp(&b.文.分型特征值)
|
||||
.partial_cmp(&a.文.分型特征值)
|
||||
.unwrap_or(std::cmp::Ordering::Equal)
|
||||
.then_with(|| b.文.时间戳.cmp(&a.文.时间戳))
|
||||
.then_with(|| a.文.时间戳().cmp(&b.文.时间戳()))
|
||||
})
|
||||
.map(|x| Arc::clone(&x.文))
|
||||
.unwrap_or_else(|| Arc::clone(&self.元素[0].文))
|
||||
.unwrap_or_else(|| Arc::clone(&self.基础序列[0].文))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,7 +118,7 @@ impl 线段特征 {
|
||||
/// tiebreaker: later时间戳 wins when特征值 equal (matches Python)
|
||||
pub fn 武(&self) -> Arc<分型> {
|
||||
if self.线段方向.是否向上() {
|
||||
self.元素
|
||||
self.基础序列
|
||||
.iter()
|
||||
.max_by(|a, b| {
|
||||
a.武
|
||||
@@ -99,41 +131,43 @@ impl 线段特征 {
|
||||
a.武
|
||||
.read()
|
||||
.unwrap()
|
||||
.时间戳
|
||||
.cmp(&b.武.read().unwrap().时间戳)
|
||||
.时间戳()
|
||||
.cmp(&b.武.read().unwrap().时间戳())
|
||||
})
|
||||
})
|
||||
.map(|x| x.武.read().unwrap().clone())
|
||||
.unwrap_or_else(|| self.元素[0].武.read().unwrap().clone())
|
||||
.unwrap_or_else(|| self.基础序列[0].武.read().unwrap().clone())
|
||||
} else {
|
||||
self.元素
|
||||
self.基础序列
|
||||
.iter()
|
||||
.min_by(|a, b| {
|
||||
a.武
|
||||
.max_by(|a, b| {
|
||||
b.武
|
||||
.read()
|
||||
.unwrap()
|
||||
.分型特征值
|
||||
.partial_cmp(&b.武.read().unwrap().分型特征值)
|
||||
.partial_cmp(&a.武.read().unwrap().分型特征值)
|
||||
.unwrap_or(std::cmp::Ordering::Equal)
|
||||
.then_with(|| {
|
||||
b.武
|
||||
a.武
|
||||
.read()
|
||||
.unwrap()
|
||||
.时间戳
|
||||
.cmp(&a.武.read().unwrap().时间戳)
|
||||
.时间戳()
|
||||
.cmp(&b.武.read().unwrap().时间戳())
|
||||
})
|
||||
})
|
||||
.map(|x| x.武.read().unwrap().clone())
|
||||
.unwrap_or_else(|| self.元素[0].武.read().unwrap().clone())
|
||||
.unwrap_or_else(|| self.基础序列[0].武.read().unwrap().clone())
|
||||
}
|
||||
}
|
||||
|
||||
/// 高 — 文和武中分型特征值的较大者
|
||||
pub fn 高(&self) -> f64 {
|
||||
let 文 = self.文();
|
||||
let 武 = self.武();
|
||||
文.分型特征值.max(武.分型特征值)
|
||||
}
|
||||
|
||||
/// 低 — 文和武中分型特征值的较小者
|
||||
pub fn 低(&self) -> f64 {
|
||||
let 文 = self.文();
|
||||
let 武 = self.武();
|
||||
@@ -146,25 +180,25 @@ impl 线段特征 {
|
||||
}
|
||||
|
||||
/// 向特征序列元素中添加虚线
|
||||
pub fn 添加(&mut self, 待添加虚线: Arc<虚线>) -> Result<(), String> {
|
||||
pub fn _添加(&mut self, 待添加虚线: Arc<虚线>) -> Result<(), String> {
|
||||
if 待添加虚线.方向() == self.线段方向 {
|
||||
return Err("添加方向与线段方向相同".into());
|
||||
}
|
||||
self.元素.push(待添加虚线);
|
||||
self.基础序列.push(待添加虚线);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 从特征序列元素中删除虚线
|
||||
pub fn 删除(&mut self, 待删除虚线: &Arc<虚线>) -> Result<(), String> {
|
||||
pub fn _删除(&mut self, 待删除虚线: &Arc<虚线>) -> Result<(), String> {
|
||||
if 待删除虚线.方向() == self.方向() {
|
||||
return Err("删除方向与特征序列方向相同".into());
|
||||
}
|
||||
if let Some(pos) = self
|
||||
.元素
|
||||
.基础序列
|
||||
.iter()
|
||||
.position(|x| Arc::as_ptr(x) == Arc::as_ptr(待删除虚线))
|
||||
{
|
||||
self.元素.remove(pos);
|
||||
self.基础序列.remove(pos);
|
||||
Ok(())
|
||||
} else {
|
||||
Err("待删除虚线不在特征序列中".into())
|
||||
@@ -173,7 +207,7 @@ impl 线段特征 {
|
||||
|
||||
/// 新建特征序列元素
|
||||
pub fn 新建(虚线序列: Vec<Arc<虚线>>, 线段方向: 相对方向) -> Self {
|
||||
let 标识 = format!("特征<虚线>");
|
||||
let 标识 = "特征<虚线>".to_string();
|
||||
Self::new(标识, 虚线序列, 线段方向)
|
||||
}
|
||||
|
||||
@@ -210,12 +244,12 @@ impl 线段特征 {
|
||||
|
||||
if 应替换 {
|
||||
let 小号虚线 = 中
|
||||
.元素
|
||||
.基础序列
|
||||
.iter()
|
||||
.min_by_key(|o| o.序号.load(Ordering::Relaxed))
|
||||
.unwrap();
|
||||
let 大号虚线 = 右
|
||||
.元素
|
||||
.基础序列
|
||||
.iter()
|
||||
.max_by_key(|o| o.序号.load(Ordering::Relaxed))
|
||||
.unwrap();
|
||||
@@ -250,7 +284,7 @@ impl 线段特征 {
|
||||
)) {
|
||||
// Clone-modify-replace
|
||||
let mut 新特征 = (*结果[最后_idx]).clone();
|
||||
let _ = 新特征.添加(Arc::clone(虚线));
|
||||
let _ = 新特征._添加(Arc::clone(虚线));
|
||||
结果[最后_idx] = Arc::new(新特征);
|
||||
} else {
|
||||
结果.push(Arc::new(Self::新建(vec![Arc::clone(虚线)], 线段方向)));
|
||||
@@ -260,7 +294,7 @@ impl 线段特征 {
|
||||
结果
|
||||
}
|
||||
|
||||
/// 获取分型序列
|
||||
/// 获取分型序列 — 从连续的特征序列元素中提取特征分型
|
||||
pub fn 获取分型序列(特征序列: &[Arc<线段特征>]) -> Vec<特征分型> {
|
||||
let mut 结果 = Vec::new();
|
||||
if 特征序列.len() < 3 {
|
||||
@@ -295,17 +329,17 @@ impl crate::types::fractal::有高低 for 线段特征 {
|
||||
|
||||
impl std::fmt::Display for 线段特征 {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
if self.元素.is_empty() {
|
||||
write!(f, "{}<{}, 空>", self.标识, self.线段方向)
|
||||
if self.基础序列.is_empty() {
|
||||
write!(f, "{}<{}, 空>", self.标识.read().unwrap(), self.线段方向)
|
||||
} else {
|
||||
write!(
|
||||
f,
|
||||
"{}<{}, {}, {}, {}>",
|
||||
self.标识,
|
||||
self.标识.read().unwrap(),
|
||||
self.线段方向,
|
||||
self.文(),
|
||||
self.武(),
|
||||
self.元素.len()
|
||||
self.基础序列.len()
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -320,13 +354,14 @@ mod tests {
|
||||
use crate::types::分型结构;
|
||||
|
||||
fn 辅助_创建K线(时间戳: i64, 高: f64, 低: f64, 开: f64, 收: f64) -> K线 {
|
||||
let mut k = K线::default();
|
||||
k.时间戳 = 时间戳;
|
||||
k.高 = 高;
|
||||
k.低 = 低;
|
||||
k.开盘价 = 开;
|
||||
k.收盘价 = 收;
|
||||
k
|
||||
K线 {
|
||||
时间戳,
|
||||
高,
|
||||
低,
|
||||
开盘价: 开,
|
||||
收盘价: 收,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn 辅助_创建缠K(
|
||||
@@ -527,7 +562,7 @@ mod tests {
|
||||
|
||||
let 文 = feat.文();
|
||||
// 向上取最大特征值:都是100 → tiebreaker取后时间戳 → 笔2.文(300)
|
||||
assert_eq!(文.时间戳, 300);
|
||||
assert_eq!(文.时间戳(), 300);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -544,7 +579,7 @@ mod tests {
|
||||
|
||||
let 武 = feat.武();
|
||||
// 向上取最大特征值:都是80 → tiebreaker取后时间戳 → 笔2.武(400)
|
||||
assert_eq!(武.时间戳, 400);
|
||||
assert_eq!(武.时间戳(), 400);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
@@ -557,7 +592,7 @@ mod tests {
|
||||
let 笔1 = 辅助_创建笔(100, 100.0, 90.0, 200, 90.0, 80.0);
|
||||
let mut feat = 线段特征::new("测试".into(), vec![], 相对方向::向上);
|
||||
|
||||
let result = feat.添加(Arc::clone(&笔1));
|
||||
let result = feat._添加(Arc::clone(&笔1));
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
|
||||
@@ -567,7 +602,7 @@ mod tests {
|
||||
let 笔1 = 辅助_创建笔(100, 100.0, 90.0, 200, 90.0, 80.0);
|
||||
let mut feat = 线段特征::new("测试".into(), vec![], 相对方向::向下);
|
||||
|
||||
let result = feat.添加(Arc::clone(&笔1));
|
||||
let result = feat._添加(Arc::clone(&笔1));
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
|
||||
@@ -91,6 +91,7 @@ impl std::fmt::Display for 买卖点类型 {
|
||||
}
|
||||
|
||||
impl 买卖点类型 {
|
||||
/// 判断是否为买点类型
|
||||
pub fn 是买点(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
@@ -106,6 +107,7 @@ impl 买卖点类型 {
|
||||
)
|
||||
}
|
||||
|
||||
/// 判断是否为卖点类型
|
||||
pub fn 是卖点(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
|
||||
@@ -48,6 +48,7 @@ pub enum 相对方向 {
|
||||
}
|
||||
|
||||
impl 相对方向 {
|
||||
/// 翻转方向(向上→向下,缺口向上→缺口向下,顺→逆 等)
|
||||
pub fn 翻转(&self) -> Self {
|
||||
match self {
|
||||
Self::向上 => Self::向下,
|
||||
@@ -62,32 +63,33 @@ impl 相对方向 {
|
||||
}
|
||||
}
|
||||
|
||||
/// 是否向上方向(向上/向上缺口/衔接向上)
|
||||
pub fn 是否向上(&self) -> bool {
|
||||
matches!(self, Self::向上 | Self::向上缺口 | Self::衔接向上)
|
||||
}
|
||||
|
||||
/// 是否向下方向(向下/向下缺口/衔接向下)
|
||||
pub fn 是否向下(&self) -> bool {
|
||||
matches!(self, Self::向下 | Self::向下缺口 | Self::衔接向下)
|
||||
}
|
||||
|
||||
/// 是否包含关系(顺/逆/同)
|
||||
pub fn 是否包含(&self) -> bool {
|
||||
matches!(self, Self::顺 | Self::逆 | Self::同)
|
||||
}
|
||||
|
||||
/// 是否缺口(向上缺口/向下缺口)
|
||||
pub fn 是否缺口(&self) -> bool {
|
||||
matches!(self, Self::向下缺口 | Self::向上缺口)
|
||||
}
|
||||
|
||||
/// 是否衔接(衔接向上/衔接向下)
|
||||
pub fn 是否衔接(&self) -> bool {
|
||||
matches!(self, Self::衔接向下 | Self::衔接向上)
|
||||
}
|
||||
|
||||
/// 分析两个K线之间的相对方向
|
||||
pub fn 分析(前高: f64, 前低: f64, 后高: f64, 后低: f64) -> Self {
|
||||
// NaN 值无法判断方向,视为"同"避免 panic
|
||||
if 前高.is_nan() || 前低.is_nan() || 后高.is_nan() || 后低.is_nan() {
|
||||
return Self::同;
|
||||
}
|
||||
if 前高 == 后高 && 前低 == 后低 {
|
||||
return Self::同;
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
*/
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tracing::warn;
|
||||
|
||||
/// 分型结构 —— 三根K线构成的结构形态
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
@@ -92,7 +93,10 @@ impl 分型结构 {
|
||||
)
|
||||
}
|
||||
|
||||
fn 分析_内部(
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
/// 分析三K线的分型结构(内部实现,接收裸f64值)
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn 分析_内部(
|
||||
左高: f64,
|
||||
左低: f64,
|
||||
中高: f64,
|
||||
@@ -111,36 +115,23 @@ impl 分型结构 {
|
||||
let 向下类 = |d: 相对方向| d.是否向下();
|
||||
|
||||
match (左中关系, 中右关系) {
|
||||
// 顺序包含 — 忽视时可以绕过
|
||||
(相对方向::顺, _) if !忽视顺序包含 => {
|
||||
panic!("顺序包含: {:?} {:?}", 左中关系, 中右关系);
|
||||
panic!("顺序包含 左中相对方向");
|
||||
}
|
||||
(_, 相对方向::顺) if !忽视顺序包含 => {
|
||||
panic!("顺序包含: {:?} {:?}", 左中关系, 中右关系);
|
||||
panic!("顺序包含 中右相对方向");
|
||||
}
|
||||
// 向上 + 向上 = 三连上
|
||||
(a, b) if 向上类(a) && 向上类(b) => Some(Self::上),
|
||||
// 向上 + 向下 = 顶分型
|
||||
(a, b) if 向上类(a) && 向下类(b) => Some(Self::顶),
|
||||
// 向上 + 逆序包含 = 上
|
||||
(a, 相对方向::逆) if 向上类(a) && 可以逆序包含 => Some(Self::上),
|
||||
// 向下 + 向上 = 底分型
|
||||
(a, b) if 向下类(a) && 向上类(b) => Some(Self::底),
|
||||
// 向下 + 向下 = 三连下
|
||||
(a, b) if 向下类(a) && 向下类(b) => Some(Self::下),
|
||||
// 向下 + 逆序包含 = 下
|
||||
(a, 相对方向::逆) if 向下类(a) && 可以逆序包含 => Some(Self::下),
|
||||
// 逆序包含 + 向上 = 底
|
||||
(相对方向::逆, a) if 向上类(a) && 可以逆序包含 => Some(Self::底),
|
||||
// 逆序包含 + 向下 = 顶
|
||||
(相对方向::逆, a) if 向下类(a) && 可以逆序包含 => Some(Self::顶),
|
||||
// 逆序包含 + 逆序包含 = 散
|
||||
(相对方向::逆, 相对方向::逆) if 可以逆序包含 => Some(Self::散),
|
||||
_ => {
|
||||
eprintln!(
|
||||
"无法识别的分型结构: 左中={:?}, 中右={:?}",
|
||||
左中关系, 中右关系
|
||||
);
|
||||
warn!("未匹配的关系 {}, {}, {}", 可以逆序包含, 左中关系, 中右关系);
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,11 +27,14 @@ use serde::{Deserialize, Serialize};
|
||||
/// 缺口 —— 两个价格区间之间的空隙
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
|
||||
pub struct 缺口 {
|
||||
/// 缺口上沿
|
||||
pub 高: f64,
|
||||
/// 缺口下沿
|
||||
pub 低: f64,
|
||||
}
|
||||
|
||||
impl 缺口 {
|
||||
/// 创建缺口,高必须大于低
|
||||
pub fn new(高: f64, 低: f64) -> Self {
|
||||
assert!(高 > 低, "缺口高必须大于低: 高={高}, 低={低}");
|
||||
Self { 高, 低 }
|
||||
|
||||
@@ -1,18 +1,46 @@
|
||||
/*
|
||||
* 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>` 一致。
|
||||
#[derive(Debug, Default)]
|
||||
/// 线程安全的 f64 原子容器
|
||||
pub struct SyncF64(AtomicU64);
|
||||
|
||||
impl SyncF64 {
|
||||
/// 创建 SyncF64
|
||||
pub fn new(v: f64) -> Self {
|
||||
Self(AtomicU64::new(v.to_bits()))
|
||||
}
|
||||
|
||||
/// 原子读取 f64 值
|
||||
pub fn get(&self) -> f64 {
|
||||
f64::from_bits(self.0.load(Ordering::Relaxed))
|
||||
}
|
||||
|
||||
/// 原子写入 f64 值
|
||||
pub fn set(&self, v: f64) {
|
||||
self.0.store(v.to_bits(), Ordering::Relaxed);
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ pub fn format_f64_g(value: f64) -> String {
|
||||
let exp = abs.log10().floor() as i32;
|
||||
|
||||
// Python :g 科学计数法边界: exp < -4 或 exp >= p (=6)
|
||||
if exp < -4 || exp >= 6 {
|
||||
if !(-4..6).contains(&exp) {
|
||||
let significand = value / 10_f64.powi(exp);
|
||||
let s = format!("{:.5}", significand);
|
||||
let s = s.trim_end_matches('0').trim_end_matches('.');
|
||||
@@ -57,12 +57,12 @@ pub fn format_f64_g(value: f64) -> String {
|
||||
}
|
||||
let s = format!("{:.prec$}", value, prec = 6 - int_digits);
|
||||
let s = s.trim_end_matches('0');
|
||||
return s.trim_end_matches('.').to_string();
|
||||
s.trim_end_matches('.').to_string()
|
||||
} else {
|
||||
let leading_zeros = (-exp) as usize;
|
||||
let s = format!("{:.prec$}", value, prec = leading_zeros + 5);
|
||||
let s = s.trim_end_matches('0');
|
||||
return s.trim_end_matches('.').to_string();
|
||||
s.trim_end_matches('.').to_string()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
# API 参考
|
||||
|
||||
## 枚举
|
||||
|
||||
| 类 | 说明 |
|
||||
|---|------|
|
||||
| `买卖点类型` | 一卖点、一买点、二卖点、二买点、三卖点、三买点 |
|
||||
| `相对方向` | 向上、向下、向上缺口、向下缺口、衔接向上、衔接向下 |
|
||||
| `分型结构` | 顶分型、底分型、散(无分型) |
|
||||
|
||||
## 数据
|
||||
|
||||
| 类 | 说明 |
|
||||
|---|------|
|
||||
| `K线` | 原始 OHLCV 数据,内嵌 MACD/RSI/KDJ 指标 |
|
||||
| `缠论K线` | 经包含处理后的 K 线,有方向(向上/向下)、分型结构标记 |
|
||||
| `缺口` | 前后两段区间的相对位置关系 |
|
||||
|
||||
## 结构
|
||||
|
||||
| 类 | 说明 |
|
||||
|---|------|
|
||||
| `分型` | 顶/底分型,由左中右三根缠K构成 |
|
||||
| `虚线` | 笔/线段的通用数据结构 |
|
||||
| `线段特征` | 线段特征序列,用于分析线段力度 |
|
||||
| `特征分型` | 特征分型结构 |
|
||||
|
||||
## 指标
|
||||
|
||||
| 类 | 说明 |
|
||||
|---|------|
|
||||
| `平滑异同移动平均线` | MACD — EMA 快慢线 + 信号线 + 柱状图 |
|
||||
| `相对强弱指数` | RSI — Wilder SMA 平滑,含超买超卖阈值 |
|
||||
| `随机指标` | KDJ — RSV → K → D → J,含超买超卖阈值 |
|
||||
| `指标` | 指标工具类 — `K线取值` 等静态方法 |
|
||||
|
||||
## 算法
|
||||
|
||||
| 类 | 说明 |
|
||||
|---|------|
|
||||
| `笔` | 笔划分算法(创建、分析、弱化、分析前检查) |
|
||||
| `线段` | 线段划分算法(分析、武斗、武终、分割序列) |
|
||||
| `中枢` | 中枢识别算法(延伸、扩展、第三买卖线) |
|
||||
| `背驰分析` | MACD/斜率/测度背离检测 |
|
||||
|
||||
## 业务
|
||||
|
||||
| 类 | 说明 |
|
||||
|---|------|
|
||||
| `缠论配置` | 50+ 参数集中控制所有分析阶段的行为 |
|
||||
| `观察者` | 单周期分析器,持有所有层级序列 |
|
||||
| `基础买卖点` | 买卖点数据结构 |
|
||||
| `买卖点` | 继承基础买卖点,添加工厂类方法 |
|
||||
| `K线合成器` | 将小周期 K 线合成为大周期 K 线 |
|
||||
| `立体分析器` | 多周期分析器,含 K线合成器 + 每周期观察者 |
|
||||
@@ -0,0 +1,46 @@
|
||||
# Sphinx 配置 — chanlun 缠论技术分析库
|
||||
#
|
||||
# 使用 MyST 解析器支持 Markdown,Furo 主题
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
# 确保文档构建时能 import chanlun
|
||||
sys.path.insert(0, os.path.abspath(".."))
|
||||
|
||||
# ---- 项目信息 ----
|
||||
project = "chanlun"
|
||||
copyright = "2026, YuWuKunCheng"
|
||||
author = "YuWuKunCheng"
|
||||
release = "26.5.103"
|
||||
language = "zh_CN"
|
||||
|
||||
# ---- 扩展 ----
|
||||
extensions = [
|
||||
"myst_parser", # Markdown 支持
|
||||
"sphinx.ext.viewcode", # 添加源码链接
|
||||
"sphinx.ext.intersphinx", # 跨项目文档链接
|
||||
]
|
||||
|
||||
# ---- MyST 配置 ----
|
||||
myst_enable_extensions = [
|
||||
"colon_fence", # ::: 围栏代码块
|
||||
"fieldlist", # 字段列表
|
||||
]
|
||||
myst_heading_anchors = 3
|
||||
|
||||
# ---- 主题 ----
|
||||
html_theme = "furo"
|
||||
html_title = "chanlun — 缠论技术分析库"
|
||||
html_theme_options = {
|
||||
"source_repository": "https://github.com/yuwukuncheng/chanlun.rs",
|
||||
"source_branch": "main",
|
||||
"source_directory": "docs/",
|
||||
}
|
||||
|
||||
# ---- 跨项目链接 ----
|
||||
intersphinx_mapping = {
|
||||
"python": ("https://docs.python.org/3", None),
|
||||
}
|
||||
|
||||
add_module_names = False
|
||||
@@ -0,0 +1,45 @@
|
||||
# chanlun — 缠论技术分析库
|
||||
|
||||
基于 Rust + PyO3 的高性能缠论技术分析 Python 绑定,API 参考 `chan.py` 设计,高度兼容。
|
||||
|
||||
## 特性
|
||||
|
||||
- **高性能**:核心算法用 Rust 实现,通过 PyO3 暴露为 Python 原生模块
|
||||
- **完全兼容**:类名、方法名、字段名与 `chan.py` 保持一致
|
||||
- **流式计算**:逐 K 线增量更新,无需重复计算
|
||||
- **多周期分析**:内置 K 线合成器和立体分析器,支持跨周期联动
|
||||
|
||||
## 安装
|
||||
|
||||
```bash
|
||||
pip install chanlun
|
||||
```
|
||||
|
||||
## 快速开始
|
||||
|
||||
```python
|
||||
import chanlun
|
||||
|
||||
config = chanlun.缠论配置()
|
||||
obs = chanlun.观察者.读取数据文件("BTCUSD-300.nb", config)
|
||||
|
||||
print(f"K线: {len(obs.普通K线序列)}")
|
||||
print(f"笔: {len(obs.笔序列)}")
|
||||
print(f"线段: {len(obs.线段序列)}")
|
||||
print(f"中枢: {len(obs.中枢序列)}")
|
||||
```
|
||||
|
||||
```{toctree}
|
||||
:hidden:
|
||||
:caption: 文档导航
|
||||
|
||||
api
|
||||
```
|
||||
|
||||
```{toctree}
|
||||
:hidden:
|
||||
:caption: 外部链接
|
||||
|
||||
GitHub <https://github.com/yuwukuncheng/chanlun.rs>
|
||||
PyPI <https://pypi.org/project/chanlun/>
|
||||
```
|
||||
@@ -0,0 +1,3 @@
|
||||
sphinx>=8.0
|
||||
myst-parser>=4.0
|
||||
furo>=2024.0
|
||||
+63
-34
@@ -657,35 +657,56 @@ class 随机数据(bt.feeds.DataBase):
|
||||
class 自定义实时数据源(bt.feed.DataBase):
|
||||
"""
|
||||
一个用于模拟实时数据推送的数据源,继承自Backtrader的DataBase。
|
||||
在实际应用中,你需要将“模拟生成数据”的部分,替换为接收WebSocket等推送数据的代码。
|
||||
|
||||
支持两种数据输入方式:
|
||||
1. 手动投喂:调用 投喂数据(时间戳, 开, 高, 低, 收, 量) 从 WebSocket 回调等外部代码推送
|
||||
2. 后台线程:传入 观察员 和 魔法 参数,start() 时自动启动线程获取历史数据
|
||||
(注意:观察员 需实现 读取任意数据 方法)
|
||||
|
||||
时间戳格式:Unix 时间戳(秒),为 int 类型。
|
||||
"""
|
||||
|
||||
def __init__(self, 数据队列: queue.Queue, 观察员: "观察者", 魔法, **魔法参数):
|
||||
# 调用父类初始化方法
|
||||
def __init__(self, 数据队列: queue.Queue = None, 观察员: "观察者" = None, 魔法=None, **魔法参数):
|
||||
super(自定义实时数据源, self).__init__()
|
||||
# 存储外部传入的数据队列,用于接收实时数据
|
||||
self.数据队列 = 数据队列
|
||||
# 定义一个标志,用于控制数据加载的停止
|
||||
self.数据队列 = 数据队列 or queue.Queue()
|
||||
self.正在运行 = False
|
||||
self.观察员 = 观察员
|
||||
self.魔法 = 魔法
|
||||
self.__魔法参数 = 魔法参数
|
||||
self.已有数据 = False
|
||||
|
||||
def 投喂数据(self, 时间戳: int, 开盘价: float, 最高价: float, 最低价: float, 收盘价: float, 成交量: float, 持仓量: float = 0):
|
||||
"""线程安全的外部数据推送接口,供 WebSocket 回调等外部代码调用。
|
||||
|
||||
:param 时间戳: Unix 时间戳(秒,int 类型)
|
||||
:param 开盘价: 开盘价
|
||||
:param 最高价: 最高价
|
||||
:param 最低价: 最低价
|
||||
:param 收盘价: 收盘价
|
||||
:param 成交量: 成交量
|
||||
:param 持仓量: 持仓量(默认为 0)
|
||||
"""
|
||||
self.数据队列.put((时间戳, 开盘价, 最高价, 最低价, 收盘价, 成交量, 持仓量))
|
||||
|
||||
def start(self):
|
||||
"""
|
||||
数据源开始工作时的初始化操作。
|
||||
这里可以不执行任何操作,或者启动数据接收线程等。
|
||||
"""
|
||||
print(f"[{datetime.now()}] 自定义数据源已启动...")
|
||||
self.正在运行 = True
|
||||
|
||||
def 运行回测():
|
||||
self.观察员.读取任意数据(self.魔法, **self.__魔法参数)
|
||||
self.正在运行 = False
|
||||
if self.观察员 is not None and self.魔法 is not None:
|
||||
if not hasattr(self.观察员, "读取任意数据"):
|
||||
print(f"[{datetime.now()}] 警告: 观察员没有 '读取任意数据' 方法,后台线程不会启动")
|
||||
return
|
||||
|
||||
回测线程 = threading.Thread(target=运行回测, daemon=True)
|
||||
回测线程.start()
|
||||
def 运行回测():
|
||||
try:
|
||||
self.观察员.读取任意数据(self.魔法, **self.__魔法参数)
|
||||
except Exception as e:
|
||||
print(f"[{datetime.now()}] 后台数据线程异常: {e}")
|
||||
finally:
|
||||
self.正在运行 = False
|
||||
|
||||
回测线程 = threading.Thread(target=运行回测, daemon=True)
|
||||
回测线程.start()
|
||||
|
||||
def stop(self):
|
||||
"""
|
||||
@@ -696,32 +717,25 @@ class 自定义实时数据源(bt.feed.DataBase):
|
||||
print(f"[{datetime.now()}] 自定义数据源已停止。")
|
||||
|
||||
def _load(self):
|
||||
"""
|
||||
(核心方法) Backtrader会循环调用此方法来获取数据。
|
||||
每次被调用时,都应该返回新的数据行(一个K线/一个数据点)。
|
||||
如果没有新数据,返回 False,Backtrader会等待下次调用。
|
||||
"""
|
||||
"""Backtrader 核心回调 — 阻塞等待数据,有数据时返回 True,数据源停止时返回 False。"""
|
||||
if not self.正在运行:
|
||||
return False
|
||||
# 当没有新数据且系统仍在运行时,进入等待
|
||||
# 从外部队列获取新数据
|
||||
|
||||
while True:
|
||||
try:
|
||||
data_point = self.数据队列.get(timeout=0.5)
|
||||
break
|
||||
except queue.Empty:
|
||||
if not self.已有数据:
|
||||
continue
|
||||
else:
|
||||
if self.正在运行:
|
||||
continue
|
||||
if not self.正在运行:
|
||||
return False
|
||||
# 解析数据元组
|
||||
dt, o, h, l, c, v, oi = data_point
|
||||
|
||||
# 将数据写入Backtrader的数据线 (lines)
|
||||
self.lines.datetime[0] = bt.date2num(dt)
|
||||
try:
|
||||
dt, o, h, l, c, v, oi = data_point
|
||||
except (ValueError, TypeError) as e:
|
||||
print(f"[{datetime.now()}] 自定义数据源: 数据格式错误,跳过: {e}")
|
||||
return True
|
||||
|
||||
self.lines.datetime[0] = bt.date2num(datetime.utcfromtimestamp(int(dt)))
|
||||
self.lines.open[0] = o
|
||||
self.lines.high[0] = h
|
||||
self.lines.low[0] = l
|
||||
@@ -729,7 +743,6 @@ class 自定义实时数据源(bt.feed.DataBase):
|
||||
self.lines.volume[0] = v
|
||||
self.lines.openinterest[0] = oi
|
||||
self.已有数据 = True
|
||||
# 返回 True 表示成功加载一行数据
|
||||
return True
|
||||
|
||||
|
||||
@@ -757,7 +770,7 @@ class 高级策略基类(bt.Strategy):
|
||||
|
||||
def 日志(self, 文本, 时间=None):
|
||||
时间 = 时间 or self.datas[0].datetime.datetime(0)
|
||||
print(f"{时间} {文本}")
|
||||
print(f"{时间} {self.p.观察员.__class__.__name__}: {文本}")
|
||||
|
||||
def 计算目标数量(self, 价格):
|
||||
"""根据资金类型和仓位比例计算目标数量"""
|
||||
@@ -946,6 +959,7 @@ class 回测(高级策略基类):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.已处理信号 = set()
|
||||
print(f"{self.p.观察员.__class__.__name__}: 加载完成...")
|
||||
|
||||
def 获取开仓限价(self, 是否做多):
|
||||
# 根据缠论分型计算限价,若无则返回 None 使用基类默认逻辑
|
||||
@@ -957,6 +971,7 @@ class 回测(高级策略基类):
|
||||
return None
|
||||
|
||||
def next(self):
|
||||
# self.日志(f"{self.p.观察员.__class__.__name__} called next ")
|
||||
# 1. 更新移动止损(基类方法)
|
||||
if self.position:
|
||||
self.更新止损订单(self.position.size > 0, self.data.close[0])
|
||||
@@ -986,15 +1001,22 @@ class 回测(高级策略基类):
|
||||
def 检查买信号(self):
|
||||
if self.p.观察员.笔序列:
|
||||
k线 = self.p.观察员.缠论K线序列[-1]
|
||||
self.日志(f"检查买信号 当前笔 {self.p.观察员.笔序列[-1]}")
|
||||
if k线.买卖点信息:
|
||||
print(f"回测-首 {self.p.观察员.__class__.__name__}", k线.买卖点信息)
|
||||
首 = True if k线.买卖点信息 and "买" in next(iter(k线.买卖点信息)) else False
|
||||
if 首:
|
||||
print(k线)
|
||||
原始差值 = self.p.观察员.当前K线.序号 - k线.标的K线.序号
|
||||
差值 = self.p.观察员.当前缠K.序号 - k线.序号
|
||||
self.日志(f"首_买入信号差值: {原始差值}, {差值}, 观察员.当前K线 时间戳: {self.p.观察员.当前K线.时间戳}")
|
||||
k线 = self.p.观察员.缠论K线序列[-2]
|
||||
if k线.买卖点信息:
|
||||
print(f"回测-尾 {self.p.观察员.__class__.__name__}", k线.买卖点信息)
|
||||
|
||||
尾 = True if self.p.观察员.缠论K线序列[-2].买卖点信息 and "买" in next(iter(self.p.观察员.缠论K线序列[-2].买卖点信息)) else False
|
||||
if 尾:
|
||||
print(k线)
|
||||
原始差值 = self.p.观察员.当前K线.序号 - k线.标的K线.序号
|
||||
差值 = self.p.观察员.当前缠K.序号 - k线.序号
|
||||
self.日志(f"尾_买入信号差值: {原始差值}, {差值}, 观察员.当前K线 时间戳: {self.p.观察员.当前K线.时间戳}")
|
||||
@@ -1002,16 +1024,23 @@ class 回测(高级策略基类):
|
||||
|
||||
def 检查卖信号(self):
|
||||
if self.p.观察员.笔序列:
|
||||
self.日志(f"检查卖信号 当前笔 {self.p.观察员.笔序列[-1]}")
|
||||
k线 = self.p.观察员.缠论K线序列[-1]
|
||||
if k线.买卖点信息:
|
||||
print(f"回测-首 {self.p.观察员.__class__.__name__}", k线.买卖点信息)
|
||||
首 = True if k线.买卖点信息 and "卖" in next(iter(k线.买卖点信息)) else False
|
||||
if 首:
|
||||
print(k线)
|
||||
原始差值 = self.p.观察员.当前K线.序号 - k线.标的K线.序号
|
||||
差值 = self.p.观察员.当前缠K.序号 - k线.序号
|
||||
self.日志(f"首_买入信号差值: {原始差值}, {差值}, 观察员.当前K线 时间戳: {self.p.观察员.当前K线.时间戳}")
|
||||
k线 = self.p.观察员.缠论K线序列[-2]
|
||||
if k线.买卖点信息:
|
||||
print(f"回测-尾 {self.p.观察员.__class__.__name__}", k线.买卖点信息)
|
||||
|
||||
尾 = True if self.p.观察员.缠论K线序列[-2].买卖点信息 and "卖" in next(iter(self.p.观察员.缠论K线序列[-2].买卖点信息)) else False
|
||||
if 尾:
|
||||
print(k线)
|
||||
原始差值 = self.p.观察员.当前K线.序号 - k线.标的K线.序号
|
||||
差值 = self.p.观察员.当前缠K.序号 - k线.序号
|
||||
self.日志(f"尾_买入信号差值: {原始差值}, {差值}, 观察员.当前K线 时间戳: {self.p.观察员.当前K线.时间戳}")
|
||||
@@ -1019,7 +1048,7 @@ class 回测(高级策略基类):
|
||||
|
||||
def log(self, 文本, dt=None):
|
||||
dt = dt or bt.num2date(self.data.datetime[0])
|
||||
print(f"[{dt.strftime('%Y-%m-%d %H:%M')}] {self.p.符号} | {文本}")
|
||||
print(f"[{dt.strftime('%Y-%m-%d %H:%M')}] {self.p.观察员.__class__.__name__}: {self.p.符号} | {文本}")
|
||||
|
||||
|
||||
# ==================== 回测运行入口 ====================
|
||||
|
||||
Reference in New Issue
Block a user