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  }