9 Commits

Author SHA1 Message Date
YuWuKunCheng c3e9ae8d77 Nothing v26.5.103 2026-05-30 22:34:10 +08:00
YuWuKunCheng b7c4e60420 1. thread_local! 导致跨线程缓存不可见(主要问题)
kline_py.rs 中 BSP_CACHE、KLINE_IDENTITY、BAR_IDENTITY 使用了 thread_local!。主线程调用 识别买卖点() →
  买卖点信息.add() 写入的是主线程的 PySet,backtrader 策略线程读取的是自己线程独立的空 PySet。

  修复:将三个缓存从 thread_local! 改为全局 static + std::sync::LazyLock<RwLock<HashMap<...>>>

  影响分析

  这些身份缓存的影响与 KLINE_IDENTITY/BAR_IDENTITY 不同——它们只影响 Python 对象身份(is
  比较),不直接影响数据内容(因为 Rust 数据通过 Arc 共享,读写都是同一份)。

  具体后果:
  - 不同线程访问同一个 Rust Arc 会得到不同的 Python wrapper 对象
  - a is b 跨线程比较返回 False,哪怕它们包装同一个底层 Rust 对象
  - 每个线程维护一份独立缓存,内存浪费(不过 wrapper 很小)
2026-05-30 22:15:09 +08:00
YuWuKunCheng 15dc44e8e0 回测测试 2026-05-30 20:06:59 +08:00
YuWuKunCheng bd4fceab02 全量支持 观察者在python端的重写机制 2026-05-30 15:51:18 +08:00
YuWuKunCheng af3ebf13c8 修复 “cargo clippy -- -D warnings” 产生的错误 2026-05-30 13:25:57 +08:00
YuWuKunCheng 22109d8b30 合并 工作流 2026-05-30 13:03:37 +08:00
YuWuKunCheng c2c09fc8ba 合并 工作流 2026-05-30 12:17:22 +08:00
YuWuKunCheng 0eb52cc06a 添加 自动发包 2026-05-30 11:56:06 +08:00
YuWuKunCheng 1e6025a968 添加 原始chan到模块
修复 相对方向的一致性
添加 观察者.投喂原始数据
2026-05-30 11:26:59 +08:00
31 changed files with 1086 additions and 788 deletions
+100 -8
View File
@@ -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: 下载所有产物
+6 -7
View File
@@ -3,7 +3,7 @@
[![PyPI](https://img.shields.io/pypi/v/chanlun)](https://pypi.org/project/chanlun/)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](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` 二进制文件格式(大端字节序)
## 许可
+30 -19
View File
@@ -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, 当前配置)
测试_读取数据(观察员, 当前配置)() # .测试_保存数据()
# 测试_周期合成(当前配置)().测试_保存数据()
+3 -3
View File
@@ -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
View File
@@ -1 +0,0 @@
../README.md
+77
View File
@@ -0,0 +1,77 @@
# chanlun — 缠论技术分析 Python 绑定
[![PyPI](https://img.shields.io/pypi/v/chanlun)](https://pypi.org/project/chanlun/)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](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
+2
View File
@@ -27,6 +27,8 @@ __all__ = [
"转化为时间戳",
"转化为时间戳_数字",
"随机指标",
"chan",
]
from ._chanlun import *
from . import chan
+28 -18
View File
@@ -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)
+1 -1
View File
@@ -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" }
+85 -90
View File
@@ -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() {
+69 -68
View File
@@ -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()?;
+6 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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 仍失败");
}
}
+88 -81
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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]);
+4 -4
View File
@@ -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(),
);
// 计算面积(绝对值求和)
+39 -38
View File
@@ -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.(),
)
}
}
+67 -83
View File
@@ -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().));
}
+71 -58
View File
@@ -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线序列 中
+1
View File
@@ -77,6 +77,7 @@ impl Default for 随机指标 {
impl {
/// 首次计算 KDJ(无历史数据时)
#[allow(clippy::too_many_arguments)]
pub fn (
: f64,
: f64,
+16
View File
@@ -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,
+41 -49
View File
@@ -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
View File
@@ -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);
}
/// 测试_周期合成 — 多周期合成分析
+70 -66
View File
@@ -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,
)
}
}
}
+9 -8
View File
@@ -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(
+1
View File
@@ -92,6 +92,7 @@ impl 分型结构 {
)
}
#[allow(clippy::too_many_arguments)]
fn _内部(
: f64,
: f64,
+24
View File
@@ -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>` 一致。
+3 -3
View File
@@ -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
View File
@@ -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线/一个数据点
如果没有新数据返回 FalseBacktrader会等待下次调用
"""
"""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.符号} | {文本}")
# ==================== 回测运行入口 ====================