Browse Source

Merge branch 'master' into feature/eta2.2.1_usda_fas

# Conflicts:
#	models/db.go
#	routers/router.go
#	utils/constants.go
xyxie 3 months ago
parent
commit
63abd0284a
49 changed files with 3736 additions and 373 deletions
  1. 36 0
      controllers/base_from_calculate.go
  2. 168 0
      controllers/base_from_hisugar.go
  3. 298 48
      controllers/base_from_ly.go
  4. 60 0
      controllers/base_from_predict.go
  5. 10 1
      controllers/base_from_predict_calculate.go
  6. 10 40
      controllers/future_good/future_good_edb_info.go
  7. 1 0
      go.mod
  8. 2 0
      go.sum
  9. 154 0
      logic/predict_edb.go
  10. 190 112
      logic/profit_chart_info.go
  11. 1 1
      models/base_from_calculate.go
  12. 276 0
      models/base_from_hisugar.go
  13. 8 2
      models/base_from_ly_classify.go
  14. 17 5
      models/base_from_ly_data.go
  15. 3 3
      models/base_from_ly_index.go
  16. 2 2
      models/base_from_ly_index_record.go
  17. 1 1
      models/base_from_yongyi.go
  18. 4 0
      models/db.go
  19. 26 11
      models/edb_data_calculate_bp.go
  20. 24 1
      models/edb_data_calculate_hbz.go
  21. 22 0
      models/edb_data_calculate_ljzzj.go
  22. 22 0
      models/edb_data_calculate_ljzzy.go
  23. 404 0
      models/edb_data_calculate_phase_shift.go
  24. 22 0
      models/edb_data_calculate_rjz.go
  25. 97 0
      models/edb_data_calculate_stl.go
  26. 21 0
      models/edb_data_calculate_tcz.go
  27. 51 0
      models/edb_data_ly.go
  28. 6 0
      models/edb_data_table.go
  29. 30 14
      models/edb_info.go
  30. 5 0
      models/future_good/future_good_edb_data.go
  31. 0 1
      models/predict_edb_data_base.go
  32. 21 1
      models/predict_edb_data_calculate_hbz.go
  33. 23 1
      models/predict_edb_data_calculate_ljzzj.go
  34. 21 0
      models/predict_edb_data_calculate_ljzzy.go
  35. 325 0
      models/predict_edb_data_calculate_phase_shift.go
  36. 20 0
      models/predict_edb_data_calculate_tcz.go
  37. 112 0
      models/predict_edb_data_static.go
  38. 160 103
      models/predict_edb_info_rule.go
  39. 87 8
      models/trade_analysis/trade_analysis.go
  40. 87 6
      routers/commentsRouter.go
  41. 5 0
      routers/router.go
  42. 156 0
      services/base_from_hisugar.go
  43. 1 1
      services/base_from_smm.go
  44. 3 0
      services/base_from_yongyi.go
  45. 495 0
      services/edb_data_calculate_stl.go
  46. 19 7
      services/trade_analysis/trade_analysis_data.go
  47. 25 1
      static/pcsg_task.json
  48. 197 0
      utils/common.go
  49. 8 3
      utils/constants.go

+ 36 - 0
controllers/base_from_calculate.go

@@ -749,6 +749,9 @@ func (this *CalculateController) BatchSave() {
 		}
 		sourName = "日均值"
 		edbInfo, err = models.AddCalculateRjz(&req, fromEdbInfo, edbCode, uniqueCode, sysUserId, sysUserName)
+	case utils.DATA_SOURCE_CALCULATE_PHASE_SHIFT:
+		sourName = "期数移位"
+		edbInfo, err = models.AddCalculatePhaseShift(&req, fromEdbInfo, edbCode, uniqueCode, sysUserId, sysUserName)
 	default:
 		// 获取通用的数据源处理服务
 		baseEdbInfoModel = models.GetBaseEdbInfoModel(req.Source)
@@ -1073,6 +1076,9 @@ func (this *CalculateController) BatchEdit() {
 	case utils.DATA_SOURCE_CALCULATE_TIME_SHIFT:
 		sourName = "时间移位"
 		err = models.EditCalculateTimeShift(edbInfo, &req, fromEdbInfo)
+	case utils.DATA_SOURCE_CALCULATE_PHASE_SHIFT:
+		sourName = "期数移位"
+		err = models.EditCalculatePhaseShift(edbInfo, &req, fromEdbInfo)
 	case utils.DATA_SOURCE_CALCULATE_ZJPJ:
 		sourName = "直接拼接"
 
@@ -1531,6 +1537,25 @@ func (this *CalculateController) Refresh() {
 		if err != nil && err.Error() != utils.ErrNoRow() {
 			errMsg = "RefreshAllCalculateTimeShift Err:" + err.Error()
 		}
+	case utils.DATA_SOURCE_CALCULATE_PHASE_SHIFT: // 期数位移
+		calculate, err := models.GetEdbInfoCalculateMappingDetail(edbInfoId)
+		if err != nil {
+			errMsg = "GetEdbInfoCalculateMappingDetail Err:" + err.Error()
+			break
+		}
+		fromEdbInfo, err := models.GetEdbInfoById(calculate.FromEdbInfoId)
+		if err != nil {
+			errMsg = "GetEdbInfoById Err:" + err.Error()
+			break
+		}
+		//startDate = edbInfo.StartDate
+		startDate = `` //只要填写日期,就会出现问题,还是把日期给去掉吧
+		endDate = time.Now().Format(utils.FormatDate)
+		formulaInt, _ := strconv.Atoi(calculate.CalculateFormula)
+		err = models.RefreshAllCalculatePhaseShift(edbInfoId, source, subSource, formulaInt, calculate.MoveType, fromEdbInfo, calculate.EdbCode, startDate, endDate, calculate.MoveFrequency)
+		if err != nil && err.Error() != utils.ErrNoRow() {
+			errMsg = "RefreshAllCalculatePhaseShift Err:" + err.Error()
+		}
 	case utils.DATA_SOURCE_CALCULATE_ZJPJ: //刷新直接拼接
 		err = models.RefreshAllCalculateZjpj(edbInfo)
 		if err != nil && err.Error() != utils.ErrNoRow() {
@@ -1680,6 +1705,17 @@ func (this *CalculateController) Refresh() {
 			errMsg = "RefreshAllCalculateRjz Err:" + err.Error()
 			break
 		}
+	case utils.DATA_SOURCE_CALCULATE_STL: // STL趋势分解
+		msg, err := services.RefreshStlData(edbInfoId)
+		if err != nil {
+			if msg == "" {
+				errMsg = "RefreshStlData Err:" + err.Error()
+			} else {
+				errMsg = msg + " Err:" + err.Error()
+			}
+			break
+		}
+
 	default:
 		// 获取通用的数据源处理服务
 		baseEdbInfoModel = models.GetBaseEdbInfoModel(source)

+ 168 - 0
controllers/base_from_hisugar.go

@@ -0,0 +1,168 @@
+package controllers
+
+import (
+	"encoding/json"
+	"eta/eta_index_lib/logic"
+	"eta/eta_index_lib/models"
+	"eta/eta_index_lib/services"
+	"eta/eta_index_lib/utils"
+	"strconv"
+	"time"
+)
+
+// HisugarController 泛糖科技
+type HisugarController struct {
+	BaseAuthController
+}
+
+// Add
+// @Title 新增泛糖科技指标接口
+// @Description 新增泛糖科技指标接口
+// @Success 200 {object} models.AddEdbInfoReq
+// @router /add [post]
+func (this *HisugarController) Add() {
+	br := new(models.BaseResponse).Init()
+	var cacheKey string
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	var req models.AddEdbInfoReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.EdbCode == "" {
+		br.Msg = "请输入指标编码!"
+		br.ErrMsg = "请输入指标编码,指标编码为空"
+		return
+	}
+
+	source := utils.DATA_SOURCE_HISUGAR
+	cacheKey = utils.CACHE_EDB_DATA_ADD + strconv.Itoa(source) + "_" + req.EdbCode
+	if !utils.Rc.IsExist(cacheKey) {
+		utils.Rc.SetNX(cacheKey, 1, 1*time.Minute)
+		err = models.AddEdbDataFromHisugar(req.EdbCode)
+		if err != nil {
+			br.Msg = "获取指标信息失败!"
+			br.ErrMsg = "获取指标信息失败 AddEdbDataFromHisugar,Err:" + err.Error()
+			return
+		}
+		br.Ret = 200
+		br.Success =  true
+		br.Msg = "获取成功"
+	} else {
+		br.Ret = 501
+		br.Success = true
+		br.Msg = "系统处理中,请稍后重试"
+	}
+}
+
+// Refresh
+// @Title 刷新泛糖科技指标接口
+// @Description 刷新泛糖科技指标接口
+// @Success 200 {object} models.RefreshEdbInfoReq
+// @router /refresh [post]
+func (this *HisugarController) Refresh() {
+	br := new(models.BaseResponse).Init()
+	var cacheKey string
+	defer func() {
+		utils.Rc.Delete(cacheKey)
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	source := utils.DATA_SOURCE_HISUGAR
+	var req models.RefreshEdbInfoReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.EdbCode == "" {
+		br.Msg = "请输入指标编码!"
+		br.ErrMsg = "请输入指标编码,指标编码为空"
+		return
+	}
+	if req.EdbInfoId <= 0 {
+		br.Msg = "请输入指标ID!"
+		br.ErrMsg = "请输入指标ID"
+		return
+	}
+
+	cacheKey = utils.CACHE_EDB_DATA_REFRESH + strconv.Itoa(source) + "_" + req.EdbCode
+	if utils.Rc.IsExist(cacheKey) {
+		br.Ret = 501
+		br.Success = true
+		br.Msg = "系统处理中,请稍后重试"
+		return
+	}
+
+	utils.Rc.SetNX(cacheKey, 1, 1*time.Minute)
+	// 获取指标详情
+	edbInfo, err := models.GetEdbInfoById(req.EdbInfoId)
+	if err != nil {
+		br.Msg = "指标不存在!"
+		br.ErrMsg = "指标不存在"
+		return
+	}
+
+	err = models.RefreshEdbDataFromHisugar(req.EdbInfoId, req.EdbCode, req.StartDate)
+	if err != nil && err.Error() != utils.ErrNoRow() {
+		br.Msg = "刷新指标信息失败!"
+		br.ErrMsg = "刷新指标信息失败 RefreshEdbDataFromHisugar,Err:" + err.Error()
+		return
+	}
+	// 更新指标最大最小值
+	err, errMsg := models.UnifiedModifyEdbInfoMaxAndMinInfo(edbInfo)
+	if err != nil {
+		br.Msg = errMsg
+		br.ErrMsg = err.Error()
+		return
+	}
+
+	// 更新ES
+	go logic.UpdateEs(edbInfo.EdbInfoId)
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+}
+
+// HandleEdbData
+// @Title 处理泛糖科技指标的接口
+// @Description 处理泛糖科技指标的接口
+// @Success 200 string "操作成功"
+// @router /handle/edb_data [post]
+func (this *HisugarController) HandleEdbData() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	var req models.HandleHisugarEdbDataReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+
+	if err = services.HandleHisugarIndex(req.List); err != nil {
+		br.Msg = "处理失败"
+		br.ErrMsg = "处理失败,Err:" + err.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "处理成功"
+}

+ 298 - 48
controllers/base_from_ly.go

@@ -7,6 +7,7 @@ import (
 	"eta/eta_index_lib/models"
 	"eta/eta_index_lib/services"
 	"eta/eta_index_lib/utils"
+	"fmt"
 	"strconv"
 	"time"
 )
@@ -143,7 +144,7 @@ func (this *BaseFromLyController) Refresh() {
 // @Title 获取分类
 // @Description 获取分类
 // @Success 200 {object} models.BaseFromLyClassify
-// @router /get/ly/classify/by/name [get]
+// @router /get/ly/classify/by/name [post]
 func (this *BaseFromLyController) GetLyClassifyByName() {
 	br := new(models.BaseResponse).Init()
 	var cacheKey string
@@ -155,9 +156,15 @@ func (this *BaseFromLyController) GetLyClassifyByName() {
 		this.Data["json"] = br
 		this.ServeJSON()
 	}()
-
-	categoryName := this.GetString("CategoryName")
-
+	var reqData struct {
+		CategoryName string `json:"CategoryName"`
+	}
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &reqData)
+	if err != nil {
+		br.ErrMsg = "无法解析请求体"
+		return
+	}
+	categoryName := reqData.CategoryName
 	if categoryName == "" {
 		br.Msg = "请输入分类!"
 		br.ErrMsg = "请输入分类"
@@ -179,7 +186,7 @@ func (this *BaseFromLyController) GetLyClassifyByName() {
 // @Title 根据url获取指标已读取记录
 // @Description 根据url获取指标已读取记录
 // @Success 200 {object} models.BaseFromLyIndexRecord
-// @router /get/ly/index/record/by/url [get]
+// @router /get/ly/index/record/by/url [post]
 func (this *BaseFromLyController) GetLyIndexRecordByUrl() {
 	br := new(models.BaseResponse).Init()
 	var cacheKey string
@@ -191,9 +198,15 @@ func (this *BaseFromLyController) GetLyIndexRecordByUrl() {
 		this.Data["json"] = br
 		this.ServeJSON()
 	}()
-
-	url := this.GetString("Url")
-
+	var reqData struct {
+		Url string `json:"Url"`
+	}
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &reqData)
+	if err != nil {
+		br.ErrMsg = "无法解析请求体"
+		return
+	}
+	url := reqData.Url
 	if url == "" {
 		br.Msg = "请输入地址链接!"
 		br.ErrMsg = "请输入地址链接"
@@ -235,13 +248,14 @@ func (this *BaseFromLyController) AddLyIndexRecord() {
 		return
 	}
 
-	_, err = models.AddLyIndexRecord(&req)
+	id, err := models.AddLyIndexRecord(&req)
 	if err != nil {
 		return
 	}
 
 	br.Ret = 200
 	br.Success = true
+	br.Data = id
 	br.Msg = "处理成功"
 }
 
@@ -262,6 +276,7 @@ func (this *BaseFromLyController) AddLyDataList() {
 		this.ServeJSON()
 	}()
 	var req []models.BaseFromLyData
+	fmt.Println(string(this.Ctx.Input.RequestBody))
 	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
 	if err != nil {
 		br.Msg = "参数解析异常!"
@@ -271,6 +286,8 @@ func (this *BaseFromLyController) AddLyDataList() {
 
 	err = models.AddLyDataList(req)
 	if err != nil {
+		br.Msg = "新增指标数据失败!"
+		br.ErrMsg = "新增指标数据失败,Err:" + err.Error()
 		return
 	}
 
@@ -318,7 +335,7 @@ func (this *BaseFromLyController) AddLyIndex() {
 // @Title 根据指标id和时间获取指标数据
 // @Description 根据指标id和时间获取指标数据
 // @Success 200 {object} models.BaseFromLyData
-// @router /get/ly/data/by/index/id/and/data/time [get]
+// @router /get/ly/data/by/index/id/and/data/time [post]
 func (this *BaseFromLyController) GetLyDataByIndexIdAndDataTime() {
 	br := new(models.BaseResponse).Init()
 	var cacheKey string
@@ -330,21 +347,24 @@ func (this *BaseFromLyController) GetLyDataByIndexIdAndDataTime() {
 		this.Data["json"] = br
 		this.ServeJSON()
 	}()
-
-	indexId, err := this.GetInt("IndexId")
+	var reqData struct {
+		IndexId  int    `json:"IndexId"`
+		DataTime string `json:"DataTime"`
+	}
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &reqData)
 	if err != nil {
-		br.Msg = "参数解析异常!"
-		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		br.ErrMsg = "无法解析请求体"
 		return
 	}
+
+	indexId := reqData.IndexId
 	if indexId == 0 {
 		br.Msg = "请输入指标id!"
 		br.ErrMsg = "请输入指标id"
 		return
 	}
 
-	dataTime := this.GetString("DataTime")
-
+	dataTime := reqData.DataTime
 	if dataTime == "" {
 		br.Msg = "请输入时间!"
 		br.ErrMsg = "请输入时间"
@@ -366,7 +386,7 @@ func (this *BaseFromLyController) GetLyDataByIndexIdAndDataTime() {
 // @Title 根据指标id和年月时间获取指标数据
 // @Description 根据指标id和年月时间获取指标数据
 // @Success 200 {object} models.BaseFromLyData
-// @router /get/ly/data/by/index/id/and/data/time/ym [get]
+// @router /get/ly/data/by/index/id/and/data/time/ym [post]
 func (this *BaseFromLyController) GetLyDataByIndexIdAndDataTimeYM() {
 	br := new(models.BaseResponse).Init()
 	var cacheKey string
@@ -378,20 +398,24 @@ func (this *BaseFromLyController) GetLyDataByIndexIdAndDataTimeYM() {
 		this.Data["json"] = br
 		this.ServeJSON()
 	}()
-
-	indexId, err := this.GetInt("IndexId")
+	var reqData struct {
+		IndexId   int    `json:"IndexId"`
+		YearMonth string `json:"YearMonth"`
+	}
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &reqData)
 	if err != nil {
-		br.Msg = "参数解析异常!"
-		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		br.ErrMsg = "无法解析请求体"
 		return
 	}
+
+	indexId := reqData.IndexId
 	if indexId == 0 {
 		br.Msg = "请输入指标id!"
 		br.ErrMsg = "请输入指标id"
 		return
 	}
 
-	yearMonth := this.GetString("YearMonth")
+	yearMonth := reqData.YearMonth
 
 	if yearMonth == "" {
 		br.Msg = "请输入时间!"
@@ -414,7 +438,7 @@ func (this *BaseFromLyController) GetLyDataByIndexIdAndDataTimeYM() {
 // @Title 更新数据源指标数据
 // @Description 更新数据源指标数据
 // @Success 200 string "处理成功"
-// @router /update/ly/data/by/id [get]
+// @router /update/ly/data/by/id [post]
 func (this *BaseFromLyController) UpdateLyDataById() {
 	br := new(models.BaseResponse).Init()
 	var cacheKey string
@@ -426,26 +450,24 @@ func (this *BaseFromLyController) UpdateLyDataById() {
 		this.Data["json"] = br
 		this.ServeJSON()
 	}()
-
-	id, err := this.GetInt("Id")
+	var reqData struct {
+		Id    int     `json:"Id"`
+		Value float64 `json:"Value"`
+	}
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &reqData)
 	if err != nil {
-		br.Msg = "参数解析异常!"
-		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		br.ErrMsg = "无法解析请求体"
 		return
 	}
+
+	id := reqData.Id
 	if id == 0 {
 		br.Msg = "请输入id!"
 		br.ErrMsg = "请输入id"
 		return
 	}
 
-	value, err := this.GetFloat("Value")
-	if err != nil {
-		br.Msg = "参数解析异常!"
-		br.ErrMsg = "参数解析失败,Err:" + err.Error()
-		return
-	}
-
+	value := reqData.Value
 	if value == 0 {
 		br.Msg = "请输入值!"
 		br.ErrMsg = "请输入值"
@@ -462,11 +484,113 @@ func (this *BaseFromLyController) UpdateLyDataById() {
 	br.Msg = "处理成功"
 }
 
+// GetLyEdbDataByIndexCodeAndDataTime
+// @Title 根据指标编码和模糊日期获取指标库数据
+// @Description 根据指标编码和模糊日期获取指标库数据
+// @Success 200 {object} []models.EdbDataLy
+// @router /get/ly/edb/data/by/index/code/and/data/time [post]
+func (this *BaseFromLyController) GetLyEdbDataByIndexCodeAndDataTime() {
+	br := new(models.BaseResponse).Init()
+	var cacheKey string
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		_ = utils.Rc.Delete(cacheKey)
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	var reqData struct {
+		IndexCode string `json:"IndexCode"`
+		DataTime  string `json:"DataTime"`
+	}
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &reqData)
+	if err != nil {
+		br.ErrMsg = "无法解析请求体"
+		return
+	}
+
+	indexCode := reqData.IndexCode
+	if indexCode == "" {
+		br.Msg = "请输入指标id!"
+		br.ErrMsg = "请输入指标id"
+		return
+	}
+
+	dataTime := reqData.DataTime
+	if dataTime == "" {
+		br.Msg = "请输入时间!"
+		br.ErrMsg = "请输入时间"
+		return
+	}
+
+	lyEdbData, err := models.GetLyEdbDataByIndexCodeAndDataTime(indexCode, dataTime)
+	if err != nil {
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Data = lyEdbData
+	br.Msg = "获取成功"
+}
+
+// GetLyEdbDataByIndexCodeAndExactDataTime
+// @Title 根据指标编码和精确日期获取指标库数据
+// @Description 根据指标编码和精确日期获取指标库数据
+// @Success 200 {object} []models.EdbDataLy
+// @router /get/ly/edb/data/by/index/code/and/exact/data/time [post]
+func (this *BaseFromLyController) GetLyEdbDataByIndexCodeAndExactDataTime() {
+	br := new(models.BaseResponse).Init()
+	var cacheKey string
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		_ = utils.Rc.Delete(cacheKey)
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	var reqData struct {
+		IndexCode string `json:"IndexCode"`
+		DataTime  string `json:"DataTime"`
+	}
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &reqData)
+	if err != nil {
+		br.ErrMsg = "无法解析请求体"
+		return
+	}
+
+	indexCode := reqData.IndexCode
+	if indexCode == "" {
+		br.Msg = "请输入指标id!"
+		br.ErrMsg = "请输入指标id"
+		return
+	}
+
+	dataTime := reqData.DataTime
+	if dataTime == "" {
+		br.Msg = "请输入时间!"
+		br.ErrMsg = "请输入时间"
+		return
+	}
+
+	lyEdbData, err := models.GetLyEdbDataByIndexCodeAndExactDataTime(indexCode, dataTime)
+	if err != nil {
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Data = lyEdbData
+	br.Msg = "获取成功"
+}
+
 // UpdateLyEdbDataById
-// @Title 更新指标库指标数据
-// @Description 更新数据源指标数据
+// @Title 更新指标库数据 须根据指标编码和日期更新 仅适合月度数据
+// @Description 更新指标库数据 须根据指标编码和日期更新 仅适合月度数据
 // @Success 200 string "处理成功"
-// @router /update/ly/edb/data/by/id [get]
+// @router /update/ly/edb/data/by/id [post]
 func (this *BaseFromLyController) UpdateLyEdbDataById() {
 	br := new(models.BaseResponse).Init()
 	var cacheKey string
@@ -478,33 +602,159 @@ func (this *BaseFromLyController) UpdateLyEdbDataById() {
 		this.Data["json"] = br
 		this.ServeJSON()
 	}()
-
-	id, err := this.GetInt("Id")
+	var reqData struct {
+		Id    int     `json:"Id"`
+		Value float64 `json:"Value"`
+	}
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &reqData)
 	if err != nil {
-		br.Msg = "参数解析异常!"
-		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		br.ErrMsg = "无法解析请求体"
 		return
 	}
+
+	id := reqData.Id
 	if id == 0 {
 		br.Msg = "请输入id!"
 		br.ErrMsg = "请输入id"
 		return
 	}
 
-	value, err := this.GetFloat("Value")
+	value := reqData.Value
+	if value == 0 {
+		br.Msg = "请输入值!"
+		br.ErrMsg = "请输入值"
+		return
+	}
+
+	err = models.UpdateLyEdbDataById(id, value)
 	if err != nil {
-		br.Msg = "参数解析异常!"
-		br.ErrMsg = "参数解析失败,Err:" + err.Error()
 		return
 	}
 
-	if value == 0 {
-		br.Msg = "请输入值!"
-		br.ErrMsg = "请输入值"
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "处理成功"
+}
+
+// GetLyIndexByCode
+// @Title 查询指标编码是否存在
+// @Description 查询指标编码是否存在
+// @Success 200 {object} models.BaseFromLyIndex
+// @router /get/ly/index/by/code [post]
+func (this *BaseFromLyController) GetLyIndexByCode() {
+	br := new(models.BaseResponse).Init()
+	var cacheKey string
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		_ = utils.Rc.Delete(cacheKey)
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	var reqData struct {
+		IndexCode string `json:"IndexCode"`
+	}
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &reqData)
+	if err != nil {
+		br.ErrMsg = "无法解析请求体"
 		return
 	}
 
-	err = models.UpdateLyDataById(id, value)
+	indexCode := reqData.IndexCode
+	if indexCode == "" {
+		br.Msg = "请输入指标id!"
+		br.ErrMsg = "请输入指标id"
+		return
+	}
+
+	lyIndex, err := models.GetLyIndexByCode(indexCode)
+	if err != nil {
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Data = lyIndex
+	br.Msg = "获取成功"
+}
+
+// GetEdbInfoByIndexCode
+// @Title 根据指标code获取指标信息
+// @Description 根据指标code获取指标信息
+// @Success 200 {object} models.EdbInfo
+// @router /get/edb/info/by/index/code [post]
+func (this *BaseFromLyController) GetEdbInfoByIndexCode() {
+	br := new(models.BaseResponse).Init()
+	var cacheKey string
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		_ = utils.Rc.Delete(cacheKey)
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	var reqData struct {
+		IndexCode string `json:"IndexCode"`
+		Source    int    `json:"Source"`
+	}
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &reqData)
+	if err != nil {
+		br.ErrMsg = "无法解析请求体"
+		return
+	}
+
+	indexCode := reqData.IndexCode
+	if indexCode == "" {
+		br.Msg = "请输入指标id!"
+		br.ErrMsg = "请输入指标id"
+		return
+	}
+	source := reqData.Source
+	if source == 0 {
+		br.Msg = "请输入来源!"
+		br.ErrMsg = "请输入来源"
+		return
+	}
+
+	lyEdbData, err := models.GetEdbInfoByEdbCode(source, indexCode)
+	if err != nil {
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Data = lyEdbData
+	br.Msg = "获取成功"
+}
+
+// AddBatchLyEdbData
+// @Title 批量增加粮油指标库数据
+// @Description 批量增加粮油指标库数据
+// @Success 200 string "处理成功"
+// @router /add/batch/ly/edb/data [post]
+func (this *BaseFromLyController) AddBatchLyEdbData() {
+	br := new(models.BaseResponse).Init()
+	var cacheKey string
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		_ = utils.Rc.Delete(cacheKey)
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	var req []models.EdbDataLy
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+
+	err = models.AddLyEdbDataList(req)
 	if err != nil {
 		return
 	}

+ 60 - 0
controllers/base_from_predict.go

@@ -153,6 +153,13 @@ func (this *PredictController) Refresh() {
 		return
 	}
 
+	if edbInfo.IsStaticData == 1 {
+		//静态数据直接返回
+		br.Ret = 200
+		br.Success = true
+		br.Msg = "获取成功"
+		return
+	}
 	cacheKey = utils.CACHE_EDB_DATA_REFRESH + strconv.Itoa(edbInfo.Source) + "_" + req.EdbCode
 	if utils.Rc.IsExist(cacheKey) {
 		br.Ret = 501
@@ -275,3 +282,56 @@ func (this *PredictController) CalculateByNinePreview() {
 	br.Msg = "获取成功"
 	br.Data = resp
 }
+
+// AddStaticEdb
+// @Title 新增/编辑预测指标运算接口
+// @Description 新增预测指标运算接口
+// @Success 200 {object} models.AddStaticPredictEdbInfoReq
+// @router /static_edb/add [post]
+func (this *PredictController) AddStaticEdb() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	var req models.AddStaticPredictEdbInfoReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	//加入缓存机制,避免创建同一个名称的指标 start
+	redisKey := fmt.Sprintf("predict_edb_info:AddStaticEdb:%s", req.EdbName)
+	isExist := utils.Rc.IsExist(redisKey)
+	if isExist {
+		br.Msg = "指标正在处理,请勿重复提交"
+		return
+	} else {
+		//设置3分钟缓存
+		utils.Rc.SetNX(redisKey, 1, time.Second*300)
+		defer func() {
+			_ = utils.Rc.Delete(redisKey)
+		}()
+	}
+
+	// 添加指标
+	edbInfo, err, errMsg := logic.AddStaticPredictEdbInfo(req.SourceEdbInfoId, req.ClassifyId, req.EdbName, req.Frequency, req.Unit, req.AdminId, req.AdminName, this.Lang)
+	if err != nil {
+		br.Msg = "保存失败"
+		if errMsg != `` {
+			br.Msg = errMsg
+		}
+		br.ErrMsg = err.Error()
+		return
+	}
+	resp := models.AddEdbInfoResp{
+		EdbInfoId:  edbInfo.EdbInfoId,
+		UniqueCode: edbInfo.UniqueCode,
+	}
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "保存成功"
+	br.Data = resp
+	br.IsAddLog = true
+}

+ 10 - 1
controllers/base_from_predict_calculate.go

@@ -802,6 +802,9 @@ func (this *PredictCalculateController) CalculateBatchSave() {
 	} else if req.Source == utils.DATA_SOURCE_PREDICT_CALCULATE_TIME_SHIFT {
 		sourName = "预测时间移位"
 		edbInfo, latestDateStr, latestValue, err = models.SavePredictCalculateTimeShift(&req, fromEdbInfo, edbCode, uniqueCode, adminId, adminName, this.Lang)
+	} else if req.Source == utils.DATA_SOURCE_PREDICT_CALCULATE_PHASE_SHIFT {
+		sourName = "预测期数移位"
+		edbInfo, latestDateStr, latestValue, err = models.SavePredictCalculatePhaseShift(&req, fromEdbInfo, edbCode, uniqueCode, adminId, adminName, this.Lang)
 	} else if req.Source == utils.DATA_SOURCE_PREDICT_CALCULATE_CJJX {
 		sourName = "预测超季节性"
 		edbInfo, latestDateStr, latestValue, err = models.SavePredictCalculateCjjx(&req, fromEdbInfo, edbCode, uniqueCode, adminId, adminName, formulaInt, this.Lang)
@@ -970,7 +973,13 @@ func (this *PredictCalculateController) Refresh() {
 		br.ErrMsg = "指标不存在"
 		return
 	}
-
+	if edbInfo.IsStaticData == 1 {
+		//静态数据直接返回
+		br.Ret = 200
+		br.Success = true
+		br.Msg = "获取成功"
+		return
+	}
 	cacheKey = utils.CACHE_EDB_DATA_REFRESH + strconv.Itoa(edbInfo.Source) + "_" + req.EdbCode
 	if utils.Rc.IsExist(cacheKey) {
 		br.Ret = 501

+ 10 - 40
controllers/future_good/future_good_edb_info.go

@@ -8,7 +8,7 @@ import (
 	"eta/eta_index_lib/models/future_good"
 	"eta/eta_index_lib/services"
 	"eta/eta_index_lib/utils"
-	"fmt"
+	"strconv"
 	"time"
 )
 
@@ -206,19 +206,19 @@ func (this *FutureGoodEdbInfoController) RefreshRelation() {
 		this.Data["json"] = br
 		this.ServeJSON()
 	}()
-	var req future_good.RefreshFutureEdbEdbInfoReq
+	var req future_good.RefreshFutureChartInfoReq
 	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
 	if err != nil {
 		br.Msg = "参数解析异常!"
 		br.ErrMsg = "参数解析失败,Err:" + err.Error()
 		return
 	}
-	if req.FutureGoodEdbInfoId <= 0 {
-		br.Msg = "请输入指标ID!"
-		br.ErrMsg = "请输入指标ID"
+	if req.ChartInfoId <= 0 {
+		br.Msg = "请输入图表ID!"
+		br.ErrMsg = "请输入图表ID"
 		return
 	}
-	cacheKey = utils.CACHE_EDB_DATA_REFRESH + "_futuregood_relation_" + req.FutureGoodEdbCode
+	cacheKey = utils.CACHE_EDB_DATA_REFRESH + "_futuregood_relation_chart" + strconv.Itoa(req.ChartInfoId)
 
 	if utils.Rc.IsExist(cacheKey) {
 		br.Ret = 501
@@ -227,47 +227,17 @@ func (this *FutureGoodEdbInfoController) RefreshRelation() {
 		return
 	}
 
-	utils.Rc.SetNX(cacheKey, 1, 1*time.Minute)
+	utils.Rc.SetNX(cacheKey, 1, 10*time.Minute)
 	defer func() {
 		utils.Rc.Delete(cacheKey)
 	}()
-
-	//获取指标信息
-	futureGoodEdbInfo, err := future_good.GetFutureGoodEdbInfo(req.FutureGoodEdbInfoId)
+	err, errMsg := logic.RefreshByChartId(req.ChartInfoId)
 	if err != nil {
-		if err.Error() != utils.ErrNoRow() {
-			br.Msg = "系统内找不到该指标"
-		} else {
-			br.Msg = "刷新失败"
-			br.ErrMsg = "添加失败,ERR:" + err.Error()
-		}
+		br.Msg = "利润曲线图表刷新失败"
+		br.ErrMsg = "利润曲线图表刷新失败,Err:" + errMsg
 		return
 	}
 
-	// 获取相关图表
-	list, err := models.GetGroupChartEdbMappingListByEdbInfoId(futureGoodEdbInfo.FutureGoodEdbInfoId, 2)
-	if err != nil {
-		br.Msg = "查找相关图表id失败"
-		br.ErrMsg = "添加失败,ERR:" + err.Error()
-		return
-	}
-
-	go func() {
-		errMsgList := make([]string, 0)
-		for _, v := range list {
-			err, errMsg := logic.RefreshByChartId(v.ChartInfoId)
-			if err != nil {
-				errMsgList = append(errMsgList, fmt.Sprint(v.ChartInfoId, "更新失败,"+errMsg))
-			}
-		}
-
-		if len(errMsgList) > 0 {
-			//br.Msg = "部分刷新失败"
-			//br.ErrMsg = "部分刷新失败,Err:" + strings.Join(errMsgList, ";")
-			return
-		}
-	}()
-
 	br.Ret = 200
 	br.Success = true
 	br.Msg = "刷新成功"

+ 1 - 0
go.mod

@@ -16,6 +16,7 @@ require (
 	github.com/qiniu/qmgo v1.1.8
 	github.com/rdlucklib/rdluck_tools v1.0.3
 	github.com/shopspring/decimal v1.4.0
+	github.com/tealeg/xlsx v1.0.5
 	go.mongodb.org/mongo-driver v1.16.0
 	gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
 )

+ 2 - 0
go.sum

@@ -219,6 +219,8 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT
 github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
 github.com/syndtr/goleveldb v0.0.0-20160425020131-cfa635847112/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0=
 github.com/syndtr/goleveldb v0.0.0-20181127023241-353a9fca669c/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0=
+github.com/tealeg/xlsx v1.0.5 h1:+f8oFmvY8Gw1iUXzPk+kz+4GpbDZPK1FhPiQRd+ypgE=
+github.com/tealeg/xlsx v1.0.5/go.mod h1:btRS8dz54TDnvKNosuAqxrM1QgN1udgk9O34bDCnORM=
 github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
 github.com/ugorji/go v0.0.0-20171122102828-84cb69a8af83/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ=
 github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=

+ 154 - 0
logic/predict_edb.go

@@ -1077,3 +1077,157 @@ func CheckExistByEdbNameAndEdbInfoId(edbInfoType, edbInfoId int, edbName, lang s
 	//指标已经入库的情况
 	return checkExistByEdbNameAndEdbInfoId(edbInfoType, edbInfoId, edbName, lang)
 }
+
+// AddStaticPredictEdbInfo 新增静态指标数据
+func AddStaticPredictEdbInfo(sourceEdbInfoId, classifyId int, edbName, frequency, unit string, sysUserId int, sysUserName, lang string) (edbInfo *models.EdbInfo, err error, errMsg string) {
+	var sourceEdbInfo *models.EdbInfo
+	// 来源指标信息校验
+	{
+		sourceEdbInfo, err = models.GetEdbInfoById(sourceEdbInfoId)
+		if err != nil && err.Error() != utils.ErrNoRow() {
+			errMsg = "新增失败"
+			err = errors.New("获取来源指标失败,Err:" + err.Error())
+			return
+		}
+		if sourceEdbInfo == nil {
+			errMsg = "找不到该来源指标"
+			err = errors.New(errMsg)
+			return
+		}
+	}
+
+	var classifyInfo *models.EdbClassify
+	// 来源分类信息校验
+	{
+		classifyInfo, err = models.GetEdbClassifyById(classifyId)
+		if err != nil && err.Error() != utils.ErrNoRow() {
+			errMsg = "新增失败"
+			err = errors.New("获取预测指标分类失败,Err:" + err.Error())
+			return
+		}
+		if classifyInfo == nil {
+			errMsg = "找不到该预测指标分类"
+			err = errors.New(errMsg)
+			return
+		}
+		//必须是预测指标分类
+		if classifyInfo.ClassifyType != 1 {
+			errMsg = "预测指标分类异常,不是预测指标分类"
+			err = errors.New(errMsg)
+			return
+		}
+	}
+
+	edbName = strings.Trim(edbName, " ")
+
+	edbCode := sourceEdbInfo.EdbCode + "_" + time.Now().Format(utils.FormatShortDateTimeUnSpace)
+
+	// 根据指标名称和指标ID校验库中是否还存在其他同名指标
+	existEdbName, err := CheckExistByEdbNameAndEdbInfoId(1, 0, edbName, lang)
+	if err != nil {
+		errMsg = "判断指标名称是否存在失败"
+		err = errors.New("判断指标名称是否存在失败,Err:" + err.Error())
+		return
+	}
+	if existEdbName {
+		errMsg = "指标名称已存在,请重新填写"
+		err = errors.New(errMsg)
+		return
+	}
+
+	timestamp := strconv.FormatInt(time.Now().UnixNano(), 10)
+	edbInfo = &models.EdbInfo{
+		//EdbInfoId:   0,
+		EdbInfoType:      sourceEdbInfo.EdbInfoType,
+		SourceName:       sourceEdbInfo.SourceName,
+		Source:           sourceEdbInfo.Source,
+		EdbCode:          edbCode,
+		EdbName:          edbName,
+		EdbNameSource:    edbName,
+		Frequency:        frequency,
+		Unit:             unit,
+		StartDate:        sourceEdbInfo.StartDate,
+		EndDate:          sourceEdbInfo.EndDate,
+		ClassifyId:       classifyId,
+		SysUserId:        sysUserId,
+		SysUserRealName:  sysUserName,
+		UniqueCode:       utils.MD5(utils.DATA_PREFIX + "_" + timestamp),
+		CreateTime:       time.Now(),
+		ModifyTime:       time.Now(),
+		MinValue:         sourceEdbInfo.MinValue,
+		MaxValue:         sourceEdbInfo.MaxValue,
+		EndValue:         sourceEdbInfo.EndValue,
+		CalculateFormula: sourceEdbInfo.CalculateFormula,
+		EdbType:          sourceEdbInfo.EdbType,
+		//Sort:             sourceEdbInfo.,
+		LatestDate:    sourceEdbInfo.LatestDate,
+		LatestValue:   sourceEdbInfo.LatestValue,
+		MoveType:      sourceEdbInfo.MoveType,
+		MoveFrequency: sourceEdbInfo.MoveFrequency,
+		NoUpdate:      sourceEdbInfo.NoUpdate,
+		IsUpdate:      sourceEdbInfo.IsUpdate,
+		ServerUrl:     "",
+		EdbNameEn:     edbName,
+		UnitEn:        sourceEdbInfo.UnitEn,
+		DataDateType:  sourceEdbInfo.DataDateType,
+		Sort:          models.GetAddEdbMaxSortByClassifyId(classifyId, utils.PREDICT_EDB_INFO_TYPE),
+		IsStaticData:  1,
+	}
+
+	// 关联关系表
+	calculateMappingList := make([]*models.EdbInfoCalculateMapping, 0)
+	fromEdbMap := make(map[int]int)
+
+	// 源指标关联关系表
+	calculateMappingItem := &models.EdbInfoCalculateMapping{
+		//EdbInfoCalculateMappingId: 0,
+		//EdbInfoId:                 0,
+		Source:         edbInfo.Source,
+		SourceName:     edbInfo.SourceName,
+		EdbCode:        edbInfo.EdbCode,
+		FromEdbInfoId:  sourceEdbInfo.EdbInfoId,
+		FromEdbCode:    sourceEdbInfo.EdbCode,
+		FromEdbName:    sourceEdbInfo.EdbName,
+		FromSource:     sourceEdbInfo.Source,
+		FromSourceName: sourceEdbInfo.SourceName,
+		//FromTag:        "",
+		Sort:       1,
+		CreateTime: time.Now(),
+		ModifyTime: time.Now(),
+	}
+	fromEdbMap[sourceEdbInfoId] = sourceEdbInfoId
+	calculateMappingList = append(calculateMappingList, calculateMappingItem)
+	newPredictEdbConfList := make([]*models.PredictEdbConf, 0)
+	//查询原先的预测指标配置项
+	if sourceEdbInfo.EdbType == 1 {
+		// 查找该预测指标配置
+		predictEdbConfList, tmpErr := models.GetPredictEdbConfListById(sourceEdbInfo.EdbInfoId)
+		if tmpErr != nil && tmpErr.Error() != utils.ErrNoRow() {
+			errMsg = "获取预测指标配置信息失败"
+			err = errors.New("获取预测指标配置信息失败,Err:" + tmpErr.Error())
+			return
+		}
+		if len(predictEdbConfList) > 0 {
+			// 遍历
+			for _, v := range predictEdbConfList {
+				tmpPredictEdbConf := &models.PredictEdbConf{
+					PredictEdbInfoId: 0,
+					SourceEdbInfoId:  sourceEdbInfoId,
+					RuleType:         v.RuleType,
+					FixedValue:       v.FixedValue,
+					Value:            v.Value,
+					EmptyType:        v.EmptyType,
+					MaxEmptyType:     v.MaxEmptyType,
+					EndDate:          v.EndDate,
+					ModifyTime:       time.Now(),
+					CreateTime:       time.Now(),
+				}
+				newPredictEdbConfList = append(newPredictEdbConfList, tmpPredictEdbConf)
+			}
+		}
+	}
+
+	err, errMsg = models.AddPredictStaticEdb(edbInfo, sourceEdbInfo, calculateMappingList, newPredictEdbConfList)
+
+	return
+}

+ 190 - 112
logic/profit_chart_info.go

@@ -105,6 +105,8 @@ type ChartInfoReq struct {
 	BaseEdbInfoId           int                     `description:"基础的指标id"`
 	DateList                []ChartInfoDateReq      `description:"日期配置"`
 	ProfitNameEn            string                  `description:"利润英文名称"`
+	EdbInfoIdList           []int                   `description:"现货指标ID列表"`
+	XDataList               []XData                 `description:"横轴配置"`
 }
 
 // RefreshByChartId 根据图表id刷新图表
@@ -136,12 +138,34 @@ func RefreshByChartId(chartInfoId int) (err error, errMsg string) {
 		}
 		return
 	}
+	if len(extraConf.EdbInfoIdList) == 0 {
+		extraConf.EdbInfoIdList = append(extraConf.EdbInfoIdList, extraConf.BaseEdbInfoId)
+	}
 
-	baseEdbInfo, err := models.GetEdbInfoById(extraConf.BaseEdbInfoId)
+	baseEdbInfo := new(models.EdbInfo)
+	// ETA指标
+	edbInfoListTmp, err := models.GetEdbInfoByIdList(extraConf.EdbInfoIdList)
 	if err != nil {
 		errMsg = "获取失败"
 		return
 	}
+	//按照请求顺序排序
+	edbInfoMap := make(map[int]*models.EdbInfo)
+	for _, v := range edbInfoListTmp {
+		edbInfoMap[v.EdbInfoId] = v
+	}
+	edbInfoList := make([]*models.EdbInfo, 0)
+	for _, v := range extraConf.EdbInfoIdList {
+		edbInfoList = append(edbInfoList, edbInfoMap[v])
+	}
+	edbInfoListMap := make(map[int]*models.EdbInfo)
+	for k, v := range edbInfoList {
+		edbInfoList[k].EdbNameSource = v.EdbName
+		edbInfoListMap[v.EdbInfoId] = v
+		if v.EdbInfoId == extraConf.BaseEdbInfoId {
+			baseEdbInfo = v
+		}
+	}
 	// 商品数据库指标
 	futureGoodEdbInfoMap := make(map[int]*future_good.FutureGoodEdbInfo)
 	zlFutureGoodEdbInfoList := make([]*future_good.FutureGoodEdbInfo, 0)
@@ -160,9 +184,7 @@ func RefreshByChartId(chartInfoId int) (err error, errMsg string) {
 		futureGoodEdbInfoMap[v.EdbInfoId] = zlFutureGoodEdbInfo
 		zlFutureGoodEdbInfoList = append(zlFutureGoodEdbInfoList, zlFutureGoodEdbInfo)
 	}
-
-	xDataList, yDataList, err := GetProfitChartEdbData(baseEdbInfo, zlFutureGoodEdbInfoList, extraConf.DateList, extraConf.CalculateFormula, extraConf.FutureGoodEdbInfoIdList)
-
+	xDataList, yDataList, err := GetProfitChartEdbData(baseEdbInfo, edbInfoList, zlFutureGoodEdbInfoList, extraConf.DateList, extraConf.CalculateFormula, extraConf.FutureGoodEdbInfoIdList, extraConf.XDataList)
 	xDataListByte, err := json.Marshal(xDataList)
 	if err != nil {
 		errMsg = "保存失败"
@@ -179,7 +201,7 @@ func RefreshByChartId(chartInfoId int) (err error, errMsg string) {
 	extraUpdateCol := make([]string, 0)
 	chartInfoFutureGoodProfit.XValue = string(xDataListByte)
 	chartInfoFutureGoodProfit.YValue = string(yDataListByte)
-	chartInfoFutureGoodProfit.ProfitName = zlFutureGoodEdbInfoList[0].FutureGoodEdbName + "盘面利润"
+	//chartInfoFutureGoodProfit.ProfitName = zlFutureGoodEdbInfoList[0].FutureGoodEdbName + "盘面利润"
 	chartInfoFutureGoodProfit.ModifyTime = time.Now()
 	extraUpdateCol = []string{"XValue", "YValue", "ProfitName", "ModifyTime"}
 	err = chartInfoFutureGoodProfit.Update(extraUpdateCol)
@@ -188,11 +210,15 @@ func RefreshByChartId(chartInfoId int) (err error, errMsg string) {
 }
 
 // GetProfitChartEdbData 获取利润图表的指标数据
-func GetProfitChartEdbData(baseEdbInfo *models.EdbInfo, zlFutureGoodEdbInfoList []*future_good.FutureGoodEdbInfo, chartInfoDateList []ChartInfoDateReq, formulaStr string, edbInfoFromTagList []models.EdbInfoFromTag) (xDataList []XData, yDataList []YData, err error) {
+func GetProfitChartEdbData(baseEdbInfo *models.EdbInfo, edbInfoList []*models.EdbInfo, zlFutureGoodEdbInfoList []*future_good.FutureGoodEdbInfo, chartInfoDateList []ChartInfoDateReq, formulaStr string, edbInfoFromTagList []models.EdbInfoFromTag, reqXDataList []XData) (xDataList []XData, yDataList []YData, err error) {
+
 	if baseEdbInfo == nil {
 		err = errors.New("ETA指标未选取")
 		return
 	}
+	if len(edbInfoList) == 0 {
+		edbInfoList = append(edbInfoList, baseEdbInfo)
+	}
 	if len(zlFutureGoodEdbInfoList) <= 0 {
 		err = errors.New("商品指标未选取")
 		return
@@ -213,12 +239,17 @@ func GetProfitChartEdbData(baseEdbInfo *models.EdbInfo, zlFutureGoodEdbInfoList
 			futureGoodEdbInfoIdMap[tmpTagEdbIdMap[tag]] = tmpTagEdbIdMap[tag]
 		}
 	}
-
+	// 指标对应的所有数据
+	//edbDataListMap := make(map[int][]*models.EdbDataList)
 	// 普通的指标数据
-	baseDataList := make([]*models.EdbDataList, 0)
-	baseDataList, err = models.GetEdbDataList(baseEdbInfo.Source, baseEdbInfo.SubSource, baseEdbInfo.EdbInfoId, "", "")
-	if err != nil {
-		return
+	baseDataListMap := make(map[int][]*models.EdbDataList)
+	for _, v := range edbInfoList {
+		baseDataList := make([]*models.EdbDataList, 0)
+		baseDataList, err = models.GetEdbDataList(v.Source, v.SubSource, v.EdbInfoId, "", "")
+		if err != nil {
+			return
+		}
+		baseDataListMap[v.EdbInfoId] = baseDataList
 	}
 
 	latestDate := zlFutureGoodEdbInfoList[0].EndDate
@@ -330,36 +361,47 @@ func GetProfitChartEdbData(baseEdbInfo *models.EdbInfo, zlFutureGoodEdbInfoList
 	sort.Slice(dateList, func(i, j int) bool {
 		return dateList[i] < dateList[j]
 	})
-
-	_, yDataList, err = ProfitChartChartData(baseDataList, futureGoodEdbInfoDateMap, futureGoodDataListMap, chartInfoDateList, baseEdbInfo.EndDate, specialFutureGoodEdbInfoMap, formulaStr, tagEdbIdMap, dateList, maxN)
-	if err != nil {
-		return
+	var reqEdbInfoIds []int
+	for _, v := range edbInfoList {
+		reqEdbInfoIds = append(reqEdbInfoIds, v.EdbInfoId)
+		tmp := XData{
+			Name:   v.EdbName,
+			NameEn: v.EdbNameEn,
+		}
+		xDataList = append(xDataList, tmp)
 	}
+	var edbIdList []int
+	futureGoodNameMap := make(map[int]map[int]string)
+	edbIdList, yDataList, futureGoodNameMap, err = ProfitChartChartData(baseEdbInfo, baseDataListMap, futureGoodEdbInfoDateMap, futureGoodDataListMap, chartInfoDateList, baseEdbInfo.EndDate, specialFutureGoodEdbInfoMap, formulaStr, tagEdbIdMap, dateList, maxN, reqEdbInfoIds)
 
-	tmpXDataList, newYDataList, err := handleProfitResultData(baseEdbInfo, yDataList, earliestDateTime)
+	// todo 最后处理数据
+	tmpXDataList, newYDataList, err := handleProfitResultData(xDataList, futureGoodNameMap, yDataList, earliestDateTime, edbIdList)
 	if err != nil {
 		return
 	}
-	xDataList = []XData{
-		{
-			Name:   "现货利润",
-			NameEn: "Spot Price",
-		},
+	if len(reqXDataList) == 0 {
+		xDataList = tmpXDataList
+	} else {
+		xDataList = reqXDataList
 	}
-	xDataList = append(xDataList, tmpXDataList...)
+
 	yDataList = newYDataList
 
 	return
 }
 
 // ProfitChartChartData 获取数据
-func ProfitChartChartData(baseDataList []*models.EdbDataList, futureGoodEdbInfoMap map[int]map[string]*future_good.FutureGoodEdbInfo, futureGoodEdbDataListMap map[int][]*models.EdbDataList, chartInfoDateList []ChartInfoDateReq, latestDate string, specialFutureGoodEdbInfoMap map[int]map[int]*future_good.FutureGoodEdbInfo, formulaStr string, tagEdbIdMap map[string]int, dateList []string, maxN int) (edbIdList []int, yDataList []YData, err error) {
+func ProfitChartChartData(baseEdbInfo *models.EdbInfo, baseDataListMap map[int][]*models.EdbDataList, futureGoodEdbInfoMap map[int]map[string]*future_good.FutureGoodEdbInfo, futureGoodEdbDataListMap map[int][]*models.EdbDataList, chartInfoDateList []ChartInfoDateReq, latestDate string, specialFutureGoodEdbInfoMap map[int]map[int]*future_good.FutureGoodEdbInfo, formulaStr string, tagEdbIdMap map[string]int, dateList []string, maxN int, reqEdbInfoIds []int) (edbIdList []int, yDataList []YData, futureGoodNameMap map[int]map[int]string, 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
+	allBaseEdbDateDataMap := make(map[int]map[string]float64)
+	for edbInfoId, baseDataList := range baseDataListMap {
+		baseEdbDateData := make(map[string]float64)
+		for _, edbData := range baseDataList {
+			baseEdbDateData[edbData.DataTime] = edbData.Value
+		}
+		allBaseEdbDateDataMap[edbInfoId] = baseEdbDateData
 	}
 
 	// 商品指标数据
@@ -378,8 +420,8 @@ func ProfitChartChartData(baseDataList []*models.EdbDataList, futureGoodEdbInfoM
 
 	// 将计算公式中的字母转大写
 	formulaStr = strings.ToUpper(formulaStr)
-
-	for _, barChartInfoDate := range chartInfoDateList {
+	futureGoodNameMap = make(map[int]map[int]string)
+	for tmpk, barChartInfoDate := range chartInfoDateList {
 		yDataMap := make(map[int]float64)
 		var maxDate time.Time
 
@@ -406,26 +448,59 @@ func ProfitChartChartData(baseDataList []*models.EdbDataList, futureGoodEdbInfoM
 			return
 		}
 
-		findDataList := make([]float64, 0) // 当前日期的数据值
-		noDataIdList := make([]int, 0)     // 没有数据的指标id
-		xEdbInfoIdList := make([]int, 0)   // 当前数据的指标id列表
+		findDataList := make([]float64, 0)  // 当前日期的数据值
+		noDataIdList := make([]int, 0)      // 没有数据的指标id
+		noDataIdMap := make(map[int]int, 0) // 没有数据的指标map
+		xEdbInfoIdList := make([]int, 0)    // 当前数据的指标id列表
 
 		// 现货指标
-		realDateTime, findDataValue, isFind, tmpErr := GetNeedDateData(findDateTime, baseDataList, baseEdbDateData, edbDataMap)
+		index := 0
+		var realDateTime time.Time
+		// 现货指标
+		baseEdbDateData, ok := allBaseEdbDateDataMap[baseEdbInfo.EdbInfoId]
+		if !ok {
+			err = fmt.Errorf("指标id: %d 没有数据", baseEdbInfo.EdbInfoId)
+			return
+		}
+		realDateTime, findDataValue, isFind, tmpErr := GetNeedDateData(findDateTime, baseDataListMap[baseEdbInfo.EdbInfoId], baseEdbDateData, edbDataMap)
 		if tmpErr != nil {
 			err = tmpErr
 			return
 		}
-		findDataList = append(findDataList, findDataValue)
-		yDataMap[0] = findDataValue
 		if isFind {
 			maxDate = realDateTime
 		}
+		edbIdList = make([]int, 0) //普通指标ID
+		for _, edbInfoId := range reqEdbInfoIds {
+			if edbInfoId == baseEdbInfo.EdbInfoId {
+				findDataList = append(findDataList, findDataValue)
+				yDataMap[index] = findDataValue
+				xEdbInfoIdList = append(xEdbInfoIdList, edbInfoId)
+				edbIdList = append(edbIdList, edbInfoId)
+				index += 1
+				continue
+			}
+			baseEdbDateDataTmp, ok := allBaseEdbDateDataMap[edbInfoId]
+			if !ok {
+				err = fmt.Errorf("指标id: %d 没有数据", edbInfoId)
+				return
+			}
+			findDataValueTmp, isFindTmp := baseEdbDateDataTmp[realDateTime.Format(utils.FormatDate)]
+			if !isFindTmp {
+				noDataIdList = append(noDataIdList, edbInfoId)
+				noDataIdMap[edbInfoId] = edbInfoId
+			}
+			findDataList = append(findDataList, findDataValueTmp)
+			yDataMap[index] = findDataValueTmp
 
-		xEdbInfoIdList = append(xEdbInfoIdList, 0)
+			xEdbInfoIdList = append(xEdbInfoIdList, edbInfoId)
+			edbIdList = append(edbIdList, edbInfoId)
+			index += 1
+		}
 
 		mList := make([]int, 0) // 间隔月份
 
+		tmpNameMap := make(map[int]string)
 		// 最小开始的n值
 		//minN := (findDateTime.Year()-earliestDateTime.Year())*12 + int(findDateTime.Month()-earliestDateTime.Month())
 		for _, date := range dateList {
@@ -437,8 +512,6 @@ func ProfitChartChartData(baseDataList []*models.EdbDataList, futureGoodEdbInfoM
 			//findDateTime
 
 			// 获取当前日期相对开始日期的期数
-			//tmpN := (currDate.Year()-findDateTime.Year())*12 + int(currDate.Month()-findDateTime.Month())
-			// 用实际日期的月份作为基准,往前推12个月(2024-5-13 16:26:43修改)
 			tmpN := (currDate.Year()-realDateTime.Year())*12 + int(currDate.Month()-realDateTime.Month())
 			if tmpN <= 0 {
 				continue
@@ -486,7 +559,17 @@ func ProfitChartChartData(baseDataList []*models.EdbDataList, futureGoodEdbInfoM
 			//}
 
 			newTagEdbIdMap := make(map[string]int)
+
 			for tag, zlEdbId := range tagEdbIdMap {
+				if tag == "A" {
+					nameTmp := strings.Split(futureGoodEdbInfoMap[zlEdbId][date].FutureGoodEdbName, "(")
+					nameTmpEn := strings.Split(futureGoodEdbInfoMap[zlEdbId][date].FutureGoodEdbNameEn, "(")
+					if len(nameTmp) > 1 && len(nameTmpEn) > 1 {
+						nameTmp[1] = strings.Trim(nameTmp[1], ")")
+						nameTmpEn[1] = strings.Trim(nameTmpEn[1], ")")
+						tmpNameMap[tmpN+1] = nameTmp[1] + "-" + nameTmpEn[1]
+					}
+				}
 				newTagEdbIdMap[tag] = zlAndChildEdbId[zlEdbId]
 			}
 			//, formulaStr string, tagEdbIdMap map[string]int
@@ -494,52 +577,19 @@ func ProfitChartChartData(baseDataList []*models.EdbDataList, futureGoodEdbInfoM
 			//计算公式异常,那么就移除该指标
 			if formulaFormStr == `` {
 				//removeDateList = append(removeDateList, sk)
-				//fmt.Println("异常了")
+				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
-			//}
-			calVal, err := engine.ParseAndExec(formulaFormStr)
+			calVal, e := engine.ParseAndExec(formulaFormStr)
 			//calVal, err := calResult.Float64()
-			if err != nil {
-				// 分母为0的报错,忽略该循环
-				if utils.IsDivideZero(err) {
-					//removeDateList = append(removeDateList, sk)
-					continue
-				}
-				err = errors.New("计算失败:获取计算值失败 Err:" + err.Error() + ";formulaStr:" + formulaFormStr)
+			if e != nil {
+				err = errors.New("计算失败:获取计算值失败 Err:" + e.Error() + ";formulaStr:" + formulaFormStr)
 				fmt.Println(err)
-				return nil, nil, err
+				return
 			}
-			//nanCheck := fmt.Sprintf("%0.f", calVal)
 			//yDataMap[n] = calVal
 			//xEdbInfoIdList = append(xEdbInfoIdList, n)
-			nanCheck := fmt.Sprintf("%0.f", calVal)
-			// 分母为0.0的报错
-			if nanCheck == "NaN" || nanCheck == "+Inf" || nanCheck == "-Inf" {
-				continue
-			}
 			calVal, _ = decimal.NewFromFloat(calVal).Round(4).Float64()
 			yDataMap[tmpN] = calVal
 			xEdbInfoIdList = append(xEdbInfoIdList, tmpN)
@@ -565,6 +615,33 @@ func ProfitChartChartData(baseDataList []*models.EdbDataList, futureGoodEdbInfoM
 			yDate = maxDate.Format(utils.FormatDate)
 		}
 
+		{
+			hasDataIndexList := make([]int, 0)
+			for dataK, edbInfoId := range xEdbInfoIdList {
+				if _, ok := noDataIdMap[edbInfoId]; !ok { // 如果是没有数据的指标id
+					hasDataIndexList = append(hasDataIndexList, dataK)
+				}
+			}
+			lenHasDataIndex := len(hasDataIndexList)
+			if lenHasDataIndex > 0 {
+				for lenHasDataI := 1; lenHasDataI < lenHasDataIndex; lenHasDataI++ {
+					perK := hasDataIndexList[lenHasDataI-1] //上一个有数据的指标下标
+					currK := hasDataIndexList[lenHasDataI]  //当前有数据的指标下标
+					preVal := findDataList[perK]            //上一个有数据的坐标的值
+					currVal := findDataList[currK]          //当前有数据的指标的值
+
+					// 环差值
+					hcValDeci := decimal.NewFromFloat(currVal).Sub(decimal.NewFromFloat(preVal)).Div(decimal.NewFromInt(int64(currK - perK)))
+					var tmpI int64
+					// 将两个中间的数据做平均值补全
+					for hcI := perK + 1; hcI < currK; hcI++ {
+						tmpI++
+						findDataList[hcI], _ = decimal.NewFromFloat(preVal).Add(hcValDeci.Mul(decimal.NewFromInt(tmpI))).RoundCeil(4).Float64()
+					}
+				}
+			}
+		}
+		futureGoodNameMap[tmpk] = tmpNameMap
 		yDataList = append(yDataList, YData{
 			Date:           yDate,
 			ConfigDate:     realDateTime,
@@ -603,8 +680,6 @@ func getProfitFutureGoodEdbInfoList(earliestDateTime time.Time, zlFutureGoodEdbI
 		lenFutureGoodEdbInfoList := len(futureGoodEdbInfoList)
 		//futureGoodEdbInfoList
 		//if isAllChina {
-		//	// 如果全是国内指标,那么只需要拼上多出的几期合约即可
-		//	maxN = lenFutureGoodEdbInfoList + monthNum
 		//}
 		// 如果全是国内指标,那么只需要拼上多出的几期合约即可
 		maxN = lenFutureGoodEdbInfoList + monthNum
@@ -612,9 +687,6 @@ func getProfitFutureGoodEdbInfoList(earliestDateTime time.Time, zlFutureGoodEdbI
 		for i := 1; i < maxN; i++ {
 			k := i % lenFutureGoodEdbInfoList
 			futureGoodEdbInfoDateMap[earliestDateTime.AddDate(0, i, 0).Format(utils.FormatYearMonthDate)] = futureGoodEdbInfoList[k]
-			if i > newMaxN {
-				newMaxN = i
-			}
 		}
 
 		//需求池604,只要是国内合约,最大必须是12期
@@ -626,7 +698,7 @@ func getProfitFutureGoodEdbInfoList(earliestDateTime time.Time, zlFutureGoodEdbI
 	for _, v := range tmpFutureGoodEdbInfoList {
 		//海外的连续日期,目前
 		if v.FutureGoodEdbType == 2 {
-			if v.Month <= newMaxN {
+			if v.Month <= maxN {
 				futureGoodEdbInfoDateMap[earliestDateTime.AddDate(0, v.Month, 0).Format(utils.FormatYearMonthDate)] = v
 				if v.Month > newMaxN {
 					newMaxN = v.Month
@@ -647,7 +719,7 @@ func getProfitFutureGoodEdbInfoList(earliestDateTime time.Time, zlFutureGoodEdbI
 		subMonth := v.Month - int(earliestDateTime.Month())
 		// 如果(当前年-最新日期的年份) * 12个月 + (当前月-最新日期的月份) 小于总月份
 		tmpN := subYear*12 + subMonth
-		if tmpN < newMaxN {
+		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 {
@@ -661,29 +733,37 @@ func getProfitFutureGoodEdbInfoList(earliestDateTime time.Time, zlFutureGoodEdbI
 }
 
 // handleProfitResultData 处理成最终的结果数据
-func handleProfitResultData(baseEdbInfo *models.EdbInfo, yDataList []YData, earliestDateTime time.Time) (xDataList []XData, newYDataList []YData, err error) {
-	xDataList = make([]XData, 0)
+func handleProfitResultData(xDataListInit []XData, futureNameMap map[int]map[int]string, yDataList []YData, earliestDateTime time.Time, allEdbInfoIds []int) (xDataList []XData, newYDataList []YData, err error) {
 	newYDataList = yDataList
+	xDataList = xDataListInit
 
 	nMap := make(map[int]int)
-
+	nList := make([]int, 0)
+	nListEdbMap := make(map[int]struct{})
 	for _, v := range yDataList {
 		for _, n := range v.XEdbInfoIdList {
-			nMap[n] = n
+			if utils.InArrayByInt(allEdbInfoIds, n) {
+				if _, ok := nListEdbMap[n]; !ok {
+					nList = append(nList, n)
+					nListEdbMap[n] = struct{}{}
+				}
+			} else {
+				nMap[n] = n
+			}
 		}
 	}
 
 	// 找出所有的N值,并进行正序排列
-	nList := make([]int, 0)
+	nListTmp := make([]int, 0)
 	for _, n := range nMap {
-		nList = append(nList, n)
+		nListTmp = append(nListTmp, n)
 	}
-	sort.Slice(nList, func(i, j int) bool {
-		return nList[i] < nList[j]
+	sort.Slice(nListTmp, func(i, j int) bool {
+		return nListTmp[i] < nListTmp[j]
 	})
-
+	nList = append(nList, nListTmp...)
 	for _, n := range nList {
-		if n == 0 {
+		if utils.InArrayByInt(allEdbInfoIds, n) {
 			continue
 		}
 		xDataList = append(xDataList, XData{
@@ -704,7 +784,7 @@ func handleProfitResultData(baseEdbInfo *models.EdbInfo, yDataList []YData, earl
 			if len(xEdbInfoIdList) > 0 {
 				currN := xEdbInfoIdList[0]
 				// 当前距离最早的日期相差的N数
-				if n == currN {
+				if n == currN { // todo 改成所有的基础现货指标
 					if needNum > 0 {
 						currVal := yData.Value[valIndex]
 						preVal := yData.Value[valIndex-1]
@@ -714,12 +794,16 @@ func handleProfitResultData(baseEdbInfo *models.EdbInfo, yDataList []YData, earl
 							newYDataList[yIndex].XEdbInfoIdList = append(newYDataList[yIndex].XEdbInfoIdList, 0)
 
 							// 赋值平均值
-							tmpVal, _ := decimal.NewFromFloat(preVal).Add(hcValDeci.Mul(decimal.NewFromInt(int64(tmpNum + 1)))).Round(4).Float64()
+							tmpVal, _ := decimal.NewFromFloat(preVal).Add(hcValDeci.Mul(decimal.NewFromInt(int64(tmpNum + 1)))).RoundCeil(4).Float64()
 							newYDataList[yIndex].Value = append(newYDataList[yIndex].Value, tmpVal)
 						}
 					}
+					if utils.InArrayByInt(allEdbInfoIds, currN) {
+						newYDataList[yIndex].XEdbInfoIdList = append(newYDataList[yIndex].XEdbInfoIdList, currN)
+					} else {
+						newYDataList[yIndex].XEdbInfoIdList = append(newYDataList[yIndex].XEdbInfoIdList, currN+1)
+					}
 
-					newYDataList[yIndex].XEdbInfoIdList = append(newYDataList[yIndex].XEdbInfoIdList, currN+1)
 					newYDataList[yIndex].Value = append(newYDataList[yIndex].Value, yData.Value[valIndex])
 					valIndex++
 					needNum = 0
@@ -746,7 +830,7 @@ func handleProfitResultData(baseEdbInfo *models.EdbInfo, yDataList []YData, earl
 	}
 
 	earliestDateTime = time.Date(earliestDateTime.Year(), earliestDateTime.Month(), 1, 0, 0, 0, 0, time.Local)
-	xDataList = xDataList[0:maxI]
+	xDataList = xDataList[0 : maxI+1]
 	for yIndex, yData := range newYDataList {
 		if len(yData.XEdbInfoIdList) > maxI+1 {
 			newYDataList[yIndex].XEdbInfoIdList = yData.XEdbInfoIdList[0 : maxI+1]
@@ -755,30 +839,23 @@ func handleProfitResultData(baseEdbInfo *models.EdbInfo, yDataList []YData, earl
 			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)
+		for k1, n := range newYDataList[yIndex].XEdbInfoIdList {
+			if utils.InArrayByInt(allEdbInfoIds, n) { // 现货价不处理
+				tmpItem := xDataListInit[k1]
+				nameList = append(nameList, tmpItem.Name)
+				enNameList = append(enNameList, tmpItem.NameEn)
 				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)
+				nameTmp := futureNameMap[yIndex][n]
+				nameTmpSlice := strings.Split(nameTmp, "-")
+				nameList = append(nameList, nameTmpSlice[0])
+				enNameList = append(enNameList, nameTmpSlice[1])
 			}
 		}
 		newYDataList[yIndex].NameList = nameList
@@ -827,6 +904,7 @@ func ReplaceFormula(tagEdbIdMap map[string]int, valArr map[int]float64, formulaS
 	for k, v := range funMap {
 		formulaStr = strings.Replace(formulaStr, v, k, -1)
 	}
+	fmt.Println(formulaStr)
 	if replaceCount == len(tagEdbIdMap) {
 		return formulaStr
 	} else {

+ 1 - 1
models/base_from_calculate.go

@@ -687,7 +687,7 @@ type EdbInfoCalculateBatchSaveReq struct {
 	CalculateFormula string                         `description:"计算公式"`
 	EdbInfoIdArr     []EdbInfoCalculateEdbInfoIdReq `description:"关联指标列表"`
 	MoveType         int                            `description:"移动方式:1:领先(默认),2:滞后"`
-	MoveFrequency    string                         `description:"移动频度:天/周/月/季/年"`
+	MoveFrequency    string                         `description:"移动频度:天/周/月/季/年/交易日/自然日"`
 	Calendar         string                         `description:"公历/农历"`
 	Data             interface{}                    `description:"数据"`
 	EmptyType        int                            `description:"空值处理类型(0查找前后35天,1不计算,2前值填充,3后值填充,4等于0)"`

+ 276 - 0
models/base_from_hisugar.go

@@ -0,0 +1,276 @@
+package models
+
+import (
+	"eta/eta_index_lib/utils"
+	"fmt"
+	"github.com/beego/beego/v2/client/orm"
+	"strconv"
+	"strings"
+	"time"
+)
+
+type BaseFromHisugarIndex struct {
+	BaseFromHisugarIndexId int       // 主键ID
+	IndexCode              string    // 指标编码
+	IndexName              string    // 指标名称
+	ClassifyId             uint      // 分类ID
+	Unit                   string    // 单位
+	Frequency              string    // 频度
+	Describe               string    // 指标描述
+	Sort                   int       // 排序
+	CreateTime             time.Time // 创建时间
+	ModifyTime             time.Time // 修改时间
+}
+
+type BaseFromHisugarData struct {
+	BaseFromHisugarDataId  int    // 数据表ID
+	BaseFromHisugarIndexId int    // 指标ID
+	IndexCode              string // 指标编码
+	DataTime               string
+	Value                  string
+	CreateTime             time.Time // 创建时间
+	ModifyTime             time.Time // 修改时间
+}
+
+
+//添加数据
+func AddBaseFromHisugarIndexMuti(items []*BaseFromHisugarIndex) (err error) {
+	o := orm.NewOrm()
+	_, err = o.InsertMulti(500, items)
+	return
+}
+func AddBaseFromHisugarIndex(item *BaseFromHisugarIndex) (lastId int64, err error) {
+	o := orm.NewOrm()
+	lastId, err = o.Insert(item)
+	return
+}
+
+func AddBaseFromHisugarData(item *BaseFromHisugarData) (lastId int64, err error) {
+	o := orm.NewOrm()
+	lastId, err = o.Insert(item)
+	return
+}
+
+func AddEdbDataFromHisugar(edbCode string) (err error) {
+	o := orm.NewOrm()
+
+	hisugarBaseDataAll, err := GetHisugarDataByCode(edbCode)
+	if err != nil && err.Error() != utils.ErrNoRow() {
+		return
+	}
+
+	var isAdd bool
+	addSql := ` INSERT INTO edb_data_hisugar(edb_info_id,edb_code,data_time,value,create_time,modify_time,data_timestamp) values `
+	existMap := make(map[string]string)
+	for _, sv := range hisugarBaseDataAll {
+		eDate := sv.DataTime
+		var timeStr string
+		var dataTime time.Time
+		var sDataTime string
+		var timestamp int64
+
+		sDataTime = eDate
+		dataTime, err = time.ParseInLocation(utils.FormatDate, eDate, time.Local)
+		if err != nil {
+			fmt.Println("time.Parse Err:" + eDate)
+			return err
+		}
+		timestamp = dataTime.UnixNano() / 1e6
+		timeStr = fmt.Sprintf("%d", timestamp)
+
+		value := strings.Replace(sv.Value, "%", "", -1)
+		if _, ok := existMap[sDataTime]; !ok {
+			addSql += GetAddSql("0", edbCode, sDataTime, timeStr, value)
+			fmt.Println("edbCode:", edbCode)
+			fmt.Println("sDataTime:", sDataTime)
+			fmt.Println("timeStr:", timeStr)
+			fmt.Println("value:", value)
+			isAdd = true
+		}
+		existMap[eDate] = value
+	}
+	if isAdd {
+		addSql = strings.TrimRight(addSql, ",")
+		utils.FileLog.Info("addSql:" + addSql)
+		_, err = o.Raw(addSql).Exec()
+		if err != nil {
+			return err
+		}
+	}
+	return
+}
+
+// GetHisugarDataByCode
+func GetHisugarDataByCode(indexCode string) (items []*BaseFromHisugarData, err error) {
+	o := orm.NewOrm()
+	sql := "SELECT * FROM base_from_hisugar_data WHERE index_code=? "
+	_, err = o.Raw(sql, indexCode).QueryRows(&items)
+	return
+}
+
+
+// RefreshEdbDataFromHisugar 刷新隆众资讯
+func RefreshEdbDataFromHisugar(edbInfoId int, edbCode, startDate string) (err error) {
+	source := utils.DATA_SOURCE_OILCHEM
+	subSource := utils.DATA_SUB_SOURCE_EDB
+
+	o := orm.NewOrm()
+	if err != nil {
+		return
+	}
+	edbInfoIdStr := strconv.Itoa(edbInfoId)
+	//计算数据
+	var condition string
+	var pars []interface{}
+
+	if edbCode != "" {
+		condition += " AND index_code=? "
+		pars = append(pars, edbCode)
+	}
+
+	if startDate != "" {
+		condition += " AND data_time>=? "
+		pars = append(pars, startDate)
+	}
+
+	hisugarDataList, err := GetBaseFromHisugarDataByCondition(condition, pars)
+	if err != nil {
+		return
+	}
+
+	// 真实数据的最大日期  , 插入规则配置的日期
+	var realDataMaxDate, edbDataInsertConfigDate time.Time
+	var edbDataInsertConfig *EdbDataInsertConfig
+	var isFindConfigDateRealData bool //是否找到配置日期的实际数据的值
+	{
+		edbDataInsertConfig, err = GetEdbDataInsertConfigByEdbId(edbInfoId)
+		if err != nil && err.Error() != utils.ErrNoRow() {
+			return
+		}
+		if edbDataInsertConfig != nil {
+			edbDataInsertConfigDate = edbDataInsertConfig.Date
+		}
+	}
+
+	var existCondition string
+	var existPars []interface{}
+
+	existCondition += " AND edb_info_id=? "
+	existPars = append(existPars, edbInfoId)
+	if startDate != "" {
+		existCondition += " AND data_time>=? "
+		existPars = append(existPars, startDate)
+	}
+
+	existList, err := GetEdbDataByCondition(source, subSource, existCondition, existPars)
+	if err != nil {
+		return err
+	}
+	existMap := make(map[string]*EdbInfoSearchData)
+	for _, v := range existList {
+		existMap[v.DataTime] = v
+	}
+	addSql := ` INSERT INTO edb_data_hisugar(edb_info_id,edb_code,data_time,value,create_time,modify_time,data_timestamp) values `
+	var isAdd bool
+	for _, v := range hisugarDataList {
+		item := v
+
+		eDate := item.DataTime
+		dataTime, err := time.ParseInLocation(utils.FormatDate, eDate, time.Local)
+		if err != nil {
+			return err
+		}
+		if findItem, ok := existMap[v.DataTime]; !ok {
+			sValue := item.Value
+			timestamp := dataTime.UnixNano() / 1e6
+			timeStr := fmt.Sprintf("%d", timestamp)
+
+			addSql += GetAddSql(edbInfoIdStr, edbCode, eDate, timeStr, sValue)
+			isAdd = true
+		} else {
+			if findItem != nil && utils.SubFloatToString(findItem.Value, 30) != item.Value {
+				err = ModifyEdbDataById(source, subSource, findItem.EdbDataId, item.Value)
+				if err != nil {
+					return err
+				}
+			}
+		}
+
+		// 下面代码主要目的是处理掉手动插入的数据判断
+		{
+			if realDataMaxDate.IsZero() || dataTime.After(realDataMaxDate) {
+				realDataMaxDate = dataTime
+			}
+			if edbDataInsertConfigDate.IsZero() || dataTime.Equal(edbDataInsertConfigDate) {
+				isFindConfigDateRealData = true
+			}
+		}
+	}
+
+	// 处理手工数据补充的配置
+	HandleConfigInsertEdbData(realDataMaxDate, edbDataInsertConfig, edbInfoId, source, subSource, existMap, isFindConfigDateRealData)
+
+	if isAdd {
+		addSql = strings.TrimRight(addSql, ",")
+		_, err = o.Raw(addSql).Exec()
+		if err != nil {
+			fmt.Println("RefreshEdbDataFromSci add Err", err.Error())
+			return
+		}
+	}
+	return
+}
+
+func GetBaseFromHisugarDataByCondition(condition string, pars []interface{}) (list []*BaseFromHisugarData, err error) {
+	o := orm.NewOrm()
+	sql := `SELECT * FROM base_from_hisugar_data WHERE 1=1 `
+	if condition != "" {
+		sql += condition
+	}
+	_, err = o.Raw(sql, pars).QueryRows(&list)
+	return
+}
+
+
+type HandleHisugarEdbDataReq struct {
+	List []*BaseFromHisugarIndexReq
+}
+
+func GetBaseFromHisugarIndex() (list []*BaseFromHisugarIndex, err error) {
+	o := orm.NewOrm()
+	sql := `SELECT * FROM base_from_hisugar_index group by index_name `
+	_, err = o.Raw(sql).QueryRows(&list)
+	return
+}
+
+func GetBaseFromHisugarData(indexCode, dataTime string) (item *BaseFromHisugarData, err error) {
+	o := orm.NewOrm()
+	sql := `SELECT * FROM base_from_hisugar_data where index_code=? And data_time = ? `
+	err = o.Raw(sql, indexCode, dataTime).QueryRow(&item)
+	return
+}
+
+// UpdateBaseFromSci99Data
+func UpdateBaseFromHisugarData(value , indexCode, dataTime string) (err error) {
+	o := orm.NewOrm()
+	sql := `UPDATE base_from_sci99_data SET value=?,modify_time=NOW() WHERE index_code = ? AND data_time = ? `
+	_, err = o.Raw(sql, value, indexCode, dataTime).Exec()
+	return
+}
+
+type BaseFromHisugarIndexReq struct {
+	BaseFromHisugarIndexId int       // 主键ID
+	IndexCode              string    // 指标编码
+	IndexName              string    // 指标名称
+	ClassifyId             uint      // 分类ID
+	Unit                   string    // 单位
+	Frequency              string    // 频度
+	Describe               string    // 指标描述
+	DataTime               string    // 数据日期
+	Sort                   int       // 排序
+	CreateTime             time.Time // 创建时间
+	ModifyTime             time.Time // 修改时间
+	IndexNameStr           string    // 指标名称字符串
+	MarketName             string    // 市场名称
+	Value                  string    // 值
+}

+ 8 - 2
models/base_from_ly_classify.go

@@ -1,7 +1,10 @@
 // @Author gmy 2024/8/7 9:26:00
 package models
 
-import "github.com/beego/beego/v2/client/orm"
+import (
+	"errors"
+	"github.com/beego/beego/v2/client/orm"
+)
 
 type BaseFromLyClassify struct {
 	BaseFromLyClassifyId int    `orm:"column(base_from_ly_classify_id);pk"` // 分类ID
@@ -19,8 +22,11 @@ func init() {
 
 // GetLyClassifyByName 根据分类名称查询
 func GetLyClassifyByName(classifyName string) (item *BaseFromLyClassify, err error) {
-	o := orm.NewOrmUsingDB("data")
+	o := orm.NewOrm()
 	sql := `SELECT * FROM base_from_ly_classify WHERE classify_name=?`
 	err = o.Raw(sql, classifyName).QueryRow(&item)
+	if errors.Is(err, orm.ErrNoRows) {
+		return nil, nil
+	}
 	return
 }

+ 17 - 5
models/base_from_ly_data.go

@@ -1,7 +1,10 @@
 // @Author gmy 2024/8/7 9:50:00
 package models
 
-import "github.com/beego/beego/v2/client/orm"
+import (
+	"errors"
+	"github.com/beego/beego/v2/client/orm"
+)
 
 type BaseFromLyData struct {
 	BaseFromLyDataId  int     `orm:"column(base_from_ly_data_id);pk"` // 数据ID
@@ -19,30 +22,36 @@ func init() {
 
 // AddLyDataList 批量插入数据记录列表
 func AddLyDataList(items []BaseFromLyData) (err error) {
-	o := orm.NewOrmUsingDB("data")
+	o := orm.NewOrm()
 	_, err = o.InsertMulti(len(items), items)
 	return
 }
 
 // GetLyDataByIndexIdAndDataTime 根据指标id和数据日期查询数据
 func GetLyDataByIndexIdAndDataTime(indexId int, dataTime string) (items []BaseFromLyData, err error) {
-	o := orm.NewOrmUsingDB("data")
+	o := orm.NewOrm()
 	sql := `SELECT * FROM base_from_ly_data WHERE base_from_ly_index_id=? AND data_time=?`
 	_, err = o.Raw(sql, indexId, dataTime).QueryRows(&items)
+	if errors.Is(err, orm.ErrNoRows) {
+		return nil, nil
+	}
 	return
 }
 
 // GetLyDataByIndexIdAndDataTimeYM 根据指标id和数据日期的年月查询数据
 func GetLyDataByIndexIdAndDataTimeYM(indexId int, dataTime string) (items []BaseFromLyData, err error) {
-	o := orm.NewOrmUsingDB("data")
+	o := orm.NewOrm()
 	sql := `SELECT * FROM base_from_ly_data WHERE base_from_ly_index_id=? AND data_time like ?`
 	_, err = o.Raw(sql, indexId, dataTime+"%").QueryRows(&items)
+	if errors.Is(err, orm.ErrNoRows) {
+		return nil, nil
+	}
 	return
 }
 
 // UpdateLyDataById 根据主键id更新数据
 func UpdateLyDataById(dataId int, value float64) (err error) {
-	o := orm.NewOrmUsingDB("data")
+	o := orm.NewOrm()
 	sql := `UPDATE base_from_ly_data SET value=? WHERE base_from_ly_data_id=?`
 	_, err = o.Raw(sql, value, dataId).Exec()
 	return
@@ -56,5 +65,8 @@ func GetBaseFromLyDataByIndexCode(condition string, pars []interface{}) (items [
 		sql += condition
 	}
 	_, err = o.Raw(sql, pars...).QueryRows(&items)
+	if errors.Is(err, orm.ErrNoRows) {
+		return nil, nil
+	}
 	return
 }

+ 3 - 3
models/base_from_ly_index.go

@@ -27,7 +27,7 @@ func init() {
 
 // AddLyIndexList 批量插入指标记录列表
 func AddLyIndexList(items []*BaseFromLyIndex) (err error) {
-	o := orm.NewOrmUsingDB("data")
+	o := orm.NewOrm()
 	_, err = o.InsertMulti(len(items), items)
 	return
 }
@@ -35,7 +35,7 @@ func AddLyIndexList(items []*BaseFromLyIndex) (err error) {
 // AddLyIndex 添加指标
 func AddLyIndex(item *BaseFromLyIndex) (int64, error) {
 	item.CreateTime = time.Now().Format("2006-01-02 15:04:05")
-	o := orm.NewOrmUsingDB("data")
+	o := orm.NewOrm()
 	id, err := o.Insert(item)
 	if err != nil {
 		return 0, err
@@ -45,7 +45,7 @@ func AddLyIndex(item *BaseFromLyIndex) (int64, error) {
 
 // GetLyIndexByCode 查询指标编码是否存在
 func GetLyIndexByCode(indexCode string) (item *BaseFromLyIndex, err error) {
-	o := orm.NewOrmUsingDB("data")
+	o := orm.NewOrm()
 	sql := `SELECT * FROM base_from_ly_index WHERE index_code=?`
 	err = o.Raw(sql, indexCode).QueryRow(&item)
 

+ 2 - 2
models/base_from_ly_index_record.go

@@ -24,7 +24,7 @@ func init() {
 
 // AddLyIndexRecord 添加指标记录
 func AddLyIndexRecord(item *BaseFromLyIndexRecord) (int64, error) {
-	o := orm.NewOrmUsingDB("data")
+	o := orm.NewOrm()
 	id, err := o.Insert(item)
 	if err != nil {
 		return 0, err
@@ -34,7 +34,7 @@ func AddLyIndexRecord(item *BaseFromLyIndexRecord) (int64, error) {
 
 // GetLyIndexRecordByUrl 查询指标记录是否存在
 func GetLyIndexRecordByUrl(url string) (item *BaseFromLyIndexRecord, err error) {
-	o := orm.NewOrmUsingDB("data")
+	o := orm.NewOrm()
 	sql := `SELECT * FROM base_from_ly_index_record WHERE url=?`
 	err = o.Raw(sql, url).QueryRow(&item)
 

+ 1 - 1
models/base_from_yongyi.go

@@ -333,7 +333,7 @@ func (y *BaseFromYongyiClassify) GetByClassifyName(classifyName string) (item *B
 
 func (y *BaseFromYongyiClassify) GetParentClassify() (items []*BaseFromYongyiClassify, err error) {
 	o := orm.NewOrm()
-	sql := ` SELECT * FROM base_from_yongyi_classify WHERE classify_name in ("日度", "月度", "周度", "年度", "旬度") and parent_id=0 `
+	sql := ` SELECT * FROM base_from_yongyi_classify WHERE classify_name in ("日度", "月度", "周度", "年度", "旬度", "半年度", "年度") and parent_id=0 `
 	_, err = o.Raw(sql).QueryRows(&items)
 	return
 }

+ 4 - 0
models/db.go

@@ -173,6 +173,10 @@ func initBaseIndex() {
 		new(BaseFromUsdaFasIndex),
 		new(BaseFromUsdaFasData),
 		new(BaseFromUsdaFasClassify),
+		new(BaseFromHisugarIndex),
+		new(BaseFromHisugarData),
+		new(EdbDataCalculateStl),
+		new(CalculateStlConfig),
 	)
 }
 

+ 26 - 11
models/edb_data_calculate_bp.go

@@ -433,18 +433,26 @@ func refreshAllCalculateBp(to orm.TxOrmer, edbInfoId, source, subSource int, fro
 			if _, ok := existMap[existKey]; !ok {
 				timestamp := needDay.UnixNano() / 1e6
 				timestampStr := fmt.Sprintf("%d", timestamp)
-				valStr := decimal.NewFromFloat(currentItem.Value).String()
+
+				currValDeci := decimal.NewFromFloat(currentItem.Value)
 				if prevItem != nil && needDayStr != currentItem.DataTime {
-					valStr = decimal.NewFromFloat(prevItem.Value).String()
+					currValDeci = decimal.NewFromFloat(prevItem.Value)
 				}
+				currValStr := currValDeci.String()
 				tmpExistData, ok2 := existDataMap[needDayStr]
 				if !ok2 {
-					addSql += GetAddSql(edbInfoIdStr, edbCode, needDayStr, timestampStr, valStr)
+					addSql += GetAddSql(edbInfoIdStr, edbCode, needDayStr, timestampStr, currValStr)
 					isAdd = true
 				} else {
-					//如果对应的值不匹配
-					if tmpExistData.Value != valStr {
-						err = ModifyEdbDataById(source, subSource, tmpExistData.EdbDataId, valStr)
+					// 已经入库的数据值
+					tmpExistVal, tmpErr := decimal.NewFromString(tmpExistData.Value)
+					if tmpErr != nil {
+						err = tmpErr
+						return
+					}
+					// 如果已经入库的数据值 与 当前计算出来的值 不匹配,那么就去更新
+					if !tmpExistVal.Equal(currValDeci) {
+						err = ModifyEdbDataById(source, subSource, tmpExistData.EdbDataId, currValStr)
 						if err != nil {
 							return err
 						}
@@ -459,15 +467,22 @@ func refreshAllCalculateBp(to orm.TxOrmer, edbInfoId, source, subSource int, fro
 			currentDate, _ := time.ParseInLocation(utils.FormatDate, currentItem.DataTime, time.Local)
 			timestamp := currentDate.UnixNano() / 1e6
 			timestampStr := fmt.Sprintf("%d", timestamp)
-			valStr := decimal.NewFromFloat(currentItem.Value).String()
+			currValDeci := decimal.NewFromFloat(currentItem.Value)
+			currValStr := currValDeci.String()
 			tmpExistData, ok2 := existDataMap[currentItem.DataTime]
 			if !ok2 {
-				addSql += GetAddSql(edbInfoIdStr, edbCode, currentItem.DataTime, timestampStr, valStr)
+				addSql += GetAddSql(edbInfoIdStr, edbCode, currentItem.DataTime, timestampStr, currValStr)
 				isAdd = true
 			} else {
-				//如果对应的值不匹配
-				if tmpExistData.Value != valStr {
-					err = ModifyEdbDataById(source, subSource, tmpExistData.EdbDataId, valStr)
+				// 已经入库的数据值
+				tmpExistVal, tmpErr := decimal.NewFromString(tmpExistData.Value)
+				if tmpErr != nil {
+					err = tmpErr
+					return
+				}
+				// 如果已经入库的数据值 与 当前计算出来的值 不匹配,那么就去更新
+				if !tmpExistVal.Equal(currValDeci) {
+					err = ModifyEdbDataById(source, subSource, tmpExistData.EdbDataId, currValStr)
 					if err != nil {
 						return err
 					}

+ 24 - 1
models/edb_data_calculate_hbz.go

@@ -235,8 +235,10 @@ func refreshAllCalculateHbz(to orm.TxOrmer, edbInfoId, source, subSource int, fr
 		return err
 	}
 	existDataMap := make(map[string]string)
+	removeDateMap := make(map[string]bool) //需要移除的日期
 	for _, v := range existDataList {
 		existDataMap[v.DataTime] = v.Value
+		removeDateMap[v.DataTime] = true
 	}
 	addSql := ` INSERT INTO edb_data_calculate_hbz(edb_info_id,edb_code,data_time,value,create_time,modify_time,data_timestamp) values `
 	var isAdd bool
@@ -251,13 +253,15 @@ func refreshAllCalculateHbz(to orm.TxOrmer, edbInfoId, source, subSource int, fr
 			preItem := dataList[j]
 			if currentItem != nil && preItem != nil {
 				existKey := edbCode + currentItem.DataTime
-
+				// 有计算出来值,那么就从待删除指标中移除
 				if _, ok := existMap[existKey]; !ok {
 					currentDate, _ := time.ParseInLocation(utils.FormatDate, currentItem.DataTime, time.Local)
 					timestamp := currentDate.UnixNano() / 1e6
 					timestampStr := fmt.Sprintf("%d", timestamp)
 					val := HbzDiv(currentItem.Value, preItem.Value)
 					if val != "" {
+						// 有计算出来值,那么就从待删除指标中移除
+						delete(removeDateMap, currentItem.DataTime)
 						if existVal, findOk := existDataMap[currentItem.DataTime]; !findOk {
 							addSql += GetAddSql(edbInfoIdStr, edbCode, currentItem.DataTime, timestampStr, val)
 							isAdd = true
@@ -279,6 +283,25 @@ func refreshAllCalculateHbz(to orm.TxOrmer, edbInfoId, source, subSource int, fr
 			}
 		}
 	}
+
+	if len(removeDateMap) > 0 {
+		removeDateList := make([]string, 0) //需要移除的日期
+		for k := range removeDateMap {
+			removeDateList = append(removeDateList, k)
+		}
+		removeDateStr := strings.Join(removeDateList, `","`)
+		removeDateStr = `"` + removeDateStr + `"`
+		//如果拼接指标变更了,那么需要删除所有的指标数据
+		tableName := GetEdbDataTableName(source, subSource)
+		sql := fmt.Sprintf(` DELETE FROM %s WHERE edb_info_id = ? and data_time in (%s) `, tableName, removeDateStr)
+
+		_, err = to.Raw(sql, edbInfoId).Exec()
+		if err != nil {
+			err = fmt.Errorf("删除计算失败的计算指标数据失败,Err:" + err.Error())
+			return
+		}
+	}
+
 	if isAdd {
 		addSql = strings.TrimRight(addSql, ",")
 		_, err = to.Raw(addSql).Exec()

+ 22 - 0
models/edb_data_calculate_ljzzj.go

@@ -288,8 +288,10 @@ func (obj Ljzzj) refresh(to orm.TxOrmer, edbInfoId, source, subSource int, fromE
 		return err
 	}
 	dataMap := make(map[string]string)
+	removeDateMap := make(map[string]bool) //需要移除的日期
 	for _, v := range existDataList {
 		dataMap[v.DataTime] = v.Value
+		removeDateMap[v.DataTime] = true
 	}
 	existDataMap := make(map[string]string)
 	for yk, yv := range yearMap {
@@ -362,6 +364,7 @@ func (obj Ljzzj) refresh(to orm.TxOrmer, edbInfoId, source, subSource int, fromE
 				}
 			}
 			if date != "" {
+				delete(removeDateMap, date)
 				tmpSql, newAdd, tmpErr := obj.calculate(edbInfoId, date, edbInfoIdStr, edbCode, dataTableName, addSql, val, dataMap, existDataMap, to)
 				if !isAdd {
 					isAdd = newAdd
@@ -373,6 +376,25 @@ func (obj Ljzzj) refresh(to orm.TxOrmer, edbInfoId, source, subSource int, fromE
 			}
 		}
 	}
+
+	if len(removeDateMap) > 0 {
+		removeDateList := make([]string, 0) //需要移除的日期
+		for k := range removeDateMap {
+			removeDateList = append(removeDateList, k)
+		}
+		removeDateStr := strings.Join(removeDateList, `","`)
+		removeDateStr = `"` + removeDateStr + `"`
+		//如果拼接指标变更了,那么需要删除所有的指标数据
+		tableName := GetEdbDataTableName(source, subSource)
+		sql := fmt.Sprintf(` DELETE FROM %s WHERE edb_info_id = ? and data_time in (%s) `, tableName, removeDateStr)
+
+		_, err = to.Raw(sql, edbInfoId).Exec()
+		if err != nil {
+			err = fmt.Errorf("删除计算失败的计算指标数据失败,Err:" + err.Error())
+			return
+		}
+	}
+
 	if isAdd {
 		addSql = strings.TrimRight(addSql, ",")
 		_, err = to.Raw(addSql).Exec()

+ 22 - 0
models/edb_data_calculate_ljzzy.go

@@ -257,8 +257,10 @@ func refreshAllCalculateLjzzy(to orm.TxOrmer, edbInfoId, source, subSource int,
 		return err
 	}
 	dataMap := make(map[string]string)
+	removeDateMap := make(map[string]bool) //需要移除的日期
 	for _, v := range existDataList {
 		dataMap[v.DataTime] = v.Value
+		removeDateMap[v.DataTime] = true
 	}
 	existDataMap := make(map[string]string)
 	for yk, yv := range yearMap {
@@ -352,6 +354,7 @@ func refreshAllCalculateLjzzy(to orm.TxOrmer, edbInfoId, source, subSource int,
 				//		}
 				//	}
 				//}
+				delete(removeDateMap, date)
 				tmpSql, newAdd, tmpErr := calculateLjzzy(edbInfoId, date, edbInfoIdStr, edbCode, dataTableName, addSql, val, dataMap, existDataMap, to)
 				if !isAdd {
 					isAdd = newAdd
@@ -363,6 +366,25 @@ func refreshAllCalculateLjzzy(to orm.TxOrmer, edbInfoId, source, subSource int,
 			}
 		}
 	}
+
+	if len(removeDateMap) > 0 {
+		removeDateList := make([]string, 0) //需要移除的日期
+		for k := range removeDateMap {
+			removeDateList = append(removeDateList, k)
+		}
+		removeDateStr := strings.Join(removeDateList, `","`)
+		removeDateStr = `"` + removeDateStr + `"`
+		//如果拼接指标变更了,那么需要删除所有的指标数据
+		tableName := GetEdbDataTableName(source, subSource)
+		sql := fmt.Sprintf(` DELETE FROM %s WHERE edb_info_id = ? and data_time in (%s) `, tableName, removeDateStr)
+
+		_, err = to.Raw(sql, edbInfoId).Exec()
+		if err != nil {
+			err = fmt.Errorf("删除计算失败的计算指标数据失败,Err:" + err.Error())
+			return
+		}
+	}
+
 	if isAdd {
 		addSql = strings.TrimRight(addSql, ",")
 		_, err = to.Raw(addSql).Exec()

+ 404 - 0
models/edb_data_calculate_phase_shift.go

@@ -0,0 +1,404 @@
+package models
+
+import (
+	"errors"
+	"eta/eta_index_lib/utils"
+	"fmt"
+	"github.com/beego/beego/v2/client/orm"
+	"strconv"
+	"strings"
+	"time"
+)
+
+// AddCalculatePhaseShift 期数移位
+func AddCalculatePhaseShift(req *EdbInfoCalculateBatchSaveReq, fromEdbInfo *EdbInfo, edbCode, uniqueCode string, sysUserId int, sysUserRealName string) (edbInfo *EdbInfo, err error) {
+	o := orm.NewOrm()
+	to, err := o.Begin()
+	if err != nil {
+		return
+	}
+
+	defer func() {
+		if err != nil {
+			fmt.Println("AddCalculatePhaseShift,Err:" + err.Error())
+			_ = to.Rollback()
+		} else {
+			_ = to.Commit()
+		}
+	}()
+
+	// 前端那边让后端处理。。。
+	if req.Frequency == "日度" && req.MoveFrequency == "" {
+		return nil, errors.New("日度指标,移动频率不能为空")
+	}
+	switch req.Frequency {
+	case "周度":
+		req.MoveFrequency = "周"
+	case "旬度":
+		req.MoveFrequency = "旬"
+	case "月度":
+		req.MoveFrequency = "月"
+	case "季度":
+		req.MoveFrequency = "季"
+	case "半年度":
+		req.MoveFrequency = "半年"
+	case "年度":
+		req.MoveFrequency = "年"
+	}
+
+	if req.EdbInfoId <= 0 {
+		edbInfo = new(EdbInfo)
+		edbInfo.Source = utils.DATA_SOURCE_CALCULATE_PHASE_SHIFT
+		edbInfo.SourceName = "期数移位"
+		edbInfo.EdbCode = edbCode
+		edbInfo.EdbName = req.EdbName
+		edbInfo.EdbNameSource = req.EdbName
+		edbInfo.Frequency = req.Frequency
+		edbInfo.Unit = req.Unit
+		edbInfo.ClassifyId = req.ClassifyId
+		edbInfo.SysUserId = sysUserId
+		edbInfo.SysUserRealName = sysUserRealName
+		edbInfo.CreateTime = time.Now()
+		edbInfo.ModifyTime = time.Now()
+		edbInfo.UniqueCode = uniqueCode
+		edbInfo.CalculateFormula = req.Formula
+		edbInfo.EdbNameEn = req.EdbName
+		edbInfo.UnitEn = req.Unit
+		edbInfo.EdbType = 2
+		edbInfo.Sort = GetAddEdbMaxSortByClassifyId(req.ClassifyId, utils.EDB_INFO_TYPE)
+		edbInfo.MoveType = req.MoveType
+		edbInfo.MoveFrequency = req.MoveFrequency
+		newEdbInfoId, tmpErr := to.Insert(edbInfo)
+		if tmpErr != nil {
+			err = tmpErr
+			return
+		}
+		edbInfo.EdbInfoId = int(newEdbInfoId)
+		//关联关系
+		{
+			calculateMappingItem := new(EdbInfoCalculateMapping)
+			calculateMappingItem.CreateTime = time.Now()
+			calculateMappingItem.ModifyTime = time.Now()
+			calculateMappingItem.Sort = 1
+			calculateMappingItem.EdbCode = edbCode
+			calculateMappingItem.EdbInfoId = edbInfo.EdbInfoId
+			calculateMappingItem.FromEdbInfoId = fromEdbInfo.EdbInfoId
+			calculateMappingItem.FromEdbCode = fromEdbInfo.EdbCode
+			calculateMappingItem.FromEdbName = fromEdbInfo.EdbName
+			calculateMappingItem.FromSource = fromEdbInfo.Source
+			calculateMappingItem.FromSourceName = fromEdbInfo.SourceName
+			calculateMappingItem.FromTag = ""
+			calculateMappingItem.Source = edbInfo.Source
+			calculateMappingItem.SourceName = edbInfo.SourceName
+			calculateMappingItem.FromSubSource = edbInfo.SubSource
+			_, err = to.Insert(calculateMappingItem)
+			if err != nil {
+				return
+			}
+		}
+	} else {
+		edbInfo, err = GetEdbInfoById(req.EdbInfoId)
+		if err != nil {
+			return
+		}
+		dataTableName := GetEdbDataTableName(utils.DATA_SOURCE_CALCULATE_PHASE_SHIFT, utils.DATA_SUB_SOURCE_EDB)
+		fmt.Println("dataTableName:" + dataTableName)
+		deleteSql := ` DELETE FROM %s WHERE edb_info_id=? `
+		deleteSql = fmt.Sprintf(deleteSql, dataTableName)
+		_, err = to.Raw(deleteSql, req.EdbInfoId).Exec()
+		if err != nil {
+			return
+		}
+	}
+
+	formulaInt, _ := strconv.Atoi(req.Formula)
+
+	//计算数据
+	err = refreshAllCalculatePhaseShift(to, edbInfo.EdbInfoId, edbInfo.Source, edbInfo.SubSource, formulaInt, edbInfo.MoveType, fromEdbInfo, edbInfo.EdbCode, "", "", edbInfo.MoveFrequency)
+
+	return
+}
+
+// EditCalculatePhaseShift 修改期数移位
+func EditCalculatePhaseShift(edbInfo *EdbInfo, req *EdbInfoCalculateBatchEditReq, fromEdbInfo *EdbInfo) (err error) {
+	o := orm.NewOrm()
+	to, err := o.Begin()
+	if err != nil {
+		return
+	}
+
+	defer func() {
+		if err != nil {
+			fmt.Println("EditCalculatePhaseShift,Err:" + err.Error())
+			_ = to.Rollback()
+		} else {
+			_ = to.Commit()
+		}
+	}()
+
+	switch req.Frequency {
+	case "周度":
+		req.MoveFrequency = "周"
+	case "旬度":
+		req.MoveFrequency = "旬"
+	case "月度":
+		req.MoveFrequency = "月"
+	case "季度":
+		req.MoveFrequency = "季"
+	case "半年度":
+		req.MoveFrequency = "半年"
+	case "年度":
+		req.MoveFrequency = "年"
+	}
+
+	oldEdbInfo := *edbInfo //旧的指标信息
+
+	//修改指标信息
+	edbInfo.EdbName = req.EdbName
+	edbInfo.EdbNameSource = req.EdbName
+	edbInfo.Frequency = req.Frequency
+	edbInfo.Unit = req.Unit
+	edbInfo.ClassifyId = req.ClassifyId
+	edbInfo.MoveType = req.MoveType
+	edbInfo.MoveFrequency = req.MoveFrequency
+	edbInfo.CalculateFormula = req.Formula
+	edbInfo.EdbNameEn = req.EdbNameEn
+	edbInfo.UnitEn = req.UnitEn
+	edbInfo.ModifyTime = time.Now()
+	_, err = to.Update(edbInfo, "EdbName", "EdbNameSource", "Frequency", "Unit", "ClassifyId", "MoveType", "MoveFrequency", "CalculateFormula", "ModifyTime", "EdbNameEn", "UnitEn")
+	if err != nil {
+		return
+	}
+
+	//判断计算指标是否被更换
+	var existCondition string
+	var existPars []interface{}
+	existCondition += " AND edb_info_id=? AND from_edb_info_id=? "
+	existPars = append(existPars, edbInfo.EdbInfoId, req.FromEdbInfoId)
+
+	count, err := GetEdbInfoCalculateCountByCondition(existCondition, existPars)
+	if err != nil {
+		err = errors.New("判断指标是否改变失败,Err:" + err.Error())
+		return
+	}
+
+	if count <= 0 || req.MoveType != oldEdbInfo.MoveType || req.MoveFrequency != oldEdbInfo.MoveFrequency || req.Formula != oldEdbInfo.CalculateFormula {
+		//如果是依赖指标变更,那么需要删除对应的关联指标,并添加新的关系
+		if count <= 0 {
+			//删除,计算指标关联的,基础指标的关联关系
+			sql := ` DELETE FROM edb_info_calculate_mapping WHERE edb_info_id = ? `
+			_, err = to.Raw(sql, edbInfo.EdbInfoId).Exec()
+			if err != nil {
+				err = errors.New("删除计算指标关联关系失败,Err:" + err.Error())
+				return
+			}
+			//关联关系
+			{
+				calculateMappingItem := &EdbInfoCalculateMapping{
+					EdbInfoCalculateMappingId: 0,
+					EdbInfoId:                 edbInfo.EdbInfoId,
+					Source:                    utils.DATA_SOURCE_CALCULATE_PHASE_SHIFT,
+					SourceName:                "期数移位",
+					EdbCode:                   edbInfo.EdbCode,
+					FromEdbInfoId:             fromEdbInfo.EdbInfoId,
+					FromEdbCode:               fromEdbInfo.EdbCode,
+					FromEdbName:               fromEdbInfo.EdbName,
+					FromSource:                fromEdbInfo.Source,
+					FromSourceName:            fromEdbInfo.SourceName,
+					FromTag:                   "",
+					Sort:                      1,
+					CreateTime:                time.Now(),
+					ModifyTime:                time.Now(),
+					FromSubSource:             fromEdbInfo.SubSource,
+				}
+				_, err = to.Insert(calculateMappingItem)
+				if err != nil {
+					return
+				}
+			}
+		}
+
+		//清空原有数据
+		sql := ` DELETE FROM edb_data_calculate_phase_shift WHERE edb_info_id = ? `
+		_, err = to.Raw(sql, edbInfo.EdbInfoId).Exec()
+		if err != nil {
+			return
+		}
+
+		//计算数据
+		formulaInt, _ := strconv.Atoi(req.Formula)
+		err = refreshAllCalculatePhaseShift(to, edbInfo.EdbInfoId, edbInfo.Source, edbInfo.SubSource, formulaInt, edbInfo.MoveType, fromEdbInfo, edbInfo.EdbCode, "", "", edbInfo.MoveFrequency)
+	}
+	return
+}
+
+// RefreshAllCalculatePhaseShift 刷新所有时间移位数据
+func RefreshAllCalculatePhaseShift(edbInfoId, source, subSource, formulaInt, moveType int, fromEdbInfo *EdbInfo, edbCode, startDate, endDate, moveFrequency string) (err error) {
+	o := orm.NewOrm()
+	to, err := o.Begin()
+	if err != nil {
+		return
+	}
+	defer func() {
+		if err != nil {
+			fmt.Println("RefreshAllCalculatePhaseShift,Err:" + err.Error())
+			_ = to.Rollback()
+		} else {
+			_ = to.Commit()
+		}
+	}()
+
+	//清空原有数据
+	sql := ` DELETE FROM edb_data_calculate_phase_shift WHERE edb_info_id = ? `
+	_, err = to.Raw(sql, edbInfoId).Exec()
+	if err != nil {
+		return
+	}
+
+	//计算数据
+	err = refreshAllCalculatePhaseShift(to, edbInfoId, source, subSource, formulaInt, moveType, fromEdbInfo, edbCode, startDate, endDate, moveFrequency)
+
+	return
+}
+
+// refreshAllCalculatePhaseShift 刷新所有时间移位数据
+func refreshAllCalculatePhaseShift(to orm.TxOrmer, edbInfoId, source, subSource, formulaInt, moveType int, fromEdbInfo *EdbInfo, edbCode, startDate, endDate, moveFrequency string) (err error) {
+	edbInfoIdStr := strconv.Itoa(edbInfoId)
+
+	//计算数据
+	dataList, err := GetEdbDataListAllByTo(to, fromEdbInfo.Source, fromEdbInfo.SubSource, FindEdbDataListAllCond{
+		EdbInfoId:         fromEdbInfo.EdbInfoId,
+		StartDataTime:     startDate,
+		StartDataTimeCond: ">=",
+	}, 0)
+	if err != nil {
+		return err
+	}
+	var dateArr []string
+	dataMap := make(map[string]*EdbInfoSearchData)
+	for _, v := range dataList {
+		dateArr = append(dateArr, v.DataTime)
+		dataMap[v.DataTime] = v
+	}
+	fmt.Println("source:", source)
+	//获取指标所有数据
+	existDataList := make([]*EdbData, 0)
+	dataTableName := GetEdbDataTableName(source, subSource)
+	fmt.Println("dataTableName:", dataTableName)
+	sql := `SELECT * FROM %s WHERE edb_info_id=? `
+	sql = fmt.Sprintf(sql, dataTableName)
+	_, err = to.Raw(sql, edbInfoId).QueryRows(&existDataList)
+	if err != nil {
+		return err
+	}
+
+	existDataMap := make(map[string]string)
+	removeDateMap := make(map[string]string)
+	for _, v := range existDataList {
+		existDataMap[v.DataTime] = v.Value
+		removeDateMap[v.DataTime] = ``
+	}
+	fmt.Println("existDataMap:", existDataMap)
+	addSql := ` INSERT INTO edb_data_calculate_phase_shift(edb_info_id,edb_code,data_time,value,create_time,modify_time,data_timestamp) values `
+	var isAdd bool
+
+	resultMap := make(map[string]float64)
+	dataLen := len(dataList)
+	var moveNum int
+	for i := 0; i < dataLen; i++ {
+		// step_1 如果 领先/滞后 之后时间key存在,将该key为目标key,填充
+		currentIndex := dataList[i]
+
+		// 领先
+		if moveType != 2 {
+			periods := dataLen - i + formulaInt - 1
+			if periods < dataLen {
+				newIndex := dataList[dataLen-periods-1]
+				resultMap[newIndex.DataTime] = currentIndex.Value
+			} else {
+				moveNum = formulaInt - i
+
+				// 新数据须根据频度补充key
+				currentDate, _ := time.ParseInLocation(utils.FormatDate, dataList[0].DataTime, time.Local)
+
+				shiftDay := CalculateIntervalDays(moveFrequency, moveNum, currentDate, resultMap, moveType)
+
+				var newDate time.Time
+				if moveFrequency == "年" {
+					newDate = currentDate.AddDate(moveNum, 0, 0)
+				} else {
+					newDate = currentDate.AddDate(0, 0, shiftDay)
+				}
+
+				format := newDate.Format(utils.FormatDate)
+				resultMap[format] = currentIndex.Value
+			}
+		} else {
+			// 滞后
+			periods := dataLen - i - formulaInt
+			if periods > 0 {
+				newIndex := dataList[dataLen-periods]
+				resultMap[newIndex.DataTime] = currentIndex.Value
+			} else {
+				moveNum = formulaInt + 1 - (dataLen - i)
+				// 新数据须根据频度补充key
+				currentDate, _ := time.ParseInLocation(utils.FormatDate, dataList[dataLen-1].DataTime, time.Local)
+
+				shiftDay := CalculateIntervalDays(moveFrequency, moveNum, currentDate, resultMap, moveType)
+
+				var newDate time.Time
+				if moveFrequency == "年" {
+					newDate = currentDate.AddDate(-moveNum, 0, 0)
+				} else {
+					newDate = currentDate.AddDate(0, 0, -shiftDay)
+				}
+
+				format := newDate.Format(utils.FormatDate)
+				resultMap[format] = currentIndex.Value
+			}
+		}
+	}
+
+	for key, value := range resultMap {
+		currentDate, _ := time.ParseInLocation(utils.FormatDate, key, time.Local)
+		timestamp := currentDate.UnixNano() / 1e6
+		timestampStr := fmt.Sprintf("%d", timestamp)
+		addSql += GetAddSql(edbInfoIdStr, edbCode, key, timestampStr, fmt.Sprintf("%f", value))
+		isAdd = true
+	}
+
+	if isAdd {
+		addSql = strings.TrimRight(addSql, ",")
+		_, err = to.Raw(addSql).Exec()
+		if err != nil {
+			return
+		}
+	}
+
+	return
+}
+
+func CalculateIntervalDays(moveFrequency string, formulaInt int, baseDate time.Time, resultMap map[string]float64, moveType int) int {
+	var shiftDay int
+	switch moveFrequency {
+	case "自然日":
+		shiftDay = formulaInt
+	case "交易日":
+		shiftDay = utils.CalculateTradingDays(baseDate, formulaInt, resultMap, moveType)
+	case "周":
+		shiftDay = formulaInt * 7
+	case "旬":
+		shiftDay = utils.CalculateDekadTime(baseDate, formulaInt, moveType)
+	case "月":
+		shiftDay = utils.CalculateEndOfMonth(baseDate, formulaInt, moveType)
+	case "季":
+		shiftDay = utils.CalculateEndOfQuarter(baseDate, formulaInt, moveType)
+	case "半年":
+		shiftDay = utils.CalculateEndOfHalfYear(baseDate, formulaInt, moveType)
+	case "年":
+		shiftDay = utils.CalculateEndOfYear(baseDate, formulaInt, moveType)
+	default:
+		shiftDay = formulaInt
+	}
+	return shiftDay
+}

+ 22 - 0
models/edb_data_calculate_rjz.go

@@ -236,8 +236,10 @@ func refreshAllCalculateRjz(to orm.TxOrmer, edbInfoId, source int, fromEdbInfo *
 		return err
 	}
 	existDataMap := make(map[string]string)
+	removeDateMap := make(map[string]bool) //需要移除的日期
 	for _, v := range existDataList {
 		existDataMap[v.DataTime] = v.Value
+		removeDateMap[v.DataTime] = true
 	}
 	fmt.Println("existDataMap:", existDataMap)
 	addSql := ` INSERT INTO edb_data_calculate_rjz(edb_info_id,edb_code,data_time,value,create_time,modify_time,data_timestamp) values `
@@ -251,6 +253,7 @@ func refreshAllCalculateRjz(to orm.TxOrmer, edbInfoId, source int, fromEdbInfo *
 			if err != nil {
 				return err
 			}
+			delete(removeDateMap, av)
 			//根据频度计算需要均分的天数
 			days := GetRjzFrequencyDays(currentDate, fromEdbInfo.Frequency)
 			val := rjzDiv(currentItem.Value, days)
@@ -277,6 +280,25 @@ func refreshAllCalculateRjz(to orm.TxOrmer, edbInfoId, source int, fromEdbInfo *
 			}
 		}
 	}
+
+	if len(removeDateMap) > 0 {
+		removeDateList := make([]string, 0) //需要移除的日期
+		for k := range removeDateMap {
+			removeDateList = append(removeDateList, k)
+		}
+		removeDateStr := strings.Join(removeDateList, `","`)
+		removeDateStr = `"` + removeDateStr + `"`
+		//如果拼接指标变更了,那么需要删除所有的指标数据
+		tableName := GetEdbDataTableName(source, utils.DATA_SUB_SOURCE_EDB)
+		sql := fmt.Sprintf(` DELETE FROM %s WHERE edb_info_id = ? and data_time in (%s) `, tableName, removeDateStr)
+
+		_, err = to.Raw(sql, edbInfoId).Exec()
+		if err != nil {
+			err = fmt.Errorf("删除计算失败的计算指标数据失败,Err:" + err.Error())
+			return
+		}
+	}
+
 	if isAdd {
 		addSql = strings.TrimRight(addSql, ",")
 		_, err = to.Raw(addSql).Exec()

+ 97 - 0
models/edb_data_calculate_stl.go

@@ -0,0 +1,97 @@
+package models
+
+import (
+	"time"
+
+	"github.com/beego/beego/v2/client/orm"
+)
+
+type EdbDataCalculateStl struct {
+	EdbDataId     int       `orm:"pk"`
+	EdbInfoId     int       `description:"指标id"`
+	EdbCode       string    `description:"指标编码"`
+	DataTime      time.Time `description:"数据时间"`
+	Value         float64   `description:"数据值"`
+	CreateTime    time.Time `description:"创建时间"`
+	ModifyTime    time.Time `description:"修改时间"`
+	DataTimestamp int64     `description:"数据时间戳"`
+}
+
+type CalculateStlConfigMapping struct {
+	Id                   int       `orm:"pk" description:"主键"`
+	CalculateStlConfigId int       `description:"stl配置id"`
+	EdbInfoId            int       `description:"edb信息id"`
+	StlEdbType           int       `description:"stl指标类型: 1-Trend, 2-Seasonal, 3-Residual"`
+	CreateTime           time.Time `description:"创建时间"`
+	ModifyTime           time.Time `description:"修改时间"`
+}
+
+type CalculateStlConfig struct {
+	CalculateStlConfigId int       `orm:"column(calculate_stl_config_id);pk"`
+	Config               string    `description:"STL计算配置"`
+	SysUserId            int       `description:"系统用户ID"`
+	CreateTime           time.Time `description:"创建时间"`
+	ModifyTime           time.Time `description:"更新时间"`
+}
+
+func GetRelationCalculateStlConfigMappingByEdbInfoId(edbInfoId int) (items []*CalculateStlConfigMapping, err error) {
+	o := orm.NewOrm()
+	sql := `SELECT calculate_stl_config_id FROM calculate_stl_config_mapping WHERE edb_info_id =? LIMIT 1`
+	var confId int
+	err = o.Raw(sql, edbInfoId).QueryRow(&confId)
+	if err != nil {
+		return
+	}
+	sql = `SELECT * FROM calculate_stl_config_mapping WHERE calculate_stl_config_id =?`
+	_, err = o.Raw(sql, confId).QueryRows(&items)
+	return
+}
+
+// GetCalculateStlConfigMappingByConfigId 根据配置文件id获取配置文件映射信息
+func GetCalculateStlConfigMappingByConfigId(configId int) (items []*CalculateStlConfigMapping, err error) {
+	o := orm.NewOrm()
+	sql := `SELECT * FROM calculate_stl_config_mapping WHERE calculate_stl_config_id=?`
+	_, err = o.Raw(sql, configId).QueryRows(&items)
+	return
+}
+
+// GetCalculateStlConfigMappingIdByEdbInfoId 获取配置文件id
+func GetCalculateStlConfigMappingIdByEdbInfoId(edbInfoId int) (configId int, err error) {
+	o := orm.NewOrm()
+	sql := `SELECT calculate_stl_config_id FROM calculate_stl_config_mapping WHERE edb_info_id=? LIMIT 1`
+	err = o.Raw(sql, edbInfoId).QueryRow(&configId)
+	return
+}
+
+func DeleteAndInsertEdbDataCalculateStl(edbCode string, dataList []*EdbDataCalculateStl) (err error) {
+	tx, err := orm.NewOrm().Begin()
+	if err != nil {
+		return
+	}
+	defer func() {
+		if err != nil {
+			tx.Rollback()
+		} else {
+			tx.Commit()
+		}
+	}()
+	sql := `DELETE FROM edb_data_calculate_stl WHERE edb_code =?`
+	_, err = tx.Raw(sql, edbCode).Exec()
+	if err != nil {
+		return
+	}
+	_, err = tx.InsertMulti(500, dataList)
+	return
+}
+func (c *CalculateStlConfig) Update(cols []string) (err error) {
+	o := orm.NewOrm()
+	_, err = o.Update(c, cols...)
+	return
+}
+
+func GetCalculateStlConfigById(id int) (item *CalculateStlConfig, err error) {
+	o := orm.NewOrm()
+	sql := "SELECT * FROM calculate_stl_config WHERE calculate_stl_config_id =?"
+	err = o.Raw(sql, id).QueryRow(&item)
+	return
+}

+ 21 - 0
models/edb_data_calculate_tcz.go

@@ -236,8 +236,10 @@ func refreshAllCalculateTcz(to orm.TxOrmer, edbInfoId, source, subSource int, fr
 		return err
 	}
 	existDataMap := make(map[string]string)
+	removeDateMap := make(map[string]bool) //需要移除的日期
 	for _, v := range existDataList {
 		existDataMap[v.DataTime] = v.Value
+		removeDateMap[v.DataTime] = true
 	}
 
 	addSql := ` INSERT INTO edb_data_calculate_tcz(edb_info_id,edb_code,data_time,value,create_time,modify_time,data_timestamp) values `
@@ -245,6 +247,7 @@ func refreshAllCalculateTcz(to orm.TxOrmer, edbInfoId, source, subSource int, fr
 	existAddDataMap := make(map[string]string)
 	for _, av := range dateArr {
 		currentItem := dataMap[av]
+		delete(removeDateMap, av)
 		if currentItem != nil {
 			//当前日期
 			currentDate, err := time.ParseInLocation(utils.FormatDate, av, time.Local)
@@ -428,6 +431,24 @@ func refreshAllCalculateTcz(to orm.TxOrmer, edbInfoId, source, subSource int, fr
 			}
 		}
 	}
+	if len(removeDateMap) > 0 {
+		removeDateList := make([]string, 0) //需要移除的日期
+		for k := range removeDateMap {
+			removeDateList = append(removeDateList, k)
+		}
+		removeDateStr := strings.Join(removeDateList, `","`)
+		removeDateStr = `"` + removeDateStr + `"`
+		//如果拼接指标变更了,那么需要删除所有的指标数据
+		tableName := GetEdbDataTableName(source, subSource)
+		sql := fmt.Sprintf(` DELETE FROM %s WHERE edb_info_id = ? and data_time in (%s) `, tableName, removeDateStr)
+
+		_, err = to.Raw(sql, edbInfoId).Exec()
+		if err != nil {
+			err = fmt.Errorf("删除计算失败的计算指标数据失败,Err:" + err.Error())
+			return
+		}
+	}
+
 	if isAdd {
 		addSql = strings.TrimRight(addSql, ",")
 		_, err = to.Raw(addSql).Exec()

+ 51 - 0
models/ebd_data_ly.go → models/edb_data_ly.go

@@ -2,6 +2,7 @@
 package models
 
 import (
+	"errors"
 	"eta/eta_index_lib/utils"
 	"fmt"
 	"github.com/beego/beego/v2/client/orm"
@@ -10,6 +11,56 @@ import (
 	"time"
 )
 
+type EdbDataLy struct {
+	edbDataId     int     `orm:"column(edb_data_id);pk"` // 数据ID
+	CreateTime    string  `orm:"column(create_time)"`    // 创建时间
+	ModifyTime    string  `orm:"column(modify_time)"`    // 修改时间
+	EdbInfoId     int     `orm:"column(edb_info_id)"`    // 指标id
+	EdbCode       string  `orm:"column(edb_code)"`       // 指标编码
+	DataTime      string  `orm:"column(data_time)"`      // 数据日期
+	Value         float64 `orm:"column(value)"`          // 数据值
+	DataTimestamp uint64  `orm:"column(data_timestamp)"` // 数据日期时间戳
+}
+
+func init() {
+	orm.RegisterModel(new(EdbDataLy))
+}
+
+func GetLyEdbDataByIndexCodeAndDataTime(indexCode string, dataTime string) (items []EdbDataLy, err error) {
+	o := orm.NewOrm()
+	sql := `SELECT * FROM edb_data_ly WHERE index_code=? AND data_time like ?`
+	_, err = o.Raw(sql, indexCode, dataTime+"%").QueryRows(&items)
+	if errors.Is(err, orm.ErrNoRows) {
+		return nil, nil
+	}
+	return
+}
+
+func GetLyEdbDataByIndexCodeAndExactDataTime(indexCode string, dataTime string) (items []EdbDataLy, err error) {
+	o := orm.NewOrm()
+	sql := `SELECT * FROM edb_data_ly WHERE edb_code=? AND data_time=?`
+	_, err = o.Raw(sql, indexCode, dataTime).QueryRows(&items)
+	if errors.Is(err, orm.ErrNoRows) {
+		return nil, nil
+	}
+	return
+}
+
+// UpdateLyEdbDataById 更新指标库数据 须根据指标编码和日期更新 仅适合月度数据
+func UpdateLyEdbDataById(id int, value float64) (err error) {
+	o := orm.NewOrm()
+	sql := `UPDATE edb_data_ly SET value=? WHERE edb_data_id=?`
+	_, err = o.Raw(sql, value, id).Exec()
+	return
+}
+
+// 新增指标库数据
+func AddLyEdbDataList(items []EdbDataLy) (err error) {
+	o := orm.NewOrm()
+	_, err = o.InsertMulti(len(items), items)
+	return
+}
+
 // AddEdbDataFromLy 新增指标数据
 func AddEdbDataFromLy(edbCode string) (err error) {
 	o := orm.NewOrm()

+ 6 - 0
models/edb_data_table.go

@@ -172,6 +172,12 @@ func GetEdbDataTableName(source, subSource int) (tableName string) {
 		tableName = "edb_data_trade_analysis"
 	case utils.DATA_SOURCE_LY: // 粮油商务网->91
 		tableName = "edb_data_ly"
+	case utils.DATA_SOURCE_CALCULATE_STL: // STL趋势分解->98
+		tableName = "edb_data_calculate_stl"
+	case utils.DATA_SOURCE_PREDICT_CALCULATE_PHASE_SHIFT: // 预测指标 - 期数位移
+		tableName = "edb_data_predict_calculate_phase_shift"
+	case utils.DATA_SOURCE_CALCULATE_PHASE_SHIFT: // 指标 - 期数位移
+		tableName = "edb_data_calculate_phase_shift"
 	default:
 		edbSource := EdbSourceIdMap[source]
 		if edbSource != nil {

+ 30 - 14
models/edb_info.go

@@ -64,6 +64,7 @@ type EdbInfo struct {
 	IndicatorCode    string  `description:"指标代码"`
 	StockCode        string  `description:"证券代码"`
 	Extra            string  `description:"指标的额外配置"`
+	IsStaticData     int     `description:"是否是静态指标,0否,1是"`
 }
 
 func (e *EdbInfo) Add() (err error) {
@@ -539,6 +540,11 @@ func GetEdbInfoByEdbCode(source int, edbCode string) (item *EdbInfo, err error)
 	o := orm.NewOrm()
 	sql := ` SELECT * FROM edb_info WHERE source=? AND edb_code=? `
 	err = o.Raw(sql, source, edbCode).QueryRow(&item)
+
+	if errors.Is(err, orm.ErrNoRows) {
+		err = nil
+	}
+
 	return
 }
 
@@ -1188,27 +1194,27 @@ func GetPredictDataListByPredictEdbConfList(edbInfo, sourceEdbInfoItem *EdbInfo,
 
 // GetPredictEdbDataListAll 获取该预测指标所有的数据 ,order:1升序,其余值为降序
 func GetPredictEdbDataListAll(edbInfo *EdbInfo, order int) (items []*EdbInfoSearchData, err error) {
-	if edbInfo.Source == utils.DATA_SOURCE_PREDICT { //普通的预测指标是没有入库数据的,直接往配置里面获取
+	/*if edbInfo.Source == utils.DATA_SOURCE_PREDICT { //普通的预测指标是没有入库数据的,直接往配置里面获取
 		items, _, err, _ = GetPredictDataListByPredictEdbInfo(edbInfo, 1, "")
-	} else {
-		items, err = GetEdbDataListAll(edbInfo.Source, edbInfo.SubSource, FindEdbDataListAllCond{
-			EdbInfoId: edbInfo.EdbInfoId,
-		}, order)
-	}
+	} else {*/
+	items, err = GetEdbDataListAll(edbInfo.Source, edbInfo.SubSource, FindEdbDataListAllCond{
+		EdbInfoId: edbInfo.EdbInfoId,
+	}, order)
+	//}
 	return
 }
 
 // GetPredictEdbDataListAllByStartDate 根据开始日期获取该预测指标所有的数据 ,order:1升序,其余值为降序
 func GetPredictEdbDataListAllByStartDate(edbInfo *EdbInfo, order int, startDate string) (items []*EdbInfoSearchData, err error) {
-	if edbInfo.Source == utils.DATA_SOURCE_PREDICT { //普通的预测指标是没有入库数据的,直接往配置里面获取
+	/*if edbInfo.Source == utils.DATA_SOURCE_PREDICT { //普通的预测指标是没有入库数据的,直接往配置里面获取
 		items, _, err, _ = GetPredictDataListByPredictEdbInfo(edbInfo, order, startDate)
-	} else {
-		items, err = GetEdbDataListAll(edbInfo.Source, edbInfo.SubSource, FindEdbDataListAllCond{
-			EdbInfoId:         edbInfo.EdbInfoId,
-			StartDataTime:     startDate,
-			StartDataTimeCond: ">=",
-		}, order)
-	}
+	} else {*/
+	items, err = GetEdbDataListAll(edbInfo.Source, edbInfo.SubSource, FindEdbDataListAllCond{
+		EdbInfoId:         edbInfo.EdbInfoId,
+		StartDataTime:     startDate,
+		StartDataTimeCond: ">=",
+	}, order)
+	//}
 	return
 }
 
@@ -1606,3 +1612,13 @@ func (m SortEdbDataList) Less(i, j int) bool {
 func (m SortEdbDataList) Swap(i, j int) {
 	m[i], m[j] = m[j], m[i]
 }
+
+func ModifyEdbInfoDataStatus(edbInfoId int64, source, subSource int, edbCode string) (err error) {
+	o := orm.NewOrm()
+	sql := ``
+	tableName := GetEdbDataTableName(source, subSource)
+	sql = ` UPDATE %s SET edb_info_id=?,modify_time=NOW() WHERE edb_code=? `
+	sql = fmt.Sprintf(sql, tableName)
+	_, err = o.Raw(sql, edbInfoId, edbCode).Exec()
+	return
+}

+ 5 - 0
models/future_good/future_good_edb_data.go

@@ -134,6 +134,11 @@ type RefreshFutureEdbEdbInfoReq struct {
 	StartDate           string `description:"开始日期"`
 }
 
+// RefreshFutureChartInfoReq 刷新商品指标请求
+type RefreshFutureChartInfoReq struct {
+	ChartInfoId int `description:"图表ID"`
+}
+
 // RefreshFutureGoodEdbDataFromThs 刷新wind期货指标数据
 func RefreshFutureGoodEdbDataFromThs(futureGoodEdbInfoId int, edbCode, startDate string, item FutureGoodDataFromThs) (err error) {
 	o := orm.NewOrm()

+ 0 - 1
models/predict_edb_data_base.go

@@ -319,7 +319,6 @@ func (obj PredictStandardBase) Refresh(params RefreshParams) (latestDateStr stri
 		err = errors.New(errMsg)
 		return
 	}
-
 	// 查找该预测指标配置
 	predictEdbConfList, err := GetPredictEdbConfAndDataListById(edbInfo.EdbInfoId)
 	if err != nil && err.Error() != utils.ErrNoRow() {

+ 21 - 1
models/predict_edb_data_calculate_hbz.go

@@ -211,8 +211,10 @@ func refreshAllPredictCalculateHbz(to orm.TxOrmer, edbInfoId, source, subSource
 		return
 	}
 	existDataMap := make(map[string]string)
+	removeDateMap := make(map[string]bool) //需要移除的日期
 	for _, v := range existDataList {
 		existDataMap[v.DataTime] = v.Value
+		removeDateMap[v.DataTime] = true
 	}
 	addSql := ` INSERT INTO edb_data_predict_calculate_hbz(edb_info_id,edb_code,data_time,value,create_time,modify_time,data_timestamp) values `
 	var isAdd bool
@@ -227,13 +229,14 @@ func refreshAllPredictCalculateHbz(to orm.TxOrmer, edbInfoId, source, subSource
 			preItem := dataList[j]
 			if currentItem != nil && preItem != nil {
 				existKey := edbCode + currentItem.DataTime
-
 				if _, ok := existMap[existKey]; !ok {
 					currentDate, _ := time.ParseInLocation(utils.FormatDate, currentItem.DataTime, time.Local)
 					timestamp := currentDate.UnixNano() / 1e6
 					timestampStr := fmt.Sprintf("%d", timestamp)
 					val := HbzDiv(currentItem.Value, preItem.Value)
 					if val != "" {
+						// 有计算出来值,那么就从待删除指标中移除
+						delete(removeDateMap, currentItem.DataTime)
 						if fromEdbInfo.LatestDate == currentItem.DataTime {
 							latestValueDecimal, tmpErr := decimal.NewFromString(val)
 							if tmpErr != nil {
@@ -267,6 +270,23 @@ func refreshAllPredictCalculateHbz(to orm.TxOrmer, edbInfoId, source, subSource
 			}
 		}
 	}
+	if len(removeDateMap) > 0 {
+		removeDateList := make([]string, 0) //需要移除的日期
+		for k := range removeDateMap {
+			removeDateList = append(removeDateList, k)
+		}
+		removeDateStr := strings.Join(removeDateList, `","`)
+		removeDateStr = `"` + removeDateStr + `"`
+		//如果拼接指标变更了,那么需要删除所有的指标数据
+		tableName := GetEdbDataTableName(source, subSource)
+		sql := fmt.Sprintf(` DELETE FROM %s WHERE edb_info_id = ? and data_time in (%s) `, tableName, removeDateStr)
+
+		_, err = to.Raw(sql, edbInfoId).Exec()
+		if err != nil {
+			err = fmt.Errorf("删除计算失败的计算指标数据失败,Err:" + err.Error())
+			return
+		}
+	}
 	if isAdd {
 		addSql = strings.TrimRight(addSql, ",")
 		_, err = to.Raw(addSql).Exec()

+ 23 - 1
models/predict_edb_data_calculate_ljzzj.go

@@ -287,8 +287,10 @@ func (obj PredictLjzzj) refresh(to orm.TxOrmer, edbInfoId, source, subSource int
 		return
 	}
 	dataMap := make(map[string]string)
+	removeDateMap := make(map[string]bool) //需要移除的日期
 	for _, v := range existDataList {
 		dataMap[v.DataTime] = v.Value
+		removeDateMap[v.DataTime] = true
 	}
 	existDataMap := make(map[string]string)
 	for yk, yv := range yearMap {
@@ -331,7 +333,8 @@ func (obj PredictLjzzj) refresh(to orm.TxOrmer, edbInfoId, source, subSource int
 								a := decimal.NewFromFloat(dataTwoItem.Value)
 								b := decimal.NewFromFloat(2.0)
 								val, _ = a.Div(b).Float64()
-
+								// 有计算出来值,那么就从待删除指标中移除
+								delete(removeDateMap, date)
 								tmpSql, newAdd, tmpErr := obj.calculate(edbInfoId, date, edbInfoIdStr, edbCode, dataTableName, addSql, val, dataMap, existDataMap, to)
 								if !isAdd {
 									isAdd = newAdd
@@ -362,6 +365,8 @@ func (obj PredictLjzzj) refresh(to orm.TxOrmer, edbInfoId, source, subSource int
 				}
 			}
 			if date != "" {
+				// 有计算出来值,那么就从待删除指标中移除
+				delete(removeDateMap, date)
 				tmpSql, newAdd, tmpErr := obj.calculate(edbInfoId, date, edbInfoIdStr, edbCode, dataTableName, addSql, val, dataMap, existDataMap, to)
 				if !isAdd {
 					isAdd = newAdd
@@ -374,6 +379,23 @@ func (obj PredictLjzzj) refresh(to orm.TxOrmer, edbInfoId, source, subSource int
 			}
 		}
 	}
+	if len(removeDateMap) > 0 {
+		removeDateList := make([]string, 0) //需要移除的日期
+		for k := range removeDateMap {
+			removeDateList = append(removeDateList, k)
+		}
+		removeDateStr := strings.Join(removeDateList, `","`)
+		removeDateStr = `"` + removeDateStr + `"`
+		//如果拼接指标变更了,那么需要删除所有的指标数据
+		tableName := GetEdbDataTableName(source, subSource)
+		sql := fmt.Sprintf(` DELETE FROM %s WHERE edb_info_id = ? and data_time in (%s) `, tableName, removeDateStr)
+
+		_, err = to.Raw(sql, edbInfoId).Exec()
+		if err != nil {
+			err = fmt.Errorf("删除计算失败的计算指标数据失败,Err:" + err.Error())
+			return
+		}
+	}
 	if isAdd {
 		addSql = strings.TrimRight(addSql, ",")
 		_, err = to.Raw(addSql).Exec()

+ 21 - 0
models/predict_edb_data_calculate_ljzzy.go

@@ -225,8 +225,10 @@ func refreshAllPredictCalculateLjzzy(to orm.TxOrmer, edbInfoId, source, subSourc
 		return
 	}
 	dataMap := make(map[string]string)
+	removeDateMap := make(map[string]bool) //需要移除的日期
 	for _, v := range existDataList {
 		dataMap[v.DataTime] = v.Value
+		removeDateMap[v.DataTime] = true
 	}
 	existDataMap := make(map[string]string)
 
@@ -270,6 +272,7 @@ func refreshAllPredictCalculateLjzzy(to orm.TxOrmer, edbInfoId, source, subSourc
 								a := decimal.NewFromFloat(dataTwoItem.Value)
 								b := decimal.NewFromFloat(2.0)
 								val, _ = a.Div(b).Float64()
+								delete(removeDateMap, date)
 								tmpSql, newAdd, tmpErr := calculateLjzzy(edbInfoId, date, edbInfoIdStr, edbCode, dataTableName, addSql, val, dataMap, existDataMap, to)
 								if !isAdd {
 									isAdd = newAdd
@@ -321,6 +324,7 @@ func refreshAllPredictCalculateLjzzy(to orm.TxOrmer, edbInfoId, source, subSourc
 				//		}
 				//	}
 				//}
+				delete(removeDateMap, date)
 				tmpSql, newAdd, tmpErr := calculateLjzzy(edbInfoId, date, edbInfoIdStr, edbCode, dataTableName, addSql, val, dataMap, existDataMap, to)
 				if !isAdd {
 					isAdd = newAdd
@@ -333,6 +337,23 @@ func refreshAllPredictCalculateLjzzy(to orm.TxOrmer, edbInfoId, source, subSourc
 			}
 		}
 	}
+	if len(removeDateMap) > 0 {
+		removeDateList := make([]string, 0) //需要移除的日期
+		for k := range removeDateMap {
+			removeDateList = append(removeDateList, k)
+		}
+		removeDateStr := strings.Join(removeDateList, `","`)
+		removeDateStr = `"` + removeDateStr + `"`
+		//如果拼接指标变更了,那么需要删除所有的指标数据
+		tableName := GetEdbDataTableName(source, subSource)
+		sql := fmt.Sprintf(` DELETE FROM %s WHERE edb_info_id = ? and data_time in (%s) `, tableName, removeDateStr)
+
+		_, err = to.Raw(sql, edbInfoId).Exec()
+		if err != nil {
+			err = fmt.Errorf("删除计算失败的计算指标数据失败,Err:" + err.Error())
+			return
+		}
+	}
 	if isAdd {
 		addSql = strings.TrimRight(addSql, ",")
 		_, err = to.Raw(addSql).Exec()

+ 325 - 0
models/predict_edb_data_calculate_phase_shift.go

@@ -0,0 +1,325 @@
+package models
+
+import (
+	"errors"
+	"eta/eta_index_lib/utils"
+	"fmt"
+	"github.com/beego/beego/v2/client/orm"
+	"strconv"
+	"strings"
+	"time"
+)
+
+// SavePredictCalculatePhaseShift 期数移位
+func SavePredictCalculatePhaseShift(req *EdbInfoCalculateBatchSaveReq, fromEdbInfo *EdbInfo, edbCode, uniqueCode string, sysUserId int, sysUserRealName, lang string) (edbInfo *EdbInfo, latestDateStr string, latestValue float64, err error) {
+	o := orm.NewOrm()
+	to, err := o.Begin()
+	if err != nil {
+		return
+	}
+
+	defer func() {
+		if err != nil {
+			fmt.Println("SavePredictCalculateTimeShift,Err:" + err.Error())
+			_ = to.Rollback()
+		} else {
+			_ = to.Commit()
+		}
+	}()
+
+	// 前端那边让后端处理。。。
+	if req.Frequency == "日度" && req.MoveFrequency == "" {
+		return nil, "", 0, errors.New("日度指标,移动频率不能为空")
+	}
+	switch req.Frequency {
+	case "周度":
+		req.MoveFrequency = "周"
+	case "旬度":
+		req.MoveFrequency = "旬"
+	case "月度":
+		req.MoveFrequency = "月"
+	case "季度":
+		req.MoveFrequency = "季"
+	case "半年度":
+		req.MoveFrequency = "半年"
+	case "年度":
+		req.MoveFrequency = "年"
+	}
+
+	if req.EdbInfoId <= 0 {
+		edbInfo = new(EdbInfo)
+		edbInfo.EdbInfoType = 1
+		edbInfo.Source = utils.DATA_SOURCE_PREDICT_CALCULATE_PHASE_SHIFT
+		edbInfo.SourceName = "预测期数位移"
+		edbInfo.EdbCode = edbCode
+		edbInfo.EdbName = req.EdbName
+		edbInfo.EdbNameSource = req.EdbName
+		edbInfo.Frequency = req.Frequency
+		edbInfo.Unit = req.Unit
+		edbInfo.ClassifyId = req.ClassifyId
+		edbInfo.SysUserId = sysUserId
+		edbInfo.SysUserRealName = sysUserRealName
+		edbInfo.CreateTime = time.Now()
+		edbInfo.ModifyTime = time.Now()
+		edbInfo.UniqueCode = uniqueCode
+		edbInfo.CalculateFormula = req.Formula
+		edbInfo.EdbType = 2
+		edbInfo.MoveType = req.MoveType
+		edbInfo.MoveFrequency = req.MoveFrequency
+		edbInfo.EdbNameEn = req.EdbName
+		edbInfo.UnitEn = req.Unit
+		edbInfo.Sort = GetAddEdbMaxSortByClassifyId(req.ClassifyId, utils.PREDICT_EDB_INFO_TYPE)
+		newEdbInfoId, tmpErr := to.Insert(edbInfo)
+		if tmpErr != nil {
+			err = tmpErr
+			return
+		}
+		edbInfo.EdbInfoId = int(newEdbInfoId)
+		//关联关系
+		{
+			calculateMappingItem := new(EdbInfoCalculateMapping)
+			calculateMappingItem.CreateTime = time.Now()
+			calculateMappingItem.ModifyTime = time.Now()
+			calculateMappingItem.Sort = 1
+			calculateMappingItem.EdbCode = edbCode
+			calculateMappingItem.EdbInfoId = edbInfo.EdbInfoId
+			calculateMappingItem.FromEdbInfoId = fromEdbInfo.EdbInfoId
+			calculateMappingItem.FromEdbCode = fromEdbInfo.EdbCode
+			calculateMappingItem.FromEdbName = fromEdbInfo.EdbName
+			calculateMappingItem.FromSource = fromEdbInfo.Source
+			calculateMappingItem.FromSourceName = fromEdbInfo.SourceName
+			calculateMappingItem.FromTag = ""
+			calculateMappingItem.Source = edbInfo.Source
+			calculateMappingItem.SourceName = edbInfo.SourceName
+			_, err = to.Insert(calculateMappingItem)
+			if err != nil {
+				return
+			}
+		}
+	} else {
+		edbInfo, err = GetEdbInfoById(req.EdbInfoId)
+		if err != nil {
+			return
+		}
+		latestDateStr = edbInfo.LatestDate
+		latestValue = edbInfo.LatestValue
+		oldEdbInfo := *edbInfo //旧的指标信息
+
+		//修改指标信息
+		switch lang {
+		case utils.EnLangVersion:
+			edbInfo.EdbNameEn = req.EdbName
+			edbInfo.UnitEn = req.Unit
+		default:
+			edbInfo.EdbName = req.EdbName
+			edbInfo.Unit = req.Unit
+			edbInfo.EdbNameSource = req.EdbName
+		}
+		edbInfo.Frequency = req.Frequency
+		edbInfo.ClassifyId = req.ClassifyId
+		edbInfo.MoveType = req.MoveType
+		edbInfo.MoveFrequency = req.MoveFrequency
+		edbInfo.CalculateFormula = req.Formula
+		edbInfo.ModifyTime = time.Now()
+		_, err = to.Update(edbInfo, "EdbName", "EdbNameSource", "Frequency", "Unit", "ClassifyId", "MoveType", "MoveFrequency", "CalculateFormula", "ModifyTime", "EdbNameEn", "UnitEn")
+		if err != nil {
+			return
+		}
+
+		//判断计算指标是否被更换
+		var existCondition string
+		var existPars []interface{}
+		existCondition += " AND edb_info_id=? AND from_edb_info_id=? "
+		existPars = append(existPars, edbInfo.EdbInfoId, req.FromEdbInfoId)
+
+		var count int
+		count, err = GetEdbInfoCalculateCountByCondition(existCondition, existPars)
+		if err != nil {
+			err = errors.New("判断指标是否改变失败,Err:" + err.Error())
+			return
+		}
+
+		if count <= 0 || req.MoveType != oldEdbInfo.MoveType || req.MoveFrequency != oldEdbInfo.MoveFrequency || req.Formula != oldEdbInfo.CalculateFormula {
+			//如果是依赖指标变更,那么需要删除对应的关联指标,并添加新的关系
+			if count <= 0 {
+				//删除,计算指标关联的,基础指标的关联关系
+				sql := ` DELETE FROM edb_info_calculate_mapping WHERE edb_info_id = ? `
+				_, err = to.Raw(sql, edbInfo.EdbInfoId).Exec()
+				if err != nil {
+					err = errors.New("删除计算指标关联关系失败,Err:" + err.Error())
+					return
+				}
+				//关联关系
+				{
+					calculateMappingItem := &EdbInfoCalculateMapping{
+						EdbInfoCalculateMappingId: 0,
+						EdbInfoId:                 edbInfo.EdbInfoId,
+						Source:                    utils.DATA_SOURCE_PREDICT_CALCULATE_PHASE_SHIFT,
+						SourceName:                "预测期数位移",
+						EdbCode:                   edbInfo.EdbCode,
+						FromEdbInfoId:             fromEdbInfo.EdbInfoId,
+						FromEdbCode:               fromEdbInfo.EdbCode,
+						FromEdbName:               fromEdbInfo.EdbName,
+						FromSource:                fromEdbInfo.Source,
+						FromSourceName:            fromEdbInfo.SourceName,
+						FromTag:                   "",
+						Sort:                      1,
+						CreateTime:                time.Now(),
+						ModifyTime:                time.Now(),
+					}
+					_, err = to.Insert(calculateMappingItem)
+					if err != nil {
+						return
+					}
+				}
+			}
+
+			//清空原有数据
+			sql := ` DELETE FROM edb_data_predict_calculate_phase_shift WHERE edb_info_id = ? `
+			_, err = to.Raw(sql, edbInfo.EdbInfoId).Exec()
+			if err != nil {
+				return
+			}
+		} else {
+			return
+		}
+	}
+
+	formulaInt, _ := strconv.Atoi(req.Formula)
+
+	//计算数据
+	latestDateStr, latestValue, err = refreshAllPredictCalculatePhaseShift(to, edbInfo.EdbInfoId, edbInfo.Source, edbInfo.SubSource, formulaInt, edbInfo.MoveType, fromEdbInfo, edbInfo.EdbCode, "", "", edbInfo.MoveFrequency)
+
+	return
+}
+
+// RefreshAllPredictCalculatePhaseShift 刷新所有时间移位数据
+func RefreshAllPredictCalculatePhaseShift(edbInfoId, source, subSource, formulaInt, moveType int, fromEdbInfo *EdbInfo, edbCode, startDate, endDate, moveFrequency string) (latestDateStr string, latestValue float64, err error) {
+	o := orm.NewOrm()
+	to, err := o.Begin()
+	if err != nil {
+		return
+	}
+	defer func() {
+		if err != nil {
+			fmt.Println("RefreshAllPredictCalculatePhaseShift,Err:" + err.Error())
+			_ = to.Rollback()
+		} else {
+			_ = to.Commit()
+		}
+	}()
+
+	//计算数据
+	latestDateStr, latestValue, err = refreshAllPredictCalculatePhaseShift(to, edbInfoId, source, subSource, formulaInt, moveType, fromEdbInfo, edbCode, startDate, endDate, moveFrequency)
+	return
+}
+
+// refreshAllPredictCalculatePhaseShift 刷新所有时间移位数据
+func refreshAllPredictCalculatePhaseShift(to orm.TxOrmer, edbInfoId, source, subSource, formulaInt, moveType int, fromEdbInfo *EdbInfo, edbCode, startDate, endDate, moveFrequency string) (latestDateStr string, latestValue float64, err error) {
+	edbInfoIdStr := strconv.Itoa(edbInfoId)
+	dataList, err := GetPredictEdbDataListAllByStartDate(fromEdbInfo, 0, startDate)
+	if err != nil {
+		return
+	}
+
+	var dateArr []string
+	dataMap := make(map[string]*EdbInfoSearchData)
+	for _, v := range dataList {
+		dateArr = append(dateArr, v.DataTime)
+		dataMap[v.DataTime] = v
+	}
+
+	addSql := ` INSERT INTO edb_data_predict_calculate_phase_shift(edb_info_id,edb_code,data_time,value,create_time,modify_time,data_timestamp) values `
+	var isAdd bool
+
+	resultMap := make(map[string]float64)
+	dataLen := len(dataList)
+	var moveNum int
+	for i := 0; i < dataLen; i++ {
+		// step_1 如果 领先/滞后 之后时间key存在,将该key为目标key,填充
+		currentIndex := dataList[i]
+
+		// 领先
+		if moveType != 2 {
+			periods := dataLen - i + formulaInt - 1
+			if periods < dataLen {
+				newIndex := dataList[dataLen-periods-1]
+				resultMap[newIndex.DataTime] = currentIndex.Value
+
+				if strings.Contains(currentIndex.DataTime, fromEdbInfo.LatestDate) {
+					latestDateStr = newIndex.DataTime
+				}
+			} else {
+				moveNum = formulaInt - i
+
+				// 新数据须根据频度补充key
+				currentDate, _ := time.ParseInLocation(utils.FormatDate, dataList[0].DataTime, time.Local)
+
+				shiftDay := CalculateIntervalDays(moveFrequency, moveNum, currentDate, resultMap, moveType)
+
+				var newDate time.Time
+				if moveFrequency == "年" {
+					newDate = currentDate.AddDate(moveNum, 0, 0)
+				} else {
+					newDate = currentDate.AddDate(0, 0, shiftDay)
+				}
+
+				format := newDate.Format(utils.FormatDate)
+
+				if strings.Contains(currentIndex.DataTime, fromEdbInfo.LatestDate) {
+					latestDateStr = format
+				}
+				resultMap[format] = currentIndex.Value
+			}
+		} else {
+			// 滞后
+			periods := dataLen - i - formulaInt
+			if periods > 0 {
+				newIndex := dataList[dataLen-periods]
+				resultMap[newIndex.DataTime] = currentIndex.Value
+
+				if strings.Contains(currentIndex.DataTime, fromEdbInfo.LatestDate) {
+					latestDateStr = newIndex.DataTime
+				}
+			} else {
+				moveNum = formulaInt + 1 - (dataLen - i)
+				// 新数据须根据频度补充key
+				currentDate, _ := time.ParseInLocation(utils.FormatDate, dataList[dataLen-1].DataTime, time.Local)
+
+				shiftDay := CalculateIntervalDays(moveFrequency, moveNum, currentDate, resultMap, moveType)
+
+				var newDate time.Time
+				if moveFrequency == "年" {
+					newDate = currentDate.AddDate(-moveNum, 0, 0)
+				} else {
+					newDate = currentDate.AddDate(0, 0, -shiftDay)
+				}
+
+				format := newDate.Format(utils.FormatDate)
+				resultMap[format] = currentIndex.Value
+
+				if strings.Contains(currentIndex.DataTime, fromEdbInfo.LatestDate) {
+					latestDateStr = format
+				}
+			}
+		}
+	}
+
+	for key, value := range resultMap {
+		currentDate, _ := time.ParseInLocation(utils.FormatDate, key, time.Local)
+		timestamp := currentDate.UnixNano() / 1e6
+		timestampStr := fmt.Sprintf("%d", timestamp)
+		addSql += GetAddSql(edbInfoIdStr, edbCode, key, timestampStr, fmt.Sprintf("%f", value))
+		isAdd = true
+	}
+
+	if isAdd {
+		addSql = strings.TrimRight(addSql, ",")
+		_, err = to.Raw(addSql).Exec()
+		if err != nil {
+			return
+		}
+	}
+
+	return
+}

+ 20 - 0
models/predict_edb_data_calculate_tcz.go

@@ -402,8 +402,10 @@ func RefreshAllPredictCalculateTcz(edbInfoId, source, subSource int, fromEdbInfo
 		return
 	}
 	existDataMap := make(map[string]string)
+	removeDateMap := make(map[string]bool) //需要移除的日期
 	for _, v := range existDataList {
 		existDataMap[v.DataTime] = v.Value
+		removeDateMap[v.DataTime] = true
 	}
 
 	addSql := ` INSERT INTO edb_data_predict_calculate_tcz(edb_info_id,edb_code,data_time,value,create_time,modify_time,data_timestamp) values `
@@ -412,6 +414,7 @@ func RefreshAllPredictCalculateTcz(edbInfoId, source, subSource int, fromEdbInfo
 	for _, av := range dateArr {
 		currentItem := dataMap[av]
 		if currentItem != nil {
+			delete(removeDateMap, av)
 			//当前日期
 			currentDate, tmpErr := time.ParseInLocation(utils.FormatDate, av, time.Local)
 			if tmpErr != nil {
@@ -674,6 +677,23 @@ func RefreshAllPredictCalculateTcz(edbInfoId, source, subSource int, fromEdbInfo
 			}
 		}
 	}
+	if len(removeDateMap) > 0 {
+		removeDateList := make([]string, 0) //需要移除的日期
+		for k := range removeDateMap {
+			removeDateList = append(removeDateList, k)
+		}
+		removeDateStr := strings.Join(removeDateList, `","`)
+		removeDateStr = `"` + removeDateStr + `"`
+		//如果拼接指标变更了,那么需要删除所有的指标数据
+		tableName := GetEdbDataTableName(source, subSource)
+		sql := fmt.Sprintf(` DELETE FROM %s WHERE edb_info_id = ? and data_time in (%s) `, tableName, removeDateStr)
+
+		_, err = to.Raw(sql, edbInfoId).Exec()
+		if err != nil {
+			err = fmt.Errorf("删除计算失败的计算指标数据失败,Err:" + err.Error())
+			return
+		}
+	}
 	if isAdd {
 		addSql = strings.TrimRight(addSql, ",")
 		_, err = o.Raw(addSql).Exec()

+ 112 - 0
models/predict_edb_data_static.go

@@ -0,0 +1,112 @@
+package models
+
+import (
+	"fmt"
+	"github.com/beego/beego/v2/client/orm"
+	"github.com/shopspring/decimal"
+	"strconv"
+	"strings"
+)
+
+// AddStaticPredictEdbInfoReq 添加预测指标静态指标请求
+type AddStaticPredictEdbInfoReq struct {
+	ClassifyId      int    `description:"分类id"`
+	SourceEdbInfoId int    `description:"来源指标id"`
+	EdbName         string `description:"指标名称"`
+	Frequency       string `description:"频率"`
+	Unit            string `description:"单位"`
+	AdminId         int    `description:"添加人id"`
+	AdminName       string `description:"添加人名称"`
+}
+
+// AddPredictStaticEdb 添加预测指标
+// edbInfo, calculateMappingList, predictEdbConfList,calculateRule9List,trendsMappingList
+func AddPredictStaticEdb(edbInfo, sourceEdbInfoItem *EdbInfo, calculateMappingList []*EdbInfoCalculateMapping, predictEdbConfList []*PredictEdbConf) (err error, errMsg string) {
+	o := orm.NewOrm()
+	tx, err := o.Begin()
+	if err != nil {
+		return
+	}
+	defer func() {
+		if err != nil {
+			tx.Rollback()
+		} else {
+			err = tx.Commit()
+		}
+	}()
+	// 新增预测指标
+	edbInfoId, err := tx.Insert(edbInfo)
+	if err != nil {
+		return
+	}
+	edbInfo.EdbInfoId = int(edbInfoId)
+
+	// 新增预测指标的关联关系
+	lenCalculateMapping := len(calculateMappingList)
+	if lenCalculateMapping > 0 {
+		for _, calculateMappingItem := range calculateMappingList {
+			calculateMappingItem.EdbInfoId = edbInfo.EdbInfoId
+			calculateMappingItem.EdbCode = edbInfo.EdbCode
+		}
+		_, err = tx.InsertMulti(lenCalculateMapping, calculateMappingList)
+		if err != nil {
+			return
+		}
+	}
+	if len(predictEdbConfList) > 0 {
+		// 新增预测指标配置
+		for _, v := range predictEdbConfList {
+			v.PredictEdbInfoId = edbInfo.EdbInfoId
+			_, tmpErr := tx.Insert(v)
+			if tmpErr != nil {
+				err = fmt.Errorf(`新增预测指标配置失败:%v`, tmpErr)
+				return
+			}
+		}
+	}
+	// 新增预测指标数据
+	dataTableName := GetEdbDataTableName(edbInfo.Source, edbInfo.SubSource)
+	edbInfoIdStr := strconv.Itoa(int(edbInfoId))
+	//获取指标数据(实际已生成)
+	fromDataList, err := GetEdbDataListAll(sourceEdbInfoItem.Source, sourceEdbInfoItem.SubSource, FindEdbDataListAllCond{
+		EdbInfoId:         sourceEdbInfoItem.EdbInfoId,
+		StartDataTime:     "",
+		StartDataTimeCond: ">=",
+	}, 1)
+	if err != nil {
+		err = fmt.Errorf(`获取原指标数据失败:%v`, err)
+		return
+	}
+
+	addSql := ` INSERT INTO ` + dataTableName + `(edb_info_id,edb_code,data_time,value,create_time,modify_time,data_timestamp) values `
+	count := 0
+	for _, tmpData := range fromDataList {
+		currDateStr := tmpData.DataTime
+		timeStr := fmt.Sprintf("%d", tmpData.DataTimestamp)
+		// 当前的实际值
+		saveValue := decimal.NewFromFloat(tmpData.Value).Round(4).String()
+		addSql += GetAddSql(edbInfoIdStr, edbInfo.EdbCode, currDateStr, timeStr, saveValue)
+		count += 1
+		if count >= 500 {
+			addSql = strings.TrimRight(addSql, ",")
+			_, err = tx.Raw(addSql).Exec()
+			if err != nil {
+				err = fmt.Errorf(`新增预测指标数据失败:%v`, err)
+				return
+			}
+			addSql = ` INSERT INTO ` + dataTableName + `(edb_info_id,edb_code,data_time,value,create_time,modify_time,data_timestamp) values `
+			count = 0
+		}
+	}
+
+	if count > 0 {
+		addSql = strings.TrimRight(addSql, ",")
+		_, err = tx.Raw(addSql).Exec()
+		if err != nil {
+			err = fmt.Errorf(`新增预测指标数据失败:%v`, err)
+			return
+		}
+	}
+
+	return
+}

+ 160 - 103
models/predict_edb_info_rule.go

@@ -1454,21 +1454,24 @@ func GetChartPredictEdbInfoDataListByRuleNAnnualAverage(edbInfoId int, configVal
 
 // AnnualValueInversionConf 年度值倒推规则
 type AnnualValueInversionConf struct {
-	Value float64 `description:"年度值"`
-	Type  int     `description:"分配方式,1:均值法;2:同比法"`
-	Year  int     `description:"同比年份"`
+	Value    float64 `description:"年度值"`
+	Type     int     `description:"分配方式,1:均值法;2:同比法"`
+	Year     int     `description:"同比年份"`
+	YearList []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+同比增速)
+// 预测指标-年度值倒推
+// 1、年度值倒推,选择同比法,支持选择多个年份(当前只可选择一个年份)。选择多个年份时,计算多个年份的余额平均,和同期平均。
+// 2、年度值倒推,同比法的算法优化:旬度,月度,季度,半年度的算法,同原先算法。
+// 日度、周度值算法更新(假设指标实际值最新日期月2024/3/1):
+// 1、设定年度值
+// 2、计算余额:年度值-年初至今累计值
+// 3、年初至今累计值计算方法:用后置填充变频成连续自然日日度数据。计算1/1至指标最新日期(2024/3/3/1)的累计值。
+// 4、计算同比年份全年累计值,年初至指标最新值同期(2023/3/1)累计值,两者相减得到同比年份同期余额,再取平均值,作为最终的同期余额
+// 5、用今年余额/去年同期余额得到同比增速。
+// 6、每一期预测值,为同比年份的同期值,乘以(1+同比)。去年同期,用变频后的序列对应。
+// 7、如果选择的同比年份是多个。则计算多个年份的平均余额。今年余额/平均余额=同比增速。同比基数为多个年份的同期平均值
 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("当前指标频度是年度,不允许配置年度值倒推")
@@ -1642,112 +1645,166 @@ func GetChartPredictEdbInfoDataListByRuleAnnualValueInversion(edbInfoId int, con
 	// 同比法分配
 	// 同比法保证每期同比相等(同比增速=余额/同比年份相应日期的余额,预测值等于同比年份同期值*同比增速);
 	// 同比法分配:同比增速=900/同比年份5.19的余额
-
+	yearList := annualValueInversionConf.YearList
+	if len(yearList) == 0 {
+		//兼容历史数据
+		yearList = append(yearList, annualValueInversionConf.Year)
+	}
 	// 每年截止到当前日期的累计值
 	dateTotalMap := make(map[time.Time]float64)
+
+	//把每一期的期数和日期绑定
+	dateIndexMap := make(map[time.Time]int)
+	indexDateMap := make(map[int]time.Time)
 	// 每年的累计值(计算使用)
 	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
+	//数据按找后值填充的方式处理成连续自然日日度数据
+	allDataListMap := make(map[string]float64)
+	// todo 如果是日度和周度,用后置填充变频成连续自然日日度数据。计算1/1至指标最新日期(2024/3/3/1)的累计值
+	switch frequency {
+	case "日度", "周度":
+		for _, v := range allDataList {
+			allDataListMap[v.DataTime] = v.Value
+		}
+		//找到最早日期的的年份的1月1日,转成time格式
+		earliestYear := allDataList[0].DataTime[:4]
+		earliestYearFirstDay, _ := time.ParseInLocation(utils.FormatDate, earliestYear+"-01-01", time.Local)
+		days := int(currDayTime.Sub(earliestYearFirstDay).Hours() / float64(24))
+		//循环累加日期,直到循环到最新日期
+		for i := 0; i <= days; i++ {
+			currentDate := earliestYearFirstDay.AddDate(0, 0, i)
+			currentDateStr := currentDate.Format(utils.FormatDate)
+			val, ok := allDataListMap[currentDateStr]
+			if !ok { //如果不存在,则填充后值
+				//循环向后查找数据,直到找到
+				for j := i + 1; j <= days; j++ {
+					//循环往后取值
+					currentDateTmp := earliestYearFirstDay.AddDate(0, 0, j)
+					currentDateTmpStr := currentDateTmp.Format(utils.FormatDate)
+					if tmpVal, ok1 := allDataListMap[currentDateTmpStr]; ok1 {
+						allDataListMap[currentDateStr] = tmpVal
+						val = tmpVal
+						break
+					}
+				}
+			}
+			//计算每一天的年初至今累计值
+			yearVal := yearTotalMap[currentDate.Year()]
+			if frequency == "周度" {
+				// 每日累计值需要当前值除7
+				yearVal = yearVal + val/7
+			} else {
+				yearVal = yearVal + val
+			}
+			yearTotalMap[currentDate.Year()] = yearVal
+			dateTotalMap[currentDate] = yearVal
+			dateIndexMap[currentDate] = i
+			indexDateMap[i] = currentDate
 		}
-		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
+	default:
+		for k, v := range allDataList {
+			currTime, tmpErr := time.ParseInLocation(utils.FormatDate, v.DataTime, time.Local)
+			if tmpErr != nil {
+				err = tmpErr
+				return
+			}
+			allDataListMap[v.DataTime] = v.Value
+			yearVal := yearTotalMap[currTime.Year()]
+			yearVal = yearVal + v.Value
+			yearTotalMap[currTime.Year()] = yearVal
+			dateTotalMap[currTime] = yearVal
+			dateIndexMap[currTime] = k
+			indexDateMap[k] = currTime
 		}
-
-		//同比年份相应日期
-		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)
+	}
+	// 当年的余额
+	currYearBalance := yearValueConfig - yearTotalMap[currDayTime.Year()]
+	//fmt.Printf("当年的余额%.4f=给定额度%.4f-当年累计值%.4f\n", currYearBalance, yearValueConfig, yearTotalMap[currDayTime.Year()])
+	// 循环统计同比年份同期余额
+	var sum, avg float64
+	for _, year := range yearList {
+		yearTotal := yearTotalMap[year]
+		//fmt.Printf("同比年份的累计值%.4f\n", yearTotal)
+		tmpDate := time.Date(year, currDayTime.Month(), currDayTime.Day(), 0, 0, 0, 0, currDayTime.Location())
+		//fmt.Printf("同比年份的同期%s\n", tmpDate)
+		dateTotal, ok := dateTotalMap[tmpDate]
+		//fmt.Printf("同比年份的同期累计值%.4f\n", dateTotal)
+		if ok {
+			sum = sum + (yearTotal - dateTotal)
+		} else {
+			// 查找下一期的余额
+			tmpIndex, ok1 := dateIndexMap[tmpDate]
+			if ok1 {
+				for tmpDateTime := indexDateMap[tmpIndex+1]; tmpDateTime.Year() == year; tmpDateTime = indexDateMap[tmpIndex+1] {
+					dateTotal, ok = dateTotalMap[tmpDateTime]
+					if ok {
+						//fmt.Printf("同比年份的同期累计值%.4f\n", dateTotal)
+						sum = sum + (yearTotal - dateTotal)
+						break
+					}
+					tmpIndex += 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
+	}
+	//fmt.Printf("同比年份的余额%.4f\n", sum)
+	avg = sum / float64(len(yearList))
+	//fmt.Printf("同比年份的余额%.4f\n", avg)
+	// 同比增速=当年余额/同比年份上一期日期的余额
+	tbVal := decimal.NewFromFloat(currYearBalance).Div(decimal.NewFromFloat(avg))
+	/*tbVal11, _ := tbVal.Round(4).Float64()
+	fmt.Printf("同比增速%.4f\n", tbVal11)*/
+	//(同比增速=余额/同比年份相应日期的余额的平均值,预测值等于同比年份同期值*同比增速);
+	for k, currentDate := range dayList {
+		// 循环遍历多个同比年份
+		var valSum float64
+		for _, year := range yearList {
+			//多个同比年份的同期值的平均值
+			tmpCurrentDate := time.Date(year, currentDate.Month(), currentDate.Day(), 0, 0, 0, 0, currentDate.Location())
+			if tmpVal, ok := allDataListMap[tmpCurrentDate.Format(utils.FormatDate)]; ok {
+				valSum += tmpVal
+			} else {
+				// 查找下一期的余额
+				tmpIndex, ok1 := dateIndexMap[tmpCurrentDate]
+				if ok1 {
+					for tmpDateTime := indexDateMap[tmpIndex+1]; tmpDateTime.Year() == year; tmpDateTime = indexDateMap[tmpIndex+1] {
+						tmpVal, ok = allDataListMap[tmpDateTime.Format(utils.FormatDate)]
+						if ok {
+							valSum += tmpVal
+							break
+						}
+						tmpIndex += 1
+					}
 				}
 			}
 		}
+		lastDateVal := valSum / float64(len(yearList))
 
-		//同比年份相应的上一期日期的余额
-		lastYearDateBalance := yearTotalMap[lastPrevDateTime.Year()] - dateTotal
-		if lastYearDateBalance == 0 {
-			continue
+		//预测值 = 同比年份同期值*同比增速
+		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
 
-		// 同比增速=当年余额/同比年份上一期日期的余额
-		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
+		yearVal := yearTotalMap[currentDate.Year()]
+		yearVal = yearVal + tmpVal
+		yearTotalMap[currentDate.Year()] = yearVal
+		dateTotalMap[currentDate] = yearVal
 
-			// 最大最小值
-			if tmpVal < minValue {
-				minValue = tmpVal
-			}
-			if tmpVal > maxValue {
-				maxValue = tmpVal
-			}
+		// 最大最小值
+		if tmpVal < minValue {
+			minValue = tmpVal
+		}
+		if tmpVal > maxValue {
+			maxValue = tmpVal
 		}
 	}
 

+ 87 - 8
models/trade_analysis/trade_analysis.go

@@ -182,6 +182,46 @@ func GetTradeDataByClassifyAndCompany(exchange, classifyName string, contracts,
 	if len(contracts) == 0 || len(companies) == 0 {
 		return
 	}
+	condBuy := fmt.Sprintf(`classify_name = ? AND classify_type IN (%s)`, utils.GetOrmInReplace(len(contracts)))
+	parsBuy := make([]interface{}, 0)
+	parsBuy = append(parsBuy, classifyName, contracts)
+
+	condSold := fmt.Sprintf(`classify_name = ? AND classify_type IN (%s)`, utils.GetOrmInReplace(len(contracts)))
+	parsSold := make([]interface{}, 0)
+	parsSold = append(parsSold, classifyName, contracts)
+
+	// 是否含有TOP20
+	var hasTop bool
+	var condCompanies []string
+	for _, v := range companies {
+		if v == TradeFuturesCompanyTop20 {
+			hasTop = true
+			continue
+		}
+		condCompanies = append(condCompanies, v)
+	}
+	if !hasTop {
+		if len(condCompanies) == 0 {
+			err = fmt.Errorf("查询条件-期货公司异常")
+			return
+		}
+		condBuy += fmt.Sprintf(` AND buy_short_name IN (%s)`, utils.GetOrmInReplace(len(condCompanies)))
+		parsBuy = append(parsBuy, condCompanies)
+		condSold += fmt.Sprintf(` AND sold_short_name IN (%s)`, utils.GetOrmInReplace(len(condCompanies)))
+		parsSold = append(parsSold, condCompanies)
+	} else {
+		// 这里rank=0或者999是因为大商所的数据并不只有999
+		if len(condCompanies) > 0 {
+			condBuy += fmt.Sprintf(` AND (rank = 999 OR rank = 0 OR buy_short_name IN (%s))`, utils.GetOrmInReplace(len(condCompanies)))
+			condSold += fmt.Sprintf(` AND (rank = 999 OR rank = 0 OR sold_short_name IN (%s))`, utils.GetOrmInReplace(len(condCompanies)))
+			parsBuy = append(parsBuy, condCompanies)
+			parsSold = append(parsSold, condCompanies)
+		} else {
+			condBuy += ` AND (rank = 999 OR rank = 0)`
+			condSold += ` AND (rank = 999 OR rank = 0)`
+		}
+	}
+
 	tableName := fmt.Sprintf("base_from_trade_%s_index", exchange)
 	sql := `SELECT
 			rank,
@@ -195,7 +235,7 @@ func GetTradeDataByClassifyAndCompany(exchange, classifyName string, contracts,
 		FROM
 			%s 
 		WHERE
-			classify_name = ? AND classify_type IN (%s) AND buy_short_name IN (%s)
+			%s
 		UNION ALL
 		(
 		SELECT
@@ -210,11 +250,11 @@ func GetTradeDataByClassifyAndCompany(exchange, classifyName string, contracts,
 		FROM
 			%s 
 		WHERE
-			classify_name = ? AND classify_type IN (%s) AND sold_short_name IN (%s)
+			%s
 		)`
-	sql = fmt.Sprintf(sql, tableName, utils.GetOrmInReplace(len(contracts)), utils.GetOrmInReplace(len(companies)), tableName, utils.GetOrmInReplace(len(contracts)), utils.GetOrmInReplace(len(companies)))
+	sql = fmt.Sprintf(sql, tableName, condBuy, tableName, condSold)
 	o := orm.NewOrm()
-	_, err = o.Raw(sql, classifyName, contracts, companies, classifyName, contracts, companies).QueryRows(&items)
+	_, err = o.Raw(sql, parsBuy, parsSold).QueryRows(&items)
 	return
 }
 
@@ -227,6 +267,45 @@ func GetTradeZhengzhouDataByClassifyAndCompany(exchange string, contracts, compa
 	if len(contracts) == 0 || len(companies) == 0 {
 		return
 	}
+	condBuy := fmt.Sprintf(`classify_name IN (%s)`, utils.GetOrmInReplace(len(contracts)))
+	parsBuy := make([]interface{}, 0)
+	parsBuy = append(parsBuy, contracts)
+
+	condSold := fmt.Sprintf(`classify_name IN (%s)`, utils.GetOrmInReplace(len(contracts)))
+	parsSold := make([]interface{}, 0)
+	parsSold = append(parsSold, contracts)
+
+	// 是否含有TOP20
+	var hasTop bool
+	var condCompanies []string
+	for _, v := range companies {
+		if v == TradeFuturesCompanyTop20 {
+			hasTop = true
+			continue
+		}
+		condCompanies = append(condCompanies, v)
+	}
+	if !hasTop {
+		if len(condCompanies) == 0 {
+			err = fmt.Errorf("查询条件-期货公司异常")
+			return
+		}
+		condBuy += fmt.Sprintf(` AND buy_short_name IN (%s)`, utils.GetOrmInReplace(len(condCompanies)))
+		parsBuy = append(parsBuy, condCompanies)
+		condSold += fmt.Sprintf(` AND sold_short_name IN (%s)`, utils.GetOrmInReplace(len(condCompanies)))
+		parsSold = append(parsSold, condCompanies)
+	} else {
+		if len(condCompanies) > 0 {
+			condBuy += fmt.Sprintf(` AND (rank = 999 OR buy_short_name IN (%s))`, utils.GetOrmInReplace(len(condCompanies)))
+			condSold += fmt.Sprintf(` AND (rank = 999 OR sold_short_name IN (%s))`, utils.GetOrmInReplace(len(condCompanies)))
+			parsBuy = append(parsBuy, condCompanies)
+			parsSold = append(parsSold, condCompanies)
+		} else {
+			condBuy += ` AND rank = 999`
+			condSold += ` AND rank = 999`
+		}
+	}
+
 	tableName := fmt.Sprintf("base_from_trade_%s_index", exchange)
 	sql := `SELECT
 			rank,
@@ -239,7 +318,7 @@ func GetTradeZhengzhouDataByClassifyAndCompany(exchange string, contracts, compa
 		FROM
 			%s 
 		WHERE
-			classify_name IN (%s) AND buy_short_name IN (%s)
+			%s
 		UNION ALL
 		(
 		SELECT
@@ -253,11 +332,11 @@ func GetTradeZhengzhouDataByClassifyAndCompany(exchange string, contracts, compa
 		FROM
 			%s 
 		WHERE
-			classify_name IN (%s) AND sold_short_name IN (%s)
+			%s
 		)`
-	sql = fmt.Sprintf(sql, tableName, utils.GetOrmInReplace(len(contracts)), utils.GetOrmInReplace(len(companies)), tableName, utils.GetOrmInReplace(len(contracts)), utils.GetOrmInReplace(len(companies)))
+	sql = fmt.Sprintf(sql, tableName, condBuy, tableName, condSold)
 	o := orm.NewOrm()
-	_, err = o.Raw(sql, contracts, companies, contracts, companies).QueryRows(&items)
+	_, err = o.Raw(sql, parsBuy, parsSold).QueryRows(&items)
 	return
 }
 

+ 87 - 6
routers/commentsRouter.go

@@ -106,6 +106,15 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_index_lib/controllers:BaseFromLyController"] = append(beego.GlobalControllerRouter["eta/eta_index_lib/controllers:BaseFromLyController"],
+        beego.ControllerComments{
+            Method: "AddBatchLyEdbData",
+            Router: `/add/batch/ly/edb/data`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_index_lib/controllers:BaseFromLyController"] = append(beego.GlobalControllerRouter["eta/eta_index_lib/controllers:BaseFromLyController"],
         beego.ControllerComments{
             Method: "AddLyDataList",
@@ -133,11 +142,20 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_index_lib/controllers:BaseFromLyController"] = append(beego.GlobalControllerRouter["eta/eta_index_lib/controllers:BaseFromLyController"],
+        beego.ControllerComments{
+            Method: "GetEdbInfoByIndexCode",
+            Router: `/get/edb/info/by/index/code`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_index_lib/controllers:BaseFromLyController"] = append(beego.GlobalControllerRouter["eta/eta_index_lib/controllers:BaseFromLyController"],
         beego.ControllerComments{
             Method: "GetLyClassifyByName",
             Router: `/get/ly/classify/by/name`,
-            AllowHTTPMethods: []string{"get"},
+            AllowHTTPMethods: []string{"post"},
             MethodParams: param.Make(),
             Filters: nil,
             Params: nil})
@@ -146,7 +164,7 @@ func init() {
         beego.ControllerComments{
             Method: "GetLyDataByIndexIdAndDataTime",
             Router: `/get/ly/data/by/index/id/and/data/time`,
-            AllowHTTPMethods: []string{"get"},
+            AllowHTTPMethods: []string{"post"},
             MethodParams: param.Make(),
             Filters: nil,
             Params: nil})
@@ -155,7 +173,34 @@ func init() {
         beego.ControllerComments{
             Method: "GetLyDataByIndexIdAndDataTimeYM",
             Router: `/get/ly/data/by/index/id/and/data/time/ym`,
-            AllowHTTPMethods: []string{"get"},
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_index_lib/controllers:BaseFromLyController"] = append(beego.GlobalControllerRouter["eta/eta_index_lib/controllers:BaseFromLyController"],
+        beego.ControllerComments{
+            Method: "GetLyEdbDataByIndexCodeAndDataTime",
+            Router: `/get/ly/edb/data/by/index/code/and/data/time`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_index_lib/controllers:BaseFromLyController"] = append(beego.GlobalControllerRouter["eta/eta_index_lib/controllers:BaseFromLyController"],
+        beego.ControllerComments{
+            Method: "GetLyEdbDataByIndexCodeAndExactDataTime",
+            Router: `/get/ly/edb/data/by/index/code/and/exact/data/time`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_index_lib/controllers:BaseFromLyController"] = append(beego.GlobalControllerRouter["eta/eta_index_lib/controllers:BaseFromLyController"],
+        beego.ControllerComments{
+            Method: "GetLyIndexByCode",
+            Router: `/get/ly/index/by/code`,
+            AllowHTTPMethods: []string{"post"},
             MethodParams: param.Make(),
             Filters: nil,
             Params: nil})
@@ -164,7 +209,7 @@ func init() {
         beego.ControllerComments{
             Method: "GetLyIndexRecordByUrl",
             Router: `/get/ly/index/record/by/url`,
-            AllowHTTPMethods: []string{"get"},
+            AllowHTTPMethods: []string{"post"},
             MethodParams: param.Make(),
             Filters: nil,
             Params: nil})
@@ -182,7 +227,7 @@ func init() {
         beego.ControllerComments{
             Method: "UpdateLyDataById",
             Router: `/update/ly/data/by/id`,
-            AllowHTTPMethods: []string{"get"},
+            AllowHTTPMethods: []string{"post"},
             MethodParams: param.Make(),
             Filters: nil,
             Params: nil})
@@ -191,7 +236,7 @@ func init() {
         beego.ControllerComments{
             Method: "UpdateLyEdbDataById",
             Router: `/update/ly/edb/data/by/id`,
-            AllowHTTPMethods: []string{"get"},
+            AllowHTTPMethods: []string{"post"},
             MethodParams: param.Make(),
             Filters: nil,
             Params: nil})
@@ -835,6 +880,33 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_index_lib/controllers:HisugarController"] = append(beego.GlobalControllerRouter["eta/eta_index_lib/controllers:HisugarController"],
+        beego.ControllerComments{
+            Method: "Add",
+            Router: `/add`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_index_lib/controllers:HisugarController"] = append(beego.GlobalControllerRouter["eta/eta_index_lib/controllers:HisugarController"],
+        beego.ControllerComments{
+            Method: "HandleEdbData",
+            Router: `/handle/edb_data`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_index_lib/controllers:HisugarController"] = append(beego.GlobalControllerRouter["eta/eta_index_lib/controllers:HisugarController"],
+        beego.ControllerComments{
+            Method: "Refresh",
+            Router: `/refresh`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_index_lib/controllers:IcpiController"] = append(beego.GlobalControllerRouter["eta/eta_index_lib/controllers:IcpiController"],
         beego.ControllerComments{
             Method: "Add",
@@ -1321,6 +1393,15 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_index_lib/controllers:PredictController"] = append(beego.GlobalControllerRouter["eta/eta_index_lib/controllers:PredictController"],
+        beego.ControllerComments{
+            Method: "AddStaticEdb",
+            Router: `/static_edb/add`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_index_lib/controllers:PythonController"] = append(beego.GlobalControllerRouter["eta/eta_index_lib/controllers:PythonController"],
         beego.ControllerComments{
             Method: "Add",

+ 5 - 0
routers/router.go

@@ -307,6 +307,11 @@ func init() {
 				&controllers.UsdaFasController{},
 			),
 		),
+		beego.NSNamespace("/hisugar",
+			beego.NSInclude(
+				&controllers.HisugarController{},
+			),
+		),
 	)
 	beego.AddNamespace(ns)
 }

+ 156 - 0
services/base_from_hisugar.go

@@ -0,0 +1,156 @@
+package services
+
+import (
+	"eta/eta_index_lib/models"
+	"eta/eta_index_lib/utils"
+	"fmt"
+	"github.com/mozillazg/go-pinyin"
+	"strings"
+)
+
+var HisugarIndexCodeMap = make(map[string]string)
+var HisugarIndexMap = make(map[string]*models.BaseFromHisugarIndex)
+func HandleHisugarIndex(list []*models.BaseFromHisugarIndexReq) (err error) {
+	allCode, e := models.GetBaseFromHisugarIndex()
+	if e != nil {
+		err = e
+		fmt.Println("select Code err:", err)
+		utils.FileLog.Info("GetBaseFromHisugarIndex err:", err)
+		return
+	}
+
+	for _, item := range allCode {
+		HisugarIndexCodeMap[item.IndexName] = item.IndexCode
+		HisugarIndexMap[item.IndexName] = item
+	}
+
+	for _, v := range list {
+		indexCode, needAdd := HisugarIndexCodeGenerator(v.IndexName, v.IndexNameStr, v.MarketName)
+		if needAdd {
+			item := models.BaseFromHisugarIndex{
+				IndexCode:              indexCode,
+				IndexName:              v.IndexName,
+				ClassifyId:             v.ClassifyId,
+				Unit:                   v.Unit,
+				Frequency:              v.Frequency,
+				Describe:               v.Describe,
+				Sort:                   v.Sort,
+				CreateTime:             v.CreateTime,
+				ModifyTime:             v.ModifyTime,
+			}
+			id, e := models.AddBaseFromHisugarIndex(&item)
+			if e != nil {
+				err = e
+				fmt.Println("AddBaseFromHisugarIndexMuti err:", err)
+				utils.FileLog.Info("AddBaseFromHisugarIndexMuti err:", err)
+				return
+			}
+			item.BaseFromHisugarIndexId = int(id)
+			HisugarIndexMap[item.IndexName] = &item
+		}
+
+
+		//获取指标数据信息
+		data, e := models.GetBaseFromHisugarData(indexCode, v.DataTime)
+		if e != nil && e.Error() != utils.ErrNoRow() {
+			err = e
+			fmt.Println("select err:", err)
+			utils.FileLog.Info("GetBaseFromTradeSci99IndexAll err:", err)
+		}
+
+		if data != nil {
+			if data.Value != v.Value {
+				// 更新
+				fmt.Println("更新指标:", indexCode+v.DataTime)
+				utils.FileLog.Info("更新指标:", indexCode+v.DataTime)
+				e = models.UpdateBaseFromHisugarData(v.Value, indexCode, v.DataTime)
+				if e != nil {
+					err = e
+					fmt.Println("Error update into database:", err)
+					utils.FileLog.Info("Error update into database:", err)
+					return
+				}
+			}
+		} else {
+			// 新增
+			dataItem := models.BaseFromHisugarData{
+				BaseFromHisugarIndexId: HisugarIndexMap[v.IndexName].BaseFromHisugarIndexId,
+				IndexCode:              indexCode,
+				DataTime:               v.DataTime,
+				Value:                  v.Value,
+				CreateTime:             v.CreateTime,
+				ModifyTime:             v.ModifyTime,
+			}
+			fmt.Println("新增数据:", indexCode+v.DataTime)
+			utils.FileLog.Info("新增数据:", indexCode+v.DataTime)
+
+			_,e = models.AddBaseFromHisugarData(&dataItem)
+			if e != nil {
+				err = e
+				fmt.Println("Error inserting into database:", err)
+				utils.FileLog.Info("Error inserting into database:", err)
+				return
+			}
+		}
+	}
+
+	return
+}
+
+
+func HisugarIndexCodeGenerator(indexName, indexCodeStr, marketSampleName string) (indexCode string, needAdd bool) {
+	strResult := ""
+	indexCodeStr = indexName
+	indexCode, _ = HisugarIndexCodeMap[indexName]
+	if indexCode == "" {
+		//首字母
+		a := pinyin.NewArgs()
+		a.Fallback = func(r rune, a pinyin.Args) []string {
+			return []string{string(r)}
+		}
+		indexCodeStr = strings.Replace(indexCodeStr, "(", "", -1)
+		indexCodeStr = strings.Replace(indexCodeStr, ")", "", -1)
+		rows := pinyin.Pinyin(indexCodeStr, a)
+		for i := 0; i < len(rows); i++ {
+			//strResult += rows[i][0]
+			if len(rows[i]) != 0 {
+				str := rows[i][0]
+				pi := str[0:1]
+				strResult += pi
+			}
+		}
+
+		// 处理括号内名称
+		if marketSampleName != "" {
+			if province, ok := ProvinceMap[marketSampleName]; ok {
+				strResult += province
+			} else {
+				a := pinyin.NewArgs()
+				rows := pinyin.LazyPinyin(marketSampleName, a)
+				for i := 0; i < len(rows); i++ {
+					strResult += rows[i]
+				}
+				if len(rows) == 0 {
+					strResult += marketSampleName
+				}
+			}
+		}
+
+
+		// 去除特殊符号
+		strResult = strings.Replace(strResult, " ", "", -1)
+		strResult = strings.Replace(strResult, "-", "", -1)
+		strResult = strings.Replace(strResult, "/", "", -1)
+		strResult = strings.Replace(strResult, "#", "", -1)
+		strResult = strings.Replace(strResult, ":", "", -1)
+		strResult = strings.Replace(strResult, "(", "", -1)
+		strResult = strings.Replace(strResult, ")", "", -1)
+
+
+		needAdd = true
+		strResult = "ftkj" + strResult
+		indexCode = strings.Replace(strResult, " ", "", -1)
+		HisugarIndexCodeMap[indexName] = indexCode
+	}
+	return
+}

+ 1 - 1
services/base_from_smm.go

@@ -235,7 +235,7 @@ func SmmIndexHandle(baseFilePath, renameFilePath, indexName, indexCode, unit, fr
 }
 
 func GetEdbDataFromSmm(edbCode string) (smmBaseDataAll []models.BaseFromSmmDataList, err error) {
-	if utils.BusinessCode == "E2023110300" {
+	if utils.SmmDataMethod == "api" {
 		return GetSmmNewIndexFromBridge(edbCode)
 	}
 

+ 3 - 0
services/base_from_yongyi.go

@@ -94,6 +94,9 @@ func handleYongyiIndex(req *models.HandleYongyiExcelData, terminalCode string, c
 					ModifyTime:      now,
 					CreateTime:      now,
 				}
+				if classifyParentId > 0 {
+					classifyObj.Level = 2
+				}
 
 				classifyId, err = classifyObj.Add()
 				if err != nil {

+ 495 - 0
services/edb_data_calculate_stl.go

@@ -0,0 +1,495 @@
+package services
+
+import (
+	"encoding/json"
+	"eta/eta_index_lib/models"
+	"eta/eta_index_lib/utils"
+	"fmt"
+	"os"
+	"os/exec"
+	"path/filepath"
+	"strconv"
+	"time"
+
+	"github.com/tealeg/xlsx"
+)
+
+const (
+	ALL_DATE = iota + 1
+	LAST_N_YEARS
+	RANGE_DATE
+	RANGE_DATE_TO_NOW
+)
+
+type EdbStlConfig struct {
+	EdbInfoId            int     `description:"指标ID"`
+	CalculateStlConfigId int     `description:"计算的STL配置ID"`
+	DataRangeType        int     `description:"数据时间类型:1-全部时间,2-最近N年,3-区间设置,4-区间设置(至今)"`
+	StartDate            string  `description:"开始日期"`
+	EndDate              string  `description:"结束日期"`
+	LastNYear            string  `description:"最近N年"`
+	Period               int     `description:"数据的周期,根据频率设置"`
+	Seasonal             int     `description:"季节性成分窗口大小,一般为period+1,可以设置为大于period的正奇数"`
+	Trend                int     `description:"趋势成分窗口大小,一般为period+1,可以设置为大于period的正奇数"`
+	Fraction             float64 `description:"趋势项的平滑系数,默认为0.2,区间为[0-1]"`
+	Robust               bool    `description:"是否使用稳健方法: true(使用) false(不使用)  "`
+	TrendDeg             int     `description:"分解中趋势多项式次数,默认为1,不超过5的正整数"`
+	SeasonalDeg          int     `description:"分解中季节性多项次数,默认为1,不超过5的正整数"`
+	LowPassDeg           int     `description:"分解中低通滤波器次数,默认为1,不超过5的正整数"`
+}
+
+type ChartEdbInfo struct {
+	EdbInfoId    int
+	Title        string
+	Unit         string
+	Frequency    string
+	MaxData      float64
+	MinData      float64
+	ClassifyId   int
+	ClassifyPath string
+	DataList     []*EdbData
+}
+
+type EdbData struct {
+	Value         float64
+	DataTime      string
+	DataTimestamp int64
+}
+
+func RefreshStlData(edbInfoId int) (msg string, err error) {
+	calculateStl, err := models.GetEdbInfoCalculateMappingDetail(edbInfoId)
+	if err != nil {
+		return
+	}
+
+	fromEdbInfo, err := models.GetEdbInfoById(calculateStl.FromEdbInfoId)
+	if err != nil {
+		return
+	}
+	var stlConfig EdbStlConfig
+	if err = json.Unmarshal([]byte(calculateStl.CalculateFormula), &stlConfig); err != nil {
+		return
+	}
+	var condition string
+	var pars []interface{}
+	switch stlConfig.DataRangeType {
+	case ALL_DATE:
+	case LAST_N_YEARS:
+		condition += " AND data_time >=?"
+		year := time.Now().Year()
+		lastNyear, er := strconv.Atoi(stlConfig.LastNYear)
+		if er != nil {
+			msg = "最近N年输入不合法"
+			err = er
+			return
+		}
+		lastDate := time.Date(year-lastNyear, 1, 1, 0, 0, 0, 0, time.Local)
+		pars = append(pars, lastDate)
+	case RANGE_DATE:
+		condition = " AND data_time >=? AND data_time <=?"
+		pars = append(pars, stlConfig.StartDate, stlConfig.EndDate)
+	case RANGE_DATE_TO_NOW:
+		condition = " AND data_time >=?"
+		pars = append(pars, stlConfig.StartDate)
+	}
+	condition += " AND edb_code =?"
+	pars = append(pars, fromEdbInfo.EdbCode)
+
+	edbData, err := models.GetEdbDataByCondition(fromEdbInfo.Source, fromEdbInfo.SubSource, condition, pars)
+	if err != nil {
+		return
+	}
+	var condMsg string
+	if stlConfig.Period < 2 || stlConfig.Period > len(edbData) {
+		condMsg += "period必须是一个大于等于2的正整数,且必须小于时间序列的长度"
+	}
+	if stlConfig.Seasonal < 3 || stlConfig.Seasonal%2 == 0 || stlConfig.Seasonal <= stlConfig.Period {
+		if condMsg != "" {
+			condMsg += "\n"
+		}
+		condMsg += "seasonal必须是一个大于等于3的奇整数,且必须大于period"
+	}
+	if stlConfig.Trend < 3 || stlConfig.Trend%2 == 0 || stlConfig.Trend <= stlConfig.Period {
+		if condMsg != "" {
+			condMsg += "\n"
+		}
+		condMsg += "trend必须是一个大于等于3的奇整数,且必须大于period"
+	}
+	if stlConfig.Fraction < 0 || stlConfig.Fraction > 1 {
+		if condMsg != "" {
+			condMsg += "\n"
+		}
+		condMsg += "fraction必须是一个介于[0-1]之间"
+	}
+	if 1 > stlConfig.TrendDeg || stlConfig.TrendDeg > 5 {
+		if condMsg != "" {
+			condMsg += "\n"
+		}
+		condMsg += "trend_deg请设置成1-5的整数"
+	}
+	if 1 > stlConfig.SeasonalDeg || stlConfig.SeasonalDeg > 5 {
+		if condMsg != "" {
+			condMsg += "\n"
+		}
+		condMsg += "seasonal_deg请设置成1-5的整数"
+	}
+	if 1 > stlConfig.LowPassDeg || stlConfig.LowPassDeg > 5 {
+		if condMsg != "" {
+			condMsg += "\n"
+		}
+		condMsg += "low_pass_deg请设置成1-5的整数"
+	}
+	if condMsg != "" {
+		msg = condMsg
+		err = fmt.Errorf("参数错误")
+		return
+	}
+
+	dir, _ := os.Executable()
+	exPath := filepath.Dir(dir) + "/static/stl_tmp"
+	err = CheckOsPathAndMake(exPath)
+	if err != nil {
+		msg = "计算失败"
+		return
+	}
+	loadFilePath := exPath + "/" + strconv.Itoa(fromEdbInfo.SysUserId) + "_" + time.Now().Format(utils.FormatDateTimeUnSpace) + ".xlsx"
+	err = SaveToExcel(edbData, loadFilePath)
+	if err != nil {
+		msg = "保存数据到Excel失败"
+		return
+	}
+	defer os.Remove(loadFilePath)
+	saveFilePath := exPath + "/" + strconv.Itoa(fromEdbInfo.SysUserId) + "_" + time.Now().Format(utils.FormatDateTimeUnSpace) + "_res" + ".xlsx"
+	err = execStlPythonCode(loadFilePath, saveFilePath, stlConfig.Period, stlConfig.Seasonal, stlConfig.Trend, stlConfig.TrendDeg, stlConfig.SeasonalDeg, stlConfig.LowPassDeg, stlConfig.Fraction, stlConfig.Robust)
+	if err != nil {
+		msg = "执行Python代码失败"
+		return
+	}
+
+	trendChart, seasonalChart, residualChart, err := ParseStlExcel(saveFilePath)
+	if err != nil {
+		msg = "解析Excel失败"
+		return
+	}
+	defer os.Remove(saveFilePath)
+	edbInfo, err := models.GetEdbInfoById(edbInfoId)
+	if err != nil {
+		msg = "获取指标信息失败"
+		return
+	}
+	err = SyncUpdateRelationEdbInfo(edbInfo, stlConfig, trendChart, seasonalChart, residualChart)
+	if err != nil {
+		msg = "更新关联指标失败"
+		return
+	}
+
+	return
+}
+
+func SyncUpdateRelationEdbInfo(edbInfo *models.EdbInfo, config EdbStlConfig, trendData, seasonalData, residualData ChartEdbInfo) (err error) {
+	configId, err := models.GetCalculateStlConfigMappingIdByEdbInfoId(edbInfo.EdbInfoId)
+	if err != nil {
+		return
+	}
+	mappingList, err := models.GetCalculateStlConfigMappingByConfigId(configId)
+	if err != nil {
+		return
+	}
+	for _, v := range mappingList {
+		edbInfo, er := models.GetEdbInfoById(v.EdbInfoId)
+		if er != nil {
+			continue
+		}
+		switch v.StlEdbType {
+		case 1:
+			// 趋势指标
+			er = UpdateStlEdbData(edbInfo, config, edbInfo.EdbCode, trendData)
+		case 2:
+			// 季节性指标
+			er = UpdateStlEdbData(edbInfo, config, edbInfo.EdbCode, seasonalData)
+		case 3:
+			// 残差指标
+			er = UpdateStlEdbData(edbInfo, config, edbInfo.EdbCode, residualData)
+		default:
+			utils.FileLog.Info("未知的stlEdbType类型, mapping:%v", v)
+			continue
+		}
+		if er != nil {
+			utils.FileLog.Error("更新指标数据失败, edbInfoId:%v, err:%v", v.EdbInfoId, er)
+			err = er
+			continue
+		}
+	}
+	// 同步更新计算配置
+	newStlConf := &models.CalculateStlConfig{
+		CalculateStlConfigId: configId,
+		Config:               edbInfo.CalculateFormula,
+		ModifyTime:           time.Now(),
+	}
+	err = newStlConf.Update([]string{"config", "modify_time"})
+	return
+}
+
+func UpdateStlEdbData(edbInfo *models.EdbInfo, config EdbStlConfig, edbCode string, edbData ChartEdbInfo) (err error) {
+	var dataList []*models.EdbDataCalculateStl
+	for _, v := range edbData.DataList {
+		dataTime, _ := time.Parse(utils.FormatDate, v.DataTime)
+		dataList = append(dataList, &models.EdbDataCalculateStl{
+			EdbInfoId:     edbData.EdbInfoId,
+			EdbCode:       edbCode,
+			DataTime:      dataTime,
+			Value:         v.Value,
+			CreateTime:    time.Now(),
+			ModifyTime:    time.Now(),
+			DataTimestamp: dataTime.UnixMilli(),
+		})
+	}
+	err = models.DeleteAndInsertEdbDataCalculateStl(edbCode, dataList)
+	if err != nil {
+		return
+	}
+
+	models.ModifyEdbInfoDataStatus(int64(edbInfo.EdbInfoId), edbInfo.Source, edbInfo.SubSource, edbInfo.EdbCode)
+
+	maxAndMinItem, _ := models.GetEdbInfoMaxAndMinInfo(edbInfo.Source, edbInfo.SubSource, edbInfo.EdbCode)
+	if maxAndMinItem != nil {
+		err = models.ModifyEdbInfoMaxAndMinInfo(edbInfo.EdbInfoId, maxAndMinItem)
+		if err != nil {
+			return
+		}
+	}
+
+	bconfig, _ := json.Marshal(config)
+	edbInfo.CalculateFormula = string(bconfig)
+	edbInfo.ModifyTime = time.Now()
+	err = edbInfo.Update([]string{"calculate_formula", "modify_time"})
+	if err != nil {
+		return
+	}
+	return
+}
+
+func CheckOsPathAndMake(path string) (err error) {
+	if _, er := os.Stat(path); os.IsNotExist(er) {
+		err = os.MkdirAll(path, os.ModePerm)
+	}
+	return
+}
+
+func SaveToExcel(data []*models.EdbInfoSearchData, filePath string) (err error) {
+	xlsxFile := xlsx.NewFile()
+	sheetNew, err := xlsxFile.AddSheet("Tmp")
+	if err != nil {
+		return
+	}
+	titleRow := sheetNew.AddRow()
+	titleRow.AddCell().SetString("日期")
+	titleRow.AddCell().SetString("值")
+
+	for i, d := range data {
+		row := sheetNew.Row(i + 1)
+		row.AddCell().SetString(d.DataTime)
+		row.AddCell().SetFloat(d.Value)
+	}
+	err = xlsxFile.Save(filePath)
+	if err != nil {
+		return
+	}
+	return
+}
+
+func ParseStlExcel(excelPath string) (TrendChart, SeasonalChart, ResidualChart ChartEdbInfo, err error) {
+	file, err := xlsx.OpenFile(excelPath)
+	if err != nil {
+		return
+	}
+	for _, sheet := range file.Sheets {
+		switch sheet.Name {
+		case "季节":
+			var MinData, MaxData float64
+			for i, row := range sheet.Rows {
+				if i == 0 {
+					continue
+				}
+				var date string
+				var dataTimestamp int64
+				if row.Cells[0].Type() == xlsx.CellTypeNumeric {
+					dataNum, _ := strconv.ParseFloat(row.Cells[0].Value, 64)
+					tmpTime := xlsx.TimeFromExcelTime(dataNum, false)
+					date = tmpTime.Format(utils.FormatDate)
+					dataTimestamp = tmpTime.UnixMilli()
+				} else {
+					timeDate, _ := time.Parse(utils.FormatDateTime, date)
+					date = timeDate.Format(utils.FormatDate)
+					dataTimestamp = timeDate.UnixMilli()
+				}
+				fv, _ := row.Cells[1].Float()
+				if MinData == 0 || fv < MinData {
+					MinData = fv
+				}
+				if MaxData == 0 || fv > MaxData {
+					MaxData = fv
+				}
+				SeasonalChart.DataList = append(SeasonalChart.DataList, &EdbData{DataTime: date, Value: fv, DataTimestamp: dataTimestamp})
+			}
+			SeasonalChart.MinData = MinData
+			SeasonalChart.MaxData = MaxData
+		case "趋势":
+			var MinData, MaxData float64
+			for i, row := range sheet.Rows {
+				if i == 0 {
+					continue
+				}
+				var date string
+				var dataTimestamp int64
+				if row.Cells[0].Type() == xlsx.CellTypeNumeric {
+					dataNum, _ := strconv.ParseFloat(row.Cells[0].Value, 64)
+					tmpTime := xlsx.TimeFromExcelTime(dataNum, false)
+					date = tmpTime.Format(utils.FormatDate)
+					dataTimestamp = tmpTime.UnixMilli()
+				} else {
+					timeDate, _ := time.Parse(utils.FormatDateTime, date)
+					date = timeDate.Format(utils.FormatDate)
+					dataTimestamp = timeDate.UnixMilli()
+				}
+				fv, _ := row.Cells[1].Float()
+				if MinData == 0 || fv < MinData {
+					MinData = fv
+				}
+				if MaxData == 0 || fv > MaxData {
+					MaxData = fv
+				}
+				TrendChart.DataList = append(TrendChart.DataList, &EdbData{DataTime: date, Value: fv, DataTimestamp: dataTimestamp})
+			}
+			TrendChart.MaxData = MaxData
+			TrendChart.MinData = MinData
+		case "残差":
+			var MinData, MaxData float64
+			for i, row := range sheet.Rows {
+				if i == 0 {
+					continue
+				}
+				var date string
+				var dataTimestamp int64
+				if row.Cells[0].Type() == xlsx.CellTypeNumeric {
+					dataNum, _ := strconv.ParseFloat(row.Cells[0].Value, 64)
+					tmpTime := xlsx.TimeFromExcelTime(dataNum, false)
+					date = tmpTime.Format(utils.FormatDate)
+					dataTimestamp = tmpTime.UnixMilli()
+				} else {
+					timeDate, _ := time.Parse(utils.FormatDateTime, date)
+					date = timeDate.Format(utils.FormatDate)
+					dataTimestamp = timeDate.UnixMilli()
+				}
+				fv, _ := row.Cells[1].Float()
+				if MinData == 0 || fv < MinData {
+					MinData = fv
+				}
+				if MaxData == 0 || fv > MaxData {
+					MaxData = fv
+				}
+				ResidualChart.DataList = append(ResidualChart.DataList, &EdbData{DataTime: date, Value: fv, DataTimestamp: dataTimestamp})
+			}
+			ResidualChart.MaxData = MaxData
+			ResidualChart.MinData = MinData
+		}
+	}
+	return
+}
+
+func execStlPythonCode(path, toPath string, period, seasonal, trend, trendDeg, seasonalDeg, lowPassDeg int, fraction float64, robust bool) (err error) {
+	pythonCode := `
+import pandas as pd
+from statsmodels.tsa.seasonal import STL
+from statsmodels.nonparametric.smoothers_lowess import lowess
+from statsmodels.tsa.stattools import adfuller
+from statsmodels.stats.diagnostic import acorr_ljungbox
+import numpy as np
+import json
+import warnings
+warnings.filterwarnings('ignore')
+
+file_path = r"%s"
+df = pd.read_excel(file_path, parse_dates=['日期'])
+df.set_index('日期', inplace=True)
+
+
+period = %d
+seasonal = %d
+trend = %d
+fraction = %g
+seasonal_deg = %d
+trend_deg = %d
+low_pass_deg = %d
+robust = %s
+
+stl = STL(
+    df['值'],
+    period=period,
+    seasonal=seasonal,
+    trend=trend,
+    low_pass=None,
+    seasonal_deg=seasonal_deg,
+    trend_deg=trend_deg,
+    low_pass_deg=low_pass_deg,
+    seasonal_jump=1,
+    trend_jump=1,
+    low_pass_jump=1,
+    robust=robust
+)
+result = stl.fit()
+
+smoothed = lowess(df['值'], df.index, frac=fraction)
+
+trend_lowess = smoothed[:, 1]
+
+# 季节图
+seasonal_component = result.seasonal
+# 趋势图
+trend_lowess_series = pd.Series(trend_lowess, index=df.index)
+# 残差图
+residual_component = df['值'] - trend_lowess - seasonal_component
+
+# 计算打印残差的均值
+residual_mean = np.mean(residual_component)
+# 计算打印残差的方差
+residual_var = np.std(residual_component)
+# 计算打印残差的ADF检验结果, 输出p-value
+adf_result = adfuller(residual_component)
+# 根据p-value判断是否平稳
+lb_test = acorr_ljungbox(residual_component, lags=period, return_df=True)
+
+output_file = r"%s"
+
+with pd.ExcelWriter(output_file) as writer:
+    # 保存季节图
+    pd.Series(seasonal_component, index=df.index, name='值').to_frame().reset_index().rename(columns={'index': '日期'}).to_excel(writer, sheet_name='季节', index=False)
+    # 保存趋势图
+    trend_lowess_series.to_frame(name='值').reset_index().rename(columns={'index': '日期'}).to_excel(writer, sheet_name='趋势', index=False)
+    # 保存残差图
+    pd.Series(residual_component, index=df.index, name='值').to_frame().reset_index().rename(columns={'index': '日期'}).to_excel(writer, sheet_name='残差', index=False)
+
+output =  json.dumps({
+    'residual_mean': residual_mean,
+    'residual_var': residual_var,
+    'adf_p_value': adf_result[1],
+    'lb_test_p_value': lb_test['lb_pvalue'].values[0],
+    'lb_test_stat': lb_test['lb_stat'].values[0]
+})
+
+print(output)
+	`
+	robustStr := "True"
+	if !robust {
+		robustStr = "False"
+	}
+
+	pythonCode = fmt.Sprintf(pythonCode, path, period, seasonal, trend, fraction, seasonalDeg, trendDeg, lowPassDeg, robustStr, toPath)
+	cmd := exec.Command(`python3`, "-c", pythonCode)
+	_, err = cmd.CombinedOutput()
+	if err != nil {
+		return
+	}
+	defer cmd.Process.Kill()
+	return
+}

+ 19 - 7
services/trade_analysis/trade_analysis_data.go

@@ -59,7 +59,6 @@ func FormatCompanyTradeData2EdbData(companyTradeData *tradeAnalysisModel.Contrac
 	return
 }
 
-// GetOriginTradeData 获取原始持仓数据
 func GetOriginTradeData(exchange, classifyName string, contracts, companies []string, predictRatio float64) (companyTradeData []*tradeAnalysisModel.ContractCompanyTradeData, err error) {
 	// 各原始数据表期货公司名称不一致
 	companyMap := make(map[string]string)
@@ -99,9 +98,8 @@ func GetOriginTradeData(exchange, classifyName string, contracts, companies []st
 	}
 	var queryCompanies []string
 	for _, v := range companies {
-		// TOP20用空名称去查询
 		if v == tradeAnalysisModel.TradeFuturesCompanyTop20 {
-			queryCompanies = append(queryCompanies, "")
+			queryCompanies = append(queryCompanies, tradeAnalysisModel.TradeFuturesCompanyTop20)
 			continue
 		}
 		companyName, ok := companyMap[v]
@@ -134,9 +132,9 @@ func GetOriginTradeData(exchange, classifyName string, contracts, companies []st
 	keyDateData := make(map[string]*tradeAnalysisModel.ContractCompanyTradeDataList)
 	keyDateDataExist := make(map[string]bool)
 	for _, v := range originList {
-		// TOP20对应数据库中的空名称
+		// Rank999和0对应的是TOP20
 		companyName := v.CompanyName
-		if companyName == "" {
+		if v.Rank == 999 || v.Rank == 0 {
 			companyName = tradeAnalysisModel.TradeFuturesCompanyTop20
 		}
 
@@ -214,7 +212,7 @@ func GetOriginTradeData(exchange, classifyName string, contracts, companies []st
 	}
 
 	// 以[公司]为组, 计算合约加总
-	companyTradeData = make([]*tradeAnalysisModel.ContractCompanyTradeData, 0)
+	mussyTradeData := make(map[string]*tradeAnalysisModel.ContractCompanyTradeData)
 	for k, v := range companyContracts {
 		companyData := new(tradeAnalysisModel.ContractCompanyTradeData)
 		companyData.CompanyName = k
@@ -283,7 +281,21 @@ func GetOriginTradeData(exchange, classifyName string, contracts, companies []st
 			return companyData.DataList[i].Date.Before(companyData.DataList[j].Date)
 		})
 		companyData.ClassifyType = strings.Join(contractArr, ",")
-		companyTradeData = append(companyTradeData, companyData)
+		mussyTradeData[k] = companyData
+	}
+
+	// 数据根据公司排序, 不然会随机乱
+	companyTradeData = make([]*tradeAnalysisModel.ContractCompanyTradeData, 0)
+	for _, v := range companies {
+		// 没数据也需要加进去, 不然edbList会少
+		if mussyTradeData[v] == nil {
+			companyData := new(tradeAnalysisModel.ContractCompanyTradeData)
+			companyData.CompanyName = v
+			companyData.DataList = make([]*tradeAnalysisModel.ContractCompanyTradeDataList, 0)
+			companyTradeData = append(companyTradeData, companyData)
+			continue
+		}
+		companyTradeData = append(companyTradeData, mussyTradeData[v])
 	}
 	return
 }

+ 25 - 1
static/pcsg_task.json

@@ -87,6 +87,14 @@
     "IndexNamePrefix": "",
     "IndexCodeSuffix": "0330"
   },
+  {
+    "TaskKey": "IDpcsgDailySnap0345",
+    "Frequency": "日度",
+    "VCode": false,
+    "ExtraLetter": "",
+    "IndexNamePrefix": "",
+    "IndexCodeSuffix": ""
+  },
   {
     "TaskKey": "IDpcsgMonthRun2",
     "Frequency": "月度",
@@ -94,5 +102,21 @@
     "ExtraLetter": "",
     "IndexNamePrefix": "",
     "IndexCodeSuffix": ""
+  },
+  {
+    "TaskKey": "IDpcsgDailyRun10",
+    "Frequency": "日度",
+    "VCode": false,
+    "ExtraLetter": "",
+    "IndexNamePrefix": "",
+    "IndexCodeSuffix": ""
+  },
+  {
+    "TaskKey": "IDpcsgDailyRun11",
+    "Frequency": "日度",
+    "VCode": false,
+    "ExtraLetter": "",
+    "IndexNamePrefix": "",
+    "IndexCodeSuffix": ""
   }
-]
+]

+ 197 - 0
utils/common.go

@@ -1500,3 +1500,200 @@ func GetTradingDays(startDate, endDate time.Time) []time.Time {
 	}
 	return tradingDays
 }
+
+// CalculateTradingDays 计算天数 跳过周末
+func CalculateTradingDays(baseDate time.Time, tradingDays int, resultMap map[string]float64, moveType int) int {
+	oldDate := baseDate
+
+	// Move to the next day
+	var moveDays int
+	if moveType != 2 {
+		moveDays = tradingDays
+	} else {
+		moveDays = -tradingDays
+	}
+
+	daysMoved := 0 // 实际移动的工作日数
+	for daysMoved < tradingDays {
+		// 根据 moveType,决定是前进一天还是后退一天
+		if moveDays > 0 {
+			baseDate = baseDate.AddDate(0, 0, 1) // 向后移动一天
+		} else {
+			baseDate = baseDate.AddDate(0, 0, -1) // 向前移动一天
+		}
+
+		// 如果当前日期不是周六或周日,则计入交易日
+		weekday := baseDate.Weekday()
+		if weekday != time.Saturday && weekday != time.Sunday {
+			daysMoved++
+		}
+	}
+
+	// 计算实际天数差(包含跳过周末后的移动天数)
+	subDays := baseDate.Sub(oldDate)
+	days := int(math.Abs(subDays.Hours() / 24))
+
+	return days
+}
+
+// getLastDayOfMonth 获取某个月的最后一天
+func getLastDayOfMonth(t time.Time) time.Time {
+	// 移动到下个月的第一天,然后回退一天得到当前月的最后一天
+	return time.Date(t.Year(), t.Month(), 1, 0, 0, 0, 0, t.Location()).AddDate(0, 1, -1)
+}
+
+// 获取某年某月的天数
+func daysInMonth(year int, month time.Month) int {
+	if month == time.February {
+		// 闰年处理
+		if (year%4 == 0 && year%100 != 0) || (year%400 == 0) {
+			return 29
+		}
+		return 28
+	}
+	if month == time.April || month == time.June || month == time.September || month == time.November {
+		return 30
+	}
+	return 31
+}
+
+// CalculateEndOfMonth 使用天数计算未来月末的天数差
+/*func CalculateEndOfMonth(baseDate time.Time, months, moveType int) int {
+	// 假设每个月28天,然后算到目标月的下个月
+	var daysToAdd int
+	// 计算目标月的下个月月初
+	if moveType == 2 {
+		daysToAdd = -(28 * months)
+	} else {
+		daysToAdd = 28 * (months + 2)
+	}
+	nextMonth := baseDate.AddDate(0, 0, daysToAdd)
+
+	// 获取目标月月初的第一天
+	firstDayOfNextMonth := time.Date(nextMonth.Year(), nextMonth.Month(), 1, 0, 0, 0, 0, nextMonth.Location())
+
+	// 获取目标月的最后一天(即月初减去1天)
+	lastDayOfTargetMonth := firstDayOfNextMonth.AddDate(0, 0, -1)
+
+	// 计算天数差
+	daysDifference := int(math.Abs(lastDayOfTargetMonth.Sub(baseDate).Hours() / 24))
+
+	return daysDifference
+}*/
+
+// CalculateEndOfMonth 计算从 baseDate 开始经过 months 后目标月的最后一天距离 baseDate 的天数差
+func CalculateEndOfMonth(baseDate time.Time, months, moveType int) int {
+	// 初始化目标日期为当前日期
+	targetDate := baseDate
+
+	// 如果 moveType == 2,表示倒退月份;否则为前进月份
+	if moveType == 2 {
+		months = -months
+	}
+
+	// 手动通过天数加减月份
+	for i := 0; i < int(math.Abs(float64(months))); i++ {
+		// 首先将日期调整到当前月份的第一天
+		targetDate = time.Date(targetDate.Year(), targetDate.Month(), 1, 0, 0, 0, 0, targetDate.Location())
+
+		// 根据 moveType 来前进或倒退到下一个月的第一天
+		if months > 0 {
+			// 前进到下一个月的第一天
+			targetDate = targetDate.AddDate(0, 1, 0)
+		} else {
+			// 倒退到上一个月的第一天
+			targetDate = targetDate.AddDate(0, -1, 0)
+		}
+
+		// 如果是倒退,调整为目标月的最后一天
+		if months < 0 {
+			daysInCurrentMonth := daysInMonth(targetDate.Year(), targetDate.Month())
+			targetDate = time.Date(targetDate.Year(), targetDate.Month(), daysInCurrentMonth, 0, 0, 0, 0, targetDate.Location())
+		}
+	}
+
+	// 获取目标月的下个月月初第一天
+	firstDayOfNextMonth := time.Date(targetDate.Year(), targetDate.Month()+1, 1, 0, 0, 0, 0, targetDate.Location())
+
+	// 获取目标月的最后一天(即下个月月初减去一天)
+	lastDayOfTargetMonth := firstDayOfNextMonth.AddDate(0, 0, -1)
+
+	// 计算天数差
+	daysDifference := int(math.Abs(lastDayOfTargetMonth.Sub(baseDate).Hours() / 24))
+
+	return daysDifference
+}
+
+// CalculateDekadTime 计算旬度时间
+func CalculateDekadTime(baseDate time.Time, tradingDays, moveType int) int {
+	// 记录原始日期
+	oldDate := baseDate
+
+	// 计算移动的旬数,1 旬为 10 天
+	var moveDekads int
+	if moveType != 2 {
+		moveDekads = tradingDays
+	} else {
+		moveDekads = -tradingDays
+	}
+
+	// 移动的天数为旬数 * 10,初步移动日期
+	baseDate = baseDate.AddDate(0, 0, moveDekads*10)
+
+	// 调整日期到最近的旬:10号、20号或月末
+	baseDate = adjustToNearestDekad(baseDate)
+
+	// 计算时间差
+	subDays := baseDate.Sub(oldDate)
+	days := int(math.Abs(subDays.Hours() / 24))
+
+	fmt.Printf("最终日期: %s, 总天数差: %d 天\n", baseDate.Format("2006-01-02"), days)
+
+	return days
+}
+
+// adjustToNearestDekad 调整日期到最近的旬
+func adjustToNearestDekad(date time.Time) time.Time {
+	day := date.Day()
+	lastDayOfMonth := getLastDayOfMonth(date).Day()
+
+	// 这里有些无可奈何了,暂时这么写吧。。。需要跟据润 平年根据每个月进行单独处理
+	if day < 5 {
+		dateOneMonthAgo := date.AddDate(0, -1, 0)
+		lastDayOfMonth2 := getLastDayOfMonth(dateOneMonthAgo).Day()
+		return time.Date(date.Year(), date.Month()-1, lastDayOfMonth2, 0, 0, 0, 0, date.Location())
+	} else if day > 5 && day <= 15 {
+		return time.Date(date.Year(), date.Month(), 10, 0, 0, 0, 0, date.Location())
+	} else if day > 11 && day <= 25 {
+		return time.Date(date.Year(), date.Month(), 20, 0, 0, 0, 0, date.Location())
+	} else {
+		return time.Date(date.Year(), date.Month(), lastDayOfMonth, 0, 0, 0, 0, date.Location())
+	}
+}
+
+/*// getLastDayOfMonth 返回指定日期所在月份的最后一天
+func getLastDayOfMonth(date time.Time) time.Time {
+	// 获取下个月的第一天
+	nextMonth := date.AddDate(0, 1, -date.Day()+1)
+	// 下个月第一天减去一天即为当前月的最后一天
+	lastDay := nextMonth.AddDate(0, 0, -1)
+	return lastDay
+}*/
+
+// CalculateEndOfQuarter 计算从当前到未来的季度末的天数差
+func CalculateEndOfQuarter(baseDate time.Time, quarters, moveType int) int {
+	// Move forward `quarters` quarters (3 months per quarter)
+	return CalculateEndOfMonth(baseDate, quarters*3, moveType)
+}
+
+// CalculateEndOfYear 计算从当前到未来的年末的天数差
+func CalculateEndOfYear(baseDate time.Time, years, moveType int) int {
+	// Move forward `years` years
+	return CalculateEndOfMonth(baseDate, years*12, moveType)
+}
+
+// CalculateEndOfHalfYear 计算从当前到未来的半年的天数差
+func CalculateEndOfHalfYear(baseDate time.Time, years, moveType int) int {
+	// Move forward `half years` years
+	return CalculateEndOfMonth(baseDate, years*6, moveType)
+}

+ 8 - 3
utils/constants.go

@@ -116,6 +116,10 @@ const (
 	DATA_SOURCE_LY                                   = 91 // 粮油商务网
 	DATA_SOURCE_TRADE_ANALYSIS                       = 92 // 持仓分析
 	DATA_SOURCE_USDA_FAS                             = 96 //美国农业部
+	DATA_SOURCE_HISUGAR                              = 93 // 泛糖科技 -> 93
+	DATA_SOURCE_CALCULATE_STL                        = 98 // STL趋势分解 -> 98
+	DATA_SOURCE_PREDICT_CALCULATE_PHASE_SHIFT        = 94 //预测指标 - 期数移位
+	DATA_SOURCE_CALCULATE_PHASE_SHIFT                = 95 // 期数移动
 )
 
 // 指标来源的中文展示
@@ -201,13 +205,14 @@ const (
 	DATA_SOURCE_NAME_CALCULATE_SUM                        = `多指标求和`
 	DATA_SOURCE_NAME_CALCULATE_AVG                        = `多指标求平均`
 	DATA_SOURCE_NAME_BUSINESS                             = `自有数据`
-	DATA_SOURCE_NAME_CCF                                  = `CCF`    // CCF化纤信息
-	DATA_SOURCE_NAME_SCI_HQ                               = `卓创红期`   // 卓创红期
-	DATA_SOURCE_NAME_OILCHEM                              = `隆众资讯`   // 隆众资讯 -> 89
 	DATA_SOURCE_NAME_CALCULATE_RANGEANLYSIS               = `区间计算`   //区间计算->87
 	DATA_SOURCE_NAME_PREDICT_CALCULATE_RANGEANLYSIS       = `预测区间计算` //区间计算->90
 	DATA_SOURCE_NAME_TRADE_ANALYSIS                       = `持仓分析`   // 持仓分析
 	DATA_SOURCE_NAME_USDA_FAS                             = `美国农业部`  //美国农业部->96
+	DATA_SOURCE_NAME_CCF                                  = `CCF`    // CCF化纤信息
+	DATA_SOURCE_NAME_SCI_HQ                               = `卓创红期`   // 卓创红期
+	DATA_SOURCE_NAME_OILCHEM                              = `隆众资讯`   // 隆众资讯 -> 89
+	DATA_SOURCE_NAME_HISUGAR                              = `泛糖科技`   // 泛糖科技 -> 93
 )
 
 // 基础数据初始化日期