package models import ( "encoding/json" "errors" "eta_gn/eta_index_lib/utils" "fmt" "github.com/nosixtools/solarlunar" "github.com/shopspring/decimal" "math" "strings" "time" ) // GetChartPredictEdbInfoDataListByRule1 根据规则1获取预测数据 func GetChartPredictEdbInfoDataListByRule1(edbInfoId int, dataValue float64, dayList []time.Time, predictEdbInfoData []*EdbInfoSearchData, existMap map[string]float64) (newPredictEdbInfoData []*EdbInfoSearchData) { newPredictEdbInfoData = predictEdbInfoData //获取后面的预测数据 predictEdbInfoData = make([]*EdbInfoSearchData, 0) for k, v := range dayList { newPredictEdbInfoData = append(newPredictEdbInfoData, &EdbInfoSearchData{ EdbDataId: edbInfoId + 10000000000 + k, DataTime: v.Format(utils.FormatDate), Value: dataValue, }) existMap[v.Format(utils.FormatDate)] = dataValue } return } // GetChartPredictEdbInfoDataListByRuleTb 根据同比值规则获取预测数据 // 2.1 同比: 在未来某一个时间段内,给定一个固定的同比增速a,用去年同期值X乘以同比增速(1+a),得到预测值Y=X(1+a) // 例: 今年1-3月值,100,100,120。给定同比增速a=0.1,则明年1-3月预测值为: 100*1.1=110,100*1.1=110,120*1.1=132。 func GetChartPredictEdbInfoDataListByRuleTb(edbInfoId int, tbValue float64, dayList []time.Time, frequency string, realPredictEdbInfoData, predictEdbInfoData []*EdbInfoSearchData, existMap map[string]float64) (newPredictEdbInfoData []*EdbInfoSearchData, minValue, maxValue float64) { allDataList := make([]*EdbInfoSearchData, 0) allDataList = append(allDataList, realPredictEdbInfoData...) allDataList = append(allDataList, predictEdbInfoData...) newPredictEdbInfoData = predictEdbInfoData index := len(allDataList) //获取后面的预测数据 predictEdbInfoData = make([]*EdbInfoSearchData, 0) for k, currentDate := range dayList { tmpData := &EdbInfoSearchData{ EdbDataId: edbInfoId + 10000000000 + index + k, DataTime: currentDate.Format(utils.FormatDate), //Value: dataValue, } var val float64 var calculateStatus bool //计算结果 //currentItem := existMap[av] //上一年的日期 preDate := currentDate.AddDate(-1, 0, 0) preDateStr := preDate.Format(utils.FormatDate) if preValue, ok := existMap[preDateStr]; ok { //上一年同期找到 val = PredictTbzDiv(preValue, tbValue) calculateStatus = true } else { switch frequency { case "月度": //向上和向下,各找一个月 nextDateDay := preDate preDateDay := preDate for i := 0; i <= 35; i++ { nextDateDayStr := nextDateDay.Format(utils.FormatDate) if preValue, ok := existMap[nextDateDayStr]; ok { //上一年同期->下一个月找到 val = PredictTbzDiv(preValue, tbValue) calculateStatus = true break } else { preDateDayStr := preDateDay.Format(utils.FormatDate) if preValue, ok := existMap[preDateDayStr]; ok { //上一年同期->上一个月找到 val = PredictTbzDiv(preValue, tbValue) calculateStatus = true break } } nextDateDay = nextDateDay.AddDate(0, 0, 1) preDateDay = preDateDay.AddDate(0, 0, -1) } case "季度", "年度": if preValue, ok := existMap[preDateStr]; ok { //上一年同期->下一个月找到 val = PredictTbzDiv(preValue, tbValue) calculateStatus = true break } default: nextDateDay := preDate preDateDay := preDate for i := 0; i < 35; i++ { nextDateDayStr := nextDateDay.Format(utils.FormatDate) if preValue, ok := existMap[nextDateDayStr]; ok { //上一年同期->下一个月找到 val = PredictTbzDiv(preValue, tbValue) calculateStatus = true break } else { preDateDayStr := preDateDay.Format(utils.FormatDate) if preValue, ok := existMap[preDateDayStr]; ok { //上一年同期->上一个月找到 val = PredictTbzDiv(preValue, tbValue) calculateStatus = true break } else { //fmt.Println("pre not find:", preDateStr, "i:", i) } } nextDateDay = nextDateDay.AddDate(0, 0, 1) preDateDay = preDateDay.AddDate(0, 0, -1) } } } if calculateStatus { tmpData.Value = val newPredictEdbInfoData = append(newPredictEdbInfoData, tmpData) allDataList = append(allDataList, tmpData) existMap[tmpData.DataTime] = val // 最大最小值 if val < minValue { minValue = val } if val > maxValue { maxValue = val } } } return } // PredictTbzDiv 同比值计算 // @params a float64 去年同期值 // @params b float64 固定同比增速 func PredictTbzDiv(a, b float64) (result float64) { // 去年同期值 af := decimal.NewFromFloat(a) // 同比增速 bf := decimal.NewFromFloat(b) // 默认1 cf := decimal.NewFromFloat(1) // 总增速 val := bf.Add(cf) // 计算 result, _ = val.Mul(af).Round(4).Float64() return } // GetChartPredictEdbInfoDataListByRuleTc 根据同差值规则获取预测数据 // 2.2 同差: 在未来某一个时间段内,给定一个固定的同比增加值a,用去年同期值X加上同比增加值A,得到预测值Y=X+a // 例: 今年1-3月值,100,100,120。给定同比增加值a=10,则明年1-3月预测值为: 100+10=110,100+10=110,120+10=130 func GetChartPredictEdbInfoDataListByRuleTc(edbInfoId int, tcValue float64, dayList []time.Time, frequency string, realPredictEdbInfoData, predictEdbInfoData []*EdbInfoSearchData, existMap map[string]float64) (newPredictEdbInfoData []*EdbInfoSearchData, minValue, maxValue float64) { allDataList := make([]*EdbInfoSearchData, 0) allDataList = append(allDataList, realPredictEdbInfoData...) allDataList = append(allDataList, predictEdbInfoData...) newPredictEdbInfoData = predictEdbInfoData index := len(allDataList) //获取后面的预测数据 predictEdbInfoData = make([]*EdbInfoSearchData, 0) for k, currentDate := range dayList { tmpData := &EdbInfoSearchData{ EdbDataId: edbInfoId + 10000000000 + index + k, DataTime: currentDate.Format(utils.FormatDate), //Value: dataValue, } var val float64 var calculateStatus bool //计算结果 //currentItem := existMap[av] //上一年的日期 preDate := currentDate.AddDate(-1, 0, 0) preDateStr := preDate.Format(utils.FormatDate) if preValue, ok := existMap[preDateStr]; ok { //上一年同期找到 val = PredictTczDiv(preValue, tcValue) calculateStatus = true } else { switch frequency { case "月度": //向上和向下,各找一个月 nextDateDay := preDate preDateDay := preDate for i := 0; i <= 35; i++ { nextDateDayStr := nextDateDay.Format(utils.FormatDate) if preValue, ok := existMap[nextDateDayStr]; ok { //上一年同期->下一个月找到 val = PredictTczDiv(preValue, tcValue) calculateStatus = true break } else { preDateDayStr := preDateDay.Format(utils.FormatDate) if preValue, ok := existMap[preDateDayStr]; ok { //上一年同期->上一个月找到 val = PredictTczDiv(preValue, tcValue) calculateStatus = true break } } nextDateDay = nextDateDay.AddDate(0, 0, 1) preDateDay = preDateDay.AddDate(0, 0, -1) } case "季度", "年度": if preValue, ok := existMap[preDateStr]; ok { //上一年同期->下一个月找到 val = PredictTczDiv(preValue, tcValue) calculateStatus = true break } default: nextDateDay := preDate preDateDay := preDate for i := 0; i < 35; i++ { nextDateDayStr := nextDateDay.Format(utils.FormatDate) if preValue, ok := existMap[nextDateDayStr]; ok { //上一年同期->下一个月找到 val = PredictTczDiv(preValue, tcValue) calculateStatus = true break } else { preDateDayStr := preDateDay.Format(utils.FormatDate) if preValue, ok := existMap[preDateDayStr]; ok { //上一年同期->上一个月找到 val = PredictTczDiv(preValue, tcValue) calculateStatus = true break } else { //fmt.Println("pre not find:", preDateStr, "i:", i) } } nextDateDay = nextDateDay.AddDate(0, 0, 1) preDateDay = preDateDay.AddDate(0, 0, -1) } } } if calculateStatus { tmpData.Value = val newPredictEdbInfoData = append(newPredictEdbInfoData, tmpData) allDataList = append(allDataList, tmpData) existMap[tmpData.DataTime] = val // 最大最小值 if val < minValue { minValue = val } if val > maxValue { maxValue = val } } } return } // PredictTczDiv 环差值计算 // @params a float64 上一期值 // @params b float64 固定的环比增加值 func PredictTczDiv(a, b float64) (result float64) { if b != 0 { // 上一期值 af := decimal.NewFromFloat(a) // 固定的环比增加值 bf := decimal.NewFromFloat(b) // 计算 result, _ = af.Add(bf).Round(4).Float64() } else { result = 0 } return } // GetChartPredictEdbInfoDataListByRuleHb 根据环比值规则获取预测数据 // 环比:在未来某一个时间段内,给定一个固定的环比增速a,用上一期值X乘以环比增速(1+a),得到预测值Y=X(1+a) // 例: 最近1期值为100,给定环比增速a=0.2,则未来3期预测值为: 100*1.2=120,120*1.2=144,144*1.2=172.8 func GetChartPredictEdbInfoDataListByRuleHb(edbInfoId int, hbValue float64, dayList []time.Time, realPredictEdbInfoData, predictEdbInfoData []*EdbInfoSearchData, existMap map[string]float64) (newPredictEdbInfoData []*EdbInfoSearchData, minValue, maxValue float64) { allDataList := make([]*EdbInfoSearchData, 0) allDataList = append(allDataList, realPredictEdbInfoData...) allDataList = append(allDataList, predictEdbInfoData...) newPredictEdbInfoData = predictEdbInfoData index := len(allDataList) //获取后面的预测数据 for k, currentDate := range dayList { tmpK := index + k - 1 //上1期的值 // 环比值计算 val := PredictHbzDiv(allDataList[tmpK].Value, hbValue) currentDateStr := currentDate.Format(utils.FormatDate) tmpData := &EdbInfoSearchData{ EdbDataId: edbInfoId + 10000000000 + index + k, DataTime: currentDateStr, Value: val, } newPredictEdbInfoData = append(newPredictEdbInfoData, tmpData) allDataList = append(allDataList, tmpData) existMap[currentDateStr] = val // 最大最小值 if val < minValue { minValue = val } if val > maxValue { maxValue = val } } return } // PredictHbzDiv 环比值计算 // @params a float64 上一期值 // @params b float64 固定的环比增速 func PredictHbzDiv(a, b float64) (result float64) { if b != 0 { // 上一期值 af := decimal.NewFromFloat(a) // 固定的环比增速 bf := decimal.NewFromFloat(b) // 默认1 cf := decimal.NewFromFloat(1) // 总增速 val := bf.Add(cf) // 计算 result, _ = val.Mul(af).Round(4).Float64() } else { result = 0 } return } // GetChartPredictEdbInfoDataListByRuleHc 根据环差值规则获取预测数据 // 2.4 环差:在未来某一个时间段内,给定一个固定的环比增加值a,用上一期值X加上环比增加值a,得到预测值Y=X+a // 例: 最近1期值为100,给定环比增加值a=10,则未来3期预测值为: 100+10=110,110+10=120,120+10=130 func GetChartPredictEdbInfoDataListByRuleHc(edbInfoId int, hcValue float64, dayList []time.Time, realPredictEdbInfoData, predictEdbInfoData []*EdbInfoSearchData, existMap map[string]float64) (newPredictEdbInfoData []*EdbInfoSearchData, minValue, maxValue float64) { allDataList := make([]*EdbInfoSearchData, 0) allDataList = append(allDataList, realPredictEdbInfoData...) allDataList = append(allDataList, predictEdbInfoData...) newPredictEdbInfoData = predictEdbInfoData index := len(allDataList) //获取后面的预测数据 for k, currentDate := range dayList { tmpK := index + k - 1 //上1期的值 // 环差别值计算 val := PredictHczDiv(allDataList[tmpK].Value, hcValue) currentDateStr := currentDate.Format(utils.FormatDate) tmpData := &EdbInfoSearchData{ EdbDataId: edbInfoId + 10000000000 + index + k, DataTime: currentDateStr, Value: val, } newPredictEdbInfoData = append(newPredictEdbInfoData, tmpData) allDataList = append(allDataList, tmpData) existMap[currentDateStr] = val // 最大最小值 if val < minValue { minValue = val } if val > maxValue { maxValue = val } } return } // PredictHczDiv 环差值计算 // @params a float64 上一期值 // @params b float64 固定的环比增加值 func PredictHczDiv(a, b float64) (result float64) { if b != 0 { // 上一期值 af := decimal.NewFromFloat(a) // 固定的环比增加值 bf := decimal.NewFromFloat(b) // 计算 result, _ = af.Add(bf).Round(4).Float64() } else { result = 0 } return } // GetChartPredictEdbInfoDataListByRuleNMoveMeanValue 根据N期移动均值规则获取预测数据 // 2.5 N期移动均值:在未来某一个时间段内,下一期值等于过去N期值得平均值。 // 例:最近3期值(N=3),为95,98,105则未来第1期值为 1/3*(95+98+105)=99.33, 未来第2期值为 1/3*(98+105+99.33)=100.78依次类推。 func GetChartPredictEdbInfoDataListByRuleNMoveMeanValue(edbInfoId int, nValue int, dayList []time.Time, realPredictEdbInfoData, predictEdbInfoData []*EdbInfoSearchData, existMap map[string]float64) (newPredictEdbInfoData []*EdbInfoSearchData, minValue, maxValue float64) { allDataList := make([]*EdbInfoSearchData, 0) allDataList = append(allDataList, realPredictEdbInfoData...) allDataList = append(allDataList, predictEdbInfoData...) newPredictEdbInfoData = predictEdbInfoData lenAllData := len(allDataList) if lenAllData < nValue || lenAllData <= 0 { return } if nValue <= 0 { return } // 分母 decimalN := decimal.NewFromInt(int64(nValue)) //获取后面的预测数据 for k, currentDate := range dayList { tmpIndex := lenAllData + k - 1 //上1期的值 // 数据集合中的最后一个数据 tmpDecimalVal := decimal.NewFromFloat(allDataList[tmpIndex].Value) for tmpK := 2; tmpK <= nValue; tmpK++ { tmpIndex2 := tmpIndex - tmpK //上N期的值 tmpDecimalVal2 := decimal.NewFromFloat(allDataList[tmpIndex2].Value) tmpDecimalVal = tmpDecimalVal.Add(tmpDecimalVal2) } // N期移动均值计算 val, _ := tmpDecimalVal.Div(decimalN).Round(4).Float64() currentDateStr := currentDate.Format(utils.FormatDate) tmpData := &EdbInfoSearchData{ EdbDataId: edbInfoId + 10000000000 + lenAllData + k, DataTime: currentDateStr, Value: val, } newPredictEdbInfoData = append(newPredictEdbInfoData, tmpData) allDataList = append(allDataList, tmpData) existMap[currentDateStr] = val // 最大最小值 if val < minValue { minValue = val } if val > maxValue { maxValue = val } } return } // GetChartPredictEdbInfoDataListByRuleNLinearRegression 根据N期移动均值规则获取预测数据 // 2.6N期段线性外推值:给出过去N期值所确定的线性回归方程(Y=aX+b)在未来一段时间内的推算值。回归方程虽然比较复杂,但各种编程语言应该都有现成的模块或函数,应该无需自己编写。 // 例1:过去5期值(N=5)分别为:3,5,7,9,11(每两期值之间的时间间隔相等)。那么按照线性回归方程推算,未来三期的预测值是:13,15,17。 // // 例2:过去6期值(N=6)分别为:3,3,5,7,9,11(每两期值之间的时间间隔相等)。那么按照线性回归方程推算,未来三期的预测值是:12.33,14.05,15.76。例1和例2的区别在于,多加了一期数据,导致回归方程发生改变,从而预测值不同。 func GetChartPredictEdbInfoDataListByRuleNLinearRegression(edbInfoId int, nValue int, dayList []time.Time, realPredictEdbInfoData, predictEdbInfoData []*EdbInfoSearchData, existMap map[string]float64) (newPredictEdbInfoData []*EdbInfoSearchData, minValue, maxValue float64, err error) { //var errMsg string //defer func() { // if errMsg != `` { // go alarm_msg.SendAlarmMsg("更新上海的token失败;ERR:"+err.Error(), 3) // } //}() allDataList := make([]*EdbInfoSearchData, 0) allDataList = append(allDataList, realPredictEdbInfoData...) allDataList = append(allDataList, predictEdbInfoData...) newPredictEdbInfoData = predictEdbInfoData lenAllData := len(allDataList) if lenAllData < nValue || lenAllData <= 0 { return } if nValue <= 1 { return } //获取后面的预测数据 // 获取线性方程公式的a、b的值 coordinateData := make([]utils.Coordinate, 0) for tmpK := nValue; tmpK > 0; tmpK-- { tmpIndex2 := lenAllData - tmpK //上N期的值 tmpCoordinate := utils.Coordinate{ X: float64(nValue - tmpK + 1), Y: allDataList[tmpIndex2].Value, } coordinateData = append(coordinateData, tmpCoordinate) } a, b := utils.GetLinearResult(coordinateData) if math.IsNaN(a) || math.IsNaN(b) { err = errors.New("线性方程公式生成失败") return } //fmt.Println("a:", a, ";======b:", b) aDecimal := decimal.NewFromFloat(a) bDecimal := decimal.NewFromFloat(b) for k, currentDate := range dayList { tmpK := nValue + k + 1 xDecimal := decimal.NewFromInt(int64(tmpK)) val, _ := aDecimal.Mul(xDecimal).Add(bDecimal).Round(4).Float64() currentDateStr := currentDate.Format(utils.FormatDate) tmpData := &EdbInfoSearchData{ EdbDataId: edbInfoId + 10000000000 + lenAllData + k, DataTime: currentDateStr, Value: val, } newPredictEdbInfoData = append(newPredictEdbInfoData, tmpData) allDataList = append(allDataList, tmpData) existMap[currentDateStr] = val // 最大最小值 if val < minValue { minValue = val } if val > maxValue { maxValue = val } } return } // GetChartPredictEdbInfoDataListByRuleTrendsHC 根据动态环比增加值的计算规则获取预测数据 // // 研究员有对预测指标进行动态环差计算的需求,即预测指标使用环差规则进行预测时,环比增加值不是固定值,而是由几个预测指标计算得出的动态变化的值; // 需求说明: // 1、增加“动态环差”预测规则; // 2、环比增加值在弹窗设置; // 3、动态环差预测举例: // 指标A实际最新数据为2022-10-27(100); // 预测指标B预测数据为2022-10-28(240)、2022-10-29(300); // 预测指标C预测数据为2022-10-28(260)、2022-10-29(310); // 计算公式为B-C; // 则指标A至2022-10-29的预测值为2022-10-28(100+(240-260)=80)、2022-10-29(80+(300-310)=90); // 注:动态环比增加值的计算遵从计算指标的计算规则,即用于计算的指标若有部分指标缺少部分日期数据,则这部分日期数据不做计算,为空;若动态环比增加值某一天为空,则往前追溯最近一期有值的环比增加值作为该天的数值参与计算; func GetChartPredictEdbInfoDataListByRuleTrendsHC(edbInfoId int, dayList []time.Time, realPredictEdbInfoData, predictEdbInfoData []*EdbInfoSearchData, hcDataMap, existMap map[string]float64) (newPredictEdbInfoData []*EdbInfoSearchData, minValue, maxValue float64) { allDataList := make([]*EdbInfoSearchData, 0) allDataList = append(allDataList, realPredictEdbInfoData...) allDataList = append(allDataList, predictEdbInfoData...) newPredictEdbInfoData = predictEdbInfoData lenAllData := len(allDataList) if lenAllData <= 0 { return } for k, currentDate := range dayList { // 最近一条数据 tmpLenAllDataList := len(allDataList) lastValue := allDataList[tmpLenAllDataList-1].Value // 动态环差值数据 currentDateStr := currentDate.Format(utils.FormatDate) hcVal, ok := hcDataMap[currentDateStr] if !ok { continue } lastValueDecimal := decimal.NewFromFloat(lastValue) hcValDecimal := decimal.NewFromFloat(hcVal) val, _ := lastValueDecimal.Add(hcValDecimal).Round(4).Float64() tmpData := &EdbInfoSearchData{ EdbDataId: edbInfoId + 10000000000 + lenAllData + k, //EdbInfoId: edbInfoId, DataTime: currentDateStr, Value: val, //DataTimestamp: (currentDate.UnixNano() / 1e6) + 1000, //前端需要让加1s,说是2022-09-01 00:00:00 这样的整点不合适 } newPredictEdbInfoData = append(newPredictEdbInfoData, tmpData) allDataList = append(allDataList, tmpData) existMap[currentDateStr] = val // 最大最小值 if val < minValue { minValue = val } if val > maxValue { maxValue = val } } return } // GetChartPredictEdbInfoDataListByRuleFinalValueHc 根据 给定终值后插值 规则获取预测数据 // // 项目背景: // 假设螺纹产量在2023年1月1号的预测值是255万吨,从当下到2023年1月1号,螺纹产量将会线性变化,那么每一期的螺纹产量是多少? // 算法:从当下(2022/10/28)到2023/1/1号,一共65天,从当前值(305.02)到255,差值-50.02, // 则每日环差为-50.02/65=-0.7695。因为数据点是周度频率,每周环差为,-0.3849*7=-5.3868。 // 从以上计算过程可看出,“给定终值后差值”的算法,是在“环差”算法的基础上,做的一个改动。即这个”环差值”=【(终值-最新值)/终值与最新值得日期差】*数据频率 // 需求说明: // 1、增加一个预测规则,名为“给定终值后插值”,给定预测截止日期和预测终值,计算最新数据日期至预测截止日期的时间差T,计算最新数据和预测终值的数据差S,数据频率与指标频度有关,日度=1,周度=7,旬度=10,月度=30,季度=90,年度=365,环差值=S/T*频率,预测数值=前一天数值+环差值; // 2、最新数据值和日期改动后,需重新计算环差值和预测数值; func GetChartPredictEdbInfoDataListByRuleFinalValueHc(edbInfoId int, finalValue float64, dayList []time.Time, realPredictEdbInfoData, predictEdbInfoData []*EdbInfoSearchData, existMap map[string]float64) (newPredictEdbInfoData []*EdbInfoSearchData, minValue, maxValue float64) { allDataList := make([]*EdbInfoSearchData, 0) allDataList = append(allDataList, realPredictEdbInfoData...) allDataList = append(allDataList, predictEdbInfoData...) newPredictEdbInfoData = predictEdbInfoData index := len(allDataList) //获取后面的预测日期 lenDay := len(dayList) if lenDay <= 0 { return } var hcValue float64 lastValueDeciamal := decimal.NewFromFloat(allDataList[index-1].Value) // 实际数据的最后一个值 finalValueDeciamal := decimal.NewFromFloat(finalValue) // 给定的终止数据 dayDecimal := decimal.NewFromInt(int64(lenDay)) // 需要作为分母的期数 hcValue, _ = finalValueDeciamal.Sub(lastValueDeciamal).Div(dayDecimal).Float64() // 计算出来的环差值 //获取后面的预测数据 predictEdbInfoData = make([]*EdbInfoSearchData, 0) lastK := lenDay - 1 // 最后的日期 for k, currentDate := range dayList { tmpK := index + k - 1 //上1期的值 var val float64 // 环差别值计算 if k == lastK { //如果是最后一天,那么就用最终值,否则就计算 val = finalValue } else { val = PredictHczDiv(allDataList[tmpK].Value, hcValue) } currentDateStr := currentDate.Format(utils.FormatDate) tmpData := &EdbInfoSearchData{ EdbDataId: edbInfoId + 10000000000 + index + k, //EdbInfoId: edbInfoId, DataTime: currentDateStr, Value: val, //DataTimestamp: (currentDate.UnixNano() / 1e6) + 1000, //前端需要让加1s,说是2022-09-01 00:00:00 这样的整点不合适 } newPredictEdbInfoData = append(newPredictEdbInfoData, tmpData) allDataList = append(allDataList, tmpData) existMap[currentDateStr] = val // 最大最小值 if val < minValue { minValue = val } if val > maxValue { maxValue = val } } return } // SeasonConf 季节性规则的配置 type SeasonConf struct { Calendar string `description:"公历、农历"` YearType int `description:"选择方式,1:连续N年;2:指定年份"` NValue int `description:"连续N年"` YearList []int `description:"指定年份列表"` } // GetChartPredictEdbInfoDataListByRuleSeason 根据 季节性 规则获取预测数据 // // ETA预测规则:季节性 // 已知选定指标A最近更新日期: 2022-12-6 200 // 设置预测截止日期2023-01-06 // 1、选择过去N年,N=3 // 则过去N年为2021、2020、2019 // 指标A日期 实际值 指标A日期 // 2019/12/5 150 2019/12/6 // 2020/12/5 180 2020/12/6 // 2021/12/5 210 2021/12/6 // 2019/12/31 200 2020/1/1 // 2020/12/31 210 2021/1/1 // 2021/12/31 250 2022/1/1 // // 计算12.7预测值,求过去N年环差均值=[(100-150)+(160-180)+(250-210)]/3=-10 // 则12.7预测值=12.6值+过去N年环差均值=200-10=190 // 以此类推... // // 计算2023.1.2预测值,求过去N年环差均值=[(300-200)+(220-210)+(260-250)]/3=40 // 则2023.1.2预测值=2023.1.1值+过去N年环差均值 func GetChartPredictEdbInfoDataListByRuleSeason(edbInfoId int, yearsList []int, calendar string, dayList []time.Time, realPredictEdbInfoData, predictEdbInfoData []*EdbInfoSearchData, existMap map[string]float64) (newPredictEdbInfoData []*EdbInfoSearchData, minValue, maxValue float64, err error) { allDataList := make([]*EdbInfoSearchData, 0) allDataList = append(allDataList, realPredictEdbInfoData...) allDataList = append(allDataList, predictEdbInfoData...) newPredictEdbInfoData = predictEdbInfoData // 插值法数据处理 handleDataMap := make(map[string]float64) _, err = HandleDataByLinearRegression(allDataList, handleDataMap) if err != nil { return } // 获取每个年份的日期数据需要平移的天数 moveDayMap := make(map[int]int, 0) // 每个年份的春节公历 { if calendar == "公历" { for _, year := range yearsList { moveDayMap[year] = 0 //公历就不平移了 } } else { currentDay := time.Now() if currentDay.Month() >= 11 { //如果大于等于11月份,那么用的是下一年的春节 currentDay = currentDay.AddDate(1, 0, 0) } currentYear := currentDay.Year() currentYearCjnl := fmt.Sprintf("%d-01-01", currentYear) //当年的春节农历 currentYearCjgl := solarlunar.LunarToSolar(currentYearCjnl, false) //当年的春节公历 currentYearCjglTime, tmpErr := time.ParseInLocation(utils.FormatDate, currentYearCjgl, time.Local) if tmpErr != nil { err = errors.New("当前春节公历日期转换失败:" + tmpErr.Error()) return } // 指定的年份 for _, year := range yearsList { tmpYearCjnl := fmt.Sprintf("%d-01-01", year) //指定年的春节农历 tmpYearCjgl := solarlunar.LunarToSolar(tmpYearCjnl, false) //指定年的春节公历 //moveDayList = append(moveDayList, 0) //公历就不平移了 tmpYearCjglTime, tmpErr := time.ParseInLocation(utils.FormatDate, tmpYearCjgl, time.Local) if tmpErr != nil { err = errors.New(fmt.Sprintf("%d公历日期转换失败:%s", year, tmpErr.Error())) return } tmpCurrentYearCjglTime := currentYearCjglTime.AddDate(year-currentYear, 0, 0) moveDay := utils.GetTimeSubDay(tmpYearCjglTime, tmpCurrentYearCjglTime) moveDayMap[year] = moveDay //公历平移 } } } index := len(allDataList) //获取后面的预测日期 //获取后面的预测数据 predictEdbInfoData = make([]*EdbInfoSearchData, 0) for k, currentDate := range dayList { // 如果遇到闰二月,如2.29,去掉该天数据 if strings.Contains(currentDate.Format(utils.FormatDate), "02-29") { continue } tmpHistoryVal := decimal.NewFromFloat(0) //往期的差值总和 tmpHistoryValNum := 0 // 往期差值计算的数量 tmpLenAllDataList := len(allDataList) tmpK := tmpLenAllDataList - 1 //上1期数据的下标 lastDayData := allDataList[tmpK] // 上1期的数据 lastDayStr := lastDayData.DataTime lastDayVal := lastDayData.Value lastDay, tmpErr := time.ParseInLocation(utils.FormatDate, lastDayStr, time.Local) if tmpErr != nil { err = errors.New("获取上期日期转换失败:" + tmpErr.Error()) } for _, year := range yearsList { moveDay := moveDayMap[year] //需要移动的天数 var tmpHistoryCurrentVal, tmpHistoryLastVal float64 var isFindHistoryCurrent, isFindHistoryLast bool //是否找到前几年的数据 //前几年当日的日期 tmpHistoryCurrentDate := currentDate.AddDate(year-currentDate.Year(), 0, -moveDay) for i := 0; i <= 35; i++ { // 前后35天找数据,找到最近的值,先向后面找,再往前面找 tmpDate := tmpHistoryCurrentDate.AddDate(0, 0, i) if val, ok := handleDataMap[tmpDate.Format(utils.FormatDate)]; ok { tmpHistoryCurrentVal = val isFindHistoryCurrent = true break } else { tmpDate := tmpHistoryCurrentDate.AddDate(0, 0, -i) if val, ok := handleDataMap[tmpDate.Format(utils.FormatDate)]; ok { tmpHistoryCurrentVal = val isFindHistoryCurrent = true break } } } //前几年上一期的日期 tmpHistoryLastDate := lastDay.AddDate(year-lastDay.Year(), 0, -moveDay) for i := 0; i <= 35; i++ { // 前后35天找数据,找到最近的值,先向后面找,再往前面找 tmpDate := tmpHistoryLastDate.AddDate(0, 0, i) if val, ok := handleDataMap[tmpDate.Format(utils.FormatDate)]; ok { tmpHistoryLastVal = val isFindHistoryLast = true break } else { tmpDate := tmpHistoryLastDate.AddDate(0, 0, -i) if val, ok := handleDataMap[tmpDate.Format(utils.FormatDate)]; ok { tmpHistoryLastVal = val isFindHistoryLast = true break } } } // 如果两个日期对应的数据都找到了,那么计算两期的差值 if isFindHistoryCurrent && isFindHistoryLast { af := decimal.NewFromFloat(tmpHistoryCurrentVal) bf := decimal.NewFromFloat(tmpHistoryLastVal) tmpHistoryVal = tmpHistoryVal.Add(af.Sub(bf)) tmpHistoryValNum++ } } //计算的差值与选择的年份数量不一致,那么当前日期不计算 if tmpHistoryValNum != len(yearsList) { continue } lastDayValDec := decimal.NewFromFloat(lastDayVal) val, _ := tmpHistoryVal.Div(decimal.NewFromInt(int64(tmpHistoryValNum))).Add(lastDayValDec).Round(4).Float64() currentDateStr := currentDate.Format(utils.FormatDate) tmpData := &EdbInfoSearchData{ EdbDataId: edbInfoId + 10000000000 + index + k, //EdbInfoId: edbInfoId, DataTime: currentDateStr, Value: val, //DataTimestamp: (currentDate.UnixNano() / 1e6) + 1000, //前端需要让加1s,说是2022-09-01 00:00:00 这样的整点不合适 } newPredictEdbInfoData = append(newPredictEdbInfoData, tmpData) allDataList = append(allDataList, tmpData) existMap[currentDateStr] = val // 继续使用插值法补充新预测日期的数据之间的值 _, err = HandleDataByLinearRegression([]*EdbInfoSearchData{ lastDayData, tmpData, }, handleDataMap) if err != nil { return } // 最大最小值 if val < minValue { minValue = val } if val > maxValue { maxValue = val } } return } // MoveAverageConf 移动平均同比规则的配置 type MoveAverageConf struct { Year int `description:"指定年份"` NValue int `description:"N期的数据"` } // GetChartPredictEdbInfoDataListByRuleMoveAverageTb 根据 移动平均同比 规则获取预测数据 // // ETA预测规则:季节性 // 2、选择指定N年,N=3 // 指定N年为2012、2015、2018 // 指标A日期 实际值 指标A日期 实际值 // 2012/12/5 150 2012/12/6 130 // 2015/12/5 180 2015/12/6 150 // 2018/12/5 210 2018/12/6 260 // 2012/12/31 200 2013/1/1 200 // 2015/12/31 210 2016/1/1 250 // 2018/12/31 250 2019/1/1 270 // 计算12.7预测值,求过去N年环差均值=[(130-150)+(150-180)+(290-210)]/3=10 // 则12.7预测值=12.6值+过去N年环差均值=200+10=210 // 以此类推... // 计算2023.1.2预测值,求过去N年环差均值=[(200-200)+(250-210)+(270-250)]/3=16.67 // 则2023.1.2预测值=2023.1.1值+过去N年环差均值 func GetChartPredictEdbInfoDataListByRuleMoveAverageTb(edbInfoId int, nValue, year int, dayList []time.Time, realPredictEdbInfoData, predictEdbInfoData []*EdbInfoSearchData, existMap map[string]float64) (newPredictEdbInfoData []*EdbInfoSearchData, minValue, maxValue float64, err error) { allDataList := make([]*EdbInfoSearchData, 0) allDataList = append(allDataList, realPredictEdbInfoData...) allDataList = append(allDataList, predictEdbInfoData...) newPredictEdbInfoData = predictEdbInfoData lenAllData := len(allDataList) if lenAllData < nValue || lenAllData <= 0 { return } if nValue <= 0 { return } // 分母 decimalN := decimal.NewFromInt(int64(nValue)) // 需要减去的年份 subYear := year - dayList[0].Year() //获取后面的预测数据 for k, currentDate := range dayList { tmpLenAllDataList := len(allDataList) tmpIndex := tmpLenAllDataList - 1 //上1期数据的下标 averageDateList := make([]string, 0) //计算平均数的日期 // 数据集合中的最后一个数据 tmpDecimalVal := decimal.NewFromFloat(allDataList[tmpIndex].Value) averageDateList = append(averageDateList, allDataList[tmpIndex].DataTime) for tmpK := 1; tmpK < nValue; tmpK++ { tmpIndex2 := tmpIndex - tmpK //上N期的值 tmpDecimalVal2 := decimal.NewFromFloat(allDataList[tmpIndex2].Value) tmpDecimalVal = tmpDecimalVal.Add(tmpDecimalVal2) averageDateList = append(averageDateList, allDataList[tmpIndex2].DataTime) } // 最近的N期平均值 tmpAverageVal := tmpDecimalVal.Div(decimalN) var tmpHistoryCurrentVal float64 // 前几年当日的数据值 var isFindHistoryCurrent, isFindHistoryLast bool //是否找到前几年的数据 tmpHistoryDecimalVal := decimal.NewFromFloat(0) //前几年N期数据总值 { // 前几年N期汇总期数 tmpHistoryValNum := 0 { //前几年当日的日期 tmpHistoryCurrentDate := currentDate.AddDate(subYear, 0, 0) for i := 0; i <= 35; i++ { // 前后35天找数据,找到最近的值,先向后面找,再往前面找 tmpDate := tmpHistoryCurrentDate.AddDate(0, 0, i) if val, ok := existMap[tmpDate.Format(utils.FormatDate)]; ok { tmpHistoryCurrentVal = val isFindHistoryCurrent = true break } else { tmpDate := tmpHistoryCurrentDate.AddDate(0, 0, -i) if val, ok := existMap[tmpDate.Format(utils.FormatDate)]; ok { tmpHistoryCurrentVal = val isFindHistoryCurrent = true break } } } } for _, averageDate := range averageDateList { lastDay, tmpErr := time.ParseInLocation(utils.FormatDate, averageDate, time.Local) if tmpErr != nil { err = tmpErr return } //前几年上一期的日期 tmpHistoryLastDate := lastDay.AddDate(subYear, 0, 0) for i := 0; i <= 35; i++ { // 前后35天找数据,找到最近的值,先向后面找,再往前面找 tmpDate := tmpHistoryLastDate.AddDate(0, 0, i) if val, ok := existMap[tmpDate.Format(utils.FormatDate)]; ok { tmpDecimalVal2 := decimal.NewFromFloat(val) tmpHistoryDecimalVal = tmpHistoryDecimalVal.Add(tmpDecimalVal2) tmpHistoryValNum++ break } else { tmpDate := tmpHistoryLastDate.AddDate(0, 0, -i) if val, ok := existMap[tmpDate.Format(utils.FormatDate)]; ok { tmpDecimalVal2 := decimal.NewFromFloat(val) tmpHistoryDecimalVal = tmpHistoryDecimalVal.Add(tmpDecimalVal2) tmpHistoryValNum++ break } } } } // 汇总期数与配置的N期数量一致 if tmpHistoryValNum == nValue { isFindHistoryLast = true } } // 如果没有找到前几年的汇总数据,或者没有找到前几年当日的数据,那么退出当前循环,进入下一循环 if !isFindHistoryLast || !isFindHistoryCurrent { continue } // 计算最近N期平均值 tmpHistoryAverageVal := tmpHistoryDecimalVal.Div(decimalN) // 计算最近N期同比值 tbVal := tmpAverageVal.Div(tmpHistoryAverageVal) // 预测值结果 = 同比年份同期值(tmpHistoryCurrentVal的值)* 同比值(tbVal的值) val, _ := decimal.NewFromFloat(tmpHistoryCurrentVal).Mul(tbVal).Round(4).Float64() currentDateStr := currentDate.Format(utils.FormatDate) tmpData := &EdbInfoSearchData{ EdbDataId: edbInfoId + 10000000000 + lenAllData + k, //EdbInfoId: edbInfoId, DataTime: currentDateStr, Value: val, //DataTimestamp: (currentDate.UnixNano() / 1e6) + 1000, //前端需要让加1s,说是2022-09-01 00:00:00 这样的整点不合适 } newPredictEdbInfoData = append(newPredictEdbInfoData, tmpData) allDataList = append(allDataList, tmpData) existMap[currentDateStr] = val // 最大最小值 if val < minValue { minValue = val } if val > maxValue { maxValue = val } } return } // GetChartPredictEdbInfoDataListByRuleTbzscz 根据 同比增速差值 规则获取预测数据 // 同比增速差值计算方式: // 1、首先计算出所选指标实际最新日期值的同比增速:(本期数值-同期数值)÷同期数值*100% // 2、根据预测截止日期的同比增速终值、最新日期值的同比增速、与最新日期距离截止日期的期数,计算出到截止日期为止的每一期的同比增速。(等差规则计算每一期的同比增速,结合去年同期值,计算出每一期的同比预测值)。公差=(末项-首项)÷(n-1),an=a1+(n-1)d,(n为正整数,n大于等于2) // 3、根据去年同期值和未来每一期的同比增速值,求出同比预测值,同比预测值=同期值*(1+同比增速) // 同比增速差值:计算最新数据的同比增速((本期数值-同期数值)÷同期数值*100%),结合同比增速终值与期数,计算每一期同比增速,进而求出同比预测值。 // // 例:如上图所示指标,(1)最新日期值2022-12-31 141175 ,结合同期值,计算同比增速; // (2)同比增速终值,若为50%, 预测日期为2023-03-31,则根据(1)中的同比增速值与同比增速终值,计算出中间两期的同比增速; // (3)求出每一期的预测同比值,预测同比值=同期值*(1+同比增速) func GetChartPredictEdbInfoDataListByRuleTbzscz(edbInfoId int, tbEndValue float64, dayList []time.Time, frequency string, realPredictEdbInfoData, predictEdbInfoData []*EdbInfoSearchData, existMap map[string]float64) (newPredictEdbInfoData []*EdbInfoSearchData, minValue, maxValue float64) { allDataList := make([]*EdbInfoSearchData, 0) allDataList = append(allDataList, realPredictEdbInfoData...) allDataList = append(allDataList, predictEdbInfoData...) newPredictEdbInfoData = predictEdbInfoData index := len(allDataList) // 获取近期数据的同比值 if index <= 0 { return } lastData := allDataList[index-1] lastDayTime, _ := time.ParseInLocation(utils.FormatDate, lastData.DataTime, time.Local) var lastTb decimal.Decimal // 计算最新数据与上一期的数据同比值 { //上一年的日期 preDate := lastDayTime.AddDate(-1, 0, 0) preDateStr := preDate.Format(utils.FormatDate) if preValue, ok := existMap[preDateStr]; ok { //上一年同期找到 lastTb = (decimal.NewFromFloat(lastData.Value)).Sub(decimal.NewFromFloat(preValue)).Div(decimal.NewFromFloat(preValue)) } else { switch frequency { case "月度": //向上和向下,各找一个月 nextDateDay := preDate preDateDay := preDate for i := 0; i <= 35; i++ { nextDateDayStr := nextDateDay.Format(utils.FormatDate) if preValue, ok := existMap[nextDateDayStr]; ok { //上一年同期->下一个月找到 lastTb = (decimal.NewFromFloat(lastData.Value)).Sub(decimal.NewFromFloat(preValue)).Div(decimal.NewFromFloat(preValue)) break } else { preDateDayStr := preDateDay.Format(utils.FormatDate) if preValue, ok := existMap[preDateDayStr]; ok { //上一年同期->上一个月找到 lastTb = (decimal.NewFromFloat(lastData.Value)).Sub(decimal.NewFromFloat(preValue)).Div(decimal.NewFromFloat(preValue)) break } } nextDateDay = nextDateDay.AddDate(0, 0, 1) preDateDay = preDateDay.AddDate(0, 0, -1) } case "季度", "年度": if preValue, ok := existMap[preDateStr]; ok { //上一年同期->下一个月找到 lastTb = (decimal.NewFromFloat(lastData.Value)).Sub(decimal.NewFromFloat(preValue)).Div(decimal.NewFromFloat(preValue)) break } default: nextDateDay := preDate preDateDay := preDate for i := 0; i < 35; i++ { nextDateDayStr := nextDateDay.Format(utils.FormatDate) if preValue, ok := existMap[nextDateDayStr]; ok { //上一年同期->下一个月找到 lastTb = (decimal.NewFromFloat(lastData.Value)).Sub(decimal.NewFromFloat(preValue)).Div(decimal.NewFromFloat(preValue)) break } else { preDateDayStr := preDateDay.Format(utils.FormatDate) if preValue, ok := existMap[preDateDayStr]; ok { //上一年同期->上一个月找到 lastTb = (decimal.NewFromFloat(lastData.Value)).Sub(decimal.NewFromFloat(preValue)).Div(decimal.NewFromFloat(preValue)) break } else { //fmt.Println("pre not find:", preDateStr, "i:", i) } } nextDateDay = nextDateDay.AddDate(0, 0, 1) preDateDay = preDateDay.AddDate(0, 0, -1) } } } } //获取后面的预测数据 lenDay := len(dayList) tbEndValueDecimal := decimal.NewFromFloat(tbEndValue) avgTbVal := tbEndValueDecimal.Sub(lastTb).Div(decimal.NewFromInt(int64(lenDay))) predictEdbInfoData = make([]*EdbInfoSearchData, 0) for k, currentDate := range dayList { var tbValue decimal.Decimal if k == lenDay-1 { // 如果是最后的日期了,那么就用终值去计算 tbValue = tbEndValueDecimal.Add(decimal.NewFromInt(1)) } else { // 最近数据的同比值 + (平均增值乘以当前期数) tbValue = lastTb.Add(avgTbVal.Mul(decimal.NewFromInt(int64(k + 1)))).Add(decimal.NewFromInt(1)) } tmpData := &EdbInfoSearchData{ EdbDataId: edbInfoId + 100000 + index + k, //EdbInfoId: edbInfoId, DataTime: currentDate.Format(utils.FormatDate), //Value: dataValue, //DataTimestamp: (currentDate.UnixNano() / 1e6) + 1000, //前端需要让加1s,说是2022-09-01 00:00:00 这样的整点不合适 } var val float64 var calculateStatus bool //计算结果 //currentItem := existMap[av] //上一年的日期 preDate := currentDate.AddDate(-1, 0, 0) preDateStr := preDate.Format(utils.FormatDate) if preValue, ok := existMap[preDateStr]; ok { //上一年同期找到 val, _ = decimal.NewFromFloat(preValue).Mul(tbValue).Round(4).Float64() calculateStatus = true } else { switch frequency { case "月度": //向上和向下,各找一个月 nextDateDay := preDate preDateDay := preDate for i := 0; i <= 35; i++ { nextDateDayStr := nextDateDay.Format(utils.FormatDate) if preValue, ok := existMap[nextDateDayStr]; ok { //上一年同期->下一个月找到 val, _ = decimal.NewFromFloat(preValue).Mul(tbValue).Round(4).Float64() calculateStatus = true break } else { preDateDayStr := preDateDay.Format(utils.FormatDate) if preValue, ok := existMap[preDateDayStr]; ok { //上一年同期->上一个月找到 val, _ = decimal.NewFromFloat(preValue).Mul(tbValue).Round(4).Float64() calculateStatus = true break } } nextDateDay = nextDateDay.AddDate(0, 0, 1) preDateDay = preDateDay.AddDate(0, 0, -1) } case "季度", "年度": if preValue, ok := existMap[preDateStr]; ok { //上一年同期->下一个月找到 val, _ = decimal.NewFromFloat(preValue).Mul(tbValue).Round(4).Float64() calculateStatus = true break } default: nextDateDay := preDate preDateDay := preDate for i := 0; i < 35; i++ { nextDateDayStr := nextDateDay.Format(utils.FormatDate) if preValue, ok := existMap[nextDateDayStr]; ok { //上一年同期->下一个月找到 val, _ = decimal.NewFromFloat(preValue).Mul(tbValue).Round(4).Float64() calculateStatus = true break } else { preDateDayStr := preDateDay.Format(utils.FormatDate) if preValue, ok := existMap[preDateDayStr]; ok { //上一年同期->上一个月找到 val, _ = decimal.NewFromFloat(preValue).Mul(tbValue).Round(4).Float64() calculateStatus = true break } else { //fmt.Println("pre not find:", preDateStr, "i:", i) } } nextDateDay = nextDateDay.AddDate(0, 0, 1) preDateDay = preDateDay.AddDate(0, 0, -1) } } } if calculateStatus { tmpData.Value = val newPredictEdbInfoData = append(newPredictEdbInfoData, tmpData) allDataList = append(allDataList, tmpData) existMap[tmpData.DataTime] = val // 最大最小值 if val < minValue { minValue = val } if val > maxValue { maxValue = val } } } return } // RuleLineNhConf 一元线性拟合规则的配置 type RuleLineNhConf struct { StartDate string `description:"开始日期"` EndDate string `description:"结束日期"` MoveDay int `description:"移动天数"` EdbInfoId int `description:"指标id"` } // GetChartPredictEdbInfoDataListByRuleLineNh 根据 一元线性拟合 的计算规则获取预测数据 // // 选择被预测的指标B(作为自变量,非预测指标),选择指标A(作为因变量,可以是基础指标和预测指标) // 2、选择拟合时间段,起始日期至今或指定时间段,选择至今,在计算时截止到指标B的最新日期 // 3、设定A领先B时间(天),正整数、负整数、0 // 4、调用拟合残差的数据预处理和算法,给出拟合方程Y=aX+b的系数a,b // 5、指标A代入拟合方程得到拟合预测指标B',拟合预测指标使用指标B的频度,在指标B的实际值后面连接拟合预测指标B'对应日期的预测值 // // 注:选择预测截止日期,若所选日期 ≤ 指标A设置领先后的日期序列,则预测指标日期最新日期有值(在指标B'的有值范围内);若所选日期 > 指标A设置领先后的日期序列,则预测指标只到指标A领先后的日期序列(超出指标B'的有值范围,最多到指标B'的最新值);指标A、B更新后,更新预测指标 func GetChartPredictEdbInfoDataListByRuleLineNh(edbInfoId int, dayList []time.Time, realPredictEdbInfoData, predictEdbInfoData []*EdbInfoSearchData, newNhccDataMap, existMap map[string]float64) (newPredictEdbInfoData []*EdbInfoSearchData, minValue, maxValue float64, err error) { allDataList := make([]*EdbInfoSearchData, 0) allDataList = append(allDataList, realPredictEdbInfoData...) allDataList = append(allDataList, predictEdbInfoData...) newPredictEdbInfoData = predictEdbInfoData lenAllData := len(allDataList) if lenAllData <= 0 { return } for k, currentDate := range dayList { // 动态拟合残差值数据 currentDateStr := currentDate.Format(utils.FormatDate) val, ok := newNhccDataMap[currentDateStr] if !ok { continue } tmpData := &EdbInfoSearchData{ EdbDataId: edbInfoId + 100000 + lenAllData + k, //EdbInfoId: edbInfoId, DataTime: currentDateStr, Value: val, //DataTimestamp: (currentDate.UnixNano() / 1e6) + 1000, //前端需要让加1s,说是2022-09-01 00:00:00 这样的整点不合适 } newPredictEdbInfoData = append(newPredictEdbInfoData, tmpData) allDataList = append(allDataList, tmpData) existMap[currentDateStr] = val // 最大最小值 if val < minValue { minValue = val } if val > maxValue { maxValue = val } } return } // getCalculateNhccData 获取计算出来的 拟合残差 数据 func getCalculateNhccData(secondDataList []*EdbInfoSearchData, ruleConf RuleLineNhConf) (newBDataMap map[string]float64, err error, errMsg string) { firstEdbInfoId := ruleConf.EdbInfoId moveDay := ruleConf.MoveDay startDate, _ := time.ParseInLocation(utils.FormatDate, ruleConf.StartDate, time.Local) endDate, _ := time.ParseInLocation(utils.FormatDate, ruleConf.EndDate, time.Local) //查询当前指标现有的数据 edbInfo, err := GetEdbInfoById(firstEdbInfoId) if err != nil { return } //第一个指标 aDataList := make([]EdbInfoSearchData, 0) aDataMap := make(map[string]float64) { //第一个指标的数据列表 var firstDataList []*EdbInfoSearchData switch edbInfo.EdbInfoType { case 0: //获取来源指标的数据 firstDataList, err = GetEdbDataListAll(edbInfo.Source, edbInfo.SubSource, FindEdbDataListAllCond{ EdbInfoId: edbInfo.EdbInfoId, }, 1) case 1: firstDataList, err = GetPredictEdbDataListAllByStartDate(edbInfo, 1, "") default: err = errors.New(fmt.Sprint("获取失败,指标类型异常", edbInfo.EdbInfoType)) } if err != nil { return } aDataList, aDataMap = handleNhccData(firstDataList, moveDay) } //第二个指标 bDataList := make([]EdbInfoSearchData, 0) bDataMap := make(map[string]float64) { bDataList, bDataMap = handleNhccData(secondDataList, 0) } if len(aDataList) <= 0 { errMsg = `自变量没有数据` err = errors.New(errMsg) return } if len(bDataList) <= 0 { errMsg = `因变量没有数据` err = errors.New(errMsg) return } // 拟合残差计算的结束日期判断 { endAData := aDataList[len(aDataList)-1] tmpEndDate, tmpErr := time.ParseInLocation(utils.FormatDate, endAData.DataTime, time.Local) if tmpErr != nil { err = tmpErr return } // 如果A指标的最新数据日期早于拟合残差的结束日期,那么就用A指标的最新数据日期 if tmpEndDate.Before(endDate) { endDate = tmpEndDate } endBData := bDataList[len(bDataList)-1] tmpEndDate, tmpErr = time.ParseInLocation(utils.FormatDate, endBData.DataTime, time.Local) if tmpErr != nil { err = tmpErr return } // 如果B指标的最新数据日期早于拟合残差的结束日期,那么就用A指标的最新数据日期 if tmpEndDate.Before(endDate) { endDate = tmpEndDate } } // 计算线性方程公式 var a, b float64 { coordinateData := make([]utils.Coordinate, 0) for i := startDate; i.Before(endDate) || i.Equal(endDate); i = i.AddDate(0, 0, 1) { dateStr := i.Format(utils.FormatDate) xValue, ok := aDataMap[dateStr] if !ok { errMsg = "自变量日期:" + dateStr + "数据异常,导致计算线性方程公式失败" err = errors.New(errMsg) return } yValue, ok := bDataMap[dateStr] if !ok { errMsg = "因变量日期:" + dateStr + "数据异常,导致计算线性方程公式失败" err = errors.New(errMsg) return } tmpCoordinate := utils.Coordinate{ X: xValue, Y: yValue, } coordinateData = append(coordinateData, tmpCoordinate) } a, b = utils.GetLinearResult(coordinateData) } if math.IsNaN(a) || math.IsNaN(b) { errMsg = "线性方程公式生成失败" err = errors.New(errMsg) return } //fmt.Println("a:", a, ";======b:", b) //计算B’ newBDataMap = make(map[string]float64) { //B’=aA+b aDecimal := decimal.NewFromFloat(a) bDecimal := decimal.NewFromFloat(b) for _, aData := range aDataList { xDecimal := decimal.NewFromFloat(aData.Value) val, _ := aDecimal.Mul(xDecimal).Add(bDecimal).Round(4).Float64() newBDataMap[aData.DataTime] = val } } return } // GetChartPredictEdbInfoDataListByRuleNAnnualAverage 根据 N年均值 规则获取预测数据 // ETA预测规则:N年均值:过去N年同期均值。过去N年可以连续或者不连续,指标数据均用线性插值补全为日度数据后计算; func GetChartPredictEdbInfoDataListByRuleNAnnualAverage(edbInfoId int, configValue string, dayList []time.Time, realPredictEdbInfoData, predictEdbInfoData []*EdbInfoSearchData, existMap map[string]float64) (newPredictEdbInfoData []*EdbInfoSearchData, minValue, maxValue float64, err error) { // 获取配置的年份列表 yearList, _, err := getYearListBySeasonConf(configValue) if err != nil { return } allDataList := make([]*EdbInfoSearchData, 0) allDataList = append(allDataList, realPredictEdbInfoData...) allDataList = append(allDataList, predictEdbInfoData...) newPredictEdbInfoData = predictEdbInfoData // 插值法数据处理 handleDataMap := make(map[string]float64) _, err = HandleDataByLinearRegression(allDataList, handleDataMap) if err != nil { return } index := len(allDataList) //获取后面的预测数据 predictEdbInfoData = make([]*EdbInfoSearchData, 0) for k, currentDate := range dayList { // 如果遇到闰二月,如2.29,去掉该天数据 if strings.Contains(currentDate.Format(utils.FormatDate), "02-29") { continue } tmpK := len(allDataList) - 1 //上1期数据的下标 lastDayData := allDataList[tmpK] // 上1期的数据 tmpHistoryVal := decimal.NewFromFloat(0) //往期的差值总和 tmpHistoryValNum := 0 // 往期差值计算的数量 for _, year := range yearList { //前几年当日的日期 tmpHistoryCurrentDate := currentDate.AddDate(year-currentDate.Year(), 0, 0) if val, ok := handleDataMap[tmpHistoryCurrentDate.Format(utils.FormatDate)]; ok { tmpHistoryVal = tmpHistoryVal.Add(decimal.NewFromFloat(val)) tmpHistoryValNum++ } } //计算的差值与选择的年份数量不一致,那么当前日期不计算 if tmpHistoryValNum != len(yearList) { continue } val, _ := tmpHistoryVal.Div(decimal.NewFromInt(int64(tmpHistoryValNum))).Round(4).Float64() currentDateStr := currentDate.Format(utils.FormatDate) tmpData := &EdbInfoSearchData{ EdbDataId: edbInfoId + 100000 + index + k, //EdbInfoId: edbInfoId, DataTime: currentDateStr, Value: val, //DataTimestamp: currentDate.UnixNano() / 1e6, } newPredictEdbInfoData = append(newPredictEdbInfoData, tmpData) allDataList = append(allDataList, tmpData) existMap[currentDateStr] = val // 继续使用插值法补充新预测日期的数据之间的值 _, err = HandleDataByLinearRegression([]*EdbInfoSearchData{ lastDayData, tmpData, }, handleDataMap) if err != nil { return } // 最大最小值 if val < minValue { minValue = val } if val > maxValue { maxValue = val } } return } // AnnualValueInversionConf 年度值倒推规则 type AnnualValueInversionConf struct { Value float64 `description:"年度值"` Type int `description:"分配方式,1:均值法;2:同比法"` Year int `description:"同比年份"` } // GetChartPredictEdbInfoDataListByRuleAnnualValueInversion 根据 年度值倒推 规则获取预测数据 // ETA预测规则:年度值倒推:设定年度值,余额=年度值-年初至今累计值(算法参考累计值),进行余额分配,均值法分配时保证每期数值相等(日度/周度:剩余期数=剩余自然日历天数/今年指标最新日期自然日历天数*今年至今指标数据期数;旬度/月度/季度/半年度:剩余期数=全年期数(36\12\4\2)-今年至今自然日历期数),同比法保证每期同比相等(同比增速=余额/同比年份相应日期的余额,预测值等于同比年份同期值*同比增速) // 举例: // 指标A 日度 最新日期 2023-05-19 年初至今累计值100 // 设置年度值1000 // 则余额=1000-100=900 // 均值法分配:剩余期数=226/139*120=195.11 // 今年之后的每一期预测值=900/195.11=4.6128 // 同比法分配:同比增速=900/同比年份5.19的余额 // 预测值=同比年份5-20的值*(1+同比增速) func GetChartPredictEdbInfoDataListByRuleAnnualValueInversion(edbInfoId int, configValue string, dayList []time.Time, frequency string, realPredictEdbInfoData, predictEdbInfoData []*EdbInfoSearchData, existMap map[string]float64) (newPredictEdbInfoData []*EdbInfoSearchData, minValue, maxValue float64, err error) { if frequency == "年度" { err = errors.New("当前指标频度是年度,不允许配置年度值倒推") return } // 获取配置 var annualValueInversionConf AnnualValueInversionConf err = json.Unmarshal([]byte(configValue), &annualValueInversionConf) if err != nil { err = errors.New("年度值倒推配置信息异常:" + err.Error()) return } allDataList := make([]*EdbInfoSearchData, 0) allDataList = append(allDataList, realPredictEdbInfoData...) allDataList = append(allDataList, predictEdbInfoData...) newPredictEdbInfoData = predictEdbInfoData index := len(allDataList) // 没有数据,直接返回 if index <= 0 { return } // 配置的年度值 yearValueConfig := annualValueInversionConf.Value // 最新数据的日期 currDayTime, err := time.ParseInLocation(utils.FormatDate, allDataList[index-1].DataTime, time.Local) if err != nil { return } // 当前年的日期 lastDayTime := dayList[len(dayList)-1] if currDayTime.Year() != lastDayTime.Year() { err = errors.New("年度值倒推不支持跨年预测") return } // 均值法 if annualValueInversionConf.Type == 1 { // 当前年的期数 currYearN := 0 // 当前已经消耗的额度 var currYearVal float64 // 计算当前年的期数以及已经消耗的额度 { if frequency != "周度" { for _, v := range allDataList { currTime, tmpErr := time.ParseInLocation(utils.FormatDate, v.DataTime, time.Local) if tmpErr != nil { err = tmpErr return } // 只是计算今年的 if currTime.Year() != currDayTime.Year() { continue } currYearN++ currYearVal = currYearVal + v.Value } } else { tmpDataList := make([]*EdbInfoSearchData, 0) // 上一期的数据 var lastData *EdbInfoSearchData // 是否第一条数据 isFirst := true for _, v := range allDataList { currTime, tmpErr := time.ParseInLocation(utils.FormatDate, v.DataTime, time.Local) if tmpErr != nil { err = tmpErr return } // 只是计算今年的 if currTime.Year() != currDayTime.Year() { lastData = v continue } if isFirst { tmpDataList = append(tmpDataList, lastData) } isFirst = false tmpDataList = append(tmpDataList, v) currYearN++ } // 需要插值法处理 tmpHandleDataMap := make(map[string]float64) _, err = HandleDataByLinearRegression(tmpDataList, tmpHandleDataMap) if err != nil { return } for tmpDate, val := range tmpHandleDataMap { tmpDateTime, tmpErr := time.ParseInLocation(utils.FormatDate, tmpDate, time.Local) if tmpErr != nil { err = tmpErr return } if tmpDateTime.Year() != currDayTime.Year() { continue } currYearVal = currYearVal + val } currYearVal = currYearVal / 7 } } var averageVal float64 switch frequency { case "半年度": averageVal, _ = (decimal.NewFromFloat(yearValueConfig).Sub(decimal.NewFromFloat(currYearVal))).Div(decimal.NewFromInt(int64(2 - currYearN))).Float64() case "季度": averageVal, _ = (decimal.NewFromFloat(yearValueConfig).Sub(decimal.NewFromFloat(currYearVal))).Div(decimal.NewFromInt(int64(4 - currYearN))).Float64() case "月度": averageVal, _ = (decimal.NewFromFloat(yearValueConfig).Sub(decimal.NewFromFloat(currYearVal))).Div(decimal.NewFromInt(int64(12 - currYearN))).Float64() case "旬度": averageVal, _ = (decimal.NewFromFloat(yearValueConfig).Sub(decimal.NewFromFloat(currYearVal))).Div(decimal.NewFromInt(int64(36 - currYearN))).Float64() case "周度", "日度": //剩余期数=剩余自然日历天数/今年指标最新日期自然日历天数*今年至今指标数据期数 // 当前年的第一天 yearFirstDay := time.Date(currDayTime.Year(), 1, 1, 0, 0, 0, 0, time.Local) subDay := utils.GetTimeSubDay(yearFirstDay, currDayTime) + 1 // 当前年的最后一天 yearLastDay := time.Date(currDayTime.Year(), 12, 31, 0, 0, 0, 0, time.Local) subDay2 := utils.GetTimeSubDay(yearFirstDay, yearLastDay) + 1 // 剩余期数 surplusN := decimal.NewFromInt(int64(subDay2 - subDay)).Div(decimal.NewFromInt(int64(subDay))).Mul(decimal.NewFromInt(int64(currYearN))) // 剩余余额 balance := decimal.NewFromFloat(annualValueInversionConf.Value).Sub(decimal.NewFromFloat(currYearVal)) averageVal, _ = balance.Div(surplusN).Round(4).Float64() } // 保留四位小数 averageVal, _ = decimal.NewFromFloat(averageVal).Round(4).Float64() for k, currentDate := range dayList { currentDateStr := currentDate.Format(utils.FormatDate) tmpData := &EdbInfoSearchData{ EdbDataId: edbInfoId + 100000 + index + k, //EdbInfoId: edbInfoId, DataTime: currentDateStr, Value: averageVal, //DataTimestamp: currentDate.UnixNano() / 1e6, } newPredictEdbInfoData = append(newPredictEdbInfoData, tmpData) allDataList = append(allDataList, tmpData) existMap[currentDateStr] = averageVal } // 最大最小值 if averageVal < minValue { minValue = averageVal } if averageVal > maxValue { maxValue = averageVal } return } // 同比法分配 // 同比法保证每期同比相等(同比增速=余额/同比年份相应日期的余额,预测值等于同比年份同期值*同比增速); // 同比法分配:同比增速=900/同比年份5.19的余额 // 每年截止到当前日期的累计值 dateTotalMap := make(map[time.Time]float64) // 每年的累计值(计算使用) yearTotalMap := make(map[int]float64) for _, v := range allDataList { currTime, tmpErr := time.ParseInLocation(utils.FormatDate, v.DataTime, time.Local) if tmpErr != nil { err = tmpErr return } yearVal := yearTotalMap[currTime.Year()] yearVal = yearVal + v.Value yearTotalMap[currTime.Year()] = yearVal dateTotalMap[currTime] = yearVal } //(同比增速=余额/同比年份相应日期的余额,预测值等于同比年份同期值*同比增速); for k, currentDate := range dayList { currYearBalance := yearValueConfig - yearTotalMap[currentDate.Year()] // 当年的余额 // 上一期的日期 prevDateStr := allDataList[len(allDataList)-1].DataTime prevDateTime, tmpErr := time.ParseInLocation(utils.FormatDate, prevDateStr, time.Local) if tmpErr != nil { err = tmpErr return } //同比年份相应日期 lastYear := annualValueInversionConf.Year + (currentDate.Year() - currDayTime.Year()) // 前N年的上一期时间;前N年的当期时间; var lastPrevDateTime, lastDateTime time.Time switch frequency { case "半年度", "季度": lastDateTime = time.Date(lastYear, currentDate.Month(), currentDate.Day(), 0, 0, 0, 0, currentDate.Location()) lastPrevDateTime = time.Date(lastYear, prevDateTime.Month(), prevDateTime.Day(), 0, 0, 0, 0, prevDateTime.Location()) case "月度": lastDateTime = time.Date(lastYear, currentDate.Month()+1, 1, 0, 0, 0, 0, currentDate.Location()).AddDate(0, 0, -1) lastPrevDateTime = time.Date(lastYear, prevDateTime.Month()+1, 1, 0, 0, 0, 0, prevDateTime.Location()).AddDate(0, 0, -1) case "旬度": if prevDateTime.Day() == 10 || prevDateTime.Day() == 20 { lastDateTime = time.Date(lastYear, currentDate.Month(), currentDate.Day(), 0, 0, 0, 0, currentDate.Location()) lastPrevDateTime = time.Date(lastYear, prevDateTime.Month(), prevDateTime.Day(), 0, 0, 0, 0, prevDateTime.Location()) } else { lastDateTime = time.Date(lastYear, currentDate.Month()+1, 1, 0, 0, 0, 0, currentDate.Location()).AddDate(0, 0, -1) lastPrevDateTime = time.Date(lastYear, prevDateTime.Month()+1, 1, 0, 0, 0, 0, prevDateTime.Location()).AddDate(0, 0, -1) } case "周度", "日度": lastDateTime = time.Date(lastYear, currentDate.Month(), currentDate.Day(), 0, 0, 0, 0, currentDate.Location()) lastPrevDateTime = time.Date(lastYear, prevDateTime.Month(), prevDateTime.Day(), 0, 0, 0, 0, prevDateTime.Location()) } // 同比年份相应日期的累计值 var dateTotal float64 dateTotal, ok := dateTotalMap[lastPrevDateTime] if !ok { //如果没有找到这个日期,那么就往前面找,一直到找到这个累计值,或者找完这一年 yearFirstDayTime := time.Date(lastPrevDateTime.Year(), 1, 1, 0, 0, 0, 0, lastDateTime.Location()) for tmpDateTime := lastPrevDateTime.AddDate(0, 0, -1); tmpDateTime.After(yearFirstDayTime) || tmpDateTime.Equal(yearFirstDayTime); tmpDateTime = tmpDateTime.AddDate(0, 0, -1) { dateTotal, ok = dateTotalMap[tmpDateTime] if ok { break } } } //同比年份相应的上一期日期的余额 lastYearDateBalance := yearTotalMap[lastPrevDateTime.Year()] - dateTotal if lastYearDateBalance == 0 { continue } // 同比增速=当年余额/同比年份上一期日期的余额 tbVal := decimal.NewFromFloat(currYearBalance).Div(decimal.NewFromFloat(lastYearDateBalance)) // 获取同比年份同期值,获取失败的话,就不处理 if lastDateVal, ok := existMap[lastDateTime.Format(utils.FormatDate)]; ok { //预测值 = 同比年份同期值*同比增速 tmpVal, _ := decimal.NewFromFloat(lastDateVal).Mul(tbVal).Round(4).Float64() currentDateStr := currentDate.Format(utils.FormatDate) tmpData := &EdbInfoSearchData{ EdbDataId: edbInfoId + 100000 + index + k, //EdbInfoId: edbInfoId, DataTime: currentDateStr, Value: tmpVal, //DataTimestamp: currentDate.UnixNano() / 1e6, } newPredictEdbInfoData = append(newPredictEdbInfoData, tmpData) allDataList = append(allDataList, tmpData) existMap[currentDateStr] = tmpVal yearVal := yearTotalMap[currentDate.Year()] yearVal = yearVal + tmpVal yearTotalMap[currentDate.Year()] = yearVal dateTotalMap[currentDate] = yearVal // 最大最小值 if tmpVal < minValue { minValue = tmpVal } if tmpVal > maxValue { maxValue = tmpVal } } } return } // getYearListBySeasonConf 根据配置获取年份列表 func getYearListBySeasonConf(configValue string) (yearList []int, seasonConf SeasonConf, err error) { tmpErr := json.Unmarshal([]byte(configValue), &seasonConf) if tmpErr != nil { err = errors.New("年份配置信息异常:" + tmpErr.Error()) return } //选择方式,1:连续N年;2:指定年份 if seasonConf.YearType == 1 { if seasonConf.NValue < 1 { err = errors.New("连续N年不允许小于1") return } currYear := time.Now().Year() for i := 0; i < seasonConf.NValue; i++ { yearList = append(yearList, currYear-i-1) } } else { yearList = seasonConf.YearList } return }