#!/usr/bin/env python
"""KST Indicator Module - Know Sure Thing indicator.
This module provides the KST (Know Sure Thing) momentum indicator
developed by Martin Pring.
Classes:
KnowSureThing: KST indicator (alias: KST).
Example:
class MyStrategy(bt.Strategy):
def __init__(self):
self.kst = bt.indicators.KST(self.data)
self.kst_signal = bt.indicators.KST(self.data, rp1=10, rp2=15, rp3=20, rp4=30)
def next(self):
if self.kst.kst[0] > self.kst.signal[0]:
self.buy()
elif self.kst.kst[0] < self.kst.signal[0]:
self.sell()
"""
import math
from . import ROC100, SMA, Indicator
[docs]
class KnowSureThing(Indicator):
"""
It is a "summed" momentum indicator. Developed by Martin Pring and
published in 1992 in Stocks & Commodities.
Formula:
- rcma1 = MovAv(roc100(rp1), period)
- rcma2 = MovAv(roc100(rp2), period)
- rcma3 = MovAv(roc100(rp3), period)
- rcma4 = MovAv(roc100(rp4), period)
- Kst = 1.0 * rcma1 + 2.0 * rcma2 + 3.0 * rcma3 + 4.0 * rcma4
- signal = MovAv(kst, speriod)
See:
- http://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:know_sure_thing_kst
Params
- ``rma1``, ``rma2``, ``rma3``, ``rma4``: for the MovingAverages on ROCs
- ``rp1``, ``rp2``, ``rp3``, ``rp4``: for the ROCs
- ``rsig``: for the MovingAverage for the signal line
- ``rfactors``: list of factors to apply to the different MovAv(ROCs)
- ``_movav`` and ``_movavs``, allows to change the Moving Average type
applied for the calculation of kst and signal
"""
alias = ("KST",)
lines = (
"kst",
"signal",
)
params = (
("rp1", 10),
("rp2", 15),
("rp3", 20),
("rp4", 30),
("rma1", 10),
("rma2", 10),
("rma3", 10),
("rma4", 10),
("rsignal", 9),
("rfactors", [1.0, 2.0, 3.0, 4.0]),
("_rmovav", SMA),
("_smovav", SMA),
)
plotinfo = dict(plothlines=[0.0])
def __init__(self):
"""Initialize the KST indicator.
Creates 4 ROC and moving average sub-indicators.
"""
super().__init__()
self.rcma1 = self.p._rmovav(ROC100(self.data, period=self.p.rp1), period=self.p.rma1)
self.rcma2 = self.p._rmovav(ROC100(self.data, period=self.p.rp2), period=self.p.rma2)
self.rcma3 = self.p._rmovav(ROC100(self.data, period=self.p.rp3), period=self.p.rma3)
self.rcma4 = self.p._rmovav(ROC100(self.data, period=self.p.rp4), period=self.p.rma4)
[docs]
def next(self):
"""Calculate KST and signal for the current bar.
Formula: KST = w1*RCMA1 + w2*RCMA2 + w3*RCMA3 + w4*RCMA4
Signal = SMA(KST, rsignal)
"""
rf = self.p.rfactors
kst_val = (
rf[0] * self.rcma1[0]
+ rf[1] * self.rcma2[0]
+ rf[2] * self.rcma3[0]
+ rf[3] * self.rcma4[0]
)
self.lines.kst[0] = kst_val
# Calculate signal (SMA of KST)
rsignal = self.p.rsignal
kst_sum = kst_val
for i in range(1, rsignal):
kst_sum += self.lines.kst[-i]
self.lines.signal[0] = kst_sum / rsignal
[docs]
def once(self, start, end):
"""Calculate KST and signal in runonce mode."""
rcma1_array = self.rcma1.lines[0].array
rcma2_array = self.rcma2.lines[0].array
rcma3_array = self.rcma3.lines[0].array
rcma4_array = self.rcma4.lines[0].array
kst_array = self.lines.kst.array
signal_array = self.lines.signal.array
rf = self.p.rfactors
rsignal = self.p.rsignal
for arr in [kst_array, signal_array]:
while len(arr) < end:
arr.append(0.0)
# Calculate KST
for i in range(
start, min(end, len(rcma1_array), len(rcma2_array), len(rcma3_array), len(rcma4_array))
):
v1 = rcma1_array[i] if i < len(rcma1_array) else 0.0
v2 = rcma2_array[i] if i < len(rcma2_array) else 0.0
v3 = rcma3_array[i] if i < len(rcma3_array) else 0.0
v4 = rcma4_array[i] if i < len(rcma4_array) else 0.0
if any(isinstance(v, float) and math.isnan(v) for v in [v1, v2, v3, v4]):
kst_array[i] = float("nan")
else:
kst_array[i] = rf[0] * v1 + rf[1] * v2 + rf[2] * v3 + rf[3] * v4
# Calculate signal (SMA of KST)
for i in range(start, min(end, len(kst_array))):
kst_val = kst_array[i]
if isinstance(kst_val, float) and math.isnan(kst_val):
signal_array[i] = float("nan")
elif i < rsignal - 1:
signal_array[i] = float("nan")
else:
kst_sum = 0.0
valid = True
for j in range(rsignal):
idx = i - j
if idx >= 0 and idx < len(kst_array):
val = kst_array[idx]
if isinstance(val, float) and math.isnan(val):
valid = False
break
kst_sum += val
if valid:
signal_array[i] = kst_sum / rsignal
else:
signal_array[i] = float("nan")