Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c3e9ae8d77 | |||
| b7c4e60420 | |||
| 15dc44e8e0 | |||
| bd4fceab02 | |||
| af3ebf13c8 | |||
| 22109d8b30 | |||
| c2c09fc8ba | |||
| 0eb52cc06a | |||
| 1e6025a968 |
@@ -16,9 +16,87 @@ env:
|
||||
|
||||
jobs:
|
||||
# ============================================================
|
||||
# Linux x86_64 (manylinux)
|
||||
# 1. 发布 chanlun 核心库至 crates.io
|
||||
# ============================================================
|
||||
publish-crates:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
version: ${{ steps.version.outputs.version }}
|
||||
exists: ${{ steps.check.outputs.exists }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: 安装 Rust 工具链
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
components: rustfmt, clippy
|
||||
|
||||
- name: 缓存依赖
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
target
|
||||
key: ${{ runner.os }}-cargo-${{ hashFiles('chanlun/Cargo.lock') }}
|
||||
|
||||
- name: 提取版本号
|
||||
id: version
|
||||
working-directory: chanlun
|
||||
run: |
|
||||
VER=$(cargo metadata --format-version 1 --no-deps 2>/dev/null \
|
||||
| jq -r '.packages[] | select(.name == "chanlun") | .version')
|
||||
echo "version=$VER" >> $GITHUB_OUTPUT
|
||||
echo "当前版本: $VER"
|
||||
|
||||
- name: 检查版本是否已存在
|
||||
id: check
|
||||
run: |
|
||||
VER="${{ steps.version.outputs.version }}"
|
||||
EXISTS=$(curl -sS "https://crates.io/api/v1/crates/chanlun" \
|
||||
| jq -r --arg v "$VER" '.versions[]?.num // empty | select(. == $v)')
|
||||
if [ -n "$EXISTS" ]; then
|
||||
echo "版本 $VER 已存在于 crates.io,跳过发布"
|
||||
echo "exists=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "版本 $VER 未发布,继续"
|
||||
echo "exists=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: 格式检查
|
||||
if: steps.check.outputs.exists == 'false'
|
||||
working-directory: chanlun
|
||||
run: cargo fmt --check
|
||||
|
||||
- name: Lint 检查
|
||||
if: steps.check.outputs.exists == 'false'
|
||||
working-directory: chanlun
|
||||
run: cargo clippy
|
||||
|
||||
- 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: 下载所有产物
|
||||
|
||||
@@ -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` 二进制文件格式(大端字节序)
|
||||
|
||||
## 许可
|
||||
|
||||
|
||||
@@ -5078,6 +5078,17 @@ class 观察者:
|
||||
self.扩展线段序列_扩展线段: List[虚线] = []
|
||||
self.扩展中枢序列_扩展线段: List[中枢] = []
|
||||
|
||||
def 投喂原始数据(self, 时间戳: datetime, 开: float, 高: float, 低: float, 收: float, 量: float):
|
||||
"""便捷入口,直接从 OHLCV 创建 K线 并投喂
|
||||
:param 时间戳: 时间戳
|
||||
:param 开: 开盘价
|
||||
:param 高: 最高价
|
||||
:param 低: 最低价
|
||||
:param 收: 收盘价
|
||||
:param 量: 成交量
|
||||
"""
|
||||
self.增加原始K线(K线.创建普K(self.标识, 时间戳, 开, 高, 低, 收, 量, 0, self.周期))
|
||||
|
||||
@final
|
||||
def 增加原始K线(self, 普K: K线):
|
||||
"""核心入口 — 投喂一根原始K线,增量更新所有层级
|
||||
@@ -5224,33 +5235,32 @@ class 观察者:
|
||||
self.配置.分析线段中枢 and 中枢.分析(self.线段_线段序列, self.线段_中枢序列)
|
||||
|
||||
def 加载本地数据(self, 文件路径: str):
|
||||
"""重置基础序列后加载数据文件
|
||||
:param 文件路径: 数据文件路径 格式如: btcusd-300-1631772074-1632222374.nb
|
||||
"""
|
||||
self.重置基础序列()
|
||||
with open(文件路径, "rb") as f:
|
||||
buffer = f.read()
|
||||
size = struct.calcsize(">6d")
|
||||
for i in range(len(buffer) // size):
|
||||
k线 = K线.读取大端字节数组(buffer[i * size : i * size + size], self.周期, self.标识)
|
||||
self.增加原始K线(k线)
|
||||
时间戳, 开盘价, 最高价, 最低价, 收盘价, 成交量 = struct.unpack(">6d", buffer[i * size : i * size + size])
|
||||
self.投喂原始数据(转化为时间戳(int(时间戳)), 开盘价, 最高价, 最低价, 收盘价, 成交量)
|
||||
|
||||
@classmethod
|
||||
def 读取数据文件(cls, 文件路径: str, 配置=缠论配置()) -> Self:
|
||||
"""
|
||||
def 读取数据文件(cls, 观察员: "观察者", 文件路径: str, 配置=缠论配置()) -> Self:
|
||||
"""加载数据文件
|
||||
:param 观察员: 观察者
|
||||
:param 文件路径: 数据文件路径 格式如: btcusd-300-1631772074-1632222374.nb
|
||||
:param 配置: 缠论配置
|
||||
:return: 观察者实例
|
||||
"""
|
||||
name = Path(文件路径).name.split(".")[0]
|
||||
符号, 周期, 起始时间戳, 结束时间戳 = name.split("-")
|
||||
实例 = cls(符号=符号, 周期=int(周期), 配置=配置)
|
||||
|
||||
with open(文件路径, "rb") as f:
|
||||
buffer = f.read()
|
||||
size = struct.calcsize(">6d")
|
||||
for i in range(len(buffer) // size):
|
||||
k线 = K线.读取大端字节数组(buffer[i * size : i * size + size], int(周期), 符号)
|
||||
实例.增加原始K线(k线)
|
||||
|
||||
return 实例
|
||||
观察员.符号 = 符号
|
||||
观察员.周期 = int(周期)
|
||||
观察员.配置 = 配置
|
||||
观察员.加载本地数据(文件路径)
|
||||
return 观察员
|
||||
|
||||
|
||||
class K线合成器:
|
||||
@@ -5503,16 +5513,16 @@ class 立体分析器:
|
||||
print(f"多级别数据拆分保存完成,目录:{保存路径.resolve()}")
|
||||
|
||||
|
||||
def 测试_读取数据(配置: 缠论配置):
|
||||
def 测试_读取数据(观察员: 观察者, 配置: 缠论配置) -> Callable[[], "观察者"]:
|
||||
"""测试_读取数据
|
||||
|
||||
:param 观察员: 观察者
|
||||
:param 配置: 缠论配置
|
||||
:return: 测试函数
|
||||
"""
|
||||
|
||||
def 魔法():
|
||||
启动时间 = datetime.now()
|
||||
观察员 = 观察者.读取数据文件(配置.加载文件路径, 配置)
|
||||
观察者.读取数据文件(观察员, 配置.加载文件路径, 配置)
|
||||
消耗用时 = datetime.now() - 启动时间
|
||||
print("测试_读取数据 耗时", 消耗用时, "普K数量", len(观察员.普通K线序列))
|
||||
return 观察员
|
||||
@@ -5552,5 +5562,6 @@ def 测试_周期合成(配置: 缠论配置, 配置组: Dict[int, 缠论配置]
|
||||
if __name__ == "__main__":
|
||||
当前配置 = 缠论配置.不推送()
|
||||
当前配置.加载文件路径 = str(Path(__file__).parent / "btcusd-300-1761327300-1776327900.nb")
|
||||
测试_读取数据(当前配置)().测试_保存数据()
|
||||
测试_周期合成(当前配置)().测试_保存数据()
|
||||
观察员 = 观察者("", 0, 当前配置)
|
||||
测试_读取数据(观察员, 当前配置)() # .测试_保存数据()
|
||||
# 测试_周期合成(当前配置)().测试_保存数据()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "chanlun-py"
|
||||
version = "26.5.94"
|
||||
version = "26.5.103"
|
||||
edition = "2021"
|
||||
description = "缠论技术分析库 — Rust 高性能 Python 绑定"
|
||||
authors = ["YuYuKunKun"]
|
||||
@@ -12,7 +12,7 @@ crate-type = ["cdylib"]
|
||||
name = "chanlun"
|
||||
|
||||
[dependencies]
|
||||
chanlun = "26.5.4" # { path = "../chanlun" }
|
||||
pyo3 = { version = "0.28", features = ["extension-module", "experimental-inspect"] }
|
||||
chanlun = "26.5.6" # { path = "../chanlun" }
|
||||
pyo3 = { version = "0.28", features = ["experimental-inspect"] }
|
||||
serde_json = "1"
|
||||
chrono = "0.4"
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
../README.md
|
||||
@@ -0,0 +1,77 @@
|
||||
# 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
|
||||
@@ -27,6 +27,8 @@ __all__ = [
|
||||
"转化为时间戳",
|
||||
"转化为时间戳_数字",
|
||||
"随机指标",
|
||||
"chan",
|
||||
]
|
||||
|
||||
from ._chanlun import *
|
||||
from . import chan
|
||||
|
||||
+28
-18
@@ -5078,6 +5078,17 @@ class 观察者:
|
||||
self.扩展线段序列_扩展线段: List[虚线] = []
|
||||
self.扩展中枢序列_扩展线段: List[中枢] = []
|
||||
|
||||
def 投喂原始数据(self, 时间戳: datetime, 开: float, 高: float, 低: float, 收: float, 量: float):
|
||||
"""便捷入口,直接从 OHLCV 创建 K线 并投喂
|
||||
:param 时间戳: 时间戳
|
||||
:param 开: 开盘价
|
||||
:param 高: 最高价
|
||||
:param 低: 最低价
|
||||
:param 收: 收盘价
|
||||
:param 量: 成交量
|
||||
"""
|
||||
self.增加原始K线(K线.创建普K(self.标识, 时间戳, 开, 高, 低, 收, 量, 0, self.周期))
|
||||
|
||||
@final
|
||||
def 增加原始K线(self, 普K: K线):
|
||||
"""核心入口 — 投喂一根原始K线,增量更新所有层级
|
||||
@@ -5224,33 +5235,32 @@ class 观察者:
|
||||
self.配置.分析线段中枢 and 中枢.分析(self.线段_线段序列, self.线段_中枢序列)
|
||||
|
||||
def 加载本地数据(self, 文件路径: str):
|
||||
"""重置基础序列后加载数据文件
|
||||
:param 文件路径: 数据文件路径 格式如: btcusd-300-1631772074-1632222374.nb
|
||||
"""
|
||||
self.重置基础序列()
|
||||
with open(文件路径, "rb") as f:
|
||||
buffer = f.read()
|
||||
size = struct.calcsize(">6d")
|
||||
for i in range(len(buffer) // size):
|
||||
k线 = K线.读取大端字节数组(buffer[i * size : i * size + size], self.周期, self.标识)
|
||||
self.增加原始K线(k线)
|
||||
时间戳, 开盘价, 最高价, 最低价, 收盘价, 成交量 = struct.unpack(">6d", buffer[i * size : i * size + size])
|
||||
self.投喂原始数据(转化为时间戳(int(时间戳)), 开盘价, 最高价, 最低价, 收盘价, 成交量)
|
||||
|
||||
@classmethod
|
||||
def 读取数据文件(cls, 文件路径: str, 配置=缠论配置()) -> Self:
|
||||
"""
|
||||
def 读取数据文件(cls, 观察员: "观察者", 文件路径: str, 配置=缠论配置()) -> Self:
|
||||
"""加载数据文件
|
||||
:param 观察员: 观察者
|
||||
:param 文件路径: 数据文件路径 格式如: btcusd-300-1631772074-1632222374.nb
|
||||
:param 配置: 缠论配置
|
||||
:return: 观察者实例
|
||||
"""
|
||||
name = Path(文件路径).name.split(".")[0]
|
||||
符号, 周期, 起始时间戳, 结束时间戳 = name.split("-")
|
||||
实例 = cls(符号=符号, 周期=int(周期), 配置=配置)
|
||||
|
||||
with open(文件路径, "rb") as f:
|
||||
buffer = f.read()
|
||||
size = struct.calcsize(">6d")
|
||||
for i in range(len(buffer) // size):
|
||||
k线 = K线.读取大端字节数组(buffer[i * size : i * size + size], int(周期), 符号)
|
||||
实例.增加原始K线(k线)
|
||||
|
||||
return 实例
|
||||
观察员.符号 = 符号
|
||||
观察员.周期 = int(周期)
|
||||
观察员.配置 = 配置
|
||||
观察员.加载本地数据(文件路径)
|
||||
return 观察员
|
||||
|
||||
|
||||
class K线合成器:
|
||||
@@ -5503,16 +5513,16 @@ class 立体分析器:
|
||||
print(f"多级别数据拆分保存完成,目录:{保存路径.resolve()}")
|
||||
|
||||
|
||||
def 测试_读取数据(配置: 缠论配置):
|
||||
def 测试_读取数据(观察员: 观察者, 配置: 缠论配置) -> Callable[()]:
|
||||
"""测试_读取数据
|
||||
|
||||
:param 观察员: 观察者
|
||||
:param 配置: 缠论配置
|
||||
:return: 测试函数
|
||||
"""
|
||||
|
||||
def 魔法():
|
||||
启动时间 = datetime.now()
|
||||
观察员 = 观察者.读取数据文件(配置.加载文件路径, 配置)
|
||||
观察者.读取数据文件(观察员, 配置.加载文件路径, 配置)
|
||||
消耗用时 = datetime.now() - 启动时间
|
||||
print("测试_读取数据 耗时", 消耗用时, "普K数量", len(观察员.普通K线序列))
|
||||
return 观察员
|
||||
@@ -5553,5 +5563,5 @@ if __name__ == "__main__":
|
||||
当前配置 = 缠论配置.不推送()
|
||||
当前配置.加载文件路径 = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "tests", "btcusd-300-1761327300-1776327900.nb")
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
测试_读取数据(当前配置)().测试_保存数据(tmpdir)
|
||||
测试_读取数据(观察者("", 0, 当前配置), 当前配置)().测试_保存数据(tmpdir)
|
||||
测试_周期合成(当前配置)().测试_保存数据(tmpdir)
|
||||
|
||||
@@ -4,7 +4,7 @@ build-backend = "maturin"
|
||||
|
||||
[project]
|
||||
name = "chanlun"
|
||||
version = "2605.94"
|
||||
version = "2605.103"
|
||||
description = "缠论技术分析库 — Rust 高性能实现"
|
||||
readme = { file = "README.md", content-type = "text/markdown" }
|
||||
license = { file = "LICENSE", content-type = "text/plain" }
|
||||
|
||||
@@ -31,26 +31,28 @@ use std::sync::atomic::Ordering;
|
||||
use std::sync::Arc;
|
||||
use std::sync::RwLock;
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -362,13 +364,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 +380,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 +398,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]
|
||||
@@ -501,7 +503,7 @@ impl 笔Py {
|
||||
) -> bool {
|
||||
let obs = 观察员.borrow();
|
||||
let obs_ref = obs.obs();
|
||||
chanlun::algorithm::bi::笔::自检(&筆.borrow().inner, &*obs_ref)
|
||||
chanlun::algorithm::bi::笔::自检(&筆.borrow().inner, &obs_ref)
|
||||
}
|
||||
|
||||
#[classmethod]
|
||||
@@ -510,12 +512,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::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()
|
||||
}
|
||||
|
||||
@@ -529,7 +532,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()
|
||||
@@ -569,8 +572,8 @@ impl 线段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);
|
||||
let ref_mut = 段.borrow_mut();
|
||||
chanlun::algorithm::segment::线段::添加虚线(&ref_mut.inner, bi_rc);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -582,9 +585,9 @@ impl 线段Py {
|
||||
武: &Bound<'_, 分型Py>,
|
||||
行号: u32,
|
||||
) -> PyResult<()> {
|
||||
let mut ref_mut = 段.borrow_mut();
|
||||
let ref_mut = 段.borrow_mut();
|
||||
chanlun::algorithm::segment::线段::武斗(
|
||||
&mut ref_mut.inner,
|
||||
&ref_mut.inner,
|
||||
&Arc::clone(&武.borrow().inner),
|
||||
行号,
|
||||
);
|
||||
@@ -594,8 +597,8 @@ impl 线段Py {
|
||||
#[classmethod]
|
||||
/// 武终
|
||||
fn 武终(_cls: &Bound<'_, PyType>, 段: &Bound<'_, 虚线Py>, 行号: u32) -> PyResult<()> {
|
||||
let mut ref_mut = 段.borrow_mut();
|
||||
chanlun::algorithm::segment::线段::武终(&mut ref_mut.inner, 行号);
|
||||
let ref_mut = 段.borrow_mut();
|
||||
chanlun::algorithm::segment::线段::武终(&ref_mut.inner, 行号);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -607,12 +610,12 @@ impl 线段Py {
|
||||
序列: Vec<Py<虚线Py>>,
|
||||
py: Python<'_>,
|
||||
) -> PyResult<()> {
|
||||
let mut ref_mut = 段.borrow_mut();
|
||||
let 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);
|
||||
chanlun::algorithm::segment::线段::验证序列(&ref_mut.inner, &rc_list);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -624,12 +627,12 @@ impl 线段Py {
|
||||
序列: Vec<Py<虚线Py>>,
|
||||
py: Python<'_>,
|
||||
) -> PyResult<()> {
|
||||
let mut ref_mut = 段.borrow_mut();
|
||||
let 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);
|
||||
chanlun::algorithm::segment::线段::序列重置(&ref_mut.inner, &rc_list);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -692,7 +695,7 @@ impl 线段Py {
|
||||
let seq: Vec<Option<Arc<chanlun::structure::segment_feat::线段特征>>> = if 序列.is_none()
|
||||
{
|
||||
vec![]
|
||||
} else if let Ok(list) = 序列.downcast::<pyo3::types::PyList>() {
|
||||
} else if let Ok(list) = 序列.cast::<pyo3::types::PyList>() {
|
||||
let mut result = Vec::with_capacity(list.len());
|
||||
for item in list.iter() {
|
||||
if item.is_none() {
|
||||
@@ -708,8 +711,8 @@ impl 线段Py {
|
||||
"序列 必须是 list 或 None",
|
||||
));
|
||||
};
|
||||
let mut ref_mut = 段.borrow_mut();
|
||||
chanlun::algorithm::segment::线段::设置特征序列(&mut ref_mut.inner, seq, 行号);
|
||||
let ref_mut = 段.borrow_mut();
|
||||
chanlun::algorithm::segment::线段::设置特征序列(&ref_mut.inner, seq, 行号);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -721,22 +724,26 @@ impl 线段Py {
|
||||
配置: &Bound<'_, 缠论配置Py>,
|
||||
py: Python<'_>,
|
||||
) -> PyResult<()> {
|
||||
let mut ref_mut = 段.borrow_mut();
|
||||
let ref_mut = 段.borrow_mut();
|
||||
let config = 配置.borrow().to_rust_config(py)?;
|
||||
chanlun::algorithm::segment::线段::刷新特征序列(&mut ref_mut.inner, &config);
|
||||
chanlun::algorithm::segment::线段::刷新特征序列(&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>,
|
||||
@@ -761,19 +768,10 @@ impl 线段Py {
|
||||
} 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()?,
|
||||
))
|
||||
Ok((wrap(a), wrap(b), wrap(c), d.map(|x| dashed_to_py(py, x))))
|
||||
}
|
||||
|
||||
#[classmethod]
|
||||
@@ -784,9 +782,9 @@ impl 线段Py {
|
||||
配置: &Bound<'_, 缠论配置Py>,
|
||||
py: Python<'_>,
|
||||
) -> PyResult<()> {
|
||||
let mut ref_mut = 段.borrow_mut();
|
||||
let ref_mut = 段.borrow_mut();
|
||||
let config = 配置.borrow().to_rust_config(py)?;
|
||||
chanlun::algorithm::segment::线段::刷新(&mut ref_mut.inner, &config);
|
||||
chanlun::algorithm::segment::线段::刷新(&ref_mut.inner, &config);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -798,16 +796,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())
|
||||
};
|
||||
@@ -818,7 +814,7 @@ impl 线段Py {
|
||||
/// 内部方法:向线段序列添加新线段
|
||||
fn _添加线段(
|
||||
_cls: &Bound<'_, PyType>,
|
||||
线段序列: &Bound<'_, PyAny>,
|
||||
_线段序列: &Bound<'_, PyAny>,
|
||||
待添加线段: &Bound<'_, 虚线Py>,
|
||||
配置: &Bound<'_, 缠论配置Py>,
|
||||
行号: String,
|
||||
@@ -839,12 +835,12 @@ impl 线段Py {
|
||||
/// 内部方法:从线段序列弹出最后一个线段
|
||||
fn _弹出线段(
|
||||
_cls: &Bound<'_, PyType>,
|
||||
线段序列: &Bound<'_, PyAny>,
|
||||
_线段序列: &Bound<'_, PyAny>,
|
||||
待弹出线段: &Bound<'_, 虚线Py>,
|
||||
配置: &Bound<'_, 缠论配置Py>,
|
||||
行号: String,
|
||||
py: Python<'_>,
|
||||
) -> PyResult<Option<虚线Py>> {
|
||||
) -> PyResult<Option<Py<虚线Py>>> {
|
||||
let config = 配置.borrow().to_rust_config(py)?;
|
||||
let mut seg_seq: Vec<Arc<chanlun::structure::dash_line::虚线>> = vec![];
|
||||
let result = chanlun::algorithm::segment::线段::_弹出线段(
|
||||
@@ -853,7 +849,7 @@ impl 线段Py {
|
||||
&config,
|
||||
行号,
|
||||
);
|
||||
Ok(result.map(|inner| 虚线Py { inner }))
|
||||
Ok(result.map(|inner| dashed_to_py(py, inner)))
|
||||
}
|
||||
|
||||
#[classmethod]
|
||||
@@ -1007,7 +1003,7 @@ impl 线段Py {
|
||||
待弹出线段: &Bound<'_, 虚线Py>,
|
||||
行号: u32,
|
||||
py: Python<'_>,
|
||||
) -> PyResult<Option<虚线Py>> {
|
||||
) -> PyResult<Option<Py<虚线Py>>> {
|
||||
let mut seg_seq: Vec<Arc<chanlun::structure::dash_line::虚线>> = 线段序列
|
||||
.iter()
|
||||
.map(|d| Arc::clone(&d.bind(py).borrow().inner))
|
||||
@@ -1017,7 +1013,7 @@ impl 线段Py {
|
||||
&Arc::clone(&待弹出线段.borrow().inner),
|
||||
行号,
|
||||
);
|
||||
Ok(result.map(|inner| 虚线Py { inner }))
|
||||
Ok(result.map(|inner| dashed_to_py(py, inner)))
|
||||
}
|
||||
|
||||
#[classmethod]
|
||||
@@ -1053,7 +1049,7 @@ impl 线段Py {
|
||||
let obs_ref = obs.obs();
|
||||
chanlun::algorithm::segment::线段::判断线段内部是否背驰(
|
||||
&当前段.borrow().inner,
|
||||
&*obs_ref,
|
||||
&obs_ref,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1063,12 +1059,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()
|
||||
}
|
||||
|
||||
@@ -1082,7 +1079,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()
|
||||
@@ -1214,10 +1211,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]
|
||||
@@ -1266,7 +1261,7 @@ impl 中枢Py {
|
||||
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())
|
||||
}
|
||||
@@ -1328,7 +1323,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.高高())?;
|
||||
@@ -1383,16 +1378,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),
|
||||
级别,
|
||||
标识,
|
||||
)),
|
||||
}
|
||||
) -> Py<中枢Py> {
|
||||
let inner = Arc::new(chanlun::algorithm::hub::中枢::创建(
|
||||
Arc::clone(&左.borrow().inner),
|
||||
Arc::clone(&中.borrow().inner),
|
||||
Arc::clone(&右.borrow().inner),
|
||||
级别,
|
||||
标识,
|
||||
));
|
||||
hub_to_py(_cls.py(), inner)
|
||||
}
|
||||
|
||||
#[classmethod]
|
||||
@@ -1403,7 +1397,7 @@ impl 中枢Py {
|
||||
起始方向: &Bound<'_, 相对方向Py>,
|
||||
标识: &str,
|
||||
py: Python<'_>,
|
||||
) -> Option<Self> {
|
||||
) -> Option<Py<中枢Py>> {
|
||||
let rc_list: Vec<Arc<chanlun::structure::dash_line::虚线>> = 虚线序列
|
||||
.iter()
|
||||
.map(|d| Arc::clone(&d.bind(py).borrow().inner))
|
||||
@@ -1413,7 +1407,7 @@ impl 中枢Py {
|
||||
起始方向.borrow().inner,
|
||||
标识,
|
||||
)
|
||||
.map(|inner| Self { inner })
|
||||
.map(|inner| hub_to_py(py, inner))
|
||||
}
|
||||
|
||||
#[classmethod]
|
||||
@@ -1423,8 +1417,9 @@ impl 中枢Py {
|
||||
中枢序列: &Bound<'_, PyAny>,
|
||||
待添加中枢: &Bound<'_, Self>,
|
||||
) -> PyResult<()> {
|
||||
let py = 中枢序列.py();
|
||||
let inner = Arc::clone(&待添加中枢.borrow().inner);
|
||||
let wrapper = Py::new(中枢序列.py(), Self { inner })?;
|
||||
let wrapper = hub_to_py(py, inner);
|
||||
中枢序列.call_method1("append", (wrapper,))?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -1434,7 +1429,7 @@ impl 中枢Py {
|
||||
fn 从中枢序列尾部弹出(
|
||||
_cls: &Bound<'_, PyType>,
|
||||
中枢序列: &Bound<'_, PyAny>,
|
||||
待弹出中枢: &Bound<'_, Self>,
|
||||
_待弹出中枢: &Bound<'_, Self>,
|
||||
) -> PyResult<Option<Self>> {
|
||||
let result = 中枢序列.call_method1("pop", ())?;
|
||||
if result.is_none() {
|
||||
|
||||
@@ -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::types_py::买卖点类型Py;
|
||||
|
||||
// ========== 基础买卖点 ==========
|
||||
@@ -100,10 +98,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]
|
||||
@@ -560,30 +556,49 @@ impl 观察者Py {
|
||||
self.obs_mut().增加原始K线((*普K.borrow().inner).clone());
|
||||
}
|
||||
|
||||
/// 投喂原始数据 — 便捷入口,直接从 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(())
|
||||
}
|
||||
@@ -600,10 +615,14 @@ impl 观察者Py {
|
||||
}
|
||||
|
||||
#[classmethod]
|
||||
#[pyo3(signature = (文件路径, 配置 = None))]
|
||||
#[pyo3(signature = (观察员, 文件路径, 配置 = None))]
|
||||
/// :param 观察员: 观察者实例
|
||||
/// :param 文件路径: 数据文件路径 格式如: btcusd-300-1631772074-1632222374.nb
|
||||
/// :param 配置: 缠论配置
|
||||
/// :return: 观察者实例
|
||||
fn 读取数据文件(
|
||||
cls: &Bound<'_, PyType>,
|
||||
_cls: &Bound<'_, PyType>,
|
||||
观察员: &Bound<'_, Self>,
|
||||
文件路径: &str,
|
||||
配置: Option<&Bound<'_, 缠论配置Py>>,
|
||||
py: Python<'_>,
|
||||
@@ -631,31 +650,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 ----
|
||||
@@ -849,11 +856,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())
|
||||
}
|
||||
|
||||
@@ -866,7 +873,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.标识,
|
||||
@@ -882,22 +890,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]
|
||||
@@ -946,7 +947,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()?;
|
||||
|
||||
@@ -239,7 +239,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 +252,7 @@ impl 缠论配置Py {
|
||||
}
|
||||
|
||||
/// 比较当前配置与另一个配置的差异
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn 对比(
|
||||
&self,
|
||||
py: Python<'_>,
|
||||
@@ -290,7 +291,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)
|
||||
}
|
||||
|
||||
|
||||
+58
-48
@@ -139,10 +139,8 @@ impl K线Py {
|
||||
|
||||
#[getter]
|
||||
/// :return: 相对方向.向上(开盘<收盘)或 相对方向.向下(开盘>收盘)
|
||||
fn 方向(&self) -> 相对方向Py {
|
||||
相对方向Py {
|
||||
inner: self.inner.方向(),
|
||||
}
|
||||
fn 方向(&self, py: Python<'_>) -> Py<相对方向Py> {
|
||||
crate::types_py::获取相对方向单例(py, self.inner.方向())
|
||||
}
|
||||
|
||||
#[getter]
|
||||
@@ -182,7 +180,7 @@ impl K线Py {
|
||||
dict.set_item("开盘价", self.开盘价())?;
|
||||
dict.set_item("收盘价", self.收盘价())?;
|
||||
dict.set_item("成交量", self.成交量())?;
|
||||
dict.set_item("方向", self.方向())?;
|
||||
dict.set_item("方向", self.方向(py))?;
|
||||
if let Some(v) = self.macd() {
|
||||
dict.set_item("macd", v)?;
|
||||
}
|
||||
@@ -349,26 +347,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 +374,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 +393,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 +413,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,10 +445,8 @@ impl 缠论K线Py {
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn 方向(&self) -> 相对方向Py {
|
||||
相对方向Py {
|
||||
inner: *self.inner.方向.read().unwrap(),
|
||||
}
|
||||
fn 方向(&self, py: Python<'_>) -> Py<相对方向Py> {
|
||||
crate::types_py::获取相对方向单例(py, *self.inner.方向.read().unwrap())
|
||||
}
|
||||
|
||||
#[getter]
|
||||
@@ -496,7 +496,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.分型特征值())?;
|
||||
@@ -534,16 +534,24 @@ 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() {
|
||||
// 复制买卖点信息到镜像
|
||||
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 {
|
||||
if let Ok(new_set) = pyo3::types::PySet::empty(py) {
|
||||
for item in src_set.bind(py).iter() {
|
||||
for item in cached_src.bind(py).iter() {
|
||||
let _ = new_set.add(item);
|
||||
}
|
||||
mirror.bsp_set = std::sync::RwLock::new(Some(new_set.into()));
|
||||
let py_set: Py<pyo3::types::PySet> = new_set.into();
|
||||
BSP_CACHE.write().unwrap().insert(dst_key, py_set);
|
||||
}
|
||||
}
|
||||
mirror
|
||||
@@ -569,18 +577,20 @@ 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 并存入全局缓存
|
||||
let set = pyo3::types::PySet::empty(py)?;
|
||||
BSP_CACHE.write().unwrap().insert(key, set.into());
|
||||
// 重新读取并返回(无法从 insert 获取 Py 引用,需要重新读)
|
||||
Ok(BSP_CACHE
|
||||
.read()
|
||||
.unwrap()
|
||||
.as_ref()
|
||||
.get(&key)
|
||||
.unwrap()
|
||||
.clone_ref(py)
|
||||
.into_any())
|
||||
@@ -641,13 +651,13 @@ impl 缠论K线Py {
|
||||
配置: &Bound<'_, 缠论配置Py>,
|
||||
py: Python<'_>,
|
||||
) -> PyResult<(Option<Py<Self>>, Option<String>)> {
|
||||
let mut ck_inner = (*当前缠K.borrow().inner).clone();
|
||||
let 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,
|
||||
&ck_inner,
|
||||
&当前普K.borrow().inner,
|
||||
&config,
|
||||
);
|
||||
|
||||
+32
-29
@@ -22,6 +22,8 @@
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#![allow(non_snake_case, clippy::too_many_arguments)]
|
||||
|
||||
use pyo3::prelude::*;
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
@@ -71,39 +73,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 仍失败");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,37 +29,45 @@ use std::sync::atomic::Ordering;
|
||||
use std::sync::Arc;
|
||||
use std::sync::RwLock;
|
||||
|
||||
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::{bar_to_py, 缠论K线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()));
|
||||
static FEATFRAC_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 +76,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,18 +101,23 @@ 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
|
||||
}
|
||||
|
||||
@@ -108,18 +126,23 @@ pub(crate) fn featfrac_to_py(
|
||||
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)))
|
||||
if let Some(cached) = FEATFRAC_IDENTITY
|
||||
.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);
|
||||
});
|
||||
FEATFRAC_IDENTITY
|
||||
.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));
|
||||
});
|
||||
FEATFRAC_IDENTITY
|
||||
.write()
|
||||
.unwrap()
|
||||
.insert(key, obj.clone_ref(py));
|
||||
obj
|
||||
}
|
||||
use crate::types_py::{分型结构Py, 相对方向Py, 缺口Py};
|
||||
@@ -233,12 +256,15 @@ impl 分型Py {
|
||||
|
||||
#[getter]
|
||||
/// 左、中、右三对相对方向关系
|
||||
fn 关系组(&self) -> Option<(相对方向Py, 相对方向Py, 相对方向Py)> {
|
||||
fn 关系组(
|
||||
&self,
|
||||
py: Python<'_>,
|
||||
) -> Option<(Py<相对方向Py>, Py<相对方向Py>, Py<相对方向Py>)> {
|
||||
self.inner.关系组().map(|(a, b, c)| {
|
||||
(
|
||||
相对方向Py { inner: a },
|
||||
相对方向Py { inner: b },
|
||||
相对方向Py { inner: c },
|
||||
crate::types_py::获取相对方向单例(py, a),
|
||||
crate::types_py::获取相对方向单例(py, b),
|
||||
crate::types_py::获取相对方向单例(py, c),
|
||||
)
|
||||
})
|
||||
}
|
||||
@@ -271,7 +297,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())
|
||||
@@ -466,15 +492,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 ----
|
||||
@@ -483,12 +507,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())
|
||||
}
|
||||
@@ -527,12 +546,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())
|
||||
}
|
||||
@@ -545,10 +559,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]
|
||||
@@ -581,11 +593,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())
|
||||
}
|
||||
@@ -972,7 +983,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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1036,10 +1047,8 @@ impl 线段特征Py {
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn 线段方向(&self) -> 相对方向Py {
|
||||
相对方向Py {
|
||||
inner: self.inner.线段方向,
|
||||
}
|
||||
fn 线段方向(&self, py: Python<'_>) -> Py<相对方向Py> {
|
||||
crate::types_py::获取相对方向单例(py, self.inner.线段方向)
|
||||
}
|
||||
|
||||
#[getter]
|
||||
@@ -1101,7 +1110,7 @@ impl 线段特征Py {
|
||||
let dict = PyDict::new(py);
|
||||
dict.set_item("序号", self.序号())?;
|
||||
dict.set_item("标识", self.标识())?;
|
||||
dict.set_item("线段方向", self.线段方向())?;
|
||||
dict.set_item("线段方向", self.线段方向(py))?;
|
||||
dict.set_item("图表标题", self.图表标题())?;
|
||||
Ok(dict.into())
|
||||
}
|
||||
@@ -1128,10 +1137,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]
|
||||
@@ -1151,7 +1158,7 @@ impl 线段特征Py {
|
||||
let inner = Arc::make_mut(&mut self.inner);
|
||||
inner
|
||||
.添加(Arc::clone(&待添加虚线.borrow().inner))
|
||||
.map_err(|e| pyo3::exceptions::PyValueError::new_err(e))
|
||||
.map_err(pyo3::exceptions::PyValueError::new_err)
|
||||
}
|
||||
|
||||
/// :param 待删除虚线: 待删除的虚线
|
||||
@@ -1159,7 +1166,7 @@ impl 线段特征Py {
|
||||
let inner = Arc::make_mut(&mut self.inner);
|
||||
inner
|
||||
.删除(&Arc::clone(&待删除虚线.borrow().inner))
|
||||
.map_err(|e| pyo3::exceptions::PyValueError::new_err(e))
|
||||
.map_err(pyo3::exceptions::PyValueError::new_err)
|
||||
}
|
||||
|
||||
// ---- classmethods ----
|
||||
|
||||
+47
-14
@@ -61,6 +61,40 @@ pub fn 获取分型结构单例(
|
||||
result
|
||||
}
|
||||
|
||||
static 相对方向_单例缓存: Mutex<Option<HashMap<u8, Py<相对方向Py>>>> = Mutex::new(None);
|
||||
|
||||
pub fn 获取相对方向单例(
|
||||
py: Python<'_>,
|
||||
inner: chanlun::types::相对方向,
|
||||
) -> Py<相对方向Py> {
|
||||
let mut guard = 相对方向_单例缓存.lock().unwrap();
|
||||
if let Some(ref map) = *guard {
|
||||
return map[&(inner as u8)].clone_ref(py);
|
||||
}
|
||||
|
||||
// 首次访问时从类属性加载单例
|
||||
let module = py.import("chanlun._chanlun").unwrap();
|
||||
let class = module.getattr("相对方向").unwrap();
|
||||
let mut map = HashMap::new();
|
||||
for (name, variant) in &[
|
||||
("向上", chanlun::types::相对方向::向上),
|
||||
("向下", chanlun::types::相对方向::向下),
|
||||
("向上缺口", chanlun::types::相对方向::向上缺口),
|
||||
("向下缺口", chanlun::types::相对方向::向下缺口),
|
||||
("衔接向上", chanlun::types::相对方向::衔接向上),
|
||||
("衔接向下", chanlun::types::相对方向::衔接向下),
|
||||
("顺", chanlun::types::相对方向::顺),
|
||||
("逆", chanlun::types::相对方向::逆),
|
||||
("同", chanlun::types::相对方向::同),
|
||||
] {
|
||||
let instance: Py<相对方向Py> = class.getattr(*name).unwrap().extract().unwrap();
|
||||
map.insert(*variant as u8, instance);
|
||||
}
|
||||
let result = map[&(inner as u8)].clone_ref(py);
|
||||
*guard = Some(map);
|
||||
result
|
||||
}
|
||||
|
||||
// ========== 买卖点类型 ==========
|
||||
|
||||
/// 买卖点类型 — 缠论的三类买卖点及扩展类型。
|
||||
@@ -189,10 +223,8 @@ impl 相对方向Py {
|
||||
}
|
||||
|
||||
/// 返回方向的对立面(向上↔向下, 缺口↔反向缺口, 衔接↔反向衔接)。
|
||||
fn 翻转(&self) -> Self {
|
||||
Self {
|
||||
inner: self.inner.翻转(),
|
||||
}
|
||||
fn 翻转(&self, py: Python<'_>) -> Py<Self> {
|
||||
获取相对方向单例(py, self.inner.翻转())
|
||||
}
|
||||
|
||||
/// 判断是否为向上方向(向上/向上缺口/衔接向上)
|
||||
@@ -245,10 +277,11 @@ impl 相对方向Py {
|
||||
#[classmethod]
|
||||
fn 分析(
|
||||
_cls: &Bound<'_, PyType>, 前高: f64, 前低: f64, 后高: f64, 后低: f64
|
||||
) -> Self {
|
||||
Self {
|
||||
inner: chanlun::types::相对方向::分析(前高, 前低, 后高, 后低),
|
||||
}
|
||||
) -> Py<Self> {
|
||||
获取相对方向单例(
|
||||
_cls.py(),
|
||||
chanlun::types::相对方向::分析(前高, 前低, 后高, 后低),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -512,7 +545,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::买卖点类型::一买),
|
||||
@@ -535,7 +568,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))?;
|
||||
@@ -544,7 +577,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::相对方向::向下),
|
||||
@@ -557,7 +590,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))?;
|
||||
@@ -566,7 +599,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::分型结构::下),
|
||||
@@ -575,7 +608,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))?;
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "chanlun"
|
||||
version = "26.5.4"
|
||||
version = "26.5.6"
|
||||
edition = "2021"
|
||||
rust-version = "1.70"
|
||||
license = "MIT"
|
||||
|
||||
+15
-24
@@ -78,16 +78,18 @@ impl 笔 {
|
||||
- 高_k.标的K线.read().unwrap().序号)
|
||||
.unsigned_abs() as usize;
|
||||
// 向上笔
|
||||
if 筆.方向().是否向上() && 低_k.低.get() < 筆.低() {
|
||||
if 原始数量 >= 配置.笔弱化_原始数量 as usize {
|
||||
return 配置.笔内元素数量 as usize;
|
||||
}
|
||||
if 筆.方向().是否向上()
|
||||
&& 低_k.低.get() < 筆.低()
|
||||
&& 原始数量 >= 配置.笔弱化_原始数量 as usize
|
||||
{
|
||||
return 配置.笔内元素数量 as usize;
|
||||
}
|
||||
// 向下笔
|
||||
if 筆.方向().是否向下() && 低_k.低.get() > 筆.高() {
|
||||
if 原始数量 >= 配置.笔弱化_原始数量 as usize {
|
||||
return 配置.笔内元素数量 as usize;
|
||||
}
|
||||
if 筆.方向().是否向下()
|
||||
&& 低_k.低.get() > 筆.高()
|
||||
&& 原始数量 >= 配置.笔弱化_原始数量 as usize
|
||||
{
|
||||
return 配置.笔内元素数量 as usize;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -960,22 +962,11 @@ impl 笔 {
|
||||
for i in 3..基础序列.len() - 1 {
|
||||
let k = &基础序列[i];
|
||||
|
||||
if *k.分型.read().unwrap() == Some(分型结构::顶) && 筆.方向() == 相对方向::向上
|
||||
{
|
||||
let 左 = Arc::clone(&基础序列[i - 1]);
|
||||
let 中 = Arc::clone(k);
|
||||
let 右 = Arc::clone(&基础序列[i + 1]);
|
||||
let 武 = 分型::new(Some(左), 中, Some(右));
|
||||
let 当前笔 = 虚线::创建笔(Arc::clone(&文), Arc::new(武), true);
|
||||
当前笔
|
||||
.序号
|
||||
.store(筆.序号.load(Ordering::Relaxed), Ordering::Relaxed);
|
||||
if Self::自检(&当前笔, 观察员) {
|
||||
笔序列.push(当前笔);
|
||||
}
|
||||
} else if *k.分型.read().unwrap() == Some(分型结构::底)
|
||||
&& 筆.方向() == 相对方向::向下
|
||||
{
|
||||
let 匹配顶 =
|
||||
*k.分型.read().unwrap() == Some(分型结构::顶) && 筆.方向() == 相对方向::向上;
|
||||
let 匹配底 =
|
||||
*k.分型.read().unwrap() == Some(分型结构::底) && 筆.方向() == 相对方向::向下;
|
||||
if 匹配顶 || 匹配底 {
|
||||
let 左 = Arc::clone(&基础序列[i - 1]);
|
||||
let 中 = Arc::clone(k);
|
||||
let 右 = Arc::clone(&基础序列[i + 1]);
|
||||
|
||||
@@ -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(),
|
||||
);
|
||||
|
||||
// 计算面积(绝对值求和)
|
||||
|
||||
@@ -415,7 +415,7 @@ impl 中枢 {
|
||||
中枢序列: &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 +430,7 @@ impl 中枢 {
|
||||
中枢序列: &mut Vec<Arc<中枢>>,
|
||||
跳过首部: bool,
|
||||
标识: &str,
|
||||
层级: i64,
|
||||
_层级: i64,
|
||||
) {
|
||||
if 虚线序列.len() < 3 {
|
||||
return;
|
||||
@@ -472,7 +472,7 @@ impl 中枢 {
|
||||
));
|
||||
Self::向中枢序列尾部添加(中枢序列, 新中枢);
|
||||
// Python: return 中枢递归分析(虚线序列, 中枢序列, ...)
|
||||
Self::分析(虚线序列, 中枢序列, 跳过首部, 标识, 层级);
|
||||
Self::分析(虚线序列, 中枢序列, 跳过首部, 标识, _层级);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -487,7 +487,7 @@ impl 中枢 {
|
||||
if needs_pop {
|
||||
let 当前中枢 = Arc::clone(&中枢序列[当前中枢_idx]);
|
||||
Self::从中枢序列尾部弹出(中枢序列, &当前中枢);
|
||||
Self::分析(虚线序列, 中枢序列, 跳过首部, 标识, 层级);
|
||||
Self::分析(虚线序列, 中枢序列, 跳过首部, 标识, _层级);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -508,8 +508,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::相对方向::分析(中枢高, 中枢低, 当前虚线.高(), 当前虚线.低()).是否缺口()
|
||||
@@ -566,6 +566,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 +599,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(
|
||||
@@ -710,7 +735,7 @@ mod tests {
|
||||
中枢.设置第三买卖线(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)
|
||||
);
|
||||
|
||||
@@ -870,27 +895,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.武(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +38,14 @@ use std::sync::Arc;
|
||||
/// 线段 — 从笔生成线段的算法集合(静态方法命名空间)
|
||||
pub struct 线段;
|
||||
|
||||
type 分割结果 = (
|
||||
Vec<Arc<虚线>>,
|
||||
Vec<Arc<虚线>>,
|
||||
Vec<Arc<虚线>>,
|
||||
Option<Arc<虚线>>,
|
||||
);
|
||||
type 中枢序列组 = (Vec<Arc<中枢>>, Vec<Arc<中枢>>, Vec<Arc<中枢>>);
|
||||
|
||||
impl 线段 {
|
||||
// ================================================================
|
||||
// 基础操作
|
||||
@@ -53,8 +61,7 @@ impl 线段 {
|
||||
let 段 = Self::取段(段_rc);
|
||||
if !段.基础序列.read().unwrap().is_empty() {
|
||||
if !分型::判断分型(
|
||||
&*段
|
||||
.基础序列
|
||||
&段.基础序列
|
||||
.read()
|
||||
.unwrap()
|
||||
.last()
|
||||
@@ -182,11 +189,9 @@ impl 线段 {
|
||||
if !序列.iter().any(|x| Arc::as_ptr(x) == Arc::as_ptr(元素)) {
|
||||
break;
|
||||
}
|
||||
if !基础序列.is_empty() {
|
||||
if !基础序列.last().unwrap().之后是(元素) {
|
||||
eprintln!("线段._验证序列 数据不连续");
|
||||
break;
|
||||
}
|
||||
if !基础序列.is_empty() && !基础序列.last().unwrap().之后是(元素) {
|
||||
eprintln!("线段._验证序列 数据不连续");
|
||||
break;
|
||||
}
|
||||
基础序列.push(Arc::clone(元素));
|
||||
}
|
||||
@@ -204,10 +209,8 @@ impl 线段 {
|
||||
if !序列.iter().any(|x| Arc::as_ptr(x) == Arc::as_ptr(元素)) {
|
||||
break;
|
||||
}
|
||||
if !基础序列.is_empty() {
|
||||
if !基础序列.last().unwrap().之后是(元素) {
|
||||
break;
|
||||
}
|
||||
if !基础序列.is_empty() && !基础序列.last().unwrap().之后是(元素) {
|
||||
break;
|
||||
}
|
||||
基础序列.push(Arc::clone(元素));
|
||||
}
|
||||
@@ -301,7 +304,7 @@ impl 线段 {
|
||||
/// 特征分型终结 — 检查特征序列是否形成正常分型终结
|
||||
pub fn 特征分型终结(段: &虚线) -> bool {
|
||||
let 特征序列 = 线段特征::静态分析(
|
||||
&*段.基础序列.read().unwrap(),
|
||||
&段.基础序列.read().unwrap(),
|
||||
段.方向(),
|
||||
&Self::四象(段),
|
||||
false,
|
||||
@@ -347,11 +350,9 @@ impl 线段 {
|
||||
return;
|
||||
}
|
||||
|
||||
for 特征 in &序列 {
|
||||
if let Some(ref f) = 特征 {
|
||||
if f.方向() == 段.方向() {
|
||||
panic!("特征序列方向不匹配[{}]", 行号);
|
||||
}
|
||||
for f in 序列.iter().flatten() {
|
||||
if f.方向() == 段.方向() {
|
||||
panic!("特征序列方向不匹配[{}]", 行号);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -472,15 +473,7 @@ impl 线段 {
|
||||
// ================================================================
|
||||
|
||||
/// 分割序列 — 将线段的基础序列分为前、后、第三买卖线、贯穿伤
|
||||
pub fn 分割序列(
|
||||
段: &虚线,
|
||||
所属中枢: Option<&中枢>,
|
||||
) -> (
|
||||
Vec<Arc<虚线>>,
|
||||
Vec<Arc<虚线>>,
|
||||
Vec<Arc<虚线>>,
|
||||
Option<Arc<虚线>>,
|
||||
) {
|
||||
pub fn 分割序列(段: &虚线, 所属中枢: Option<&中枢>) -> 分割结果 {
|
||||
if 段.模式.read().unwrap().as_str() != "文武" {
|
||||
return (
|
||||
段.基础序列.read().unwrap().clone(),
|
||||
@@ -517,9 +510,9 @@ impl 线段 {
|
||||
|
||||
let mut 状态 = None;
|
||||
|
||||
if let Some(ref 中枢) = 所属中枢 {
|
||||
if let Some(中枢) = 所属中枢 {
|
||||
*中枢.本级_第三买卖线.write().unwrap() = None;
|
||||
let 尾部 = if let Some(ref 后笔) = 后.last() {
|
||||
let 尾部 = if let Some(后笔) = 后.last() {
|
||||
后笔.武.read().unwrap().clone()
|
||||
} else {
|
||||
段.武.read().unwrap().clone()
|
||||
@@ -572,7 +565,7 @@ impl 线段 {
|
||||
|
||||
if !第三买卖线.is_empty() {
|
||||
第三买卖线.reverse();
|
||||
if let Some(ref 中枢) = 所属中枢 {
|
||||
if let Some(中枢) = 所属中枢 {
|
||||
*中枢.本级_第三买卖线.write().unwrap() = Some(Arc::clone(&第三买卖线[0]));
|
||||
}
|
||||
}
|
||||
@@ -626,13 +619,11 @@ impl 线段 {
|
||||
}) == Some(true)
|
||||
{
|
||||
Some(Arc::clone(最近特征.元素.last().unwrap()))
|
||||
} else if let Some(b) = 笔::以武会友(
|
||||
&*段2.基础序列.read().unwrap(),
|
||||
&*最近特征.元素.last().unwrap().武.read().unwrap(),
|
||||
) {
|
||||
Some(b)
|
||||
} else {
|
||||
None
|
||||
笔::以武会友(
|
||||
&段2.基础序列.read().unwrap(),
|
||||
&最近特征.元素.last().unwrap().武.read().unwrap(),
|
||||
)
|
||||
};
|
||||
|
||||
if 特征后一笔.is_none() {
|
||||
@@ -686,14 +677,11 @@ impl 线段 {
|
||||
}
|
||||
|
||||
/// 获取内部中枢序列 — 内部实现
|
||||
fn 获取内部中枢序列_内部(
|
||||
段: &虚线,
|
||||
_配置: &缠论配置,
|
||||
) -> (Vec<Arc<中枢>>, Vec<Arc<中枢>>, Vec<Arc<中枢>>) {
|
||||
fn 获取内部中枢序列_内部(段: &虚线, _配置: &缠论配置) -> 中枢序列组 {
|
||||
if 段.模式.read().unwrap().as_str() != "文武" {
|
||||
中枢::分析(
|
||||
&*段.基础序列.read().unwrap(),
|
||||
&mut *段.合_中枢序列.write().unwrap(),
|
||||
&段.基础序列.read().unwrap(),
|
||||
&mut 段.合_中枢序列.write().unwrap(),
|
||||
true,
|
||||
&format!(
|
||||
"{}_{}_合_",
|
||||
@@ -714,7 +702,7 @@ impl 线段 {
|
||||
|
||||
中枢::分析(
|
||||
&前,
|
||||
&mut *段.实_中枢序列.write().unwrap(),
|
||||
&mut 段.实_中枢序列.write().unwrap(),
|
||||
true,
|
||||
&format!(
|
||||
"{}_{}_实_",
|
||||
@@ -725,7 +713,7 @@ impl 线段 {
|
||||
);
|
||||
中枢::分析(
|
||||
&后,
|
||||
&mut *段.虚_中枢序列.write().unwrap(),
|
||||
&mut 段.虚_中枢序列.write().unwrap(),
|
||||
true,
|
||||
&format!(
|
||||
"{}_{}_虚_",
|
||||
@@ -735,8 +723,8 @@ impl 线段 {
|
||||
0,
|
||||
);
|
||||
中枢::分析(
|
||||
&*段.基础序列.read().unwrap(),
|
||||
&mut *段.合_中枢序列.write().unwrap(),
|
||||
&段.基础序列.read().unwrap(),
|
||||
&mut 段.合_中枢序列.write().unwrap(),
|
||||
true,
|
||||
&format!(
|
||||
"{}_{}_合_",
|
||||
@@ -755,9 +743,8 @@ impl 线段 {
|
||||
|
||||
/// 获取内部中枢序列
|
||||
pub fn 获取内部中枢序列(
|
||||
段_rc: &Arc<虚线>,
|
||||
配置: &缠论配置,
|
||||
) -> (Vec<Arc<中枢>>, Vec<Arc<中枢>>, Vec<Arc<中枢>>) {
|
||||
段_rc: &Arc<虚线>, 配置: &缠论配置
|
||||
) -> 中枢序列组 {
|
||||
let 段 = Self::取段(段_rc);
|
||||
Self::获取内部中枢序列_内部(段, 配置)
|
||||
}
|
||||
@@ -942,7 +929,7 @@ impl 线段 {
|
||||
*cur.基础序列.write().unwrap() = 当前线段基础序列;
|
||||
}
|
||||
let idx = 线段序列.len() - 1;
|
||||
Self::刷新(&mut 线段序列[idx], 配置);
|
||||
Self::刷新(&线段序列[idx], 配置);
|
||||
true
|
||||
}
|
||||
|
||||
@@ -1038,7 +1025,7 @@ impl 线段 {
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(_) = 开始序号_opt {
|
||||
if 开始序号_opt.is_some() {
|
||||
let idx = 线段序列.len() - 1;
|
||||
let seg_rc = Arc::clone(&线段序列[idx]);
|
||||
for 临时虚线 in &待添加元素 {
|
||||
@@ -1047,7 +1034,7 @@ impl 线段 {
|
||||
线段序列[idx] = seg_rc;
|
||||
}
|
||||
let idx = 线段序列.len() - 1;
|
||||
Self::刷新(&mut 线段序列[idx], 配置);
|
||||
Self::刷新(&线段序列[idx], 配置);
|
||||
|
||||
let 当前线段 = Arc::clone(&线段序列[idx]);
|
||||
if 当前线段.特征序列.read().unwrap().len() >= 3
|
||||
@@ -1310,8 +1297,8 @@ impl 线段 {
|
||||
}
|
||||
|
||||
// ---- 3. 确保当前线段有效 ----
|
||||
let mut 当前线段_rc = Arc::clone(线段序列.last().unwrap());
|
||||
Self::序列重置(&mut 当前线段_rc, 笔序列);
|
||||
let 当前线段_rc = Arc::clone(线段序列.last().unwrap());
|
||||
Self::序列重置(&当前线段_rc, 笔序列);
|
||||
let seg_idx = 线段序列.len() - 1;
|
||||
线段序列[seg_idx] = 当前线段_rc;
|
||||
|
||||
@@ -1347,7 +1334,7 @@ impl 线段 {
|
||||
|
||||
// Refresh current segment
|
||||
let idx = 线段序列.len() - 1;
|
||||
Self::刷新(&mut 线段序列[idx], 配置);
|
||||
Self::刷新(&线段序列[idx], 配置);
|
||||
|
||||
// ---- 5. 调用一次全局修正 ----
|
||||
Self::_缺口突破(线段序列, 配置, 当前层级);
|
||||
@@ -1375,20 +1362,20 @@ impl 线段 {
|
||||
|
||||
let mut 需要递归 = false;
|
||||
|
||||
for i in 起始索引..笔序列.len() {
|
||||
let 当前虚线 = Arc::clone(&笔序列[i]);
|
||||
for 当前虚线_ref in &笔序列[起始索引..] {
|
||||
let 当前虚线 = Arc::clone(当前虚线_ref);
|
||||
let 当前线段 = Arc::clone(线段序列.last().unwrap());
|
||||
let 四象 = Self::四象(&当前线段);
|
||||
|
||||
// 向当前线段添加笔
|
||||
let mut 线段_rc = Arc::clone(线段序列.last().unwrap());
|
||||
Self::添加虚线(&mut 线段_rc, Arc::clone(&当前虚线));
|
||||
let 线段_rc = Arc::clone(线段序列.last().unwrap());
|
||||
Self::添加虚线(&线段_rc, Arc::clone(&当前虚线));
|
||||
let seg_idx = 线段序列.len() - 1;
|
||||
线段序列[seg_idx] = 线段_rc;
|
||||
|
||||
// 刷新
|
||||
let idx = 线段序列.len() - 1;
|
||||
Self::刷新(&mut 线段序列[idx], 配置);
|
||||
Self::刷新(&线段序列[idx], 配置);
|
||||
|
||||
// 依次尝试四种修正
|
||||
if Self::_缺口突破(线段序列, 配置, 当前层级) {
|
||||
@@ -1444,14 +1431,14 @@ impl 线段 {
|
||||
break;
|
||||
}
|
||||
// 向新段添加当前虚线
|
||||
let mut 新段_rc = Arc::clone(线段序列.last().unwrap());
|
||||
Self::添加虚线(&mut 新段_rc, Arc::clone(&当前虚线));
|
||||
let 新段_rc = Arc::clone(线段序列.last().unwrap());
|
||||
Self::添加虚线(&新段_rc, Arc::clone(&当前虚线));
|
||||
let seg_idx = 线段序列.len() - 1;
|
||||
线段序列[seg_idx] = 新段_rc;
|
||||
}
|
||||
|
||||
let idx = 线段序列.len() - 1;
|
||||
Self::刷新(&mut 线段序列[idx], 配置);
|
||||
Self::刷新(&线段序列[idx], 配置);
|
||||
}
|
||||
|
||||
if 需要递归 {
|
||||
@@ -1572,8 +1559,8 @@ impl 线段 {
|
||||
}
|
||||
|
||||
// 验证当前线段
|
||||
let mut 当前线段_rc = Arc::clone(线段序列.last().unwrap());
|
||||
Self::验证序列(&mut 当前线段_rc, 虚线序列);
|
||||
let 当前线段_rc = Arc::clone(线段序列.last().unwrap());
|
||||
Self::验证序列(&当前线段_rc, 虚线序列);
|
||||
let seg_idx = 线段序列.len() - 1;
|
||||
线段序列[seg_idx] = 当前线段_rc;
|
||||
|
||||
@@ -1597,7 +1584,7 @@ impl 线段 {
|
||||
*cur.基础序列.write().unwrap() = 前三个;
|
||||
}
|
||||
let seg_idx = 线段序列.len() - 1;
|
||||
Self::武终(&mut 线段序列[seg_idx], 0);
|
||||
Self::武终(&线段序列[seg_idx], 0);
|
||||
} else {
|
||||
let 当前 = Arc::clone(线段序列.last().unwrap());
|
||||
Self::_弹出扩展线段(线段序列, &当前, line!());
|
||||
@@ -1608,7 +1595,7 @@ impl 线段 {
|
||||
|
||||
// 武终
|
||||
let idx = 线段序列.len() - 1;
|
||||
Self::武终(&mut 线段序列[idx], 0);
|
||||
Self::武终(&线段序列[idx], 0);
|
||||
|
||||
let 当前线段 = Arc::clone(线段序列.last().unwrap());
|
||||
if 当前线段
|
||||
@@ -1647,18 +1634,18 @@ impl 线段 {
|
||||
let 相对关系 = 相对方向::分析(左.高(), 左.低(), 右.高(), 右.低());
|
||||
|
||||
if 相对关系.是否缺口() {
|
||||
let mut 段_rc = Arc::clone(线段序列.last().unwrap());
|
||||
Self::添加虚线(&mut 段_rc, Arc::clone(左));
|
||||
let 段_rc = Arc::clone(线段序列.last().unwrap());
|
||||
Self::添加虚线(&段_rc, Arc::clone(左));
|
||||
let seg_idx = 线段序列.len() - 1;
|
||||
线段序列[seg_idx] = 段_rc;
|
||||
|
||||
let mut 段_rc = Arc::clone(线段序列.last().unwrap());
|
||||
Self::添加虚线(&mut 段_rc, Arc::clone(中));
|
||||
let 段_rc = Arc::clone(线段序列.last().unwrap());
|
||||
Self::添加虚线(&段_rc, Arc::clone(中));
|
||||
let seg_idx = 线段序列.len() - 1;
|
||||
线段序列[seg_idx] = 段_rc;
|
||||
|
||||
let seg_idx = 线段序列.len() - 1;
|
||||
Self::武终(&mut 线段序列[seg_idx], 0);
|
||||
Self::武终(&线段序列[seg_idx], 0);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1730,8 +1717,8 @@ impl 线段 {
|
||||
{
|
||||
let k线序列 = K线::截取rc(
|
||||
&观察员.普通K线序列,
|
||||
&*阳[阳.len() - 3].文.中.标的K线.read().unwrap(),
|
||||
&*阳[阳.len() - 1]
|
||||
&阳[阳.len() - 3].文.中.标的K线.read().unwrap(),
|
||||
&阳[阳.len() - 1]
|
||||
.武
|
||||
.read()
|
||||
.unwrap()
|
||||
@@ -1851,15 +1838,13 @@ impl 线段 {
|
||||
for 筆 in &阳 {
|
||||
if 笔序列.len() >= 3 {
|
||||
let 筆停顿 = 笔::获取所有停顿位置(筆, 观察员);
|
||||
let mut 停顿列表: Vec<Arc<虚线>> =
|
||||
筆停顿.into_iter().map(|b| Arc::new(b)).collect();
|
||||
let mut 停顿列表: Vec<Arc<虚线>> = 筆停顿.into_iter().map(Arc::new).collect();
|
||||
停顿列表.push(Arc::clone(筆));
|
||||
|
||||
for 停顿 in &停顿列表 {
|
||||
笔序列.push(Arc::clone(停顿));
|
||||
线段序列.clear();
|
||||
let 笔序列_slice: Vec<Arc<虚线>> =
|
||||
笔序列.iter().map(|b| Arc::clone(b)).collect();
|
||||
let 笔序列_slice: Vec<Arc<虚线>> = 笔序列.iter().map(Arc::clone).collect();
|
||||
Self::分析(
|
||||
&笔序列_slice,
|
||||
&mut 线段序列,
|
||||
@@ -1881,13 +1866,12 @@ impl 线段 {
|
||||
if !重复 {
|
||||
if let Some(最后线段) = 线段序列.last() {
|
||||
if 最后线段.基础序列.read().unwrap().len() % 2 == 1 {
|
||||
let 新段 =
|
||||
虚线::创建线段(&*最后线段.基础序列.read().unwrap());
|
||||
let 新段 = 虚线::创建线段(&最后线段.基础序列.read().unwrap());
|
||||
新段
|
||||
.序号
|
||||
.store(段.序号.load(Ordering::Relaxed), Ordering::Relaxed);
|
||||
let mut 新段_rc = Arc::new(新段);
|
||||
Self::刷新(&mut 新段_rc, &观察员.配置);
|
||||
let 新段_rc = Arc::new(新段);
|
||||
Self::刷新(&新段_rc, &观察员.配置);
|
||||
let 新段_inner =
|
||||
Arc::try_unwrap(新段_rc).unwrap_or_else(|rc| (*rc).clone());
|
||||
if 新段_inner.方向() == 段.方向() {
|
||||
@@ -1919,8 +1903,8 @@ impl 线段 {
|
||||
let mut 结果 = Vec::new();
|
||||
|
||||
for 段 in 停顿位置 {
|
||||
let mut 段_rc = Arc::new(段);
|
||||
Self::获取内部中枢序列(&mut 段_rc, &观察员.配置);
|
||||
let 段_rc = Arc::new(段);
|
||||
Self::获取内部中枢序列(&段_rc, &观察员.配置);
|
||||
if Self::判断线段内部是否背驰(&段_rc, 观察员) {
|
||||
结果.push(Arc::clone(&段_rc.武.read().unwrap().中));
|
||||
}
|
||||
|
||||
@@ -155,6 +155,14 @@ impl 观察者 {
|
||||
self.__处理数据(普K);
|
||||
}
|
||||
|
||||
/// 投喂原始数据 — 便捷入口,直接从 OHLCV 创建 K线 并投喂
|
||||
pub fn 投喂原始数据(
|
||||
&mut self, 时间戳: i64, 开: f64, 高: f64, 低: f64, 收: f64, 量: f64
|
||||
) {
|
||||
let 普K = K线::创建普K(&self.符号, 时间戳, 开, 高, 低, 收, 量, 0, self.周期);
|
||||
self.增加原始K线(普K);
|
||||
}
|
||||
|
||||
/// 核心数据处理管道
|
||||
fn __处理数据(&mut self, 普K: K线) {
|
||||
// Step 1: 缠论K线分析 (普K is consumed by 分析 as &mut)
|
||||
@@ -479,39 +487,26 @@ impl 观察者 {
|
||||
println!("全部数据拆分保存完成,目录:{}", 保存路径.display());
|
||||
}
|
||||
|
||||
/// 解析本地数据文件 — 从 .nb 文件读取并解析所有 K线
|
||||
pub fn 解析本地数据(&self, 文件路径: &str) -> Result<Vec<K线>, String> {
|
||||
let data = std::fs::read(文件路径).map_err(|e| format!("read file: {}", e))?;
|
||||
let mut bars = Vec::new();
|
||||
let size = 48;
|
||||
for i in 0..data.len() / size {
|
||||
let offset = i * size;
|
||||
if let Some(k线) = K线::from_bytes(&data[offset..offset + size], self.周期, &self.符号)
|
||||
{
|
||||
bars.push(k线);
|
||||
}
|
||||
}
|
||||
Ok(bars)
|
||||
}
|
||||
|
||||
/// 加载本地数据 — 从 .nb 文件加载数据到当前观察者(先重置再投喂)
|
||||
pub fn 加载本地数据(&mut self, 文件路径: &str) -> Result<(), String> {
|
||||
self.重置基础序列();
|
||||
let bars = self.解析本地数据(文件路径)?;
|
||||
for k线 in bars {
|
||||
self.增加原始K线(k线);
|
||||
let data = std::fs::read(文件路径).map_err(|e| format!("read file: {}", e))?;
|
||||
let size = 48;
|
||||
for i in 0..data.len() / size {
|
||||
let offset = i * size;
|
||||
if let Some((时间戳, 开, 高, 低, 收, 量)) =
|
||||
K线::解析原始数据(&data[offset..offset + size])
|
||||
{
|
||||
self.投喂原始数据(时间戳, 开, 高, 低, 收, 量);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 读取数据文件 — 从 .nb 文件加载数据
|
||||
/// 读取数据文件 — 更新当前观察者并加载 .nb 文件
|
||||
pub fn 读取数据文件(
|
||||
文件路径: &str,
|
||||
配置: Option<缠论配置>,
|
||||
) -> Result<Arc<RwLock<Self>>, String> {
|
||||
let 配置 = 配置.unwrap_or_default();
|
||||
|
||||
// Parse filename: btcusd-300-1631772074-1632222374.nb
|
||||
&mut self, 文件路径: &str, 配置: 缠论配置
|
||||
) -> Result<(), String> {
|
||||
let path = std::path::Path::new(文件路径);
|
||||
let name = path
|
||||
.file_stem()
|
||||
@@ -521,23 +516,12 @@ impl 观察者 {
|
||||
if parts.len() < 4 {
|
||||
return Err(format!("invalid filename format: {}", name));
|
||||
}
|
||||
let 符号 = parts[0].to_string();
|
||||
let 周期: i64 = parts[1]
|
||||
self.符号 = parts[0].to_string();
|
||||
self.周期 = parts[1]
|
||||
.parse()
|
||||
.map_err(|e| format!("parse period: {}", e))?;
|
||||
|
||||
let 实例 = Self::new(符号, 周期, 配置);
|
||||
|
||||
let data = std::fs::read(文件路径).map_err(|e| format!("read file: {}", e))?;
|
||||
let size = 48; // 6 × 8 bytes (big-endian double)
|
||||
for i in 0..data.len() / size {
|
||||
let offset = i * size;
|
||||
if let Some(k线) = K线::from_bytes(&data[offset..offset + size], 周期, "nb") {
|
||||
实例.write().unwrap().增加原始K线(k线);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(实例)
|
||||
self.配置 = 配置;
|
||||
self.加载本地数据(文件路径)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -546,12 +530,23 @@ mod tests {
|
||||
use super::*;
|
||||
use crate::config::缠论配置;
|
||||
|
||||
const TEST_DATA_PATH: &str = "/home/moscow/chanlun.rs/btcusd-300-1777649100-1778398800.nb";
|
||||
fn test_data_path() -> String {
|
||||
let manifest = std::path::Path::new(env!("CARGO_MANIFEST_DIR"));
|
||||
manifest
|
||||
.parent()
|
||||
.unwrap()
|
||||
.join("btcusd-300-1777649100-1778398800.nb")
|
||||
.to_string_lossy()
|
||||
.to_string()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_普k序列指针一致性() {
|
||||
let config = 缠论配置::default();
|
||||
let obs = 观察者::读取数据文件(TEST_DATA_PATH, Some(config)).unwrap();
|
||||
let obs = 观察者::new("btcusd".into(), 300, Default::default());
|
||||
obs.write()
|
||||
.unwrap()
|
||||
.读取数据文件(&test_data_path(), Default::default())
|
||||
.unwrap();
|
||||
let obs_ref = obs.read().unwrap();
|
||||
|
||||
for (i, bi) in obs_ref.笔序列.iter().enumerate() {
|
||||
@@ -595,7 +590,7 @@ mod tests {
|
||||
let config = 缠论配置::default();
|
||||
let obs_ref = 观察者::new("btcusd".into(), 300, config);
|
||||
|
||||
let data = std::fs::read(TEST_DATA_PATH).unwrap();
|
||||
let data = std::fs::read(test_data_path()).unwrap();
|
||||
let size = 48;
|
||||
|
||||
for i in 0..data.len() / size {
|
||||
@@ -633,8 +628,11 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_分型到笔的文武Rc指针一致性() {
|
||||
let config = 缠论配置::default();
|
||||
let obs = 观察者::读取数据文件(TEST_DATA_PATH, Some(config)).unwrap();
|
||||
let obs = 观察者::new("btcusd".into(), 300, Default::default());
|
||||
obs.write()
|
||||
.unwrap()
|
||||
.读取数据文件(&test_data_path(), Default::default())
|
||||
.unwrap();
|
||||
let obs_ref = obs.read().unwrap();
|
||||
|
||||
// 每个笔的文/武 分型 Rc 指针必须在 分型序列 中
|
||||
@@ -663,8 +661,11 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_笔到线段的基础序列Rc指针一致性() {
|
||||
let config = 缠论配置::default();
|
||||
let obs = 观察者::读取数据文件(TEST_DATA_PATH, Some(config)).unwrap();
|
||||
let obs = 观察者::new("btcusd".into(), 300, Default::default());
|
||||
obs.write()
|
||||
.unwrap()
|
||||
.读取数据文件(&test_data_path(), Default::default())
|
||||
.unwrap();
|
||||
let obs_ref = obs.read().unwrap();
|
||||
|
||||
// 每个线段的基础序列中的笔 Rc 指针必须在 笔序列 中
|
||||
@@ -685,8 +686,11 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_中枢基础序列与笔序列Rc指针一致() {
|
||||
let config = 缠论配置::default();
|
||||
let obs = 观察者::读取数据文件(TEST_DATA_PATH, Some(config)).unwrap();
|
||||
let obs = 观察者::new("btcusd".into(), 300, Default::default());
|
||||
obs.write()
|
||||
.unwrap()
|
||||
.读取数据文件(&test_data_path(), Default::default())
|
||||
.unwrap();
|
||||
let obs_ref = obs.read().unwrap();
|
||||
|
||||
for (i, hub) in obs_ref.笔_中枢序列.iter().enumerate() {
|
||||
@@ -716,7 +720,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_重复计算后结果一致() {
|
||||
let data = std::fs::read(TEST_DATA_PATH).unwrap();
|
||||
let data = std::fs::read(test_data_path()).unwrap();
|
||||
let size = 48;
|
||||
|
||||
let 计算 = || {
|
||||
@@ -760,7 +764,7 @@ mod tests {
|
||||
let config = 缠论配置::default();
|
||||
let obs_ref = 观察者::new("btcusd".into(), 300, config);
|
||||
|
||||
let data = std::fs::read(TEST_DATA_PATH).unwrap();
|
||||
let data = std::fs::read(test_data_path()).unwrap();
|
||||
let size = 48;
|
||||
|
||||
for i in 0..data.len() / size {
|
||||
@@ -800,8 +804,11 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_RefCell借用安全性_连续读取不panic() {
|
||||
let config = 缠论配置::default();
|
||||
let obs = 观察者::读取数据文件(TEST_DATA_PATH, Some(config)).unwrap();
|
||||
let obs = 观察者::new("btcusd".into(), 300, Default::default());
|
||||
obs.write()
|
||||
.unwrap()
|
||||
.读取数据文件(&test_data_path(), Default::default())
|
||||
.unwrap();
|
||||
let obs_ref = obs.read().unwrap();
|
||||
|
||||
// 连续大量读取所有 RefCell 字段,不应 panic
|
||||
@@ -831,8 +838,11 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_RefCell借用安全性_交替读写不panic() {
|
||||
let config = 缠论配置::default();
|
||||
let obs = 观察者::读取数据文件(TEST_DATA_PATH, Some(config)).unwrap();
|
||||
let obs = 观察者::new("btcusd".into(), 300, Default::default());
|
||||
obs.write()
|
||||
.unwrap()
|
||||
.读取数据文件(&test_data_path(), Default::default())
|
||||
.unwrap();
|
||||
let obs_ref = obs.read().unwrap();
|
||||
|
||||
// 交替读写 RefCell 字段 — 先读再写同字段,分离 borrow 作用域
|
||||
@@ -860,8 +870,11 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_缠K到分型的Rc指针一致性() {
|
||||
let config = 缠论配置::default();
|
||||
let obs = 观察者::读取数据文件(TEST_DATA_PATH, Some(config)).unwrap();
|
||||
let obs = 观察者::new("btcusd".into(), 300, Default::default());
|
||||
obs.write()
|
||||
.unwrap()
|
||||
.读取数据文件(&test_data_path(), Default::default())
|
||||
.unwrap();
|
||||
let obs_ref = obs.read().unwrap();
|
||||
|
||||
// 每个分型的左/中/右 缠K 指针必须在 缠论K线序列 中
|
||||
|
||||
@@ -77,6 +77,7 @@ impl Default for 随机指标 {
|
||||
|
||||
impl 随机指标 {
|
||||
/// 首次计算 KDJ(无历史数据时)
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn 首次计算(
|
||||
初始最高价: f64,
|
||||
初始最低价: f64,
|
||||
|
||||
@@ -125,7 +125,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,
|
||||
|
||||
@@ -199,19 +199,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 +216,7 @@ impl 缠论K线 {
|
||||
}
|
||||
|
||||
/// 创建缠K
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn 创建缠K(
|
||||
时间戳: i64,
|
||||
高: f64,
|
||||
@@ -420,43 +418,41 @@ impl 缠论K线 {
|
||||
if 之前普K.时间戳 == 当前K线.时间戳 {
|
||||
// 同时间戳更新
|
||||
当前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,
|
||||
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线.时间戳,
|
||||
));
|
||||
}
|
||||
&配置.指标计算方式,
|
||||
),
|
||||
当前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();
|
||||
@@ -514,7 +510,7 @@ 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, 配置);
|
||||
|
||||
@@ -614,7 +610,7 @@ impl 缠论K线 {
|
||||
Arc::clone(&缠K序列[idx - 2]),
|
||||
Some(Arc::clone(&缠K序列[idx - 1])),
|
||||
));
|
||||
return (状态, Some(形态));
|
||||
(状态, Some(形态))
|
||||
}
|
||||
|
||||
/// 截取缠K序列从始到终
|
||||
@@ -623,12 +619,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())
|
||||
}
|
||||
}
|
||||
|
||||
+29
-31
@@ -77,38 +77,36 @@ fn 测试_读取数据(文件路径: &str) {
|
||||
let 启动时间 = Instant::now();
|
||||
|
||||
let 配置 = 缠论配置::default().不推送();
|
||||
match 观察者::读取数据文件(文件路径, Some(配置)) {
|
||||
Ok(观察员) => {
|
||||
let 观察员 = 观察员.read().unwrap();
|
||||
let 消耗用时 = 启动时间.elapsed();
|
||||
println!(
|
||||
"测试_读取数据 耗时 {:.2?} 普K数量 {}",
|
||||
消耗用时,
|
||||
观察员.普通K线序列.len()
|
||||
);
|
||||
println!("符号: {}", 观察员.符号);
|
||||
println!("周期: {}", 观察员.周期);
|
||||
println!("缠K数量: {}", 观察员.缠论K线序列.len());
|
||||
println!("分型数量: {}", 观察员.分型序列.len());
|
||||
println!("笔数量: {}", 观察员.笔序列.len());
|
||||
println!("笔中枢数量: {}", 观察员.笔_中枢序列.len());
|
||||
println!("线段数量: {}", 观察员.线段序列.len());
|
||||
println!("中枢数量: {}", 观察员.中枢序列.len());
|
||||
println!("扩展线段数量: {}", 观察员.扩展线段序列.len());
|
||||
println!("线段_线段序列数量: {}", 观察员.线段_线段序列.len());
|
||||
println!(
|
||||
"扩展线段_扩展线段数量: {}",
|
||||
观察员.扩展线段序列_扩展线段.len()
|
||||
);
|
||||
let 观察员 = 观察者::new("".into(), 0, 缠论配置::default());
|
||||
观察员
|
||||
.write()
|
||||
.unwrap()
|
||||
.读取数据文件(文件路径, 配置)
|
||||
.expect("读取数据文件失败");
|
||||
let 观察员 = 观察员.read().unwrap();
|
||||
let 消耗用时 = 启动时间.elapsed();
|
||||
println!(
|
||||
"测试_读取数据 耗时 {:.2?} 普K数量 {}",
|
||||
消耗用时,
|
||||
观察员.普通K线序列.len()
|
||||
);
|
||||
println!("符号: {}", 观察员.符号);
|
||||
println!("周期: {}", 观察员.周期);
|
||||
println!("缠K数量: {}", 观察员.缠论K线序列.len());
|
||||
println!("分型数量: {}", 观察员.分型序列.len());
|
||||
println!("笔数量: {}", 观察员.笔序列.len());
|
||||
println!("笔中枢数量: {}", 观察员.笔_中枢序列.len());
|
||||
println!("线段数量: {}", 观察员.线段序列.len());
|
||||
println!("中枢数量: {}", 观察员.中枢序列.len());
|
||||
println!("扩展线段数量: {}", 观察员.扩展线段序列.len());
|
||||
println!("线段_线段序列数量: {}", 观察员.线段_线段序列.len());
|
||||
println!(
|
||||
"扩展线段_扩展线段数量: {}",
|
||||
观察员.扩展线段序列_扩展线段.len()
|
||||
);
|
||||
|
||||
println!("\n===== 保存分析数据 =====\n");
|
||||
观察员.测试_保存数据(None);
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("读取失败: {}", e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
println!("\n===== 保存分析数据 =====\n");
|
||||
观察员.测试_保存数据(None);
|
||||
}
|
||||
|
||||
/// 测试_周期合成 — 多周期合成分析
|
||||
|
||||
@@ -435,8 +435,8 @@ 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;
|
||||
@@ -453,8 +453,8 @@ impl 虚线 {
|
||||
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()
|
||||
@@ -473,8 +473,8 @@ impl 虚线 {
|
||||
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()
|
||||
@@ -549,8 +549,8 @@ impl 虚线 {
|
||||
};
|
||||
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()
|
||||
@@ -583,7 +583,7 @@ 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.macd.as_ref().is_some_and(|m| m.MACD柱 > 0.0))
|
||||
.collect();
|
||||
if 柱子序列.is_empty() {
|
||||
return [false, false, false];
|
||||
@@ -603,7 +603,7 @@ impl 虚线 {
|
||||
.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].高 {
|
||||
@@ -649,7 +649,7 @@ 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.macd.as_ref().is_some_and(|m| m.MACD柱 < 0.0))
|
||||
.collect();
|
||||
if 柱子序列.is_empty() {
|
||||
return [false, false, false];
|
||||
@@ -670,7 +670,7 @@ impl 虚线 {
|
||||
.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].低 {
|
||||
@@ -947,16 +947,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 +1012,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线
|
||||
@@ -1236,46 +1283,3 @@ mod tests {
|
||||
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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,7 +173,7 @@ impl 线段特征 {
|
||||
|
||||
/// 新建特征序列元素
|
||||
pub fn 新建(虚线序列: Vec<Arc<虚线>>, 线段方向: 相对方向) -> Self {
|
||||
let 标识 = format!("特征<虚线>");
|
||||
let 标识 = "特征<虚线>".to_string();
|
||||
Self::new(标识, 虚线序列, 线段方向)
|
||||
}
|
||||
|
||||
@@ -320,13 +320,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(
|
||||
|
||||
@@ -92,6 +92,7 @@ impl 分型结构 {
|
||||
)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn 分析_内部(
|
||||
左高: f64,
|
||||
左低: f64,
|
||||
|
||||
@@ -1,3 +1,27 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2026 YuYuKunKun
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
|
||||
/// f64 原子类型 — 基于 AtomicU64 + 位转换,API 与 `Cell<f64>` 一致。
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+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