Переглянути джерело

Merge branch 'feature/eta_2.1.4' into debug

hsun 1 тиждень тому
батько
коміт
54896a82f5

+ 133 - 2
controllers/base_from_trade_analysis.go

@@ -4,6 +4,7 @@ import (
 	"encoding/json"
 	"eta/eta_index_lib/logic"
 	"eta/eta_index_lib/models"
+	excelModel "eta/eta_index_lib/models/excel"
 	tradeAnalysisModel "eta/eta_index_lib/models/trade_analysis"
 	tradeAnalysisService "eta/eta_index_lib/services/trade_analysis"
 	"eta/eta_index_lib/utils"
@@ -109,8 +110,8 @@ func (this *BaseFromTradeAnalysisController) EdbRefresh() {
 		return
 	}
 
-	// 获取指标数据, 该图表未用实际指标, 为了统一数据格式用ChartEdbInfoMapping
-	companyTradeData, e := tradeAnalysisService.GetOriginTradeData(extraConfig.Exchange, extraConfig.ClassifyName, extraConfig.Contracts, extraConfig.Companies, extraConfig.PredictRatio)
+	// 获取持仓数据
+	companyTradeData, e := tradeAnalysisService.GetWarehouseTradeData(extraConfig.Exchange, extraConfig.ClassifyName, extraConfig.Contracts, extraConfig.Companies, extraConfig.PredictRatio)
 	if e != nil {
 		br.Msg = "获取失败"
 		br.ErrMsg = fmt.Sprintf("获取期货公司持仓加总数据失败, %v", e)
@@ -152,3 +153,133 @@ func (this *BaseFromTradeAnalysisController) EdbRefresh() {
 	br.Success = true
 	br.Msg = "操作成功"
 }
+
+// TableRefresh
+// @Title 表格刷新
+// @Description 表格刷新
+// @Success 200 {object} tradeAnalysisModel.RefreshTableReq
+// @router /table/refresh [post]
+func (this *BaseFromTradeAnalysisController) TableRefresh() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	var req tradeAnalysisModel.RefreshTableReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = fmt.Sprintf("参数解析失败, %v", e)
+		return
+	}
+	if req.ExcelInfoId <= 0 {
+		br.Msg = "请输入表格ID"
+		br.ErrMsg = "请输入表格ID"
+		return
+	}
+
+	cacheKey := fmt.Sprintf("%s_%d", utils.CACHE_EXCEL_REFRESH, req.ExcelInfoId)
+	if utils.Rc.IsExist(cacheKey) {
+		br.Ret = 200
+		br.Success = true
+		br.Msg = "系统处理中,请稍后重试"
+		return
+	}
+	utils.Rc.SetNX(cacheKey, 1, 2*time.Minute)
+	defer func() {
+		_ = utils.Rc.Delete(cacheKey)
+	}()
+
+	// 获取表格信息
+	excelOb := new(excelModel.ExcelInfo)
+	item, e := excelOb.GetItemById(req.ExcelInfoId)
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Msg = "表格不存在"
+			br.ErrMsg = fmt.Sprintf("表格不存在, ExcelId: %d", req.ExcelInfoId)
+			return
+		}
+		br.Msg = "操作失败"
+		br.ErrMsg = fmt.Sprintf("获取表格信息失败, %v", e)
+		return
+	}
+	if item.IsDelete == 1 {
+		br.Msg = "表格已被删除"
+		br.ErrMsg = fmt.Sprintf("表格已被删除, ExcelId: %d", req.ExcelInfoId)
+		return
+	}
+
+	// 获取表格数据
+	switch item.Source {
+	case utils.TRADE_ANALYSIS_TABLE:
+		// 多空分析
+		var tableConfig tradeAnalysisModel.TableExtraConfig
+		if item.ExtraConfig == "" {
+			br.Msg = "操作失败"
+			br.ErrMsg = fmt.Sprintf("表格配置为空, ExcelId: %d", item.ExcelInfoId)
+			return
+		}
+		if e = json.Unmarshal([]byte(item.ExtraConfig), &tableConfig); e != nil {
+			br.Msg = "操作失败"
+			br.ErrMsg = fmt.Sprintf("表格配置解析失败, ExcelId: %d, Err: %v", item.ExcelInfoId, e)
+			return
+		}
+		tableData, e := tradeAnalysisService.GetTableRowsDataByConfig(tableConfig)
+		if e != nil {
+			br.Msg = "操作失败"
+			br.ErrMsg = fmt.Sprintf("获取表格行数据失败, %v", e)
+			return
+		}
+		content, e := json.Marshal(tableData)
+		if e != nil {
+			br.Msg = "表格数据JSON格式化失败"
+			br.ErrMsg = fmt.Sprintf("表格数据JSON格式化失败, %v", e)
+			return
+		}
+		item.Content = string(content)
+	case utils.TRADE_ANALYSIS_CORRELATION_TABLE:
+		// 相关性表格
+		var tableConfig tradeAnalysisModel.CorrelationTableExtraConfig
+		if item.ExtraConfig == "" {
+			br.Msg = "操作失败"
+			br.ErrMsg = fmt.Sprintf("表格配置为空, ExcelId: %d", item.ExcelInfoId)
+			return
+		}
+		if e = json.Unmarshal([]byte(item.ExtraConfig), &tableConfig); e != nil {
+			br.Msg = "操作失败"
+			br.ErrMsg = fmt.Sprintf("表格配置解析失败, ExcelId: %d, Err: %v", item.ExcelInfoId, e)
+			return
+		}
+		tableData, e := tradeAnalysisService.GetCorrelationTableRowsDataByConfig(tableConfig)
+		if e != nil {
+			br.Msg = "操作失败"
+			br.ErrMsg = fmt.Sprintf("获取表格数据失败, %v", e)
+			return
+		}
+		content, e := json.Marshal(tableData)
+		if e != nil {
+			br.Msg = "表格数据JSON格式化失败"
+			br.ErrMsg = fmt.Sprintf("表格数据JSON格式化失败, %v", e)
+			return
+		}
+		item.Content = string(content)
+	default:
+		br.Msg = "表格来源有误"
+		br.ErrMsg = fmt.Sprintf("表格来源有误, ExcelId: %d, Source: %d", item.ExcelInfoId, item.Source)
+		return
+	}
+
+	// 更新内容
+	updateCols := []string{"Content", "ModifyTime"}
+	if e = item.Update(updateCols); e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = fmt.Sprintf("更新表格数据失败, %v", e)
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}

+ 67 - 0
models/chart_edb_mapping.go

@@ -45,3 +45,70 @@ func GetGroupChartEdbMappingListByEdbInfoId(edbInfoId, source int) (list []*Char
 	_, err = o.Raw(sql, edbInfoId, source).QueryRows(&list)
 	return
 }
+
+type ChartEdbInfoMapping struct {
+	EdbInfoId           int     `description:"指标id"`
+	SourceName          string  `description:"来源名称"`
+	Source              int     `description:"来源id"`
+	SubSource           int     `description:"来源id"`
+	EdbCode             string  `description:"指标编码"`
+	EdbName             string  `description:"指标名称"`
+	EdbAliasName        string  `description:"指标名称(别名)"`
+	EdbNameEn           string  `description:"英文指标名称"`
+	EdbAliasNameEn      string  `description:"英文指标名称(别名)"`
+	EdbType             int     `description:"指标类型:1:基础指标,2:计算指标"`
+	Frequency           string  `description:"频率"`
+	FrequencyEn         string  `description:"英文频率"`
+	Unit                string  `description:"单位"`
+	UnitEn              string  `description:"英文单位"`
+	StartDate           string  `description:"起始日期"`
+	EndDate             string  `description:"终止日期"`
+	ModifyTime          string  `description:"指标最后更新时间"`
+	ChartEdbMappingId   int     `description:"图表指标id"`
+	ChartInfoId         int     `description:"图表id"`
+	MaxData             float64 `description:"上限"`
+	MinData             float64 `description:"下限"`
+	IsOrder             bool    `description:"true:正序,false:逆序"`
+	IsAxis              int     `description:"1:左轴,0:右轴"`
+	EdbInfoType         int     `description:"1:标准指标,0:领先指标"`
+	EdbInfoCategoryType int     `description:"0:普通指标,1:预测指标"`
+	LeadValue           int     `description:"领先值"`
+	LeadUnit            string  `description:"领先单位"`
+	LeadUnitEn          string  `description:"领先英文单位"`
+	ChartStyle          string  `description:"图表类型"`
+	ChartColor          string  `description:"颜色"`
+	PredictChartColor   string  `description:"预测数据的颜色"`
+	ChartWidth          float64 `description:"线条大小"`
+	ChartType           int     `description:"生成样式:1:曲线图,2:季节性图,3:面积图,4:柱状图,5:散点图,6:组合图,7:柱方图,8:商品价格曲线图,9:相关性图"`
+	LatestDate          string  `description:"数据最新日期"`
+	LatestValue         float64 `description:"数据最新值"`
+	MoveLatestDate      string  `description:"移动后的数据最新日期"`
+	UniqueCode          string  `description:"指标唯一编码"`
+	MinValue            float64 `json:"-" description:"最小值"`
+	MaxValue            float64 `json:"-" description:"最大值"`
+	DataList            interface{}
+	IsNullData          bool    `json:"-" description:"是否空数据"`
+	MappingSource       int     `description:"1:ETA图库;2:商品价格曲线"`
+	RegionType          string  `description:"交易所来源,海外还是国内" json:"-"`
+	ClassifyId          int     `description:"分类id"`
+	SubSourceName       string  `description:"子数据来源名称"`
+	IndicatorCode       string  `description:"指标代码"`
+	IsConvert           int     `description:"是否数据转换 0不转 1转"`
+	ConvertType         int     `description:"数据转换类型 1乘 2除 3对数"`
+	ConvertValue        float64 `description:"数据转换值"`
+	ConvertUnit         string  `description:"数据转换单位"`
+	ConvertEnUnit       string  `description:"数据转换单位"`
+	IsJoinPermission    int     `description:"是否加入权限管控,0:不加入;1:加入;默认:0"`
+	HaveOperaAuth       bool    `description:"是否有数据权限,默认:false"`
+	UniqueFlag          string  `description:"唯一标识(与唯一编码不是一个东西)"`
+}
+
+// GetChartEdbMappingByEdbInfoId 根据指标id获取edb_mapping
+func GetChartEdbMappingByEdbInfoId(edbInfoId int) (item *ChartEdbInfoMapping, err error) {
+	o := orm.NewOrm()
+	sql := ` SELECT edb_info_id,source_name,classify_id,source,sub_source,edb_code,edb_name,edb_name_en,frequency,unit,unit_en,start_date,end_date,modify_time,latest_date,latest_value,unique_code,edb_info_type AS edb_info_category_type,edb_type,max_value,min_value,is_join_permission
+             FROM edb_info
+			 WHERE edb_info_id = ? limit 1`
+	err = o.Raw(sql, edbInfoId).QueryRow(&item)
+	return
+}

+ 37 - 15
models/excel/excel_info.go

@@ -7,21 +7,30 @@ import (
 
 // ExcelInfo excel表格详情表
 type ExcelInfo struct {
-	ExcelInfoId     int       `orm:"column(excel_info_id);pk"`
-	Source          int       `description:"表格来源,1:excel插件的表格,2:自定义表格,3:混合表格,4:自定义分析,默认:1"`
-	ExcelType       int       `description:"表格类型,1:指标列,2:日期列,默认:1"`
-	ExcelName       string    `description:"表格名称"`
-	UniqueCode      string    `description:"表格唯一编码"`
-	ExcelClassifyId int       `description:"表格分类id"`
-	SysUserId       int       `description:"操作人id"`
-	SysUserRealName string    `description:"操作人真实姓名"`
-	Content         string    `description:"表格内容"`
-	ExcelImage      string    `description:"表格图片"`
-	FileUrl         string    `description:"表格下载地址"`
-	Sort            int       `description:"排序字段,数字越小越排前面"`
-	IsDelete        int       `description:"是否删除,0:未删除,1:已删除"`
-	ModifyTime      time.Time `description:"最近修改日期"`
-	CreateTime      time.Time `description:"创建日期"`
+	ExcelInfoId        int       `orm:"column(excel_info_id);pk"`
+	Source             int       `description:"表格来源,1:excel插件的表格,2:自定义表格,3:混合表格,4:自定义分析,默认:1"`
+	ExcelType          int       `description:"表格类型,1:指标列,2:日期列,默认:1"`
+	ExcelName          string    `description:"表格名称"`
+	UniqueCode         string    `description:"表格唯一编码"`
+	ExcelClassifyId    int       `description:"表格分类id"`
+	SysUserId          int       `description:"操作人id"`
+	SysUserRealName    string    `description:"操作人真实姓名"`
+	Content            string    `description:"表格内容"`
+	ExcelImage         string    `description:"表格图片"`
+	FileUrl            string    `description:"表格下载地址"`
+	Sort               int       `description:"排序字段,数字越小越排前面"`
+	IsDelete           int       `description:"是否删除,0:未删除,1:已删除"`
+	ModifyTime         time.Time `description:"最近修改日期"`
+	CreateTime         time.Time `description:"创建日期"`
+	IsJoinPermission   int       `description:"是否加入权限管控,0:不加入;1:加入;默认:0"`
+	ParentId           int       `description:"表格的父级id"`
+	BalanceType        int       `description:"平衡表类型:0 动态表,1静态表"`
+	UpdateUserId       int       `description:"更新人id"`
+	UpdateUserRealName string    `description:"更新人真实姓名"`
+	RelExcelInfoId     int       `description:"平衡表里静态表关联的动态表excel id"`
+	VersionName        string    `description:"静态表版本名称"`
+	SourcesFrom        string    `description:"图表来源"`
+	ExtraConfig        string    `description:"额外配置:如多空分析、相关性表格参数"`
 }
 
 // GetNoContentExcelInfoAll 获取不含content的表格列表 用于分类展示
@@ -110,3 +119,16 @@ type MixedTableCellDataReq struct {
 	ShowValue    string `description:"展示值"`
 	Value        string `description:"实际值"`
 }
+
+func (m *ExcelInfo) Update(cols []string) (err error) {
+	o := orm.NewOrm()
+	_, err = o.Update(m, cols...)
+	return
+}
+
+func (m *ExcelInfo) GetItemById(id int) (item *ExcelInfo, err error) {
+	o := orm.NewOrm()
+	sql := `SELECT * FROM excel_info WHERE excel_info_id = ? LIMIT 1`
+	err = o.Raw(sql, id).QueryRow(&item)
+	return
+}

+ 249 - 358
models/trade_analysis/trade_analysis.go

@@ -4,9 +4,67 @@ import (
 	"eta/eta_index_lib/utils"
 	"fmt"
 	"github.com/beego/beego/v2/client/orm"
+	"strings"
 	"time"
 )
 
+const (
+	TradeDataTypeNull      = 0 // 无值
+	TradeDataTypeOrigin    = 1 // 原始值
+	TradeDataTypeCalculate = 2 // 推算值
+
+	WarehouseBuyChartType     = 1 // 多单图
+	WarehouseSoldChartType    = 2 // 空单图
+	WarehousePureBuyChartType = 3 // 净多单图
+
+	WarehouseDefaultUnit      = "手"
+	WarehouseDefaultFrequency = "日度"
+
+	GuangZhouTopCompanyAliasName = "日成交持仓排名" // 广期所TOP20对应的公司名称
+	GuangZhouSeatNameBuy         = "持买单量"    // 广期所指标名称中的多单名称
+	GuangZhouSeatNameSold        = "持卖单量"    // 广期所指标名称中的空单名称
+	GuangZhouTopSeatNameBuy      = "持买单量总计"  // 广期所指标名称中的TOP20多单名称
+	GuangZhouTopSeatNameSold     = "持卖单量总计"  // 广期所指标名称中的TOP20空单名称
+	GuangZhouTopSeatNameDeal     = "成交量总计"   // 广期所指标名称中的TOP20成交量名称
+)
+
+const (
+	TradeExchangeDalian    = "dalian"
+	TradeExchangeZhengzhou = "zhengzhou"
+	TradeExchangeGuangzhou = "guangzhou"
+)
+
+var WarehouseTypeSuffixNames = map[int]string{
+	WarehouseBuyChartType:     "席位多单",
+	WarehouseSoldChartType:    "席位空单",
+	WarehousePureBuyChartType: "席位净多单",
+}
+
+// GuangzhouSeatNameValType 广期所数据名称对应的席位方向
+var GuangzhouSeatNameValType = map[string]int{
+	GuangZhouSeatNameBuy:     1,
+	GuangZhouSeatNameSold:    2,
+	GuangZhouTopSeatNameBuy:  1,
+	GuangZhouTopSeatNameSold: 2,
+	GuangZhouTopSeatNameDeal: 3,
+}
+
+// 合约查询方式
+var (
+	ContractQueryTypeTop   = 1 // 主力合约
+	ContractQueryTypeTop2  = 2 // 成交量前2
+	ContractQueryTypeTop3  = 3 // 成交量前3
+	ContractQueryTypeAll   = 4 // 所有合约(多个)
+	ContractQueryTypeTotal = 5 // 合约加总(1个)
+)
+
+// 合约方向
+var (
+	ContractPositionBuy     = 1 // 多单
+	ContractPositionSold    = 2 // 空单
+	ContractPositionPureBuy = 3 // 净多单
+)
+
 // 上期能源持仓榜单表
 type TradePositionTop struct {
 	Id            uint64    `gorm:"primaryKey;column:id" json:"id"`
@@ -173,220 +231,90 @@ type OriginTradeData struct {
 	ValType      int       `description:"数据类型: 1-多单; 2-空单"`
 }
 
-// GetTradeDataByClassifyAndCompany 根据品种和公司名称获取持仓数据
-func GetTradeDataByClassifyAndCompany(exchange, classifyName string, contracts, companies []string) (items []*OriginTradeData, err error) {
+// BaseFromTradeCommonIndex 郑商所/大商所/上期所/上期能源指标表通用字段
+type BaseFromTradeCommonIndex struct {
+	Rank          int       `description:"排名"`
+	DealShortName string    `description:"成交量公司简称"`
+	DealName      string    `description:"成交量指标名称"`
+	DealCode      string    `description:"成交量指标编码"`
+	DealValue     int       `description:"成交量"`
+	DealChange    int       `description:"成交变化量"`
+	BuyShortName  string    `description:"持买单量公司简称"`
+	BuyName       string    `description:"持买单量指标名称"`
+	BuyCode       string    `description:"持买单量指标编码"`
+	BuyValue      int       `description:"持买单量"`
+	BuyChange     int       `description:"持买单量变化量"`
+	SoldShortName string    `description:"持卖单量公司简称"`
+	SoldName      string    `description:"持卖单量指标名称"`
+	SoldCode      string    `description:"持卖单量指标编码"`
+	SoldValue     int       `description:"持卖单量"`
+	SoldChange    int       `description:"持卖单变化量"`
+	Frequency     string    `description:"频度"`
+	ClassifyName  string    `description:"品种"`
+	ClassifyType  string    `description:"合约"`
+	CreateTime    time.Time `description:"创建时间"`
+	ModifyTime    time.Time `description:"更新时间"`
+	DataTime      time.Time `description:"数据日期"`
+}
+
+// GetTradeDataByContracts 根据合约获取持仓数据
+func GetTradeDataByContracts(exchange string, classifyNames, contracts []string, startDate, endDate time.Time) (items []*BaseFromTradeCommonIndex, err error) {
 	if exchange == "" {
 		err = fmt.Errorf("数据表名称有误")
 		return
 	}
-	if len(contracts) == 0 || len(companies) == 0 {
-		return
+	var cond string
+	var pars []interface{}
+	if len(classifyNames) > 0 {
+		cond += fmt.Sprintf(` AND classify_name IN (%s)`, utils.GetOrmInReplace(len(classifyNames)))
+		pars = append(pars, classifyNames)
 	}
-	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 len(contracts) > 0 {
+		cond += fmt.Sprintf(` AND classify_type IN (%s)`, utils.GetOrmInReplace(len(contracts)))
+		pars = append(pars, contracts)
 	}
-	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)`
-		}
+	if !startDate.IsZero() && !endDate.IsZero() {
+		cond += ` AND (data_time BETWEEN ? AND ?)`
+		pars = append(pars, startDate.Format(utils.FormatDate), endDate.Format(utils.FormatDate))
 	}
-
+	fields := []string{"rank", "buy_short_name", "buy_value", "buy_change", "sold_short_name", "sold_value", "sold_change", "classify_name", "classify_type", "data_time"}
 	tableName := fmt.Sprintf("base_from_trade_%s_index", exchange)
-	sql := `SELECT
-			rank,
-			buy_short_name AS company_name,
-			buy_value AS val,
-			buy_change AS val_change,
-			classify_name,
-			classify_type,
-			data_time,
-			1 AS val_type 
-		FROM
-			%s 
-		WHERE
-			%s
-		UNION ALL
-		(
-		SELECT
-			rank,
-			sold_short_name,
-			sold_value,
-			sold_change,
-			classify_name,
-			classify_type,
-			data_time,
-			2 AS val_type 
-		FROM
-			%s 
-		WHERE
-			%s
-		)`
-	sql = fmt.Sprintf(sql, tableName, condBuy, tableName, condSold)
-	o := orm.NewOrm()
-	_, err = o.Raw(sql, parsBuy, parsSold).QueryRows(&items)
+	sql := fmt.Sprintf(`SELECT %s FROM %s WHERE 1=1 %s ORDER BY data_time DESC`, strings.Join(fields, ","), tableName, cond)
+	_, err = orm.NewOrm().Raw(sql, pars).QueryRows(&items)
 	return
 }
 
-// GetTradeZhengzhouDataByClassifyAndCompany 郑商所-根据品种和公司名称获取持仓数据
-func GetTradeZhengzhouDataByClassifyAndCompany(exchange string, contracts, companies []string) (items []*OriginTradeData, err error) {
-	if exchange == "" {
-		err = fmt.Errorf("数据表名称有误")
-		return
-	}
-	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)
+// GetZhengzhouTradeDataByContracts 郑商所-根据合约获取持仓数据
+func GetZhengzhouTradeDataByContracts(classifyNames []string, startDate, endDate time.Time) (items []*BaseFromTradeCommonIndex, err error) {
+	var cond string
+	var pars []interface{}
+	if len(classifyNames) > 0 {
+		cond += fmt.Sprintf(` AND classify_name IN (%s)`, utils.GetOrmInReplace(len(classifyNames)))
+		pars = append(pars, classifyNames)
 	}
-	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`
-		}
+	if !startDate.IsZero() && !endDate.IsZero() {
+		cond += ` AND (data_time BETWEEN ? AND ?)`
+		pars = append(pars, startDate.Format(utils.FormatDate), endDate.Format(utils.FormatDate))
 	}
-
-	tableName := fmt.Sprintf("base_from_trade_%s_index", exchange)
-	sql := `SELECT
-			rank,
-			buy_short_name AS company_name,
-			buy_value AS val,
-			buy_change AS val_change,
-			classify_name AS classify_type,
-			data_time,
-			1 AS val_type 
-		FROM
-			%s 
-		WHERE
-			%s
-		UNION ALL
-		(
-		SELECT
-			rank,
-			sold_short_name,
-			sold_value,
-			sold_change,
-			classify_name AS classify_type,
-			data_time,
-			2 AS val_type 
-		FROM
-			%s 
-		WHERE
-			%s
-		)`
-	sql = fmt.Sprintf(sql, tableName, condBuy, tableName, condSold)
-	o := orm.NewOrm()
-	_, err = o.Raw(sql, parsBuy, parsSold).QueryRows(&items)
+	// ps.classify_name实为合约代码
+	fields := []string{"rank", "buy_short_name", "buy_value", "buy_change", "sold_short_name", "sold_value", "sold_change", "classify_name AS classify_type", "data_time"}
+	sql := fmt.Sprintf(`SELECT %s FROM base_from_trade_zhengzhou_index WHERE 1=1 %s ORDER BY data_time DESC`, strings.Join(fields, ","), cond)
+	_, err = orm.NewOrm().Raw(sql, pars).QueryRows(&items)
 	return
 }
 
 // ContractCompanyTradeData [合约-期货公司]持仓数据
 type ContractCompanyTradeData struct {
-	CompanyName  string                          `description:"期货公司名称"`
+	Exchange     string                          `description:"交易所"`
+	ClassifyName string                          `description:"品种"`
 	ClassifyType string                          `description:"合约代码"`
+	CompanyName  string                          `description:"期货公司名称"`
+	IsTotal      bool                            `description:"是否为合约加总"`
 	StartDate    time.Time                       `description:"数据开始日期"`
 	EndDate      time.Time                       `description:"数据结束日期"`
 	DataList     []*ContractCompanyTradeDataList `description:"数据序列"`
 }
 
-const (
-	TradeDataTypeNull      = 0 // 无值
-	TradeDataTypeOrigin    = 1 // 原始值
-	TradeDataTypeCalculate = 2 // 推算值
-
-	WarehouseBuyChartType     = 1 // 多单图
-	WarehouseSoldChartType    = 2 // 空单图
-	WarehousePureBuyChartType = 3 // 净多单图
-
-	WarehouseDefaultUnit      = "手"
-	WarehouseDefaultFrequency = "日度"
-
-	GuangZhouTopCompanyAliasName = "日成交持仓排名" // 广期所TOP20对应的公司名称
-	GuangZhouSeatNameBuy         = "持买单量"       // 广期所指标名称中的多单名称
-	GuangZhouSeatNameSold        = "持卖单量"       // 广期所指标名称中的空单名称
-	GuangZhouTopSeatNameBuy      = "持买单量总计"   // 广期所指标名称中的TOP20多单名称
-	GuangZhouTopSeatNameSold     = "持卖单量总计"   // 广期所指标名称中的TOP20空单名称
-)
-
-const (
-	TradeExchangeZhengzhou = "zhengzhou"
-	TradeExchangeGuangzhou = "guangzhou"
-)
-
-var WarehouseTypeSuffixNames = map[int]string{
-	WarehouseBuyChartType:     "席位多单",
-	WarehouseSoldChartType:    "席位空单",
-	WarehousePureBuyChartType: "席位净多单",
-}
-
-// GuangzhouSeatNameValType 广期所数据名称对应的席位方向
-var GuangzhouSeatNameValType = map[string]int{
-	GuangZhouSeatNameBuy:     1,
-	GuangZhouSeatNameSold:    2,
-	GuangZhouTopSeatNameBuy:  1,
-	GuangZhouTopSeatNameSold: 2,
-}
-
 // ContractCompanyTradeDataList [合约-期货公司]持仓数据详情
 type ContractCompanyTradeDataList struct {
 	Date              time.Time `description:"数据日期"`
@@ -404,139 +332,6 @@ type ContractCompanyTradeDataList struct {
 	PureBuyChangeType int       `description:"净多单持仓增减类型: 0-无值; 1-原始值; 2-推算值"`
 }
 
-// GetLastTradeDataByClassify 获取[合约]末位多空单数据
-func GetLastTradeDataByClassify(exchange, classifyName string, contracts []string) (items []*OriginTradeData, err error) {
-	if exchange == "" {
-		err = fmt.Errorf("数据表名称有误")
-		return
-	}
-	if len(contracts) == 0 {
-		return
-	}
-	contractReplacer := utils.GetOrmInReplace(len(contracts))
-
-	tableName := fmt.Sprintf("base_from_trade_%s_index", exchange)
-	sql := `SELECT 
-			tpt.rank,
-			tpt.buy_short_name AS company_name,
-			tpt.buy_value AS val,
-			tpt.buy_change AS val_change,
-			tpt.classify_name,
-			tpt.classify_type,
-			tpt.data_time,
-			1 AS val_type
-		FROM 
-			%s tpt
-		JOIN 
-			(
-				SELECT
-					data_time, classify_type, MAX(rank) AS max_rank
-				FROM 
-					%s
-				WHERE 
-					classify_name = ? AND classify_type IN (%s) AND buy_short_name <> ''
-				GROUP BY 
-					data_time,
-					classify_type
-			) sub
-		ON
-			tpt.data_time = sub.data_time AND tpt.classify_type = sub.classify_type AND tpt.rank = sub.max_rank
-		WHERE 
-			tpt.classify_name = ? AND tpt.classify_type IN (%s)
-		UNION ALL
-		(
-		SELECT 
-			tpt.rank, tpt.sold_short_name, tpt.sold_value, tpt.sold_change, tpt.classify_name, tpt.classify_type, tpt.data_time, 2 AS val_type
-		FROM 
-			%s tpt
-		JOIN 
-			(
-				SELECT 
-					data_time, classify_type, MAX(rank) AS max_rank
-				FROM 
-					%s
-				WHERE 
-					classify_name = ? AND classify_type IN (%s) AND sold_short_name <> ''
-				GROUP BY 
-					data_time, classify_type
-			) sub
-		ON 
-			tpt.data_time = sub.data_time AND tpt.classify_type = sub.classify_type AND tpt.rank = sub.max_rank
-		WHERE 
-			tpt.classify_name = ? AND tpt.classify_type IN (%s)
-		)`
-	sql = fmt.Sprintf(sql, tableName, tableName, contractReplacer, contractReplacer, tableName, tableName, contractReplacer, contractReplacer)
-	o := orm.NewOrm()
-	_, err = o.Raw(sql, classifyName, contracts, classifyName, contracts, classifyName, contracts, classifyName, contracts).QueryRows(&items)
-	return
-}
-
-// GetLastTradeZhengzhouDataByClassify 郑商所-获取[合约]末位多空单数据
-func GetLastTradeZhengzhouDataByClassify(exchange string, contracts []string) (items []*OriginTradeData, err error) {
-	if exchange == "" {
-		err = fmt.Errorf("数据表名称有误")
-		return
-	}
-	if len(contracts) == 0 {
-		return
-	}
-	contractReplacer := utils.GetOrmInReplace(len(contracts))
-
-	tableName := fmt.Sprintf("base_from_trade_%s_index", exchange)
-	sql := `SELECT 
-			tpt.rank,
-			tpt.buy_short_name AS company_name,
-			tpt.buy_value AS val,
-			tpt.buy_change AS val_change,
-  			tpt.classify_name AS classify_type,
-			tpt.data_time,
-			1 AS val_type
-		FROM 
-			%s tpt
-		JOIN 
-			(
-				SELECT
-					data_time, classify_name, MAX(rank) AS max_rank
-				FROM 
-					%s
-				WHERE 
-					classify_name IN (%s) AND buy_short_name <> ''
-				GROUP BY 
-					data_time,
-					classify_name
-			) sub
-		ON
-			tpt.data_time = sub.data_time AND tpt.classify_name = sub.classify_name AND tpt.rank = sub.max_rank
-		WHERE 
-			tpt.classify_name IN (%s)
-		UNION ALL
-		(
-		SELECT 
-			tpt.rank, tpt.sold_short_name, tpt.sold_value, tpt.sold_change, tpt.classify_name AS classify_type, tpt.data_time, 2 AS val_type
-		FROM 
-			%s tpt
-		JOIN 
-			(
-				SELECT 
-					data_time, classify_name, MAX(rank) AS max_rank
-				FROM 
-					%s
-				WHERE 
-					classify_name IN (%s) AND sold_short_name <> ''
-				GROUP BY 
-					data_time, classify_name
-			) sub
-		ON 
-			tpt.data_time = sub.data_time AND tpt.classify_name = sub.classify_name AND tpt.rank = sub.max_rank
-		WHERE 
-			tpt.classify_name IN (%s)
-		)`
-	sql = fmt.Sprintf(sql, tableName, tableName, contractReplacer, contractReplacer, tableName, tableName, contractReplacer, contractReplacer)
-	o := orm.NewOrm()
-	_, err = o.Raw(sql, contracts, contracts, contracts, contracts).QueryRows(&items)
-	return
-}
-
 type BaseFromTradeGuangzhouIndex struct {
 	BaseFromTradeGuangzhouIndexId    int       `orm:"column(base_from_trade_guangzhou_index_id);pk"`
 	BaseFromTradeGuangzhouClassifyId int       `description:"分类id"`
@@ -550,10 +345,27 @@ type BaseFromTradeGuangzhouIndex struct {
 	ModifyTime                       time.Time `description:"修改日期"`
 }
 
-func GetBaseFromTradeGuangzhouIndexByClassifyId(classifyId int) (list []*BaseFromTradeGuangzhouIndex, err error) {
+func GetBaseFromTradeGuangzhouIndex(classifyIds []int, contracts []string, indexKeyword string) (list []*BaseFromTradeGuangzhouIndex, err error) {
 	o := orm.NewOrm()
-	sql := `SELECT * FROM base_from_trade_guangzhou_index WHERE base_from_trade_guangzhou_classify_id = ?`
-	_, err = o.Raw(sql, classifyId).QueryRows(&list)
+	cond := ``
+	pars := make([]interface{}, 0)
+	if len(classifyIds) > 0 {
+		cond += fmt.Sprintf(` AND b.base_from_trade_guangzhou_classify_id IN (%s)`, utils.GetOrmInReplace(len(classifyIds)))
+		pars = append(pars, classifyIds)
+	}
+	if len(contracts) > 0 {
+		cond += fmt.Sprintf(` AND b.contract IN (%s)`, utils.GetOrmInReplace(len(contracts)))
+		pars = append(pars, contracts)
+	}
+	if indexKeyword != "" {
+		cond += fmt.Sprintf(` AND a.index_name LIKE ?`)
+		pars = append(pars, indexKeyword)
+	}
+	sql := `SELECT a.* FROM base_from_trade_guangzhou_index AS a
+		JOIN base_from_trade_guangzhou_contract AS b ON a.base_from_trade_guangzhou_contract_id = b.base_from_trade_guangzhou_contract_id
+		WHERE 1=1 %s`
+	sql = fmt.Sprintf(sql, cond)
+	_, err = o.Raw(sql, pars).QueryRows(&list)
 	return
 }
 
@@ -568,45 +380,124 @@ type BaseFromTradeGuangzhouData struct {
 	ModifyTime                    time.Time `description:"修改日期"`
 }
 
-// GetBaseFromTradeGuangzhouDataByIndexIds 获取指标数据
-func GetBaseFromTradeGuangzhouDataByIndexIds(indexIds []int) (list []*BaseFromTradeGuangzhouData, err error) {
+// GetBaseFromTradeGuangzhouDataByIndexIds 广期所-获取指标数据
+func GetBaseFromTradeGuangzhouDataByIndexIds(indexIds []int, startDate, endDate time.Time) (list []*BaseFromTradeGuangzhouData, err error) {
 	if len(indexIds) == 0 {
 		return
 	}
-	o := orm.NewOrm()
-	sql := fmt.Sprintf(`SELECT * FROM base_from_trade_guangzhou_data WHERE base_from_trade_guangzhou_index_id IN (%s) ORDER BY base_from_trade_guangzhou_index_id`, utils.GetOrmInReplace(len(indexIds)))
-	_, err = o.Raw(sql, indexIds).QueryRows(&list)
+	cond := fmt.Sprintf(` AND base_from_trade_guangzhou_index_id IN (%s)`, utils.GetOrmInReplace(len(indexIds)))
+	pars := make([]interface{}, 0)
+	pars = append(pars, indexIds)
+	if !startDate.IsZero() && !endDate.IsZero() {
+		if startDate.Equal(endDate) {
+			cond += ` AND data_time = ?`
+			pars = append(pars, startDate.Format(utils.FormatDate))
+		}
+		if !startDate.Equal(endDate) {
+			cond += ` AND (data_time BETWEEN ? AND ?)`
+			pars = append(pars, startDate.Format(utils.FormatDate), endDate.Format(utils.FormatDate))
+		}
+	}
+	sql := fmt.Sprintf(`SELECT * FROM base_from_trade_guangzhou_data WHERE 1=1 %s ORDER BY base_from_trade_guangzhou_index_id`, cond)
+	_, err = orm.NewOrm().Raw(sql, pars).QueryRows(&list)
 	return
 }
 
-// GetBaseFromTradeGuangzhouMinDataByIndexIds 获取指标中的末位数据
-func GetBaseFromTradeGuangzhouMinDataByIndexIds(indexIds []int) (list []*BaseFromTradeGuangzhouData, err error) {
-	indexLen := len(indexIds)
-	if indexLen == 0 {
+// ContractTopRankData TOP20合约排名数据
+type ContractTopRankData struct {
+	Exchange      string    `description:"交易所"`
+	DealValue     int       `description:"成交量"`
+	BuyValue      int       `description:"多单持仓量"`
+	BuyChange     int       `description:"多单变化"`
+	SoldValue     int       `description:"空单持仓量"`
+	SoldChange    int       `description:"空单变化"`
+	PureBuyValue  int       `description:"净多单持仓量"`
+	PureBuyChange int       `description:"净多单变化"`
+	ClassifyName  string    `description:"品种名称"`
+	ClassifyType  string    `description:"合约代码"`
+	DataTime      time.Time `description:"数据日期"`
+}
+
+// GetContractTopRankData 获取合约TOP20根据当日成交量排名
+func GetContractTopRankData(exchange string, classifyNames []string, dataDate time.Time) (items []*ContractTopRankData, err error) {
+	if exchange == "" {
+		err = fmt.Errorf("数据表名称有误")
 		return
 	}
-	o := orm.NewOrm()
-	sql := fmt.Sprintf(`SELECT 
-			t1.data_time,
-			t1.min_value AS value
-		FROM 
-			(
-				SELECT 
-					data_time,
-					MIN(value) AS min_value
-				FROM 
-					base_from_trade_guangzhou_data
-				WHERE 
-					base_from_trade_guangzhou_index_id IN (%s)
-				GROUP BY 
-					data_time
-			) t1
-		JOIN 
-			base_from_trade_guangzhou_data t2
-		ON 
-			t1.data_time = t2.data_time AND t1.min_value = t2.value AND t2.base_from_trade_guangzhou_index_id IN (%s)
-		GROUP BY 
-			t1.data_time`, utils.GetOrmInReplace(indexLen), utils.GetOrmInReplace(indexLen))
-	_, err = o.Raw(sql, indexIds, indexIds).QueryRows(&list)
+	if len(classifyNames) == 0 {
+		return
+	}
+	tableName := fmt.Sprintf("base_from_trade_%s_index", exchange)
+	// 大商所存在TOP20的rank=0
+	queryRank := ` rank = 999`
+	if exchange == TradeExchangeDalian {
+		queryRank = ` (rank = 999 OR rank = 0)`
+	}
+	sql := `SELECT * FROM %s WHERE data_time = ? AND classify_name IN (%s) AND %s GROUP BY classify_type ORDER BY deal_value DESC`
+	sql = fmt.Sprintf(sql, tableName, utils.GetOrmInReplace(len(classifyNames)), queryRank)
+	_, err = orm.NewOrm().Raw(sql, dataDate.Format(utils.FormatDate), classifyNames).QueryRows(&items)
+	return
+}
+
+// GetZhengzhouContractTopRankData 郑商所-获取合约根据当日成交量排名
+func GetZhengzhouContractTopRankData(classifyNames []string, dataDate time.Time) (items []*ContractTopRankData, err error) {
+	if len(classifyNames) == 0 {
+		return
+	}
+	sql := `SELECT * FROM base_from_trade_zhengzhou_index WHERE data_time = ? AND classify_name IN (%s) AND rank = 999 GROUP BY classify_name ORDER BY deal_value DESC`
+	sql = fmt.Sprintf(sql, utils.GetOrmInReplace(len(classifyNames)))
+	_, err = orm.NewOrm().Raw(sql, dataDate.Format(utils.FormatDate), classifyNames).QueryRows(&items)
 	return
 }
+
+// ContractCompanyTradeEdb [合约-期货公司]指标
+type ContractCompanyTradeEdb struct {
+	Exchange         string                         `description:"交易所"`
+	ClassifyName     string                         `description:"品种"`
+	ClassifyType     string                         `description:"合约代码"`
+	CompanyName      string                         `description:"期货公司名称"`
+	IsTotal          bool                           `description:"是否为合约加总"`
+	ContractPosition int                            `description:"合约方向"`
+	StartDate        time.Time                      `description:"数据开始日期"`
+	EndDate          time.Time                      `description:"数据结束日期"`
+	DataList         []*ContractCompanyTradeEdbData `description:"数据序列"`
+}
+
+// ContractCompanyTradeEdbData [合约-期货公司]指标数据
+type ContractCompanyTradeEdbData struct {
+	DataTime time.Time `description:"数据日期"`
+	Val      int       `description:"数据值"`
+}
+
+// GetClassifyNewestDataTime 获取品种最新数据日期
+func GetClassifyNewestDataTime(exchange string, classifyNames []string) (dateTime time.Time, err error) {
+	if exchange == "" {
+		err = fmt.Errorf("数据表名称有误")
+		return
+	}
+	if len(classifyNames) == 0 {
+		return
+	}
+	tableName := fmt.Sprintf("base_from_trade_%s_index", exchange)
+	sql := `SELECT data_time FROM %s WHERE classify_name IN (%s) ORDER BY data_time DESC LIMIT 1`
+	sql = fmt.Sprintf(sql, tableName, utils.GetOrmInReplace(len(classifyNames)))
+	err = orm.NewOrm().Raw(sql, classifyNames).QueryRow(&dateTime)
+	return
+}
+
+// GetGuangzhouClassifyNewestDataTime 广期所-获取品种最新数据日期
+func GetGuangzhouClassifyNewestDataTime(indexIds []int) (dateTime time.Time, err error) {
+	if len(indexIds) == 0 {
+		return
+	}
+	cond := fmt.Sprintf(` AND base_from_trade_guangzhou_index_id IN (%s)`, utils.GetOrmInReplace(len(indexIds)))
+	pars := make([]interface{}, 0)
+	pars = append(pars, indexIds)
+	sql := fmt.Sprintf(`SELECT data_time FROM base_from_trade_guangzhou_data WHERE 1=1 %s ORDER BY data_time DESC LIMIT 1`, cond)
+	err = orm.NewOrm().Raw(sql, pars).QueryRow(&dateTime)
+	return
+}
+
+type RefreshTableReq struct {
+	ExcelInfoId int `description:"表格ID"`
+}

+ 42 - 0
models/trade_analysis/trade_analysis_correlation.go

@@ -0,0 +1,42 @@
+package trade_analysis
+
+// CorrelationTableExtraConfig 相关性表格配置
+type CorrelationTableExtraConfig struct {
+	BaseEdbInfoId    int                          `description:"标的指标ID"`
+	Exchange         string                       `description:"交易所标识"`
+	ClassifyName     string                       `description:"(单选)品种"`
+	CompanyNames     []string                     `description:"(多选)期货公司"`
+	ContractType     int                          `description:"合约类型: 1-主力合约; 2-成交量前2; 3-成交量前3; 4-所有合约(多个); 5-合约加总(1个)"`
+	ContractPosition int                          `description:"(单选)合约方向: 1-多; 2-空; 3-净多"`
+	PredictRatio     float64                      `description:"预估参数, 0-1之间"`
+	RollConfig       []CorrelationTableRollConfig `description:"滚动相关性配置"`
+}
+
+// CorrelationTableRollConfig 滚动相关性配置
+type CorrelationTableRollConfig struct {
+	CalculateValue int `description:"计算窗口"`
+	LeadValue      int `description:"领先期数"`
+}
+
+// CorrelationTableRowData 相关性表格行数据
+type CorrelationTableRowData struct {
+	Exchange     string                        `description:"交易所"`
+	ClassifyName string                        `description:"品种"`
+	ClassifyType string                        `description:"合约"`
+	CompanyName  string                        `description:"公司名称"`
+	RowName      string                        `description:"合约持仓名称"`
+	DayData      []*CorrelationTableRowDayData `description:"天数对应的相关性系数"`
+}
+
+// CorrelationTableRowDayData 相关性表格行数据值
+type CorrelationTableRowDayData struct {
+	Day      int     `description:"天数: 从0到-10, 0为最新数据, -1表示前一天的滚动相关性"`
+	DataDate string  `description:"对应的日期"`
+	DataVal  float64 `description:"相关性系数"`
+}
+
+// CorrelationTableData 相关性表格数据
+type CorrelationTableData struct {
+	CorrelationTableRollConfig `description:"滚动相关性配置, 每个配置单独为一张表"`
+	RowsData                   []*CorrelationTableRowData `description:"表格行数据"`
+}

+ 41 - 0
models/trade_analysis/trade_analysis_table.go

@@ -0,0 +1,41 @@
+package trade_analysis
+
+// TableExtraConfig 表格配置
+type TableExtraConfig struct {
+	CompanyName  string                     `description:"期货公司"`
+	ClassifyList []TableExtraConfigClassify `description:"交易所品种信息"`
+	ContractType int                        `description:"合约类型: 1-主力合约; 2-成交量前2; 3-成交量前3; 4-所有合约(多个); 5-合约加总(1个)"`
+	DateType     int                        `description:"0-最新日期(默认); 1-固定日期"`
+	IntervalMove int                        `description:"前移期数"`
+	FixedDate    string                     `description:"固定日期"`
+	PredictRatio float64                    `description:"预估参数, 0-1之间"`
+}
+
+// TableExtraConfigClassify 表格配置品种
+type TableExtraConfigClassify struct {
+	Exchange      string   `description:"交易所"`
+	ClassifyNames []string `description:"品种"`
+}
+
+// TableRowData 表格行数据
+type TableRowData struct {
+	Exchange         string  `description:"交易所"`
+	ClassifyName     string  `description:"品种"`
+	ClassifyType     string  `description:"合约"`
+	BuyValue         int     `description:"多单持仓量"`
+	BuyChange        int     `description:"多单变化"`
+	SoldValue        int     `description:"空单持仓量"`
+	SoldChange       int     `description:"空单变化"`
+	PureBuyVal       int     `description:"净多单持仓量"`
+	PureBuyChange    int     `description:"净多单持仓增减"`
+	BuySoldRatio     float64 `description:"多空比"`
+	BuyTopRatio      float64 `description:"多单占前20比例"`
+	SoldTopRatio     float64 `description:"空单占前20比例"`
+	TopBuyValue      int     `description:"前20多单"`
+	TopSoldValue     int     `description:"前20空单"`
+	TopBuyChange     int     `description:"前20多单变动"`
+	TopSoldChange    int     `description:"前20空单变动"`
+	TopPureBuy       int     `description:"前20净多单"`
+	TopPureBuyChange int     `description:"前20净多单变动"`
+	TopBuySoldRatio  float64 `description:"前20多空比"`
+}

+ 76 - 0
models/trade_analysis/trade_classify.go

@@ -0,0 +1,76 @@
+package trade_analysis
+
+import (
+	"fmt"
+	"github.com/beego/beego/v2/client/orm"
+	"strings"
+	"time"
+)
+
+// BaseFromTradeClassify 交易所分类表
+type BaseFromTradeClassify struct {
+	Id           uint64    `orm:"column(id);pk"`
+	ClassifyName string    //分类名称
+	ClassifyType string    //分类名称下的类型
+	Exchange     string    //交易所
+	LatestDate   time.Time //数据最近的日期
+	CreateTime   time.Time //插入时间
+	ModifyTime   time.Time //修改时间
+}
+
+func (m *BaseFromTradeClassify) TableName() string {
+	return "base_from_trade_classify"
+}
+
+type BaseFromTradeClassifyCols struct {
+	PrimaryId    string
+	Exchange     string
+	ClassifyName string
+	ClassifyType string
+	LatestDate   string
+	CreateTime   string
+	ModifyTime   string
+}
+
+func (m *BaseFromTradeClassify) Cols() BaseFromTradeClassifyCols {
+	return BaseFromTradeClassifyCols{
+		PrimaryId:    "id",
+		Exchange:     "exchange",
+		ClassifyName: "classify_name",
+		ClassifyType: "classify_type",
+		LatestDate:   "latest_date",
+		CreateTime:   "create_time",
+		ModifyTime:   "modify_time",
+	}
+}
+
+func (m *BaseFromTradeClassify) GetItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string) (items []*BaseFromTradeClassify, err error) {
+	o := orm.NewOrm()
+	fields := strings.Join(fieldArr, ",")
+	if len(fieldArr) == 0 {
+		fields = `*`
+	}
+	order := fmt.Sprintf(`ORDER BY %s DESC`, m.Cols().CreateTime)
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sql := fmt.Sprintf(`SELECT %s FROM %s WHERE 1=1 %s %s`, fields, m.TableName(), condition, order)
+	_, err = o.Raw(sql, pars).QueryRows(&items)
+	return
+}
+
+// GetClassifyItemsByCondition 获取品种信息
+func (m *BaseFromTradeClassify) GetClassifyItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string) (items []*BaseFromTradeClassify, err error) {
+	o := orm.NewOrm()
+	fields := strings.Join(fieldArr, ",")
+	if len(fieldArr) == 0 {
+		fields = `*`
+	}
+	order := fmt.Sprintf(`ORDER BY %s DESC`, m.Cols().CreateTime)
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sql := fmt.Sprintf(`SELECT %s FROM %s WHERE 1=1 %s GROUP BY %s %s`, fields, m.TableName(), condition, m.Cols().ClassifyName, order)
+	_, err = o.Raw(sql, pars).QueryRows(&items)
+	return
+}

+ 9 - 0
routers/commentsRouter.go

@@ -385,6 +385,15 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_index_lib/controllers:BaseFromTradeAnalysisController"] = append(beego.GlobalControllerRouter["eta/eta_index_lib/controllers:BaseFromTradeAnalysisController"],
+        beego.ControllerComments{
+            Method: "TableRefresh",
+            Router: `/table/refresh`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_index_lib/controllers:BloombergController"] = append(beego.GlobalControllerRouter["eta/eta_index_lib/controllers:BloombergController"],
         beego.ControllerComments{
             Method: "Add",

+ 148 - 0
services/trade_analysis/trade_analysis.go

@@ -0,0 +1,148 @@
+package trade_analysis
+
+import (
+	"eta/eta_index_lib/models/trade_analysis"
+	"eta/eta_index_lib/utils"
+	"fmt"
+	"strings"
+)
+
+func GetZhengzhouClassifyName(code string) (name string) {
+	if strings.HasPrefix(code, "PTA") {
+		name = "PTA"
+		return
+	}
+	if strings.HasPrefix(code, "TA") {
+		name = "PTA"
+		return
+	}
+	if strings.HasPrefix(code, "ZC") {
+		name = "动力煤"
+		return
+	}
+	if strings.HasPrefix(code, "WH") {
+		name = "强麦"
+		return
+	}
+	if strings.HasPrefix(code, "UR") {
+		name = "尿素"
+		return
+	}
+	if strings.HasPrefix(code, "SR") {
+		name = "白糖"
+		return
+	}
+	if strings.HasPrefix(code, "SM") {
+		name = "锰硅"
+		return
+	}
+	if strings.HasPrefix(code, "SF") {
+		name = "硅铁"
+		return
+	}
+	if strings.HasPrefix(code, "SA") {
+		name = "纯碱"
+		return
+	}
+	if strings.HasPrefix(code, "RS") {
+		name = "油菜籽"
+		return
+	}
+	if strings.HasPrefix(code, "RM") {
+		name = "菜籽粕"
+		return
+	}
+	if strings.HasPrefix(code, "RI") {
+		name = "早籼稻"
+		return
+	}
+	if strings.HasPrefix(code, "PM") {
+		name = "普麦"
+		return
+	}
+	if strings.HasPrefix(code, "PK") {
+		name = "花生"
+		return
+	}
+	if strings.HasPrefix(code, "PF") {
+		name = "涤纶短纤"
+		return
+	}
+	if strings.HasPrefix(code, "OI") {
+		name = "菜油"
+		return
+	}
+	if strings.HasPrefix(code, "MA") {
+		name = "甲醇"
+		return
+	}
+	if strings.HasPrefix(code, "LR") {
+		name = "晚籼稻"
+		return
+	}
+	if strings.HasPrefix(code, "JR") {
+		name = "粳稻"
+		return
+	}
+	if strings.HasPrefix(code, "FG") {
+		name = "玻璃"
+		return
+	}
+	if strings.HasPrefix(code, "CY") {
+		name = "棉纱"
+		return
+	}
+	if strings.HasPrefix(code, "CJ") {
+		name = "红枣"
+		return
+	}
+	if strings.HasPrefix(code, "CF") {
+		name = "棉花"
+		return
+	}
+	if strings.HasPrefix(code, "AP") {
+		name = "苹果"
+		return
+	}
+	if strings.HasPrefix(code, "PX") {
+		name = "PX"
+		return
+	}
+	if strings.HasPrefix(code, "SH") {
+		name = "烧碱"
+		return
+	}
+	if strings.HasPrefix(code, "PR") {
+		name = "瓶片"
+		return
+	}
+	if name == "" {
+		utils.FileLog.Info(fmt.Sprintf("郑商所-合约暂未归类: %s", code))
+	}
+	return
+}
+
+// GetZhengzhouContractsByClassifyNames 郑商所-获取所选品种下的所有合约
+func GetZhengzhouContractsByClassifyNames(classifyNames []string) (contracts []string, err error) {
+	var cond string
+	var pars []interface{}
+	classifyOb := new(trade_analysis.BaseFromTradeClassify)
+	cond += fmt.Sprintf(` AND %s = ?`, classifyOb.Cols().Exchange)
+	pars = append(pars, trade_analysis.TradeExchangeZhengzhou)
+	fields := []string{classifyOb.Cols().ClassifyName, classifyOb.Cols().Exchange}
+	list, e := classifyOb.GetClassifyItemsByCondition(cond, pars, fields, "id ASC")
+	if e != nil {
+		err = fmt.Errorf("获取郑商所品种合约失败, %v", e)
+		return
+	}
+	for _, v := range list {
+		classifyName := GetZhengzhouClassifyName(v.ClassifyName)
+		if classifyName == "" {
+			continue
+		}
+		if utils.InArrayByStr(classifyNames, classifyName) {
+			contracts = append(contracts, v.ClassifyName)
+		}
+	}
+	return
+}

+ 344 - 0
services/trade_analysis/trade_analysis_correlation.go

@@ -0,0 +1,344 @@
+package trade_analysis
+
+import (
+	"eta/eta_index_lib/models"
+	tradeAnalysisModel "eta/eta_index_lib/models/trade_analysis"
+	"eta/eta_index_lib/utils"
+	"fmt"
+	"math"
+	"time"
+)
+
+// CheckAnalysisCorrelationExtraConfig 校验相关性表格配置
+func CheckAnalysisCorrelationExtraConfig(extraConfig tradeAnalysisModel.CorrelationTableExtraConfig) (pass bool, tips string) {
+	if extraConfig.BaseEdbInfoId <= 0 {
+		tips = "请选择标的指标"
+		return
+	}
+	if extraConfig.Exchange == "" {
+		tips = "请选择交易所"
+		return
+	}
+	if extraConfig.ClassifyName == "" {
+		tips = "请选择品种"
+		return
+	}
+	if len(extraConfig.CompanyNames) == 0 {
+		tips = "请选择期货公司"
+		return
+	}
+	if len(extraConfig.CompanyNames) > 5 {
+		tips = "最多选择5个期货公司"
+		return
+	}
+	typeArr := []int{tradeAnalysisModel.ContractQueryTypeTop, tradeAnalysisModel.ContractQueryTypeTop2, tradeAnalysisModel.ContractQueryTypeTop3, tradeAnalysisModel.ContractQueryTypeAll, tradeAnalysisModel.ContractQueryTypeTotal}
+	if !utils.InArrayByInt(typeArr, extraConfig.ContractType) {
+		tips = "请选择正确的合约"
+		return
+	}
+	positionArr := []int{tradeAnalysisModel.ContractPositionBuy, tradeAnalysisModel.ContractPositionSold, tradeAnalysisModel.ContractPositionPureBuy}
+	if !utils.InArrayByInt(positionArr, extraConfig.ContractPosition) {
+		tips = "请选择正确的合约方向"
+		return
+	}
+	if len(extraConfig.RollConfig) == 0 {
+		tips = "请设置滚动相关性配置"
+		return
+	}
+	for _, v := range extraConfig.RollConfig {
+		if v.CalculateValue < 2 || v.CalculateValue > 200 {
+			tips = "计算窗口请输入2-200的整数"
+			return
+		}
+		if v.LeadValue < 0 || v.LeadValue > 60 {
+			tips = "B领先A请输入0-60的整数"
+			return
+		}
+	}
+	if extraConfig.PredictRatio < 0 || extraConfig.PredictRatio > 1 {
+		tips = "请输入正确的估计参数"
+		return
+	}
+	pass = true
+	return
+}
+
+// GetCorrelationTableRowsDataByConfig 根据配置获取相关性表格数据
+func GetCorrelationTableRowsDataByConfig(tableConfig tradeAnalysisModel.CorrelationTableExtraConfig) (tables []*tradeAnalysisModel.CorrelationTableData, err error) {
+	tables = make([]*tradeAnalysisModel.CorrelationTableData, 0)
+
+	// 获取标的指标数据
+	baseDateData := make(map[time.Time]float64)
+	{
+		baseEdb, e := models.GetChartEdbMappingByEdbInfoId(tableConfig.BaseEdbInfoId)
+		if e != nil {
+			err = fmt.Errorf("获取持仓相关性, 标的指标mapping信息失败, %v", e)
+			return
+		}
+		baseEdbData := make([]*models.EdbDataList, 0)
+		baseEdbData, e = models.GetEdbDataList(baseEdb.Source, baseEdb.SubSource, baseEdb.EdbInfoId, "", "")
+		if e != nil {
+			err = fmt.Errorf("获取标的指标数据失败, %v", e)
+			return
+		}
+		for _, v := range baseEdbData {
+			t, e := time.ParseInLocation(utils.FormatDate, v.DataTime, time.Local)
+			if e != nil {
+				continue
+			}
+			baseDateData[t] = v.Value
+		}
+	}
+
+	// 获取品种最新数据日期
+	baseDate, e := GetTradeClassifyNewestDataTime(tableConfig.Exchange, []string{tableConfig.ClassifyName})
+	if e != nil {
+		err = fmt.Errorf("获取持仓品种最新数据日期失败, %v", e)
+		return
+	}
+
+	// 根据配置取出需要查询的合约
+	contracts, e := GetTopContractsByType(tableConfig.ContractType, tableConfig.Exchange, tableConfig.ClassifyName, baseDate)
+	if e != nil {
+		err = fmt.Errorf("获取持仓相关性合约失败, %v", e)
+		return
+	}
+
+	// [合约+期货公司]持仓数据
+	tradeData, e := GetCorrelationTableTradeData(tableConfig.Exchange, tableConfig.ClassifyName, contracts, tableConfig.CompanyNames, tableConfig.PredictRatio, tableConfig.ContractType)
+	if e != nil {
+		err = fmt.Errorf("获取[合约+期货公司]持仓数据失败, %v", e)
+		return
+	}
+
+	// 持仓数据转为指标
+	tradeEdbList, tradeEdbDataMap, e := TransTradeData2EdbData(tradeData, tableConfig.ContractPosition)
+	if e != nil {
+		err = fmt.Errorf("[合约+期货公司]持仓数据转为指标失败, %v", e)
+		return
+	}
+
+	// 遍历相关性配置(目前最多有两个滚动相关性), 遍历指标, 分别计算滚动相关性
+	maxPre := 10 // 需要展示的以标的指标日期为准往前推N个交易日的滚动相关性
+	startBase := baseDate.AddDate(0, 0, -maxPre)
+
+	// 从20个自然日里截取11个数据对应的交易日,且当日排最前,后面依次为0、-1到-10
+	rowDays := utils.GetTradingDays(baseDate.AddDate(0, 0, -20), baseDate)
+	rowDays = utils.ReverseTimeSlice(rowDays)
+	rowDays = rowDays[:11]
+	for _, rc := range tableConfig.RollConfig {
+		tableData := new(tradeAnalysisModel.CorrelationTableData)
+		tableData.CalculateValue = rc.CalculateValue
+		tableData.LeadValue = rc.LeadValue
+		tableData.RowsData = make([]*tradeAnalysisModel.CorrelationTableRowData, 0)
+
+		// 计算滚动相关性的开始日期, 用计算窗口往前推(可以往前多推俩月否则可能取不到)
+		startDate := startBase.AddDate(0, 0, -(rc.CalculateValue + rc.LeadValue + 60))
+
+		for kd, kv := range tradeEdbList {
+			row := new(tradeAnalysisModel.CorrelationTableRowData)
+			row.Exchange = kv.Exchange
+			row.CompanyName = kv.CompanyName
+			row.ClassifyName = kv.ClassifyName
+			row.ClassifyType = kv.ClassifyType
+			// 数据为合约加总时名称为品种名+方向,否则为合约+期货公司+方向
+			if kv.IsTotal {
+				row.RowName = fmt.Sprintf("%s%s%s", kv.ClassifyName, kv.CompanyName, tradeAnalysisModel.WarehouseTypeSuffixNames[kv.ContractPosition])
+			} else {
+				row.RowName = fmt.Sprintf("%s%s%s", kv.ClassifyType, kv.CompanyName, tradeAnalysisModel.WarehouseTypeSuffixNames[kv.ContractPosition])
+			}
+			row.DayData = make([]*tradeAnalysisModel.CorrelationTableRowDayData, 0)
+
+			// 这里加行数据
+			rd, e := CalculateRollCorrelationData(rc.CalculateValue, rc.LeadValue, baseDateData, tradeEdbDataMap[kd], startDate, baseDate)
+			if e != nil {
+				err = fmt.Errorf("计算行数据-滚动相关性失败, %v", e)
+				return
+			}
+			for day, dt := range rowDays {
+				// 这里展示要加负号
+				var showDay int
+				if day == 0 {
+					showDay = 0
+				} else {
+					showDay = -day
+				}
+				row.DayData = append(row.DayData, &tradeAnalysisModel.CorrelationTableRowDayData{
+					Day:      showDay,
+					DataDate: dt.Format(utils.FormatDate),
+					DataVal:  rd[dt], // 对应日期无值的话就是0,无相关性
+				})
+			}
+			tableData.RowsData = append(tableData.RowsData, row)
+		}
+		tables = append(tables, tableData)
+	}
+	return
+}
+
+// GetTopContractsByType 根据配置获取需要取的合约
+func GetTopContractsByType(contractType int, exchange, classifyName string, baseDate time.Time) (contracts []string, err error) {
+	// 查询基准日期TOP20合约排名, 根据类型取出合约数
+	exchangeContracts := make(map[string][]*tradeAnalysisModel.ContractTopRankData) // 交易所最终取的合约
+	var contractMax int                                                             // 需要取出的品种对应的最大合约数
+	classifyMax := make(map[string]int)                                             // 品种对应的合约数
+	switch contractType {
+	case tradeAnalysisModel.ContractQueryTypeTop:
+		contractMax = 1
+	case tradeAnalysisModel.ContractQueryTypeTop2:
+		contractMax = 2
+	case tradeAnalysisModel.ContractQueryTypeTop3:
+		contractMax = 3
+	case tradeAnalysisModel.ContractQueryTypeAll, tradeAnalysisModel.ContractQueryTypeTotal:
+		contractMax = 999
+	}
+
+	// 遍历交易所, 查询各品种下的合约排名情况及TOP当日的多空单数据
+	contractRanks, e := GetTopContractRank(exchange, []string{classifyName}, baseDate)
+	if e != nil {
+		err = fmt.Errorf("获取基准日期合约排名失败, %v", e)
+		return
+	}
+
+	// ps.正常来讲这里查出来的合约是唯一的, 根据品种分组, 取出ContractType所需的合约数
+	for _, v := range contractRanks {
+		if classifyMax[v.ClassifyName] >= contractMax {
+			continue
+		}
+		classifyMax[v.ClassifyName] += 1
+		if exchangeContracts[v.Exchange] == nil {
+			exchangeContracts[v.Exchange] = make([]*tradeAnalysisModel.ContractTopRankData, 0)
+		}
+		contracts = append(contracts, v.ClassifyType)
+	}
+	return
+}
+
+// CalculateRollCorrelationData 计算滚动相关性
+func CalculateRollCorrelationData(calculateValue, leadValue int, baseEdbData map[time.Time]float64, changeEdbData map[time.Time]int, startDate, endDate time.Time) (dateRatio map[time.Time]float64, err error) {
+	dateRatio = make(map[time.Time]float64)
+
+	// 计算窗口,不包含第一天
+	startDate = startDate.AddDate(0, 0, 1)
+
+	// 平移变频指标
+	changeDataMap := MoveDataDaysToNewDataList(changeEdbData, leadValue)
+
+	// 计算计算时,需要多少个日期内数据
+	calculateDay := calculateValue
+
+	var minRatio, maxRatio float64
+	// 计算 每个日期的相关性值
+	startDateTime := startDate
+	endDateTime := endDate.AddDate(0, 0, -(calculateDay - 1))
+
+	// 是否开始第一条数据
+	var isStart, isNotFirst bool
+	for currDay := startDateTime; !currDay.After(endDateTime); currDay = currDay.AddDate(0, 0, 1) {
+		yCalculateData := make([]float64, 0)
+		baseCalculateData := make([]float64, 0)
+
+		// 取出对应的基准日期的值
+		for i := 0; i < calculateDay; i++ {
+			iDay := currDay.AddDate(0, 0, i)
+
+			tmpBaseValue, ok1 := baseEdbData[iDay]
+			tmpChangeValue, ok2 := changeDataMap[iDay]
+			if !ok1 || !ok2 {
+				continue
+			}
+			baseCalculateData = append(baseCalculateData, tmpBaseValue)
+			yCalculateData = append(yCalculateData, tmpChangeValue)
+		}
+
+		// 没有数据的话,那就不返回
+		if len(baseCalculateData) == 0 {
+			continue
+		}
+		// 公式计算出领先/滞后频度对应点的相关性系数
+		ratio := utils.CalculateCorrelationByIntArr(baseCalculateData, yCalculateData)
+
+		// 过滤前面都是0的数据
+		{
+			if ratio != 0 {
+				isStart = true
+			}
+
+			if !isStart {
+				continue
+			}
+		}
+
+		dataTime := currDay.AddDate(0, 0, calculateDay-1)
+
+		// 保留4位小数
+		ratio = math.Round(ratio*10000) / 10000
+		dateRatio[dataTime] = ratio
+
+		if !isNotFirst {
+			minRatio = ratio
+			maxRatio = ratio
+			isNotFirst = true
+		}
+		if minRatio > ratio {
+			minRatio = ratio
+		}
+		if maxRatio < ratio {
+			maxRatio = ratio
+		}
+	}
+	return
+}
+
+// MoveDataDaysToNewDataList 平移指标数据生成新的数据序列
+func MoveDataDaysToNewDataList(originDateData map[time.Time]int, moveDay int) (newDateData map[time.Time]float64) {
+	// 取出最早最晚的日期
+	var minDate, maxDate time.Time
+	for k, _ := range originDateData {
+		if minDate.IsZero() || k.Before(minDate) {
+			minDate = k
+		}
+		if maxDate.IsZero() || k.After(maxDate) {
+			maxDate = k
+		}
+	}
+
+	// 处理领先、滞后数据
+	newDateMap := make(map[time.Time]float64)
+	for currDate, value := range originDateData {
+		newDate := currDate.AddDate(0, 0, moveDay)
+		newDateMap[newDate] = float64(value)
+	}
+	minDate = minDate.AddDate(0, 0, moveDay)
+	maxDate = maxDate.AddDate(0, 0, moveDay)
+
+	// 创建一个有序的数据序列, 用于处理找不到数据时取前个交易日值的情况
+	type OrderedData struct {
+		DataTime time.Time
+		Val      float64
+	}
+	newDataList := make([]*OrderedData, 0)
+
+	// 获取日期相差日
+	dayNum := utils.GetTimeSubDay(minDate, maxDate)
+	newDateData = make(map[time.Time]float64)
+	for i := 0; i <= dayNum; i++ {
+		currDate := minDate.AddDate(0, 0, i)
+		tmpValue, ok := newDateMap[currDate]
+		if !ok {
+			//找不到数据,那么就用前面的数据吧
+			if len(newDataList)-1 < 0 {
+				tmpValue = 0
+			} else {
+				tmpValue = newDataList[len(newDataList)-1].Val
+			}
+		}
+		tmpData := &OrderedData{
+			DataTime: currDate,
+			Val:      tmpValue,
+		}
+		newDateData[currDate] = tmpData.Val
+		newDataList = append(newDataList, tmpData)
+	}
+	return
+}

+ 586 - 132
services/trade_analysis/trade_analysis_data.go

@@ -59,144 +59,18 @@ func FormatCompanyTradeData2EdbData(companyTradeData *tradeAnalysisModel.Contrac
 	return
 }
 
-func GetOriginTradeData(exchange, classifyName string, contracts, companies []string, predictRatio float64) (companyTradeData []*tradeAnalysisModel.ContractCompanyTradeData, err error) {
-	// 各原始数据表期货公司名称不一致
-	companyMap := make(map[string]string)
-	{
-		ob := new(tradeAnalysisModel.TradeFuturesCompany)
-		list, e := ob.GetItemsByCondition(``, make([]interface{}, 0), []string{}, "")
-		if e != nil {
-			err = fmt.Errorf("获取期货公司名称失败: %v", e)
-			return
-		}
-		switch exchange {
-		case "zhengzhou":
-			for _, v := range list {
-				companyMap[v.CompanyName] = v.ZhengzhouName
-			}
-		case "dalian":
-			for _, v := range list {
-				companyMap[v.CompanyName] = v.DalianName
-			}
-		case "shanghai":
-			for _, v := range list {
-				companyMap[v.CompanyName] = v.ShanghaiName
-			}
-		case "cffex":
-			for _, v := range list {
-				companyMap[v.CompanyName] = v.CffexName
-			}
-		case "ine":
-			for _, v := range list {
-				companyMap[v.CompanyName] = v.IneName
-			}
-		case "guangzhou":
-			for _, v := range list {
-				companyMap[v.CompanyName] = v.GuangzhouName
-			}
-		}
-	}
-	var queryCompanies []string
-	for _, v := range companies {
-		if v == tradeAnalysisModel.TradeFuturesCompanyTop20 {
-			queryCompanies = append(queryCompanies, tradeAnalysisModel.TradeFuturesCompanyTop20)
-			continue
-		}
-		companyName, ok := companyMap[v]
-		if !ok {
-			utils.FileLog.Info(fmt.Sprintf("交易所%s公司名称映射不存在: %s", exchange, v))
-			continue
-		}
-		queryCompanies = append(queryCompanies, companyName)
-	}
-
-	// 郑商所/广期所查询方式不一样
-	var tradeAnalysis TradeAnalysisInterface
-	switch exchange {
-	case tradeAnalysisModel.TradeExchangeZhengzhou:
-		tradeAnalysis = &ZhengzhouTradeAnalysis{}
-	case tradeAnalysisModel.TradeExchangeGuangzhou:
-		tradeAnalysis = &GuangzhouTradeAnalysis{}
-	default:
-		tradeAnalysis = &BaseTradeAnalysis{}
-	}
-
-	// 获取多单/空单原始数据
-	originList, e := tradeAnalysis.GetTradeDataByClassifyAndCompany(exchange, classifyName, contracts, queryCompanies)
+func GetWarehouseTradeData(exchange, classifyName string, contracts, companies []string, predictRatio float64) (companyTradeData []*tradeAnalysisModel.ContractCompanyTradeData, err error) {
+	// 获取合约持仓数据
+	contractTradeData, lastBuyVal, lastSoldVal, e := GetContractCompanyTradeData(exchange, []string{classifyName}, contracts, companies, time.Time{}, time.Time{})
 	if e != nil {
-		err = fmt.Errorf("获取多空单原始数据失败, %v", e)
+		err = fmt.Errorf("获取合约-持仓数据失败, %v", e)
 		return
 	}
 
-	keyItems := make(map[string]*tradeAnalysisModel.ContractCompanyTradeData)
-	keyDateData := make(map[string]*tradeAnalysisModel.ContractCompanyTradeDataList)
-	keyDateDataExist := make(map[string]bool)
-	for _, v := range originList {
-		// Rank999和0对应的是TOP20
-		companyName := v.CompanyName
-		if v.Rank == 999 || v.Rank == 0 {
-			companyName = tradeAnalysisModel.TradeFuturesCompanyTop20
-		}
-
-		k := fmt.Sprintf("%s-%s", v.ClassifyType, companyName)
-		if keyItems[k] == nil {
-			keyItems[k] = new(tradeAnalysisModel.ContractCompanyTradeData)
-			keyItems[k].CompanyName = companyName
-			keyItems[k].ClassifyType = v.ClassifyType
-			keyItems[k].DataList = make([]*tradeAnalysisModel.ContractCompanyTradeDataList, 0)
-		}
-
-		kd := fmt.Sprintf("%s-%s", k, v.DataTime.Format(utils.FormatDate))
-		if keyDateData[kd] == nil {
-			keyDateData[kd] = new(tradeAnalysisModel.ContractCompanyTradeDataList)
-			keyDateData[kd].Date = v.DataTime
-		}
-		if v.ValType == 1 {
-			keyDateData[kd].BuyVal = v.Val
-			keyDateData[kd].BuyValType = tradeAnalysisModel.TradeDataTypeOrigin
-			keyDateData[kd].BuyChange = v.ValChange
-			keyDateData[kd].BuyChangeType = tradeAnalysisModel.TradeDataTypeOrigin
-		}
-		if v.ValType == 2 {
-			keyDateData[kd].SoldVal = v.Val
-			keyDateData[kd].SoldValType = tradeAnalysisModel.TradeDataTypeOrigin
-			keyDateData[kd].SoldChange = v.ValChange
-			keyDateData[kd].SoldChangeType = tradeAnalysisModel.TradeDataTypeOrigin
-		}
-		if !keyDateDataExist[kd] {
-			keyItems[k].DataList = append(keyItems[k].DataList, keyDateData[kd])
-			keyDateDataExist[kd] = true
-		}
-	}
-
-	// 获取[合约]每日的末位多空单
-	contractLastBuyDateVal := make(map[string]map[time.Time]int)
-	contractLastSoldDateVal := make(map[string]map[time.Time]int)
-	{
-		lastOriginList, e := tradeAnalysis.GetLastTradeDataByClassify(exchange, classifyName, contracts)
-		if e != nil {
-			err = fmt.Errorf("获取末位多空单原始数据失败, %v", e)
-			return
-		}
-		for _, v := range lastOriginList {
-			if v.ValType == 1 {
-				if contractLastBuyDateVal[v.ClassifyType] == nil {
-					contractLastBuyDateVal[v.ClassifyType] = make(map[time.Time]int)
-				}
-				contractLastBuyDateVal[v.ClassifyType][v.DataTime] = v.Val
-				continue
-			}
-			if contractLastSoldDateVal[v.ClassifyType] == nil {
-				contractLastSoldDateVal[v.ClassifyType] = make(map[time.Time]int)
-			}
-			contractLastSoldDateVal[v.ClassifyType][v.DataTime] = v.Val
-		}
-	}
-
 	// 填充[合约-公司]预估数据, 并根据[公司-多合约]分组, [公司]算作一个指标, 指标值为[多个合约]的计算加总
 	companyContracts := make(map[string][]*tradeAnalysisModel.ContractCompanyTradeData)
-	for _, v := range keyItems {
-		td, fd, ed, e := PredictingTradeData(v.DataList, contractLastBuyDateVal[v.ClassifyType], contractLastSoldDateVal[v.ClassifyType], predictRatio)
+	for _, v := range contractTradeData {
+		td, fd, ed, e := PredictingTradeData(v.DataList, lastBuyVal[v.ClassifyType], lastSoldVal[v.ClassifyType], predictRatio)
 		if e != nil {
 			err = fmt.Errorf("数据补全失败, %v", e)
 			return
@@ -302,6 +176,87 @@ func GetOriginTradeData(exchange, classifyName string, contracts, companies []st
 
 // PredictingTradeData 根据数据库中的多空数据填充预估数据
 func PredictingTradeData(originData []*tradeAnalysisModel.ContractCompanyTradeDataList, lastBuyDateVal, lastSoldDateVal map[time.Time]int, predictRatio float64) (newData []*tradeAnalysisModel.ContractCompanyTradeDataList, firstDate, endDate time.Time, err error) {
+	// 测试用的验证数据
+	//lastBuyDateVal, lastSoldDateVal = make(map[time.Time]int), make(map[time.Time]int)
+	//lastBuyDateVal[time.Date(2024, 7, 16, 0, 0, 0, 0, time.Local)] = 4602
+	//lastBuyDateVal[time.Date(2024, 7, 17, 0, 0, 0, 0, time.Local)] = 5116
+	//lastBuyDateVal[time.Date(2024, 7, 18, 0, 0, 0, 0, time.Local)] = 5130
+	//lastBuyDateVal[time.Date(2024, 7, 19, 0, 0, 0, 0, time.Local)] = 5354
+	//lastBuyDateVal[time.Date(2024, 7, 22, 0, 0, 0, 0, time.Local)] = 5916
+	//lastBuyDateVal[time.Date(2024, 7, 23, 0, 0, 0, 0, time.Local)] = 6524
+	//lastBuyDateVal[time.Date(2024, 7, 26, 0, 0, 0, 0, time.Local)] = 6575
+	//lastBuyDateVal[time.Date(2024, 7, 29, 0, 0, 0, 0, time.Local)] = 7461
+	//lastBuyDateVal[time.Date(2024, 7, 30, 0, 0, 0, 0, time.Local)] = 8488
+	//
+	//lastSoldDateVal[time.Date(2024, 7, 11, 0, 0, 0, 0, time.Local)] = 5467
+	//lastSoldDateVal[time.Date(2024, 7, 12, 0, 0, 0, 0, time.Local)] = 5248
+	//lastSoldDateVal[time.Date(2024, 7, 15, 0, 0, 0, 0, time.Local)] = 5102
+	//lastSoldDateVal[time.Date(2024, 7, 16, 0, 0, 0, 0, time.Local)] = 4771
+	//lastSoldDateVal[time.Date(2024, 7, 23, 0, 0, 0, 0, time.Local)] = 5989
+	//lastSoldDateVal[time.Date(2024, 7, 26, 0, 0, 0, 0, time.Local)] = 6745
+	//lastSoldDateVal[time.Date(2024, 7, 30, 0, 0, 0, 0, time.Local)] = 7272
+	//
+	//originData = make([]*tradeAnalysisModel.ContractCompanyTradeDataList, 0)
+	//originData = append(originData, &tradeAnalysisModel.ContractCompanyTradeDataList{
+	//	Date:           time.Date(2024, 7, 10, 0, 0, 0, 0, time.Local),
+	//	BuyVal:         14324,
+	//	BuyValType:     tradeAnalysisModel.TradeDataTypeOrigin,
+	//	BuyChange:      -1107,
+	//	BuyChangeType:  tradeAnalysisModel.TradeDataTypeOrigin,
+	//	SoldVal:        0,
+	//	SoldValType:    tradeAnalysisModel.TradeDataTypeNull,
+	//	SoldChange:     0,
+	//	SoldChangeType: tradeAnalysisModel.TradeDataTypeNull,
+	//}, &tradeAnalysisModel.ContractCompanyTradeDataList{
+	//	Date:          time.Date(2024, 7, 11, 0, 0, 0, 0, time.Local),
+	//	BuyVal:        14280,
+	//	BuyValType:    tradeAnalysisModel.TradeDataTypeOrigin,
+	//	BuyChange:     -44,
+	//	BuyChangeType: tradeAnalysisModel.TradeDataTypeOrigin,
+	//}, &tradeAnalysisModel.ContractCompanyTradeDataList{
+	//	Date:          time.Date(2024, 7, 12, 0, 0, 0, 0, time.Local),
+	//	BuyVal:        14214,
+	//	BuyValType:    tradeAnalysisModel.TradeDataTypeOrigin,
+	//	BuyChange:     -66,
+	//	BuyChangeType: tradeAnalysisModel.TradeDataTypeOrigin,
+	//}, &tradeAnalysisModel.ContractCompanyTradeDataList{
+	//	Date:          time.Date(2024, 7, 15, 0, 0, 0, 0, time.Local),
+	//	BuyVal:        14269,
+	//	BuyValType:    tradeAnalysisModel.TradeDataTypeOrigin,
+	//	BuyChange:     55,
+	//	BuyChangeType: tradeAnalysisModel.TradeDataTypeOrigin,
+	//}, &tradeAnalysisModel.ContractCompanyTradeDataList{
+	//	Date:           time.Date(2024, 7, 17, 0, 0, 0, 0, time.Local),
+	//	SoldVal:        5254,
+	//	SoldValType:    tradeAnalysisModel.TradeDataTypeOrigin,
+	//	SoldChange:     708,
+	//	SoldChangeType: tradeAnalysisModel.TradeDataTypeOrigin,
+	//}, &tradeAnalysisModel.ContractCompanyTradeDataList{
+	//	Date:           time.Date(2024, 7, 18, 0, 0, 0, 0, time.Local),
+	//	SoldVal:        6595,
+	//	SoldValType:    tradeAnalysisModel.TradeDataTypeOrigin,
+	//	SoldChange:     1341,
+	//	SoldChangeType: tradeAnalysisModel.TradeDataTypeOrigin,
+	//}, &tradeAnalysisModel.ContractCompanyTradeDataList{
+	//	Date:           time.Date(2024, 7, 19, 0, 0, 0, 0, time.Local),
+	//	SoldVal:        5938,
+	//	SoldValType:    tradeAnalysisModel.TradeDataTypeOrigin,
+	//	SoldChange:     -657,
+	//	SoldChangeType: tradeAnalysisModel.TradeDataTypeOrigin,
+	//}, &tradeAnalysisModel.ContractCompanyTradeDataList{
+	//	Date:           time.Date(2024, 7, 22, 0, 0, 0, 0, time.Local),
+	//	SoldVal:        6131,
+	//	SoldValType:    tradeAnalysisModel.TradeDataTypeOrigin,
+	//	SoldChange:     193,
+	//	SoldChangeType: tradeAnalysisModel.TradeDataTypeOrigin,
+	//}, &tradeAnalysisModel.ContractCompanyTradeDataList{
+	//	Date:           time.Date(2024, 7, 29, 0, 0, 0, 0, time.Local),
+	//	SoldVal:        6679,
+	//	SoldValType:    tradeAnalysisModel.TradeDataTypeOrigin,
+	//	SoldChange:     312,
+	//	SoldChangeType: tradeAnalysisModel.TradeDataTypeOrigin,
+	//})
+
 	if len(originData) == 0 {
 		return
 	}
@@ -478,3 +433,502 @@ func PredictingTradeData(originData []*tradeAnalysisModel.ContractCompanyTradeDa
 	}
 	return
 }
+
+// GetTopContractRank 获取TOP20根据成交量的合约排名
+func GetTopContractRank(exchange string, classifyNames []string, dataDate time.Time) (items []*tradeAnalysisModel.ContractTopRankData, err error) {
+	// 郑商所/广期所查询方式不一样
+	var tradeAnalysis TradeAnalysisInterface
+	switch exchange {
+	case tradeAnalysisModel.TradeExchangeZhengzhou:
+		tradeAnalysis = &ZhengzhouTradeAnalysis{}
+	case tradeAnalysisModel.TradeExchangeGuangzhou:
+		tradeAnalysis = &GuangzhouTradeAnalysis{}
+	default:
+		tradeAnalysis = &BaseTradeAnalysis{}
+	}
+
+	// 郑商所-需要把所选品种转为实际合约进行后续的查询
+	if exchange == tradeAnalysisModel.TradeExchangeZhengzhou {
+		classifies, e := GetZhengzhouContractsByClassifyNames(classifyNames)
+		if e != nil {
+			err = fmt.Errorf("获取郑商所实际合约失败, %v", e)
+			return
+		}
+		classifyNames = classifies
+	}
+
+	// 获取多单/空单原始数据
+	rankData, e := tradeAnalysis.GetContractTopRankData(exchange, classifyNames, dataDate)
+	if e != nil {
+		err = fmt.Errorf("获取多空单原始数据失败, %v", e)
+		return
+	}
+	items = make([]*tradeAnalysisModel.ContractTopRankData, 0)
+	for _, v := range rankData {
+		v.Exchange = exchange
+		// 郑商所-这里注意把查出来的品种和合约赋值,不然后续是乱的
+		if v.Exchange == tradeAnalysisModel.TradeExchangeZhengzhou {
+			v.ClassifyType = v.ClassifyName
+			v.ClassifyName = GetZhengzhouClassifyName(v.ClassifyName)
+		}
+		items = append(items, v)
+	}
+	return
+}
+
+// GetTableTradeData 获取多空分析表格持仓数据
+func GetTableTradeData(exchange string, classifyName string, contracts []string, companyName string, predictRatio float64, startDate, endDate time.Time, contractType int) (companyTradeData []*tradeAnalysisModel.ContractCompanyTradeData, err error) {
+	companyTradeData = make([]*tradeAnalysisModel.ContractCompanyTradeData, 0)
+
+	// 获取合约持仓数据
+	contractTradeData, lastBuyVal, lastSoldVal, e := GetContractCompanyTradeData(exchange, []string{classifyName}, contracts, []string{companyName}, startDate, endDate)
+	if e != nil {
+		err = fmt.Errorf("获取合约-持仓数据失败, %v", e)
+		return
+	}
+
+	// 填充[合约-公司]预估数据, 并根据[公司-多合约]分组, [公司]算作一个指标, 指标值为[多个合约]的计算加总
+	companyContracts := make(map[string][]*tradeAnalysisModel.ContractCompanyTradeData)
+	for _, v := range contractTradeData {
+		td, fd, ed, e := PredictingTradeData(v.DataList, lastBuyVal[v.ClassifyType], lastSoldVal[v.ClassifyType], predictRatio)
+		if e != nil {
+			err = fmt.Errorf("数据补全失败, %v", e)
+			return
+		}
+		v.DataList = td
+		v.StartDate = fd
+		v.EndDate = ed
+
+		// 合约类型参数不为合约加总时, 每个合约算一行数据
+		if contractType != tradeAnalysisModel.ContractQueryTypeTotal {
+			companyTradeData = append(companyTradeData, v)
+			continue
+		}
+
+		// 往下计算合约加总
+		if companyContracts[v.CompanyName] == nil {
+			companyContracts[v.CompanyName] = make([]*tradeAnalysisModel.ContractCompanyTradeData, 0)
+		}
+		companyContracts[v.CompanyName] = append(companyContracts[v.CompanyName], v)
+	}
+
+	// 类型为合约加总才往下合并
+	if contractType != tradeAnalysisModel.ContractQueryTypeTotal {
+		return
+	}
+
+	// 以[公司]为组, 计算合约加总
+	for k, v := range companyContracts {
+		companyData := new(tradeAnalysisModel.ContractCompanyTradeData)
+		companyData.CompanyName = k
+		companyData.DataList = make([]*tradeAnalysisModel.ContractCompanyTradeDataList, 0)
+		contractArr := make([]string, 0)
+
+		// 合约加总
+		sumDateData := make(map[time.Time]*tradeAnalysisModel.ContractCompanyTradeDataList)
+		for _, vv := range v {
+			contractArr = append(contractArr, vv.ClassifyType)
+			for _, dv := range vv.DataList {
+				if sumDateData[dv.Date] == nil {
+					sumDateData[dv.Date] = new(tradeAnalysisModel.ContractCompanyTradeDataList)
+					sumDateData[dv.Date].Date = dv.Date
+				}
+				// 数据类型以第一个非零值为准, 只处理多空和净多, 变化就不管了
+				if sumDateData[dv.Date].BuyValType == tradeAnalysisModel.TradeDataTypeNull && dv.BuyValType > tradeAnalysisModel.TradeDataTypeNull {
+					sumDateData[dv.Date].BuyValType = dv.BuyValType
+				}
+				if sumDateData[dv.Date].BuyValType == tradeAnalysisModel.TradeDataTypeOrigin && dv.BuyValType == tradeAnalysisModel.TradeDataTypeCalculate {
+					sumDateData[dv.Date].BuyValType = dv.BuyValType
+				}
+				if dv.BuyValType > tradeAnalysisModel.TradeDataTypeNull {
+					sumDateData[dv.Date].BuyVal += dv.BuyVal
+				}
+				// 空单
+				if sumDateData[dv.Date].SoldValType == tradeAnalysisModel.TradeDataTypeNull && dv.SoldValType > tradeAnalysisModel.TradeDataTypeNull {
+					sumDateData[dv.Date].SoldValType = dv.SoldValType
+				}
+				if sumDateData[dv.Date].SoldValType == tradeAnalysisModel.TradeDataTypeOrigin && dv.SoldValType == tradeAnalysisModel.TradeDataTypeCalculate {
+					sumDateData[dv.Date].SoldValType = dv.SoldValType
+				}
+				if dv.SoldValType > tradeAnalysisModel.TradeDataTypeNull {
+					sumDateData[dv.Date].SoldVal += dv.SoldVal
+				}
+				// 净多单
+				if sumDateData[dv.Date].PureBuyValType == tradeAnalysisModel.TradeDataTypeNull && dv.PureBuyValType > tradeAnalysisModel.TradeDataTypeNull {
+					sumDateData[dv.Date].PureBuyValType = dv.PureBuyValType
+				}
+				if sumDateData[dv.Date].PureBuyValType == tradeAnalysisModel.TradeDataTypeOrigin && dv.PureBuyValType == tradeAnalysisModel.TradeDataTypeCalculate {
+					sumDateData[dv.Date].PureBuyValType = dv.PureBuyValType
+				}
+				if dv.PureBuyValType > tradeAnalysisModel.TradeDataTypeNull {
+					sumDateData[dv.Date].PureBuyVal += dv.PureBuyVal
+				}
+			}
+
+			// 多个合约比对开始结束时间
+			if companyData.StartDate.IsZero() {
+				companyData.StartDate = vv.StartDate
+			}
+			if vv.StartDate.Before(companyData.StartDate) {
+				companyData.StartDate = vv.StartDate
+			}
+			if companyData.EndDate.IsZero() {
+				companyData.EndDate = vv.EndDate
+			}
+			if vv.EndDate.Before(companyData.EndDate) {
+				companyData.EndDate = vv.EndDate
+			}
+		}
+		for _, sv := range sumDateData {
+			companyData.DataList = append(companyData.DataList, sv)
+		}
+		sort.Slice(companyData.DataList, func(i, j int) bool {
+			return companyData.DataList[i].Date.Before(companyData.DataList[j].Date)
+		})
+		//companyData.ClassifyType = strings.Join(contractArr, ",")
+		companyData.Exchange = exchange
+		companyData.CompanyName = k
+		companyData.ClassifyType = classifyName
+		companyTradeData = append(companyTradeData, companyData)
+	}
+	return
+}
+
+// GetCorrelationTableTradeData 获取相关性表格持仓数据
+func GetCorrelationTableTradeData(exchange string, classifyName string, contracts, companies []string, predictRatio float64, contractType int) (companyTradeData []*tradeAnalysisModel.ContractCompanyTradeData, err error) {
+	companyTradeData = make([]*tradeAnalysisModel.ContractCompanyTradeData, 0)
+
+	// 获取合约持仓数据
+	contractTradeData, lastBuyVal, lastSoldVal, e := GetContractCompanyTradeData(exchange, []string{classifyName}, contracts, companies, time.Time{}, time.Time{})
+	if e != nil {
+		err = fmt.Errorf("获取合约-持仓数据失败, %v", e)
+		return
+	}
+
+	// 填充[合约-公司]预估数据, 并根据[公司-多合约]分组, [公司]算作一个指标, 指标值为[多个合约]的计算加总
+	companyContracts := make(map[string][]*tradeAnalysisModel.ContractCompanyTradeData)
+	for _, v := range contractTradeData {
+		td, fd, ed, e := PredictingTradeData(v.DataList, lastBuyVal[v.ClassifyType], lastSoldVal[v.ClassifyType], predictRatio)
+		if e != nil {
+			err = fmt.Errorf("数据补全失败, %v", e)
+			return
+		}
+		v.DataList = td
+		v.StartDate = fd
+		v.EndDate = ed
+
+		// 合约类型参数不为合约加总时, 每个合约算一行数据
+		if contractType != tradeAnalysisModel.ContractQueryTypeTotal {
+			companyTradeData = append(companyTradeData, v)
+			continue
+		}
+
+		// 往下计算合约加总
+		if companyContracts[v.CompanyName] == nil {
+			companyContracts[v.CompanyName] = make([]*tradeAnalysisModel.ContractCompanyTradeData, 0)
+		}
+		companyContracts[v.CompanyName] = append(companyContracts[v.CompanyName], v)
+	}
+
+	// 类型为合约加总才往下合并
+	if contractType != tradeAnalysisModel.ContractQueryTypeTotal {
+		return
+	}
+
+	// 以[公司]为组, 计算合约加总
+	for k, v := range companyContracts {
+		companyData := new(tradeAnalysisModel.ContractCompanyTradeData)
+		companyData.CompanyName = k
+		companyData.DataList = make([]*tradeAnalysisModel.ContractCompanyTradeDataList, 0)
+		contractArr := make([]string, 0)
+
+		// 合约加总
+		sumDateData := make(map[time.Time]*tradeAnalysisModel.ContractCompanyTradeDataList)
+		for _, vv := range v {
+			contractArr = append(contractArr, vv.ClassifyType)
+			for _, dv := range vv.DataList {
+				if sumDateData[dv.Date] == nil {
+					sumDateData[dv.Date] = new(tradeAnalysisModel.ContractCompanyTradeDataList)
+					sumDateData[dv.Date].Date = dv.Date
+				}
+				// 数据类型以第一个非零值为准, 只处理多空和净多, 变化就不管了
+				if sumDateData[dv.Date].BuyValType == tradeAnalysisModel.TradeDataTypeNull && dv.BuyValType > tradeAnalysisModel.TradeDataTypeNull {
+					sumDateData[dv.Date].BuyValType = dv.BuyValType
+				}
+				if sumDateData[dv.Date].BuyValType == tradeAnalysisModel.TradeDataTypeOrigin && dv.BuyValType == tradeAnalysisModel.TradeDataTypeCalculate {
+					sumDateData[dv.Date].BuyValType = dv.BuyValType
+				}
+				if dv.BuyValType > tradeAnalysisModel.TradeDataTypeNull {
+					sumDateData[dv.Date].BuyVal += dv.BuyVal
+				}
+				// 空单
+				if sumDateData[dv.Date].SoldValType == tradeAnalysisModel.TradeDataTypeNull && dv.SoldValType > tradeAnalysisModel.TradeDataTypeNull {
+					sumDateData[dv.Date].SoldValType = dv.SoldValType
+				}
+				if sumDateData[dv.Date].SoldValType == tradeAnalysisModel.TradeDataTypeOrigin && dv.SoldValType == tradeAnalysisModel.TradeDataTypeCalculate {
+					sumDateData[dv.Date].SoldValType = dv.SoldValType
+				}
+				if dv.SoldValType > tradeAnalysisModel.TradeDataTypeNull {
+					sumDateData[dv.Date].SoldVal += dv.SoldVal
+				}
+				// 净多单
+				if sumDateData[dv.Date].PureBuyValType == tradeAnalysisModel.TradeDataTypeNull && dv.PureBuyValType > tradeAnalysisModel.TradeDataTypeNull {
+					sumDateData[dv.Date].PureBuyValType = dv.PureBuyValType
+				}
+				if sumDateData[dv.Date].PureBuyValType == tradeAnalysisModel.TradeDataTypeOrigin && dv.PureBuyValType == tradeAnalysisModel.TradeDataTypeCalculate {
+					sumDateData[dv.Date].PureBuyValType = dv.PureBuyValType
+				}
+				if dv.PureBuyValType > tradeAnalysisModel.TradeDataTypeNull {
+					sumDateData[dv.Date].PureBuyVal += dv.PureBuyVal
+				}
+			}
+
+			// 多个合约比对开始结束时间
+			if companyData.StartDate.IsZero() {
+				companyData.StartDate = vv.StartDate
+			}
+			if vv.StartDate.Before(companyData.StartDate) {
+				companyData.StartDate = vv.StartDate
+			}
+			if companyData.EndDate.IsZero() {
+				companyData.EndDate = vv.EndDate
+			}
+			if vv.EndDate.Before(companyData.EndDate) {
+				companyData.EndDate = vv.EndDate
+			}
+		}
+		for _, sv := range sumDateData {
+			companyData.DataList = append(companyData.DataList, sv)
+		}
+		sort.Slice(companyData.DataList, func(i, j int) bool {
+			return companyData.DataList[i].Date.Before(companyData.DataList[j].Date)
+		})
+		//companyData.ClassifyType = strings.Join(contractArr, ",")
+		companyData.IsTotal = true
+		companyData.Exchange = exchange
+		companyData.CompanyName = k
+		companyData.ClassifyName = classifyName
+		companyData.ClassifyType = classifyName
+		companyTradeData = append(companyTradeData, companyData)
+	}
+	return
+}
+
+// TransTradeData2EdbData 持仓数据转为指标数据
+func TransTradeData2EdbData(tradeData []*tradeAnalysisModel.ContractCompanyTradeData, contractPosition int) (edbData []*tradeAnalysisModel.ContractCompanyTradeEdb, edbDataMap []map[time.Time]int, err error) {
+	if len(tradeData) == 0 {
+		return
+	}
+	edbData = make([]*tradeAnalysisModel.ContractCompanyTradeEdb, 0)
+	edbDataMap = make([]map[time.Time]int, 0)
+
+	for _, v := range tradeData {
+		newEdb := new(tradeAnalysisModel.ContractCompanyTradeEdb)
+		newEdb.Exchange = v.Exchange
+		newEdb.ClassifyName = v.ClassifyName
+		newEdb.ClassifyType = v.ClassifyType
+		newEdb.CompanyName = v.CompanyName
+		newEdb.IsTotal = v.IsTotal
+		newEdb.ContractPosition = contractPosition
+		newEdb.StartDate = v.StartDate
+		newEdb.EndDate = v.EndDate
+		newEdb.DataList = make([]*tradeAnalysisModel.ContractCompanyTradeEdbData, 0)
+		dataMap := make(map[time.Time]int)
+		for _, d := range v.DataList {
+			var vd int
+			switch contractPosition {
+			case tradeAnalysisModel.ContractPositionBuy:
+				if d.BuyValType == tradeAnalysisModel.TradeDataTypeNull {
+					continue
+				}
+				vd = d.BuyVal
+			case tradeAnalysisModel.ContractPositionSold:
+				if d.SoldValType == tradeAnalysisModel.TradeDataTypeNull {
+					continue
+				}
+				vd = d.SoldVal
+			case tradeAnalysisModel.ContractPositionPureBuy:
+				if d.PureBuyValType == tradeAnalysisModel.TradeDataTypeNull {
+					continue
+				}
+				vd = d.PureBuyVal
+			default:
+				continue
+			}
+			newEdb.DataList = append(newEdb.DataList, &tradeAnalysisModel.ContractCompanyTradeEdbData{
+				DataTime: d.Date,
+				Val:      vd,
+			})
+			dataMap[d.Date] = vd
+		}
+		edbData = append(edbData, newEdb)
+		edbDataMap = append(edbDataMap, dataMap)
+	}
+	return
+}
+
+// GetContractCompanyTradeData 获取合约持仓数据
+func GetContractCompanyTradeData(exchange string, classifyNames, contracts, companies []string, startDate, endDate time.Time) (contractTradeData map[string]*tradeAnalysisModel.ContractCompanyTradeData, lastBuyVal, lastSoldVal map[string]map[time.Time]int, err error) {
+	// 各原始数据表期货公司名称不一致
+	companyMap := make(map[string]string)
+	{
+		ob := new(tradeAnalysisModel.TradeFuturesCompany)
+		list, e := ob.GetItemsByCondition(``, make([]interface{}, 0), []string{}, "")
+		if e != nil {
+			err = fmt.Errorf("获取期货公司名称失败: %v", e)
+			return
+		}
+		switch exchange {
+		case "zhengzhou":
+			for _, v := range list {
+				companyMap[v.CompanyName] = v.ZhengzhouName
+			}
+		case "dalian":
+			for _, v := range list {
+				companyMap[v.CompanyName] = v.DalianName
+			}
+		case "shanghai":
+			for _, v := range list {
+				companyMap[v.CompanyName] = v.ShanghaiName
+			}
+		case "cffex":
+			for _, v := range list {
+				companyMap[v.CompanyName] = v.CffexName
+			}
+		case "ine":
+			for _, v := range list {
+				companyMap[v.CompanyName] = v.IneName
+			}
+		case "guangzhou":
+			for _, v := range list {
+				companyMap[v.CompanyName] = v.GuangzhouName
+			}
+		}
+	}
+	var queryCompanies []string
+	for _, v := range companies {
+		if v == tradeAnalysisModel.TradeFuturesCompanyTop20 {
+			queryCompanies = append(queryCompanies, tradeAnalysisModel.TradeFuturesCompanyTop20)
+			continue
+		}
+		companyName, ok := companyMap[v]
+		if !ok {
+			utils.FileLog.Info(fmt.Sprintf("交易所%s公司名称映射不存在: %s", exchange, v))
+			continue
+		}
+		queryCompanies = append(queryCompanies, companyName)
+	}
+
+	// 郑商所/广期所查询方式不一样
+	var tradeAnalysis TradeAnalysisInterface
+	switch exchange {
+	case tradeAnalysisModel.TradeExchangeZhengzhou:
+		tradeAnalysis = &ZhengzhouTradeAnalysis{}
+	case tradeAnalysisModel.TradeExchangeGuangzhou:
+		tradeAnalysis = &GuangzhouTradeAnalysis{}
+	default:
+		tradeAnalysis = &BaseTradeAnalysis{}
+	}
+
+	// 获取多单/空单原始数据
+	originList, _, lastOriginList, e := tradeAnalysis.GetTradeDataByContracts(exchange, classifyNames, contracts, queryCompanies, startDate, endDate)
+	if e != nil {
+		err = fmt.Errorf("获取多空单原始数据失败, %v", e)
+		return
+	}
+
+	// [合约-期货公司]数据分组
+	contractTradeData = make(map[string]*tradeAnalysisModel.ContractCompanyTradeData)
+	{
+		keyDateData := make(map[string]*tradeAnalysisModel.ContractCompanyTradeDataList)
+		keyDateDataExist := make(map[string]bool)
+		for _, v := range originList {
+			companyName := v.CompanyName
+
+			k := fmt.Sprintf("%s-%s", v.ClassifyType, companyName)
+			if contractTradeData[k] == nil {
+				contractTradeData[k] = new(tradeAnalysisModel.ContractCompanyTradeData)
+				contractTradeData[k].Exchange = exchange
+				contractTradeData[k].CompanyName = companyName
+				contractTradeData[k].ClassifyName = v.ClassifyName
+				contractTradeData[k].ClassifyType = v.ClassifyType
+				contractTradeData[k].DataList = make([]*tradeAnalysisModel.ContractCompanyTradeDataList, 0)
+			}
+
+			kd := fmt.Sprintf("%s-%s", k, v.DataTime.Format(utils.FormatDate))
+			if keyDateData[kd] == nil {
+				keyDateData[kd] = new(tradeAnalysisModel.ContractCompanyTradeDataList)
+				keyDateData[kd].Date = v.DataTime
+			}
+			if v.ValType == 1 {
+				keyDateData[kd].BuyVal = v.Val
+				keyDateData[kd].BuyValType = tradeAnalysisModel.TradeDataTypeOrigin
+				keyDateData[kd].BuyChange = v.ValChange
+				keyDateData[kd].BuyChangeType = tradeAnalysisModel.TradeDataTypeOrigin
+			}
+			if v.ValType == 2 {
+				keyDateData[kd].SoldVal = v.Val
+				keyDateData[kd].SoldValType = tradeAnalysisModel.TradeDataTypeOrigin
+				keyDateData[kd].SoldChange = v.ValChange
+				keyDateData[kd].SoldChangeType = tradeAnalysisModel.TradeDataTypeOrigin
+			}
+			if !keyDateDataExist[kd] {
+				contractTradeData[k].DataList = append(contractTradeData[k].DataList, keyDateData[kd])
+				keyDateDataExist[kd] = true
+			}
+		}
+	}
+
+	// 合约的[日期-末位值]
+	lastBuyVal = make(map[string]map[time.Time]int)
+	lastSoldVal = make(map[string]map[time.Time]int)
+	{
+		for _, v := range lastOriginList {
+			if v.ValType == 1 {
+				if lastBuyVal[v.ClassifyType] == nil {
+					lastBuyVal[v.ClassifyType] = make(map[time.Time]int)
+				}
+				lastBuyVal[v.ClassifyType][v.DataTime] = v.Val
+				continue
+			}
+			if lastSoldVal[v.ClassifyType] == nil {
+				lastSoldVal[v.ClassifyType] = make(map[time.Time]int)
+			}
+			lastSoldVal[v.ClassifyType][v.DataTime] = v.Val
+		}
+	}
+	return
+}
+
+// GetTradeClassifyNewestDataTime 获取数据最新日期
+func GetTradeClassifyNewestDataTime(exchange string, classifyNames []string) (dataTime time.Time, err error) {
+	var tradeAnalysis TradeAnalysisInterface
+	switch exchange {
+	case tradeAnalysisModel.TradeExchangeZhengzhou:
+		tradeAnalysis = &ZhengzhouTradeAnalysis{}
+	case tradeAnalysisModel.TradeExchangeGuangzhou:
+		tradeAnalysis = &GuangzhouTradeAnalysis{}
+	default:
+		tradeAnalysis = &BaseTradeAnalysis{}
+	}
+	if exchange == tradeAnalysisModel.TradeExchangeZhengzhou {
+		classifies, e := GetZhengzhouContractsByClassifyNames(classifyNames)
+		if e != nil {
+			err = fmt.Errorf("获取郑商所实际合约失败, %v", e)
+			return
+		}
+		classifyNames = classifies
+	}
+
+	d, e := tradeAnalysis.GetClassifyNewestDataTime(exchange, classifyNames)
+	if e != nil && e.Error() != utils.ErrNoRow() {
+		err = fmt.Errorf("获取品种最新数据日期失败, %v", e)
+		return
+	}
+	if !d.IsZero() {
+		dataTime = d
+	} else {
+		dataTime = time.Now().Local()
+	}
+	return
+}

+ 333 - 67
services/trade_analysis/trade_analysis_interface.go

@@ -4,63 +4,226 @@ import (
 	tradeAnalysisModel "eta/eta_index_lib/models/trade_analysis"
 	"eta/eta_index_lib/utils"
 	"fmt"
-	"strconv"
+	"sort"
 	"strings"
+	"time"
 )
 
 // TradeAnalysisInterface 持仓分析查询接口
 type TradeAnalysisInterface interface {
-	GetTradeDataByClassifyAndCompany(exchange, classifyName string, contracts, queryCompanies []string) (items []*tradeAnalysisModel.OriginTradeData, err error) // 根据品种和公司获取原始数据
-	GetLastTradeDataByClassify(exchange, classifyName string, contracts []string) (items []*tradeAnalysisModel.OriginTradeData, err error)                       // 获取品种末位数据
+	GetTradeDataByContracts(exchange string, classifyNames, contracts, queryCompanies []string, startDate, endDate time.Time) (items []*tradeAnalysisModel.OriginTradeData, topItems, lastItems []*tradeAnalysisModel.OriginTradeData, err error)
+	GetContractTopRankData(exchange string, classifyNames []string, dataDate time.Time) (items []*tradeAnalysisModel.ContractTopRankData, err error)
+	GetClassifyNewestDataTime(exchange string, classifyNames []string) (dataTime time.Time, err error)
 }
 
 // BaseTradeAnalysis 通用交易所
 type BaseTradeAnalysis struct{}
 
-func (b *BaseTradeAnalysis) GetTradeDataByClassifyAndCompany(exchange, classifyName string, contracts, queryCompanies []string) (items []*tradeAnalysisModel.OriginTradeData, err error) {
-	return tradeAnalysisModel.GetTradeDataByClassifyAndCompany(exchange, classifyName, contracts, queryCompanies)
+// GetTradeDataByContracts 根据合约获取公司的持仓数据、TOP20数据以及每日的末位数据
+func (b *BaseTradeAnalysis) GetTradeDataByContracts(exchange string, classifyNames, contracts, queryCompanies []string, startDate, endDate time.Time) (items, topItems, lastItems []*tradeAnalysisModel.OriginTradeData, err error) {
+	// 根据合约获取数据
+	originData, e := tradeAnalysisModel.GetTradeDataByContracts(exchange, classifyNames, contracts, startDate, endDate)
+	if e != nil {
+		err = fmt.Errorf("根据合约获取持仓数据失败, %v", e)
+		return
+	}
+	items, topItems, lastItems = formatOriginData2UseData(originData, queryCompanies)
+	return
+}
+
+// formatOriginData2UseData 原始数据转换为持仓数据、TOP20以及末位数据
+func formatOriginData2UseData(originData []*tradeAnalysisModel.BaseFromTradeCommonIndex, queryCompanies []string) (items, topItems, lastItems []*tradeAnalysisModel.OriginTradeData) {
+	items, topItems, lastItems = make([]*tradeAnalysisModel.OriginTradeData, 0), make([]*tradeAnalysisModel.OriginTradeData, 0), make([]*tradeAnalysisModel.OriginTradeData, 0)
+	buyMaxRank, soldMaxRang := make(map[string]*tradeAnalysisModel.BaseFromTradeCommonIndex), make(map[string]*tradeAnalysisModel.BaseFromTradeCommonIndex)
+	for _, v := range originData {
+		// TOP20(大商所的Rank存在为0的)
+		if v.Rank == 0 || v.Rank == 999 {
+			topBuy := &tradeAnalysisModel.OriginTradeData{
+				Rank:         v.Rank,
+				CompanyName:  tradeAnalysisModel.TradeFuturesCompanyTop20,
+				Val:          v.BuyValue,
+				ValChange:    v.BuyChange,
+				ValType:      1,
+				DataTime:     v.DataTime,
+				ClassifyName: v.ClassifyName,
+				ClassifyType: v.ClassifyType,
+			}
+			topSold := &tradeAnalysisModel.OriginTradeData{
+				Rank:         v.Rank,
+				CompanyName:  tradeAnalysisModel.TradeFuturesCompanyTop20,
+				Val:          v.SoldValue,
+				ValChange:    v.SoldChange,
+				ValType:      2,
+				DataTime:     v.DataTime,
+				ClassifyName: v.ClassifyName,
+				ClassifyType: v.ClassifyType,
+			}
+			topItems = append(topItems, topBuy, topSold)
+			continue
+		}
+
+		// 查询的公司-买单
+		contractDateKey := fmt.Sprintf("%s_%s", v.DataTime.Format(utils.FormatDate), v.ClassifyType)
+		if utils.InArrayByStr(queryCompanies, v.BuyShortName) {
+			items = append(items, &tradeAnalysisModel.OriginTradeData{
+				Rank:         v.Rank,
+				CompanyName:  v.BuyShortName,
+				Val:          v.BuyValue,
+				ValChange:    v.BuyChange,
+				ValType:      1,
+				DataTime:     v.DataTime,
+				ClassifyName: v.ClassifyName,
+				ClassifyType: v.ClassifyType,
+			})
+
+			// 比对[合约-数据日期]对应的rank,取出末位
+			if buyMaxRank[contractDateKey] != nil && v.Rank > buyMaxRank[contractDateKey].Rank {
+				buyMaxRank[contractDateKey] = v
+			}
+			if buyMaxRank[contractDateKey] == nil {
+				buyMaxRank[contractDateKey] = v
+			}
+		}
+
+		// 查询的公司-卖单
+		if utils.InArrayByStr(queryCompanies, v.SoldShortName) {
+			items = append(items, &tradeAnalysisModel.OriginTradeData{
+				Rank:         v.Rank,
+				CompanyName:  v.SoldShortName,
+				Val:          v.SoldValue,
+				ValChange:    v.SoldChange,
+				ValType:      2,
+				DataTime:     v.DataTime,
+				ClassifyName: v.ClassifyName,
+				ClassifyType: v.ClassifyType,
+			})
+
+			// 比对数据日期对应的rank,取出末位
+			if soldMaxRang[contractDateKey] != nil && v.Rank > soldMaxRang[contractDateKey].Rank {
+				soldMaxRang[contractDateKey] = v
+			}
+			if soldMaxRang[contractDateKey] == nil {
+				soldMaxRang[contractDateKey] = v
+			}
+		}
+	}
+
+	// 如果查询的公司中含TOP20,那么追加进items
+	var hasTop bool
+	if utils.InArrayByStr(queryCompanies, tradeAnalysisModel.TradeFuturesCompanyTop20) {
+		hasTop = true
+	}
+	if hasTop {
+		items = append(items, topItems...)
+	}
+
+	// 末位数据
+	for _, v := range buyMaxRank {
+		if v == nil {
+			continue
+		}
+		lastItems = append(lastItems, &tradeAnalysisModel.OriginTradeData{
+			Rank:         v.Rank,
+			CompanyName:  v.BuyShortName,
+			Val:          v.BuyValue,
+			ValChange:    v.BuyChange,
+			ValType:      1,
+			DataTime:     v.DataTime,
+			ClassifyName: v.ClassifyName,
+			ClassifyType: v.ClassifyType,
+		})
+	}
+	for _, v := range buyMaxRank {
+		if v == nil {
+			continue
+		}
+		lastItems = append(lastItems, &tradeAnalysisModel.OriginTradeData{
+			Rank:         v.Rank,
+			CompanyName:  v.SoldShortName,
+			Val:          v.SoldValue,
+			ValChange:    v.SoldChange,
+			ValType:      2,
+			DataTime:     v.DataTime,
+			ClassifyName: v.ClassifyName,
+			ClassifyType: v.ClassifyType,
+		})
+	}
+	return
+}
+
+func (b *BaseTradeAnalysis) GetContractTopRankData(exchange string, classifyNames []string, dataDate time.Time) (items []*tradeAnalysisModel.ContractTopRankData, err error) {
+	return tradeAnalysisModel.GetContractTopRankData(exchange, classifyNames, dataDate)
 }
 
-func (b *BaseTradeAnalysis) GetLastTradeDataByClassify(exchange, classifyName string, contracts []string) (items []*tradeAnalysisModel.OriginTradeData, err error) {
-	return tradeAnalysisModel.GetLastTradeDataByClassify(exchange, classifyName, contracts)
+func (b *BaseTradeAnalysis) GetClassifyNewestDataTime(exchange string, classifyNames []string) (dataTime time.Time, err error) {
+	return tradeAnalysisModel.GetClassifyNewestDataTime(exchange, classifyNames)
 }
 
 // ZhengzhouTradeAnalysis 郑商所
 type ZhengzhouTradeAnalysis struct{}
 
-func (z *ZhengzhouTradeAnalysis) GetTradeDataByClassifyAndCompany(exchange, classifyName string, contracts, queryCompanies []string) (items []*tradeAnalysisModel.OriginTradeData, err error) {
-	return tradeAnalysisModel.GetTradeZhengzhouDataByClassifyAndCompany(exchange, contracts, queryCompanies)
+func (z *ZhengzhouTradeAnalysis) GetTradeDataByContracts(exchange string, classifyNames, contracts, queryCompanies []string, startDate, endDate time.Time) (items, topItems, lastItems []*tradeAnalysisModel.OriginTradeData, err error) {
+	// 根据品种获取合约
+	//classifies, e := GetZhengzhouContractsByClassifyNames(classifyNames)
+	//if e != nil {
+	//	err = fmt.Errorf("获取郑商所实际合约失败, %v", e)
+	//	return
+	//}
+	//contracts = classifies
+
+	// 根据合约获取数据
+	originData, e := tradeAnalysisModel.GetZhengzhouTradeDataByContracts(contracts, startDate, endDate)
+	if e != nil {
+		err = fmt.Errorf("根据合约获取持仓数据失败, %v", e)
+		return
+	}
+	items, topItems, lastItems = formatOriginData2UseData(originData, queryCompanies)
+	return
+}
+
+func (z *ZhengzhouTradeAnalysis) GetContractTopRankData(exchange string, classifyNames []string, dataDate time.Time) (items []*tradeAnalysisModel.ContractTopRankData, err error) {
+	return tradeAnalysisModel.GetZhengzhouContractTopRankData(classifyNames, dataDate)
 }
 
-func (z *ZhengzhouTradeAnalysis) GetLastTradeDataByClassify(exchange, classifyName string, contracts []string) (items []*tradeAnalysisModel.OriginTradeData, err error) {
-	return tradeAnalysisModel.GetLastTradeZhengzhouDataByClassify(exchange, contracts)
+func (z *ZhengzhouTradeAnalysis) GetClassifyNewestDataTime(exchange string, classifyNames []string) (dataTime time.Time, err error) {
+	return tradeAnalysisModel.GetClassifyNewestDataTime(exchange, classifyNames)
 }
 
 // GuangzhouTradeAnalysis 广期所
 type GuangzhouTradeAnalysis struct{}
 
-func (g *GuangzhouTradeAnalysis) GetTradeDataByClassifyAndCompany(exchange, classifyName string, contracts, queryCompanies []string) (items []*tradeAnalysisModel.OriginTradeData, err error) {
-	classifyIdMap := map[string]int{"si": 7, "lc": 8}
-	classifyId := classifyIdMap[classifyName]
-	if classifyId == 0 {
-		err = fmt.Errorf("品种有误")
-		return
-	}
+func (g *GuangzhouTradeAnalysis) GetTradeDataByContracts(exchange string, classifyNames, contracts, queryCompanies []string, startDate, endDate time.Time) (items, topItems, lastItems []*tradeAnalysisModel.OriginTradeData, err error) {
+	items, topItems, lastItems = make([]*tradeAnalysisModel.OriginTradeData, 0), make([]*tradeAnalysisModel.OriginTradeData, 0), make([]*tradeAnalysisModel.OriginTradeData, 0)
 
-	// TOP20
-	seatNameArr := []string{tradeAnalysisModel.GuangZhouSeatNameBuy, tradeAnalysisModel.GuangZhouSeatNameSold}
-	if utils.InArrayByStr(queryCompanies, tradeAnalysisModel.TradeFuturesCompanyTop20) {
-		seatNameArr = append(seatNameArr, tradeAnalysisModel.GuangZhouTopSeatNameBuy, tradeAnalysisModel.GuangZhouTopSeatNameSold)
+	// 取品种ID
+	classifyNameId := map[string]int{"si": 7, "lc": 8}
+	classifyIdName := map[int]string{7: "si", 8: "lc"}
+	var classifyIds []int
+	for _, v := range classifyNames {
+		if classifyNameId[v] > 0 {
+			classifyIds = append(classifyIds, classifyNameId[v])
+		}
 	}
 
-	// 查询品种下所有指标
-	indexes, e := tradeAnalysisModel.GetBaseFromTradeGuangzhouIndexByClassifyId(classifyId)
+	// 查询指标
+	indexes, e := tradeAnalysisModel.GetBaseFromTradeGuangzhouIndex(classifyIds, contracts, "")
 	if e != nil {
 		err = fmt.Errorf("获取广期所指标失败, %v", e)
 		return
 	}
 	var indexIds []int
+
+	// 过滤掉成交量,只取买单卖单
+	seatNameArr := []string{tradeAnalysisModel.GuangZhouSeatNameBuy, tradeAnalysisModel.GuangZhouSeatNameSold, tradeAnalysisModel.GuangZhouTopSeatNameBuy, tradeAnalysisModel.GuangZhouTopSeatNameSold}
 	indexInfo := make(map[int]*tradeAnalysisModel.OriginTradeData)
+
+	// 查询公司中是否含TOP20
+	var hasTop bool
+	if utils.InArrayByStr(queryCompanies, tradeAnalysisModel.TradeFuturesCompanyTop20) {
+		hasTop = true
+	}
+	isTopIndex := make(map[int]bool)
+
 	for _, v := range indexes {
 		// eg.永安期货_si2401_持买单量
 		nameArr := strings.Split(v.IndexName, "_")
@@ -68,28 +231,32 @@ func (g *GuangzhouTradeAnalysis) GetTradeDataByClassifyAndCompany(exchange, clas
 			continue
 		}
 		companyName := nameArr[0]
+		var isTop bool
 		if nameArr[0] == tradeAnalysisModel.GuangZhouTopCompanyAliasName {
+			isTop = true
+			isTopIndex[v.BaseFromTradeGuangzhouIndexId] = true
 			companyName = tradeAnalysisModel.TradeFuturesCompanyTop20
 		}
 		if !utils.InArrayByStr(seatNameArr, nameArr[2]) {
 			continue
 		}
-		if !utils.InArrayByStr(queryCompanies, companyName) {
-			continue
-		}
-		if !utils.InArrayByStr(contracts, nameArr[1]) {
+		// 过滤掉非TOP20以及非查询公司
+		if !isTop && !utils.InArrayByStr(queryCompanies, companyName) {
 			continue
 		}
 		indexIds = append(indexIds, v.BaseFromTradeGuangzhouIndexId)
+
+		// 指标信息
 		if indexInfo[v.BaseFromTradeGuangzhouIndexId] == nil {
-			if tradeAnalysisModel.GuangzhouSeatNameValType[nameArr[2]] == 0 {
+			contractType := tradeAnalysisModel.GuangzhouSeatNameValType[nameArr[2]]
+			if contractType == 0 {
 				continue
 			}
 			indexInfo[v.BaseFromTradeGuangzhouIndexId] = new(tradeAnalysisModel.OriginTradeData)
 			indexInfo[v.BaseFromTradeGuangzhouIndexId].CompanyName = companyName
-			indexInfo[v.BaseFromTradeGuangzhouIndexId].ClassifyName = classifyName
+			indexInfo[v.BaseFromTradeGuangzhouIndexId].ClassifyName = classifyIdName[v.BaseFromTradeGuangzhouClassifyId]
 			indexInfo[v.BaseFromTradeGuangzhouIndexId].ClassifyType = nameArr[1]
-			indexInfo[v.BaseFromTradeGuangzhouIndexId].ValType = tradeAnalysisModel.GuangzhouSeatNameValType[nameArr[2]]
+			indexInfo[v.BaseFromTradeGuangzhouIndexId].ValType = contractType
 		}
 	}
 	if len(indexIds) == 0 {
@@ -97,18 +264,19 @@ func (g *GuangzhouTradeAnalysis) GetTradeDataByClassifyAndCompany(exchange, clas
 	}
 
 	// 查询指标数据
-	indexesData, e := tradeAnalysisModel.GetBaseFromTradeGuangzhouDataByIndexIds(indexIds)
+	indexesData, e := tradeAnalysisModel.GetBaseFromTradeGuangzhouDataByIndexIds(indexIds, startDate, endDate)
 	if e != nil {
 		err = fmt.Errorf("获取广期所指标数据失败, %v", e)
 		return
 	}
-	items = make([]*tradeAnalysisModel.OriginTradeData, 0)
+	// 取出持仓数据、TOP20,比对末位数据
+	contractMinData := make(map[string]*tradeAnalysisModel.OriginTradeData) // 合约末位
 	for _, v := range indexesData {
 		info, ok := indexInfo[v.BaseFromTradeGuangzhouIndexId]
 		if !ok {
 			continue
 		}
-		items = append(items, &tradeAnalysisModel.OriginTradeData{
+		t := &tradeAnalysisModel.OriginTradeData{
 			CompanyName:  info.CompanyName,
 			Val:          int(v.Value),
 			ValChange:    int(v.QtySub),
@@ -116,66 +284,164 @@ func (g *GuangzhouTradeAnalysis) GetTradeDataByClassifyAndCompany(exchange, clas
 			ClassifyName: info.ClassifyName,
 			ClassifyType: info.ClassifyType,
 			ValType:      info.ValType,
-		})
+		}
+
+		// 如果是TOP20的指标,查询公司中含有TOP20那么追加进items,否则仅追加进topItems, 且TOP20不参与末位数据的比对
+		if isTopIndex[v.BaseFromTradeGuangzhouIndexId] {
+			if hasTop {
+				items = append(items, t)
+			}
+			topItems = append(topItems, t)
+			continue
+		}
+		items = append(items, t)
+
+		// 比对末位数据
+		k := fmt.Sprintf("%s-%d", info.ClassifyType, info.ValType)
+		if contractMinData[k] == nil {
+			contractMinData[k] = t
+			continue
+		}
+		if t.Val < contractMinData[k].Val {
+			contractMinData[k] = t
+		}
+	}
+
+	// 末位数据
+	for _, v := range contractMinData {
+		lastItems = append(lastItems, v)
 	}
 	return
 }
 
-func (g *GuangzhouTradeAnalysis) GetLastTradeDataByClassify(exchange, classifyName string, contracts []string) (items []*tradeAnalysisModel.OriginTradeData, err error) {
-	classifyIdMap := map[string]int{"si": 7, "lc": 8}
-	classifyId := classifyIdMap[classifyName]
-	if classifyId == 0 {
-		err = fmt.Errorf("品种有误")
-		return
+func (g *GuangzhouTradeAnalysis) GetContractTopRankData(exchange string, classifyNames []string, dataTime time.Time) (items []*tradeAnalysisModel.ContractTopRankData, err error) {
+	items = make([]*tradeAnalysisModel.ContractTopRankData, 0)
+
+	// 取品种ID
+	classifyNameId := map[string]int{"si": 7, "lc": 8}
+	classifyIdName := map[int]string{7: "si", 8: "lc"}
+	var classifyIds []int
+	for _, v := range classifyNames {
+		if classifyNameId[v] > 0 {
+			classifyIds = append(classifyIds, classifyNameId[v])
+		}
 	}
-	seatNameArr := []string{tradeAnalysisModel.GuangZhouSeatNameBuy, tradeAnalysisModel.GuangZhouSeatNameSold}
 
-	// 查询品种下所有指标
-	indexes, e := tradeAnalysisModel.GetBaseFromTradeGuangzhouIndexByClassifyId(classifyId)
+	// 查询TOP20指标
+	indexKeyword := fmt.Sprint("%", tradeAnalysisModel.GuangZhouTopCompanyAliasName, "%")
+	indexes, e := tradeAnalysisModel.GetBaseFromTradeGuangzhouIndex(classifyIds, []string{}, indexKeyword)
 	if e != nil {
 		err = fmt.Errorf("获取广期所指标失败, %v", e)
 		return
 	}
+	var indexIds []int
 
-	// 获取各合约下的指标
-	contractIndexIds := make(map[string][]int)
+	indexIdContract := make(map[int]string)                                      // [指标ID-合约_方向]
+	contractRankData := make(map[string]*tradeAnalysisModel.ContractTopRankData) // [合约-合约TOP数据]
 	for _, v := range indexes {
 		// eg.永安期货_si2401_持买单量
 		nameArr := strings.Split(v.IndexName, "_")
 		if len(nameArr) != 3 {
 			continue
 		}
-		if !utils.InArrayByStr(contracts, nameArr[1]) {
+		contractCode := nameArr[1]
+		indexIds = append(indexIds, v.BaseFromTradeGuangzhouIndexId)
+
+		// 指标对应的[合约+方向]
+		k := fmt.Sprintf("%s_%d", contractCode, tradeAnalysisModel.GuangzhouSeatNameValType[nameArr[2]])
+		indexIdContract[v.BaseFromTradeGuangzhouIndexId] = k
+
+		// 合约对应的数据
+		if contractRankData[contractCode] == nil {
+			contractRankData[contractCode] = new(tradeAnalysisModel.ContractTopRankData)
+			contractRankData[contractCode].Exchange = exchange
+			contractRankData[contractCode].ClassifyName = classifyIdName[v.BaseFromTradeGuangzhouClassifyId]
+			contractRankData[contractCode].ClassifyType = contractCode
+			contractRankData[contractCode].DataTime = dataTime
+		}
+	}
+	if len(indexIds) == 0 {
+		return
+	}
+
+	// 查询指标数据
+	indexesData, e := tradeAnalysisModel.GetBaseFromTradeGuangzhouDataByIndexIds(indexIds, dataTime, dataTime)
+	if e != nil {
+		err = fmt.Errorf("获取广期所指标数据失败, %v", e)
+		return
+	}
+	for _, v := range indexesData {
+		k := indexIdContract[v.BaseFromTradeGuangzhouIndexId]
+		if k == "" {
 			continue
 		}
-		if !utils.InArrayByStr(seatNameArr, nameArr[2]) {
+		nameArr := strings.Split(k, "_")
+		if len(nameArr) != 2 {
+			continue
+		}
+		contractCode := nameArr[0]
+		if contractRankData[contractCode] == nil {
 			continue
 		}
-		if tradeAnalysisModel.GuangzhouSeatNameValType[nameArr[2]] == 0 {
+
+		// 根据方向赋值:1-多单;2-空单;3-成交量
+		switch nameArr[1] {
+		case "1":
+			contractRankData[contractCode].BuyValue = int(v.Value)
+		case "2":
+			contractRankData[contractCode].SoldValue = int(v.Value)
+		case "3":
+			contractRankData[contractCode].DealValue = int(v.Value)
+		default:
 			continue
 		}
-		k := fmt.Sprintf("%s-%d", nameArr[1], tradeAnalysisModel.GuangzhouSeatNameValType[nameArr[2]])
-		contractIndexIds[k] = append(contractIndexIds[k], v.BaseFromTradeGuangzhouIndexId)
 	}
 
-	// ps.如果后面如果有空可以优化一下这里, 把末位数据每天写进一张表里面
-	for k, v := range contractIndexIds {
-		keyArr := strings.Split(k, "-")
-		contract := keyArr[0]
-		valType, _ := strconv.Atoi(keyArr[1])
-		lastVales, e := tradeAnalysisModel.GetBaseFromTradeGuangzhouMinDataByIndexIds(v)
-		if e != nil {
-			err = fmt.Errorf("获取合约末位数据失败, %v", e)
-			return
+	// 根据成交量排序并返回排名数据
+	for _, v := range contractRankData {
+		items = append(items, v)
+	}
+	sort.Slice(items, func(i, j int) bool {
+		return items[i].DealValue > items[j].DealValue
+	})
+	return
+}
+
+func (g *GuangzhouTradeAnalysis) GetClassifyNewestDataTime(exchange string, classifyNames []string) (dataTime time.Time, err error) {
+	// 取品种ID
+	classifyNameId := map[string]int{"si": 7, "lc": 8}
+	var classifyIds []int
+	for _, v := range classifyNames {
+		if classifyNameId[v] > 0 {
+			classifyIds = append(classifyIds, classifyNameId[v])
 		}
-		for _, vv := range lastVales {
-			items = append(items, &tradeAnalysisModel.OriginTradeData{
-				Val:          int(vv.Value),
-				DataTime:     vv.DataTime,
-				ClassifyType: contract,
-				ValType:      valType,
-			})
+	}
+
+	// 查询TOP20的最新日期
+	indexKeyword := fmt.Sprint("%", tradeAnalysisModel.GuangZhouTopCompanyAliasName, "%")
+	indexes, e := tradeAnalysisModel.GetBaseFromTradeGuangzhouIndex(classifyIds, []string{}, indexKeyword)
+	if e != nil {
+		err = fmt.Errorf("获取广期所指标失败, %v", e)
+		return
+	}
+	var indexIds []int
+	for _, v := range indexes {
+		// eg.永安期货_si2401_持买单量
+		nameArr := strings.Split(v.IndexName, "_")
+		if len(nameArr) != 3 {
+			continue
 		}
+		indexIds = append(indexIds, v.BaseFromTradeGuangzhouIndexId)
+	}
+	if len(indexIds) == 0 {
+		return
+	}
+
+	d, e := tradeAnalysisModel.GetGuangzhouClassifyNewestDataTime(indexIds)
+	if e != nil {
+		err = fmt.Errorf("获取广期品种最新数据失败, %v", e)
+		return
 	}
+	dataTime = d
 	return
 }

+ 287 - 0
services/trade_analysis/trade_analysis_table.go

@@ -0,0 +1,287 @@
+package trade_analysis
+
+import (
+	tradeAnalysisModel "eta/eta_index_lib/models/trade_analysis"
+	"eta/eta_index_lib/utils"
+	"fmt"
+	"math"
+	"strings"
+	"time"
+)
+
+// CheckAnalysisTableExtraConfig 校验表格配置
+func CheckAnalysisTableExtraConfig(extraConfig tradeAnalysisModel.TableExtraConfig) (pass bool, tips string) {
+	if extraConfig.CompanyName == "" {
+		tips = "请选择期货公司"
+		return
+	}
+	if len(extraConfig.ClassifyList) == 0 {
+		tips = "请选择品种"
+		return
+	}
+	var classifyTotal int
+	for _, v := range extraConfig.ClassifyList {
+		if v.Exchange == "" {
+			tips = "请选择交易所"
+			return
+		}
+		if len(v.ClassifyNames) == 0 {
+			tips = "请选择品种"
+			return
+		}
+		classifyTotal += len(v.ClassifyNames)
+	}
+	// TODO:品种选择上限
+	if classifyTotal > 5 {
+		tips = "选择品种不超过5个"
+		return
+	}
+	typeArr := []int{tradeAnalysisModel.ContractQueryTypeTop, tradeAnalysisModel.ContractQueryTypeTop2, tradeAnalysisModel.ContractQueryTypeTop3, tradeAnalysisModel.ContractQueryTypeAll, tradeAnalysisModel.ContractQueryTypeTotal}
+	if !utils.InArrayByInt(typeArr, extraConfig.ContractType) {
+		tips = "请选择正确的合约"
+		return
+	}
+	if extraConfig.DateType == 1 && extraConfig.FixedDate == "" {
+		tips = "请选择固定日期"
+		return
+	}
+	if extraConfig.FixedDate != "" {
+		_, e := time.Parse(utils.FormatDate, extraConfig.FixedDate)
+		if e != nil {
+			tips = "固定日期格式有误"
+			return
+		}
+	}
+	if extraConfig.PredictRatio < 0 || extraConfig.PredictRatio > 1 {
+		tips = "请输入正确的估计参数"
+		return
+	}
+	pass = true
+	return
+}
+
+// GetAnalysisTableBaseDate 根据配置获取基准交易日期
+func GetAnalysisTableBaseDate(dateType, intervalMove int, fixedDate string) (baseDate time.Time, err error) {
+	if dateType == 1 {
+		// 固定日期
+		t, _ := time.ParseInLocation(utils.FormatDate, fixedDate, time.Local)
+		baseDate = t
+	} else {
+		// 最新交易日
+		st := time.Now().AddDate(0, 0, -7) // 默认前移7天, 在开始结束区间内取出指定交易日
+		if intervalMove > 0 {
+			period := intervalMove * 7
+			st = st.AddDate(0, 0, -period)
+		}
+		tradeDates := utils.GetTradingDays(st, time.Now())
+		dateLen := len(tradeDates)
+		if dateLen == 0 {
+			err = fmt.Errorf("交易日序列异常")
+			return
+		}
+		index := dateLen - 1
+		if intervalMove > 0 {
+			index -= intervalMove
+		}
+		if index < 0 || index > dateLen {
+			err = fmt.Errorf("交易日序列异常")
+			return
+		}
+		baseDate = tradeDates[index]
+	}
+	return
+}
+
+// CalculateTableRowData 计算表格行数据
+func CalculateTableRowData(exchange string, baseDate time.Time, topData []*tradeAnalysisModel.ContractTopRankData, contractsData []*tradeAnalysisModel.ContractCompanyTradeData) (rows []*tradeAnalysisModel.TableRowData, err error) {
+	contractTopData := make(map[string]*tradeAnalysisModel.ContractTopRankData)
+	for _, v := range topData {
+		contractTopData[v.ClassifyType] = v
+	}
+
+	rows = make([]*tradeAnalysisModel.TableRowData, 0)
+	for _, v := range contractsData {
+		// 取出基准日期的数据
+		cd := new(tradeAnalysisModel.ContractCompanyTradeDataList)
+		for _, d := range v.DataList {
+			if d.Date.Equal(baseDate) {
+				cd = d
+				break
+			}
+		}
+		// 合约基准日期无数据
+		if cd.Date.IsZero() {
+			continue
+		}
+
+		td, ok := contractTopData[v.ClassifyType]
+		if !ok {
+			// 无前20数据
+			continue
+		}
+		row := new(tradeAnalysisModel.TableRowData)
+		row.Exchange = exchange
+		row.ClassifyName = td.ClassifyName
+		row.ClassifyType = v.ClassifyType
+		row.BuyValue = cd.BuyVal
+		row.BuyChange = cd.BuyChange
+		row.SoldValue = cd.SoldVal
+		row.SoldChange = cd.SoldChange
+		row.PureBuyVal = cd.PureBuyVal
+		row.PureBuyChange = cd.PureBuyChange
+		row.TopBuyValue = td.BuyValue
+		row.TopSoldValue = td.SoldValue
+		row.TopBuyChange = td.BuyChange
+		row.TopSoldChange = td.SoldChange
+
+		// 计算值
+		row.BuySoldRatio = math.Round(float64(cd.BuyVal)/float64(cd.BuyVal+cd.SoldVal)*100) / 100
+		row.BuyTopRatio = math.Round(float64(cd.BuyVal)/float64(td.BuyValue)*100) / 100
+		row.SoldTopRatio = math.Round(float64(cd.SoldVal)/float64(td.SoldValue)*100) / 100
+		row.TopPureBuy = td.BuyValue - td.SoldValue
+		row.TopPureBuyChange = int(math.Abs(float64(td.BuyChange))) + int(math.Abs(float64(td.SoldChange))) // 净多变化=Abs(多单变化)+Abs(空单变化)
+		row.TopBuySoldRatio = math.Round(float64(td.BuyValue)/float64(td.BuyValue+td.SoldValue)*100) / 100
+		rows = append(rows, row)
+	}
+	return
+}
+
+// GetTableRowsDataByConfig 根据配置获取表格行数据
+func GetTableRowsDataByConfig(tableConfig tradeAnalysisModel.TableExtraConfig) (tableRows []*tradeAnalysisModel.TableRowData, err error) {
+	// 基准日期
+	baseDate, e := GetAnalysisTableBaseDate(tableConfig.DateType, tableConfig.IntervalMove, tableConfig.FixedDate)
+	if e != nil {
+		err = fmt.Errorf("获取基准日期失败, %v", e)
+		return
+	}
+
+	// 根据类型取出合约数
+	var contractMax int // 需要取出的品种对应的最大合约数
+	//classifyMax := make(map[string]int) // 品种对应的合约数
+	switch tableConfig.ContractType {
+	case tradeAnalysisModel.ContractQueryTypeTop:
+		contractMax = 1
+	case tradeAnalysisModel.ContractQueryTypeTop2:
+		contractMax = 2
+	case tradeAnalysisModel.ContractQueryTypeTop3:
+		contractMax = 3
+	case tradeAnalysisModel.ContractQueryTypeAll, tradeAnalysisModel.ContractQueryTypeTotal:
+		contractMax = 999
+	}
+
+	var sortRules []string                                                              // 最终排序(合约加总以品种排序,其他以为合约排序)
+	classifyContractsData := make(map[string][]*tradeAnalysisModel.ContractTopRankData) // 根据品种分组的合约数据
+	classifyContracts := make(map[string][]string)                                      // 品种合约
+	for _, v := range tableConfig.ClassifyList {
+		for _, classify := range v.ClassifyNames {
+			// 获取合约排名及持仓数据
+			contractRanks, e := GetTopContractRank(v.Exchange, []string{classify}, baseDate)
+			if e != nil {
+				err = fmt.Errorf("获取基准日期合约排名失败, %v", e)
+				return
+			}
+			if len(contractRanks) == 0 {
+				continue
+			}
+			flag := fmt.Sprintf("%s-%s", v.Exchange, classify)
+
+			// 取出指定数量的合约
+			var contractNum int
+			for _, rd := range contractRanks {
+				if contractNum >= contractMax {
+					continue
+				}
+				contractNum += 1
+				classifyContracts[classify] = append(classifyContracts[classify], rd.ClassifyType)
+
+				if classifyContractsData[flag] == nil {
+					classifyContractsData[flag] = make([]*tradeAnalysisModel.ContractTopRankData, 0)
+				}
+				classifyContractsData[flag] = append(classifyContractsData[flag], rd)
+
+				// 以合约排序
+				if tableConfig.ContractType != tradeAnalysisModel.ContractQueryTypeTotal {
+					sortRules = append(sortRules, rd.ClassifyType)
+				}
+			}
+
+			// 以品种排序
+			if tableConfig.ContractType == tradeAnalysisModel.ContractQueryTypeTotal {
+				sortRules = append(sortRules, classify)
+			}
+		}
+	}
+
+	mussyRows := make([]*tradeAnalysisModel.TableRowData, 0)
+	for k, contracts := range classifyContractsData {
+		var exchange, classifyName string
+		keyArr := strings.Split(k, "-")
+		if len(keyArr) != 2 {
+			continue
+		}
+		exchange = keyArr[0]
+		classifyName = keyArr[1]
+		classifyTypes := classifyContracts[classifyName]
+		if len(classifyTypes) == 0 {
+			continue
+		}
+
+		// 合约加总时,contracts也需要加总,且ClassifyType为品种名
+		if tableConfig.ContractType == tradeAnalysisModel.ContractQueryTypeTotal {
+			contracts = MergeClassifyTypeTopRankData(contracts)
+		}
+
+		contractRowData, e := GetTableTradeData(exchange, classifyName, classifyTypes, tableConfig.CompanyName, tableConfig.PredictRatio, baseDate.AddDate(0, 0, -5), baseDate.AddDate(0, 0, 5), tableConfig.ContractType)
+		if e != nil {
+			err = fmt.Errorf("获取公司合约多空单数据失败, %v", e)
+			return
+		}
+		if len(contractRowData) == 0 {
+			continue
+		}
+
+		// 计算行数据
+		rows, e := CalculateTableRowData(exchange, baseDate, contracts, contractRowData)
+		if e != nil {
+			err = fmt.Errorf("计算合约数据失败, %v", e)
+			return
+		}
+		mussyRows = append(mussyRows, rows...)
+	}
+
+	// 排序
+	tableRows = make([]*tradeAnalysisModel.TableRowData, 0)
+	contractRow := make(map[string]*tradeAnalysisModel.TableRowData)
+	for _, v := range mussyRows {
+		contractRow[v.ClassifyType] = v
+	}
+	for _, v := range sortRules {
+		t := contractRow[v]
+		if t != nil {
+			tableRows = append(tableRows, t)
+		}
+	}
+	return
+}
+
+// MergeClassifyTypeTopRankData 类型为合约加总时-合并当日的TOP数据
+func MergeClassifyTypeTopRankData(classifyTypesData []*tradeAnalysisModel.ContractTopRankData) (mergedData []*tradeAnalysisModel.ContractTopRankData) {
+	mergedData = make([]*tradeAnalysisModel.ContractTopRankData, 0)
+
+	mergeData := new(tradeAnalysisModel.ContractTopRankData)
+	for _, v := range classifyTypesData {
+		mergeData.Exchange = v.Exchange
+		mergeData.DealValue += v.DealValue
+		mergeData.BuyValue += v.BuyValue
+		mergeData.BuyChange += v.BuyChange
+		mergeData.SoldValue += v.SoldValue
+		mergeData.SoldChange += v.SoldChange
+		mergeData.PureBuyValue += v.PureBuyValue
+		mergeData.PureBuyChange += v.PureBuyChange
+		mergeData.ClassifyName = v.ClassifyName
+		mergeData.ClassifyType = v.ClassifyName // 合约合并后为品种名
+		mergeData.DataTime = v.DataTime
+	}
+	mergedData = append(mergedData, mergeData)
+	return
+}

+ 8 - 0
utils/common.go

@@ -1759,3 +1759,11 @@ func parseYearOnlyDate(dateStr string) (time.Time, error) {
 	}
 	return time.Date(year, time.January, 1, 0, 0, 0, 0, time.UTC), nil
 }
+
+// ReverseTimeSlice 反转时间类型切片
+func ReverseTimeSlice(s []time.Time) []time.Time {
+	for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
+		s[i], s[j] = s[j], s[i]
+	}
+	return s
+}

+ 8 - 4
utils/constants.go

@@ -249,6 +249,7 @@ const (
 	CACHE_SELF_EDB_HANDLE            = "CACHE_SELF_EDB_HANDLE:"           // 自定义指标缓存
 	CACHE_BASE_EDB_ADD               = "CACHE_BASE_EDB_ADD_"              // 添加至数据源缓存
 	CACHE_BASE_EDB_REFRESH           = "CACHE_BASE_EDB_REFRESH_"          // 刷新数据源缓存
+	CACHE_EXCEL_REFRESH              = "CACHE_EXCEL_REFRESH"              // 表格刷新
 )
 
 // 图表类型
@@ -299,10 +300,13 @@ const (
 
 // ETA表格
 const (
-	EXCEL_DEFAULT         = 1 // 自定义excel
-	TIME_TABLE            = 2 // 时间序列表格
-	MIXED_TABLE           = 3 // 混合表格
-	CUSTOM_ANALYSIS_TABLE = 4 // 自定义分析表格
+	EXCEL_DEFAULT                    = 1 // 自定义excel
+	TIME_TABLE                       = 2 // 时间序列表格
+	MIXED_TABLE                      = 3 // 混合表格
+	CUSTOM_ANALYSIS_TABLE            = 4 // 自定义分析表格
+	BALANCE_TABLE                    = 5 // 平衡表
+	TRADE_ANALYSIS_TABLE             = 6 // 持仓分析-多空分析表格
+	TRADE_ANALYSIS_CORRELATION_TABLE = 7 // 持仓分析-相关性表格
 )
 
 // 子数据来源渠道