Initial commit
This commit is contained in:
@@ -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
@@ -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
@@ -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
|
||||
@@ -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
@@ -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
|
||||
@@ -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
|
||||
@@ -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)
|
||||
Reference in New Issue
Block a user