Browse Source

Merge branch 'master' into feature/eta2.2.8_predit_date

xyxie 2 weeks ago
parent
commit
df30e2c920
46 changed files with 4272 additions and 195 deletions
  1. 1 1
      controllers/base_from_business.go
  2. 39 3
      controllers/base_from_calculate.go
  3. 5 2
      controllers/base_from_predict_calculate.go
  4. 709 0
      controllers/base_from_rzd.go
  5. 174 0
      controllers/base_from_usda_fas.go
  6. 10 10
      controllers/exchange_crawler.go
  7. 10 40
      controllers/future_good/future_good_edb_info.go
  8. 1 0
      go.mod
  9. 2 0
      go.sum
  10. 4 4
      logic/predict_edb.go
  11. 190 112
      logic/profit_chart_info.go
  12. 1 1
      models/base_from_calculate.go
  13. 42 0
      models/base_from_rzd_classify.go
  14. 71 0
      models/base_from_rzd_data.go
  15. 54 0
      models/base_from_rzd_index.go
  16. 353 0
      models/base_from_usda_fas.go
  17. 5 0
      models/db.go
  18. 24 1
      models/edb_data_calculate_hbz.go
  19. 22 0
      models/edb_data_calculate_ljzzj.go
  20. 22 0
      models/edb_data_calculate_ljzzy.go
  21. 404 0
      models/edb_data_calculate_phase_shift.go
  22. 1 0
      models/edb_data_calculate_qjjs.go
  23. 22 0
      models/edb_data_calculate_rjz.go
  24. 97 0
      models/edb_data_calculate_stl.go
  25. 21 0
      models/edb_data_calculate_tcz.go
  26. 231 0
      models/edb_data_rzd.go
  27. 6 0
      models/edb_data_table.go
  28. 10 0
      models/edb_info.go
  29. 5 0
      models/future_good/future_good_edb_data.go
  30. 21 1
      models/predict_edb_data_calculate_hbz.go
  31. 23 1
      models/predict_edb_data_calculate_ljzzj.go
  32. 21 0
      models/predict_edb_data_calculate_ljzzy.go
  33. 325 0
      models/predict_edb_data_calculate_phase_shift.go
  34. 20 0
      models/predict_edb_data_calculate_tcz.go
  35. 87 8
      models/trade_analysis/trade_analysis.go
  36. 153 0
      routers/commentsRouter.go
  37. 10 0
      routers/router.go
  38. 1 1
      services/base_from_calculate.go
  39. 12 2
      services/base_from_mysteel_chemical.go
  40. 314 0
      services/base_from_usda_fas.go
  41. 495 0
      services/edb_data_calculate_stl.go
  42. 19 7
      services/trade_analysis/trade_analysis_data.go
  43. 25 1
      static/pcsg_task.json
  44. 202 0
      utils/common.go
  45. 2 0
      utils/config.go
  46. 6 0
      utils/constants.go

+ 1 - 1
controllers/base_from_business.go

@@ -313,7 +313,7 @@ func (c *BusinessIndexController) Edit() {
 	}
 
 	// 根据指标名称和指标ID校验库中是否还存在其他同名指标
-	existEdbName, err := logic.CheckExistByEdbNameAndEdbInfoId(0, req.EdbInfoId, req.EdbName, c.Lang)
+	existEdbName, err := logic.CheckExistByEdbNameAndEdbInfoId(utils.EDB_INFO_TYPE, req.EdbInfoId, req.EdbName, c.Lang)
 	if err != nil {
 		br.Msg = "判断指标名称是否存在失败"
 		br.ErrMsg = "判断指标名称是否存在失败,Err:" + err.Error()

+ 39 - 3
controllers/base_from_calculate.go

@@ -226,7 +226,7 @@ func (this *CalculateController) Edit() {
 	}
 
 	// 根据指标名称和指标ID校验库中是否还存在其他同名指标
-	existEdbName, err := logic.CheckExistByEdbNameAndEdbInfoId(0, req.EdbInfoId, req.EdbName, this.Lang)
+	existEdbName, err := logic.CheckExistByEdbNameAndEdbInfoId(utils.EDB_INFO_TYPE, req.EdbInfoId, req.EdbName, this.Lang)
 	if err != nil {
 		br.Msg = "判断指标名称是否存在失败"
 		br.ErrMsg = "判断指标名称是否存在失败,Err:" + err.Error()
@@ -514,7 +514,7 @@ func (this *CalculateController) BatchSave() {
 	//加入缓存机制,避免创建同一个名称的指标 end
 
 	// 根据指标名称和指标ID校验库中是否还存在其他同名指标
-	existEdbName, err := logic.CheckExistByEdbNameAndEdbInfoId(0, req.EdbInfoId, req.EdbName, this.Lang)
+	existEdbName, err := logic.CheckExistByEdbNameAndEdbInfoId(utils.EDB_INFO_TYPE, req.EdbInfoId, req.EdbName, this.Lang)
 	if err != nil {
 		br.Msg = "判断指标名称是否存在失败"
 		br.ErrMsg = "判断指标名称是否存在失败,Err:" + err.Error()
@@ -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)
@@ -879,7 +882,7 @@ func (this *CalculateController) BatchEdit() {
 	}
 
 	// 根据指标名称和指标ID校验库中是否还存在其他同名指标
-	existEdbName, err := logic.CheckExistByEdbNameAndEdbInfoId(0, req.EdbInfoId, req.EdbName, this.Lang)
+	existEdbName, err := logic.CheckExistByEdbNameAndEdbInfoId(utils.EDB_INFO_TYPE, req.EdbInfoId, req.EdbName, this.Lang)
 	if err != nil {
 		br.Msg = "判断指标名称是否存在失败"
 		br.ErrMsg = "判断指标名称是否存在失败,Err:" + err.Error()
@@ -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)

+ 5 - 2
controllers/base_from_predict_calculate.go

@@ -127,7 +127,7 @@ func addPredictCalculate(br *models.BaseResponse, req models.EdbInfoCalculateSav
 	}
 
 	// 根据指标名称和指标ID校验库中是否还存在其他同名指标
-	existEdbName, err := logic.CheckExistByEdbNameAndEdbInfoId(1, 0, req.EdbName, lang)
+	existEdbName, err := logic.CheckExistByEdbNameAndEdbInfoId(utils.PREDICT_EDB_INFO_TYPE, 0, req.EdbName, lang)
 	if err != nil {
 		br.Msg = "判断指标名称是否存在失败"
 		br.ErrMsg = "判断指标名称是否存在失败,Err:" + err.Error()
@@ -610,7 +610,7 @@ func (this *PredictCalculateController) CalculateBatchSave() {
 	//加入缓存机制,避免创建同一个名称的指标 end
 
 	// 根据指标名称和指标ID校验库中是否还存在其他同名指标
-	existEdbName, err := logic.CheckExistByEdbNameAndEdbInfoId(1, req.EdbInfoId, req.EdbName, this.Lang)
+	existEdbName, err := logic.CheckExistByEdbNameAndEdbInfoId(utils.PREDICT_EDB_INFO_TYPE, req.EdbInfoId, req.EdbName, this.Lang)
 	if err != nil {
 		br.Msg = "判断指标名称是否存在失败"
 		br.ErrMsg = "判断指标名称是否存在失败,Err:" + err.Error()
@@ -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)

+ 709 - 0
controllers/base_from_rzd.go

@@ -0,0 +1,709 @@
+// @Author gmy 2024/8/13 16:01:00
+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"
+	"fmt"
+	"strconv"
+	"time"
+)
+
+type BaseFromRzdController struct {
+	BaseAuthController
+}
+
+// Add
+// @Title 新增睿姿得指标
+// @Description 新增粮油商务网指标
+// @router /add [post]
+func (this *BaseFromRzdController) Add() {
+	br := new(models.BaseResponse).Init()
+	var cacheKey string
+	defer func() {
+		utils.Rc.Delete(cacheKey)
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	source := utils.DATA_SOURCE_RZD
+	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
+	}
+	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.AddEdbDataFromRzd(req.EdbCode)
+		if err != nil {
+			br.Msg = "获取指标信息失败!"
+			br.ErrMsg = "获取指标信息失败 AddEdbDataFromSci99,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 *BaseFromRzdController) Refresh() {
+	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()
+	}()
+	source := utils.DATA_SOURCE_RZD
+	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
+	}
+
+	// 获取指标详情
+	edbInfo, err := models.GetEdbInfoByEdbCode(source, req.EdbCode)
+	if err != nil {
+		br.Msg = "指标不存在!"
+		br.ErrMsg = "指标不存在"
+		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
+	}
+	dataUpdateTime := time.Now().Format(utils.FormatDateTime)
+	utils.Rc.SetNX(cacheKey, 1, 1*time.Minute)
+	err = models.RefreshEdbDataFromBloomberg(req.EdbInfoId, req.EdbCode, req.StartDate)
+	if err != nil && err.Error() != utils.ErrNoRow() {
+		br.Msg = "刷新指标信息失败!"
+		br.ErrMsg = "刷新指标信息失败 RefreshEdbDataFromBloomberg,Err:" + err.Error()
+		return
+	}
+
+	// 更新指标最大最小值
+	erDataUpdateDate, err, errMsg := models.UnifiedModifyEdbInfoMaxAndMinInfoDataUpdate(edbInfo, dataUpdateTime)
+	if err != nil {
+		br.Msg = errMsg
+		br.ErrMsg = err.Error()
+		return
+	}
+	// 添加指标刷新成功日志
+	if erDataUpdateDate != "" {
+		_ = services.AddEdbInfoUpdateLog(edbInfo.EdbInfoId, 1, "", 1, "", 0, 0)
+	} else {
+		_ = services.AddEdbInfoUpdateLog(edbInfo.EdbInfoId, 1, "", 2, "未刷新到数据", 0, 0)
+	}
+
+	// 更新ES
+	go logic.UpdateEs(edbInfo.EdbInfoId)
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+}
+
+// AddRzdClassify
+// @Title 新增分类
+// @Description 获取分类
+// @Success 200 {object} models.BaseFromRzdClassify
+// @router /add/rzd/classify [post]
+func (this *BaseFromRzdController) AddRzdClassify() {
+	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 {
+		ClassifyName string `json:"ClassifyName"`
+		ParentId     int    `json:"parentId"`
+	}
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &reqData)
+	if err != nil {
+		br.ErrMsg = "无法解析请求体"
+		return
+	}
+	categoryName := reqData.ClassifyName
+	if categoryName == "" {
+		br.Msg = "请输入分类!"
+		br.ErrMsg = "请输入分类"
+		return
+	}
+	rzdClassify := models.BaseFromRzdClassify{
+		ClassifyName: categoryName,
+		ParentId:     reqData.ParentId,
+		CreateTime:   utils.GetCurrentTime(),
+		ModifyTime:   utils.GetCurrentTime(),
+	}
+
+	lyClassify, err := models.AddRzdClassify(&rzdClassify)
+	if err != nil {
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Data = lyClassify
+	br.Msg = "获取成功"
+}
+
+// GetRzdClassifyByName
+// @Title 获取分类
+// @Description 获取分类
+// @Success 200 {object} models.BaseFromRzdClassify
+// @router /get/rzd/classify/by/name [post]
+func (this *BaseFromRzdController) GetRzdClassifyByName() {
+	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 {
+		ClassifyName string `json:"ClassifyName"`
+	}
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &reqData)
+	if err != nil {
+		br.ErrMsg = "无法解析请求体"
+		return
+	}
+	categoryName := reqData.ClassifyName
+	if categoryName == "" {
+		br.Msg = "请输入分类!"
+		br.ErrMsg = "请输入分类"
+		return
+	}
+
+	lyClassify, err := models.GetRzdClassifyByName(categoryName)
+	if err != nil {
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Data = lyClassify
+	br.Msg = "获取成功"
+}
+
+// AddBatchRzdData
+// @Title 新增数据源指标数据
+// @Description 新增数据源指标数据
+// @Success 200 string "处理成功"
+// @router /add/batch/rzd/data [post]
+func (this *BaseFromRzdController) AddBatchRzdData() {
+	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.BaseFromRzdData
+	fmt.Println(string(this.Ctx.Input.RequestBody))
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+
+	err = models.AddRzdDataList(req)
+	if err != nil {
+		br.Msg = "新增指标数据失败!"
+		br.ErrMsg = "新增指标数据失败,Err:" + err.Error()
+		return
+	}
+	// 同步新增指标库数据
+	// 判断是否存在于指标库
+	// 获取指标详情
+	/* 调用方对 指标库数据做了维护处理
+	var dataList []models.EdbDataRzd
+	for _, data := range req {
+		edbInfo, err := models.GetEdbInfoByEdbCode(utils.DATA_SOURCE_RZD, data.IndexCode)
+		if err != nil {
+			br.Msg = "指标不存在!"
+			br.ErrMsg = "指标不存在"
+			return
+		}
+		if edbInfo.EdbInfoId == 0 {
+			continue
+		}
+		dataRzd := models.EdbDataRzd{
+			EdbInfoId:     data.BaseFromRzdIndexId,
+			DataTime:      data.DataTime,
+			Value:         data.Value,
+			EdbCode:       data.IndexCode,
+			CreateTime:    utils.GetCurrentTime(),
+			ModifyTime:    utils.GetCurrentTime(),
+			DataTimestamp: uint64(time.Now().UnixNano() / int64(time.Millisecond)),
+		}
+		dataList = append(dataList, dataRzd)
+	}
+	_ = models.AddRzdEdbDataList(dataList)*/
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "处理成功"
+}
+
+// AddRzdIndex
+// @Title 新增指标
+// @Description 新增指标
+// @Success 200 string "处理成功"
+// @router /add/rzd/index [post]
+func (this *BaseFromRzdController) AddRzdIndex() {
+	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.BaseFromRzdIndex
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+
+	indexId, err := models.AddRzdIndex(&req)
+	if err != nil {
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Data = indexId
+	br.Msg = "处理成功"
+}
+
+// GetRzdIndexDataByIndexIdAndDataTime
+// @Title 根据指标code和时间获取指标数据
+// @Description 根据指标code和时间获取指标数据
+// @Success 200 {object} models.BaseFromLyData
+// @router /get/rzd/index/data/by/code/and/time [post]
+func (this *BaseFromRzdController) GetRzdIndexDataByIndexIdAndDataTime() {
+	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 = "请输入指标code!"
+		br.ErrMsg = "请输入指标code"
+		return
+	}
+
+	dataTime := reqData.DataTime
+	if dataTime == "" {
+		br.Msg = "请输入时间!"
+		br.ErrMsg = "请输入时间"
+		return
+	}
+
+	rzdData, err := models.GetRzdDataByIndexCodeAndDataTime(indexCode, dataTime)
+	if err != nil {
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Data = rzdData
+	br.Msg = "获取成功"
+}
+
+// UpdateRzdDataById
+// @Title 更新数据源指标数据
+// @Description 更新数据源指标数据
+// @Success 200 string "处理成功"
+// @router /update/rzd/data/by/id [post]
+func (this *BaseFromRzdController) UpdateRzdDataById() {
+	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 {
+		Id    int     `json:"Id"`
+		Value float64 `json:"Value"`
+	}
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &reqData)
+	if err != nil {
+		br.ErrMsg = "无法解析请求体"
+		return
+	}
+
+	id := reqData.Id
+	if id == 0 {
+		br.Msg = "请输入id!"
+		br.ErrMsg = "请输入id"
+		return
+	}
+
+	value := reqData.Value
+	if value == 0 {
+		br.Msg = "请输入值!"
+		br.ErrMsg = "请输入值"
+		return
+	}
+
+	err = models.UpdateRzdDataById(id, value)
+	if err != nil {
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "处理成功"
+}
+
+// GetRzdEdbDataByIndexCodeAndDataTime
+// @Title 根据指标编码和精确日期获取指标库数据
+// @Description 根据指标编码和精确日期获取指标库数据
+// @Success 200 {object} []models.EdbDataRzd
+// @router /get/edb/rzd/data/by/code/and/time [post]
+func (this *BaseFromRzdController) GetRzdEdbDataByIndexCodeAndDataTime() {
+	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 = "获取成功"
+}
+
+// GetRzdEdbInfoByIndexCode
+// @Title 根据指标编码获取指标库指标
+// @Description 根据指标编码获取指标库指标
+// @Success 200 {object} []models.EdbDataRzd
+// @router /get/rzd/edb/info/by/code [post]
+func (this *BaseFromRzdController) GetRzdEdbInfoByIndexCode() {
+	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 = "获取成功"
+}
+
+// UpdateRzdEdbDataById
+// @Title 更新指标库数据 须根据指标编码和日期更新 仅适合月度数据
+// @Description 更新指标库数据 须根据指标编码和日期更新 仅适合月度数据
+// @Success 200 string "处理成功"
+// @router /update/rzd/edb/data/by/id [post]
+func (this *BaseFromRzdController) UpdateRzdEdbDataById() {
+	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 {
+		Id    int     `json:"Id"`
+		Value float64 `json:"Value"`
+	}
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &reqData)
+	if err != nil {
+		br.ErrMsg = "无法解析请求体"
+		return
+	}
+
+	id := reqData.Id
+	if id == 0 {
+		br.Msg = "请输入id!"
+		br.ErrMsg = "请输入id"
+		return
+	}
+
+	value := reqData.Value
+	if value == 0 {
+		br.Msg = "请输入值!"
+		br.ErrMsg = "请输入值"
+		return
+	}
+
+	err = models.UpdateRzdEdbDataById(id, value)
+	if err != nil {
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "处理成功"
+}
+
+// GetRzdIndexByCode
+// @Title 查询指标编码是否存在
+// @Description 查询指标编码是否存在
+// @Success 200 {object} models.BaseFromLyIndex
+// @router /get/rzd/index/by/code [post]
+func (this *BaseFromRzdController) GetRzdIndexByCode() {
+	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
+	}
+
+	indexCode := reqData.IndexCode
+	if indexCode == "" {
+		br.Msg = "请输入指标id!"
+		br.ErrMsg = "请输入指标id"
+		return
+	}
+
+	rzdIndex, err := models.GetRzdIndexByCode(indexCode)
+	if err != nil {
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Data = rzdIndex
+	br.Msg = "获取成功"
+}
+
+// AddBatchRzdEdbData
+// @Title 批量增加睿咨得指标库数据
+// @Description 批量增加睿咨得指标库数据
+// @Success 200 string "处理成功"
+// @router /add/batch/rzd/edb/data [post]
+func (this *BaseFromRzdController) AddBatchRzdEdbData() {
+	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.EdbDataRzd
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+
+	err = models.AddRzdEdbDataList(req)
+	if err != nil {
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "处理成功"
+}
+
+// UpdateRzdEdbData
+// @Title 修改指标数据
+// @Description 修改指标数据
+// @Success 200 string "处理成功"
+// @router /update/rzd/edb/data [post]
+func (this *BaseFromRzdController) UpdateRzdEdbData() {
+	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.BaseFromRzdData
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+
+	err = models.UpdateRzdData(&req)
+	if err != nil {
+		return
+	}
+
+	// 同步更新指标库数据
+	edbInfo, err := models.GetEdbInfoByEdbCode(utils.DATA_SOURCE_RZD, req.IndexCode)
+	if err != nil {
+		br.Msg = "指标不存在!"
+		br.ErrMsg = "指标不存在"
+		return
+	}
+	if edbInfo.EdbInfoId > 0 {
+		err = models.UpdateRzdEdbDataByIndexCodeAndDataTime(req.BaseFromRzdIndexId, req.DataTime, req.Value)
+		if err != nil {
+			return
+		}
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "处理成功"
+}

+ 174 - 0
controllers/base_from_usda_fas.go

@@ -0,0 +1,174 @@
+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"
+	"fmt"
+	"strconv"
+	"time"
+)
+
+// UsdaFasController 涌益咨询
+type UsdaFasController struct {
+	BaseAuthController
+}
+
+// Add
+// @Title 新增涌益咨询指标接口
+// @Description 新增涌益咨询指标接口
+// @Success 200 {object} models.AddEdbInfoReq
+// @router /add [post]
+func (this *UsdaFasController) Add() {
+	br := new(models.BaseResponse).Init()
+	var cacheKey string
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	usdaFas := new(models.BaseFromUsdaFas)
+	source := usdaFas.GetSource()
+	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
+	}
+	cacheKey = utils.CACHE_EDB_DATA_ADD + 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)
+	defer func() {
+		utils.Rc.Delete(cacheKey)
+	}()
+	err = usdaFas.Add(req.EdbCode)
+	if err != nil {
+		br.Msg = "获取指标信息失败!"
+		br.ErrMsg = "获取指标信息失败 AddEdbDataFromUsdaFas,Err:" + err.Error()
+		return
+	}
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	return
+}
+
+// Refresh
+// @Title 刷新涌益咨询指标接口
+// @Description 刷新涌益咨询指标接口
+// @Success 200 {object} models.RefreshEdbInfoReq
+// @router /refresh [post]
+func (this *UsdaFasController) Refresh() {
+	br := new(models.BaseResponse).Init()
+	var cacheKey string
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	usdaFas := new(models.BaseFromUsdaFas)
+	source := usdaFas.GetSource()
+	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
+	}
+
+	// 获取指标详情
+	edbInfo, err := models.GetEdbInfoByEdbCode(source, req.EdbCode)
+	if err != nil {
+		br.Msg = "指标不存在!"
+		br.ErrMsg = "指标不存在"
+		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)
+	defer func() {
+		utils.Rc.Delete(cacheKey)
+	}()
+	if req.EdbInfoId <= 0 {
+		req.EdbInfoId = edbInfo.EdbInfoId
+	}
+	err = usdaFas.Refresh(req.EdbInfoId, req.EdbCode, req.StartDate)
+	if err != nil && err.Error() != utils.ErrNoRow() {
+		br.Msg = "刷新指标信息失败!"
+		br.ErrMsg = "刷新指标信息失败 RefreshEdbDataFromusdaFas,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 = "获取成功"
+}
+
+// HandleExcelData
+// @Title 处理涌益咨询指标的接口
+// @Description 处理涌益咨询指标的接口
+// @Success 200 {object} models.HandleUsdaFasExcelDataReq
+// @router /handle/excel_data [post]
+func (this *UsdaFasController) HandleExcelData() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	var req models.HandleUsdaFasExcelDataReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+
+	// 处理excel表数据
+	err = services.HandleUsdaFasIndex(&req)
+	if err != nil {
+		fmt.Println("HandleMysteelIndex Err:" + err.Error())
+		br.Msg = "处理失败"
+		br.ErrMsg = "处理失败,Err:" + err.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "处理成功"
+}

+ 10 - 10
controllers/exchange_crawler.go

@@ -62,12 +62,12 @@ func (this *ExchangeCrawler) RefreshIne() {
 		if p.Rank > 0 && p.Rank < 40 && p.Participantabbr1 != "" {
 			//成交量
 			item.Rank = p.Rank
-			item.DealShortName = p.Participantabbr1
-			item.BuyShortName = p.Participantabbr2
-			item.SoldShortName = p.Participantabbr3
-			item.DealName = strings.Replace(fmt.Sprintf("%s", p.Participantabbr1+"_"+p.Instrumentid+"_成交量(手)"), " ", "", -1)
-			item.BuyName = strings.Replace(fmt.Sprintf("%s", p.Participantabbr2+"_"+p.Instrumentid+"_持买单量(手)"), " ", "", -1)
-			item.SoldName = strings.Replace(fmt.Sprintf("%s", p.Participantabbr3+"_"+p.Instrumentid+"_持卖单量(手)"), " ", "", -1)
+			item.DealShortName = strings.Trim(p.Participantabbr1, " ")
+			item.BuyShortName = strings.Trim(p.Participantabbr2, " ")
+			item.SoldShortName = strings.Trim(p.Participantabbr3, " ")
+			item.DealName = strings.Replace(fmt.Sprintf("%s", item.DealShortName+"_"+p.Instrumentid+"_成交量(手)"), " ", "", -1)
+			item.BuyName = strings.Replace(fmt.Sprintf("%s", item.BuyShortName+"_"+p.Instrumentid+"_持买单量(手)"), " ", "", -1)
+			item.SoldName = strings.Replace(fmt.Sprintf("%s", item.SoldShortName+"_"+p.Instrumentid+"_持卖单量(手)"), " ", "", -1)
 			item.DealCode = IneIndexCodeGenerator(item.DealShortName, item.DealName, p.Instrumentid, "deal")
 			item.BuyCode = IneIndexCodeGenerator(item.BuyShortName, item.BuyName, p.Instrumentid, "buy")
 			item.SoldCode = IneIndexCodeGenerator(item.SoldShortName, item.SoldName, p.Instrumentid, "sold")
@@ -114,9 +114,9 @@ func (this *ExchangeCrawler) RefreshIne() {
 		} else if p.Rank == 999 {
 			//Top 20
 			item.Rank = p.Rank
-			item.DealShortName = p.Participantabbr1
-			item.BuyShortName = p.Participantabbr2
-			item.SoldShortName = p.Participantabbr3
+			item.DealShortName = strings.Trim(p.Participantabbr1, " ")
+			item.BuyShortName = strings.Trim(p.Participantabbr2, " ")
+			item.SoldShortName = strings.Trim(p.Participantabbr3, " ")
 			item.DealName = strings.Replace(fmt.Sprintf("%s", "top20_"+p.Instrumentid+"_成交量(手)"), " ", "", -1)
 			item.BuyName = strings.Replace(fmt.Sprintf("%s", "top20_"+p.Instrumentid+"_持买单量(手)"), " ", "", -1)
 			item.SoldName = strings.Replace(fmt.Sprintf("%s", "top20_"+p.Instrumentid+"_持卖单量(手)"), " ", "", -1)
@@ -392,4 +392,4 @@ func IneIndexCodeGenerator(shortName, indexName, Instrumentid, suffix string) st
 		}
 	}
 	return strings.Replace(ineIndexCode, " ", "", -1)
-}
+}

+ 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=

+ 4 - 4
logic/predict_edb.go

@@ -66,7 +66,7 @@ func AddPredictEdbInfo(sourceEdbInfoId, classifyId int, edbName, dataDateType st
 	edbCode := sourceEdbInfo.EdbCode + "_" + time.Now().Format(utils.FormatShortDateTimeUnSpace)
 
 	// 根据指标名称和指标ID校验库中是否还存在其他同名指标
-	existEdbName, err := CheckExistByEdbNameAndEdbInfoId(0, 0, edbName, lang)
+	existEdbName, err := CheckExistByEdbNameAndEdbInfoId(utils.PREDICT_EDB_INFO_TYPE, 0, edbName, lang)
 	if err != nil {
 		errMsg = "判断指标名称是否存在失败"
 		err = errors.New("判断指标名称是否存在失败,Err:" + err.Error())
@@ -463,7 +463,7 @@ func EditPredictEdbInfo(edbInfoId, classifyId int, edbName, dataDateType string,
 	}
 
 	// 根据指标名称和指标ID校验库中是否还存在其他同名指标
-	existEdbName, err := CheckExistByEdbNameAndEdbInfoId(1, edbInfoId, edbName, lang)
+	existEdbName, err := CheckExistByEdbNameAndEdbInfoId(utils.PREDICT_EDB_INFO_TYPE, edbInfoId, edbName, lang)
 	if err != nil {
 		errMsg = "判断指标名称是否存在失败"
 		err = errors.New("判断指标名称是否存在失败,Err:" + err.Error())
@@ -1035,7 +1035,7 @@ func checkExistByEdbName(edbInfoType int, edbName, lang string) (has bool, err e
 	var pars []interface{}
 
 	condition += " AND edb_info_type=? "
-	pars = append(pars, 0)
+	pars = append(pars, edbInfoType)
 
 	switch lang {
 	case utils.EnLangVersion:
@@ -1165,7 +1165,7 @@ func AddStaticPredictEdbInfo(sourceEdbInfoId, classifyId int, edbName, frequency
 	edbCode := sourceEdbInfo.EdbCode + "_" + time.Now().Format(utils.FormatShortDateTimeUnSpace)
 
 	// 根据指标名称和指标ID校验库中是否还存在其他同名指标
-	existEdbName, err := CheckExistByEdbNameAndEdbInfoId(1, 0, edbName, lang)
+	existEdbName, err := CheckExistByEdbNameAndEdbInfoId(utils.PREDICT_EDB_INFO_TYPE, 0, edbName, lang)
 	if err != nil {
 		errMsg = "判断指标名称是否存在失败"
 		err = errors.New("判断指标名称是否存在失败,Err:" + err.Error())

+ 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)"`

+ 42 - 0
models/base_from_rzd_classify.go

@@ -0,0 +1,42 @@
+// @Author gmy 2024/8/7 9:26:00
+package models
+
+import (
+	"errors"
+	"github.com/beego/beego/v2/client/orm"
+)
+
+type BaseFromRzdClassify struct {
+	BaseFromRzdClassifyId int    `orm:"column(base_from_rzd_classify_id);pk"`
+	CreateTime            string `orm:"column(create_time)"`
+	ModifyTime            string `orm:"column(modify_time)"`
+	ClassifyName          string `orm:"column(classify_name)"`
+	ParentId              int    `orm:"column(parent_id)"`
+	Sort                  int    `orm:"column(sort)"`
+	ClassifyNameEn        string `orm:"column(classify_name_en)"`
+}
+
+func init() {
+	orm.RegisterModel(new(BaseFromRzdClassify))
+}
+
+// GetRzdClassifyByName 根据分类名称查询
+func GetRzdClassifyByName(classifyName string) (item *BaseFromRzdClassify, err error) {
+	o := orm.NewOrm()
+	sql := `SELECT * FROM base_from_rzd_classify WHERE classify_name=?`
+	err = o.Raw(sql, classifyName).QueryRow(&item)
+	if errors.Is(err, orm.ErrNoRows) {
+		return nil, nil
+	}
+	return
+}
+
+// AddRzdClassify 新增分类
+func AddRzdClassify(classify *BaseFromRzdClassify) (int64, error) {
+	o := orm.NewOrm()
+	id, err := o.Insert(classify)
+	if err != nil {
+		return 0, err
+	}
+	return id, nil
+}

+ 71 - 0
models/base_from_rzd_data.go

@@ -0,0 +1,71 @@
+// @Author gmy 2024/10/21 9:50:00
+package models
+
+import (
+	"errors"
+	"github.com/beego/beego/v2/client/orm"
+)
+
+type BaseFromRzdData struct {
+	BaseFromRzdDataId  int     `orm:"column(base_from_rzd_data_id);pk"`
+	BaseFromRzdIndexId int     `orm:"column(base_from_rzd_index_id)"`
+	CreateTime         string  `orm:"column(create_time)"`
+	DataTime           string  `orm:"column(data_time)"`
+	IndexCode          string  `orm:"column(index_code)"`
+	ModifyTime         string  `orm:"column(modify_time)"`
+	Value              float64 `orm:"column(value)"`
+}
+
+func init() {
+	orm.RegisterModel(new(BaseFromRzdData))
+}
+
+// AddRzdDataList 批量插入数据记录列表
+func AddRzdDataList(items []BaseFromRzdData) (err error) {
+	o := orm.NewOrm()
+	_, err = o.InsertMulti(len(items), items)
+	return
+}
+
+// GetRzdDataByIndexCodeAndDataTime 根据指标id和数据日期查询数据
+func GetRzdDataByIndexCodeAndDataTime(indexCode string, dataTime string) (items BaseFromRzdData, err error) {
+	o := orm.NewOrm()
+	sql := `SELECT * FROM base_from_rzd_data WHERE index_code=? AND data_time=?`
+	err = o.Raw(sql, indexCode, dataTime).QueryRow(&items) // 使用 QueryRow
+	if errors.Is(err, orm.ErrNoRows) {
+		return items, nil
+	}
+	if err != nil {
+		return items, err
+	}
+	return
+}
+
+// UpdateRzdDataById 根据主键id更新数据
+func UpdateRzdDataById(dataId int, value float64) (err error) {
+	o := orm.NewOrm()
+	sql := `UPDATE base_from_rzd_data SET value=? WHERE base_from_rzd_data_id=?`
+	_, err = o.Raw(sql, value, dataId).Exec()
+	return
+}
+
+// GetBaseFromRzdDataByCondition 添加查询
+func GetBaseFromRzdDataByCondition(condition string, pars []interface{}) (items []BaseFromRzdData, err error) {
+	sql := `SELECT * FROM base_from_rzd_data WHERE 1=1 `
+	o := orm.NewOrm()
+	if condition != "" {
+		sql += condition
+	}
+	_, err = o.Raw(sql, pars...).QueryRows(&items)
+	if errors.Is(err, orm.ErrNoRows) {
+		return nil, nil
+	}
+	return
+}
+
+// UpdateRzdData 修改睿姿得数据
+func UpdateRzdData(item *BaseFromRzdData) (err error) {
+	o := orm.NewOrm()
+	_, err = o.Update(item)
+	return
+}

+ 54 - 0
models/base_from_rzd_index.go

@@ -0,0 +1,54 @@
+// Package models
+// @Author gmy 2024/8/7 9:38:00
+package models
+
+import (
+	"errors"
+	"github.com/beego/beego/v2/client/orm"
+)
+
+type BaseFromRzdIndex struct {
+	BaseFromRzdIndexId   int    `orm:"column(base_from_rzd_index_id);pk"`
+	CreateTime           string `orm:"column(create_time)"`
+	ModifyTime           string `orm:"column(modify_time)"`
+	BaseFromLyClassifyId int    `orm:"column(base_from_rzd_classify_id)"`
+	IndexCode            string `orm:"column(index_code)"`
+	IndexName            string `orm:"column(index_name)"`
+	Frequency            string `orm:"column(frequency)"`
+	Unit                 string `orm:"column(unit)"`
+}
+
+// 在 init 函数中注册模型
+func init() {
+	orm.RegisterModel(new(BaseFromRzdIndex))
+}
+
+// AddRzdIndexList 批量插入指标记录列表
+func AddRzdIndexList(items []*BaseFromRzdIndex) (err error) {
+	o := orm.NewOrm()
+	_, err = o.InsertMulti(len(items), items)
+	return
+}
+
+// AddRzdIndex 添加指标
+func AddRzdIndex(item *BaseFromRzdIndex) (int64, error) {
+	o := orm.NewOrm()
+	id, err := o.Insert(item)
+	if err != nil {
+		return 0, err
+	}
+	return id, nil
+}
+
+// GetRzdIndexByCode 查询指标编码是否存在
+func GetRzdIndexByCode(indexCode string) (item *BaseFromRzdIndex, err error) {
+	o := orm.NewOrm()
+	sql := `SELECT * FROM base_from_rzd_index WHERE index_code=?`
+	err = o.Raw(sql, indexCode).QueryRow(&item)
+
+	if errors.Is(err, orm.ErrNoRows) {
+		return nil, nil
+	}
+
+	return
+}

+ 353 - 0
models/base_from_usda_fas.go

@@ -0,0 +1,353 @@
+package models
+
+import (
+	"eta/eta_index_lib/utils"
+	"fmt"
+	"strconv"
+	"strings"
+	"time"
+
+	"github.com/beego/beego/v2/client/orm"
+)
+
+// BaseFromUsdaFas 美国农业部
+type BaseFromUsdaFas struct{}
+
+type BaseFromUsdaFasData struct {
+	BaseFromUsdaFasDataId  int `orm:"column(base_from_usda_fas_data_id);pk"`
+	BaseFromUsdaFasIndexId int
+	IndexCode              string
+	DataTime               string
+	Value                  string
+	CreateTime             time.Time
+	ModifyTime             time.Time
+	DataTimestamp          int64
+}
+
+func GetBaseFromUsdaFasDataByCondition(condition string, pars []interface{}) (list []*BaseFromUsdaFasData, err error) {
+	o := orm.NewOrm()
+	sql := `SELECT * FROM base_from_usda_fas_data WHERE 1=1 `
+	if condition != "" {
+		sql += condition
+	}
+	_, err = o.Raw(sql, pars).QueryRows(&list)
+	return
+}
+
+// Add 添加
+func (obj BaseFromUsdaFas) Add(edbCode string) (err error) {
+	o := orm.NewOrm()
+
+	var condition string
+	var pars []interface{}
+	if edbCode != "" {
+		condition += " AND index_code=? "
+		pars = append(pars, edbCode)
+	}
+	UsdaFasBaseDataAll, err := GetBaseFromUsdaFasDataByCondition(condition, pars)
+	if err != nil && err.Error() != utils.ErrNoRow() {
+		return
+	}
+	var isAdd bool
+	addSql := ` INSERT INTO edb_data_usda_fas(edb_info_id,edb_code,data_time,value,create_time,modify_time,data_timestamp) values `
+	existMap := make(map[string]string)
+	for _, sv := range UsdaFasBaseDataAll {
+		eDate := sv.DataTime
+		dataTime, err := time.Parse(utils.FormatDate, eDate)
+		if err != nil {
+			return err
+		}
+		timestamp := dataTime.UnixNano() / 1e6
+		timeStr := fmt.Sprintf("%d", timestamp)
+		if _, ok := existMap[eDate]; !ok {
+			addSql += GetAddSql("0", edbCode, eDate, timeStr, sv.Value)
+			isAdd = true
+		}
+		existMap[eDate] = sv.Value
+	}
+	if isAdd {
+		addSql = strings.TrimRight(addSql, ",")
+		utils.FileLog.Info("addSql:" + addSql)
+		_, err = o.Raw(addSql).Exec()
+		if err != nil {
+			return err
+		}
+	}
+	return
+}
+
+// Refresh 刷新涌益咨询指标数据
+func (obj BaseFromUsdaFas) Refresh(edbInfoId int, edbCode, startDate string) (err error) {
+	source := obj.GetSource()
+	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)
+	}
+
+	UsdaFasDataList, err := GetBaseFromUsdaFasDataByCondition(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, 0, 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_usda_fas(edb_info_id,edb_code,data_time,value,create_time,modify_time,data_timestamp) values `
+	var isAdd bool
+	for _, v := range UsdaFasDataList {
+		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, 0, 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, 0, existMap, isFindConfigDateRealData)
+
+	if isAdd {
+		addSql = strings.TrimRight(addSql, ",")
+		_, err = o.Raw(addSql).Exec()
+		if err != nil {
+			fmt.Println("RefreshEdbDataFromBaiinfo add Err", err.Error())
+			return
+		}
+	}
+	return
+}
+
+// GetSource 获取来源编码id
+func (obj BaseFromUsdaFas) GetSource() int {
+	return utils.DATA_SOURCE_USDA_FAS
+}
+
+// GetSourceName 获取来源名称
+func (obj BaseFromUsdaFas) GetSourceName() string {
+	return utils.DATA_SOURCE_NAME_USDA_FAS
+}
+
+type BaseFromUsdaFasIndex struct {
+	BaseFromUsdaFasIndexId int64 `orm:"column(base_from_usda_fas_index_id);pk"`
+	IndexCode              string
+	IndexName              string
+	Frequency              string
+	Unit                   string
+	StartDate              string
+	EndDate                string
+	ClassifyId             int64
+	Sort                   int
+	BaseFileName           string
+	RenameFileName         string
+	TerminalCode           string
+	Country                string `description:"国家"`
+	Commodity              string `description:"属性"`
+	CreateTime             time.Time
+	ModifyTime             time.Time
+}
+
+type BaseFromUsdaFasIndexList struct {
+	BaseFromUsdaFasIndexId int64 `orm:"column(base_from_usda_fas_index_id);pk"`
+	IndexCode              string
+	IndexName              string
+	Frequency              string
+	Unit                   string
+	Sort                   int
+	ClassifyId             int64
+	StartDate              string
+	EndDate                string
+	TerminalCode           string
+	CreateTime             string
+	ModifyTime             string
+}
+
+func (y *BaseFromUsdaFasData) GetByIndexCode(indexCode string) (list []*BaseFromUsdaFasData, err error) {
+	o := orm.NewOrm()
+	sql := ` SELECT * FROM base_from_usda_fas_data WHERE index_code=? `
+	_, err = o.Raw(sql, indexCode).QueryRows(&list)
+	return
+}
+
+func (y *BaseFromUsdaFasData) AddMulti(item []*BaseFromUsdaFasData) (err error) {
+	o := orm.NewOrm()
+	_, err = o.InsertMulti(1, item)
+	return
+}
+
+// Update 修改
+func (y *BaseFromUsdaFasData) Update(updateCols []string) (err error) {
+	o := orm.NewOrm()
+	_, err = o.Update(y, updateCols...)
+	return
+}
+
+// HandleUsdaFasExcelData 涌益咨询的excel数据
+type HandleUsdaFasExcelData struct {
+	ClassifyName       string `description:"指标目录"`
+	ParentClassifyName string `description:"父级指标目录"`
+	ClassifySort       int    `description:"指标目录排序号"`
+	IndexName          string `description:"指标名称"`
+	IndexCode          string `description:"指标编码"`
+	Unit               string `description:"单位"`
+	Sort               int    `description:"排序号"`
+	Frequency          string `description:"频度"`
+	Country            string `description:"国家"`
+	Commodity          string `description:"属性"`
+	ExcelDataMap       map[string]string
+}
+
+type HandleUsdaFasExcelDataReq struct {
+	List         []*HandleUsdaFasExcelData
+	TerminalCode string `description:"编码"`
+}
+
+func (y *BaseFromUsdaFasData) GetMaxAndMinDateByIndexCode(indexCode string) (item *EdbInfoMaxAndMinInfo, err error) {
+	o := orm.NewOrm()
+	sql := ` SELECT MIN(data_time) AS min_date,MAX(data_time) AS max_date,MIN(value) AS min_value,MAX(value) AS max_value FROM base_from_usda_fas_data WHERE index_code=? `
+	err = o.Raw(sql, indexCode).QueryRow(&item)
+	var latest_value float64
+	sql = ` SELECT value AS latest_value FROM %s WHERE index_code=? ORDER BY data_time DESC LIMIT 1 `
+	sql = fmt.Sprintf(sql, "base_from_usda_fas_data")
+	err = o.Raw(sql, indexCode).QueryRow(&latest_value)
+	item.LatestValue = latest_value
+	return
+}
+
+func (y *BaseFromUsdaFasIndex) ModifyIndexMaxAndMinDate(indexCode string, item *EdbInfoMaxAndMinInfo) (err error) {
+	o := orm.NewOrm()
+	sql := ` UPDATE base_from_usda_fas_index SET start_date=?,end_date=?, end_value=?, modify_time=NOW() WHERE index_code=? `
+	_, err = o.Raw(sql, item.MinDate, item.MaxDate, item.LatestValue, indexCode).Exec()
+	return
+}
+
+func (y *BaseFromUsdaFasIndex) GetByIndexCode(indexCode string) (item *BaseFromUsdaFasIndex, err error) {
+	o := orm.NewOrm()
+	sql := ` SELECT * FROM base_from_usda_fas_index WHERE index_code=? `
+	err = o.Raw(sql, indexCode).QueryRow(&item)
+	return
+}
+
+func (y *BaseFromUsdaFasIndex) Add() (lastId int64, err error) {
+	o := orm.NewOrm()
+	lastId, err = o.Insert(y)
+	return
+}
+
+// Update 修改
+func (y *BaseFromUsdaFasIndex) Update(updateCols []string) (err error) {
+	o := orm.NewOrm()
+	_, err = o.Update(y, updateCols...)
+
+	return
+}
+
+// BaseFromUsdaFasClassify UsdaFas原始数据分类表
+type BaseFromUsdaFasClassify struct {
+	ClassifyId      int64     `orm:"column(classify_id);pk"`
+	ClassifyName    string    `description:"分类名称"`
+	ClassifyNameEn  string    `description:"分类名称"`
+	ParentId        int       `description:"父级id"`
+	SysUserId       int       `description:"创建人id"`
+	SysUserRealName string    `description:"创建人姓名"`
+	Level           int       `description:"层级"`
+	Sort            int       `description:"排序字段,越小越靠前,默认值:10"`
+	ModifyTime      time.Time `description:"修改时间"`
+	CreateTime      time.Time `description:"创建时间"`
+}
+
+func (y *BaseFromUsdaFasClassify) Add() (lastId int64, err error) {
+	o := orm.NewOrm()
+	lastId, err = o.Insert(y)
+	return
+}
+
+// Update 修改
+func (y *BaseFromUsdaFasClassify) Update(updateCols []string) (err error) {
+	o := orm.NewOrm()
+	_, err = o.Update(y, updateCols...)
+
+	return
+}
+
+func (y *BaseFromUsdaFasClassify) GetByClassifyName(classifyName string) (item *BaseFromUsdaFasClassify, err error) {
+	o := orm.NewOrm()
+	sql := ` SELECT * FROM base_from_usda_fas_classify WHERE classify_name=? `
+	err = o.Raw(sql, classifyName).QueryRow(&item)
+	return
+}
+
+func (y *BaseFromUsdaFasClassify) GetParentClassify() (items []*BaseFromUsdaFasClassify, err error) {
+	o := orm.NewOrm()
+	sql := ` SELECT * FROM base_from_usda_fas_classify WHERE parent_id=0 `
+	_, err = o.Raw(sql).QueryRows(&items)
+	return
+}

+ 5 - 0
models/db.go

@@ -170,8 +170,13 @@ func initBaseIndex() {
 		new(BaseFromOilchemData),
 		new(BaseFromFenweiClassify),
 		new(EdbDataTradeAnalysis),
+		new(BaseFromUsdaFasIndex),
+		new(BaseFromUsdaFasData),
+		new(BaseFromUsdaFasClassify),
 		new(BaseFromHisugarIndex),
 		new(BaseFromHisugarData),
+		new(EdbDataCalculateStl),
+		new(CalculateStlConfig),
 	)
 }
 

+ 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
+}

+ 1 - 0
models/edb_data_calculate_qjjs.go

@@ -854,6 +854,7 @@ func GetRangeAnalysisChartDataByEdbInfo(fromEdbInfo *EdbInfo, calculateFormula R
 					dealDataList = append(dealDataList, v)
 				}
 			}
+			newDataList = dealDataList
 		case 2:
 			for i, v := range newDataList {
 				if utils.CompareFloatByOpStrings(calculateFormula.UnNormalDataConf.Formula, v.Value, calculateFormula.UnNormalDataConf.Value) {

+ 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()

+ 231 - 0
models/edb_data_rzd.go

@@ -0,0 +1,231 @@
+// @Author gmy 2024/9/14 16:13:00
+package models
+
+import (
+	"errors"
+	"eta/eta_index_lib/utils"
+	"fmt"
+	"github.com/beego/beego/v2/client/orm"
+	"strconv"
+	"strings"
+	"time"
+)
+
+type EdbDataRzd struct {
+	edbDataId     int     `orm:"column(edb_data_id);pk"`
+	CreateTime    string  `orm:"column(create_time)"`
+	ModifyTime    string  `orm:"column(modify_time)"`
+	EdbInfoId     int     `orm:"column(edb_info_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(EdbDataRzd))
+}
+
+// GetRzdEdbDataByIndexCodeAndDataTime 根据指标编码和时间获取指标库数据
+func GetRzdEdbDataByIndexCodeAndDataTime(indexCode string, dataTime string) (items []EdbDataRzd, err error) {
+	o := orm.NewOrm()
+	sql := `SELECT * FROM edb_data_rzd WHERE edb_code=? AND data_time=?`
+	_, err = o.Raw(sql, indexCode, dataTime).QueryRows(&items)
+	if errors.Is(err, orm.ErrNoRows) {
+		return nil, nil
+	}
+	return
+}
+
+// UpdateRzdEdbDataById 更新指标库数据 须根据指标编码和日期更新 仅适合月度数据
+func UpdateRzdEdbDataById(id int, value float64) (err error) {
+	o := orm.NewOrm()
+	sql := `UPDATE edb_data_rzd SET value=? WHERE edb_data_id=?`
+	_, err = o.Raw(sql, value, id).Exec()
+	return
+}
+
+// UpdateRzdEdbDataByIndexCodeAndDataTime 根据指标编码和时间更新数据
+func UpdateRzdEdbDataByIndexCodeAndDataTime(indexCode int, dataTime string, value float64) (err error) {
+	o := orm.NewOrm()
+	sql := `UPDATE edb_data_rzd SET value=? WHERE edb_info_id=? AND data_time=?`
+	_, err = o.Raw(sql, value, indexCode, dataTime).Exec()
+	return
+}
+
+// AddRzdEdbDataList 新增指标库数据
+func AddRzdEdbDataList(items []EdbDataRzd) (err error) {
+	o := orm.NewOrm()
+	_, err = o.InsertMulti(len(items), items)
+	return
+}
+
+// AddEdbDataFromRzd 新增指标数据
+func AddEdbDataFromRzd(edbCode string) (err error) {
+	o := orm.NewOrm()
+
+	var condition string
+	var pars []interface{}
+
+	if edbCode != "" {
+		condition += " AND index_code = ? "
+		pars = append(pars, edbCode)
+	}
+
+	dataAll, err := GetBaseFromRzdDataByCondition(condition, pars)
+	if err != nil {
+		return err
+	}
+	dataLen := len(dataAll)
+
+	existMap := make(map[string]string)
+	if dataLen > 0 {
+		var isAdd bool
+		addSql := ` INSERT INTO edb_data_rzd (edb_info_id,edb_code,data_time,value,create_time,modify_time,data_timestamp) values `
+		for i := 0; i < dataLen; i++ {
+			item := dataAll[i]
+			eDate := item.DataTime
+			sValue := utils.SubFloatToString(item.Value, 4)
+			if sValue != "" {
+				if _, ok := existMap[eDate]; !ok {
+					dataTime, err := time.ParseInLocation(utils.FormatDate, eDate, time.Local)
+					if err != nil {
+						return err
+					}
+					timestamp := dataTime.UnixNano() / 1e6
+					timeStr := fmt.Sprintf("%d", timestamp)
+					addSql += GetAddSql("0", edbCode, eDate, timeStr, sValue)
+					isAdd = true
+				}
+			}
+			existMap[eDate] = eDate
+		}
+		if isAdd {
+			addSql = strings.TrimRight(addSql, ",")
+			utils.FileLog.Info("addSql:" + addSql)
+			_, err = o.Raw(addSql).Exec()
+			if err != nil {
+				return err
+			}
+		}
+	}
+	return
+}
+
+func RefreshEdbDataFromRzd(edbInfoId int, edbCode, startDate string) (err error) {
+	source := utils.DATA_SOURCE_RZD
+	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)
+	}
+
+	dataList, err := GetBaseFromRzdDataByCondition(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_rzd(edb_info_id,edb_code,data_time,value,create_time,modify_time,data_timestamp) values `
+	var isAdd bool
+	addMap := make(map[string]string)
+	for _, v := range dataList {
+		item := v
+		eDate := item.DataTime
+		sValue := utils.SubFloatToString(item.Value, 4)
+
+		dataTime, err := time.ParseInLocation(utils.FormatDate, eDate, time.Local)
+		if err != nil {
+			return err
+		}
+		if findItem, ok := existMap[v.DataTime]; !ok {
+			if sValue != "" {
+				timestamp := dataTime.UnixNano() / 1e6
+				timeStr := fmt.Sprintf("%d", timestamp)
+				saveValue := sValue
+
+				if _, addOk := addMap[eDate]; !addOk {
+					addSql += GetAddSql(edbInfoIdStr, edbCode, eDate, timeStr, saveValue)
+					isAdd = true
+				}
+			}
+		} else {
+			if findItem != nil && utils.SubFloatToString(findItem.Value, 4) != sValue {
+				err = ModifyEdbDataById(source, subSource, findItem.EdbDataId, sValue)
+				if err != nil {
+					return err
+				}
+			}
+		}
+		addMap[v.DataTime] = v.DataTime
+
+		// 下面代码主要目的是处理掉手动插入的数据判断
+		{
+			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 {
+			return err
+		}
+	}
+	return
+}

+ 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 {

+ 10 - 0
models/edb_info.go

@@ -1691,3 +1691,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()

+ 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()

+ 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
 }
 

+ 153 - 0
routers/commentsRouter.go

@@ -241,6 +241,132 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_index_lib/controllers:BaseFromRzdController"] = append(beego.GlobalControllerRouter["eta/eta_index_lib/controllers:BaseFromRzdController"],
+        beego.ControllerComments{
+            Method: "Add",
+            Router: `/add`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_index_lib/controllers:BaseFromRzdController"] = append(beego.GlobalControllerRouter["eta/eta_index_lib/controllers:BaseFromRzdController"],
+        beego.ControllerComments{
+            Method: "AddBatchRzdData",
+            Router: `/add/batch/rzd/data`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_index_lib/controllers:BaseFromRzdController"] = append(beego.GlobalControllerRouter["eta/eta_index_lib/controllers:BaseFromRzdController"],
+        beego.ControllerComments{
+            Method: "AddBatchRzdEdbData",
+            Router: `/add/batch/rzd/edb/data`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_index_lib/controllers:BaseFromRzdController"] = append(beego.GlobalControllerRouter["eta/eta_index_lib/controllers:BaseFromRzdController"],
+        beego.ControllerComments{
+            Method: "AddRzdClassify",
+            Router: `/add/rzd/classify`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_index_lib/controllers:BaseFromRzdController"] = append(beego.GlobalControllerRouter["eta/eta_index_lib/controllers:BaseFromRzdController"],
+        beego.ControllerComments{
+            Method: "AddRzdIndex",
+            Router: `/add/rzd/index`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_index_lib/controllers:BaseFromRzdController"] = append(beego.GlobalControllerRouter["eta/eta_index_lib/controllers:BaseFromRzdController"],
+        beego.ControllerComments{
+            Method: "GetRzdEdbDataByIndexCodeAndDataTime",
+            Router: `/get/edb/rzd/data/by/code/and/time`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_index_lib/controllers:BaseFromRzdController"] = append(beego.GlobalControllerRouter["eta/eta_index_lib/controllers:BaseFromRzdController"],
+        beego.ControllerComments{
+            Method: "GetRzdClassifyByName",
+            Router: `/get/rzd/classify/by/name`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_index_lib/controllers:BaseFromRzdController"] = append(beego.GlobalControllerRouter["eta/eta_index_lib/controllers:BaseFromRzdController"],
+        beego.ControllerComments{
+            Method: "GetRzdEdbInfoByIndexCode",
+            Router: `/get/rzd/edb/info/by/code`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_index_lib/controllers:BaseFromRzdController"] = append(beego.GlobalControllerRouter["eta/eta_index_lib/controllers:BaseFromRzdController"],
+        beego.ControllerComments{
+            Method: "GetRzdIndexByCode",
+            Router: `/get/rzd/index/by/code`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_index_lib/controllers:BaseFromRzdController"] = append(beego.GlobalControllerRouter["eta/eta_index_lib/controllers:BaseFromRzdController"],
+        beego.ControllerComments{
+            Method: "GetRzdIndexDataByIndexIdAndDataTime",
+            Router: `/get/rzd/index/data/by/code/and/time`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_index_lib/controllers:BaseFromRzdController"] = append(beego.GlobalControllerRouter["eta/eta_index_lib/controllers:BaseFromRzdController"],
+        beego.ControllerComments{
+            Method: "Refresh",
+            Router: `/refresh`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_index_lib/controllers:BaseFromRzdController"] = append(beego.GlobalControllerRouter["eta/eta_index_lib/controllers:BaseFromRzdController"],
+        beego.ControllerComments{
+            Method: "UpdateRzdDataById",
+            Router: `/update/rzd/data/by/id`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_index_lib/controllers:BaseFromRzdController"] = append(beego.GlobalControllerRouter["eta/eta_index_lib/controllers:BaseFromRzdController"],
+        beego.ControllerComments{
+            Method: "UpdateRzdEdbData",
+            Router: `/update/rzd/edb/data`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_index_lib/controllers:BaseFromRzdController"] = append(beego.GlobalControllerRouter["eta/eta_index_lib/controllers:BaseFromRzdController"],
+        beego.ControllerComments{
+            Method: "UpdateRzdEdbDataById",
+            Router: `/update/rzd/edb/data/by/id`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_index_lib/controllers:BaseFromTradeAnalysisController"] = append(beego.GlobalControllerRouter["eta/eta_index_lib/controllers:BaseFromTradeAnalysisController"],
         beego.ControllerComments{
             Method: "EdbRefresh",
@@ -1726,6 +1852,33 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_index_lib/controllers:UsdaFasController"] = append(beego.GlobalControllerRouter["eta/eta_index_lib/controllers:UsdaFasController"],
+        beego.ControllerComments{
+            Method: "Add",
+            Router: `/add`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_index_lib/controllers:UsdaFasController"] = append(beego.GlobalControllerRouter["eta/eta_index_lib/controllers:UsdaFasController"],
+        beego.ControllerComments{
+            Method: "HandleExcelData",
+            Router: `/handle/excel_data`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_index_lib/controllers:UsdaFasController"] = append(beego.GlobalControllerRouter["eta/eta_index_lib/controllers:UsdaFasController"],
+        beego.ControllerComments{
+            Method: "Refresh",
+            Router: `/refresh`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_index_lib/controllers:WindController"] = append(beego.GlobalControllerRouter["eta/eta_index_lib/controllers:WindController"],
         beego.ControllerComments{
             Method: "Add",

+ 10 - 0
routers/router.go

@@ -292,6 +292,11 @@ func init() {
 				&controllers.BaseFromLyController{},
 			),
 		),
+		beego.NSNamespace("/rzd",
+			beego.NSInclude(
+				&controllers.BaseFromRzdController{},
+			),
+		),
 		beego.NSNamespace("/oilchem",
 			beego.NSInclude(
 				&controllers.OilchemController{},
@@ -302,6 +307,11 @@ func init() {
 				&controllers.BaseFromTradeAnalysisController{},
 			),
 		),
+		beego.NSNamespace("/usda_fas",
+			beego.NSInclude(
+				&controllers.UsdaFasController{},
+			),
+		),
 		beego.NSNamespace("/hisugar",
 			beego.NSInclude(
 				&controllers.HisugarController{},

+ 1 - 1
services/base_from_calculate.go

@@ -947,7 +947,7 @@ func EdbCalculateAdd(req models.EdbInfoCalculateSaveReq, lang string) (edbInfo *
 	}
 
 	// 校验指标名称是否存在
-	existEdbName, err := logic.CheckExistByEdbNameAndEdbInfoId(0, 0, req.EdbName, lang)
+	existEdbName, err := logic.CheckExistByEdbNameAndEdbInfoId(utils.EDB_INFO_TYPE, 0, req.EdbName, lang)
 	if err != nil {
 		errMsg = "判断指标名称是否存在失败,Err:" + err.Error()
 		err = fmt.Errorf("判断指标名称是否存在失败")

+ 12 - 2
services/base_from_mysteel_chemical.go

@@ -553,7 +553,12 @@ func GetEdbDataFromMySteelChemical(indexCodes []string, startTime, endTime, orde
 		err = er
 		return
 	}
-	postUrl := `https://mds.mysteel.com/dynamic/order/api/fcAbRA`
+	// postUrl := `https://mds.mysteel.com/dynamic/order/api/fcAbRA`
+	postUrl := utils.MySteelChemicalApiUrl
+	if postUrl == "" {
+		err = errors.New("钢联化工接口url未配置")
+		return
+	}
 	body, err := MySteelChemicalPost(postUrl, "data", postData)
 	if err != nil {
 		return
@@ -605,7 +610,12 @@ func getPageIndexInfoMap(pageNum, pageSize int, includeInfo bool) (item *models.
 		err = er
 		return
 	}
-	postUrl := `https://mds.mysteel.com/dynamic/order/api/fcAbRA`
+	// postUrl := `https://mds.mysteel.com/dynamic/order/api/fcAbRA`
+	postUrl := utils.MySteelChemicalApiUrl
+	if postUrl == "" {
+		err = errors.New("钢联化工接口url未配置")
+		return
+	}
 	body, er := MySteelChemicalPost(postUrl, "info", postData)
 	if er != nil {
 		err = er

+ 314 - 0
services/base_from_usda_fas.go

@@ -0,0 +1,314 @@
+package services
+
+import (
+	"eta/eta_index_lib/logic"
+	"eta/eta_index_lib/models"
+	"eta/eta_index_lib/services/alarm_msg"
+	"eta/eta_index_lib/utils"
+	"fmt"
+	"strconv"
+	"strings"
+	"time"
+)
+
+// HandleUsdaFasIndex 处理美国农业部的excel数据
+func HandleUsdaFasIndex(req *models.HandleUsdaFasExcelDataReq) (err error) {
+	errMsgList := make([]string, 0)
+	defer func() {
+		if len(errMsgList) > 0 {
+			msg := fmt.Sprint("数据源-美国农业部数据处理失败,err:", strings.Join(errMsgList, "\n"))
+			utils.FileLog.Info(msg)
+			go alarm_msg.SendAlarmMsg(msg, 3)
+		}
+	}()
+	// 查询所有的一级分类
+	classifyObj := new(models.BaseFromUsdaFasClassify)
+	classifyList, err := classifyObj.GetParentClassify()
+	if err != nil {
+		err = fmt.Errorf("查询一级目录信息失败 Err:%s", err)
+		return
+	}
+	classifyMap := make(map[string]int, 0)
+	for _, v := range classifyList {
+		classifyMap[v.ClassifyName] = int(v.ClassifyId)
+	}
+
+	for _, v := range req.List {
+		if v.IndexName == "" || v.IndexCode == "" {
+			errMsgList = append(errMsgList, fmt.Sprintf("新增指标异常,指标编码%s或者指标ID%s为空:", v.IndexCode, v.IndexName))
+			continue
+		}
+		err = handleUsdaFasIndex(v, req.TerminalCode, classifyMap)
+		if err != nil {
+			errMsgList = append(errMsgList, fmt.Sprintf("新增指标异常,指标编码:%s, Err: %s", v.IndexCode, err))
+			return
+		}
+	}
+	return
+}
+
+func handleUsdaFasIndex(req *models.HandleUsdaFasExcelData, terminalCode string, classifyMap map[string]int) (err error) {
+	indexName := req.IndexName
+	indexCode := req.IndexCode
+	excelDataMap := req.ExcelDataMap
+	errMsgList := make([]string, 0)
+	defer func() {
+		if len(errMsgList) > 0 {
+			msg := fmt.Sprint("数据源-美国农业部数据处理失败,err:", strings.Join(errMsgList, "\n"))
+			utils.FileLog.Info(msg)
+			go alarm_msg.SendAlarmMsg(msg, 3)
+		}
+	}()
+	indexObj := new(models.BaseFromUsdaFasIndex)
+	dataObj := new(models.BaseFromUsdaFasData)
+	classifyObj := new(models.BaseFromUsdaFasClassify)
+
+	var indexId int64
+
+	addDataList := make([]*models.BaseFromUsdaFasData, 0)
+
+	exitDataMap := make(map[string]*models.BaseFromUsdaFasData)
+
+	// 修改指标信息
+	if indexName == "" {
+		utils.FileLog.Info("未刷新到指标数据:indexName:" + indexName)
+		return
+	}
+	// 判断目录是否存在
+	var classifyId int64
+	now := time.Now()
+	if req.ClassifyName != "" {
+		classifyParentId := 0
+		level := 1
+		classifyParentId, _ = classifyMap[req.ParentClassifyName]
+		if classifyParentId > 0 {
+			level = 2
+		}
+		classifyObj, err = classifyObj.GetByClassifyName(req.ClassifyName)
+		if err != nil {
+			if err.Error() == utils.ErrNoRow() {
+				//新增分类
+				classifyObj = &models.BaseFromUsdaFasClassify{
+					ClassifyName:    req.ClassifyName,
+					ClassifyNameEn:  req.ClassifyName,
+					ParentId:        classifyParentId,
+					SysUserId:       0,
+					SysUserRealName: "",
+					Level:           level,
+					Sort:            req.ClassifySort,
+					ModifyTime:      now,
+					CreateTime:      now,
+				}
+
+				classifyId, err = classifyObj.Add()
+				if err != nil {
+					err = fmt.Errorf("新增分类失败 Err:%s", err)
+					return
+				}
+				classifyObj.ClassifyId = classifyId
+			} else {
+				return
+			}
+		} else {
+			classifyId = classifyObj.ClassifyId
+			classifyObj.ModifyTime = now
+			//classifyObj.Sort = req.ClassifySort
+			classifyObj.ParentId = classifyParentId
+			//e := classifyObj.Update([]string{"ParentId", "Sort", "ModifyTime"})
+			e := classifyObj.Update([]string{"ParentId", "ModifyTime"})
+			if e != nil {
+				fmt.Println("classifyObj Update Err:" + e.Error())
+				return
+			}
+		}
+	}
+
+	//判断指标是否存在
+	var isAdd int
+	item, err := indexObj.GetByIndexCode(indexCode)
+	if err != nil {
+		if err.Error() == utils.ErrNoRow() {
+			isAdd = 1
+			err = nil
+		} else {
+			isAdd = -1
+			err = fmt.Errorf("查询数据源指标库失败 GetByIndexCode Err:%s", err)
+			return
+		}
+	}
+	if item != nil && item.BaseFromUsdaFasIndexId > 0 {
+		fmt.Println("item:", item)
+		isAdd = 2
+	} else {
+		isAdd = 1
+	}
+
+	if isAdd == 1 {
+		indexObj.IndexCode = indexCode
+		indexObj.IndexName = indexName
+		indexObj.Frequency = req.Frequency
+		indexObj.ClassifyId = classifyId
+		indexObj.Country = req.Country
+		indexObj.Commodity = req.Commodity
+		indexObj.Unit = req.Unit
+		indexObj.Sort = req.Sort
+		indexObj.ModifyTime = time.Now()
+		indexObj.CreateTime = time.Now()
+		indexObj.TerminalCode = terminalCode
+		indexId, err = indexObj.Add()
+		if err != nil {
+			err = fmt.Errorf("数据源新增美国农业部指标失败 Err:%s", err)
+			return
+		}
+		indexObj.BaseFromUsdaFasIndexId = indexId
+	} else if isAdd == 2 {
+		indexId = item.BaseFromUsdaFasIndexId
+		if item.TerminalCode == `` && terminalCode != `` {
+			item.TerminalCode = terminalCode
+			err = item.Update([]string{"TerminalCode"})
+			if err != nil {
+				err = fmt.Errorf("数据源更新美国农业部指标失败 Err:%s", err)
+				return
+			}
+		}
+
+		indexObj.BaseFromUsdaFasIndexId = item.BaseFromUsdaFasIndexId
+		indexObj.IndexName = indexName
+		indexObj.Frequency = req.Frequency
+		indexObj.ClassifyId = classifyId
+		indexObj.Country = req.Country
+		indexObj.Commodity = req.Commodity
+		indexObj.Unit = req.Unit
+		indexObj.Sort = req.Sort
+		indexObj.ModifyTime = time.Now()
+
+		//修改数据
+		updateColsArr := make([]string, 0)
+		updateColsArr = append(updateColsArr, "index_name")
+		updateColsArr = append(updateColsArr, "classify_id")
+		updateColsArr = append(updateColsArr, "country")
+		updateColsArr = append(updateColsArr, "commodity")
+		updateColsArr = append(updateColsArr, "frequency")
+		updateColsArr = append(updateColsArr, "sort")
+		updateColsArr = append(updateColsArr, "modify_time")
+
+		e := indexObj.Update(updateColsArr)
+		if e != nil {
+			fmt.Println("Index Update Err:" + e.Error())
+			return
+		}
+	}
+
+	//获取已存在的所有数据
+	var exitDataList []*models.BaseFromUsdaFasData
+	exitDataList, err = dataObj.GetByIndexCode(indexCode)
+	if err != nil {
+		err = fmt.Errorf("数据源查询美国农业部指标数据失败 Err:%s", err)
+		return
+	}
+	fmt.Println("exitDataListLen:", len(exitDataList))
+	for _, v := range exitDataList {
+		dateStr := v.DataTime
+		exitDataMap[dateStr] = v
+	}
+
+	// 遍历excel数据,然后跟现有的数据做校验,不存在则入库
+	for date, value := range excelDataMap {
+		if findData, ok := exitDataMap[date]; !ok {
+			_, err = time.ParseInLocation(utils.FormatDate, date, time.Local)
+			if err != nil {
+				err = fmt.Errorf("%s 转换日期格式失败 Err:%s", date, err)
+				return
+			}
+			//if !strings.Contains(value, "#N/A") {
+			var saveDataTime time.Time
+			if strings.Contains(date, "00:00:00") {
+				saveDataTime, err = time.Parse(utils.FormatDateTime, date)
+			} else {
+				saveDataTime, err = time.Parse(utils.FormatDate, date)
+			}
+			if err != nil {
+				err = fmt.Errorf("%s 转换日期格式失败 Err:%s", date, err)
+				continue
+			}
+			timestamp := saveDataTime.UnixNano() / 1e6
+
+			dataItem := new(models.BaseFromUsdaFasData)
+			dataItem.BaseFromUsdaFasIndexId = int(indexId)
+			dataItem.IndexCode = indexCode
+			dataItem.DataTime = date
+			dataItem.Value = value
+			dataItem.CreateTime = time.Now()
+			dataItem.ModifyTime = time.Now()
+			dataItem.DataTimestamp = timestamp
+			addDataList = append(addDataList, dataItem)
+			if len(addDataList) > 500 {
+				err = dataObj.AddMulti(addDataList)
+				if err != nil {
+					err = fmt.Errorf("批量新增指标失败 Err:%s", err)
+					return
+				}
+				addDataList = make([]*models.BaseFromUsdaFasData, 0)
+			}
+			//}
+		} else {
+			if findData != nil && findData.Value != value && !strings.Contains(value, "#N/A") { //修改数据
+				// 过滤0.50和0.5的比较
+				oldV, _ := strconv.ParseFloat(findData.Value, 64)
+				newV, _ := strconv.ParseFloat(value, 64)
+				if oldV == newV {
+					continue
+				}
+				dataObj.BaseFromUsdaFasIndexId = findData.BaseFromUsdaFasIndexId
+				dataObj.Value = value
+				dataObj.ModifyTime = time.Now()
+
+				updateDataColsArr := make([]string, 0)
+				updateDataColsArr = append(updateDataColsArr, "value")
+				updateDataColsArr = append(updateDataColsArr, "modify_time")
+				dataObj.Update(updateDataColsArr)
+			}
+		}
+	}
+
+	if len(addDataList) > 0 {
+		err = dataObj.AddMulti(addDataList)
+		if err != nil {
+			err = fmt.Errorf("批量新增指标失败 Err:%s", err)
+			return
+		}
+	}
+
+	var dateItem *models.EdbInfoMaxAndMinInfo
+	dateItem, err = dataObj.GetMaxAndMinDateByIndexCode(indexCode)
+	if err != nil {
+		err = fmt.Errorf("查询指标最新日期失败 Err:%s", err)
+		return
+	}
+
+	go func() {
+		indexObj.ModifyIndexMaxAndMinDate(indexCode, dateItem)
+	}()
+
+	// 同步刷新ETA指标库的指标
+	{
+		// 获取指标详情
+		baseObj := new(models.BaseFromUsdaFas)
+		var edbInfo *models.EdbInfo
+		edbInfo, err = models.GetEdbInfoByEdbCode(baseObj.GetSource(), indexCode)
+		if err != nil {
+			if err.Error() != utils.ErrNoRow() {
+				errMsgList = append(errMsgList, fmt.Sprint("刷新ETA指标异常,指标编码:", indexCode, err.Error()))
+				return
+			} else {
+				err = nil
+			}
+		}
+
+		// 已经加入到指标库的话,那么就去更新ETA指标库吧
+		if edbInfo != nil {
+			go logic.RefreshBaseEdbInfo(edbInfo, ``)
+		}
+	}
+	return
+}

+ 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 json
+import warnings
+warnings.filterwarnings('ignore')
+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
+
+file_path = r"%s"
+df = pd.read_excel(file_path, parse_dates=['日期'], engine='openpyxl')
+df.set_index('日期', inplace=True)
+df = df[df.index.notna()]
+
+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['值'], np.arange(len(df)), 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": ""
   }
-]
+]

+ 202 - 0
utils/common.go

@@ -1500,3 +1500,205 @@ 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)
+}
+
+// GetCurrentTime 获取当前时间 格式为 2024-08-07 15:29:58
+func GetCurrentTime() string {
+	return time.Now().Format("2006-01-02 15:04:05")
+}

+ 2 - 0
utils/config.go

@@ -75,6 +75,7 @@ var (
 	ThsDataMethod           string //同花顺数据获取的方式,app是通过终端;api是通过接口
 	ThsRefreshToken         string // 同花顺的刷新token
 	MysteelChemicalApiToken string // 钢联化工的api数据token
+	MySteelChemicalApiUrl   string // 钢联化工的api数据地址
 )
 
 type WindUrlMap struct {
@@ -232,6 +233,7 @@ func init() {
 			ThsDataMethod = "api"
 		}
 		MysteelChemicalApiToken = config["mysteel_chemical_api_token"]
+		MySteelChemicalApiUrl = config["mysteel_chemical_api_url"]
 	}
 
 	// ES配置

+ 6 - 0
utils/constants.go

@@ -115,7 +115,12 @@ const (
 	DATA_SOURCE_PREDICT_CALCULATE_RANGEANLYSIS       = 90 // 预测指标区间计算->90
 	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 // 期数移动
+	DATA_SOURCE_RZD                                  = 97 // 睿咨得科技 -> 93
 )
 
 // 指标来源的中文展示
@@ -204,6 +209,7 @@ const (
 	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