package future_good import ( "errors" "fmt" "github.com/shopspring/decimal" "github.com/yidane/formula" "hongze/hz_crm_api/models/data_manage" "hongze/hz_crm_api/models/data_manage/future_good" "hongze/hz_crm_api/models/data_manage/future_good/request" "hongze/hz_crm_api/services/data" "hongze/hz_crm_api/utils" "sort" "strconv" "strings" "time" ) // GetProfitChartEdbData 获取利润图表的指标数据 func GetProfitChartEdbData(baseEdbInfo *data_manage.EdbInfo, zlFutureGoodEdbInfoList []*future_good.FutureGoodEdbInfo, chartInfoDateList []request.ChartInfoDateReq, formulaStr string, edbInfoFromTagList []data_manage.EdbInfoFromTag) (barConfigEdbInfoIdList []data_manage.BarChartInfoEdbItemReq, edbList []*data_manage.ChartEdbInfoMapping, xEdbIdValue []int, xDataList []data_manage.XData, yDataList []data_manage.YData, err error) { edbList = make([]*data_manage.ChartEdbInfoMapping, 0) if baseEdbInfo == nil { err = errors.New("ETA指标未选取") return } if len(zlFutureGoodEdbInfoList) <= 0 { err = errors.New("商品指标未选取") return } // 标签与期货商品指标的关联关系 tagEdbIdMap := make(map[string]int) // 有效的期货商品指标 futureGoodEdbInfoIdMap := make(map[int]int) { tmpTagEdbIdMap := make(map[string]int) for _, v := range edbInfoFromTagList { tmpTagEdbIdMap[v.FromTag] = v.EdbInfoId } formulaMap := CheckFormula(formulaStr) for _, tag := range formulaMap { tagEdbIdMap[tag] = tmpTagEdbIdMap[tag] futureGoodEdbInfoIdMap[tmpTagEdbIdMap[tag]] = tmpTagEdbIdMap[tag] } } // 指标对应的所有数据 //edbDataListMap := make(map[int][]*data_manage.EdbDataList) item := new(data_manage.ChartEdbInfoMapping) item.FrequencyEn = data.GetFrequencyEn(baseEdbInfo.Frequency) if baseEdbInfo.Unit == `无` { item.Unit = `` } // 普通的指标数据 baseDataList := make([]*data_manage.EdbDataList, 0) baseDataList, err = data_manage.GetEdbDataList(baseEdbInfo.Source, baseEdbInfo.EdbInfoId, "", "") if err != nil { return } latestDate := zlFutureGoodEdbInfoList[0].EndDate latestDateTime, _ := time.ParseInLocation(utils.FormatDate, latestDate, time.Local) earliestDateTime := latestDateTime // 数据的最早日期,目的是为了找出最早的合约 for _, barChartInfoDate := range chartInfoDateList { var findDateTime time.Time switch barChartInfoDate.Type { case 1: //最新值 findDateTime = latestDateTime case 2: //近期几天 findDateTime = latestDateTime.AddDate(0, 0, -barChartInfoDate.Value) case 3: // 固定日期 //寻找固定日期的数据 tmpFindDateTime, tmpErr := time.ParseInLocation(utils.FormatDate, barChartInfoDate.Date, time.Local) if tmpErr != nil { err = tmpErr return } findDateTime = tmpFindDateTime default: err = errors.New(fmt.Sprint("日期类型异常,Type:", barChartInfoDate.Type)) return } if findDateTime.IsZero() { err = errors.New("错误的日期") return } if findDateTime.Before(earliestDateTime) { earliestDateTime = findDateTime } } monthNum := (latestDateTime.Year()-earliestDateTime.Year())*12 + int(latestDateTime.Month()-earliestDateTime.Month()) // 存储主力合约下的所有月份合约 futureGoodEdbInfoDateMap := make(map[int]map[string]*future_good.FutureGoodEdbInfo) futureGoodDataListMap := make(map[int][]*data_manage.EdbDataList, 0) // 特殊的商品期货合约(只有M+N的合约,没有固定日期的合约) specialFutureGoodEdbInfoMap := make(map[int]map[int]*future_good.FutureGoodEdbInfo, 0) isAllChina := true // 是否都是国内的期货合约 for _, v := range zlFutureGoodEdbInfoList { if v.RegionType != "国内" { isAllChina = false break } } nMap := make(map[string]string) var maxN int // 最大N值 for _, v := range zlFutureGoodEdbInfoList { // 如果不是有效的商品期货指标,那么就过滤掉,不做数据查询处理,避免没必要的请求 if _, ok := futureGoodEdbInfoIdMap[v.FutureGoodEdbInfoId]; !ok { continue } // 获取期货指标以及期货数据 tmpFutureGoodEdbInfoList, tmpErr := future_good.GetChildFutureGoodEdbInfoListByParentId(v.FutureGoodEdbInfoId) if tmpErr != nil { err = tmpErr return } childFutureGoodEdbInfoMap, tmpMaxN, tmpErr := getProfitFutureGoodEdbInfoList(earliestDateTime, v, tmpFutureGoodEdbInfoList, isAllChina, monthNum) if tmpErr != nil { err = tmpErr return } if maxN < tmpMaxN { maxN = tmpMaxN } futureGoodEdbInfoDateMap[v.FutureGoodEdbInfoId] = childFutureGoodEdbInfoMap if v.FutureGoodEdbType == 2 { specialFutureGoodEdbInfoMap[v.FutureGoodEdbInfoId] = make(map[int]*future_good.FutureGoodEdbInfo) } // 获取数据 for date, childFutureGoodEdbInfo := range childFutureGoodEdbInfoMap { nMap[date] = date dataList := make([]*data_manage.EdbDataList, 0) tmpDataList, tmpErr := future_good.GetFutureGoodEdbDataListByDate(childFutureGoodEdbInfo.FutureGoodEdbInfoId, "", "") if tmpErr != nil { return } for _, tmpData := range tmpDataList { dataList = append(dataList, &data_manage.EdbDataList{ EdbDataId: tmpData.FutureGoodEdbDataId, EdbInfoId: tmpData.FutureGoodEdbInfoId, DataTime: tmpData.DataTime.Format(utils.FormatDate), DataTimestamp: tmpData.DataTimestamp, Value: tmpData.Close, }) } futureGoodDataListMap[childFutureGoodEdbInfo.FutureGoodEdbInfoId] = dataList if childFutureGoodEdbInfo.FutureGoodEdbType == 2 { specialFutureGoodEdbInfoMap[v.FutureGoodEdbInfoId][childFutureGoodEdbInfo.Month] = childFutureGoodEdbInfo } } } // 找出所有的N值,并进行正序排列 dateList := make([]string, 0) for _, n := range nMap { dateList = append(dateList, n) } sort.Slice(dateList, func(i, j int) bool { return dateList[i] < dateList[j] }) _, yDataList, err = ProfitChartChartData(baseDataList, futureGoodEdbInfoDateMap, futureGoodDataListMap, chartInfoDateList, latestDate, specialFutureGoodEdbInfoMap, formulaStr, tagEdbIdMap, dateList, maxN) tmpXDataList, newYDataList, err := handleProfitResultData(baseEdbInfo, yDataList, earliestDateTime) if err != nil { return } xDataList = []data_manage.XData{ { Name: "现货利润", NameEn: "Spot Price", }, } xDataList = append(xDataList, tmpXDataList...) yDataList = newYDataList return } // ProfitChartChartData 获取数据 func ProfitChartChartData(baseDataList []*data_manage.EdbDataList, futureGoodEdbInfoMap map[int]map[string]*future_good.FutureGoodEdbInfo, futureGoodEdbDataListMap map[int][]*data_manage.EdbDataList, chartInfoDateList []request.ChartInfoDateReq, latestDate string, specialFutureGoodEdbInfoMap map[int]map[int]*future_good.FutureGoodEdbInfo, formulaStr string, tagEdbIdMap map[string]int, dateList []string, maxN int) (edbIdList []int, yDataList []data_manage.YData, err error) { // 指标数据数组(10086:{"2022-12-02":100.01,"2022-12-01":102.3}) //earliestDateTime time.Time // ETA指标数据 baseEdbDateData := make(map[string]float64) for _, edbData := range baseDataList { baseEdbDateData[edbData.DataTime] = edbData.Value } // 商品指标数据 edbDataMap := make(map[int]map[string]float64) for edbInfoId, edbDataList := range futureGoodEdbDataListMap { edbDateData := make(map[string]float64) for _, edbData := range edbDataList { edbDateData[edbData.DataTime] = edbData.Value } edbDataMap[edbInfoId] = edbDateData } latestDateTime, _ := time.ParseInLocation(utils.FormatDate, latestDate, time.Local) yDataList = make([]data_manage.YData, 0) //y轴的数据列表 // 将计算公式中的字母转大写 formulaStr = strings.ToUpper(formulaStr) for _, barChartInfoDate := range chartInfoDateList { yDataMap := make(map[int]float64) var maxDate time.Time var findDateTime time.Time switch barChartInfoDate.Type { case 1: //最新值 findDateTime = latestDateTime case 2: //近期几天 findDateTime = latestDateTime.AddDate(0, 0, -barChartInfoDate.Value) case 3: // 固定日期 //寻找固定日期的数据 tmpFindDateTime, tmpErr := time.ParseInLocation(utils.FormatDate, barChartInfoDate.Date, time.Local) if tmpErr != nil { err = tmpErr return } findDateTime = tmpFindDateTime default: err = errors.New(fmt.Sprint("日期类型异常,Type:", barChartInfoDate.Type)) return } if findDateTime.IsZero() { err = errors.New("错误的日期") return } findDataList := make([]float64, 0) // 当前日期的数据值 noDataIdList := make([]int, 0) // 没有数据的指标id xEdbInfoIdList := make([]int, 0) // 当前数据的指标id列表 // 现货指标 realDateTime, findDataValue, isFind, tmpErr := GetNeedDateData(findDateTime, baseDataList, baseEdbDateData) if tmpErr != nil { err = tmpErr return } findDataList = append(findDataList, findDataValue) yDataMap[0] = findDataValue if isFind { maxDate = realDateTime } xEdbInfoIdList = append(xEdbInfoIdList, 0) mList := make([]int, 0) // 间隔月份 // 最小开始的n值 //minN := (findDateTime.Year()-earliestDateTime.Year())*12 + int(findDateTime.Month()-earliestDateTime.Month()) for _, date := range dateList { currDate, _ := time.ParseInLocation(utils.FormatYearMonthDate, date, time.Local) // 如果当前的n值小于最小开始的n值,那么就不处理 //if n < minN { // continue //} //findDateTime // 获取当前日期相对开始日期的期数 tmpN := (currDate.Year()-findDateTime.Year())*12 + int(currDate.Month()-findDateTime.Month()) if tmpN <= 0 { continue } // 如果期数大于最大期数,那么就退出当前匹配 if tmpN >= maxN { break } zlAndChildEdbId := make(map[int]int) childFutureGoodEdbInfoIdList := make([]int, 0) for zlFutureGoodEdbInfoId, futureGoodEdbInfoList := range futureGoodEdbInfoMap { // 判断是否特殊合约 if childFutureGoodEdbInfoIdMap, ok := specialFutureGoodEdbInfoMap[zlFutureGoodEdbInfoId]; ok { if childFutureGoodEdbInfo, ok2 := childFutureGoodEdbInfoIdMap[tmpN]; ok2 { childFutureGoodEdbInfoIdList = append(childFutureGoodEdbInfoIdList, childFutureGoodEdbInfo.FutureGoodEdbInfoId) zlAndChildEdbId[zlFutureGoodEdbInfoId] = childFutureGoodEdbInfo.FutureGoodEdbInfoId } } else { if childFutureGoodEdbInfo, ok2 := futureGoodEdbInfoList[date]; ok2 { childFutureGoodEdbInfoIdList = append(childFutureGoodEdbInfoIdList, childFutureGoodEdbInfo.FutureGoodEdbInfoId) zlAndChildEdbId[zlFutureGoodEdbInfoId] = childFutureGoodEdbInfo.FutureGoodEdbInfoId } } } // 合约不全,不参与计算 //if len(childFutureGoodEdbInfoIdList) != lenZlFutureGoodEdbInfo { // continue //} calculateMap := make(map[int]float64) for _, childFutureGoodEdbInfoId := range childFutureGoodEdbInfoIdList { tmpRealDateTime := findDateTime tmpFindDataValue, tmpIsFind := edbDataMap[childFutureGoodEdbInfoId][findDateTime.Format(utils.FormatDate)] if tmpIsFind && tmpFindDataValue != 0 { calculateMap[childFutureGoodEdbInfoId] = tmpFindDataValue if maxDate.IsZero() || maxDate.Before(tmpRealDateTime) { maxDate = tmpRealDateTime } } } // 合约的数据不全,不参与计算 //if len(calculateMap) != lenZlFutureGoodEdbInfo { // continue //} newTagEdbIdMap := make(map[string]int) for tag, zlEdbId := range tagEdbIdMap { newTagEdbIdMap[tag] = zlAndChildEdbId[zlEdbId] } //, formulaStr string, tagEdbIdMap map[string]int formulaFormStr := ReplaceFormula(newTagEdbIdMap, calculateMap, formulaStr) //计算公式异常,那么就移除该指标 if formulaFormStr == `` { //removeDateList = append(removeDateList, sk) //fmt.Println("异常了") continue } expression := formula.NewExpression(formulaFormStr) calResult, evaluateErr := expression.Evaluate() if evaluateErr != nil { // 分母为0的报错 if strings.Contains(evaluateErr.Error(), "divide by zero") { //removeDateList = append(removeDateList, sk) continue } err = errors.New("计算失败:Err:" + evaluateErr.Error() + ";formulaStr:" + formulaFormStr) fmt.Println(err) continue } // 如果计算结果是NAN,那么就退出当前循环 if calResult.IsNan() { continue } calVal, tmpErr := calResult.Float64() if tmpErr != nil { err = errors.New("计算失败:获取计算值失败 Err:" + tmpErr.Error() + ";formulaStr:" + formulaFormStr) fmt.Println(err) continue } //yDataMap[n] = calVal //xEdbInfoIdList = append(xEdbInfoIdList, n) calVal, _ = decimal.NewFromFloat(calVal).Round(4).Float64() yDataMap[tmpN] = calVal xEdbInfoIdList = append(xEdbInfoIdList, tmpN) findDataList = append(findDataList, calVal) } yName := barChartInfoDate.Name yNameEn := barChartInfoDate.Name if yName == `` { if barChartInfoDate.Type == 2 { yName = strconv.Itoa(barChartInfoDate.Value) + "天前" if barChartInfoDate.Value == 1 { yNameEn = strconv.Itoa(barChartInfoDate.Value) + "day ago" } else { yNameEn = strconv.Itoa(barChartInfoDate.Value) + " days ago" } } else { yName = maxDate.Format(utils.FormatDate) yNameEn = maxDate.Format(utils.FormatDate) } } yDate := "0000-00-00" if !maxDate.IsZero() { yDate = maxDate.Format(utils.FormatDate) } yDataList = append(yDataList, data_manage.YData{ Date: yDate, ConfigDate: findDateTime, Value: findDataList, NoDataEdbList: noDataIdList, XEdbInfoIdList: xEdbInfoIdList, Color: barChartInfoDate.Color, Name: yName, NameEn: yNameEn, EdbValMap: yDataMap, M: mList, }) } return } // getFutureGoodEdbInfoList 获取适用的指标列表 func getProfitFutureGoodEdbInfoList(earliestDateTime time.Time, zlFutureGoodEdbInfo *future_good.FutureGoodEdbInfo, tmpFutureGoodEdbInfoList []*future_good.FutureGoodEdbInfo, isAllChina bool, monthNum int) (futureGoodEdbInfoDateMap map[string]*future_good.FutureGoodEdbInfo, newMaxN int, err error) { maxN := 36 //最大36期合约 futureGoodEdbInfoList := make([]*future_good.FutureGoodEdbInfo, 0) futureGoodEdbInfoDateMap = make(map[string]*future_good.FutureGoodEdbInfo) earliestDateTime = time.Date(earliestDateTime.Year(), earliestDateTime.Month(), 1, 0, 0, 0, 0, time.Local) if zlFutureGoodEdbInfo.RegionType == "国内" { startMonth := int(earliestDateTime.Month()) if startMonth == 1 { futureGoodEdbInfoList = tmpFutureGoodEdbInfoList } else { // 因为是下标,所以对应想下标是需要-1的 index := startMonth - 1 futureGoodEdbInfoList = tmpFutureGoodEdbInfoList[index:] futureGoodEdbInfoList = append(futureGoodEdbInfoList, tmpFutureGoodEdbInfoList[:index]...) } lenFutureGoodEdbInfoList := len(futureGoodEdbInfoList) //futureGoodEdbInfoList //if isAllChina { //} // 如果全是国内指标,那么只需要拼上多出的几期合约即可 maxN = lenFutureGoodEdbInfoList + monthNum for i := 1; i < maxN; i++ { k := i % lenFutureGoodEdbInfoList futureGoodEdbInfoDateMap[earliestDateTime.AddDate(0, i, 0).Format(utils.FormatYearMonthDate)] = futureGoodEdbInfoList[k] } //需求池604,只要是国内合约,最大必须是12期 newMaxN = 12 return } for _, v := range tmpFutureGoodEdbInfoList { //海外的连续日期,目前 if v.FutureGoodEdbType == 2 { if v.Month <= maxN { futureGoodEdbInfoDateMap[earliestDateTime.AddDate(0, v.Month, 0).Format(utils.FormatYearMonthDate)] = v if v.Month > newMaxN { newMaxN = v.Month } } continue } if v.Year < earliestDateTime.Year() { continue } // 小于等于当前年,那么就肯定是ok的 if v.Year == earliestDateTime.Year() && v.Month <= int(earliestDateTime.Month()) { continue } subYear := v.Year - earliestDateTime.Year() subMonth := v.Month - int(earliestDateTime.Month()) // 如果(当前年-最新日期的年份) * 12个月 + (当前月-最新日期的月份) 小于总月份 tmpN := subYear*12 + subMonth if tmpN < maxN { tmpDateTime := time.Date(v.Year, time.Month(v.Month), 0, 0, 0, 0, 0, time.Local) futureGoodEdbInfoDateMap[tmpDateTime.Format(utils.FormatYearMonthDate)] = v if tmpN > newMaxN { newMaxN = tmpN } continue } } return } // handleProfitResultData 处理成最终的结果数据 func handleProfitResultData(baseEdbInfo *data_manage.EdbInfo, yDataList []data_manage.YData, earliestDateTime time.Time) (xDataList []data_manage.XData, newYDataList []data_manage.YData, err error) { xDataList = make([]data_manage.XData, 0) newYDataList = yDataList nMap := make(map[int]int) for _, v := range yDataList { for _, n := range v.XEdbInfoIdList { nMap[n] = n } } // 找出所有的N值,并进行正序排列 nList := make([]int, 0) for _, n := range nMap { nList = append(nList, n) } sort.Slice(nList, func(i, j int) bool { return nList[i] < nList[j] }) for _, n := range nList { if n == 0 { continue } xDataList = append(xDataList, data_manage.XData{ Name: fmt.Sprint("M+", n), NameEn: fmt.Sprint("M+", n), }) } for yIndex, yData := range yDataList { newYDataList[yIndex].XEdbInfoIdList = []int{} newYDataList[yIndex].Value = []float64{} tmpNList := nList xEdbInfoIdList := yData.XEdbInfoIdList valIndex := 0 needNum := 0 for _, n := range tmpNList { if len(xEdbInfoIdList) > 0 { currN := xEdbInfoIdList[0] // 当前距离最早的日期相差的N数 if n == currN { if needNum > 0 { currVal := yData.Value[valIndex] preVal := yData.Value[valIndex-1] hcValDeci := decimal.NewFromFloat(currVal).Sub(decimal.NewFromFloat(preVal)).Div(decimal.NewFromInt(int64(needNum + 1))) for tmpNum := 0; tmpNum < needNum; tmpNum++ { newYDataList[yIndex].XEdbInfoIdList = append(newYDataList[yIndex].XEdbInfoIdList, 0) // 赋值平均值 tmpVal, _ := decimal.NewFromFloat(preVal).Add(hcValDeci.Mul(decimal.NewFromInt(int64(tmpNum + 1)))).RoundCeil(4).Float64() newYDataList[yIndex].Value = append(newYDataList[yIndex].Value, tmpVal) } } newYDataList[yIndex].XEdbInfoIdList = append(newYDataList[yIndex].XEdbInfoIdList, currN+1) newYDataList[yIndex].Value = append(newYDataList[yIndex].Value, yData.Value[valIndex]) valIndex++ needNum = 0 if len(xEdbInfoIdList) > 0 { xEdbInfoIdList = xEdbInfoIdList[1:] } } else { needNum++ } } } } maxI := 0 for _, yData := range newYDataList { lenEdb := len(yData.XEdbInfoIdList) for i := 0; i < lenEdb; i++ { if yData.XEdbInfoIdList[i] != 0 && !utils.InArrayByInt(yData.NoDataEdbList, yData.XEdbInfoIdList[i]) { if maxI < i { maxI = i } } } } earliestDateTime = time.Date(earliestDateTime.Year(), earliestDateTime.Month(), 1, 0, 0, 0, 0, time.Local) xDataList = xDataList[0:maxI] for yIndex, yData := range newYDataList { if len(yData.XEdbInfoIdList) > maxI+1 { newYDataList[yIndex].XEdbInfoIdList = yData.XEdbInfoIdList[0 : maxI+1] } if len(yData.Value) > maxI+1 { newYDataList[yIndex].Value = yData.Value[0 : maxI+1] } currDate, _ := time.ParseInLocation(utils.FormatDate, yData.Date, time.Local) nameList := make([]string, 0) enNameList := make([]string, 0) for _, n := range newYDataList[yIndex].XEdbInfoIdList { if n == 1 { // 现货价不处理 nameList = append(nameList, baseEdbInfo.EdbName) enNameList = append(enNameList, baseEdbInfo.EdbNameEn) continue } if n <= 0 { nameList = append(nameList, `无合约`) enNameList = append(enNameList, `no contract`) } else { var date string currDateTime := currDate.AddDate(0, n-1, 0) month := int(currDateTime.Month()) date = fmt.Sprintf("%d%d", currDateTime.Year(), month) if month < 10 { date = fmt.Sprintf("%d0%d", currDateTime.Year(), month) } nameList = append(nameList, date) enNameList = append(enNameList, date) } } newYDataList[yIndex].NameList = nameList newYDataList[yIndex].EnNameList = enNameList } return } func CheckFormula(formula string) map[string]string { mathFormula := []string{"MAX", "MIN", "ABS", "ACOS", "ASIN", "CEIL", "MOD", "POW", "ROUND", "SIGN", "SIN", "TAN", "LOG10", "LOG2", "LOG", "LN"} str := strings.ToUpper(formula) for _, v := range mathFormula { str = strings.Replace(str, v, "", -1) } str = strings.Replace(str, "(", "", -1) str = strings.Replace(str, ")", "", -1) byteMap := make(map[string]string) for i := 0; i < len(str); i++ { byteInt := str[i] if byteInt >= 65 && byteInt <= 90 { byteStr := string(byteInt) if _, ok := byteMap[byteStr]; !ok { byteMap[byteStr] = byteStr } } } return byteMap } func ReplaceFormula(tagEdbIdMap map[string]int, valArr map[int]float64, formulaStr string) string { funMap := GetFormulaMap() for k, v := range funMap { formulaStr = strings.Replace(formulaStr, k, v, -1) } replaceCount := 0 for tag, edbInfoId := range tagEdbIdMap { if val, valOk := valArr[edbInfoId]; valOk { //值存在 dvStr := fmt.Sprintf("%v", val) formulaStr = strings.Replace(formulaStr, tag, dvStr, -1) replaceCount++ } } for k, v := range funMap { formulaStr = strings.Replace(formulaStr, v, k, -1) } if replaceCount == len(tagEdbIdMap) { return formulaStr } else { return "" } } func GetFormulaMap() map[string]string { funMap := make(map[string]string) funMap["MAX"] = "[@@]" funMap["MIN"] = "[@!]" funMap["ABS"] = "[@#]" funMap["CEIL"] = "[@$]" funMap["COS"] = "[@%]" funMap["FLOOR"] = "[@^]" funMap["MOD"] = "[@&]" funMap["POW"] = "[@*]" funMap["ROUND"] = "[@(]" return funMap }