9 Commits
Author SHA1 Message Date
jackyu66git cae126fafc fix: pipeline MACD 参数统一为标准 12/26/9(与 web/交易所一致) 2026-09-12 02:15:24 +08:00
jackyu66gitandGitHub 8413aa1d17 Merge pull request #19 from jackyu66git/dev
Dev
2026-04-08 01:29:56 +08:00
jackyu66gitandGitHub dbe189348e Merge pull request #18 from jackyu66git/dev
web端进行优化,减少内存开销,data provider提供websocket服务
2026-04-05 18:24:40 +08:00
jackyu66gitandGitHub c40f34fac9 Merge pull request #17 from jackyu66git/dev
添加了很多识别功能,特别是中枢相关的
2026-04-05 17:56:14 +08:00
jackyu66gitandGitHub 62dfb5e28f Merge pull request #16 from jackyu66git/dev
Dev
2026-03-24 16:35:18 +08:00
jackyu66gitandGitHub 9fdf0e11bc Merge pull request #15 from jackyu66git/dev
Dev
2026-03-21 01:41:05 +08:00
jackyu66gitandGitHub 6f2d5c4fa6 Merge pull request #14 from jackyu66git/dev
Dev
2026-03-15 17:09:49 +08:00
jackyu66gitandGitHub 4c37463472 Merge pull request #13 from jackyu66git/dev
增加缠论说明,优化线段中枢
2026-03-12 17:43:20 +08:00
jackyu66gitandGitHub 2273d9190e Merge pull request #12 from jackyu66git/dev
Dev
2026-03-11 11:38:12 +08:00
2213 changed files with 35679 additions and 201157 deletions
Vendored
BIN
View File
Binary file not shown.
+26 -9
View File
@@ -1,22 +1,39 @@
# MacOS
.DS_Store
# Python
# Python编译文件和缓存
__pycache__/
*.py[cod]
*$py.class
*.pyc
*.pyo
.pytest_cache/
# Logs & databases
# 策略文件的缓存
strategies/__pycache__/
# Machine Learning / AI model files
*_model*_xgb_model.json
*modelchan*.json
*.libsvm
feature_meta
*_model_feature_data.csv
*.pem
# Log files
*.log
# Database files
*.sqlite
*.sqlite-shm
*.sqlite-wal
# Local data
data/
# Local tooling
.gstack/
.DS_Store
交易记录/~$交易规则.docx
/datasvc/data
.DS_Store
.DS_Store
/data_provider/data
.DS_Store
.DS_Store
.DS_Store
.DS_Store
.DS_Store
+2 -2
View File
@@ -1,6 +1,6 @@
from decimal import Decimal
import chanlun.core.ChanKLC as ChanKLC
from chanlun.core.ChanEnum import Chan_BI_DIR
import ChanKLC
from ChanEnum import Chan_BI_DIR
class ChanBI():
def __init__(self, klc: ChanKLC, index, ddir=Chan_BI_DIR.UP):
self.start_klc = klc
+2 -2
View File
@@ -1,5 +1,5 @@
from chanlun.core.ChanEnum import Chan_ZS_DIR, Chan_ZS_TYPE, Chan_BI_DIR
import chanlun.core.ChanBI as ChanBI
from ChanEnum import Chan_ZS_DIR, Chan_ZS_TYPE, Chan_BI_DIR
import ChanBI
# 中枢
class ChanBIZS():
def __init__(self, start_bi: ChanBI, index, ddir: Chan_ZS_DIR):
+2 -2
View File
@@ -1,5 +1,5 @@
import chanlun.core.ChanBI as ChanBI
from chanlun.core.ChanEnum import Chan_BSP_TYPE, Chan_BSP_DIR
import ChanBI
from ChanEnum import Chan_BSP_TYPE, Chan_BSP_DIR
class ChanBSP():
def __init__(self, bi: ChanBI, index, type: Chan_BSP_TYPE, ddir: Chan_BSP_DIR, sure_time, zs_count, zs, seg):
+8 -8
View File
@@ -1,12 +1,12 @@
import copy
from typing import Dict, Optional
from chanlun.core.ChanEnum import Chan_FX_TYPE, Chan_KLINE_DIR, Chan_BI_DIR, Chan_KLC_FX
from chanlun.core.ChanEnum import Chan_K_DIR, Chan_MACD_STATE, Chan_PRICE_TREND, Chan_EMA_POS
from chanlun.core.ChanEnum import Chan_EMA_SEMANTIC, Chan_BSP_TYPE, Chan_KLC_STATE, Chan_FX
import chanlun.core.ChanKLU as ChanKLU
import chanlun.core.ChanCTime as ChanCTime
import chanlun.core.Chan_FX_Box as Chan_FX_Box
from ChanEnum import Chan_FX_TYPE, Chan_KLINE_DIR, Chan_BI_DIR, Chan_KLC_FX
from ChanEnum import Chan_K_DIR, Chan_MACD_STATE, Chan_PRICE_TREND, Chan_EMA_POS
from ChanEnum import Chan_EMA_SEMANTIC, Chan_BSP_TYPE, Chan_KLC_STATE, Chan_FX
import ChanKLU
import ChanCTime
import Chan_FX_Box
# 根据结合律合并K线后的K线
class ChanKLC():
def __init__(self, klu: ChanKLU, index, ddir=Chan_KLINE_DIR.UP):
@@ -372,14 +372,14 @@ class ChanKLC():
end_time = self.next.end_time
high = self.high
low = self.pre.low if self.pre.low < self.next.low else self.next.low
if self.next.close < self.pre.low or True:
if self.next.close < self.pre.low:
display = True
elif self.fx == Chan_FX_TYPE.BOTTOM:
start_time = self.pre.end_time
end_time = self.next.end_time
high = self.pre.high if self.pre.high > self.next.high else self.next.high
low = self.low
if self.next.close > self.pre.high or True:
if self.next.close > self.pre.high:
display = True
if high > 0 and self.next.end_time and display:
#print(start_time, end_time, high, low)
+13 -1
View File
@@ -1,4 +1,4 @@
from chanlun.core.ChanEnum import Chan_FX_TYPE, Chan_KLU_TYPE, Chan_K_DIR, Chan_MACD_STATE, Chan_MACDHIST_STATE, Chan_PRICE_TREND, Chan_KLU_PATTERN, Chan_KLC_FX
from ChanEnum import Chan_FX_TYPE, Chan_KLU_TYPE, Chan_K_DIR, Chan_MACD_STATE, Chan_MACDHIST_STATE, Chan_PRICE_TREND, Chan_KLU_PATTERN, Chan_KLC_FX
class ChanKLU:
def __init__(self, time, open, high, low, close, volume):
# _time, _close, _open, _high, _low, _extra_info={}
@@ -96,6 +96,7 @@ class ChanKLU:
self.trend = trend
def set_separate_div(self, separate_div):
self.separate_div = separate_div
bb2633_status = self.check_bb2633()
if self.klc and self.klc.pre and self.klc.next:
fx = self.check_fx_dir(self.klc.pre, self.klc.next)
if fx == Chan_FX_TYPE.TOP:
@@ -108,6 +109,17 @@ class ChanKLU:
self.separate_div = separate_div
else:
self.separate_div = 0
if bb2633_status == 0:
self.separate_div = 0
def check_bb2633(self, threadhold=300):
#print(self.time, self.high, self.bb2633upper, self.low, self.bb2633lower)
if abs(self.high - self.bb2633upper) < threadhold:
#print(self.time, self.high, self.bb2633upper)
return 1
if abs(self.low - self.bb2633lower) < threadhold:
#print(self.time, self.low, self.bb2633lower)
return -1
return 0
def check_fx_dir(self, pre, next):
fx = Chan_FX_TYPE.UNKNOWN
if pre.klc_fx_type == Chan_KLC_FX.TOP1 or pre.klc_fx_type == Chan_KLC_FX.TOP2 or next.klc_fx_type == Chan_KLC_FX.TOP1 or next.klc_fx_type == Chan_KLC_FX.TOP2 or self.klc.klc_fx_type == Chan_KLC_FX.TOP1 or self.klc.klc_fx_type == Chan_KLC_FX.TOP2:
+11 -34
View File
@@ -9,22 +9,21 @@ warnings.filterwarnings(
from datetime import timedelta
from pandas import DataFrame
from chanlun.core.ChanEnum import Chan_FX_TYPE, Chan_KLINE_DIR, Chan_BI_DIR, Chan_SEG_DIR, Chan_ZS_DIR, Chan_BSP_DIR, Chan_BSP_TYPE, Chan_KLC_FX, Chan_MACD_STATE, Chan_PRICE_TREND, Chan_KLU_PATTERN
from chanlun.core.ChanKLU import ChanKLU
from chanlun.core.ChanKLC import ChanKLC
from chanlun.core.ChanBI import ChanBI
from chanlun.core.ChanSBI import ChanSBI
from chanlun.core.ChanSEG import ChanSEG
from chanlun.core.ChanZS import ChanZS
from chanlun.core.ChanBSP import ChanBSP
from ChanEnum import Chan_FX_TYPE, Chan_KLINE_DIR, Chan_BI_DIR, Chan_SEG_DIR, Chan_ZS_DIR, Chan_BSP_DIR, Chan_BSP_TYPE, Chan_KLC_FX, Chan_MACD_STATE, Chan_PRICE_TREND, Chan_KLU_PATTERN
from ChanKLU import ChanKLU
from ChanKLC import ChanKLC
from ChanBI import ChanBI
from ChanSBI import ChanSBI
from ChanSEG import ChanSEG
from ChanZS import ChanZS
from ChanBSP import ChanBSP
import talib.abstract as ta
import pandas as pd
from technical.util import resample_to_interval
from decimal import Decimal
import numpy as np
from chanlun.indicators.ChanMACD import ChanMACD
from chanlun.pipeline.timeframe import TF_DF
from chanlun.analysis.ChanZone import StructureZone, StructureZoneConfig, analyze_structure_zones
from ChanMACD import ChanMACD
from TF_DF import TF_DF
class ChanLun():
def __init__(self):
@@ -126,18 +125,7 @@ class ChanLun():
def get_bsp_state(self, dataframe):
return self.tf_df.get_bsp_state(dataframe)
def get_structure_zones(self, current_price=None, config=None):
if config is None:
config = StructureZoneConfig()
return analyze_structure_zones(
self.tf_df_dict,
self.ema_symbols,
current_price=current_price,
config=config,
)
# TF_DF methods ------------------------------------------
def get_ema_state(self, dataframe):
return self.tf_df.get_ema_state(dataframe)
@@ -178,17 +166,6 @@ class ChanLun():
def cal_bi_zs_list(self, bi_list):
#return self.tf_df.cal_bi_zs(bi_list)
return self.tf_df.cal_bi_zs_list(bi_list)
def cal_bi_zs_list_pure(self, bi_list):
return self.tf_df.cal_bi_zs_list_pure(bi_list)
def init_stream(self, dataframe, interval=1, timeframe=None):
self.tf_df.init_stream(dataframe, interval, timeframe)
return self.tf_df
def append_bar(self, row):
return self.tf_df.append_bar(row)
def replace_last_bar(self, row):
return self.tf_df.replace_last_bar(row)
def get_bi_zs_list(self, bi_list):
return self.tf_df.get_bi_zs_list(bi_list)
def get_decimal(self, value):
return Decimal("{:.2f}".format(value))
def get_klc_list(self, klu_list):
@@ -6,14 +6,14 @@ sys.path.append(os.path.abspath("/Users/jack/Project/freqtrade/user_data/Chan"))
import numpy as np
from datetime import timedelta
from pandas import DataFrame
from chanlun.core.ChanEnum import Chan_FX_TYPE, Chan_KLINE_DIR, Chan_BI_DIR, Chan_SEG_DIR, Chan_ZS_DIR, Chan_BSP_DIR, Chan_BSP_TYPE, Chan_KLC_FX
from chanlun.core.ChanKLU import ChanKLU
from chanlun.core.ChanKLC import ChanKLC
from chanlun.core.ChanBI import ChanBI
from chanlun.core.ChanSBI import ChanSBI
from chanlun.core.ChanSEG import ChanSEG
from chanlun.core.ChanZS import ChanZS
from chanlun.core.ChanBSP import ChanBSP
from ChanEnum import Chan_FX_TYPE, Chan_KLINE_DIR, Chan_BI_DIR, Chan_SEG_DIR, Chan_ZS_DIR, Chan_BSP_DIR, Chan_BSP_TYPE, Chan_KLC_FX
from ChanKLU import ChanKLU
from ChanKLC import ChanKLC
from ChanBI import ChanBI
from ChanSBI import ChanSBI
from ChanSEG import ChanSEG
from ChanZS import ChanZS
from ChanBSP import ChanBSP
import talib.abstract as ta
import pandas as pd
import matplotlib.pyplot as plt
@@ -21,7 +21,7 @@ from matplotlib.dates import DateFormatter, date2num
import matplotlib.patches as patches
from technical.util import resample_to_interval
from decimal import Decimal
from chanlun.pipeline.orchestrator import ChanLun
from ChanLun import ChanLun
import xgboost as xgb
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score, classification_report
@@ -1,8 +1,8 @@
from chanlun.core.ChanKLU import ChanKLU
from chanlun.core.ChanEnum import Chan_MACD_STATE, Chan_MACDSEG_DIR, Chan_MACDHISTSET_DIR, Chan_MACDUNITTF_DIR, Chan_MACDUNITTF_TYPE
from chanlun.indicators.ChanMACDSeg import ChanMACDSeg
from chanlun.indicators.ChanMACDUnitTF import ChanMACDUnitTF
from chanlun.indicators.ChanMACDHistSet import ChanMACDHistSet
from ChanKLU import ChanKLU
from ChanEnum import Chan_MACD_STATE, Chan_MACDSEG_DIR, Chan_MACDHISTSET_DIR, Chan_MACDUNITTF_DIR, Chan_MACDUNITTF_TYPE
from ChanMACDSeg import ChanMACDSeg
from ChanMACDUnitTF import ChanMACDUnitTF
from ChanMACDHistSet import ChanMACDHistSet
class ChanMACD():
def __init__(self, klu_list: list[ChanKLU]):
@@ -31,7 +31,7 @@ class ChanMACD():
if self.klu_list:
for klu in self.klu_list:
hist = klu.macdhist
signal = False
singal = False
if klu.pre and klu.next:
if klu.signal > 0:
signal = klu.pre.signal > klu.signal and klu.next.signal < klu.signal
@@ -1,4 +1,4 @@
from chanlun.core.ChanEnum import Chan_MACDHISTSET_DIR, Chan_MACDUNITTF_DIV, Chan_MACD_STATE
from ChanEnum import Chan_MACDHISTSET_DIR, Chan_MACDUNITTF_DIV, Chan_MACD_STATE
class ChanMACDHistSet():
def __init__(self, index, start_time, start_klu, pre_histset, dir):
@@ -1,4 +1,4 @@
from chanlun.core.ChanEnum import Chan_MACDSEG_DIR
from ChanEnum import Chan_MACDSEG_DIR
class ChanMACDSeg():
@@ -1,4 +1,4 @@
from chanlun.core.ChanEnum import Chan_MACD_STATE, Chan_MACDUNITTF_DIR, Chan_MACDHISTSET_DIR, Chan_MACDUNITTF_DIV, Chan_MACDUNITTF_TYPE
from ChanEnum import Chan_MACD_STATE, Chan_MACDUNITTF_DIR, Chan_MACDHISTSET_DIR, Chan_MACDUNITTF_DIV, Chan_MACDUNITTF_TYPE
class ChanMACDUnitTF():
+3 -3
View File
@@ -1,9 +1,9 @@
import copy
from typing import Dict, Optional
from chanlun.core.ChanEnum import Chan_FX_TYPE, Chan_KLINE_DIR, Chan_BI_DIR
import chanlun.core.ChanKLU as ChanKLU
from chanlun.core.ChanBI import ChanBI
from ChanEnum import Chan_FX_TYPE, Chan_KLINE_DIR, Chan_BI_DIR
import ChanKLU
from ChanBI import ChanBI
class ChanSBI():
def __init__(self, start_bi: ChanBI, index, dir=Chan_BI_DIR.UP):
+4 -10
View File
@@ -1,10 +1,10 @@
import copy
from typing import Dict, Optional
from chanlun.core.ChanEnum import Chan_FX_TYPE, Chan_KLINE_DIR, Chan_SEG_DIR, Chan_BI_DIR, Chan_ZS_DIR
import chanlun.core.ChanCTime as ChanCTime
from chanlun.core.ChanBI import ChanBI
from chanlun.core.ChanBIZS import ChanBIZS
from ChanEnum import Chan_FX_TYPE, Chan_KLINE_DIR, Chan_SEG_DIR, Chan_BI_DIR, Chan_ZS_DIR
import ChanCTime
from ChanBI import ChanBI
from ChanBIZS import ChanBIZS
class ChanSEG():
def __init__(self, start_bi: ChanBI, index, ddir=Chan_SEG_DIR.UP, pre_end_bi: ChanBI = None):
self.start_bi = start_bi
@@ -96,10 +96,7 @@ class ChanSEG():
if self.dir == Chan_SEG_DIR.UP:
for index in range(1, len(self.bi_list)):
bi = self.bi_list[index]
#print(bi.end_time, bi.next,"UP SEG BI ZS Index")
if bi.next == None or bi.next.next == None:
if last_zs and (bi.low > last_zs.zg or bi.high < last_zs.zd):
last_zs.set_end_bi(last_zs.bi_list[-1], last_zs.bi_list[-1].sure_time)
continue
bi2 = bi.next
bi3 = bi.next.next
@@ -121,7 +118,6 @@ class ChanSEG():
else:
if bi.index > last_zs.bi_list[-1].index and bi.dir == Chan_BI_DIR.DOWN and bi.is_sure:
if bi.low > last_zs.zg or bi.high < last_zs.zd:
#print(bi.end_time, "UP SEG BI ZS End")
last_zs.set_end_bi(last_zs.bi_list[-1], last_zs.bi_list[-1].sure_time)
if bi3.is_sure and bi3.index <= self.bi_list[-1].index and bi.check_bi_zs_overlap() and bi.dir == Chan_BI_DIR.DOWN:
zg = min(bi.high, bi2.high, bi3.high)
@@ -147,8 +143,6 @@ class ChanSEG():
for index in range(1, len(self.bi_list)):
bi = self.bi_list[index]
if bi.next == None or bi.next.next == None:
if last_zs and (bi.low > last_zs.zg or bi.high < last_zs.zd):
last_zs.set_end_bi(last_zs.bi_list[-1], last_zs.bi_list[-1].sure_time)
continue
bi2 = bi.next
bi3 = bi.next.next
+3 -4
View File
@@ -1,9 +1,8 @@
from typing import Dict, Optional
import chanlun.core.ChanKLC as ChanKLC
import chanlun.core.ChanSEG as ChanSEG
import chanlun.core.ChanCTime as ChanCTime
from chanlun.core.ChanEnum import Chan_ZS_DIR
import ChanKLC, ChanSEG
import ChanCTime
from ChanEnum import Chan_ZS_DIR
# 中枢
class ChanZS():
def __init__(self, start_seg: ChanSEG, index, ddir: Chan_ZS_DIR):
+236
View File
@@ -0,0 +1,236 @@
均线
5m, 15m, 30m, 1h, 2h, 4h, 8h, 12h, 16h, 1d, 2d, 3d, 1w, 2w, 1M
参考时间周期
大周期:1h
小周期:15m
价格在1h周期ema156之上为大周期上涨,反之为大周期下跌
在1h大周期上涨时,小周期15m,下跌触碰到
顺大逆小
大周期看多,小周期跌完做多,跌完:顶分型和EMA52归零轴反弹
大周期看空,小周期涨完做空,涨完:顶分型和EMA52归零轴反抽
中枢分类
常规中枢
上升中枢
收敛中枢
扩散中枢
下行中枢
止损放到顶底分型的高低点
1. 从大周期开始找到价格接近ema52,MACD也接近零轴的周期,需要看这个周期的长级别是否高位空,大趋势方向
2. 然后去小于这个时间周期的周期找买卖点,小级趋势方向和大趋势相反并且开始反向,小级别需要检查MACD是否归零轴反转,同时价格是否接近EMA52
EMA52线的反弹比零轴的反弹弱
EMA52线和MACD白线同时归零轴同时满足的话是完美形态,最佳买卖点
跟随策略,先调整小级别,然后依次往大级别调整,直到整个周期结束
大级别MACD在零轴之上为多头趋势,回调踩EMA52做多,直到跳空背离,隐形,更大级别EMA52顶部归零轴平仓
大级别MACD在零轴之下为空头趋势,上涨踩EMA52做空,直到跳空背离,隐形,更大级别EMA52底部归零轴平仓
盘整趋势在零轴上下移动,价格在大级别EMA52之间移动,根据连续跳空背离,隐形,归零轴EMA52线开仓和平仓
MACD归零轴的两种情况,两者是或的关系,满足任意一种都是归零轴,归零轴的四种走势:
1. K线下跌或者上涨后触碰当前时间级别的EMA52附近
2. MACD的白线快线无限接近零轴,
3. MACD归零轴时,如果此时k线始终保持在EMA24附近,如果一直是EMA24之上之后出现反弹行情就会很大(最强反弹)这种反弹是2个时间级别同时归零轴形成的反弹,容易创新高新低,一般出现在强势行情。
4. K线先触碰EMA52,而MACD黄白线都未归零轴
高位空
当MACD的黄白线远离零轴运行时,与零轴有一定的距离,形成了零轴的高危形态。随着K线出现缓慢上涨或者下跌,或者盘整,MACD的能量柱出现衰减,同时能量柱与MACD黄白线形成空间夹角,随着能量柱越来越小,夹角越来越大形成高位空。这种容易形成回调下跌,特别是导致次一级的MACD穿越零轴
穿越零轴的定义,需要同时满足以下条件
1. 在某个时间级别,k线的价格或者指数有效击穿当前时间级别的EMA52
2. MACD黄线慢线有效击穿零轴
MACD黄白线和零轴的几种形态:
离开零轴
当MACD黄白线穿过零轴那么进入第一阶段离开零轴,此时能量柱变化越来越大,不断增长,k线加速上涨
高位
当MACD黄白线离开零轴,到一高点时,能量柱此时处于最大,开始减弱时MACD处于高位,高位时MACD黄白线和能量柱是同向的,高位过后是高位空
高位空
当MACD黄白线处于高位,随着K线出现缓慢上涨或者横盘整理,MACD黄白线保持高位出现平滑横盘走势,此时,MACD的能量柱出现衰减变化,同时能量柱和黄白线之间形成一定的空间夹脚,随着能量柱的不断衰减就导致黄白线和能量柱之间的空间夹脚越来越大,因此就形成高位空
归零轴
当MACD黄白线在高位,驱动K线上涨的能量所产生的加速度小于或者等于零,K线减速上涨或者下跌,能量变化越来越小,能量柱呈现出一根比一根短的排列方式
穿零轴
同时满足以下两个条件
1. 在某个时间级别,K线的价格或者指数有效击穿当前时间级别的EMA52
2. 当前时间级别MACD的黄线慢线有效击穿零轴
有效的定义是:当前K线正好击穿EMA52的支撑位后,如果当前这个K线收盘后的第二根K线任然保持在EMA52之下才算有效击穿,如果只是上下影线击穿,后期K线任然运行在EMA52之上不算有效击穿,MACD同理
零轴缠绕/纠缠
具体是指MACD跟零轴无限接近或者缠绕的状态,或者是已经完成第一次归零轴调整之后,在等待大级别调整的时候。
分为无限接近和上下缠绕状态。代表本级别已经调整完毕,即不产生反弹支撑,也不形成阻力压力,不考虑次级别的技术形态,通过更大的时间级别或者其他时间级别进行分析
隐形形态
当MACD的黄白线发生交叉时,必有相应的能量柱释放出来。金叉,则会释放零轴之上的能量柱,反之死叉,则会释放出零轴之下的能量柱。如果黄白线无交叉,而k线出现上涨或者下跌,则代表能量柱的隐形状态,代表K线的运行无能量配合,那么这种上涨或者下跌就变成无效的结果。无能量配合的上涨必下跌,无能量配合的下跌必反弹
1. 远离零轴的高位隐形形态
某个时间级别的MACD黄白线处在远离零轴的高位,且K线继续拉升上涨或者下跌,但是并没有释放出相对应方向的能量柱,此后,K线将出现归零轴的走势下跌或者上涨。此形态是高位隐形形态+高位空的形态,则当前级别的MACD在后续走势中必将出现会拉零轴甚至穿零轴的走势。因此,远离零轴的高位隐形形态解决的是当前时间级别归零轴的需求。
2. 归零轴的隐形形态
归零轴后反弹出现的隐形形态,我们称之为归零轴的隐形形态。这种形态必然会导致当前级别的MACD黄白线出现穿零轴的走势。MACD在归零轴的情况下,零轴所提供的反弹或者支撑能量是最大的,而如果零轴所能提供的最大能量都产生不了相应的能量柱,这种支撑就变成了无效的支撑,MACD的黄白线就只能击穿零轴渠道零轴的反方向。
如何确认隐形形态的顶部
1. 通过顶底分型来确认
2. 通过次级别的背离确认隐形高位
3. 通过单位调整周期之内的时间级别嵌套逻辑确认隐形的高位
顶底分型在K线动能理论的应用
1. 分型对应的MACD处在高位空的形态
2. 分型所对应的MACD出现隐形形态
零轴之上高位隐形 + K线顶分型 = 下跌归零轴
零轴之上归零轴隐形 + K线顶分型 = 下跌穿零轴
零轴之下高位隐形 + K线底分型 = 上涨归零轴
零轴之下归零轴隐形 + K线底分型 = 上涨穿零轴
K线的3种盘整结构,上涨和下跌均适用,下面是上涨结构的分析,下跌反之
1. K线强势的走势结构
一般应用是在单边上涨行情中,某个时间级别的K线经过一轮拉升之后,进入调整的阶段,此时K线如果在高位一直处于横盘震荡走势,而MACD的黄白线却趋于归零轴运行,则当黄白线归零轴之后,出现有效反弹行情。
2. K线超强势结构
超强势结构往往容易发生破前高的走势。在某个时间级别,当K线经过一轮强势拉升上涨后,变为倾斜向上缓慢上行,K线一直处于这个时间级别的EMA24之上或者附近,经过一段时间的运行,导致当前级别的MACD黄白线无限归零轴的形态,此时,K线往往容易出现速度快,力度强且破前高的走势。对于超强势调整结构,最重要的是次级别不破零轴,并保持在当前级别的EMA24之上或者附近,而当前级别的黄白线运动一段时间后无限趋于零轴。一般来说,超强势调整结构容易出现在某个大的时间级别处于单边行情中。
3. 弱势调整结构
在弱势调整结构中,K线是通过下跌的方式快速地将当前级别的MACD的黄白线拉回零轴,同时,K线的价格也会快速下跌到本级别EMA52附近位置。在弱势调整结构中,MACD归零轴后所形成的支撑反弹往往不会破前高,而是走出能量不足的走势形态。在此结构中,只有MACD的黄白线出现死叉后才会继续下跌,并且只有在弱势调整结构中,死叉下跌才有效。
单位调整周期
指K线在某个时间级别,MACD的黄白线由零轴出发到远离零轴再到回到零轴的区间段称为这个时间级别的调整周期。可以分为零轴同方向和穿零轴出发两种。一个单位调整周期的起点往往是买点,同时终点也是另一个周期的买点。单位周期的判断依据是黄白线归零轴不是量能柱的多少。
注意:如果单位调整周期的起始和终止都直接穿零轴的,代表这个时间级别在运行的过程中,是无效的时间级别,也就是这个时间级别在我们的分析的过程中要跳过的。
隐形单位调整周期
MACD黄白线归零轴后,由于零轴的支撑或者压力而发生的反弹或者反抽,没有释放出相应的能量柱而形成的周期,为隐形单位调整周期。隐形单位调整周期会引起黄白线反向穿零轴的走势。
零轴粘合
MACD黄白线在刚穿零轴的时候会出现:黄白线离零轴的距离比较近,黄白线沿着能量柱运行,黄白线在运行的过程中没有释放出反向能量柱。零轴粘合属性是:强支撑,弱反弹。这种形态我们更强调支撑能量,弱化反弹属性。零轴粘合几乎是贴近零轴运行,因此其反弹的动能就是为无效。斜率小,黄白线喝能量柱之间基本没有空隙。能量柱可以略微减弱但是不能释放下跌方向的能量柱。由此可见黄线没有跟白线有交叉,黄线对白线有支撑作用。如果当前时间级别内部逻辑关系走完,这个时间级别则被视为无效时间级别。
零轴倒挂
MACD黄白线在穿零轴的时候与零轴的距离比较近,同时黄白线沿着能量柱运行,在运行的过程中,能量柱衰减导致它跟黄白线之间形成夹角空位,同时黄白线产生交叉并释放反向能量柱。
1. 黄白线穿零轴后未远离零轴形成一定高度,而是靠近零轴运行
2. 能量柱的衰减导致其与黄白线之间形成了一定的夹角空位
3. 黄白线在运行的过程中发生了交叉而放出反向能量柱
弱支撑,弱反弹。如果当前时间级别内部逻辑关系走完,这个时间级别则被视为无效时间级别。如果某个时间级别的盘口形态是零轴倒挂,那么这个时间级别很容易直接击穿零轴,而无法形成有效的反弹和反抽行情。
零轴粘合和倒挂的有效性
在上涨行情中,当K线处在零轴粘合或者零轴倒挂的形态时,如果K线的价格处在当前级别的EMA52之上或者处在多级别EMA均线交汇处之上和附近时,此时由于K线受到EMA均线的支撑,零轴粘合或者零轴倒挂反而容易形成强支撑的特点。此时需要结合MACD的形态和K线均线支撑综合分析盘面。
线段
上涨线段是指MACD的黄白线第一次上穿零轴到下一次下穿零轴中间的运行区域,以黄线穿零轴为准。在这个线段找到阶段性的卖点,阶段性高点。
下跌线段是指MACD的黄白线第一次下穿零轴到下一次上穿零轴中间的运行区域,以黄线穿零轴为准。在这个线段找到阶段性的买点,阶段性低点。
1. 同一条线段是比较背离的区域,背离的比较不可再跨线段的区域进行
2. 线段可以将不同时间级别的K线化繁为简,一个时间级别的形态只需要确定盘面所处的线段即可
背离
在K线分析中,背离是指价格跟能量之间的相悖性,当价格创出阶段性新高点,而推动价格上涨的能量出现衰减,这种情况就是背离,也就是说价格和能量之间产生了不匹配关系。
顶背离 - 确认卖点
在某个时间级别,MACD运行在零轴上方,当K线的价格走势一峰比一峰高,价格一直处在上涨趋势中时,MACD的黄白线或者能量柱的高度却一波比一波低,即当价格的高点比前一次价格的高点高,而MACD指标的高点比前一次高点低,这种形态称为顶背离形态。需要注意的是,这两次高点在运行的过程中,MACD的黄白线始终处在同一条上涨线段周期中,不能跨线段比较。
顶背离包括黄白线背离和柱背离两种情况,也可能出现多次背离的情况。线背离以白线的最高点作为参考点,K线上影线最高点作为K线的参考点,能量堆的最高点可能和K线的最高点不是一一对应,但是不影响判断,可以使用能量堆的面积进行计算。
底背离 - 确认买点
在某个时间级别,MACD运行在零轴下方,一般出现价格的低位区。当K线的价格走势持续下跌,而MACD的黄白线或者能量柱却持续靠近零轴,即当前价格的低点比前一次低点好要低,而MACD指标的低点却比前一次低点高,但是下跌能量却在衰减的现象,于是价格跌无可跌,是短期买入信号
底背离包括黄白线背离和柱背离两种情况,也可能出现多次背离的情况。线背离以白线的最低点作为参考点,K线上影线最低点作为K线的参考点,能量堆的最低点可能和K线的最低点不是一一对应,但是不影响判断,可以使用能量堆的面积进行计算。
顶底背离高低点有效性的方法
1. 通过顶底分型确认顶底背离的高低点
2. 通过次级别的背离确认当前级别的背离高点,这里的次级别不是单指一级次级别,可以是多级的
单位调整周期内的连续跳空背离
K线经过一波上涨或者下跌后,MACD的黄白线由高位回零轴且未归到零轴,能量柱在连续的衰减调整过程中,反而逐渐由衰减转为增长,于是就出现了跳空走势,这种走势称为MACD的连续跳空形态。
1. 连续跳空发生在单位调整周期内,线跟柱在零轴同方向
2. MACD的能量柱需包含在黄白线之内
3. 能量柱在衰减的过程中未放出反向能量柱,而是由衰减转为增长
单位调整周期之内的背离是价格跟能量柱之间的关系,同时单位调整周期之内的背离,解决的是归零轴的需求
连续跳空的应用和意义
单位调整周期之内,能量堆连接在一起的时候,出现的价格跟能量柱之间的关系即为连续跳空,而连续跳空的意义
1. 连续跳空背离解决归零轴的需求,主要是小级别的走势,比如5分钟的连续跳空背离则为归零轴走势,因为5分钟级别只包含一个3分钟级别,是5分钟内的小级别,因此,5分钟级别如果出现连续跳空背离,会导致5分钟级别MACD黄白线归零轴走势
2. 连续跳空更大的作用是确认穿零轴之后的第一个背离参考点,当MACD黄白线穿零轴的时候,最重要的是确认当前线段的1号参考点,有了1号参考点,后续行情才有参考的对象。连续跳空往往发生在MACD黄白线刚刚穿零轴的位置,一般由零轴粘合的形态演化而成连续跳空,这是因为零轴粘合具有强支撑的特点,容易形成跳空走势。
穿零轴时的参考点确认方法
1. 如果MACD黄白线穿零轴之后出现连续跳空,则以连续跳空的高点作为1号参考点,此时连续跳空形态代表新的单位周期调整周期的开始。当黄白线穿零轴后,黄线之后的第一个能量堆没有更高的能量柱,而是到了第一个跳空高低点出现第一个高低点。穿零轴产生的能量柱左侧黄白线处在下跌线段,右侧处在上涨线段。因此穿零轴的能量柱被切成两半,左侧在下跌线段,所以不能以黄线击穿零轴的左侧作为上涨线段的1号参考点。背离一定要在同一线段中进行比较,而不能跨线段找背离。那么穿零轴之后跳空产生的高低点才能作为1号参考点。后续的背离要以这个参考点进行比较。
2. 如果MACD黄白线穿零轴后无连续跳空,没有更高的能量柱出现则不能确定1号参考点,如果穿零轴后没有找到更高低的能量柱,那么这个线段的第一个单位调整周期是无效周期。
3. 如果黄线穿零轴之后有更高低的能量柱,则以更高的能量柱作为1号参考点,此参考点可以是穿零轴时的能量柱高点确定。
单位调整周期之内的分立跳空背离
在某个时间级别,当一个单位调整周期之内包含两个或者两个以上的能量堆,能量堆之间被反向能量堆分隔开,同时MACD黄白线一直处于远离零轴的高位,未归零轴,且一直保持原有的趋势运行,被分割的能量堆与黄白线都处在零轴的同方向,能量堆包含在黄白线之内,就形成分离跳空形态。
分立跳空背离
如果在某个时间级别的单位调整周期内,黄白线未归零轴,K线在经过一段时间的调整并放出反向能量柱之后,继续沿着原有方向运行,导致再一次出现的能量堆,同时黄白线再一次远离零轴,能量堆和能量堆之间形成背离关系,这种就是分离跳空背离。分立跳空背离解决的是归零轴的需求
分立跳空背离 + 黄白线高位 = 归零轴
分立跳空不背离 = 单边上涨或者下跌行情
最佳买卖点
单位周期之内 + 隐形 + 分立跳空 + 背离 + 黄白线高位空
跳空产生的原理:跳空的产生是当前级别之下的小级别归零轴反弹或反抽导致的,比如1小时时间级别的单位调整周期跳空,是因为1小时时间级别之内包含3分钟,5分钟,15分钟,30分钟这些下级别。1小时级别的MACD归零轴的途中,必然导致其内部的小级别先于本级别归零轴。因此,当这些小级别归零轴后,如果产生反弹反抽的走势,则会导致当前1小时级别的MACD黄白线再一次被拉高,此时便产生了跳空的走势。
时间级别在高位中归零轴的顺序是:由小级别到大级别依此归零轴。当K线经过一轮拉升下跌后,当前级别的MACD黄白线处在高位,此时如果K线进入调整状态,则这个时间级别所包含的小级别由小到大依次归零轴,直至本级别归零轴为止,如此才完成了次级别的单位调整周期的调整。
单位周期之内的分立跳空顶背离
MACD黄白线在零轴之上第一个单位调整周期 + 分立跳空背离(一次或多次) + 黄白线处在零轴的高位空 + 分立跳空隐形状态
单位周期之内的分立跳空底背离
MACD黄白线在零轴之下第一个单位调整周期 + 分立跳空背离(一次或多次) + 黄白线处在零轴的高位空 + 分立跳空隐形形态
单位调整周期之内的跳空非背离
如果在单位调整周期内发生了跳空的形态,当跳空能量堆的高度高于前一个能量堆的高度,此时的跳空则为非背离跳空。非背离跳空可以理解为单位周期的单边行情,前一个能量堆失效,以新的最高的能量堆作为后续行情的1号参考点。不管是连续跳空还是分立跳空都遵守这个法则。
单位调整周期之间的背离
单位调整周期之间的背离,是指两个或者两个以上的单位调整周期相比较而建立的关系,相比较的周期必须在同一个线段周期内,不可跨线段比较。当K线在某个时间级别的线段中,MACD经过了一个单位调整周期的运行,黄白线在此回零轴后,由于零轴的支撑反弹或压力反抽的作用,因此出现第二个单位调整周期,当第二个单位调整周期的黄白线这里特指白线离开零轴的距离小于第一个单位调整周期黄白线离开零轴的距离时,单位调整周期之间就产生了相悖的关系,即价格穿新高或新低,而白线能量区出现了减弱或增强,这种背离称为单位调整周期之间的背离,单位调整周期之间的背离也叫区间背离。
上涨线段单位调整周期之间的顶背离为卖点(前提:长级别MACD在高位)
下跌线段单位调整周期之间的底背离为买点(前提:长级别MACD在高位)
单位调整周期之间的背离,其核心的本质是描述某个时间级别在线段中的运行逻辑,即为线段完成调整的重要依据。
单位调整周期之间的背离是为了满足线段调整的需求
判断某个时间级别线段结束趋势的依据为:在某个时间级别的线段中,第二个单位调整周期与第一个单位调整周期之间产生背离关系,即为此线段终结的依据,而后出现的单位调整周期无论归零轴多少次,从能量产生的逻辑上都是依次减弱直至趋于零。
单位调整周期之间的背离而穿零轴变盘的依据是:当某个级别在线段中出现周期之间的背离形态,同时完成了线段的调整,但是其长级别MACD的黄白线处在高位空的形态时,当前级别的背离会导致穿零轴走势。
在K线的时间逻辑中,判断一个时间级别完成自己的当值任务的标准是:本级别在当前的线段中产生了周期间的背离关系,并趋向于零轴的调整。本级别是否穿零轴并非由本级别背离的属性决定,而是由长级别决定。因此,当本级别完成线段调整之后,就要看长级别处在什么形态之下,长级别的形态属性决定了后续的行情走势。
时间级别升级:是指当本级别在其线段中产生了背离关系而趋向于零轴,达到了平衡状态且保持在零轴之上(代表完成了其线段的调整任务),其长级别同时也处在归零轴的形态,此时当前级别即要发生时间级别升级。时间级别升级之后,本级别将会产生新的线段,之后本级别的线段关系则升级为线段与线段之间的关系。
确定时间级别升级的条件
1. 当前级别多次归零轴背离而完成了线段的调整,之后新的单位调整周期MACD的白线DIF比前一个单位调整周期的白线DIF高
2. 当前级别完成线段调整,长级别MACD的黄白线无限接近零轴
线段背离
当某个时间级别升级之后,便产生了一条新的线段,如果线段和线段之间构成相悖关系时,我们称为线段背离,而线段背离则必然导致当前时间级别穿零轴。线段背离比较的是两个线段中最高或最低的白线DIF。
动能不足
如果K线走势中不破前高点上涨动能衰减,或者不破前低点,下跌动能也衰减,这种形态称为动能不足,本质上和背离是一样的,都是能量衰减的一种变现,背离所具有的属性和原理适用于动能不足。
单位调整周期内,之间,线段之内,线段之间的隐形背离或隐形动能不足
主级别归零轴启动反弹的内部过程:
1. 第一过程,主级别所包含的小级别在零轴之下首先完成超跌反弹的过程
2. 第二过程,小级别完成底部调整后,才正式启动主级别归零轴反弹
底部形态变盘的四个阶段
确认底部区域需要四个条件
1. 确认引起下跌行情中的主要时间级别
当K线出现下跌行情时,一定是某个时间级别在零轴之上进行的归零轴调整而引起的,受到零轴的引力作用,这个级别的MACD黄白线会被拉回零轴。这个时间级别是:上穿零轴后第一次开始归零轴的时间级别。
2. 确认主级别归零轴后的形态属性
K线价格要触碰到EMA52均线的位置附近,同时MACD的黄白线无限接近零轴。也就是说,K线的价格一旦触碰到EMA52的位置附近,因为这个位置能否形成支撑反弹,主要是看归零轴的这两个条件能否一直保持,并开始归零轴反弹的第一个过程,即主级别所包含的小级别在零轴之下的超跌反弹。
3. 在归零轴的形态满足条件的情况下,确认子级别是否有高位空的形态
当主级别第一次触碰到当前级别EMA52均线的位置附近时,我们要看包含在主级别之下的小级别在零轴的下方是否产生了高位空的形态。只有当这些小级别出现高位空的形态时,才能出现有效的归零轴的超跌反弹,而超跌反弹的变向则为主级别归零轴后出现的止跌反弹的走势。
4. 确认零轴之下的最大子级别运行底部变盘的四个阶段
当主级别进入底部区域后,小级别的超跌反弹将逐级别开始,而时间级别的运行逻辑则是从小级别依次运行。因此,当主级别包含的小级别零轴之下的最后一个子级别完成调整后,主级别的归零轴反弹才正式开启。这些零轴之下的子级别的运行逻辑即为主级别底部变盘的四个阶段。
第一阶段:零轴之下的子级别的单边下跌
第二阶段:零轴之下的子级别的超跌反弹
第三阶段:零轴之下的子级别的归零轴反抽之后产生背离/动能不足
第四阶段:零轴之下的子级别跟零轴形成粘合或者零轴纠缠
底部形态V字反转的条件
当主级别归零轴后,由于小级别的超跌反弹和反抽容易在行情的底部走出横盘震荡的走势,当某个时间级别的超跌反弹导致K线突破这个横盘区间时,我们便把这种走势称为V字反转的走势
V字反转走势发生的条件
1. 主级别保持归零轴形态且MACD出现收敛的状态
2. 零轴之下大多数小级别已经完成线段调整,并形成了底部的横盘结构,剩下未调整的级别没有明显的高位空
3. 下一个长级别的超跌反弹归零轴所触碰到EMA52均线的位置需要有效突破底部横盘震荡的区间
MACD收敛
K线在下跌行情中进入底部区域,当MACD的黄白线由倾斜向下趋于零轴的方向转为拐头形态,同时下跌能量柱由逐渐增长转为衰减时,我们把这种形态称为MACD收敛形态
抢底原理
当行情运行到当前级别底部调整的第三阶段时,即背离/下跌动能不足时,即为我们最佳买入机会。因为一旦启动了V字反转的走势,K线的价格将不容易再出现大的回调机会,而V字反转的行情极容易导致剩下的海味调整的其他小级别直接击穿零轴,或者出现以横代跌的走势而不在出现明显的反抽下跌。这就是V字反转结构形成的条件下的抢底原理。
第一代时间级别当值的有效性满足两个条件
1. 第一代时间级别不能击穿零轴,如果击穿零轴,则本级别当值作用失效
2. 每个当值的第一代时间级别的反弹行情必须推动其长级别在上涨线段中的第一个单位调整周期处于高位的形态
+2260
View File
File diff suppressed because it is too large Load Diff
+13
View File
@@ -0,0 +1,13 @@
1. **第一类买卖点**
- 定义:趋势反转的起始点,即在下跌趋势结束时形成的买点(第一类买点),或在上涨趋势结束时形成的卖点(第一类卖点)。这是市场多空力量发生根本性转变的位置。
- 与MACD背驰的关系:Macd背驰是指出中枢后形成的Macd的红绿柱面积比进入中枢时的面积绝对值小,背驰比较的黄白线和柱子面积都在0轴的一个方向上。第一类买点都是在0轴之下背驰形成的,第一类卖点都是在0轴之上的背驰形成的。
2. **第二类买卖点**
- 定义:趋势确认后的回调点。在第一类买卖点之后,价格会回调或反弹,形成第二类买点(回调不破前低)或第二类卖点(反弹不破前高),是对第一类买卖点的确认。第二类买点都是第一次上0轴后回抽确认形成的。第二类卖点都是第一次0轴之下上涨确认形成的。第二类买卖点只会在趋势确认后,第一类买卖点出现之后出现一次,不会重复出现,除非趋势反转之后。
3. **第三类买卖点**
- 定义:趋势延续的确认点。价格突破回调或反弹的中枢区间后,回踩不破关键位置(如中枢上沿或下沿),形成第三类买点(上升趋势延续)或第三类卖点(下降趋势延续)。第三类买卖点只会在中枢确认之后出现。
我们交易的是币安的比特币合约, 数据格式是json, 数据包括现有的持仓, 仓位历史, 账户余额, 你用缠论分析之后, 给出以下分析, 最近的一个中枢在哪里,现在的趋势是什么,现在是否是买卖点,如果是,是那一类买卖点,应该进行何种操作。
-11
View File
@@ -1,11 +0,0 @@
"""缠论引擎正式包。
推荐::
from chanlun import ChanLun, TF_DF
from chanlun.core.ChanEnum import Chan_BI_DIR
"""
from chanlun.pipeline.orchestrator import ChanLun
from chanlun.pipeline.timeframe import TF_DF
__all__ = ["ChanLun", "TF_DF"]
-292
View File
@@ -1,292 +0,0 @@
"""
中枢结构特征提取 + 标签化
Market Structure Dataset Builder — Phase 1
定位: 训练数据集构建工具,不是交易信号生成器。
Feature 描述中枢内部结构,Label 记录中枢后实际演化。
"""
import math
import json
from typing import Optional
from chanlun.core.ChanEnum import Chan_BI_DIR
class ChanPivotClassifier:
"""
中枢结构特征提取 + 标签化
输入: bi_zs_list (list[ChanBIZS])
输出: 结构化数据集 (list[dict])
"""
DATASET_VERSION = "pivot_v1"
FEATURE_SCHEMA = ["duration_norm", "contraction", "shift_norm"]
LABEL_SCHEMA = {"name": "break_direction", "values": ["up", "down", "none"]}
def __init__(self, bi_zs_list: list, symbol: str = "", timeframe: str = ""):
self.bi_zs_list = bi_zs_list
self.symbol = symbol
self.timeframe = timeframe
# ------------------------------------------------------------------
# Feature extraction
# ------------------------------------------------------------------
@staticmethod
def calc_duration(zs) -> int:
"""持续时间: 第一笔首K → 最后一笔末K 的 index 差"""
bi_list = zs.bi_list
start_idx = bi_list[0].start_klc.index
end_idx = bi_list[-1].end_klc.index
return end_idx - start_idx
@staticmethod
def calc_contraction(zs) -> float:
"""收敛率: 后窗口振幅均值 / 前窗口振幅均值"""
bi_list = zs.bi_list
if len(bi_list) < 4:
return 1.0
n = min(3, len(bi_list) // 2)
first_ranges = [bi.high - bi.low for bi in bi_list[:n]]
last_ranges = [bi.high - bi.low for bi in bi_list[-n:]]
first_mean = sum(first_ranges) / len(first_ranges)
last_mean = sum(last_ranges) / len(last_ranges)
if first_mean == 0:
return 1.0
return last_mean / first_mean
@staticmethod
def calc_shift(zs) -> tuple[float, float]:
"""重心漂移: 前后半段重心均值差 (原始值, 归一化值)"""
bi_list = zs.bi_list
mid = len(bi_list) // 2
first_centers = [(bi.high + bi.low) / 2 for bi in bi_list[:mid]]
last_centers = [(bi.high + bi.low) / 2 for bi in bi_list[mid:]]
shift_raw = (
sum(last_centers) / len(last_centers)
- sum(first_centers) / len(first_centers)
)
zs_height = zs.zg - zs.zd
if zs_height == 0:
shift_norm = 0.0
else:
shift_norm = shift_raw / zs_height
return shift_raw, shift_norm
@staticmethod
def compute_duration_norm(duration_raw: int, historical_durations: list) -> float:
"""用历史窗口均值归一化 duration"""
if not historical_durations:
return 1.0
avg = sum(historical_durations) / len(historical_durations)
if avg == 0:
return 1.0
return duration_raw / avg
@staticmethod
def compute_features(zs, historical_durations: Optional[list] = None):
"""计算单个中枢的全部结构特征(实时友好)"""
duration_raw = ChanPivotClassifier.calc_duration(zs)
contraction = ChanPivotClassifier.calc_contraction(zs)
shift_raw, shift_norm = ChanPivotClassifier.calc_shift(zs)
if historical_durations is not None and len(historical_durations) > 0:
duration_norm = ChanPivotClassifier.compute_duration_norm(
duration_raw, historical_durations
)
else:
duration_norm = 1.0
return {
"duration_raw": duration_raw,
"duration_norm": round(duration_norm, 4),
"contraction": round(contraction, 4),
"shift_raw": round(shift_raw, 6),
"shift_norm": round(shift_norm, 4),
"zs_height": round(zs.zg - zs.zd, 6),
}
# ------------------------------------------------------------------
# Label computation
# ------------------------------------------------------------------
@staticmethod
def _clamp(x: float, lo: float = 0.0, hi: float = 1.0) -> float:
return max(lo, min(hi, x))
def _compute_label(self, zs, contraction: float, shift_norm: float) -> dict:
"""计算标签: up / down / none + 连续置信度"""
bi_out = zs.bi_out
if bi_out is None:
return {
"label": "none",
"label_confidence": 0.0,
"label_detail": {
"bi_out_dir": "none",
"score_breakout": 0.0,
"score_shift": 0.0,
"score_contraction": 0.0,
},
}
zs_height = zs.zg - zs.zd
if zs_height == 0:
zs_height = 1e-8
# ---- 向上突破分数 ----
if bi_out.dir == Chan_BI_DIR.UP:
raw_breakout = (bi_out.high - zs.gg) / zs_height
score_breakout_up = self._clamp(raw_breakout)
score_shift_up = math.tanh(self._clamp(shift_norm, -3.0, 3.0))
score_contraction_up = max(0.0, 1.0 - contraction)
else:
score_breakout_up = 0.0
score_shift_up = 0.0
score_contraction_up = 0.0
up_score = (
score_breakout_up * 0.5
+ score_shift_up * 0.3
+ score_contraction_up * 0.2
)
# ---- 向下突破分数 ----
if bi_out.dir == Chan_BI_DIR.DOWN:
raw_breakout = (zs.dd - bi_out.low) / zs_height
score_breakout_down = self._clamp(raw_breakout)
score_shift_down = math.tanh(self._clamp(-shift_norm, -3.0, 3.0))
score_contraction_down = max(0.0, 1.0 - contraction)
else:
score_breakout_down = 0.0
score_shift_down = 0.0
score_contraction_down = 0.0
down_score = (
score_breakout_down * 0.5
+ score_shift_down * 0.3
+ score_contraction_down * 0.2
)
# ---- 判定 ----
threshold = 0.15
if up_score > down_score and up_score > threshold:
label = "up"
confidence = up_score
detail = {
"bi_out_dir": "up",
"score_breakout": round(score_breakout_up, 4),
"score_shift": round(score_shift_up, 4),
"score_contraction": round(score_contraction_up, 4),
}
elif down_score > up_score and down_score > threshold:
label = "down"
confidence = down_score
detail = {
"bi_out_dir": "down",
"score_breakout": round(score_breakout_down, 4),
"score_shift": round(score_shift_down, 4),
"score_contraction": round(score_contraction_down, 4),
}
else:
label = "none"
confidence = max(up_score, down_score)
bi_dir = "up" if bi_out.dir == Chan_BI_DIR.UP else "down"
detail = {
"bi_out_dir": bi_dir,
"score_breakout": round(max(score_breakout_up, score_breakout_down), 4),
"score_shift": round(max(score_shift_up, score_shift_down), 4),
"score_contraction": round(max(score_contraction_up, score_contraction_down), 4),
}
return {
"label": label,
"label_confidence": round(confidence, 4),
"label_detail": detail,
}
# ------------------------------------------------------------------
# Public API
# ------------------------------------------------------------------
def extract(self) -> list[dict]:
"""主入口:对每个中枢提取 3 特征 + 1 标签"""
# 第一遍:计算原始值
raw = []
for i, zs in enumerate(self.bi_zs_list):
if not zs.is_sure or len(zs.bi_list) < 3:
continue
duration_raw = ChanPivotClassifier.calc_duration(zs)
contraction = ChanPivotClassifier.calc_contraction(zs)
shift_raw, shift_norm = ChanPivotClassifier.calc_shift(zs)
raw.append({
"zs": zs,
"zs_index": i,
"duration_raw": duration_raw,
"contraction": contraction,
"shift_raw": shift_raw,
"shift_norm": shift_norm,
"zs_height": zs.zg - zs.zd,
})
# 第二遍:组装输出 + 计算 label
result = []
for r in raw:
zs = r["zs"]
historical = [x["duration_raw"] for x in raw]
duration_norm = ChanPivotClassifier.compute_duration_norm(
r["duration_raw"], historical
)
label_info = self._compute_label(zs, r["contraction"], r["shift_norm"])
# 时间处理
start_time = None
end_time = None
if hasattr(zs, "start_time") and zs.start_time is not None:
start_time = str(zs.start_time)
if hasattr(zs, "end_time") and zs.end_time is not None:
end_time = str(zs.end_time)
result.append({
"dataset_version": self.DATASET_VERSION,
"feature_schema": self.FEATURE_SCHEMA,
"label_schema": self.LABEL_SCHEMA,
"symbol": self.symbol,
"timeframe": self.timeframe,
"zs_index": r["zs_index"],
"zs_start_time": start_time,
"zs_end_time": end_time,
"duration_norm": round(duration_norm, 4),
"contraction": round(r["contraction"], 4),
"shift_norm": round(r["shift_norm"], 4),
"label": label_info["label"],
"label_confidence": label_info["label_confidence"],
"label_detail": label_info["label_detail"],
"duration_raw": r["duration_raw"],
"shift_raw": round(r["shift_raw"], 6),
"zs_height": round(r["zs_height"], 6),
})
return result
def export_json(self, path: str):
"""导出为 JSON 文件"""
data = self.extract()
with open(path, "w", encoding="utf-8") as f:
json.dump(data, f, indent=2, ensure_ascii=False, default=str)
return len(data)
-145
View File
@@ -1,145 +0,0 @@
"""
实时中枢特征跟踪器
Real-time Pivot Feature Tracker
定位: 观察者 — 不修改管线,只观察 bi_zs_list 中当前中枢的特征变化。
每次管线重算后调用 update(),检测 bi_count 是否增长,若增长则重新计算
shift / contraction / duration。
"""
from collections import deque
from typing import Optional
from chanlun.analysis.ChanPivotClassifier import ChanPivotClassifier
class ChanPivotMonitor:
"""
实时追踪当前中枢的结构特征。
update() 每次管线重算后调用,对比 bi_count 判断是否有新笔加入中枢。
若 bi_count 增长则重新计算 3 个结构特征并返回最新值。
"""
def __init__(self, window_size: int = 10):
self._window_size = window_size
self._duration_history: deque[int] = deque(maxlen=window_size)
self._current_zs_id: Optional[tuple] = None
self._current_bi_count: int = 0
self._current_is_sure: bool = False
self._current_state: Optional[dict] = None
self._duration_added_for_zs: set = set() # 已加入窗口的中枢 ID(上限 200)
# ------------------------------------------------------------------
# Public API
# ------------------------------------------------------------------
def update(self, bi_zs_list: list) -> Optional[dict]:
"""
主入口:检测当前中枢特征变化。
参数:
bi_zs_list: 当前管线产出的笔中枢列表
返回:
特征 dict(有变化时),无变化返回 None
"""
if not bi_zs_list:
self._current_zs_id = None
self._current_bi_count = 0
self._current_is_sure = False
self._current_state = None
return None
zs = self._find_current_zs(bi_zs_list)
if zs is None:
return None
zs_id = self._make_zs_id(zs)
bi_count = len(zs.bi_list)
is_sure = zs.is_sure
# 无变化 → 跳过
if (zs_id == self._current_zs_id
and bi_count == self._current_bi_count
and is_sure == self._current_is_sure):
return None
# 中枢切换 → 将旧中枢 duration 加入窗口
if zs_id != self._current_zs_id:
self._maybe_add_to_history()
self._current_zs_id = zs_id
self._current_bi_count = bi_count
self._current_is_sure = is_sure
features = ChanPivotClassifier.compute_features(
zs, list(self._duration_history)
)
self._current_state = {
"zs_id": zs_id,
"zs_index": zs.index,
"zs_dir": str(zs.dir),
"bi_count": bi_count,
"is_sure": zs.is_sure,
"zg": round(zs.zg, 6),
"zd": round(zs.zd, 6),
"gg": round(zs.gg, 6),
"dd": round(zs.dd, 6),
**features,
"start_time": str(t) if (t := getattr(zs, "start_time", None)) else None,
}
# 中枢刚变为已确认时,将其 duration 加入滚动窗口
if is_sure and zs_id not in self._duration_added_for_zs:
self._add_duration(features["duration_raw"])
self._duration_added_for_zs.add(zs_id)
return self._current_state
def get_current(self) -> Optional[dict]:
"""返回当前中枢的最新特征"""
return self._current_state
def get_duration_history(self) -> list[int]:
"""返回用于归一化的 duration 滚动窗口"""
return list(self._duration_history)
# ------------------------------------------------------------------
# Internal
# ------------------------------------------------------------------
@staticmethod
def _make_zs_id(zs) -> tuple:
"""生成中枢的稳定标识(基于首笔首K线时间戳,不随 DataFrame 窗口偏移而变化)"""
bi0 = zs.bi_list[0]
return (bi0.start_klc.start_time,)
@staticmethod
def _find_current_zs(bi_zs_list: list):
"""
找到当前活跃中枢:
优先取最后一个 is_sure=False(形成中)的中枢,
没有则取最后一个 is_sure=True 的中枢。
"""
forming = None
last_sure = None
for zs in bi_zs_list:
if len(zs.bi_list) < 3:
continue
if not zs.is_sure:
forming = zs
else:
last_sure = zs
return forming if forming is not None else last_sure
def _add_duration(self, duration_raw: int):
"""将已确认中枢的 duration 加入滚动窗口"""
self._duration_history.append(duration_raw)
def _maybe_add_to_history(self):
"""旧中枢切换前,若已确认且未记录过,则将其 duration 加入窗口"""
if (self._current_state and self._current_state["is_sure"]
and self._current_zs_id not in self._duration_added_for_zs):
self._add_duration(self._current_state["duration_raw"])
self._duration_added_for_zs.add(self._current_zs_id)
-566
View File
@@ -1,566 +0,0 @@
"""
结构价值区 (Structure Zone) 系统
将多时间周期的 Chan 中枢边界 (ZD/ZG/GG/DD) 和 EMA52 统一表示为带强度评分的价值区对象。
"""
from dataclasses import dataclass, field
from typing import List, Dict, Optional, Any
from datetime import datetime
# ============================================================
# Dataclasses
# ============================================================
@dataclass
class RawZonePoint:
"""内部中间结构:从 Chan 中枢提取的单个价格点"""
price: float
timeframe: str # '5m', '1h', '4h' 等
structure_type: str # 'bi_zhongshu' | 'xd_zhongshu' | 'ema52'
boundary_type: str # 'ZD' | 'ZG' | 'GG' | 'DD' | 'EMA52'
source_zs_id: int # 来源 ZS 在列表中的 index(调试用)
is_sure: bool # 来源 ZS 是否已完成
candle_time: Optional[str] = None # 来源 ZS 的 end_time(用于 recency 计算)
@dataclass
class StructureZone:
"""统一的价值区对象"""
id: int
lower: float
upper: float
center: float # (lower + upper) / 2
width_pct: float # (upper - lower) / center * 100
zone_type: str # 'support' | 'resistance' | 'neutral'
timeframes: List[str] # 参与形成此区间的时间周期
structure_types: List[str] # 参与形成的结构类型
boundary_types: List[str] # 参与形成的边界类型
overlap_count: int # 聚类中的原始点数
touch_count: int # MVP: 等于 overlap_count
recency_score: float # 0.0 - 1.0, 1.0 = 最近
ema52_distance_pct: float # 到最近 EMA52 的距离百分比
ema52_aligned: bool # 是否有 EMA52 落在区间内
strength_score: float # 0-100 综合评分
confidence: float # 0.0 - 1.0
first_seen: Optional[str] # 最早的 candle_time
last_seen: Optional[str] # 最晚的 candle_time
metadata: Dict[str, Any] = field(default_factory=dict)
@dataclass
class StructureZoneConfig:
"""StructureZone 提取与评分配置"""
cluster_radius_pct: float = 0.5 # 价格聚类半径(百分比)
min_overlap_for_zone: int = 2 # 最少重叠点数才能形成区间
max_zones: int = 20 # 返回的最大区间数
recency_halflife_bars: int = 50 # recency 衰减半衰期(K线数)
zone_timeframes: List[str] = field(default_factory=lambda: ['4h', '1h', '30m', '15m', '5m'])
kl_lines_per_tf: int = 500 # 每个时间周期使用最近多少根K线
structure_weights: Dict[str, float] = field(default_factory=lambda: {
'bi_zhongshu': 1.0, # 笔中枢 — 最直接的价格行为
'xd_zhongshu': 0.8, # 线段中枢 — 较高级别但粒度较粗
'ema52': 0.4, # EMA — 趋势参考,弱于结构
})
# ============================================================
# Extraction
# ============================================================
def extract_raw_points_from_tf_df(
tf_df_dict: Dict[str, Any],
ema_symbols: List[str],
config: StructureZoneConfig,
) -> List[RawZonePoint]:
"""
从 ChanLun.tf_df_dict 中提取所有原始价格点。
仅处理 config.zone_timeframes 中存在的时间周期。
"""
points: List[RawZonePoint] = []
for tf_name in config.zone_timeframes:
if tf_name not in tf_df_dict:
continue
tf_df = tf_df_dict[tf_name]
# 1. 笔中枢 (ChanBIZS)
try:
if hasattr(tf_df, 'seg_list') and tf_df.seg_list:
bi_zs_result = tf_df.cal_bi_zs(tf_df.seg_list)
if bi_zs_result:
_extract_from_zs_objects(
points, tf_name, 'bi_zhongshu', bi_zs_result, config.kl_lines_per_tf
)
except Exception:
pass
# 2. 线段中枢 (ChanZS)
try:
zs_list = getattr(tf_df, 'zs_list', None)
if zs_list:
_extract_from_zs_objects(
points, tf_name, 'xd_zhongshu', zs_list, config.kl_lines_per_tf
)
except Exception:
pass
# 3. EMA52 值
for tf_name in config.zone_timeframes:
if tf_name in tf_df_dict:
try:
ema_val = tf_df_dict[tf_name].get_ema52()
if ema_val is not None and ema_val > 0:
points.append(RawZonePoint(
price=float(ema_val),
timeframe=tf_name,
structure_type='ema52',
boundary_type='EMA52',
source_zs_id=-1,
is_sure=True,
candle_time=None,
))
except Exception:
pass
return points
def _extract_from_zs_objects(
points: List[RawZonePoint],
tf_name: str,
structure_type: str,
zs_list,
kl_limit: int,
):
"""从 ZS 链表中提取 ZD/ZG/GG/DD 点"""
count = 0
node = zs_list
while hasattr(node, 'next'):
node = node.next
# 从链表头开始遍历
head = zs_list
# 收集所有节点
all_nodes = []
cur = head
while cur is not None and hasattr(cur, 'next'):
all_nodes.append(cur)
cur = cur.next
# 只取最近 kl_limit 根K线内的 ZS
all_nodes = all_nodes[-kl_limit:] if len(all_nodes) > kl_limit else all_nodes
for idx, zs in enumerate(all_nodes):
if not getattr(zs, 'is_sure', False):
continue
try:
zg = float(zs.zg)
zd = float(zs.zd)
gg = float(zs.gg) if getattr(zs, 'gg', 0) else zg
dd = float(zs.dd) if getattr(zs, 'dd', 0) else zd
end_time = str(zs.end_time) if hasattr(zs, 'end_time') and zs.end_time else None
except (ValueError, TypeError, AttributeError):
continue
if zg <= 0 or zd <= 0:
continue
zs_id = getattr(zs, 'index', idx)
points.append(RawZonePoint(price=zg, timeframe=tf_name, structure_type=structure_type,
boundary_type='ZG', source_zs_id=zs_id, is_sure=True,
candle_time=end_time))
points.append(RawZonePoint(price=zd, timeframe=tf_name, structure_type=structure_type,
boundary_type='ZD', source_zs_id=zs_id, is_sure=True,
candle_time=end_time))
points.append(RawZonePoint(price=gg, timeframe=tf_name, structure_type=structure_type,
boundary_type='GG', source_zs_id=zs_id, is_sure=True,
candle_time=end_time))
points.append(RawZonePoint(price=dd, timeframe=tf_name, structure_type=structure_type,
boundary_type='DD', source_zs_id=zs_id, is_sure=True,
candle_time=end_time))
def extract_raw_points_from_serialized(
analyses: Dict[str, Dict],
ema52_dict: Dict[str, Optional[float]],
config: StructureZoneConfig,
) -> List[RawZonePoint]:
"""
从已序列化的分析结果中提取价格点(用于 web API,避免重复计算)。
analyses: {'5m': {'zs_list': [...], 'bi_zs_list': [...]}, '15m': {...}, ...}
ema52_dict: {'5m': 123.45, '15m': None, ...}
"""
points: List[RawZonePoint] = []
for tf_name in config.zone_timeframes:
if tf_name not in analyses:
continue
analysis = analyses[tf_name]
# 笔中枢
bi_zs_items = analysis.get('bi_zs_list', [])
for idx, zs in enumerate(bi_zs_items):
if not zs.get('is_sure', False):
continue
try:
zg = float(zs['zg']); zd = float(zs['zd'])
gg = float(zs.get('gg', zg)); dd = float(zs.get('dd', zd))
end_time = zs.get('end_time')
except (ValueError, KeyError):
continue
if zg <= 0 or zd <= 0:
continue
points.append(RawZonePoint(price=zg, timeframe=tf_name, structure_type='bi_zhongshu',
boundary_type='ZG', source_zs_id=idx, is_sure=True,
candle_time=str(end_time) if end_time else None))
points.append(RawZonePoint(price=zd, timeframe=tf_name, structure_type='bi_zhongshu',
boundary_type='ZD', source_zs_id=idx, is_sure=True,
candle_time=str(end_time) if end_time else None))
points.append(RawZonePoint(price=gg, timeframe=tf_name, structure_type='bi_zhongshu',
boundary_type='GG', source_zs_id=idx, is_sure=True,
candle_time=str(end_time) if end_time else None))
points.append(RawZonePoint(price=dd, timeframe=tf_name, structure_type='bi_zhongshu',
boundary_type='DD', source_zs_id=idx, is_sure=True,
candle_time=str(end_time) if end_time else None))
# 线段中枢
zs_items = analysis.get('zs_list', [])
for idx, zs in enumerate(zs_items):
if not zs.get('is_sure', False):
continue
try:
zg = float(zs['zg']); zd = float(zs['zd'])
gg = float(zs.get('gg', zg)); dd = float(zs.get('dd', zd))
end_time = zs.get('end_time')
except (ValueError, KeyError):
continue
if zg <= 0 or zd <= 0:
continue
points.append(RawZonePoint(price=zg, timeframe=tf_name, structure_type='xd_zhongshu',
boundary_type='ZG', source_zs_id=idx, is_sure=True,
candle_time=str(end_time) if end_time else None))
points.append(RawZonePoint(price=zd, timeframe=tf_name, structure_type='xd_zhongshu',
boundary_type='ZD', source_zs_id=idx, is_sure=True,
candle_time=str(end_time) if end_time else None))
points.append(RawZonePoint(price=gg, timeframe=tf_name, structure_type='xd_zhongshu',
boundary_type='GG', source_zs_id=idx, is_sure=True,
candle_time=str(end_time) if end_time else None))
points.append(RawZonePoint(price=dd, timeframe=tf_name, structure_type='xd_zhongshu',
boundary_type='DD', source_zs_id=idx, is_sure=True,
candle_time=str(end_time) if end_time else None))
# EMA52
for tf_name in config.zone_timeframes:
ema_val = ema52_dict.get(tf_name)
if ema_val is not None and ema_val > 0:
points.append(RawZonePoint(
price=float(ema_val),
timeframe=tf_name,
structure_type='ema52',
boundary_type='EMA52',
source_zs_id=-1,
is_sure=True,
candle_time=None,
))
return points
# ============================================================
# Clustering
# ============================================================
def cluster_raw_points(
points: List[RawZonePoint],
config: StructureZoneConfig,
) -> List[List[RawZonePoint]]:
"""
贪心单通聚类:将价格相近的 RawZonePoint 归为一组。
仅在 1D 价格轴上操作,O(n log n)。
"""
if not points:
return []
sorted_points = sorted(points, key=lambda p: p.price)
clusters: List[List[RawZonePoint]] = []
for p in sorted_points:
placed = False
for cluster in reversed(clusters):
# 检查是否可以放入当前聚类(与聚类均价比较)
avg_price = sum(pt.price for pt in cluster) / len(cluster)
if abs(p.price - avg_price) / avg_price * 100 <= config.cluster_radius_pct:
cluster.append(p)
placed = True
break
if not placed:
clusters.append([p])
# 过滤点数不足的聚类
return [c for c in clusters if len(c) >= config.min_overlap_for_zone]
# ============================================================
# Scoring & Building
# ============================================================
def build_structure_zones(
clusters: List[List[RawZonePoint]],
current_price: float,
ema52_values: Dict[str, Optional[float]],
latest_candle_time: Optional[str],
config: StructureZoneConfig,
) -> List[StructureZone]:
"""
从聚类构建 StructureZone 列表,计算所有字段和评分。
"""
zones: List[StructureZone] = []
# 收集所有 EMA52 值
ema_prices = [v for v in ema52_values.values() if v is not None and v > 0]
for zone_id, cluster in enumerate(clusters):
prices = [p.price for p in cluster]
lower = min(prices)
upper = max(prices)
center = (lower + upper) / 2
width_pct = (upper - lower) / center * 100 if center > 0 else 0.0
# 区间类型
if upper < current_price:
zone_type = 'support' # 区间在当前价格下方 → 支撑
elif lower > current_price:
zone_type = 'resistance' # 区间在当前价格上方 → 阻力
else:
zone_type = 'neutral' # 区间跨越当前价格
timeframes = sorted(set(p.timeframe for p in cluster))
structure_types = sorted(set(p.structure_type for p in cluster))
boundary_types = sorted(set(p.boundary_type for p in cluster))
overlap_count = len(cluster)
# Recency
times = [p.candle_time for p in cluster if p.candle_time]
first_seen = min(times) if times else None
last_seen = max(times) if times else None
recency_score = _calc_recency(last_seen, latest_candle_time, config.recency_halflife_bars)
# EMA52 alignment
ema52_distance_pct = 999.0
ema52_aligned = False
if ema_prices:
distances = [abs(center - ep) / ep * 100 for ep in ema_prices]
ema52_distance_pct = round(min(distances), 2)
ema52_aligned = any(lower <= ep <= upper for ep in ema_prices)
# Strength score
strength_score = _calc_strength(cluster, config, recency_score, ema52_aligned, ema52_distance_pct, width_pct)
# Confidence
confidence = _calc_confidence(overlap_count, len(timeframes), cluster)
zones.append(StructureZone(
id=zone_id + 1,
lower=round(lower, 2),
upper=round(upper, 2),
center=round(center, 2),
width_pct=round(width_pct, 2),
zone_type=zone_type,
timeframes=timeframes,
structure_types=structure_types,
boundary_types=boundary_types,
overlap_count=overlap_count,
touch_count=overlap_count, # MVP: 等于 overlap_count
recency_score=round(recency_score, 3),
ema52_distance_pct=ema52_distance_pct,
ema52_aligned=ema52_aligned,
strength_score=round(strength_score, 1),
confidence=round(confidence, 2),
first_seen=first_seen,
last_seen=last_seen,
))
# 按强度降序排列
zones.sort(key=lambda z: z.strength_score, reverse=True)
# 截断
if config.max_zones > 0 and len(zones) > config.max_zones:
zones = zones[:config.max_zones]
return zones
def _calc_recency(
last_seen: Optional[str],
latest_time: Optional[str],
halflife_bars: int,
) -> float:
"""计算 recency 分数:越近越高"""
if not last_seen or not latest_time:
return 0.5
try:
# 尝试解析 ISO 格式时间
from dateutil import parser
t_last = parser.parse(last_seen)
t_latest = parser.parse(latest_time)
offset_seconds = (t_latest - t_last).total_seconds()
if offset_seconds < 0:
return 1.0
# 假设每根K线平均 5 分钟
bar_seconds = 300
offset_bars = offset_seconds / bar_seconds
# 指数衰减: 2 ^ (-offset / halflife)
score = 2.0 ** (-offset_bars / halflife_bars)
return float(score)
except Exception:
return 0.5
def _calc_strength(
cluster: List[RawZonePoint],
config: StructureZoneConfig,
recency_score: float,
ema52_aligned: bool,
ema52_distance_pct: float,
width_pct: float,
) -> float:
"""计算综合强度评分 (0-100)"""
# 组件 1: 结构类型多样性 (0-40)
structure_type_counts: Dict[str, int] = {}
for p in cluster:
structure_type_counts[p.structure_type] = structure_type_counts.get(p.structure_type, 0) + 1
total = sum(structure_type_counts.values())
structure_score = 0.0
for st, count in structure_type_counts.items():
weight = config.structure_weights.get(st, 0.5)
structure_score += weight * count
structure_score = min(structure_score / max(1, total), 1.0)
c1 = structure_score * 40
# 组件 2: 多周期确认 (0-25)
tf_set = set(p.timeframe for p in cluster)
tf_diversity = len(tf_set)
c2 = min(tf_diversity / 5, 1.0) * 25
# 组件 3: 区间紧密度 (0-15) — 越窄越强
tightness = max(0.0, 1.0 - (width_pct / 3.0))
c3 = tightness * 15
# 组件 4: Recency (0-10)
c4 = recency_score * 10
# 组件 5: EMA52 共振 (0-10)
if ema52_aligned:
ema_proximity = max(0.0, 1.0 - (ema52_distance_pct / 2.0))
c5 = ema_proximity * 10
else:
c5 = 0.0
return c1 + c2 + c3 + c4 + c5
def _calc_confidence(
overlap_count: int,
tf_count: int,
cluster: List[RawZonePoint],
) -> float:
"""计算置信度 (0-1)"""
base = min(overlap_count / 6.0, 0.85)
# 多周期加分
tf_bonus = min(tf_count / 5.0, 0.1)
# 是否所有点都来自 sure 的 ZS
all_sure = all(p.is_sure for p in cluster)
sure_bonus = 0.05 if all_sure else 0.0
return min(base + tf_bonus + sure_bonus, 1.0)
# ============================================================
# Top-level pipeline
# ============================================================
def analyze_structure_zones(
tf_df_dict: Dict[str, Any],
ema_symbols: List[str],
current_price: Optional[float] = None,
config: Optional[StructureZoneConfig] = None,
) -> List[StructureZone]:
"""
一站式分析:提取 → 聚类 → 评分 → 返回排序后的 StructureZone 列表。
"""
if config is None:
config = StructureZoneConfig()
# 提取
raw_points = extract_raw_points_from_tf_df(tf_df_dict, ema_symbols, config)
if not raw_points:
return []
# 获取当前价格
if current_price is None:
for tf_name in config.zone_timeframes:
if tf_name in tf_df_dict:
try:
ema_val = tf_df_dict[tf_name].get_ema52()
if ema_val and ema_val > 0:
current_price = float(ema_val)
break
except Exception:
pass
if current_price is None:
current_price = 0.0
# EMA52 值
ema52_values = {}
for tf_name in config.zone_timeframes:
if tf_name in tf_df_dict:
try:
ema52_values[tf_name] = tf_df_dict[tf_name].get_ema52()
except Exception:
ema52_values[tf_name] = None
# 最晚时间
latest_time = None
times = [p.candle_time for p in raw_points if p.candle_time]
if times:
latest_time = max(times)
# 聚类
clusters = cluster_raw_points(raw_points, config)
# 构建 & 评分
return build_structure_zones(clusters, current_price, ema52_values, latest_time, config)
def analyze_structure_zones_from_serialized(
analyses: Dict[str, Dict],
ema52_dict: Dict[str, Optional[float]],
current_price: float,
config: Optional[StructureZoneConfig] = None,
) -> List[StructureZone]:
"""
从已序列化的分析结果构建 StructureZone(用于 web API)。
"""
if config is None:
config = StructureZoneConfig()
raw_points = extract_raw_points_from_serialized(analyses, ema52_dict, config)
if not raw_points:
return []
# 最晚时间
latest_time = None
times = [p.candle_time for p in raw_points if p.candle_time]
if times:
latest_time = max(times)
# EMA52 值(用于 alignment 检测)
ema_values = {tf: v for tf, v in ema52_dict.items() if v is not None and v > 0}
clusters = cluster_raw_points(raw_points, config)
return build_structure_zones(clusters, current_price, ema_values, latest_time, config)
View File
View File
View File
-682
View File
@@ -1,682 +0,0 @@
"""TF_DF builder mixin — 由 split_tfdf_builders 自动生成,逻辑与原 TF_DF 一致。"""
from __future__ import annotations
from datetime import timedelta
from decimal import Decimal
import numpy as np
import pandas as pd
import talib.abstract as ta
from pandas import DataFrame
from technical.util import resample_to_interval
from chanlun.core.ChanBI import ChanBI
from chanlun.core.ChanBIZS import ChanBIZS
from chanlun.core.ChanBSP import ChanBSP
from chanlun.core.ChanEnum import (
Chan_BI_DIR,
Chan_BSP_DIR,
Chan_BSP_TYPE,
Chan_FX_TYPE,
Chan_K_DIR,
Chan_KLC_FX,
Chan_KLC_STATE,
Chan_KLINE_DIR,
Chan_KLU_PATTERN,
Chan_PRICE_TREND,
Chan_SEG_DIR,
Chan_ZS_DIR,
)
from chanlun.core.ChanKLC import ChanKLC
from chanlun.core.ChanKLU import ChanKLU
from chanlun.core.ChanSBI import ChanSBI
from chanlun.core.ChanSEG import ChanSEG
from chanlun.core.ChanZS import ChanZS, ChanZS_Big
from chanlun.indicators.ChanMACD import ChanMACD
class BiBuilderMixin:
def cal_trend(self, klc_list):
"""
基于价格与EMA24/EMA52的位置关系、以及MACD/Signal/Hist的方向,
为每个KLC打上趋势标签:'UP' / 'DOWN' / 'FLAT'
仅设置 klc.trend,不影响其它字段。
"""
if not klc_list:
return klc_list
last_trend = Chan_PRICE_TREND.UNKNOWN
# 趋势延续性:参考近 N 根已完成的KLC
lookback_n = 5
prev_klcs = []
for klc in klc_list:
price = getattr(klc, 'close', None)
ema24 = getattr(klc, 'ema24', None)
ema52 = getattr(klc, 'ema52', None)
macd_raw = getattr(klc, 'macd', None)
signal_raw = getattr(klc, 'signal', None)
hist_raw = getattr(klc, 'macdhist', None)
macd = macd_raw if macd_raw is not None else 0
signal = signal_raw if signal_raw is not None else 0
hist = hist_raw if hist_raw is not None else 0
rsi = getattr(klc, 'rsi', None)
macd_ready = macd_raw is not None and signal_raw is not None
hist_ready = hist_raw is not None
trend = Chan_PRICE_TREND.UNKNOWN
score = 0
try:
# 有效性
price_valid = price is not None and price != 0
ema24_valid = ema24 is not None and ema24 != 0
ema52_valid = ema52 is not None and ema52 != 0
# 多因子投票
# 1) 均线结构 + 价位
if ema24_valid or ema52_valid:
ma_votes = 0
if ema24_valid and ema52_valid:
ma_votes += 1 if ema24 > ema52 else -1
if price_valid and ema24_valid:
ma_votes += 1 if price > ema24 else 0
if price_valid and ema52_valid:
ma_votes += 1 if price > ema52 else -1
# 限幅,避免相关因子重复计分
score += max(-2, min(2, ma_votes))
# 2) MACD结构
if macd_ready:
score += 1 if macd >= signal else -1
if hist_ready and hist != 0:
score += 1 if hist > 0 else -1
# 3) 动量与均线差分斜率
pre = getattr(klc, 'pre', None)
pre_hist = getattr(pre, 'macdhist', None) if pre else None
if pre:
pre_close = getattr(pre, 'close', None)
if price_valid and pre_close is not None:
score += 1 if price >= pre_close else -1
pre_ema24 = getattr(pre, 'ema24', None)
pre_ema52 = getattr(pre, 'ema52', None)
if ema24_valid and ema52_valid and pre_ema24 not in (None, 0) and pre_ema52 not in (None, 0):
spread_now = ema24 - ema52
spread_pre = pre_ema24 - pre_ema52
score += 1 if spread_now >= spread_pre else -1
# 3.1) MACD柱体动量趋势:考虑 macdhist 的斜率与过零
if hist_ready and pre_hist is not None:
# 柱体斜率:上升加分,下降减分
if hist > pre_hist:
score += 1
elif hist < pre_hist:
score -= 1
# 过零加权:负转正更偏多,正转负更偏空
if pre_hist < 0 and hist > 0:
score += 1
elif pre_hist > 0 and hist < 0:
score -= 1
# 3.2) EMA52 突破/跌破加权
if ema52_valid and price_valid and pre_close is not None and pre_ema52 not in (None, 0):
# 看多突破:从均线下方上破且动量配合
if pre_close <= pre_ema52 and price > ema52 and (hist is None or pre_hist is None or hist >= pre_hist):
score += 1
# 看空跌破:从均线上方下破且动量配合
if pre_close >= pre_ema52 and price < ema52 and (hist is None or pre_hist is None or hist <= pre_hist):
score -= 1
# 3.3) EMA52 支撑/阻力触碰(非强穿越)
if ema52_valid and price_valid:
low_v = getattr(klc, 'low', None)
high_v = getattr(klc, 'high', None)
if low_v is not None and high_v is not None and ema52 not in (None, 0):
# 触碰容差(相对EMA52的0.15%
touch_tol = 0.0015
# 作为支撑:收盘在上,最低靠近EMA52
near_support_touch = (price > ema52) and (abs(low_v - ema52) / abs(ema52) <= touch_tol)
# 作为阻力:收盘在下,最高靠近EMA52
near_resistance_touch = (price < ema52) and (abs(high_v - ema52) / abs(ema52) <= touch_tol)
if near_support_touch:
# 若动量不弱,则更偏多
score += 1 if (hist is None or pre_hist is None or hist >= pre_hist) else 0
if near_resistance_touch:
# 若动量不强,则更偏空
score -= 1 if (hist is None or pre_hist is None or hist <= pre_hist) else 0
# 3.4) 多次对 EMA52 的"拒绝"配合 MACD 逆向:易形成压/支并反向
# 统计近窗口内的上/下拒绝次数:
# - 上拒绝:价格位于 EMA52 下方,最高触及/越过 EMA52 但收盘仍在下方
# - 下拒绝:价格位于 EMA52 上方,最低触及/跌破 EMA52 但收盘仍在上方
recent_up_rejects = 0
recent_down_rejects = 0
if ema52_valid:
window_rej = prev_klcs[-lookback_n:] if len(prev_klcs) > 0 else []
rej_tol = 0.0015
for wk in window_rej:
wk_close = getattr(wk, 'close', None)
wk_ema52 = getattr(wk, 'ema52', None)
wk_high = getattr(wk, 'high', None)
wk_low = getattr(wk, 'low', None)
if wk_close is None or wk_ema52 in (None, 0):
continue
# 上拒绝(阻力):下方多次试图上破但未站上
if wk_close < wk_ema52 and wk_high is not None:
if wk_high >= wk_ema52 or abs(wk_high - wk_ema52) / abs(wk_ema52) <= rej_tol:
recent_up_rejects += 1
# 下拒绝(支撑):上方多次试图下破但未跌破
if wk_close > wk_ema52 and wk_low is not None:
if wk_low <= wk_ema52 or abs(wk_low - wk_ema52) / abs(wk_ema52) <= rej_tol:
recent_down_rejects += 1
# 定义 MACD 的方向偏好
macd_bias_up = macd_ready and (macd >= signal) and (not hist_ready or pre_hist is None or hist >= pre_hist)
macd_bias_down = macd_ready and (macd <= signal) and (not hist_ready or pre_hist is None or hist <= pre_hist)
# 若多次上拒绝且 MACD 偏空,则更偏向下行;若多次下拒绝且 MACD 偏多,则更偏向上行
if recent_up_rejects >= 2 and macd_bias_down:
score -= 2
if recent_down_rejects >= 2 and macd_bias_up:
score += 2
# 4) RSI 辅助
if rsi is not None:
if rsi >= 55:
score += 1
elif rsi <= 45:
score -= 1
# 5) 指标未就绪回退(EMA/MACD缺失时,用动量与RSI辅助,延续趋势)
has_full_ind = ema24_valid and ema52_valid and not (macd == 0 and signal == 0 and hist == 0)
if not has_full_ind:
# 仅根据价动量/RSI做轻量判断,默认延续 last_trend,除非出现强反向
strong_up = False
strong_down = False
pre = getattr(klc, 'pre', None)
if pre:
pre_close = getattr(pre, 'close', None)
if price_valid and pre_close is not None:
strong_up = (price >= pre_close)
strong_down = (price < pre_close)
if rsi is not None:
if rsi >= 60:
strong_up = True
elif rsi <= 40:
strong_down = True
if last_trend == Chan_PRICE_TREND.UP and not strong_down:
trend = Chan_PRICE_TREND.UP
elif last_trend == Chan_PRICE_TREND.DOWN and not strong_up:
trend = Chan_PRICE_TREND.DOWN
else:
trend = Chan_PRICE_TREND.UP if strong_up and not strong_down else (Chan_PRICE_TREND.DOWN if strong_down and not strong_up else Chan_PRICE_TREND.FLAT)
else:
# 6) 震荡过滤(仅当极近EMA52且MACD贴合时判作震荡)
near_flat = False
if price_valid and ema52_valid:
near_ema52 = abs(price - ema52) / abs(ema52) <= 0.0005 # 0.05%
if macd_ready:
macd_scale = max(abs(macd), abs(signal), 1e-6)
near_macd = abs(macd - signal) / macd_scale <= 0.05
else:
near_macd = False
near_flat = near_ema52 and near_macd
# 7) 动态阈值 + 趋势记忆(更强粘滞:趋势中容忍小幅反分)
# 引入过去 N 根KLC 的趋势延续性来动态调整翻转阈值,并结合 EMA52 支撑/阻力触碰强化门槛
force_flip_down = False
force_flip_up = False
if near_flat:
trend = Chan_PRICE_TREND.FLAT
else:
# 计算过去窗口的趋势一致性
window = prev_klcs[-lookback_n:] if len(prev_klcs) > 0 else []
persist_up = 0
persist_down = 0
for wk in window:
if getattr(wk, 'trend', None) == Chan_PRICE_TREND.UP:
persist_up += 1
elif getattr(wk, 'trend', None) == Chan_PRICE_TREND.DOWN:
persist_down += 1
persist_ratio_up = (persist_up / len(window)) if len(window) > 0 else 0
persist_ratio_down = (persist_down / len(window)) if len(window) > 0 else 0
# 基准阈值
down_flip_threshold = -2
up_flip_threshold = 2
# 若最近多为UP,则从UP翻转需更强反向信号;同理对DOWN
if last_trend == Chan_PRICE_TREND.UP and persist_ratio_up >= 0.6:
down_flip_threshold = -3
elif last_trend == Chan_PRICE_TREND.DOWN and persist_ratio_down >= 0.6:
up_flip_threshold = 3
# EMA52 触碰强化门槛:UP时若出现支撑触碰,下翻更难;DOWN时若出现阻力触碰,上翻更难
if ema52_valid and price_valid:
low_v = getattr(klc, 'low', None)
high_v = getattr(klc, 'high', None)
if low_v is not None and high_v is not None and ema52 not in (None, 0):
touch_tol = 0.0015
near_support_touch = (price > ema52) and (abs(low_v - ema52) / abs(ema52) <= touch_tol)
near_resistance_touch = (price < ema52) and (abs(high_v - ema52) / abs(ema52) <= touch_tol)
if last_trend == Chan_PRICE_TREND.UP and near_support_touch:
# 强化维持UP:进一步降低向下翻转阈值
down_flip_threshold = min(down_flip_threshold - 1, -3)
if last_trend == Chan_PRICE_TREND.DOWN and near_resistance_touch:
# 强化维持DOWN:进一步提高向上翻转阈值
up_flip_threshold = max(up_flip_threshold + 1, 3)
# 7.1) 复合拐头信号:MACD/Signal 同向拐头 + hist 连续减弱 + 多次未能越过 EMA52
pre_macd = getattr(pre, 'macd', None) if pre else None
pre_signal = getattr(pre, 'signal', None) if pre else None
macd_slope = (macd - pre_macd) if (macd_ready and pre_macd is not None) else 0
signal_slope = (signal - pre_signal) if (macd_ready and pre_signal is not None) else 0
# hist 连续减弱(绝对值缩小)
hist_seq = []
for wk in prev_klcs[-2:]:
val = getattr(wk, 'macdhist', None)
if val is not None:
hist_seq.append(val)
if hist is not None:
hist_seq.append(hist)
weaken_steps = 0
for i in range(1, len(hist_seq)):
if abs(hist_seq[i]) < abs(hist_seq[i-1]):
weaken_steps += 1
# 近窗口对 EMA52 的"未能站上/跌破"统计(放宽窗口与条件)
window_ema = prev_klcs[-4:] if len(prev_klcs) > 0 else []
no_up_break = False
no_down_break = False
if ema52_valid:
# 未能有效上破:最近若干根收盘大多数不在 EMA52 上方,且高点多次触及/接近
cnt_touch_up = 0
cnt_close_above = 0
for wk in window_ema:
wk_close = getattr(wk, 'close', None)
wk_high = getattr(wk, 'high', None)
wk_ema = getattr(wk, 'ema52', None)
if wk_close is not None and wk_ema not in (None, 0):
if wk_close > wk_ema:
cnt_close_above += 1
if wk_high is not None and (wk_high >= wk_ema or abs(wk_high - wk_ema) / abs(wk_ema) <= 0.0015):
cnt_touch_up += 1
no_up_break = (cnt_close_above <= 1 and cnt_touch_up >= 1 and price <= ema52)
# 未能有效下破:最近若干根收盘大多数不在 EMA52 下方,且低点多次触及/接近
cnt_touch_down = 0
cnt_close_below = 0
for wk in window_ema:
wk_close = getattr(wk, 'close', None)
wk_low = getattr(wk, 'low', None)
wk_ema = getattr(wk, 'ema52', None)
if wk_close is not None and wk_ema not in (None, 0):
if wk_close < wk_ema:
cnt_close_below += 1
if wk_low is not None and (wk_low <= wk_ema or abs(wk_low - wk_ema) / abs(wk_ema) <= 0.0015):
cnt_touch_down += 1
no_down_break = (cnt_close_below <= 1 and cnt_touch_down >= 1 and price >= ema52)
# 若当前为UP趋势,出现明显拐头+hist减弱+未能上破EMA52,则加速看空
if last_trend == Chan_PRICE_TREND.UP and macd_slope < 0 and signal_slope < 0 and weaken_steps >= 1 and no_up_break and macd_bias_down:
score -= 3
down_flip_threshold = max(down_flip_threshold, 0)
force_flip_down = True
# 若当前为DOWN趋势,出现明显拐头+hist减弱+未能下破EMA52,则加速看多
if last_trend == Chan_PRICE_TREND.DOWN and macd_slope > 0 and signal_slope > 0 and weaken_steps >= 1 and no_down_break and macd_bias_up:
score += 3
up_flip_threshold = min(up_flip_threshold, 0)
force_flip_up = True
# 多次对 EMA52 的拒绝配合 MACD 逆向:加速反向翻转(降低相反方向阈值)
if recent_up_rejects >= 2 and macd_bias_down:
# 从 UP 向 DOWN 的翻转更容易
down_flip_threshold = max(down_flip_threshold, -1)
if recent_down_rejects >= 2 and macd_bias_up:
# 从 DOWN 向 UP 的翻转更容易
up_flip_threshold = min(up_flip_threshold, 1)
if force_flip_down:
trend = Chan_PRICE_TREND.DOWN
elif force_flip_up:
trend = Chan_PRICE_TREND.UP
elif last_trend == Chan_PRICE_TREND.UP:
if score <= down_flip_threshold:
trend = Chan_PRICE_TREND.DOWN
else:
trend = Chan_PRICE_TREND.UP
elif last_trend == Chan_PRICE_TREND.DOWN:
if score >= up_flip_threshold:
trend = Chan_PRICE_TREND.UP
else:
trend = Chan_PRICE_TREND.DOWN
else:
# 初始无记忆时,降低进入门槛
if score >= 1:
trend = Chan_PRICE_TREND.UP
elif score <= -1:
trend = Chan_PRICE_TREND.DOWN
else:
trend = Chan_PRICE_TREND.FLAT
except Exception:
trend = Chan_PRICE_TREND.UNKNOWN
# 写回趋势
if klc.end_time is None:
trend = Chan_PRICE_TREND.FLAT
if hasattr(klc, 'set_trend'):
klc.set_trend(trend)
else:
setattr(klc, 'trend', trend)
last_trend = trend
# 更新滑窗:仅向后看
prev_klcs.append(klc)
price_diff = klc.close - klc.pre.close if klc.pre else 0
#if klc.index > len(klc_list) - 10:
#print(klc.start_time, klc.end_time, klc.close, klc.ema24, klc.ema52, klc.macd, klc.signal, klc.macdhist, klc.trend, price_diff, score)
#print(klc.start_time, klc.end_time, klc.trend, price_diff, score)
return klc_list
def get_bi_list(self, dataframe):
bi_list = self.cal_bi_list(self.get_klc_list(dataframe))
#bi_list = self.cal_bi_list_chanlun(self.get_klc_list(dataframe))
return bi_list
def cal_bi_list(self, klc_list):
bi_list = []
last_top = None
last_bottom = None
bi_klc_min = 4
last_fx_klc = None
for klc in klc_list:
if last_fx_klc:
klc.check_klc_state(last_fx_klc)
klc.check_fx_confirmed(last_top, last_bottom)
fx = self.check_fx(klc)
if fx == Chan_FX_TYPE.TOP:
if last_bottom:
if self.check_top_fx(last_bottom, klc) == False:
fx = Chan_FX_TYPE.UNKNOWN
if fx == Chan_FX_TYPE.BOTTOM:
if last_top:
if self.check_bottom_fx(last_top, klc) == False:
#print(klc.end_time, last_top.end_time, "---")
fx = Chan_FX_TYPE.UNKNOWN
# Do nothing
if fx == Chan_FX_TYPE.UNKNOWN:
if len(bi_list) > 0:
bi_list[-1].add_klc(klc)
klc.set_bi(bi_list[-1])
#continue
if len(bi_list) > 0 and klc.end_klu:
last_bi = bi_list[-1]
#print(klc.start_time, last_bi.start_time, last_bi.end_time, last_bi.dir, last_bi.high, last_bi.low, last_bottom.end_time, "last bi")
if last_top and last_bi.dir == Chan_BI_DIR.DOWN:
if last_bottom and klc.high > last_bi.high:
#print(klc.end_time, "Top 7, 1", last_bi.start_time, klc.high, last_bi.high)
#klc.klc_fx_type = Chan_KLC_FX.TOP7
#klc.fx = Chan_FX_TYPE.TOP
"""
last_bi.set_end_klc(last_bottom, klc)
bi = ChanBI(last_bottom, len(bi_list), Chan_BI_DIR.UP)
#klc.set_klc_fx_type(Chan_KLC_FX.BOTTOM7)
#klc.bb_out = True
last_bi.set_next(bi)
bi.set_pre(last_bi)
for klc_index in range(last_bi.end_klc.index, len(klc_list)):
bi.add_klc(klc_list[klc_index])
bi_list.append(bi)
last_top = klc
klc.set_bi(bi)
#print(klc.start_time, bi.start_time, bi.end_time, bi.dir, bi.high, bi.low, bi.is_sure)
"""
else:
if last_bottom and last_bi.dir == Chan_BI_DIR.UP:
if last_top and klc.low < last_bi.low:
#print(klc.end_time, "Bottom 8, 2", last_bi.start_time)
#klc.klc_fx_type = Chan_KLC_FX.BOTTOM8
#klc.fx = Chan_FX_TYPE.BOTTOM
"""
last_bi.set_end_klc(last_top, klc)
bi = ChanBI(last_top, len(bi_list), Chan_BI_DIR.DOWN)
#klc.set_klc_fx_type(Chan_KLC_FX.TOP6)
#klc.bb_out = True
last_bi.set_next(bi)
bi.set_pre(last_bi)
for klc_index in range(last_bi.end_klc.index, len(klc_list)):
bi.add_klc(klc_list[klc_index])
bi_list.append(bi)
last_bottom = klc
klc.set_bi(bi)
#print(klc.start_time, bi.start_time, bi.end_time, bi.dir, bi.high, bi.low, bi.is_sure)
"""
else:
last_fx_klc = klc
if fx == Chan_FX_TYPE.TOP:
#print(klc.end_time, fx, klc.pre.high, klc.high, klc.pre.start_time, klc.pre.end_time)
if last_top:
if last_bottom:
#print(klc.start_time, last_bottom.start_time, last_top.start_time)
if last_bottom.index < last_top.index:
# Second top lower to be second sell point
if last_top.high > klc.high:
bi_list[-1].add_klc(klc)
klc.set_bi(bi_list[-1])
#klc.set_klc_fx_type(Chan_KLC_FX.TOP3)
#print(klc.end_time, klc.fx, "二类卖点Sell 1")
else:
# A new top found
#last_top.set_fx(Chan_FX_TYPE.UNKNOWN)
last_top = klc
#print(klc.start_time, klc.fx, bi_list[-1].dir, "Last Top Change 1")
klc.set_klc_fx_type(Chan_KLC_FX.TOP1)
self.check_fx_pattern(klc)
#print(klc.end_time, klc.fx, "一类卖点Sell 1")
bi_list[-1].add_klc(klc)
klc.set_bi(bi_list[-1])
# 不满足结合律的分型
else:
#klc.set_klc_fx_type(Chan_KLC_FX.TOP0)
#print(klc.end_time, klc.klc_fx_type)
if last_bottom.index + bi_klc_min > klc.index:
if last_top.high > klc.high:
#print(klc.start_time, klc.fx, "二类卖点Sell 1")
#klc.set_klc_fx_type(Chan_KLC_FX.TOP8)
bi_list[-1].add_klc(klc)
klc.set_bi(bi_list[-1])
# New TOP Found前面的UKNOWN可能出现TOP7,但是这里的也可能出现TOP8分型
else:
# 顶分型在出现2之前超过前一个笔的顶 TOP8
if last_top.index + bi_klc_min < klc.index and len(bi_list) > 1:
pre_last_bi = bi_list[-2]
last_bi = bi_list[-1]
if pre_last_bi.is_sure and not last_bi.is_sure and pre_last_bi.dir == Chan_BI_DIR.UP and False:
#pre_last_bi.update_bi(klc)
bi_list.remove(last_bi)
pre_last_bi.set_next(None)
#last_top.set_fx(Chan_FX_TYPE.PTOP)
last_top = klc
last_bottom = pre_last_bi.start_klc
#print(klc.start_time, klc.fx, bi_list[-1].dir, "Last Top Bottom Change 1")
klc.set_klc_fx_type(Chan_KLC_FX.TOP2)
#print(klc.start_time, last_bi.start_klc.start_time, "New TOP Found reset last bi")
#klc.set_state("10")
#print(klc.start_time, klc.fx, "笔卖点Sell 1")
###klc.set_klc_fx_type(Chan_KLC_FX.TOP2) # when bi is down but the fx is top
bi_list[-1].add_klc(klc)
klc.set_bi(bi_list[-1])
#klc.set_klc_fx_type(Chan_KLC_FX.TOP8)
#print(klc.start_time, last_bi.start_klc.start_time, "New TOP Found reset last bi")
else:
#klc.set_fx(Chan_FX_TYPE.PTOP)
bi_list[-1].add_klc(klc)
klc.set_bi(bi_list[-1])
#print(klc.end_time, klc.fx, "无效顶分型")
# 满足结合律
else:
# New Temp TOP and last bottom confirmed ***** confirm last down bi(last bottom and last top)
last_bi = bi_list[-1]
if not last_bi.is_sure:
last_bi.set_end_klc(last_bottom, klc)
bi = ChanBI(last_bottom, len(bi_list), Chan_BI_DIR.UP)
last_bi.set_next(bi)
bi.set_pre(last_bi)
bi.add_klc(klc)
bi_list.append(bi)
last_top = klc
#print(klc.end_time, klc.fx, bi_list[-1].dir, "Last Top Change 2")
klc.set_klc_fx_type(Chan_KLC_FX.TOP2)
self.check_fx_pattern(klc)
#bi_list[-1].add_klc(klc)
klc.set_bi(bi_list[-1])
#print(klc.start_time, last_bottom.start_time, "Normal TOP Found, Confirm down bi 4")
# last bottom = None 初始化的时候用,其他时间不用
else:
# 初始化的时候用,其他时间不用
if last_top.high < klc.high:
last_bi = bi_list[-1]
last_bi.set_start_klc(klc, Chan_BI_DIR.DOWN)
last_top = klc
#print(klc.start_time, klc.fx, bi_list[-1].dir, "Last Top Change 3")
bi_list[-1].add_klc(klc)
klc.set_bi(bi_list[-1])
# 初始化的时候用,其他时间不用
else:
#klc.set_fx(Chan_FX_TYPE.TT)
#print(klc.start_time, klc.fx, "二类卖点Sell 2")
bi_list[-1].add_klc(klc)
klc.set_bi(bi_list[-1])
# last_top == None 初始化的时候用,其他时间不用
else:
if last_bottom:
# 不满足结合律的分型
if last_bottom.index + bi_klc_min > klc.index:
#klc.set_fx(Chan_FX_TYPE.PTOP)
bi_list[-1].add_klc(klc)
klc.set_bi(bi_list[-1])
#print(klc.start_time, klc.fx, "中枢卖点Sell 1")
else:
# First temp top and last bottom confirmed
last_top = klc
#print(klc.start_time, klc.fx, bi_list[-1].dir, "Last Top Change 4")
bi_list[-1].add_klc(klc)
klc.set_bi(bi_list[-1])
# Last top = None, last bottom = None, create first down bi 初始化的时候用,其他时间不用
else:
# First temp top
last_top = klc
bi = ChanBI(klc, len(bi_list), Chan_BI_DIR.DOWN)
bi_list.append(bi)
bi.add_klc(klc)
klc.set_bi(bi_list[-1])
#print(klc.start_time, klc.fx, bi_list[-1].dir, "Last Top Change 5")
#klc.fx = Bottom ========================
else:
if last_bottom:
if last_top:
# Bottom after top and find a new bottom
if last_top.index < last_bottom.index:
# Second bottom uppper to be second buy point and confirm last bi
if last_bottom.low < klc.low:
bi_list[-1].add_klc(klc)
klc.set_bi(bi_list[-1])
#klc.set_klc_fx_type(Chan_KLC_FX.BOTTOM3)
#print(last_bottom.start_time, last_bottom.end_time, "--------------------------------1")
#print(klc.end_time, klc.fx, "二类买点Buy 1")
else:
# A new bottom found
last_bottom = klc
#print(klc.end_time, klc.fx, bi_list[-1].dir, "Last Bottom Change 1")
klc.set_klc_fx_type(Chan_KLC_FX.BOTTOM1)
self.check_fx_pattern(klc)
#print(klc.end_time, klc.fx, "一类买点Buy 1")
bi_list[-1].add_klc(klc)
klc.set_bi(bi_list[-1])
# 不满足结合律的分型
else:
#klc.set_klc_fx_type(Chan_KLC_FX.BOTTOM0)
#print(klc.end_time, klc.klc_fx_type)
if last_top.index + bi_klc_min > klc.index:
if last_bottom.low < klc.low:
#print(klc.end_time, klc.fx, "中枢买点Buy 1")
bi_list[-1].add_klc(klc)
klc.set_bi(bi_list[-1])
#klc.set_klc_fx_type(Chan_KLC_FX.BOTTOM8)
# Found new bottom没有意义,上面UNKNOWN的时候已经是笔破坏了
else:
#print(klc.end_time, last_bottom.end_time, "Found a new bottom")
if last_bottom.index + bi_klc_min < klc.index and len(bi_list) > 1:
pre_last_bi = bi_list[-2]
last_bi = bi_list[-1]
if pre_last_bi.is_sure and not last_bi.is_sure and pre_last_bi.dir == Chan_BI_DIR.DOWN and False:
#pre_last_bi.update_bi(klc)
bi_list.remove(last_bi)
pre_last_bi.set_next(None)
last_bottom = klc
last_top = pre_last_bi.start_klc
#print(klc.start_time, klc.fx, bi_list[-1].dir, "Last Top Bottom Change 2")
klc.set_klc_fx_type(Chan_KLC_FX.BOTTOM2)
#print(klc.start_time, last_bi.start_klc.start_time, "New BOTTOM Found reset last bi")
#print(klc.start_time, klc.fx, "笔买点Buy 1")
###klc.set_klc_fx_type(Chan_KLC_FX.BOTTOM2) # when bi is up but the fx is bottom
bi_list[-1].add_klc(klc)
klc.set_bi(bi_list[-1])
#klc.set_klc_fx_type(Chan_KLC_FX.BOTTOM8)
else:
#klc.set_fx(Chan_FX_TYPE.UNKNOWN)
bi_list[-1].add_klc(klc)
klc.set_bi(bi_list[-1])
print(klc.end_time, klc.fx, "无效底分型")
# 满足结合律的分型
else:
# New Temp Bottom and last top confirmed ***** confirm last up bi(last bottom and last top)
last_bi = bi_list[-1]
if not last_bi.is_sure:
last_bi.set_end_klc(last_top, klc)
bi = ChanBI(last_top, len(bi_list), Chan_BI_DIR.DOWN)
last_bi.set_next(bi)
bi.set_pre(last_bi)
bi.add_klc(klc)
bi_list.append(bi)
last_bottom = klc
#print(klc.start_time, klc.fx, bi_list[-1].dir, "Last Bottom Change 2")
klc.set_klc_fx_type(Chan_KLC_FX.BOTTOM2)
self.check_fx_pattern(klc)
#bi_list[-1].add_klc(klc)
klc.set_bi(bi_list[-1])
#print(klc.start_time, last_top.start_time, "Normal Bottom Found, Confirm up bi 6")
# last_top = None 初始化的时候用,其他时间不用
else:
if last_bottom.low > klc.low:
last_bi = bi_list[-1]
last_bi.set_start_klc(klc, Chan_BI_DIR.UP)
#last_bottom.set_fx(Chan_FX_TYPE.UNKNOWN)
last_bottom = klc
#print(klc.start_time, klc.fx, bi_list[-1].dir, "Last Bottom Change 3")
bi_list[-1].add_klc(klc)
klc.set_bi(bi_list[-1])
#print(klc.start_time, klc.fx, "笔买点Buy 3")
else:
#klc.set_fx(Chan_FX_TYPE.BB)
#klc.set_state('-20')
#print(klc.start_time, klc.fx, "二类买点Buy 2")
bi_list[-1].add_klc(klc)
klc.set_bi(bi_list[-1])
# last_bottom = None 初始化的时候用,其他时间不用
else:
if last_top:
# 不满足结合律的分型
if last_top.index + bi_klc_min > klc.index:
#klc.set_fx(Chan_FX_TYPE.PBOTTOM)
bi_list[-1].add_klc(klc)
klc.set_bi(bi_list[-1])
#print(klc.start_time, klc.fx, "中枢买点Buy 1")
else:
# First temp bottom and last top confirmed
last_bottom = klc
#print(klc.start_time, klc.fx, bi_list[-1].dir, "Last Bottom Change 4")
bi_list[-1].add_klc(klc)
klc.set_bi(bi_list[-1])
#print(klc.start_time, klc.fx, "一类买点Buy 1")
# Last top = None, last bottom = None, create first up bi
else:
# First temp bottom and no top yet
last_bottom = klc
bi = ChanBI(klc, len(bi_list), Chan_BI_DIR.UP)
#klc.set_klc_fx_type(Chan_KLC_FX.BOTTOM7)
bi_list.append(bi)
bi_list[-1].add_klc(klc)
klc.set_bi(bi_list[-1])
#print(klc.start_time, klc.fx, bi_list[-1].dir, "Last Bottom Change 5")
#print(klc.start_time, klc.fx, "笔买点Buy 4")
self.get_above_zero_bsp(klc_list)
#print(bi_list[-1].start_time, bi_list[-1].end_time, len(bi_list[-1].klc_list))
return bi_list
def check_top_fx(self, last_bottom, klc):
if (last_bottom.high > klc.pre.low or last_bottom.high > klc.next.low) and (klc.index - last_bottom.index < 100):
return False
return True
def check_bottom_fx(self, last_top, klc):
if (last_top.low < klc.pre.high or last_top.low < klc.next.high) and (klc.index - last_top.index < 100):
return False
return True
# 线段内的中枢
-412
View File
@@ -1,412 +0,0 @@
"""TF_DF builder mixin — 由 split_tfdf_builders 自动生成,逻辑与原 TF_DF 一致。"""
from __future__ import annotations
from datetime import timedelta
from decimal import Decimal
import numpy as np
import pandas as pd
import talib.abstract as ta
from pandas import DataFrame
from technical.util import resample_to_interval
from chanlun.core.ChanBI import ChanBI
from chanlun.core.ChanBIZS import ChanBIZS
from chanlun.core.ChanBSP import ChanBSP
from chanlun.core.ChanEnum import (
Chan_BI_DIR,
Chan_BSP_DIR,
Chan_BSP_TYPE,
Chan_FX_TYPE,
Chan_K_DIR,
Chan_KLC_FX,
Chan_KLC_STATE,
Chan_KLINE_DIR,
Chan_KLU_PATTERN,
Chan_PRICE_TREND,
Chan_SEG_DIR,
Chan_ZS_DIR,
)
from chanlun.core.ChanKLC import ChanKLC
from chanlun.core.ChanKLU import ChanKLU
from chanlun.core.ChanSBI import ChanSBI
from chanlun.core.ChanSEG import ChanSEG
from chanlun.core.ChanZS import ChanZS, ChanZS_Big
from chanlun.indicators.ChanMACD import ChanMACD
class BspBuilderMixin:
def get_bsp_state(self, dataframe):
klu_list = self.get_klu_list(dataframe)
klc_list = self.get_klc_list(klu_list)
bi_list = self.cal_bi_list(klc_list)
seg_list = self.get_seg_list(bi_list)
bi_zs_list = self.cal_bi_zs(seg_list)
bsp_list = self.find_all_bsp(bi_list, bi_zs_list)
bsp_state_list = [0] * len(dataframe)
klc_index = 0
for index in range(0, len(dataframe)):
if klc_index == len(klc_list):
klc_index = len(klc_list) - 1
klc = klc_list[klc_index]
if klc.end_klu and klc.end_klu.idx == index:
if klc.klc_fx_type == Chan_KLC_FX.TOP2:
bi = klc.bi.pre
if bi and bi.is_sure and bi.end_klc.bsp_type == Chan_BSP_TYPE.B3:
# 第三类买点
bsp_state_list[index] = -1
#print(klc.end_time, "B3")
else:
bsp_state_list[index] = 0
elif klc.klc_fx_type == Chan_KLC_FX.BOTTOM2:
bi = klc.bi.pre
if bi and bi.is_sure and bi.end_klc.bsp_type == Chan_BSP_TYPE.S3:
# 第三类卖点
bsp_state_list[index] = 1
#print(klc.end_time, "S3")
else:
bsp_state_list[index] = 0
klc_index += 1
else:
bsp_state_list[index] = 0
return bsp_state_list
def get_above_zero_bsp(self, klc_list):
buy_bsp_list = []
sell_bsp_list = []
above_zero = False
buy_bsp = None
sell_bsp = None
for klc in klc_list:
if klc.pre and klc.pre.signal < 0 and klc.signal > 0:
above_zero = True
if klc.pre and klc.pre.signal > 0 and klc.signal < 0:
above_zero = False
if above_zero and klc.klc_fx_type == Chan_KLC_FX.BOTTOM2 and klc.macd > 0:
buy_bsp = klc
buy_bsp_list.append(klc)
#print(klc.end_time, "MACD 0轴上穿,回调笔底分型做多")
if buy_bsp and klc.pre and klc.pre.macdhist > 0 and klc.macdhist < 0:
sell_bsp = klc
sell_bsp_list.append(klc)
buy_bsp = None
#print(klc.end_time, "Sell BSP Found")
return buy_bsp_list
def find_all_bsp(self, bi_list, bi_zs_list):
"""
笔中枢的三类买卖点识别
三类买点:中枢形成后,一笔向上离开中枢(低点 > zg),
随后回拉的一笔低点不跌回中枢(低点 >= zg),确认支撑有效。
三类卖点:中枢形成后,一笔向下离开中枢(高点 < zd),
随后反弹的一笔高点不回到中枢(高点 <= zd),确认压力有效。
参数:
bi_list: 笔列表
bi_zs_list: 笔中枢列表(二维列表,每个seg内的中枢列表)
返回:
bsp_list: ChanBSP 列表,包含所有识别到的三类买卖点
"""
bsp_list = []
if len(bi_list) < 4 or len(bi_zs_list) == 0:
return bsp_list
for zs in bi_zs_list:
if not zs.is_sure or len(zs.bi_list) < 3:
continue
#print(zs.start_time, zs.end_time, zs.dir, zs.is_sure, len(zs.bi_list))
# 中枢结束后的第一笔(离开笔)
last_zs_bi = zs.bi_list[-1]
if last_zs_bi.dir == Chan_BI_DIR.UP:
if last_zs_bi.is_sure and last_zs_bi.end_klc.high <= zs.zg or (last_zs_bi.next and last_zs_bi.next.is_sure and last_zs_bi.next.end_klc.low < zs.zd):
leave_bi = last_zs_bi.next
else:
leave_bi = last_zs_bi
else:
if last_zs_bi.is_sure and last_zs_bi.end_klc.low >= zs.zd or (last_zs_bi.next and last_zs_bi.next.is_sure and last_zs_bi.next.end_klc.high > zs.zg):
leave_bi = last_zs_bi.next
else:
leave_bi = last_zs_bi
#print(zs.zg, zs.zd)
if leave_bi is None or not leave_bi.is_sure:
continue
if (zs.dir == Chan_ZS_DIR.UP and leave_bi.dir == Chan_BI_DIR.UP and leave_bi.end_klc.high < zs.zg and leave_bi.end_klc.high > zs.zd) or (zs.dir == Chan_ZS_DIR.DOWN and leave_bi.dir == Chan_BI_DIR.DOWN and leave_bi.end_klc.low < zs.zg and leave_bi.end_klc.low > zs.zd):
#print("--------------------", leave_bi.dir, leave_bi.end_klc.high, leave_bi.end_klc.low, zs.zg, zs.zd)
leave_bi = leave_bi.next
# 三类买点:向上离开中枢后回拉不破 zg
#print("Leave bi:", leave_bi.start_time, leave_bi.end_time, leave_bi.dir, leave_bi.is_sure, leave_bi.low, leave_bi.high)
if leave_bi.dir == Chan_BI_DIR.UP:
first_bsp_bi_div = self.check_bi_div(zs, leave_bi)
# 确认一类卖点:离开断能量小于进入段能量
if first_bsp_bi_div:
bsp = ChanBSP(
leave_bi, len(bsp_list),
Chan_BSP_TYPE.S1,
Chan_BSP_DIR.SELL,
leave_bi.sure_time,
zs.index+1, zs, None
)
leave_bi.end_klc.set_bsp_type(Chan_BSP_TYPE.S1)
bsp_list.append(bsp)
# 回拉笔
pullback_bi = leave_bi.next
#print(pullback_bi.start_klc.start_time, pullback_bi.dir, pullback_bi.is_sure, pullback_bi.low, pullback_bi.high)
if pullback_bi and pullback_bi.is_sure and pullback_bi.dir == Chan_BI_DIR.DOWN:
if pullback_bi.low >= zs.zg:
# 确认三类买点:回拉笔的低点不跌回中枢
bsp = ChanBSP(
pullback_bi, len(bsp_list),
Chan_BSP_TYPE.B3,
Chan_BSP_DIR.BUY,
pullback_bi.sure_time,
zs.index+1, zs, None
)
pullback_bi.end_klc.set_bsp_type(Chan_BSP_TYPE.B3)
bsp_list.append(bsp)
# 二类卖点
if first_bsp_bi_div:
second_bsp_bi = pullback_bi.next
if second_bsp_bi and second_bsp_bi.is_sure and second_bsp_bi.end_klc.high < leave_bi.end_klc.high:
# 确认二类卖点:一类卖点后回拉不超过一类卖点高点
bsp = ChanBSP(
second_bsp_bi, len(bsp_list),
Chan_BSP_TYPE.S2,
Chan_BSP_DIR.SELL,
second_bsp_bi.sure_time,
zs.index+1, zs, None
)
second_bsp_bi.end_klc.set_bsp_type(Chan_BSP_TYPE.B2)
bsp_list.append(bsp)
# 三类卖点:向下离开中枢后反弹不破 zd
elif leave_bi.dir == Chan_BI_DIR.DOWN:
first_bsp_bi_div = self.check_bi_div(zs, leave_bi)
# 确认一类买点:离开段能量小于进入段
if first_bsp_bi_div:
bsp = ChanBSP(
leave_bi, len(bsp_list),
Chan_BSP_TYPE.B1,
Chan_BSP_DIR.BUY,
leave_bi.sure_time,
zs.index+1, zs, None
)
leave_bi.end_klc.set_bsp_type(Chan_BSP_TYPE.B1)
bsp_list.append(bsp)
# 反弹笔
bounce_bi = leave_bi.next
#print(bounce_bi.start_klc.start_time, bounce_bi.dir, bounce_bi.is_sure, bounce_bi.low, bounce_bi.high)
if bounce_bi and bounce_bi.is_sure and bounce_bi.dir == Chan_BI_DIR.UP:
if bounce_bi.high <= zs.zd:
# 确认三类卖点:反弹笔的高点不回到中枢
bsp = ChanBSP(
bounce_bi, len(bsp_list),
Chan_BSP_TYPE.S3,
Chan_BSP_DIR.SELL,
bounce_bi.sure_time,
zs.index+1, zs, None
)
bounce_bi.end_klc.set_bsp_type(Chan_BSP_TYPE.S3)
bsp_list.append(bsp)
# 二类卖点
if first_bsp_bi_div:
second_bsp_bi = bounce_bi.next
if second_bsp_bi and second_bsp_bi.is_sure and second_bsp_bi.end_klc.low > leave_bi.end_klc.low:
# 确认二类买点:一类买点后回拉不超过一类卖点高点
bsp = ChanBSP(
second_bsp_bi, len(bsp_list),
Chan_BSP_TYPE.B2,
Chan_BSP_DIR.BUY,
second_bsp_bi.sure_time,
zs.index+1, zs, None
)
second_bsp_bi.end_klc.set_bsp_type(Chan_BSP_TYPE.B2)
bsp_list.append(bsp)
return bsp_list
def check_bi_div(self, zs, leave_bi):
enter_bi = zs.bi_list[0].pre
macdhist_div = 0
if enter_bi and enter_bi.dir == leave_bi.dir:
macdhist_div = abs(leave_bi.macd_hist) - abs(enter_bi.macd_hist)
#print(enter_bi.end_time, leave_bi.end_time, macdhist_div < 0)
return macdhist_div < 0
def find_first_bsp(self, bi_list, bi_zs_list):
"""
笔中枢的一类买卖点识别
一类买点:下跌趋势中,最后一个中枢完成后,向下离开中枢的笔创新低,
但该笔与进入中枢前的最后一笔下跌形成底背驰(力度减弱),
即趋势力竭的转折点。
一类卖点:上涨趋势中,最后一个中枢完成后,向上离开中枢的笔创新高,
但该笔与进入中枢前的最后一笔上涨形成顶背驰(力度减弱),
即趋势力竭的转折点。
简化判断:中枢形成后,离开中枢的笔(突破笔)本身即为一类买卖点的触发笔。
参数:
bi_list: 笔列表
bi_zs_list: 笔中枢列表(扁平列表,每个元素是一个中枢对象)
返回:
bsp_list: ChanBSP 列表,包含所有识别到的一类买卖点
"""
bsp_list = []
if len(bi_list) < 4 or len(bi_zs_list) == 0:
return bsp_list
for zs in bi_zs_list:
if not zs.is_sure or len(zs.bi_list) < 3:
continue
# 找到中枢的最后一笔
last_zs_bi = zs.bi_list[-1]
# 确定离开笔:中枢最后一笔之后的第一笔
if last_zs_bi.dir == Chan_BI_DIR.UP:
# 中枢最后一笔向上,如果没有真正离开中枢,取下一笔
if last_zs_bi.is_sure and last_zs_bi.end_klc.high <= zs.zg:
leave_bi = last_zs_bi.next
else:
leave_bi = last_zs_bi
else:
# 中枢最后一笔向下,如果没有真正离开中枢,取下一笔
if last_zs_bi.is_sure and last_zs_bi.end_klc.low >= zs.zd:
leave_bi = last_zs_bi.next
else:
leave_bi = last_zs_bi
if leave_bi is None or not leave_bi.is_sure:
continue
# 一类买点:向下离开中枢(leave_bi向下,低点 < zd),趋势力竭
if leave_bi.dir == Chan_BI_DIR.DOWN and leave_bi.low < zs.zd:
# 背驰判断:比较离开笔与中枢内最后一笔同向笔的MACD柱状累积面积
# 缠论原文:两段同向走势的MACD柱状面积比较,面积缩小即为背驰
compare_bi = None
for bi in reversed(zs.bi_list):
if bi.dir == Chan_BI_DIR.DOWN and bi is not leave_bi:
compare_bi = bi
break
is_divergence = False
if compare_bi:
# 笔的macd_hist是该笔内所有KLU的macdhist累积面积
leave_macd_area = abs(leave_bi.macd_hist)
compare_macd_area = abs(compare_bi.macd_hist)
# 价格创新低但MACD面积缩小 = 底背驰
if leave_bi.low <= compare_bi.low and leave_macd_area < compare_macd_area:
is_divergence = True
# 即使没创新低,MACD面积明显缩小也算背驰
elif leave_macd_area < compare_macd_area * 0.5:
is_divergence = True
else:
# 没有对比笔时,只要离开中枢就算一类买点
is_divergence = True
if is_divergence:
bsp = ChanBSP(
leave_bi, len(bsp_list),
Chan_BSP_TYPE.T1,
Chan_BSP_DIR.BUY,
leave_bi.sure_time,
1, zs, None
)
bsp_list.append(bsp)
# 一类卖点:向上离开中枢(leave_bi向上,高点 > zg),趋势力竭
elif leave_bi.dir == Chan_BI_DIR.UP and leave_bi.high > zs.zg:
# 背驰判断:比较离开笔与中枢内最后一笔同向笔的MACD柱状累积面积
compare_bi = None
for bi in reversed(zs.bi_list):
if bi.dir == Chan_BI_DIR.UP and bi is not leave_bi:
compare_bi = bi
break
is_divergence = False
if compare_bi:
leave_macd_area = abs(leave_bi.macd_hist)
compare_macd_area = abs(compare_bi.macd_hist)
# 价格创新高但MACD面积缩小 = 顶背驰
if leave_bi.high >= compare_bi.high and leave_macd_area < compare_macd_area:
is_divergence = True
# 即使没创新高,MACD面积明显缩小也算背驰
elif leave_macd_area < compare_macd_area * 0.5:
is_divergence = True
else:
is_divergence = True
if is_divergence:
bsp = ChanBSP(
leave_bi, len(bsp_list),
Chan_BSP_TYPE.T1,
Chan_BSP_DIR.SELL,
leave_bi.sure_time,
1, zs, None
)
bsp_list.append(bsp)
return bsp_list
def find_second_bsp(self, bi_list, first_bsp_list):
"""
笔中枢的二类买卖点识别
二类买点:一类买点出现后,价格向上反弹一笔,再回落一笔,
回落笔的低点不跌破一类买点的低点,确认底部成立。
二类卖点:一类卖点出现后,价格向下回落一笔,再反弹一笔,
反弹笔的高点不超过一类卖点的高点,确认顶部成立。
参数:
bi_list: 笔列表
first_bsp_list: 一类买卖点列表(find_first_bsp 的返回值)
返回:
bsp_list: ChanBSP 列表,包含所有识别到的二类买卖点
"""
bsp_list = []
if not first_bsp_list or len(bi_list) < 4:
return bsp_list
for first_bsp in first_bsp_list:
trigger_bi = first_bsp.bi # 一类买卖点的触发笔
if first_bsp.dir == Chan_BSP_DIR.BUY:
# 一买之后:trigger_bi 向下 -> 反弹笔(向上) -> 回落笔(向下)
# 回落笔的低点 > trigger_bi 的低点 => 二类买点
bounce_bi = trigger_bi.next # 反弹笔(向上)
if bounce_bi and bounce_bi.is_sure and bounce_bi.dir == Chan_BI_DIR.UP:
pullback_bi = bounce_bi.next # 回落笔(向下)
if pullback_bi and pullback_bi.is_sure and pullback_bi.dir == Chan_BI_DIR.DOWN:
if pullback_bi.low > trigger_bi.low:
bsp = ChanBSP(
pullback_bi, len(bsp_list),
Chan_BSP_TYPE.T2,
Chan_BSP_DIR.BUY,
pullback_bi.sure_time,
1, first_bsp.zs, None
)
bsp_list.append(bsp)
elif first_bsp.dir == Chan_BSP_DIR.SELL:
# 一卖之后:trigger_bi 向上 -> 回落笔(向下) -> 反弹笔(向上)
# 反弹笔的高点 < trigger_bi 的高点 => 二类卖点
drop_bi = trigger_bi.next # 回落笔(向下)
if drop_bi and drop_bi.is_sure and drop_bi.dir == Chan_BI_DIR.DOWN:
bounce_bi = drop_bi.next # 反弹笔(向上)
if bounce_bi and bounce_bi.is_sure and bounce_bi.dir == Chan_BI_DIR.UP:
if bounce_bi.high < trigger_bi.high:
bsp = ChanBSP(
bounce_bi, len(bsp_list),
Chan_BSP_TYPE.T2,
Chan_BSP_DIR.SELL,
bounce_bi.sure_time,
1, first_bsp.zs, None
)
bsp_list.append(bsp)
return bsp_list
-171
View File
@@ -1,171 +0,0 @@
"""增量更新:新K只追加 KLU/KLC,笔与笔中枢在当前列表上重算。
不改 init_TF_DF 的整段语义笔必须整表重扫最后一笔 is_sure 允许收回
OWN_CHAN_ZS_001 60 天出现 7 笔中枢用 cal_bi_zs_list_pure
"""
from __future__ import annotations
from datetime import datetime
import pandas as pd
from pandas import DataFrame
from technical.util import resample_to_interval
from chanlun.core.ChanEnum import Chan_FX_TYPE, Chan_KLC_FX, Chan_KLC_STATE
from chanlun.core.ChanKLU import ChanKLU
class IncrementalBuilderMixin:
def init_stream(self, df, interval=1, timeframe=None):
"""用历史K线初始化流式状态,之后用 append_bar / replace_last_bar。"""
if df is None or df.empty:
raise ValueError("DataFrame for stream is empty.")
if "date" not in df.columns:
raise ValueError(f"DataFrame missing 'date' column. Columns: {df.columns.tolist()}")
self.timeframe = timeframe
self.interval = interval
if interval == 1:
self.dataframe = df.copy()
else:
self.dataframe = resample_to_interval(df, interval)
self.dataframe = self.add_indicators(self.dataframe)
self.klu_list = []
self.klc_list = []
self.bi_list = []
self.bi_zs_list = []
self.seg_list = []
self.zs_list = []
self.bsp_list = []
self.klc_fx_list = []
self.big_zs_list = []
self._klc_feed_last_klu = None
for i in range(len(self.dataframe)):
self._append_row_at(i, rebuild=False)
self.rebuild_bi_zs()
return self
def append_bar(self, row):
"""追加一根已收盘K线。同一时间戳则改为替换最后一根。"""
self._ensure_stream_state()
item = self._normalize_row(row)
if self.klu_list and self.klu_list[-1].time == self._row_time_str(item):
return self.replace_last_bar(item)
self._append_item_to_dataframe(item)
self.dataframe = self.add_indicators(self.dataframe)
self._append_row_at(len(self.dataframe) - 1, rebuild=True)
return self
def replace_last_bar(self, row):
"""更新最后一根K(未完成K线走新OHLC)。包含关系从 KLU 列表重放。"""
self._ensure_stream_state()
if not self.klu_list:
return self.append_bar(row)
item = self._normalize_row(row)
idx = self.dataframe.index[-1]
for key, val in item.items():
self.dataframe.at[idx, key] = val
self.dataframe = self.add_indicators(self.dataframe)
self._apply_item_to_klu(self.klu_list[-1], self.dataframe.iloc[-1])
self._rebuild_klc_from_klu()
self.rebuild_bi_zs()
return self
def rebuild_bi_zs(self):
"""在当前 KLC 上重算笔 + cal_bi_zs_list_pure。会先清分型标记。"""
self._reset_klc_bi_marks(self.klc_list)
self.bi_list = self.cal_bi_list(self.klc_list) if self.klc_list else []
self.bi_zs_list = self.cal_bi_zs_list_pure(self.bi_list) if self.bi_list else []
return self.bi_zs_list
def _ensure_stream_state(self):
if not hasattr(self, "klu_list") or self.klu_list is None:
self.klu_list = []
if not hasattr(self, "klc_list") or self.klc_list is None:
self.klc_list = []
if not hasattr(self, "dataframe") or self.dataframe is None:
self.dataframe = DataFrame(
columns=["date", "open", "high", "low", "close", "volume"]
)
if not hasattr(self, "_klc_feed_last_klu"):
self._klc_feed_last_klu = self.klu_list[-1] if self.klu_list else None
if not hasattr(self, "bi_zs_list"):
self.bi_zs_list = []
def _rebuild_klc_from_klu(self):
self.klc_list = []
last_klu = None
for klu in self.klu_list:
self._push_klu_into_klc_list(self.klc_list, klu, last_klu)
last_klu = klu
self._klc_feed_last_klu = last_klu
def _append_row_at(self, idx, rebuild=True):
item = self.dataframe.iloc[idx]
klu = self._klu_from_item(item, idx)
if self.klu_list:
self.klu_list[-1].set_next(klu)
klu.set_pre(self.klu_list[-1])
self._push_klu_into_klc_list(self.klc_list, klu, self._klc_feed_last_klu)
self._klc_feed_last_klu = klu
self.klu_list.append(klu)
if rebuild:
self.rebuild_bi_zs()
def _klu_from_item(self, item, idx):
klu = ChanKLU(
self._item_time_str(item),
item["open"],
item["high"],
item["low"],
item["close"],
item["volume"],
)
klu.set_idx(idx)
if not hasattr(klu, "ema13"):
klu.ema13 = 0
if "macd" in item:
klu.set_indicators(item)
return klu
def _apply_item_to_klu(self, klu, item):
klu.time = self._item_time_str(item)
klu.open = item["open"]
klu.high = item["high"]
klu.low = item["low"]
klu.close = item["close"]
klu.volume = item["volume"]
klu.range = klu.high - klu.low
klu.body = abs(klu.close - klu.open)
if "macd" in item:
klu.set_indicators(item)
def _reset_klc_bi_marks(self, klc_list):
for klc in klc_list:
klc.fx = Chan_FX_TYPE.UNKNOWN
klc.klc_fx_type = Chan_KLC_FX.UNKNOWN
klc.klc_state = Chan_KLC_STATE.UNKNOWN
klc.bi = None
klc.fx_confirmed = False
def _item_time_str(self, item):
date = item["date"]
if hasattr(date, "to_pydatetime"):
date = date.to_pydatetime()
if isinstance(date, datetime):
return date.strftime("%Y-%m-%d %H:%M:%S")
return str(date)
def _row_time_str(self, item):
return self._item_time_str(item)
def _normalize_row(self, row):
if isinstance(row, pd.Series):
return row
return pd.Series(row)
def _append_item_to_dataframe(self, item):
row_df = DataFrame([item])
if self.dataframe is None or self.dataframe.empty:
self.dataframe = row_df
else:
self.dataframe = pd.concat([self.dataframe, row_df], ignore_index=True)
-133
View File
@@ -1,133 +0,0 @@
"""TF_DF builder mixin — 由 split_tfdf_builders 自动生成,逻辑与原 TF_DF 一致。"""
from __future__ import annotations
from datetime import timedelta
from decimal import Decimal
import numpy as np
import pandas as pd
import talib.abstract as ta
from pandas import DataFrame
from technical.util import resample_to_interval
from chanlun.core.ChanBI import ChanBI
from chanlun.core.ChanBIZS import ChanBIZS
from chanlun.core.ChanBSP import ChanBSP
from chanlun.core.ChanEnum import (
Chan_BI_DIR,
Chan_BSP_DIR,
Chan_BSP_TYPE,
Chan_FX_TYPE,
Chan_K_DIR,
Chan_KLC_FX,
Chan_KLC_STATE,
Chan_KLINE_DIR,
Chan_KLU_PATTERN,
Chan_PRICE_TREND,
Chan_SEG_DIR,
Chan_ZS_DIR,
)
from chanlun.core.ChanKLC import ChanKLC
from chanlun.core.ChanKLU import ChanKLU
from chanlun.core.ChanSBI import ChanSBI
from chanlun.core.ChanSEG import ChanSEG
from chanlun.core.ChanZS import ChanZS, ChanZS_Big
from chanlun.indicators.ChanMACD import ChanMACD
class IndicatorsBuilderMixin:
def get_ema52(self, index=-1):
if self.klu_list:
ema52_value = self.klu_list[index].ema52
# 处理NaN值
if pd.isna(ema52_value) or ema52_value is None:
return None
return float(ema52_value)
return None
def get_ema24(self, index=-1):
if self.klu_list:
ema24_value = self.klu_list[index].ema24
# 处理NaN值
if pd.isna(ema24_value) or ema24_value is None:
return None
return float(ema24_value)
return None
def add_indicators(self, df):
fast = 26
slow = 52
period = 9
macd = ta.MACD(df, fastperiod=fast, slowperiod=slow, signalperiod=period)
bb365 = ta.BBANDS(df, timeperiod=365, nbdevup=3.0, nbdevdn=3.0, matype=0)
bb120 = ta.BBANDS(df, timeperiod=120, nbdevup=3.0, nbdevdn=3.0, matype=0)
bb30 = ta.BBANDS(df, timeperiod=41, nbdevup=2.3, nbdevdn=2.3, matype=0)
bb302 = ta.BBANDS(df, timeperiod=41, nbdevup=2.0, nbdevdn=2.0, matype=0)
bb30 = ta.BBANDS(df, timeperiod=20, nbdevup=2.0, nbdevdn=2.0, matype=0)
bb302 = ta.BBANDS(df, timeperiod=20, nbdevup=2.0, nbdevdn=2.0, matype=0)
bb2633 = ta.BBANDS(df, timeperiod=26, nbdevup=3.0, nbdevdn=3.0, matype=0)
# 计算布林带中轨(移动平均线)
bb30_middle = ta.SMA(df, timeperiod=90)
# 手动计算布林带 %B 指标 (BBP)
# %B = (Price - Lower Band) / (Upper Band - Lower Band)
bbp365 = (df['close'] - bb365['lowerband']) / (bb365['upperband'] - bb365['lowerband'])
bbp120 = (df['close'] - bb120['lowerband']) / (bb120['upperband'] - bb120['lowerband'])
bbp30 = (df['close'] - bb30['lowerband']) / (bb30['upperband'] - bb30['lowerband'])
bbp302 = (df['close'] - bb302['lowerband']) / (bb302['upperband'] - bb302['lowerband'])
bbp2633 = (df['close'] - bb2633['lowerband']) / (bb2633['upperband'] - bb2633['lowerband'])
df['bb2633upper'] = bb2633['upperband']
df['bb2633lower'] = bb2633['lowerband']
df['bbp2633'] = bbp2633
df['bb2633middle'] = bb2633['middleband']
df['atr'] = ta.ATR(df, timeperiod=14)
df['bbup365'] = bb365['upperband']
df['bblow365'] = bb365['lowerband']
df['bbp365'] = bbp365
df['bbup120'] = bb120['upperband']
df['bblow120'] = bb120['lowerband']
df['bbp120'] = bbp120
df['bbup30'] = bb30['upperband']
df['bblow30'] = bb30['lowerband']
df['bbmiddle30'] = bb30_middle # 添加bb30中轨
df['bbp30'] = bbp30
df['bbup302'] = bb302['upperband']
df['bblow302'] = bb302['lowerband']
df['bbp302'] = bbp302
df['macd'] = macd['macd']
df['macdsignal'] = macd['macdsignal']
df['macdhist'] = macd['macdhist']
df['ema5'] = ta.EMA(df, timeperiod=5)
df['ema10'] = ta.EMA(df, timeperiod=10)
df['ema24'] = ta.EMA(df, timeperiod=24)
df['ema52'] = ta.EMA(df, timeperiod=52)
df['ema104'] = ta.EMA(df, timeperiod=104)
df['ema156'] = ta.EMA(df, timeperiod=156)
df['ema208'] = ta.EMA(df, timeperiod=208)
df['ema26'] = ta.EMA(df, timeperiod=26)
df['ema13'] = ta.EMA(df, timeperiod=13)
df['ema7'] = ta.EMA(df, timeperiod=7)
df['rsi'] = ta.RSI(df, timeperiod=14)
df['volume_ratio'] = self.cal_volume_ratio(df)
return df
def get_ema_state(self, dataframe):
klu_list = self.get_klu_list(dataframe)
klc_list = self.get_klc_list(klu_list)
bi_list = self.cal_bi_list(klc_list)
klu_state_list = []
for klu in klu_list:
if klu.near0_return == 1:
klu_state_list.append("1")
elif klu.near0_return == 9:
klu_state_list.append("-1")
elif klu.candle_dir == Chan_K_DIR.BULL:
klu_state_list.append("2")
elif klu.candle_dir == Chan_K_DIR.BEAR:
klu_state_list.append("-2")
else:
klu_state_list.append("0")
return klu_state_list
def get_decimal(self, value):
return Decimal("{:.2f}".format(value))
-568
View File
@@ -1,568 +0,0 @@
"""TF_DF builder mixin — 由 split_tfdf_builders 自动生成,逻辑与原 TF_DF 一致。"""
from __future__ import annotations
from datetime import timedelta
from decimal import Decimal
import numpy as np
import pandas as pd
import talib.abstract as ta
from pandas import DataFrame
from technical.util import resample_to_interval
from chanlun.core.ChanBI import ChanBI
from chanlun.core.ChanBIZS import ChanBIZS
from chanlun.core.ChanBSP import ChanBSP
from chanlun.core.ChanEnum import (
Chan_BI_DIR,
Chan_BSP_DIR,
Chan_BSP_TYPE,
Chan_FX_TYPE,
Chan_K_DIR,
Chan_KLC_FX,
Chan_KLC_STATE,
Chan_KLINE_DIR,
Chan_KLU_PATTERN,
Chan_PRICE_TREND,
Chan_SEG_DIR,
Chan_ZS_DIR,
)
from chanlun.core.ChanKLC import ChanKLC
from chanlun.core.ChanKLU import ChanKLU
from chanlun.core.ChanSBI import ChanSBI
from chanlun.core.ChanSEG import ChanSEG
from chanlun.core.ChanZS import ChanZS, ChanZS_Big
from chanlun.indicators.ChanMACD import ChanMACD
class KlineBuilderMixin:
def get_klu_state(self, dataframe):
klc_list = self.get_klc_list(self.get_klu_list(dataframe))
bi_list = self.cal_bi_list(klc_list)
klu_state_list = []
klc_index = 0
for index in range(0, len(dataframe)):
if klc_index == len(klc_list):
klc_index = len(klc_list) - 1
klc = klc_list[klc_index]
if klc.end_klu and klc.end_klu.idx == index:
if klc.klc_state == Chan_KLC_STATE.S10:
klu_state_list.append("10")
#print(klc.end_time, klc.klc_fx_type)
elif klc.klc_state == Chan_KLC_STATE.S_10:
klu_state_list.append("-10")
#print(klc.end_time, klc.klc_fx_type)
elif klc.klc_state == Chan_KLC_STATE.S11:
klu_state_list.append("11")
#print(klc.end_time, klc.klc_fx_type)
elif klc.klc_state == Chan_KLC_STATE.S_11:
klu_state_list.append("-11")
#print(klc.end_time, klc.klc_fx_type)
else:
klu_state_list.append("00")
klc_index += 1
else:
klu_state_list.append("00")
print(klu_state_list[:20])
return klu_state_list
def check_fx1(self, klc):
if klc.pre and klc.next:
if klc.high > klc.pre.high and klc.high > klc.next.high and klc.low > klc.pre.low and klc.low > klc.next.low:
if klc.pre.pre and klc.next.next:
if klc.high > klc.pre.pre.high and klc.high > klc.next.next.high:
#if (klc.close > klc.ema52 or klc.next.close > klc.next.ema52) and klc.macd > 0:
klc.set_fx(Chan_FX_TYPE.TOP)
#print(klc.start_time, klc.end_time,klc.next.start_time, klc.next.end_time, klc.macd, klc.state, klc.fx, "TOP")
return Chan_FX_TYPE.TOP
elif klc.low < klc.pre.low and klc.low < klc.next.low and klc.high < klc.pre.high and klc.high < klc.next.high:
#if (klc.close < klc.ema52 or klc.next.close < klc.next.ema52) and klc.macd < 0:
if klc.pre.pre and klc.next.next:
if klc.low < klc.pre.pre.low and klc.low < klc.next.next.low:
klc.set_fx(Chan_FX_TYPE.BOTTOM)
#print(klc.start_time, klc.end_time,klc.next.start_time, klc.next.end_time, klc.macd, klc.state, klc.fx, "BOTTOM")
return Chan_FX_TYPE.BOTTOM
return Chan_FX_TYPE.UNKNOWN
def check_fx(self, klc):
# 右K未完成(仍在包含合并)时不分型:否则确认笔会随 next 扩区间被 check_*_fx 收回
if klc.pre and klc.next and klc.next.end_klu is not None:
if klc.high > klc.pre.high and klc.high > klc.next.high and klc.low > klc.pre.low and klc.low > klc.next.low:
#if (klc.close > klc.ema52 or klc.next.close > klc.next.ema52) and klc.macd > 0:
klc.set_fx(Chan_FX_TYPE.TOP)
#print(klc.start_time, klc.end_time,klc.next.start_time, klc.next.end_time, klc.macd, klc.state, klc.fx, "TOP")
return Chan_FX_TYPE.TOP
elif klc.low < klc.pre.low and klc.low < klc.next.low and klc.high < klc.pre.high and klc.high < klc.next.high:
#if (klc.close < klc.ema52 or klc.next.close < klc.next.ema52) and klc.macd < 0:
klc.set_fx(Chan_FX_TYPE.BOTTOM)
#print(klc.start_time, klc.end_time,klc.next.start_time, klc.next.end_time, klc.macd, klc.state, klc.fx, "BOTTOM")
return Chan_FX_TYPE.BOTTOM
return Chan_FX_TYPE.UNKNOWN
def check_fx3(self, klc):
# 右K未完成(仍在包含合并)时不分型:否则确认笔会随 next 扩区间被 check_*_fx 收回
if klc.pre and klc.next and klc.next.end_klu is not None:
next_klu = klc.next.end_klu.next
if klc.high > klc.pre.high and klc.high > klc.next.high and klc.low > klc.pre.low and klc.low > klc.next.low and klc.high > next_klu.high:
#if (klc.close > klc.ema52 or klc.next.close > klc.next.ema52) and klc.macd > 0:
klc.set_fx(Chan_FX_TYPE.TOP)
#print(klc.start_time, klc.end_time,klc.next.start_time, klc.next.end_time, klc.macd, klc.state, klc.fx, "TOP")
return Chan_FX_TYPE.TOP
elif klc.low < klc.pre.low and klc.low < klc.next.low and klc.high < klc.pre.high and klc.high < klc.next.high and klc.low < next_klu.low:
#if (klc.close < klc.ema52 or klc.next.close < klc.next.ema52) and klc.macd < 0:
klc.set_fx(Chan_FX_TYPE.BOTTOM)
#print(klc.start_time, klc.end_time,klc.next.start_time, klc.next.end_time, klc.macd, klc.state, klc.fx, "BOTTOM")
return Chan_FX_TYPE.BOTTOM
return Chan_FX_TYPE.UNKNOWN
def check_fx2(self, klc):
if klc.pre and klc.next:
if klc.high > klc.pre.close and klc.close > klc.next.close and klc.close > klc.pre.close and klc.close > klc.next.close:
#if (klc.close > klc.ema52 or klc.next.close > klc.next.ema52) and klc.macd > 0:
klc.set_fx(Chan_FX_TYPE.TOP)
#print(klc.start_time, klc.end_time,klc.next.start_time, klc.next.end_time, klc.macd, klc.state, klc.fx, "TOP")
return Chan_FX_TYPE.TOP
elif klc.low < klc.pre.close and klc.close < klc.next.close and klc.close < klc.pre.close and klc.close < klc.next.close:
#if (klc.close < klc.ema52 or klc.next.close < klc.next.ema52) and klc.macd < 0:
klc.set_fx(Chan_FX_TYPE.BOTTOM)
#print(klc.start_time, klc.end_time,klc.next.start_time, klc.next.end_time, klc.macd, klc.state, klc.fx, "BOTTOM")
return Chan_FX_TYPE.BOTTOM
return Chan_FX_TYPE.UNKNOWN
def check_fx_pattern(self, klc):
klu_list = klc.pre.klu_list + klc.klu_list + klc.next.klu_list
self.cal_klu_pattern(klu_list)
p = ""
for klu in klu_list:
p += klu.to_string()
#print(p)
def cal_volume_ratio(self, dataframe, window=10):
df = dataframe.copy()
# 计算过去N根K线的平均成交量
df['avg_volume'] = df['volume'].rolling(window=window).mean()
# 计算量比
df['volume_ratio'] = df['volume'] / df['avg_volume']
# 填充缺失值(前N根K线)
df['volume_ratio'] = df['volume_ratio'].fillna(1.0)
return df['volume_ratio']
def cal_kl_data(self, dataframe:DataFrame):
fields = "time,open,high,low,close,volume"
klu_list = []
last_klu = None
for i in range(0, len(dataframe)):
item = dataframe.iloc[i]
date = item['date']
o = item['open']
h = item['high']
l = item['low']
c = item['close']
v = item['volume']
# time_obj = date.fromtimestamp(date)
# date = date + timedelta(hours=8)
time_str = date.strftime('%Y-%m-%d %H:%M:%S')
item_data = [
time_str,
o,
h,
l,
c,
v
]
# klu = KLU(self.create_item_dict(item_data, GetColumnNameFromFieldList(fields)))
klu = ChanKLU(time_str, o, h, l, c, v)
# print(klu.time, klu.open, klu.high, klu.low, klu.close, klu.volume)
klu.set_idx(i)
klu_list.append(klu)
if last_klu:
last_klu.set_next(klu)
klu.set_pre(last_klu)
last_klu = klu
if 'macd' in item:
klu.set_indicators(item)
return klu_list
def get_kl_data(self, dataframe:DataFrame):
return self.cal_kl_data(dataframe)
def _push_klu_into_klc_list(self, klc_list, klu, last_klu):
"""把一根 KLU 并入包含K线列表。与 get_klc_list 的几何规则相同。"""
if len(klc_list) > 0:
last_klc = klc_list[-1]
if klu.exception:
ddir = Chan_KLINE_DIR.DOWN
if last_klc.high < klu.high:
ddir = Chan_KLINE_DIR.UP
klc = ChanKLC(klu, index=len(klc_list), ddir=ddir)
klc.high = klu.close if klu.close > klu.open else klu.open
klc.low = klu.open if klu.close > klu.open else klu.close
klc_list.append(klc)
last_klc.set_next(klc)
klc.set_pre(last_klc)
last_klc.set_end_klu(last_klu)
klc.set_pre_fx()
else:
included = last_klc.check_klu_included(klu)
if not included:
ddir = Chan_KLINE_DIR.DOWN
if last_klc.high < klu.high:
ddir = Chan_KLINE_DIR.UP
klc = ChanKLC(klu, index=len(klc_list), ddir=ddir)
klc_list.append(klc)
last_klc.set_next(klc)
klc.set_pre(last_klc)
last_klc.set_end_klu(last_klu)
klc.set_pre_fx()
else:
last_klc.add_klu(klu)
else:
ddir = Chan_KLINE_DIR.UP
if klu.open > klu.close:
ddir = Chan_KLINE_DIR.DOWN
klc = ChanKLC(klu, 0, ddir)
klc_list.append(klc)
def get_klc_list(self, klu_list):
klc_list = []
last_klu = None
# ChanMACD.__init__ 已调用 cal_macd_state,切勿再调一次(会重复堆积 seg/unittf)
macd = ChanMACD(klu_list)
klu_list = macd.klu_list
self._last_chan_macd = macd
ema_up_list = []
ema_down_list = []
ema_up_count = 0
ema_down_count = 0
last_klu = None
for klu in klu_list:
ema = klu.ema52
last_ema = last_klu.ema52 if last_klu else 0
if klu.close >= ema:
ema_up_count += 1
elif klu.close < ema:
ema_down_count += 1
if last_klu and last_klu.close >= last_ema and klu.close < ema:
ema_up_list.append(ema_up_count)
#print(last_klu.time, ema_up_count, "UP END")
ema_up_count = 0
elif last_klu and last_klu.close < last_ema and klu.close >= ema:
ema_down_list.append(ema_down_count)
#print(last_klu.time, ema_down_count, "DOWN END")
ema_down_count = 0
self._push_klu_into_klc_list(klc_list, klu, last_klu)
last_klu = klu
klc_list = self.cal_trend(klc_list)
#print(ema52_up_list, ema52_down_list)
return klc_list
def get_klu_list(self, dataframe):
klu_list = self.get_kl_data(dataframe)
#klu_list = self.cal_klu_pattern(klu_list)
return klu_list
def cal_klu_pattern(self, klu_list):
"""
计算裸K的pattern - 识别反转形态
"""
if not klu_list or len(klu_list) < 3:
return klu_list
for i, klu in enumerate(klu_list):
# 单根K线反转模式识别
self._detect_single_reversal_pattern(klu)
# 双根K线形态识别
if i >= 1:
self._detect_double_pattern(klu_list[i-1], klu)
# 三根K线形态识别
if i >= 2:
self._detect_triple_pattern(klu_list[i-2], klu_list[i-1], klu)
#if klu.pattern != Chan_KLU_PATTERN.UNKNOWN:
#print(klu.time, klu.pattern, klu.lower_shadow_ratio, klu.upper_shadow_ratio, klu.body_ratio, klu.lower_shadow_ratio/klu.body_ratio, klu.upper_shadow_ratio/klu.body_ratio)
return klu_list
def _detect_single_reversal_pattern(self, klu):
"""检测单根K线反转模式"""
body = abs(klu.close - klu.open)
upper_shadow = klu.high - max(klu.close, klu.open)
lower_shadow = min(klu.close, klu.open) - klu.low
total_range = klu.high - klu.low
# 避免除零
if total_range == 0:
return
body_ratio = body / total_range
upper_ratio = upper_shadow / total_range
lower_ratio = lower_shadow / total_range
#print(klu.time, upper_ratio, lower_ratio, body_ratio, upper_ratio/body_ratio, lower_ratio/body_ratio)
# 避免body_ratio为0时的除零错误
if body_ratio == 0:
return
# 锤子线/上吊线 - 反转信号
if lower_ratio / body_ratio >= 2:
# 锤子线:底部反转,需要前面一段
if klu.close > klu.open and klu.pre:
klu.set_pattern(Chan_KLU_PATTERN.HAMMER) # 底部反转
# 上吊线:顶部反转,需要前一根是上涨趋势
elif klu.close < klu.open and klu.pre:
klu.set_pattern(Chan_KLU_PATTERN.HANGING_MAN) # 顶部反转
# 倒锤子线/射击之星 - 反转信号
elif upper_ratio / body_ratio >= 2:
# 倒锤子线:底部反转,需要前一根是下跌趋势
if klu.close > klu.open and klu.pre:
klu.set_pattern(Chan_KLU_PATTERN.INVERTED_HAMMER) # 底部反转
# 射击之星:顶部反转,需要前一根是上涨趋势
elif klu.close < klu.open and klu.pre:
klu.set_pattern(Chan_KLU_PATTERN.SHOOTING_STAR) # 顶部反转
# 十字星 - 反转信号
elif body_ratio <= 0.1:
if upper_ratio > 0.4 and lower_ratio > 0.4:
klu.set_pattern(Chan_KLU_PATTERN.LONG_LEGGED_DOJI) # 强烈反转信号
elif upper_ratio > 0.4 and lower_ratio <= 0.1:
# 墓碑十字星:顶部反转,需要前一根是上涨趋势
if klu.pre and klu.pre.close > klu.pre.open:
klu.set_pattern(Chan_KLU_PATTERN.GRAVESTONE_DOJI) # 顶部反转
elif lower_ratio > 0.4 and upper_ratio <= 0.1:
# 蜻蜓十字星:底部反转,需要前一根是下跌趋势
if klu.pre and klu.pre.close < klu.pre.open:
klu.set_pattern(Chan_KLU_PATTERN.DRAGONFLY_DOJI) # 底部反转
else:
klu.set_pattern(Chan_KLU_PATTERN.DOJI) # 一般反转信号
def _detect_double_pattern(self, prev_klu, curr_klu):
"""检测两根K线形成的形态
包括吞没形态(看涨/看跌)乌云盖顶曙光初现
"""
# 如果前一根K线已经有形态,不再识别双K线形态
if prev_klu.pattern != Chan_KLU_PATTERN.UNKNOWN:
return
# 计算K线实体
prev_body = abs(prev_klu.close - prev_klu.open)
curr_body = abs(curr_klu.close - curr_klu.open)
# 判断K线颜色(阴阳)
prev_bullish = prev_klu.close > prev_klu.open
curr_bullish = curr_klu.close > curr_klu.open
# 检查是否存在长期趋势(至少需要5根K线的趋势)
def check_long_trend(klu, bullish_trend=True, min_bars=5):
"""检查是否存在长期趋势
bullish_trend=True: 检查上涨趋势
bullish_trend=False: 检查下跌趋势
min_bars: 最少需要多少根K线形成趋势
"""
if not klu or not klu.pre:
return False
return True
# 使用EMA指标判断长期趋势
if klu.ema52 > 0:
if bullish_trend and klu.close < klu.ema52:
return False
if not bullish_trend and klu.close > klu.ema52:
return False
# 检查连续的K线方向
count = 0
current = klu.pre
while current and count < min_bars:
if not current.pre:
break
if bullish_trend:
# 上涨趋势:当前收盘价高于前一根收盘价
if current.close <= current.pre.close:
break
else:
# 下跌趋势:当前收盘价低于前一根收盘价
if current.close >= current.pre.close:
break
count += 1
current = current.pre
return count >= min_bars
# 1. 看涨吞没形态:前阴后阳,后者完全吞没前者
# 要求前面有明显的下跌趋势
if not prev_bullish and curr_bullish and \
abs(curr_klu.open - prev_klu.close) < 10 and \
curr_klu.close > prev_klu.open and \
check_long_trend(prev_klu, bullish_trend=False, min_bars=5):
curr_klu.set_pattern(Chan_KLU_PATTERN.BULLISH_ENGULFING)
return
# 2. 看跌吞没形态:前阳后阴,后者完全吞没前者
# 要求前面有明显的上涨趋势
if prev_bullish and not curr_bullish and \
abs(curr_klu.open - prev_klu.close) < 10 and \
curr_klu.close < prev_klu.open and \
check_long_trend(prev_klu, bullish_trend=True, min_bars=5):
curr_klu.set_pattern(Chan_KLU_PATTERN.BEARISH_ENGULFING)
return
# 3. 乌云盖顶:前阳后阴,后者开盘价高于前者最高价,收盘价在前者实体中部以下
# 要求前面有明显的上涨趋势
if prev_bullish and not curr_bullish and \
curr_klu.open > prev_klu.high and \
curr_klu.close < (prev_klu.open + prev_klu.close) / 2 and \
curr_klu.close > prev_klu.open and \
check_long_trend(prev_klu, bullish_trend=True, min_bars=5):
curr_klu.set_pattern(Chan_KLU_PATTERN.DARK_CLOUD_COVER)
return
# 4. 曙光初现:前阴后阳,后者开盘价低于前者最低价,收盘价在前者实体中部以上
# 要求前面有明显的下跌趋势
if not prev_bullish and curr_bullish and \
curr_klu.open < prev_klu.low and \
curr_klu.close > (prev_klu.open + prev_klu.close) / 2 and \
curr_klu.close < prev_klu.open and \
check_long_trend(prev_klu, bullish_trend=False, min_bars=5):
curr_klu.set_pattern(Chan_KLU_PATTERN.PIERCING_LINE)
return
# 平顶和平底移至三根K线形态中判断
def _detect_triple_pattern(self, first_klu, second_klu, third_klu):
"""检测三根K线形成的形态
包括早晨之星黄昏之星平顶平底
"""
# 如果前两根K线已经有形态,不再识别三K线形态
if first_klu.pattern != Chan_KLU_PATTERN.UNKNOWN or \
second_klu.pattern != Chan_KLU_PATTERN.UNKNOWN:
return
# 判断K线颜色(阴阳)
first_bullish = first_klu.close > first_klu.open
second_bullish = second_klu.close > second_klu.open
third_bullish = third_klu.close > third_klu.open
# 计算实体大小
first_body = abs(first_klu.close - first_klu.open)
second_body = abs(second_klu.close - second_klu.open)
third_body = abs(third_klu.close - third_klu.open)
# 检查是否存在长期趋势(至少需要5根K线的趋势)
def check_long_trend(klu, bullish_trend=True, min_bars=5):
"""检查是否存在长期趋势
bullish_trend=True: 检查上涨趋势
bullish_trend=False: 检查下跌趋势
min_bars: 最少需要多少根K线形成趋势
"""
if not klu or not klu.pre:
return False
# 使用EMA指标判断长期趋势
if klu.ema52 > 0:
if bullish_trend and klu.close < klu.ema52:
return False
if not bullish_trend and klu.close > klu.ema52:
return False
# 检查连续的K线方向
count = 0
current = klu.pre
while current and count < min_bars:
if not current.pre:
break
if bullish_trend:
# 上涨趋势:当前收盘价高于前一根收盘价
if current.close <= current.pre.close:
break
else:
# 下跌趋势:当前收盘价低于前一根收盘价
if current.close >= current.pre.close:
break
count += 1
current = current.pre
return count >= min_bars
# 1. 早晨之星:第一根阴线,第二根十字星或小实体,第三根阳线
# 要求前面有明显的下跌趋势
if not first_bullish and third_bullish and \
second_body < first_body * 0.3 and \
third_body > first_body * 0.5 and \
max(second_klu.open, second_klu.close) < first_klu.close and \
min(second_klu.open, second_klu.close) < third_klu.open and \
third_klu.close > (first_klu.open + first_klu.close) / 2 and \
check_long_trend(first_klu, bullish_trend=False, min_bars=7):
third_klu.set_pattern(Chan_KLU_PATTERN.MORNING_STAR)
return
# 2. 黄昏之星:第一根阳线,第二根十字星或小实体,第三根阴线
# 要求前面有明显的上涨趋势
if first_bullish and not third_bullish and \
second_body < first_body * 0.3 and \
third_body > first_body * 0.5 and \
min(second_klu.open, second_klu.close) > first_klu.close and \
max(second_klu.open, second_klu.close) > third_klu.open and \
third_klu.close < (first_klu.open + first_klu.close) / 2 and \
check_long_trend(first_klu, bullish_trend=True, min_bars=7):
third_klu.set_pattern(Chan_KLU_PATTERN.EVENING_STAR)
return
# 3. 平顶:三根K线的最高点几乎相同(上升趋势中更有意义)
# 要求前面有明显的上涨趋势
if (abs(first_klu.high - second_klu.high) / first_klu.high < 0.0002 and
abs(second_klu.high - third_klu.high) / second_klu.high < 0.0002 and
check_long_trend(first_klu, bullish_trend=True, min_bars=7)):
# 额外确认:价格接近阻力位或关键技术指标
is_near_resistance = False
# 检查是否接近EMA52阻力位
if first_klu.ema52 > 0:
resistance_level = first_klu.ema52
if abs(first_klu.high - resistance_level) / resistance_level < 0.01:
is_near_resistance = True
# 检查是否有成交量确认(成交量减少表示上涨动能减弱)
volume_confirmation = False
if (first_klu.volume > 0 and second_klu.volume > 0 and third_klu.volume > 0 and
third_klu.volume < second_klu.volume and second_klu.volume < first_klu.volume):
volume_confirmation = True
if is_near_resistance or volume_confirmation:
third_klu.set_pattern(Chan_KLU_PATTERN.TWEEZER_TOP)
return
# 4. 平底:三根K线的最低点几乎相同(下降趋势中更有意义)
# 要求前面有明显的下跌趋势
if (abs(first_klu.low - second_klu.low) / first_klu.low < 0.0002 and
abs(second_klu.low - third_klu.low) / second_klu.low < 0.0002 and
check_long_trend(first_klu, bullish_trend=False, min_bars=7)):
# 额外确认:价格接近支撑位或关键技术指标
is_near_support = False
# 检查是否接近EMA52支撑位
if first_klu.ema52 > 0:
support_level = first_klu.ema52
if abs(first_klu.low - support_level) / support_level < 0.01:
is_near_support = True
# 检查是否有成交量确认(成交量减少表示下跌动能减弱)
volume_confirmation = False
if (first_klu.volume > 0 and second_klu.volume > 0 and third_klu.volume > 0 and
third_klu.volume < second_klu.volume and second_klu.volume < first_klu.volume):
volume_confirmation = True
if is_near_support or volume_confirmation:
third_klu.set_pattern(Chan_KLU_PATTERN.TWEEZER_BOTTOM)
return
-317
View File
@@ -1,317 +0,0 @@
"""TF_DF builder mixin — 由 split_tfdf_builders 自动生成,逻辑与原 TF_DF 一致。"""
from __future__ import annotations
from datetime import timedelta
from decimal import Decimal
import numpy as np
import pandas as pd
import talib.abstract as ta
from pandas import DataFrame
from technical.util import resample_to_interval
from chanlun.core.ChanBI import ChanBI
from chanlun.core.ChanBIZS import ChanBIZS
from chanlun.core.ChanBSP import ChanBSP
from chanlun.core.ChanEnum import (
Chan_BI_DIR,
Chan_BSP_DIR,
Chan_BSP_TYPE,
Chan_FX_TYPE,
Chan_K_DIR,
Chan_KLC_FX,
Chan_KLC_STATE,
Chan_KLINE_DIR,
Chan_KLU_PATTERN,
Chan_PRICE_TREND,
Chan_SEG_DIR,
Chan_ZS_DIR,
)
from chanlun.core.ChanKLC import ChanKLC
from chanlun.core.ChanKLU import ChanKLU
from chanlun.core.ChanSBI import ChanSBI
from chanlun.core.ChanSEG import ChanSEG
from chanlun.core.ChanZS import ChanZS, ChanZS_Big
from chanlun.indicators.ChanMACD import ChanMACD
class SegBuilderMixin:
def get_seg_list(self, bi_list):
seg_list = []
up_bi_list = []
down_bi_list = []
last_up_bi = None
last_down_bi = None
last_up_sbi = None
last_down_sbi = None
last_seg = None
up_sbi_list = []
down_sbi_list = []
look_for_bottom = False
look_for_top = False
for bi in bi_list:
#print(len(up_sbi_list), len(down_sbi_list))
if len(seg_list) > 0:
# Last seg is up
if last_seg.dir == Chan_SEG_DIR.UP:
if bi.dir == Chan_BI_DIR.DOWN:
if len(down_sbi_list) > 1:
# Check down sbi inclusion
included = last_down_sbi.check_bi_included(bi)
if not included:
down_sbi = ChanSBI(bi, len(down_sbi_list), bi.dir)
last_down_sbi.set_next(down_sbi)
last_down_sbi.set_end_bi(last_down_bi)
down_sbi.set_pre(last_down_sbi)
down_sbi_list.append(down_sbi)
fx = last_down_sbi.check_fx()
# Found top
if fx == Chan_FX_TYPE.TOP:
if look_for_top:
seg_list[-2].set_sure(bi)
look_for_top = False
#print(bi.start_time, look_for_top, "UP 1")
# Has gap and search for bottom fx
if last_down_sbi.has_fx_gap:
look_for_bottom = True
last_seg.pre_set_end_bi(bi_list[last_down_sbi.start_bi.index - 1])
seg = ChanSEG(last_down_sbi.start_bi, len(seg_list), Chan_SEG_DIR.DOWN, bi)
seg_list.append(seg)
last_seg.set_next(seg)
seg.set_pre(last_seg)
last_seg = seg
up_sbi_list = []
last_up_sbi = ChanSBI(last_up_bi, len(up_sbi_list), last_up_bi.dir)
up_sbi_list.append(last_up_sbi)
#up_sbi_list.append(last_up_sbi)
#print(last_up_bi.start_time, last_up_sbi.start_bi.start_time, "Reset up sbi list 1")
#print(bi.start_time, look_for_top, "UP 2")
# No gap end SEG
else:
if look_for_bottom:
look_for_bottom = False
last_seg.set_start_bi(last_down_sbi.start_bi)
seg_list[-2].set_end_bi(bi_list[last_down_sbi.start_bi.index - 1], bi)
up_sbi_list = []
last_up_sbi = ChanSBI(last_up_bi, len(up_sbi_list), last_up_bi.dir)
up_sbi_list.append(last_up_sbi)
last_seg.add_bi(bi)
#up_sbi_list.append(last_up_sbi)
#print(last_up_bi.start_time, last_up_sbi.start_bi.start_time, "Reset up sbi list 2")
#print(bi.start_time, look_for_top, "UP 3")
else:
last_seg.set_end_bi(bi_list[last_down_sbi.start_bi.index - 1], bi)
seg = ChanSEG(last_down_sbi.start_bi, len(seg_list), Chan_SEG_DIR.DOWN, bi)
seg_list.append(seg)
last_seg.set_next(seg)
seg.set_pre(last_seg)
last_seg = seg
#print(last_down_sbi.end_bi.start_time, "Normal UP SEG", last_up_sbi.start_bi.start_time, bi.start_time)
#l_up_sbi = up_sbi_list[-1]
up_sbi_list = []
last_up_sbi = ChanSBI(last_up_bi, len(up_sbi_list), last_up_bi.dir)
up_sbi_list.append(last_up_sbi)
#up_sbi_list.append(last_up_sbi)
#print(last_up_bi.start_time, last_up_sbi.start_bi.start_time, "Reset up sbi list 3")
last_down_sbi = down_sbi
last_seg.add_bi(bi)
else:
if len(down_sbi_list) == 1:
included = last_down_sbi.check_bi_included(bi)
if not included:
down_sbi = ChanSBI(bi, len(down_sbi_list), bi.dir)
last_down_sbi.set_next(down_sbi)
last_down_sbi.set_end_bi(last_down_bi)
down_sbi.set_pre(last_down_sbi)
down_sbi_list.append(down_sbi)
last_down_sbi = down_sbi
#print(bi.start_time, look_for_top, "UP 4")
last_seg.add_bi(bi)
else:
last_down_sbi = ChanSBI(bi, len(down_sbi_list), bi.dir)
down_sbi_list.append(last_down_sbi)
last_seg.add_bi(bi)
#print(bi.start_time, look_for_top, "UP 5")
else:
if last_up_sbi:
included = last_up_sbi.check_bi_included(bi)
if not included:
up_sbi = ChanSBI(bi, len(up_sbi_list), bi.dir)
last_up_sbi.set_next(up_sbi)
last_up_sbi.set_end_bi(last_up_bi)
up_sbi.set_pre(last_up_sbi)
up_sbi_list.append(up_sbi)
last_up_sbi = up_sbi
#print(bi.start_time, look_for_top, "UP 6")
last_seg.add_bi(bi)
# Last seg is down
else:
if bi.dir == Chan_BI_DIR.UP:
if len(up_sbi_list) > 1:
# Check down sbi inclusion
included = last_up_sbi.check_bi_included(bi)
if not included:
up_sbi = ChanSBI(bi, len(up_sbi_list), bi.dir)
last_up_sbi.set_next(up_sbi)
last_up_sbi.set_end_bi(last_up_bi)
up_sbi.set_pre(last_up_sbi)
up_sbi_list.append(up_sbi)
fx = last_up_sbi.check_fx()
# Found bottom
if fx == Chan_FX_TYPE.BOTTOM:
if look_for_bottom:
seg_list[-2].set_sure(bi)
look_for_bottom = False
#print(bi.start_time, look_for_top, "DOWN 1")
# Has gap and search for bottom fx
if last_up_sbi.has_fx_gap:
look_for_top = True
last_seg.pre_set_end_bi(bi_list[last_up_sbi.start_bi.index - 1])
seg = ChanSEG(last_up_sbi.start_bi, len(seg_list), Chan_SEG_DIR.UP, bi)
seg_list.append(seg)
last_seg.set_next(seg)
seg.set_pre(last_seg)
last_seg = seg
down_sbi_list = []
last_down_sbi = ChanSBI(last_down_bi, len(down_sbi_list), last_down_bi.dir)
down_sbi_list.append(last_down_sbi)
#down_sbi_list.append(last_down_sbi)
#print(last_down_bi.start_time, last_down_sbi.start_bi.start_time, "Reset down sbi list 1")
#print(bi.start_time, look_for_top, "DOWN 2")
# No gap end SEG
else:
if look_for_top:
look_for_top = False
last_seg.set_start_bi(last_up_sbi.start_bi)
seg_list[-2].set_end_bi(bi_list[last_up_sbi.start_bi.index - 1], bi)
down_sbi_list = []
last_down_sbi = ChanSBI(last_down_bi, len(down_sbi_list), last_down_bi.dir)
down_sbi_list.append(last_down_sbi)
last_seg.add_bi(bi)
#down_sbi_list.append(last_down_sbi)
#print(last_down_bi.start_time, last_down_sbi.start_bi.start_time, "Reset down sbi list 2")
#print(bi.start_time, look_for_top, "DOWN 3")
else:
last_seg.set_end_bi(bi_list[last_up_sbi.start_bi.index - 1], bi)
seg = ChanSEG(last_up_sbi.start_bi, len(seg_list), Chan_SEG_DIR.UP, bi)
#print(last_up_sbi.start_bi.start_time)
last_seg.set_next(seg)
seg.set_pre(last_seg)
seg_list.append(seg)
last_seg = seg
#print(last_up_sbi.end_bi.start_time, "Normal DOWN SEG", last_down_sbi.start_bi.start_time, bi.start_time)
down_sbi_list = []
last_down_sbi = ChanSBI(last_down_bi, len(down_sbi_list), last_down_bi.dir)
down_sbi_list.append(last_down_sbi)
#down_sbi_list.append(last_down_sbi)
#print(last_down_bi.start_time, last_down_sbi.start_bi.start_time, "Reset down sbi list 3")
last_up_sbi = up_sbi
last_seg.add_bi(bi)
else:
if len(up_sbi_list) == 1:
#last_up_sbi = up_sbi_list[-1]
included = last_up_sbi.check_bi_included(bi)
if not included:
up_sbi = ChanSBI(bi, len(up_sbi_list), bi.dir)
last_up_sbi.set_next(up_sbi)
last_up_sbi.set_end_bi(last_up_bi)
up_sbi.set_pre(last_up_sbi)
up_sbi_list.append(up_sbi)
last_up_sbi = up_sbi
last_seg.add_bi(bi)
#print(bi.start_time, look_for_top, "DOWN 4")
else:
last_up_sbi = ChanSBI(bi, len(up_sbi_list), bi.dir)
up_sbi_list.append(last_up_sbi)
last_seg.add_bi(bi)
#print(bi.start_time, look_for_top, "DOWN 5")
else:
if last_down_sbi:
included = last_down_sbi.check_bi_included(bi)
if not included:
down_sbi = ChanSBI(bi, len(down_sbi_list), bi.dir)
last_down_sbi.set_next(down_sbi)
last_down_sbi.set_end_bi(last_down_bi)
down_sbi.set_pre(last_down_sbi)
down_sbi_list.append(down_sbi)
last_down_sbi = down_sbi
last_seg.add_bi(bi)
#print(bi.start_time, look_for_top, look_for_bottom, "DOWN 6")
# len(seg_list) = 0
else:
if bi.check_overlap():
if bi.dir == Chan_BI_DIR.UP:
seg = ChanSEG(bi, len(seg_list), Chan_SEG_DIR.UP, bi)
last_up_bi = bi
last_up_sbi = ChanSBI(bi, len(up_sbi_list), bi.dir)
seg_list.append(seg)
last_seg = seg
#print(bi.start_time, 'Create first UP SEG')
else:
seg = ChanSEG(bi, len(seg_list), Chan_SEG_DIR.DOWN, bi)
last_down_bi = bi
last_down_sbi = ChanSBI(bi, len(down_sbi_list), bi.dir)
seg_list.append(seg)
last_seg = seg
#print(bi.start_time, 'Create first DOWN SEG')
if bi.dir == Chan_BI_DIR.UP:
last_up_bi = bi
up_bi_list.append(bi)
else:
last_down_bi = bi
down_bi_list.append(bi)
"""
if len(seg_list) > 1:
seg = seg_list[-1]
last_seg = seg_list[-2]
last_seg_bi = last_seg.bi_list[-3]
bi_index = seg.start_bi.index
for i in range(bi_index, len(bi_list) - 1):
# last seg is down
if seg.dir == Chan_SEG_DIR.UP:
if bi_list[i].dir == Chan_BI_DIR.UP:
last_seg_peak = last_seg_bi.high
if bi_list[i].high > last_seg_peak:
# The confirmed
print("Last UP seg is broken, create a new seg. 1")
seg.pre_set_end_bi(bi_list[i])
seg = ChanSEG(bi_list[i+1], len(seg_list), Chan_SEG_DIR.DOWN, bi)
seg_list.append(seg)
last_seg = seg_list[-2]
if len(last_seg.bi_list) > 3:
last_seg_bi = last_seg.bi_list[-3]
else:
if bi_list[i].dir == Chan_BI_DIR.DOWN:
last_seg_peak = last_seg_bi.low
if bi_list[i].low < last_seg_peak:
print("Last DOWN seg is broken, create a new seg. 1")
seg.pre_set_end_bi(bi_list[i])
seg = ChanSEG(bi_list[i+1], len(seg_list), Chan_SEG_DIR.UP, bi)
seg_list.append(seg)
last_seg = seg_list[-2]
if len(last_seg.bi_list) > 3:
last_seg_bi = last_seg.bi_list[-3]
else:
if len(seg_list) == 1:
last_seg = seg_list[-1]
bi_index = last_seg.bi_list[0].index
for i in range(bi_index, len(bi_list) - 1):
if i > bi_index + 2:
last_seg_peak = bi_list[i-2].high
# last seg is down
if last_seg.dir == Chan_SEG_DIR.DOWN:
if bi_list[i].dir == Chan_BI_DIR.UP:
if bi_list[i].high > last_seg_peak:
print("Last seg is broken, create a new seg. 2")
last_seg.pre_set_end_bi(bi_list[i-1])
seg = ChanSEG(bi_list[i], len(seg_list), Chan_SEG_DIR.UP, bi)
seg_list.append(seg)
last_seg = seg
last_seg_bi = bi_list[i]
break
"""
#self.cal_bi_zs(seg_list)
return seg_list
-704
View File
@@ -1,704 +0,0 @@
"""TF_DF builder mixin — 由 split_tfdf_builders 自动生成,逻辑与原 TF_DF 一致。"""
from __future__ import annotations
from datetime import timedelta
from decimal import Decimal
import numpy as np
import pandas as pd
import talib.abstract as ta
from pandas import DataFrame
from technical.util import resample_to_interval
from chanlun.core.ChanBI import ChanBI
from chanlun.core.ChanBIZS import ChanBIZS
from chanlun.core.ChanBSP import ChanBSP
from chanlun.core.ChanEnum import (
Chan_BI_DIR,
Chan_BSP_DIR,
Chan_BSP_TYPE,
Chan_FX_TYPE,
Chan_K_DIR,
Chan_KLC_FX,
Chan_KLC_STATE,
Chan_KLINE_DIR,
Chan_KLU_PATTERN,
Chan_PRICE_TREND,
Chan_SEG_DIR,
Chan_ZS_DIR,
)
from chanlun.core.ChanKLC import ChanKLC
from chanlun.core.ChanKLU import ChanKLU
from chanlun.core.ChanSBI import ChanSBI
from chanlun.core.ChanSEG import ChanSEG
from chanlun.core.ChanZS import ChanZS, ChanZS_Big
from chanlun.indicators.ChanMACD import ChanMACD
class ZsBuilderMixin:
def get_zs_state(self, df):
bi_list = self.cal_bi_list(self.get_klc_list(self.get_kl_data(df)))
seg_list = self.get_seg_list(bi_list)
zs_list = self.calculate_zs(seg_list)
for zs in zs_list:
last_zs = zs
return zs_list
def cal_bi_zs(self, seg_list):
bi_zs_list = []
for seg in seg_list:
zs_list = seg.cal_bi_zs()
if len(zs_list) > 0:
bi_zs_list = list(bi_zs_list) + list(zs_list)
return bi_zs_list
# 跨段不相连的中枢
def cal_bi_zs_list(self, bi_list):
"""
根据缠论笔中枢定义计算中枢参照 get_zs_list 线段中枢判断规则
从第4根笔开始索引3每3根笔为一组检查
上涨中枢后中枢 zd > 前中枢 zg不重叠上移
下跌中枢后中枢 zg < 前中枢 zd不重叠下移
中枢可按两笔一组继续扩展到5根7...
"""
bi_zs_list = []
if len(bi_list) < 3:
return bi_zs_list
last_zs = None
start_idx = 3
while start_idx < len(bi_list):
if start_idx + 2 >= len(bi_list):
break
bi1 = bi_list[start_idx]
bi2 = bi_list[start_idx + 1]
bi3 = bi_list[start_idx + 2]
if not (bi1.is_sure and bi2.is_sure and bi3.is_sure):
start_idx += 1
continue
zg = min(bi1.high, bi2.high, bi3.high)
zd = max(bi1.low, bi2.low, bi3.low)
if zg <= zd:
start_idx += 1
continue
valid = False
if last_zs is None:
if bi1.dir == Chan_BI_DIR.DOWN:
zs_dir = Chan_ZS_DIR.UP
valid = (bi2.dir == Chan_BI_DIR.UP and bi3.dir == Chan_BI_DIR.DOWN)
else:
zs_dir = Chan_ZS_DIR.DOWN
valid = (bi2.dir == Chan_BI_DIR.DOWN and bi3.dir == Chan_BI_DIR.UP)
else:
is_up_zs = zg > last_zs.zg
is_down_zs = zd < last_zs.zd
if is_up_zs:
zs_dir = Chan_ZS_DIR.UP
valid = (bi1.dir == Chan_BI_DIR.DOWN and bi2.dir == Chan_BI_DIR.UP and bi3.dir == Chan_BI_DIR.DOWN)
elif is_down_zs:
zs_dir = Chan_ZS_DIR.DOWN
valid = (bi1.dir == Chan_BI_DIR.UP and bi2.dir == Chan_BI_DIR.DOWN and bi3.dir == Chan_BI_DIR.UP)
if not valid:
start_idx += 1
continue
gg = max(bi1.high, bi2.high, bi3.high)
dd = min(bi1.low, bi2.low, bi3.low)
zs = ChanBIZS(bi1, len(bi_zs_list), zs_dir)
zs.set_zg(zg)
zs.set_zd(zd)
zs.set_gg(gg)
zs.set_dd(dd)
zs.is_sure = False
zs.bi_list = [bi1, bi2, bi3]
added_after_leave = []
leave_index = start_idx + 4
while leave_index < len(bi_list):
b = bi_list[leave_index]
if not b.is_sure:
break
if b.high >= zs.zd and b.low <= zs.zg:
added_after_leave.append(b.pre)
added_after_leave.append(b)
else:
break
leave_index += 2
if added_after_leave:
bis_for_zs = list(zs.bi_list) + list(added_after_leave)
bi_highs = [bi.high for bi in bis_for_zs]
bi_lows = [bi.low for bi in bis_for_zs]
zs.set_gg(max(bi_highs))
zs.set_dd(min(bi_lows))
zs.bi_list = bis_for_zs
bi = bis_for_zs[-1]
if bi.is_sure:
zs.set_end_bi(bi, bi.sure_time)
start_idx = start_idx + len(added_after_leave)
else:
zs.set_end_bi(bi3, bi3.sure_time)
if last_zs:
last_zs.set_next(zs)
zs.set_pre(last_zs)
bi_zs_list.append(zs)
last_zs = zs
start_idx += 4
if last_zs:
last_zs.is_sure = bi_list[-1].is_sure
if last_zs and not last_zs.is_sure:
if last_zs.bi_list and len(last_zs.bi_list) > 0:
last_bi_of_zs = last_zs.bi_list[-1]
last_bi_idx = -1
for i, bi in enumerate(bi_list):
if bi == last_bi_of_zs:
last_bi_idx = i
break
has_leave = False
if last_bi_idx >= 0 and last_bi_idx + 1 < len(bi_list):
for i in range(last_bi_idx + 1, len(bi_list)):
bi = bi_list[i]
if bi.is_sure:
leave = (bi.low > last_zs.zg and bi.high > last_zs.zg) or \
(bi.high < last_zs.zd and bi.low < last_zs.zd)
if leave:
has_leave = True
break
if has_leave:
if last_bi_of_zs.is_sure:
last_zs.set_end_bi(last_bi_of_zs, last_bi_of_zs.sure_time)
return bi_zs_list
def get_bi_zs_list(self, bi_list):
"""
根据缠论笔中枢定义计算中枢完全参照 get_seg_zs_list 线段中枢判断规则
从第4根笔开始索引3每3根笔为一组检查
上涨中枢后中枢 zd > 前中枢 zg不重叠上移
下跌中枢后中枢 zg < 前中枢 zd不重叠下移
盘整/扩张后中枢与前中枢整体区间有交集 合并扩展
中枢可按两笔一组继续扩展到5根7...
"""
bi_zs_list = []
if len(bi_list) < 3:
return bi_zs_list
last_zs = None
start_idx = 3
while start_idx < len(bi_list):
if start_idx + 2 >= len(bi_list):
break
bi1 = bi_list[start_idx]
bi2 = bi_list[start_idx + 1]
bi3 = bi_list[start_idx + 2]
if not (bi1.is_sure and bi2.is_sure and bi3.is_sure):
start_idx += 1
continue
zg = min(bi1.high, bi2.high, bi3.high)
zd = max(bi1.low, bi2.low, bi3.low)
if zg <= zd:
start_idx += 1
continue
valid = False
if last_zs is None:
if bi1.dir == Chan_BI_DIR.DOWN:
zs_dir = Chan_ZS_DIR.UP
valid = (bi2.dir == Chan_BI_DIR.UP and bi3.dir == Chan_BI_DIR.DOWN)
else:
zs_dir = Chan_ZS_DIR.DOWN
valid = (bi2.dir == Chan_BI_DIR.DOWN and bi3.dir == Chan_BI_DIR.UP)
else:
is_up_zs = zd > last_zs.zg
is_down_zs = zg < last_zs.zd
if is_up_zs:
zs_dir = Chan_ZS_DIR.UP
valid = (bi1.dir == Chan_BI_DIR.DOWN and bi2.dir == Chan_BI_DIR.UP and bi3.dir == Chan_BI_DIR.DOWN)
elif is_down_zs:
zs_dir = Chan_ZS_DIR.DOWN
valid = (bi1.dir == Chan_BI_DIR.UP and bi2.dir == Chan_BI_DIR.DOWN and bi3.dir == Chan_BI_DIR.UP)
create_new_zs = False
if not valid:
# 如果新中枢和前一个中枢的中枢区间有重叠,不形成新中枢,合并扩展
if last_zs is not None:
is_in_last_zs = (zd > last_zs.zd and zd < last_zs.zg) or \
(zg < last_zs.zg and zg > last_zs.zd) or \
(zg > last_zs.zg and zd < last_zs.zd) or \
(zg < last_zs.zg and zd > last_zs.zd)
if is_in_last_zs:
# 扩展当前中枢:将 bi1-bi3 加入 last_zs
for bi in [bi1, bi2, bi3]:
if bi not in last_zs.bi_list:
last_zs.add_bi(bi)
create_new_zs = False
else:
start_idx += 1
continue
else:
start_idx += 1
continue
else:
create_new_zs = True
# 新中枢形成时确认前一个中枢
if last_zs and create_new_zs:
last_bi = last_zs.bi_list[-1]
if last_bi and last_bi.is_sure:
last_zs.is_sure = True
last_zs.set_end_bi(last_bi, last_bi.sure_time)
zs = last_zs
if create_new_zs:
gg = max(bi1.high, bi2.high, bi3.high)
dd = min(bi1.low, bi2.low, bi3.low)
zs = ChanBIZS(bi1, len(bi_zs_list), zs_dir)
zs.set_zg(zg)
zs.set_zd(zd)
zs.set_gg(gg)
zs.set_dd(dd)
zs.is_sure = False
zs.bi_list = [bi1, bi2, bi3]
# 离开后回抽扩展检查
added_after_leave = []
leave_index = start_idx + 4
while leave_index < len(bi_list):
b = bi_list[leave_index]
if not b.is_sure:
break
if b.high >= zs.zd and b.low <= zs.zg:
added_after_leave.append(b.pre)
added_after_leave.append(b)
else:
break
leave_index += 2
if added_after_leave:
bis_for_zs = list(zs.bi_list) + list(added_after_leave)
bi_highs = [bi.high for bi in bis_for_zs]
bi_lows = [bi.low for bi in bis_for_zs]
zs.set_gg(max(bi_highs))
zs.set_dd(min(bi_lows))
zs.bi_list = bis_for_zs
bi = bis_for_zs[-1]
if bi.is_sure:
zs.set_end_bi(bi, bi.sure_time)
start_idx = start_idx + len(added_after_leave)
else:
if create_new_zs:
zs.set_end_bi(bi3, bi3.sure_time)
if create_new_zs:
if last_zs:
last_zs.set_next(zs)
zs.set_pre(last_zs)
bi_zs_list.append(zs)
last_zs = zs
start_idx += 4
# 最后一个中枢:根据 bi_list 最后一笔确认状态
if last_zs:
last_zs.is_sure = bi_list[-1].is_sure
if last_zs and not last_zs.is_sure:
if last_zs.bi_list and len(last_zs.bi_list) > 0:
last_bi_of_zs = last_zs.bi_list[-1]
last_bi_idx = -1
for i, bi in enumerate(bi_list):
if bi == last_bi_of_zs:
last_bi_idx = i
break
has_leave = False
if last_bi_idx >= 0 and last_bi_idx + 1 < len(bi_list):
for i in range(last_bi_idx + 1, len(bi_list)):
bi = bi_list[i]
if bi.is_sure:
leave = (bi.low > last_zs.zg and bi.high > last_zs.zg) or \
(bi.high < last_zs.zd and bi.low < last_zs.zd)
if leave:
has_leave = True
break
if has_leave:
if last_bi_of_zs.is_sure:
last_zs.set_end_bi(last_bi_of_zs, last_bi_of_zs.sure_time)
return bi_zs_list
def cal_bi_zs_list_pure(self, bi_list):
bi_zs_list = []
if len(bi_list) < 3:
return bi_zs_list
def get_zs_range(bis):
bis_list = bis[0:3]
zg = min(bi.high for bi in bis_list)
zd = max(bi.low for bi in bis_list)
dd = min(bi.low for bi in bis_list)
gg = max(bi.high for bi in bis_list)
return zg, zd, dd, gg
def is_bi_overlap_range(bi, zg, zd):
return bi.high >= zd and bi.low <= zg
def check_zs_position_filter(last_zs, zg, zd, bis):
if last_zs is None:
return True
if zg <= last_zs.zd:
return bis[0].dir == Chan_BI_DIR.UP and bis[-1].dir == Chan_BI_DIR.UP
if zd >= last_zs.zg:
return bis[0].dir == Chan_BI_DIR.DOWN and bis[-1].dir == Chan_BI_DIR.DOWN
return True
def set_zs_bi_list(zs, bis):
zs.bi_list = list(bis)
for bi in zs.bi_list:
bi.set_bi_zs(zs)
#zs.set_gg(max(bi.high for bi in zs.bi_list))
#zs.set_dd(min(bi.low for bi in zs.bi_list))
zs.classify_zs()
last_zs = None
start_idx = 0
while start_idx + 2 < len(bi_list):
bi1 = bi_list[start_idx]
bi2 = bi_list[start_idx + 1]
bi3 = bi_list[start_idx + 2]
if not (bi1.is_sure and bi2.is_sure and bi3.is_sure):
start_idx += 1
continue
if not (bi1.dir != bi2.dir and bi1.dir == bi3.dir):
start_idx += 1
continue
zg, zd, dd, gg = get_zs_range([bi1, bi2, bi3])
if zg <= zd:
start_idx += 1
continue
bis_for_zs = [bi1, bi2, bi3]
extend_idx = start_idx + 3
while extend_idx + 1 < len(bi_list):
leave_bi = bi_list[extend_idx]
back_bi = bi_list[extend_idx + 1]
if not (leave_bi.is_sure and back_bi.is_sure):
break
if not is_bi_overlap_range(back_bi, zg, zd):
break
bis_for_zs.append(leave_bi)
bis_for_zs.append(back_bi)
extend_idx += 2
if not check_zs_position_filter(last_zs, zg, zd, bis_for_zs):
start_idx += 1
continue
zs_dir = Chan_ZS_DIR.UP if bi1.dir == Chan_BI_DIR.DOWN else Chan_ZS_DIR.DOWN
zs = ChanBIZS(bi1, len(bi_zs_list), zs_dir)
zs.set_zg(zg)
zs.set_zd(zd)
zs.set_dd(dd)
zs.set_gg(gg)
set_zs_bi_list(zs, bis_for_zs)
zs.set_end_bi(bis_for_zs[-1], bis_for_zs[-1].sure_time)
if last_zs:
last_zs.set_next(zs)
zs.set_pre(last_zs)
bi_zs_list.append(zs)
last_zs = zs
start_idx = start_idx + len(bis_for_zs)
# 与 cal_bi_zs_list 一致:最后一笔未确认时末中枢标为未完成;若其后已出现确认的离开笔,仍按离开前最后一笔确认中枢结束
if last_zs:
last_zs.is_sure = bi_list[-1].is_sure
if last_zs and not last_zs.is_sure:
if last_zs.bi_list and len(last_zs.bi_list) > 0:
last_bi_of_zs = last_zs.bi_list[-1]
last_bi_idx = -1
for i, bi in enumerate(bi_list):
if bi == last_bi_of_zs:
last_bi_idx = i
break
has_leave = False
if last_bi_idx >= 0 and last_bi_idx + 1 < len(bi_list):
for i in range(last_bi_idx + 1, len(bi_list)):
bi = bi_list[i]
if bi.is_sure:
leave = (bi.low > last_zs.zg and bi.high > last_zs.zg) or \
(bi.high < last_zs.zd and bi.low < last_zs.zd)
if leave:
has_leave = True
break
if has_leave:
if last_bi_of_zs.is_sure:
last_zs.set_end_bi(last_bi_of_zs, last_bi_of_zs.sure_time)
return bi_zs_list
def get_zs_list(self, bi_list, seg_list):
"""兼容历史 API:线段中枢列表。"""
return self.get_seg_zs_list(seg_list)
def calculate_seg_zs(self, seg_list):
return self.get_seg_zs_list(seg_list)
def get_seg_zs_list(self, seg_list):
"""
根据缠论线段中枢定义计算中枢
从第4根线段开始索引3每3根线段为一组检查
上涨中枢后中枢 zd > 前中枢 zg不重叠上移
下跌中枢后中枢 zg < 前中枢 zd不重叠下移
盘整/扩张后中枢与前中枢整体区间GG/DD有交集
中枢可按两段一组继续扩展到5根7...
"""
zs_list = []
if len(seg_list) < 3:
return zs_list
last_zs = None
# 从第4根线段开始(索引3),每3根为一组
start_idx = 3
while start_idx < len(seg_list):
# 取连续3个线段
if start_idx + 2 >= len(seg_list):
break
seg1 = seg_list[start_idx]
seg2 = seg_list[start_idx + 1]
seg3 = seg_list[start_idx + 2]
# 三个线段都必须是已确认的
if not (seg1.is_sure and seg2.is_sure and seg3.is_sure):
start_idx += 1
continue
# 计算这3个线段的中枢区间
zg = min(seg1.high, seg2.high, seg3.high)
zd = max(seg1.low, seg2.low, seg3.low)
if zg <= zd:
start_idx += 1
#print(seg1.start_bi.start_klc.end_time, "not valid", zg, zd)
continue
# 判断中枢类型(按注释定义)
# 上涨中枢:后中枢 zd > 前中枢 zg(不重叠上移)
# 下跌中枢:后中枢 zg < 前中枢 zd(不重叠下移)
# 盘整/扩张:后中枢与前中枢区间有交集
if last_zs is None:
# 第一个中枢仅按线段形态判定方向
if seg1.dir == Chan_SEG_DIR.DOWN:
# 下跌+上涨+下跌,对应上涨中枢
zs_dir = Chan_ZS_DIR.UP
valid = (seg2.dir == Chan_SEG_DIR.UP and seg3.dir == Chan_SEG_DIR.DOWN)
else:
# 上涨+下跌+上涨,对应下跌中枢
zs_dir = Chan_ZS_DIR.DOWN
valid = (seg2.dir == Chan_SEG_DIR.DOWN and seg3.dir == Chan_SEG_DIR.UP)
else:
is_up_zs = zd > last_zs.zg
is_down_zs = zg < last_zs.zd
if is_up_zs:
# 不重叠上移
zs_dir = Chan_ZS_DIR.UP
valid = (seg1.dir == Chan_SEG_DIR.DOWN and seg2.dir == Chan_SEG_DIR.UP and seg3.dir == Chan_SEG_DIR.DOWN)
elif is_down_zs:
# 不重叠下移
zs_dir = Chan_ZS_DIR.DOWN
valid = (seg1.dir == Chan_SEG_DIR.UP and seg2.dir == Chan_SEG_DIR.DOWN and seg3.dir == Chan_SEG_DIR.UP)
create_new_zs = False
# 验证是否有效
if not valid:
# 如果新中枢和前一个中枢的中枢区间有重叠,不行成新中枢需要合并两个中枢
is_in_last_zs = (zd > last_zs.zd and zd < last_zs.zg) or (zg < last_zs.zg and zg > last_zs.zd) or (zg > last_zs.zg and zd < last_zs.zd) or (zg < last_zs.zg and zd > last_zs.zd)
if is_in_last_zs:
#print(seg1.start_time, "New zs is in last zs, not valid")
last_zs.extend_zs(seg_list[last_zs.seg_list[-1].index:(seg3.index + 1)])
create_new_zs = False
else:
start_idx += 1
continue
else:
create_new_zs = True
if last_zs and create_new_zs:
last_seg = last_zs.seg_list[-1]
last_bi = last_seg.end_bi
if last_bi:
last_zs.is_sure = True
last_zs.set_end_klc(last_bi.end_klc, last_bi.sure_time, 0, last_seg)
last_zs.set_end_seg(last_seg)
zs = last_zs
if create_new_zs:
# 创建新中枢
gg = max(seg1.high, seg2.high, seg3.high)
dd = min(seg1.low, seg2.low, seg3.low)
zs = ChanZS(seg1, len(zs_list), zs_dir)
zs.set_zg(zg)
zs.set_zd(zd)
zs.set_gg(gg)
zs.set_dd(dd)
zs.is_sure = False
zs.seg_list = [seg1, seg2, seg3]
# 若第二线段与 [zd,zg] 重叠(如离开后回抽回到前中枢)则并入扩展
added_after_leave = []
leave_index = start_idx + 4
is_break = False
while leave_index < len(seg_list):
s = seg_list[leave_index]
if not s.is_sure:
break
sh = max(s.start_bi.high, s.end_bi.high) if s.end_bi else s.start_bi.high
sl = min(s.start_bi.low, s.end_bi.low) if s.end_bi else s.start_bi.low
if sh >= zs.zd and sl <= zs.zg:
added_after_leave.append(s.pre)
added_after_leave.append(s)
leave_index += 2
else:
next_seg = s.next
if next_seg and next_seg.is_sure:
if next_seg.dir == Chan_SEG_DIR.UP:
if next_seg.high <= zs.zg and next_seg.low >= zs.zd:
leave_index += 2
continue
else:
is_break = True
else:
if next_seg.low >= zs.zd and next_seg.low <= zs.zg:
leave_index += 2
continue
else:
is_break = True
else:
break
if is_break:
break
if added_after_leave:
#print(len(added_after_leave))
segs_for_zs = list(zs.seg_list) + list(added_after_leave)
seg_highs = [s.high for s in segs_for_zs]
seg_lows = [s.low for s in segs_for_zs]
zs.set_gg(max(seg_highs))
zs.set_dd(min(seg_lows))
zs.seg_list = segs_for_zs
seg = segs_for_zs[-1]
#if seg.end_bi:
#zs.set_end_klc(seg.end_bi.end_klc, seg.sure_time, 0, seg)
#zs.set_end_seg(seg)
#zs.is_sure = True
start_idx = start_idx + len(added_after_leave)
if last_zs and last_zs.index != zs.index:
last_zs.set_next(zs)
zs.set_pre(last_zs)
zs_list.append(zs)
last_zs = zs
# 移动到下一组
start_idx += 4
if last_zs:
last_zs.is_sure = seg_list[-1].is_sure
"""
# 处理最后一个未确认的中枢 - 不自动扩展,保持未完成状态
if last_zs and not last_zs.is_sure:
# 获取中枢最后一个线段的索引
if last_zs.seg_list and len(last_zs.seg_list) > 0:
last_seg_of_zs = last_zs.seg_list[-1]
# 找到这个线段在seg_list中的索引
last_seg_idx = -1
for i, seg in enumerate(seg_list):
if seg == last_seg_of_zs:
last_seg_idx = i
break
# 从中枢最后一个线段之后检查是否有离开
has_leave = False
if last_seg_idx >= 0 and last_seg_idx + 1 < len(seg_list):
for i in range(last_seg_idx + 1, len(seg_list)):
seg = seg_list[i]
if seg.is_sure:
# 检查是否离开中枢
leave = (seg.low > last_zs.zg and seg.high > last_zs.zg) or \
(seg.high < last_zs.zd and seg.low < last_zs.zd)
if leave:
has_leave = True
break
if not has_leave:
# 没有离开,保持未完成状态
pass
else:
# 有离开,确认中枢
if last_seg_of_zs.end_bi:
#print(last_seg_of_zs.start_time, "last_seg_of_zs.end_time", last_seg_of_zs.end_time)
last_zs.set_end_klc(last_seg_of_zs.end_bi.end_klc, last_seg_of_zs.sure_time, 0, last_seg_of_zs)
last_zs.set_end_seg(last_seg_of_zs)
last_zs.is_sure = True
"""
return zs_list
def get_big_zs_list(self, zs_list):
"""
中枢扩张将区间重叠的连续中枢合并为大级别中枢便于显示更大级别的震荡区间
重叠定义两中枢 [zd,zg] 有交集 (zs_i.zg >= zs_j.zd and zs_i.zd <= zs_j.zg)
"""
big_list = []
if len(zs_list) < 2:
return big_list
i = 0
while i < len(zs_list):
group = [zs_list[i]]
j = i + 1
while j < len(zs_list):
cur = zs_list[j]
# 与当前组内任一中枢有重叠即算扩张(通常只需与组内最后一个比)
last_in_group = group[-1]
overlap = (last_in_group.zg >= cur.zd and last_in_group.zd <= cur.zg)
if overlap:
group.append(cur)
j += 1
else:
break
if len(group) >= 2:
big = ChanZS_Big(group)
big.index = len(big_list)
big_list.append(big)
i = j if len(group) >= 2 else i + 1
return big_list
-84
View File
@@ -1,84 +0,0 @@
from datetime import timedelta
import numpy as np
import pandas as pd
import talib.abstract as ta
from pandas import DataFrame
from technical.util import resample_to_interval
from chanlun.core.ChanBI import ChanBI
from chanlun.core.ChanBIZS import ChanBIZS
from chanlun.core.ChanBSP import ChanBSP
from chanlun.core.ChanEnum import (
Chan_BI_DIR,
Chan_BSP_DIR,
Chan_BSP_TYPE,
Chan_FX_TYPE,
Chan_K_DIR,
Chan_KLC_FX,
Chan_KLC_STATE,
Chan_KLINE_DIR,
Chan_KLU_PATTERN,
Chan_PRICE_TREND,
Chan_SEG_DIR,
Chan_ZS_DIR,
)
from chanlun.core.ChanKLC import ChanKLC
from chanlun.core.ChanKLU import ChanKLU
from chanlun.core.ChanSBI import ChanSBI
from chanlun.core.ChanSEG import ChanSEG
from chanlun.core.ChanZS import ChanZS, ChanZS_Big
from chanlun.indicators.ChanMACD import ChanMACD
from chanlun.pipeline.builders.bi import BiBuilderMixin
from chanlun.pipeline.builders.bsp import BspBuilderMixin
from chanlun.pipeline.builders.incremental import IncrementalBuilderMixin
from chanlun.pipeline.builders.indicators import IndicatorsBuilderMixin
from chanlun.pipeline.builders.kline import KlineBuilderMixin
from chanlun.pipeline.builders.seg import SegBuilderMixin
from chanlun.pipeline.builders.zs import ZsBuilderMixin
class TF_DF(IndicatorsBuilderMixin, KlineBuilderMixin, BiBuilderMixin, SegBuilderMixin, ZsBuilderMixin, BspBuilderMixin, IncrementalBuilderMixin):
def __init__(self, df=None, interval=0, timeframe=None):
if df is not None:
self.init_TF_DF(df, interval, timeframe)
def init_TF_DF(self, df, interval, timeframe):
self.timeframe = timeframe
self.interval = interval
# 检查 DataFrame 是否为空或没有 date 列
if df is None or df.empty:
raise ValueError(f"DataFrame for {timeframe} is empty. Please download data first.")
if 'date' not in df.columns:
raise ValueError(f"DataFrame for {timeframe} missing 'date' column. Columns: {df.columns.tolist()}")
# interval=1 时不需要重采样
if interval == 1:
self.dataframe = df.copy()
else:
self.dataframe = resample_to_interval(df, interval)
#print(self.timeframe, len(self.dataframe))
self.dataframe = self.add_indicators(self.dataframe)
self.klu_list = []
self.klc_list = []
self.bi_list = []
self.zs_list = []
self.bi_zs_list = []
self.bsp_list = []
self.seg_list = []
self.klc_fx_list = []
self.klu_list = self.cal_kl_data(self.dataframe)
self.klc_list = self.get_klc_list(self.klu_list)
self.bi_list = self.cal_bi_list(self.klc_list)
self.bi_zs_list = self.cal_bi_zs_list_pure(self.bi_list)
self.seg_list = self.get_seg_list(self.bi_list)
self.zs_list = self.get_zs_list(self.bi_list, self.seg_list)
self.big_zs_list = self.get_big_zs_list(self.zs_list)
# get_klc_list 内已算过 ChanMACD,直接复用
self.chanmacd = getattr(self, '_last_chan_macd', None)
if self.chanmacd is None:
self.chanmacd = ChanMACD(self.klu_list)
self.klu_list = self.chanmacd.klu_list
def get_current_klc(self):
if len(self.klc_list) > 0:
return self.klc_list[-2]
return None
-1
View File
@@ -1 +0,0 @@
from __future__ import annotations
-141
View File
@@ -1,141 +0,0 @@
from __future__ import annotations
import sys
import unittest
from pathlib import Path
import pandas as pd
_CHAN = Path(__file__).resolve().parents[2]
if str(_CHAN) not in sys.path:
sys.path.insert(0, str(_CHAN))
from chanlun.pipeline.timeframe import TF_DF # noqa: E402
def _zigzag_df(n=160, step=8):
dates = pd.date_range("2024-01-01", periods=n, freq="5min")
rows = []
price = 100.0
for i, date in enumerate(dates):
up = (i // step) % 2 == 0
if up:
o = price
c = price + 1.5
h = c + 0.3
l = o - 0.2
else:
o = price
c = price - 1.5
h = o + 0.2
l = c - 0.3
price = c
rows.append(
{
"date": date,
"open": o,
"high": h,
"low": l,
"close": c,
"volume": 1.0,
}
)
return pd.DataFrame(rows)
def _sure_bi_key(bi):
return (str(bi.start_time), bi.dir.name, round(float(bi.high), 6), round(float(bi.low), 6))
def _zs_key(zs):
return (
str(zs.start_time),
round(float(zs.zg), 6),
round(float(zs.zd), 6),
len(zs.bi_list),
)
class TestIncremental(unittest.TestCase):
def test_init_stream_matches_batch_push(self):
df = _zigzag_df()
stream = TF_DF()
stream.init_stream(df, 1, "5m")
batch = TF_DF()
indexed = batch.add_indicators(df.copy())
klu = batch.cal_kl_data(indexed)
klc = []
last = None
for k in klu:
batch._push_klu_into_klc_list(klc, k, last)
last = k
batch.klc_list = klc
batch.rebuild_bi_zs()
self.assertEqual(len(stream.klu_list), len(klu))
self.assertEqual(len(stream.klc_list), len(klc))
self.assertEqual(
[_sure_bi_key(b) for b in stream.bi_list if b.is_sure],
[_sure_bi_key(b) for b in batch.bi_list if b.is_sure],
)
self.assertEqual(
[_zs_key(z) for z in stream.bi_zs_list],
[_zs_key(z) for z in batch.bi_zs_list],
)
def test_append_bar_matches_init_stream(self):
df = _zigzag_df()
stream = TF_DF()
stream.init_stream(df, 1, "5m")
inc = TF_DF()
for _, row in df.iterrows():
inc.append_bar(row)
self.assertEqual(len(inc.klu_list), len(stream.klu_list))
self.assertEqual(len(inc.klc_list), len(stream.klc_list))
self.assertEqual(
[_sure_bi_key(b) for b in inc.bi_list if b.is_sure],
[_sure_bi_key(b) for b in stream.bi_list if b.is_sure],
)
self.assertEqual(
[_zs_key(z) for z in inc.bi_zs_list],
[_zs_key(z) for z in stream.bi_zs_list],
)
def test_replace_last_bar_keeps_count(self):
df = _zigzag_df(n=80)
tf = TF_DF()
tf.init_stream(df, 1, "5m")
n_klu = len(tf.klu_list)
last = df.iloc[-1].copy()
last["close"] = float(last["close"]) + 0.01
last["high"] = max(float(last["high"]), float(last["close"]))
tf.replace_last_bar(last)
self.assertEqual(len(tf.klu_list), n_klu)
self.assertGreater(len(tf.klc_list), 0)
def test_check_fx_skips_forming_right_wing(self):
from types import SimpleNamespace
from chanlun.core.ChanEnum import Chan_FX_TYPE
tf = TF_DF()
pre = SimpleNamespace(high=10, low=8)
nxt_open = SimpleNamespace(high=11, low=7, end_klu=None)
nxt_done = SimpleNamespace(high=11, low=7, end_klu=object())
center = SimpleNamespace(
pre=pre,
next=nxt_open,
high=12,
low=9,
set_fx=lambda *_a, **_k: None,
)
self.assertEqual(tf.check_fx(center), Chan_FX_TYPE.UNKNOWN)
center.next = nxt_done
self.assertEqual(tf.check_fx(center), Chan_FX_TYPE.TOP)
if __name__ == "__main__":
unittest.main()
+84
View File
@@ -0,0 +1,84 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 1,
"stake_currency": "USDT",
"stake_amount": "unlimited",
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD",
"dry_run": true,
"db_url": "sqlite:///tradesv3.btc_chan.sqlite",
"dry_run_wallet": 1000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"can_short" : true,
"timeframe" : "1m",
"process_only_new_candles" : false,
"unfilledtimeout": {
"entry": 1,
"exit": 1,
"exit_timeout_count": 0,
"unit": "minutes"
},
"entry_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1,
"price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"exit_pricing":{
"price_side": "same",
"use_order_book": true,
"order_book_top": 1
},
"exchange": {
"name": "binance",
"key": "hvoXanRExQvcN4tyGFvEnsSF4gqxXp6ZJnBu5lnhvlVuHaDbj2PhLBQGCLkkyeI8",
"secret": "3UKA2oyDj7OoXrausmnaLwLlNfXmlNf2imBdmQqqKHArcJfk6X9xjaUF19wzu82l",
"ccxt_config": {},
"ccxt_async_config": {},
"pair_whitelist": [
"SOL/USDT:USDT",
],
"pair_blacklist": [
"BNB/.*"
]
},
"pairlists": [
{
"method": "StaticPairList",
"number_assets": 1,
"sort_key": "quoteVolume",
"min_value": 0,
"refresh_period": 1800,
}
],
"telegram": {
"enabled": false,
"token": "7677670958:AAFL_jgZvNUTPR3R3vWieREX_tDVi9w2C1Y",
"chat_id": "580807463"
},
"api_server": {
"enabled": true,
"listen_ip_address": "127.0.0.1",
"listen_port": 8882,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d",
"ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg",
"CORS_origins": [],
"username": "freqtrader",
"password": "FreqTrade007"
},
"bot_name": "freqtrade",
"initial_state": "running",
"force_entry_enable": false,
"internals": {
"process_throttle_secs": 2
}
}
+83
View File
@@ -0,0 +1,83 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 1,
"stake_currency": "USDT",
"stake_amount": "unlimited",
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD",
"dry_run": true,
"db_url": "sqlite:///tradesv3.btc_perpetual.sqlite",
"dry_run_wallet": 1000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"can_short": true,
"timeframe": "1m",
"process_only_new_candles": false,
"unfilledtimeout": {
"entry": 1,
"exit": 1,
"exit_timeout_count": 5,
"unit": "minutes"
},
"entry_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1,
"price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"exit_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1
},
"exchange": {
"name": "binance",
"key": "YOUR_BINANCE_API_KEY",
"secret": "YOUR_BINANCE_API_SECRET",
"ccxt_config": {},
"ccxt_async_config": {},
"pair_whitelist": [
"BTC/USDT:USDT"
],
"pair_blacklist": [
"BNB/.*"
]
},
"pairlists": [
{
"method": "StaticPairList",
"number_assets": 1,
"sort_key": "quoteVolume",
"min_value": 0,
"refresh_period": 1800
}
],
"telegram": {
"enabled": true,
"token": "YOUR_TELEGRAM_BOT_TOKEN",
"chat_id": "YOUR_TELEGRAM_CHAT_ID"
},
"api_server": {
"enabled": true,
"listen_ip_address": "0.0.0.0",
"listen_port": 8820,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "change_me_to_a_random_secret_key",
"ws_token": "change_me_to_a_random_ws_token",
"CORS_origins": [],
"username": "freqtrader",
"password": "FreqTrade007"
},
"bot_name": "BTC_Perpetual_Bot",
"initial_state": "running",
"force_entry_enable": false,
"internals": {
"process_throttle_secs": 2
}
}
+84
View File
@@ -0,0 +1,84 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 1,
"stake_currency": "USDT",
"stake_amount": "unlimited",
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD",
"dry_run": true,
"db_url": "sqlite:///tradesv3.chan.sqlite",
"dry_run_wallet": 1000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"can_short" : true,
"timeframe" : "1m",
"process_only_new_candles" : false,
"unfilledtimeout": {
"entry": 1,
"exit": 1,
"exit_timeout_count": 0,
"unit": "minutes"
},
"entry_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1,
"price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"exit_pricing":{
"price_side": "same",
"use_order_book": true,
"order_book_top": 1
},
"exchange": {
"name": "binance",
"key": "hvoXanRExQvcN4tyGFvEnsSF4gqxXp6ZJnBu5lnhvlVuHaDbj2PhLBQGCLkkyeI8",
"secret": "3UKA2oyDj7OoXrausmnaLwLlNfXmlNf2imBdmQqqKHArcJfk6X9xjaUF19wzu82l",
"ccxt_config": {},
"ccxt_async_config": {},
"pair_whitelist": [
"BTC/USDT:USDT",
],
"pair_blacklist": [
"BNB/.*"
]
},
"pairlists": [
{
"method": "StaticPairList",
"number_assets": 1,
"sort_key": "quoteVolume",
"min_value": 0,
"refresh_period": 1800,
}
],
"telegram": {
"enabled": false,
"token": "7677670958:AAFL_jgZvNUTPR3R3vWieREX_tDVi9w2C1Y",
"chat_id": "580807463"
},
"api_server": {
"enabled": true,
"listen_ip_address": "127.0.0.1",
"listen_port": 8800,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d",
"ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg",
"CORS_origins": [],
"username": "freqtrader",
"password": "FreqTrade007"
},
"bot_name": "freqtrade",
"initial_state": "running",
"force_entry_enable": false,
"internals": {
"process_throttle_secs": 2
}
}
+83
View File
@@ -0,0 +1,83 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 1,
"stake_currency": "USDT",
"stake_amount": "unlimited",
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD",
"dry_run": true,
"db_url": "sqlite:///tradesv3.chanlun_btc_15.sqlite",
"dry_run_wallet": 1000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"can_short" : true,
"timeframe" : "1m",
"process_only_new_candles" : false,
"unfilledtimeout": {
"entry": 1,
"exit": 1,
"exit_timeout_count": 5,
"unit": "minutes"
},
"entry_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1,
"price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"exit_pricing":{
"price_side": "same",
"use_order_book": true,
"order_book_top": 1
},
"exchange": {
"name": "binance",
"key": "hvoXanRExQvcN4tyGFvEnsSF4gqxXp6ZJnBu5lnhvlVuHaDbj2PhLBQGCLkkyeI8",
"secret": "3UKA2oyDj7OoXrausmnaLwLlNfXmlNf2imBdmQqqKHArcJfk6X9xjaUF19wzu82l",
"ccxt_config": {},
"ccxt_async_config": {},
"pair_whitelist": [
"BTC/USDT:USDT"
],
"pair_blacklist": [
"BNB/.*"
]
},
"pairlists": [
{
"method": "StaticPairList",
"number_assets": 1,
"sort_key": "quoteVolume",
"min_value": 0,
"refresh_period": 1800
}
],
"telegram": {
"enabled": false,
"token": "7677670958:AAFL_jgZvNUTPR3R3vWieREX_tDVi9w2C1Y",
"chat_id": "580807463"
},
"api_server": {
"enabled": true,
"listen_ip_address": "127.0.0.1",
"listen_port": 8811,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d",
"ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg",
"CORS_origins": [],
"username": "freqtrader",
"password": "FreqTrade007"
},
"bot_name": "freqtrade",
"initial_state": "running",
"force_entry_enable": false,
"internals": {
"process_throttle_secs": 2
}
}
+83
View File
@@ -0,0 +1,83 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 1,
"stake_currency": "USDT",
"stake_amount": "unlimited",
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD",
"dry_run": true,
"db_url": "sqlite:///tradesv3.chanlun_btc_15.sqlite",
"dry_run_wallet": 1000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"can_short" : true,
"timeframe" : "1m",
"process_only_new_candles" : false,
"unfilledtimeout": {
"entry": 1,
"exit": 1,
"exit_timeout_count": 5,
"unit": "minutes"
},
"entry_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1,
"price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"exit_pricing":{
"price_side": "same",
"use_order_book": true,
"order_book_top": 1
},
"exchange": {
"name": "binance",
"key": "hvoXanRExQvcN4tyGFvEnsSF4gqxXp6ZJnBu5lnhvlVuHaDbj2PhLBQGCLkkyeI8",
"secret": "3UKA2oyDj7OoXrausmnaLwLlNfXmlNf2imBdmQqqKHArcJfk6X9xjaUF19wzu82l",
"ccxt_config": {},
"ccxt_async_config": {},
"pair_whitelist": [
"BTC/USDT:USDT"
],
"pair_blacklist": [
"BNB/.*"
]
},
"pairlists": [
{
"method": "StaticPairList",
"number_assets": 1,
"sort_key": "quoteVolume",
"min_value": 0,
"refresh_period": 1800
}
],
"telegram": {
"enabled": true,
"token": "7677670958:AAFL_jgZvNUTPR3R3vWieREX_tDVi9w2C1Y",
"chat_id": "580807463"
},
"api_server": {
"enabled": true,
"listen_ip_address": "0.0.0.0",
"listen_port": 8813,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d",
"ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg",
"CORS_origins": [],
"username": "freqtrader",
"password": "FreqTrade007"
},
"bot_name": "freqtrade",
"initial_state": "running",
"force_entry_enable": false,
"internals": {
"process_throttle_secs": 2
}
}
+83
View File
@@ -0,0 +1,83 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 1,
"stake_currency": "USDT",
"stake_amount": "unlimited",
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD",
"dry_run": true,
"db_url": "sqlite:///tradesv3.chanlun_btc_60.sqlite",
"dry_run_wallet": 1000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"can_short" : true,
"timeframe" : "1m",
"process_only_new_candles" : false,
"unfilledtimeout": {
"entry": 1,
"exit": 1,
"exit_timeout_count": 5,
"unit": "minutes"
},
"entry_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1,
"price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"exit_pricing":{
"price_side": "same",
"use_order_book": true,
"order_book_top": 1
},
"exchange": {
"name": "binance",
"key": "hvoXanRExQvcN4tyGFvEnsSF4gqxXp6ZJnBu5lnhvlVuHaDbj2PhLBQGCLkkyeI8",
"secret": "3UKA2oyDj7OoXrausmnaLwLlNfXmlNf2imBdmQqqKHArcJfk6X9xjaUF19wzu82l",
"ccxt_config": {},
"ccxt_async_config": {},
"pair_whitelist": [
"BTC/USDT:USDT"
],
"pair_blacklist": [
"BNB/.*"
]
},
"pairlists": [
{
"method": "StaticPairList",
"number_assets": 1,
"sort_key": "quoteVolume",
"min_value": 0,
"refresh_period": 1800
}
],
"telegram": {
"enabled": true,
"token": "7677670958:AAFL_jgZvNUTPR3R3vWieREX_tDVi9w2C1Y",
"chat_id": "580807463"
},
"api_server": {
"enabled": true,
"listen_ip_address": "0.0.0.0",
"listen_port": 8814,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d",
"ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg",
"CORS_origins": [],
"username": "freqtrader",
"password": "FreqTrade007"
},
"bot_name": "freqtrade",
"initial_state": "running",
"force_entry_enable": false,
"internals": {
"process_throttle_secs": 2
}
}
+83
View File
@@ -0,0 +1,83 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 1,
"stake_currency": "USDT",
"stake_amount": "unlimited",
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD",
"dry_run": true,
"db_url": "sqlite:///tradesv3.chanlun_btc_k.sqlite",
"dry_run_wallet": 1000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"can_short" : true,
"timeframe" : "1m",
"process_only_new_candles" : false,
"unfilledtimeout": {
"entry": 1,
"exit": 1,
"exit_timeout_count": 5,
"unit": "minutes"
},
"entry_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1,
"price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"exit_pricing":{
"price_side": "same",
"use_order_book": true,
"order_book_top": 1
},
"exchange": {
"name": "binance",
"key": "hvoXanRExQvcN4tyGFvEnsSF4gqxXp6ZJnBu5lnhvlVuHaDbj2PhLBQGCLkkyeI8",
"secret": "3UKA2oyDj7OoXrausmnaLwLlNfXmlNf2imBdmQqqKHArcJfk6X9xjaUF19wzu82l",
"ccxt_config": {},
"ccxt_async_config": {},
"pair_whitelist": [
"BTC/USDT:USDT"
],
"pair_blacklist": [
"BNB/.*"
]
},
"pairlists": [
{
"method": "StaticPairList",
"number_assets": 1,
"sort_key": "quoteVolume",
"min_value": 0,
"refresh_period": 1800
}
],
"telegram": {
"enabled": false,
"token": "7677670958:AAFL_jgZvNUTPR3R3vWieREX_tDVi9w2C1Y",
"chat_id": "580807463"
},
"api_server": {
"enabled": true,
"listen_ip_address": "127.0.0.1",
"listen_port": 8815,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d",
"ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg",
"CORS_origins": [],
"username": "freqtrader",
"password": "FreqTrade007"
},
"bot_name": "freqtrade",
"initial_state": "running",
"force_entry_enable": false,
"internals": {
"process_throttle_secs": 2
}
}
+87
View File
@@ -0,0 +1,87 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 1,
"stake_currency": "USDT",
"stake_amount": "unlimited",
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD",
"dry_run": true,
"db_url": "sqlite:///tradesv3.chanlun_btc_k.sqlite",
"dry_run_wallet": 1000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"can_short": true,
"timeframe": "1m",
"process_only_new_candles": false,
"unfilledtimeout": {
"entry": 1,
"exit": 1,
"exit_timeout_count": 5,
"unit": "minutes"
},
"entry_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1,
"price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"exit_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1
},
"exchange": {
"name": "binance",
"key": "hvoXanRExQvcN4tyGFvEnsSF4gqxXp6ZJnBu5lnhvlVuHaDbj2PhLBQGCLkkyeI8",
"secret": "3UKA2oyDj7OoXrausmnaLwLlNfXmlNf2imBdmQqqKHArcJfk6X9xjaUF19wzu82l",
"ccxt_config": {},
"ccxt_async_config": {},
"pair_whitelist": [
"BTC/USDT:USDT"
],
"pair_blacklist": [
"BNB/.*"
]
},
"pairlists": [
{
"method": "StaticPairList",
"number_assets": 1,
"sort_key": "quoteVolume",
"min_value": 0,
"refresh_period": 1800
}
],
"telegram": {
"enabled": false,
"token": "7677670958:AAFL_jgZvNUTPR3R3vWieREX_tDVi9w2C1Y",
"chat_id": "580807463"
},
"api_server": {
"enabled": true,
"listen_ip_address": "127.0.0.1",
"listen_port": 8815,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d",
"ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg",
"CORS_origins": [],
"username": "freqtrader",
"password": "FreqTrade007"
},
"bot_name": "freqtrade",
"initial_state": "running",
"force_entry_enable": false,
"internals": {
"process_throttle_secs": 2
}
}
+83
View File
@@ -0,0 +1,83 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 1,
"stake_currency": "USDT",
"stake_amount": "unlimited",
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD",
"dry_run": true,
"db_url": "sqlite:///tradesv3.chanlun_btc_15.sqlite",
"dry_run_wallet": 1000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"can_short" : true,
"timeframe" : "1m",
"process_only_new_candles" : false,
"unfilledtimeout": {
"entry": 1,
"exit": 1,
"exit_timeout_count": 5,
"unit": "minutes"
},
"entry_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1,
"price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"exit_pricing":{
"price_side": "same",
"use_order_book": true,
"order_book_top": 1
},
"exchange": {
"name": "binance",
"key": "hvoXanRExQvcN4tyGFvEnsSF4gqxXp6ZJnBu5lnhvlVuHaDbj2PhLBQGCLkkyeI8",
"secret": "3UKA2oyDj7OoXrausmnaLwLlNfXmlNf2imBdmQqqKHArcJfk6X9xjaUF19wzu82l",
"ccxt_config": {},
"ccxt_async_config": {},
"pair_whitelist": [
"BTC/USDT:USDT"
],
"pair_blacklist": [
"BNB/.*"
]
},
"pairlists": [
{
"method": "StaticPairList",
"number_assets": 1,
"sort_key": "quoteVolume",
"min_value": 0,
"refresh_period": 1800
}
],
"telegram": {
"enabled": false,
"token": "7677670958:AAFL_jgZvNUTPR3R3vWieREX_tDVi9w2C1Y",
"chat_id": "580807463"
},
"api_server": {
"enabled": true,
"listen_ip_address": "0.0.0.0",
"listen_port": 8820,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d",
"ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg",
"CORS_origins": [],
"username": "freqtrader",
"password": "FreqTrade007"
},
"bot_name": "freqtrade",
"initial_state": "running",
"force_entry_enable": false,
"internals": {
"process_throttle_secs": 2
}
}
+83
View File
@@ -0,0 +1,83 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 1,
"stake_currency": "USDT",
"stake_amount": "unlimited",
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD",
"dry_run": true,
"db_url": "sqlite:///tradesv3.chanlun_btc_15.sqlite",
"dry_run_wallet": 1000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"can_short" : true,
"timeframe" : "1m",
"process_only_new_candles" : false,
"unfilledtimeout": {
"entry": 1,
"exit": 1,
"exit_timeout_count": 5,
"unit": "minutes"
},
"entry_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1,
"price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"exit_pricing":{
"price_side": "same",
"use_order_book": true,
"order_book_top": 1
},
"exchange": {
"name": "binance",
"key": "hvoXanRExQvcN4tyGFvEnsSF4gqxXp6ZJnBu5lnhvlVuHaDbj2PhLBQGCLkkyeI8",
"secret": "3UKA2oyDj7OoXrausmnaLwLlNfXmlNf2imBdmQqqKHArcJfk6X9xjaUF19wzu82l",
"ccxt_config": {},
"ccxt_async_config": {},
"pair_whitelist": [
"BTC/USDT:USDT"
],
"pair_blacklist": [
"BNB/.*"
]
},
"pairlists": [
{
"method": "StaticPairList",
"number_assets": 1,
"sort_key": "quoteVolume",
"min_value": 0,
"refresh_period": 1800
}
],
"telegram": {
"enabled": true,
"token": "7677670958:AAFL_jgZvNUTPR3R3vWieREX_tDVi9w2C1Y",
"chat_id": "580807463"
},
"api_server": {
"enabled": true,
"listen_ip_address": "0.0.0.0",
"listen_port": 8820,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d",
"ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg",
"CORS_origins": [],
"username": "freqtrader",
"password": "FreqTrade007"
},
"bot_name": "freqtrade",
"initial_state": "running",
"force_entry_enable": false,
"internals": {
"process_throttle_secs": 2
}
}
+83
View File
@@ -0,0 +1,83 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 1,
"stake_currency": "USDT",
"stake_amount": "unlimited",
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD",
"dry_run": true,
"db_url": "sqlite:///tradesv3.chanlun_eth_60.sqlite",
"dry_run_wallet": 1000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"can_short" : true,
"timeframe" : "1m",
"process_only_new_candles" : false,
"unfilledtimeout": {
"entry": 1,
"exit": 1,
"exit_timeout_count": 5,
"unit": "minutes"
},
"entry_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1,
"price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"exit_pricing":{
"price_side": "same",
"use_order_book": true,
"order_book_top": 1
},
"exchange": {
"name": "binance",
"key": "hvoXanRExQvcN4tyGFvEnsSF4gqxXp6ZJnBu5lnhvlVuHaDbj2PhLBQGCLkkyeI8",
"secret": "3UKA2oyDj7OoXrausmnaLwLlNfXmlNf2imBdmQqqKHArcJfk6X9xjaUF19wzu82l",
"ccxt_config": {},
"ccxt_async_config": {},
"pair_whitelist": [
"ETH/USDT:USDT"
],
"pair_blacklist": [
"BNB/.*"
]
},
"pairlists": [
{
"method": "StaticPairList",
"number_assets": 1,
"sort_key": "quoteVolume",
"min_value": 0,
"refresh_period": 1800
}
],
"telegram": {
"enabled": true,
"token": "7677670958:AAFL_jgZvNUTPR3R3vWieREX_tDVi9w2C1Y",
"chat_id": "580807463"
},
"api_server": {
"enabled": true,
"listen_ip_address": "127.0.0.1",
"listen_port": 8813,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d",
"ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg",
"CORS_origins": [],
"username": "freqtrader",
"password": "FreqTrade007"
},
"bot_name": "freqtrade",
"initial_state": "running",
"force_entry_enable": false,
"internals": {
"process_throttle_secs": 2
}
}
+83
View File
@@ -0,0 +1,83 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 1,
"stake_currency": "USDT",
"stake_amount": "unlimited",
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD",
"dry_run": true,
"db_url": "sqlite:///tradesv3.chanlun_btc_15.sqlite",
"dry_run_wallet": 1000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"can_short" : true,
"timeframe" : "5m",
"process_only_new_candles" : false,
"unfilledtimeout": {
"entry": 1,
"exit": 1,
"exit_timeout_count": 5,
"unit": "minutes"
},
"entry_pricing": {
"price_side": "other",
"use_order_book": false,
"order_book_top": 1,
"price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"exit_pricing":{
"price_side": "other",
"use_order_book": false,
"order_book_top": 1
},
"exchange": {
"name": "binance",
"key": "hvoXanRExQvcN4tyGFvEnsSF4gqxXp6ZJnBu5lnhvlVuHaDbj2PhLBQGCLkkyeI8",
"secret": "3UKA2oyDj7OoXrausmnaLwLlNfXmlNf2imBdmQqqKHArcJfk6X9xjaUF19wzu82l",
"ccxt_config": {},
"ccxt_async_config": {},
"pair_whitelist": [
"BTC/USDT:USDT"
],
"pair_blacklist": [
"BNB/.*"
]
},
"pairlists": [
{
"method": "StaticPairList",
"number_assets": 1,
"sort_key": "quoteVolume",
"min_value": 0,
"refresh_period": 1800
}
],
"telegram": {
"enabled": true,
"token": "7677670958:AAFL_jgZvNUTPR3R3vWieREX_tDVi9w2C1Y",
"chat_id": "580807463"
},
"api_server": {
"enabled": true,
"listen_ip_address": "127.0.0.1",
"listen_port": 8813,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d",
"ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg",
"CORS_origins": [],
"username": "freqtrader",
"password": "FreqTrade007"
},
"bot_name": "freqtrade",
"initial_state": "running",
"force_entry_enable": false,
"internals": {
"process_throttle_secs": 2
}
}
+83
View File
@@ -0,0 +1,83 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 1,
"stake_currency": "USDT",
"stake_amount": "unlimited",
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD",
"dry_run": true,
"db_url": "sqlite:///tradesv3.chanlun_sol.sqlite",
"dry_run_wallet": 1000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"can_short" : true,
"timeframe" : "1m",
"process_only_new_candles" : false,
"unfilledtimeout": {
"entry": 1,
"exit": 1,
"exit_timeout_count": 0,
"unit": "minutes"
},
"entry_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1,
"price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"exit_pricing":{
"price_side": "same",
"use_order_book": true,
"order_book_top": 1
},
"exchange": {
"name": "binance",
"key": "hvoXanRExQvcN4tyGFvEnsSF4gqxXp6ZJnBu5lnhvlVuHaDbj2PhLBQGCLkkyeI8",
"secret": "3UKA2oyDj7OoXrausmnaLwLlNfXmlNf2imBdmQqqKHArcJfk6X9xjaUF19wzu82l",
"ccxt_config": {},
"ccxt_async_config": {},
"pair_whitelist": [
"BTC/USDT:USDT"
],
"pair_blacklist": [
"BNB/.*"
]
},
"pairlists": [
{
"method": "StaticPairList",
"number_assets": 1,
"sort_key": "quoteVolume",
"min_value": 0,
"refresh_period": 1800
}
],
"telegram": {
"enabled": false,
"token": "7677670958:AAFL_jgZvNUTPR3R3vWieREX_tDVi9w2C1Y",
"chat_id": "580807463"
},
"api_server": {
"enabled": true,
"listen_ip_address": "127.0.0.1",
"listen_port": 8811,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d",
"ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg",
"CORS_origins": [],
"username": "freqtrader",
"password": "FreqTrade007"
},
"bot_name": "freqtrade",
"initial_state": "running",
"force_entry_enable": false,
"internals": {
"process_throttle_secs": 2
}
}
+151
View File
@@ -0,0 +1,151 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 2,
"stake_currency": "USDT",
"stake_amount": "unlimited",
"tradable_balance_ratio": 0.95,
"fiat_display_currency": "USD",
"dry_run": true,
"db_url": "sqlite:///tradesv3.chanlun_sol_optimized.sqlite",
"dry_run_wallet": 1000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"can_short": true,
"timeframe": "5m",
"process_only_new_candles": false,
"unfilledtimeout": {
"entry": 2,
"exit": 2,
"exit_timeout_count": 0,
"unit": "minutes"
},
"entry_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1,
"price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"exit_pricing":{
"price_side": "other",
"use_order_book": true,
"order_book_top": 1
},
"exchange": {
"name": "binance",
"key": "",
"secret": "",
"ccxt_config": {
"options": {"defaultType": "swap"}
},
"ccxt_async_config": {
"enableRateLimit": true,
"rateLimit": 1000,
"timeout": 30000
},
"pair_whitelist": [
"SOL/USDT:USDT"
],
"pair_blacklist": []
},
"telegram": {
"enabled": false,
"token": "",
"chat_id": ""
},
"api_server": {
"enabled": false,
"listen_ip_address": "0.0.0.0",
"listen_port": 8080,
"verbosity": "error",
"jwt_secret_key": "",
"username": "",
"password": ""
},
"discord": {
"enabled": false,
"webhook": "",
"webhook_avatar": "",
"poll_delay_seconds": 10
},
"notification_settings": {
"status": "on",
"status_inactive_after": 7,
"timeframe_condition_change": "on",
"telegram": { },
"discord": { },
"notify_all": true
},
"bot_name": "SOL_Chan_Optimized",
"initial_state": "running",
"force_entry_enable": false,
"internals": {
"process_throttle_secs": 5
},
"edge": {
"enabled": false,
"process_throttle_secs": 3600,
"calculate_since_number_of_days": 7,
"allowed_risk": 0.01,
"stoploss_range_min": -0.01,
"stoploss_range_max": -0.007,
"stoploss_range_step": 0.001,
"minimum_winrate": 0.60,
"minimum_expectancy": 0.20,
"min_trade_number": 10,
"max_trade_duration_minute": 1440,
"remove_pumps": false
},
"order_types": {
"entry": "limit",
"exit": "market",
"emergency_exit": "market",
"force_exit": "market",
"force_entry": "market",
"stoploss": "market",
"stoploss_on_exchange": false,
"stoploss_on_exchange_interval": 60
},
"order_time_in_force": {
"entry": "GTC",
"exit": "GTC"
},
"strategy_path": "./user_data/Chan/strategies/",
"strategy": "ChanLun_SOL_Optimized",
"minimal_roi": {
"0": 0.012,
"120": 0.010,
"240": 0.007,
"360": 0.005
},
"stoploss": -0.007,
"trailing_stop": true,
"trailing_stop_positive": 0.003,
"trailing_stop_positive_offset": 0.005,
"trailing_only_offset_is_reached": true,
"use_custom_stoploss": true,
"max_open_trades_per_pair": 1,
"dry_run_wallet_refresh_time": 5,
"caches": {
"dataframe": {
"enabled": true,
"refresh_period": 60
},
"strategy": {
"enabled": true,
"refresh_period": 300
}
},
"pairlists": [
{
"method": "StaticPairList",
"config": {
"pairs": ["SOL/USDT:USDT"]
}
}
]
}
+125
View File
@@ -0,0 +1,125 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 1,
"stake_currency": "USDT",
"stake_amount": "unlimited",
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD",
"dry_run": true,
"db_url": "sqlite:///tradesv3.chanlun_sol.sqlite",
"dry_run_wallet": 1000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"can_short" : true,
"timeframe" : "1m",
"process_only_new_candles" : true,
"unfilledtimeout": {
"entry": 1,
"exit": 1,
"exit_timeout_count": 0,
"unit": "minutes"
},
"entry_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1,
"price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"exit_pricing":{
"price_side": "same",
"use_order_book": true,
"order_book_top": 1
},
"exchange": {
"name": "binance",
"key": "hvoXanRExQvcN4tyGFvEnsSF4gqxXp6ZJnBu5lnhvlVuHaDbj2PhLBQGCLkkyeI8",
"secret": "3UKA2oyDj7OoXrausmnaLwLlNfXmlNf2imBdmQqqKHArcJfk6X9xjaUF19wzu82l",
"ccxt_config": {},
"ccxt_async_config": {},
"pair_whitelist": [
"SOL/USDT:USDT"
],
"pair_blacklist": [
"BNB/.*"
]
},
"freqai": {
"enabled": true,
"purge_old_models": true,
"train_period_days": 30,
"backtest_period_days": 7,
"identifier": "chanLun1",
"live_retrain_hours": 1,
"expiration_hours": 48,
"fit_live_predictions_candles": 0,
"data_kitchen_thread_count": 4,
"save_backtest_models": true,
"save_metadata": true,
"feature_parameters": {
"include_timeframes": [
"1m",
"5m",
"15m"
],
"include_corr_pairlist": [
"BTC/USDT:USDT",
"ETH/USDT:USDT"
],
"label_period_candles": 24,
"include_shifted_candles": 2,
"indicator_periods_candles": [10, 20, 30],
"allow_duplicate_train": true
},
"data_split_parameters": {
"test_size": 0.25
},
"model_training_parameters": {
"n_estimators": 100,
"learning_rate": 0.1,
"max_depth": 5,
"subsample": 0.8,
"colsample_bytree": 0.8,
"use_label_for_weight": true,
"booster": "gbtree",
"num_class": 2
}
},
"freqaimodel": "XGBoostClassifier",
"pairlists": [
{
"method": "StaticPairList",
"number_assets": 1,
"sort_key": "quoteVolume",
"min_value": 0,
"refresh_period": 1800
}
],
"telegram": {
"enabled": false,
"token": "7677670958:AAFL_jgZvNUTPR3R3vWieREX_tDVi9w2C1Y",
"chat_id": "580807463"
},
"api_server": {
"enabled": true,
"listen_ip_address": "127.0.0.1",
"listen_port": 8811,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d",
"ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg",
"CORS_origins": [],
"username": "freqtrader",
"password": "FreqTrade007"
},
"bot_name": "freqtrade",
"initial_state": "running",
"force_entry_enable": false,
"internals": {
"process_throttle_secs": 2
}
}
+103
View File
@@ -0,0 +1,103 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 3,
"stake_currency": "USDT",
"stake_amount": "unlimited",
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD",
"dry_run": true,
"db_url": "sqlite:///tradesv3.btc_chan.sqlite",
"dry_run_wallet": 1000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"can_short" : true,
"timeframe" : "30m",
"process_only_new_candles" : false,
"unfilledtimeout": {
"entry": 1,
"exit": 1,
"exit_timeout_count": 0,
"unit": "minutes"
},
"entry_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1,
"price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"exit_pricing":{
"price_side": "same",
"use_order_book": true,
"order_book_top": 1
},
"exchange": {
"name": "binance",
"key": "ocQUqAPSD9PDhIL2lTMlMan0wMFwvvu5Fv8eYF3wUM8yPytm2jBgz51cgiHXw7J6",
"secret": "yHIc6FOnSoOI2FvygpRKKku4FKaZGI5DSwC83Ip4wRfUcxszennF6hy2vhbVuLYJ",
"ccxt_config": {},
"ccxt_async_config": {},
"pair_whitelist": [
"BTC/USDT:USDT",
"ETH/USDT:USDT",
"SOL/USDT:USDT",
"WIF/USDT:USDT",
"1000PEPE/USDT:USDT",
"DOGS/USDT:USDT",
"ORDI/USDT:USDT",
"AAVE/USDT:USDT",
"REEF/USDT:USDT",
"1000SATS/USDT:USDT",
"SUI/USDT:USDT",
"1INCH/USDT:USDT",
"DOGE/USDT:USDT",
"TON/USDT:USDT",
"UNI/USDT:USDT",
"XRP/USDT:USDT",
"SUN/USDT:USDT",
"NOT/USDT:USDT",
"RARE/USDT:USDT",
"RDNT/USDT:USDT"
],
"pair_blacklist": [
"BNB/.*"
]
},
"pairlists": [
{
"method": "VolumePairList",
"number_assets": 10,
"sort_key": "quoteVolume",
"min_value": 0,
"refresh_period": 1800
}
],
"telegram": {
"enabled": true,
"token": "7677670958:AAFL_jgZvNUTPR3R3vWieREX_tDVi9w2C1Y",
"chat_id": "580807463"
},
"api_server": {
"enabled": true,
"listen_ip_address": "127.0.0.1",
"listen_port": 8088,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d",
"ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg",
"CORS_origins": [],
"username": "freqtrader",
"password": "FreqTrade007"
},
"bot_name": "freqtrade",
"initial_state": "running",
"force_entry_enable": false,
"internals": {
"process_throttle_secs": 2
}
}
+83
View File
@@ -0,0 +1,83 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 1,
"stake_currency": "USDT",
"stake_amount": "unlimited",
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD",
"dry_run": true,
"dry_run_wallet": 1000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"can_short" : true,
"timeframe" : "5m",
"process_only_new_candles" : false,
"unfilledtimeout": {
"entry": 1,
"exit": 1,
"exit_timeout_count": 0,
"unit": "minutes"
},
"entry_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1,
"price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"exit_pricing":{
"price_side": "same",
"use_order_book": true,
"order_book_top": 1
},
"exchange": {
"name": "binance",
"key": "hvoXanRExQvcN4tyGFvEnsSF4gqxXp6ZJnBu5lnhvlVuHaDbj2PhLBQGCLkkyeI8",
"secret": "3UKA2oyDj7OoXrausmnaLwLlNfXmlNf2imBdmQqqKHArcJfk6X9xjaUF19wzu82l",
"ccxt_config": {},
"ccxt_async_config": {},
"pair_whitelist": [
"BTC/USDT:USDT",
],
"pair_blacklist": [
"BNB/.*"
]
},
"pairlists": [
{
"method": "StaticPairList",
"number_assets": 1,
"sort_key": "quoteVolume",
"min_value": 0,
"refresh_period": 1800
}
],
"telegram": {
"enabled": false,
"token": "5985766683:AAEx2Nm_4y2IC0Tj4Hhz7djVRJRso0JKaj0",
"chat_id": "580807463"
},
"api_server": {
"enabled": true,
"listen_ip_address": "127.0.0.1",
"listen_port": 8088,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d",
"ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg",
"CORS_origins": [],
"username": "freqtrader",
"password": "FreqTrade007"
},
"bot_name": "freqtrade",
"initial_state": "running",
"force_entry_enable": false,
"internals": {
"process_throttle_secs": 2
}
}
+84
View File
@@ -0,0 +1,84 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 1,
"stake_currency": "USDT",
"stake_amount": "unlimited",
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD",
"dry_run": true,
"db_url": "sqlite:///tradesv3.btc_chan.sqlite",
"dry_run_wallet": 1000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"can_short" : true,
"timeframe" : "1m",
"process_only_new_candles" : false,
"unfilledtimeout": {
"entry": 1,
"exit": 1,
"exit_timeout_count": 0,
"unit": "minutes"
},
"entry_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1,
"price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"exit_pricing":{
"price_side": "same",
"use_order_book": true,
"order_book_top": 1
},
"exchange": {
"name": "binance",
"key": "hvoXanRExQvcN4tyGFvEnsSF4gqxXp6ZJnBu5lnhvlVuHaDbj2PhLBQGCLkkyeI8",
"secret": "3UKA2oyDj7OoXrausmnaLwLlNfXmlNf2imBdmQqqKHArcJfk6X9xjaUF19wzu82l",
"ccxt_config": {},
"ccxt_async_config": {},
"pair_whitelist": [
"SOL/USDT:USDT",
],
"pair_blacklist": [
"BNB/.*"
]
},
"pairlists": [
{
"method": "StaticPairList",
"number_assets": 1,
"sort_key": "quoteVolume",
"min_value": 0,
"refresh_period": 1800,
}
],
"telegram": {
"enabled": true,
"token": "7677670958:AAFL_jgZvNUTPR3R3vWieREX_tDVi9w2C1Y",
"chat_id": "580807463"
},
"api_server": {
"enabled": false,
"listen_ip_address": "127.0.0.1",
"listen_port": 8888,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d",
"ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg",
"CORS_origins": [],
"username": "freqtrader",
"password": "FreqTrade007"
},
"bot_name": "freqtrade",
"initial_state": "running",
"force_entry_enable": false,
"internals": {
"process_throttle_secs": 2
}
}
+84
View File
@@ -0,0 +1,84 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 1,
"stake_currency": "USDT",
"stake_amount": "unlimited",
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD",
"dry_run": true,
"strategy": "ChanStrategy",
"db_url": "sqlite:///tradesv3.btc_chan.sqlite",
"dry_run_wallet": 1000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"can_short" : true,
"timeframe" : "1m",
"process_only_new_candles" : false,
"unfilledtimeout": {
"entry": 1,
"exit": 1,
"exit_timeout_count": 0,
"unit": "minutes"
},
"entry_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1,
"price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"exit_pricing":{
"price_side": "same",
"use_order_book": true,
"order_book_top": 1
},
"exchange": {
"name": "binance",
"key": "hvoXanRExQvcN4tyGFvEnsSF4gqxXp6ZJnBu5lnhvlVuHaDbj2PhLBQGCLkkyeI8",
"secret": "3UKA2oyDj7OoXrausmnaLwLlNfXmlNf2imBdmQqqKHArcJfk6X9xjaUF19wzu82l",
"ccxt_config": {},
"ccxt_async_config": {},
"pair_whitelist": [
"SOL/USDT:USDT"
],
"pair_blacklist": [
"BNB/.*"
]
},
"pairlists": [
{
"method": "StaticPairList",
"number_assets": 1,
"sort_key": "quoteVolume",
"min_value": 0,
"refresh_period": 1800
}
],
"telegram": {
"enabled": false,
"token": "7677670958:AAFL_jgZvNUTPR3R3vWieREX_tDVi9w2C1Y",
"chat_id": "580807463"
},
"api_server": {
"enabled": false,
"listen_ip_address": "127.0.0.1",
"listen_port": 8818,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d",
"ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg",
"CORS_origins": [],
"username": "freqtrader",
"password": "FreqTrade007"
},
"bot_name": "freqtrade",
"initial_state": "running",
"force_entry_enable": false,
"internals": {
"process_throttle_secs": 2
}
}
+84
View File
@@ -0,0 +1,84 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 1,
"stake_currency": "USDT",
"stake_amount": "unlimited",
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD",
"dry_run": true,
"db_url": "sqlite:///tradesv3.chan.sqlite",
"dry_run_wallet": 1000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"can_short" : true,
"timeframe" : "1m",
"process_only_new_candles" : false,
"unfilledtimeout": {
"entry": 1,
"exit": 1,
"exit_timeout_count": 0,
"unit": "minutes"
},
"entry_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1,
"price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"exit_pricing":{
"price_side": "same",
"use_order_book": true,
"order_book_top": 1
},
"exchange": {
"name": "binance",
"key": "hvoXanRExQvcN4tyGFvEnsSF4gqxXp6ZJnBu5lnhvlVuHaDbj2PhLBQGCLkkyeI8",
"secret": "3UKA2oyDj7OoXrausmnaLwLlNfXmlNf2imBdmQqqKHArcJfk6X9xjaUF19wzu82l",
"ccxt_config": {},
"ccxt_async_config": {},
"pair_whitelist": [
"SOL/USDT:USDT",
],
"pair_blacklist": [
"BNB/.*"
]
},
"pairlists": [
{
"method": "StaticPairList",
"number_assets": 1,
"sort_key": "quoteVolume",
"min_value": 0,
"refresh_period": 1800,
}
],
"telegram": {
"enabled": false,
"token": "7677670958:AAFL_jgZvNUTPR3R3vWieREX_tDVi9w2C1Y",
"chat_id": "580807463"
},
"api_server": {
"enabled": true,
"listen_ip_address": "127.0.0.1",
"listen_port": 8815,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d",
"ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg",
"CORS_origins": [],
"username": "freqtrader",
"password": "FreqTrade007"
},
"bot_name": "freqtrade",
"initial_state": "running",
"force_entry_enable": false,
"internals": {
"process_throttle_secs": 2
}
}
+84
View File
@@ -0,0 +1,84 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 1,
"stake_currency": "USDC",
"stake_amount": "unlimited",
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD",
"dry_run": true,
"db_url": "sqlite:///tradesv3.chan.sqlite",
"dry_run_wallet": 1000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"can_short" : true,
"timeframe" : "1m",
"process_only_new_candles" : false,
"unfilledtimeout": {
"entry": 1,
"exit": 1,
"exit_timeout_count": 0,
"unit": "minutes"
},
"entry_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1,
"price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"exit_pricing":{
"price_side": "same",
"use_order_book": true,
"order_book_top": 1
},
"exchange": {
"name": "hyperliquid",
"walletAddress": "0xA834b6d3Fa1D8A55ea8e502685ef5cbD2b2D3343",
"privateKey": "0xa399cea4c01be67c16b88e1d2121ed6e72bab6b4e8d81684ed03ce78f8b4f827",
"ccxt_config": {},
"ccxt_async_config": {},
"pair_whitelist": [
"PURR/USDC:USDC",
],
"pair_blacklist": [
"BNB/.*"
]
},
"pairlists": [
{
"method": "StaticPairList",
"number_assets": 1,
"sort_key": "quoteVolume",
"min_value": 0,
"refresh_period": 1800,
}
],
"telegram": {
"enabled": false,
"token": "7677670958:AAFL_jgZvNUTPR3R3vWieREX_tDVi9w2C1Y",
"chat_id": "580807463"
},
"api_server": {
"enabled": true,
"listen_ip_address": "127.0.0.1",
"listen_port": 8815,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d",
"ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg",
"CORS_origins": [],
"username": "freqtrader",
"password": "FreqTrade007"
},
"bot_name": "freqtrade",
"initial_state": "running",
"force_entry_enable": false,
"internals": {
"process_throttle_secs": 2
}
}
+84
View File
@@ -0,0 +1,84 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 1,
"stake_currency": "USDT",
"stake_amount": "unlimited",
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD",
"dry_run": true,
"db_url": "sqlite:///tradesv3.chan_sol_30.sqlite",
"dry_run_wallet": 1000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"can_short" : true,
"timeframe" : "1m",
"process_only_new_candles" : false,
"unfilledtimeout": {
"entry": 1,
"exit": 1,
"exit_timeout_count": 0,
"unit": "minutes"
},
"entry_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1,
"price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"exit_pricing":{
"price_side": "same",
"use_order_book": true,
"order_book_top": 1
},
"exchange": {
"name": "binance",
"key": "hvoXanRExQvcN4tyGFvEnsSF4gqxXp6ZJnBu5lnhvlVuHaDbj2PhLBQGCLkkyeI8",
"secret": "3UKA2oyDj7OoXrausmnaLwLlNfXmlNf2imBdmQqqKHArcJfk6X9xjaUF19wzu82l",
"ccxt_config": {},
"ccxt_async_config": {},
"pair_whitelist": [
"SOL/USDT:USDT",
],
"pair_blacklist": [
"BNB/.*"
]
},
"pairlists": [
{
"method": "StaticPairList",
"number_assets": 1,
"sort_key": "quoteVolume",
"min_value": 0,
"refresh_period": 1800,
}
],
"telegram": {
"enabled": false,
"token": "7677670958:AAFL_jgZvNUTPR3R3vWieREX_tDVi9w2C1Y",
"chat_id": "580807463"
},
"api_server": {
"enabled": true,
"listen_ip_address": "127.0.0.1",
"listen_port": 8818,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d",
"ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg",
"CORS_origins": [],
"username": "freqtrader",
"password": "FreqTrade007"
},
"bot_name": "freqtrade",
"initial_state": "running",
"force_entry_enable": false,
"internals": {
"process_throttle_secs": 2
}
}
+84
View File
@@ -0,0 +1,84 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 1,
"stake_currency": "USDT",
"stake_amount": "unlimited",
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD",
"dry_run": true,
"db_url": "sqlite:///tradesv3.chan.sqlite",
"dry_run_wallet": 1000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"can_short" : true,
"timeframe" : "1m",
"process_only_new_candles" : false,
"unfilledtimeout": {
"entry": 1,
"exit": 1,
"exit_timeout_count": 0,
"unit": "minutes"
},
"entry_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1,
"price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"exit_pricing":{
"price_side": "same",
"use_order_book": true,
"order_book_top": 1
},
"exchange": {
"name": "binance",
"key": "hvoXanRExQvcN4tyGFvEnsSF4gqxXp6ZJnBu5lnhvlVuHaDbj2PhLBQGCLkkyeI8",
"secret": "3UKA2oyDj7OoXrausmnaLwLlNfXmlNf2imBdmQqqKHArcJfk6X9xjaUF19wzu82l",
"ccxt_config": {},
"ccxt_async_config": {},
"pair_whitelist": [
"BTC/USDT:USDT",
],
"pair_blacklist": [
"BNB/.*"
]
},
"pairlists": [
{
"method": "StaticPairList",
"number_assets": 1,
"sort_key": "quoteVolume",
"min_value": 0,
"refresh_period": 1800,
}
],
"telegram": {
"enabled": false,
"token": "7677670958:AAFL_jgZvNUTPR3R3vWieREX_tDVi9w2C1Y",
"chat_id": "580807463"
},
"api_server": {
"enabled": true,
"listen_ip_address": "127.0.0.1",
"listen_port": 8801,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d",
"ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg",
"CORS_origins": [],
"username": "freqtrader",
"password": "FreqTrade007"
},
"bot_name": "freqtrade",
"initial_state": "running",
"force_entry_enable": false,
"internals": {
"process_throttle_secs": 2
}
}
+93
View File
@@ -0,0 +1,93 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 3,
"stake_currency": "USDT",
"stake_amount": "unlimited",
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD",
"db_url": "sqlite:///tradesv3.deepseek_trader.sqlite",
"dry_run": true,
"dry_run_wallet": 10000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"can_short" : true,
"timeframe" : "1m",
"process_only_new_candles" : false,
"unfilledtimeout": {
"entry": 1,
"exit": 1,
"exit_timeout_count": 0,
"unit": "minutes"
},
"entry_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1,
"price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"exit_pricing":{
"price_side": "same",
"use_order_book": true,
"order_book_top": 1
},
"exchange": {
"name": "binance",
"key": "hvoXanRExQvcN4tyGFvEnsSF4gqxXp6ZJnBu5lnhvlVuHaDbj2PhLBQGCLkkyeI8",
"secret": "3UKA2oyDj7OoXrausmnaLwLlNfXmlNf2imBdmQqqKHArcJfk6X9xjaUF19wzu82l",
"ccxt_config": {},
"ccxt_async_config": {},
"pair_whitelist": [
"BTC/USDT:USDT",
"ETH/USDT:USDT",
"SOL/USDT:USDT",
"WIF/USDT:USDT",
"1000PEPE/USDT:USDT",
"DOGS/USDT:USDT",
"ORDI/USDT:USDT",
"AAVE/USDT:USDT",
"REEF/USDT:USDT",
"1000SATS/USDT:USDT",
],
"pair_blacklist": [
"BNB/.*"
]
},
"pairlists": [
{
"method": "StaticPairList",
"number_assets": 10,
"sort_key": "quoteVolume",
"min_value": 0,
"refresh_period": 1800
}
],
"telegram": {
"enabled": true,
"token": "5985766683:AAEx2Nm_4y2IC0Tj4Hhz7djVRJRso0JKaj0",
"chat_id": "580807463"
},
"api_server": {
"enabled": true,
"listen_ip_address": "127.0.0.1",
"listen_port": 8001,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d",
"ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg",
"CORS_origins": [],
"username": "freqtrader",
"password": "FreqTrade007"
},
"bot_name": "freqtrade",
"initial_state": "running",
"force_entry_enable": true,
"internals": {
"process_throttle_secs": 15
}
}
+83
View File
@@ -0,0 +1,83 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 1,
"stake_currency": "USDT",
"stake_amount": "unlimited",
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD",
"dry_run": true,
"db_url": "sqlite:///tradesv3.ema26_ema52_cross.sqlite",
"dry_run_wallet": 1000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"can_short" : true,
"timeframe" : "1m",
"process_only_new_candles" : false,
"unfilledtimeout": {
"entry": 1,
"exit": 1,
"exit_timeout_count": 5,
"unit": "minutes"
},
"entry_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1,
"price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"exit_pricing":{
"price_side": "same",
"use_order_book": true,
"order_book_top": 1
},
"exchange": {
"name": "binance",
"key": "hvoXanRExQvcN4tyGFvEnsSF4gqxXp6ZJnBu5lnhvlVuHaDbj2PhLBQGCLkkyeI8",
"secret": "3UKA2oyDj7OoXrausmnaLwLlNfXmlNf2imBdmQqqKHArcJfk6X9xjaUF19wzu82l",
"ccxt_config": {},
"ccxt_async_config": {},
"pair_whitelist": [
"SOL/USDT:USDT"
],
"pair_blacklist": [
"BNB/.*"
]
},
"pairlists": [
{
"method": "StaticPairList",
"number_assets": 1,
"sort_key": "quoteVolume",
"min_value": 0,
"refresh_period": 1800
}
],
"telegram": {
"enabled": true,
"token": "7677670958:AAFL_jgZvNUTPR3R3vWieREX_tDVi9w2C1Y",
"chat_id": "580807463"
},
"api_server": {
"enabled": true,
"listen_ip_address": "0.0.0.0",
"listen_port": 8820,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d",
"ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg",
"CORS_origins": [],
"username": "freqtrader",
"password": "FreqTrade007"
},
"bot_name": "freqtrade",
"initial_state": "running",
"force_entry_enable": false,
"internals": {
"process_throttle_secs": 2
}
}
+83
View File
@@ -0,0 +1,83 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 1,
"stake_currency": "USDT",
"stake_amount": "unlimited",
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD",
"dry_run": true,
"db_url": "sqlite:///tradesv3.ema_pattern.sqlite",
"dry_run_wallet": 1000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"can_short" : true,
"timeframe" : "1h",
"process_only_new_candles" : false,
"unfilledtimeout": {
"entry": 1,
"exit": 1,
"exit_timeout_count": 5,
"unit": "minutes"
},
"entry_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1,
"price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"exit_pricing":{
"price_side": "same",
"use_order_book": true,
"order_book_top": 1
},
"exchange": {
"name": "binance",
"key": "hvoXanRExQvcN4tyGFvEnsSF4gqxXp6ZJnBu5lnhvlVuHaDbj2PhLBQGCLkkyeI8",
"secret": "3UKA2oyDj7OoXrausmnaLwLlNfXmlNf2imBdmQqqKHArcJfk6X9xjaUF19wzu82l",
"ccxt_config": {},
"ccxt_async_config": {},
"pair_whitelist": [
"BTC/USDT:USDT"
],
"pair_blacklist": [
"BNB/.*"
]
},
"pairlists": [
{
"method": "StaticPairList",
"number_assets": 1,
"sort_key": "quoteVolume",
"min_value": 0,
"refresh_period": 1800
}
],
"telegram": {
"enabled": false,
"token": "7677670958:AAFL_jgZvNUTPR3R3vWieREX_tDVi9w2C1Y",
"chat_id": "580807463"
},
"api_server": {
"enabled": true,
"listen_ip_address": "0.0.0.0",
"listen_port": 8888,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d",
"ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg",
"CORS_origins": [],
"username": "freqtrader",
"password": "FreqTrade007"
},
"bot_name": "freqtrade",
"initial_state": "running",
"force_entry_enable": false,
"internals": {
"process_throttle_secs": 2
}
}
+70
View File
@@ -0,0 +1,70 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 2,
"stake_currency": "USDT",
"stake_amount": "unlimited",
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD",
"dry_run": true,
"db_url": "sqlite:///tradesv3.elliottwave_btc.sqlite",
"dry_run_wallet": 10000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"unfilledtimeout": {
"entry": 5,
"exit": 5,
"exit_timeout_count": 3,
"unit": "minutes"
},
"entry_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1,
"price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"exit_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1
},
"exchange": {
"name": "binance",
"key": "",
"secret": "",
"ccxt_config": {},
"ccxt_async_config": {},
"pair_whitelist": [
"BTC/USDT:USDT"
]
},
"pairlists": [
{
"method": "StaticPairList",
"number_assets": 1,
"sort_key": "quoteVolume",
"min_value": 0
}
],
"telegram": {
"enabled": false,
"token": "",
"chat_id": ""
},
"api_server": {
"enabled": false,
"listen_ip_address": "127.0.0.1",
"listen_port": 8080,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "freqtrade_secret",
"ws_token": "freqtrade_ws",
"username": "freqtrade",
"password": "freqtrade"
},
"bot_name": "ElliottWaveBTC"
}
+123
View File
@@ -0,0 +1,123 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 1,
"stake_currency": "USDT",
"stake_amount": "unlimited",
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD",
"dry_run": true,
"db_url": "sqlite:///tradesv3.freqai_sol.sqlite",
"dry_run_wallet": 1000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"can_short": true,
"timeframe": "5m",
"process_only_new_candles": true,
"unfilledtimeout": {
"entry": 1,
"exit": 1,
"exit_timeout_count": 5,
"unit": "minutes"
},
"entry_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1,
"price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"exit_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1
},
"exchange": {
"name": "binance",
"key": "",
"secret": "",
"ccxt_config": {},
"ccxt_async_config": {},
"pair_whitelist": [
"SOL/USDT:USDT"
],
"pair_blacklist": [
"BNB/.*"
]
},
"pairlists": [
{
"method": "StaticPairList"
}
],
"freqai": {
"enabled": true,
"purge_old_models": 2,
"train_period_days": 10,
"backtest_period_days": 7,
"live_retrain_hours": 1,
"identifier": "sol_futures_lgbm_v1",
"feature_parameters": {
"include_timeframes": [
"5m",
"15m"
],
"include_corr_pairlist": [
"BTC/USDT:USDT"
],
"label_period_candles": 12,
"include_shifted_candles": 1,
"DI_threshold": 0.9,
"weight_factor": 0.9,
"principal_component_analysis": false,
"use_SVM_to_remove_outliers": true,
"indicator_periods_candles": [
14
],
"plot_feature_importances": 0
},
"data_split_parameters": {
"test_size": 0.15,
"random_state": 42
},
"model_training_parameters": {
"n_estimators": 300,
"learning_rate": 0.05,
"max_depth": 5,
"num_leaves": 31,
"min_child_samples": 20,
"subsample": 0.8,
"colsample_bytree": 0.8,
"reg_alpha": 0.1,
"reg_lambda": 0.1,
"n_jobs": 1,
"verbosity": -1
}
},
"telegram": {
"enabled": false,
"token": "",
"chat_id": ""
},
"api_server": {
"enabled": true,
"listen_ip_address": "0.0.0.0",
"listen_port": 8822,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d",
"ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg",
"CORS_origins": [],
"username": "freqtrader",
"password": "FreqTrade007"
},
"bot_name": "freqai_sol",
"initial_state": "running",
"force_entry_enable": false,
"internals": {
"process_throttle_secs": 5
}
}
+83
View File
@@ -0,0 +1,83 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 1,
"stake_currency": "USDT",
"stake_amount": "unlimited",
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD",
"dry_run": true,
"db_url": "sqlite:///tradesv3.heikinashi_btc.sqlite",
"dry_run_wallet": 1000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"can_short" : true,
"timeframe" : "1m",
"process_only_new_candles" : false,
"unfilledtimeout": {
"entry": 1,
"exit": 1,
"exit_timeout_count": 5,
"unit": "minutes"
},
"entry_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1,
"price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"exit_pricing":{
"price_side": "same",
"use_order_book": true,
"order_book_top": 1
},
"exchange": {
"name": "binance",
"key": "hvoXanRExQvcN4tyGFvEnsSF4gqxXp6ZJnBu5lnhvlVuHaDbj2PhLBQGCLkkyeI8",
"secret": "3UKA2oyDj7OoXrausmnaLwLlNfXmlNf2imBdmQqqKHArcJfk6X9xjaUF19wzu82l",
"ccxt_config": {},
"ccxt_async_config": {},
"pair_whitelist": [
"BTC/USDT:USDT"
],
"pair_blacklist": [
"BNB/.*"
]
},
"pairlists": [
{
"method": "StaticPairList",
"number_assets": 1,
"sort_key": "quoteVolume",
"min_value": 0,
"refresh_period": 1800
}
],
"telegram": {
"enabled": true,
"token": "7677670958:AAFL_jgZvNUTPR3R3vWieREX_tDVi9w2C1Y",
"chat_id": "580807463"
},
"api_server": {
"enabled": true,
"listen_ip_address": "0.0.0.0",
"listen_port": 8814,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d",
"ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg",
"CORS_origins": [],
"username": "freqtrader",
"password": "FreqTrade007"
},
"bot_name": "freqtrade",
"initial_state": "running",
"force_entry_enable": false,
"internals": {
"process_throttle_secs": 2
}
}
+83
View File
@@ -0,0 +1,83 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 1,
"stake_currency": "USDT",
"stake_amount": "unlimited",
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD",
"dry_run": true,
"db_url": "sqlite:///tradesv3.ema26_ema52_cross.sqlite",
"dry_run_wallet": 1000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"timeframe": "1m",
"can_short" : true,
"process_only_new_candles" : true,
"unfilledtimeout": {
"entry": 1,
"exit": 1,
"exit_timeout_count": 5,
"unit": "minutes"
},
"entry_pricing": {
"price_side": "other",
"use_order_book": true,
"order_book_top": 1,
"price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"exit_pricing":{
"price_side": "other",
"use_order_book": true,
"order_book_top": 1
},
"exchange": {
"name": "binance",
"key": "hvoXanRExQvcN4tyGFvEnsSF4gqxXp6ZJnBu5lnhvlVuHaDbj2PhLBQGCLkkyeI8",
"secret": "3UKA2oyDj7OoXrausmnaLwLlNfXmlNf2imBdmQqqKHArcJfk6X9xjaUF19wzu82l",
"ccxt_config": {},
"ccxt_async_config": {},
"pair_whitelist": [
"BTC/USDT:USDT"
],
"pair_blacklist": [
"BNB/.*"
]
},
"pairlists": [
{
"method": "StaticPairList",
"number_assets": 1,
"sort_key": "quoteVolume",
"min_value": 0,
"refresh_period": 1800
}
],
"telegram": {
"enabled": false,
"token": "7677670958:AAFL_jgZvNUTPR3R3vWieREX_tDVi9w2C1Y",
"chat_id": "580807463"
},
"api_server": {
"enabled": true,
"listen_ip_address": "0.0.0.0",
"listen_port": 8821,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d",
"ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg",
"CORS_origins": [],
"username": "freqtrader",
"password": "FreqTrade007"
},
"bot_name": "freqtrade",
"initial_state": "running",
"force_entry_enable": false,
"internals": {
"process_throttle_secs": 2
}
}
+81
View File
@@ -0,0 +1,81 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 1,
"stake_currency": "USDT",
"stake_amount": "unlimited",
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD",
"dry_run": true,
"db_url": "sqlite:///tradesv3.sol5m.sqlite",
"dry_run_wallet": 1000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"can_short": true,
"unfilledtimeout": {
"entry": 1,
"exit": 1,
"exit_timeout_count": 5,
"unit": "minutes"
},
"entry_pricing": {
"price_side": "other",
"use_order_book": true,
"order_book_top": 1,
"price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"exit_pricing": {
"price_side": "other",
"use_order_book": true,
"order_book_top": 1
},
"exchange": {
"name": "binance",
"key": "hvoXanRExQvcN4tyGFvEnsSF4gqxXp6ZJnBu5lnhvlVuHaDbj2PhLBQGCLkkyeI8",
"secret": "3UKA2oyDj7OoXrausmnaLwLlNfXmlNf2imBdmQqqKHArcJfk6X9xjaUF19wzu82l",
"ccxt_config": {},
"ccxt_async_config": {},
"pair_whitelist": [
"SOL/USDT:USDT"
],
"pair_blacklist": [
"BNB/.*"
]
},
"pairlists": [
{
"method": "StaticPairList",
"number_assets": 1,
"sort_key": "quoteVolume",
"min_value": 0,
"refresh_period": 1800
}
],
"telegram": {
"enabled": false,
"token": "7677670958:AAFL_jgZvNUTPR3R3vWieREX_tDVi9w2C1Y",
"chat_id": "580807463"
},
"api_server": {
"enabled": true,
"listen_ip_address": "0.0.0.0",
"listen_port": 8822,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d",
"ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg",
"CORS_origins": [],
"username": "freqtrader",
"password": "FreqTrade007"
},
"bot_name": "SOL5m",
"initial_state": "running",
"force_entry_enable": false,
"internals": {
"process_throttle_secs": 5
}
}
+83
View File
@@ -0,0 +1,83 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 1,
"stake_currency": "USDT",
"stake_amount": "unlimited",
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD",
"dry_run": true,
"db_url": "sqlite:///tradesv3.chanlun_btc_15.sqlite",
"dry_run_wallet": 1000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"can_short" : true,
"timeframe" : "15m",
"process_only_new_candles" : false,
"unfilledtimeout": {
"entry": 1,
"exit": 1,
"exit_timeout_count": 5,
"unit": "minutes"
},
"entry_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1,
"price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"exit_pricing":{
"price_side": "same",
"use_order_book": true,
"order_book_top": 1
},
"exchange": {
"name": "binance",
"key": "hvoXanRExQvcN4tyGFvEnsSF4gqxXp6ZJnBu5lnhvlVuHaDbj2PhLBQGCLkkyeI8",
"secret": "3UKA2oyDj7OoXrausmnaLwLlNfXmlNf2imBdmQqqKHArcJfk6X9xjaUF19wzu82l",
"ccxt_config": {},
"ccxt_async_config": {},
"pair_whitelist": [
"WIF/USDT:USDT"
],
"pair_blacklist": [
"BNB/.*"
]
},
"pairlists": [
{
"method": "StaticPairList",
"number_assets": 1,
"sort_key": "quoteVolume",
"min_value": 0,
"refresh_period": 1800
}
],
"telegram": {
"enabled": false,
"token": "7677670958:AAFL_jgZvNUTPR3R3vWieREX_tDVi9w2C1Y",
"chat_id": "580807463"
},
"api_server": {
"enabled": true,
"listen_ip_address": "127.0.0.1",
"listen_port": 8811,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d",
"ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg",
"CORS_origins": [],
"username": "freqtrader",
"password": "FreqTrade007"
},
"bot_name": "freqtrade",
"initial_state": "running",
"force_entry_enable": false,
"internals": {
"process_throttle_secs": 2
}
}
Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

+20
View File
@@ -0,0 +1,20 @@
FROM python:3.11-slim
ENV PYTHONUNBUFFERED=1 \
PIP_NO_CACHE_DIR=1
WORKDIR /app
COPY requirements.txt /app/requirements.txt
RUN pip install --no-cache-dir -r requirements.txt
COPY . /app
ENV CONFIG_PATH=/app/config.json \
UVICORN_HOST=0.0.0.0 \
UVICORN_PORT=9009
EXPOSE 9009
CMD ["python", "-m", "main"]
+18
View File
@@ -0,0 +1,18 @@
{
"exchange": "binance",
"symbols": [
"BTC/USDT:USDT",
"ETH/USDT:USDT",
"SOL/USDT:USDT",
"WIF/USDT:USDT",
"AAVE/USDT:USDT",
"SUI/USDT:USDT",
"1INCH/USDT:USDT",
"DOGE/USDT:USDT",
"UNI/USDT:USDT"
],
"start_time": "2024-01-01T00:00:00Z",
"timeframes": ["1m", "1h", "1d", "1w"],
"data_dir": "./data"
}
+15
View File
@@ -0,0 +1,15 @@
services:
data_provider:
build: .
container_name: data-provider
restart: unless-stopped
environment:
CONFIG_PATH: /app/config.json
UVICORN_HOST: 0.0.0.0
UVICORN_PORT: "9009"
volumes:
- ./config.json:/app/config.json:ro
- ./data:/app/data
ports:
- "9009:9009"
+947
View File
@@ -0,0 +1,947 @@
"""
Chan 数据服务 ccxt 从交易所拉取 K 线内存缓存 + CSV 落盘
后台线程定期增量刷新断线时记录 resume_since 以免漏 K
配置中的基础周期 1m/1h可合成 DERIVED_TIMEFRAME_PLAN 中的衍生周期
"""
import asyncio
import csv
import json
import logging
import os
import threading
import time
from contextlib import asynccontextmanager
from datetime import datetime, timezone
from pathlib import Path
from typing import Dict, Iterable, List, Optional
import ccxt # type: ignore
import pandas as pd # type: ignore
from fastapi import FastAPI, HTTPException, Query, WebSocket, WebSocketDisconnect
from fastapi.middleware.cors import CORSMiddleware
import uvicorn
from technical.util import resample_to_interval
# docker compose logs --tail=200
# docker compose down && docker compose build --no-cache && docker compose up -d
# 基础周期枚举顺序(用于衍生周期展示顺序);仅允许集合内周期作为交易所直接拉取的 tf
TIMEFRAME_ORDER = ["1m", "1h", "1d", "1w"]
ALLOWED_TIMEFRAMES = set(TIMEFRAME_ORDER)
# 各基础周期一根 K 线的毫秒长度(用于历史分页与断线回退)
TIMEFRAME_TO_MS: Dict[str, int] = {
"1m": 60_000,
"1h": 3_600_000,
"1d": 86_400_000,
"1w": 604_800_000,
}
# 每个基础周期可派生出的合成周期列表(由该基础周期 K 线 resample 得到)
DERIVED_TIMEFRAME_PLAN: Dict[str, List[str]] = {
"1m": ["2m", "3m", "4m", "5m", "10m", "15m", "20m", "25m", "30m", "45m"],
"1h": ["2h", "3h", "4h", "5h", "6h", "7h", "8h", "9h", "10", "11h", "12h", "16h", "20h"],
"1d": ["2d", "3d", "4d", "5d", "6d"],
"1w": ["2w", "3w"],
}
CSV_FIELDNAMES = ["timestamp", "datetime", "open", "high", "low", "close", "volume"]
DEFAULT_LIMIT = 500
RECENT_CANDLE_LIMIT = 10
RECENT_FETCH_INTERVAL = 5 # 后台刷新循环休眠秒数
PERSIST_INTERVAL = 600 # 全量落盘周期(秒)
WS_UPDATE_CANDLE_COUNT = 2 # WebSocket 增量推送最近 K 线根数
logger = logging.getLogger("data_provider")
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
)
def to_utc_iso(timestamp_ms: int) -> str:
"""将毫秒时间戳格式化为 UTC ISO 字符串(末尾 Z)。"""
dt = datetime.fromtimestamp(timestamp_ms / 1000, tz=timezone.utc)
return dt.isoformat().replace("+00:00", "Z")
def parse_timestamp(value: Optional[object]) -> Optional[int]:
"""解析查询参数中的时间为 UTC 毫秒时间戳;支持数字或 ISO 字符串。"""
if value is None:
return None
if isinstance(value, (int, float)):
return int(value)
if isinstance(value, str):
text = value.strip()
if not text:
return None
if text.isdigit():
return int(text)
if text.endswith("Z"):
text = text[:-1] + "+00:00"
try:
dt = datetime.fromisoformat(text)
except ValueError as exc: # pragma: no cover - informative logging
raise ValueError(f"无法解析时间字符串: {value}") from exc
if dt.tzinfo is None:
dt = dt.replace(tzinfo=timezone.utc)
else:
dt = dt.astimezone(timezone.utc)
return int(dt.timestamp() * 1000)
raise ValueError(f"不支持的时间格式: {value}")
def candle_to_dict(candle: Iterable[float]) -> Dict[str, float]:
"""ccxt OHLCV 单根 [ts, o, h, l, c, v] 转为内部字典结构。"""
ts = int(candle[0])
return {
"timestamp": ts,
"datetime": to_utc_iso(ts),
"open": float(candle[1]),
"high": float(candle[2]),
"low": float(candle[3]),
"close": float(candle[4]),
"volume": float(candle[5]),
}
def timeframe_to_minutes(tf: str) -> Optional[int]:
"""将如 15m、2h 转为「分钟数」,供 resample 与衍生周期计算。"""
if not tf:
return None
unit = tf[-1]
try:
value = int(tf[:-1])
except ValueError:
return None
multiplier = {
"m": 1,
"h": 60,
"d": 1_440,
"w": 10_080,
}.get(unit)
if multiplier is None:
return None
return value * multiplier
class WebSocketManager:
"""管理 WebSocket 连接及订阅,线程安全地广播 K 线更新。"""
def __init__(self) -> None:
self._subscriptions: Dict[tuple, set] = {}
self._async_lock = asyncio.Lock()
self._loop: Optional[asyncio.AbstractEventLoop] = None
def set_loop(self, loop: asyncio.AbstractEventLoop) -> None:
self._loop = loop
async def connect(self, ws: WebSocket) -> None:
await ws.accept()
logger.info("WebSocket 客户端已连接")
async def disconnect(self, ws: WebSocket) -> None:
async with self._async_lock:
for key in list(self._subscriptions):
self._subscriptions[key].discard(ws)
if not self._subscriptions[key]:
del self._subscriptions[key]
logger.info("WebSocket 客户端已断开")
async def subscribe(self, ws: WebSocket, symbol: str, timeframe: str) -> None:
key = (symbol, timeframe)
async with self._async_lock:
self._subscriptions.setdefault(key, set()).add(ws)
logger.info("WebSocket 订阅: %s %s", symbol, timeframe)
async def unsubscribe(self, ws: WebSocket, symbol: str, timeframe: str) -> None:
key = (symbol, timeframe)
async with self._async_lock:
if key in self._subscriptions:
self._subscriptions[key].discard(ws)
if not self._subscriptions[key]:
del self._subscriptions[key]
def has_subscribers(self, symbol: str, timeframe: str) -> bool:
"""非异步快速检查(供同步线程调用)。"""
return bool(self._subscriptions.get((symbol, timeframe)))
async def broadcast(
self, symbol: str, timeframe: str, candles: List[Dict], msg_type: str = "kline",
) -> None:
key = (symbol, timeframe)
async with self._async_lock:
subscribers = list(self._subscriptions.get(key, set()))
if not subscribers:
return
message = json.dumps(
{"type": msg_type, "symbol": symbol, "timeframe": timeframe, "data": candles},
ensure_ascii=False,
)
dead: list = []
for ws in subscribers:
try:
await ws.send_text(message)
except Exception:
dead.append(ws)
if dead:
async with self._async_lock:
for ws in dead:
self._subscriptions.get(key, set()).discard(ws)
def broadcast_from_thread(
self, symbol: str, timeframe: str, candles: List[Dict], msg_type: str = "kline",
) -> None:
"""供同步后台线程调用,将广播提交到 asyncio 事件循环。"""
if self._loop is None or self._loop.is_closed():
return
asyncio.run_coroutine_threadsafe(
self.broadcast(symbol, timeframe, candles, msg_type),
self._loop,
)
class DataProvider:
"""封装交易所连接、本地 CSV、内存缓存、断线恢复与衍生周期聚合。"""
def __init__(self, config_path: Path) -> None:
self.config_path = config_path
self.config = self._load_config()
self.exchange_name: str = self.config["exchange"]
self.symbols: List[str] = self._load_symbols(self.config)
self.timeframes: List[str] = self._validate_timeframes(self.config.get("timeframes"))
self.data_dir = Path(self.config.get("data_dir", "./data")).expanduser()
start = parse_timestamp(self.config.get("start_time"))
if start is None:
raise ValueError("配置文件必须包含 start_time 字段")
self.start_time_ms: int = start
self.exchange = self._init_exchange()
self.data: Dict[str, Dict[str, List[Dict[str, float]]]] = {
symbol: {tf: [] for tf in self.timeframes} for symbol in self.symbols
}
# 衍生周期 -> 用于合成的交易所基础周期(每个衍生只对应一个 base)
self.derived_map: Dict[str, str] = {}
for base_tf in self.timeframes:
for derived_tf in DERIVED_TIMEFRAME_PLAN.get(base_tf, []):
self.derived_map.setdefault(derived_tf, base_tf)
# 衍生周期展示顺序:按 TIMEFRAME_ORDER 中的基础周期依次展开
derived_order: List[str] = []
for base_tf in TIMEFRAME_ORDER:
if base_tf not in self.timeframes:
continue
for derived_tf in DERIVED_TIMEFRAME_PLAN.get(base_tf, []):
if derived_tf in self.derived_map and derived_tf not in derived_order:
derived_order.append(derived_tf)
self.available_timeframes: List[str] = list(self.timeframes) + derived_order
self._lock = threading.RLock()
self._ready = threading.Event()
self._stop_event = threading.Event()
self._fetch_thread: Optional[threading.Thread] = None
self._persist_thread: Optional[threading.Thread] = None
# 记录断线后需要从哪个 since 重新拉取(symbol -> timeframe -> since_ms
self._resume_since: Dict[str, Dict[str, int]] = {}
# 恢复点持久化文件
self._resume_file: Path = self.data_dir / "resume_since.json"
# 尝试加载历史恢复点
self._load_resume_since()
self._update_callbacks: List = []
def _load_config(self) -> Dict[str, object]:
"""读取 JSON 配置文件。"""
if not self.config_path.exists():
raise FileNotFoundError(f"未找到配置文件: {self.config_path}")
with self.config_path.open("r", encoding="utf-8") as fp:
return json.load(fp)
def _load_symbols(self, config: Dict[str, object]) -> List[str]:
"""从 symbols 列表、逗号分隔字符串或单字段 symbol 解析交易对,去重保序。"""
raw_symbols: List[str] = []
symbols_value = config.get("symbols")
if isinstance(symbols_value, list):
raw_symbols = [str(item).strip() for item in symbols_value if isinstance(item, str) and item.strip()]
elif isinstance(symbols_value, str) and symbols_value.strip():
raw_symbols = [item.strip() for item in symbols_value.split(",") if item.strip()]
symbol_single = config.get("symbol")
if not raw_symbols and isinstance(symbol_single, str) and symbol_single.strip():
raw_symbols = [symbol_single.strip()]
if not raw_symbols:
raise ValueError("配置文件必须提供 symbols(列表或逗号分隔字符串)或 symbol 字段")
unique: List[str] = []
for item in raw_symbols:
if item not in unique:
unique.append(item)
return unique
def _validate_timeframes(self, configured: Optional[Iterable[str]]) -> List[str]:
"""校验周期在允许集合内;未配置则默认 TIMEFRAME_ORDER 全部;顺序优先按 TIMEFRAME_ORDER。"""
if not configured:
return list(TIMEFRAME_ORDER)
invalid = [tf for tf in configured if tf not in ALLOWED_TIMEFRAMES]
if invalid:
raise ValueError(f"不支持的时间周期: {invalid}. 允许值: {sorted(ALLOWED_TIMEFRAMES)}")
unique = []
seen = set()
for tf in TIMEFRAME_ORDER:
if tf in configured and tf not in seen:
unique.append(tf)
seen.add(tf)
for tf in configured:
if tf not in seen:
unique.append(tf)
seen.add(tf)
return unique
def _init_exchange(self):
"""实例化 ccxt 交易所,币安期货默认 defaultType=future,并 load_markets。"""
if not hasattr(ccxt, self.exchange_name):
raise ValueError(f"不支持的交易所: {self.exchange_name}")
exchange_class = getattr(ccxt, self.exchange_name)
exchange = exchange_class({"enableRateLimit": True})
if exchange.id == "binance":
exchange.options.setdefault("defaultType", "future")
exchange.load_markets()
logger.info("已初始化交易所 %s", exchange.id)
return exchange
def _data_file_path(self, symbol: str, timeframe: str) -> Path:
"""单交易对单周期的 CSV 路径:data_dir/tf/exchange_symbol_tf.csv。"""
symbol_safe = symbol.replace("/", "_").replace(":", "_")
return self.data_dir / timeframe / f"{self.exchange.id}_{symbol_safe}_{timeframe}.csv"
def _load_local(self, symbol: str, timeframe: str) -> List[Dict[str, float]]:
"""启动时从磁盘加载已有 K 线,损坏行跳过,按时间排序。"""
path = self._data_file_path(symbol, timeframe)
if not path.exists():
return []
loaded: List[Dict[str, float]] = []
with path.open("r", encoding="utf-8", newline="") as fp:
reader = csv.DictReader(fp)
for row in reader:
try:
loaded.append(
{
"timestamp": int(row["timestamp"]),
"datetime": row.get("datetime") or to_utc_iso(int(row["timestamp"])),
"open": float(row["open"]),
"high": float(row["high"]),
"low": float(row["low"]),
"close": float(row["close"]),
"volume": float(row["volume"]),
}
)
except (KeyError, ValueError):
logger.warning("忽略损坏的行: %s", row)
loaded.sort(key=lambda item: item["timestamp"])
logger.info("交易对 %s 时间周期 %s 加载本地K线数量: %s", symbol, timeframe, len(loaded))
return loaded
def _merge_candles(
self,
timeframe: str,
base: List[Dict[str, float]],
new_candles: Iterable[Iterable[float]],
) -> List[Dict[str, float]]:
"""按 timestamp 去重合并,新数据覆盖同时间戳旧数据。"""
merged = {entry["timestamp"]: entry for entry in base}
for candle in new_candles:
entry = candle_to_dict(candle)
merged[entry["timestamp"]] = entry
ordered = list(sorted(merged.values(), key=lambda item: item["timestamp"]))
logger.debug("时间周期 %s 合并后K线数量: %s", timeframe, len(ordered))
return ordered
def _write_to_disk(self, symbol: str, timeframe: str, data: List[Dict[str, float]]) -> None:
"""先写临时文件再 replace,避免写入中断导致 CSV 损坏。"""
path = self._data_file_path(symbol, timeframe)
path.parent.mkdir(parents=True, exist_ok=True)
tmp_path = path.with_suffix(path.suffix + ".tmp")
try:
with tmp_path.open("w", encoding="utf-8", newline="") as fp:
writer = csv.DictWriter(fp, fieldnames=CSV_FIELDNAMES)
writer.writeheader()
writer.writerows(data)
os.replace(tmp_path, path)
finally:
if tmp_path.exists():
try:
tmp_path.unlink()
except OSError:
pass
logger.info("交易对 %s 时间周期 %s 已写入磁盘 (%s 根K线)", symbol, timeframe, len(data))
def _fetch_history(self, symbol: str, timeframe: str, since_ms: int) -> List[List[float]]:
"""从 since_ms 分页拉取直到接近当前时间;遇限频则 sleep 重试。"""
results: List[List[float]] = []
limit = 1500
now_ms = self.exchange.milliseconds()
tf_ms = TIMEFRAME_TO_MS[timeframe]
fetch_since = since_ms
max_rounds = 5000
rounds = 0
while fetch_since < now_ms and rounds < max_rounds:
rounds += 1
try:
candles = self.exchange.fetch_ohlcv(
symbol,
timeframe=timeframe,
since=fetch_since,
limit=limit,
)
except ccxt.RateLimitExceeded as exc:
logger.warning("触发频率限制,等待: %s", exc)
time.sleep(self.exchange.rateLimit / 1000 if self.exchange.rateLimit else 1)
continue
except ccxt.BaseError as exc:
logger.error("拉取历史K线失败 (%s, %s): %s", timeframe, fetch_since, exc)
time.sleep(5)
continue
if not candles:
break
results.extend(candles)
last_ts = candles[-1][0]
fetch_since = last_ts + tf_ms
if last_ts >= now_ms - tf_ms:
break
time.sleep(self.exchange.rateLimit / 1000 if self.exchange.rateLimit else 0.2)
logger.info("交易对 %s 时间周期 %s 拉取历史K线数量: %s", symbol, timeframe, len(results))
return results
def initialize(self) -> None:
"""阻塞式启动:加载本地、从倒数第二根或配置起点补历史、写盘并 set _ready。"""
logger.info("开始初始化数据提供商")
for symbol in self.symbols:
for timeframe in self.timeframes:
existing = self._load_local(symbol, timeframe)
tf_ms = TIMEFRAME_TO_MS[timeframe]
last_ts = existing[-1]["timestamp"] if existing else None
if last_ts is not None:
if len(existing) >= 2:
# 从倒数第二根起拉,避免最后一根未收盘重复/缺口
fetch_since = existing[-2]["timestamp"]
else:
fetch_since = max(0, last_ts - tf_ms)
else:
fetch_since = self.start_time_ms
logger.debug(
"初始化拉取参数",
extra={
"symbol": symbol,
"timeframe": timeframe,
"existing_last": last_ts,
"fetch_since": fetch_since,
"tf_ms": tf_ms,
},
)
history = self._fetch_history(symbol, timeframe, fetch_since)
merged = self._merge_candles(timeframe, existing, history)
with self._lock:
self.data.setdefault(symbol, {})[timeframe] = merged
self._write_to_disk(symbol, timeframe, merged)
self._ready.set()
logger.info("数据初始化完成")
def resample_df(self, df: pd.DataFrame, interval: int) -> pd.DataFrame:
"""将基础周期 DataFrame 聚合为 interval 分钟周期(freqtrade technical.util)。"""
return resample_to_interval(df, interval)
def _save_resume_since(self) -> None:
"""将断线恢复点持久化到 resume_since.json(原子替换)。"""
path = self._resume_file
path.parent.mkdir(parents=True, exist_ok=True)
tmp_path = path.with_suffix(path.suffix + ".tmp")
with self._lock:
snapshot = {
symbol: {tf: int(since) for tf, since in tf_map.items()}
for symbol, tf_map in self._resume_since.items()
}
try:
with tmp_path.open("w", encoding="utf-8") as fp:
json.dump(snapshot, fp, ensure_ascii=False, separators=(",", ":"))
os.replace(tmp_path, path)
finally:
if tmp_path.exists():
try:
tmp_path.unlink()
except OSError:
pass
logger.debug("恢复点已保存到磁盘: %s", path)
def _load_resume_since(self) -> None:
"""启动时加载恢复点;与内存合并时取更早的 since,避免漏拉。"""
path = self._resume_file
if not path.exists():
return
try:
with path.open("r", encoding="utf-8") as fp:
raw = json.load(fp)
except Exception as exc:
logger.warning("恢复点文件读取失败,忽略: %s (%s)", path, exc)
return
if not isinstance(raw, dict):
logger.warning("恢复点文件格式错误,忽略: %s", path)
return
loaded: Dict[str, Dict[str, int]] = {}
for symbol, tf_map in raw.items():
if not isinstance(tf_map, dict):
continue
per_symbol: Dict[str, int] = {}
for timeframe, since in tf_map.items():
try:
per_symbol[str(timeframe)] = int(since)
except Exception:
continue
if per_symbol:
loaded[str(symbol)] = per_symbol
if not loaded:
return
with self._lock:
# 合并为更早的 since,避免遗漏
for symbol, tf_map in loaded.items():
cur = self._resume_since.setdefault(symbol, {})
for timeframe, since in tf_map.items():
prev = cur.get(timeframe)
if prev is None or since < prev:
cur[timeframe] = since
logger.info("已加载恢复点: %s", path)
def _get_resume_since(self, symbol: str, timeframe: str) -> Optional[int]:
"""若曾断线,返回应从哪一毫秒起补拉该 symbol/tf。"""
with self._lock:
return self._resume_since.get(symbol, {}).get(timeframe)
def _set_resume_since(self, symbol: str, timeframe: str, since_ms: int) -> None:
"""断线时写入恢复点(取更早的 since 以免漏数据),并持久化到磁盘。"""
with self._lock:
per_symbol = self._resume_since.setdefault(symbol, {})
prev = per_symbol.get(timeframe)
# 取更早的 since,避免跳过数据
if prev is None or since_ms < prev:
per_symbol[timeframe] = since_ms
logger.warning(
"记录断线恢复点: %s %s since=%s (%s)",
symbol,
timeframe,
since_ms,
to_utc_iso(since_ms),
)
# 同步写盘
self._save_resume_since()
def _clear_resume_since(self, symbol: str, timeframe: str) -> None:
"""补数成功后清除该 symbol/tf 的恢复点。"""
with self._lock:
if symbol in self._resume_since and timeframe in self._resume_since[symbol]:
del self._resume_since[symbol][timeframe]
if not self._resume_since[symbol]:
del self._resume_since[symbol]
logger.info("清除断线恢复点: %s %s", symbol, timeframe)
# 同步写盘
self._save_resume_since()
def on_update(self, callback) -> None:
"""注册数据更新回调(签名: callback(symbol, timeframe))。"""
self._update_callbacks.append(callback)
def _notify_update(self, symbol: str, timeframe: str) -> None:
"""通知所有回调:某 symbol/timeframe 数据已更新。"""
for cb in self._update_callbacks:
try:
cb(symbol, timeframe)
except Exception as exc:
logger.error("数据更新回调异常: %s", exc)
def start_background_workers(self) -> None:
"""启动增量刷新线程与周期性落盘线程。"""
if self._fetch_thread and self._fetch_thread.is_alive():
return
self._stop_event.clear()
self._fetch_thread = threading.Thread(target=self._refresh_loop, name="refresh-loop", daemon=True)
self._persist_thread = threading.Thread(target=self._persist_loop, name="persist-loop", daemon=True)
self._fetch_thread.start()
self._persist_thread.start()
logger.info("后台线程已启动")
def stop(self) -> None:
"""停止后台线程(应用关闭时 lifespan finally 调用)。"""
self._stop_event.set()
if self._fetch_thread:
self._fetch_thread.join(timeout=5)
if self._persist_thread:
self._persist_thread.join(timeout=5)
logger.info("数据提供商已停止")
def _refresh_loop(self) -> None:
"""轮询各 symbol/tf:有恢复点则先补历史,否则 fetch 最近 RECENT_CANDLE_LIMIT 根。"""
while not self._stop_event.is_set():
for symbol in self.symbols:
for timeframe in self.timeframes:
try:
# 若存在断线恢复点,则优先从该 since 补齐历史数据
resume_since = self._get_resume_since(symbol, timeframe)
if resume_since is not None:
logger.info(
"开始断线后补数: %s %s since=%s (%s)",
symbol,
timeframe,
resume_since,
to_utc_iso(resume_since),
)
history = self._fetch_history(symbol, timeframe, resume_since)
with self._lock:
current = self.data.setdefault(symbol, {}).get(timeframe, [])
merged = self._merge_candles(timeframe, current, history)
self.data[symbol][timeframe] = merged
self._notify_update(symbol, timeframe)
self._clear_resume_since(symbol, timeframe)
else:
# 正常增量获取最近若干根K线
candles = self.exchange.fetch_ohlcv(
symbol,
timeframe=timeframe,
limit=RECENT_CANDLE_LIMIT,
)
if not candles:
continue
with self._lock:
current = self.data.setdefault(symbol, {}).get(timeframe, [])
merged = self._merge_candles(timeframe, current, candles)
self.data[symbol][timeframe] = merged
self._notify_update(symbol, timeframe)
except ccxt.BaseError as exc:
logger.error("更新最新K线失败 (%s %s): %s", symbol, timeframe, exc)
# 记录应当从何时恢复拉取,避免重连后从当前时间开始导致丢K
with self._lock:
current = self.data.get(symbol, {}).get(timeframe, [])
if current:
last_ts = int(current[-1]["timestamp"])
else:
last_ts = self.start_time_ms
tf_ms = TIMEFRAME_TO_MS[timeframe]
# 回退一个周期,确保包含可能未完全收盘的K线,去重由 _merge_candles 处理
since_ms = max(self.start_time_ms, last_ts - tf_ms)
self._set_resume_since(symbol, timeframe, since_ms)
time.sleep(2)
continue
if self._stop_event.wait(RECENT_FETCH_INTERVAL):
break
def _persist_loop(self) -> None:
"""每隔 PERSIST_INTERVAL 秒把内存快照写 CSV 并保存恢复点。"""
while not self._stop_event.wait(PERSIST_INTERVAL):
self._persist_all()
def _persist_all(self) -> None:
"""在锁内复制 data 后落盘,避免长时间持锁。"""
if not self._ready.is_set():
return
with self._lock:
snapshot = {
symbol: {tf: list(data) for tf, data in tf_map.items()}
for symbol, tf_map in self.data.items()
}
for symbol, tf_map in snapshot.items():
for timeframe, data in tf_map.items():
self._write_to_disk(symbol, timeframe, data)
# 周期性也保存一次恢复点,保证一致性
self._save_resume_since()
def is_ready(self) -> bool:
return self._ready.is_set()
def wait_ready(self, timeout: Optional[float] = None) -> bool:
return self._ready.wait(timeout)
def get_available_timeframes(self) -> List[str]:
return list(self.available_timeframes)
def get_derived_timeframes(self) -> List[str]:
return list(self.derived_map.keys())
def _get_base_klines(
self,
symbol: str,
timeframe: str,
start_ms: Optional[int],
end_ms: Optional[int],
limit: Optional[int],
) -> List[Dict[str, float]]:
"""从内存读取已缓存的基础周期 K 线并按时间/limit 裁剪。"""
with self._lock:
candles = list(self.data.get(symbol, {}).get(timeframe, []))
if start_ms is not None:
candles = [row for row in candles if row["timestamp"] >= start_ms]
if end_ms is not None:
candles = [row for row in candles if row["timestamp"] <= end_ms]
if limit:
candles = candles[-limit:]
return candles
def get_klines(
self,
symbol: str,
timeframe: str,
start_time: Optional[object] = None,
end_time: Optional[object] = None,
limit: Optional[int] = None,
) -> List[Dict[str, float]]:
"""对外查询:基础周期直接返回;衍生周期从 derived_map 取 baseresample 后对齐时间戳再裁剪。"""
if symbol not in self.symbols:
raise HTTPException(status_code=404, detail=f"symbol {symbol} 不可用")
self.wait_ready()
start_ms = parse_timestamp(start_time)
end_ms = parse_timestamp(end_time)
if timeframe in self.timeframes:
return self._get_base_klines(symbol, timeframe, start_ms, end_ms, limit)
base_tf = self.derived_map.get(timeframe)
if not base_tf:
raise HTTPException(status_code=404, detail=f"{symbol} 时间周期 {timeframe} 不可用")
target_minutes = timeframe_to_minutes(timeframe)
if target_minutes is None:
raise HTTPException(status_code=400, detail=f"不支持的时间周期: {timeframe}")
target_ms = target_minutes * 60_000
# 起点前移一根目标周期长度,保证首根合成 K 边界完整
adjusted_start = None if start_ms is None else max(0, start_ms - target_ms)
base_candles = self._get_base_klines(symbol, base_tf, adjusted_start, end_ms, None)
if not base_candles:
return []
df = pd.DataFrame(base_candles)
if df.empty:
return []
df = df.drop_duplicates(subset=["timestamp"], keep="last").sort_values("timestamp")
df["date"] = pd.to_datetime(df["timestamp"], unit="ms", utc=True)
# resample_to_interval 按「分钟」目标周期聚合 OHLCV
resampled = self.resample_df(df, target_minutes)
if resampled is None or resampled.empty:
return []
# 统一得到毫秒 timestamp 列(resample 可能返回 date 或 DatetimeIndex
if "timestamp" in resampled.columns:
resampled_df = resampled.copy()
else:
resampled_df = resampled.copy()
if "date" in resampled_df.columns:
dates = pd.to_datetime(resampled_df["date"], utc=True, errors="coerce")
resampled_df["timestamp"] = (dates.astype("int64", copy=False) // 1_000_000).astype("int64")
elif isinstance(resampled_df.index, pd.DatetimeIndex):
idx = resampled_df.index
if idx.tz is None:
idx = idx.tz_localize("UTC")
else:
idx = idx.tz_convert("UTC")
resampled_df["timestamp"] = (idx.astype("int64", copy=False) // 1_000_000).astype("int64")
else:
raise HTTPException(status_code=500, detail=f"聚合结果缺少 timestamp 列 ({timeframe})")
resampled_df = resampled_df.dropna(subset=["timestamp"]).sort_values("timestamp")
if start_ms is not None:
resampled_df = resampled_df[resampled_df["timestamp"] >= start_ms]
if end_ms is not None:
resampled_df = resampled_df[resampled_df["timestamp"] <= end_ms]
if resampled_df.empty:
return []
resampled_df["datetime"] = resampled_df["timestamp"].apply(to_utc_iso)
for column in ["open", "high", "low", "close", "volume"]:
if column not in resampled_df.columns:
resampled_df[column] = 0.0
resampled_df = resampled_df[["timestamp", "datetime", "open", "high", "low", "close", "volume"]]
result = resampled_df.to_dict("records")
if limit:
result = result[-limit:]
logger.debug(
"衍生周期返回",
extra={
"symbol": symbol,
"timeframe": timeframe,
"base_timeframe": base_tf,
"count": len(result),
},
)
return result
def create_app(provider: DataProvider) -> FastAPI:
"""构造 FastAPI 应用:lifespan 内同步 initialize 并启动后台拉数;WebSocket 实时推送。"""
ws_manager = WebSocketManager()
def _on_data_update(symbol: str, base_tf: str) -> None:
"""后台刷新线程回调:广播基础及衍生周期更新给 WebSocket 订阅者。"""
with provider._lock:
base_data = list(provider.data.get(symbol, {}).get(base_tf, []))
recent = base_data[-WS_UPDATE_CANDLE_COUNT:] if base_data else []
if recent:
ws_manager.broadcast_from_thread(symbol, base_tf, recent)
for derived_tf, src_base in provider.derived_map.items():
if src_base != base_tf or not ws_manager.has_subscribers(symbol, derived_tf):
continue
try:
target_min = timeframe_to_minutes(derived_tf)
if target_min is None:
continue
now_ms = int(time.time() * 1000)
window_ms = target_min * 60_000 * (WS_UPDATE_CANDLE_COUNT + 2)
derived = provider.get_klines(
symbol, derived_tf, start_time=now_ms - window_ms, limit=WS_UPDATE_CANDLE_COUNT,
)
if derived:
ws_manager.broadcast_from_thread(symbol, derived_tf, derived)
except Exception as exc:
logger.debug("衍生周期广播失败 %s %s: %s", symbol, derived_tf, exc)
@asynccontextmanager
async def lifespan(app: FastAPI):
loop = asyncio.get_running_loop()
ws_manager.set_loop(loop)
provider.on_update(_on_data_update)
await loop.run_in_executor(None, provider.initialize)
provider.start_background_workers()
try:
yield
finally:
provider.stop()
app = FastAPI(title="Chan 数据提供商", version="1.0.0", lifespan=lifespan)
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
@app.get("/health")
async def health() -> Dict[str, object]:
"""存活检查:交易所、交易对、基础/衍生周期、是否已完成冷启动。"""
return {
"status": "ok",
"exchange": provider.exchange_name,
"symbols": provider.symbols,
"base_timeframes": provider.timeframes,
"derived_timeframes": provider.get_derived_timeframes(),
"timeframes": provider.get_available_timeframes(),
"ready": provider.is_ready(),
}
@app.get("/timeframes")
async def list_timeframes() -> Dict[str, List[str]]:
"""返回配置的基础周期与可合成的衍生周期列表。"""
provider.wait_ready()
return {
"base_timeframes": provider.timeframes,
"derived_timeframes": provider.get_derived_timeframes(),
"timeframes": provider.get_available_timeframes(),
}
@app.get("/api/candles")
async def api_candles(
symbol: str = Query(..., description="如 BTC/USDT"),
tf: str = Query("1m", description="时间周期"),
start: Optional[int] = Query(None, description="开始时间戳(ms)"),
end: Optional[int] = Query(None, description="结束时间戳(ms)"),
limit: Optional[int] = Query(None, description="可选,限制返回数量"),
):
"""按交易对与时间周期返回 OHLCV;tf 支持配置的基础周期及衍生合成周期。"""
data = provider.get_klines(symbol=symbol, timeframe=tf, start_time=start, end_time=end, limit=limit)
return data
@app.get("/")
async def root() -> Dict[str, object]:
"""根路径:服务名、交易所、交易对与可用周期(含 ready 标志)。"""
return {
"service": "Data Provider",
"exchange": provider.exchange_name,
"symbols": provider.symbols,
"base_timeframes": provider.timeframes,
"derived_timeframes": provider.get_derived_timeframes(),
"timeframes": provider.get_available_timeframes(),
"ready": provider.is_ready(),
}
@app.websocket("/ws")
async def websocket_endpoint(ws: WebSocket):
"""WebSocket 实时 K 线推送。
客户端发送 JSON:
{"action": "subscribe", "symbol": "BTC/USDT:USDT", "timeframe": "1m"}
{"action": "unsubscribe", "symbol": "BTC/USDT:USDT", "timeframe": "1m"}
{"action": "ping"}
服务端推送:
{"type": "subscribed", "symbol": "...", "timeframe": "..."}
{"type": "snapshot", "symbol": "...", "timeframe": "...", "data": [...]}
{"type": "kline", "symbol": "...", "timeframe": "...", "data": [...]}
{"type": "pong"}
{"type": "error", "message": "..."}
"""
await ws_manager.connect(ws)
try:
while True:
raw = await ws.receive_text()
try:
msg = json.loads(raw)
except json.JSONDecodeError:
await ws.send_text(json.dumps({"type": "error", "message": "invalid JSON"}))
continue
action = msg.get("action", "")
symbol = str(msg.get("symbol", "")).strip()
timeframe = str(msg.get("timeframe", "")).strip()
if action == "ping":
await ws.send_text(json.dumps({"type": "pong"}))
elif action == "subscribe":
if not symbol or not timeframe:
await ws.send_text(json.dumps(
{"type": "error", "message": "需要 symbol 和 timeframe 字段"}
))
continue
await ws_manager.subscribe(ws, symbol, timeframe)
await ws.send_text(json.dumps(
{"type": "subscribed", "symbol": symbol, "timeframe": timeframe},
ensure_ascii=False,
))
try:
snapshot = provider.get_klines(symbol, timeframe, limit=DEFAULT_LIMIT)
if snapshot:
await ws.send_text(json.dumps(
{"type": "snapshot", "symbol": symbol, "timeframe": timeframe, "data": snapshot},
ensure_ascii=False,
))
except Exception as exc:
await ws.send_text(json.dumps({"type": "error", "message": str(exc)}))
elif action == "unsubscribe":
await ws_manager.unsubscribe(ws, symbol, timeframe)
await ws.send_text(json.dumps(
{"type": "unsubscribed", "symbol": symbol, "timeframe": timeframe},
ensure_ascii=False,
))
else:
await ws.send_text(json.dumps({"type": "error", "message": f"未知 action: {action}"}))
except WebSocketDisconnect:
pass
finally:
await ws_manager.disconnect(ws)
return app
def build_app() -> FastAPI:
"""默认入口:从环境变量 CONFIG_PATH(或 config.json)加载配置并创建 FastAPI app。"""
config_path = Path(os.getenv("CONFIG_PATH", "config.json"))
provider = DataProvider(config_path)
return create_app(provider)
app = build_app()
def main() -> None:
"""直接运行本模块时启动 uvicorn(监听 UVICORN_HOST / UVICORN_PORT)。"""
host = os.getenv("UVICORN_HOST", "0.0.0.0")
port = int(os.getenv("UVICORN_PORT", "9009"))
uvicorn.run(app, host=host, port=port, log_level=os.getenv("UVICORN_LOG_LEVEL", "info"))
if __name__ == "__main__":
main()
+5
View File
@@ -0,0 +1,5 @@
ccxt>=4.0.0,<5.0.0
fastapi>=0.110.0,<1.0.0
uvicorn[standard]>=0.23.0,<1.0.0
pandas>=2.0.0,<3.0.0
technical==1.5.0
+82
View File
@@ -0,0 +1,82 @@
#!/bin/bash
# 缠论分析系统生产环境部署脚本
# 显示执行的命令
set -x
# 确保脚本在错误时停止
set -e
# 项目根目录
PROJECT_DIR=$(pwd)
echo "项目将部署在: $PROJECT_DIR"
# 创建虚拟环境
echo "创建Python虚拟环境..."
python3 -m venv venv
source venv/bin/activate
# 安装依赖
echo "安装依赖包..."
pip install --upgrade pip
pip install flask pandas matplotlib ccxt pytz talib-binary gunicorn
# 安装任何额外的系统依赖
# sudo apt-get update
# sudo apt-get install -y python3-dev
# 检查目录结构
echo "检查目录结构..."
mkdir -p web/templates
# 创建日志目录
mkdir -p logs
# 创建启动脚本
echo "创建启动脚本..."
cat > start_service.sh << 'EOF'
#!/bin/bash
# 缠论分析系统启动脚本
# 项目根目录
PROJECT_DIR=$(pwd)
cd $PROJECT_DIR
# 激活虚拟环境
source venv/bin/activate
# 启动服务
cd web
echo "启动缠论分析系统服务..."
gunicorn app:app --bind=0.0.0.0:8123 --workers=4 --timeout=120 --log-level=info --log-file=logs/chanlun.log --daemon
echo "服务已启动,端口8123"
echo "日志文件位置: $PROJECT_DIR/web/logs/chanlun.log"
EOF
# 创建停止脚本
echo "创建停止脚本..."
cat > stop_service.sh << 'EOF'
#!/bin/bash
# 停止缠论分析系统服务
echo "停止缠论分析系统服务..."
pkill -f "gunicorn app:app"
echo "服务已停止"
EOF
# 添加执行权限
chmod +x start_service.sh
chmod +x stop_service.sh
# 修改app.py中的调试模式(生产环境应关闭调试模式)
if [ -f web/app.py ]; then
echo "配置app.py为生产环境模式..."
sed -i 's/app.run(debug=True, host='\''0.0.0.0'\'', port=8123)/# 在生产环境中,使用gunicorn启动服务\n# app.run(debug=False, host='\''0.0.0.0'\'', port=8124)/' user_data/Chan/web/app.py
else
echo "警告: 未找到app.py文件"
fi
echo "部署完成!"
echo "使用 ./start_service.sh 启动服务"
echo "使用 ./stop_service.sh 停止服务"
+162
View File
@@ -0,0 +1,162 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
分型强度检测使用示例
该文件展示如何使用ChanKLC类中新增的分型强度检测功能
"""
from ChanKLC import ChanKLC
from ChanEnum import Chan_FX_TYPE
import ChanKLU
def demo_fx_strength_detection():
"""
演示分型强度检测功能
"""
print("=== 分型强度检测功能演示 ===\n")
# 假设我们有一个已经确定为分型的KLC对象
# 这里仅为演示,实际使用中KLC对象应该通过正常流程创建
print("1. 分型强度计算方法:")
print(" - calculate_fx_strength(): 返回0-100的强度分数")
print(" - get_fx_strength_level(): 返回强度等级描述")
print(" - is_strong_fx(threshold): 判断是否为强分型")
print()
print("2. 强度评分维度 (总分100分):")
print(" - 价格差异强度: 40分 (与相邻K线的价格差异)")
print(" - 突破历史点位: 20分 (是否突破重要高低点)")
print(" - 成交量确认: 15分 (分型形成时的成交量)")
print(" - RSI背离确认: 15分 (价格与RSI的背离)")
print(" - MACD背离确认: 10分 (价格与MACD的背离)")
print()
print("3. 强度等级分类:")
print(" - 极强: 80-100分")
print(" - 强: 60-79分")
print(" - 中等: 40-59分")
print(" - 弱: 20-39分")
print(" - 极弱: 0-19分")
print()
print("4. 在特征数据中的应用:")
print(" 分型强度会自动集成到get_feature_data()方法返回的特征中:")
print(" - klc_fx_strength: 强度分数")
print(" - klc_fx_strength_level: 强度等级")
print(" - klc_is_strong_fx: 是否为强分型(布尔值)")
print(" - klc_fx_strength_extreme: 是否为极强分型")
print(" - klc_fx_strength_strong: 是否为强分型")
print(" - klc_fx_strength_medium: 是否为中等分型")
print(" - klc_fx_strength_weak: 是否为弱分型")
print(" - klc_fx_strength_very_weak: 是否为极弱分型")
print()
def analyze_fx_strength(klc):
"""
分析单个KLC的分型强度
Args:
klc: ChanKLC对象
"""
if klc.fx == Chan_FX_TYPE.UNKNOWN:
print(f"时间: {klc.start_time} - 无分型")
return
fx_type = "顶分型" if klc.fx == Chan_FX_TYPE.TOP else "底分型"
strength = klc.calculate_fx_strength()
strength_level = klc.get_fx_strength_level()
is_strong = klc.is_strong_fx()
print(f"时间: {klc.start_time}")
print(f"分型类型: {fx_type}")
print(f"强度分数: {strength}")
print(f"强度等级: {strength_level}")
print(f"是否强分型: {'' if is_strong else ''}")
print("-" * 30)
def filter_strong_fractals(klc_list, min_strength=60):
"""
筛选强分型
Args:
klc_list: KLC对象列表
min_strength: 最小强度阈值
Returns:
强分型列表
"""
strong_fractals = []
for klc in klc_list:
if klc.fx != Chan_FX_TYPE.UNKNOWN and klc.is_strong_fx(min_strength):
strong_fractals.append(klc)
return strong_fractals
def get_fractal_statistics(klc_list):
"""
获取分型强度统计信息
Args:
klc_list: KLC对象列表
Returns:
统计信息字典
"""
stats = {
'total_fractals': 0,
'top_fractals': 0,
'bottom_fractals': 0,
'extreme_strength': 0, # 极强
'strong_strength': 0, # 强
'medium_strength': 0, # 中等
'weak_strength': 0, # 弱
'very_weak_strength': 0,# 极弱
'avg_strength': 0
}
strengths = []
for klc in klc_list:
if klc.fx != Chan_FX_TYPE.UNKNOWN:
stats['total_fractals'] += 1
if klc.fx == Chan_FX_TYPE.TOP:
stats['top_fractals'] += 1
else:
stats['bottom_fractals'] += 1
strength = klc.calculate_fx_strength()
strengths.append(strength)
if strength >= 80:
stats['extreme_strength'] += 1
elif strength >= 60:
stats['strong_strength'] += 1
elif strength >= 40:
stats['medium_strength'] += 1
elif strength >= 20:
stats['weak_strength'] += 1
else:
stats['very_weak_strength'] += 1
if strengths:
stats['avg_strength'] = sum(strengths) / len(strengths)
return stats
if __name__ == "__main__":
demo_fx_strength_detection()
print("=== 使用建议 ===")
print("1. 在交易策略中,可以只关注强度>=60的分型")
print("2. 极强分型(>=80分)通常是重要的转折点")
print("3. 结合成交量和技术指标背离的分型更可靠")
print("4. 可以用分型强度来设置止损和止盈位置")
print("5. 分型强度可以作为机器学习模型的重要特征")
+4
View File
@@ -0,0 +1,4 @@
#!/bin/bash
cd /path/to/your/project/user_data/Chan/web
source /path/to/your/virtualenv/bin/activate # 如果使用虚拟环境
exec gunicorn app:app -b 0.0.0.0:8123 --workers=4 --timeout 120
+220
View File
@@ -0,0 +1,220 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
实时K线分型强弱判断示例
解决KLC滞后问题提供即时的分型信号
"""
from ChanKLU import ChanKLU
from ChanEnum import Chan_FX_TYPE
import pandas as pd
from datetime import datetime, timedelta
class RealtimeFxAnalyzer:
"""实时分型分析器"""
def __init__(self):
self.klu_list = []
self.latest_signals = []
def add_kline(self, time, open_price, high, low, close, volume, indicators=None):
"""
添加新的K线数据并进行实时分析
Args:
time: 时间
open_price, high, low, close, volume: K线数据
indicators: 技术指标字典 {'macd': xx, 'rsi': xx, 'ma5': xx, ...}
"""
# 创建新的KLU对象
new_klu = ChanKLU(time, open_price, high, low, close, volume)
# 设置技术指标
if indicators:
new_klu.set_indicators(indicators)
# 设置索引
new_klu.set_idx(len(self.klu_list))
# 建立前后关系链
if len(self.klu_list) >= 1:
prev_klu = self.klu_list[-1]
new_klu.set_pre(prev_klu)
prev_klu.set_next(new_klu)
# 如果有足够的数据,设置前一根K线的next关系
if len(self.klu_list) >= 2:
prev_prev_klu = self.klu_list[-2]
prev_prev_klu.set_next(self.klu_list[-1])
self.klu_list.append(new_klu)
# 实时分析最近的K线分型
self._analyze_recent_fractals()
return new_klu
def _analyze_recent_fractals(self):
"""分析最近的分型情况"""
if len(self.klu_list) < 3:
return
# 检查倒数第二根K线的分型(因为需要左右两根K线确认)
target_idx = len(self.klu_list) - 2
if target_idx >= 1:
target_klu = self.klu_list[target_idx]
# 进行实时分型分析
target_klu.update_realtime_analysis()
# 如果发现分型,记录信号
if target_klu.fx_confirmed:
signal = target_klu.get_fx_signal()
signal_info = {
'time': target_klu.time,
'price': target_klu.close,
'signal_type': signal[0],
'strength': signal[1],
'suggestion': signal[2],
'fx_type': target_klu.fx_type
}
self.latest_signals.append(signal_info)
# 保持最近20个信号
if len(self.latest_signals) > 20:
self.latest_signals.pop(0)
print(f"🔔 分型信号: {signal_info['time']} - {signal_info['signal_type']} "
f"(强度: {signal_info['strength']}) - {signal_info['suggestion']}")
def get_latest_signal(self):
"""获取最新的分型信号"""
return self.latest_signals[-1] if self.latest_signals else None
def get_current_fx_status(self):
"""获取当前分型状态统计"""
if len(self.klu_list) < 10:
return {"status": "数据不足"}
recent_10 = self.klu_list[-10:]
top_fx_count = sum(1 for klu in recent_10 if klu.fx_type == Chan_FX_TYPE.TOP)
bottom_fx_count = sum(1 for klu in recent_10 if klu.fx_type == Chan_FX_TYPE.BOTTOM)
strong_fx_count = sum(1 for klu in recent_10 if klu.fx_strength >= 65)
return {
"最近10根K线": len(recent_10),
"顶分型数量": top_fx_count,
"底分型数量": bottom_fx_count,
"强分型数量": strong_fx_count,
"最新K线时间": recent_10[-1].time,
"最新信号": self.get_latest_signal()
}
def simulate_realtime_trading():
"""模拟实时交易场景"""
print("=== 实时K线分型分析示例 ===\n")
# 创建分析器
analyzer = RealtimeFxAnalyzer()
# 模拟实时K线数据流
base_time = datetime.now()
base_price = 100.0
print("开始接收K线数据...\n")
for i in range(20):
# 模拟价格波动
if i < 5: # 上涨阶段
price_change = 0.5
elif i < 10: # 下跌阶段
price_change = -0.8
elif i < 15: # 震荡阶段
price_change = 0.3 * ((-1) ** i)
else: # 再次上涨
price_change = 0.6
current_price = base_price + price_change
# 构造K线数据
open_price = base_price
high = max(open_price, current_price) + abs(price_change) * 0.2
low = min(open_price, current_price) - abs(price_change) * 0.2
close = current_price
volume = 1000 + i * 50
# 模拟技术指标
indicators = {
'ma5': base_price + (i - 10) * 0.1,
'ma10': base_price + (i - 10) * 0.05,
'rsi': 50 + (i % 7 - 3) * 10,
'macd': (i % 6 - 3) * 0.01,
'macdhist': (i % 4 - 2) * 0.005,
'volume_ratio': 1.0 + (i % 3 - 1) * 0.2
}
# 添加K线数据
kline_time = base_time + timedelta(minutes=i)
analyzer.add_kline(
time=kline_time.strftime("%Y-%m-%d %H:%M:%S"),
open_price=open_price,
high=high,
low=low,
close=close,
volume=volume,
indicators=indicators
)
base_price = current_price
# 每5根K线显示一次状态
if (i + 1) % 5 == 0:
status = analyzer.get_current_fx_status()
print(f"\n--- 第{i+1}根K线后的状态 ---")
for key, value in status.items():
if key != "最新信号":
print(f"{key}: {value}")
if "最新信号" in status and status["最新信号"]:
signal = status["最新信号"]
print(f"最新信号: {signal['signal_type']} (强度: {signal['strength']})")
print()
print("\n=== 所有分型信号汇总 ===")
for signal in analyzer.latest_signals:
print(f"{signal['time']} | {signal['signal_type']} | 强度: {signal['strength']} | {signal['suggestion']}")
def compare_latency():
"""对比KLC和KLU方法的延迟差异"""
print("\n=== 延迟对比分析 ===")
print("假设场景:连续包含关系的K线序列")
print("原始K线: K1, K2(包含K1), K3(包含K2), K4(突破), K5, K6")
print()
print("KLC方法:")
print("- 需要等待K4确认包含关系结束")
print("- KLC1 = [K1+K2+K3], 在K4完成时才确定")
print("- 分型检测: 需要等待KLC1, KLC2, KLC3")
print("- 实际延迟: 可能6-8根原始K线")
print()
print("KLU实时方法:")
print("- 每根K线完成时立即检测")
print("- K3完成时就能检测K2的分型状态")
print("- 实际延迟: 最多1根K线")
print()
print("延迟改善: 从6-8根K线缩短到1根K线")
print("时间价值: 在5分钟K线下,可节省25-40分钟的反应时间")
if __name__ == "__main__":
# 运行模拟
simulate_realtime_trading()
# 显示延迟对比
compare_latency()
-239
View File
@@ -1,239 +0,0 @@
"""突破跟随的事件驱动回测。
趋势跟随是低胜率高赔率固定持有期会把大赢利截断把小亏损放大
必须用止损/止盈/时间三重出场才能测出真实期望
"""
from __future__ import annotations
from dataclasses import dataclass
import numpy as np
import pandas as pd
FEE = 0.0008 # 双边
@dataclass
class Trade:
entry_idx: int
exit_idx: int
direction: int
entry: float
exit: float
ret: float # 已扣费
reason: str # sl / tp / time
bars_held: int
gross: float # 未扣费,便于事后做费率 what-if
risk_pct: float # 止损距离占入场价的比例,用于反推名义仓位
boosted: bool = False # 持仓期间是否等到了更大级别的同向确认
def run_trades(
df: pd.DataFrame,
entries: list[tuple[int, int]],
sl_atr: float = 1.5,
tp_atr: float = 3.0,
max_bars: int = 48,
trail: bool = False,
fee: float = FEE,
entry_delay: int = 0,
slippage: float = 0.0,
boost_dir: np.ndarray | None = None,
tp_boost: float = 2.0,
boost_breakeven: bool = False,
) -> pd.DataFrame:
"""按 (entry_idx, direction) 逐笔模拟。
出场优先级同一根内若同时触及止损与止盈保守地判为止损
entry_delay=1 表示信号次根开盘成交用来检验收盘价入场是否过于乐观
boost_dir 给出每根K线上趋势被更大级别确认的方向+1/-1/0持仓期间
一旦等到同向确认就把止盈目标放大 tp_boost boost_breakeven 同时把
止损收到成本价用来检验新证据出现后该不该改单
"""
high = df["high"].to_numpy(dtype=float)
low = df["low"].to_numpy(dtype=float)
close = df["close"].to_numpy(dtype=float)
open_ = df["open"].to_numpy(dtype=float) if "open" in df.columns else close
atr = (
df["atr"].to_numpy(dtype=float)
if "atr" in df.columns
else pd.Series(close).rolling(14).std().bfill().to_numpy()
)
n = len(df)
out: list[Trade] = []
for e_idx, d in entries:
sig_idx = e_idx
e_idx = e_idx + entry_delay
if e_idx >= n - 1:
continue
entry = open_[e_idx] if entry_delay else close[e_idx]
a = atr[sig_idx]
if not np.isfinite(a) or a <= 0:
continue
sl = entry - d * sl_atr * a
tp = entry + d * tp_atr * a
best = entry
exit_idx, exit_px, reason = None, None, "time"
boosted = False
for j in range(e_idx + 1, min(e_idx + max_bars + 1, n)):
if boost_dir is not None and not boosted and boost_dir[j] == d:
tp = entry + d * tp_atr * tp_boost * a
if boost_breakeven:
sl = max(sl, entry) if d == 1 else min(sl, entry)
boosted = True
if trail:
best = max(best, high[j]) if d == 1 else min(best, low[j])
sl = max(sl, best - sl_atr * a) if d == 1 else min(sl, best + sl_atr * a)
hit_sl = low[j] <= sl if d == 1 else high[j] >= sl
hit_tp = high[j] >= tp if d == 1 else low[j] <= tp
if hit_sl:
exit_idx, exit_px, reason = j, sl, "sl"
break
if hit_tp:
exit_idx, exit_px, reason = j, tp, "tp"
break
if exit_idx is None:
exit_idx = min(e_idx + max_bars, n - 1)
exit_px = close[exit_idx]
gross = d * (exit_px - entry) / entry
out.append(Trade(sig_idx, exit_idx, d, entry, float(exit_px),
gross - fee - slippage, reason, exit_idx - e_idx,
gross, sl_atr * a / entry, boosted))
return pd.DataFrame([t.__dict__ for t in out])
def run_trades_dynamic(
df: pd.DataFrame,
entries: list[tuple[int, int]],
upgrades: dict[int, int],
sl_atr: float = 1.5,
tp_atr: float = 3.0,
tp_atr_up: float = 6.0,
max_bars: int = 48,
max_bars_up: int = 96,
lock_breakeven: bool = True,
fee: float = FEE,
entry_delay: int = 0,
slippage: float = 0.0,
) -> pd.DataFrame:
"""持仓中若出现更大级别的同向确认,就把目标放远、并把止损收到成本价。
upgrades: {K线索引: 方向}表示该根出现了大级别同向三买/中枢突破
对应的交易逻辑是小级别进场大级别接力趋势被更高级别确认后
原本 3 ATR 的目标就过早了但同时不该再让这笔回到亏损
"""
high = df["high"].to_numpy(dtype=float)
low = df["low"].to_numpy(dtype=float)
close = df["close"].to_numpy(dtype=float)
open_ = df["open"].to_numpy(dtype=float) if "open" in df.columns else close
atr = (
df["atr"].to_numpy(dtype=float)
if "atr" in df.columns
else pd.Series(close).rolling(14).std().bfill().to_numpy()
)
n = len(df)
out: list[dict] = []
for e_idx, d in entries:
sig_idx = e_idx
e_idx = e_idx + entry_delay
if e_idx >= n - 1:
continue
entry = open_[e_idx] if entry_delay else close[e_idx]
a = atr[sig_idx]
if not np.isfinite(a) or a <= 0:
continue
sl = entry - d * sl_atr * a
tp = entry + d * tp_atr * a
limit = max_bars
upgraded = False
exit_idx, exit_px, reason = None, None, "time"
j = e_idx + 1
while j < min(e_idx + limit + 1, n):
if not upgraded and upgrades.get(j) == d:
upgraded = True
tp = entry + d * tp_atr_up * a
limit = max_bars_up
if lock_breakeven:
sl = max(sl, entry) if d == 1 else min(sl, entry)
hit_sl = low[j] <= sl if d == 1 else high[j] >= sl
hit_tp = high[j] >= tp if d == 1 else low[j] <= tp
if hit_sl:
exit_idx, exit_px, reason = j, sl, "be" if upgraded and sl == entry else "sl"
break
if hit_tp:
exit_idx, exit_px, reason = j, tp, "tp_up" if upgraded else "tp"
break
j += 1
if exit_idx is None:
exit_idx = min(e_idx + limit, n - 1)
exit_px = close[exit_idx]
gross = d * (exit_px - entry) / entry
out.append({
"entry_idx": sig_idx, "exit_idx": exit_idx, "direction": d,
"entry": entry, "exit": float(exit_px),
"ret": gross - fee - slippage, "reason": reason,
"bars_held": exit_idx - e_idx, "gross": gross,
"risk_pct": sl_atr * a / entry, "upgraded": upgraded,
})
return pd.DataFrame(out)
def summarize_trades(tr: pd.DataFrame, label: str) -> dict:
if tr.empty:
return {"策略": label, "笔数": 0}
r = tr["ret"].to_numpy()
win = r[r > 0]
loss = r[r <= 0]
pf = win.sum() / abs(loss.sum()) if len(loss) and loss.sum() != 0 else np.inf
sd = r.std(ddof=1)
eq = np.cumprod(1 + r)
dd = float((1 - eq / np.maximum.accumulate(eq)).max()) if len(eq) else 0.0
return {
"策略": label,
"笔数": len(r),
"胜率": f"{(r > 0).mean() * 100:.1f}%",
"均收益": f"{r.mean() * 100:+.3f}%",
"赔率": f"{(win.mean() / abs(loss.mean())):.2f}" if len(win) and len(loss) else "",
"盈亏比PF": f"{pf:.2f}",
"总收益": f"{(eq[-1] - 1) * 100:+.1f}%",
"最大回撤": f"{dd * 100:.1f}%",
"t值": f"{r.mean() / (sd / np.sqrt(len(r))):+.2f}" if sd else "",
"均持有": f"{tr['bars_held'].mean():.0f}",
}
def find_breakout_entries(
sig: pd.DataFrame, df: pd.DataFrame, window: int = 10, mode: str = "fail"
) -> list[tuple[int, int]]:
"""分型突破入场点。
mode="fail" 分型失败 -> 顺势跟随顶分型被向上突破则做多
mode="reverse" 传统反转 -> 分型成立方向顶分型做空作为对照
"""
close = df["close"].to_numpy(dtype=float)
n = len(df)
entries: list[tuple[int, int]] = []
for _, r in sig.iterrows():
d_fx = int(r["direction"]) # +1 底分型 / -1 顶分型
lvl = float(r["price"])
c0 = int(r["confirm_idx"])
if mode == "reverse":
entries.append((c0, d_fx))
continue
# 突破方向与分型指向相反:顶分型(-1)被向上(+1)突破
d_bo = -d_fx
for j in range(c0 + 1, min(c0 + window + 1, n)):
broken = close[j] > lvl if d_bo == 1 else close[j] < lvl
if broken:
entries.append((j, d_bo))
break
return entries
-200
View File
@@ -1,200 +0,0 @@
"""缠论买卖点信号有效性评估:事件研究(event study)。
核心口径约定
- 入场时刻一律取 bsp.sure_time笔被确认的那根K线收盘而非分型时间 end_time
分型时间在当时是不可知的用它回测等于开了未来函数
- BUY 视为做多SELL 视为做空收益按方向调整后统一为正=盈利
"""
from __future__ import annotations
import sys
from dataclasses import dataclass
from pathlib import Path
import numpy as np
import pandas as pd
sys.path.insert(0, str(Path(__file__).resolve().parents[2]))
from chanlun import TF_DF
from chanlun.core.ChanEnum import Chan_BSP_DIR
DEFAULT_HORIZONS = (1, 3, 5, 10, 20, 40)
@dataclass
class BspEvent:
"""一个可交易的买卖点事件。"""
bsp_type: str
direction: int # +1 做多 / -1 做空
fx_time: pd.Timestamp # 分型时间(信号形态出现)
entry_time: pd.Timestamp # 确认时间(可交易)
entry_idx: int
entry_price: float
lag_bars: int # 确认滞后了多少根K线
def build_bi_zs(chan: TF_DF, zs_source: str) -> list:
"""构造笔中枢列表。
seg 在每个线段内部找笔中枢中枢必须等所属线段成形确认慢一层
pure 直接在扁平笔序列上滚动不依赖线段确认更快
"""
if zs_source == "seg":
return chan.cal_bi_zs(chan.seg_list)
if zs_source == "pure":
return chan.cal_bi_zs_list_pure(chan.bi_list)
raise ValueError(f"未知的中枢来源: {zs_source}")
def run_pipeline(df: pd.DataFrame, tf: str, zs_source: str = "seg") -> tuple[TF_DF, list]:
"""跑完整缠论 pipeline,返回引擎与买卖点列表。"""
chan = TF_DF(df, 1, tf)
bi_zs_list = build_bi_zs(chan, zs_source)
bsp_list = chan.find_all_bsp(chan.bi_list, bi_zs_list) if bi_zs_list else []
return chan, bsp_list
def _time_index_map(df: pd.DataFrame) -> dict[str, int]:
"""K线收盘时间 -> 行号。缠论内部把时间存成无时区字符串,按字符串对齐最稳。"""
keys = df["date"].dt.strftime("%Y-%m-%d %H:%M:%S")
return {k: i for i, k in enumerate(keys)}
def to_events(bsp_list: list, df: pd.DataFrame) -> list[BspEvent]:
"""把 ChanBSP 转成以确认时刻为准的可交易事件。"""
idx_map = _time_index_map(df)
closes = df["close"].to_numpy(dtype=float)
events: list[BspEvent] = []
for bsp in bsp_list:
if not bsp.is_sure or bsp.sure_time is None:
continue
entry_key, fx_key = str(bsp.sure_time), str(bsp.end_time)
if entry_key not in idx_map or fx_key not in idx_map:
continue
entry_idx = idx_map[entry_key]
fx_idx = idx_map[fx_key]
entry_ts = pd.Timestamp(entry_key)
fx_ts = pd.Timestamp(fx_key)
events.append(
BspEvent(
bsp_type=str(bsp.type).replace("Chan_BSP_TYPE.", ""),
direction=1 if bsp.dir == Chan_BSP_DIR.BUY else -1,
fx_time=fx_ts,
entry_time=entry_ts,
entry_idx=entry_idx,
entry_price=float(closes[entry_idx]),
lag_bars=entry_idx - fx_idx,
)
)
return events
def forward_returns(
events: list[BspEvent], df: pd.DataFrame, horizons=DEFAULT_HORIZONS
) -> pd.DataFrame:
"""计算每个事件在各持有期的方向调整收益,以及 MFE/MAE。"""
closes = df["close"].to_numpy(dtype=float)
highs = df["high"].to_numpy(dtype=float)
lows = df["low"].to_numpy(dtype=float)
n = len(df)
rows = []
for ev in events:
row = {
"bsp_type": ev.bsp_type,
"direction": ev.direction,
"side": "LONG" if ev.direction == 1 else "SHORT",
"fx_time": ev.fx_time,
"entry_time": ev.entry_time,
"entry_idx": ev.entry_idx,
"entry_price": ev.entry_price,
"lag_bars": ev.lag_bars,
}
for h in horizons:
j = ev.entry_idx + h
if j >= n:
row[f"ret_{h}"] = np.nan
row[f"mfe_{h}"] = np.nan
row[f"mae_{h}"] = np.nan
continue
seg = slice(ev.entry_idx + 1, j + 1)
row[f"ret_{h}"] = ev.direction * (closes[j] - ev.entry_price) / ev.entry_price
if ev.direction == 1:
best, worst = highs[seg].max(), lows[seg].min()
else:
best, worst = lows[seg].min(), highs[seg].max()
row[f"mfe_{h}"] = ev.direction * (best - ev.entry_price) / ev.entry_price
row[f"mae_{h}"] = ev.direction * (worst - ev.entry_price) / ev.entry_price
rows.append(row)
return pd.DataFrame(rows)
def baseline_stats(df: pd.DataFrame, horizons=DEFAULT_HORIZONS) -> pd.DataFrame:
"""基准:全样本每根K线无条件持有的收益分布(多头视角)。"""
closes = df["close"].to_numpy(dtype=float)
rows = []
for h in horizons:
fwd = (closes[h:] - closes[:-h]) / closes[:-h]
rows.append(
{
"horizon": h,
"base_mean_long": fwd.mean(),
"base_median_long": np.median(fwd),
"base_winrate_long": (fwd > 0).mean(),
"base_std": fwd.std(ddof=1),
}
)
return pd.DataFrame(rows)
def _tstat(x: np.ndarray) -> float:
if len(x) < 2:
return np.nan
sd = x.std(ddof=1)
return np.nan if sd == 0 else float(x.mean() / (sd / np.sqrt(len(x))))
def summarize(
fwd: pd.DataFrame, df: pd.DataFrame, horizons=DEFAULT_HORIZONS, by_type: bool = True
) -> pd.DataFrame:
"""汇总各类买卖点在各持有期的表现,并给出对基准的超额。"""
base = baseline_stats(df, horizons).set_index("horizon")
groups: list[tuple[str, pd.DataFrame]] = [("ALL", fwd)]
if by_type:
groups += [("ALL_LONG", fwd[fwd.direction == 1]), ("ALL_SHORT", fwd[fwd.direction == -1])]
groups += [(t, g) for t, g in fwd.groupby("bsp_type")]
rows = []
for name, g in groups:
if g.empty:
continue
for h in horizons:
r = g[f"ret_{h}"].dropna().to_numpy()
if len(r) == 0:
continue
# 基准需按方向调整:做空的无条件期望是多头期望的相反数
dirs = g.loc[g[f"ret_{h}"].notna(), "direction"].to_numpy()
base_mean = float(np.mean(dirs) * base.loc[h, "base_mean_long"])
rows.append(
{
"group": name,
"horizon": h,
"n": len(r),
"mean": r.mean(),
"median": np.median(r),
"winrate": (r > 0).mean(),
"excess": r.mean() - base_mean,
"tstat": _tstat(r),
"mfe": g[f"mfe_{h}"].dropna().mean(),
"mae": g[f"mae_{h}"].dropna().mean(),
}
)
return pd.DataFrame(rows)
def fmt_pct(x: float) -> str:
return "n/a" if pd.isna(x) else f"{x * 100:+.2f}%"
-108
View File
@@ -1,108 +0,0 @@
"""研究用数据层:本地历史数据优先,回落到远端数据服务。
远端服务的小周期只保留 90 派生自 1m做区间套时样本严重不足
本项目 data/ 下是完整下载的 BTC/ETH/SOL 全周期数据1m~1w2172~2543
是主力数据源旧的 freqtrade 目录作为备用
"""
from __future__ import annotations
from pathlib import Path
import pandas as pd
import requests
DATA_SERVICE_URL = "https://provider.jackyu66.com"
CACHE_DIR = Path(__file__).resolve().parents[1] / ".cache"
NUMERIC_COLS = ("open", "high", "low", "close", "volume")
# 按优先级查找,第一个命中的目录生效
LOCAL_DATA_DIRS = (
Path(__file__).resolve().parents[2] / "data",
Path("/Users/jack/Project/freqtrade/user_data/data"),
)
def _cache_path(symbol: str, tf: str, limit: int) -> Path:
safe = symbol.replace("/", "_").replace(":", "-")
return CACHE_DIR / f"{safe}__{tf}__{limit}.parquet"
def _normalize(raw: list[dict]) -> pd.DataFrame:
df = pd.DataFrame(raw)
df["timestamp"] = pd.to_numeric(df["timestamp"], errors="coerce")
for col in NUMERIC_COLS:
df[col] = pd.to_numeric(df[col], errors="coerce")
df = df.dropna(subset=["timestamp", *NUMERIC_COLS])
df = df.drop_duplicates(subset=["timestamp"]).sort_values("timestamp").reset_index(drop=True)
df["timestamp"] = df["timestamp"].astype("int64")
df["date"] = pd.to_datetime(df["timestamp"], unit="ms", utc=True).dt.tz_convert("Asia/Shanghai")
return df[["timestamp", "date", *NUMERIC_COLS]]
def _freqtrade_path(symbol: str, tf: str, exchange: str = "binance") -> Path | None:
"""定位 feather 文件。BTC/USDT:USDT -> BTC_USDT_USDT-1h-futures.feather"""
for root in LOCAL_DATA_DIRS:
base = root / exchange
if ":" in symbol: # 永续合约
name = symbol.replace("/", "_").replace(":", "_")
cand = base / "futures" / f"{name}-{tf}-futures.feather"
else:
name = symbol.replace("/", "_")
cand = base / f"{name}-{tf}.feather"
if cand.exists():
return cand
return None
def load_local(symbol: str, tf: str, exchange: str = "binance") -> pd.DataFrame | None:
"""读取 freqtrade 本地历史数据,转成与远端一致的列结构。"""
path = _freqtrade_path(symbol, tf, exchange)
if path is None:
return None
df = pd.read_feather(path)
df = df.rename(columns={c: c.lower() for c in df.columns})
if "date" not in df.columns:
return None
date = pd.to_datetime(df["date"], utc=True)
# 不能用 date.astype("int64")//10**6:该值的单位取决于列的精度
# datetime64[ns] 给纳秒、[ms] 给毫秒),对毫秒精度的文件会把时间戳砸平,
# 进而被 drop_duplicates 删掉九成数据。下面的写法与底层精度无关。
epoch_ms = (date - pd.Timestamp("1970-01-01", tz="UTC")) // pd.Timedelta("1ms")
out = pd.DataFrame({
"timestamp": epoch_ms.astype("int64"),
"date": date.dt.tz_convert("Asia/Shanghai"),
})
for col in NUMERIC_COLS:
out[col] = pd.to_numeric(df[col], errors="coerce") if col in df else 0.0
out = out.dropna(subset=list(NUMERIC_COLS))
return out.drop_duplicates(subset=["timestamp"]).sort_values("timestamp").reset_index(drop=True)
def fetch_ohlcv(
symbol: str,
tf: str,
limit: int = 5000,
refresh: bool = False,
prefer_local: bool = True,
) -> pd.DataFrame:
"""获取K线。优先本地 freqtrade 数据,其次本地缓存,最后回源数据服务。"""
if prefer_local and not refresh:
local = load_local(symbol, tf)
if local is not None and len(local) > 0:
return local.tail(limit).reset_index(drop=True) if limit and len(local) > limit else local
path = _cache_path(symbol, tf, limit)
if path.exists() and not refresh:
return pd.read_parquet(path)
resp = requests.get(
f"{DATA_SERVICE_URL}/api/candles",
params={"symbol": symbol, "tf": tf, "limit": limit},
timeout=60,
)
resp.raise_for_status()
df = _normalize(resp.json())
CACHE_DIR.mkdir(parents=True, exist_ok=True)
df.to_parquet(path, index=False)
return df
-141
View File
@@ -1,141 +0,0 @@
"""快速三类买卖点:不等笔确认,突破回抽当根即入场。
引擎的 B3/S3 要等 pullback_bi.sure_time回拉笔被确认滞后 9~10
此时价格已从回抽低点反弹完毕入场价被吃掉
但三买的形态条件本身是实时可判的
中枢已成 -> 收盘突破 zg -> 回抽最低不跌回中枢(low >= zg) -> 重新上行
最后一步发生的当根就能下单滞后 1~2
全部判定只使用当根及之前的数据无未来函数
"""
from __future__ import annotations
import numpy as np
import pandas as pd
def find_fast_bsp3(
df: pd.DataFrame,
zones: pd.DataFrame,
scan: int = 200,
pullback_win: int = 30,
tol: float = 0.003,
max_per_zone: int = 1,
diag: dict | None = None,
) -> pd.DataFrame:
"""扫描每个中枢,找突破后回抽不回中枢的入场点。
zones 需含 zg / zd / available_ts available_ts 已是可用时刻
max_per_zone > 1 同一中枢在首次入场后继续往后找二次三次突破回抽
用来检验趋势里同一中枢反复给机会是否值得做
返回列
entry_idx 实时可下单的K线
direction +1 三买 / -1 三卖
bo_idx 突破根
pb_idx 回抽极值根
lag entry_idx - bo_idx
depth 回抽深度相对中枢边界负值表示曾插入中枢
occ 这是该中枢的第几次入场
"""
if zones.empty:
return pd.DataFrame()
ts = df["timestamp"].to_numpy()
close = df["close"].to_numpy(dtype=float)
high = df["high"].to_numpy(dtype=float)
low = df["low"].to_numpy(dtype=float)
n = len(df)
rows = []
def note(key: str) -> None:
if diag is not None:
diag[key] = diag.get(key, 0) + 1
for zone_i, (_, z) in enumerate(zones.iterrows()):
note("中枢总数")
zg, zd = float(z["zg"]), float(z["zd"])
if zg <= zd:
note("×无效中枢")
continue
start = int(np.searchsorted(ts, z["available_ts"], side="left"))
if start >= n - 2:
note("×中枢太靠后")
continue
# 允许多次入场时按比例放宽扫描窗口,否则后几次机会会被窗口截断
scan_end = min(start + scan * max_per_zone, n)
cursor = start
for occ in range(1, max_per_zone + 1):
if cursor >= n - 2:
break
# 第一步:找突破。要求突破前确实待在中枢内,避免把远处的价格当突破。
was_inside = False
bo_idx, d = None, 0
for j in range(cursor, scan_end):
c = close[j]
if zd <= c <= zg:
was_inside = True
continue
if not was_inside:
continue
bo_idx, d = j, (1 if c > zg else -1)
break
if bo_idx is None:
if occ == 1:
note("×窗口内未突破")
break
edge = zg if d == 1 else zd
# 第二步:突破后监控回抽,回抽不跌回中枢且重新顺势 -> 入场
touched = False
pb_idx = None
pb_ext = None
entry_idx = None
fell_back = False
for j in range(bo_idx + 1, min(bo_idx + pullback_win + 1, n)):
# 收盘跌回中枢 -> 突破失效
if zd <= close[j] <= zg:
fell_back = True
break
# 回抽触及边界附近(允许 tol 的毛刺)
near = (low[j] <= edge * (1 + tol)) if d == 1 else (high[j] >= edge * (1 - tol))
if near:
touched = True
ext = low[j] if d == 1 else high[j]
if pb_ext is None or ((ext < pb_ext) if d == 1 else (ext > pb_ext)):
pb_ext, pb_idx = ext, j
continue
# 回抽后重新顺势:收盘创出前一根之上(三买)/ 之下(三卖)
if touched and pb_idx is not None:
go = close[j] > high[j - 1] if d == 1 else close[j] < low[j - 1]
if go:
entry_idx = j
break
if entry_idx is None or pb_ext is None:
if occ == 1:
note("×突破后跌回中枢" if fell_back
else "×回抽未触及边界" if not touched
else "×触及边界但未转强")
# 这次突破没走成,从突破点之后继续找下一次
cursor = bo_idx + 1
continue
if occ == 1:
note("√成交")
# 回抽深度:>0 表示未插入中枢,越大表示回抽越浅
depth = (pb_ext - zg) / zg if d == 1 else (zd - pb_ext) / zd
rows.append({
"entry_idx": entry_idx, "direction": d,
"bo_idx": bo_idx, "pb_idx": pb_idx,
"lag": entry_idx - bo_idx,
"depth": depth,
"zg": zg, "zd": zd,
"width_pct": (zg - zd) / close[bo_idx],
"occ": occ,
"zone_i": zone_i,
})
cursor = entry_idx + 1
return pd.DataFrame(rows)
-159
View File
@@ -1,159 +0,0 @@
"""分型级信号:绕开笔/线段/中枢的确认链,直接用分型 + 背驰做预设转折点。
动机笔确认滞后 9~10 线段 101~121 买卖点 16~21 全部超过 alpha 半衰期4~6
而分型只需右侧 KLC 完成即可确认滞后通常 1~3 是唯一来得及的结构
"""
from __future__ import annotations
import sys
from dataclasses import dataclass
from pathlib import Path
import numpy as np
import pandas as pd
sys.path.insert(0, str(Path(__file__).resolve().parents[2]))
from chanlun import TF_DF
from chanlun.core.ChanEnum import Chan_FX_TYPE
@dataclass
class FxSignal:
"""一个分型转折信号。confirm_idx 是实时可交易时刻。"""
direction: int # +1 底分型(潜在买) / -1 顶分型(潜在卖)
fx_idx: int # 分型极值所在K线
confirm_idx: int # 右侧KLC完成、分型可被确认的K线
lag: int # confirm_idx - fx_idx
price: float # 分型极值价
confirm_price: float # 确认时刻收盘价
seg_macd_area: float # 本段(前一反向分型 -> 本分型)的 MACD 面积
prev_seg_macd_area: float # 前一个同向段的 MACD 面积
prev_extreme: float # 前一个同向分型的极值价
is_divergence: bool # 是否背驰:价格创新极值但力度衰减
ratio: float # 面积比 本段/前段,越小背驰越强
def extract_fx_signals(chan: TF_DF, df: pd.DataFrame) -> list[FxSignal]:
"""从已建好的缠论结构里抽取分型信号,并就地算好背驰。"""
idx_of = {t: i for i, t in enumerate(df["date"].dt.strftime("%Y-%m-%d %H:%M:%S"))}
n = len(df)
closes = df["close"].to_numpy(dtype=float)
macdhist = (
df["macdhist"].to_numpy(dtype=float)
if "macdhist" in df.columns
else np.zeros(n)
)
if "macdhist" not in df.columns and hasattr(chan, "dataframe"):
if "macdhist" in chan.dataframe.columns:
macdhist = chan.dataframe["macdhist"].to_numpy(dtype=float)
# 按时间收集已成型的分型
raw = []
for klc in chan.klc_list:
if klc.fx not in (Chan_FX_TYPE.TOP, Chan_FX_TYPE.BOTTOM):
continue
if klc.next is None or klc.next.end_klu is None:
continue
e_key = str(klc.end_time)
c_key = str(klc.next.end_klu.time)
if e_key not in idx_of or c_key not in idx_of:
continue
fx_idx = idx_of[e_key]
confirm_idx = idx_of[c_key]
if confirm_idx <= fx_idx:
continue
d = 1 if klc.fx == Chan_FX_TYPE.BOTTOM else -1
raw.append({
"d": d,
"fx_idx": fx_idx,
"confirm_idx": confirm_idx,
"price": float(klc.low if d == 1 else klc.high),
})
raw.sort(key=lambda r: r["fx_idx"])
def area(lo: int, hi: int, sign: int) -> float:
"""区间内顺方向的 MACD 柱面积。sign=-1 取负柱(下跌段),+1 取正柱。"""
if hi <= lo:
return 0.0
seg = macdhist[lo : hi + 1]
vals = seg[seg < 0] if sign < 0 else seg[seg > 0]
return float(np.abs(vals).sum())
signals: list[FxSignal] = []
for i, r in enumerate(raw):
d = r["d"]
# 本段起点 = 上一个反向分型;前一同向段 = 再往前一组
prev_opp = None
prev_same = None
prev_opp2 = None
for j in range(i - 1, -1, -1):
if prev_opp is None and raw[j]["d"] == -d:
prev_opp = raw[j]
continue
if prev_opp is not None and prev_same is None and raw[j]["d"] == d:
prev_same = raw[j]
continue
if prev_same is not None and prev_opp2 is None and raw[j]["d"] == -d:
prev_opp2 = raw[j]
break
if prev_opp is None:
continue
sign = -1 if d == 1 else 1 # 底分型前是下跌段,取负柱
cur_area = area(prev_opp["fx_idx"], r["fx_idx"], sign)
prev_area = (
area(prev_opp2["fx_idx"], prev_same["fx_idx"], sign)
if (prev_same is not None and prev_opp2 is not None)
else 0.0
)
# 背驰:价格创新极值(底更低 / 顶更高)但力度反而衰减
new_extreme = False
prev_extreme = np.nan
if prev_same is not None:
prev_extreme = prev_same["price"]
new_extreme = (
r["price"] <= prev_extreme if d == 1 else r["price"] >= prev_extreme
)
ratio = cur_area / prev_area if prev_area > 0 else np.nan
is_div = bool(new_extreme and prev_area > 0 and cur_area < prev_area)
signals.append(
FxSignal(
direction=d,
fx_idx=r["fx_idx"],
confirm_idx=r["confirm_idx"],
lag=r["confirm_idx"] - r["fx_idx"],
price=r["price"],
confirm_price=float(closes[r["confirm_idx"]]),
seg_macd_area=cur_area,
prev_seg_macd_area=prev_area,
prev_extreme=float(prev_extreme) if prev_extreme == prev_extreme else np.nan,
is_divergence=is_div,
ratio=float(ratio) if ratio == ratio else np.nan,
)
)
return signals
def signals_to_frame(signals: list[FxSignal]) -> pd.DataFrame:
return pd.DataFrame([s.__dict__ for s in signals])
def add_forward_returns(
sig: pd.DataFrame, df: pd.DataFrame, horizons=(3, 5, 10, 20, 40)
) -> pd.DataFrame:
"""以 confirm_idx 收盘价入场的方向调整收益。"""
closes = df["close"].to_numpy(dtype=float)
n = len(df)
out = sig.copy()
for h in horizons:
vals = []
for i, d in zip(out["confirm_idx"], out["direction"]):
j = int(i) + h
vals.append(d * (closes[j] - closes[int(i)]) / closes[int(i)] if j < n else np.nan)
out[f"ret_{h}"] = vals
return out
-88
View File
@@ -1,88 +0,0 @@
"""真正的区间套:大级别分型定位 + 小级别三类买卖点入场。
结构缠论正统
1. 大级别1h / 4h出顶底分型 确认滞后仅 1 负责预设转折点与方向
2. 小级别15m在该位置形成中枢并被突破
3. 突破后回抽不回中枢 -> 15m 的第三类买卖点 负责精确入场
关键中枢与三买都在小级别大级别只出分型
"""
from __future__ import annotations
import sys
from pathlib import Path
import numpy as np
import pandas as pd
sys.path.insert(0, str(Path(__file__).resolve().parents[2]))
def htf_fx_timeline(sig_htf: pd.DataFrame, df_htf: pd.DataFrame) -> pd.DataFrame:
"""把大级别分型压成一条按确认时间排序的时间线。
confirm_ts 是该分型最早可被使用的时刻用它做对齐可避免未来函数
注意 timestamp 是K线开盘时刻而分型要等这根K线收盘才算数
所以整体后移一个大级别周期否则小级别会提前一整根大级别K线拿到信号
"""
ts = df_htf["timestamp"].to_numpy()
period = int(np.median(np.diff(ts))) if len(ts) > 1 else 0
out = pd.DataFrame({
"confirm_ts": ts[sig_htf["confirm_idx"].to_numpy().astype(int)] + period,
"fx_ts": ts[sig_htf["fx_idx"].to_numpy().astype(int)],
"direction": sig_htf["direction"].to_numpy(),
"price": sig_htf["price"].to_numpy(),
"is_divergence": sig_htf["is_divergence"].to_numpy(),
"ratio": sig_htf["ratio"].to_numpy(),
})
return out.sort_values("confirm_ts").reset_index(drop=True)
def attach_htf_context(
ev: pd.DataFrame, df_ltf: pd.DataFrame, tl: pd.DataFrame, prefix: str
) -> pd.DataFrame:
"""给每个小级别买卖点挂上「入场时刻之前最近的大级别分型」。
ev 需含 entry_idx产出列
{prefix}_dir 最近大级别分型方向+1 / -1
{prefix}_age_bars 距该分型确认过了多少根小级别K线
{prefix}_agree 大级别分型方向与本信号方向是否一致
{prefix}_div 该大级别分型是否背驰
{prefix}_dist 入场价相对该分型极值的距离相对值
"""
out = ev.copy()
if tl.empty or ev.empty:
for c in ("dir", "age_bars", "agree", "div", "dist"):
out[f"{prefix}_{c}"] = np.nan
return out
ts_ltf = df_ltf["timestamp"].to_numpy()
close = df_ltf["close"].to_numpy(dtype=float)
entry_idx = ev["entry_idx"].to_numpy().astype(int)
entry_ts = ts_ltf[entry_idx]
k = np.searchsorted(tl["confirm_ts"].to_numpy(), entry_ts, side="right") - 1
valid = k >= 0
k_safe = np.clip(k, 0, len(tl) - 1)
fx_dir = tl["direction"].to_numpy()[k_safe].astype(float)
fx_ts = tl["confirm_ts"].to_numpy()[k_safe]
fx_px = tl["price"].to_numpy()[k_safe].astype(float)
fx_div = tl["is_divergence"].to_numpy()[k_safe].astype(float)
# 用小级别K线间隔把时间差换算成根数
step = np.median(np.diff(ts_ltf)) if len(ts_ltf) > 1 else 1
age = (entry_ts - fx_ts) / max(step, 1)
out[f"{prefix}_dir"] = np.where(valid, fx_dir, np.nan)
out[f"{prefix}_age_bars"] = np.where(valid, age, np.nan)
# 分型确认时刻当作该分型的唯一标识,用来判断多个小级别信号是否同源
out[f"{prefix}_fx_ts"] = np.where(valid, fx_ts, np.nan)
out[f"{prefix}_div"] = np.where(valid, fx_div, np.nan)
out[f"{prefix}_dist"] = np.where(
valid, (close[entry_idx] - fx_px) / np.clip(np.abs(fx_px), 1e-9, None), np.nan
)
out[f"{prefix}_agree"] = np.where(
valid, (fx_dir == out["direction"].to_numpy()).astype(float), np.nan
)
return out
-106
View File
@@ -1,106 +0,0 @@
"""区间套:用大级别中枢边界给小级别信号定位。
思路缠论正统做法
大级别中枢的 zg/zd 是支撑压力位 -> 小级别在这些位置附近出现的分型+背驰
才是高质量的预设转折点位置本身就是过滤器不需要等笔/中枢确认
严格性只使用在信号时刻之前就已经确认sure_time 已过的大级别中枢
避免用到当时尚不可知的结构
"""
from __future__ import annotations
import sys
from pathlib import Path
import numpy as np
import pandas as pd
sys.path.insert(0, str(Path(__file__).resolve().parents[2]))
from chanlun import TF_DF
def build_htf_zones(df_htf: pd.DataFrame, tf: str, chan: TF_DF | None = None) -> pd.DataFrame:
"""算 pure 笔中枢,返回带生效时间的区间表。
available_ts 该中枢最早可被使用的时间戳其确认时刻
传入已构建好的 chan 可避免重复跑一遍 pipeline大数据集上省一半时间
"""
if chan is None:
chan = TF_DF(df_htf, 1, tf)
zs_list = chan.cal_bi_zs_list_pure(chan.bi_list)
# 用引擎自己的 dataframe 对齐,避免调用方传入的 df 与引擎内部行数不一致
src = chan.dataframe if getattr(chan, "dataframe", None) is not None else df_htf
ts_of = dict(zip(src["date"].dt.strftime("%Y-%m-%d %H:%M:%S"), src["timestamp"]))
rows = []
for zs in zs_list:
bis = getattr(zs, "bi_list", [])
if not bis:
continue
# 中枢可用时刻:构成它的最后一笔被确认之时
last_bi = bis[-1]
sure_key = str(getattr(last_bi, "sure_time", "") or "")
end_key = str(getattr(last_bi, "end_time", "") or "")
avail = ts_of.get(sure_key) or ts_of.get(end_key)
if avail is None:
continue
start_key = str(bis[0].start_time)
rows.append({
"zg": float(zs.zg), "zd": float(zs.zd),
"gg": float(getattr(zs, "gg", zs.zg)), "dd": float(getattr(zs, "dd", zs.zd)),
"start_ts": ts_of.get(start_key, avail),
"available_ts": int(avail),
})
out = pd.DataFrame(rows)
return out.sort_values("available_ts").reset_index(drop=True) if not out.empty else out
def annotate_position(
sig: pd.DataFrame, df_ltf: pd.DataFrame, zones: pd.DataFrame, tol: float = 0.01
) -> pd.DataFrame:
"""给每个小级别信号标注它相对大级别中枢的位置。
tol 判定"贴近"边界的相对距离阈值默认 1%
"""
if zones.empty:
out = sig.copy()
for c in ("near_support", "near_resistance", "inside_zone", "outside_zone", "zone_pos"):
out[c] = False if c != "zone_pos" else np.nan
return out
ts = df_ltf["timestamp"].to_numpy()
zone_avail = zones["available_ts"].to_numpy()
zg = zones["zg"].to_numpy()
zd = zones["zd"].to_numpy()
near_sup, near_res, inside, outside, zpos = [], [], [], [], []
for _, r in sig.iterrows():
i = int(r["confirm_idx"])
now_ts = ts[i]
price = float(r["price"])
# 最近一个在此刻之前已可用的大级别中枢
k = np.searchsorted(zone_avail, now_ts, side="right") - 1
if k < 0:
near_sup.append(False); near_res.append(False)
inside.append(False); outside.append(False); zpos.append(np.nan)
continue
z_g, z_d = zg[k], zd[k]
width = z_g - z_d
near_sup.append(abs(price - z_d) / price <= tol)
near_res.append(abs(price - z_g) / price <= tol)
inside.append(z_d <= price <= z_g)
outside.append(price > z_g or price < z_d)
zpos.append((price - z_d) / width if width > 0 else np.nan)
out = sig.copy()
out["near_support"] = near_sup
out["near_resistance"] = near_res
out["inside_zone"] = inside
out["outside_zone"] = outside
out["zone_pos"] = zpos # 0=中枢下沿, 1=中枢上沿
# 顺位:买信号贴支撑 / 卖信号贴压力,才算"位置正确"
out["position_ok"] = np.where(
out["direction"] == 1, out["near_support"], out["near_resistance"]
)
return out
-185
View File
@@ -1,185 +0,0 @@
"""Walk-forward 重放:用滑动窗口逐步重算缠论,检验买卖点在实时环境下是否稳定。
要回答三个问题
1. 幻影率实时曾报出但在完整历史上并不存在的信号占多少
2. 撤销率报出后又被结构演化抹掉的信号占多少
3. 真实滞后信号第一次可被观测到的时刻 sure_time 晚多少
"""
from __future__ import annotations
import os
import pickle
import sys
from concurrent.futures import ProcessPoolExecutor
from dataclasses import dataclass
from pathlib import Path
import pandas as pd
sys.path.insert(0, str(Path(__file__).resolve().parents[2]))
from chanlun.core.ChanEnum import Chan_BSP_DIR
# 子进程共享的只读数据,避免每次任务重复 pickle 整个 DataFrame
_G: dict = {}
BspKey = tuple[str, int, str] # (类型, 方向, 分型时间)
def _init_worker(df: pd.DataFrame, tf: str, zs_source: str) -> None:
_G["df"] = df
_G["tf"] = tf
_G["zs_source"] = zs_source
def _bsp_keys_for_window(args: tuple[int, int]) -> tuple[int, list[BspKey]]:
"""在 df[start:end] 这个窗口上重算缠论,返回该时点可观测到的买卖点集合。"""
from chanlun import TF_DF # 延迟导入,避免父进程重复加载
start, end = args
df = _G["df"].iloc[start:end].reset_index(drop=True)
try:
chan = TF_DF(df, 1, _G["tf"])
if _G["zs_source"] == "pure":
bi_zs_list = chan.cal_bi_zs_list_pure(chan.bi_list)
else:
bi_zs_list = chan.cal_bi_zs(chan.seg_list)
bsp_list = chan.find_all_bsp(chan.bi_list, bi_zs_list) if bi_zs_list else []
except Exception:
return end - 1, []
keys = [
(
str(b.type).replace("Chan_BSP_TYPE.", ""),
1 if b.dir == Chan_BSP_DIR.BUY else -1,
str(b.end_time),
)
for b in bsp_list
if b.is_sure and b.sure_time is not None
]
return end - 1, keys
@dataclass
class ReplayResult:
observations: dict[int, set[BspKey]] # 每个重算时点 -> 当时可见的买卖点集合
checkpoints: list[int]
window: int
step: int
def replay(
df: pd.DataFrame,
tf: str,
window: int = 3000,
step: int = 6,
workers: int | None = None,
cache_key: str | None = None,
zs_source: str = "seg",
) -> ReplayResult:
"""滑动窗口重放。窗口右端每 step 根前进一次,每次完整重算一遍缠论。
重放很贵分钟级cache_key 非空时把结果落盘复用
"""
n = len(df)
if n <= window:
raise ValueError(f"数据长度 {n} 不足以支撑窗口 {window}")
cache_path = None
if cache_key:
cache_dir = Path(__file__).resolve().parents[1] / ".cache"
cache_dir.mkdir(parents=True, exist_ok=True)
cache_path = cache_dir / f"replay__{cache_key}__{zs_source}__w{window}_s{step}_n{n}.pkl"
if cache_path.exists():
with cache_path.open("rb") as fh:
obs = pickle.load(fh)
print(f" [cache] 命中重放缓存 {cache_path.name}")
return ReplayResult(observations=obs, checkpoints=sorted(obs), window=window, step=step)
tasks = [(end - window, end) for end in range(window, n + 1, step)]
workers = workers or max(1, (os.cpu_count() or 4) - 1)
observations: dict[int, set[BspKey]] = {}
with ProcessPoolExecutor(
max_workers=workers, initializer=_init_worker, initargs=(df, tf, zs_source)
) as pool:
for i, (bar_idx, keys) in enumerate(pool.map(_bsp_keys_for_window, tasks, chunksize=8)):
observations[bar_idx] = set(keys)
if (i + 1) % 500 == 0:
print(f" ...{i + 1}/{len(tasks)} 窗口", flush=True)
if cache_path is not None:
with cache_path.open("wb") as fh:
pickle.dump(observations, fh)
return ReplayResult(observations=observations, checkpoints=sorted(observations), window=window, step=step)
def analyze_stability(
result: ReplayResult,
final_keys: set[BspKey],
df: pd.DataFrame,
window: int,
maturity_bars: int = 300,
edge_buffer: int = 500,
) -> pd.DataFrame:
"""把重放结果整理成每个信号的生命周期:首次出现、最后可见、是否被撤销。
两处偏差必须剔除否则统计会失真
- 左截断分型早于第一个窗口起点的信号 first_seen 是假的标记 truncated
- 右截断临近重放末尾才出现的信号还没经历足够演化谈不上"没被撤销"标记 immature
"""
checkpoints = result.checkpoints
times = df["date"].dt.strftime("%Y-%m-%d %H:%M:%S").to_numpy()
idx_of = {t: i for i, t in enumerate(times)}
first_seen: dict[BspKey, int] = {}
last_seen: dict[BspKey, int] = {}
seen_count: dict[BspKey, int] = {}
for bar_idx in checkpoints:
for key in result.observations[bar_idx]:
first_seen.setdefault(key, bar_idx)
last_seen[key] = bar_idx
seen_count[key] = seen_count.get(key, 0) + 1
last_cp = checkpoints[-1]
rows = []
for key, first in first_seen.items():
bsp_type, direction, fx_time = key
fx_idx = idx_of.get(fx_time)
# 窗口是滑动的,信号的分型一旦滑出窗口左端就必然不可见。
# 只在「信号仍被窗口覆盖」的检查点上评价持续性,否则会把正常的滑出误判成撤销。
# 另需 edge_buffer:贴着窗口左端时缠论缺少前置K线构造包含关系,信号会因边界效应消失。
if fx_idx is None:
in_scope = [cp for cp in checkpoints if cp >= first]
else:
in_scope = [
cp for cp in checkpoints
if cp >= first and (cp - window) < (fx_idx - edge_buffer)
]
scope_end = in_scope[-1] if in_scope else first
visible_in_scope = sum(1 for cp in in_scope if key in result.observations[cp])
rows.append(
{
"bsp_type": bsp_type,
"direction": direction,
"fx_time": fx_time,
"fx_idx": fx_idx,
"first_seen_idx": first,
"first_seen_time": times[first],
"last_seen_idx": last_seen[key],
"observed_lag": (first - fx_idx) if fx_idx is not None else None,
"scope_checkpoints": len(in_scope),
"persist_ratio": visible_in_scope / len(in_scope) if in_scope else 0.0,
"in_final": key in final_keys,
# 在「仍被窗口覆盖」的最后一个检查点上是否还活着
"alive_at_scope_end": key in result.observations.get(scope_end, set()),
# 分型发生在首个窗口完全覆盖之后,first_seen 才是真实的
"truncated": fx_idx is None or fx_idx < window,
# 覆盖范围太短则谈不上"没被撤销"
"immature": len(in_scope) < maturity_bars // max(result.step, 1),
}
)
return pd.DataFrame(rows).sort_values("first_seen_idx").reset_index(drop=True)
-676
View File
@@ -1,676 +0,0 @@
zs_start_ts,zg,zd,width_pct,bo_idx,dir,is_fake,conf_idx,tf
1672643700000.0,16730.7,16703.9,0.0016052229642717663,294,-1,True,,15m
1673656200000.0,21240.0,20670.7,0.02671616015617664,1890,1,False,1892.0,15m
1674258300000.0,22812.4,22600.2,0.0092994719197143,2298,1,True,,15m
1674565200000.0,23011.9,22864.0,0.006363589425857147,2390,1,True,,15m
1674609300000.0,22766.7,22327.2,0.0192770767267129,2506,1,True,,15m
1674683100000.0,23189.3,23028.8,0.006920221274614213,2823,1,True,,15m
1674997200000.0,23675.8,23568.0,0.004549041451979731,3060,1,True,,15m
1675411200000.0,23465.4,23328.8,0.0058589645159492585,3410,-1,False,3412.0,15m
1675982700000.0,21917.8,21697.2,0.010168241530306455,4229,-1,True,,15m
1676506500000.0,24934.1,24312.0,0.0255994535271775,4583,-1,True,,15m
1676592000000.0,23924.7,23520.0,0.016862640522004382,4805,1,False,,15m
1676664900000.0,24799.2,24502.6,0.012131524375529258,4960,-1,True,4963.0,15m
1676997000000.0,24468.5,24267.2,0.00830496936691624,5128,-1,True,,15m
1677153600000.0,24061.6,23711.5,0.014774583159253995,5527,-1,True,,15m
1677265200000.0,23147.6,23010.1,0.0059242727147387295,5656,1,True,,15m
1677807900000.0,22437.9,22300.0,0.006184549837425786,6298,-1,True,,15m
1678316400000.0,21798.0,21550.0,0.01152459199226737,6498,-1,False,6502.0,15m
1678668300000.0,22542.0,22105.0,0.019077887549604693,6872,1,False,,15m
1678770000000.0,24850.0,24147.2,0.028035854618855157,7190,1,False,7198.0,15m
1679093100000.0,27600.0,27050.0,0.019922122611609163,7618,1,False,7624.0,15m
1679362200000.0,27918.8,27712.0,0.007405232362440979,7852,1,True,,15m
1679463000000.0,28280.0,27962.1,0.011218350242611433,7836,1,True,7838.0,15m
1679515200000.0,27421.8,27101.0,0.011689598880596988,7965,1,True,,15m
1679584500000.0,28439.0,28078.4,0.012854790066947524,8120,-1,False,8122.0,15m
1679693400000.0,27648.4,27302.0,0.012861545316154957,8219,-1,False,8226.0,15m
1679872500000.0,27929.2,27757.4,0.006151069992588615,8524,1,True,8531.0,15m
1680078600000.0,28649.9,28119.5,0.018879274443570456,9336,-1,False,9351.0,15m
1681178400000.0,30158.0,29983.8,0.0057700652196235455,9833,1,True,,15m
1681490700000.0,30464.0,30270.0,0.006410192867503957,10173,-1,True,,15m
1681692300000.0,29913.6,29781.6,0.0044362292051756,10221,-1,False,10224.0,15m
1681745400000.0,29524.0,29370.0,0.005263049971634211,10401,-1,False,10407.0,15m
1682019000000.0,28225.8,27983.0,0.008681319074230974,10625,-1,True,,15m
1682116200000.0,27387.8,27166.0,0.008089310657976771,10814,1,False,10818.0,15m
1682184600000.0,27780.0,27512.0,0.009754712654555778,11020,-1,True,,15m
1682512200000.0,29899.0,29504.8,0.013419300371737114,11227,-1,True,,15m
1682539200000.0,29272.6,28858.7,0.014128305081274375,11505,1,True,11509.0,15m
1683053100000.0,28820.0,28600.6,0.007682261119845425,11783,-1,True,,15m
1683535500000.0,27747.9,27550.0,0.00718768613891598,12481,-1,False,12495.0,15m
1683940500000.0,26974.1,26674.9,0.011091381566509257,12869,1,False,12871.0,15m
1684131300000.0,27550.0,27200.0,0.012876262793486819,12994,-1,False,12999.0,15m
1684199700000.0,27149.8,26971.0,0.00663564096283593,13080,-1,True,,15m
1684309500000.0,26907.5,26700.0,0.007837523418142261,13223,-1,True,13229.0,15m
1684358100000.0,27471.5,27139.0,0.012262404390125168,13459,-1,False,13466.0,15m
1684431900000.0,26950.0,26733.4,0.008115794324938964,13525,-1,True,,15m
1684604700000.0,27144.4,26952.8,0.007130257448439686,13592,-1,False,13597.0,15m
1685246400000.0,27236.0,27127.8,0.003968588730235025,14175,1,False,14184.0,15m
1685512800000.0,27194.9,26942.0,0.009394921021739508,14891,-1,False,14894.0,15m
1685991600000.0,25809.9,25585.4,0.008802333696666484,15383,-1,True,,15m
1686153600000.0,26459.5,26220.0,0.009269903198213367,15377,-1,False,,15m
1686375900000.0,25743.9,25559.5,0.007223723900184176,15825,-1,False,15827.0,15m
1686623400000.0,26155.5,25963.7,0.007388375058263357,15748,-1,True,,15m
1686671100000.0,25921.1,25790.6,0.005063261671691129,16002,-1,False,,15m
1686938400000.0,26526.9,26364.3,0.006109566393627496,16300,1,False,16303.0,15m
1687365000000.0,30488.0,29800.0,0.022554419092578024,16771,1,True,16776.0,15m
1687576500000.0,30765.9,30542.4,0.0073281812011659515,17033,-1,True,,15m
1687799700000.0,30488.0,30222.5,0.008793458064644337,17118,-1,True,,15m
1687874400000.0,30738.8,30533.1,0.006738054448196931,17242,-1,True,,15m
1687939200000.0,30347.7,30126.6,0.007279156131479212,17266,1,False,17270.0,15m
1688044500000.0,30614.8,30380.0,0.0077316956715016965,17808,-1,True,,15m
1688412600000.0,31319.4,30875.2,0.014180140779875844,17891,1,True,,15m
1688562900000.0,30438.1,30222.7,0.007144302302826803,18698,-1,True,18702.0,15m
1689361200000.0,30360.0,30241.0,0.00393525025215364,19236,-1,True,,15m
1690210800000.0,29159.7,29033.0,0.0043434887093290984,19952,1,True,,15m
1690403400000.0,29485.9,29366.0,0.004087741548364271,20032,-1,True,,15m
1690482600000.0,29299.9,29112.8,0.006379569012547811,20316,1,True,,15m
1690764300000.0,29461.4,29317.4,0.004925788211631017,20605,-1,True,,15m
1690992900000.0,29229.1,29120.7,0.003727981181260973,20919,-1,True,,15m
1691611200000.0,29650.0,29461.4,0.006402444199270086,21474,-1,False,,15m
1692307800000.0,26597.7,26150.0,0.017128122333893204,22144,-1,True,,15m
1692428400000.0,25955.0,25858.0,0.0037324919193473913,22318,1,True,22320.0,15m
1692463500000.0,26153.4,26041.4,0.004277241637419754,22528,1,False,22530.0,15m
1692898200000.0,26200.0,25960.0,0.009144424758530034,23310,1,True,,15m
1693515600000.0,26094.1,25941.7,0.005887943624098758,23571,-1,False,23585.0,15m
1693678500000.0,25899.9,25835.5,0.002496888582162812,23821,-1,False,23823.0,15m
1693882800000.0,25830.0,25610.0,0.008507182769087991,24206,1,True,,15m
1694276100000.0,25908.5,25834.3,0.0028800546511718456,24398,-1,True,,15m
1694491200000.0,25989.6,25868.0,0.0047012066172575475,24511,-1,True,,15m
1694622600000.0,26294.0,26150.0,0.005473224907734351,24734,1,False,24749.0,15m
1694715300000.0,26647.0,26523.2,0.00462956037874139,25292,1,True,25295.0,15m
1695303900000.0,26685.7,26484.0,0.007538552388640995,25871,1,True,,15m
1696869000000.0,27695.1,27482.0,0.007773995965255912,27123,-1,True,,15m
1697482800000.0,28548.0,28220.7,0.011459121082261969,27992,1,True,,15m
1697796900000.0,29784.5,29443.5,0.011431559821251974,28167,1,True,,15m
1697910300000.0,30206.0,29828.5,0.012415598596297357,28325,1,True,,15m
1698100200000.0,35154.3,34203.5,0.027815402618298922,28847,-1,False,28854.0,15m
1698392700000.0,34185.3,33978.6,0.0060332925665283425,29061,1,False,29063.0,15m
1698591600000.0,34579.7,34384.6,0.005624942337854003,29261,1,True,,15m
1698893100000.0,35487.0,35050.0,0.012501001224354354,29568,-1,True,,15m
1698974100000.0,34706.0,34364.1,0.009832595672967738,29657,1,False,29659.0,15m
1699538400000.0,36798.0,36461.8,0.009119489178405014,30386,1,True,,15m
1699633800000.0,37422.2,37080.0,0.009137492289740137,30604,1,True,,15m
1699856100000.0,37065.9,36760.0,0.008332425365003308,30680,-1,True,,15m
1700091900000.0,37645.9,37351.0,0.007895582329317308,31005,-1,True,,15m
1700163000000.0,36465.4,36143.8,0.008819028919614074,31165,1,False,31168.0,15m
1700435700000.0,37445.5,37050.0,0.010554096750779215,31282,1,True,31285.0,15m
1700688600000.0,37650.0,37250.2,0.010609868398355786,31485,1,False,31490.0,15m
1700860500000.0,37840.0,37708.1,0.0034830876158939037,31843,1,False,31846.0,15m
1701199800000.0,38091.9,37862.3,0.006025155549257445,32080,1,True,,15m
1701686700000.0,42179.0,41630.0,0.013191375867711404,32491,-1,True,,15m
1702260000000.0,41963.0,40272.1,0.040248503385977685,33473,1,True,,15m
1702504800000.0,43379.3,42670.5,0.016632055659193103,33571,-1,False,33574.0,15m
1702655100000.0,42280.0,41668.0,0.014690138884221543,33690,-1,True,,15m
1702951200000.0,43297.1,42840.2,0.010702113954441681,33908,-1,True,,15m
1703085300000.0,44290.0,43511.8,0.017892123051455305,34443,-1,True,,15m
1703456100000.0,43360.4,43083.2,0.006479133310739055,34486,-1,False,,15m
1703583900000.0,42630.4,42109.0,0.012226721976911367,34732,1,True,,15m
1703673900000.0,43277.1,42821.0,0.010674923875926503,34795,-1,True,,15m
1703780100000.0,42788.7,42320.5,0.01106371917719194,34916,-1,True,,15m
1703871900000.0,42250.0,41680.0,0.013482667675895591,35038,1,False,35042.0,15m
1704015000000.0,42766.7,42381.0,0.009013809333510876,35090,1,True,,15m
1704499200000.0,44149.9,43712.1,0.009910897006356804,35756,1,False,35760.0,15m
1704740400000.0,46980.0,46304.1,0.014605843201659639,35976,-1,True,,15m
1704836700000.0,46228.4,45266.3,0.02128963731716489,36156,-1,False,36158.0,15m
1705609800000.0,41372.0,40739.6,0.015277797909811744,37062,1,True,,15m
1705899600000.0,41253.7,40666.0,0.014496435707061914,37120,-1,True,,15m
1706299200000.0,41886.0,41681.8,0.004871857270328364,37614,1,False,37619.0,15m
1706418900000.0,42688.0,42259.5,0.01002742623932904,37961,1,True,,15m
1706580900000.0,43518.0,43200.4,0.007294374879422296,37989,1,True,,15m
1707502500000.0,47489.9,47020.1,0.009875266430400242,38961,1,False,38965.0,15m
1707758100000.0,49783.5,49320.9,0.009279318313113778,39293,1,True,,15m
1707921000000.0,52100.0,51651.0,0.008705886458953392,40034,-1,True,40039.0,15m
1708515900000.0,51459.5,50884.3,0.011174530152969781,40389,1,False,40395.0,15m
1708827300000.0,51853.2,51550.0,0.005833136137932836,40476,1,False,,15m
1708999200000.0,57679.5,56625.3,0.018208134417553822,40638,1,False,,15m
1709139600000.0,62710.0,61105.2,0.025540028041830438,41064,1,True,,15m
1709603100000.0,67566.7,65600.0,0.02895096721579337,41444,1,True,,15m
1709712900000.0,67689.8,66117.4,0.02318421751054243,41519,1,True,,15m
1709910900000.0,68608.7,67996.0,0.008873652912287366,41668,1,False,41672.0,15m
1710065700000.0,69845.9,68672.9,0.017087766732317874,41778,-1,True,,15m
1710183600000.0,72850.0,72037.1,0.011299003119075206,41961,-1,True,,15m
1710263700000.0,71730.9,70623.8,0.015688036083474795,42112,-1,True,,15m
1710444600000.0,70715.9,68600.0,0.03085108399444179,42282,-1,False,42288.0,15m
1710630900000.0,66630.7,64750.0,0.02936877512203801,42556,-1,False,42561.0,15m
1710714600000.0,68972.4,67350.6,0.024172705620027192,42707,-1,True,,15m
1710846900000.0,63544.1,62545.0,0.015577980335352995,42877,1,True,,15m
1710972900000.0,67716.6,66695.6,0.015310077809984089,43106,-1,True,,15m
1711395900000.0,71228.3,70517.8,0.010076456264208796,43301,-1,True,,15m
1711463400000.0,70368.0,69888.0,0.006811186806731155,43502,1,True,,15m
1711637100000.0,70976.9,70455.6,0.007400351491852775,43727,-1,True,,15m
1711726200000.0,69789.5,69327.0,0.006626083991524344,43670,1,True,,15m
1711762200000.0,70245.0,69872.3,0.00538488879128242,43799,-1,False,43808.0,15m
1711951200000.0,69835.0,69222.0,0.00895004183013025,43881,-1,False,,15m
1712253600000.0,68787.1,67420.2,0.019798495378082006,44347,1,False,44356.0,15m
1712455200000.0,69579.2,69198.3,0.005470186121323446,44709,1,True,,15m
1712946600000.0,67261.4,65754.6,0.023260588677520812,45007,-1,False,45021.0,15m
1713038400000.0,64891.6,62139.3,0.04433222998059059,45368,-1,False,45371.0,15m
1713367800000.0,61743.7,60700.0,0.017200207648382024,45512,-1,True,,15m
1713452400000.0,63815.6,62268.4,0.024230199423060615,45658,1,False,45663.0,15m
1713634200000.0,65400.0,64728.3,0.010405580625821968,46046,-1,True,,15m
1713768300000.0,66449.0,65750.0,0.010501802884615385,46029,1,True,,15m
1714180500000.0,63157.6,62650.0,0.008022087607644666,46464,1,True,,15m
1714273200000.0,64041.4,63770.7,0.004249300678444011,46560,-1,True,,15m
1714373100000.0,62658.7,62150.0,0.008217443207242029,46595,-1,False,46598.0,15m
1714506300000.0,60331.8,59555.0,0.013049759768840555,46858,-1,True,,15m
1714786200000.0,63430.0,62846.7,0.009173114424105223,47198,1,True,,15m
1715122800000.0,62743.9,62203.6,0.008693665354223064,47407,-1,False,47410.0,15m
1715210100000.0,61779.0,60843.6,0.015379809273265398,47585,-1,True,,15m
1715271300000.0,62637.3,62633.2,6.549144138685029e-05,47850,-1,False,,15m
1715591700000.0,63289.3,62540.2,0.011987518002880554,48040,-1,True,,15m
1715817600000.0,66573.0,65633.0,0.01409976030407483,48489,1,True,,15m
1716319800000.0,70321.8,69242.0,0.01562380177247246,49007,-1,True,,15m
1716579900000.0,69273.5,68844.1,0.006253221996662145,49354,-1,True,,15m
1716921000000.0,68710.0,68302.8,0.005967130955957196,49786,-1,False,49793.0,15m
1717783200000.0,69613.6,69257.9,0.005108804465069418,50577,1,True,,15m
1718119800000.0,67605.0,66920.0,0.010248354278874924,50940,-1,False,50943.0,15m
1718384400000.0,66648.8,66162.8,0.00736240929542046,51266,-1,False,,15m
1718977500000.0,64342.1,63444.6,0.014167190208143254,51838,-1,False,51840.0,15m
1719261000000.0,61566.0,60600.5,0.01567614433418195,52056,1,True,,15m
1719341100000.0,62448.1,61605.7,0.013677989218678988,52190,-1,True,,15m
1719432900000.0,61064.2,60766.1,0.00490861188868761,52297,-1,True,,15m
1719497700000.0,61997.2,61514.2,0.00786158403593867,52448,-1,True,,15m
1719604800000.0,61023.0,60706.5,0.005178846090912066,52727,1,True,,15m
1720058400000.0,58088.0,57611.0,0.008352010085446141,52894,-1,False,52900.0,15m
1720121400000.0,58653.4,58111.6,0.009335729584337805,53088,-1,True,,15m
1720152900000.0,56847.0,56300.0,0.009618427993669774,53149,1,True,,15m
1720307700000.0,57899.9,57041.7,0.014819803484777917,53668,1,True,,15m
1721178900000.0,64722.8,63871.2,0.013080609486360374,54295,1,False,,15m
1721417400000.0,66729.0,66422.9,0.004582541132086858,54399,1,False,54405.0,15m
1721495700000.0,67400.0,66799.8,0.008994603537597609,54779,-1,False,54781.0,15m
1721763900000.0,66033.1,65450.8,0.008905249249102331,54806,-1,True,,15m
1721962800000.0,67470.4,67277.0,0.002858359000431476,54996,1,False,55016.0,15m
1722030300000.0,68200.0,67864.8,0.004978567800310672,55265,-1,False,,15m
1723158000000.0,61450.0,60208.5,0.020197862589458715,56710,1,True,,15m
1723488300000.0,59339.1,58803.0,0.0090165244081907,56793,1,True,,15m
1723836600000.0,59363.0,58967.6,0.006654224355403595,57457,1,True,,15m
1724270400000.0,61392.0,60060.3,0.02145875027796304,57656,1,True,,15m
1724796900000.0,59625.0,58475.2,0.01972537501887108,58473,-1,True,,15m
1725168600000.0,58286.0,57800.0,0.00842994817142976,58715,-1,True,,15m
1725411600000.0,56818.0,56107.9,0.01248602114236277,58886,1,True,,15m
1725516900000.0,56688.9,56500.8,0.0033365261813537417,59302,-1,True,,15m
1726151400000.0,58321.9,57708.0,0.010524655535687677,59916,1,False,59927.0,15m
1726267500000.0,60247.9,59848.6,0.006544067154945137,60060,1,True,60076.0,15m
1726586100000.0,60747.7,60166.7,0.00967936479376789,60175,-1,True,,15m
1726767900000.0,63830.1,63080.1,0.01195219123505976,60567,-1,True,,15m
1727227800000.0,63971.0,63243.2,0.011338285329710778,60905,1,False,,15m
1727370900000.0,65350.0,64788.0,0.008588505156923602,61251,1,True,,15m
1727690400000.0,63845.0,63222.0,0.009757121289004992,61386,1,True,,15m
1727814600000.0,61858.2,61325.0,0.008698233925722386,61563,-1,False,61567.0,15m
1727946000000.0,61047.3,60101.5,0.01547450175801994,61690,1,False,61692.0,15m
1728074700000.0,62344.0,61671.9,0.01077775942552824,62017,1,False,62027.0,15m
1728371700000.0,62544.6,61941.0,0.00962693244400635,62374,1,True,62378.0,15m
1728673200000.0,63400.0,62900.0,0.00796589323153984,62585,-1,True,62589.0,15m
1729044000000.0,67548.6,67357.1,0.00284658892431641,62972,-1,True,,15m
1729232100000.0,68371.0,68173.4,0.0028988781517598774,63189,-1,True,,15m
1729522800000.0,67784.9,66865.9,0.013544523753026883,63670,1,True,,15m
1729804500000.0,68216.4,67800.0,0.006153764303733405,63905,-1,True,,15m
1730556900000.0,69570.0,69221.6,0.00503787807583126,64515,-1,False,64520.0,15m
1730601000000.0,68575.8,68183.8,0.005707089457171351,64643,1,False,64650.0,15m
1731009600000.0,76350.2,75576.3,0.010125856851079565,65173,1,False,65181.0,15m
1731366000000.0,89800.0,87055.0,0.03052319763019895,65734,1,True,,15m
1731708000000.0,91443.5,90120.0,0.014432933478735005,66065,1,True,,15m
1732021200000.0,92885.0,91523.2,0.014647152904531098,66176,1,True,66179.0,15m
1732167000000.0,97899.9,96757.6,0.011659715544994358,66501,1,True,,15m
1732302000000.0,98842.8,98355.0,0.004966159259168794,66533,-1,True,,15m
1732392900000.0,98088.4,97455.6,0.006502314034640527,66681,-1,False,66683.0,15m
1732738500000.0,95985.1,94878.2,0.011502900925823059,67212,1,False,67215.0,15m
1732893300000.0,97687.4,96903.2,0.008093129818260597,67527,-1,False,67531.0,15m
1733160600000.0,96375.0,95171.5,0.012456451582854291,67675,1,True,,15m
1733436900000.0,98765.4,97236.7,0.015471121820789184,67997,1,True,67999.0,15m
1733517000000.0,100499.0,99111.0,0.01379943887584283,68224,1,True,,15m
1733933700000.0,101080.7,100309.8,0.00768528321599538,68401,-1,True,,15m
1734309000000.0,105390.0,104245.2,0.01084518143511083,68841,1,True,,15m
1734637500000.0,97824.8,95946.9,0.019628645551335182,69330,-1,False,69335.0,15m
1734897600000.0,95787.7,94390.0,0.014586694663546906,69645,1,True,,15m
1735207200000.0,96543.5,95341.2,0.01244729080155255,69742,1,True,,15m
1735822800000.0,96991.8,96300.0,0.007129202627850088,70425,1,True,,15m
1735931700000.0,98300.0,97762.1,0.005505274480765254,70819,-1,False,70821.0,15m
1736319600000.0,95358.7,95222.0,0.0014371305328736731,71120,-1,False,71122.0,15m
1736395200000.0,93832.8,93760.0,0.0007777445408316195,71355,-1,True,,15m
1736972100000.0,100658.9,99507.3,0.011332805203879205,71718,1,False,71726.0,15m
1737143100000.0,104962.1,103110.0,0.01761092046322213,72347,1,True,,15m
1737606600000.0,102955.0,101550.4,0.013856156795741602,72676,-1,True,,15m
1737671400000.0,104674.2,104310.0,0.0034942022339100437,72946,-1,True,72951.0,15m
1737962100000.0,102267.8,98820.6,0.03369463116440596,73145,1,True,,15m
1738356300000.0,102543.0,102058.8,0.004747314806190501,73210,-1,True,,15m
1738451700000.0,100470.1,100215.0,0.002521049926868856,73422,1,False,,15m
1738620000000.0,100371.0,97792.8,0.026403728597280126,74051,-1,True,,15m
1738961100000.0,96466.0,95714.4,0.007858726940606865,74185,-1,True,74189.0,15m
1739182500000.0,97611.0,97090.3,0.0053652811594859665,74280,-1,True,,15m
1739302200000.0,96437.5,95050.0,0.014350696900870041,74462,1,False,74469.0,15m
1739505600000.0,97150.0,96505.0,0.006639005857764703,74629,1,True,,15m
1739714400000.0,97134.0,96629.5,0.005224968826251403,74745,-1,True,,15m
1739748600000.0,96605.7,96000.0,0.00631233390651865,74938,-1,True,,15m
1739905200000.0,95799.8,95020.0,0.008122087536897304,75165,1,True,75167.0,15m
1740478500000.0,89442.7,86800.0,0.02887193303310977,76002,1,False,,15m
1740687300000.0,84894.0,82667.0,0.026988639806486484,76181,-1,True,,15m
1740880800000.0,86449.9,85732.0,0.00840911966040222,76114,-1,True,,15m
1740937500000.0,93666.0,91100.0,0.02826084562265273,76356,-1,True,,15m
1741101300000.0,87850.0,86328.0,0.017720835138017595,76418,-1,True,,15m
1741381200000.0,86622.9,85555.0,0.012485239615119242,76647,-1,False,76650.0,15m
1741538700000.0,82761.0,82176.9,0.0070494608809835435,76874,1,True,76878.0,15m
1741743900000.0,82887.4,82061.0,0.01008082715480113,77101,-1,True,,15m
1741966200000.0,84625.0,83654.7,0.011604113522505654,77617,-1,True,,15m
1742428800000.0,86285.8,85383.0,0.010583660506932518,78042,-1,True,,15m
1743248700000.0,82759.4,81963.0,0.009617172763741594,78682,1,True,,15m
1743304500000.0,83456.0,82818.5,0.007701898472668227,78777,-1,True,,15m
1743381900000.0,82275.5,81250.0,0.01245510756573394,79062,1,True,,15m
1743637500000.0,83774.1,82962.1,0.009801460079570473,79328,-1,True,,15m
1743981300000.0,79311.5,77111.0,0.02771197678767752,79747,1,True,,15m
1744406100000.0,83998.9,83260.0,0.00878271081358633,79926,1,False,79928.0,15m
1744471800000.0,85388.0,84389.5,0.011844616660280737,80326,-1,True,80333.0,15m
1744875000000.0,84829.4,84144.1,0.00807755775577544,80557,1,True,,15m
1745214300000.0,87702.3,86610.0,0.012452390099648793,80836,1,False,80838.0,15m
1745396100000.0,94178.0,92901.0,0.013555773995418425,81166,1,True,81168.0,15m
1745592300000.0,95157.1,94402.0,0.008008331804351138,81399,-1,False,81401.0,15m
1745731800000.0,94140.8,93586.7,0.005882159360765071,81599,1,False,81608.0,15m
1745868600000.0,94874.2,94244.5,0.006633427544789036,81716,1,True,,15m
1746111600000.0,97262.0,96283.1,0.010052413441829645,82275,1,True,,15m
1746366300000.0,95649.0,95200.1,0.004676041666666606,82266,1,False,,15m
1746579600000.0,97446.8,96153.2,0.013237131504598161,82371,1,False,82376.0,15m
1746738900000.0,104305.3,102327.9,0.01894142946774534,83575,1,False,83581.0,15m
1747705500000.0,106371.3,105480.1,0.00846806990338492,83595,-1,True,,15m
1747809000000.0,107950.0,106085.3,0.017235036841774444,83699,1,False,83705.0,15m
1748002500000.0,109760.0,107950.0,0.016471720967226735,84123,1,True,,15m
1748165400000.0,107698.4,106653.0,0.009700197641294914,84360,1,True,,15m
1748273400000.0,109863.2,108807.0,0.009713389708147651,84429,-1,False,84435.0,15m
1748447100000.0,107789.3,107000.0,0.007378531865666437,84447,-1,False,84452.0,15m
1748546100000.0,106266.0,105580.0,0.006507285598693989,84761,-1,True,,15m
1748623500000.0,103863.6,103650.2,0.0020541352225474426,84711,1,True,,15m
1748704500000.0,104800.0,104215.0,0.005614311215186472,84844,-1,True,,15m
1748916000000.0,106470.8,105526.2,0.0089663028001899,85110,-1,False,85112.0,15m
1749776400000.0,105459.7,104360.0,0.010410641442058888,86264,1,True,,15m
1750179600000.0,105288.0,104143.4,0.0110192572254308,86555,-1,True,,15m
1750440600000.0,103641.0,102903.6,0.007167622641289299,86859,-1,True,,15m
1750859100000.0,107965.9,107022.6,0.008726462003857556,87707,1,False,87711.0,15m
1751476500000.0,109690.4,108706.0,0.009057367622026906,88090,-1,True,,15m
1751655600000.0,108229.0,107883.1,0.0031886473461124514,88183,1,True,,15m
1751837400000.0,109667.3,108622.6,0.009622691387658942,88280,-1,True,,15m
1751898600000.0,108475.0,107903.2,0.005270046082949335,88339,1,True,,15m
1752000300000.0,109107.4,108570.1,0.0049243385869997495,88390,1,False,88393.0,15m
1752226200000.0,118128.0,117055.0,0.009073013661100532,88906,1,True,,15m
1752478200000.0,120292.6,119200.0,0.009181589761258546,89054,-1,True,,15m
1752590700000.0,117727.4,116300.0,0.012099235088290292,89138,1,True,,15m
1752660000000.0,119299.9,118359.4,0.00795212995327648,89324,-1,True,,15m
1752870600000.0,118340.7,117904.0,0.0036844485392135773,89696,1,True,,15m
1753188300000.0,119475.2,118377.0,0.009284779454783386,90106,-1,True,,15m
1753559100000.0,118289.3,117852.0,0.003693337297216973,90205,1,True,,15m
1753633800000.0,119550.0,118949.9,0.005048958321063654,90271,-1,True,,15m
1753727400000.0,118329.8,117406.3,0.0078043858366665005,90479,1,True,,15m
1754009100000.0,115956.0,114239.0,0.015049126589711902,90600,-1,False,90604.0,15m
1754075700000.0,114000.0,113113.0,0.007757951900698216,90945,1,True,90948.0,15m
1754269200000.0,114739.3,114166.0,0.004996030539140826,91135,1,False,91144.0,15m
1754566200000.0,116747.6,116139.8,0.005205754937244898,91401,1,False,91403.0,15m
1754798400000.0,118371.0,117966.9,0.0034107598035078733,91625,1,True,,15m
1754887500000.0,120799.0,119345.0,0.012195725456941288,91655,-1,True,,15m
1754948700000.0,119114.5,118377.2,0.006185631013225345,91704,1,False,91712.0,15m
1755014400000.0,120163.6,119585.4,0.0048043047634242315,91720,1,False,91724.0,15m
1755195300000.0,117853.5,117004.0,0.007285722984719279,92165,-1,False,92167.0,15m
1755508500000.0,116761.0,116171.5,0.005087378640776699,92257,-1,True,,15m
1755630000000.0,113524.5,112714.4,0.00713285728122006,92486,1,True,,15m
1755804600000.0,112700.0,112210.3,0.0043683509096196236,92816,-1,True,,15m
1756061100000.0,112897.7,110532.2,0.021406542411287956,93058,-1,True,,15m
1756169100000.0,110508.5,109529.5,0.008939635091218064,93252,-1,True,,15m
1756823400000.0,111487.9,110486.9,0.0089699841569111,93740,1,True,,15m
1756909800000.0,112207.1,111724.1,0.004300983349050177,93916,1,True,93918.0,15m
1756968300000.0,111111.0,110237.2,0.007859334286143476,93962,1,True,93971.0,15m
1757058300000.0,112949.0,112080.0,0.007758547571324622,94219,-1,True,,15m
1757093400000.0,111256.2,110504.9,0.006752414754471153,94281,1,False,94287.0,15m
1757344500000.0,112460.4,111847.0,0.005452439597831416,94399,1,True,,15m
1757511900000.0,114287.9,113722.2,0.004947356177286214,94513,1,True,,15m
1757637900000.0,115613.0,114993.0,0.0053600482750154315,94734,1,False,94752.0,15m
1757706300000.0,116245.4,115701.4,0.004704685832495888,94833,-1,True,,15m
1757860200000.0,115605.5,115275.5,0.0028516617410691138,95100,1,True,,15m
1758311100000.0,115670.0,115355.0,0.0027348854169832043,95512,-1,True,,15m
1758596400000.0,112466.6,111413.5,0.009479308625261766,95857,-1,True,,15m
1759177800000.0,114377.2,113960.6,0.003641996786370498,96396,1,True,,15m
1759509000000.0,122930.3,121636.4,0.010649558017415996,97024,-1,True,,15m
1759776300000.0,125098.0,123261.6,0.014906739528185898,97132,-1,True,,15m
1760130900000.0,113300.1,110987.0,0.02039125179947447,97646,1,True,,15m
1760646600000.0,107505.0,107350.0,0.0014386165520723505,98153,1,True,98156.0,15m
1760778000000.0,107248.0,106568.0,0.00633282143026772,98488,1,True,,15m
1760894100000.0,109340.7,108556.4,0.007123498188925771,98223,1,False,,15m
1760949000000.0,111552.3,110552.3,0.009051821679112922,98569,-1,False,98576.0,15m
1761396300000.0,111686.0,111291.0,0.0035345838899930116,98816,1,True,,15m
1761685200000.0,113233.0,112121.4,0.009810748577061998,99124,1,True,,15m
1761761700000.0,110957.9,109670.6,0.01174391639039457,99559,-1,False,99562.0,15m
1762291800000.0,101859.5,98909.7,0.028924825654824033,99903,1,True,,15m
1762370100000.0,104062.9,103240.5,0.007973249283289763,100017,-1,True,,15m
1762446600000.0,102298.0,100500.0,0.017566811037584965,100516,1,True,,15m
1763064900000.0,99862.8,98657.0,0.012362095179316024,100626,-1,False,,15m
1763123400000.0,96800.0,94500.0,0.024515577517723167,100950,-1,True,,15m
1763409600000.0,92370.9,91250.0,0.0123628767374093,101240,-1,True,,15m
1763913600000.0,87472.0,86546.2,0.010578878807962188,101824,1,False,101828.0,15m
1764235800000.0,91800.0,90612.2,0.013161962615061936,102401,-1,True,,15m
1764703800000.0,92273.2,91733.2,0.005830727502029957,102588,1,True,102593.0,15m
1764875700000.0,92455.4,91750.6,0.007719402775830519,102661,-1,False,,15m
1764952200000.0,89762.0,88856.7,0.010083548767597235,103214,1,False,103218.0,15m
1765299600000.0,93278.9,91900.0,0.015066487472820382,103299,-1,True,,15m
1765422000000.0,90494.7,89321.0,0.012969519364220581,103369,1,True,,15m
1765557000000.0,90599.0,89843.0,0.00852512996312543,103838,-1,False,,15m
1765823400000.0,86478.6,85226.0,0.01446503839713616,103799,1,True,103806.0,15m
1765899900000.0,87900.0,87015.8,0.009965343326477102,103926,1,True,103929.0,15m
1766157300000.0,88551.8,87698.5,0.009734888846804164,104535,-1,True,,15m
1766545200000.0,87430.0,86817.9,0.0069980198382030125,104578,1,True,,15m
1766613600000.0,87933.5,87617.1,0.0036187833613738818,105186,-1,True,,15m
1767150000000.0,88780.0,88280.1,0.005671980484483964,105288,-1,True,,15m
1767311100000.0,88881.4,88533.6,0.0039060832923406663,105344,1,False,105348.0,15m
1767348900000.0,89900.0,89510.9,0.004327434779852525,105406,1,False,105412.0,15m
1767393900000.0,90368.7,89914.5,0.005022330684317402,105493,1,False,105502.0,15m
1767506400000.0,91408.9,90968.0,0.004815426840789409,105856,1,True,,15m
1767576600000.0,93186.0,92496.9,0.007451501978849086,105765,-1,True,105767.0,15m
1767779100000.0,91666.0,91549.9,0.0012730291075200365,106046,-1,True,,15m
1768780800000.0,93312.8,92660.0,0.007055154055903049,107035,-1,True,,15m
1768948200000.0,90088.0,88109.7,0.02247692714966935,107650,-1,False,107653.0,15m
1769382000000.0,87922.0,87028.5,0.010149234065077775,107916,1,True,107923.0,15m
1769736600000.0,83300.0,82108.4,0.014608562608650948,108153,-1,False,108162.0,15m
1769883300000.0,79094.9,78245.7,0.010859321151763196,108434,-1,True,,15m
1770144300000.0,76920.7,75377.3,0.020488109223896964,108534,-1,False,108542.0,15m
1770399000000.0,71451.6,69674.5,0.02555250406200132,109128,-1,False,109131.0,15m
1770705900000.0,69376.3,68633.3,0.010686834050583389,109472,1,False,109475.0,15m
1770828300000.0,68021.0,66757.4,0.018489416433281034,109404,1,False,109408.0,15m
1771000200000.0,69082.8,68689.1,0.005695075502785294,109594,1,True,,15m
1771069500000.0,70230.6,69212.2,0.014851088093026502,109685,-1,False,,15m
1771182000000.0,68869.8,68112.2,0.011125322885476518,110054,-1,True,,15m
1771443000000.0,67042.3,66651.8,0.0059668970912713695,110308,-1,False,,15m
1771628400000.0,68079.0,67745.3,0.004894691525891072,110163,1,False,110170.0,15m
1771686900000.0,68632.5,68156.4,0.006927055673973066,110566,1,True,,15m
1771772400000.0,67634.4,67288.9,0.005093879235991832,110561,1,False,,15m
1771811100000.0,65980.0,65534.6,0.00683070165752637,110368,-1,True,,15m
1771877700000.0,64719.1,63860.0,0.013598991987184537,110811,-1,True,,15m
1772044200000.0,68850.0,67750.0,0.016239877758011056,110889,-1,True,,15m
1772213400000.0,65994.7,65080.0,0.013784196773289325,110952,1,True,,15m
1772342100000.0,67219.1,66100.0,0.01652818105824084,111120,1,True,,15m
1772469900000.0,69500.0,68519.8,0.014477897647378515,111138,-1,True,111140.0,15m
1772530200000.0,68000.0,66600.5,0.02049273346267892,111432,1,True,111436.0,15m
1772732700000.0,71330.4,70612.8,0.010174119795439954,111402,-1,False,111407.0,15m
1772818200000.0,68215.6,67712.3,0.007447293346117921,111672,-1,True,111674.0,15m
1773110700000.0,70555.0,69719.4,0.011837152736952086,112030,1,True,,15m
1773361800000.0,71979.2,71201.0,0.010797281116412743,112310,1,True,,15m
1773615600000.0,73199.9,72841.9,0.004923114158491272,112560,-1,False,112563.0,15m
1773846000000.0,71333.1,70821.2,0.007163217528683078,112621,1,True,,15m
1773904500000.0,70535.0,69421.1,0.01575962852607355,113042,1,True,113045.0,15m
1774179900000.0,68434.3,68030.0,0.00590589713250658,113355,1,True,,15m
1774632600000.0,66148.0,65874.4,0.0041569996672577905,113664,-1,False,,15m
1774710900000.0,67100.0,66375.5,0.010779822315199326,113677,1,False,113681.0,15m
1774859400000.0,67920.0,67333.3,0.008718044732916036,114205,-1,True,,15m
1775104200000.0,66898.5,66171.8,0.010850625101719899,114277,1,True,,15m
1775466900000.0,69955.3,69283.0,0.00970632694281746,114514,-1,True,,15m
1775603700000.0,71945.0,71367.0,0.008018311715336061,114764,1,False,114773.0,15m
1775851200000.0,73066.2,72615.0,0.006157860689320057,115087,1,True,,15m
1776121200000.0,74476.1,74112.2,0.004918491446366395,115672,-1,True,115679.0,15m
1776537000000.0,75843.3,75395.9,0.0059422772974194625,115738,-1,True,,15m
1776716100000.0,76232.3,75556.2,0.008862445306089354,115871,1,True,,15m
1776872700000.0,78310.1,77410.7,0.011636271425022883,116592,-1,False,116595.0,15m
1777386600000.0,76448.0,76150.0,0.0039236649664118484,116606,-1,False,116611.0,15m
1777437000000.0,77432.1,76888.0,0.0070188972294642216,116784,1,False,116786.0,15m
1777916700000.0,80363.2,79846.5,0.006473436609307329,117373,-1,True,,15m
1777959000000.0,81278.2,80651.2,0.007776319803942483,117597,-1,True,,15m
1778351400000.0,80809.4,80619.6,0.002356347239671904,117878,-1,True,,15m
1778643900000.0,81268.0,80847.0,0.005165301930915007,118048,1,True,118055.0,15m
1778688000000.0,79659.9,79188.0,0.0059697980591514035,118137,-1,True,,15m
1779084000000.0,77193.5,76627.0,0.007320625296411019,118617,1,True,118619.0,15m
1779272100000.0,77639.3,77286.1,0.0045762736020792385,118992,-1,False,118995.0,15m
1779570000000.0,76983.1,76611.0,0.00485707460785102,119018,-1,True,,15m
1779701400000.0,77699.0,77244.8,0.005880555584326767,119180,-1,True,119185.0,15m
1779765300000.0,77045.9,76606.0,0.005708420276765524,119186,1,True,,15m
1779940800000.0,73592.1,72667.9,0.012549716130907194,119676,1,True,,15m
1780166700000.0,74108.8,73750.0,0.004878314072059863,119710,-1,True,,15m
1780245000000.0,73704.0,73417.2,0.0038886817396020868,119722,1,True,,15m
1780428600000.0,67493.8,66388.0,0.016683337934905153,119966,-1,False,119969.0,15m
1780538400000.0,64463.7,62150.8,0.03733741861850411,120146,-1,False,120151.0,15m
1780685100000.0,61475.0,59451.3,0.032875915634132946,120368,1,False,120370.0,15m
1780821900000.0,62267.2,61509.1,0.012361038190243544,120538,-1,True,,15m
1780870500000.0,64179.5,62381.2,0.028860582347267495,120640,-1,False,120645.0,15m
1781277300000.0,63828.4,63360.0,0.007336196392044854,120898,1,True,,15m
1781364600000.0,64323.0,64186.5,0.002121677010813558,121329,1,True,,15m
1781733600000.0,64631.5,63881.2,0.011750978856695425,121398,-1,True,,15m
1781915400000.0,63757.2,63360.0,0.0062194663660277635,121718,1,True,,15m
1782202500000.0,63053.5,62402.8,0.010436330684287719,121964,-1,True,,15m
1782323100000.0,61256.0,60648.0,0.009918806088972344,122062,1,True,,15m
1783000800000.0,61847.2,61229.0,0.00997225448444956,122826,1,True,122832.0,15m
1783112400000.0,62735.2,62404.0,0.005276632896428508,123283,1,True,,15m
1783371600000.0,63424.2,62958.4,0.007401957434771968,123392,-1,True,,15m
1783583100000.0,63286.5,62897.0,0.006202584861552573,123758,-1,True,123763.0,15m
1783661400000.0,64185.0,63765.5,0.00657892673318257,123892,-1,True,,15m
1783917000000.0,62965.0,62434.9,0.008503191303357461,123849,-1,True,,15m
1784043000000.0,64950.0,64456.7,0.0076547673543492034,124298,-1,True,,15m
1784313900000.0,64237.7,64000.0,0.0037260732642878072,124442,-1,True,,15m
1784428200000.0,64928.0,64259.5,0.010279554680772542,124760,1,True,,15m
1784570400000.0,65634.4,65145.1,0.00744466708964176,124535,1,False,124538.0,15m
1784642400000.0,66467.4,66172.0,0.004466486233122067,124676,-1,False,124681.0,15m
1784705400000.0,66077.3,65668.6,0.006228882077980622,124829,-1,False,124832.0,15m
1784828700000.0,65193.5,64733.2,0.0071538362310799904,124852,-1,False,,15m
1784864700000.0,65440.0,65230.1,0.0032194683505861682,125088,-1,True,125093.0,15m
1784902500000.0,64305.8,63760.5,0.008553001000699594,125179,-1,True,,15m
1785083400000.0,64909.0,64872.0,0.0005703627198594133,125155,-1,True,,15m
1785132000000.0,65412.8,65066.3,0.005346949923846471,125478,-1,False,,15m
1785200400000.0,63642.2,63021.0,0.009734464737637952,125278,1,False,125290.0,15m
1785257100000.0,64047.9,63646.6,0.006313719320327296,125751,-1,True,,15m
1785784500000.0,64058.8,63428.8,0.009827641880729866,125966,1,True,125973.0,15m
1785895200000.0,64345.8,64080.0,0.004127931716741309,126010,1,False,126017.0,15m
1569513600000.0,8128.78,7851.94,0.03388453017710923,599,1,True,,1h
1569898800000.0,8386.0,8149.35,0.029151376514079213,1059,-1,False,,1h
1571227200000.0,8108.54,7900.24,0.027581111374460947,1075,-1,False,,1h
1574431200000.0,7324.92,7073.38,0.034308924903363784,2003,1,True,,1h
1574877600000.0,7669.2,7416.0,0.03521435276937517,2091,-1,True,2106.0,1h
1575266400000.0,7349.65,7231.35,0.016092523166159625,2212,1,True,,1h
1575691200000.0,7559.39,7376.0,0.02519979553249499,2430,-1,False,2432.0,1h
1576112400000.0,7136.19,7080.0,0.00787045337397184,2771,1,True,,1h
1577556000000.0,7359.15,7283.6,0.010416810291339677,2805,-1,True,,1h
1578441600000.0,8261.33,7956.28,0.036853582366337075,3055,1,False,3058.0,1h
1579093200000.0,8921.2,8832.0,0.009984284825230323,3386,1,True,,1h
1580414400000.0,9477.76,9220.0,0.02713186046291318,3598,1,False,3601.0,1h
1582765200000.0,8809.05,8533.9,0.03275595238095234,4366,-1,False,,1h
1584698400000.0,6408.91,5670.0,0.11470106565456646,4732,1,False,4735.0,1h
1585036800000.0,6777.5,6522.34,0.039538357599310736,4901,-1,False,4906.0,1h
1585846800000.0,7049.11,6720.05,0.04635966852541139,5056,1,False,5062.0,1h
1586217600000.0,7390.0,7152.72,0.033330992147663226,5364,-1,True,,1h
1586530800000.0,6908.0,6757.29,0.021752716744150472,5437,1,False,5442.0,1h
1587247200000.0,7208.55,7055.0,0.021821401316247626,5461,-1,True,,1h
1588226400000.0,9065.0,8656.0,0.045041026979465035,5776,1,False,5778.0,1h
1588892400000.0,9804.9,9528.23,0.02917392051643545,5985,-1,True,5987.0,1h
1589068800000.0,9164.0,8457.12,0.07709918252267277,6163,1,True,,1h
1589763600000.0,9838.0,9450.0,0.041081284827770945,6311,-1,True,,1h
1590084000000.0,9294.44,9076.9,0.02399832318418508,6292,-1,True,,1h
1590706800000.0,9621.65,9371.76,0.02751180500737084,6647,-1,True,,1h
1591606800000.0,9816.0,9622.23,0.020149973118810744,6927,-1,True,,1h
1591894800000.0,9460.0,9361.0,0.010582248118444217,6956,-1,False,6964.0,1h
1593054000000.0,9188.0,9005.0,0.019793305968817433,7342,1,False,,1h
1594080000000.0,9380.0,9201.23,0.018816943985988124,7637,1,False,,1h
1599174000000.0,10348.97,9901.16,0.0432061780174422,9161,1,False,9166.0,1h
1600171200000.0,10923.42,10740.0,0.01710222547114571,9174,-1,False,9178.0,1h
1600696800000.0,10531.94,10374.12,0.01491610517829478,9337,1,True,,1h
1602298800000.0,11429.09,11303.77,0.011096176901322627,9703,-1,True,,1h
1604134800000.0,13855.0,13605.9,0.017771848279355504,10134,1,True,,1h
1604624400000.0,15869.0,14822.0,0.06596322691854077,10320,1,True,,1h
1605229200000.0,16355.0,15955.6,0.024379658550490714,10433,1,True,,1h
1605672000000.0,18200.0,17715.7,0.02744498677053029,10670,-1,False,,1h
1606230000000.0,19385.81,18635.01,0.038377198587179466,10773,1,True,,1h
1606816800000.0,19640.0,18510.0,0.05703714733502762,11153,1,False,11155.0,1h
1609063200000.0,27538.82,25913.01,0.058765654147926796,11479,1,False,11482.0,1h
1610038800000.0,40565.62,38655.0,0.049661953431164294,11867,-1,True,,1h
1612530000000.0,38411.82,37403.27,0.026114220553112497,12443,1,False,12445.0,1h
1612854000000.0,48266.36,46252.1,0.04148373991693206,12623,1,True,,1h
1615402800000.0,57500.92,55074.3,0.04215233986463967,13442,1,True,,1h
1616180400000.0,59550.0,58024.0,0.02635887697365546,13626,-1,True,,1h
1618714800000.0,57060.0,53330.82,0.07006387736087806,14297,-1,True,,1h
1627261200000.0,40894.0,39365.33,0.038940984158628164,16651,-1,True,,1h
1627689600000.0,42494.0,41030.19,0.03443702159203906,16751,1,True,16754.0,1h
1628402400000.0,45350.0,44670.05,0.014839732201705933,16909,1,False,,1h
1628924400000.0,48074.23,45480.0,0.05337919614024857,17085,1,False,,1h
1629565200000.0,49833.0,48801.01,0.020641641234398066,17389,1,True,17396.0,1h
1630328400000.0,48188.0,47400.0,0.016899658918559084,17520,-1,False,17522.0,1h
1631026800000.0,48150.0,47038.21,0.023025505771443203,17790,1,True,,1h
1634360400000.0,61445.0,60220.0,0.0199275547979289,18851,1,False,18853.0,1h
1635865200000.0,63277.12,61623.56,0.027132587475422063,19186,-1,False,19188.0,1h
1637139600000.0,58980.0,58336.68,0.01103969671208137,19341,-1,False,19344.0,1h
1637611200000.0,57888.0,55900.0,0.03389252013561782,19512,1,True,19516.0,1h
1637928000000.0,55238.0,53958.93,0.02373731890613879,19610,-1,False,19613.0,1h
1638594000000.0,50847.53,48533.33,0.04779865328502652,19844,-1,False,,1h
1639422000000.0,47958.62,46435.25,0.03175370446336564,20217,1,True,,1h
1640822400000.0,47911.68,46803.81,0.023942593274151363,20394,-1,False,20399.0,1h
1644300000000.0,44325.52,43282.32,0.023427113650246065,21390,1,True,,1h
1644645600000.0,42980.0,41711.0,0.03075245365321701,21433,-1,False,,1h
1647493200000.0,41498.4,40430.0,0.025550089798378164,22209,1,False,,1h
1648213200000.0,45142.0,44206.2,0.020703494018818606,22457,1,True,,1h
1649710800000.0,40700.0,39224.0,0.03625947635027244,22948,1,True,,1h
1650643200000.0,40000.0,39133.0,0.022234988177243887,23276,-1,False,23278.0,1h
1652140800000.0,32197.6,30129.6,0.06909224549797868,23471,-1,False,23478.0,1h
1652331600000.0,29959.8,27968.6,0.06635364841763612,24129,1,True,24132.0,1h
1655172000000.0,22793.0,20823.0,0.09475253715549997,24378,-1,True,24380.0,1h
1655582400000.0,20821.0,19763.0,0.05075362902838941,24554,1,True,,1h
1656140400000.0,21627.6,20894.0,0.03513090283930095,24790,-1,True,,1h
1656615600000.0,19375.3,18959.0,0.021439754444513074,24919,1,True,24921.0,1h
1657242000000.0,22048.0,21175.2,0.04126557860695573,25016,-1,True,,1h
1657911600000.0,21200.0,20742.0,0.02208548723092354,25246,-1,True,,1h
1658156400000.0,22800.0,21682.3,0.048884709587123897,25277,1,True,,1h
1659074400000.0,24500.0,23621.8,0.03742579405159155,25576,-1,False,,1h
1659625200000.0,23341.8,22746.9,0.025432967521557433,25779,1,True,,1h
1660950000000.0,21368.9,20760.0,0.028155142278490447,25987,1,True,,1h
1661727600000.0,20440.0,20069.3,0.018757273693265228,26240,-1,False,,1h
1662274800000.0,20023.2,19620.2,0.01995899244232693,26452,1,True,,1h
1663178400000.0,20068.8,19600.0,0.024686287209786013,26618,-1,False,,1h
1663693200000.0,19476.2,18690.1,0.0402302968270216,27003,1,True,27008.0,1h
1664913600000.0,20443.9,19707.0,0.03753336185644732,27158,-1,True,,1h
1665158400000.0,19550.0,19304.4,0.012746853509796213,27065,-1,True,,1h
1668031200000.0,17460.0,16880.0,0.034448555833387774,28282,-1,True,,1h
1668794400000.0,16800.0,16541.1,0.01538690122429582,28456,1,True,,1h
1669896000000.0,17160.0,16851.2,0.018353313165292703,28775,-1,True,,1h
1671231600000.0,16795.0,16663.3,0.007908437468099078,29109,-1,True,,1h
1674320400000.0,23193.1,22310.5,0.037862603279194815,30146,1,False,,1h
1676505600000.0,24934.1,23827.0,0.04661611079063377,30472,-1,False,30476.0,1h
1677355200000.0,23515.0,23088.0,0.01778499729268191,30766,1,False,,1h
1681236000000.0,30564.0,29925.0,0.021416075127189366,31820,-1,False,31824.0,1h
1682114400000.0,27782.3,27279.7,0.01796584844487811,31948,1,False,,1h
1683871200000.0,27200.0,26809.0,0.014374418681597436,32670,1,True,,1h
1685548800000.0,27290.0,26826.0,0.017317956182585005,32827,-1,False,32830.0,1h
1686171600000.0,26199.0,26111.0,0.003350644999162339,33057,1,False,33060.0,1h
1687532400000.0,31053.2,30222.0,0.02751343067191428,33859,-1,False,33863.0,1h
1689696000000.0,30178.5,29811.0,0.012355391489404621,33951,-1,False,33954.0,1h
1690210800000.0,29379.5,29145.2,0.007969198012292199,34378,1,True,,1h
1691769600000.0,29470.0,29220.0,0.008569744004607093,34490,-1,True,,1h
1692306000000.0,26129.4,25797.4,0.012915775141023147,34893,-1,True,,1h
1693587600000.0,26135.2,25531.2,0.023073866935607104,35462,1,True,,1h
1694714400000.0,26880.0,26400.0,0.01785056842903841,35865,1,True,,1h
1696251600000.0,27890.0,27533.1,0.012752577135404623,35972,1,False,35975.0,1h
1697461200000.0,29100.0,28126.6,0.03330960756669455,36058,1,False,36061.0,1h
1699538400000.0,37570.0,36588.0,0.026093426157198278,36904,1,False,36910.0,1h
1700838000000.0,37886.5,37570.9,0.008261239447680088,37008,1,True,37011.0,1h
1701198000000.0,38132.0,37570.9,0.014689212290663634,37069,1,False,37072.0,1h
1701813600000.0,44528.1,43091.5,0.033750972049609154,37388,-1,True,,1h
1702321200000.0,42125.0,40556.0,0.037182914344215674,37681,1,False,37685.0,1h
1703084400000.0,44332.8,43317.2,0.022479072505854514,37831,1,False,,1h
1703811600000.0,42951.0,42123.1,0.019203915474008987,38100,1,True,38103.0,1h
1704394800000.0,44336.7,43391.3,0.021807780584293373,38508,-1,True,,1h
1705680000000.0,41826.5,41544.0,0.006823704462340398,38422,-1,True,,1h
1706292000000.0,42239.3,41600.0,0.015120767462322325,38488,1,True,,1h
1706612400000.0,43748.6,42341.0,0.03190867174146628,38715,1,False,,1h
1709600400000.0,67729.5,65702.8,0.029497121167135276,39589,1,True,,1h
1709928000000.0,69447.0,67996.0,0.021503527853070588,39587,-1,True,,1h
1710248400000.0,73370.0,70623.8,0.039284968371180104,39844,-1,True,,1h
1710493200000.0,69044.4,65580.0,0.05348021198195088,40298,-1,False,40303.0,1h
1713038400000.0,64483.3,61521.0,0.04591467431317089,40567,1,True,,1h
1716249600000.0,69900.0,68736.8,0.016501163252567626,41542,1,False,,1h
1717171200000.0,68545.9,67296.3,0.018621590971747093,41704,-1,True,41706.0,1h
1718384400000.0,66482.6,65801.0,0.010375013509132261,41848,-1,True,,1h
1718737200000.0,65711.9,64600.0,0.017324873713372112,41942,-1,False,,1h
1719259200000.0,62379.7,60605.0,0.029335775364031685,42231,-1,False,42235.0,1h
1719799200000.0,63800.0,62580.0,0.019496604075109867,42515,-1,True,,1h
1721088000000.0,64988.8,63206.4,0.027377734086999284,42620,1,False,42622.0,1h
1722837600000.0,57699.0,54613.0,0.053052769110429186,43281,1,False,43285.0,1h
1725033600000.0,59436.0,57525.0,0.03324640439527556,43900,-1,True,,1h
1725915600000.0,58033.2,56361.0,0.028771160705222017,43928,1,True,,1h
1726765200000.0,63673.8,62669.2,0.0157474637272238,44370,1,True,,1h
1727812800000.0,61452.6,60413.3,0.01721020192552892,44595,-1,True,,1h
1728068400000.0,62344.0,61671.9,0.01068861323155214,44640,1,False,44651.0,1h
1728673200000.0,63400.0,62458.0,0.014654271206690562,44698,1,False,,1h
1729076400000.0,68400.0,68000.0,0.005882993149254477,45030,-1,True,,1h
1731394800000.0,90070.1,86714.0,0.037138557216683146,45549,1,False,45555.0,1h
1731913200000.0,92414.9,91500.0,0.009899372430209849,45738,1,True,,1h
1732258800000.0,99537.6,97199.9,0.024079792833642817,45862,-1,True,,1h
1732964400000.0,97128.4,96150.0,0.01005042676306195,46062,1,True,46064.0,1h
1733511600000.0,100497.5,98876.9,0.016045544554455504,46149,1,True,,1h
1734573600000.0,99485.0,98744.0,0.0075248441717492365,46444,-1,True,,1h
1734919200000.0,96499.0,93655.3,0.029466928069899083,46755,1,True,,1h
1735930800000.0,98760.9,97438.2,0.013308836032765444,46945,1,True,46961.0,1h
1740495600000.0,87066.0,86020.4,0.012175128085701046,48178,-1,True,,1h
1741561200000.0,83741.8,79933.4,0.0453941353790335,48687,1,True,,1h
1742824800000.0,88500.0,86260.0,0.02597233945847672,48793,-1,True,,1h
1743267600000.0,83490.0,81601.0,0.0225654625382263,48800,1,True,,1h
1743541200000.0,85435.2,83846.0,0.019255001550868452,48833,-1,True,,1h
1743634800000.0,83774.1,82137.3,0.019385578541320524,48840,1,True,,1h
1745409600000.0,94904.3,92700.0,0.023208385889031874,49478,1,False,49481.0,1h
1746774000000.0,104373.0,102700.0,0.015954773509926197,50185,1,True,,1h
1747933200000.0,109440.2,106721.9,0.024726249320057733,50428,1,True,,1h
1748660400000.0,104859.3,103727.1,0.010916464268875515,50504,-1,True,,1h
1749326400000.0,105878.3,105253.2,0.00602600697748182,50503,-1,True,,1h
1749502800000.0,110265.5,108282.0,0.018426205994485606,50597,-1,False,,1h
1749776400000.0,106099.4,104225.4,0.017652583221002993,50783,1,True,,1h
1750906800000.0,108249.9,107233.1,0.009354590308872493,50974,1,False,,1h
1752224400000.0,118882.8,116862.4,0.017291579298717157,51848,-1,True,,1h
1754175600000.0,115674.2,112582.4,0.026630628582059883,52102,1,True,52104.0,1h
1754604000000.0,116941.5,116304.0,0.005438004614839995,52035,1,False,52037.0,1h
1754884800000.0,122450.0,118050.0,0.03735953028881464,52079,-1,False,52087.0,1h
1755644400000.0,114569.0,112666.6,0.016599349602859814,52249,1,True,,1h
1756170000000.0,112330.0,110306.9,0.017981944282726595,52350,1,True,,1h
1756918800000.0,112541.2,109850.0,0.023734433978904266,52651,1,False,,1h
1757703600000.0,116118.6,115100.5,0.008748637993201188,53152,1,False,53156.0,1h
1758596400000.0,113236.0,111405.1,0.016447755535811927,53010,-1,True,,1h
1762290000000.0,104070.7,99200.0,0.049228327993450616,54199,-1,True,,1h
1763438400000.0,93150.0,89012.0,0.0466446406570379,54460,-1,False,54462.0,1h
1763708400000.0,85599.9,83456.0,0.024945545430004868,54620,1,True,,1h
1763935200000.0,88100.0,86061.1,0.02377235252898229,54611,-1,True,,1h
1764234000000.0,91931.9,90092.7,0.02000593912720933,54653,1,True,,1h
1767373200000.0,90945.1,89261.2,0.01847676167841293,55814,1,True,,1h
1768946400000.0,90088.0,88109.7,0.022473505619228437,56031,-1,False,56038.0,1h
1770336000000.0,71524.9,67250.0,0.06408588158750804,56623,-1,False,,1h
1773154800000.0,71286.0,69329.7,0.02844406059575066,57653,-1,False,57665.0,1h
1776175200000.0,75500.0,74508.2,0.013133205770771438,58202,1,True,58205.0,1h
1776870000000.0,78459.1,77206.8,0.01591408453031025,58244,1,True,,1h
1778925600000.0,78180.0,77601.8,0.007452260166625386,58822,-1,True,,1h
1779519600000.0,77600.0,76056.0,0.020339983348614667,58848,-1,True,,1h
1780686000000.0,62942.4,61150.2,0.028419154783066555,59403,1,True,,1h
1781377200000.0,64738.0,63650.0,0.017102319973717757,59814,-1,True,,1h
1782201600000.0,61931.1,61870.0,0.0009907604694680142,59733,-1,True,59738.0,1h
1783191600000.0,63450.0,62410.1,0.016318146582431848,59912,1,False,,1h
1783677600000.0,64438.9,63619.9,0.012701100762066046,60295,1,True,,1h
1784642400000.0,65780.0,64636.0,0.017707059606544184,60557,-1,True,,1h
1569513600000.0,8499.0,7760.0,0.08674818726808946,418,1,True,420.0,4h
1578441600000.0,8468.42,8247.0,0.027545077042120017,1093,-1,False,,4h
1588219200000.0,9479.77,8526.0,0.10020434428597698,1909,1,False,1914.0,4h
1596326400000.0,11920.0,11127.0,0.07155108865026495,2261,-1,False,2264.0,4h
1606305600000.0,19554.19,18510.0,0.052873485227486715,2788,1,False,,4h
1613923200000.0,52681.51,46320.0,0.11807420650137483,3284,1,False,,4h
1621425600000.0,38199.0,34739.06,0.09039322781003722,4118,1,True,,4h
1628884800000.0,48300.0,46280.0,0.04141519408656637,4536,1,True,,4h
1637280000000.0,59449.62,55604.0,0.0713680317197851,4902,-1,False,,4h
1638590400000.0,50768.0,46724.35,0.0877945655149834,5094,-1,False,5101.0,4h
1641816000000.0,43800.0,41666.0,0.051330047151964125,5177,-1,True,,4h
1643025600000.0,38886.58,35513.13,0.0867208567587231,5378,1,True,,4h
1644508800000.0,44777.0,41526.0,0.0784486898272488,5444,-1,True,,4h
1645488000000.0,39250.0,36310.1,0.0722859868602227,5718,1,False,,4h
1652328000000.0,30784.4,28629.6,0.07589381628117493,6041,-1,False,6044.0,4h
1655568000000.0,21740.0,19745.7,0.1020232766338406,6511,-1,True,,4h
1661716800000.0,20290.0,19508.0,0.04040571053596986,6644,-1,False,6646.0,4h
1679486400000.0,28881.0,27166.0,0.05925910568852861,7954,1,True,,4h
1681444800000.0,30450.0,29078.1,0.04922161747136009,7956,-1,True,,4h
1687492800000.0,31395.2,29804.6,0.05344937665916201,8581,-1,True,,4h
1690200000000.0,29670.5,28830.0,0.029271027777003873,8629,-1,False,8631.0,4h
1710388800000.0,69044.4,64559.8,0.07131793301082334,10635,-1,True,,4h
1719259200000.0,62465.1,60038.0,0.04108853902149989,10751,-1,False,,4h
1721174400000.0,66100.0,63800.0,0.03616170255584625,10881,-1,True,,4h
1722830400000.0,60250.0,55969.0,0.07061722855832643,11107,1,False,11114.0,4h
1726804800000.0,64126.7,62714.2,0.0225328700734771,11168,-1,True,,4h
1731513600000.0,93421.1,90866.4,0.02634762528967755,11579,1,True,,4h
1732881600000.0,98713.3,94039.7,0.04995409259934229,11721,-1,True,,4h
1747022400000.0,104949.2,102591.0,0.022442974174686294,12558,1,True,,4h
1747929600000.0,110400.0,106497.0,0.03514522279802077,13074,1,True,13079.0,4h
1752465600000.0,120951.5,115678.1,0.04560738934149754,13184,-1,True,,4h
1755849600000.0,113429.0,111625.9,0.015884678170306425,13455,1,True,,4h
1762286400000.0,104497.6,101300.0,0.03045916408760159,13540,1,True,,4h
1763697600000.0,93080.0,83786.0,0.11128619735039706,14019,-1,False,,4h
1770321600000.0,70938.5,65081.0,0.09135542915606767,14761,-1,False,,4h
1776441600000.0,78300.0,74868.0,0.043787965199285,14564,1,True,,4h
1780675200000.0,64179.5,60691.9,0.05384857673993463,15100,1,True,,4h
1 zs_start_ts zg zd width_pct bo_idx dir is_fake conf_idx tf
2 1672643700000.0 16730.7 16703.9 0.0016052229642717663 294 -1 True 15m
3 1673656200000.0 21240.0 20670.7 0.02671616015617664 1890 1 False 1892.0 15m
4 1674258300000.0 22812.4 22600.2 0.0092994719197143 2298 1 True 15m
5 1674565200000.0 23011.9 22864.0 0.006363589425857147 2390 1 True 15m
6 1674609300000.0 22766.7 22327.2 0.0192770767267129 2506 1 True 15m
7 1674683100000.0 23189.3 23028.8 0.006920221274614213 2823 1 True 15m
8 1674997200000.0 23675.8 23568.0 0.004549041451979731 3060 1 True 15m
9 1675411200000.0 23465.4 23328.8 0.0058589645159492585 3410 -1 False 3412.0 15m
10 1675982700000.0 21917.8 21697.2 0.010168241530306455 4229 -1 True 15m
11 1676506500000.0 24934.1 24312.0 0.0255994535271775 4583 -1 True 15m
12 1676592000000.0 23924.7 23520.0 0.016862640522004382 4805 1 False 15m
13 1676664900000.0 24799.2 24502.6 0.012131524375529258 4960 -1 True 4963.0 15m
14 1676997000000.0 24468.5 24267.2 0.00830496936691624 5128 -1 True 15m
15 1677153600000.0 24061.6 23711.5 0.014774583159253995 5527 -1 True 15m
16 1677265200000.0 23147.6 23010.1 0.0059242727147387295 5656 1 True 15m
17 1677807900000.0 22437.9 22300.0 0.006184549837425786 6298 -1 True 15m
18 1678316400000.0 21798.0 21550.0 0.01152459199226737 6498 -1 False 6502.0 15m
19 1678668300000.0 22542.0 22105.0 0.019077887549604693 6872 1 False 15m
20 1678770000000.0 24850.0 24147.2 0.028035854618855157 7190 1 False 7198.0 15m
21 1679093100000.0 27600.0 27050.0 0.019922122611609163 7618 1 False 7624.0 15m
22 1679362200000.0 27918.8 27712.0 0.007405232362440979 7852 1 True 15m
23 1679463000000.0 28280.0 27962.1 0.011218350242611433 7836 1 True 7838.0 15m
24 1679515200000.0 27421.8 27101.0 0.011689598880596988 7965 1 True 15m
25 1679584500000.0 28439.0 28078.4 0.012854790066947524 8120 -1 False 8122.0 15m
26 1679693400000.0 27648.4 27302.0 0.012861545316154957 8219 -1 False 8226.0 15m
27 1679872500000.0 27929.2 27757.4 0.006151069992588615 8524 1 True 8531.0 15m
28 1680078600000.0 28649.9 28119.5 0.018879274443570456 9336 -1 False 9351.0 15m
29 1681178400000.0 30158.0 29983.8 0.0057700652196235455 9833 1 True 15m
30 1681490700000.0 30464.0 30270.0 0.006410192867503957 10173 -1 True 15m
31 1681692300000.0 29913.6 29781.6 0.0044362292051756 10221 -1 False 10224.0 15m
32 1681745400000.0 29524.0 29370.0 0.005263049971634211 10401 -1 False 10407.0 15m
33 1682019000000.0 28225.8 27983.0 0.008681319074230974 10625 -1 True 15m
34 1682116200000.0 27387.8 27166.0 0.008089310657976771 10814 1 False 10818.0 15m
35 1682184600000.0 27780.0 27512.0 0.009754712654555778 11020 -1 True 15m
36 1682512200000.0 29899.0 29504.8 0.013419300371737114 11227 -1 True 15m
37 1682539200000.0 29272.6 28858.7 0.014128305081274375 11505 1 True 11509.0 15m
38 1683053100000.0 28820.0 28600.6 0.007682261119845425 11783 -1 True 15m
39 1683535500000.0 27747.9 27550.0 0.00718768613891598 12481 -1 False 12495.0 15m
40 1683940500000.0 26974.1 26674.9 0.011091381566509257 12869 1 False 12871.0 15m
41 1684131300000.0 27550.0 27200.0 0.012876262793486819 12994 -1 False 12999.0 15m
42 1684199700000.0 27149.8 26971.0 0.00663564096283593 13080 -1 True 15m
43 1684309500000.0 26907.5 26700.0 0.007837523418142261 13223 -1 True 13229.0 15m
44 1684358100000.0 27471.5 27139.0 0.012262404390125168 13459 -1 False 13466.0 15m
45 1684431900000.0 26950.0 26733.4 0.008115794324938964 13525 -1 True 15m
46 1684604700000.0 27144.4 26952.8 0.007130257448439686 13592 -1 False 13597.0 15m
47 1685246400000.0 27236.0 27127.8 0.003968588730235025 14175 1 False 14184.0 15m
48 1685512800000.0 27194.9 26942.0 0.009394921021739508 14891 -1 False 14894.0 15m
49 1685991600000.0 25809.9 25585.4 0.008802333696666484 15383 -1 True 15m
50 1686153600000.0 26459.5 26220.0 0.009269903198213367 15377 -1 False 15m
51 1686375900000.0 25743.9 25559.5 0.007223723900184176 15825 -1 False 15827.0 15m
52 1686623400000.0 26155.5 25963.7 0.007388375058263357 15748 -1 True 15m
53 1686671100000.0 25921.1 25790.6 0.005063261671691129 16002 -1 False 15m
54 1686938400000.0 26526.9 26364.3 0.006109566393627496 16300 1 False 16303.0 15m
55 1687365000000.0 30488.0 29800.0 0.022554419092578024 16771 1 True 16776.0 15m
56 1687576500000.0 30765.9 30542.4 0.0073281812011659515 17033 -1 True 15m
57 1687799700000.0 30488.0 30222.5 0.008793458064644337 17118 -1 True 15m
58 1687874400000.0 30738.8 30533.1 0.006738054448196931 17242 -1 True 15m
59 1687939200000.0 30347.7 30126.6 0.007279156131479212 17266 1 False 17270.0 15m
60 1688044500000.0 30614.8 30380.0 0.0077316956715016965 17808 -1 True 15m
61 1688412600000.0 31319.4 30875.2 0.014180140779875844 17891 1 True 15m
62 1688562900000.0 30438.1 30222.7 0.007144302302826803 18698 -1 True 18702.0 15m
63 1689361200000.0 30360.0 30241.0 0.00393525025215364 19236 -1 True 15m
64 1690210800000.0 29159.7 29033.0 0.0043434887093290984 19952 1 True 15m
65 1690403400000.0 29485.9 29366.0 0.004087741548364271 20032 -1 True 15m
66 1690482600000.0 29299.9 29112.8 0.006379569012547811 20316 1 True 15m
67 1690764300000.0 29461.4 29317.4 0.004925788211631017 20605 -1 True 15m
68 1690992900000.0 29229.1 29120.7 0.003727981181260973 20919 -1 True 15m
69 1691611200000.0 29650.0 29461.4 0.006402444199270086 21474 -1 False 15m
70 1692307800000.0 26597.7 26150.0 0.017128122333893204 22144 -1 True 15m
71 1692428400000.0 25955.0 25858.0 0.0037324919193473913 22318 1 True 22320.0 15m
72 1692463500000.0 26153.4 26041.4 0.004277241637419754 22528 1 False 22530.0 15m
73 1692898200000.0 26200.0 25960.0 0.009144424758530034 23310 1 True 15m
74 1693515600000.0 26094.1 25941.7 0.005887943624098758 23571 -1 False 23585.0 15m
75 1693678500000.0 25899.9 25835.5 0.002496888582162812 23821 -1 False 23823.0 15m
76 1693882800000.0 25830.0 25610.0 0.008507182769087991 24206 1 True 15m
77 1694276100000.0 25908.5 25834.3 0.0028800546511718456 24398 -1 True 15m
78 1694491200000.0 25989.6 25868.0 0.0047012066172575475 24511 -1 True 15m
79 1694622600000.0 26294.0 26150.0 0.005473224907734351 24734 1 False 24749.0 15m
80 1694715300000.0 26647.0 26523.2 0.00462956037874139 25292 1 True 25295.0 15m
81 1695303900000.0 26685.7 26484.0 0.007538552388640995 25871 1 True 15m
82 1696869000000.0 27695.1 27482.0 0.007773995965255912 27123 -1 True 15m
83 1697482800000.0 28548.0 28220.7 0.011459121082261969 27992 1 True 15m
84 1697796900000.0 29784.5 29443.5 0.011431559821251974 28167 1 True 15m
85 1697910300000.0 30206.0 29828.5 0.012415598596297357 28325 1 True 15m
86 1698100200000.0 35154.3 34203.5 0.027815402618298922 28847 -1 False 28854.0 15m
87 1698392700000.0 34185.3 33978.6 0.0060332925665283425 29061 1 False 29063.0 15m
88 1698591600000.0 34579.7 34384.6 0.005624942337854003 29261 1 True 15m
89 1698893100000.0 35487.0 35050.0 0.012501001224354354 29568 -1 True 15m
90 1698974100000.0 34706.0 34364.1 0.009832595672967738 29657 1 False 29659.0 15m
91 1699538400000.0 36798.0 36461.8 0.009119489178405014 30386 1 True 15m
92 1699633800000.0 37422.2 37080.0 0.009137492289740137 30604 1 True 15m
93 1699856100000.0 37065.9 36760.0 0.008332425365003308 30680 -1 True 15m
94 1700091900000.0 37645.9 37351.0 0.007895582329317308 31005 -1 True 15m
95 1700163000000.0 36465.4 36143.8 0.008819028919614074 31165 1 False 31168.0 15m
96 1700435700000.0 37445.5 37050.0 0.010554096750779215 31282 1 True 31285.0 15m
97 1700688600000.0 37650.0 37250.2 0.010609868398355786 31485 1 False 31490.0 15m
98 1700860500000.0 37840.0 37708.1 0.0034830876158939037 31843 1 False 31846.0 15m
99 1701199800000.0 38091.9 37862.3 0.006025155549257445 32080 1 True 15m
100 1701686700000.0 42179.0 41630.0 0.013191375867711404 32491 -1 True 15m
101 1702260000000.0 41963.0 40272.1 0.040248503385977685 33473 1 True 15m
102 1702504800000.0 43379.3 42670.5 0.016632055659193103 33571 -1 False 33574.0 15m
103 1702655100000.0 42280.0 41668.0 0.014690138884221543 33690 -1 True 15m
104 1702951200000.0 43297.1 42840.2 0.010702113954441681 33908 -1 True 15m
105 1703085300000.0 44290.0 43511.8 0.017892123051455305 34443 -1 True 15m
106 1703456100000.0 43360.4 43083.2 0.006479133310739055 34486 -1 False 15m
107 1703583900000.0 42630.4 42109.0 0.012226721976911367 34732 1 True 15m
108 1703673900000.0 43277.1 42821.0 0.010674923875926503 34795 -1 True 15m
109 1703780100000.0 42788.7 42320.5 0.01106371917719194 34916 -1 True 15m
110 1703871900000.0 42250.0 41680.0 0.013482667675895591 35038 1 False 35042.0 15m
111 1704015000000.0 42766.7 42381.0 0.009013809333510876 35090 1 True 15m
112 1704499200000.0 44149.9 43712.1 0.009910897006356804 35756 1 False 35760.0 15m
113 1704740400000.0 46980.0 46304.1 0.014605843201659639 35976 -1 True 15m
114 1704836700000.0 46228.4 45266.3 0.02128963731716489 36156 -1 False 36158.0 15m
115 1705609800000.0 41372.0 40739.6 0.015277797909811744 37062 1 True 15m
116 1705899600000.0 41253.7 40666.0 0.014496435707061914 37120 -1 True 15m
117 1706299200000.0 41886.0 41681.8 0.004871857270328364 37614 1 False 37619.0 15m
118 1706418900000.0 42688.0 42259.5 0.01002742623932904 37961 1 True 15m
119 1706580900000.0 43518.0 43200.4 0.007294374879422296 37989 1 True 15m
120 1707502500000.0 47489.9 47020.1 0.009875266430400242 38961 1 False 38965.0 15m
121 1707758100000.0 49783.5 49320.9 0.009279318313113778 39293 1 True 15m
122 1707921000000.0 52100.0 51651.0 0.008705886458953392 40034 -1 True 40039.0 15m
123 1708515900000.0 51459.5 50884.3 0.011174530152969781 40389 1 False 40395.0 15m
124 1708827300000.0 51853.2 51550.0 0.005833136137932836 40476 1 False 15m
125 1708999200000.0 57679.5 56625.3 0.018208134417553822 40638 1 False 15m
126 1709139600000.0 62710.0 61105.2 0.025540028041830438 41064 1 True 15m
127 1709603100000.0 67566.7 65600.0 0.02895096721579337 41444 1 True 15m
128 1709712900000.0 67689.8 66117.4 0.02318421751054243 41519 1 True 15m
129 1709910900000.0 68608.7 67996.0 0.008873652912287366 41668 1 False 41672.0 15m
130 1710065700000.0 69845.9 68672.9 0.017087766732317874 41778 -1 True 15m
131 1710183600000.0 72850.0 72037.1 0.011299003119075206 41961 -1 True 15m
132 1710263700000.0 71730.9 70623.8 0.015688036083474795 42112 -1 True 15m
133 1710444600000.0 70715.9 68600.0 0.03085108399444179 42282 -1 False 42288.0 15m
134 1710630900000.0 66630.7 64750.0 0.02936877512203801 42556 -1 False 42561.0 15m
135 1710714600000.0 68972.4 67350.6 0.024172705620027192 42707 -1 True 15m
136 1710846900000.0 63544.1 62545.0 0.015577980335352995 42877 1 True 15m
137 1710972900000.0 67716.6 66695.6 0.015310077809984089 43106 -1 True 15m
138 1711395900000.0 71228.3 70517.8 0.010076456264208796 43301 -1 True 15m
139 1711463400000.0 70368.0 69888.0 0.006811186806731155 43502 1 True 15m
140 1711637100000.0 70976.9 70455.6 0.007400351491852775 43727 -1 True 15m
141 1711726200000.0 69789.5 69327.0 0.006626083991524344 43670 1 True 15m
142 1711762200000.0 70245.0 69872.3 0.00538488879128242 43799 -1 False 43808.0 15m
143 1711951200000.0 69835.0 69222.0 0.00895004183013025 43881 -1 False 15m
144 1712253600000.0 68787.1 67420.2 0.019798495378082006 44347 1 False 44356.0 15m
145 1712455200000.0 69579.2 69198.3 0.005470186121323446 44709 1 True 15m
146 1712946600000.0 67261.4 65754.6 0.023260588677520812 45007 -1 False 45021.0 15m
147 1713038400000.0 64891.6 62139.3 0.04433222998059059 45368 -1 False 45371.0 15m
148 1713367800000.0 61743.7 60700.0 0.017200207648382024 45512 -1 True 15m
149 1713452400000.0 63815.6 62268.4 0.024230199423060615 45658 1 False 45663.0 15m
150 1713634200000.0 65400.0 64728.3 0.010405580625821968 46046 -1 True 15m
151 1713768300000.0 66449.0 65750.0 0.010501802884615385 46029 1 True 15m
152 1714180500000.0 63157.6 62650.0 0.008022087607644666 46464 1 True 15m
153 1714273200000.0 64041.4 63770.7 0.004249300678444011 46560 -1 True 15m
154 1714373100000.0 62658.7 62150.0 0.008217443207242029 46595 -1 False 46598.0 15m
155 1714506300000.0 60331.8 59555.0 0.013049759768840555 46858 -1 True 15m
156 1714786200000.0 63430.0 62846.7 0.009173114424105223 47198 1 True 15m
157 1715122800000.0 62743.9 62203.6 0.008693665354223064 47407 -1 False 47410.0 15m
158 1715210100000.0 61779.0 60843.6 0.015379809273265398 47585 -1 True 15m
159 1715271300000.0 62637.3 62633.2 6.549144138685029e-05 47850 -1 False 15m
160 1715591700000.0 63289.3 62540.2 0.011987518002880554 48040 -1 True 15m
161 1715817600000.0 66573.0 65633.0 0.01409976030407483 48489 1 True 15m
162 1716319800000.0 70321.8 69242.0 0.01562380177247246 49007 -1 True 15m
163 1716579900000.0 69273.5 68844.1 0.006253221996662145 49354 -1 True 15m
164 1716921000000.0 68710.0 68302.8 0.005967130955957196 49786 -1 False 49793.0 15m
165 1717783200000.0 69613.6 69257.9 0.005108804465069418 50577 1 True 15m
166 1718119800000.0 67605.0 66920.0 0.010248354278874924 50940 -1 False 50943.0 15m
167 1718384400000.0 66648.8 66162.8 0.00736240929542046 51266 -1 False 15m
168 1718977500000.0 64342.1 63444.6 0.014167190208143254 51838 -1 False 51840.0 15m
169 1719261000000.0 61566.0 60600.5 0.01567614433418195 52056 1 True 15m
170 1719341100000.0 62448.1 61605.7 0.013677989218678988 52190 -1 True 15m
171 1719432900000.0 61064.2 60766.1 0.00490861188868761 52297 -1 True 15m
172 1719497700000.0 61997.2 61514.2 0.00786158403593867 52448 -1 True 15m
173 1719604800000.0 61023.0 60706.5 0.005178846090912066 52727 1 True 15m
174 1720058400000.0 58088.0 57611.0 0.008352010085446141 52894 -1 False 52900.0 15m
175 1720121400000.0 58653.4 58111.6 0.009335729584337805 53088 -1 True 15m
176 1720152900000.0 56847.0 56300.0 0.009618427993669774 53149 1 True 15m
177 1720307700000.0 57899.9 57041.7 0.014819803484777917 53668 1 True 15m
178 1721178900000.0 64722.8 63871.2 0.013080609486360374 54295 1 False 15m
179 1721417400000.0 66729.0 66422.9 0.004582541132086858 54399 1 False 54405.0 15m
180 1721495700000.0 67400.0 66799.8 0.008994603537597609 54779 -1 False 54781.0 15m
181 1721763900000.0 66033.1 65450.8 0.008905249249102331 54806 -1 True 15m
182 1721962800000.0 67470.4 67277.0 0.002858359000431476 54996 1 False 55016.0 15m
183 1722030300000.0 68200.0 67864.8 0.004978567800310672 55265 -1 False 15m
184 1723158000000.0 61450.0 60208.5 0.020197862589458715 56710 1 True 15m
185 1723488300000.0 59339.1 58803.0 0.0090165244081907 56793 1 True 15m
186 1723836600000.0 59363.0 58967.6 0.006654224355403595 57457 1 True 15m
187 1724270400000.0 61392.0 60060.3 0.02145875027796304 57656 1 True 15m
188 1724796900000.0 59625.0 58475.2 0.01972537501887108 58473 -1 True 15m
189 1725168600000.0 58286.0 57800.0 0.00842994817142976 58715 -1 True 15m
190 1725411600000.0 56818.0 56107.9 0.01248602114236277 58886 1 True 15m
191 1725516900000.0 56688.9 56500.8 0.0033365261813537417 59302 -1 True 15m
192 1726151400000.0 58321.9 57708.0 0.010524655535687677 59916 1 False 59927.0 15m
193 1726267500000.0 60247.9 59848.6 0.006544067154945137 60060 1 True 60076.0 15m
194 1726586100000.0 60747.7 60166.7 0.00967936479376789 60175 -1 True 15m
195 1726767900000.0 63830.1 63080.1 0.01195219123505976 60567 -1 True 15m
196 1727227800000.0 63971.0 63243.2 0.011338285329710778 60905 1 False 15m
197 1727370900000.0 65350.0 64788.0 0.008588505156923602 61251 1 True 15m
198 1727690400000.0 63845.0 63222.0 0.009757121289004992 61386 1 True 15m
199 1727814600000.0 61858.2 61325.0 0.008698233925722386 61563 -1 False 61567.0 15m
200 1727946000000.0 61047.3 60101.5 0.01547450175801994 61690 1 False 61692.0 15m
201 1728074700000.0 62344.0 61671.9 0.01077775942552824 62017 1 False 62027.0 15m
202 1728371700000.0 62544.6 61941.0 0.00962693244400635 62374 1 True 62378.0 15m
203 1728673200000.0 63400.0 62900.0 0.00796589323153984 62585 -1 True 62589.0 15m
204 1729044000000.0 67548.6 67357.1 0.00284658892431641 62972 -1 True 15m
205 1729232100000.0 68371.0 68173.4 0.0028988781517598774 63189 -1 True 15m
206 1729522800000.0 67784.9 66865.9 0.013544523753026883 63670 1 True 15m
207 1729804500000.0 68216.4 67800.0 0.006153764303733405 63905 -1 True 15m
208 1730556900000.0 69570.0 69221.6 0.00503787807583126 64515 -1 False 64520.0 15m
209 1730601000000.0 68575.8 68183.8 0.005707089457171351 64643 1 False 64650.0 15m
210 1731009600000.0 76350.2 75576.3 0.010125856851079565 65173 1 False 65181.0 15m
211 1731366000000.0 89800.0 87055.0 0.03052319763019895 65734 1 True 15m
212 1731708000000.0 91443.5 90120.0 0.014432933478735005 66065 1 True 15m
213 1732021200000.0 92885.0 91523.2 0.014647152904531098 66176 1 True 66179.0 15m
214 1732167000000.0 97899.9 96757.6 0.011659715544994358 66501 1 True 15m
215 1732302000000.0 98842.8 98355.0 0.004966159259168794 66533 -1 True 15m
216 1732392900000.0 98088.4 97455.6 0.006502314034640527 66681 -1 False 66683.0 15m
217 1732738500000.0 95985.1 94878.2 0.011502900925823059 67212 1 False 67215.0 15m
218 1732893300000.0 97687.4 96903.2 0.008093129818260597 67527 -1 False 67531.0 15m
219 1733160600000.0 96375.0 95171.5 0.012456451582854291 67675 1 True 15m
220 1733436900000.0 98765.4 97236.7 0.015471121820789184 67997 1 True 67999.0 15m
221 1733517000000.0 100499.0 99111.0 0.01379943887584283 68224 1 True 15m
222 1733933700000.0 101080.7 100309.8 0.00768528321599538 68401 -1 True 15m
223 1734309000000.0 105390.0 104245.2 0.01084518143511083 68841 1 True 15m
224 1734637500000.0 97824.8 95946.9 0.019628645551335182 69330 -1 False 69335.0 15m
225 1734897600000.0 95787.7 94390.0 0.014586694663546906 69645 1 True 15m
226 1735207200000.0 96543.5 95341.2 0.01244729080155255 69742 1 True 15m
227 1735822800000.0 96991.8 96300.0 0.007129202627850088 70425 1 True 15m
228 1735931700000.0 98300.0 97762.1 0.005505274480765254 70819 -1 False 70821.0 15m
229 1736319600000.0 95358.7 95222.0 0.0014371305328736731 71120 -1 False 71122.0 15m
230 1736395200000.0 93832.8 93760.0 0.0007777445408316195 71355 -1 True 15m
231 1736972100000.0 100658.9 99507.3 0.011332805203879205 71718 1 False 71726.0 15m
232 1737143100000.0 104962.1 103110.0 0.01761092046322213 72347 1 True 15m
233 1737606600000.0 102955.0 101550.4 0.013856156795741602 72676 -1 True 15m
234 1737671400000.0 104674.2 104310.0 0.0034942022339100437 72946 -1 True 72951.0 15m
235 1737962100000.0 102267.8 98820.6 0.03369463116440596 73145 1 True 15m
236 1738356300000.0 102543.0 102058.8 0.004747314806190501 73210 -1 True 15m
237 1738451700000.0 100470.1 100215.0 0.002521049926868856 73422 1 False 15m
238 1738620000000.0 100371.0 97792.8 0.026403728597280126 74051 -1 True 15m
239 1738961100000.0 96466.0 95714.4 0.007858726940606865 74185 -1 True 74189.0 15m
240 1739182500000.0 97611.0 97090.3 0.0053652811594859665 74280 -1 True 15m
241 1739302200000.0 96437.5 95050.0 0.014350696900870041 74462 1 False 74469.0 15m
242 1739505600000.0 97150.0 96505.0 0.006639005857764703 74629 1 True 15m
243 1739714400000.0 97134.0 96629.5 0.005224968826251403 74745 -1 True 15m
244 1739748600000.0 96605.7 96000.0 0.00631233390651865 74938 -1 True 15m
245 1739905200000.0 95799.8 95020.0 0.008122087536897304 75165 1 True 75167.0 15m
246 1740478500000.0 89442.7 86800.0 0.02887193303310977 76002 1 False 15m
247 1740687300000.0 84894.0 82667.0 0.026988639806486484 76181 -1 True 15m
248 1740880800000.0 86449.9 85732.0 0.00840911966040222 76114 -1 True 15m
249 1740937500000.0 93666.0 91100.0 0.02826084562265273 76356 -1 True 15m
250 1741101300000.0 87850.0 86328.0 0.017720835138017595 76418 -1 True 15m
251 1741381200000.0 86622.9 85555.0 0.012485239615119242 76647 -1 False 76650.0 15m
252 1741538700000.0 82761.0 82176.9 0.0070494608809835435 76874 1 True 76878.0 15m
253 1741743900000.0 82887.4 82061.0 0.01008082715480113 77101 -1 True 15m
254 1741966200000.0 84625.0 83654.7 0.011604113522505654 77617 -1 True 15m
255 1742428800000.0 86285.8 85383.0 0.010583660506932518 78042 -1 True 15m
256 1743248700000.0 82759.4 81963.0 0.009617172763741594 78682 1 True 15m
257 1743304500000.0 83456.0 82818.5 0.007701898472668227 78777 -1 True 15m
258 1743381900000.0 82275.5 81250.0 0.01245510756573394 79062 1 True 15m
259 1743637500000.0 83774.1 82962.1 0.009801460079570473 79328 -1 True 15m
260 1743981300000.0 79311.5 77111.0 0.02771197678767752 79747 1 True 15m
261 1744406100000.0 83998.9 83260.0 0.00878271081358633 79926 1 False 79928.0 15m
262 1744471800000.0 85388.0 84389.5 0.011844616660280737 80326 -1 True 80333.0 15m
263 1744875000000.0 84829.4 84144.1 0.00807755775577544 80557 1 True 15m
264 1745214300000.0 87702.3 86610.0 0.012452390099648793 80836 1 False 80838.0 15m
265 1745396100000.0 94178.0 92901.0 0.013555773995418425 81166 1 True 81168.0 15m
266 1745592300000.0 95157.1 94402.0 0.008008331804351138 81399 -1 False 81401.0 15m
267 1745731800000.0 94140.8 93586.7 0.005882159360765071 81599 1 False 81608.0 15m
268 1745868600000.0 94874.2 94244.5 0.006633427544789036 81716 1 True 15m
269 1746111600000.0 97262.0 96283.1 0.010052413441829645 82275 1 True 15m
270 1746366300000.0 95649.0 95200.1 0.004676041666666606 82266 1 False 15m
271 1746579600000.0 97446.8 96153.2 0.013237131504598161 82371 1 False 82376.0 15m
272 1746738900000.0 104305.3 102327.9 0.01894142946774534 83575 1 False 83581.0 15m
273 1747705500000.0 106371.3 105480.1 0.00846806990338492 83595 -1 True 15m
274 1747809000000.0 107950.0 106085.3 0.017235036841774444 83699 1 False 83705.0 15m
275 1748002500000.0 109760.0 107950.0 0.016471720967226735 84123 1 True 15m
276 1748165400000.0 107698.4 106653.0 0.009700197641294914 84360 1 True 15m
277 1748273400000.0 109863.2 108807.0 0.009713389708147651 84429 -1 False 84435.0 15m
278 1748447100000.0 107789.3 107000.0 0.007378531865666437 84447 -1 False 84452.0 15m
279 1748546100000.0 106266.0 105580.0 0.006507285598693989 84761 -1 True 15m
280 1748623500000.0 103863.6 103650.2 0.0020541352225474426 84711 1 True 15m
281 1748704500000.0 104800.0 104215.0 0.005614311215186472 84844 -1 True 15m
282 1748916000000.0 106470.8 105526.2 0.0089663028001899 85110 -1 False 85112.0 15m
283 1749776400000.0 105459.7 104360.0 0.010410641442058888 86264 1 True 15m
284 1750179600000.0 105288.0 104143.4 0.0110192572254308 86555 -1 True 15m
285 1750440600000.0 103641.0 102903.6 0.007167622641289299 86859 -1 True 15m
286 1750859100000.0 107965.9 107022.6 0.008726462003857556 87707 1 False 87711.0 15m
287 1751476500000.0 109690.4 108706.0 0.009057367622026906 88090 -1 True 15m
288 1751655600000.0 108229.0 107883.1 0.0031886473461124514 88183 1 True 15m
289 1751837400000.0 109667.3 108622.6 0.009622691387658942 88280 -1 True 15m
290 1751898600000.0 108475.0 107903.2 0.005270046082949335 88339 1 True 15m
291 1752000300000.0 109107.4 108570.1 0.0049243385869997495 88390 1 False 88393.0 15m
292 1752226200000.0 118128.0 117055.0 0.009073013661100532 88906 1 True 15m
293 1752478200000.0 120292.6 119200.0 0.009181589761258546 89054 -1 True 15m
294 1752590700000.0 117727.4 116300.0 0.012099235088290292 89138 1 True 15m
295 1752660000000.0 119299.9 118359.4 0.00795212995327648 89324 -1 True 15m
296 1752870600000.0 118340.7 117904.0 0.0036844485392135773 89696 1 True 15m
297 1753188300000.0 119475.2 118377.0 0.009284779454783386 90106 -1 True 15m
298 1753559100000.0 118289.3 117852.0 0.003693337297216973 90205 1 True 15m
299 1753633800000.0 119550.0 118949.9 0.005048958321063654 90271 -1 True 15m
300 1753727400000.0 118329.8 117406.3 0.0078043858366665005 90479 1 True 15m
301 1754009100000.0 115956.0 114239.0 0.015049126589711902 90600 -1 False 90604.0 15m
302 1754075700000.0 114000.0 113113.0 0.007757951900698216 90945 1 True 90948.0 15m
303 1754269200000.0 114739.3 114166.0 0.004996030539140826 91135 1 False 91144.0 15m
304 1754566200000.0 116747.6 116139.8 0.005205754937244898 91401 1 False 91403.0 15m
305 1754798400000.0 118371.0 117966.9 0.0034107598035078733 91625 1 True 15m
306 1754887500000.0 120799.0 119345.0 0.012195725456941288 91655 -1 True 15m
307 1754948700000.0 119114.5 118377.2 0.006185631013225345 91704 1 False 91712.0 15m
308 1755014400000.0 120163.6 119585.4 0.0048043047634242315 91720 1 False 91724.0 15m
309 1755195300000.0 117853.5 117004.0 0.007285722984719279 92165 -1 False 92167.0 15m
310 1755508500000.0 116761.0 116171.5 0.005087378640776699 92257 -1 True 15m
311 1755630000000.0 113524.5 112714.4 0.00713285728122006 92486 1 True 15m
312 1755804600000.0 112700.0 112210.3 0.0043683509096196236 92816 -1 True 15m
313 1756061100000.0 112897.7 110532.2 0.021406542411287956 93058 -1 True 15m
314 1756169100000.0 110508.5 109529.5 0.008939635091218064 93252 -1 True 15m
315 1756823400000.0 111487.9 110486.9 0.0089699841569111 93740 1 True 15m
316 1756909800000.0 112207.1 111724.1 0.004300983349050177 93916 1 True 93918.0 15m
317 1756968300000.0 111111.0 110237.2 0.007859334286143476 93962 1 True 93971.0 15m
318 1757058300000.0 112949.0 112080.0 0.007758547571324622 94219 -1 True 15m
319 1757093400000.0 111256.2 110504.9 0.006752414754471153 94281 1 False 94287.0 15m
320 1757344500000.0 112460.4 111847.0 0.005452439597831416 94399 1 True 15m
321 1757511900000.0 114287.9 113722.2 0.004947356177286214 94513 1 True 15m
322 1757637900000.0 115613.0 114993.0 0.0053600482750154315 94734 1 False 94752.0 15m
323 1757706300000.0 116245.4 115701.4 0.004704685832495888 94833 -1 True 15m
324 1757860200000.0 115605.5 115275.5 0.0028516617410691138 95100 1 True 15m
325 1758311100000.0 115670.0 115355.0 0.0027348854169832043 95512 -1 True 15m
326 1758596400000.0 112466.6 111413.5 0.009479308625261766 95857 -1 True 15m
327 1759177800000.0 114377.2 113960.6 0.003641996786370498 96396 1 True 15m
328 1759509000000.0 122930.3 121636.4 0.010649558017415996 97024 -1 True 15m
329 1759776300000.0 125098.0 123261.6 0.014906739528185898 97132 -1 True 15m
330 1760130900000.0 113300.1 110987.0 0.02039125179947447 97646 1 True 15m
331 1760646600000.0 107505.0 107350.0 0.0014386165520723505 98153 1 True 98156.0 15m
332 1760778000000.0 107248.0 106568.0 0.00633282143026772 98488 1 True 15m
333 1760894100000.0 109340.7 108556.4 0.007123498188925771 98223 1 False 15m
334 1760949000000.0 111552.3 110552.3 0.009051821679112922 98569 -1 False 98576.0 15m
335 1761396300000.0 111686.0 111291.0 0.0035345838899930116 98816 1 True 15m
336 1761685200000.0 113233.0 112121.4 0.009810748577061998 99124 1 True 15m
337 1761761700000.0 110957.9 109670.6 0.01174391639039457 99559 -1 False 99562.0 15m
338 1762291800000.0 101859.5 98909.7 0.028924825654824033 99903 1 True 15m
339 1762370100000.0 104062.9 103240.5 0.007973249283289763 100017 -1 True 15m
340 1762446600000.0 102298.0 100500.0 0.017566811037584965 100516 1 True 15m
341 1763064900000.0 99862.8 98657.0 0.012362095179316024 100626 -1 False 15m
342 1763123400000.0 96800.0 94500.0 0.024515577517723167 100950 -1 True 15m
343 1763409600000.0 92370.9 91250.0 0.0123628767374093 101240 -1 True 15m
344 1763913600000.0 87472.0 86546.2 0.010578878807962188 101824 1 False 101828.0 15m
345 1764235800000.0 91800.0 90612.2 0.013161962615061936 102401 -1 True 15m
346 1764703800000.0 92273.2 91733.2 0.005830727502029957 102588 1 True 102593.0 15m
347 1764875700000.0 92455.4 91750.6 0.007719402775830519 102661 -1 False 15m
348 1764952200000.0 89762.0 88856.7 0.010083548767597235 103214 1 False 103218.0 15m
349 1765299600000.0 93278.9 91900.0 0.015066487472820382 103299 -1 True 15m
350 1765422000000.0 90494.7 89321.0 0.012969519364220581 103369 1 True 15m
351 1765557000000.0 90599.0 89843.0 0.00852512996312543 103838 -1 False 15m
352 1765823400000.0 86478.6 85226.0 0.01446503839713616 103799 1 True 103806.0 15m
353 1765899900000.0 87900.0 87015.8 0.009965343326477102 103926 1 True 103929.0 15m
354 1766157300000.0 88551.8 87698.5 0.009734888846804164 104535 -1 True 15m
355 1766545200000.0 87430.0 86817.9 0.0069980198382030125 104578 1 True 15m
356 1766613600000.0 87933.5 87617.1 0.0036187833613738818 105186 -1 True 15m
357 1767150000000.0 88780.0 88280.1 0.005671980484483964 105288 -1 True 15m
358 1767311100000.0 88881.4 88533.6 0.0039060832923406663 105344 1 False 105348.0 15m
359 1767348900000.0 89900.0 89510.9 0.004327434779852525 105406 1 False 105412.0 15m
360 1767393900000.0 90368.7 89914.5 0.005022330684317402 105493 1 False 105502.0 15m
361 1767506400000.0 91408.9 90968.0 0.004815426840789409 105856 1 True 15m
362 1767576600000.0 93186.0 92496.9 0.007451501978849086 105765 -1 True 105767.0 15m
363 1767779100000.0 91666.0 91549.9 0.0012730291075200365 106046 -1 True 15m
364 1768780800000.0 93312.8 92660.0 0.007055154055903049 107035 -1 True 15m
365 1768948200000.0 90088.0 88109.7 0.02247692714966935 107650 -1 False 107653.0 15m
366 1769382000000.0 87922.0 87028.5 0.010149234065077775 107916 1 True 107923.0 15m
367 1769736600000.0 83300.0 82108.4 0.014608562608650948 108153 -1 False 108162.0 15m
368 1769883300000.0 79094.9 78245.7 0.010859321151763196 108434 -1 True 15m
369 1770144300000.0 76920.7 75377.3 0.020488109223896964 108534 -1 False 108542.0 15m
370 1770399000000.0 71451.6 69674.5 0.02555250406200132 109128 -1 False 109131.0 15m
371 1770705900000.0 69376.3 68633.3 0.010686834050583389 109472 1 False 109475.0 15m
372 1770828300000.0 68021.0 66757.4 0.018489416433281034 109404 1 False 109408.0 15m
373 1771000200000.0 69082.8 68689.1 0.005695075502785294 109594 1 True 15m
374 1771069500000.0 70230.6 69212.2 0.014851088093026502 109685 -1 False 15m
375 1771182000000.0 68869.8 68112.2 0.011125322885476518 110054 -1 True 15m
376 1771443000000.0 67042.3 66651.8 0.0059668970912713695 110308 -1 False 15m
377 1771628400000.0 68079.0 67745.3 0.004894691525891072 110163 1 False 110170.0 15m
378 1771686900000.0 68632.5 68156.4 0.006927055673973066 110566 1 True 15m
379 1771772400000.0 67634.4 67288.9 0.005093879235991832 110561 1 False 15m
380 1771811100000.0 65980.0 65534.6 0.00683070165752637 110368 -1 True 15m
381 1771877700000.0 64719.1 63860.0 0.013598991987184537 110811 -1 True 15m
382 1772044200000.0 68850.0 67750.0 0.016239877758011056 110889 -1 True 15m
383 1772213400000.0 65994.7 65080.0 0.013784196773289325 110952 1 True 15m
384 1772342100000.0 67219.1 66100.0 0.01652818105824084 111120 1 True 15m
385 1772469900000.0 69500.0 68519.8 0.014477897647378515 111138 -1 True 111140.0 15m
386 1772530200000.0 68000.0 66600.5 0.02049273346267892 111432 1 True 111436.0 15m
387 1772732700000.0 71330.4 70612.8 0.010174119795439954 111402 -1 False 111407.0 15m
388 1772818200000.0 68215.6 67712.3 0.007447293346117921 111672 -1 True 111674.0 15m
389 1773110700000.0 70555.0 69719.4 0.011837152736952086 112030 1 True 15m
390 1773361800000.0 71979.2 71201.0 0.010797281116412743 112310 1 True 15m
391 1773615600000.0 73199.9 72841.9 0.004923114158491272 112560 -1 False 112563.0 15m
392 1773846000000.0 71333.1 70821.2 0.007163217528683078 112621 1 True 15m
393 1773904500000.0 70535.0 69421.1 0.01575962852607355 113042 1 True 113045.0 15m
394 1774179900000.0 68434.3 68030.0 0.00590589713250658 113355 1 True 15m
395 1774632600000.0 66148.0 65874.4 0.0041569996672577905 113664 -1 False 15m
396 1774710900000.0 67100.0 66375.5 0.010779822315199326 113677 1 False 113681.0 15m
397 1774859400000.0 67920.0 67333.3 0.008718044732916036 114205 -1 True 15m
398 1775104200000.0 66898.5 66171.8 0.010850625101719899 114277 1 True 15m
399 1775466900000.0 69955.3 69283.0 0.00970632694281746 114514 -1 True 15m
400 1775603700000.0 71945.0 71367.0 0.008018311715336061 114764 1 False 114773.0 15m
401 1775851200000.0 73066.2 72615.0 0.006157860689320057 115087 1 True 15m
402 1776121200000.0 74476.1 74112.2 0.004918491446366395 115672 -1 True 115679.0 15m
403 1776537000000.0 75843.3 75395.9 0.0059422772974194625 115738 -1 True 15m
404 1776716100000.0 76232.3 75556.2 0.008862445306089354 115871 1 True 15m
405 1776872700000.0 78310.1 77410.7 0.011636271425022883 116592 -1 False 116595.0 15m
406 1777386600000.0 76448.0 76150.0 0.0039236649664118484 116606 -1 False 116611.0 15m
407 1777437000000.0 77432.1 76888.0 0.0070188972294642216 116784 1 False 116786.0 15m
408 1777916700000.0 80363.2 79846.5 0.006473436609307329 117373 -1 True 15m
409 1777959000000.0 81278.2 80651.2 0.007776319803942483 117597 -1 True 15m
410 1778351400000.0 80809.4 80619.6 0.002356347239671904 117878 -1 True 15m
411 1778643900000.0 81268.0 80847.0 0.005165301930915007 118048 1 True 118055.0 15m
412 1778688000000.0 79659.9 79188.0 0.0059697980591514035 118137 -1 True 15m
413 1779084000000.0 77193.5 76627.0 0.007320625296411019 118617 1 True 118619.0 15m
414 1779272100000.0 77639.3 77286.1 0.0045762736020792385 118992 -1 False 118995.0 15m
415 1779570000000.0 76983.1 76611.0 0.00485707460785102 119018 -1 True 15m
416 1779701400000.0 77699.0 77244.8 0.005880555584326767 119180 -1 True 119185.0 15m
417 1779765300000.0 77045.9 76606.0 0.005708420276765524 119186 1 True 15m
418 1779940800000.0 73592.1 72667.9 0.012549716130907194 119676 1 True 15m
419 1780166700000.0 74108.8 73750.0 0.004878314072059863 119710 -1 True 15m
420 1780245000000.0 73704.0 73417.2 0.0038886817396020868 119722 1 True 15m
421 1780428600000.0 67493.8 66388.0 0.016683337934905153 119966 -1 False 119969.0 15m
422 1780538400000.0 64463.7 62150.8 0.03733741861850411 120146 -1 False 120151.0 15m
423 1780685100000.0 61475.0 59451.3 0.032875915634132946 120368 1 False 120370.0 15m
424 1780821900000.0 62267.2 61509.1 0.012361038190243544 120538 -1 True 15m
425 1780870500000.0 64179.5 62381.2 0.028860582347267495 120640 -1 False 120645.0 15m
426 1781277300000.0 63828.4 63360.0 0.007336196392044854 120898 1 True 15m
427 1781364600000.0 64323.0 64186.5 0.002121677010813558 121329 1 True 15m
428 1781733600000.0 64631.5 63881.2 0.011750978856695425 121398 -1 True 15m
429 1781915400000.0 63757.2 63360.0 0.0062194663660277635 121718 1 True 15m
430 1782202500000.0 63053.5 62402.8 0.010436330684287719 121964 -1 True 15m
431 1782323100000.0 61256.0 60648.0 0.009918806088972344 122062 1 True 15m
432 1783000800000.0 61847.2 61229.0 0.00997225448444956 122826 1 True 122832.0 15m
433 1783112400000.0 62735.2 62404.0 0.005276632896428508 123283 1 True 15m
434 1783371600000.0 63424.2 62958.4 0.007401957434771968 123392 -1 True 15m
435 1783583100000.0 63286.5 62897.0 0.006202584861552573 123758 -1 True 123763.0 15m
436 1783661400000.0 64185.0 63765.5 0.00657892673318257 123892 -1 True 15m
437 1783917000000.0 62965.0 62434.9 0.008503191303357461 123849 -1 True 15m
438 1784043000000.0 64950.0 64456.7 0.0076547673543492034 124298 -1 True 15m
439 1784313900000.0 64237.7 64000.0 0.0037260732642878072 124442 -1 True 15m
440 1784428200000.0 64928.0 64259.5 0.010279554680772542 124760 1 True 15m
441 1784570400000.0 65634.4 65145.1 0.00744466708964176 124535 1 False 124538.0 15m
442 1784642400000.0 66467.4 66172.0 0.004466486233122067 124676 -1 False 124681.0 15m
443 1784705400000.0 66077.3 65668.6 0.006228882077980622 124829 -1 False 124832.0 15m
444 1784828700000.0 65193.5 64733.2 0.0071538362310799904 124852 -1 False 15m
445 1784864700000.0 65440.0 65230.1 0.0032194683505861682 125088 -1 True 125093.0 15m
446 1784902500000.0 64305.8 63760.5 0.008553001000699594 125179 -1 True 15m
447 1785083400000.0 64909.0 64872.0 0.0005703627198594133 125155 -1 True 15m
448 1785132000000.0 65412.8 65066.3 0.005346949923846471 125478 -1 False 15m
449 1785200400000.0 63642.2 63021.0 0.009734464737637952 125278 1 False 125290.0 15m
450 1785257100000.0 64047.9 63646.6 0.006313719320327296 125751 -1 True 15m
451 1785784500000.0 64058.8 63428.8 0.009827641880729866 125966 1 True 125973.0 15m
452 1785895200000.0 64345.8 64080.0 0.004127931716741309 126010 1 False 126017.0 15m
453 1569513600000.0 8128.78 7851.94 0.03388453017710923 599 1 True 1h
454 1569898800000.0 8386.0 8149.35 0.029151376514079213 1059 -1 False 1h
455 1571227200000.0 8108.54 7900.24 0.027581111374460947 1075 -1 False 1h
456 1574431200000.0 7324.92 7073.38 0.034308924903363784 2003 1 True 1h
457 1574877600000.0 7669.2 7416.0 0.03521435276937517 2091 -1 True 2106.0 1h
458 1575266400000.0 7349.65 7231.35 0.016092523166159625 2212 1 True 1h
459 1575691200000.0 7559.39 7376.0 0.02519979553249499 2430 -1 False 2432.0 1h
460 1576112400000.0 7136.19 7080.0 0.00787045337397184 2771 1 True 1h
461 1577556000000.0 7359.15 7283.6 0.010416810291339677 2805 -1 True 1h
462 1578441600000.0 8261.33 7956.28 0.036853582366337075 3055 1 False 3058.0 1h
463 1579093200000.0 8921.2 8832.0 0.009984284825230323 3386 1 True 1h
464 1580414400000.0 9477.76 9220.0 0.02713186046291318 3598 1 False 3601.0 1h
465 1582765200000.0 8809.05 8533.9 0.03275595238095234 4366 -1 False 1h
466 1584698400000.0 6408.91 5670.0 0.11470106565456646 4732 1 False 4735.0 1h
467 1585036800000.0 6777.5 6522.34 0.039538357599310736 4901 -1 False 4906.0 1h
468 1585846800000.0 7049.11 6720.05 0.04635966852541139 5056 1 False 5062.0 1h
469 1586217600000.0 7390.0 7152.72 0.033330992147663226 5364 -1 True 1h
470 1586530800000.0 6908.0 6757.29 0.021752716744150472 5437 1 False 5442.0 1h
471 1587247200000.0 7208.55 7055.0 0.021821401316247626 5461 -1 True 1h
472 1588226400000.0 9065.0 8656.0 0.045041026979465035 5776 1 False 5778.0 1h
473 1588892400000.0 9804.9 9528.23 0.02917392051643545 5985 -1 True 5987.0 1h
474 1589068800000.0 9164.0 8457.12 0.07709918252267277 6163 1 True 1h
475 1589763600000.0 9838.0 9450.0 0.041081284827770945 6311 -1 True 1h
476 1590084000000.0 9294.44 9076.9 0.02399832318418508 6292 -1 True 1h
477 1590706800000.0 9621.65 9371.76 0.02751180500737084 6647 -1 True 1h
478 1591606800000.0 9816.0 9622.23 0.020149973118810744 6927 -1 True 1h
479 1591894800000.0 9460.0 9361.0 0.010582248118444217 6956 -1 False 6964.0 1h
480 1593054000000.0 9188.0 9005.0 0.019793305968817433 7342 1 False 1h
481 1594080000000.0 9380.0 9201.23 0.018816943985988124 7637 1 False 1h
482 1599174000000.0 10348.97 9901.16 0.0432061780174422 9161 1 False 9166.0 1h
483 1600171200000.0 10923.42 10740.0 0.01710222547114571 9174 -1 False 9178.0 1h
484 1600696800000.0 10531.94 10374.12 0.01491610517829478 9337 1 True 1h
485 1602298800000.0 11429.09 11303.77 0.011096176901322627 9703 -1 True 1h
486 1604134800000.0 13855.0 13605.9 0.017771848279355504 10134 1 True 1h
487 1604624400000.0 15869.0 14822.0 0.06596322691854077 10320 1 True 1h
488 1605229200000.0 16355.0 15955.6 0.024379658550490714 10433 1 True 1h
489 1605672000000.0 18200.0 17715.7 0.02744498677053029 10670 -1 False 1h
490 1606230000000.0 19385.81 18635.01 0.038377198587179466 10773 1 True 1h
491 1606816800000.0 19640.0 18510.0 0.05703714733502762 11153 1 False 11155.0 1h
492 1609063200000.0 27538.82 25913.01 0.058765654147926796 11479 1 False 11482.0 1h
493 1610038800000.0 40565.62 38655.0 0.049661953431164294 11867 -1 True 1h
494 1612530000000.0 38411.82 37403.27 0.026114220553112497 12443 1 False 12445.0 1h
495 1612854000000.0 48266.36 46252.1 0.04148373991693206 12623 1 True 1h
496 1615402800000.0 57500.92 55074.3 0.04215233986463967 13442 1 True 1h
497 1616180400000.0 59550.0 58024.0 0.02635887697365546 13626 -1 True 1h
498 1618714800000.0 57060.0 53330.82 0.07006387736087806 14297 -1 True 1h
499 1627261200000.0 40894.0 39365.33 0.038940984158628164 16651 -1 True 1h
500 1627689600000.0 42494.0 41030.19 0.03443702159203906 16751 1 True 16754.0 1h
501 1628402400000.0 45350.0 44670.05 0.014839732201705933 16909 1 False 1h
502 1628924400000.0 48074.23 45480.0 0.05337919614024857 17085 1 False 1h
503 1629565200000.0 49833.0 48801.01 0.020641641234398066 17389 1 True 17396.0 1h
504 1630328400000.0 48188.0 47400.0 0.016899658918559084 17520 -1 False 17522.0 1h
505 1631026800000.0 48150.0 47038.21 0.023025505771443203 17790 1 True 1h
506 1634360400000.0 61445.0 60220.0 0.0199275547979289 18851 1 False 18853.0 1h
507 1635865200000.0 63277.12 61623.56 0.027132587475422063 19186 -1 False 19188.0 1h
508 1637139600000.0 58980.0 58336.68 0.01103969671208137 19341 -1 False 19344.0 1h
509 1637611200000.0 57888.0 55900.0 0.03389252013561782 19512 1 True 19516.0 1h
510 1637928000000.0 55238.0 53958.93 0.02373731890613879 19610 -1 False 19613.0 1h
511 1638594000000.0 50847.53 48533.33 0.04779865328502652 19844 -1 False 1h
512 1639422000000.0 47958.62 46435.25 0.03175370446336564 20217 1 True 1h
513 1640822400000.0 47911.68 46803.81 0.023942593274151363 20394 -1 False 20399.0 1h
514 1644300000000.0 44325.52 43282.32 0.023427113650246065 21390 1 True 1h
515 1644645600000.0 42980.0 41711.0 0.03075245365321701 21433 -1 False 1h
516 1647493200000.0 41498.4 40430.0 0.025550089798378164 22209 1 False 1h
517 1648213200000.0 45142.0 44206.2 0.020703494018818606 22457 1 True 1h
518 1649710800000.0 40700.0 39224.0 0.03625947635027244 22948 1 True 1h
519 1650643200000.0 40000.0 39133.0 0.022234988177243887 23276 -1 False 23278.0 1h
520 1652140800000.0 32197.6 30129.6 0.06909224549797868 23471 -1 False 23478.0 1h
521 1652331600000.0 29959.8 27968.6 0.06635364841763612 24129 1 True 24132.0 1h
522 1655172000000.0 22793.0 20823.0 0.09475253715549997 24378 -1 True 24380.0 1h
523 1655582400000.0 20821.0 19763.0 0.05075362902838941 24554 1 True 1h
524 1656140400000.0 21627.6 20894.0 0.03513090283930095 24790 -1 True 1h
525 1656615600000.0 19375.3 18959.0 0.021439754444513074 24919 1 True 24921.0 1h
526 1657242000000.0 22048.0 21175.2 0.04126557860695573 25016 -1 True 1h
527 1657911600000.0 21200.0 20742.0 0.02208548723092354 25246 -1 True 1h
528 1658156400000.0 22800.0 21682.3 0.048884709587123897 25277 1 True 1h
529 1659074400000.0 24500.0 23621.8 0.03742579405159155 25576 -1 False 1h
530 1659625200000.0 23341.8 22746.9 0.025432967521557433 25779 1 True 1h
531 1660950000000.0 21368.9 20760.0 0.028155142278490447 25987 1 True 1h
532 1661727600000.0 20440.0 20069.3 0.018757273693265228 26240 -1 False 1h
533 1662274800000.0 20023.2 19620.2 0.01995899244232693 26452 1 True 1h
534 1663178400000.0 20068.8 19600.0 0.024686287209786013 26618 -1 False 1h
535 1663693200000.0 19476.2 18690.1 0.0402302968270216 27003 1 True 27008.0 1h
536 1664913600000.0 20443.9 19707.0 0.03753336185644732 27158 -1 True 1h
537 1665158400000.0 19550.0 19304.4 0.012746853509796213 27065 -1 True 1h
538 1668031200000.0 17460.0 16880.0 0.034448555833387774 28282 -1 True 1h
539 1668794400000.0 16800.0 16541.1 0.01538690122429582 28456 1 True 1h
540 1669896000000.0 17160.0 16851.2 0.018353313165292703 28775 -1 True 1h
541 1671231600000.0 16795.0 16663.3 0.007908437468099078 29109 -1 True 1h
542 1674320400000.0 23193.1 22310.5 0.037862603279194815 30146 1 False 1h
543 1676505600000.0 24934.1 23827.0 0.04661611079063377 30472 -1 False 30476.0 1h
544 1677355200000.0 23515.0 23088.0 0.01778499729268191 30766 1 False 1h
545 1681236000000.0 30564.0 29925.0 0.021416075127189366 31820 -1 False 31824.0 1h
546 1682114400000.0 27782.3 27279.7 0.01796584844487811 31948 1 False 1h
547 1683871200000.0 27200.0 26809.0 0.014374418681597436 32670 1 True 1h
548 1685548800000.0 27290.0 26826.0 0.017317956182585005 32827 -1 False 32830.0 1h
549 1686171600000.0 26199.0 26111.0 0.003350644999162339 33057 1 False 33060.0 1h
550 1687532400000.0 31053.2 30222.0 0.02751343067191428 33859 -1 False 33863.0 1h
551 1689696000000.0 30178.5 29811.0 0.012355391489404621 33951 -1 False 33954.0 1h
552 1690210800000.0 29379.5 29145.2 0.007969198012292199 34378 1 True 1h
553 1691769600000.0 29470.0 29220.0 0.008569744004607093 34490 -1 True 1h
554 1692306000000.0 26129.4 25797.4 0.012915775141023147 34893 -1 True 1h
555 1693587600000.0 26135.2 25531.2 0.023073866935607104 35462 1 True 1h
556 1694714400000.0 26880.0 26400.0 0.01785056842903841 35865 1 True 1h
557 1696251600000.0 27890.0 27533.1 0.012752577135404623 35972 1 False 35975.0 1h
558 1697461200000.0 29100.0 28126.6 0.03330960756669455 36058 1 False 36061.0 1h
559 1699538400000.0 37570.0 36588.0 0.026093426157198278 36904 1 False 36910.0 1h
560 1700838000000.0 37886.5 37570.9 0.008261239447680088 37008 1 True 37011.0 1h
561 1701198000000.0 38132.0 37570.9 0.014689212290663634 37069 1 False 37072.0 1h
562 1701813600000.0 44528.1 43091.5 0.033750972049609154 37388 -1 True 1h
563 1702321200000.0 42125.0 40556.0 0.037182914344215674 37681 1 False 37685.0 1h
564 1703084400000.0 44332.8 43317.2 0.022479072505854514 37831 1 False 1h
565 1703811600000.0 42951.0 42123.1 0.019203915474008987 38100 1 True 38103.0 1h
566 1704394800000.0 44336.7 43391.3 0.021807780584293373 38508 -1 True 1h
567 1705680000000.0 41826.5 41544.0 0.006823704462340398 38422 -1 True 1h
568 1706292000000.0 42239.3 41600.0 0.015120767462322325 38488 1 True 1h
569 1706612400000.0 43748.6 42341.0 0.03190867174146628 38715 1 False 1h
570 1709600400000.0 67729.5 65702.8 0.029497121167135276 39589 1 True 1h
571 1709928000000.0 69447.0 67996.0 0.021503527853070588 39587 -1 True 1h
572 1710248400000.0 73370.0 70623.8 0.039284968371180104 39844 -1 True 1h
573 1710493200000.0 69044.4 65580.0 0.05348021198195088 40298 -1 False 40303.0 1h
574 1713038400000.0 64483.3 61521.0 0.04591467431317089 40567 1 True 1h
575 1716249600000.0 69900.0 68736.8 0.016501163252567626 41542 1 False 1h
576 1717171200000.0 68545.9 67296.3 0.018621590971747093 41704 -1 True 41706.0 1h
577 1718384400000.0 66482.6 65801.0 0.010375013509132261 41848 -1 True 1h
578 1718737200000.0 65711.9 64600.0 0.017324873713372112 41942 -1 False 1h
579 1719259200000.0 62379.7 60605.0 0.029335775364031685 42231 -1 False 42235.0 1h
580 1719799200000.0 63800.0 62580.0 0.019496604075109867 42515 -1 True 1h
581 1721088000000.0 64988.8 63206.4 0.027377734086999284 42620 1 False 42622.0 1h
582 1722837600000.0 57699.0 54613.0 0.053052769110429186 43281 1 False 43285.0 1h
583 1725033600000.0 59436.0 57525.0 0.03324640439527556 43900 -1 True 1h
584 1725915600000.0 58033.2 56361.0 0.028771160705222017 43928 1 True 1h
585 1726765200000.0 63673.8 62669.2 0.0157474637272238 44370 1 True 1h
586 1727812800000.0 61452.6 60413.3 0.01721020192552892 44595 -1 True 1h
587 1728068400000.0 62344.0 61671.9 0.01068861323155214 44640 1 False 44651.0 1h
588 1728673200000.0 63400.0 62458.0 0.014654271206690562 44698 1 False 1h
589 1729076400000.0 68400.0 68000.0 0.005882993149254477 45030 -1 True 1h
590 1731394800000.0 90070.1 86714.0 0.037138557216683146 45549 1 False 45555.0 1h
591 1731913200000.0 92414.9 91500.0 0.009899372430209849 45738 1 True 1h
592 1732258800000.0 99537.6 97199.9 0.024079792833642817 45862 -1 True 1h
593 1732964400000.0 97128.4 96150.0 0.01005042676306195 46062 1 True 46064.0 1h
594 1733511600000.0 100497.5 98876.9 0.016045544554455504 46149 1 True 1h
595 1734573600000.0 99485.0 98744.0 0.0075248441717492365 46444 -1 True 1h
596 1734919200000.0 96499.0 93655.3 0.029466928069899083 46755 1 True 1h
597 1735930800000.0 98760.9 97438.2 0.013308836032765444 46945 1 True 46961.0 1h
598 1740495600000.0 87066.0 86020.4 0.012175128085701046 48178 -1 True 1h
599 1741561200000.0 83741.8 79933.4 0.0453941353790335 48687 1 True 1h
600 1742824800000.0 88500.0 86260.0 0.02597233945847672 48793 -1 True 1h
601 1743267600000.0 83490.0 81601.0 0.0225654625382263 48800 1 True 1h
602 1743541200000.0 85435.2 83846.0 0.019255001550868452 48833 -1 True 1h
603 1743634800000.0 83774.1 82137.3 0.019385578541320524 48840 1 True 1h
604 1745409600000.0 94904.3 92700.0 0.023208385889031874 49478 1 False 49481.0 1h
605 1746774000000.0 104373.0 102700.0 0.015954773509926197 50185 1 True 1h
606 1747933200000.0 109440.2 106721.9 0.024726249320057733 50428 1 True 1h
607 1748660400000.0 104859.3 103727.1 0.010916464268875515 50504 -1 True 1h
608 1749326400000.0 105878.3 105253.2 0.00602600697748182 50503 -1 True 1h
609 1749502800000.0 110265.5 108282.0 0.018426205994485606 50597 -1 False 1h
610 1749776400000.0 106099.4 104225.4 0.017652583221002993 50783 1 True 1h
611 1750906800000.0 108249.9 107233.1 0.009354590308872493 50974 1 False 1h
612 1752224400000.0 118882.8 116862.4 0.017291579298717157 51848 -1 True 1h
613 1754175600000.0 115674.2 112582.4 0.026630628582059883 52102 1 True 52104.0 1h
614 1754604000000.0 116941.5 116304.0 0.005438004614839995 52035 1 False 52037.0 1h
615 1754884800000.0 122450.0 118050.0 0.03735953028881464 52079 -1 False 52087.0 1h
616 1755644400000.0 114569.0 112666.6 0.016599349602859814 52249 1 True 1h
617 1756170000000.0 112330.0 110306.9 0.017981944282726595 52350 1 True 1h
618 1756918800000.0 112541.2 109850.0 0.023734433978904266 52651 1 False 1h
619 1757703600000.0 116118.6 115100.5 0.008748637993201188 53152 1 False 53156.0 1h
620 1758596400000.0 113236.0 111405.1 0.016447755535811927 53010 -1 True 1h
621 1762290000000.0 104070.7 99200.0 0.049228327993450616 54199 -1 True 1h
622 1763438400000.0 93150.0 89012.0 0.0466446406570379 54460 -1 False 54462.0 1h
623 1763708400000.0 85599.9 83456.0 0.024945545430004868 54620 1 True 1h
624 1763935200000.0 88100.0 86061.1 0.02377235252898229 54611 -1 True 1h
625 1764234000000.0 91931.9 90092.7 0.02000593912720933 54653 1 True 1h
626 1767373200000.0 90945.1 89261.2 0.01847676167841293 55814 1 True 1h
627 1768946400000.0 90088.0 88109.7 0.022473505619228437 56031 -1 False 56038.0 1h
628 1770336000000.0 71524.9 67250.0 0.06408588158750804 56623 -1 False 1h
629 1773154800000.0 71286.0 69329.7 0.02844406059575066 57653 -1 False 57665.0 1h
630 1776175200000.0 75500.0 74508.2 0.013133205770771438 58202 1 True 58205.0 1h
631 1776870000000.0 78459.1 77206.8 0.01591408453031025 58244 1 True 1h
632 1778925600000.0 78180.0 77601.8 0.007452260166625386 58822 -1 True 1h
633 1779519600000.0 77600.0 76056.0 0.020339983348614667 58848 -1 True 1h
634 1780686000000.0 62942.4 61150.2 0.028419154783066555 59403 1 True 1h
635 1781377200000.0 64738.0 63650.0 0.017102319973717757 59814 -1 True 1h
636 1782201600000.0 61931.1 61870.0 0.0009907604694680142 59733 -1 True 59738.0 1h
637 1783191600000.0 63450.0 62410.1 0.016318146582431848 59912 1 False 1h
638 1783677600000.0 64438.9 63619.9 0.012701100762066046 60295 1 True 1h
639 1784642400000.0 65780.0 64636.0 0.017707059606544184 60557 -1 True 1h
640 1569513600000.0 8499.0 7760.0 0.08674818726808946 418 1 True 420.0 4h
641 1578441600000.0 8468.42 8247.0 0.027545077042120017 1093 -1 False 4h
642 1588219200000.0 9479.77 8526.0 0.10020434428597698 1909 1 False 1914.0 4h
643 1596326400000.0 11920.0 11127.0 0.07155108865026495 2261 -1 False 2264.0 4h
644 1606305600000.0 19554.19 18510.0 0.052873485227486715 2788 1 False 4h
645 1613923200000.0 52681.51 46320.0 0.11807420650137483 3284 1 False 4h
646 1621425600000.0 38199.0 34739.06 0.09039322781003722 4118 1 True 4h
647 1628884800000.0 48300.0 46280.0 0.04141519408656637 4536 1 True 4h
648 1637280000000.0 59449.62 55604.0 0.0713680317197851 4902 -1 False 4h
649 1638590400000.0 50768.0 46724.35 0.0877945655149834 5094 -1 False 5101.0 4h
650 1641816000000.0 43800.0 41666.0 0.051330047151964125 5177 -1 True 4h
651 1643025600000.0 38886.58 35513.13 0.0867208567587231 5378 1 True 4h
652 1644508800000.0 44777.0 41526.0 0.0784486898272488 5444 -1 True 4h
653 1645488000000.0 39250.0 36310.1 0.0722859868602227 5718 1 False 4h
654 1652328000000.0 30784.4 28629.6 0.07589381628117493 6041 -1 False 6044.0 4h
655 1655568000000.0 21740.0 19745.7 0.1020232766338406 6511 -1 True 4h
656 1661716800000.0 20290.0 19508.0 0.04040571053596986 6644 -1 False 6646.0 4h
657 1679486400000.0 28881.0 27166.0 0.05925910568852861 7954 1 True 4h
658 1681444800000.0 30450.0 29078.1 0.04922161747136009 7956 -1 True 4h
659 1687492800000.0 31395.2 29804.6 0.05344937665916201 8581 -1 True 4h
660 1690200000000.0 29670.5 28830.0 0.029271027777003873 8629 -1 False 8631.0 4h
661 1710388800000.0 69044.4 64559.8 0.07131793301082334 10635 -1 True 4h
662 1719259200000.0 62465.1 60038.0 0.04108853902149989 10751 -1 False 4h
663 1721174400000.0 66100.0 63800.0 0.03616170255584625 10881 -1 True 4h
664 1722830400000.0 60250.0 55969.0 0.07061722855832643 11107 1 False 11114.0 4h
665 1726804800000.0 64126.7 62714.2 0.0225328700734771 11168 -1 True 4h
666 1731513600000.0 93421.1 90866.4 0.02634762528967755 11579 1 True 4h
667 1732881600000.0 98713.3 94039.7 0.04995409259934229 11721 -1 True 4h
668 1747022400000.0 104949.2 102591.0 0.022442974174686294 12558 1 True 4h
669 1747929600000.0 110400.0 106497.0 0.03514522279802077 13074 1 True 13079.0 4h
670 1752465600000.0 120951.5 115678.1 0.04560738934149754 13184 -1 True 4h
671 1755849600000.0 113429.0 111625.9 0.015884678170306425 13455 1 True 4h
672 1762286400000.0 104497.6 101300.0 0.03045916408760159 13540 1 True 4h
673 1763697600000.0 93080.0 83786.0 0.11128619735039706 14019 -1 False 4h
674 1770321600000.0 70938.5 65081.0 0.09135542915606767 14761 -1 False 4h
675 1776441600000.0 78300.0 74868.0 0.043787965199285 14564 1 True 4h
676 1780675200000.0 64179.5 60691.9 0.05384857673993463 15100 1 True 4h
File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More