add
This commit is contained in:
@@ -1,39 +1,44 @@
|
|||||||
package crud
|
package crud
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"cn_futures_trading_records/db"
|
"cn_futures_trading_records/db"
|
||||||
|
"encoding/csv"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/gocarina/gocsv"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
/* ---------- 公共 Payload 结构体 ---------- */
|
/* ---------- 公共 Payload 结构体 ---------- */
|
||||||
type Payload struct {
|
type Payload struct {
|
||||||
Status int `json:"status" binding:"required,min=-1,max=1"` // 状态(-1:亏损,0:持仓中,1:盈利)
|
Status int `csv:"status" json:"status" binding:"required,min=-1,max=1"`
|
||||||
OpenYear int `json:"open_year" binding:"required,min=1900,max=2200"` // 开仓时间:年(1900-2200)
|
OpenYear int `csv:"open_year" json:"open_year" binding:"required,min=1900,max=2200"`
|
||||||
OpenMonth int `json:"open_month" binding:"required,min=1,max=12"` // 开仓时间:月(1-12)
|
OpenMonth int `csv:"open_month" json:"open_month" binding:"required,min=1,max=12"`
|
||||||
OpenDay int `json:"open_day" binding:"required,min=1,max=31"` // 开仓时间:日(1-31)
|
OpenDay int `csv:"open_day" json:"open_day" binding:"required,min=1,max=31"`
|
||||||
Symbol string `json:"symbol" binding:"required"` // 品种代码(如 RB、CU)
|
Symbol string `csv:"symbol" json:"symbol" binding:"required"`
|
||||||
Contract string `json:"contract" binding:"required"` // 合约代码(如 2505)
|
Contract string `csv:"contract" json:"contract" binding:"required"`
|
||||||
Direction int `json:"direction" binding:"required,min=-1,max=1"` // 1 多头 / -1 空头
|
Direction int `csv:"direction" json:"direction" binding:"required,min=-1,max=1"`
|
||||||
OpenPrice float64 `json:"open_price" binding:"required"` // 开仓价格(单位:元)
|
OpenPrice float64 `csv:"open_price" json:"open_price" binding:"required"`
|
||||||
OpenFee float64 `json:"open_fee" binding:"required,min=0"` // 开仓手续费(≥0)
|
OpenFee float64 `csv:"open_fee" json:"open_fee" binding:"required,min=0"`
|
||||||
CloseYear int `json:"close_year" binding:"required,min=1900,max=2200"` // 平仓时间:年(1900-2200)
|
CloseYear int `csv:"close_year" json:"close_year" binding:"required,min=1900,max=2200"`
|
||||||
CloseMonth int `json:"close_month" binding:"required,min=1,max=12"` // 平仓时间:月(1-12)
|
CloseMonth int `csv:"close_month" json:"close_month" binding:"required,min=1,max=12"`
|
||||||
CloseDay int `json:"close_day" binding:"required,min=1,max=31"` // 平仓时间:日(1-31)
|
CloseDay int `csv:"close_day" json:"close_day" binding:"required,min=1,max=31"`
|
||||||
ClosePrice float64 `json:"close_price" binding:"required"` // 平仓价格(单位:元)
|
ClosePrice float64 `csv:"close_price" json:"close_price" binding:"required"`
|
||||||
CloseFee float64 `json:"close_fee" binding:"required,min=0"` // 平仓手续费(≥0)
|
CloseFee float64 `csv:"close_fee" json:"close_fee" binding:"required,min=0"`
|
||||||
PriceDiff float64 `json:"price_diff" binding:"required"` // 平仓差价 = ClosePrice - OpenPrice
|
PriceDiff float64 `csv:"price_diff" json:"price_diff" binding:"required"`
|
||||||
MinTick float64 `json:"min_tick" binding:"required"` // 品种最小跳点(如 1)
|
MinTick float64 `csv:"min_tick" json:"min_tick" binding:"required"`
|
||||||
TickPrice float64 `json:"tick_price" binding:"required"` // 每跳价格(如 10 元)
|
TickPrice float64 `csv:"tick_price" json:"tick_price" binding:"required"`
|
||||||
DiffPnL float64 `json:"diff_pnl" binding:"required"` // 差价盈亏(已考虑方向与跳点)
|
DiffPnL float64 `csv:"diff_pnl" json:"diff_pnl" binding:"required"`
|
||||||
TotalFee float64 `json:"total_fee" binding:"required,min=0"` // 手续费合计 = OpenFee + CloseFee(≥0)
|
TotalFee float64 `csv:"total_fee" json:"total_fee" binding:"required,min=0"`
|
||||||
ClosePnL float64 `json:"close_pnl" binding:"required"` // 平仓净盈亏 = DiffPnL - TotalFee
|
ClosePnL float64 `csv:"close_pnl" json:"close_pnl" binding:"required"` // 客户端算好
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------- 单条创建 ---------- */
|
/* ---------- 单条创建 ---------- */
|
||||||
@@ -52,7 +57,7 @@ type TradingRecordsCreateData struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func CreateHandler(c *gin.Context) {
|
func CreateHandler(c *gin.Context) {
|
||||||
startTime := time.Now()
|
start := time.Now()
|
||||||
reqID := c.Request.Header.Get("X-TradingRecordsRequest-ID")
|
reqID := c.Request.Header.Get("X-TradingRecordsRequest-ID")
|
||||||
if reqID == "" {
|
if reqID == "" {
|
||||||
reqID = uuid.New().String()
|
reqID = uuid.New().String()
|
||||||
@@ -112,8 +117,7 @@ func CreateHandler(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
duration := time.Since(startTime)
|
zap.L().Info("✅ 交易记录创建完成", zap.String("req_id", reqID), zap.String("record_id", recordID), zap.Duration("duration", time.Since(start)))
|
||||||
zap.L().Info("✅ 交易记录创建完成", zap.String("req_id", reqID), zap.String("record_id", recordID), zap.Duration("duration", duration))
|
|
||||||
c.JSON(http.StatusOK, TradingRecordsCreateResponse{Success: true, Message: "创建成功", Data: TradingRecordsCreateData{RecordID: recordID}})
|
c.JSON(http.StatusOK, TradingRecordsCreateResponse{Success: true, Message: "创建成功", Data: TradingRecordsCreateData{RecordID: recordID}})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,7 +133,7 @@ type TradingRecordsBatchCreateResponse struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func CreateBatchHandler(c *gin.Context) {
|
func CreateBatchHandler(c *gin.Context) {
|
||||||
startTime := time.Now()
|
start := time.Now()
|
||||||
reqID := c.Request.Header.Get("X-TradingRecordsRequest-ID")
|
reqID := c.Request.Header.Get("X-TradingRecordsRequest-ID")
|
||||||
if reqID == "" {
|
if reqID == "" {
|
||||||
reqID = uuid.New().String()
|
reqID = uuid.New().String()
|
||||||
@@ -176,33 +180,21 @@ func CreateBatchHandler(c *gin.Context) {
|
|||||||
|
|
||||||
var recordIDs []string
|
var recordIDs []string
|
||||||
for idx, row := range req.Rows {
|
for idx, row := range req.Rows {
|
||||||
if len(row) != 18 {
|
if len(row) != 19 { // 19 列(含 ClosePnL)
|
||||||
tx.Rollback()
|
tx.Rollback()
|
||||||
zap.L().Warn("⚠️ 字段数量不匹配", zap.String("req_id", reqID), zap.Int("row_index", idx), zap.Int("field_count", len(row)))
|
zap.L().Warn("⚠️ 字段数量不匹配", zap.String("req_id", reqID), zap.Int("row_index", idx), zap.Int("field_count", len(row)))
|
||||||
c.JSON(http.StatusOK, TradingRecordsBatchCreateResponse{Success: false, Message: "第 " + strconv.Itoa(idx+1) + " 行字段数量不足 18 个"})
|
c.JSON(http.StatusOK, TradingRecordsBatchCreateResponse{Success: false, Message: "第 " + strconv.Itoa(idx+1) + " 行字段数量不足 19 个"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 用 gocsv 一次性映射
|
||||||
var p Payload
|
var p Payload
|
||||||
p.OpenYear, _ = strconv.Atoi(row[0])
|
if err := gocsv.UnmarshalCSV(csv.NewReader(bytes.NewReader([]byte(strings.Join(row, ",")+"\n"))), &p); err != nil {
|
||||||
p.OpenMonth, _ = strconv.Atoi(row[1])
|
tx.Rollback()
|
||||||
p.OpenDay, _ = strconv.Atoi(row[2])
|
zap.L().Error("❌ CSV 映射失败", zap.String("req_id", reqID), zap.Int("row_index", idx), zap.Error(err))
|
||||||
p.Symbol = row[3]
|
c.JSON(http.StatusOK, TradingRecordsBatchCreateResponse{Success: false, Message: fmt.Sprintf("第 %d 行数据格式错误: %s", idx+1, err.Error())})
|
||||||
p.Contract = row[4]
|
return
|
||||||
p.Direction, _ = strconv.Atoi(row[5])
|
}
|
||||||
p.OpenPrice, _ = strconv.ParseFloat(row[6], 64)
|
|
||||||
p.OpenFee, _ = strconv.ParseFloat(row[7], 64)
|
|
||||||
p.CloseYear, _ = strconv.Atoi(row[8])
|
|
||||||
p.CloseMonth, _ = strconv.Atoi(row[9])
|
|
||||||
p.CloseDay, _ = strconv.Atoi(row[10])
|
|
||||||
p.ClosePrice, _ = strconv.ParseFloat(row[11], 64)
|
|
||||||
p.CloseFee, _ = strconv.ParseFloat(row[12], 64)
|
|
||||||
p.PriceDiff, _ = strconv.ParseFloat(row[13], 64)
|
|
||||||
p.MinTick, _ = strconv.ParseFloat(row[14], 64)
|
|
||||||
p.TickPrice, _ = strconv.ParseFloat(row[15], 64)
|
|
||||||
p.DiffPnL, _ = strconv.ParseFloat(row[16], 64)
|
|
||||||
p.TotalFee, _ = strconv.ParseFloat(row[17], 64)
|
|
||||||
p.ClosePnL = p.DiffPnL - p.TotalFee
|
|
||||||
|
|
||||||
payloadBytes, _ := json.Marshal(p)
|
payloadBytes, _ := json.Marshal(p)
|
||||||
var id string
|
var id string
|
||||||
@@ -222,7 +214,6 @@ func CreateBatchHandler(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
duration := time.Since(startTime)
|
zap.L().Info("✅ 交易记录批量创建完成", zap.String("req_id", reqID), zap.Int("inserted_count", len(recordIDs)), zap.Duration("duration", time.Since(start)))
|
||||||
zap.L().Info("✅ 交易记录批量创建完成", zap.String("req_id", reqID), zap.Int("inserted_count", len(recordIDs)), zap.Duration("duration", duration))
|
|
||||||
c.JSON(http.StatusOK, TradingRecordsBatchCreateResponse{Success: true, Message: "批量创建成功", RecordIDs: recordIDs})
|
c.JSON(http.StatusOK, TradingRecordsBatchCreateResponse{Success: true, Message: "批量创建成功", RecordIDs: recordIDs})
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user