6 Commits
Author SHA1 Message Date
YuWuKunCheng 9c972c2673 移除 配置兼容 2026-06-19 20:59:25 +08:00
YuWuKunCheng 0f69cfda79 Nothing 2026-06-16 11:57:19 +08:00
YuWuKunCheng e44f9dd837 修复 中枢完整性 2026-06-14 11:48:47 +08:00
YuWuKunCheng 1477cde86b 优化代码逻辑 2026-06-12 14:41:50 +08:00
YuWuKunCheng da5222d960 添加 全局变量“扩展线段模式” 默认为 True
线段 扩展分析采用“端点高, 端点低” 作为高低取值。中枢分析时高低取值不变,此举在中枢<扩展**> 中更贴合原文同级别分级时的中枢高低取值
2026-06-10 23:21:33 +08:00
YuWuKunCheng 15a1d43b1d Nothing 2026-06-09 15:13:49 +08:00
47 changed files with 4406 additions and 3242 deletions
+123 -49
View File
@@ -16,16 +16,19 @@ on:
env: env:
CARGO_TERM_COLOR: always CARGO_TERM_COLOR: always
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
jobs: jobs:
# ============================================================ # ============================================================
# 1. 发布 chanlun 核心库至 crates.io # 1. 校验 & 发布
# 解析 chanlun-py 依赖的版本号 → 检查 crates.io 是否可用 →
# 不可用时检查本地 chanlun 版本是否匹配 → 匹配则自动发布 →
# 等待索引同步
# ============================================================ # ============================================================
publish-crates: check-version:
runs-on: ubuntu-latest runs-on: ubuntu-latest
outputs: outputs:
version: ${{ steps.version.outputs.version }} version: ${{ steps.parse.outputs.version }}
exists: ${{ steps.check.outputs.exists }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@@ -34,72 +37,108 @@ jobs:
with: with:
components: rustfmt, clippy components: rustfmt, clippy
- name: 缓存依赖 - name: 解析 chanlun-py 依赖的 chanlun 版本
uses: actions/cache@v4 id: parse
with: working-directory: chanlun-py
path: | run: |
~/.cargo/registry VER=$(grep -oP 'chanlun\s*=\s*"=?\s*\K[0-9]+\.[0-9]+\.[0-9]+(?=")' Cargo.toml | head -1)
~/.cargo/git if [ -z "$VER" ]; then
target echo "::error::无法从 chanlun-py/Cargo.toml 解析 chanlun 版本号"
key: ${{ runner.os }}-cargo-${{ hashFiles('chanlun/Cargo.lock') }} echo "请确保 Cargo.toml 中包含: chanlun = \"=X.Y.Z\""
exit 1
fi
echo "version=$VER" >> $GITHUB_OUTPUT
echo "依赖的 chanlun 版本: $VER"
- name: 提取版本 - name: 解析本地 chanlun 核心库版本
id: version id: local-ver
working-directory: chanlun working-directory: chanlun
run: | run: |
VER=$(cargo metadata --format-version 1 --no-deps 2>/dev/null \ VER=$(grep -oP '^version\s*=\s*"\K[0-9]+\.[0-9]+\.[0-9]+(?=")' Cargo.toml | head -1)
| jq -r '.packages[] | select(.name == "chanlun") | .version')
echo "version=$VER" >> $GITHUB_OUTPUT echo "version=$VER" >> $GITHUB_OUTPUT
echo "当前版本: $VER" echo "本地 chanlun 版本: $VER"
- name: 检查版本是否已存在 - name: 检查 crates.io 并决定是否发布
id: check id: check
run: | run: |
VER="${{ steps.version.outputs.version }}" DEP_VER="${{ steps.parse.outputs.version }}"
EXISTS=$(curl -sS "https://crates.io/api/v1/crates/chanlun" \ LOCAL_VER="${{ steps.local-ver.outputs.version }}"
| jq -r --arg v "$VER" '.versions[]?.num // empty | select(. == $v)')
if [ -n "$EXISTS" ]; then HTTP_CODE=$(curl -sS -o /dev/null -w "%{http_code}" \
echo "版本 $VER 已存在于 crates.io,跳过发布" -H "User-Agent: chanlun-rs/ci" \
echo "exists=true" >> $GITHUB_OUTPUT "https://crates.io/api/v1/crates/chanlun/$DEP_VER")
else
echo "版本 $VER 未发布,继续" if [ "$HTTP_CODE" = "200" ]; then
echo "exists=false" >> $GITHUB_OUTPUT echo "chanlun $DEP_VER 在 crates.io 已可用,无需发布"
echo "need-publish=false" >> $GITHUB_OUTPUT
exit 0
fi fi
echo "chanlun $DEP_VER 在 crates.io 不存在 (HTTP $HTTP_CODE)"
if [ "$DEP_VER" != "$LOCAL_VER" ]; then
echo "::error::本地 chanlun 版本 ($LOCAL_VER) 与依赖版本 ($DEP_VER) 不匹配"
echo ""
echo "请先发布 chanlun 核心库至 crates.io:"
echo " cd chanlun && cargo publish"
echo ""
echo "或修改 chanlun-py/Cargo.toml 中的版本号为已发布版本"
exit 1
fi
echo "本地版本 $LOCAL_VER 与依赖一致,将自动发布 chanlun 至 crates.io"
echo "need-publish=true" >> $GITHUB_OUTPUT
- name: 格式检查 - name: 格式检查
if: steps.check.outputs.exists == 'false' if: steps.check.outputs.need-publish == 'true'
working-directory: chanlun working-directory: chanlun
run: cargo fmt --check run: cargo fmt --check
- name: Lint 检查 - name: Lint 检查
if: steps.check.outputs.exists == 'false' if: steps.check.outputs.need-publish == 'true'
working-directory: chanlun working-directory: chanlun
run: cargo clippy run: cargo clippy
- name: 运行测试 - name: 运行测试
if: steps.check.outputs.exists == 'false' if: steps.check.outputs.need-publish == 'true'
working-directory: chanlun working-directory: chanlun
run: cargo test run: cargo test
- name: 验证打包 - name: 验证打包
if: steps.check.outputs.exists == 'false' if: steps.check.outputs.need-publish == 'true'
working-directory: chanlun working-directory: chanlun
run: cargo publish --dry-run --allow-dirty run: cargo publish --dry-run --allow-dirty
- name: 登录 crates.io - name: 登录 crates.io 并发布
if: steps.check.outputs.exists == 'false' if: steps.check.outputs.need-publish == 'true'
run: cargo login ${{ secrets.CARGO_TOKEN }} run: |
cargo login ${{ secrets.CARGO_TOKEN }}
cd chanlun && cargo publish --allow-dirty
- name: 发布 chanlun 至 crates.io - name: 等待 crates.io 索引同步
if: steps.check.outputs.exists == 'false' if: steps.check.outputs.need-publish == 'true'
working-directory: chanlun run: |
run: cargo publish --allow-dirty DEP_VER="${{ steps.parse.outputs.version }}"
echo "等待 crates.io 索引同步 (最多 2 分钟)..."
for i in $(seq 1 12); do
HTTP_CODE=$(curl -sS -o /dev/null -w "%{http_code}" \
-H "User-Agent: chanlun-rs/ci" \
"https://crates.io/api/v1/crates/chanlun/$DEP_VER")
if [ "$HTTP_CODE" = "200" ]; then
echo "chanlun $DEP_VER 已在 crates.io 可用 (尝试 $i/12)"
exit 0
fi
echo " 等待中... ($i/12)"
sleep 10
done
echo "::error::等待超时:chanlun $DEP_VER 在 crates.io 仍不可用"
exit 1
# ============================================================ # ============================================================
# 2. 构建 wheel — Linux x86_64 (manylinux) # 2. 构建 wheel — Linux x86_64 (manylinux)
# ============================================================ # ============================================================
linux-x86_64: linux-x86_64:
needs: [publish-crates] needs: [check-version]
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@@ -112,9 +151,19 @@ jobs:
- name: 安装 Rust 工具链 - name: 安装 Rust 工具链
uses: dtolnay/rust-toolchain@stable uses: dtolnay/rust-toolchain@stable
- name: 更新 cargo 索引(确保新版本可见 - name: 更新 cargo 索引(含重试
run: cargo update
working-directory: chanlun-py working-directory: chanlun-py
run: |
for i in $(seq 1 6); do
if cargo update 2>&1; then
echo "cargo update 成功"
exit 0
fi
echo "cargo update 失败,重试... ($i/6)"
sleep 10
done
echo "::error::cargo update 失败"
exit 1
- name: 构建 wheel (manylinux) - name: 构建 wheel (manylinux)
uses: PyO3/maturin-action@v1 uses: PyO3/maturin-action@v1
@@ -134,7 +183,7 @@ jobs:
# 3. 构建 wheel — macOS (x86_64 + arm64) # 3. 构建 wheel — macOS (x86_64 + arm64)
# ============================================================ # ============================================================
macos: macos:
needs: [publish-crates] needs: [check-version]
runs-on: macos-latest runs-on: macos-latest
strategy: strategy:
matrix: matrix:
@@ -150,9 +199,19 @@ jobs:
with: with:
python-version: '3.12' python-version: '3.12'
- name: 更新 cargo 索引 - name: 更新 cargo 索引(含重试)
run: cargo update
working-directory: chanlun-py working-directory: chanlun-py
run: |
for i in $(seq 1 6); do
if cargo update 2>&1; then
echo "cargo update 成功"
exit 0
fi
echo "cargo update 失败,重试... ($i/6)"
sleep 10
done
echo "::error::cargo update 失败"
exit 1
- name: 构建 wheel - name: 构建 wheel
uses: PyO3/maturin-action@v1 uses: PyO3/maturin-action@v1
@@ -171,7 +230,7 @@ jobs:
# 4. 构建 wheel — Windows x86_64 # 4. 构建 wheel — Windows x86_64
# ============================================================ # ============================================================
windows: windows:
needs: [publish-crates] needs: [check-version]
runs-on: windows-latest runs-on: windows-latest
strategy: strategy:
matrix: matrix:
@@ -187,9 +246,21 @@ jobs:
with: with:
python-version: '3.12' python-version: '3.12'
- name: 更新 cargo 索引 - name: 更新 cargo 索引(含重试)
run: cargo update
working-directory: chanlun-py working-directory: chanlun-py
shell: pwsh
run: |
for ($i = 1; $i -le 6; $i++) {
cargo update
if ($LASTEXITCODE -eq 0) {
Write-Host "cargo update 成功"
exit 0
}
Write-Host "cargo update 失败,重试... ($i/6)"
Start-Sleep -Seconds 10
}
Write-Host "::error::cargo update 失败"
exit 1
- name: 构建 wheel - name: 构建 wheel
uses: PyO3/maturin-action@v1 uses: PyO3/maturin-action@v1
@@ -208,7 +279,7 @@ jobs:
# 5. 源码分发包 (sdist) # 5. 源码分发包 (sdist)
# ============================================================ # ============================================================
sdist: sdist:
needs: [publish-crates] needs: [check-version]
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@@ -218,6 +289,9 @@ jobs:
with: with:
python-version: '3.12' python-version: '3.12'
- name: 安装 Rust 工具链
uses: dtolnay/rust-toolchain@stable
- name: 构建 sdist - name: 构建 sdist
uses: PyO3/maturin-action@v1 uses: PyO3/maturin-action@v1
with: with:
@@ -235,7 +309,7 @@ jobs:
# 6. 发布至 PyPI # 6. 发布至 PyPI
# ============================================================ # ============================================================
publish: publish:
needs: [publish-crates, linux-x86_64, macos, windows, sdist] needs: [linux-x86_64, macos, windows, sdist]
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: startsWith(github.ref, 'refs/tags/v') || github.event.inputs.publish-to-pypi == 'true' if: startsWith(github.ref, 'refs/tags/v') || github.event.inputs.publish-to-pypi == 'true'
permissions: permissions:
+10
View File
@@ -40,6 +40,10 @@ analyzer = chanlun.立体分析器("BTCUSD", [60, 60*5, 60*5*6], config)
```bash ```bash
pip install maturin pip install maturin
# 推荐:一键清理缓存 + 构建 + 安装
./clean_install.sh
# 或手动:
# 开发模式(直接安装到当前 venv) # 开发模式(直接安装到当前 venv)
maturin develop maturin develop
@@ -48,6 +52,12 @@ maturin build --release
pip install target/wheels/chanlun-*.whl pip install target/wheels/chanlun-*.whl
``` ```
> **注意**:若修改了 `chan.py`,安装前需清除 `__pycache__`,否则旧 `.pyc` 会被打包进 wheel 导致修改不生效:
> ```bash
> find . -type d -name __pycache__ -exec rm -rf {} + 2>/dev/null
> find . -type f -name "*.pyc" -delete 2>/dev/null
> ```
也可使用项目内的 `build.sh`: 也可使用项目内的 `build.sh`:
```bash ```bash
+460 -307
View File
File diff suppressed because it is too large Load Diff
+7 -6
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "chanlun-py" name = "chanlun-py"
version = "26.6.47" version = "26.6.87"
edition = "2024" edition = "2024"
description = "缠论技术分析库 — Rust 高性能 Python 绑定" description = "缠论技术分析库 — Rust 高性能 Python 绑定"
authors = ["YuYuKunKun"] authors = ["YuYuKunKun"]
@@ -12,11 +12,12 @@ crate-type = ["cdylib"]
name = "chanlun" name = "chanlun"
[dependencies] [dependencies]
chanlun = "26.6.3" # { path = "../chanlun" } chanlun = { path = "../chanlun" }
lru = "0.18" parking_lot = "0.12"
tracing-subscriber = { version = "0.3", features = ["env-filter", "ansi", "std", "registry"] }
tracing-core = "0.1"
dashmap = "6"
tracing = "0.1"
pyo3 = { version = "0.28", features = ["experimental-inspect"] } pyo3 = { version = "0.28", features = ["experimental-inspect"] }
serde_json = "1" serde_json = "1"
chrono = "0.4" chrono = "0.4"
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter", "ansi", "std", "registry"] }
tracing-core = "0.1"
+71
View File
@@ -72,6 +72,77 @@ pip install target/wheels/chanlun-*.whl
- 类名 / 方法名 / 字段名与 `chan.py` 保持一致 - 类名 / 方法名 / 字段名与 `chan.py` 保持一致
- 支持 `.nb` 二进制文件格式(大端字节序) - 支持 `.nb` 二进制文件格式(大端字节序)
## 性能配置
### 缓存模式
Python 对象缓存有两种模式,通过环境变量 `CHANLUN_CACHE_MODE` 或函数调用切换:
```python
from chanlun._chanlun import set_cache_mode, get_cache_mode
# 默认:thread_local,每线程独立缓存,零锁,多线程场景最佳
print(get_cache_mode()) # "thread_local"
# 全局:dashmap 分片哈希表,跨线程 Python `is` 身份一致
set_cache_mode("global") # 必须在创建任何观察者之前调用
```
```bash
# 环境变量方式
CHANLUN_CACHE_MODE=global python main.py # 全局缓存
python main.py # 默认:线程局部缓存
```
| 模式 | 性能 | Python `is` 跨线程 | 适用场景 |
|------|------|---------------------|----------|
| `thread_local`(默认) | 零锁,最快 | 否 | 批量回测、多线程独立分析 |
| `global` | dashmap 分片锁 | 是 | 测试验证、跨线程对象共享 |
### 日志模式
日志输出有三种模式,通过环境变量 `CHANLUN_LOG_MODE` 或函数调用切换:
```python
from chanlun._chanlun import set_log_mode, set_log_level, get_log_mode
# 默认:off,不输出,零开销
print(get_log_mode()) # "off"
# 简单模式:直接 eprintln/println
set_log_mode("simple")
set_log_level("debug") # 必需:设置日志级别启用输出
# Tracing 模式:带时间戳和文件位置格式化输出
set_log_mode("tracing")
set_log_level("debug")
```
```bash
# 环境变量方式
CHANLUN_LOG_MODE=simple python main.py # 简单输出
CHANLUN_LOG_MODE=tracing python main.py # 格式化输出
python main.py # 默认:静默
```
| 模式 | 输出方式 | 性能 | 格式 |
|------|---------|------|------|
| `off`(默认) | 无 | 零开销 | — |
| `simple` | `eprintln!` / `println!` | 极轻 | 纯文本 |
| `tracing` | tracing-subscriber | 稍重 | `2026-06-12 01:57:59.942 WARN file.rs:line` |
### 观察者直传(避免 Python list 转换)
背驰分析新增 `_OBS` 后缀方法,直接接受观察者引用,跳过 `list[K线]``Vec<Arc<K线>>` 转换:
```python
# 旧方式:构建 Python 列表
result = 背驰分析.MACD背驰(进入段, 离开段, obs.普通K线序列, "")
# 新方式:直接传观察者
result = 背驰分析.MACD背驰_OBS(进入段, 离开段, obs, "")
```
## 许可 ## 许可
本项目主体采用 MIT 许可。包含以下第三方开源代码:czsc(Apache 2.0)、parseMIT)、termcolorMIT)。 本项目主体采用 MIT 许可。包含以下第三方开源代码:czsc(Apache 2.0)、parseMIT)、termcolorMIT)。
+53 -4
View File
@@ -5,11 +5,18 @@ from typing import Any, ClassVar, Optional, List, Dict, Tuple, Union
from datetime import datetime from datetime import datetime
# ========== Module-level functions ========== # ========== Module-level functions ==========
def get_rs_log_level() -> str: ...
def set_rs_log_level(level: str) -> None: ...
def get_log_level() -> str: ... def get_log_level() -> str: ...
def set_log_level(level: str) -> None: ... def set_log_level(level: str) -> None: ...
def get_log_mode() -> str: ...
def set_log_mode(mode: str) -> None: ...
def get_cache_mode() -> str: ...
def set_cache_mode(mode: str) -> None: ...
def get_分型模式() -> bool: ... def get_分型模式() -> bool: ...
def set_分型模式(value: bool) -> None: ... def set_分型模式(value: bool) -> None: ...
def get_扩展线段模式() -> bool: ...
def set_扩展线段模式(value: bool) -> None: ...
def 转化为时间戳(ts: Any) -> int: ... def 转化为时间戳(ts: Any) -> int: ...
def 转化为时间戳_数字(ts: Any) -> int: ... def 转化为时间戳_数字(ts: Any) -> int: ...
def K线相等(A: K线, B: K线, 浮点容差: float = 1e-9) -> Tuple[bool, str]: ... def K线相等(A: K线, B: K线, 浮点容差: float = 1e-9) -> Tuple[bool, str]: ...
@@ -75,6 +82,8 @@ class 相对方向:
def 翻转(self) -> 相对方向: ... def 翻转(self) -> 相对方向: ...
@classmethod @classmethod
def 分析(cls, 前高: float, 前低: float, 后高: float, 后低: float) -> 相对方向: ... def 分析(cls, 前高: float, 前低: float, 后高: float, 后低: float) -> 相对方向: ...
@classmethod
def 从序列中机选(cls, 数量: int, 可选方向: List[相对方向], 可重复: bool = True) -> List[相对方向]: ...
def __str__(self) -> str: ... def __str__(self) -> str: ...
def __repr__(self) -> str: ... def __repr__(self) -> str: ...
def __hash__(self) -> int: ... def __hash__(self) -> int: ...
@@ -326,6 +335,7 @@ class K线:
def 截取(序列: List[K线], : K线, : K线) -> List[K线]: ... def 截取(序列: List[K线], : K线, : K线) -> List[K线]: ...
def __str__(self) -> str: ... def __str__(self) -> str: ...
def __repr__(self) -> str: ... def __repr__(self) -> str: ...
def 根据当前K线生成新K线(self, 方向: 相对方向, 居中: bool = False) -> K线: ...
def __bytes__(self) -> bytes: ... def __bytes__(self) -> bytes: ...
def __eq__(self, other: Any) -> bool: ... def __eq__(self, other: Any) -> bool: ...
def __hash__(self) -> int: ... def __hash__(self) -> int: ...
@@ -438,9 +448,9 @@ class 虚线:
@property @property
def 模式(self) -> str: ... def 模式(self) -> str: ...
@property @property
def 特征序列_显示(self) -> bool: ... def _特征序列_显示(self) -> bool: ...
@特征序列_显示.setter @_特征序列_显示.setter
def 特征序列_显示(self, value: bool) -> None: ... def _特征序列_显示(self, value: bool) -> None: ...
@property @property
def 特征序列(self) -> List[Optional[线段特征]]: ... def 特征序列(self) -> List[Optional[线段特征]]: ...
@property @property
@@ -526,6 +536,8 @@ class 虚线:
class 线段特征: class 线段特征:
@property @property
def 序号(self) -> int: ... def 序号(self) -> int: ...
@序号.setter
def 序号(self, value: int) -> None: ...
@property @property
def 标识(self) -> str: ... def 标识(self) -> str: ...
@标识.setter @标识.setter
@@ -570,6 +582,18 @@ class 背驰分析:
def 任选背驰(cls, 进入段: 虚线, 离开段: 虚线, 普K序列: List[K线]) -> bool: ... def 任选背驰(cls, 进入段: 虚线, 离开段: 虚线, 普K序列: List[K线]) -> bool: ...
@classmethod @classmethod
def 背驰模式(cls, 进入段: 虚线, 离开段: 虚线, 普K序列: List[K线], 配置: 缠论配置, 模式: str) -> bool: ... def 背驰模式(cls, 进入段: 虚线, 离开段: 虚线, 普K序列: List[K线], 配置: 缠论配置, 模式: str) -> bool: ...
@classmethod
def MACD背驰_OBS(cls, 进入段: 虚线, 离开段: 虚线, 观察员: 观察者, 方式: str = "") -> bool: ...
@classmethod
def 全量背驰_OBS(cls, 进入段: 虚线, 离开段: 虚线, 观察员: 观察者) -> bool: ...
@classmethod
def 任意背驰_OBS(cls, 进入段: 虚线, 离开段: 虚线, 观察员: 观察者) -> bool: ...
@classmethod
def 配置背驰_OBS(cls, 进入段: 虚线, 离开段: 虚线, 观察员: 观察者, 配置: 缠论配置) -> bool: ...
@classmethod
def 任选背驰_OBS(cls, 进入段: 虚线, 离开段: 虚线, 观察员: 观察者) -> bool: ...
@classmethod
def 背驰模式_OBS(cls, 进入段: 虚线, 离开段: 虚线, 观察员: 观察者, 配置: 缠论配置, 模式: str) -> bool: ...
class : class :
@classmethod @classmethod
@@ -793,6 +817,30 @@ class 观察者:
def 扩展线段序列_扩展线段(self) -> List[虚线]: ... def 扩展线段序列_扩展线段(self) -> List[虚线]: ...
@property @property
def 扩展中枢序列_扩展线段(self) -> List[中枢]: ... def 扩展中枢序列_扩展线段(self) -> List[中枢]: ...
@property
def 线段分析层次(self) -> int: ...
@线段分析层次.setter
def 线段分析层次(self, value: int) -> None: ...
@property
def 扩展线段分析层次(self) -> int: ...
@扩展线段分析层次.setter
def 扩展线段分析层次(self, value: int) -> None: ...
@property
def 混合扩展线段分析层次(self) -> int: ...
@混合扩展线段分析层次.setter
def 混合扩展线段分析层次(self, value: int) -> None: ...
@property
def 线段序列组(self) -> List[List[虚线]]: ...
@property
def 中枢序列组(self) -> List[List[中枢]]: ...
@property
def 扩展线段序列组(self) -> List[List[虚线]]: ...
@property
def 扩展中枢序列组(self) -> List[List[中枢]]: ...
@property
def 混合扩展线段序列组(self) -> List[List[虚线]]: ...
@property
def 混合扩展中枢序列组(self) -> List[List[中枢]]: ...
def 重置基础序列(self) -> None: ... def 重置基础序列(self) -> None: ...
def 增加原始K线(self, 普K: K线) -> None: ... def 增加原始K线(self, 普K: K线) -> None: ...
def 投喂原始数据(self, 时间戳: int, : float, : float, : float, : float, : float) -> None: ... def 投喂原始数据(self, 时间戳: int, : float, : float, : float, : float, : float) -> None: ...
@@ -838,6 +886,7 @@ class 缠论配置:
def from_json(cls, json_str: str) -> 缠论配置: ... def from_json(cls, json_str: str) -> 缠论配置: ...
@classmethod @classmethod
def 不推送(cls) -> 缠论配置: ... def 不推送(cls) -> 缠论配置: ...
def 展示标签(self, 标签: str) -> bool: ...
@classmethod @classmethod
def 按序号重组字典(cls, 默认配置: Any, 原始字典: Dict[str, Any]) -> Dict[str, Any]: ... def 按序号重组字典(cls, 默认配置: Any, 原始字典: Dict[str, Any]) -> Dict[str, Any]: ...
def __str__(self) -> str: ... def __str__(self) -> str: ...
+2
View File
@@ -32,6 +32,8 @@ __all__ = [
"布林带", "布林带",
"get_分型模式", "get_分型模式",
"set_分型模式", "set_分型模式",
"get_扩展线段模式",
"set_扩展线段模式",
"get_log_level", "get_log_level",
"set_log_level", "set_log_level",
"get_rs_log_level", "get_rs_log_level",
+24
View File
@@ -9,8 +9,16 @@ def get_rs_log_level() -> str: ...
def set_rs_log_level(level: str) -> None: ... def set_rs_log_level(level: str) -> None: ...
def get_log_level() -> str: ... def get_log_level() -> str: ...
def set_log_level(level: str) -> None: ... def set_log_level(level: str) -> None: ...
def get_log_mode() -> str: ...
def set_log_mode(mode: str) -> None: ...
def get_cache_mode() -> str: ...
def set_cache_mode(mode: str) -> None: ...
def get_分型模式() -> bool: ... def get_分型模式() -> bool: ...
def set_分型模式(value: bool) -> None: ... def set_分型模式(value: bool) -> None: ...
def get_扩展线段模式() -> bool: ...
def set_扩展线段模式(value: bool) -> None: ...
def 转化为时间戳(ts: Any) -> int: ...
def 转化为时间戳_数字(ts: Any) -> int: ...
def K线相等(A: K线, B: K线, 浮点容差: float = 1e-9) -> Tuple[bool, str]: ... def K线相等(A: K线, B: K线, 浮点容差: float = 1e-9) -> Tuple[bool, str]: ...
def 缠论K线相等(A: 缠论K线, B: 缠论K线, 浮点容差: float = 1e-9) -> Tuple[bool, str]: ... def 缠论K线相等(A: 缠论K线, B: 缠论K线, 浮点容差: float = 1e-9) -> Tuple[bool, str]: ...
def 分型相等(A: 分型, B: 分型, 浮点容差: float = 1e-9) -> Tuple[bool, str]: ... def 分型相等(A: 分型, B: 分型, 浮点容差: float = 1e-9) -> Tuple[bool, str]: ...
@@ -74,6 +82,8 @@ class 相对方向:
def 翻转(self) -> 相对方向: ... def 翻转(self) -> 相对方向: ...
@classmethod @classmethod
def 分析(cls, 前高: float, 前低: float, 后高: float, 后低: float) -> 相对方向: ... def 分析(cls, 前高: float, 前低: float, 后高: float, 后低: float) -> 相对方向: ...
@classmethod
def 从序列中机选(cls, 数量: int, 可选方向: List[相对方向], 可重复: bool = True) -> List[相对方向]: ...
def __str__(self) -> str: ... def __str__(self) -> str: ...
def __repr__(self) -> str: ... def __repr__(self) -> str: ...
def __hash__(self) -> int: ... def __hash__(self) -> int: ...
@@ -325,6 +335,7 @@ class K线:
def 截取(序列: List[K线], : K线, : K线) -> List[K线]: ... def 截取(序列: List[K线], : K线, : K线) -> List[K线]: ...
def __str__(self) -> str: ... def __str__(self) -> str: ...
def __repr__(self) -> str: ... def __repr__(self) -> str: ...
def 根据当前K线生成新K线(self, 方向: 相对方向, 居中: bool = False) -> K线: ...
def __bytes__(self) -> bytes: ... def __bytes__(self) -> bytes: ...
def __eq__(self, other: Any) -> bool: ... def __eq__(self, other: Any) -> bool: ...
def __hash__(self) -> int: ... def __hash__(self) -> int: ...
@@ -571,6 +582,18 @@ class 背驰分析:
def 任选背驰(cls, 进入段: 虚线, 离开段: 虚线, 普K序列: List[K线]) -> bool: ... def 任选背驰(cls, 进入段: 虚线, 离开段: 虚线, 普K序列: List[K线]) -> bool: ...
@classmethod @classmethod
def 背驰模式(cls, 进入段: 虚线, 离开段: 虚线, 普K序列: List[K线], 配置: 缠论配置, 模式: str) -> bool: ... def 背驰模式(cls, 进入段: 虚线, 离开段: 虚线, 普K序列: List[K线], 配置: 缠论配置, 模式: str) -> bool: ...
@classmethod
def MACD背驰_OBS(cls, 进入段: 虚线, 离开段: 虚线, 观察员: 观察者, 方式: str = "") -> bool: ...
@classmethod
def 全量背驰_OBS(cls, 进入段: 虚线, 离开段: 虚线, 观察员: 观察者) -> bool: ...
@classmethod
def 任意背驰_OBS(cls, 进入段: 虚线, 离开段: 虚线, 观察员: 观察者) -> bool: ...
@classmethod
def 配置背驰_OBS(cls, 进入段: 虚线, 离开段: 虚线, 观察员: 观察者, 配置: 缠论配置) -> bool: ...
@classmethod
def 任选背驰_OBS(cls, 进入段: 虚线, 离开段: 虚线, 观察员: 观察者) -> bool: ...
@classmethod
def 背驰模式_OBS(cls, 进入段: 虚线, 离开段: 虚线, 观察员: 观察者, 配置: 缠论配置, 模式: str) -> bool: ...
class : class :
@classmethod @classmethod
@@ -863,6 +886,7 @@ class 缠论配置:
def from_json(cls, json_str: str) -> 缠论配置: ... def from_json(cls, json_str: str) -> 缠论配置: ...
@classmethod @classmethod
def 不推送(cls) -> 缠论配置: ... def 不推送(cls) -> 缠论配置: ...
def 展示标签(self, 标签: str) -> bool: ...
@classmethod @classmethod
def 按序号重组字典(cls, 默认配置: Any, 原始字典: Dict[str, Any]) -> Dict[str, Any]: ... def 按序号重组字典(cls, 默认配置: Any, 原始字典: Dict[str, Any]) -> Dict[str, Any]: ...
def __str__(self) -> str: ... def __str__(self) -> str: ...
+462 -309
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "maturin"
[project] [project]
name = "chanlun" name = "chanlun"
version = "2606.47" version = "2606.87"
description = "缠论技术分析库 — Rust 高性能实现" description = "缠论技术分析库 — Rust 高性能实现"
readme = { file = "README.md", content-type = "text/markdown" } readme = { file = "README.md", content-type = "text/markdown" }
license = { file = "LICENSE", content-type = "text/plain" } license = { file = "LICENSE", content-type = "text/plain" }
+266 -89
View File
@@ -26,33 +26,20 @@ use crate::kline_py::chan_kline_to_py;
use crate::structure_py::{dashed_to_py, fractal_to_py}; use crate::structure_py::{dashed_to_py, fractal_to_py};
use pyo3::prelude::*; use pyo3::prelude::*;
use pyo3::types::{PyDict, PyList, PyType}; use pyo3::types::{PyDict, PyList, PyType};
use std::collections::HashMap;
use std::sync::Arc; use std::sync::Arc;
use std::sync::RwLock;
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
// 使用全局 static 而非 thread_local!,保证跨线程对象标识一致性 // 缓存通过 crate::cache 模块管理(支持 thread_local / global 运行时切换)
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( pub(crate) fn hub_to_py(
py: Python<'_>, inner: Arc<chanlun::algorithm::hub::> py: Python<'_>, inner: Arc<chanlun::algorithm::hub::>
) -> Py<Py> { ) -> Py<Py> {
let key = Arc::as_ptr(&inner) as usize; let key = Arc::as_ptr(&inner) as usize;
if let Some(cached) = HUB_IDENTITY if let Some(cached) = crate::cache::hub_get(py, key) {
.read()
.unwrap()
.get(&key)
.map(|p| p.clone_ref(py))
{
return cached; return cached;
} }
HUB_IDENTITY
.write()
.unwrap()
.retain(|_, v| v.get_refcnt(py) > 1);
let obj = Py::new(py, Py { inner }).unwrap(); let obj = Py::new(py, Py { inner }).unwrap();
HUB_IDENTITY.write().unwrap().insert(key, obj.clone_ref(py)); crate::cache::hub_insert(py, key, &obj);
obj obj
} }
@@ -90,16 +77,21 @@ impl 背驰分析Py {
: &str, : &str,
py: Python<'_>, py: Python<'_>,
) -> bool { ) -> bool {
let _inner = Arc::clone(&.borrow().inner);
let _inner = Arc::clone(&.borrow().inner);
let = .to_string();
let rc_list: Vec<Arc<chanlun::kline::bar::K线>> = K线序列 let rc_list: Vec<Arc<chanlun::kline::bar::K线>> = K线序列
.iter() .iter()
.map(|k| k.bind(py).borrow().inner.clone()) .map(|k| k.bind(py).borrow().inner.clone())
.collect(); .collect();
chanlun::algorithm::divergence::::MACD背驰( py.detach(move || {
&.borrow().inner, chanlun::algorithm::divergence::::MACD背驰(
&.borrow().inner, &_inner,
&rc_list, &_inner,
, &rc_list,
) &,
)
})
} }
#[classmethod] #[classmethod]
@@ -137,15 +129,19 @@ impl 背驰分析Py {
K序列: Vec<Py<K线Py>>, K序列: Vec<Py<K线Py>>,
py: Python<'_>, py: Python<'_>,
) -> bool { ) -> bool {
let _inner = Arc::clone(&.borrow().inner);
let _inner = Arc::clone(&.borrow().inner);
let rc_list: Vec<Arc<chanlun::kline::bar::K线>> = K序列 let rc_list: Vec<Arc<chanlun::kline::bar::K线>> = K序列
.iter() .iter()
.map(|k| k.bind(py).borrow().inner.clone()) .map(|k| k.bind(py).borrow().inner.clone())
.collect(); .collect();
chanlun::algorithm::divergence::::( py.detach(move || {
&.borrow().inner, chanlun::algorithm::divergence::::(
&.borrow().inner, &_inner,
&rc_list, &_inner,
) &rc_list,
)
})
} }
#[classmethod] #[classmethod]
@@ -157,15 +153,19 @@ impl 背驰分析Py {
K序列: Vec<Py<K线Py>>, K序列: Vec<Py<K线Py>>,
py: Python<'_>, py: Python<'_>,
) -> bool { ) -> bool {
let _inner = Arc::clone(&.borrow().inner);
let _inner = Arc::clone(&.borrow().inner);
let rc_list: Vec<Arc<chanlun::kline::bar::K线>> = K序列 let rc_list: Vec<Arc<chanlun::kline::bar::K线>> = K序列
.iter() .iter()
.map(|k| k.bind(py).borrow().inner.clone()) .map(|k| k.bind(py).borrow().inner.clone())
.collect(); .collect();
chanlun::algorithm::divergence::::( py.detach(move || {
&.borrow().inner, chanlun::algorithm::divergence::::(
&.borrow().inner, &_inner,
&rc_list, &_inner,
) &rc_list,
)
})
} }
#[classmethod] #[classmethod]
@@ -178,17 +178,21 @@ impl 背驰分析Py {
: &Bound<'_, Py>, : &Bound<'_, Py>,
py: Python<'_>, py: Python<'_>,
) -> PyResult<bool> { ) -> PyResult<bool> {
let _inner = Arc::clone(&.borrow().inner);
let _inner = Arc::clone(&.borrow().inner);
let rc_list: Vec<Arc<chanlun::kline::bar::K线>> = K序列 let rc_list: Vec<Arc<chanlun::kline::bar::K线>> = K序列
.iter() .iter()
.map(|k| k.bind(py).borrow().inner.clone()) .map(|k| k.bind(py).borrow().inner.clone())
.collect(); .collect();
let config = .borrow().to_rust_config(py)?; let config = .borrow().to_rust_config(py)?;
Ok(chanlun::algorithm::divergence::::( Ok(py.detach(move || {
&.borrow().inner, chanlun::algorithm::divergence::::(
&.borrow().inner, &_inner,
&rc_list, &_inner,
&config, &rc_list,
)) &config,
)
}))
} }
#[classmethod] #[classmethod]
@@ -200,15 +204,19 @@ impl 背驰分析Py {
K序列: Vec<Py<K线Py>>, K序列: Vec<Py<K线Py>>,
py: Python<'_>, py: Python<'_>,
) -> bool { ) -> bool {
let _inner = Arc::clone(&.borrow().inner);
let _inner = Arc::clone(&.borrow().inner);
let rc_list: Vec<Arc<chanlun::kline::bar::K线>> = K序列 let rc_list: Vec<Arc<chanlun::kline::bar::K线>> = K序列
.iter() .iter()
.map(|k| k.bind(py).borrow().inner.clone()) .map(|k| k.bind(py).borrow().inner.clone())
.collect(); .collect();
chanlun::algorithm::divergence::::( py.detach(move || {
&.borrow().inner, chanlun::algorithm::divergence::::(
&.borrow().inner, &_inner,
&rc_list, &_inner,
) &rc_list,
)
})
} }
#[classmethod] #[classmethod]
@@ -222,18 +230,169 @@ impl 背驰分析Py {
: &str, : &str,
py: Python<'_>, py: Python<'_>,
) -> PyResult<bool> { ) -> PyResult<bool> {
let _inner = Arc::clone(&.borrow().inner);
let _inner = Arc::clone(&.borrow().inner);
let rc_list: Vec<Arc<chanlun::kline::bar::K线>> = K序列 let rc_list: Vec<Arc<chanlun::kline::bar::K线>> = K序列
.iter() .iter()
.map(|k| k.bind(py).borrow().inner.clone()) .map(|k| k.bind(py).borrow().inner.clone())
.collect(); .collect();
let config = .borrow().to_rust_config(py)?; let config = .borrow().to_rust_config(py)?;
Ok(chanlun::algorithm::divergence::::( let = .to_string();
&.borrow().inner, Ok(py.detach(move || {
&.borrow().inner, chanlun::algorithm::divergence::::(
&rc_list, &_inner,
&config, &_inner,
, &rc_list,
)) &config,
&,
)
}))
}
// ---- 观察者直传(跳过 Python list→Vec 转换,直接借用观察者内部 &[Arc<K线>] ----
#[classmethod]
#[pyo3(name = "MACD背驰_OBS", signature = (进入段, 离开段, 观察员, 方式 = ""))]
fn MACD背驰_obs(
_cls: &Bound<'_, PyType>,
: &Bound<'_, 线Py>,
: &Bound<'_, 线Py>,
: &Bound<'_, Py>,
: &str,
py: Python<'_>,
) -> bool {
let _inner = Arc::clone(&.borrow().inner);
let _inner = Arc::clone(&.borrow().inner);
let = .to_string();
let obs_arc = .borrow().inner.clone().expect("观察者未初始化");
py.detach(move || {
let guard = obs_arc.read();
chanlun::algorithm::divergence::::MACD背驰(
&_inner,
&_inner,
&guard.K线序列,
&,
)
})
}
#[classmethod]
#[pyo3(name = "全量背驰_OBS", signature = (进入段, 离开段, 观察员))]
fn _obs(
_cls: &Bound<'_, PyType>,
: &Bound<'_, 线Py>,
: &Bound<'_, 线Py>,
: &Bound<'_, Py>,
py: Python<'_>,
) -> bool {
let _inner = Arc::clone(&.borrow().inner);
let _inner = Arc::clone(&.borrow().inner);
let obs_arc = .borrow().inner.clone().expect("观察者未初始化");
py.detach(move || {
let guard = obs_arc.read();
chanlun::algorithm::divergence::::(
&_inner,
&_inner,
&guard.K线序列,
)
})
}
#[classmethod]
#[pyo3(name = "任意背驰_OBS", signature = (进入段, 离开段, 观察员))]
fn _obs(
_cls: &Bound<'_, PyType>,
: &Bound<'_, 线Py>,
: &Bound<'_, 线Py>,
: &Bound<'_, Py>,
py: Python<'_>,
) -> bool {
let _inner = Arc::clone(&.borrow().inner);
let _inner = Arc::clone(&.borrow().inner);
let obs_arc = .borrow().inner.clone().expect("观察者未初始化");
py.detach(move || {
let guard = obs_arc.read();
chanlun::algorithm::divergence::::(
&_inner,
&_inner,
&guard.K线序列,
)
})
}
#[classmethod]
#[pyo3(name = "配置背驰_OBS", signature = (进入段, 离开段, 观察员, 配置))]
fn _obs(
_cls: &Bound<'_, PyType>,
: &Bound<'_, 线Py>,
: &Bound<'_, 线Py>,
: &Bound<'_, Py>,
: &Bound<'_, Py>,
py: Python<'_>,
) -> PyResult<bool> {
let _inner = Arc::clone(&.borrow().inner);
let _inner = Arc::clone(&.borrow().inner);
let config = .borrow().to_rust_config(py)?;
let obs_arc = .borrow().inner.clone().expect("观察者未初始化");
Ok(py.detach(move || {
let guard = obs_arc.read();
chanlun::algorithm::divergence::::(
&_inner,
&_inner,
&guard.K线序列,
&config,
)
}))
}
#[classmethod]
#[pyo3(name = "任选背驰_OBS", signature = (进入段, 离开段, 观察员))]
fn _obs(
_cls: &Bound<'_, PyType>,
: &Bound<'_, 线Py>,
: &Bound<'_, 线Py>,
: &Bound<'_, Py>,
py: Python<'_>,
) -> bool {
let _inner = Arc::clone(&.borrow().inner);
let _inner = Arc::clone(&.borrow().inner);
let obs_arc = .borrow().inner.clone().expect("观察者未初始化");
py.detach(move || {
let guard = obs_arc.read();
chanlun::algorithm::divergence::::(
&_inner,
&_inner,
&guard.K线序列,
)
})
}
#[classmethod]
#[pyo3(name = "背驰模式_OBS", signature = (进入段, 离开段, 观察员, 配置, 模式))]
fn _obs(
_cls: &Bound<'_, PyType>,
: &Bound<'_, 线Py>,
: &Bound<'_, 线Py>,
: &Bound<'_, Py>,
: &Bound<'_, Py>,
: &str,
py: Python<'_>,
) -> PyResult<bool> {
let _inner = Arc::clone(&.borrow().inner);
let _inner = Arc::clone(&.borrow().inner);
let config = .borrow().to_rust_config(py)?;
let = .to_string();
let obs_arc = .borrow().inner.clone().expect("观察者未初始化");
Ok(py.detach(move || {
let guard = obs_arc.read();
chanlun::algorithm::divergence::::(
&_inner,
&_inner,
&guard.K线序列,
&config,
&,
)
}))
} }
} }
@@ -345,7 +504,7 @@ impl 笔Py {
.map(|k| k.bind(py).borrow().inner.clone()) .map(|k| k.bind(py).borrow().inner.clone())
.collect(); .collect();
let config = .borrow().to_rust_config(py)?; let config = .borrow().to_rust_config(py)?;
let depth = match _rc { let depth = py.detach(|| match _rc {
Some(fr) => chanlun::algorithm::bi::::( Some(fr) => chanlun::algorithm::bi::::(
fr, fr,
&mut fr_seq, &mut fr_seq,
@@ -356,17 +515,21 @@ impl 笔Py {
&config, &config,
), ),
None => , None => ,
}; });
// 写回 Python 列表 // 写回 Python 列表 (bulk extend)
let fr_items: Vec<Py<PyAny>> = fr_seq
.iter()
.map(|f| fractal_to_py(py, Arc::clone(f)).into_any())
.collect();
.call_method0("clear")?; .call_method0("clear")?;
for f in fr_seq { .call_method1("extend", (PyList::new(py, &fr_items)?,))?;
.call_method1("append", (fractal_to_py(py, f),))?; let bi_items: Vec<Py<PyAny>> = bi_seq
} .iter()
.map(|d| dashed_to_py(py, Arc::clone(d)).into_any())
.collect();
.call_method0("clear")?; .call_method0("clear")?;
for d in bi_seq { .call_method1("extend", (PyList::new(py, &bi_items)?,))?;
.call_method1("append", (dashed_to_py(py, d),))?;
}
Ok(depth) Ok(depth)
} }
@@ -552,19 +715,23 @@ impl 线段Py {
let rel_list: Vec<chanlun::types::> = let rel_list: Vec<chanlun::types::> =
.map(|v| v.into_iter().map(|d| d.inner).collect()) .map(|v| v.into_iter().map(|d| d.inner).collect())
.unwrap_or(default_rel); .unwrap_or(default_rel);
chanlun::algorithm::segment::线::( py.detach(|| {
&bi_list, chanlun::algorithm::segment::线::(
&mut seg_seq, &bi_list,
&config, &mut seg_seq,
, &config,
&rel_list, ,
); &rel_list,
);
});
// 写回 Python 列表 // 写回 Python 列表 (bulk extend)
let items: Vec<Py<PyAny>> = seg_seq
.iter()
.map(|d| dashed_to_py(py, Arc::clone(d)).into_any())
.collect();
线.call_method0("clear")?; 线.call_method0("clear")?;
for d in seg_seq { 线.call_method1("extend", (PyList::new(py, &items)?,))?;
线.call_method1("append", (dashed_to_py(py, d),))?;
}
Ok(()) Ok(())
} }
@@ -590,13 +757,17 @@ impl 线段Py {
} }
let config = .borrow().to_rust_config(py)?; let config = .borrow().to_rust_config(py)?;
chanlun::algorithm::segment::线::(&dash_list, &mut seg_seq, &config); py.detach(|| {
chanlun::algorithm::segment::线::(&dash_list, &mut seg_seq, &config);
});
// 写回 Python 列表 // 写回 Python 列表 (bulk extend)
let items: Vec<Py<PyAny>> = seg_seq
.iter()
.map(|d| dashed_to_py(py, Arc::clone(d)).into_any())
.collect();
线.call_method0("clear")?; 线.call_method0("clear")?;
for d in seg_seq { 线.call_method1("extend", (PyList::new(py, &items)?,))?;
线.call_method1("append", (dashed_to_py(py, d),))?;
}
Ok(()) Ok(())
} }
@@ -714,7 +885,7 @@ impl 中枢Py {
#[getter] #[getter]
fn (&self) -> String { fn (&self) -> String {
self.inner..read().unwrap().clone() self.inner..read().clone()
} }
#[getter] #[getter]
@@ -725,7 +896,7 @@ impl 中枢Py {
#[getter] #[getter]
fn (&self, py: Python<'_>) -> PyResult<Py<PyAny>> { fn (&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
let list = pyo3::types::PyList::empty(py); let list = pyo3::types::PyList::empty(py);
for d in self.inner..read().unwrap().iter() { for d in self.inner..read().iter() {
list.append(dashed_to_py(py, Arc::clone(d)))?; list.append(dashed_to_py(py, Arc::clone(d)))?;
} }
Ok(list.into()) Ok(list.into())
@@ -736,7 +907,6 @@ impl 中枢Py {
self.inner self.inner
.线 .线
.read() .read()
.unwrap()
.as_ref() .as_ref()
.map(|d| dashed_to_py(py, Arc::clone(d))) .map(|d| dashed_to_py(py, Arc::clone(d)))
} }
@@ -746,7 +916,6 @@ impl 中枢Py {
self.inner self.inner
._第三买卖线 ._第三买卖线
.read() .read()
.unwrap()
.as_ref() .as_ref()
.map(|d| dashed_to_py(py, Arc::clone(d))) .map(|d| dashed_to_py(py, Arc::clone(d)))
} }
@@ -845,13 +1014,17 @@ impl 中枢Py {
hub_seq.push(Arc::clone(&h.inner)); hub_seq.push(Arc::clone(&h.inner));
} }
let config = .borrow().to_rust_config(.py())?; let config = .borrow().to_rust_config(.py())?;
self.inner.(&mut hub_seq, &config); py.detach(|| {
self.inner.(&mut hub_seq, &config);
});
// 写回 Python 列表 // 写回 Python 列表 (bulk extend)
let items: Vec<Py<PyAny>> = hub_seq
.iter()
.map(|h| hub_to_py(py, Arc::clone(h)).into_any())
.collect();
.call_method0("clear")?; .call_method0("clear")?;
for h in hub_seq { .call_method1("extend", (PyList::new(py, &items)?,))?;
.call_method1("append", (hub_to_py(py, h),))?;
}
Ok(()) Ok(())
} }
@@ -958,13 +1131,17 @@ impl 中枢Py {
hub_seq.push(Arc::clone(&h.inner)); hub_seq.push(Arc::clone(&h.inner));
} }
chanlun::algorithm::hub::::(&rc_list, &mut hub_seq, , , ); py.detach(|| {
chanlun::algorithm::hub::::(&rc_list, &mut hub_seq, , , );
});
// 写回 Python 列表 // 写回 Python 列表 (bulk extend)
let items: Vec<Py<PyAny>> = hub_seq
.iter()
.map(|h| hub_to_py(py, Arc::clone(h)).into_any())
.collect();
.call_method0("clear")?; .call_method0("clear")?;
for h in hub_seq { .call_method1("extend", (PyList::new(py, &items)?,))?;
.call_method1("append", (hub_to_py(py, h),))?;
}
Ok(()) Ok(())
} }
+74 -25
View File
@@ -22,15 +22,16 @@
* SOFTWARE. * SOFTWARE.
*/ */
use parking_lot::RwLock;
use pyo3::prelude::*; use pyo3::prelude::*;
use pyo3::types::{PyDict, PyType}; use pyo3::types::{PyDict, PyType};
use std::sync::RwLock;
use crate::algorithm_py::hub_to_py; use crate::algorithm_py::hub_to_py;
use crate::kline_py::bar_to_py; use crate::kline_py::bar_to_py;
use crate::structure_py::{dashed_to_py, fractal_to_py, Py}; use crate::structure_py::{dashed_to_py, fractal_to_py, Py};
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::Arc; use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering};
use crate::config_py::Py; use crate::config_py::Py;
use crate::kline_py::{K线Py, K线Py}; use crate::kline_py::{K线Py, K线Py};
@@ -693,28 +694,27 @@ impl 买卖点Py {
#[pyclass(name = "观察者", module = "chanlun._chanlun", subclass)] #[pyclass(name = "观察者", module = "chanlun._chanlun", subclass)]
pub struct Py { pub struct Py {
pub(crate) inner: Option<Arc<RwLock<chanlun::business::observer::>>>, pub(crate) inner: Option<Arc<RwLock<chanlun::business::observer::>>>,
: std::sync::Mutex<Option<Py<Py>>>, : parking_lot::Mutex<Option<Py<Py>>>,
: AtomicU64,
} }
impl Py { impl Py {
pub(crate) fn obs( pub(crate) fn obs(
&self, &self,
) -> std::sync::RwLockReadGuard<'_, chanlun::business::observer::> { ) -> parking_lot::RwLockReadGuard<'_, chanlun::business::observer::> {
self.inner self.inner
.as_ref() .as_ref()
.expect("观察者 尚未初始化,请通过 __init__(符号, 周期, 配置) 构造") .expect("观察者 尚未初始化,请通过 __init__(符号, 周期, 配置) 构造")
.read() .read()
.unwrap_or_else(|e| e.into_inner())
} }
pub(crate) fn obs_mut( pub(crate) fn obs_mut(
&self, &self,
) -> std::sync::RwLockWriteGuard<'_, chanlun::business::observer::> { ) -> parking_lot::RwLockWriteGuard<'_, chanlun::business::observer::> {
self.inner self.inner
.as_ref() .as_ref()
.expect("观察者 尚未初始化,请通过 __init__(符号, 周期, 配置) 构造") .expect("观察者 尚未初始化,请通过 __init__(符号, 周期, 配置) 构造")
.write() .write()
.unwrap_or_else(|e| e.into_inner())
} }
} }
@@ -770,7 +770,8 @@ impl 观察者Py {
inner: Some(chanlun::business::observer::::new( inner: Some(chanlun::business::observer::::new(
, , config, , , config,
)), )),
: std::sync::Mutex::new(None), : parking_lot::Mutex::new(None),
: AtomicU64::new(0),
}) })
} }
@@ -824,7 +825,7 @@ impl 观察者Py {
#[getter] #[getter]
fn (&self, py: Python<'_>) -> PyResult<Py<Py>> { fn (&self, py: Python<'_>) -> PyResult<Py<Py>> {
let mut cache = self..lock().unwrap(); let mut cache = self..lock();
if let Some(ref cached) = *cache { if let Some(ref cached) = *cache {
Ok(cached.clone_ref(py)) Ok(cached.clone_ref(py))
} else { } else {
@@ -838,11 +839,8 @@ impl 观察者Py {
#[setter] #[setter]
fn set_配置(&self, value: &Bound<'_, Py>) -> PyResult<()> { fn set_配置(&self, value: &Bound<'_, Py>) -> PyResult<()> {
let config = value.borrow().to_rust_config(value.py())?; let config = value.borrow().to_rust_config(value.py())?;
*self..lock() = Some(value.clone().unbind());
self.obs_mut(). = config; self.obs_mut(). = config;
self.
.lock()
.unwrap()
.replace(value.clone().unbind());
Ok(()) Ok(())
} }
@@ -861,25 +859,53 @@ impl 观察者Py {
/// 核心入口 — 投喂一根原始K线,增量更新所有层级(内部实现) /// 核心入口 — 投喂一根原始K线,增量更新所有层级(内部实现)
#[pyo3(name = "_增加原始K线")] #[pyo3(name = "_增加原始K线")]
fn K线_impl(&mut self, K: &Bound<'_, K线Py>) -> PyResult<()> { fn K线_impl(slf: &Bound<'_, Self>, K: &Bound<'_, K线Py>) -> PyResult<()> {
self.obs_mut().K线((*K.borrow().inner).clone()); let kline = (*K.borrow().inner).clone();
Ok(()) let obs_arc = slf
.borrow()
.inner
.clone()
.expect("观察者 尚未初始化,请通过 __init__(符号, 周期, 配置) 构造");
let symbol = obs_arc.read()..clone();
let result = slf.py().detach(move || {
std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
obs_arc.write().K线(kline);
}))
});
match result {
Ok(()) => Ok(()),
Err(e) => {
let msg = e
.downcast_ref::<&str>()
.map(|s| s.to_string())
.or_else(|| e.downcast_ref::<String>().cloned())
.unwrap_or_else(|| "未知算法错误".into());
Err(pyo3::exceptions::PyRuntimeError::new_err(format!(
"[{symbol}] 算法异常: {msg}"
)))
}
}
} }
/// 核心入口 — 投喂一根原始K线,增量更新所有层级(公开分发器,支持子类重写) /// 核心入口 — 投喂一根原始K线,增量更新所有层级(公开分发器,支持子类重写)
fn K线(slf: &Bound<'_, Self>, K: &Bound<'_, K线Py>) -> PyResult<()> { fn K线(slf: &Bound<'_, Self>, K: &Bound<'_, K线Py>) -> PyResult<()> {
// 同步缓存的 Python 配置到 Rust 观察者(支持 obs.配置 直接修改) // 版本对比,仅在配置变更时同步(支持 obs.配置.field = value 直接修改)
{ {
let me = slf.borrow(); let me = slf.borrow();
if let Some(ref cached) = *me..lock().unwrap() { if let Some(ref cached) = *me..lock() {
let py = slf.py(); let py = slf.py();
if let Ok(config) = cached.bind(py).borrow().to_rust_config(py) { let cfg_ref = cached.bind(py).borrow();
me.obs_mut(). = config; let current_version = cfg_ref..load(Ordering::Relaxed);
if current_version != me..load(Ordering::Relaxed) {
if let Ok(config) = cfg_ref.to_rust_config(py) {
me.obs_mut(). = config;
}
me..store(current_version, Ordering::Relaxed);
} }
} }
} }
slf.call_method1("_增加原始K线", (K,))?; // 直接调用 Rust 实现,跳过 Python dispatch
Ok(()) Self::K线_impl(slf, K)
} }
/// 投喂原始数据 — 便捷入口,直接从 OHLCV 创建 K线 并通过 Python 分发 增加原始K线, /// 投喂原始数据 — 便捷入口,直接从 OHLCV 创建 K线 并通过 Python 分发 增加原始K线,
@@ -931,9 +957,31 @@ impl 观察者Py {
/// 静态重新分析(内部实现) /// 静态重新分析(内部实现)
#[pyo3(name = "_静态重新分析")] #[pyo3(name = "_静态重新分析")]
fn _impl(&mut self) -> PyResult<()> { fn _impl(slf: &Bound<'_, Self>) -> PyResult<()> {
self.obs_mut().(); let obs_arc = slf
Ok(()) .borrow()
.inner
.clone()
.expect("观察者 尚未初始化,请通过 __init__(符号, 周期, 配置) 构造");
let symbol = obs_arc.read()..clone();
let result = slf.py().detach(move || {
std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
obs_arc.write().();
}))
});
match result {
Ok(()) => Ok(()),
Err(e) => {
let msg = e
.downcast_ref::<&str>()
.map(|s| s.to_string())
.or_else(|| e.downcast_ref::<String>().cloned())
.unwrap_or_else(|| "未知算法错误".into());
Err(pyo3::exceptions::PyRuntimeError::new_err(format!(
"[{symbol}] 静态重新分析异常: {msg}"
)))
}
}
} }
/// 静态重新分析(公开分发器,支持子类重写) /// 静态重新分析(公开分发器,支持子类重写)
@@ -1413,7 +1461,8 @@ impl 立体分析器Py {
for (, obs_rc) in &self.inner. { for (, obs_rc) in &self.inner. {
let obs_py = Py { let obs_py = Py {
inner: Some(obs_rc.clone()), inner: Some(obs_rc.clone()),
: std::sync::Mutex::new(None), : parking_lot::Mutex::new(None),
: AtomicU64::new(0),
}; };
dict.set_item(, obs_py)?; dict.set_item(, obs_py)?;
} }
+199
View File
@@ -0,0 +1,199 @@
use dashmap::DashMap;
use pyo3::prelude::*;
use pyo3::types::PySet;
/// 缓存模式:线程局部(默认,零锁)或全局(dashmap,跨线程共享)
use std::cell::RefCell;
use std::collections::HashMap;
use std::sync::OnceLock;
pub enum CacheMode {
ThreadLocal,
Global,
}
static CACHE_MODE: OnceLock<CacheMode> = OnceLock::new();
pub fn get_mode() -> &'static CacheMode {
CACHE_MODE.get_or_init(|| match std::env::var("CHANLUN_CACHE_MODE").as_deref() {
Ok("global") => CacheMode::Global,
_ => CacheMode::ThreadLocal,
})
}
pub fn peek_mode() -> Option<&'static CacheMode> {
CACHE_MODE.get()
}
pub fn set_mode(mode: CacheMode) -> Result<(), String> {
CACHE_MODE
.set(mode)
.map_err(|_| "缓存模式已初始化,请在创建任何观察者之前调用 set_cache_mode".into())
}
// ========== BAR_IDENTITY ==========
thread_local! {
static BAR_LOCAL: RefCell<HashMap<usize, Py<super::kline_py::K线Py>>> = RefCell::new(HashMap::new());
}
static BAR_GLOBAL: std::sync::LazyLock<DashMap<usize, Py<super::kline_py::K线Py>>> =
std::sync::LazyLock::new(DashMap::new);
pub fn bar_get(py: Python<'_>, key: usize) -> Option<Py<super::kline_py::K线Py>> {
match get_mode() {
CacheMode::ThreadLocal => BAR_LOCAL.with(|m| m.borrow().get(&key).map(|p| p.clone_ref(py))),
CacheMode::Global => BAR_GLOBAL.get(&key).map(|p| p.clone_ref(py)),
}
}
pub fn bar_insert(py: Python<'_>, key: usize, obj: &Py<super::kline_py::K线Py>) {
match get_mode() {
CacheMode::ThreadLocal => BAR_LOCAL.with(|m| {
let mut m = m.borrow_mut();
m.retain(|_, v| v.get_refcnt(py) > 1);
m.insert(key, obj.clone_ref(py));
}),
CacheMode::Global => {
BAR_GLOBAL.retain(|_, v| v.get_refcnt(py) > 1);
BAR_GLOBAL.insert(key, obj.clone_ref(py));
}
}
}
// ========== KLINE_IDENTITY ==========
thread_local! {
static KLINE_LOCAL: RefCell<HashMap<usize, Py<super::kline_py::K线Py>>> = RefCell::new(HashMap::new());
}
static KLINE_GLOBAL: std::sync::LazyLock<DashMap<usize, Py<super::kline_py::K线Py>>> =
std::sync::LazyLock::new(DashMap::new);
pub fn kline_get(py: Python<'_>, key: usize) -> Option<Py<super::kline_py::K线Py>> {
match get_mode() {
CacheMode::ThreadLocal => {
KLINE_LOCAL.with(|m| m.borrow().get(&key).map(|p| p.clone_ref(py)))
}
CacheMode::Global => KLINE_GLOBAL.get(&key).map(|p| p.clone_ref(py)),
}
}
pub fn kline_insert(py: Python<'_>, key: usize, obj: &Py<super::kline_py::K线Py>) {
match get_mode() {
CacheMode::ThreadLocal => KLINE_LOCAL.with(|m| {
let mut m = m.borrow_mut();
m.retain(|_, v| v.get_refcnt(py) > 1);
m.insert(key, obj.clone_ref(py));
}),
CacheMode::Global => {
KLINE_GLOBAL.retain(|_, v| v.get_refcnt(py) > 1);
KLINE_GLOBAL.insert(key, obj.clone_ref(py));
}
}
}
// ========== FRACTAL_IDENTITY ==========
use crate::structure_py::Py;
thread_local! {
static FRACTAL_LOCAL: RefCell<HashMap<usize, Py<Py>>> = RefCell::new(HashMap::new());
}
static FRACTAL_GLOBAL: std::sync::LazyLock<DashMap<usize, Py<Py>>> =
std::sync::LazyLock::new(DashMap::new);
pub fn fractal_get(py: Python<'_>, key: usize) -> Option<Py<Py>> {
match get_mode() {
CacheMode::ThreadLocal => {
FRACTAL_LOCAL.with(|m| m.borrow().get(&key).map(|p| p.clone_ref(py)))
}
CacheMode::Global => FRACTAL_GLOBAL.get(&key).map(|p| p.clone_ref(py)),
}
}
pub fn fractal_insert(py: Python<'_>, key: usize, obj: &Py<Py>) {
match get_mode() {
CacheMode::ThreadLocal => FRACTAL_LOCAL.with(|m| {
let mut m = m.borrow_mut();
m.retain(|_, v| v.get_refcnt(py) > 1);
m.insert(key, obj.clone_ref(py));
}),
CacheMode::Global => {
FRACTAL_GLOBAL.retain(|_, v| v.get_refcnt(py) > 1);
FRACTAL_GLOBAL.insert(key, obj.clone_ref(py));
}
}
}
// ========== DASHED_IDENTITY ==========
use crate::structure_py::线Py;
thread_local! {
static DASHED_LOCAL: RefCell<HashMap<usize, Py<线Py>>> = RefCell::new(HashMap::new());
}
static DASHED_GLOBAL: std::sync::LazyLock<DashMap<usize, Py<线Py>>> =
std::sync::LazyLock::new(DashMap::new);
pub fn dashed_get(py: Python<'_>, key: usize) -> Option<Py<线Py>> {
match get_mode() {
CacheMode::ThreadLocal => {
DASHED_LOCAL.with(|m| m.borrow().get(&key).map(|p| p.clone_ref(py)))
}
CacheMode::Global => DASHED_GLOBAL.get(&key).map(|p| p.clone_ref(py)),
}
}
pub fn dashed_insert(py: Python<'_>, key: usize, obj: &Py<线Py>) {
match get_mode() {
CacheMode::ThreadLocal => DASHED_LOCAL.with(|m| {
let mut m = m.borrow_mut();
m.retain(|_, v| v.get_refcnt(py) > 1);
m.insert(key, obj.clone_ref(py));
}),
CacheMode::Global => {
DASHED_GLOBAL.retain(|_, v| v.get_refcnt(py) > 1);
DASHED_GLOBAL.insert(key, obj.clone_ref(py));
}
}
}
// ========== HUB_IDENTITY ==========
use crate::algorithm_py::Py;
thread_local! {
static HUB_LOCAL: RefCell<HashMap<usize, Py<Py>>> = RefCell::new(HashMap::new());
}
static HUB_GLOBAL: std::sync::LazyLock<DashMap<usize, Py<Py>>> =
std::sync::LazyLock::new(DashMap::new);
pub fn hub_get(py: Python<'_>, key: usize) -> Option<Py<Py>> {
match get_mode() {
CacheMode::ThreadLocal => HUB_LOCAL.with(|m| m.borrow().get(&key).map(|p| p.clone_ref(py))),
CacheMode::Global => HUB_GLOBAL.get(&key).map(|p| p.clone_ref(py)),
}
}
pub fn hub_insert(py: Python<'_>, key: usize, obj: &Py<Py>) {
match get_mode() {
CacheMode::ThreadLocal => HUB_LOCAL.with(|m| {
let mut m = m.borrow_mut();
m.retain(|_, v| v.get_refcnt(py) > 1);
m.insert(key, obj.clone_ref(py));
}),
CacheMode::Global => {
HUB_GLOBAL.retain(|_, v| v.get_refcnt(py) > 1);
HUB_GLOBAL.insert(key, obj.clone_ref(py));
}
}
}
// ========== BSP_CACHE ==========
thread_local! {
static BSP_LOCAL: RefCell<HashMap<usize, Py<PySet>>> = RefCell::new(HashMap::new());
}
static BSP_GLOBAL: std::sync::LazyLock<DashMap<usize, Py<PySet>>> =
std::sync::LazyLock::new(DashMap::new);
pub fn bsp_get(py: Python<'_>, key: usize) -> Option<Py<PySet>> {
match get_mode() {
CacheMode::ThreadLocal => BSP_LOCAL.with(|m| m.borrow().get(&key).map(|p| p.clone_ref(py))),
CacheMode::Global => BSP_GLOBAL.get(&key).map(|p| p.clone_ref(py)),
}
}
pub fn bsp_insert(py: Python<'_>, key: usize, obj: Py<PySet>) {
match get_mode() {
CacheMode::ThreadLocal => BSP_LOCAL.with(|m| {
m.borrow_mut().insert(key, obj);
}),
CacheMode::Global => {
BSP_GLOBAL.insert(key, obj);
}
}
}
+43 -7
View File
@@ -22,10 +22,11 @@
* SOFTWARE. * SOFTWARE.
*/ */
use chanlun::warn;
use pyo3::prelude::*; use pyo3::prelude::*;
use pyo3::types::{PyDict, PyType}; use pyo3::types::{PyDict, PyType};
use std::collections::HashMap; use std::collections::HashMap;
use tracing::warn; use std::sync::atomic::{AtomicU64, Ordering};
/// 缠论配置 — 控制所有分析阶段行为的参数集(共 60+ 字段,均有默认值)。 /// 缠论配置 — 控制所有分析阶段行为的参数集(共 60+ 字段,均有默认值)。
/// ///
@@ -95,6 +96,8 @@ use tracing::warn;
#[pyclass(name = "缠论配置", module = "chanlun._chanlun")] #[pyclass(name = "缠论配置", module = "chanlun._chanlun")]
pub struct Py { pub struct Py {
fields: HashMap<String, Py<PyAny>>, fields: HashMap<String, Py<PyAny>>,
: parking_lot::Mutex<Option<chanlun::config::>>,
pub(crate) : AtomicU64,
} }
#[pymethods] #[pymethods]
@@ -120,7 +123,11 @@ impl 缠论配置Py {
// 全部通过 serde_json 往返验证类型,统一处理字符串数字/布尔强制转换 // 全部通过 serde_json 往返验证类型,统一处理字符串数字/布尔强制转换
let config = dict_to_rust_config(&fields)?; let config = dict_to_rust_config(&fields)?;
let fields = config_to_field_dict(&config)?; let fields = config_to_field_dict(&config)?;
Ok(Self { fields }) Ok(Self {
fields,
: parking_lot::Mutex::new(Some(config)),
: AtomicU64::new(1),
})
} }
fn __getattr__(&self, name: &str, py: Python<'_>) -> PyResult<Py<PyAny>> { fn __getattr__(&self, name: &str, py: Python<'_>) -> PyResult<Py<PyAny>> {
@@ -135,10 +142,13 @@ impl 缠论配置Py {
fn __setattr__(&mut self, name: &str, value: &Bound<'_, PyAny>) -> PyResult<()> { fn __setattr__(&mut self, name: &str, value: &Bound<'_, PyAny>) -> PyResult<()> {
if self.fields.contains_key(name) { if self.fields.contains_key(name) {
self.fields.insert(name.to_string(), value.clone().unbind()); self.fields.insert(name.to_string(), value.clone().unbind());
*self..lock() = None;
self..fetch_add(1, Ordering::Relaxed);
// 通过 serde 往返验证类型 // 通过 serde 往返验证类型
match dict_to_rust_config(&self.fields) { match dict_to_rust_config(&self.fields) {
Ok(config) => { Ok(config) => {
self.fields = config_to_field_dict(&config)?; self.fields = config_to_field_dict(&config)?;
*self..lock() = Some(config);
Ok(()) Ok(())
} }
Err(e) => Err(pyo3::exceptions::PyValueError::new_err(format!( Err(e) => Err(pyo3::exceptions::PyValueError::new_err(format!(
@@ -217,7 +227,11 @@ impl 缠论配置Py {
let config = dict_to_rust_config(&fields)?; let config = dict_to_rust_config(&fields)?;
let fields = config_to_field_dict(&config)?; let fields = config_to_field_dict(&config)?;
Ok(Self { fields }) Ok(Self {
fields,
: parking_lot::Mutex::new(Some(config)),
: AtomicU64::new(1),
})
} }
#[classmethod] #[classmethod]
@@ -231,7 +245,16 @@ impl 缠论配置Py {
fn (_cls: &Bound<'_, PyType>) -> PyResult<Self> { fn (_cls: &Bound<'_, PyType>) -> PyResult<Self> {
let config = chanlun::config::::default().(); let config = chanlun::config::::default().();
let fields = config_to_field_dict(&config)?; let fields = config_to_field_dict(&config)?;
Ok(Self { fields }) Ok(Self {
fields,
: parking_lot::Mutex::new(Some(config)),
: AtomicU64::new(1),
})
}
/// 判断指定标签是否应展示。None = 全部展示,空列表 = 全部隐藏。
fn (&self, : &str) -> bool {
self..lock().as_ref().map_or(true, |c| c.())
} }
#[classmethod] #[classmethod]
@@ -302,18 +325,31 @@ impl 缠论配置Py {
let config = dict_to_rust_config(&fields)?; let config = dict_to_rust_config(&fields)?;
let fields = config_to_field_dict(&config)?; let fields = config_to_field_dict(&config)?;
Ok(Self { fields }) Ok(Self {
fields,
: parking_lot::Mutex::new(Some(config)),
: AtomicU64::new(1),
})
} }
pub(crate) fn to_rust_config( pub(crate) fn to_rust_config(
&self, &self,
_py: Python<'_>, _py: Python<'_>,
) -> PyResult<chanlun::config::> { ) -> PyResult<chanlun::config::> {
dict_to_rust_config(&self.fields) if let Some(ref cached) = *self..lock() {
return Ok(cached.clone());
}
let config = dict_to_rust_config(&self.fields)?;
*self..lock() = Some(config.clone());
Ok(config)
} }
pub(crate) fn from_rust_config(config: &chanlun::config::) -> PyResult<Self> { pub(crate) fn from_rust_config(config: &chanlun::config::) -> PyResult<Self> {
config_to_field_dict(config).map(|fields| Self { fields }) config_to_field_dict(config).map(|fields| Self {
fields,
: parking_lot::Mutex::new(Some(config.clone())),
: AtomicU64::new(1),
})
} }
} }
+291 -373
View File
@@ -22,33 +22,10 @@
* SOFTWARE. * SOFTWARE.
*/ */
use std::num::NonZeroUsize;
use crate::business_py::Py; use crate::business_py::Py;
use crate::business_py::Py; use crate::business_py::Py;
use std::sync::Mutex;
use lru::LruCache;
use pyo3::prelude::*; use pyo3::prelude::*;
/// 缓存辅助宏:在调用点创建静态 LruCache,先查后存
macro_rules! with_cache {
($cache:ident, $size:literal, $key_expr:expr, $compute:expr) => {{
use std::sync::LazyLock;
static $cache: LazyLock<Mutex<LruCache<(usize, usize, i64), (bool, String)>>> =
LazyLock::new(|| Mutex::new(LruCache::new(NonZeroUsize::new($size).unwrap())));
let key = $key_expr;
if let Some(cached) = $cache.lock().unwrap().get(&key) {
return Ok(cached.clone());
}
let result: PyResult<(bool, String)> = $compute;
if let Ok(ref r) = result {
$cache.lock().unwrap().put(key, r.clone());
}
result
}};
}
/// 从 Python 值中提取时间戳(兼容 i64 和 datetime 两种类型) /// 从 Python 值中提取时间戳(兼容 i64 和 datetime 两种类型)
fn (val: &Bound<'_, PyAny>) -> PyResult<i64> { fn (val: &Bound<'_, PyAny>) -> PyResult<i64> {
if let Ok(ts) = val.extract::<i64>() { if let Ok(ts) = val.extract::<i64>() {
@@ -129,71 +106,60 @@ fn K线相等(
B: &Bound<'_, PyAny>, B: &Bound<'_, PyAny>,
: f64, : f64,
) -> PyResult<(bool, String)> { ) -> PyResult<(bool, String)> {
with_cache!( // 快速路径
C_KLINE, if let (Ok(a), Ok(b)) = (
128, A.cast::<crate::kline_py::K线Py>(),
( B.cast::<crate::kline_py::K线Py>(),
A.as_ptr() as usize, ) {
B.as_ptr() as usize, return Ok(a.borrow().inner.(&b.borrow().inner, ));
.to_bits() as i64 }
), // 回退路径
{ let = "K线校验";
// 快速路径 let = [
if let (Ok(a), Ok(b)) = ( "标识",
A.cast::<crate::kline_py::K线Py>(), "序号",
B.cast::<crate::kline_py::K线Py>(), "周期",
) { "时间戳",
return Ok(a.borrow().inner.(&b.borrow().inner, )); "",
} "",
// 回退路径 "开盘价",
let = "K线校验"; "收盘价",
let = [ "成交量",
"标识", ];
"序号", for & in & {
"周期", let (a有, b有) = (A.hasattr()?, B.hasattr()?);
"时间戳", if a有 && !b有 {
"", return Ok((false, format!("{标签}: [{字段}] A存在属性 B缺失属性")));
"",
"开盘价",
"收盘价",
"成交量",
];
for & in & {
let (a有, b有) = (A.hasattr()?, B.hasattr()?);
if a有 && !b有 {
return Ok((false, format!("{标签}: [{字段}] A存在属性 B缺失属性")));
}
if !a有 && b有 {
return Ok((false, format!("{标签}: [{字段}] B存在属性 A缺失属性")));
}
if !a有 && !b有 {
continue;
}
let valA = A.getattr()?;
let valB = B.getattr()?;
if let Some(r) = (&valA, &valB, ) {
let (ok, m) = r?;
if !ok {
return Ok((false, format!("{标签}: [{字段}]{}", m)));
}
} else if == "时间戳" {
let a = (&valA).unwrap_or(0);
let b = (&valB).unwrap_or(0);
if a != b {
return Ok((false, format!("{标签}: [{字段}] 数值不等 A={a},B={b}")));
}
} else {
let eq: bool = valA.eq(&valB)?;
if !eq {
let ra = valA.repr()?.extract::<String>().unwrap_or_default();
let rb = valB.repr()?.extract::<String>().unwrap_or_default();
return Ok((false, format!("{标签}: [{字段}] 数值不等 A={ra},B={rb}")));
}
}
}
Ok((true, format!("{标签}: 全部字段一致")))
} }
) if !a有 && b有 {
return Ok((false, format!("{标签}: [{字段}] B存在属性 A缺失属性")));
}
if !a有 && !b有 {
continue;
}
let valA = A.getattr()?;
let valB = B.getattr()?;
if let Some(r) = (&valA, &valB, ) {
let (ok, m) = r?;
if !ok {
return Ok((false, format!("{标签}: [{字段}]{}", m)));
}
} else if == "时间戳" {
let a = (&valA).unwrap_or(0);
let b = (&valB).unwrap_or(0);
if a != b {
return Ok((false, format!("{标签}: [{字段}] 数值不等 A={a},B={b}")));
}
} else {
let eq: bool = valA.eq(&valB)?;
if !eq {
let ra = valA.repr()?.extract::<String>().unwrap_or_default();
let rb = valB.repr()?.extract::<String>().unwrap_or_default();
return Ok((false, format!("{标签}: [{字段}] 数值不等 A={ra},B={rb}")));
}
}
}
Ok((true, format!("{标签}: 全部字段一致")))
} }
// ========== 缠论K线相等 ========== // ========== 缠论K线相等 ==========
@@ -205,102 +171,91 @@ fn 缠论K线相等(
B: &Bound<'_, PyAny>, B: &Bound<'_, PyAny>,
: f64, : f64,
) -> PyResult<(bool, String)> { ) -> PyResult<(bool, String)> {
with_cache!( if let (Ok(a), Ok(b)) = (
C_CHAN_K, A.cast::<crate::kline_py::K线Py>(),
4096, B.cast::<crate::kline_py::K线Py>(),
( ) {
A.as_ptr() as usize, return Ok(a.borrow().inner.(&b.borrow().inner, ));
B.as_ptr() as usize, }
.to_bits() as i64 let = "缠论K线校验";
), let = [
{ "序号",
if let (Ok(a), Ok(b)) = ( "时间戳",
A.cast::<crate::kline_py::K线Py>(), "",
B.cast::<crate::kline_py::K线Py>(), "",
) { "方向",
return Ok(a.borrow().inner.(&b.borrow().inner, )); "分型",
"周期",
"标识",
"分型特征值",
"原始起始序号",
"原始结束序号",
"标的K线",
"买卖点信息",
];
for & in & {
let (a有, b有) = (A.hasattr()?, B.hasattr()?);
if a有 && !b有 {
return Ok((false, format!("{标签}: [{字段}] A存在 B缺失属性")));
}
if !a有 && b有 {
return Ok((false, format!("{标签}: [{字段}] B存在 A缺失属性")));
}
if !a有 && !b有 {
continue;
}
let valA = A.getattr()?;
let valB = B.getattr()?;
if let Some(r) = (&valA, &valB, ) {
let (ok, m) = r?;
if !ok {
return Ok((false, format!("{标签}: [{字段}]{m}")));
} }
let = "缠论K线校验"; } else if == "标的K线" {
let = [ if let Some(r) = (&valA, &valB, , ) {
"序号", if !r.0 {
"时间戳", return Ok((false, r.1));
"", } else {
"",
"方向",
"分型",
"周期",
"标识",
"分型特征值",
"原始起始序号",
"原始结束序号",
"标的K线",
"买卖点信息",
];
for & in & {
let (a有, b有) = (A.hasattr()?, B.hasattr()?);
if a有 && !b有 {
return Ok((false, format!("{标签}: [{字段}] A存在 B缺失属性")));
}
if !a有 && b有 {
return Ok((false, format!("{标签}: [{字段}] B存在 A缺失属性")));
}
if !a有 && !b有 {
continue; continue;
} }
let valA = A.getattr()?;
let valB = B.getattr()?;
if let Some(r) = (&valA, &valB, ) {
let (ok, m) = r?;
if !ok {
return Ok((false, format!("{标签}: [{字段}]{m}")));
}
} else if == "标的K线" {
if let Some(r) = (&valA, &valB, , ) {
if !r.0 {
return Ok((false, r.1));
} else {
continue;
}
}
let (eq, msg) = K线相等(&valA, &valB, )?;
if !eq {
return Ok((false, format!("{标签}: 标的K线子项异常 >> {msg}")));
}
} else if == "时间戳" {
let a = (&valA).unwrap_or(0);
let b = (&valB).unwrap_or(0);
if a != b {
return Ok((false, format!("{标签}: [{字段}] 数值不等 A={a},B={b}")));
}
} else if == "方向" || == "分型" {
let sa = valA.str()?.extract::<String>().unwrap_or_default();
let sb = valB.str()?.extract::<String>().unwrap_or_default();
if sa != sb {
return Ok((false, format!("{标签}: [{字段}] 数值不等 A={sa},B={sb}")));
}
} else if == "买卖点信息" {
let py = A.py();
let set_a = py.import("builtins")?.getattr("set")?.call1((&valA,))?;
let set_b = py.import("builtins")?.getattr("set")?.call1((&valB,))?;
let eq: bool = set_a.eq(set_b)?;
if !eq {
let ra = valA.repr()?.extract::<String>().unwrap_or_default();
let rb = valB.repr()?.extract::<String>().unwrap_or_default();
return Ok((false, format!("{标签}: [{字段}] 数值不等 A={ra},B={rb}")));
}
} else {
let eq: bool = valA.eq(&valB)?;
if !eq {
let ra = valA.repr()?.extract::<String>().unwrap_or_default();
let rb = valB.repr()?.extract::<String>().unwrap_or_default();
return Ok((false, format!("{标签}: [{字段}] 数值不等 A={ra},B={rb}")));
}
}
} }
Ok((true, format!("{标签}: 全部字段嵌套校验一致"))) let (eq, msg) = K线相等(&valA, &valB, )?;
if !eq {
return Ok((false, format!("{标签}: 标的K线子项异常 >> {msg}")));
}
} else if == "时间戳" {
let a = (&valA).unwrap_or(0);
let b = (&valB).unwrap_or(0);
if a != b {
return Ok((false, format!("{标签}: [{字段}] 数值不等 A={a},B={b}")));
}
} else if == "方向" || == "分型" {
let sa = valA.str()?.extract::<String>().unwrap_or_default();
let sb = valB.str()?.extract::<String>().unwrap_or_default();
if sa != sb {
return Ok((false, format!("{标签}: [{字段}] 数值不等 A={sa},B={sb}")));
}
} else if == "买卖点信息" {
let py = A.py();
let set_a = py.import("builtins")?.getattr("set")?.call1((&valA,))?;
let set_b = py.import("builtins")?.getattr("set")?.call1((&valB,))?;
let eq: bool = set_a.eq(set_b)?;
if !eq {
let ra = valA.repr()?.extract::<String>().unwrap_or_default();
let rb = valB.repr()?.extract::<String>().unwrap_or_default();
return Ok((false, format!("{标签}: [{字段}] 数值不等 A={ra},B={rb}")));
}
} else {
let eq: bool = valA.eq(&valB)?;
if !eq {
let ra = valA.repr()?.extract::<String>().unwrap_or_default();
let rb = valB.repr()?.extract::<String>().unwrap_or_default();
return Ok((false, format!("{标签}: [{字段}] 数值不等 A={ra},B={rb}")));
}
} }
) }
Ok((true, format!("{标签}: 全部字段嵌套校验一致")))
} }
// ========== 分型相等 ========== // ========== 分型相等 ==========
@@ -312,88 +267,77 @@ fn 分型相等(
B: &Bound<'_, PyAny>, B: &Bound<'_, PyAny>,
: f64, : f64,
) -> PyResult<(bool, String)> { ) -> PyResult<(bool, String)> {
with_cache!( if let (Ok(a), Ok(b)) = (
C_FRACTAL, A.cast::<crate::structure_py::Py>(),
4096, B.cast::<crate::structure_py::Py>(),
( ) {
A.as_ptr() as usize, return Ok(a.borrow().inner.(&b.borrow().inner, ));
B.as_ptr() as usize, }
.to_bits() as i64 let = "分型校验";
), // Python 分型内部用 _结构/_时间戳/_分型特征值 作为 slot 名,Rust 用 结构/时间戳/分型特征值 作为 getter
{ for & in &["", "", ""] {
if let (Ok(a), Ok(b)) = ( let valA = A.getattr()?;
A.cast::<crate::structure_py::Py>(), let valB = B.getattr()?;
B.cast::<crate::structure_py::Py>(), if let Some(r) = (&valA, &valB, , ) {
) { if !r.0 {
return Ok(a.borrow().inner.(&b.borrow().inner, )); return Ok((false, r.1));
} else {
continue;
} }
let = "分型校验";
// Python 分型内部用 _结构/_时间戳/_分型特征值 作为 slot 名,Rust 用 结构/时间戳/分型特征值 作为 getter
for & in &["", "", ""] {
let valA = A.getattr()?;
let valB = B.getattr()?;
if let Some(r) = (&valA, &valB, , ) {
if !r.0 {
return Ok((false, r.1));
} else {
continue;
}
}
let (eq, msg) = K线相等(&valA, &valB, )?;
if !eq {
return Ok((false, format!("{标签}: [{字段}]缠论K线子项异常 >> {msg}")));
}
}
for &(, ) in &[
("_结构", "结构"),
("_时间戳", "时间戳"),
("_分型特征值", "分型特征值"),
] {
// 先尝试 Python 侧的下划线名,再尝试 Rust 侧的无下划线名
let valA = (A, &[, ])?;
let valB = (B, &[, ])?;
let (a有, b有) = (valA.is_some(), valB.is_some());
if a有 && !b有 {
return Ok((false, format!("{标签}: [{字段}] A存在属性 B缺失属性")));
}
if !a有 && b有 {
return Ok((false, format!("{标签}: [{字段}] B存在属性 A缺失属性")));
}
if !a有 && !b有 {
continue;
}
let valA = valA.unwrap();
let valB = valB.unwrap();
if let Some(r) = (&valA, &valB, ) {
let (ok, m) = r?;
if !ok {
return Ok((false, format!("{标签}: [{字段}]{m}")));
}
} else if == "_时间戳" {
let a = (&valA).unwrap_or(0);
let b = (&valB).unwrap_or(0);
if a != b {
return Ok((false, format!("{标签}: [{字段}] 数值不等 A={a},B={b}")));
}
} else if == "_结构" {
let sa = valA.str()?.extract::<String>().unwrap_or_default();
let sb = valB.str()?.extract::<String>().unwrap_or_default();
if sa != sb {
return Ok((false, format!("{标签}: [{字段}] 数值不等 A={sa},B={sb}")));
}
} else {
let eq: bool = valA.eq(&valB)?;
if !eq {
let ra = valA.repr()?.extract::<String>().unwrap_or_default();
let rb = valB.repr()?.extract::<String>().unwrap_or_default();
return Ok((false, format!("{标签}: [{字段}] 数值不等 A={ra},B={rb}")));
}
}
}
Ok((true, format!("{标签}: 自有字段+三根缠论K线全部校验一致")))
} }
) let (eq, msg) = K线相等(&valA, &valB, )?;
if !eq {
return Ok((false, format!("{标签}: [{字段}]缠论K线子项异常 >> {msg}")));
}
}
for &(, ) in &[
("_结构", "结构"),
("_时间戳", "时间戳"),
("_分型特征值", "分型特征值"),
] {
// 先尝试 Python 侧的下划线名,再尝试 Rust 侧的无下划线名
let valA = (A, &[, ])?;
let valB = (B, &[, ])?;
let (a有, b有) = (valA.is_some(), valB.is_some());
if a有 && !b有 {
return Ok((false, format!("{标签}: [{字段}] A存在属性 B缺失属性")));
}
if !a有 && b有 {
return Ok((false, format!("{标签}: [{字段}] B存在属性 A缺失属性")));
}
if !a有 && !b有 {
continue;
}
let valA = valA.unwrap();
let valB = valB.unwrap();
if let Some(r) = (&valA, &valB, ) {
let (ok, m) = r?;
if !ok {
return Ok((false, format!("{标签}: [{字段}]{m}")));
}
} else if == "_时间戳" {
let a = (&valA).unwrap_or(0);
let b = (&valB).unwrap_or(0);
if a != b {
return Ok((false, format!("{标签}: [{字段}] 数值不等 A={a},B={b}")));
}
} else if == "_结构" {
let sa = valA.str()?.extract::<String>().unwrap_or_default();
let sb = valB.str()?.extract::<String>().unwrap_or_default();
if sa != sb {
return Ok((false, format!("{标签}: [{字段}] 数值不等 A={sa},B={sb}")));
}
} else {
let eq: bool = valA.eq(&valB)?;
if !eq {
let ra = valA.repr()?.extract::<String>().unwrap_or_default();
let rb = valB.repr()?.extract::<String>().unwrap_or_default();
return Ok((false, format!("{标签}: [{字段}] 数值不等 A={ra},B={rb}")));
}
}
}
Ok((true, format!("{标签}: 自有字段+三根缠论K线全部校验一致")))
} }
// ========== 缺口相等 ========== // ========== 缺口相等 ==========
@@ -405,54 +349,42 @@ fn 缺口相等(
B: &Bound<'_, PyAny>, B: &Bound<'_, PyAny>,
: f64, : f64,
) -> PyResult<(bool, String)> { ) -> PyResult<(bool, String)> {
with_cache!( if let (Ok(a), Ok(b)) = (
C_GAP, A.cast::<crate::types_py::Py>(),
4096, B.cast::<crate::types_py::Py>(),
( ) {
A.as_ptr() as usize, return Ok(a.borrow().inner.(&b.borrow().inner, ));
B.as_ptr() as usize, }
.to_bits() as i64 let = "缺口校验";
), for & in &["", ""] {
{ let (a有, b有) = (A.hasattr()?, B.hasattr()?);
if let (Ok(a), Ok(b)) = ( if a有 && !b有 {
A.cast::<crate::types_py::Py>(), return Ok((false, format!("{标签}: [{字段}] A存在 B缺失属性")));
B.cast::<crate::types_py::Py>(),
) {
return Ok(a.borrow().inner.(&b.borrow().inner, ));
}
let = "缺口校验";
for & in &["", ""] {
let (a有, b有) = (A.hasattr()?, B.hasattr()?);
if a有 && !b有 {
return Ok((false, format!("{标签}: [{字段}] A存在 B缺失属性")));
}
if !a有 && b有 {
return Ok((false, format!("{标签}: [{字段}] B存在 A缺失属性")));
}
if !a有 && !b有 {
continue;
}
let valA = A.getattr()?;
let valB = B.getattr()?;
if let Some(r) = (&valA, &valB, ) {
let (ok, m) = r?;
if !ok {
return Ok((false, format!("{标签}: [{字段}]{m}")));
}
} else {
let eq: bool = valA.eq(&valB)?;
if !eq {
let ra = valA.repr()?.extract::<String>().unwrap_or_default();
let rb = valB.repr()?.extract::<String>().unwrap_or_default();
return Ok((false, format!("{标签}: [{字段}] 数值不等 A={ra},B={rb}")));
}
}
}
Ok((true, format!("{标签}: 上下沿价格校验完全一致")))
} }
) if !a有 && b有 {
return Ok((false, format!("{标签}: [{字段}] B存在 A缺失属性")));
}
if !a有 && !b有 {
continue;
}
let valA = A.getattr()?;
let valB = B.getattr()?;
if let Some(r) = (&valA, &valB, ) {
let (ok, m) = r?;
if !ok {
return Ok((false, format!("{标签}: [{字段}]{m}")));
}
} else {
let eq: bool = valA.eq(&valB)?;
if !eq {
let ra = valA.repr()?.extract::<String>().unwrap_or_default();
let rb = valB.repr()?.extract::<String>().unwrap_or_default();
return Ok((false, format!("{标签}: [{字段}] 数值不等 A={ra},B={rb}")));
}
}
}
Ok((true, format!("{标签}: 上下沿价格校验完全一致")))
} }
// ========== 线段特征相等 ========== // ========== 线段特征相等 ==========
#[pyfunction] #[pyfunction]
@@ -462,74 +394,60 @@ fn 线段特征相等(
B: &Bound<'_, PyAny>, B: &Bound<'_, PyAny>,
: f64, : f64,
) -> PyResult<(bool, String)> { ) -> PyResult<(bool, String)> {
with_cache!( if let (Ok(a), Ok(b)) = (
C_SEG_FEAT, A.cast::<crate::structure_py::线Py>(),
4096, B.cast::<crate::structure_py::线Py>(),
( ) {
A.as_ptr() as usize, return Ok(a.borrow().inner.(&b.borrow().inner, ));
B.as_ptr() as usize, }
.to_bits() as i64 let = "线段特征校验";
), for & in &["序号", "标识", "线段方向", "基础序列"] {
{ let (a有, b有) = (A.hasattr()?, B.hasattr()?);
if let (Ok(a), Ok(b)) = ( if a有 && !b有 {
A.cast::<crate::structure_py::线Py>(), return Ok((false, format!("{标签}: [{字段}] A存在 B缺失属性")));
B.cast::<crate::structure_py::线Py>(),
) {
return Ok(a.borrow().inner.(&b.borrow().inner, ));
}
let = "线段特征校验";
for & in &["序号", "标识", "线段方向", "基础序列"] {
let (a有, b有) = (A.hasattr()?, B.hasattr()?);
if a有 && !b有 {
return Ok((false, format!("{标签}: [{字段}] A存在 B缺失属性")));
}
if !a有 && b有 {
return Ok((false, format!("{标签}: [{字段}] B存在 A缺失属性")));
}
if !a有 && !b有 {
continue;
}
let valA = A.getattr()?;
let valB = B.getattr()?;
if == "基础序列" {
let len_a: usize = valA.len()?;
let len_b: usize = valB.len()?;
if len_a != len_b {
return Ok((
false,
format!("{标签}: [基础序列] 列表长度不一致 A={len_a},B={len_b}"),
));
}
for idx in 0..len_a {
let itemA = valA.get_item(idx)?;
let itemB = valB.get_item(idx)?;
let (eq, msg) = 线(&itemA, &itemB, )?;
if !eq {
return Ok((
false,
format!("{标签}: 基础序列[{idx}]子虚线异常 >> {msg}"),
));
}
}
} else if == "线段方向" {
let sa = valA.str()?.extract::<String>().unwrap_or_default();
let sb = valB.str()?.extract::<String>().unwrap_or_default();
if sa != sb {
return Ok((false, format!("{标签}: [{字段}] 数值不等 A={sa},B={sb}")));
}
} else {
let eq: bool = valA.eq(&valB)?;
if !eq {
let ra = valA.repr()?.extract::<String>().unwrap_or_default();
let rb = valB.repr()?.extract::<String>().unwrap_or_default();
return Ok((false, format!("{标签}: [{字段}] 数值不等 A={ra},B={rb}")));
}
}
}
Ok((true, format!("{标签}: 字段与内部虚线序列全部一致")))
} }
) if !a有 && b有 {
return Ok((false, format!("{标签}: [{字段}] B存在 A缺失属性")));
}
if !a有 && !b有 {
continue;
}
let valA = A.getattr()?;
let valB = B.getattr()?;
if == "基础序列" {
let len_a: usize = valA.len()?;
let len_b: usize = valB.len()?;
if len_a != len_b {
return Ok((
false,
format!("{标签}: [基础序列] 列表长度不一致 A={len_a},B={len_b}"),
));
}
for idx in 0..len_a {
let itemA = valA.get_item(idx)?;
let itemB = valB.get_item(idx)?;
let (eq, msg) = 线(&itemA, &itemB, )?;
if !eq {
return Ok((false, format!("{标签}: 基础序列[{idx}]子虚线异常 >> {msg}")));
}
}
} else if == "线段方向" {
let sa = valA.str()?.extract::<String>().unwrap_or_default();
let sb = valB.str()?.extract::<String>().unwrap_or_default();
if sa != sb {
return Ok((false, format!("{标签}: [{字段}] 数值不等 A={sa},B={sb}")));
}
} else {
let eq: bool = valA.eq(&valB)?;
if !eq {
let ra = valA.repr()?.extract::<String>().unwrap_or_default();
let rb = valB.repr()?.extract::<String>().unwrap_or_default();
return Ok((false, format!("{标签}: [{字段}] 数值不等 A={ra},B={rb}")));
}
}
}
Ok((true, format!("{标签}: 字段与内部虚线序列全部一致")))
} }
// ========== 中枢相等 ========== // ========== 中枢相等 ==========
@@ -790,8 +708,8 @@ fn 观察者相等(
.inner .inner
.clone() .clone()
.ok_or_else(|| pyo3::exceptions::PyValueError::new_err("观察者B 内部为空"))?; .ok_or_else(|| pyo3::exceptions::PyValueError::new_err("观察者B 内部为空"))?;
let obs_a = arc_a.read().unwrap(); let obs_a = arc_a.read();
let obs_b = arc_b.read().unwrap(); let obs_b = arc_b.read();
Ok(obs_a.(&obs_b, )) Ok(obs_a.(&obs_b, ))
} }
+17 -21
View File
@@ -290,7 +290,7 @@ impl 相对强弱指数Py {
} }
#[getter] #[getter]
fn RSI历史队列(&self) -> Vec<f64> { fn RSI历史队列(&self) -> Vec<f64> {
self.inner.RSI历史队列.clone() self.inner.RSI历史队列.iter().copied().collect()
} }
fn __str__(&self) -> String { fn __str__(&self) -> String {
@@ -470,11 +470,11 @@ impl 随机指标Py {
} }
#[getter] #[getter]
fn (&self) -> Vec<f64> { fn (&self) -> Vec<f64> {
self.inner..clone() self.inner..iter().copied().collect()
} }
#[getter] #[getter]
fn (&self) -> Vec<f64> { fn (&self) -> Vec<f64> {
self.inner..clone() self.inner..iter().copied().collect()
} }
#[getter] #[getter]
fn RSV(&self) -> Option<f64> { fn RSV(&self) -> Option<f64> {
@@ -796,16 +796,12 @@ impl 指标容器Py {
} }
fn __getitem__(&self, : &str, py: Python<'_>) -> PyResult<Py<PyAny>> { fn __getitem__(&self, : &str, py: Python<'_>) -> PyResult<Py<PyAny>> {
if self.() { match self.inner.() {
match self.inner.() { Some(v) => _to_py(v, py),
Some(v) => _to_py(v, py), None => Err(pyo3::exceptions::PyKeyError::new_err(format!(
None => Ok(py.None()),
}
} else {
Err(pyo3::exceptions::PyKeyError::new_err(format!(
"指标 '{}' 不存在", "指标 '{}' 不存在",
))) ))),
} }
} }
@@ -822,16 +818,12 @@ impl 指标容器Py {
} }
return Ok(dict.into()); return Ok(dict.into());
} }
if self.() { match self.inner.() {
match self.inner.() { Some(v) => _to_py(v, py),
Some(v) => _to_py(v, py), None => Err(pyo3::exceptions::PyAttributeError::new_err(format!(
None => Ok(py.None()),
}
} else {
Err(pyo3::exceptions::PyAttributeError::new_err(format!(
"指标 '{}' 不存在于 指标容器 中", "指标 '{}' 不存在于 指标容器 中",
))) ))),
} }
} }
@@ -917,7 +909,12 @@ impl 均线工具Py {
return Ok(sum / (n.max(1)) as f64); return Ok(sum / (n.max(1)) as f64);
} }
let prev_key = format!("SMA_{}", period); let prev_key = {
let mut s = String::with_capacity(8);
use std::fmt::Write;
write!(&mut s, "SMA_{}", period).unwrap();
s
};
// 尝试从前一根K线的均线缓存中读取 // 尝试从前一根K线的均线缓存中读取
let prev_cached = K序列[n - 2] let prev_cached = K序列[n - 2]
.bind(py) .bind(py)
@@ -925,7 +922,6 @@ impl 均线工具Py {
.inner .inner
. .
.read() .read()
.unwrap()
.线() .线()
.and_then(|m| m.get(&prev_key)) .and_then(|m| m.get(&prev_key))
.copied(); .copied();
+47 -56
View File
@@ -22,11 +22,11 @@
* SOFTWARE. * SOFTWARE.
*/ */
use parking_lot::RwLock;
use pyo3::prelude::*; use pyo3::prelude::*;
use pyo3::types::{PyBytes, PyDict, PyList, PyType}; use pyo3::types::{PyBytes, PyDict, PyList, PyType};
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::Arc; use std::sync::Arc;
use std::sync::RwLock;
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
use crate::config_py::Py; use crate::config_py::Py;
@@ -149,7 +149,6 @@ impl K线Py {
self.inner self.inner
. .
.read() .read()
.unwrap()
.macd_cloned() .macd_cloned()
.map(|m| 线Py { inner: m }) .map(|m| 线Py { inner: m })
} }
@@ -159,7 +158,6 @@ impl K线Py {
self.inner self.inner
. .
.read() .read()
.unwrap()
.rsi_cloned() .rsi_cloned()
.map(|r| Py { inner: r }) .map(|r| Py { inner: r })
} }
@@ -169,7 +167,6 @@ impl K线Py {
self.inner self.inner
. .
.read() .read()
.unwrap()
.kdj_cloned() .kdj_cloned()
.map(|k| Py { inner: k }) .map(|k| Py { inner: k })
} }
@@ -178,7 +175,7 @@ impl K线Py {
#[getter] #[getter]
fn (&self) -> Py { fn (&self) -> Py {
Py { Py {
inner: self.inner..read().unwrap().clone(), inner: self.inner..read().clone(),
} }
} }
@@ -339,6 +336,39 @@ impl K线Py {
.take(end_idx - start_idx + 1) .take(end_idx - start_idx + 1)
.collect()) .collect())
} }
/// 根据当前K线和方向生成下一根K线(用于随机回测)
#[pyo3(signature = (方向, 居中 = false))]
fn K线生成新K线(
&self, : &Bound<'_, PyAny>, : bool
) -> PyResult<Self> {
let dir: chanlun::types:: = if let Ok(d) = .extract::<PyRef<'_, Py>>()
{
d.inner
} else if let Ok(i) = .extract::<i64>() {
match i {
0 => chanlun::types::::,
1 => chanlun::types::::,
2 => chanlun::types::::,
3 => chanlun::types::::,
4 => chanlun::types::::,
5 => chanlun::types::::,
_ => {
return Err(pyo3::exceptions::PyValueError::new_err(format!(
"无效方向: {i}"
)));
}
}
} else {
return Err(pyo3::exceptions::PyTypeError::new_err(
"方向 必须是 相对方向 或 int (0-5)",
));
};
let new_bar = self.inner.K线生成新K线(dir, );
Ok(Self {
inner: Arc::new(new_bar),
})
}
} }
// ========== 缠论K线 ========== // ========== 缠论K线 ==========
@@ -371,57 +401,29 @@ impl 缠论K线Py {
} }
} }
/// 对象标识缓存: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 KLINE_IDENTITY: std::sync::LazyLock<RwLock<HashMap<usize, Py<K线Py>>>> =
std::sync::LazyLock::new(|| 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( pub(crate) fn bar_to_py(
py: Python<'_>, py: Python<'_>,
inner: std::sync::Arc<chanlun::kline::bar::K线>, inner: std::sync::Arc<chanlun::kline::bar::K线>,
) -> Py<K线Py> { ) -> Py<K线Py> {
let key = Arc::as_ptr(&inner) as usize; let key = Arc::as_ptr(&inner) as usize;
if let Some(cached) = BAR_IDENTITY if let Some(cached) = crate::cache::bar_get(py, key) {
.read()
.unwrap()
.get(&key)
.map(|p| p.clone_ref(py))
{
return cached; return cached;
} }
let obj = Py::new(py, K线Py { inner }).unwrap(); let obj = Py::new(py, K线Py { inner }).unwrap();
BAR_IDENTITY.write().unwrap().insert(key, obj.clone_ref(py)); crate::cache::bar_insert(py, key, &obj);
obj obj
} }
/// 将 Rc<缠论K线> 转为 Py<缠论K线Py>,确保同一 Rc 地址总是返回同一 Python 对象
pub(crate) fn chan_kline_to_py( pub(crate) fn chan_kline_to_py(
py: Python<'_>, py: Python<'_>,
inner: std::sync::Arc<chanlun::kline::chan_kline::K线>, inner: std::sync::Arc<chanlun::kline::chan_kline::K线>,
) -> Py<K线Py> { ) -> Py<K线Py> {
let key = Arc::as_ptr(&inner) as usize; let key = Arc::as_ptr(&inner) as usize;
if let Some(cached) = KLINE_IDENTITY if let Some(cached) = crate::cache::kline_get(py, key) {
.read()
.unwrap()
.get(&key)
.map(|p| p.clone_ref(py))
{
return cached; return cached;
} }
let obj = Py::new(py, K线Py::from_rc(inner)).unwrap(); let obj = Py::new(py, K线Py::from_rc(inner)).unwrap();
KLINE_IDENTITY crate::cache::kline_insert(py, key, &obj);
.write()
.unwrap()
.insert(key, obj.clone_ref(py));
obj obj
} }
@@ -462,7 +464,7 @@ impl 缠论K线Py {
#[getter] #[getter]
fn (&self, py: Python<'_>) -> Py<Py> { fn (&self, py: Python<'_>) -> Py<Py> {
crate::types_py::(py, *self.inner..read().unwrap()) crate::types_py::(py, *self.inner..read())
} }
#[getter] #[getter]
@@ -470,7 +472,6 @@ impl 缠论K线Py {
self.inner self.inner
. .
.read() .read()
.unwrap()
.map(|f| crate::types_py::(py, f)) .map(|f| crate::types_py::(py, f))
} }
@@ -501,7 +502,7 @@ impl 缠论K线Py {
#[getter] #[getter]
fn K线(&self, py: Python<'_>) -> Py<K线Py> { fn K线(&self, py: Python<'_>) -> Py<K线Py> {
bar_to_py(py, self.inner.K线.read().unwrap().clone()) bar_to_py(py, self.inner.K线.read().clone())
} }
/// pandas 兼容 — 返回所有字段构成的字典 /// pandas 兼容 — 返回所有字段构成的字典
@@ -556,11 +557,7 @@ impl 缠论K线Py {
// 复制买卖点信息到镜像 // 复制买卖点信息到镜像
let src_key = Arc::as_ptr(&self.inner) as usize; let src_key = Arc::as_ptr(&self.inner) as usize;
let dst_key = Arc::as_ptr(&mirror.inner) as usize; let dst_key = Arc::as_ptr(&mirror.inner) as usize;
let cached_src = BSP_CACHE let cached_src = crate::cache::bsp_get(py, src_key);
.read()
.unwrap()
.get(&src_key)
.map(|p| p.clone_ref(py));
if let Some(cached_src) = cached_src if let Some(cached_src) = cached_src
&& let Ok(new_set) = pyo3::types::PySet::empty(py) && let Ok(new_set) = pyo3::types::PySet::empty(py)
{ {
@@ -568,7 +565,7 @@ impl 缠论K线Py {
let _ = new_set.add(item); let _ = new_set.add(item);
} }
let py_set: Py<pyo3::types::PySet> = new_set.into(); let py_set: Py<pyo3::types::PySet> = new_set.into();
BSP_CACHE.write().unwrap().insert(dst_key, py_set); crate::cache::bsp_insert(py, dst_key, py_set);
} }
mirror mirror
} }
@@ -595,25 +592,19 @@ impl 缠论K线Py {
fn (&self, py: Python<'_>) -> PyResult<Py<PyAny>> { fn (&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
let key = Arc::as_ptr(&self.inner) as usize; let key = Arc::as_ptr(&self.inner) as usize;
// 检查全局缓存 // 检查全局缓存
let cached = BSP_CACHE.read().unwrap().get(&key).map(|p| p.clone_ref(py)); let cached = crate::cache::bsp_get(py, key);
if let Some(set) = cached { if let Some(set) = cached {
return Ok(set.into_any()); return Ok(set.into_any());
} }
// 创建新的 PySet,从 Rust HashSet 同步已有内容 // 创建新的 PySet,从 Rust HashSet 同步已有内容
let set = pyo3::types::PySet::empty(py)?; let set = pyo3::types::PySet::empty(py)?;
let bsp_info = self.inner..read().unwrap(); let bsp_info = self.inner..read();
for item in bsp_info.iter() { for item in bsp_info.iter() {
set.add(item.as_str())?; set.add(item.as_str())?;
} }
drop(bsp_info); drop(bsp_info);
BSP_CACHE.write().unwrap().insert(key, set.into()); crate::cache::bsp_insert(py, key, set.into());
Ok(BSP_CACHE Ok(crate::cache::bsp_get(py, key).unwrap().into_any())
.read()
.unwrap()
.get(&key)
.unwrap()
.clone_ref(py)
.into_any())
} }
#[classmethod] #[classmethod]
+87 -15
View File
@@ -102,6 +102,7 @@ fn init_tracing() {
mod algorithm_py; mod algorithm_py;
mod business_py; mod business_py;
pub(crate) mod cache;
mod config_py; mod config_py;
mod equality_py; mod equality_py;
mod indicators_py; mod indicators_py;
@@ -121,15 +122,25 @@ fn set_分型模式(value: bool) {
chanlun::structure::fractal_obj::.store(value, Ordering::Relaxed); chanlun::structure::fractal_obj::.store(value, Ordering::Relaxed);
} }
/// 扩展线段模式 — 控制虚线高低取值方式,默认 False
#[pyfunction]
fn get_扩展线段模式() -> bool {
chanlun::structure::dash_line::线.load(Ordering::Relaxed)
}
/// 设置 扩展线段模式
#[pyfunction]
fn set_扩展线段模式(value: bool) {
chanlun::structure::dash_line::线.store(value, Ordering::Relaxed);
}
/// 获取当前日志级别 ("trace" / "debug" / "info" / "warn" / "error" / "off") /// 获取当前日志级别 ("trace" / "debug" / "info" / "warn" / "error" / "off")
#[pyfunction] #[pyfunction]
fn get_log_level() -> &'static str { fn get_log_level() -> &'static str {
(LOG_LEVEL.load(Ordering::Relaxed)) (LOG_LEVEL.load(Ordering::Relaxed))
} }
/// 设置日志级别 (不区分大小写: "trace" / "debug" / "info" / "warn" / "error" / "off") /// 设置日志级别 — 自动启用日志,同步更新 tracing subscriber
///
/// 设为 "off" 可完全关闭日志输出。
#[pyfunction] #[pyfunction]
fn set_log_level(level: &str) -> PyResult<()> { fn set_log_level(level: &str) -> PyResult<()> {
let = (level).ok_or_else(|| { let = (level).ok_or_else(|| {
@@ -138,30 +149,91 @@ fn set_log_level(level: &str) -> PyResult<()> {
level level
)) ))
})?; })?;
let guard =
.get()
.ok_or_else(|| pyo3::exceptions::PyRuntimeError::new_err("日志系统尚未初始化"))?;
let handle = guard.lock().unwrap();
let = ();
let filter = tracing_subscriber::EnvFilter::new();
handle
.reload(filter)
.map_err(|_| pyo3::exceptions::PyRuntimeError::new_err("切换日志级别失败"))?;
LOG_LEVEL.store(, Ordering::Relaxed); LOG_LEVEL.store(, Ordering::Relaxed);
chanlun::log::.store( < 5, Ordering::Relaxed);
// 同步更新 tracing subscriber
if let Some(guard) = .get() {
let handle = guard.lock().unwrap();
let = ();
let filter = tracing_subscriber::EnvFilter::new();
let _ = handle.reload(filter);
}
Ok(()) Ok(())
} }
/// 获取日志输出模式 ("off", "simple", "tracing")
#[pyfunction]
fn get_log_mode() -> &'static str {
match chanlun::log::get_log_mode() {
0 => "off",
1 => "simple",
2 => "tracing",
_ => "unknown",
}
}
/// 设置日志输出模式(必须在任何日志输出之前调用)
/// - "off": 不输出
/// - "simple": 直接 eprintln/println(默认)
/// - "tracing": 带时间戳和格式化的 tracing subscriber
#[pyfunction]
fn set_log_mode(mode: &str) -> PyResult<()> {
let m = match mode.to_lowercase().as_str() {
"off" | "0" => 0u8,
"simple" | "on" | "1" => 1u8,
"tracing" | "2" => 2u8,
_ => {
return Err(pyo3::exceptions::PyValueError::new_err(
"无效日志模式,有效值: 'off', 'simple', 'tracing'",
));
}
};
if m == 2 {
init_tracing();
}
chanlun::log::set_log_mode(m);
Ok(())
}
/// 获取缓存模式 ("thread_local" 或 "global")
#[pyfunction]
fn get_cache_mode() -> &'static str {
match crate::cache::peek_mode().unwrap_or(&crate::cache::CacheMode::ThreadLocal) {
crate::cache::CacheMode::ThreadLocal => "thread_local",
crate::cache::CacheMode::Global => "global",
}
}
/// 设置缓存模式(必须在创建任何观察者之前调用)
#[pyfunction]
fn set_cache_mode(mode: &str) -> PyResult<()> {
let m = match mode.to_lowercase().as_str() {
"thread_local" | "local" => crate::cache::CacheMode::ThreadLocal,
"global" => crate::cache::CacheMode::Global,
_ => {
return Err(pyo3::exceptions::PyValueError::new_err(
"无效缓存模式,有效值: 'thread_local', 'global'",
));
}
};
crate::cache::set_mode(m).map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e))
}
/// 缠论技术分析库 — Rust 高性能实现 /// 缠论技术分析库 — Rust 高性能实现
#[pymodule] #[pymodule]
/// 缠论技术分析库 — Rust 高性能实现 /// 缠论技术分析库 — Rust 高性能实现
fn _chanlun(_py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> { fn _chanlun(_py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> {
init_tracing(); chanlun::log::init_from_env();
m.add_function(wrap_pyfunction!(get_分型模式, m)?)?; m.add_function(wrap_pyfunction!(get_分型模式, m)?)?;
m.add_function(wrap_pyfunction!(set_分型模式, m)?)?; m.add_function(wrap_pyfunction!(set_分型模式, m)?)?;
m.add_function(wrap_pyfunction!(get_扩展线段模式, m)?)?;
m.add_function(wrap_pyfunction!(set_扩展线段模式, m)?)?;
m.add_function(wrap_pyfunction!(get_log_level, m)?)?; m.add_function(wrap_pyfunction!(get_log_level, m)?)?;
m.add_function(wrap_pyfunction!(set_log_level, m)?)?; m.add_function(wrap_pyfunction!(set_log_level, m)?)?;
m.add_function(wrap_pyfunction!(get_log_mode, m)?)?;
m.add_function(wrap_pyfunction!(set_log_mode, m)?)?;
m.add_function(wrap_pyfunction!(get_cache_mode, m)?)?;
m.add_function(wrap_pyfunction!(set_cache_mode, m)?)?;
// 阶段 1: 枚举和基础类型 // 阶段 1: 枚举和基础类型
types_py::register(m)?; types_py::register(m)?;
// 阶段 2: 配置 // 阶段 2: 配置
+18 -75
View File
@@ -24,9 +24,7 @@
use pyo3::prelude::*; use pyo3::prelude::*;
use pyo3::types::{PyDict, PyType}; use pyo3::types::{PyDict, PyType};
use std::collections::HashMap;
use std::sync::Arc; use std::sync::Arc;
use std::sync::RwLock;
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
use crate::algorithm_py::hub_to_py; use crate::algorithm_py::hub_to_py;
@@ -35,37 +33,18 @@ use crate::kline_py::{K线Py, bar_to_py, 缠论K线Py};
// ---- 身份缓存 (弱引用:通过 refcnt 检测存活,仅缓存持有则视为过期) ---- // ---- 身份缓存 (弱引用:通过 refcnt 检测存活,仅缓存持有则视为过期) ----
// 使用全局 static 而非 thread_local!,保证跨线程对象标识一致性 // 缓存通过 crate::cache 模块管理(支持 thread_local / global 运行时切换)
static FRACTAL_IDENTITY: std::sync::LazyLock<RwLock<HashMap<usize, Py<Py>>>> =
std::sync::LazyLock::new(|| RwLock::new(HashMap::new()));
static DASHED_IDENTITY: std::sync::LazyLock<RwLock<HashMap<usize, Py<线Py>>>> =
std::sync::LazyLock::new(|| RwLock::new(HashMap::new()));
static SEGFEAT_IDENTITY: std::sync::LazyLock<RwLock<HashMap<usize, Py<线Py>>>> =
std::sync::LazyLock::new(|| RwLock::new(HashMap::new()));
pub(crate) fn fractal_to_py( pub(crate) fn fractal_to_py(
py: Python<'_>, py: Python<'_>,
inner: Arc<chanlun::structure::fractal_obj::>, inner: Arc<chanlun::structure::fractal_obj::>,
) -> Py<Py> { ) -> Py<Py> {
let key = Arc::as_ptr(&inner) as usize; let key = Arc::as_ptr(&inner) as usize;
if let Some(cached) = FRACTAL_IDENTITY if let Some(cached) = crate::cache::fractal_get(py, key) {
.read()
.unwrap()
.get(&key)
.map(|p| p.clone_ref(py))
{
return cached; return cached;
} }
// 清理 refcnt==1 的过期条目(仅缓存持有,Python 侧已无引用)
FRACTAL_IDENTITY
.write()
.unwrap()
.retain(|_, v| v.get_refcnt(py) > 1);
let obj = Py::new(py, Py { inner }).unwrap(); let obj = Py::new(py, Py { inner }).unwrap();
FRACTAL_IDENTITY crate::cache::fractal_insert(py, key, &obj);
.write()
.unwrap()
.insert(key, obj.clone_ref(py));
obj obj
} }
@@ -74,23 +53,11 @@ pub(crate) fn dashed_to_py(
inner: Arc<chanlun::structure::dash_line::线>, inner: Arc<chanlun::structure::dash_line::线>,
) -> Py<线Py> { ) -> Py<线Py> {
let key = Arc::as_ptr(&inner) as usize; let key = Arc::as_ptr(&inner) as usize;
if let Some(cached) = DASHED_IDENTITY if let Some(cached) = crate::cache::dashed_get(py, key) {
.read()
.unwrap()
.get(&key)
.map(|p| p.clone_ref(py))
{
return cached; return cached;
} }
DASHED_IDENTITY
.write()
.unwrap()
.retain(|_, v| v.get_refcnt(py) > 1);
let obj = Py::new(py, 线Py { inner }).unwrap(); let obj = Py::new(py, 线Py { inner }).unwrap();
DASHED_IDENTITY crate::cache::dashed_insert(py, key, &obj);
.write()
.unwrap()
.insert(key, obj.clone_ref(py));
obj obj
} }
@@ -98,25 +65,7 @@ pub(crate) fn segfeat_to_py(
py: Python<'_>, py: Python<'_>,
inner: Arc<chanlun::structure::segment_feat::线>, inner: Arc<chanlun::structure::segment_feat::线>,
) -> Py<线Py> { ) -> Py<线Py> {
let key = Arc::as_ptr(&inner) as usize; Py::new(py, 线Py { inner }).unwrap()
if let Some(cached) = SEGFEAT_IDENTITY
.read()
.unwrap()
.get(&key)
.map(|p| p.clone_ref(py))
{
return cached;
}
SEGFEAT_IDENTITY
.write()
.unwrap()
.retain(|_, v| v.get_refcnt(py) > 1);
let obj = Py::new(py, 线Py { inner }).unwrap();
SEGFEAT_IDENTITY
.write()
.unwrap()
.insert(key, obj.clone_ref(py));
obj
} }
use crate::types_py::{Py, Py, Py}; use crate::types_py::{Py, Py, Py};
@@ -393,7 +342,7 @@ impl 虚线Py {
#[getter] #[getter]
fn (&self) -> String { fn (&self) -> String {
self.inner..read().unwrap().clone() self.inner..read().clone()
} }
#[getter] #[getter]
@@ -413,7 +362,7 @@ impl 虚线Py {
#[getter] #[getter]
fn (&self, py: Python<'_>) -> Py<Py> { fn (&self, py: Python<'_>) -> Py<Py> {
fractal_to_py(py, Arc::clone(&*self.inner..read().unwrap())) fractal_to_py(py, Arc::clone(&*self.inner..read()))
} }
#[getter] #[getter]
@@ -423,7 +372,7 @@ impl 虚线Py {
#[getter] #[getter]
fn (&self) -> String { fn (&self) -> String {
self.inner..read().unwrap().clone() self.inner..read().clone()
} }
#[getter(_特征序列_显示)] #[getter(_特征序列_显示)]
@@ -439,7 +388,7 @@ impl 虚线Py {
#[getter] #[getter]
fn (&self, py: Python<'_>) -> PyResult<Py<PyAny>> { fn (&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
let list = pyo3::types::PyList::empty(py); let list = pyo3::types::PyList::empty(py);
for item in self.inner..read().unwrap().iter() { for item in self.inner..read().iter() {
match item { match item {
Some(feat) => list.append(segfeat_to_py(py, Arc::clone(feat)))?, Some(feat) => list.append(segfeat_to_py(py, Arc::clone(feat)))?,
None => { None => {
@@ -460,18 +409,13 @@ impl 虚线Py {
self.inner self.inner
.K线 .K线
.read() .read()
.unwrap()
.as_ref() .as_ref()
.map(|k| crate::kline_py::chan_kline_to_py(py, Arc::clone(k))) .map(|k| crate::kline_py::chan_kline_to_py(py, Arc::clone(k)))
} }
#[getter] #[getter]
fn (&self) -> Option<Py> { fn (&self) -> Option<Py> {
self.inner self.inner..read().map(|q| Py { inner: q })
.
.read()
.unwrap()
.map(|q| Py { inner: q })
} }
#[getter] #[getter]
@@ -479,7 +423,6 @@ impl 虚线Py {
self.inner self.inner
. .
.read() .read()
.unwrap()
.as_ref() .as_ref()
.map(|d| dashed_to_py(py, Arc::clone(d))) .map(|d| dashed_to_py(py, Arc::clone(d)))
} }
@@ -489,7 +432,7 @@ impl 虚线Py {
#[getter] #[getter]
fn (&self, py: Python<'_>) -> PyResult<Py<PyAny>> { fn (&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
let list = pyo3::types::PyList::empty(py); let list = pyo3::types::PyList::empty(py);
for d in self.inner..read().unwrap().iter() { for d in self.inner..read().iter() {
list.append(dashed_to_py(py, Arc::clone(d)))?; list.append(dashed_to_py(py, Arc::clone(d)))?;
} }
Ok(list.into()) Ok(list.into())
@@ -498,7 +441,7 @@ impl 虚线Py {
#[getter] #[getter]
fn _中枢序列(&self, py: Python<'_>) -> PyResult<Py<PyAny>> { fn _中枢序列(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
let list = pyo3::types::PyList::empty(py); let list = pyo3::types::PyList::empty(py);
for h in self.inner._中枢序列.read().unwrap().iter() { for h in self.inner._中枢序列.read().iter() {
list.append(hub_to_py(py, Arc::clone(h)))?; list.append(hub_to_py(py, Arc::clone(h)))?;
} }
Ok(list.into()) Ok(list.into())
@@ -507,7 +450,7 @@ impl 虚线Py {
#[getter] #[getter]
fn _中枢序列(&self, py: Python<'_>) -> PyResult<Py<PyAny>> { fn _中枢序列(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
let list = pyo3::types::PyList::empty(py); let list = pyo3::types::PyList::empty(py);
for h in self.inner._中枢序列.read().unwrap().iter() { for h in self.inner._中枢序列.read().iter() {
list.append(hub_to_py(py, Arc::clone(h)))?; list.append(hub_to_py(py, Arc::clone(h)))?;
} }
Ok(list.into()) Ok(list.into())
@@ -516,7 +459,7 @@ impl 虚线Py {
#[getter] #[getter]
fn _中枢序列(&self, py: Python<'_>) -> PyResult<Py<PyAny>> { fn _中枢序列(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
let list = pyo3::types::PyList::empty(py); let list = pyo3::types::PyList::empty(py);
for h in self.inner._中枢序列.read().unwrap().iter() { for h in self.inner._中枢序列.read().iter() {
list.append(hub_to_py(py, Arc::clone(h)))?; list.append(hub_to_py(py, Arc::clone(h)))?;
} }
Ok(list.into()) Ok(list.into())
@@ -528,7 +471,7 @@ impl 虚线Py {
/// 笔序列 /// 笔序列
fn (&self, py: Python<'_>) -> PyResult<Py<PyAny>> { fn (&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
let list = pyo3::types::PyList::empty(py); let list = pyo3::types::PyList::empty(py);
for d in self.inner..read().unwrap().iter() { for d in self.inner..read().iter() {
list.append(dashed_to_py(py, Arc::clone(d)))?; list.append(dashed_to_py(py, Arc::clone(d)))?;
} }
Ok(list.into()) Ok(list.into())
@@ -1005,12 +948,12 @@ impl 线段特征Py {
#[getter] #[getter]
fn (&self) -> String { fn (&self) -> String {
self.inner..read().unwrap().clone() self.inner..read().clone()
} }
#[setter] #[setter]
fn set_标识(&self, value: String) { fn set_标识(&self, value: String) {
*self.inner..write().unwrap() = value; *self.inner..write() = value;
} }
#[getter] #[getter]
+19 -3
View File
@@ -22,8 +22,8 @@
* SOFTWARE. * SOFTWARE.
*/ */
use parking_lot::Mutex;
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::Mutex;
use pyo3::basic::CompareOp; use pyo3::basic::CompareOp;
use pyo3::prelude::*; use pyo3::prelude::*;
@@ -37,7 +37,7 @@ pub fn 获取分型结构单例(
py: Python<'_>, py: Python<'_>,
inner: chanlun::types::, inner: chanlun::types::,
) -> Py<Py> { ) -> Py<Py> {
let mut guard = _单例缓存.lock().unwrap(); let mut guard = _单例缓存.lock();
if let Some(ref map) = *guard { if let Some(ref map) = *guard {
return map[&(inner as u8)].clone_ref(py); return map[&(inner as u8)].clone_ref(py);
} }
@@ -67,7 +67,7 @@ pub fn 获取相对方向单例(
py: Python<'_>, py: Python<'_>,
inner: chanlun::types::, inner: chanlun::types::,
) -> Py<Py> { ) -> Py<Py> {
let mut guard = _单例缓存.lock().unwrap(); let mut guard = _单例缓存.lock();
if let Some(ref map) = *guard { if let Some(ref map) = *guard {
return map[&(inner as u8)].clone_ref(py); return map[&(inner as u8)].clone_ref(py);
} }
@@ -312,6 +312,22 @@ impl 相对方向Py {
chanlun::types::::(, , , ), chanlun::types::::(, , , ),
) )
} }
/// 从可选方向序列中随机选取指定数量
#[classmethod]
#[pyo3(signature = (数量, 可选方向, 可重复 = true))]
fn (
_cls: &Bound<'_, PyType>,
: usize,
: Vec<Py<Self>>,
: bool,
py: Python<'_>,
) -> Vec<Py<Self>> {
let dirs: Vec<chanlun::types::> =
.iter().map(|d| d.borrow(py).inner).collect();
let result = chanlun::types::::(, &dirs, );
result.iter().map(|d| (py, *d)).collect()
}
} }
// ========== 分型结构 ========== // ========== 分型结构 ==========
+69
View File
@@ -2776,5 +2776,74 @@ class Test缠论配置双端一致(unittest.TestCase):
self.assertFalse(diff_py[k], f"不推送差异字段 {k} 应为 False") self.assertFalse(diff_py[k], f"不推送差异字段 {k} 应为 False")
class Test生成K线双端一致(unittest.TestCase):
"""根据当前K线生成新K线 Rust vs chan.py 输出一致."""
def test_生成K线_居中各方向双端一致(self):
"""居中模式下各方向生成K线双端OHLC一致(居中=确定性输出)"""
import chanlun
from chanlun import chan
bar_rs = chanlun.K线.创建普K("btcusd", 1000000000, 50000, 50200, 49800, 50100, 100, 0, 300)
bar_py = chan.K线.创建普K("btcusd", chan.转化为时间戳(1000000000), 50000, 50200, 49800, 50100, 100, 0, 300)
directions = {
"向上": 0,
"向下": 1,
"向上缺口": 2,
"向下缺口": 3,
"衔接向上": 4,
"衔接向下": 5,
}
py_dirs = {
"向上": chan.相对方向.向上,
"向下": chan.相对方向.向下,
"向上缺口": chan.相对方向.向上缺口,
"向下缺口": chan.相对方向.向下缺口,
"衔接向上": chan.相对方向.衔接向上,
"衔接向下": chan.相对方向.衔接向下,
}
for name in directions:
new_rs = bar_rs.根据当前K线生成新K线(directions[name], 居中=True)
new_py = bar_py.根据当前K线生成新K线(py_dirs[name], 居中=True)
# 居中模式下,高和低是确定性的(偏移=高低差*0.5)
# 开盘价/收盘价/成交量含随机,不比较
tol = 1.0 + abs(new_py.) * 1e-6
self.assertAlmostEqual(new_rs., new_py., delta=tol, msg=f"{name}: 最高价不一致 (R={new_rs.}, P={new_py.})")
self.assertAlmostEqual(new_rs., new_py., delta=tol, msg=f"{name}: 最低价不一致 (R={new_rs.}, P={new_py.})")
# 时间戳和序号
self.assertEqual(new_rs.序号, new_py.序号)
self.assertEqual(int(new_rs.时间戳), int(chan.转化为时间戳_数字(new_py.时间戳)))
def test_生成K线_居中外推验证(self):
"""居中向上生成:新K线的高/低应整体高于原K线."""
import chanlun
bar = chanlun.K线.创建普K("btcusd", 1000000000, 50000, 50200, 49800, 50100, 100, 0, 300)
new = bar.根据当前K线生成新K线(0, 居中=True)
self.assertGreater(new., bar., "向上:新高应高于原高")
self.assertGreater(new., bar., "向上:新低应高于原低")
new_down = bar.根据当前K线生成新K线(1, 居中=True)
self.assertLess(new_down., bar., "向下:新高应低于原高")
self.assertLess(new_down., bar., "向下:新低应低于原低")
def test_生成K线_衔接验证(self):
"""衔接向上:新K线的低 = 原K线的高(无缝衔接)."""
import chanlun
bar = chanlun.K线.创建普K("btcusd", 1000000000, 50000, 50200, 49800, 50100, 100, 0, 300)
new = bar.根据当前K线生成新K线(4, 居中=True)
self.assertAlmostEqual(new., bar., delta=1e-6, msg="衔接向上:新低应=原高")
new_down = bar.根据当前K线生成新K线(5, 居中=True)
self.assertAlmostEqual(new_down., bar., delta=1e-6, msg="衔接向下:新高应=原低")
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()
+3 -3
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "chanlun" name = "chanlun"
version = "26.6.3" version = "26.6.4"
edition = "2024" edition = "2024"
license = "MIT" license = "MIT"
description = "基于缠论(缠中说禅)理论的量化技术分析核心库,支持流式数据处理和多周期联立分析。" description = "基于缠论(缠中说禅)理论的量化技术分析核心库,支持流式数据处理和多周期联立分析。"
@@ -17,6 +17,6 @@ serde = { version = "1", features = ["derive"] }
serde_json = "1" serde_json = "1"
byteorder = "1" byteorder = "1"
chrono = { version = "0.4", features = ["serde"] } chrono = { version = "0.4", features = ["serde"] }
cached = "1" parking_lot = "0.12"
tracing = "0.1" tracing = "0.1"
tracing-subscriber = "0.3" fastrand = "2"
+39 -42
View File
@@ -29,9 +29,9 @@ use crate::kline::chan_kline::缠论K线;
use crate::structure::dash_line::线; use crate::structure::dash_line::线;
use crate::structure::fractal_obj::; use crate::structure::fractal_obj::;
use crate::types::{, }; use crate::types::{, };
use crate::{error, warn};
use std::sync::Arc; use std::sync::Arc;
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
use tracing::{error, warn};
/// 笔 — 从分型生成笔的算法集合(静态方法命名空间) /// 笔 — 从分型生成笔的算法集合(静态方法命名空间)
pub struct ; pub struct ;
@@ -54,8 +54,7 @@ impl 笔 {
if let (Some(), Some()) = (&, &) { if let (Some(), Some()) = (&, &) {
let = 1 let = 1
+ (.K线.read().unwrap(). - .K线.read().unwrap().) + (.K线.read(). - .K线.read().).unsigned_abs() as usize;
.unsigned_abs() as usize;
if >= . as usize { if >= . as usize {
return . as usize; return . as usize;
} }
@@ -76,8 +75,8 @@ impl 笔 {
&& let (Some(_k), Some(_k)) = (&, &) && let (Some(_k), Some(_k)) = (&, &)
{ {
let = 1 let = 1
+ (_k.K线.read().unwrap(). - _k.K线.read().unwrap().) + (_k.K线.read(). - _k.K线.read().).unsigned_abs()
.unsigned_abs() as usize; as usize;
// 向上笔 // 向上笔
if .().() if .().()
&& _k..get() < .() && _k..get() < .()
@@ -219,7 +218,7 @@ impl 笔 {
/// 判断笔的相对关系是否合理 /// 判断笔的相对关系是否合理
pub fn _相对关系(: &线, : &) -> bool { pub fn _相对关系(: &线, : &) -> bool {
let = &.; let = &.;
let = ..read().unwrap(); let = ..read();
let = if . { let = if . {
let _rc = Arc::clone(&.); let _rc = Arc::clone(&.);
@@ -261,8 +260,8 @@ impl 笔 {
...get(), ...get(),
); );
if .K线包含整笔 { if .K线包含整笔 {
let = ..K线.read().unwrap(); let = ..K线.read();
let = ..K线.read().unwrap(); let = ..K线.read();
if crate::types::::(., ., ., .) if crate::types::::(., ., ., .)
.() .()
{ {
@@ -280,10 +279,7 @@ impl 笔 {
/// 以文会友 — 根据起点分型找笔 /// 以文会友 — 根据起点分型找笔
pub fn (: &[Arc<线>], : &Arc<>) -> Option<Arc<线>> { pub fn (: &[Arc<线>], : &Arc<>) -> Option<Arc<线>> {
.iter().find(|b| Arc::ptr_eq(&b., )).cloned()
.iter()
.find(|b| Arc::as_ptr(&b.) == Arc::as_ptr())
.cloned()
} }
/// 以武会友 — 根据终点分型找笔 /// 以武会友 — 根据终点分型找笔
@@ -291,7 +287,7 @@ impl 笔 {
.iter() .iter()
.rev() .rev()
.find(|b| Arc::as_ptr(&*b..read().unwrap()) == Arc::as_ptr()) .find(|b| Arc::ptr_eq(&*b..read(), ))
.cloned() .cloned()
} }
@@ -305,8 +301,7 @@ impl 笔 {
for b in .iter().rev() { for b in .iter().rev() {
// Python: 筆.文.中.序号 - 偏移 <= 缠K.序号 <= 筆.武.中.序号 // Python: 筆.文.中.序号 - 偏移 <= 缠K.序号 <= 筆.武.中.序号
if b....load(Ordering::Relaxed) - <= K..load(Ordering::Relaxed) if b....load(Ordering::Relaxed) - <= K..load(Ordering::Relaxed)
&& K..load(Ordering::Relaxed) && K..load(Ordering::Relaxed) <= b..read()...load(Ordering::Relaxed)
<= b..read().unwrap()...load(Ordering::Relaxed)
&& b... == K. && b... == K.
&& b... == K. && b... == K.
{ {
@@ -323,7 +318,7 @@ impl 笔 {
let = .pop(); let = .pop();
if let (Some(), Some()) = (.pop(), ) { if let (Some(), Some()) = (.pop(), ) {
assert!( assert!(
Arc::as_ptr(&..read().unwrap()) == Arc::as_ptr(&), Arc::ptr_eq(&..read(), &),
"最后一笔终点错误{}", "最后一笔终点错误{}",
); );
@@ -388,7 +383,7 @@ impl 笔 {
// Python line 2343-2348: 笔弱化模式 // Python line 2343-2348: 笔弱化模式
if . && !.is_empty() { if . && !.is_empty() {
let = .last().unwrap(); let = .last().unwrap();
let K数 = ..read().unwrap()...load(Ordering::Relaxed) let K数 = ..read()...load(Ordering::Relaxed)
- ....load(Ordering::Relaxed) - ....load(Ordering::Relaxed)
+ 1; + 1;
if K数 == 3 { if K数 == 3 {
@@ -434,7 +429,7 @@ impl 笔 {
// Python line 2359-2367: 文官调整 // Python line 2359-2367: 文官调整
if let Some(ref _k) = if let Some(ref _k) =
&& Arc::as_ptr(_k) != Arc::as_ptr(&.) && !Arc::ptr_eq(_k, &.)
&& let Some() = && let Some() =
::K序列中获取分型(K序列, _k) ::K序列中获取分型(K序列, _k)
{ {
@@ -476,7 +471,7 @@ impl 笔 {
if Self::_相对关系(&, ) if Self::_相对关系(&, )
&& let Some(ref _k) = && let Some(ref _k) =
&& Arc::as_ptr(_k) == Arc::as_ptr(&.) && Arc::ptr_eq(_k, &.)
{ {
// 直接添加(对照 Python _添加新笔:直接 append // 直接添加(对照 Python _添加新笔:直接 append
Self::_添加新笔(, , , , line!()); Self::_添加新笔(, , , , line!());
@@ -490,7 +485,7 @@ impl 笔 {
_ => Self::_次高(&, .), _ => Self::_次高(&, .),
}; };
if let Some(ref _k) = if let Some(ref _k) =
&& Arc::as_ptr(_k) == Arc::as_ptr(&.) && Arc::ptr_eq(_k, &.)
&& Self::_相对关系(&, ) && Self::_相对关系(&, )
{ {
Self::_添加新笔(, , , , line!()); Self::_添加新笔(, , , , line!());
@@ -557,13 +552,12 @@ impl 笔 {
if !.is_empty() if !.is_empty()
&& Arc::as_ptr(.last().unwrap()) && Arc::as_ptr(.last().unwrap())
== Arc::as_ptr(&_rc) == Arc::as_ptr(&_rc)
&& let Some(_idx) = K序列 && let Some(_idx) =
.iter() K序列.iter().position(|k| Arc::ptr_eq(k, _k))
.position(|k| Arc::as_ptr(k) == Arc::as_ptr(_k))
{ {
for ck in &K序列[_idx..] { for ck in &K序列[_idx..] {
if (*ck..read().unwrap() == Some(::) if (*ck..read() == Some(::)
|| *ck..read().unwrap() == Some(::)) || *ck..read() == Some(::))
&& let Some() = && let Some() =
::K序列中获取分型(K序列, ck) ::K序列中获取分型(K序列, ck)
{ {
@@ -577,6 +571,15 @@ impl 笔 {
+ 1, + 1,
, ,
); );
if !.is_empty()
&& Arc::as_ptr(.last().unwrap())
== Arc::as_ptr(&_rc)
{
warn!(
"笔.分析 事后修复错过的笔:{}, 当前分型: {}",
_rc,
);
}
} }
} }
} }
@@ -649,14 +652,10 @@ impl 笔 {
. .
.store(..load(Ordering::Relaxed) + 1, Ordering::Relaxed); .store(..load(Ordering::Relaxed) + 1, Ordering::Relaxed);
if ..read().unwrap()..is_none() || ..read().unwrap()..is_none() if ..read()..is_none() || ..read()..is_none() {
{
..store(false, Ordering::Relaxed); ..store(false, Ordering::Relaxed);
} }
if matches!( if matches!(..read().(), :: | ::) {
..read().unwrap().(),
:: | ::
) {
error!("_添加新笔[{}] 出现无效分型 {}", , ); error!("_添加新笔[{}] 出现无效分型 {}", , );
} }
} }
@@ -676,7 +675,7 @@ impl 笔 {
Self::_实际低点(&, .), Self::_实际低点(&, .),
) )
&& Arc::ptr_eq(&.., &) && Arc::ptr_eq(&.., &)
&& Arc::ptr_eq(&..read().unwrap()., &) && Arc::ptr_eq(&..read()., &)
{ {
return true; return true;
} }
@@ -686,7 +685,7 @@ impl 笔 {
Self::_实际高点(&, .), Self::_实际高点(&, .),
) )
&& Arc::ptr_eq(&.., &) && Arc::ptr_eq(&.., &)
&& Arc::ptr_eq(&..read().unwrap()., &) && Arc::ptr_eq(&..read()., &)
{ {
return true; return true;
} }
@@ -696,9 +695,9 @@ impl 笔 {
/// 获取所有停顿位置 — 在笔范围内找出所有能成笔的分型组合 /// 获取所有停顿位置 — 在笔范围内找出所有能成笔的分型组合
pub fn (: &线, : &) -> Vec<线> { pub fn (: &线, : &) -> Vec<线> {
let mut = Vec::new();
let = Arc::clone(&.);
let = .K序列(&.K线序列); let = .K序列(&.K线序列);
let mut = Vec::with_capacity(.len() / 2);
let = Arc::clone(&.);
if .len() < 5 { if .len() < 5 {
return ; return ;
@@ -707,10 +706,8 @@ impl 笔 {
for i in 3...len() - 1 { for i in 3...len() - 1 {
let k = &[i]; let k = &[i];
let = let = *k..read() == Some(::) && .() == ::;
*k..read().unwrap() == Some(::) && .() == ::; let = *k..read() == Some(::) && .() == ::;
let =
*k..read().unwrap() == Some(::) && .() == ::;
if || { if || {
let = Arc::clone(&[i - 1]); let = Arc::clone(&[i - 1]);
let = Arc::clone(k); let = Arc::clone(k);
@@ -737,12 +734,12 @@ impl 笔 {
for in & { for in & {
let k线范围 = K线::rc( let k线范围 = K线::rc(
&.K线序列, &.K线序列,
&...K线.read().unwrap().clone(), &...K线.read().clone(),
&..read().unwrap()..K线.read().unwrap().clone(), &..read()..K线.read().clone(),
); );
let = 线::K线序列MACD趋向背驰(&k线范围, .()); let = 线::K线序列MACD趋向背驰(&k线范围, .());
if .iter().all(|&x| x) { if .iter().all(|&x| x) {
.push(Arc::clone(&..read().unwrap().)); .push(Arc::clone(&..read().));
} }
} }
+15 -19
View File
@@ -39,13 +39,13 @@ impl 背驰分析 {
) -> bool { ) -> bool {
let MACD = Self::_获取MACD面积( let MACD = Self::_获取MACD面积(
K线序列, K线序列,
&...K线.read().unwrap(), &...K线.read(),
&..read().unwrap()..K线.read().unwrap(), &..read()..K线.read(),
); );
let MACD = Self::_获取MACD面积( let MACD = Self::_获取MACD面积(
K线序列, K线序列,
&...K线.read().unwrap(), &...K线.read(),
&..read().unwrap()..K线.read().unwrap(), &..read()..K线.read(),
); );
// 计算面积(绝对值求和) // 计算面积(绝对值求和)
@@ -69,18 +69,18 @@ impl 背驰分析 {
/// 斜率背驰 — 价格斜率背驰 /// 斜率背驰 — 价格斜率背驰
pub fn (: &线, : &线) -> bool { pub fn (: &线, : &线) -> bool {
let dx = (..read().unwrap().() - ..()) as f64; let dx = (..read().() - ..()) as f64;
if dx == 0.0 { if dx == 0.0 {
return false; return false;
} }
let dy = ..read().unwrap(). - ..; let dy = ..read(). - ..;
let = dy / dx; let = dy / dx;
let dx = (..read().unwrap().() - ..()) as f64; let dx = (..read().() - ..()) as f64;
if dx == 0.0 { if dx == 0.0 {
return false; return false;
} }
let dy = ..read().unwrap(). - ..; let dy = ..read(). - ..;
let = dy / dx; let = dy / dx;
if .() == :: { if .() == :: {
@@ -92,12 +92,12 @@ impl 背驰分析 {
/// 测度背驰 — 价格时间测度背驰 /// 测度背驰 — 价格时间测度背驰
pub fn (: &线, : &线) -> bool { pub fn (: &线, : &线) -> bool {
let dx = (..read().unwrap().() - ..()) as f64; let dx = (..read().() - ..()) as f64;
let dy = ..read().unwrap(). - ..; let dy = ..read(). - ..;
let = (dx * dx + dy * dy).sqrt(); let = (dx * dx + dy * dy).sqrt();
let dx = (..read().unwrap().() - ..()) as f64; let dx = (..read().() - ..()) as f64;
let dy = ..read().unwrap(). - ..; let dy = ..read(). - ..;
let = (dx * dx + dy * dy).sqrt(); let = (dx * dx + dy * dy).sqrt();
if .() == :: { if .() == :: {
@@ -187,12 +187,8 @@ impl 背驰分析 {
// ---- 内部辅助 ---- // ---- 内部辅助 ----
fn _获取MACD面积(K线序列: &[Arc<K线>], : &Arc<K线>, : &Arc<K线>) -> MACD面积 { fn _获取MACD面积(K线序列: &[Arc<K线>], : &Arc<K线>, : &Arc<K线>) -> MACD面积 {
let _idx = K线序列 let _idx = K线序列.iter().position(|k| Arc::ptr_eq(k, ));
.iter() let _idx = K线序列.iter().position(|k| Arc::ptr_eq(k, ));
.position(|k| Arc::as_ptr(k) == Arc::as_ptr());
let _idx = K线序列
.iter()
.position(|k| Arc::as_ptr(k) == Arc::as_ptr());
let mut = 0.0f64; let mut = 0.0f64;
let mut = 0.0f64; let mut = 0.0f64;
@@ -200,7 +196,7 @@ impl 背驰分析 {
if let (Some(), Some()) = (_idx, _idx) { if let (Some(), Some()) = (_idx, _idx) {
let (, ) = if <= { (, ) } else { (, ) }; let (, ) = if <= { (, ) } else { (, ) };
for k in &K线序列[..=] { for k in &K线序列[..=] {
if let Some(macd) = k..read().unwrap().macd() { if let Some(macd) = k..read().macd() {
let hist = macd.MACD柱; let hist = macd.MACD柱;
if hist >= 0.0 { if hist >= 0.0 {
+= hist; += hist;
+160 -176
View File
@@ -25,8 +25,9 @@
use crate::structure::dash_line::线; use crate::structure::dash_line::线;
use crate::structure::fractal_obj::; use crate::structure::fractal_obj::;
use crate::types::; use crate::types::;
use parking_lot::RwLock;
use std::sync::Arc;
use std::sync::atomic::{AtomicI64, Ordering}; use std::sync::atomic::{AtomicI64, Ordering};
use std::sync::{Arc, RwLock};
/// 中枢 — 三段虚线重叠区间构成的价格中枢 /// 中枢 — 三段虚线重叠区间构成的价格中枢
/// ///
@@ -59,11 +60,11 @@ impl Clone for 中枢 {
fn clone(&self) -> Self { fn clone(&self) -> Self {
Self { Self {
: AtomicI64::new(self..load(Ordering::Relaxed)), : AtomicI64::new(self..load(Ordering::Relaxed)),
: RwLock::new(self..read().unwrap().clone()), : RwLock::new(self..read().clone()),
: AtomicI64::new(self..load(Ordering::Relaxed)), : AtomicI64::new(self..load(Ordering::Relaxed)),
: RwLock::new(self..read().unwrap().clone()), : RwLock::new(self..read().clone()),
线: RwLock::new(self.线.read().unwrap().clone()), 线: RwLock::new(self.线.read().clone()),
_第三买卖线: RwLock::new(self._第三买卖线.read().unwrap().clone()), _第三买卖线: RwLock::new(self._第三买卖线.read().clone()),
} }
} }
} }
@@ -83,9 +84,9 @@ impl 中枢 {
/// 向基础序列尾部添加虚线(中枢延伸),并清除第三买卖线 /// 向基础序列尾部添加虚线(中枢延伸),并清除第三买卖线
pub fn _添加虚线(&self, 线: Arc<线>) { pub fn _添加虚线(&self, 线: Arc<线>) {
self..write().unwrap().push(线); self..write().push(线);
*self._第三买卖线.write().unwrap() = None; *self._第三买卖线.write() = None;
*self.线.write().unwrap() = None; *self.线.write() = None;
} }
/// 返回图表标题字符串,格式为 "文.标识:文.周期:中枢标识:序号" /// 返回图表标题字符串,格式为 "文.标识:文.周期:中枢标识:序号"
@@ -94,24 +95,25 @@ impl 中枢 {
"{}:{}:{}:{}", "{}:{}:{}:{}",
self.().., self.()..,
self.().., self.()..,
self..read().unwrap(), self..read(),
self..load(Ordering::Relaxed) self..load(Ordering::Relaxed)
) )
} }
/// 返回基础序列的最后一根虚线(当前离开段) /// 返回基础序列的最后一根虚线(当前离开段)
pub fn (&self) -> Arc<线> { pub fn (&self) -> Arc<线> {
Arc::clone(&self..read().unwrap()[self..read().unwrap().len() - 1]) let guard = self..read();
Arc::clone(&guard[guard.len() - 1])
} }
/// 返回中枢方向(与基础序列第一段方向相反) /// 返回中枢方向(与基础序列第一段方向相反)
pub fn (&self) -> { pub fn (&self) -> {
self..read().unwrap()[0].().() self..read()[0].().()
} }
/// 中枢上沿 = min(前三段的高) /// 中枢上沿 = min(前三段的高)
pub fn (&self) -> f64 { pub fn (&self) -> f64 {
self..read().unwrap()[..3] self..read()[..3]
.iter() .iter()
.map(|x| x.()) .map(|x| x.())
.min_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)) .min_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
@@ -120,7 +122,7 @@ impl 中枢 {
/// 中枢下沿 = max(前三段的低) /// 中枢下沿 = max(前三段的低)
pub fn (&self) -> f64 { pub fn (&self) -> f64 {
self..read().unwrap()[..3] self..read()[..3]
.iter() .iter()
.map(|x| x.()) .map(|x| x.())
.max_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)) .max_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
@@ -131,7 +133,6 @@ impl 中枢 {
pub fn (&self) -> f64 { pub fn (&self) -> f64 {
self. self.
.read() .read()
.unwrap()
.iter() .iter()
.map(|x| x.()) .map(|x| x.())
.max_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)) .max_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
@@ -142,7 +143,6 @@ impl 中枢 {
pub fn (&self) -> f64 { pub fn (&self) -> f64 {
self. self.
.read() .read()
.unwrap()
.iter() .iter()
.map(|x| x.()) .map(|x| x.())
.min_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)) .min_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
@@ -151,46 +151,47 @@ impl 中枢 {
/// 返回基础序列第一段的起点分型 /// 返回基础序列第一段的起点分型
pub fn (&self) -> Arc<> { pub fn (&self) -> Arc<> {
Arc::clone(&self..read().unwrap()[0].) Arc::clone(&self..read()[0].)
} }
/// 返回基础序列最后一段的终点分型 /// 返回基础序列最后一段的终点分型
pub fn (&self) -> Arc<> { pub fn (&self) -> Arc<> {
Arc::clone( let guard = self..read();
&*self..read().unwrap()[self..read().unwrap().len() - 1] Arc::clone(&*guard[guard.len() - 1]..read())
.
.read()
.unwrap(),
)
} }
/// 设置第三类买卖点对应的虚线 /// 设置第三类买卖点对应的虚线
pub fn 线(&self, 线: Option<Arc<线>>) { pub fn 线(&self, 线: Option<Arc<线>>) {
*self.线.write().unwrap() = 线; *self.线.write() = 线;
} }
/// 获取序列 — 基础序列 + 第三买卖线(若有) /// 获取序列 — 基础序列 + 第三买卖线(若有)
pub fn (&self) -> Vec<Arc<线>> { pub fn (&self) -> Vec<Arc<线>> {
let mut : Vec<Arc<线>> = self..read().unwrap().clone(); let mut : Vec<Arc<线>> = self..read().clone();
if let Some(ref ) = *self.线.read().unwrap() { if let Some(ref ) = *self.线.read() {
.push(Arc::clone()); .push(Arc::clone());
} }
} }
/// 获取基础序列最后一个元素
pub fn (&self) -> Option<Arc<线>> {
self..read().last().cloned()
}
/// 返回序列化数据文本,用于调试和存储 /// 返回序列化数据文本,用于调试和存储
pub fn (&self) -> String { pub fn (&self) -> String {
let 线_str = match &*self.线.read().unwrap() { let 线_str = match &*self.线.read() {
Some(x) => format!("{}", x), Some(x) => format!("{}", x),
None => "None".to_string(), None => "None".to_string(),
}; };
let _第三买卖线_str = match &*self._第三买卖线.read().unwrap() { let _第三买卖线_str = match &*self._第三买卖线.read() {
Some(x) => format!("{}", x), Some(x) => format!("{}", x),
None => "None".to_string(), None => "None".to_string(),
}; };
format!( format!(
"{}, {}, {}, 文:({},{}), 武:({},{}), {}, {}", "{}, {}, {}, 文:({},{}), 武:({},{}), {}, {}",
self..read().unwrap(), self..read(),
self..load(Ordering::Relaxed), self..load(Ordering::Relaxed),
self..load(Ordering::Relaxed), self..load(Ordering::Relaxed),
self.().(), self.().(),
@@ -204,67 +205,63 @@ impl 中枢 {
/// 校验中枢合法性 /// 校验中枢合法性
pub fn _校验合法性(&self, : &[Arc<线>]) -> bool { pub fn _校验合法性(&self, : &[Arc<线>]) -> bool {
let mut = self..read().unwrap().clone(); let guard = self..read();
let mut = guard.clone();
let mut : Vec<Arc<线>> = Vec::new(); let mut : Vec<Arc<线>> = Vec::new();
for in self..read().unwrap().iter() { let = [0]..load(Ordering::Relaxed);
if !.iter().any(|x| Arc::as_ptr(x) == Arc::as_ptr()) { for in guard.iter() {
let idx = (..load(Ordering::Relaxed) - ) as usize;
if idx >= .len() || !Arc::ptr_eq(&[idx], ) {
.push(Arc::clone()); .push(Arc::clone());
} }
} }
if !.is_empty() { if !.is_empty() {
let = &[0]; let = &[0];
if let Some(pos) = self // Python: 序号 = 线段._索引(self.基础序列, 无效)
. let pos = crate::algorithm::segment::线::_索引(&guard, );
.read() = guard[..pos].to_vec();
.unwrap()
.iter()
.position(|x| Arc::as_ptr(x) == Arc::as_ptr())
{
= self..read().unwrap()[..pos].to_vec();
}
} }
drop(guard);
if .len() < 3 { if .len() < 3 {
self.线(None); self.线(None);
*self._第三买卖线.write().unwrap() = None; *self._第三买卖线.write() = None;
return false; return false;
} }
*self..write().unwrap() = ; *self..write() = ;
let = self.(); let = self.();
let = self.(); let = self.();
= Vec::new(); = Vec::new();
for in self..read().unwrap().iter() { for in self..read().iter() {
if crate::types::::(, , .(), .()).() if crate::types::::(, , .(), .()).()
{ {
break; break;
} }
.push(Arc::clone()); .push(Arc::clone());
} }
*self..write().unwrap() = ; *self..write() = ;
if self..read().unwrap().len() < 3 { let = {
return false; let guard = self..read();
} if guard.len() < 3 {
for i in 1..self..read().unwrap().len() {
let = &self..read().unwrap()[i - 1];
let = &self..read().unwrap()[i];
if !.() {
return false; return false;
} }
} for i in 1..guard.len() {
if !guard[i - 1].(&guard[i]) {
if !crate::types::::( return false;
self..read().unwrap()[0].(), }
self..read().unwrap()[0].(), }
self..read().unwrap()[2].(), crate::types::::(
self..read().unwrap()[2].(), guard[0].(),
) guard[0].(),
.() guard[2].(),
{ guard[2].(),
)
.()
};
if ! {
let = self.(); let = self.();
let = self.(); let = self.();
if > { if > {
@@ -272,10 +269,12 @@ impl 中枢 {
} }
} }
let 线_opt = self.线.read().unwrap().clone(); let 线_opt = self.线.read().clone();
if let Some(ref 线) = 线_opt { if let Some(ref 线) = 线_opt {
if .iter().any(|x| Arc::as_ptr(x) == Arc::as_ptr(线)) { let = [0]..load(Ordering::Relaxed);
if !self..read().unwrap().last().unwrap().(线) { let idx = (线..load(Ordering::Relaxed) - ) as usize;
if idx < .len() && Arc::ptr_eq(&[idx], 线) {
if !self..read().last().unwrap().(线) {
self.线(None); self.线(None);
} else if !crate::types::::( } else if !crate::types::::(
self.(), self.(),
@@ -298,25 +297,44 @@ impl 中枢 {
/// 完整性 — 详见教你炒股票43:有关背驰的补习课 /// 完整性 — 详见教你炒股票43:有关背驰的补习课
/// 不完整时下一个中枢大概率会与当前中枢发生扩展 /// 不完整时下一个中枢大概率会与当前中枢发生扩展
pub fn (&self, : &str) -> bool { pub fn (&self, : &str) -> bool {
if *self..read().unwrap()[0]..read().unwrap() == "" { if *self..read()[0]..read() == "" {
return self.线.read().unwrap().is_some(); return self.线.read().is_some();
} }
let _ref = self..read().unwrap(); // if self.本级_第三买卖线: return True # 暂未启用
let = self.();
if == "中枢之中" {
return false;
}
let _ref = self..read();
let = _ref.last().unwrap(); let = _ref.last().unwrap();
let _vec = if == "" { let _vec = if == "" {
._中枢序列.read().unwrap() ._中枢序列.read()
} else { } else {
._中枢序列.read().unwrap() ._中枢序列.read()
}; };
if _vec.is_empty() {
return false;
}
let = self.();
let = self.();
for in _vec.iter() { for in _vec.iter() {
if crate::types::::( let = .();
self.(), let = .();
self.(), if == "中枢之下" {
.(), if <= {
.(), continue;
) }
.() } else {
// 中枢之上
if >= {
continue;
}
}
if crate::types::::(, , , ).()
{ {
return true; return true;
} }
@@ -330,24 +348,19 @@ impl 中枢 {
: &mut Vec<Arc<>>, : &mut Vec<Arc<>>,
: &crate::config::, : &crate::config::,
) { ) {
if self..read().unwrap().len() >= 9 { if self..read().len() >= 9 {
let mut 线: Vec<Arc<线>> = Vec::new(); let mut 线: Vec<Arc<线>> = Vec::new();
let _ref = self..read().unwrap(); let _ref = self..read();
crate::algorithm::segment::线::(&_ref, &mut 线, ); crate::algorithm::segment::线::(&_ref, &mut 线, );
::( let = format!("{}_扩展中枢_", self..read());
&线, ::(&线, , false, &, 0);
,
false,
&format!("{}_扩展中枢_", self..read().unwrap()),
0,
);
} }
} }
/// 当前状态 — 详见教你炒股票49:利润率最大的操作模式 /// 当前状态 — 详见教你炒股票49:利润率最大的操作模式
/// 返回当前中枢最后一段所处的位置关系:中枢之中/中枢之上/中枢之下 /// 返回当前中枢最后一段所处的位置关系:中枢之中/中枢之上/中枢之下
pub fn (&self) -> &str { pub fn (&self) -> &str {
let _ref = self..read().unwrap(); let _ref = self..read();
let = Arc::clone(_ref.last().unwrap()); let = Arc::clone(_ref.last().unwrap());
let = ._武(); let = ._武();
let = crate::types::::( let = crate::types::::(
@@ -390,7 +403,7 @@ impl 中枢 {
assert!(Self::(&, &, &), "中枢.创建 基础检查失败"); assert!(Self::(&, &, &), "中枢.创建 基础检查失败");
Self::new( Self::new(
0, 0,
format!("{}中枢<{}>", , ..read().unwrap()), format!("{}中枢<{}>", , ..read()),
, ,
vec![, , ], vec![, , ],
) )
@@ -422,18 +435,8 @@ impl 中枢 {
. .
.store(..load(Ordering::Relaxed) + 1, Ordering::Relaxed); .store(..load(Ordering::Relaxed) + 1, Ordering::Relaxed);
let _last_序号 = let _last_序号 = .().unwrap()..load(Ordering::Relaxed);
.() let new_last_序号 = .().unwrap()..load(Ordering::Relaxed);
.last()
.unwrap()
.
.load(Ordering::Relaxed);
let new_last_序号 =
.()
.last()
.unwrap()
.
.load(Ordering::Relaxed);
if _last_序号 > new_last_序号 { if _last_序号 > new_last_序号 {
panic!( panic!(
"向中枢序列尾部添加 序号错误 前last={} > new_last={}", "向中枢序列尾部添加 序号错误 前last={} > new_last={}",
@@ -478,11 +481,8 @@ impl 中枢 {
let = &线[i + 1]; let = &线[i + 1];
if Self::(, , ) { if Self::(, , ) {
// Python: 序号 = 虚线序列.index(左) // Python: 序号 = 线段._索引(虚线序列, 左)
let = 线 let : usize = crate::algorithm::segment::线::_索引(线, );
.iter()
.position(|x| Arc::as_ptr(x) == Arc::as_ptr())
.expect("中枢.分析: 左元素不在虚线序列中");
if && (..load(Ordering::Relaxed) == 0 || == 0) { if && (..load(Ordering::Relaxed) == 0 || == 0) {
continue; continue;
} }
@@ -525,22 +525,16 @@ impl 中枢 {
return; return;
} }
// 找到当前中枢最后一个元素在虚线序列中的位置 // Python: 序号 = 线段._索引(虚线序列, 当前中枢.基础序列[-1]) + 1
let = { let = {
let cur = &[_idx]; let cur = &[_idx];
let = &cur..read().unwrap()[cur..read().unwrap().len() - 1]; let guard = cur..read();
match 线 crate::algorithm::segment::线::_索引(线, &guard[guard.len() - 1]) + 1
.iter()
.position(|x| Arc::as_ptr(x) == Arc::as_ptr())
{
Some(idx) => idx + 1,
None => return,
}
}; };
let mut = [_idx].(); let mut = [_idx].();
let mut = [_idx].(); let mut = [_idx].();
let mut : Vec<Arc<线>> = Vec::new(); let mut = Vec::with_capacity(3);
for 线_ref in &线[..] { for 线_ref in &线[..] {
let 线 = Arc::clone(线_ref); let 线 = Arc::clone(线_ref);
@@ -553,12 +547,7 @@ impl 中枢 {
// Python: if 当前中枢.基础序列[-1].之后是(当前虚线): // Python: if 当前中枢.基础序列[-1].之后是(当前虚线):
let needs_三买 = { let needs_三买 = {
let cur = &[_idx]; let cur = &[_idx];
cur. cur..read().last().unwrap().(&线)
.read()
.unwrap()
.last()
.unwrap()
.(&线)
}; };
if needs_三买 { if needs_三买 {
[_idx].线(Some(线.clone())); [_idx].线(Some(线.clone()));
@@ -570,17 +559,11 @@ impl 中枢 {
[_idx] [_idx]
. .
.read() .read()
.unwrap()
.last() .last()
.unwrap() .unwrap()
.(&线), .(&线),
"中枢延伸: 不连续 {}, {}", "中枢延伸: 不连续 {}, {}",
[_idx] [_idx]..read().last().unwrap(),
.
.read()
.unwrap()
.last()
.unwrap(),
线 线
); );
[_idx]._添加虚线(线); [_idx]._添加虚线(线);
@@ -594,7 +577,6 @@ impl 中枢 {
let = [_idx] let = [_idx]
. .
.read() .read()
.unwrap()
.last() .last()
.unwrap() .unwrap()
.() .()
@@ -628,13 +610,13 @@ impl 中枢 {
), ),
); );
} }
if *self..read().unwrap() != *other..read().unwrap() { if *self..read() != *other..read() {
return ( return (
false, false,
format!( format!(
"中枢: [标识] 不等 A={},B={}", "中枢: [标识] 不等 A={},B={}",
self..read().unwrap(), self..read(),
other..read().unwrap() other..read()
), ),
); );
} }
@@ -649,8 +631,8 @@ impl 中枢 {
); );
} }
// 基础序列 // 基础序列
let a_seq = self..read().unwrap(); let a_seq = self..read();
let b_seq = other..read().unwrap(); let b_seq = other..read();
if a_seq.len() != b_seq.len() { if a_seq.len() != b_seq.len() {
return ( return (
false, false,
@@ -692,16 +674,16 @@ impl 中枢 {
}; };
( (
"第三买卖线", "第三买卖线",
&self.线.read().unwrap(), &self.线.read(),
&other.线.read().unwrap(), &other.线.read(),
, ,
) )
.map_err(|e| (false, e)) .map_err(|e| (false, e))
.ok(); .ok();
( (
"本级_第三买卖线", "本级_第三买卖线",
&self._第三买卖线.read().unwrap(), &self._第三买卖线.read(),
&other._第三买卖线.read().unwrap(), &other._第三买卖线.read(),
, ,
) )
.map_err(|e| (false, e)) .map_err(|e| (false, e))
@@ -712,21 +694,26 @@ impl 中枢 {
impl std::fmt::Display for { impl std::fmt::Display for {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let _str = self let guard = self..read();
. let len = guard.len();
.read() let _str = if let Some((first, rest)) = guard.split_first() {
.unwrap() let mut s = format!("{}", first);
.iter() for d in rest {
.map(|d| format!("{}", d)) use std::fmt::Write;
.collect::<Vec<_>>() write!(&mut s, ", {}", d).unwrap();
.join(", "); }
s
} else {
String::new()
};
drop(guard);
write!( write!(
f, f,
"{}({}, {}, 元素数量: {}, [{}], {} ===>>> {})", "{}({}, {}, 元素数量: {}, [{}], {} ===>>> {})",
self..read().unwrap(), self..read(),
crate::utils::format_f64_g(self.()), crate::utils::format_f64_g(self.()),
crate::utils::format_f64_g(self.()), crate::utils::format_f64_g(self.()),
self..read().unwrap().len(), len,
_str, _str,
self.(), self.(),
self.(), self.(),
@@ -851,11 +838,11 @@ mod tests {
); );
assert_eq!(..load(Ordering::Relaxed), 1); assert_eq!(..load(Ordering::Relaxed), 1);
assert_eq!(*..read().unwrap(), "测试中枢"); assert_eq!(*..read(), "测试中枢");
assert_eq!(..load(Ordering::Relaxed), 1); assert_eq!(..load(Ordering::Relaxed), 1);
assert_eq!(..read().unwrap().len(), 3); assert_eq!(..read().len(), 3);
assert!(.线.read().unwrap().is_none()); assert!(.线.read().is_none());
assert!(._第三买卖线.read().unwrap().is_none()); assert!(._第三买卖线.read().is_none());
} }
#[test] #[test]
@@ -877,16 +864,16 @@ mod tests {
// RefCell 第三买卖线读写 // RefCell 第三买卖线读写
.线(Some(Arc::clone(&1))); .线(Some(Arc::clone(&1)));
assert!(.线.read().unwrap().is_some()); assert!(.线.read().is_some());
assert_eq!( assert_eq!(
Arc::as_ptr(.线.read().unwrap().as_ref().unwrap()), Arc::as_ptr(.线.read().as_ref().unwrap()),
Arc::as_ptr(&1) Arc::as_ptr(&1)
); );
// 本级_第三买卖线 // 本级_第三买卖线
assert!(._第三买卖线.read().unwrap().is_none()); assert!(._第三买卖线.read().is_none());
*._第三买卖线.write().unwrap() = Some(Arc::clone(&3)); *._第三买卖线.write() = Some(Arc::clone(&3));
assert!(._第三买卖线.read().unwrap().is_some()); assert!(._第三买卖线.read().is_some());
} }
// ============================================================ // ============================================================
@@ -906,14 +893,11 @@ mod tests {
1, 1,
vec![Arc::clone(&1), Arc::clone(&2), Arc::clone(&3)], vec![Arc::clone(&1), Arc::clone(&2), Arc::clone(&3)],
); );
assert_eq!(..read().unwrap().len(), 3); assert_eq!(..read().len(), 3);
._添加虚线(Arc::clone(&4)); ._添加虚线(Arc::clone(&4));
assert_eq!(..read().unwrap().len(), 4); assert_eq!(..read().len(), 4);
assert_eq!( assert_eq!(Arc::as_ptr(&..read()[3]), Arc::as_ptr(&4));
Arc::as_ptr(&..read().unwrap()[3]),
Arc::as_ptr(&4)
);
} }
#[test] #[test]
@@ -930,14 +914,14 @@ mod tests {
vec![Arc::clone(&1), Arc::clone(&2), Arc::clone(&3)], vec![Arc::clone(&1), Arc::clone(&2), Arc::clone(&3)],
); );
.线(Some(Arc::clone(&1))); .线(Some(Arc::clone(&1)));
*._第三买卖线.write().unwrap() = Some(Arc::clone(&2)); *._第三买卖线.write() = Some(Arc::clone(&2));
assert!(.线.read().unwrap().is_some()); assert!(.线.read().is_some());
assert!(._第三买卖线.read().unwrap().is_some()); assert!(._第三买卖线.read().is_some());
._添加虚线(Arc::clone(&4)); ._添加虚线(Arc::clone(&4));
// 添加虚线后第三买卖线被清除 // 添加虚线后第三买卖线被清除
assert!(.线.read().unwrap().is_none()); assert!(.线.read().is_none());
assert!(._第三买卖线.read().unwrap().is_none()); assert!(._第三买卖线.read().is_none());
} }
// ============================================================ // ============================================================
@@ -963,15 +947,15 @@ mod tests {
// 基础序列中的 Rc 指针应一致 // 基础序列中的 Rc 指针应一致
for i in 0..3 { for i in 0..3 {
assert_eq!( assert_eq!(
Arc::as_ptr(&..read().unwrap()[i]), Arc::as_ptr(&..read()[i]),
Arc::as_ptr(&..read().unwrap()[i]) Arc::as_ptr(&..read()[i])
); );
} }
// 第三买卖线 Rc 指针应一致 // 第三买卖线 Rc 指针应一致
assert_eq!( assert_eq!(
Arc::as_ptr(.线.read().unwrap().as_ref().unwrap()), Arc::as_ptr(.线.read().as_ref().unwrap()),
Arc::as_ptr(.线.read().unwrap().as_ref().unwrap()) Arc::as_ptr(.线.read().as_ref().unwrap())
); );
} }
@@ -1030,12 +1014,12 @@ mod tests {
// 通过 rc1 添加虚线 // 通过 rc1 添加虚线
1._线(Arc::clone(&4)); 1._线(Arc::clone(&4));
assert_eq!(2..read().unwrap().len(), 4); assert_eq!(2..read().len(), 4);
// 验证共享的 Arc<虚线> 指针一致 // 验证共享的 Arc<虚线> 指针一致
assert_eq!( assert_eq!(
Arc::as_ptr(&1..read().unwrap()[3]), Arc::as_ptr(&1..read()[3]),
Arc::as_ptr(&2..read().unwrap()[3]) Arc::as_ptr(&2..read()[3])
); );
} }
} }
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -347,7 +347,7 @@ impl 买卖点 {
let = .(); let = .();
// 当前K线 — 从缠K获取其标的K线 // 当前K线 — 从缠K获取其标的K线
let K线 = Arc::clone(&*K.K线.read().unwrap()); let K线 = Arc::clone(&*K.K线.read());
// 当前缠K序号 — 与买卖点K线(分型.中.序号)同尺度,用于偏移计算 // 当前缠K序号 — 与买卖点K线(分型.中.序号)同尺度,用于偏移计算
let K序号 = K..load(Ordering::Relaxed); let K序号 = K..load(Ordering::Relaxed);
+16 -20
View File
@@ -26,10 +26,10 @@ use crate::business::observer::观察者;
use crate::business::synthesizer::K线合成器; use crate::business::synthesizer::K线合成器;
use crate::config::; use crate::config::;
use crate::kline::bar::K线; use crate::kline::bar::K线;
use crate::{error, warn};
use parking_lot::RwLock;
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::Arc; use std::sync::Arc;
use std::sync::RwLock;
use tracing::{error, info};
/// 立体分析器 — 多周期协调器 /// 立体分析器 — 多周期协调器
pub struct { pub struct {
@@ -61,8 +61,7 @@ impl 立体分析器 {
.get(&) .get(&)
.cloned() .cloned()
.unwrap_or_else(|| .clone()); .unwrap_or_else(|| .clone());
.K线 = false; . = Some(vec![]);
.线 = false;
. = .clone(); . = .clone();
let = ::new(.clone(), , ); let = ::new(.clone(), , );
@@ -72,10 +71,8 @@ impl 立体分析器 {
// 显示周期特殊配置 // 显示周期特殊配置
{ {
let = .get(&).expect("显示周期观察者不存在"); let = .get(&).expect("显示周期观察者不存在");
let mut guard = .write().unwrap(); let mut guard = .write();
guard..K线 = true; guard.. = None; // None = 全部展示
guard.. = true;
guard..线 = true;
guard.. = true; guard.. = true;
guard.(); guard.();
} }
@@ -84,14 +81,14 @@ impl 立体分析器 {
{ {
let K序列 = let K序列 =
.get(&) .get(&)
.map(|o| o.read().unwrap().K线序列.clone()) .map(|o| o.read().K线序列.clone())
.unwrap_or_default(); .unwrap_or_default();
for & in & { for & in & {
if != if !=
&& let Some() = .get(&) && let Some() = .get(&)
{ {
.write().unwrap().K序列 = K序列.clone(); .write().K序列 = K序列.clone();
} }
} }
} }
@@ -119,7 +116,7 @@ impl 立体分析器 {
/// __K线回调 — 对应 Python 立体分析器.__K线回调 /// __K线回调 — 对应 Python 立体分析器.__K线回调
fn __K线回调(&self, _信号类型: String, _标识: String, : i64, K线: K线) { fn __K线回调(&self, _信号类型: String, _标识: String, : i64, K线: K线) {
if let Some() = self..get(&) { if let Some() = self..get(&) {
let mut obs = .write().unwrap(); let mut obs = .write();
obs.K线(K线); obs.K线(K线);
// 对应 Python: if 当前K线 := self._K线合成器.获取当前K线(周期) // 对应 Python: if 当前K线 := self._K线合成器.获取当前K线(周期)
// _完成K线刚清空当前K线,获取当前K线返回 None,所以这里不添加 // _完成K线刚清空当前K线,获取当前K线返回 None,所以这里不添加
@@ -133,7 +130,7 @@ impl 立体分析器 {
K线: K线, K线: K线,
) { ) {
if let Some() = .get(&) { if let Some() = .get(&) {
.write().unwrap().K线(K线); .write().K线(K线);
} }
} }
@@ -165,22 +162,22 @@ impl 立体分析器 {
let = self let = self
. .
.get(&self.) .get(&self.)
.and_then(|o| o.read().unwrap().K线序列.first().map(|k| k.)) .and_then(|o| o.read().K线序列.first().map(|k| k.))
.unwrap_or(0); .unwrap_or(0);
let = self let = self
. .
.get(&self.) .get(&self.)
.and_then(|o| o.read().unwrap().K线序列.last().map(|k| k.)) .and_then(|o| o.read().K线序列.last().map(|k| k.))
.unwrap_or(0); .unwrap_or(0);
let = self let = self
. .
.get(&self.) .get(&self.)
.map(|o| o.read().unwrap()..clone()) .map(|o| o.read()..clone())
.unwrap_or_default(); .unwrap_or_default();
let = self let = self
. .
.get(&self.) .get(&self.)
.map(|o| o.read().unwrap().) .map(|o| o.read().)
.unwrap_or_default(); .unwrap_or_default();
let = format!("RustM_{}:{}_{}_{}", , , , ); let = format!("RustM_{}:{}_{}_{}", , , , );
@@ -195,12 +192,11 @@ impl 立体分析器 {
if let Some() = self..get() { if let Some() = self..get() {
.read() .read()
.unwrap()
._保存数据(Some(&.to_string_lossy())); ._保存数据(Some(&.to_string_lossy()));
} }
} }
info!("多级别数据拆分保存完成,目录:{}", .display()); warn!("多级别数据拆分保存完成,目录:{}", .display());
} }
/// 相等 — 各周期观察者全量比对,对应 Python `立体分析器相等` /// 相等 — 各周期观察者全量比对,对应 Python `立体分析器相等`
@@ -213,11 +209,11 @@ impl 立体分析器 {
for in &self. { for in &self. {
let a_obs = match self..get() { let a_obs = match self..get() {
Some(o) => o.read().unwrap(), Some(o) => o.read(),
None => return (false, format!("{标签}: 周期{周期} 观察者不存在 (A)")), None => return (false, format!("{标签}: 周期{周期} 观察者不存在 (A)")),
}; };
let b_obs = match other..get() { let b_obs = match other..get() {
Some(o) => o.read().unwrap(), Some(o) => o.read(),
None => return (false, format!("{标签}: 周期{周期} 观察者不存在 (B)")), None => return (false, format!("{标签}: 周期{周期} 观察者不存在 (B)")),
}; };
let (eq, msg) = a_obs.(&b_obs, ); let (eq, msg) = a_obs.(&b_obs, );
+196 -109
View File
@@ -32,9 +32,10 @@ use crate::structure::dash_line::虚线;
use crate::structure::fractal_obj::; use crate::structure::fractal_obj::;
use crate::types::; use crate::types::;
use crate::utils::datetime; use crate::utils::datetime;
use crate::{error, warn};
use parking_lot::RwLock;
use std::sync::Arc;
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
use std::sync::{Arc, RwLock};
use tracing::{error, info};
/// 观察者 — 单周期分析器,持有所有层级序列,接收K线流式输入后逐层计算 /// 观察者 — 单周期分析器,持有所有层级序列,接收K线流式输入后逐层计算
pub struct { pub struct {
@@ -298,17 +299,15 @@ impl 观察者 {
&[::, ::], &[::, ::],
); );
} }
} else { } else if self..线 {
if self..线 { let (left, right) = self.线.split_at_mut(i);
let = self.线[i - 1].clone(); 线::(
线::( &left[i - 1],
&, &mut right[0],
&mut self.线[i], &self.,
&self., 0,
0, &[::, ::],
&[::, ::], );
);
}
} }
if self..线 { if self..线 {
::(&self.线[i], &mut self.[i], true, "", 0); ::(&self.线[i], &mut self.[i], true, "", 0);
@@ -323,11 +322,9 @@ impl 观察者 {
if self..线 { if self..线 {
线::(&self., &mut self.线[i], &self.); 线::(&self., &mut self.线[i], &self.);
} }
} else { } else if self..线 {
if self..线 { let (left, right) = self.线.split_at_mut(i);
let = self.线[i - 1].clone(); 线::(&left[i - 1], &mut right[0], &self.);
线::(&, &mut self.线[i], &self.);
}
} }
if self..线 { if self..线 {
::( ::(
@@ -345,8 +342,11 @@ impl 观察者 {
if self..线 || self..线 { if self..线 || self..线 {
for i in 0..self.线.min(self.线.len()) { for i in 0..self.线.min(self.线.len()) {
if self..线 { if self..线 {
let = self.线[i].clone(); 线::(
线::(&, &mut self.线[i], &self.); &self.线[i],
&mut self.线[i],
&self.,
);
} }
if self..线 { if self..线 {
::( ::(
@@ -430,17 +430,15 @@ impl 观察者 {
&[::, ::], &[::, ::],
); );
} }
} else { } else if self..线 {
if self..线 { let (left, right) = self.线.split_at_mut(i);
let = self.线[i - 1].clone(); 线::(
线::( &left[i - 1],
&, &mut right[0],
&mut self.线[i], &self.,
&self., 0,
0, &[::, ::],
&[::, ::], );
);
}
} }
if self..线 { if self..线 {
::(&self.线[i], &mut self.[i], true, "", 0); ::(&self.线[i], &mut self.[i], true, "", 0);
@@ -454,11 +452,9 @@ impl 观察者 {
if self..线 { if self..线 {
线::(&self., &mut self.线[i], &self.); 线::(&self., &mut self.线[i], &self.);
} }
} else { } else if self..线 {
if self..线 { let (left, right) = self.线.split_at_mut(i);
let = self.线[i - 1].clone(); 线::(&left[i - 1], &mut right[0], &self.);
线::(&, &mut self.线[i], &self.);
}
} }
if self..线 { if self..线 {
::( ::(
@@ -475,8 +471,11 @@ impl 观察者 {
if self..线 || self..线 { if self..线 || self..线 {
for i in 0..self.线.min(self.线.len()) { for i in 0..self.线.min(self.线.len()) {
if self..线 { if self..线 {
let = self.线[i].clone(); 线::(
线::(&, &mut self.线[i], &self.); &self.线[i],
&mut self.线[i],
&self.,
);
} }
if self..线 { if self..线 {
::( ::(
@@ -517,7 +516,7 @@ impl 观察者 {
return Ok(()); return Ok(());
} }
let : Vec<String> = .iter().map(|d| d.()).collect(); let : Vec<String> = .iter().map(|d| d.()).collect();
let = format!("{}.txt", [0]..read().unwrap()); let = format!("{}.txt", [0]..read());
std::fs::write(.join(&), .join("\n") + "\n")?; std::fs::write(.join(&), .join("\n") + "\n")?;
Ok(()) Ok(())
}; };
@@ -528,7 +527,7 @@ impl 观察者 {
return Ok(()); return Ok(());
} }
let : Vec<String> = .iter().map(|h| h.()).collect(); let : Vec<String> = .iter().map(|h| h.()).collect();
let = format!("{}.txt", [0]..read().unwrap()); let = format!("{}.txt", [0]..read());
std::fs::write(.join(&), .join("\n") + "\n")?; std::fs::write(.join(&), .join("\n") + "\n")?;
Ok(()) Ok(())
}; };
@@ -563,7 +562,7 @@ impl 观察者 {
ck..load(Ordering::Relaxed), ck..load(Ordering::Relaxed),
ck..load(Ordering::Relaxed), ck..load(Ordering::Relaxed),
ck., ck.,
*ck..read().unwrap(), *ck..read(),
ck..get(), ck..get(),
ck..get(), ck..get(),
ck., ck.,
@@ -597,7 +596,7 @@ impl 观察者 {
_数据文本.join("\n") + "\n", _数据文本.join("\n") + "\n",
); );
info!("全部数据拆分保存完成,目录:{}", .display()); warn!("全部数据拆分保存完成,目录:{}", .display());
.display().to_string() .display().to_string()
} }
@@ -734,6 +733,7 @@ impl 观察者 {
mod tests { mod tests {
use super::*; use super::*;
use crate::config::; use crate::config::;
use crate::{error, info};
fn test_data_path() -> String { fn test_data_path() -> String {
let manifest = std::path::Path::new(env!("CARGO_MANIFEST_DIR")); let manifest = std::path::Path::new(env!("CARGO_MANIFEST_DIR"));
@@ -749,10 +749,9 @@ mod tests {
fn test_普k序列指针一致性() { fn test_普k序列指针一致性() {
let obs = ::new("btcusd".into(), 300, Default::default()); let obs = ::new("btcusd".into(), 300, Default::default());
obs.write() obs.write()
.unwrap()
.(&test_data_path(), Default::default()) .(&test_data_path(), Default::default())
.unwrap(); .unwrap();
let obs_ref = obs.read().unwrap(); let obs_ref = obs.read();
for (i, bi) in obs_ref..iter().enumerate() { for (i, bi) in obs_ref..iter().enumerate() {
let pu_seq = bi.K序列(&obs_ref.K线序列); let pu_seq = bi.K序列(&obs_ref.K线序列);
@@ -761,12 +760,7 @@ mod tests {
info!(" 文.中.标的K线 原始起始序号: {}", bi...); info!(" 文.中.标的K线 原始起始序号: {}", bi...);
info!( info!(
" 武.中.标的K线 原始结束序号: {}", " 武.中.标的K线 原始结束序号: {}",
bi. bi..read()...load(Ordering::Relaxed)
.read()
.unwrap()
.
.
.load(Ordering::Relaxed)
); );
info!(" 普通K线序列.len: {}", obs_ref.K线序列.len()); info!(" 普通K线序列.len: {}", obs_ref.K线序列.len());
} else { } else {
@@ -777,7 +771,7 @@ mod tests {
.any(|k| Arc::as_ptr(k) == first_ptr); .any(|k| Arc::as_ptr(k) == first_ptr);
if !found { if !found {
info!("笔 {}: 获取普K序列[0] 的 Rc 指针不在 普通K线序列 中!", i); info!("笔 {}: 获取普K序列[0] 的 Rc 指针不在 普通K线序列 中!", i);
let wen_ptr = Arc::as_ptr(&*bi...K线.read().unwrap()); let wen_ptr = Arc::as_ptr(&*bi...K线.read());
let wen_found = obs_ref let wen_found = obs_ref
.K线序列 .K线序列
.iter() .iter()
@@ -802,11 +796,11 @@ mod tests {
let offset = i * size; let offset = i * size;
if let Some(k线) = K线::from_bytes(&data[offset..offset + size], 300, "btcusd") { if let Some(k线) = K线::from_bytes(&data[offset..offset + size], 300, "btcusd") {
let _k线_py_inner = Arc::new(k线.clone()); let _k线_py_inner = Arc::new(k线.clone());
obs_ref.write().unwrap().K线(k线); obs_ref.write().K线(k线);
} }
} }
let obs = obs_ref.read().unwrap(); let obs = obs_ref.read();
info!("普通K线序列.len: {}", obs.K线序列.len()); info!("普通K线序列.len: {}", obs.K线序列.len());
info!("笔序列.len: {}", obs..len()); info!("笔序列.len: {}", obs..len());
@@ -835,10 +829,9 @@ mod tests {
fn test_分型到笔的文武Rc指针一致性() { fn test_分型到笔的文武Rc指针一致性() {
let obs = ::new("btcusd".into(), 300, Default::default()); let obs = ::new("btcusd".into(), 300, Default::default());
obs.write() obs.write()
.unwrap()
.(&test_data_path(), Default::default()) .(&test_data_path(), Default::default())
.unwrap(); .unwrap();
let obs_ref = obs.read().unwrap(); let obs_ref = obs.read();
// 每个笔的文/武 分型 Rc 指针必须在 分型序列 中 // 每个笔的文/武 分型 Rc 指针必须在 分型序列 中
for (i, bi) in obs_ref..iter().enumerate() { for (i, bi) in obs_ref..iter().enumerate() {
@@ -848,13 +841,13 @@ mod tests {
info!("笔 {}: 文(时间戳={}) 不在分型序列中!", i, bi..()); info!("笔 {}: 文(时间戳={}) 不在分型序列中!", i, bi..());
} }
let _ptr = Arc::as_ptr(&*bi..read().unwrap()); let _ptr = Arc::as_ptr(&*bi..read());
let _found = obs_ref..iter().any(|f| Arc::as_ptr(f) == _ptr); let _found = obs_ref..iter().any(|f| Arc::as_ptr(f) == _ptr);
if !_found { if !_found {
info!( info!(
"笔 {}: 武(时间戳={}) 不在分型序列中!", "笔 {}: 武(时间戳={}) 不在分型序列中!",
i, i,
bi..read().unwrap().() bi..read().()
); );
} }
} }
@@ -868,14 +861,13 @@ mod tests {
fn test_笔到线段的基础序列Rc指针一致性() { fn test_笔到线段的基础序列Rc指针一致性() {
let obs = ::new("btcusd".into(), 300, Default::default()); let obs = ::new("btcusd".into(), 300, Default::default());
obs.write() obs.write()
.unwrap()
.(&test_data_path(), Default::default()) .(&test_data_path(), Default::default())
.unwrap(); .unwrap();
let obs_ref = obs.read().unwrap(); let obs_ref = obs.read();
// 每个线段的基础序列中的笔 Rc 指针必须在 笔序列 中 // 每个线段的基础序列中的笔 Rc 指针必须在 笔序列 中
for (i, seg) in obs_ref.线().iter().enumerate() { for (i, seg) in obs_ref.线().iter().enumerate() {
for (j, bi_in_seg) in seg..read().unwrap().iter().enumerate() { for (j, bi_in_seg) in seg..read().iter().enumerate() {
let bi_ptr = Arc::as_ptr(bi_in_seg); let bi_ptr = Arc::as_ptr(bi_in_seg);
let found = obs_ref..iter().any(|b| Arc::as_ptr(b) == bi_ptr); let found = obs_ref..iter().any(|b| Arc::as_ptr(b) == bi_ptr);
if !found { if !found {
@@ -893,13 +885,12 @@ mod tests {
fn test_中枢基础序列与笔序列Rc指针一致() { fn test_中枢基础序列与笔序列Rc指针一致() {
let obs = ::new("btcusd".into(), 300, Default::default()); let obs = ::new("btcusd".into(), 300, Default::default());
obs.write() obs.write()
.unwrap()
.(&test_data_path(), Default::default()) .(&test_data_path(), Default::default())
.unwrap(); .unwrap();
let obs_ref = obs.read().unwrap(); let obs_ref = obs.read();
for (i, hub) in obs_ref._中枢序列.iter().enumerate() { for (i, hub) in obs_ref._中枢序列.iter().enumerate() {
for (j, bi_in_hub) in hub..read().unwrap().iter().enumerate() { for (j, bi_in_hub) in hub..read().iter().enumerate() {
let bi_ptr = Arc::as_ptr(bi_in_hub); let bi_ptr = Arc::as_ptr(bi_in_hub);
let found = obs_ref..iter().any(|b| Arc::as_ptr(b) == bi_ptr); let found = obs_ref..iter().any(|b| Arc::as_ptr(b) == bi_ptr);
if !found { if !found {
@@ -909,7 +900,7 @@ mod tests {
} }
for (i, hub) in obs_ref.().iter().enumerate() { for (i, hub) in obs_ref.().iter().enumerate() {
for (j, seg_in_hub) in hub..read().unwrap().iter().enumerate() { for (j, seg_in_hub) in hub..read().iter().enumerate() {
let seg_ptr = Arc::as_ptr(seg_in_hub); let seg_ptr = Arc::as_ptr(seg_in_hub);
let found = obs_ref.线().iter().any(|s| Arc::as_ptr(s) == seg_ptr); let found = obs_ref.线().iter().any(|s| Arc::as_ptr(s) == seg_ptr);
if !found { if !found {
@@ -934,10 +925,10 @@ mod tests {
for i in 0..data.len() / size { for i in 0..data.len() / size {
let offset = i * size; let offset = i * size;
if let Some(k线) = K线::from_bytes(&data[offset..offset + size], 300, "btcusd") { if let Some(k线) = K线::from_bytes(&data[offset..offset + size], 300, "btcusd") {
obs_ref.write().unwrap().K线(k线); obs_ref.write().K线(k线);
} }
} }
let obs = obs_ref.read().unwrap(); let obs = obs_ref.read();
( (
obs..len(), obs..len(),
obs.线().len(), obs.线().len(),
@@ -975,28 +966,28 @@ mod tests {
for i in 0..data.len() / size { for i in 0..data.len() / size {
let offset = i * size; let offset = i * size;
if let Some(k线) = K线::from_bytes(&data[offset..offset + size], 300, "btcusd") { if let Some(k线) = K线::from_bytes(&data[offset..offset + size], 300, "btcusd") {
obs_ref.write().unwrap().K线(k线); obs_ref.write().K线(k线);
} }
} }
let = obs_ref.read().unwrap()..len(); let = obs_ref.read()..len();
let = obs_ref.read().unwrap().线().len(); let = obs_ref.read().线().len();
// 重置 // 重置
obs_ref.write().unwrap().(); obs_ref.write().();
assert_eq!(obs_ref.read().unwrap()..len(), 0); assert_eq!(obs_ref.read()..len(), 0);
assert_eq!(obs_ref.read().unwrap().线().len(), 0); assert_eq!(obs_ref.read().线().len(), 0);
// 重新投喂 // 重新投喂
for i in 0..data.len() / size { for i in 0..data.len() / size {
let offset = i * size; let offset = i * size;
if let Some(k线) = K线::from_bytes(&data[offset..offset + size], 300, "btcusd") { if let Some(k线) = K线::from_bytes(&data[offset..offset + size], 300, "btcusd") {
obs_ref.write().unwrap().K线(k线); obs_ref.write().K线(k线);
} }
} }
let = obs_ref.read().unwrap()..len(); let = obs_ref.read()..len();
let = obs_ref.read().unwrap().线().len(); let = obs_ref.read().线().len();
assert_eq!(, , "重置后重新投喂笔数不一致"); assert_eq!(, , "重置后重新投喂笔数不一致");
assert_eq!(, , "重置后重新投喂线段数不一致"); assert_eq!(, , "重置后重新投喂线段数不一致");
@@ -1011,31 +1002,30 @@ mod tests {
fn test_RefCell借用安全性_连续读取不panic() { fn test_RefCell借用安全性_连续读取不panic() {
let obs = ::new("btcusd".into(), 300, Default::default()); let obs = ::new("btcusd".into(), 300, Default::default());
obs.write() obs.write()
.unwrap()
.(&test_data_path(), Default::default()) .(&test_data_path(), Default::default())
.unwrap(); .unwrap();
let obs_ref = obs.read().unwrap(); let obs_ref = obs.read();
// 连续大量读取所有 RefCell 字段,不应 panic // 连续大量读取所有 RefCell 字段,不应 panic
for _ in 0..100 { for _ in 0..100 {
for bi in &obs_ref. { for bi in &obs_ref. {
let _标识 = bi..read().unwrap().clone(); let _标识 = bi..read().clone();
let _wu = bi..read().unwrap().clone(); let _wu = bi..read().clone();
let _基础序列 = bi..read().unwrap().len(); let _基础序列 = bi..read().len();
let _特征序列 = bi..read().unwrap().len(); let _特征序列 = bi..read().len();
let _模式 = bi..read().unwrap().clone(); let _模式 = bi..read().clone();
let _实中枢 = bi._中枢序列.read().unwrap().len(); let _实中枢 = bi._中枢序列.read().len();
let _虚中枢 = bi._中枢序列.read().unwrap().len(); let _虚中枢 = bi._中枢序列.read().len();
let _合中枢 = bi._中枢序列.read().unwrap().len(); let _合中枢 = bi._中枢序列.read().len();
let _确认K = bi.K线.read().unwrap().is_some(); let _确认K = bi.K线.read().is_some();
let _序号 = bi..load(Ordering::Relaxed); let _序号 = bi..load(Ordering::Relaxed);
let _有效性 = bi..load(Ordering::Relaxed); let _有效性 = bi..load(Ordering::Relaxed);
let _短路 = bi..load(Ordering::Relaxed); let _短路 = bi..load(Ordering::Relaxed);
let _前一缺口 = *bi..read().unwrap(); let _前一缺口 = *bi..read();
} }
for seg in obs_ref.线() { for seg in obs_ref.线() {
let _ = seg..read().unwrap().clone(); let _ = seg..read().clone();
let _ = seg..read().unwrap().len(); let _ = seg..read().len();
} }
} }
// 到达这里 = 无 panic // 到达这里 = 无 panic
@@ -1045,27 +1035,26 @@ mod tests {
fn test_RefCell借用安全性_交替读写不panic() { fn test_RefCell借用安全性_交替读写不panic() {
let obs = ::new("btcusd".into(), 300, Default::default()); let obs = ::new("btcusd".into(), 300, Default::default());
obs.write() obs.write()
.unwrap()
.(&test_data_path(), Default::default()) .(&test_data_path(), Default::default())
.unwrap(); .unwrap();
let obs_ref = obs.read().unwrap(); let obs_ref = obs.read();
// 交替读写 RefCell 字段 — 先读再写同字段,分离 borrow 作用域 // 交替读写 RefCell 字段 — 先读再写同字段,分离 borrow 作用域
if !obs_ref..is_empty() { if !obs_ref..is_empty() {
let bi = &obs_ref.[0]; let bi = &obs_ref.[0];
// 读 // 读
let old_mode = bi..read().unwrap().clone(); let old_mode = bi..read().clone();
// Ref 已释放,可以写 // Ref 已释放,可以写
*bi..write().unwrap() = "测试模式".into(); *bi..write() = "测试模式".into();
let new_mode = bi..read().unwrap().clone(); let new_mode = bi..read().clone();
assert_eq!(new_mode, "测试模式"); assert_eq!(new_mode, "测试模式");
// 恢复 // 恢复
*bi..write().unwrap() = old_mode; *bi..write() = old_mode;
// 读武 // 读武
let old_wu = bi..read().unwrap().clone(); let old_wu = bi..read().clone();
// Ref 已释放,可以检查 // Ref 已释放,可以检查
assert!(Arc::as_ptr(&old_wu) == Arc::as_ptr(&old_wu)); assert!(Arc::ptr_eq(&old_wu, &old_wu));
} }
} }
@@ -1077,10 +1066,9 @@ mod tests {
fn test_缠K到分型的Rc指针一致性() { fn test_缠K到分型的Rc指针一致性() {
let obs = ::new("btcusd".into(), 300, Default::default()); let obs = ::new("btcusd".into(), 300, Default::default());
obs.write() obs.write()
.unwrap()
.(&test_data_path(), Default::default()) .(&test_data_path(), Default::default())
.unwrap(); .unwrap();
let obs_ref = obs.read().unwrap(); let obs_ref = obs.read();
// 每个分型的左/中/右 缠K 指针必须在 缠论K线序列 中 // 每个分型的左/中/右 缠K 指针必须在 缠论K线序列 中
for (i, f) in obs_ref..iter().enumerate() { for (i, f) in obs_ref..iter().enumerate() {
@@ -1196,7 +1184,7 @@ mod tests {
99 99
}); });
assert_eq!(handle.join().unwrap(), 99); assert_eq!(handle.join().unwrap(), 99);
assert_eq!(dash..read().unwrap().as_str(), ""); assert_eq!(dash..read().as_str(), "");
} }
/// 测试:Arc<中枢> 可跨线程传递 /// 测试:Arc<中枢> 可跨线程传递
@@ -1222,11 +1210,11 @@ mod tests {
let obs3 = Arc::clone(&obs); let obs3 = Arc::clone(&obs);
let h1 = std::thread::spawn(move || { let h1 = std::thread::spawn(move || {
let guard = obs2.read().unwrap(); let guard = obs2.read();
guard..clone() guard..clone()
}); });
let h2 = std::thread::spawn(move || { let h2 = std::thread::spawn(move || {
let guard = obs3.read().unwrap(); let guard = obs3.read();
guard. guard.
}); });
@@ -1274,7 +1262,7 @@ mod tests {
let obs = ::new("ethusd".into(), 7200, Default::default()); let obs = ::new("ethusd".into(), 7200, Default::default());
let handle = std::thread::spawn(move || { let handle = std::thread::spawn(move || {
let guard = obs.read().unwrap(); let guard = obs.read();
(guard..clone(), guard.) (guard..clone(), guard.)
}); });
@@ -1288,7 +1276,7 @@ mod tests {
let mut config = ::default(); let mut config = ::default();
config. = test_data_path(); config. = test_data_path();
let obs = ::new("btcusd".into(), 300, config); let obs = ::new("btcusd".into(), 300, config);
let mut obs_w = obs.write().unwrap(); let mut obs_w = obs.write();
obs_w.线 = 0; obs_w.线 = 0;
obs_w.(); obs_w.();
drop(obs_w); drop(obs_w);
@@ -1299,11 +1287,11 @@ mod tests {
for i in 0..(data.len() / size).min(500) { for i in 0..(data.len() / size).min(500) {
let offset = i * size; let offset = i * size;
if let Some(k线) = K线::from_bytes(&data[offset..offset + size], 300, "btcusd") { if let Some(k线) = K线::from_bytes(&data[offset..offset + size], 300, "btcusd") {
obs.write().unwrap().K线(k线); obs.write().K线(k线);
} }
} }
let obs_r = obs.read().unwrap(); let obs_r = obs.read();
assert!(obs_r.K线序列.len() > 0, "缠K序列应有数据"); assert!(obs_r.K线序列.len() > 0, "缠K序列应有数据");
assert!(obs_r..len() > 0, "分型序列应有数据"); assert!(obs_r..len() > 0, "分型序列应有数据");
assert!(obs_r.线.is_empty(), "线段序列组应为空"); assert!(obs_r.线.is_empty(), "线段序列组应为空");
@@ -1329,17 +1317,16 @@ mod tests {
// 先正常投喂数据 // 先正常投喂数据
obs.write() obs.write()
.unwrap()
.(&test_data_path(), Default::default()) .(&test_data_path(), Default::default())
.unwrap(); .unwrap();
// 设为0后执行静态重新分析,不应 panic // 设为0后执行静态重新分析,不应 panic
let mut obs_w = obs.write().unwrap(); let mut obs_w = obs.write();
obs_w.线 = 0; obs_w.线 = 0;
obs_w.(); obs_w.();
drop(obs_w); drop(obs_w);
let obs_r = obs.read().unwrap(); let obs_r = obs.read();
assert!(obs_r..len() > 0, "静态重新分析后分型序列应有数据"); assert!(obs_r..len() > 0, "静态重新分析后分型序列应有数据");
assert!(obs_r.线.is_empty(), "线段序列组应为空"); assert!(obs_r.线.is_empty(), "线段序列组应为空");
assert!( assert!(
@@ -1352,4 +1339,104 @@ mod tests {
obs_r..len() obs_r..len()
); );
} }
/// 创建随机配置(与 main.py 随机配置 对齐)
fn () -> crate::config:: {
let mut cfg = crate::config::::default().();
cfg.K合并替换 = fastrand::bool();
cfg. = fastrand::i64(3..=9);
cfg. = fastrand::bool();
cfg. = fastrand::bool();
cfg.K线包含整笔 = fastrand::bool();
cfg. = fastrand::bool();
cfg. = fastrand::bool();
cfg._原始数量 = fastrand::i64(3..=9);
cfg.线_非缺口下穿刺 = fastrand::bool();
cfg.线_特征序列忽视老阴老阳 = fastrand::bool();
cfg.线_修正 = fastrand::bool();
cfg.线_缺口后紧急修正 = fastrand::bool();
cfg.线_当下分析 = fastrand::bool();
cfg. = fastrand::bool();
cfg.MACD柱强相关 = fastrand::bool();
cfg
}
/// 单线程工作函数(与 main.py 运行单个回测 对齐)
fn (线: usize, limit: usize) {
let = ();
let =
crate::business::observer::::new(format!("btcusd_{}", 线), 300, );
let = [
crate::types::::,
crate::types::::,
crate::types::::,
crate::types::::,
crate::types::::,
crate::types::::,
];
let mut K线 = crate::kline::bar::K线::K(
&format!("btcusd_{}", 线),
1218124800 + 线 as i64 * 300,
8888.55,
10000.00,
9000.22,
9527.33,
888.0,
0,
300,
);
.write().K线(K线.clone());
let = crate::types::::(limit, &, true);
for in & {
K线 = K线.K线生成新K线(*, false);
.write().K线(K线.clone());
}
}
#[test]
fn test_50线程压测_10000K线() {
let 线 = 50usize;
let 线K线数 = 10000usize;
let start = std::time::Instant::now();
let mut = Vec::with_capacity(线);
let = std::sync::Arc::new(std::sync::atomic::AtomicUsize::new(0));
for i in 1..=线 {
let _ref = .clone();
.push(std::thread::spawn(move || {
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
(i, 线K线数);
}));
if result.is_err() {
_ref.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
}
}));
}
for h in {
let _ = h.join();
}
let = start.elapsed();
let K线数 = 线 * (线K线数 + 1);
let = .load(std::sync::atomic::Ordering::Relaxed);
eprintln!(
"核心层 {}线程×{}K线 = {} 次 耗时 {:.2?} ({:.0} K/s) 异常线程: {}",
线,
线K线数,
K线数,
,
K线数 as f64 / .as_secs_f64(),
);
}
#[test]
fn test_单线程基准_10000K线() {
let start = std::time::Instant::now();
(0, 10000);
let = start.elapsed();
eprintln!(
"核心层 单线程 10000 K线 耗时 {:.2?} ({:.0} K/s)",
,
10001.0 / .as_secs_f64()
);
}
} }
+2 -2
View File
@@ -23,8 +23,8 @@
*/ */
use crate::kline::bar::K线; use crate::kline::bar::K线;
use crate::warn;
use std::collections::HashMap; use std::collections::HashMap;
use tracing;
/// 事件回调类型 — fn(信号类型, 标识, 周期, 完成K线) /// 事件回调类型 — fn(信号类型, 标识, 周期, 完成K线)
type = Box<dyn Fn(String, String, i64, K线) + Send + Sync>; type = Box<dyn Fn(String, String, i64, K线) + Send + Sync>;
@@ -178,7 +178,7 @@ impl K线合成器 {
.map(|s| s.to_string()) .map(|s| s.to_string())
.or_else(|| e.downcast_ref::<String>().cloned()) .or_else(|| e.downcast_ref::<String>().cloned())
.unwrap_or_else(|| "未知错误".into()); .unwrap_or_else(|| "未知错误".into());
tracing::error!("K线合成器 事件回调 异常: {}", msg); warn!("K线合成器 事件回调 异常: {}", msg);
} }
} }
} }
+28 -175
View File
@@ -22,9 +22,9 @@
* SOFTWARE. * SOFTWARE.
*/ */
use crate::warn;
use serde::{Deserialize, Deserializer, Serialize}; use serde::{Deserialize, Deserializer, Serialize};
use std::collections::HashMap; use std::collections::HashMap;
use tracing::warn;
/// 缠论配置 —— 控制所有分析阶段的行为 /// 缠论配置 —— 控制所有分析阶段的行为
/// ///
@@ -92,103 +92,34 @@ pub struct 缠论配置 {
// ---- 指标 ---- // ---- 指标 ----
/// 是否计算技术指标 /// 是否计算技术指标
pub : bool, pub : bool,
/// 是否计算布林带 /// 指标计算方式(均线使用,MACD/RSI/KDJ/BOLL 在参数元组中指定)
pub BOLL: bool,
/// 指标计算方式(开/高/低/收/高低均值/高低收均值/开高低收均值)
#[serde(deserialize_with = "deserialize_指标计算方式")] #[serde(deserialize_with = "deserialize_指标计算方式")]
pub : String, pub : String,
// ---- MACD ---- /// MACD 参数列表 (key, 计算方式, 快线, 慢线, 信号)
/// MACD 快线 EMA 周期
pub 线_快线周期: i64,
/// MACD 慢线 EMA 周期
pub 线_慢线周期: i64,
/// MACD 信号线周期
pub 线_信号周期: i64,
/// MACD 多参数列表: Vec<(key, 快线, 慢线, 信号)>
#[serde(default)] #[serde(default)]
pub MACD_参数列表: Vec<(String, i64, i64, i64)>, pub MACD_参数列表: Vec<(String, String, i64, i64, i64)>,
// ---- RSI ---- /// RSI 参数列表 (key, 计算方式, 周期, MA周期, 超买, 超卖)
/// RSI 计算周期
pub _周期: i64,
/// RSI SMA 平滑周期
pub _移动平均线周期: i64,
/// RSI 超买阈值
pub _超买阈值: f64,
/// RSI 超卖阈值
pub _超卖阈值: f64,
/// RSI 多周期列表: Vec<(key, 周期)>
#[serde(default)] #[serde(default)]
pub RSI_周期列表: Vec<(String, i64)>, pub RSI_周期列表: Vec<(String, String, i64, i64, f64, f64)>,
// ---- KDJ ---- /// KDJ 参数列表 (key, 计算方式, RSV, K平滑, D平滑, 超买, 超卖)
/// KDJ RSV 周期
pub _RSV周期: i64,
/// KDJ K 值平滑周期
pub _K值平滑周期: i64,
/// KDJ D 值平滑周期
pub _D值平滑周期: i64,
/// KDJ 超买阈值
pub _超买阈值: f64,
/// KDJ 超卖阈值
pub _超卖阈值: f64,
/// KDJ 多参数列表: Vec<(key, RSV周期, K平滑, D平滑)>
#[serde(default)] #[serde(default)]
pub KDJ_参数列表: Vec<(String, i64, i64, i64)>, pub KDJ_参数列表: Vec<(String, String, i64, i64, i64, f64, f64)>,
// ---- BOLL ---- /// BOLL 参数列表 (key, 计算方式, 周期, 标准差倍数)
/// 布林带周期
pub _周期: i64,
/// 布林带标准差倍数
pub _标准差倍数: f64,
/// BOLL 多参数列表: Vec<(key, 周期, 标准差倍数)>
#[serde(default)] #[serde(default)]
pub BOLL_参数列表: Vec<(String, i64, f64)>, pub BOLL_参数列表: Vec<(String, String, i64, f64)>,
// ---- 均线 ---- /// 均线参数列表 (key, 计算方式, 类型, 周期) — 如 ("SMA_5", "收", "SMA", 5)
/// 均线类型列表: ["SMA", "EMA", ...]
#[serde(default)] #[serde(default)]
pub 线_类型列表: Vec<String>, pub 线: Vec<(String, String, String, i64)>,
/// 均线周期列表: [5, 10, 20, ...]
#[serde(default)]
pub 线_周期列表: Vec<i64>,
// ---- 推送/显示 ---- // ---- 推送/显示 ----
/// 是否启用图表展示
pub : bool, pub : bool,
/// 是否推送K线 /// 图表展示标签: None=全部, [] = 不展示
pub K线: bool, pub : Option<Vec<String>>,
/// 是否推送笔
pub : bool,
/// 是否推送线段
pub 线: bool,
/// 是否推送中枢
pub : bool,
// ---- 图表展示细分 ----
/// 图表展示笔
pub _笔: bool,
/// 图表展示线段
pub _线段: bool,
/// 图表展示扩展线段
pub _扩展线段: bool,
/// 图表展示扩展线段(线段级)
pub _扩展线段_线段: bool,
/// 图表展示线段之线段
pub _线段_线段: bool,
/// 图表展示笔中枢
pub _中枢_笔: bool,
/// 图表展示线段中枢
pub _中枢_线段: bool,
/// 图表展示扩展中枢
pub _中枢_扩展线段: bool,
/// 图表展示扩展中枢(线段级)
pub _中枢_扩展线段_线段: bool,
/// 图表展示线段之中枢
pub _中枢_线段_线段: bool,
/// 图表展示线段内部中枢
pub _中枢_线段内部: bool,
// ---- 买卖点 ---- // ---- 买卖点 ----
/// 买卖点偏移量 /// 买卖点偏移量
@@ -310,44 +241,14 @@ impl Default for 缠论配置 {
线: true, 线: true,
: String::new(), : String::new(),
: true, : true,
BOLL: false,
: "".into(), : "".into(),
线_快线周期: 13, MACD_参数列表: vec![("macd".into(), "".into(), 13, 31, 11)],
线_慢线周期: 31, RSI_周期列表: vec![("rsi".into(), "".into(), 14, 13, 75.0, 25.0)],
线_信号周期: 11, KDJ_参数列表: vec![("kdj".into(), "".into(), 13, 5, 5, 80.0, 20.0)],
_周期: 13, BOLL_参数列表: vec![("boll".into(), "".into(), 20, 2.0)],
_移动平均线周期: 13, 线: Vec::new(),
_超买阈值: 75.0,
_超卖阈值: 25.0,
_RSV周期: 13,
_K值平滑周期: 5,
_D值平滑周期: 5,
_超买阈值: 80.0,
_超卖阈值: 20.0,
MACD_参数列表: Vec::new(),
RSI_周期列表: Vec::new(),
KDJ_参数列表: Vec::new(),
_周期: 20,
_标准差倍数: 2.0,
BOLL_参数列表: Vec::new(),
线_类型列表: Vec::new(),
线_周期列表: Vec::new(),
: true, : true,
K线: true, : None,
: true,
线: true,
: true,
_笔: true,
_线段: true,
_扩展线段: true,
_扩展线段_线段: true,
_线段_线段: true,
_中枢_笔: true,
_中枢_线段: true,
_中枢_扩展线段: true,
_中枢_扩展线段_线段: true,
_中枢_线段_线段: true,
_中枢_线段内部: true,
: 1, : 1,
: false, : false,
MACD柱强相关: false, MACD柱强相关: false,
@@ -366,46 +267,12 @@ impl Default for 缠论配置 {
} }
impl { impl {
/// 解析MACD参数列表 — 如果列表非空则使用列表,否则返回默认单组 /// 展示标签判定 — None=全部, [] = 全关
pub fn _解析MACD参数列表(&self) -> Vec<(String, i64, i64, i64)> { pub fn (&self, : &str) -> bool {
if !self.MACD_参数列表.is_empty() { match &self. {
return self.MACD_参数列表.clone(); None => true,
Some(tags) => tags.iter().any(|t| t == ),
} }
vec![(
"macd".into(),
self.线_快线周期,
self.线_慢线周期,
self.线_信号周期,
)]
}
/// 解析RSI周期列表 — 如果列表非空则使用列表,否则返回默认单组
pub fn _解析RSI周期列表(&self) -> Vec<(String, i64)> {
if !self.RSI_周期列表.is_empty() {
return self.RSI_周期列表.clone();
}
vec![("rsi".into(), self._周期)]
}
/// 解析KDJ参数列表 — 如果列表非空则使用列表,否则返回默认单组
pub fn _解析KDJ参数列表(&self) -> Vec<(String, i64, i64, i64)> {
if !self.KDJ_参数列表.is_empty() {
return self.KDJ_参数列表.clone();
}
vec![(
"kdj".into(),
self._RSV周期,
self._K值平滑周期,
self._D值平滑周期,
)]
}
/// 解析BOLL参数列表 — 如果列表非空则使用列表,否则返回默认单组
pub fn _解析BOLL参数列表(&self) -> Vec<(String, i64, f64)> {
if !self.BOLL_参数列表.is_empty() {
return self.BOLL_参数列表.clone();
}
vec![("boll".into(), self._周期, self._标准差倍数)]
} }
/// 序列化为 JSON 字典(对应 Python to_dict,仅返回 model_fields 中的字段) /// 序列化为 JSON 字典(对应 Python to_dict,仅返回 model_fields 中的字段)
@@ -576,21 +443,7 @@ impl 缠论配置 {
Self { Self {
线: false, 线: false,
: false, : false,
K线: false, : Some(vec![]),
: false,
线: false,
: false,
_笔: false,
_线段: false,
_扩展线段: false,
_扩展线段_线段: false,
_线段_线段: false,
_中枢_笔: false,
_中枢_线段: false,
_中枢_扩展线段: false,
_中枢_扩展线段_线段: false,
_中枢_线段_线段: false,
_中枢_线段内部: false,
..self.clone() ..self.clone()
} }
} }
@@ -795,7 +648,7 @@ mod tests {
#[test] #[test]
fn test_对比_有差异() { fn test_对比_有差异() {
let mut a = ::default(); let a = ::default();
let mut b = ::default(); let mut b = ::default();
b. = "changed".into(); b. = "changed".into();
b. = 99; b. = 99;
@@ -854,7 +707,7 @@ mod tests {
#[test] #[test]
fn test_对比_boolean_difference() { fn test_对比_boolean_difference() {
let mut a = ::default(); let a = ::default();
let mut b = ::default(); let mut b = ::default();
b.K线 = false; b.K线 = false;
b. = false; b. = false;
+6 -5
View File
@@ -24,6 +24,7 @@
use crate::kline::bar::K线; use crate::kline::bar::K线;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::collections::VecDeque;
/// 布林带(BOLL)— 基于移动平均和标准差的波动率通道 /// 布林带(BOLL)— 基于移动平均和标准差的波动率通道
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
@@ -43,7 +44,7 @@ pub struct 布林带 {
pub : f64, pub : f64,
/// 内部历史队列(不序列化) /// 内部历史队列(不序列化)
#[serde(skip)] #[serde(skip)]
_历史队列: Vec<f64>, _历史队列: VecDeque<f64>,
/// 内部均值缓存(不序列化) /// 内部均值缓存(不序列化)
#[serde(skip)] #[serde(skip)]
_均值: f64, _均值: f64,
@@ -61,7 +62,7 @@ impl Default for 布林带 {
: 0.0, : 0.0,
: 0.0, : 0.0,
: 0.0, : 0.0,
_历史队列: Vec::new(), _历史队列: VecDeque::new(),
_均值: 0.0, _均值: 0.0,
_方差和: 0.0, _方差和: 0.0,
} }
@@ -98,7 +99,7 @@ impl 布林带 {
: , : ,
: , : ,
: , : ,
_历史队列: vec![], _历史队列: VecDeque::from([]),
_均值: , _均值: ,
_方差和: 0.0, _方差和: 0.0,
} }
@@ -110,9 +111,9 @@ impl 布林带 {
let = prev.; let = prev.;
let mut q = prev._历史队列.clone(); let mut q = prev._历史队列.clone();
q.push(); q.push_back();
if q.len() > { if q.len() > {
q.remove(0); q.pop_front();
} }
let (_均值, _方差和) = if q.len() < { let (_均值, _方差和) = if q.len() < {
+125 -197
View File
@@ -45,7 +45,7 @@ impl 指标计算器 {
let has_prev; let has_prev;
{ {
let prev_guard = if n > 1 { let prev_guard = if n > 1 {
Some([n - 2]..read().unwrap()) Some([n - 2]..read())
} else { } else {
None None
}; };
@@ -56,7 +56,7 @@ impl 指标计算器 {
Self::_计算KDJ组(K线, prev, ); Self::_计算KDJ组(K线, prev, );
Self::_计算BOLL组(K线, prev, ); Self::_计算BOLL组(K线, prev, );
} }
Self::_更新均线(K线, , ); Self::_更新均线(K线, , ); // 当前K线在 全序列[n-1], 现有序列不含它
has_prev = n > 1; has_prev = n > 1;
} // prev_guard dropped here } // prev_guard dropped here
@@ -66,10 +66,9 @@ impl 指标计算器 {
} }
fn _计算MACD组(K线: &K线, prev: Option<&>, : &) { fn _计算MACD组(K线: &K线, prev: Option<&>, : &) {
let = &.; for (i, (key, , , , )) in .MACD_参数列表.iter().enumerate()
for (i, (key, , , )) in ._解析MACD参数列表().into_iter().enumerate()
{ {
let val = if let Some(prev_val) = prev.and_then(|p| p.(&key)) { let val = if let Some(prev_val) = prev.and_then(|p| p.(key)) {
if let ::MACD(prev_macd) = prev_val { if let ::MACD(prev_macd) = prev_val {
::MACD(线::( ::MACD(线::(
prev_macd, prev_macd,
@@ -95,21 +94,18 @@ impl 指标计算器 {
, ,
), ),
K线., K线.,
, *,
, *,
, *,
)) ))
}; };
K线..write().unwrap().(&key, val.clone()); K线..write().(key, val.clone());
if i == 0 {
K线..write().unwrap().("macd", val);
}
} }
} }
fn _计算RSI组(K线: &K线, prev: Option<&>, : &) { fn _计算RSI组(K线: &K线, prev: Option<&>, : &) {
let = &.; for (key, , , ma周期, , ) in .RSI_周期列表.iter()
for (i, (key, )) in ._解析RSI周期列表().into_iter().enumerate() { {
let val = if let Some(prev_val) = prev.and_then(|p| p.(&key)) { let val = if let Some(prev_val) = prev.and_then(|p| p.(&key)) {
if let ::RSI(prev_rsi) = prev_val { if let ::RSI(prev_rsi) = prev_val {
::RSI(::( ::RSI(::(
@@ -136,23 +132,19 @@ impl 指标计算器 {
, ,
), ),
K线., K线.,
, *,
._超买阈值, *,
._超卖阈值, *,
Some(._移动平均线周期), Some(*ma周期),
)) ))
}; };
K线..write().unwrap().(&key, val.clone()); K线..write().(&key, val.clone());
if i == 0 {
K线..write().unwrap().("rsi", val);
}
} }
} }
fn _计算KDJ组(K线: &K线, prev: Option<&>, : &) { fn _计算KDJ组(K线: &K线, prev: Option<&>, : &) {
for (i, (key, rsv, k平滑, d平滑)) in ._解析KDJ参数列表().into_iter().enumerate() for (key, _fm, rsv, k平滑, d平滑, , ) in .KDJ_参数列表.iter() {
{ let val = if let Some(prev_val) = prev.and_then(|p| p.(key)) {
let val = if let Some(prev_val) = prev.and_then(|p| p.(&key)) {
if let ::KDJ(prev_kdj) = prev_val { if let ::KDJ(prev_kdj) = prev_val {
::KDJ(::( ::KDJ(::(
prev_kdj, prev_kdj,
@@ -170,24 +162,19 @@ impl 指标计算器 {
K线., K线.,
K线., K线.,
K线., K线.,
rsv, *rsv,
k平滑, *k平滑,
d平滑, *d平滑,
._超买阈值, *,
._超卖阈值, *,
)) ))
}; };
K线..write().unwrap().(&key, val.clone()); K线..write().(key, val.clone());
if i == 0 {
K线..write().unwrap().("kdj", val);
}
} }
} }
fn _计算BOLL组(K线: &K线, prev: Option<&>, : &) { fn _计算BOLL组(K线: &K线, prev: Option<&>, : &) {
let = &.; for (key, , , ) in .BOLL_参数列表.iter() {
for (i, (key, , )) in ._解析BOLL参数列表().into_iter().enumerate()
{
let val = if let Some(prev_val) = prev.and_then(|p| p.(&key)) { let val = if let Some(prev_val) = prev.and_then(|p| p.(&key)) {
if let ::BOLL(prev_boll) = prev_val { if let ::BOLL(prev_boll) = prev_val {
::BOLL(::( ::BOLL(::(
@@ -214,22 +201,37 @@ impl 指标计算器 {
K线., K线.,
, ,
), ),
as usize, * as usize,
, *,
)) ))
}; };
K线..write().unwrap().(&key, val.clone()); K线..write().(&key, val.clone());
if i == 0 {
K线..write().unwrap().("boll", val);
}
} }
} }
fn _更新均线(K线: &K线, : &[Arc<K线>], : &) { fn _更新均线(K线: &K线, : &[Arc<K线>], : &) {
if .线_类型列表.is_empty() || .线_周期列表.is_empty() { if .线.is_empty() {
return; return;
} }
let = &.; for (key, , ma_type, period) in &.线 {
let = match ma_type.as_str() {
"SMA" => Self::_增量SMA(K线, , , *period, key),
"EMA" => Self::_增量EMA(K线, , , *period, key),
_ => continue,
};
if let Some(线_map) = K线..write().线_mut() {
线_map.insert(key.clone(), );
}
}
}
fn _增量SMA(
K线: &K线,
: &[Arc<K线>],
: &str,
period: i64,
prev_key: &str,
) -> f64 {
let = super::K线取值( let = super::K线取值(
K线., K线.,
K线., K线.,
@@ -237,46 +239,20 @@ impl 指标计算器 {
K线., K线.,
, ,
); );
for ma_type in &.线_类型列表 {
for period in &.线_周期列表 {
let key = format!("{}_{}", ma_type, period);
let = match ma_type.as_str() {
"SMA" => Self::_增量SMA(, , *period, , &key),
"EMA" => Self::_增量EMA(, , *period, , &key),
_ => continue,
};
if let Some(线_map) = K线..write().unwrap().线_mut() {
线_map.insert(key, );
}
}
}
}
/// 增量 SMA: 现有序列 (不含当前K线) + 当前价
fn _增量SMA(
: &[Arc<K线>],
: f64,
period: i64,
: &str,
prev_key: &str,
) -> f64 {
let existing_len = .len(); let existing_len = .len();
let p = period as usize; let p = period as usize;
// 现有序列 + 当前 = total_len if existing_len + 1 <= p {
let total_len = existing_len + 1; let sum: f64 =
if total_len <= p {
let mut sum: f64 = [existing_len.saturating_sub(p.saturating_sub(1))..]
.iter() .iter()
.map(|k| super::K线取值(k., k., k., k., )) .map(|k| super::K线取值(k., k., k., k., ))
.sum(); .sum::<f64>()
sum += ; + ;
return sum / (total_len as f64).max(1.0); return sum / ((existing_len + 1) as f64).max(1.0);
} }
// 尝试从前一根K线获取缓存的SMA if let Some(prev_sma) =
if let Some(prev) = .last().and_then(|k| { .last()
let guard = k..read().unwrap(); .and_then(|k| k..read().线().and_then(|m| m.get(prev_key)).copied())
guard.线().and_then(|m| m.get(prev_key)).copied() {
}) {
let oldest = super::K线取值( let oldest = super::K线取值(
[existing_len - p]., [existing_len - p].,
[existing_len - p]., [existing_len - p].,
@@ -284,29 +260,33 @@ impl 指标计算器 {
[existing_len - p]., [existing_len - p].,
, ,
); );
return prev + ( - oldest) / period as f64; return prev_sma + ( - oldest) / period as f64;
} }
// 回退:完整计算 let sum: f64 = [existing_len.saturating_sub(p.saturating_sub(1))..]
let mut sum: f64 = [existing_len.saturating_sub(p.saturating_sub(1))..]
.iter() .iter()
.map(|k| super::K线取值(k., k., k., k., )) .map(|k| super::K线取值(k., k., k., k., ))
.sum(); .sum::<f64>()
sum += ; + ;
sum / (total_len as f64).min(p as f64) sum / ((existing_len + 1) as f64).min(p as f64)
} }
/// 增量 EMA: 现有序列 (不含当前K线) + 当前价
fn _增量EMA( fn _增量EMA(
K线: &K线,
: &[Arc<K线>], : &[Arc<K线>],
: f64, : &str,
period: i64, period: i64,
_计算方式: &str,
prev_key: &str, prev_key: &str,
) -> f64 { ) -> f64 {
let = .last().and_then(|k| { let = super::K线取值(
let guard = k..read().unwrap(); K线.,
guard.线().and_then(|m| m.get(prev_key)).copied() K线.,
}); K线.,
K线.,
,
);
let =
.last()
.and_then(|k| k..read().线().and_then(|m| m.get(prev_key)).copied());
match { match {
None => , None => ,
Some(prev) => { Some(prev) => {
@@ -318,158 +298,106 @@ impl 指标计算器 {
/// 运行中新增指标参数时,回填所有历史K线 /// 运行中新增指标参数时,回填所有历史K线
fn _回填新指标(: &[Arc<K线>], : &) { fn _回填新指标(: &[Arc<K线>], : &) {
// 作用域化首尾读锁:在回填写循环之前释放,避免读锁与写锁冲突
let (MACD, RSI, KDJ, BOLL) = { let (MACD, RSI, KDJ, BOLL) = {
let K_guard = [0]..read().unwrap(); let K_guard = [0]..read();
let K_guard = [.len() - 1]..read().unwrap(); let K_guard = [.len() - 1]..read();
let MACD: Vec<_> = let MACD: Vec<_> =
._解析MACD参数列表() .MACD_参数列表
.into_iter() .iter()
.filter(|(key, _, _, _)| K_guard.(key) && !K_guard.(key)) .filter(|(key, ..)| K_guard.(key) && !K_guard.(key))
.cloned()
.collect(); .collect();
let RSI: Vec<_> = let RSI: Vec<_> =
._解析RSI周期列表() .RSI_周期列表
.into_iter() .iter()
.filter(|(key, _)| K_guard.(key) && !K_guard.(key)) .filter(|(key, ..)| K_guard.(key) && !K_guard.(key))
.cloned()
.collect(); .collect();
let KDJ: Vec<_> = let KDJ: Vec<_> =
._解析KDJ参数列表() .KDJ_参数列表
.into_iter() .iter()
.filter(|(key, _, _, _)| K_guard.(key) && !K_guard.(key)) .filter(|(key, ..)| K_guard.(key) && !K_guard.(key))
.cloned()
.collect(); .collect();
let BOLL: Vec<_> = let BOLL: Vec<_> =
._解析BOLL参数列表() .BOLL_参数列表
.into_iter() .iter()
.filter(|(key, _, _)| K_guard.(key) && !K_guard.(key)) .filter(|(key, ..)| K_guard.(key) && !K_guard.(key))
.cloned()
.collect(); .collect();
(MACD, RSI, KDJ, BOLL) (MACD, RSI, KDJ, BOLL)
}; // 首K_guard, 尾K_guard dropped here };
if MACD.is_empty() && RSI.is_empty() && KDJ.is_empty() && BOLL.is_empty() { if MACD.is_empty() && RSI.is_empty() && KDJ.is_empty() && BOLL.is_empty() {
return; return;
} }
let = &.;
// 从第一根K线开始逐根回填,每次只持有一根prev读锁
for i in 0...len() { for i in 0...len() {
let k线 = &[i]; let k线 = &[i];
let prev_guard = if i > 0 { let prev_guard = if i > 0 {
Some([i - 1]..read().unwrap()) Some([i - 1]..read())
} else { } else {
None None
}; };
for (key, , , ) in &MACD { for (key, , , , ) in &MACD {
let val = if let Some(ref prev) = prev_guard { let val = match prev_guard.as_ref().and_then(|p| p.(key)) {
if let Some(::MACD(prev_macd)) = prev.(key) { Some(::MACD(prev_macd)) => ::MACD(
::MACD(线::_K线( 线::_K线(prev_macd, k线, ),
prev_macd, ),
k线, _ => ::MACD(线::_K线(
,
))
} else {
::MACD(线::_K线(
k线,
,
*,
*,
*,
))
}
} else {
::MACD(线::_K线(
k线, k线,
, ,
*, *,
*, *,
*, *,
)) )),
}; };
k线..write().unwrap().(key, val); k线..write().(key, val);
} }
for (key, ) in &RSI { for (key, , , ma周期, , ) in &RSI {
let val = if let Some(ref prev) = prev_guard { let val = match prev_guard.as_ref().and_then(|p| p.(key)) {
if let Some(::RSI(prev_rsi)) = prev.(key) { Some(::RSI(prev_rsi)) => ::RSI(
::RSI(::_K线( ::_K线(prev_rsi, k线, ),
prev_rsi, ),
k线, _ => ::RSI(::_K线(
,
))
} else {
::RSI(::_K线(
k线,
,
*,
._超买阈值,
._超卖阈值,
Some(._移动平均线周期),
))
}
} else {
::RSI(::_K线(
k线, k线,
, ,
*, *,
._超买阈值, *,
._超卖阈值, *,
Some(._移动平均线周期), Some(*ma周期),
)) )),
}; };
k线..write().unwrap().(key, val); k线..write().(key, val);
} }
for (key, rsv, k平滑, d平滑) in &KDJ { for (key, _fm, rsv, k平滑, d平滑, , ) in &KDJ {
let val = if let Some(ref prev) = prev_guard { let val = match prev_guard.as_ref().and_then(|p| p.(key)) {
if let Some(::KDJ(prev_kdj)) = prev.(key) { Some(::KDJ(prev_kdj)) => {
::KDJ(::_K线(prev_kdj, k线)) ::KDJ(::_K线(prev_kdj, k线))
} else {
::KDJ(::_K线(
k线,
*rsv,
*k平滑,
*d平滑,
._超买阈值,
._超卖阈值,
))
} }
} else { _ => ::KDJ(::_K线(
::KDJ(::_K线( k线, *rsv, *k平滑, *d平滑, *, *,
k线, )),
*rsv,
*k平滑,
*d平滑,
._超买阈值,
._超卖阈值,
))
}; };
k线..write().unwrap().(key, val); k线..write().(key, val);
} }
for (key, , ) in &BOLL { for (key, , , ) in &BOLL {
let val = if let Some(ref prev) = prev_guard { let val = match prev_guard.as_ref().and_then(|p| p.(key)) {
if let Some(::BOLL(prev_boll)) = prev.(key) { Some(::BOLL(prev_boll)) => {
::BOLL(::_K线(prev_boll, k线, )) ::BOLL(::_K线(prev_boll, k线, ))
} else {
::BOLL(::_K线(
k线,
,
* as usize,
*,
))
} }
} else { _ => ::BOLL(::_K线(
::BOLL(::_K线(
k线, k线,
, ,
* as usize, * as usize,
*, *,
)) )),
}; };
k线..write().unwrap().(key, val); k线..write().(key, val);
} }
} }
} }
+1 -3
View File
@@ -70,9 +70,7 @@ impl 指标容器 {
/// 预注册指标(不覆盖已有值) /// 预注册指标(不覆盖已有值)
pub fn (&mut self, : &str, : Option<>) { pub fn (&mut self, : &str, : Option<>) {
if !self._数据.contains_key() { self._数据.entry(.to_string()).or_insert();
self._数据.insert(.to_string(), );
}
} }
/// 按名称获取指标值 /// 按名称获取指标值
+13 -12
View File
@@ -24,6 +24,7 @@
use crate::kline::bar::K线; use crate::kline::bar::K线;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::collections::VecDeque;
/// 随机指标 (KDJ) /// 随机指标 (KDJ)
/// ///
@@ -58,9 +59,9 @@ pub struct 随机指标 {
/// J 值 (3K - 2D) /// J 值 (3K - 2D)
pub J: Option<f64>, pub J: Option<f64>,
/// 历史最高价队列(滑动窗口) /// 历史最高价队列(滑动窗口)
pub : Vec<f64>, pub : VecDeque<f64>,
/// 历史最低价队列(滑动窗口) /// 历史最低价队列(滑动窗口)
pub : Vec<f64>, pub : VecDeque<f64>,
/// 前一个 RSV(用于平滑递推) /// 前一个 RSV(用于平滑递推)
pub RSV: Option<f64>, pub RSV: Option<f64>,
/// 前一个 K(用于平滑递推) /// 前一个 K(用于平滑递推)
@@ -85,8 +86,8 @@ impl Default for 随机指标 {
K: None, K: None,
D: None, D: None,
J: None, J: None,
: Vec::new(), : VecDeque::new(),
: Vec::new(), : VecDeque::new(),
RSV: None, RSV: None,
K: None, K: None,
D: None, D: None,
@@ -124,8 +125,8 @@ impl 随机指标 {
K: None, K: None,
D: None, D: None,
J: None, J: None,
: vec![], : VecDeque::from([]),
: vec![], : VecDeque::from([]),
RSV: None, RSV: None,
K: None, K: None,
D: None, D: None,
@@ -181,16 +182,16 @@ impl 随机指标 {
// 更新历史最高价队列 // 更新历史最高价队列
let mut = KDJ..clone(); let mut = KDJ..clone();
.push(); .push_back();
if .len() > N as usize { if .len() > N as usize {
.remove(0); .pop_front();
} }
// 更新历史最低价队列 // 更新历史最低价队列
let mut = KDJ..clone(); let mut = KDJ..clone();
.push(); .push_back();
if .len() > N as usize { if .len() > N as usize {
.remove(0); .pop_front();
} }
// RSV // RSV
@@ -260,8 +261,8 @@ mod tests {
#[test] #[test]
fn test_first_calc() { fn test_first_calc() {
let kdj = ::(110.0, 90.0, 100.0, 1000, 9, 3, 3, 80.0, 20.0); let kdj = ::(110.0, 90.0, 100.0, 1000, 9, 3, 3, 80.0, 20.0);
assert_eq!(kdj., vec![110.0]); assert_eq!(kdj., VecDeque::from([110.0]));
assert_eq!(kdj., vec![90.0]); assert_eq!(kdj., VecDeque::from([90.0]));
assert_eq!(kdj.K, None); assert_eq!(kdj.K, None);
} }
+20 -10
View File
@@ -24,6 +24,7 @@
use crate::kline::bar::K线; use crate::kline::bar::K线;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::collections::VecDeque;
/// 相对强弱指数 (RSI) /// 相对强弱指数 (RSI)
/// ///
@@ -58,7 +59,9 @@ pub struct 相对强弱指数 {
/// RSI SMA 值 /// RSI SMA 值
pub RSI_SMA: Option<f64>, pub RSI_SMA: Option<f64>,
/// RSI 历史队列(用于滚动计算) /// RSI 历史队列(用于滚动计算)
pub RSI历史队列: Vec<f64>, pub RSI历史队列: VecDeque<f64>,
/// RSI 历史队列运行和(O(1) SMA
pub RSI和: f64,
} }
impl Default for { impl Default for {
@@ -77,7 +80,8 @@ impl Default for 相对强弱指数 {
: 0.0, : 0.0,
: 0.0, : 0.0,
RSI_SMA: None, RSI_SMA: None,
RSI历史队列: Vec::new(), RSI历史队列: VecDeque::new(),
RSI和: 0.0,
} }
} }
} }
@@ -106,7 +110,8 @@ impl 相对强弱指数 {
: 0.0, : 0.0,
: 1.0 / as f64, : 1.0 / as f64,
RSI_SMA: None, RSI_SMA: None,
RSI历史队列: Vec::new(), RSI历史队列: VecDeque::new(),
RSI和: 0.0,
} }
} }
@@ -167,21 +172,25 @@ impl 相对强弱指数 {
}; };
// RSI_SMA // RSI_SMA
let (RSI_SMA, RSI历史队列) = match RSI_SMA周期 { let (RSI_SMA, RSI历史队列, RSI和) = match RSI_SMA周期 {
Some(sma周期) if sma周期 > 0 => { Some(sma周期) if sma周期 > 0 => {
let mut = RSI.RSI历史队列.clone(); let mut = RSI.RSI历史队列.clone();
.push(RSI); let mut sum = RSI.RSI和;
if .len() > sma周期 as usize { .push_back(RSI);
.remove(0); sum += RSI;
if .len() > sma周期 as usize
&& let Some(old) = .pop_front()
{
sum -= old;
} }
let sma = if .is_empty() { let sma = if .is_empty() {
None None
} else { } else {
Some(.iter().sum::<f64>() / .len() as f64) Some(sum / .len() as f64)
}; };
(sma, ) (sma, , sum)
} }
_ => (None, Vec::new()), _ => (None, VecDeque::new(), 0.0),
}; };
Self { Self {
@@ -199,6 +208,7 @@ impl 相对强弱指数 {
, ,
RSI_SMA, RSI_SMA,
RSI历史队列, RSI历史队列,
RSI和,
} }
} }
} }
+190 -5
View File
@@ -23,16 +23,18 @@
*/ */
use crate::indicators::; use crate::indicators::;
use crate::info;
use crate::types::; use crate::types::;
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
use parking_lot::RwLock;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::collections::HashMap; use std::collections::HashMap;
use std::io::Write; use std::io::Write;
use std::sync::{Arc, RwLock}; use std::sync::Arc;
mod rwlock_container_serde { mod rwlock_container_serde {
use parking_lot::RwLock;
use serde::{Deserialize, Deserializer, Serialize, Serializer}; use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::sync::RwLock;
/// Serde 序列化辅助(RwLock<指标容器> → 序列化器) /// Serde 序列化辅助(RwLock<指标容器> → 序列化器)
pub fn serialize<S>( pub fn serialize<S>(
@@ -42,7 +44,7 @@ mod rwlock_container_serde {
where where
S: Serializer, S: Serializer,
{ {
val.read().unwrap().serialize(ser) val.read().serialize(ser)
} }
/// Serde 反序列化辅助(反序列化器 → RwLock<指标容器> /// Serde 反序列化辅助(反序列化器 → RwLock<指标容器>
@@ -115,7 +117,7 @@ impl Clone for K线 {
: self., : self.,
: self., : self.,
: self., : self.,
: RwLock::new(self..read().unwrap().clone()), : RwLock::new(self..read().clone()),
} }
} }
} }
@@ -223,6 +225,7 @@ impl K线 {
/// 保存K线序列到 DAT 文件 /// 保存K线序列到 DAT 文件
pub fn DAT文件(: &str, K线序列: &[&Self]) -> std::io::Result<()> { pub fn DAT文件(: &str, K线序列: &[&Self]) -> std::io::Result<()> {
info!("保存到DAT文件: {}", );
let mut f = std::fs::File::create()?; let mut f = std::fs::File::create()?;
for k in K线序列 { for k in K线序列 {
f.write_all(&k.to_bytes())?; f.write_all(&k.to_bytes())?;
@@ -245,7 +248,7 @@ impl K线 {
let mut = 0.0f64; let mut = 0.0f64;
let mut = 0.0f64; let mut = 0.0f64;
for k in { for k in {
if let Some(macd) = k..read().unwrap().macd() { if let Some(macd) = k..read().macd() {
let hist = macd.MACD柱; let hist = macd.MACD柱;
if hist >= 0.0 { if hist >= 0.0 {
+= hist; += hist;
@@ -314,6 +317,73 @@ impl K线 {
(true, "K线: 全部字段一致".into()) (true, "K线: 全部字段一致".into())
} }
/// 根据当前K线和方向生成下一根K线(与 chan.py 对齐)
pub fn K线生成新K线(&self, : , : bool) -> Self {
let = self. - self.;
let = if {
* 0.5
} else {
let lo = ( * 0.1279) as i64;
let hi = ( * 0.883) as i64;
if hi > lo {
fastrand::i64(lo..=hi) as f64
} else {
lo as f64
}
};
let = if {
* 1.5
} else {
let lo = ( * 1.1279) as i64;
let hi = ( * 1.883) as i64;
if hi > lo {
fastrand::i64(lo..=hi) as f64
} else {
lo as f64
}
};
let (, ) = match {
:: => (self. + , self. + ),
:: => (self. - , self. - ),
:: => (self. + , self. + ),
:: => (self. - , self. - ),
:: => {
let off = ;
(self. + off, self.)
}
:: => {
let off = ;
(self., self. - off)
}
_ => (self., self.),
};
let = [self., self., self., self.]
.iter()
.map(|v| {
let s = format!("{v}");
s.split('.').nth(1).map(|d| d.len()).unwrap_or(0)
})
.max()
.unwrap_or(2);
let round = |v: f64| -> f64 {
let scale = 10_f64.powi( as i32);
(v * scale).round() / scale
};
let = round( + ( - ) * fastrand::f64());
let = round( + ( - ) * fastrand::f64());
Self::K(
&self.,
self. + self.,
,
round(),
round(),
,
998.0 * fastrand::f64(),
self. + 1,
self.,
)
}
/// 截取Arc<K线>序列中从始到终的片段 /// 截取Arc<K线>序列中从始到终的片段
pub fn rc(: &[Arc<Self>], : &Arc<Self>, : &Arc<Self>) -> Vec<Arc<Self>> { pub fn rc(: &[Arc<Self>], : &Arc<Self>, : &Arc<Self>) -> Vec<Arc<Self>> {
let _ptr = Arc::as_ptr(); let _ptr = Arc::as_ptr();
@@ -349,6 +419,7 @@ impl std::fmt::Display for K线 {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::{error, info, warn};
#[test] #[test]
fn test_方向() { fn test_方向() {
@@ -385,4 +456,118 @@ mod tests {
assert_eq!(result.get(""), Some(&0.0)); assert_eq!(result.get(""), Some(&0.0));
assert_eq!(result.get(""), Some(&0.0)); assert_eq!(result.get(""), Some(&0.0));
} }
// ---- 根据当前K线生成新K线 ----
#[test]
fn test_生成K线_居中向上() {
let bar = K线::K(
"test", 1000, 50000.0, 50200.0, 49800.0, 50100.0, 100.0, 0, 300,
);
let new = bar.K线生成新K线(::, true);
// 居中: 偏移 = (50200-49800)*0.5 = 200
assert!((new. - 50400.0).abs() < 1.0); // 50200 + 200
assert!((new. - 50000.0).abs() < 1.0); // 49800 + 200
assert_eq!(new., 1);
assert_eq!(new., 1300);
}
#[test]
fn test_生成K线_居中向下() {
let bar = K线::K(
"test", 1000, 50000.0, 50200.0, 49800.0, 50100.0, 100.0, 0, 300,
);
let new = bar.K线生成新K线(::, true);
assert!((new. - 50000.0).abs() < 1.0); // 50200 - 200
assert!((new. - 49600.0).abs() < 1.0); // 49800 - 200
}
#[test]
fn test_生成K线_居中向上缺口() {
let bar = K线::K(
"test", 1000, 50000.0, 50200.0, 49800.0, 50100.0, 100.0, 0, 300,
);
let new = bar.K线生成新K线(::, true);
// 居中缺口: 偏移 = 400*1.5 = 600
assert!((new. - 50800.0).abs() < 1.0); // 50200 + 600
assert!((new. - 50400.0).abs() < 1.0); // 49800 + 600
}
#[test]
fn test_生成K线_衔接向上() {
let bar = K线::K(
"test", 1000, 50000.0, 50200.0, 49800.0, 50100.0, 100.0, 0, 300,
);
let new = bar.K线生成新K线(::, true);
let = 50200.0 - 49800.0;
assert!((new. - (50200.0 + )).abs() < 1.0);
assert!((new. - 50200.0).abs() < 1.0); // 衔接向上: 低 = 原高
}
#[test]
fn test_生成K线_衔接向下() {
let bar = K线::K(
"test", 1000, 50000.0, 50200.0, 49800.0, 50100.0, 100.0, 0, 300,
);
let new = bar.K线生成新K线(::, true);
assert!((new. - 49800.0).abs() < 1.0); // 衔接向下: 高 = 原低
}
#[test]
fn test_生成K线_非居中随机范围() {
let bar = K线::K(
"test", 1000, 50000.0, 50200.0, 49800.0, 50100.0, 100.0, 0, 300,
);
// 非居中:偏移在 [高低差*0.1279, 高低差*0.883] 范围内随机
for _ in 0..20 {
let new = bar.K线生成新K线(::, false);
assert!(new. > bar., "向上:新高应高于原高");
assert!(new. > bar., "向上:新低应高于原低");
let = new. - bar.;
let = bar. - bar.;
let lo = * 0.1279;
let hi = * 0.883;
assert!(
>= lo && <= hi + 1.0,
"偏移 {偏移} 应在 [{lo}, {hi}] 范围内"
);
}
}
// ---- 从序列中机选 ----
#[test]
fn test_从序列中机选_可重复() {
let dirs = vec![::, ::, ::];
let result = ::(5, &dirs, true);
assert_eq!(result.len(), 5);
for d in &result {
assert!(dirs.contains(d));
}
}
#[test]
fn test_从序列中机选_不可重复() {
let dirs = vec![::, ::, ::];
let result = ::(3, &dirs, false);
assert_eq!(result.len(), 3);
for (i, d) in result.iter().enumerate() {
for prev in result[..i].iter() {
assert_ne!(prev, d, "重复方向: {:?}", d);
}
}
}
#[test]
#[should_panic(expected = "数量超过可选方向数")]
fn test_从序列中机选_数量超限() {
let dirs = vec![::, ::];
::(3, &dirs, false);
}
#[test]
fn test_从序列中机选_空序列() {
let result = ::(0, &[], true);
assert!(result.is_empty());
}
} }
+38 -42
View File
@@ -29,9 +29,10 @@ use crate::structure::fractal_obj::分型;
use crate::types::SyncF64; use crate::types::SyncF64;
use crate::types::; use crate::types::;
use crate::types::; use crate::types::;
use parking_lot::RwLock;
use std::collections::HashSet; use std::collections::HashSet;
use std::sync::Arc;
use std::sync::atomic::{AtomicI64, Ordering}; use std::sync::atomic::{AtomicI64, Ordering};
use std::sync::{Arc, RwLock};
/// 缠论K线 — 经包含处理过后的K线 /// 缠论K线 — 经包含处理过后的K线
/// ///
@@ -74,15 +75,15 @@ impl Clone for 缠论K线 {
: AtomicI64::new(self..load(Ordering::Relaxed)), : AtomicI64::new(self..load(Ordering::Relaxed)),
: SyncF64::new(self..get()), : SyncF64::new(self..get()),
: SyncF64::new(self..get()), : SyncF64::new(self..get()),
: RwLock::new(*self..read().unwrap()), : RwLock::new(*self..read()),
: RwLock::new(*self..read().unwrap()), : RwLock::new(*self..read()),
: self., : self.,
: self..clone(), : self..clone(),
: SyncF64::new(self..get()), : SyncF64::new(self..get()),
: self., : self.,
: AtomicI64::new(self..load(Ordering::Relaxed)), : AtomicI64::new(self..load(Ordering::Relaxed)),
K线: RwLock::new(Arc::clone(&self.K线.read().unwrap())), K线: RwLock::new(Arc::clone(&self.K线.read())),
: RwLock::new(self..read().unwrap().clone()), : RwLock::new(self..read().clone()),
} }
} }
} }
@@ -97,10 +98,9 @@ impl std::fmt::Display for 缠论K线 {
self..load(Ordering::Relaxed), self..load(Ordering::Relaxed),
self. self.
.read() .read()
.unwrap()
.map_or("None".to_string(), |fx| fx.to_string()), .map_or("None".to_string(), |fx| fx.to_string()),
self., self.,
*self..read().unwrap(), *self..read(),
self..load(Ordering::Relaxed), self..load(Ordering::Relaxed),
format_f64_g(self..get()), format_f64_g(self..get()),
format_f64_g(self..get()) format_f64_g(self..get())
@@ -116,23 +116,23 @@ impl 缠论K线 {
: AtomicI64::new(self..load(Ordering::Relaxed)), : AtomicI64::new(self..load(Ordering::Relaxed)),
: SyncF64::new(self..get()), : SyncF64::new(self..get()),
: SyncF64::new(self..get()), : SyncF64::new(self..get()),
: RwLock::new(*self..read().unwrap()), : RwLock::new(*self..read()),
: RwLock::new(*self..read().unwrap()), : RwLock::new(*self..read()),
: self., : self.,
: self..clone(), : self..clone(),
: SyncF64::new(self..get()), : SyncF64::new(self..get()),
: self., : self.,
: AtomicI64::new(self..load(Ordering::Relaxed)), : AtomicI64::new(self..load(Ordering::Relaxed)),
K线: RwLock::new(Arc::clone(&self.K线.read().unwrap())), K线: RwLock::new(Arc::clone(&self.K线.read())),
: RwLock::new(self..read().unwrap().clone()), : RwLock::new(self..read().clone()),
} }
} }
/// 与MACD柱子匹配 — 底分型时MACD柱应<0, 顶分型时>0 /// 与MACD柱子匹配 — 底分型时MACD柱应<0, 顶分型时>0
pub fn MACD柱子匹配(&self) -> bool { pub fn MACD柱子匹配(&self) -> bool {
let = self.K线.read().unwrap(); let = self.K线.read();
let = ..read().unwrap(); let = ..read();
match *self..read().unwrap() { match *self..read() {
Some(::) | Some(::) => { Some(::) | Some(::) => {
if let Some(macd) = .macd() { if let Some(macd) = .macd() {
macd.MACD柱 < 0.0 macd.MACD柱 < 0.0
@@ -153,9 +153,9 @@ impl 缠论K线 {
/// 与RSI匹配 — 底分型时RSI应低于SMA, 顶分型时高于SMA /// 与RSI匹配 — 底分型时RSI应低于SMA, 顶分型时高于SMA
pub fn RSI匹配(&self) -> bool { pub fn RSI匹配(&self) -> bool {
let = self.K线.read().unwrap(); let = self.K线.read();
let = ..read().unwrap(); let = ..read();
match *self..read().unwrap() { match *self..read() {
Some(::) | Some(::) => { Some(::) | Some(::) => {
if let Some(rsi) = .rsi() { if let Some(rsi) = .rsi() {
match (rsi.RSI, rsi.RSI_SMA) { match (rsi.RSI, rsi.RSI_SMA) {
@@ -182,9 +182,9 @@ impl 缠论K线 {
/// 与KDJ匹配 — 底分型时K应低于D(死叉后), 顶分型时K应高于D(金叉后) /// 与KDJ匹配 — 底分型时K应低于D(死叉后), 顶分型时K应高于D(金叉后)
pub fn KDJ匹配(&self) -> bool { pub fn KDJ匹配(&self) -> bool {
let = self.K线.read().unwrap(); let = self.K线.read();
let = ..read().unwrap(); let = ..read();
match *self..read().unwrap() { match *self..read() {
Some(::) | Some(::) => { Some(::) | Some(::) => {
if let Some(kdj) = .kdj() { if let Some(kdj) = .kdj() {
match (kdj.K, kdj.D) { match (kdj.K, kdj.D) {
@@ -356,12 +356,12 @@ impl 缠论K线 {
// 逆序包含时更新时间和标的K线 // 逆序包含时更新时间和标的K线
if != :: { if != :: {
K..store(K., Ordering::Relaxed); K..store(K., Ordering::Relaxed);
*K.K线.write().unwrap() = Arc::clone(K); *K.K线.write() = Arc::clone(K);
} }
K..set((K..get(), K.)); K..set((K..get(), K.));
K..set((K..get(), K.)); K..set((K..get(), K.));
K..store(K., Ordering::Relaxed); K..store(K., Ordering::Relaxed);
*K..write().unwrap() = K.(); *K..write() = K.();
if let Some() = K { if let Some() = K {
K K
@@ -466,29 +466,29 @@ impl 缠论K线 {
let = ::(&*, &*, &*, false, false); let = ::(&*, &*, &*, false, false);
// 对齐 Python:无条件设置 中.分型、中.分型特征值、右.分型特征值、右.分型 // 对齐 Python:无条件设置 中.分型、中.分型特征值、右.分型特征值、右.分型
*K序列[idx - 2]..write().unwrap() = ; *K序列[idx - 2]..write() = ;
if let Some() = { if let Some() = {
match { match {
:: => { :: => {
K序列[idx - 2]..set(K序列[idx - 2]..get()); K序列[idx - 2]..set(K序列[idx - 2]..get());
K序列[idx - 1]..set(K序列[idx - 1]..get()); K序列[idx - 1]..set(K序列[idx - 1]..get());
*K序列[idx - 1]..write().unwrap() = Some(::); *K序列[idx - 1]..write() = Some(::);
} }
:: => { :: => {
K序列[idx - 2]..set(K序列[idx - 2]..get()); K序列[idx - 2]..set(K序列[idx - 2]..get());
K序列[idx - 1]..set(K序列[idx - 1]..get()); K序列[idx - 1]..set(K序列[idx - 1]..get());
*K序列[idx - 1]..write().unwrap() = Some(::); *K序列[idx - 1]..write() = Some(::);
} }
:: => { :: => {
K序列[idx - 2]..set(K序列[idx - 2]..get()); K序列[idx - 2]..set(K序列[idx - 2]..get());
K序列[idx - 1]..set(K序列[idx - 1]..get()); K序列[idx - 1]..set(K序列[idx - 1]..get());
*K序列[idx - 1]..write().unwrap() = Some(::); *K序列[idx - 1]..write() = Some(::);
} }
:: => { :: => {
K序列[idx - 2]..set(K序列[idx - 2]..get()); K序列[idx - 2]..set(K序列[idx - 2]..get());
K序列[idx - 1]..set(K序列[idx - 1]..get()); K序列[idx - 1]..set(K序列[idx - 1]..get());
*K序列[idx - 1]..write().unwrap() = Some(::); *K序列[idx - 1]..write() = Some(::);
} }
:: => {} :: => {}
} }
@@ -573,23 +573,23 @@ impl 缠论K线 {
), ),
); );
} }
if *self..read().unwrap() != *other..read().unwrap() { if *self..read() != *other..read() {
return ( return (
false, false,
format!( format!(
"缠论K线: [方向] 不等 A={},B={}", "缠论K线: [方向] 不等 A={},B={}",
self..read().unwrap(), self..read(),
other..read().unwrap() other..read()
), ),
); );
} }
if *self..read().unwrap() != *other..read().unwrap() { if *self..read() != *other..read() {
return ( return (
false, false,
format!( format!(
"缠论K线: [分型] 不等 A={:?},B={:?}", "缠论K线: [分型] 不等 A={:?},B={:?}",
self..read().unwrap(), self..read(),
other..read().unwrap() other..read()
), ),
); );
} }
@@ -636,17 +636,13 @@ impl 缠论K线 {
); );
} }
// 标的K线 递归 // 标的K线 递归
let (eq, msg) = self let (eq, msg) = self.K线.read().(&other.K线.read(), );
.K线
.read()
.unwrap()
.(&other.K线.read().unwrap(), );
if !eq { if !eq {
return (false, format!("缠论K线: 标的K线子项异常 >> {msg}")); return (false, format!("缠论K线: 标的K线子项异常 >> {msg}"));
} }
// 买卖点信息 // 买卖点信息
let a_guard = self..read().unwrap(); let a_guard = self..read();
let b_guard = other..read().unwrap(); let b_guard = other..read();
let a_set: std::collections::HashSet<&String> = a_guard.iter().collect(); let a_set: std::collections::HashSet<&String> = a_guard.iter().collect();
let b_set: std::collections::HashSet<&String> = b_guard.iter().collect(); let b_set: std::collections::HashSet<&String> = b_guard.iter().collect();
if a_set != b_set { if a_set != b_set {
@@ -654,8 +650,8 @@ impl 缠论K线 {
false, false,
format!( format!(
"缠论K线: [买卖点信息] 集合不等 A={:?},B={:?}", "缠论K线: [买卖点信息] 集合不等 A={:?},B={:?}",
self..read().unwrap(), self..read(),
other..read().unwrap() other..read()
), ),
); );
} }
+1
View File
@@ -30,6 +30,7 @@ pub mod business;
pub mod config; pub mod config;
pub mod indicators; pub mod indicators;
pub mod kline; pub mod kline;
pub mod log;
pub mod structure; pub mod structure;
pub mod types; pub mod types;
pub mod utils; pub mod utils;
+68
View File
@@ -0,0 +1,68 @@
use std::sync::atomic::{AtomicBool, AtomicU8, Ordering};
/// 日志模式: 0=Off, 1=Simple (eprintln), 2=Tracing (tracing subscriber)
pub static LOG_MODE: AtomicU8 = AtomicU8::new(0);
/// 向后兼容:set_log_level 设置此标志
pub static : AtomicBool = AtomicBool::new(false);
pub fn init_from_env() {
if let Ok(val) = std::env::var("CHANLUN_LOG_MODE") {
match val.to_lowercase().as_str() {
"simple" | "on" | "debug" | "1" => {
LOG_MODE.store(1, Ordering::Relaxed);
.store(true, Ordering::Relaxed);
}
"tracing" | "2" => {
LOG_MODE.store(2, Ordering::Relaxed);
.store(true, Ordering::Relaxed);
}
_ => {}
}
}
}
pub fn set_log_mode(mode: u8) {
LOG_MODE.store(mode.min(2), Ordering::Relaxed);
.store(mode > 0, Ordering::Relaxed);
}
pub fn get_log_mode() -> u8 {
LOG_MODE.load(Ordering::Relaxed)
}
#[macro_export]
macro_rules! warn {
($($arg:tt)*) => {
if $crate::log::.load(std::sync::atomic::Ordering::Relaxed) {
match $crate::log::LOG_MODE.load(std::sync::atomic::Ordering::Relaxed) {
2 => tracing::warn!($($arg)*),
_ => eprintln!($($arg)*),
}
}
};
}
#[macro_export]
macro_rules! error {
($($arg:tt)*) => {
if $crate::log::.load(std::sync::atomic::Ordering::Relaxed) {
match $crate::log::LOG_MODE.load(std::sync::atomic::Ordering::Relaxed) {
2 => tracing::error!($($arg)*),
_ => eprintln!($($arg)*),
}
}
};
}
#[macro_export]
macro_rules! info {
($($arg:tt)*) => {
if $crate::log::.load(std::sync::atomic::Ordering::Relaxed) {
match $crate::log::LOG_MODE.load(std::sync::atomic::Ordering::Relaxed) {
2 => tracing::info!($($arg)*),
_ => println!($($arg)*),
}
}
};
}
+2 -3
View File
@@ -80,10 +80,9 @@ fn 测试_读取数据(文件路径: &str) {
let = ::new("".into(), 0, ::default()); let = ::new("".into(), 0, ::default());
.write() .write()
.unwrap()
.(, ) .(, )
.expect("读取数据文件失败"); .expect("读取数据文件失败");
let = .read().unwrap(); let = .read();
let = .elapsed(); let = .elapsed();
println!( println!(
"测试_读取数据 耗时 {:.2?} 普K数量 {}", "测试_读取数据 耗时 {:.2?} 普K数量 {}",
@@ -160,7 +159,7 @@ fn 测试_周期合成(文件路径: &str) {
// Display stats per period // Display stats per period
for &p in &[, * 5, * 5 * 6] { for &p in &[, * 5, * 5 * 6] {
if let Some() = .(p) { if let Some() = .(p) {
let = .read().unwrap(); let = .read();
println!( println!(
"周期<{}>: 缠K={}, 分型={}, 笔={}, 线段={}, 中枢={}", "周期<{}>: 缠K={}, 分型={}, 笔={}, 线段={}, 中枢={}",
p, p,
File diff suppressed because it is too large Load Diff
+21 -25
View File
@@ -25,10 +25,10 @@
use crate::kline::chan_kline::K线; use crate::kline::chan_kline::K线;
use crate::types::; use crate::types::;
use crate::types::; use crate::types::;
use crate::warn;
use std::sync::Arc; use std::sync::Arc;
use std::sync::atomic::AtomicBool; use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
use tracing::warn;
/// 分型模式 — True 时使用构造时缓存值(默认),False 时从 中 缠K 实时读取 /// 分型模式 — True 时使用构造时缓存值(默认),False 时从 中 缠K 实时读取
pub static : AtomicBool = AtomicBool::new(true); pub static : AtomicBool = AtomicBool::new(true);
@@ -75,7 +75,7 @@ impl 分型 {
..load(Ordering::Relaxed), ..load(Ordering::Relaxed),
); );
} }
let = ..read().unwrap().unwrap_or(::); let = ..read().unwrap_or(::);
let = ..load(Ordering::Relaxed); let = ..load(Ordering::Relaxed);
let = ..get(); let = ..get();
Self { Self {
@@ -102,7 +102,7 @@ impl 分型 {
if .load(Ordering::Relaxed) { if .load(Ordering::Relaxed) {
self. self.
} else { } else {
self...read().unwrap().unwrap_or(::) // FIXME 错误 self...read().unwrap_or(::) // FIXME 错误
} }
} }
@@ -157,19 +157,17 @@ impl 分型 {
if let (Some(), Some()) = (&self., &self.) { if let (Some(), Some()) = (&self., &self.) {
if self.() == :: { if self.() == :: {
if .K线.read().unwrap(). > .K线.read().unwrap(). { if .K线.read(). > .K线.read(). {
return ""; return "";
} else if .K线.read().unwrap(). > self..K线.read().unwrap(). } else if .K线.read(). > self..K线.read(). {
{
return ""; return "";
} else { } else {
return ""; return "";
} }
} else if self.() == :: { } else if self.() == :: {
if .K线.read().unwrap(). < .K线.read().unwrap(). { if .K线.read(). < .K线.read(). {
return ""; return "";
} else if .K线.read().unwrap(). < self..K线.read().unwrap(). } else if .K线.read(). < self..K线.read(). {
{
return ""; return "";
} else { } else {
return ""; return "";
@@ -183,12 +181,12 @@ impl 分型 {
pub fn MACD柱子分型匹配(&self) -> bool { pub fn MACD柱子分型匹配(&self) -> bool {
if let (Some(), Some()) = (&self., &self.) { if let (Some(), Some()) = (&self., &self.) {
if self.() == :: { if self.() == :: {
let _k = .K线.read().unwrap(); let _k = .K线.read();
let _k = self..K线.read().unwrap(); let _k = self..K线.read();
let _k = .K线.read().unwrap(); let _k = .K线.read();
let _m = _k..read().unwrap(); let _m = _k..read();
let _m = _k..read().unwrap(); let _m = _k..read();
let _m = _k..read().unwrap(); let _m = _k..read();
if let (Some(macd), Some(macd), Some(macd)) = if let (Some(macd), Some(macd), Some(macd)) =
(_m.macd(), _m.macd(), _m.macd()) (_m.macd(), _m.macd(), _m.macd())
{ {
@@ -196,12 +194,12 @@ impl 分型 {
} }
} }
if self.() == :: { if self.() == :: {
let _k = .K线.read().unwrap(); let _k = .K线.read();
let _k = self..K线.read().unwrap(); let _k = self..K线.read();
let _k = .K线.read().unwrap(); let _k = .K线.read();
let _m = _k..read().unwrap(); let _m = _k..read();
let _m = _k..read().unwrap(); let _m = _k..read();
let _m = _k..read().unwrap(); let _m = _k..read();
if let (Some(macd), Some(macd), Some(macd)) = if let (Some(macd), Some(macd), Some(macd)) =
(_m.macd(), _m.macd(), _m.macd()) (_m.macd(), _m.macd(), _m.macd())
{ {
@@ -214,7 +212,7 @@ impl 分型 {
/// 判断两个分型是否匹配 /// 判断两个分型是否匹配
pub fn (: &Arc<>, : &Arc<>, _模式: &str) -> bool { pub fn (: &Arc<>, : &Arc<>, _模式: &str) -> bool {
Arc::as_ptr() == Arc::as_ptr() Arc::ptr_eq(, )
} }
/// 从缠K序列中获取以指定缠K为中元素的分型 /// 从缠K序列中获取以指定缠K为中元素的分型
@@ -222,9 +220,7 @@ impl 分型 {
K线序列: &[Arc<K线>], K线序列: &[Arc<K线>],
: &Arc<K线>, : &Arc<K线>,
) -> Option<Self> { ) -> Option<Self> {
let idx = K线序列 let idx = K线序列.iter().position(|k| Arc::ptr_eq(k, ))?;
.iter()
.position(|k| Arc::as_ptr(k) == Arc::as_ptr())?;
let = if idx > 0 { let = if idx > 0 {
Some(Arc::clone(&K线序列[idx - 1])) Some(Arc::clone(&K线序列[idx - 1]))
} else { } else {
+35 -50
View File
@@ -26,9 +26,10 @@ use crate::structure::dash_line::虚线;
use crate::structure::feat_fractal::; use crate::structure::feat_fractal::;
use crate::structure::fractal_obj::; use crate::structure::fractal_obj::;
use crate::types::{, }; use crate::types::{, };
use parking_lot::RwLock;
use std::sync::Arc;
use std::sync::atomic::AtomicI64; use std::sync::atomic::AtomicI64;
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
use std::sync::{Arc, RwLock};
/// 线段特征 — 特征序列元素,内部是虚线的集合。 /// 线段特征 — 特征序列元素,内部是虚线的集合。
/// ///
@@ -62,7 +63,7 @@ impl Clone for 线段特征 {
fn clone(&self) -> Self { fn clone(&self) -> Self {
Self { Self {
: AtomicI64::new(self..load(Ordering::Relaxed)), : AtomicI64::new(self..load(Ordering::Relaxed)),
: RwLock::new(self..read().unwrap().clone()), : RwLock::new(self..read().clone()),
线: self.线, 线: self.线,
: self..clone(), : self..clone(),
} }
@@ -82,7 +83,7 @@ impl 线段特征 {
/// 图表标题 — 返回标识字符串 /// 图表标题 — 返回标识字符串
pub fn (&self) -> String { pub fn (&self) -> String {
self..read().unwrap().clone() self..read().clone()
} }
/// 文 — 取特征序列元素中分型特征值最大/最小的文分型 /// 文 — 取特征序列元素中分型特征值最大/最小的文分型
@@ -118,47 +119,31 @@ impl 线段特征 {
/// 武 — 取特征序列元素中分型特征值最大/最小的武分型 /// 武 — 取特征序列元素中分型特征值最大/最小的武分型
/// tiebreaker: later时间戳 wins when特征值 equal (matches Python) /// tiebreaker: later时间戳 wins when特征值 equal (matches Python)
pub fn (&self) -> Arc<> { pub fn (&self) -> Arc<> {
if self.线.() { let best = if self.线.() {
self. self..iter().max_by(|a, b| {
.iter() let a_武 = a..read();
.max_by(|a, b| { let b_武 = b..read();
a. a_武
.read() .
.unwrap() .partial_cmp(&b_武.)
. .unwrap_or(std::cmp::Ordering::Equal)
.partial_cmp(&b..read().unwrap().) .then_with(|| a_武.().cmp(&b_武.()))
.unwrap_or(std::cmp::Ordering::Equal) })
.then_with(|| {
a.
.read()
.unwrap()
.()
.cmp(&b..read().unwrap().())
})
})
.map(|x| x..read().unwrap().clone())
.unwrap_or_else(|| self.[0]..read().unwrap().clone())
} else { } else {
self. self..iter().max_by(|a, b| {
.iter() let a_武 = a..read();
.max_by(|a, b| { let b_武 = b..read();
b. b_武
.read() .
.unwrap() .partial_cmp(&a_武.)
. .unwrap_or(std::cmp::Ordering::Equal)
.partial_cmp(&a..read().unwrap().) .then_with(|| a_武.().cmp(&b_武.()))
.unwrap_or(std::cmp::Ordering::Equal) })
.then_with(|| { };
a. best.map_or_else(
.read() || self.[0]..read().clone(),
.unwrap() |x| x..read().clone(),
.() )
.cmp(&b..read().unwrap().())
})
})
.map(|x| x..read().unwrap().clone())
.unwrap_or_else(|| self.[0]..read().unwrap().clone())
}
} }
/// 高 — 文和武中分型特征值的较大者 /// 高 — 文和武中分型特征值的较大者
@@ -197,7 +182,7 @@ impl 线段特征 {
if let Some(pos) = self if let Some(pos) = self
. .
.iter() .iter()
.position(|x| Arc::as_ptr(x) == Arc::as_ptr(线)) .position(|x| Arc::ptr_eq(x, 线))
{ {
self..remove(pos); self..remove(pos);
Ok(()) Ok(())
@@ -256,7 +241,7 @@ impl 线段特征 {
.unwrap(); .unwrap();
let fake = 线::( let fake = 线::(
Arc::clone(&线.), Arc::clone(&线.),
线..read().unwrap().clone(), 线..read().clone(),
false, false,
); );
.pop(); .pop();
@@ -330,13 +315,13 @@ impl 线段特征 {
), ),
); );
} }
if *self..read().unwrap() != *other..read().unwrap() { if *self..read() != *other..read() {
return ( return (
false, false,
format!( format!(
"线段特征: [标识] 不等 A={},B={}", "线段特征: [标识] 不等 A={},B={}",
self..read().unwrap(), self..read(),
other..read().unwrap() other..read()
), ),
); );
} }
@@ -381,12 +366,12 @@ impl crate::types::fractal::有高低 for 线段特征 {
impl std::fmt::Display for 线 { impl std::fmt::Display for 线 {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self..is_empty() { if self..is_empty() {
write!(f, "{}<{}, 空>", self..read().unwrap(), self.线) write!(f, "{}<{}, 空>", self..read(), self.线)
} else { } else {
write!( write!(
f, f,
"{}<{}, {}, {}, {}>", "{}<{}, {}, {}, {}>",
self..read().unwrap(), self..read(),
self.线, self.线,
self.(), self.(),
self.(), self.(),
+28
View File
@@ -122,6 +122,34 @@ impl 相对方向 {
, , , , , ,
); );
} }
/// 从可选方向序列中随机选取指定数量(与 chan.py 对齐)
pub fn (
: usize,
: &[],
: bool,
) -> Vec<> {
if == 0 || .is_empty() {
return Vec::new();
}
if ! && > .len() {
panic!("数量超过可选方向数");
}
let mut result = Vec::with_capacity();
if {
for _ in 0.. {
let idx = fastrand::usize(...len());
result.push([idx]);
}
} else {
let mut indices: Vec<usize> = (0...len()).collect();
fastrand::shuffle(&mut indices);
for &idx in indices.iter().take() {
result.push([idx]);
}
}
result
}
} }
impl std::fmt::Display for { impl std::fmt::Display for {
+1 -1
View File
@@ -22,8 +22,8 @@
* SOFTWARE. * SOFTWARE.
*/ */
use crate::warn;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use tracing::warn;
/// 分型结构 —— 三根K线构成的结构形态 /// 分型结构 —— 三根K线构成的结构形态
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
+27
View File
@@ -0,0 +1,27 @@
#!/usr/bin/env bash
set -e
cd "$(dirname "$0")"
echo "=== 1/4 清除 Python 缓存 ==="
find . -type d -name __pycache__ -exec rm -rf {} + 2>/dev/null
find . -type f -name "*.pyc" -delete 2>/dev/null
echo " Python 缓存已清除"
echo "=== 2/4 清除 Cargo 编译缓存 ==="
rm -rf chanlun/target chanlun-py/target
echo " target/ 已清除"
echo "=== 3/4 构建 Release ==="
cd chanlun-py
maturin build --release
echo " 构建完成"
echo "=== 4/4 安装 ==="
pip install --break-system-packages --force-reinstall --no-deps \
target/wheels/chanlun-*.whl
echo " 安装完成"
echo
echo "✓ 清理 + 构建 + 安装完毕"
echo " pip show chanlun | grep Version"
pip show chanlun 2>/dev/null | grep Version