统一单入口 Go 工程与前端依赖目录,增加后台供应商配置、候选审核、计划预算、成交冲正、持仓复盘和站内通知。接入策略版本、Python 回测、可配置 A 股公开行情及 zap 文件轮转日志,补充迁移、契约与验证。
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user