Browse Source

Merge remote-tracking branch 'origin/eta_2.1.9_index_phase_shift_0909@guomengyuan'

# Conflicts:
#	models/edb_data_table.go
#	utils/common.go
#	utils/constants.go
gmy 4 tháng trước cách đây
mục cha
commit
8c9eae4c3b

+ 25 - 0
controllers/base_from_calculate.go

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

+ 3 - 0
controllers/base_from_predict_calculate.go

@@ -802,6 +802,9 @@ func (this *PredictCalculateController) CalculateBatchSave() {
 	} else if req.Source == utils.DATA_SOURCE_PREDICT_CALCULATE_TIME_SHIFT {
 		sourName = "预测时间移位"
 		edbInfo, latestDateStr, latestValue, err = models.SavePredictCalculateTimeShift(&req, fromEdbInfo, edbCode, uniqueCode, adminId, adminName, this.Lang)
+	} else if req.Source == utils.DATA_SOURCE_PREDICT_CALCULATE_PHASE_SHIFT {
+		sourName = "预测期数移位"
+		edbInfo, latestDateStr, latestValue, err = models.SavePredictCalculatePhaseShift(&req, fromEdbInfo, edbCode, uniqueCode, adminId, adminName, this.Lang)
 	} else if req.Source == utils.DATA_SOURCE_PREDICT_CALCULATE_CJJX {
 		sourName = "预测超季节性"
 		edbInfo, latestDateStr, latestValue, err = models.SavePredictCalculateCjjx(&req, fromEdbInfo, edbCode, uniqueCode, adminId, adminName, formulaInt, this.Lang)

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

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

+ 4 - 0
models/edb_data_table.go

@@ -174,6 +174,10 @@ func GetEdbDataTableName(source, subSource int) (tableName string) {
 		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 {

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

+ 197 - 0
utils/common.go

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

+ 2 - 0
utils/constants.go

@@ -117,6 +117,8 @@ const (
 	DATA_SOURCE_TRADE_ANALYSIS                       = 92 // 持仓分析
 	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 // 期数移动
 )
 
 // 指标来源的中文展示