245 lines
7.8 KiB
Go
245 lines
7.8 KiB
Go
package logic
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"time"
|
|
|
|
"git.apinb.com/quant/collector/internal/impl"
|
|
"git.apinb.com/quant/collector/internal/models"
|
|
"git.apinb.com/quant/collector/internal/types"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
// SaveStatus 保存完整状态数据(使用事务)
|
|
func SaveData(status *types.StatusData) error {
|
|
// 验证必要字段 - AccountID是所有Upsert的共同条件
|
|
if status.Data.Assets.AccountID == "" {
|
|
return fmt.Errorf("账户ID不能为空")
|
|
}
|
|
|
|
// 计算Ymd (年月日数字格式,如20260407)
|
|
now := time.Now()
|
|
ymd := now.Year()*10000 + int(now.Month())*100 + now.Day()
|
|
|
|
// 保存资产快照 (先查询后更新/插入)
|
|
var existingAsset models.CollectorAssets
|
|
err := impl.DBService.Where("account_id = ? AND ymd = ?", status.Data.Assets.AccountID, ymd).First(&existingAsset).Error
|
|
|
|
asset := models.CollectorAssets{
|
|
AccountID: status.Data.Assets.AccountID,
|
|
Ymd: ymd,
|
|
Cash: status.Data.Assets.Cash,
|
|
FrozenCash: status.Data.Assets.FrozenCash,
|
|
MarketValue: status.Data.Assets.MarketValue,
|
|
Profit: status.Data.Assets.Profit,
|
|
TotalAsset: status.Data.Assets.TotalAsset,
|
|
CollectedAt: now,
|
|
}
|
|
|
|
if err == gorm.ErrRecordNotFound {
|
|
// 记录不存在,插入新记录
|
|
if err := impl.DBService.Create(&asset).Error; err != nil {
|
|
return fmt.Errorf("插入资产快照失败: %w", err)
|
|
}
|
|
} else if err != nil {
|
|
// 查询出错
|
|
return fmt.Errorf("查询资产快照失败: %w", err)
|
|
} else {
|
|
// 记录存在,更新现有记录
|
|
if asset.Cash != existingAsset.Cash || asset.FrozenCash != existingAsset.FrozenCash || asset.MarketValue != existingAsset.MarketValue || asset.Profit != existingAsset.Profit || asset.TotalAsset != existingAsset.TotalAsset {
|
|
asset.ID = existingAsset.ID
|
|
if err := impl.DBService.Save(&asset).Error; err != nil {
|
|
return fmt.Errorf("更新资产快照失败: %w", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// 批量保存订单 (先查询后更新/插入)
|
|
if len(status.Data.Orders) > 0 {
|
|
for _, order := range status.Data.Orders {
|
|
// 验证必要条件: OrderID和StockCode
|
|
if order.OrderID == 0 {
|
|
continue
|
|
}
|
|
if order.StockCode == "" {
|
|
continue
|
|
}
|
|
// 只存储成交的订单
|
|
if order.OrderStatus != 56 {
|
|
continue
|
|
}
|
|
var open_price float64
|
|
var info types.OrderInfo
|
|
err = json.Unmarshal([]byte(order.OrderRemark), &info)
|
|
if err == nil {
|
|
open_price = info.Op
|
|
}
|
|
|
|
// 查询是否存在
|
|
var cnt int64
|
|
impl.DBService.Where("account_id = ? AND order_id = ? AND ymd = ?", status.Data.Assets.AccountID, order.OrderID, ymd).Count(&cnt)
|
|
if cnt > 0 {
|
|
continue
|
|
}
|
|
|
|
orderRecord := models.CollectorOrder{
|
|
OrderID: order.OrderID,
|
|
AccountID: status.Data.Assets.AccountID,
|
|
StockCode: order.StockCode,
|
|
Ymd: ymd,
|
|
Price: order.Price,
|
|
Volume: order.Volume,
|
|
OpenPrice: open_price,
|
|
TradedPrice: order.TradedPrice,
|
|
TradedVolume: order.TradedVolume,
|
|
OrderStatus: order.OrderStatus,
|
|
OrderTime: order.OrderTime,
|
|
OrderRemark: order.OrderRemark,
|
|
OffsetFlag: order.OffsetFlag,
|
|
CollectedAt: now,
|
|
CreatedAt: now,
|
|
}
|
|
|
|
// 记录不存在,插入新记录
|
|
if err := impl.DBService.Create(&orderRecord).Error; err != nil {
|
|
return fmt.Errorf("插入订单失败: %w", err)
|
|
}
|
|
|
|
// 处理订单簿逻辑
|
|
if err := processOrderBook(status.Data.Assets.AccountID, order, ymd, now, open_price); err != nil {
|
|
fmt.Printf("处理订单簿失败: %v\n", err)
|
|
// 不返回错误,避免影响主流程
|
|
}
|
|
}
|
|
}
|
|
|
|
// 批量保存持仓 (先查询后更新/插入)
|
|
if len(status.Data.Positions) > 0 {
|
|
for _, pos := range status.Data.Positions {
|
|
// 验证必要条件: Code
|
|
if pos.Code == "" {
|
|
continue
|
|
}
|
|
|
|
// 查询是否存在
|
|
var existingPosition models.CollectorPosition
|
|
err := impl.DBService.Where("account_id = ? AND code = ? AND ymd = ?",
|
|
status.Data.Assets.AccountID, pos.Code, ymd).First(&existingPosition).Error
|
|
|
|
positionRecord := models.CollectorPosition{
|
|
AccountID: status.Data.Assets.AccountID,
|
|
Code: pos.Code,
|
|
Ymd: ymd,
|
|
Volume: pos.Volume,
|
|
CanUseVolume: pos.CanUseVolume,
|
|
FrozenVolume: pos.FrozenVolume,
|
|
AvgPrice: pos.AvgPrice,
|
|
OpenPrice: pos.OpenPrice,
|
|
CurrentPrice: pos.CurrentPrice,
|
|
MarketValue: pos.MarketValue,
|
|
Profit: pos.Profit,
|
|
ProfitRate: pos.ProfitRate,
|
|
MinProfitRate: pos.MinProfitRate,
|
|
CollectedAt: now,
|
|
}
|
|
|
|
if err == gorm.ErrRecordNotFound {
|
|
// 记录不存在,插入新记录
|
|
if err := impl.DBService.Create(&positionRecord).Error; err != nil {
|
|
return fmt.Errorf("插入持仓失败: %w", err)
|
|
}
|
|
} else if err != nil {
|
|
// 查询出错
|
|
return fmt.Errorf("查询持仓失败: %w", err)
|
|
} else {
|
|
if positionRecord.Volume != existingPosition.Volume ||
|
|
positionRecord.CanUseVolume != existingPosition.CanUseVolume ||
|
|
positionRecord.FrozenVolume != existingPosition.FrozenVolume ||
|
|
positionRecord.AvgPrice != existingPosition.AvgPrice ||
|
|
positionRecord.OpenPrice != existingPosition.OpenPrice ||
|
|
positionRecord.CurrentPrice != existingPosition.CurrentPrice ||
|
|
positionRecord.MarketValue != existingPosition.MarketValue ||
|
|
positionRecord.Profit != existingPosition.Profit ||
|
|
positionRecord.ProfitRate != existingPosition.ProfitRate {
|
|
// 记录存在,更新现有记录
|
|
positionRecord.ID = existingPosition.ID
|
|
if err := impl.DBService.Save(&positionRecord).Error; err != nil {
|
|
return fmt.Errorf("更新持仓失败: %w", err)
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
// processOrderBook 处理订单簿逻辑
|
|
func processOrderBook(accountID string, order types.Order, ymd int, now time.Time, openPrice float64) error {
|
|
// OffsetFlag: 通常 0=买入, 1=卖出 (需要根据实际系统确认)
|
|
// 这里假设: OffsetFlag == 0 表示买入, OffsetFlag == 1 表示卖出
|
|
|
|
if order.OffsetFlag == 0 {
|
|
// 买入订单 - 创建新的订单簿记录
|
|
orderBook := models.OrderBook{
|
|
AccountID: accountID,
|
|
StockCode: order.StockCode,
|
|
Ymd: ymd,
|
|
BuyOrderID: order.OrderID,
|
|
BuyPrice: order.TradedPrice,
|
|
BuyVolume: order.TradedVolume,
|
|
BuyTime: order.OrderTime,
|
|
BuyCollectedAt: now,
|
|
IsClosed: false,
|
|
}
|
|
|
|
if err := impl.DBService.Create(&orderBook).Error; err != nil {
|
|
return fmt.Errorf("创建订单簿记录失败: %w", err)
|
|
}
|
|
} else if order.OffsetFlag == 1 {
|
|
// 卖出订单 - 查找对应的买入记录并闭合
|
|
var orderBook models.OrderBook
|
|
// 查找同一账户、同一股票、未闭合的订单簿记录
|
|
err := impl.DBService.Where(
|
|
"account_id = ? AND stock_code = ? AND is_closed = ?",
|
|
accountID, order.StockCode, false,
|
|
).Order("buy_time ASC").First(&orderBook).Error
|
|
|
|
if err == gorm.ErrRecordNotFound {
|
|
// 没有找到对应的买入记录,可能是之前就已经持有的仓位
|
|
fmt.Printf("未找到对应的买入记录: account=%s, stock=%s\n", accountID, order.StockCode)
|
|
return nil
|
|
} else if err != nil {
|
|
return fmt.Errorf("查询订单簿记录失败: %w", err)
|
|
}
|
|
|
|
// 计算盈亏
|
|
buyAmount := float64(orderBook.BuyVolume) * orderBook.BuyPrice
|
|
sellAmount := float64(order.TradedVolume) * order.TradedPrice
|
|
profit := sellAmount - buyAmount
|
|
var profitRate float64
|
|
if buyAmount > 0 {
|
|
profitRate = (profit / buyAmount) * 100
|
|
}
|
|
|
|
// 更新订单簿记录
|
|
orderBook.SellOrderID = &order.OrderID
|
|
orderBook.SellPrice = &order.TradedPrice
|
|
orderBook.SellVolume = &order.TradedVolume
|
|
orderBook.SellTime = &order.OrderTime
|
|
orderBook.SellCollectedAt = &now
|
|
orderBook.IsClosed = true
|
|
orderBook.Profit = &profit
|
|
orderBook.ProfitRate = &profitRate
|
|
|
|
if err := impl.DBService.Save(&orderBook).Error; err != nil {
|
|
return fmt.Errorf("更新订单簿记录失败: %w", err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|