package org.ripple.power.hft.computer;
import java.util.Arrays;
import org.apache.commons.math3.analysis.interpolation.SplineInterpolator;
import org.apache.commons.math3.analysis.polynomials.PolynomialSplineFunction;
import org.apache.commons.math3.fitting.PolynomialCurveFitter;
import org.apache.commons.math3.fitting.WeightedObservedPoints;
/**
* 极值点计算器, 用来辅助判断局部顶或底
*
*/
public class ExtremumComputer {
/**
* 寻找input[input.length - lookbackPeriod] 到 input[input.length -
* 1]这些点之间有没极值点,
*
* 返回结果int[2] result, result[0]表示是否有极值, -1表示有极小值, 1表示有极大值, 0表示没有
*
* 如果result[0] != 0, input[input.length - result[1] - 1])是极值
*
* @param input
* @param lookbackPeriod
* 取>=5的奇数, 因为目前算法是取中间点对两边的点分别做二次曲线拟合, 两边点数量最好对称
* @param variancePercent
* lookbackPeriod期间内最小值与最大值变动比例要超过这个百分比, 否则忽略极值
* @return
*/
public int[] findExtremum(double[] input, int lookbackPeriod, double variancePercent) {
double[] samplePoints = Arrays.copyOfRange(input, input.length - lookbackPeriod,
input.length);
int centerIndex = (lookbackPeriod - 1) / 2;
// 把x,y正则化到0-1之间
double yMin = samplePoints[0];
double yMax = samplePoints[0];
for (double samplePoint : samplePoints) {
yMin = Math.min(yMin, samplePoint);
yMax = Math.max(yMax, samplePoint);
}
// 如果振幅过小,忽略
if (yMax / yMin < 1 + variancePercent) {
return new int[] { 0, 0 };
} else {
// 正则化
double[] normalizedSamplePoints = new double[samplePoints.length];
for (int i = 0; i < samplePoints.length; i++) {
normalizedSamplePoints[i] = (samplePoints[i] - yMin) / (yMax - yMin);
}
// 以中心点分割,分两段做1阶线性拟合, 两段斜率符号要相反
if (this.isLinearCoeffDiff(normalizedSamplePoints, centerIndex)) {
// 插值拟合导数校验
double[] derivatives = this.splineDerivatives(normalizedSamplePoints);
double firstHalfCoeff = derivatives[centerIndex - 1];
double secondHalfCoeff = derivatives[centerIndex + 1];
int[] result = new int[2];
result[0] = 0;
// 同向, 非极值
if (firstHalfCoeff * secondHalfCoeff < 0) {
// 比较相邻值
if (firstHalfCoeff > 0 && secondHalfCoeff < 0) {
if (samplePoints[centerIndex] >= samplePoints[centerIndex - 1]
&& samplePoints[centerIndex] >= samplePoints[centerIndex + 1]) {
result[0] = 1;
result[1] = centerIndex;
}
} else {
if (samplePoints[centerIndex] <= samplePoints[centerIndex - 1]
&& samplePoints[centerIndex] <= samplePoints[centerIndex + 1]) {
result[0] = -1;
result[1] = centerIndex;
}
}
}
return result;
} else {
return new int[] { 0, 0 };
}
}
}
private boolean isLinearCoeffDiff(double[] input, int centerIndex) {
double xStep = 2.0 / (input.length + 1);
WeightedObservedPoints firstHalf = new WeightedObservedPoints();
WeightedObservedPoints secondHalf = new WeightedObservedPoints();
for (int i = 0; i < input.length; i++) {
if (i <= centerIndex) {
firstHalf.add(i * xStep, input[i]);
}
if (i >= centerIndex) {
secondHalf.add((i - centerIndex) * xStep, input[i]);
}
}
PolynomialCurveFitter fitter = PolynomialCurveFitter.create(1);
double[] firstHalfCoeffs = fitter.fit(firstHalf.toList());
double[] secondHalfCoeffs = fitter.fit(secondHalf.toList());
double firstHalfCoeff = firstHalfCoeffs[1];
double secondHalfCoeff = secondHalfCoeffs[1];
return firstHalfCoeff * secondHalfCoeff < 0;
}
/**
* 计算分段插值拟合的导数值
*
* @param input
* @return
*/
private double[] splineDerivatives(double[] input) {
double xStep = 1.0 / input.length;
double[] x = new double[input.length];
double[] y = new double[input.length];
for (int i = 0; i < input.length; i++) {
x[i] = i * xStep;
y[i] = input[i];
}
SplineInterpolator fitter = new SplineInterpolator();
PolynomialSplineFunction func = fitter.interpolate(x, y);
double[] derivatives = new double[input.length];
for (int i = 0; i < derivatives.length; i++) {
derivatives[i] = func.derivative().value(x[i]);
}
return derivatives;
}
}