完善知行短线
This commit is contained in:
173
frontend/src/indicators/zhixing.md
Normal file
173
frontend/src/indicators/zhixing.md
Normal file
@@ -0,0 +1,173 @@
|
||||
# 知行指标移植文档(通达信 → 任意平台)
|
||||
|
||||
> 供后续导入到其他项目/平台使用的完整规格:公式原文、参数、数值口径、参考实现、
|
||||
> 校验向量、显示规范。框架无关部分可直接拷贝;klinecharts v10 集成见第八节。
|
||||
> 本仓库实现:`zhixing.ts`(算法+模板)/ `index.ts`(注册表)/ `DetailKLine.vue`(数据管道)。
|
||||
|
||||
## 一、原始通达信公式
|
||||
|
||||
```
|
||||
知行短期趋势线:EMA(EMA(C,10),10),COLORE61851,LINETHICK1;
|
||||
MA1:=MA(CLOSE,60);
|
||||
MA2:=EMA(CLOSE,13);
|
||||
Z1:=STRCAT(HYBLOCK,' ');
|
||||
Z2:=STRCAT(Z1,DYBLOCK);
|
||||
Z3:=STRCAT(Z2,' ');
|
||||
DRAWTEXT_FIX(ISLASTBAR,0,0,0,STRCAT(Z3,GNBLOCK)),COLOR00C0C0;
|
||||
知行多空线:(MA(CLOSE,M1)+MA(CLOSE,M2)+MA(CLOSE,M3)+MA(CLOSE,M4))/4;
|
||||
```
|
||||
|
||||
**参数表(通达信公式管理器里配置,公式文本不带——移植时最容易丢的就是这个):**
|
||||
|
||||
| 参数 | 缺省值 |
|
||||
|------|--------|
|
||||
| M1 | 14 |
|
||||
| M2 | 28 |
|
||||
| M3 | 57 |
|
||||
| M4 | 114 |
|
||||
|
||||
## 二、逐行语义
|
||||
|
||||
| 行 | 作用 | 移植处理 |
|
||||
|----|------|----------|
|
||||
| 知行短期趋势线 | EMA 嵌套两层,主图线 | 画线,#E61851 |
|
||||
| MA1 / MA2 | `:=` 隐藏赋值,且未被后续引用 → 死代码 | 不画 |
|
||||
| Z1~Z3 + DRAWTEXT_FIX | 左上角固定文本:行业板块+地域板块+概念板块(通达信本地板块数据) | 无本地板块数据源,用「行业+地域」近似做成图例行;概念板块缺失 |
|
||||
| 知行多空线 | 4 条 MA 的等权平均,主图线 | 画线,周期 14/28/57/114 |
|
||||
|
||||
## 三、数值口径(与通达信逐位一致)
|
||||
|
||||
**EMA(通达信式)**:`E[i] = (2·X[i] + (N−1)·E[i−1]) / (N+1)`,首根以自身为种子
|
||||
(`E[0] = X[0]`)。等价于系数 `k = 2/(N+1)` 的 `E[i] = k·X[i] + (1−k)·E[i−1]`。
|
||||
无限记忆,但衰减因子 `(N−1)/(N+1)`(N=10 时 ≈0.82/根),截断起点的影响几十根后可忽略。
|
||||
|
||||
**MA(通达信式,严格窗口)**:窗口不满输出空值(null/NaN),**不是** min_periods=1 的渐进均值——
|
||||
很多图表库内置 MA 是渐进口径,直接用会整条线偏掉。
|
||||
|
||||
**多空线**:`dkx[i] = (MA14[i] + MA28[i] + MA57[i] + MA114[i]) / 4`,四个 MA 任一为空则为空。
|
||||
即前 113 根没有多空线(通达信从上市日起算,同样如此)。
|
||||
|
||||
**复权口径**:序列必须与图表展示用同一复权基准(前复权/不复权)的收盘价计算,否则数值对不上。
|
||||
|
||||
## 四、参考实现(框架无关 TypeScript,可直接拷贝)
|
||||
|
||||
```ts
|
||||
export const DKX_PERIODS = [14, 28, 57, 114];
|
||||
|
||||
function ema(src: number[], n: number): number[] {
|
||||
const out = new Array<number>(src.length);
|
||||
const k = 2 / (n + 1);
|
||||
let prev = NaN;
|
||||
for (let i = 0; i < src.length; i++) {
|
||||
prev = Number.isNaN(prev) ? src[i] : src[i] * k + prev * (1 - k);
|
||||
out[i] = prev;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function sma(src: number[], n: number): (number | null)[] {
|
||||
const out = new Array<number | null>(src.length).fill(null);
|
||||
let sum = 0;
|
||||
for (let i = 0; i < src.length; i++) {
|
||||
sum += src[i];
|
||||
if (i >= n) sum -= src[i - n];
|
||||
if (i >= n - 1) out[i] = sum / n;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/** closes 为整段历史收盘价(建议从上市日或至少 ≥ 需求长度+114 根起) */
|
||||
export function computeZhixingSeries(closes: number[]) {
|
||||
const trend = ema(ema(closes, 10), 10);
|
||||
const mas = DKX_PERIODS.map((p) => sma(closes, p));
|
||||
const dkx = new Array<number | null>(closes.length).fill(null);
|
||||
for (let i = 0; i < closes.length; i++) {
|
||||
const m = mas.map((a) => a[i]);
|
||||
if (m.every((v) => v != null)) dkx[i] = m.reduce((s, v) => s + (v as number), 0) / m.length;
|
||||
}
|
||||
return { trend, dkx };
|
||||
}
|
||||
```
|
||||
|
||||
## 五、参考实现(Python / numpy)
|
||||
|
||||
```python
|
||||
import numpy as np
|
||||
|
||||
DKX_PERIODS = (14, 28, 57, 114)
|
||||
|
||||
def ema_tdx(x: np.ndarray, n: int) -> np.ndarray:
|
||||
out = np.empty(len(x))
|
||||
k = 2.0 / (n + 1)
|
||||
prev = np.nan
|
||||
for i, v in enumerate(x):
|
||||
prev = v if np.isnan(prev) else v * k + prev * (1 - k)
|
||||
out[i] = prev
|
||||
return out
|
||||
# pandas 等价写法(adjust=False 且首根种子=自身,与通达信一致):
|
||||
# s.ewm(span=n, adjust=False).mean()
|
||||
|
||||
def sma_tdx(x: np.ndarray, n: int) -> np.ndarray:
|
||||
c = np.concatenate(([0.0], np.cumsum(x)))
|
||||
out = np.full(len(x), np.nan)
|
||||
out[n - 1:] = (c[n:] - c[:-n]) / n # 窗口不满保持 NaN
|
||||
return out
|
||||
|
||||
def zhixing(close: np.ndarray):
|
||||
trend = ema_tdx(ema_tdx(close.astype(float), 10), 10)
|
||||
stack = np.vstack([sma_tdx(close.astype(float), p) for p in DKX_PERIODS])
|
||||
dkx = np.full(len(close), np.nan)
|
||||
ok = ~np.isnan(stack).any(axis=0)
|
||||
dkx[ok] = stack[:, ok].mean(axis=0)
|
||||
return trend, dkx
|
||||
```
|
||||
|
||||
## 六、校验向量(移植后跑一遍,全对即口径正确)
|
||||
|
||||
**趋势线** `closes = [10,11,12,13,14,15]`:
|
||||
|
||||
```
|
||||
EMA(C,10) = 10.000000, 10.181818, 10.512397, 10.964688, 11.516563, 12.149915
|
||||
EMA(EMA(C,10),10) = 10.000000, 10.033058, 10.120210, 10.273752, 10.499717, 10.799753
|
||||
```
|
||||
|
||||
**MA 严格窗口** `sma([1,2,3,4,5,6], 3)`:
|
||||
|
||||
```
|
||||
null, null, 2.000000, 3.000000, 4.000000, 5.000000
|
||||
```
|
||||
|
||||
**多空线预热** `closes[i] = i+1`(线性爬坡,116 根):索引 0~112 全为 null;
|
||||
索引 113 起有值,闭式解 `dkx[i] = (i+1) − (13+27+56+113)/8 = i+1 − 26.125`:
|
||||
|
||||
```
|
||||
dkx[113] = 87.875000 dkx[114] = 88.875000 dkx[115] = 89.875000
|
||||
```
|
||||
|
||||
## 七、数据与预热要求
|
||||
|
||||
- MA114 需预热 113 根:**喂给算法的历史长度 = 可视区最左端再往左 ≥113 根**。
|
||||
分页加载的图表不要只算当前窗口(首屏窗口常见 240 根,左边会缺一大段),
|
||||
应在「全部已加载历史」上整段计算,左滑前插后整体重算。
|
||||
- EMA 理论上要上市日起算;实际截断到几百根即可(衰减 0.82/根,60 根后残差 < 1e−5)。
|
||||
- 建议 closes 传入前先按 timestamp 升序、剔除停牌空K。
|
||||
|
||||
## 八、klinecharts v10 集成要点(本仓库方案)
|
||||
|
||||
- 注册:`registerIndicator(makeZhixingIndicator(get))`,模板工厂注入「(key, timestamp) → 预计算值」
|
||||
访问器;序列由 DetailKLine 在 allData 上预计算写入 `PV['zx_trend']/PV['zx_dkx']`,
|
||||
`calc` 里按 bar.timestamp 取值(**不要用图表相对索引**,翻页后会错位)。
|
||||
- 回调参数的 `Indicator<D>` 不带 E 泛型,`extendData` 是 unknown,取值需 `as` 收窄。
|
||||
- `createTooltipDataSource` 的 `legends` 会整体替换默认图例行,线值图例须自己拼回;
|
||||
`calcParams: DKX_PERIODS` 让图例显示「知行(14,28,57,114)」(通达信式)。
|
||||
- 长周期指标 + 分页窗口的完整坑位说明见
|
||||
`memory/custom-indicator-framework.md`。
|
||||
|
||||
## 九、移植检查清单
|
||||
|
||||
1. M1~M4 = **14/28/57/114**(不是 BBI 的 3/6/12/24)
|
||||
2. EMA 首根种子=自身;MA 窗口不满=null(两条都与常见库默认不同)
|
||||
3. 计算输入与显示用同一复权基准的收盘价
|
||||
4. 预热 ≥113 根覆盖到可视区最左端之外
|
||||
5. 跑第六节校验向量,逐位一致
|
||||
6. 颜色:趋势线 #E61851、多空线 #F5C518、板块文本 #00C0C0
|
||||
Reference in New Issue
Block a user