claude实现缠论(买卖点)

缠论(Chanlun) 完整实现 --- 从分型到买卖点 + 多级别联立

参考的最佳 GitHub 开源库

仓库 Stars 特点
czsc (zengbin93) ~5k+ 最全面、工业级、持续维护
chan.py (Vespa314) ~1k+ 代码清晰、学术严谨
chanlun-pro --- 带 Web UI、专业级

以下代码综合了上述库的核心算法思路,重新整理为单文件、即插即用的实现。


完整源码:chanlun.py

python 复制代码
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
chanlun.py --- 缠论核心引擎 (完整实现)
=====================================
层次:  K线合并 → 分型 → 笔 → 线段 → 中枢 → 买卖点
参考:  czsc / chan.py / chanlun-pro

用法:
    from chanlun import ChanLun, MultiTimeframeChanLun

    analyzer = ChanLun(df)           # df: 含 OHLC 的 DataFrame
    signals  = analyzer.get_signals()

    mtf = MultiTimeframeChanLun({'5min': df5, '1min': df1})
    combined = mtf.get_combined_signals(higher='5min', lower='1min')
"""

from __future__ import annotations

import pandas as pd
import numpy as np
from dataclasses import dataclass, field
from enum import Enum, IntEnum
from typing import List, Optional, Dict

# ================================================================
#  1. 枚举 & 数据结构
# ================================================================

class Direction(IntEnum):
    UP   =  1
    DOWN = -1

class FxType(IntEnum):
    TOP    =  1   # 顶分型
    BOTTOM = -1   # 底分型

class Mark(Enum):
    B1 = 'B1'   # 第一类买点 --- 趋势背驰 / 跌破中枢后反转
    B2 = 'B2'   # 第二类买点 --- 回调不破前低 / 不破中枢下沿
    B3 = 'B3'   # 第三类买点 --- 回踩不破中枢上沿(强势)
    S1 = 'S1'   # 第一类卖点
    S2 = 'S2'   # 第二类卖点
    S3 = 'S3'   # 第三类卖点


@dataclass
class RawBar:
    i: int;  dt: str
    o: float; h: float; l: float; c: float; v: float = 0.0


@dataclass
class NewBar:
    """合并后K线"""
    i: int;  dt: str
    o: float; h: float; l: float; c: float
    raw_start: int; raw_end: int
    direction: Optional[Direction] = None
    elements: int = 1               # 包含了多少根原始K线


@dataclass
class Fractal:
    """分型"""
    fx_type: FxType
    idx: int          # 分型序号
    ki: int           # 中间K线在 merged 列表中的下标
    dt: str
    val: float        # 顶分型=high, 底分型=low
    high: float       # 三根K线区间最高
    low:  float       # 三根K线区间最低


@dataclass
class Bi:
    """笔"""
    idx: int
    direction: Direction          # UP = 底→顶, DOWN = 顶→底
    fx_a: Fractal                 # 起始分型
    fx_b: Fractal                 # 结束分型

    @property
    def high(self):  return max(self.fx_a.high, self.fx_b.high)
    @property
    def low(self):   return min(self.fx_a.low, self.fx_b.low)
    @property
    def sdt(self):   return self.fx_a.dt
    @property
    def edt(self):   return self.fx_b.dt
    @property
    def power(self): return abs(self.fx_b.val - self.fx_a.val)


@dataclass
class Xd:
    """线段"""
    idx: int
    direction: Direction
    bis: List[Bi] = field(default_factory=list)

    @property
    def high(self): return max(b.high for b in self.bis) if self.bis else 0
    @property
    def low(self):  return min(b.low  for b in self.bis) if self.bis else 0
    @property
    def sdt(self):  return self.bis[0].sdt  if self.bis else ''
    @property
    def edt(self):  return self.bis[-1].edt if self.bis else ''


@dataclass
class ZhongShu:
    """中枢"""
    idx: int
    ZG: float          # 中枢上沿 min(各笔high)
    ZD: float          # 中枢下沿 max(各笔low)
    GG: float          # 区间最高
    DD: float          # 区间最低
    bis: List[Bi] = field(default_factory=list)

    @property
    def valid(self): return self.ZD < self.ZG
    @property
    def sdt(self): return self.bis[0].sdt  if self.bis else ''
    @property
    def edt(self): return self.bis[-1].edt if self.bis else ''


@dataclass
class Signal:
    """买卖点"""
    mark: Mark
    dt: str
    price: float
    bi_idx: int
    zs_ZG: float = 0
    zs_ZD: float = 0

    @property
    def is_buy(self):  return self.mark in (Mark.B1, Mark.B2, Mark.B3)
    @property
    def name_cn(self):
        _m = {Mark.B1:'第一类买点', Mark.B2:'第二类买点', Mark.B3:'第三类买点',
              Mark.S1:'第一类卖点', Mark.S2:'第二类卖点', Mark.S3:'第三类卖点'}
        return _m[self.mark]


# ================================================================
#  2. 缠论参数
# ================================================================

@dataclass
class ChanConfig:
    bi_min_gap: int = 4          # 笔: 两分型中间K线最小间隔 (新笔=4, 旧笔=3)
    zs_min_bi: int  = 3          # 中枢: 至少几笔


# ================================================================
#  3. 核心引擎
# ================================================================

class ChanLun:
    """
    缠论分析器
    ----------
    参数:
        df      : pd.DataFrame --- 必须含 open/high/low/close 列
        config  : ChanConfig
        freq    : 周期标签 (如 '5min')
    """

    def __init__(self, df: pd.DataFrame,
                 config: ChanConfig | None = None,
                 freq: str = ''):
        self.cfg  = config or ChanConfig()
        self.freq = freq
        self.df   = self._norm(df)

        # ---------- 结果容器 ----------
        self.bars:      List[NewBar]    = []
        self.fractals:  List[Fractal]   = []
        self.bis:       List[Bi]        = []
        self.xds:       List[Xd]        = []
        self.zhong_shus:List[ZhongShu]  = []
        self.signals:   List[Signal]    = []

        self._run()

    # ---------- 列名标准化 ----------
    @staticmethod
    def _norm(df: pd.DataFrame) -> pd.DataFrame:
        df = df.copy()
        alias = {
            '日期':'datetime','时间':'datetime','date':'datetime',
            'dt':'datetime','Date':'datetime','Datetime':'datetime',
            'timestamp':'datetime','Timestamp':'datetime',
            '开盘':'open','开盘价':'open','Open':'open',
            '最高':'high','最高价':'high','High':'high',
            '最低':'low','最低价':'low','Low':'low',
            '收盘':'close','收盘价':'close','Close':'close',
            '成交量':'volume','Volume':'volume','vol':'volume',
        }
        rn = {k: v for k, v in alias.items()
              if k in df.columns and v not in df.columns}
        if rn:
            df = df.rename(columns=rn)
        for c in ('open','high','low','close'):
            if c not in df.columns:
                raise ValueError(f"缺少列: {c}")
        if 'datetime' not in df.columns:
            if df.index.name and 'date' in str(df.index.name).lower():
                df = df.reset_index()
                df.rename(columns={df.columns[0]:'datetime'}, inplace=True)
            else:
                df['datetime'] = range(len(df))
        if 'volume' not in df.columns:
            df['volume'] = 0.0
        return df.reset_index(drop=True)

    # ---------- 主流程 ----------
    def _run(self):
        raws = self._build_raw()
        self.bars      = self._merge(raws)
        self.fractals  = self._find_fx()
        self.bis       = self._find_bi()
        self.xds       = self._find_xd()
        self.zhong_shus= self._find_zs()
        self.signals   = self._find_signals()

    # ============================================================
    #  Step 0  原始K线
    # ============================================================
    def _build_raw(self) -> List[RawBar]:
        out = []
        for i, r in self.df.iterrows():
            out.append(RawBar(
                i=int(i), dt=str(r['datetime']),
                o=float(r['open']),  h=float(r['high']),
                l=float(r['low']),   c=float(r['close']),
                v=float(r.get('volume', 0))
            ))
        return out

    # ============================================================
    #  Step 1  K线合并 (处理包含关系)
    # ============================================================
    @staticmethod
    def _merge(raws: List[RawBar]) -> List[NewBar]:
        if not raws:
            return []

        def _incl(a: NewBar, b: NewBar) -> bool:
            return (a.h >= b.h and a.l <= b.l) or (b.h >= a.h and b.l <= a.l)

        res: List[NewBar] = []
        first = raws[0]
        res.append(NewBar(i=0, dt=first.dt,
                          o=first.o, h=first.h, l=first.l, c=first.c,
                          raw_start=0, raw_end=0))

        for k in range(1, len(raws)):
            r = raws[k]
            cur = NewBar(i=0, dt=r.dt,
                         o=r.o, h=r.h, l=r.l, c=r.c,
                         raw_start=r.i, raw_end=r.i)
            last = res[-1]

            if _incl(last, cur):
                # 判断方向
                if len(res) >= 2:
                    d = Direction.UP if last.h >= res[-2].h else Direction.DOWN
                else:
                    d = Direction.UP if cur.c >= cur.o else Direction.DOWN

                if d == Direction.UP:
                    nh, nl = max(last.h, cur.h), max(last.l, cur.l)
                else:
                    nh, nl = min(last.h, cur.h), min(last.l, cur.l)

                res[-1] = NewBar(
                    i=last.i, dt=cur.dt,
                    o=last.o, h=nh, l=nl, c=cur.c,
                    raw_start=last.raw_start, raw_end=cur.raw_end,
                    direction=d, elements=last.elements + 1
                )
            else:
                cur.direction = Direction.UP if cur.h > last.h else Direction.DOWN
                cur.i = len(res)
                res.append(cur)

        for idx, b in enumerate(res):
            b.i = idx
        return res

    # ============================================================
    #  Step 2  分型识别
    # ============================================================
    def _find_fx(self) -> List[Fractal]:
        bars = self.bars
        if len(bars) < 3:
            return []
        out: List[Fractal] = []
        fi = 0
        for i in range(1, len(bars) - 1):
            L, M, R = bars[i-1], bars[i], bars[i+1]
            if M.h > L.h and M.h > R.h:
                out.append(Fractal(FxType.TOP, fi, M.i, M.dt,
                                   M.h, max(L.h,M.h,R.h), min(L.l,M.l,R.l)))
                fi += 1
            elif M.l < L.l and M.l < R.l:
                out.append(Fractal(FxType.BOTTOM, fi, M.i, M.dt,
                                   M.l, max(L.h,M.h,R.h), min(L.l,M.l,R.l)))
                fi += 1
        return out

    # ============================================================
    #  Step 3  笔
    # ============================================================
    def _find_bi(self) -> List[Bi]:
        fxs = self._alternate(self.fractals)
        if len(fxs) < 2:
            return []

        bis: List[Bi] = []
        pending = fxs[0]

        for f in fxs[1:]:
            # 同类 → 保留极值
            if f.fx_type == pending.fx_type:
                if f.fx_type == FxType.TOP  and f.val > pending.val:
                    pending = f
                elif f.fx_type == FxType.BOTTOM and f.val < pending.val:
                    pending = f
                continue

            # 异类 → 检查最小间隔
            if abs(f.ki - pending.ki) < self.cfg.bi_min_gap:
                continue

            # 检查价格关系
            ok = False
            if pending.fx_type == FxType.BOTTOM and f.fx_type == FxType.TOP:
                ok = f.val > pending.val              # 向上笔: 顶 > 底
            elif pending.fx_type == FxType.TOP and f.fx_type == FxType.BOTTOM:
                ok = f.val < pending.val              # 向下笔: 底 < 顶

            if ok:
                d = Direction.UP if pending.fx_type == FxType.BOTTOM else Direction.DOWN
                bis.append(Bi(idx=len(bis), direction=d, fx_a=pending, fx_b=f))
                pending = f

        return bis

    @staticmethod
    def _alternate(fxs: List[Fractal]) -> List[Fractal]:
        """确保分型严格交替: 顶-底-顶-底 ..."""
        if not fxs:
            return []
        res = [fxs[0]]
        for f in fxs[1:]:
            if f.fx_type == res[-1].fx_type:
                if f.fx_type == FxType.TOP  and f.val >= res[-1].val:
                    res[-1] = f
                elif f.fx_type == FxType.BOTTOM and f.val <= res[-1].val:
                    res[-1] = f
            else:
                res.append(f)
        return res

    # ============================================================
    #  Step 4  线段 (简化特征序列法)
    # ============================================================
    def _find_xd(self) -> List[Xd]:
        bis = self.bis
        if len(bis) < 3:
            return []

        xds: List[Xd] = []
        start = 0
        d = bis[0].direction

        i = 2
        while i < len(bis):
            broken = False
            seg_bis = bis[start:i+1]

            if d == Direction.UP:
                ups = [b for b in seg_bis if b.direction == Direction.UP]
                dns = [b for b in seg_bis if b.direction == Direction.DOWN]
                if len(ups) >= 2 and ups[-1].high < ups[-2].high:
                    if (len(dns) >= 2 and dns[-1].low < dns[-2].low) \
                            or bis[i].low < bis[start].low:
                        broken = True
            else:
                dns = [b for b in seg_bis if b.direction == Direction.DOWN]
                ups = [b for b in seg_bis if b.direction == Direction.UP]
                if len(dns) >= 2 and dns[-1].low > dns[-2].low:
                    if (len(ups) >= 2 and ups[-1].high > ups[-2].high) \
                            or bis[i].high > bis[start].high:
                        broken = True

            if broken and (i - start) >= 2:
                chunk = bis[start:i]
                if len(chunk) >= 3:
                    xds.append(Xd(idx=len(xds), direction=d, bis=chunk))
                start = i - 1
                d = Direction.DOWN if d == Direction.UP else Direction.UP
            i += 1

        # 尾部
        rest = bis[start:]
        if len(rest) >= 3:
            xds.append(Xd(idx=len(xds), direction=d, bis=rest))
        return xds

    # ============================================================
    #  Step 5  中枢
    # ============================================================
    def _find_zs(self) -> List[ZhongShu]:
        bis = self.bis
        n = self.cfg.zs_min_bi
        if len(bis) < n:
            return []

        out: List[ZhongShu] = []
        i = 0
        while i <= len(bis) - n:
            grp = bis[i:i+n]
            zg = min(b.high for b in grp)
            zd = max(b.low  for b in grp)
            if zd >= zg:
                i += 1
                continue

            # 扩展中枢
            members = list(grp)
            gg = max(b.high for b in grp)
            dd = min(b.low  for b in grp)
            j = i + n
            while j < len(bis):
                bj = bis[j]
                if bj.high > zd and bj.low < zg:
                    members.append(bj)
                    gg = max(gg, bj.high)
                    dd = min(dd, bj.low)
                    j += 1
                else:
                    break

            out.append(ZhongShu(idx=len(out),
                                ZG=zg, ZD=zd, GG=gg, DD=dd,
                                bis=members))
            i = j if j > i + n else i + n
        return out

    # ============================================================
    #  Step 6  买卖点
    # ============================================================
    def _find_signals(self) -> List[Signal]:
        if not self.bis or not self.zhong_shus:
            return []

        all_sig: List[Signal] = []

        for zs in self.zhong_shus:
            last_bi_idx = zs.bis[-1].idx
            post = [b for b in self.bis if b.idx > last_bi_idx]
            if not post:
                continue

            # ---- 买点 ----
            self._buy(zs, post, all_sig)
            # ---- 卖点 ----
            self._sell(zs, post, all_sig)

        # 去重 & 排序
        seen = set()
        uniq = []
        for s in all_sig:
            key = (s.mark.value, s.dt, s.price)
            if key not in seen:
                seen.add(key)
                uniq.append(s)
        uniq.sort(key=lambda x: x.bi_idx)
        return uniq

    # ---- B1 / B2 / B3 ----
    @staticmethod
    def _buy(zs: ZhongShu, post: List[Bi], out: List[Signal]):
        # B1: 向下笔跌破 ZD 后反转
        for i, b in enumerate(post):
            if b.direction == Direction.DOWN and b.low < zs.ZD:
                if i + 1 < len(post) and post[i+1].direction == Direction.UP:
                    out.append(Signal(Mark.B1, b.fx_b.dt, b.low,
                                      b.idx, zs.ZG, zs.ZD))
                    # B2: 后续第一次回调不破 ZD
                    for j in range(i+2, len(post)):
                        if post[j].direction == Direction.DOWN:
                            if post[j].low >= zs.ZD:
                                out.append(Signal(Mark.B2, post[j].fx_b.dt,
                                                  post[j].low, post[j].idx,
                                                  zs.ZG, zs.ZD))
                            break
                    break
        # B3: 回踩不破 ZG (强势回踩)
        for b in post:
            if b.direction == Direction.DOWN and b.low > zs.ZG:
                out.append(Signal(Mark.B3, b.fx_b.dt, b.low,
                                  b.idx, zs.ZG, zs.ZD))
                break

    # ---- S1 / S2 / S3 ----
    @staticmethod
    def _sell(zs: ZhongShu, post: List[Bi], out: List[Signal]):
        # S1: 向上笔突破 ZG 后反转
        for i, b in enumerate(post):
            if b.direction == Direction.UP and b.high > zs.ZG:
                if i + 1 < len(post) and post[i+1].direction == Direction.DOWN:
                    out.append(Signal(Mark.S1, b.fx_b.dt, b.high,
                                      b.idx, zs.ZG, zs.ZD))
                    # S2: 后续第一次反弹不过 ZG
                    for j in range(i+2, len(post)):
                        if post[j].direction == Direction.UP:
                            if post[j].high <= zs.ZG:
                                out.append(Signal(Mark.S2, post[j].fx_b.dt,
                                                  post[j].high, post[j].idx,
                                                  zs.ZG, zs.ZD))
                            break
                    break
        # S3: 反弹不过 ZD (弱势反弹)
        for b in post:
            if b.direction == Direction.UP and b.high < zs.ZD:
                out.append(Signal(Mark.S3, b.fx_b.dt, b.high,
                                  b.idx, zs.ZG, zs.ZD))
                break

    # ============================================================
    #  输出接口
    # ============================================================
    def get_signals(self) -> pd.DataFrame:
        """→ DataFrame[datetime, signal, name, price, zs_ZG, zs_ZD, is_buy, freq]"""
        if not self.signals:
            return pd.DataFrame(columns=[
                'datetime','signal','name','price',
                'zs_ZG','zs_ZD','is_buy','freq'])
        rows = [{
            'datetime': s.dt, 'signal': s.mark.value,
            'name': s.name_cn, 'price': s.price,
            'zs_ZG': s.zs_ZG, 'zs_ZD': s.zs_ZD,
            'is_buy': s.is_buy, 'freq': self.freq
        } for s in self.signals]
        return pd.DataFrame(rows)

    def get_fractals_df(self) -> pd.DataFrame:
        return pd.DataFrame([
            {'datetime':f.dt, 'type':'顶' if f.fx_type==FxType.TOP else '底',
             'value':f.val, 'ki':f.ki} for f in self.fractals
        ]) if self.fractals else pd.DataFrame()

    def get_bi_df(self) -> pd.DataFrame:
        return pd.DataFrame([
            {'idx':b.idx, 'dir':'↑' if b.direction==Direction.UP else '↓',
             'sdt':b.sdt, 'edt':b.edt, 'high':b.high, 'low':b.low,
             'power':b.power} for b in self.bis
        ]) if self.bis else pd.DataFrame()

    def get_zs_df(self) -> pd.DataFrame:
        return pd.DataFrame([
            {'idx':z.idx, 'ZG':z.ZG, 'ZD':z.ZD, 'GG':z.GG, 'DD':z.DD,
             'sdt':z.sdt, 'edt':z.edt, 'n_bi':len(z.bis)}
            for z in self.zhong_shus
        ]) if self.zhong_shus else pd.DataFrame()

    def trend(self) -> str:
        """当前趋势: uptrend / downtrend / consolidation"""
        if not self.zhong_shus or not self.bis:
            return 'unknown'
        zs = self.zhong_shus[-1]
        price = self.bis[-1].fx_b.val
        if price > zs.ZG: return 'uptrend'
        if price < zs.ZD: return 'downtrend'
        return 'consolidation'

    def summary(self) -> str:
        tag = f' ({self.freq})' if self.freq else ''
        lines = [
            f"══════ 缠论分析{tag} ══════",
            f"原始K线: {len(self.df)}  合并K线: {len(self.bars)}",
            f"分型: {len(self.fractals)}  "
            f"(顶{sum(1 for f in self.fractals if f.fx_type==FxType.TOP)} / "
            f"底{sum(1 for f in self.fractals if f.fx_type==FxType.BOTTOM)})",
            f"笔: {len(self.bis)}   线段: {len(self.xds)}   "
            f"中枢: {len(self.zhong_shus)}",
            f"买卖点: {len(self.signals)}   当前趋势: {self.trend()}",
        ]
        if self.signals:
            lines.append("------ 最近信号 ------")
            for s in self.signals[-5:]:
                lines.append(f"  {s.dt}  {s.mark.value} {s.name_cn}  "
                             f"价格={s.price:.4f}")
        return '\n'.join(lines)


# ================================================================
#  4. 多级别联立
# ================================================================

class MultiTimeframeChanLun:
    """
    多级别缠论联立
    ──────────────
    高级别定方向, 低级别找入场.

        mtf = MultiTimeframeChanLun({'5min': df5, '1min': df1})
        sig = mtf.get_combined_signals('5min', '1min')
    """

    def __init__(self, dfs: Dict[str, pd.DataFrame],
                 config: ChanConfig | None = None):
        self.analyzers: Dict[str, ChanLun] = {
            tf: ChanLun(df, config=config, freq=tf)
            for tf, df in dfs.items()
        }

    def get_combined_signals(self,
                             higher: str,
                             lower: str) -> pd.DataFrame:
        """
        联立规则
        --------
        1. 高级别 uptrend   + 低级别 Buy  → STRONG_BUY
        2. 高级别 downtrend + 低级别 Sell → STRONG_SELL
        3. 高级别 consolidation             → NORMAL
        4. 方向相反                         → WATCH (逆势)
        """
        h = self.analyzers[higher]
        l = self.analyzers[lower]
        h_trend  = h.trend()
        h_sig_df = h.get_signals()
        l_sig_df = l.get_signals()

        if l_sig_df.empty:
            return pd.DataFrame()

        df = l_sig_df.copy()
        df['higher_tf']    = higher
        df['higher_trend'] = h_trend

        # --- 信号强度 ---
        def _strength(row):
            buy = row['is_buy']
            t   = row['higher_trend']
            if buy and t == 'uptrend':       return 'strong'
            if not buy and t == 'downtrend': return 'strong'
            if t == 'consolidation':         return 'medium'
            return 'weak_counter'
        df['strength'] = df.apply(_strength, axis=1)

        # --- 优先级 / 综合评分 ---
        prio = {'B1':5,'S1':5,'B2':4,'S2':4,'B3':3,'S3':3}
        smap = {'strong':3,'medium':2,'weak_counter':1}
        df['priority'] = df['signal'].map(prio).fillna(1)
        df['score']    = df['strength'].map(smap) * df['priority']

        # --- 高级别最新信号 ---
        if not h_sig_df.empty:
            last = h_sig_df.iloc[-1]
            df['higher_last_signal']       = last['signal']
            df['higher_last_signal_price'] = last['price']
        else:
            df['higher_last_signal']       = ''
            df['higher_last_signal_price'] = np.nan

        # --- 决策建议 ---
        def _decide(row):
            act = 'BUY' if row['is_buy'] else 'SELL'
            if row['score'] >= 12: return f'STRONG_{act}'
            if row['strength'] == 'weak_counter': return 'WATCH'
            return f'NORMAL_{act}'
        df['decision'] = df.apply(_decide, axis=1)

        return df.sort_values('datetime').reset_index(drop=True)

    def summary(self):
        parts = ["══════ 多级别缠论联立 ══════\n"]
        for tf, a in self.analyzers.items():
            parts.append(a.summary()); parts.append('')
        return '\n'.join(parts)


# ================================================================
#  5. 快速测试 / Demo
# ================================================================

def demo():
    """用合成数据快速验证"""
    np.random.seed(42)
    n = 300
    px = [100.0]
    for i in range(1, n):
        drift = {i < 60: 0.12, 60 <= i < 130: -0.10,
                 130 <= i < 220: 0.14}.get(True, -0.06)
        px.append(px[-1] + drift + np.random.randn() * 0.5)

    df = pd.DataFrame({
        'datetime': pd.date_range('2024-01-02 09:30', periods=n, freq='5min'),
        'open':  px,
        'high':  [p + abs(np.random.randn()*0.3) for p in px],
        'low':   [p - abs(np.random.randn()*0.3) for p in px],
        'close': [p + np.random.randn()*0.1 for p in px],
        'volume': np.random.randint(100, 8000, n),
    })

    print("=" * 50)
    print("  单级别 Demo (5min)")
    print("=" * 50)
    c = ChanLun(df, freq='5min')
    print(c.summary())
    print("\n分型 (前10):")
    print(c.get_fractals_df().head(10))
    print("\n笔:")
    print(c.get_bi_df())
    print("\n中枢:")
    print(c.get_zs_df())
    print("\n买卖点:")
    print(c.get_signals())

    # ---------- 模拟多级别 ----------
    df1 = df.copy()                          # 假设同数据 (实际应为1min)
    print("\n" + "=" * 50)
    print("  多级别 Demo (5min + 1min)")
    print("=" * 50)
    mtf = MultiTimeframeChanLun({'5min': df, '1min': df1})
    print(mtf.summary())
    combined = mtf.get_combined_signals('5min', '1min')
    print("\n联立信号:")
    print(combined[['datetime','signal','name','price',
                    'higher_trend','strength','score','decision']])

    return c, mtf


if __name__ == '__main__':
    demo()

使用示例

1. 单级别 --- 输入 CSV 直接拿买卖点

python 复制代码
import pandas as pd
from chanlun import ChanLun

# -------- 读入任意 OHLCV 数据 --------
df = pd.read_csv('stock_5min.csv')   # 须含 open/high/low/close
# 亦可传入中文列名: 开盘/最高/最低/收盘

analyzer = ChanLun(df, freq='5min')
print(analyzer.summary())

# -------- 各层级结果 --------
print(analyzer.get_fractals_df())     # 分型
print(analyzer.get_bi_df())           # 笔
print(analyzer.get_zs_df())           # 中枢
signals = analyzer.get_signals()      # ★ 买卖点
print(signals)

#        datetime signal   name     price    zs_ZG   zs_ZD  is_buy  freq
# 0  2024-01-03    B1   第一类买点   98.12   102.3    99.1   True   5min
# 1  2024-01-04    B2   第二类买点   99.45   102.3    99.1   True   5min
# 2  2024-01-05    S1   第一类卖点  107.88   102.3    99.1   False  5min

2. 多级别联立 --- 5 分钟定方向 + 1 分钟找入场

python 复制代码
from chanlun import MultiTimeframeChanLun, ChanConfig

df_5m = pd.read_csv('stock_5min.csv')
df_1m = pd.read_csv('stock_1min.csv')

# 可自定义参数
cfg = ChanConfig(bi_min_gap=4, zs_min_bi=3)

mtf = MultiTimeframeChanLun(
    dfs={'5min': df_5m, '1min': df_1m},
    config=cfg
)
print(mtf.summary())

# ★ 联立信号
combined = mtf.get_combined_signals(higher='5min', lower='1min')
print(combined)

# -------- 只看强信号 --------
strong = combined[combined['decision'].str.startswith('STRONG')]
print(strong[['datetime','signal','name','price',
              'higher_trend','strength','score','decision']])

3. 实时 / 回测框架中嵌入

python 复制代码
def on_bar(new_df: pd.DataFrame):
    """每根K线结束时调用"""
    c = ChanLun(new_df, freq='1min')

    for sig in c.signals:
        if sig.is_buy and sig.mark.value == 'B1':
            print(f"[{sig.dt}] ★ 第一类买点 @ {sig.price:.2f}")
            # → 下单逻辑
        elif not sig.is_buy and sig.mark.value == 'S1':
            print(f"[{sig.dt}] ★ 第一类卖点 @ {sig.price:.2f}")
            # → 平仓逻辑

买卖点含义速查

信号 中文 触发条件 强度
B1 第一类买点 下跌笔突破中枢下沿 (ZD) 后反转向上 ★★★★★ 趋势反转
B2 第二类买点 B1 后第一次回调不破 ZD ★★★★ 确认反转
B3 第三类买点 回踩低点仍在中枢上沿 (ZG) 之上 ★★★ 强势延续
S1 第一类卖点 上涨笔突破中枢上沿 (ZG) 后反转向下 ★★★★★ 趋势反转
S2 第二类卖点 S1 后第一次反弹不过 ZG ★★★★ 确认反转
S3 第三类卖点 反弹高点仍在中枢下沿 (ZD) 之下 ★★★ 弱势延续

架构流水线

复制代码
CSV / DataFrame
       │
       ▼
┌─────────────┐    ┌──────────┐    ┌──────────┐
│  K线合并     │───▶│  分型识别  │───▶│  笔构建   │
│ (包含关系)   │    │ 顶/底分型  │    │ 新笔标准  │
└─────────────┘    └──────────┘    └──────────┘
                                        │
       ┌────────────────────────────────┘
       ▼
┌──────────┐    ┌──────────┐    ┌──────────────┐
│  线段识别  │───▶│  中枢构建  │───▶│  买卖点识别   │
│ 特征序列   │    │ 重叠区间   │    │ B1/B2/B3     │
│ (简化)    │    │ ZG/ZD    │    │ S1/S2/S3     │
└──────────┘    └──────────┘    └──────────────┘
                                        │
                                        ▼
                              ┌──────────────────┐
                              │  多级别联立        │
                              │  高TF趋势 × 低TF信号│
                              │  → 强/中/弱 决策    │
                              └──────────────────┘

💡 进阶优化建议 (点击展开)

  1. MACD 背驰辅助判定 B1/S1 --- 在 _buy() / _sell() 中加入 DIF/DEA 背离检测,可大幅提高 B1/S1 准确率。
  2. 中枢级别升级 --- 当同一方向出现 2 个以上中枢时,可判定为趋势,B1 信号权重更高。
  3. 区间套 --- 先在 30 分钟级别找到中枢区间,再在 5 分钟/1 分钟里做精确进出场。
  4. 中枢扩展 / 中枢新生 --- 两个相邻中枢重叠时可合并成更大级别中枢。
  5. 笔的力度 --- 可用笔的 power(幅度)做 MACD 面积类比,辅助背驰判断。
  6. 特征序列标准化 --- 当前线段检测使用简化规则,可替换为完整特征序列+包含处理以提高准确性。
相关推荐
Hello.Reader2 小时前
Spark 4.0 新特性Python Data Source API 快速上手
python·ajax·spark
IDZSY04302 小时前
从工具到协作者:AI Agent发展正在催生新型社交需求
大数据·人工智能
安全测评-Sean2 小时前
资产风险安全度量四象限闭环
大数据·安全度量
王小义笔记3 小时前
大模型微调步骤与精髓总结
python·大模型·llm
YA8888888888893 小时前
B端拓客号码核验:行业困局突围与技术赋能路径探析,氪迹科技法人股东核验系统,阶梯式价格
大数据·人工智能
jialan753 小时前
不干胶管理
大数据·数据库
wanhengidc3 小时前
算力服务器都有哪些功能
大数据·运维·服务器·智能手机
通信瓦工3 小时前
IEC 61975-2022标准介绍
大数据·网络
程序猿追3 小时前
HarmonyOS 6.0 游戏开发实战:用 ArkUI 从零打造消消乐小游戏
大数据·人工智能·harmonyos