153 lines
3.2 KiB
Go
153 lines
3.2 KiB
Go
package logic
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"path"
|
|
"slices"
|
|
"sync"
|
|
|
|
"big-qmt/go-client/config"
|
|
"big-qmt/go-client/sdk"
|
|
)
|
|
|
|
var (
|
|
StatusNone = ""
|
|
StatusIng = "ING" // 处理中
|
|
StatusOk = "OK" // 成功
|
|
QuantState *State
|
|
)
|
|
|
|
type State struct {
|
|
AbsPath string
|
|
mu sync.Mutex
|
|
Items map[string]*StateItem
|
|
Codes []string
|
|
}
|
|
|
|
type StateItem struct {
|
|
Code string `json:"code"`
|
|
BaseOrderId string `json:"base_order_id"`
|
|
BaseQty int `json:"base_qty"`
|
|
BaseCost float64 `json:"base_cost"`
|
|
BaseStatus string `json:"base_status,omitempty"`
|
|
AddedOrderId string `json:"added_order_id"`
|
|
AddedNum int `json:"add_num"`
|
|
AddedQty int `json:"add_qty"`
|
|
AddedCost float64 `json:"add_cost"`
|
|
AddedStatus string `json:"added_status,omitempty"`
|
|
}
|
|
|
|
func InitState(sn string) error {
|
|
absPath := path.Join(config.Global.QMTDataDir, fmt.Sprintf("%s_%s_state.json", sn, config.Account.AccountID))
|
|
items, err := loadStateFile(absPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var codes []string
|
|
for code, _ := range items {
|
|
codes = append(codes, code)
|
|
}
|
|
|
|
QuantState = &State{
|
|
AbsPath: absPath,
|
|
Items: items,
|
|
Codes: codes,
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func loadStateFile(fp string) (map[string]*StateItem, error) {
|
|
raw, err := os.ReadFile(fp)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("[状态] 读取失败: %v", err)
|
|
}
|
|
var items map[string]*StateItem
|
|
if err := json.Unmarshal(raw, &items); err != nil {
|
|
return nil, fmt.Errorf("[状态] 解析失败:%s", err)
|
|
}
|
|
return items, nil
|
|
|
|
}
|
|
|
|
func SyncPositions(positions []sdk.Position) error {
|
|
for _, pos := range positions {
|
|
code := pos.StockCode
|
|
if code == "" || pos.Volume <= 0 || pos.OpenPrice <= 0 {
|
|
continue
|
|
}
|
|
if !slices.Contains(QuantState.Codes, code) {
|
|
item := &StateItem{
|
|
Code: code,
|
|
BaseQty: pos.Volume,
|
|
BaseCost: pos.OpenPrice,
|
|
BaseStatus: StatusOk,
|
|
}
|
|
QuantState.Append(item)
|
|
logf("WARNING", "[状态] %s 首次接管为底仓 数量=%d 成本=%.2f", code, pos.Volume, pos.OpenPrice)
|
|
}
|
|
}
|
|
|
|
return QuantState.Save()
|
|
}
|
|
|
|
func (s *State) Append(i *StateItem) {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
s.Items[i.Code] = i
|
|
s.Codes = append(s.Codes, i.Code)
|
|
}
|
|
|
|
func (s *State) Get(code string) (*StateItem, error) {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
if i, ok := s.Items[code]; ok {
|
|
return i, nil
|
|
} else {
|
|
return nil, fmt.Errorf("%s not found.", code)
|
|
}
|
|
}
|
|
|
|
func (s *State) Set(i *StateItem) {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
if _, ok := s.Items[i.Code]; !ok {
|
|
s.Codes = append(s.Codes, i.Code)
|
|
}
|
|
s.Items[i.Code] = i
|
|
}
|
|
|
|
func (s *State) Delete(code string) {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
delete(s.Items, code)
|
|
if index := slices.Index(s.Codes, code); index >= 0 {
|
|
s.Codes = slices.Delete(s.Codes, index, index+1)
|
|
}
|
|
}
|
|
|
|
func (s *State) Save() error {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
// 写入AbsPath文件
|
|
f, err := os.OpenFile(s.AbsPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o644)
|
|
if err != nil {
|
|
return fmt.Errorf("[状态] 打开文件失败: %v", err)
|
|
}
|
|
defer f.Close()
|
|
|
|
encoder := json.NewEncoder(f)
|
|
encoder.SetIndent("", " ")
|
|
if err := encoder.Encode(s.Items); err != nil {
|
|
return fmt.Errorf("[状态] 写入失败: %v", err)
|
|
}
|
|
return nil
|
|
}
|