Initial commit

This commit is contained in:
jackyu66git
2025-06-10 01:16:09 +08:00
commit 05bab03b8c
155 changed files with 19979 additions and 0 deletions
+28
View File
@@ -0,0 +1,28 @@
import math
def _truncate(x):
return x if x != 0 else 1e-7
class BOLL_Metric:
def __init__(self, ma, theta):
self.theta = _truncate(theta)
self.UP = ma + 2*theta
self.DOWN = _truncate(ma - 2*theta)
self.MID = ma
class BollModel:
def __init__(self, N=20):
assert N > 1
self.N = N
self.arr = []
def add(self, value) -> BOLL_Metric:
self.arr.append(value)
if len(self.arr) > self.N:
self.arr = self.arr[-self.N:]
ma = sum(self.arr)/len(self.arr)
theta = math.sqrt(sum((x-ma)**2 for x in self.arr) / len(self.arr))
return BOLL_Metric(ma, theta)
+207
View File
@@ -0,0 +1,207 @@
import copy
from dataclasses import dataclass
from typing import List, Literal, Optional, TypedDict
from Common.CEnum import BI_DIR
@dataclass
class C_KL:
idx: int
close: float
high: float
low: float
def v(self, is_close: bool, _dir: BI_DIR) -> float:
if is_close:
return self.close
return self.high if _dir == BI_DIR.UP else self.low
T_DEMARK_TYPE = Literal['setup', 'countdown']
class T_DEMARK_INDEX(TypedDict):
type: T_DEMARK_TYPE
dir: BI_DIR
idx: int
series: 'CDemarkSetup'
class CDemarkIndex:
def __init__(self):
self.data: List[T_DEMARK_INDEX] = []
def add(self, _dir: BI_DIR, _type: T_DEMARK_TYPE, idx: int, series: 'CDemarkSetup'):
self.data.append({"dir": _dir, "idx": idx, "type": _type, "series": series})
def get_setup(self) -> List[T_DEMARK_INDEX]:
return [info for info in self.data if info['type'] == 'setup']
def get_countdown(self) -> List[T_DEMARK_INDEX]:
return [info for info in self.data if info['type'] == 'countdown']
def update(self, demark_index: 'CDemarkIndex'):
self.data.extend(demark_index.data)
class CDemarkCountdown:
def __init__(self, _dir: BI_DIR, kl_list: List[C_KL], TDST_peak: float):
self.dir = _dir
self.kl_list: List[C_KL] = copy.deepcopy(kl_list)
self.idx = 0
self.TDST_peak = TDST_peak
self.finish = False
def update(self, kl: C_KL) -> bool:
if self.finish:
return False
self.kl_list.append(kl)
if len(self.kl_list) <= CDemarkEngine.COUNTDOWN_BIAS:
return False
if self.idx == CDemarkEngine.MAX_COUNTDOWN:
self.finish = True
return False
if (self.dir == BI_DIR.DOWN and kl.high > self.TDST_peak) or (self.dir == BI_DIR.UP and kl.low < self.TDST_peak):
self.finish = True
return False
if self.dir == BI_DIR.DOWN and self.kl_list[-1].close < self.kl_list[-1 - CDemarkEngine.COUNTDOWN_BIAS].v(CDemarkEngine.COUNTDOWN_CMP2CLOSE, self.dir):
self.idx += 1
return True
if self.dir == BI_DIR.UP and self.kl_list[-1].close > self.kl_list[-1 - CDemarkEngine.COUNTDOWN_BIAS].v(CDemarkEngine.COUNTDOWN_CMP2CLOSE, self.dir):
self.idx += 1
return True
return False
class CDemarkSetup:
def __init__(self, _dir: BI_DIR, kl_list: List[C_KL], pre_kl: C_KL):
self.dir = _dir
self.kl_list: List[C_KL] = copy.deepcopy(kl_list)
self.pre_kl = pre_kl # 跳空时用
assert len(self.kl_list) == CDemarkEngine.SETUP_BIAS
self.countdown: Optional[CDemarkCountdown] = None
self.setup_finished = False
self.idx = 0
self.TDST_peak: Optional[float] = None
self.last_demark_index = CDemarkIndex() # 缓存用
def update(self, kl: C_KL) -> CDemarkIndex:
self.last_demark_index = CDemarkIndex()
if not self.setup_finished:
self.kl_list.append(kl)
if self.dir == BI_DIR.DOWN:
if self.kl_list[-1].close < self.kl_list[-1-CDemarkEngine.SETUP_BIAS].v(CDemarkEngine.SETUP_CMP2CLOSE, self.dir):
self.add_setup()
else:
self.setup_finished = True
elif self.kl_list[-1].close > self.kl_list[-1-CDemarkEngine.SETUP_BIAS].v(CDemarkEngine.SETUP_CMP2CLOSE, self.dir):
self.add_setup()
else:
self.setup_finished = True
if self.idx == CDemarkEngine.DEMARK_LEN and not self.setup_finished and self.countdown is None:
self.countdown = CDemarkCountdown(self.dir, self.kl_list[:-1], self.cal_TDST_peak())
if self.countdown is not None and self.countdown.update(kl):
self.last_demark_index.add(self.dir, 'countdown', self.countdown.idx, self)
return self.last_demark_index
def add_setup(self):
self.idx += 1
self.last_demark_index.add(self.dir, 'setup', self.idx, self)
def cal_TDST_peak(self) -> float:
assert len(self.kl_list) == CDemarkEngine.SETUP_BIAS+CDemarkEngine.DEMARK_LEN
arr = self.kl_list[CDemarkEngine.SETUP_BIAS:CDemarkEngine.SETUP_BIAS+CDemarkEngine.DEMARK_LEN]
assert len(arr) == CDemarkEngine.DEMARK_LEN
if self.dir == BI_DIR.DOWN:
res = max(kl.high for kl in arr)
if CDemarkEngine.TIAOKONG_ST and arr[0].high < self.pre_kl.close:
res = max(res, self.pre_kl.close)
else:
res = min(kl.low for kl in arr)
if CDemarkEngine.TIAOKONG_ST and arr[0].low > self.pre_kl.close:
res = min(res, self.pre_kl.close)
self.TDST_peak = res
return res
class CDemarkEngine:
DEMARK_LEN = 9
SETUP_BIAS = 4
COUNTDOWN_BIAS = 2
MAX_COUNTDOWN = 13
TIAOKONG_ST = True # 第一根跳空时是否跟前一根的close比
SETUP_CMP2CLOSE = True
COUNTDOWN_CMP2CLOSE = True
def __init__(
self,
demark_len=9,
setup_bias=4,
countdown_bias=2,
max_countdown=13,
tiaokong_st=True,
setup_cmp2close=True,
countdown_cmp2close=True
):
CDemarkEngine.DEMARK_LEN = demark_len
CDemarkEngine.SETUP_BIAS = setup_bias
CDemarkEngine.COUNTDOWN_BIAS = countdown_bias
CDemarkEngine.MAX_COUNTDOWN = max_countdown
CDemarkEngine.TIAOKONG_ST = tiaokong_st
CDemarkEngine.SETUP_CMP2CLOSE = setup_cmp2close
CDemarkEngine.COUNTDOWN_CMP2CLOSE = countdown_cmp2close
self.kl_lst: List[C_KL] = []
self.series: List[CDemarkSetup] = []
def update(self, idx: int, close: float, high: float, low: float) -> CDemarkIndex:
self.kl_lst.append(C_KL(idx, close, high, low))
if len(self.kl_lst) <= CDemarkEngine.SETUP_BIAS+1:
return CDemarkIndex()
if self.kl_lst[-1].close < self.kl_lst[-1-self.SETUP_BIAS].close:
if not any(series.dir == BI_DIR.DOWN and not series.setup_finished for series in self.series):
self.series.append(CDemarkSetup(BI_DIR.DOWN, self.kl_lst[-CDemarkEngine.SETUP_BIAS-1:-1], self.kl_lst[-CDemarkEngine.SETUP_BIAS-2]))
for series in self.series:
if series.dir == BI_DIR.UP and series.countdown is None and not series.setup_finished:
series.setup_finished = True
elif self.kl_lst[-1].close > self.kl_lst[-1-self.SETUP_BIAS].close:
if not any(series.dir == BI_DIR.UP and not series.setup_finished for series in self.series):
self.series.append(CDemarkSetup(BI_DIR.UP, self.kl_lst[-CDemarkEngine.SETUP_BIAS-1:-1], self.kl_lst[-CDemarkEngine.SETUP_BIAS-2]))
for series in self.series:
if series.dir == BI_DIR.DOWN and series.countdown is None and not series.setup_finished:
series.setup_finished = True
self.clear()
self.clean_series_from_setup_finish()
result = self.cal_result()
self.clear()
return result
def cal_result(self) -> CDemarkIndex:
demark_index = CDemarkIndex()
for series in self.series:
demark_index.update(series.last_demark_index)
return demark_index
def clear(self):
invalid_series = [series for series in self.series if series.setup_finished and series.countdown is None]
for s in invalid_series:
self.series.remove(s)
invalid_series = [series for series in self.series if series.countdown is not None and series.countdown.finish]
for s in invalid_series:
self.series.remove(s)
def clean_series_from_setup_finish(self):
finished_setup: Optional[int] = None
for series in self.series:
demark_idx = series.update(self.kl_lst[-1])
for setup_idx in demark_idx.get_setup():
if setup_idx['idx'] == CDemarkEngine.DEMARK_LEN:
assert finished_setup is None
finished_setup = id(series)
if finished_setup is not None:
self.series = [series for series in self.series if id(series) == finished_setup]
+34
View File
@@ -0,0 +1,34 @@
class KDJ_Item:
def __init__(self, k, d, j):
self.k = k
self.d = d
self.j = j
class KDJ:
def __init__(self, period: int = 9):
super(KDJ, self).__init__()
self.arr = []
self.period = period
self.pre_kdj = KDJ_Item(50, 50, 50)
def add(self, high, low, close) -> KDJ_Item:
self.arr.append({
'high': high,
'low': low,
})
if len(self.arr) > self.period:
self.arr.pop(0)
hn = max([x['high'] for x in self.arr])
ln = min([x['low'] for x in self.arr])
cn = close
rsv = 100 * (cn - ln) / (hn - ln) if hn != ln else 0.0
cur_k = 2 / 3 * self.pre_kdj.k + 1 / 3 * rsv
cur_d = 2 / 3 * self.pre_kdj.d + 1 / 3 * cur_k
cur_j = 3 * cur_k - 2 * cur_d
cur_kdj = KDJ_Item(cur_k, cur_d, cur_j)
self.pre_kdj = cur_kdj
return cur_kdj
+29
View File
@@ -0,0 +1,29 @@
from typing import List
class CMACD_item:
def __init__(self, fast_ema, slow_ema, DIF, DEA):
self.fast_ema = fast_ema
self.slow_ema = slow_ema
self.DIF = DIF
self.DEA = DEA
self.macd = 2 * (DIF - DEA)
class CMACD:
def __init__(self, fastperiod=12, slowperiod=26, signalperiod=9):
self.macd_info: List[CMACD_item] = []
self.fastperiod = fastperiod
self.slowperiod = slowperiod
self.signalperiod = signalperiod
def add(self, value) -> CMACD_item:
if not self.macd_info:
self.macd_info.append(CMACD_item(fast_ema=value, slow_ema=value, DIF=0, DEA=0))
else:
_fast_ema = (2 * value + (self.fastperiod - 1) * self.macd_info[-1].fast_ema) / (self.fastperiod + 1)
_slow_ema = (2 * value + (self.slowperiod - 1) * self.macd_info[-1].slow_ema) / (self.slowperiod + 1)
_dif = _fast_ema - _slow_ema
_dea = (2 * _dif + (self.signalperiod - 1) * self.macd_info[-1].DEA) / (self.signalperiod + 1)
self.macd_info.append(CMACD_item(fast_ema=_fast_ema, slow_ema=_slow_ema, DIF=_dif, DEA=_dea))
return self.macd_info[-1]
+29
View File
@@ -0,0 +1,29 @@
class RSI:
def __init__(self, period: int = 14):
super(RSI, self).__init__()
self.close_arr = []
self.period = period
self.diff = []
self.up = []
self.down = []
def add(self, close):
self.close_arr.append(close)
if len(self.close_arr) == 1:
return 50.0
self.diff.append(self.close_arr[-1] - self.close_arr[-2])
if len(self.diff) < self.period:
self.up.append(sum(x for x in self.diff if x > 0)/self.period)
self.down.append(sum(-x for x in self.diff if x < 0)/self.period)
else:
if self.diff[-1] > 0:
upval = self.diff[-1]
downval = 0.0
else:
upval = 0.0
downval = -self.diff[-1]
self.up.append((self.up[-1] * (self.period - 1) + upval) / self.period)
self.down.append((self.down[-1] * (self.period - 1) + downval) / self.period)
rs = self.up[-1] / self.down[-1] if self.down[-1] != 0 else 0
rsi = 100.0 - 100.0 / (1.0 + rs)
return rsi
+75
View File
@@ -0,0 +1,75 @@
import copy
from dataclasses import dataclass
from math import sqrt
from Common.CEnum import BI_DIR, TREND_LINE_SIDE
@dataclass
class Point:
x: int
y: float
def cal_slope(self, p):
return (self.y-p.y)/(self.x-p.x) if self.x != p.x else float("inf")
@dataclass
class Line:
p: Point
slope: float
def cal_dis(self, p):
return abs(self.slope*p.x - p.y + self.p.y - self.slope*self.p.x) / sqrt(self.slope**2 + 1)
class CTrendLine:
def __init__(self, lst, side=TREND_LINE_SIDE.OUTSIDE):
self.line = None
self.side = side
self.cal(lst)
def cal(self, lst):
bench = float('inf')
if self.side == TREND_LINE_SIDE.INSIDE:
all_p = [Point(bi.get_begin_klu().idx, bi.get_begin_val()) for bi in lst[-1::-2]]
else:
all_p = [Point(bi.get_end_klu().idx, bi.get_end_val()) for bi in lst[-1::-2]]
c_p = copy.copy(all_p)
while True:
line, idx = cal_tl(c_p, lst[-1].dir, self.side)
dis = sum(line.cal_dis(p) for p in all_p)
if dis < bench:
bench = dis
self.line = line
c_p = c_p[idx:]
if len(c_p) == 1:
break
def init_peak_slope(_dir, side):
if side == TREND_LINE_SIDE.INSIDE:
return 0
elif _dir == BI_DIR.UP:
return float("inf")
else:
return -float("inf")
def cal_tl(c_p, _dir, side):
p = c_p[0]
peak_slope = init_peak_slope(_dir, side)
idx = 1
for point_idx, p2 in enumerate(c_p[1:]):
slope = p.cal_slope(p2)
if (_dir == BI_DIR.UP and slope < 0) or (_dir == BI_DIR.DOWN and slope > 0):
continue
if side == TREND_LINE_SIDE.INSIDE:
if (_dir == BI_DIR.UP and slope > peak_slope) or (_dir == BI_DIR.DOWN and slope < peak_slope):
peak_slope = slope
idx = point_idx+1
else:
if (_dir == BI_DIR.UP and slope < peak_slope) or (_dir == BI_DIR.DOWN and slope > peak_slope):
peak_slope = slope
idx = point_idx+1
return Line(p, peak_slope), idx
+22
View File
@@ -0,0 +1,22 @@
from Common.CEnum import TREND_TYPE
from Common.ChanException import CChanException, ErrCode
class CTrendModel:
def __init__(self, trend_type: TREND_TYPE, T: int):
self.T = T
self.arr = []
self.type = trend_type
def add(self, value) -> float:
self.arr.append(value)
if len(self.arr) > self.T:
self.arr = self.arr[-self.T:]
if self.type == TREND_TYPE.MEAN:
return sum(self.arr)/len(self.arr)
elif self.type == TREND_TYPE.MAX:
return max(self.arr)
elif self.type == TREND_TYPE.MIN:
return min(self.arr)
else:
raise CChanException(f"Unknown trendModel Type = {self.type}", ErrCode.PARA_ERROR)
View File