Checks / verify (push) Failing after 9m52s
统一单入口 Go 工程与前端依赖目录,增加后台供应商配置、候选审核、计划预算、成交冲正、持仓复盘和站内通知。接入策略版本、Python 回测、可配置 A 股公开行情及 zap 文件轮转日志,补充迁移、契约与验证。
103 lines
3.8 KiB
Go
103 lines
3.8 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"git.sundynix.cn/Blizzard/Veyra/internal/apperror"
|
|
"git.sundynix.cn/Blizzard/Veyra/internal/model"
|
|
"github.com/google/uuid"
|
|
"regexp"
|
|
"strings"
|
|
"unicode/utf8"
|
|
)
|
|
|
|
type StrategyRepository interface {
|
|
List(context.Context) ([]model.Strategy, error)
|
|
Detail(context.Context, string) (model.StrategyDetail, error)
|
|
Save(context.Context, string, StrategyInput, string, string) (model.StrategyDetail, error)
|
|
Deploy(context.Context, string, string, string, int64, string, string, string) (model.StrategyDetail, error)
|
|
}
|
|
type StrategyService struct {
|
|
repo StrategyRepository
|
|
development bool
|
|
}
|
|
|
|
func NewStrategyService(repo StrategyRepository, development bool) *StrategyService {
|
|
return &StrategyService{repo, development}
|
|
}
|
|
func (s *StrategyService) authorize(actor model.User) error {
|
|
if actor.Role != "ADMIN" || actor.Status != "ACTIVE" {
|
|
return apperror.ErrForbidden
|
|
}
|
|
if s == nil || s.repo == nil || !s.development {
|
|
return apperror.ErrUnavailable
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type StrategyInput struct {
|
|
Code string `json:"code"`
|
|
Name string `json:"name"`
|
|
Description string `json:"description"`
|
|
Parameters model.StrategyParameters `json:"parameters"`
|
|
ExpectedVersion int64 `json:"expected_version"`
|
|
}
|
|
|
|
var strategyCodePattern = regexp.MustCompile(`^[a-z][a-z0-9_]{2,63}$`)
|
|
|
|
func (s *StrategyService) List(ctx context.Context, actor model.User) ([]model.Strategy, error) {
|
|
if err := s.authorize(actor); err != nil {
|
|
return nil, err
|
|
}
|
|
return s.repo.List(ctx)
|
|
}
|
|
func (s *StrategyService) Detail(ctx context.Context, actor model.User, id string) (model.StrategyDetail, error) {
|
|
if err := s.authorize(actor); err != nil {
|
|
return model.StrategyDetail{}, err
|
|
}
|
|
if _, err := uuid.Parse(id); err != nil {
|
|
return model.StrategyDetail{}, apperror.ErrInvalidInput
|
|
}
|
|
return s.repo.Detail(ctx, id)
|
|
}
|
|
func (s *StrategyService) Save(ctx context.Context, actor model.User, id string, input StrategyInput, requestID string) (model.StrategyDetail, error) {
|
|
if err := s.authorize(actor); err != nil {
|
|
return model.StrategyDetail{}, err
|
|
}
|
|
if id != "" {
|
|
if _, err := uuid.Parse(id); err != nil {
|
|
return model.StrategyDetail{}, apperror.ErrInvalidInput
|
|
}
|
|
if input.ExpectedVersion < 1 {
|
|
return model.StrategyDetail{}, apperror.ErrInvalidInput
|
|
}
|
|
}
|
|
p := input.Parameters
|
|
input.Code = strings.TrimSpace(input.Code)
|
|
input.Name = strings.TrimSpace(input.Name)
|
|
input.Description = strings.TrimSpace(input.Description)
|
|
if !strategyCodePattern.MatchString(input.Code) || input.Name == "" || utf8.RuneCountInString(input.Name) > 80 || input.Description == "" || utf8.RuneCountInString(input.Description) > 2000 || p.ShortWindow < 2 || p.LongWindow <= p.ShortWindow || p.LongWindow > 200 || p.FeeBPS < 0 || p.FeeBPS > 1000 || p.SlippageBPS < 0 || p.SlippageBPS > 1000 || p.LotSize < 1 || p.LotSize > 10000 {
|
|
return model.StrategyDetail{}, apperror.ErrInvalidInput
|
|
}
|
|
return s.repo.Save(ctx, id, input, actor.ID, requestID)
|
|
}
|
|
func (s *StrategyService) Deploy(ctx context.Context, actor model.User, id, versionID, status string, expected int64, reason, requestID string) (model.StrategyDetail, error) {
|
|
if err := s.authorize(actor); err != nil {
|
|
return model.StrategyDetail{}, err
|
|
}
|
|
if _, err := uuid.Parse(id); err != nil || expected < 1 {
|
|
return model.StrategyDetail{}, apperror.ErrInvalidInput
|
|
}
|
|
if status != "ACTIVE" && status != "PAUSED" {
|
|
return model.StrategyDetail{}, apperror.ErrInvalidInput
|
|
}
|
|
if status == "ACTIVE" {
|
|
if _, err := uuid.Parse(versionID); err != nil {
|
|
return model.StrategyDetail{}, apperror.ErrInvalidInput
|
|
}
|
|
}
|
|
if err := model.ValidateReason(reason); err != nil {
|
|
return model.StrategyDetail{}, err
|
|
}
|
|
return s.repo.Deploy(ctx, id, versionID, status, expected, reason, actor.ID, requestID)
|
|
}
|