gitee.com/quant1x/engine@v1.8.4/trader/positions.go (about) 1 package trader 2 3 import ( 4 "fmt" 5 "gitee.com/quant1x/engine/cache" 6 "gitee.com/quant1x/engine/models" 7 "gitee.com/quant1x/exchange" 8 "gitee.com/quant1x/gox/api" 9 "gitee.com/quant1x/gox/concurrent" 10 "gitee.com/quant1x/gox/coroutine" 11 "gitee.com/quant1x/gox/logger" 12 "sync" 13 ) 14 15 const ( 16 qmtPositionsPath = "qmt" // 持仓缓存路径 17 qmtPositionsFilename = "positions.csv" // 持仓数据文件名 18 ) 19 20 // Position 持仓 21 type Position struct { 22 AccountType int `name:"账户类型" dataframe:"account_type"` // 账户类型 23 AccountId string `name:"资金账户" dataframe:"account_id"` // 资金账号 24 StrategyCode int `name:"策略编码" dataframe:"strategy_code"` // 策略编码 25 OrderFlag string `name:"订单标识" dataframe:"order_flag"` // 订单标识 26 SecurityCode string `name:"证券代码" dataframe:"stock_code"` // 证券代码, 例如"sh600000" 27 Volume int `name:"持仓数量" dataframe:"volume"` // 持仓数量,股票以'股'为单位, 债券以'张'为单位 28 CanUseVolume int `name:"可卖数量" dataframe:"can_use_volume"` // 可用数量, 股票以'股'为单位, 债券以'张'为单位 29 OpenPrice float64 `name:"开仓价" dataframe:"open_price"` // 开仓价 30 MarketValue float64 `name:"市值" dataframe:"market_value"` // 市值 31 FrozenVolume int `name:"冻结数量" dataframe:"frozen_volume"` // 冻结数量 32 OnRoadVolume int `name:"在途股份" dataframe:"on_road_volume"` // 在途股份 33 YesterdayVolume int `name:"昨夜拥股" dataframe:"yesterday_volume"` // 昨夜拥股 34 AvgPrice float64 `name:"成本价" dataframe:"avg_price"` // 成本价 35 CreateTime string `name:"创建时间" dataframe:"create_time"` // 创建时间 36 LastOrderId string `name:"前订单ID" dataframe:"last_order_id"` // 前订单ID 37 BuyTime string `name:"买入时间" dataframe:"buy_time"` // 买入时间 38 BuyPrice float64 `name:"买入价格" dataframe:"buy_price"` // 买入价格 39 BuyVolume int `name:"买入数量" dataframe:"buy_volume"` // 买入数量 40 SellTime string `name:"卖出时间" dataframe:"sell_time"` // 卖出时间 41 SellPrice float64 `name:"卖出价格" dataframe:"sell_price"` // 卖出价格 42 SellVolume int `name:"卖出数量" dataframe:"sell_volume"` // 卖出数量 43 CancelTime string `name:"撤单时间" dataframe:"cancel_time"` // 撤单时间 44 UpdateTime string `name:"更新时间" dataframe:"update_time"` // 更新时间 45 } 46 47 // Key 用证券代码作为关键字 48 func (p *Position) Key() string { 49 return fmt.Sprintf("%s", p.SecurityCode) 50 } 51 52 func (p *Position) Sync(other PositionDetail) bool { 53 err := api.Copy(p, &other) 54 if err != nil { 55 return false 56 } 57 if len(p.CreateTime) == 0 && p.YesterdayVolume > 0 { 58 // 如果创建时间等于空且昨夜拥股大于0, 则持股日期往前推一天 59 today := exchange.Today() 60 dates := exchange.LastNDate(today, 1) 61 frontDate := dates[0] + " 00:00:00" 62 p.CreateTime = frontDate 63 } 64 return true 65 } 66 67 // MergeFromOrder 订单合并到持仓 68 func (p *Position) MergeFromOrder(order OrderDetail) bool { 69 // 1. 或者成交量为0, 直接返回 70 if order.TradedVolume == 0 { 71 return false 72 } 73 // 2. 获取快照 74 snapshot := models.GetStrategySnapshot(p.SecurityCode) 75 plus := order.OrderType == STOCK_BUY 76 // 3. 缓存持仓和订单成本 77 // 3.1 计算当前持仓的买入成本 78 openValue := p.OpenPrice * float64(p.Volume) 79 // 3.2 计算订单的买入成本 80 orderValue := order.TradedPrice * float64(order.TradedVolume) 81 // 4. 更新持仓量, TODO: 持仓量边界保护未实现 82 if plus { 83 // 增加持仓股份 84 p.Volume += order.TradedVolume 85 // 增加在途股份 86 p.OnRoadVolume += order.TradedVolume 87 // 更新开仓价 88 p.OpenPrice = (openValue + orderValue) / float64(p.Volume) 89 // 更新买入信息 90 p.BuyTime = order.OrderTime 91 p.BuyPrice = order.TradedPrice 92 p.BuyVolume = order.TradedVolume 93 } else { 94 // 减少持仓 95 p.Volume -= order.TradedVolume 96 if p.Volume < 0 { 97 p.Volume = 0 98 } 99 // 减少可用 100 p.CanUseVolume -= order.TradedVolume 101 if p.CanUseVolume < 0 { 102 p.CanUseVolume = 0 103 } 104 // 更新卖出信息 105 p.SellTime = order.OrderTime 106 p.SellPrice = order.TradedPrice 107 p.SellVolume = order.TradedVolume 108 if p.Volume > 0 { 109 p.OpenPrice = (openValue - orderValue) / float64(p.Volume) 110 } 111 } 112 // 5. 更新市值 113 p.MarketValue = snapshot.Price * float64(p.Volume) 114 // 6. 修改 更新时间 115 p.UpdateTime = order.OrderTime 116 return true 117 } 118 119 var ( 120 periodicOnce coroutine.PeriodicOnce 121 mutexPositions sync.RWMutex 122 mapPositions = concurrent.NewTreeMap[string, *Position]() 123 ) 124 125 // 持仓缓存路径 126 func getPositionsPath() string { 127 path := fmt.Sprintf("%s/%s", cache.GetRootPath(), qmtPositionsPath) 128 return path 129 } 130 131 // 持仓缓存文件名 132 func positionsFilename() string { 133 filename := fmt.Sprintf("%s/%s-%s", getPositionsPath(), traderParameter.AccountId, qmtPositionsFilename) 134 return filename 135 } 136 137 // 加载本地的持仓数据 138 func lazyLoadLocalPositions() { 139 filename := positionsFilename() 140 var list []Position 141 err := api.CsvToSlices(filename, &list) 142 if err != nil || len(list) == 0 { 143 logger.Errorf("%s 没有有效数据, error=%+v", filename, err) 144 return 145 } 146 for _, v := range list { 147 code := v.SecurityCode 148 mapPositions.Put(code, &v) 149 } 150 } 151 152 // SyncPositions 同步持仓 153 func SyncPositions() { 154 periodicOnce.Do(lazyLoadLocalPositions) 155 list, err := QueryHolding() 156 if err != nil { 157 return 158 } 159 for _, v := range list { 160 securityCode := exchange.CorrectSecurityCode(v.StockCode) 161 position, found := mapPositions.Get(securityCode) 162 if !found { 163 position = &Position{ 164 AccountType: v.AccountType, 165 AccountId: v.AccountId, 166 SecurityCode: securityCode, 167 } 168 } 169 ok := position.Sync(v) 170 if ok { 171 mapPositions.Put(securityCode, position) 172 } 173 } 174 } 175 176 // UpdatePositions 更新持仓 177 func UpdatePositions() { 178 periodicOnce.Do(lazyLoadLocalPositions) 179 list, err := QueryOrders() 180 if err != nil { 181 return 182 } 183 for _, v := range list { 184 securityCode := exchange.CorrectSecurityCode(v.StockCode) 185 position, found := mapPositions.Get(securityCode) 186 if !found { 187 position = &Position{ 188 AccountType: v.AccountType, 189 AccountId: v.AccountId, 190 SecurityCode: securityCode, 191 } 192 } 193 ok := position.MergeFromOrder(v) 194 if ok { 195 mapPositions.Put(securityCode, position) 196 } 197 } 198 } 199 200 // CacheSync 缓存同步 201 func CacheSync() { 202 methodName := "CacheSync" 203 periodicOnce.Do(lazyLoadLocalPositions) 204 length := mapPositions.Size() 205 list := make([]Position, 0, length) 206 mapPositions.Each(func(key string, value *Position) { 207 list = append(list, *value) 208 }) 209 cacheFilename := positionsFilename() 210 err := api.SlicesToCsv(cacheFilename, list) 211 if err != nil { 212 logger.Errorf("services.trader:%s, error:%+v", methodName, err) 213 } 214 }