Skip to content

Commit 1abacac

Browse files
committed
WebUI支持回测报告删除
回测限价单触发不考虑网络延迟 fix并发预热导致数据竞态
1 parent 56333f4 commit 1abacac

File tree

12 files changed

+191
-35
lines changed

12 files changed

+191
-35
lines changed

biz/odmgr_local.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -395,7 +395,7 @@ func (o *LocalOrderMgr) tryFillTriggers(od *ormo.InOutOrder, bar *banexg.Kline,
395395
curMS := btime.TimeMS()
396396
// The time when the simulation is triggered
397397
// 模拟触发时的时间
398-
var rate = config.BTNetCost / tfSecs
398+
var rate = float64(0) // 限价单触发不考虑网络延迟
399399
odType := banexg.OdTypeMarket
400400
if fillPrice > 0 {
401401
odType = banexg.OdTypeLimit

core/data.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,8 @@ var (
4848
NewNumInSim int // 撮合时创建新订单的数量
4949

5050
ConcurNum = 2 // The maximum number of K-line tasks to be downloaded at the same time. If it is too high, a 429 current limit will occur. 最大同时下载K线任务数,过大会出现429限流
51-
Version = "v0.2.17-beta.1"
52-
UIVersion = "v0.2.16"
51+
Version = "v0.2.17-beta.2"
52+
UIVersion = "v0.2.17-beta.2"
5353
SysLang string // language code for current system 当前系统语言设置
5454
LogFile string
5555
DevDbPath string

core/types.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,19 +27,19 @@ type PerfSta struct {
2727
Delta float64 `yaml:"delta" mapstructure:"delta"` // Multiplier before logarithmizing TotProfit 对TotProfit进行对数处理前的乘数
2828
}
2929

30-
type StrVal struct {
31-
Str string
32-
Val float64
30+
type StrAny struct {
31+
Str string `json:"str"`
32+
Val any `json:"val"`
3333
}
3434

35-
type StrAny struct {
36-
Str string
37-
Any any
35+
type StrVal[T comparable] struct {
36+
Str string `json:"str"`
37+
Val T `json:"val"`
3838
}
3939

4040
type StrInt64 struct {
41-
Str string
42-
Int int64
41+
Str string `json:"str"`
42+
Int int64 `json:"int"`
4343
}
4444

4545
type Param struct {

data/provider.go

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,8 @@ func (p *Provider[IKlineFeeder]) warmJobs(warmJobs []*WarmJob, pb *utils.StagedP
137137
}
138138
skipWarms := make(map[string][2]int)
139139
startTime := btime.TimeMS()
140-
retErr := utils.ParallelRun(warmJobs, core.ConcurNum, func(_ int, job *WarmJob) *errs.Error {
140+
// 这里不可使用并行预热,因预热过程会读写btime等全局变量,可能导致指标计算时repeat append on Series panic
141+
for _, job := range warmJobs {
141142
hold := job.hold
142143
if job.timeMS == 0 {
143144
job.timeMS = startTime
@@ -149,12 +150,14 @@ func (p *Provider[IKlineFeeder]) warmJobs(warmJobs []*WarmJob, pb *utils.StagedP
149150
skipWarms[k] = v
150151
}
151152
lockMap.Unlock()
152-
return err
153-
})
153+
if err != nil {
154+
return sinceMap, err
155+
}
156+
}
154157
if len(skipWarms) > 0 {
155158
log.Warn("warm lacks", zap.String("items", StrWarmLacks(skipWarms)))
156159
}
157-
return sinceMap, retErr
160+
return sinceMap, nil
158161
}
159162

160163
type HistProvider struct {

entry/entry.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -263,10 +263,15 @@ func runMergeAssets(args []string) error {
263263
if len(files) < 2 {
264264
return errs.NewMsg(errs.CodeParamRequired, "at least 2 files need to merge")
265265
}
266+
if outPath == "" {
267+
return errs.NewMsg(errs.CodeParamRequired, "-out is required")
268+
}
266269
filesMap := make(map[string]string)
267270
for _, file := range files {
268-
filesMap[file] = ""
271+
path := config.ParsePath(file)
272+
filesMap[path] = ""
269273
}
274+
outPath = config.ParsePath(outPath)
270275

271276
lineArr := utils.SplitSolid(lines, ",", true)
272277
err2 := opt.MergeAssetsHtml(outPath, filesMap, lineArr, false)

utils/file_util.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,41 @@ func EnsureDir(dir string, perm os.FileMode) error {
111111
return nil
112112
}
113113

114+
func RemovePath(path string, recursive bool) error {
115+
fileInfo, err := os.Stat(path)
116+
if err != nil {
117+
if os.IsNotExist(err) {
118+
return nil
119+
}
120+
return fmt.Errorf("check path fail: %s %w", path, err)
121+
}
122+
123+
// check is dir
124+
if fileInfo.IsDir() {
125+
// not allow to delete by recursive
126+
if !recursive {
127+
// check is empty
128+
entries, err := os.ReadDir(path)
129+
if err != nil {
130+
return fmt.Errorf("read dir fail: %s %w", path, err)
131+
}
132+
133+
if len(entries) > 0 {
134+
return fmt.Errorf("dir is not empty, enable recursive to delete: %s", path)
135+
}
136+
137+
// delete empty dir
138+
return os.Remove(path)
139+
}
140+
141+
// delete all
142+
return os.RemoveAll(path)
143+
}
144+
145+
// delete file
146+
return os.Remove(path)
147+
}
148+
114149
// FindSubPath searches for the first occurrence of a directory named tgtName
115150
// within the specified workDir and its subdirectories up to two levels deep.
116151
func FindSubPath(parDir, tgtName string, maxDepth int) (string, error) {

utils/text_utils.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ func MapToStr[T any](m map[string]T, value bool, precFlt int) string {
5454
var b strings.Builder
5555
arr := make([]*core.StrAny, 0, len(m))
5656
for k, v := range m {
57-
arr = append(arr, &core.StrAny{Str: k, Any: v})
57+
arr = append(arr, &core.StrAny{Str: k, Val: v})
5858
}
5959
sort.Slice(arr, func(i, j int) bool {
6060
return arr[i].Str < arr[j].Str
@@ -65,12 +65,12 @@ func MapToStr[T any](m map[string]T, value bool, precFlt int) string {
6565
}
6666
if value {
6767
var valStr string
68-
if fltVal, ok := p.Any.(float64); ok {
68+
if fltVal, ok := p.Val.(float64); ok {
6969
valStr = strconv.FormatFloat(fltVal, 'f', precFlt, 64)
70-
} else if flt32Val, ok := p.Any.(float32); ok {
70+
} else if flt32Val, ok := p.Val.(float32); ok {
7171
valStr = strconv.FormatFloat(float64(flt32Val), 'f', precFlt, 64)
7272
} else {
73-
valStr = fmt.Sprintf("%v", p.Any)
73+
valStr = fmt.Sprintf("%v", p.Val)
7474
}
7575
item := fmt.Sprintf("%s: %v", p.Str, valStr)
7676
b.WriteString(item)

web/dev/api_dev.go

Lines changed: 81 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ func regApiDev(api fiber.Router) {
7373
api.Get("/download", handleDownload)
7474
api.Get("/compare_assets", getCompareAssets)
7575
api.Post("/update_note", handleUpdateNote)
76+
api.Post("/del_bt_reports", delBacktestReports)
7677
}
7778

7879
func onWsDev(c *websocket.Conn) {
@@ -523,20 +524,25 @@ func getBtOptions(c *fiber.Ctx) error {
523524
}
524525

525526
// 处理策略列表
526-
stratMap := make(map[string]bool)
527+
stratMap := make(map[string]int)
527528
for _, o := range options {
528529
strats := strings.Split(o.Strats, ",")
529530
for _, s := range strats {
530531
if s = strings.TrimSpace(s); s != "" {
531-
stratMap[s] = true
532+
oldNum, _ := stratMap[s]
533+
stratMap[s] = oldNum + 1
532534
}
533535
}
534536
}
535-
strats := make([]string, 0, len(stratMap))
536-
for s := range stratMap {
537-
strats = append(strats, s)
537+
strats := make([]core.StrVal[int], 0, len(stratMap))
538+
for s, v := range stratMap {
539+
strats = append(strats, core.StrVal[int]{
540+
Str: s, Val: v,
541+
})
538542
}
539-
sort.Strings(strats)
543+
sort.Slice(strats, func(i, j int) bool {
544+
return strats[i].Val > strats[j].Val
545+
})
540546

541547
// 处理周期列表
542548
periodMap := make(map[string]int)
@@ -1464,6 +1470,75 @@ func getCompareAssets(c *fiber.Ctx) error {
14641470
return c.Send(content)
14651471
}
14661472

1473+
func delBacktestReports(c *fiber.Ctx) error {
1474+
type DelArgs struct {
1475+
IDs []int64 `json:"ids"`
1476+
Hashes []string `json:"hashes"`
1477+
}
1478+
var args = new(DelArgs)
1479+
if err := base.VerifyArg(c, args, base.ArgBody); err != nil {
1480+
return err
1481+
}
1482+
1483+
qu, conn, err2 := ormu.Conn()
1484+
if err2 != nil {
1485+
return err2
1486+
}
1487+
defer conn.Close()
1488+
1489+
// 构建files参数
1490+
files := make(map[string]bool)
1491+
tasks := make([]int64, 0, len(args.IDs))
1492+
failNum := 0
1493+
for _, id := range args.IDs {
1494+
task, err := qu.GetTask(context.Background(), id)
1495+
if err != nil {
1496+
failNum += 1
1497+
log.Error("query task fail", zap.Int64("id", id), zap.Error(err))
1498+
continue
1499+
}
1500+
tasks = append(tasks, id)
1501+
if task.Path != "" {
1502+
path := filepath.Join(config.GetDataDir(), "backtest", task.Path)
1503+
files[path] = utils.Exists(path)
1504+
}
1505+
}
1506+
for _, hash := range args.Hashes {
1507+
path := filepath.Join(config.GetDataDir(), "backtest", hash)
1508+
files[path] = utils.Exists(path)
1509+
rows, err := qu.FindTasks(context.Background(), ormu.FindTasksParams{
1510+
Path: hash,
1511+
})
1512+
if err != nil {
1513+
log.Error("FindTasks by hash fail", zap.String("hash", hash), zap.Error(err))
1514+
continue
1515+
}
1516+
for _, r := range rows {
1517+
tasks = append(tasks, r.ID)
1518+
}
1519+
}
1520+
for path, exist := range files {
1521+
if !exist {
1522+
continue
1523+
}
1524+
err := utils.RemovePath(path, true)
1525+
if err != nil {
1526+
log.Error("delete fail", zap.Error(err))
1527+
failNum += 1
1528+
}
1529+
}
1530+
if len(tasks) > 0 {
1531+
err := qu.DelTasks(context.Background(), tasks)
1532+
if err != nil {
1533+
log.Error("delete records fail", zap.Error(err))
1534+
}
1535+
}
1536+
return c.JSON(fiber.Map{
1537+
"success": len(files) - failNum,
1538+
"fail": failNum,
1539+
})
1540+
}
1541+
14671542
// handleUpdateNote 处理更新回测任务备注的请求
14681543
func handleUpdateNote(c *fiber.Ctx) error {
14691544
type UpdateNoteArgs struct {

web/ui/messages/en-US.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,8 @@
355355
"confirm_file_target": "Are you sure to {op} {src} to {target} ?",
356356
"input_file_name": "input target file name: ",
357357
"confirm_delete": "Are you sure to delete {src}? This operation cannot be undone!",
358+
"confirm_delete_n": "Are you sure to delete {src} items? This operation cannot be undone!",
359+
"delete_result": "delete {success} success, {fail} fail",
358360
"create": "Create",
359361
"bad_strat_name": "Please use the format \"folder:strategy\", such as Trend:MaCross",
360362
"only_strat_need": "Please enter the strategy name directly",

web/ui/messages/zh-CN.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,8 @@
355355
"confirm_file_target": "确定要将 {src} {op} 到 {target} 吗?",
356356
"input_file_name": "请输入目标文件名称",
357357
"confirm_delete": "确定要删除 {src} 吗?此操作不可恢复!",
358+
"confirm_delete_n": "确定要删除 {src} 个条目吗? 此操作不可恢复!",
359+
"delete_result": "已成功删除 {success} 个, 失败{fail} 个",
358360
"create": "创建",
359361
"bad_strat_name": "请使用\"目录:策略名\"的格式,例如 Trend:MaCross",
360362
"only_strat_need": "请直接输入策略名",

0 commit comments

Comments
 (0)