@@ -465,6 +465,7 @@ func getBtTasks(c *fiber.Ctx) error {
465465 MaxStart int64 `query:"maxStart"`
466466 MaxID int64 `query:"maxId"`
467467 Limit int64 `query:"limit"`
468+ Assets bool `query:"assets"`
468469 }
469470
470471 var args = new (TaskArgs )
@@ -506,14 +507,119 @@ func getBtTasks(c *fiber.Ctx) error {
506507 // 处理返回数据
507508 result := make ([]map [string ]interface {}, 0 , len (tasks ))
508509 for _ , task := range tasks {
509- result = append (result , task .ToMap ())
510+ taskMap := task .ToMap ()
511+ if args .Assets {
512+ appendTaskAssets (task , taskMap )
513+ }
514+ result = append (result , taskMap )
510515 }
511516
512517 return c .JSON (fiber.Map {
513518 "data" : result ,
514519 })
515520}
516521
522+ const btCardAssetPoints = 160
523+
524+ type assetChartDataset struct {
525+ Label string `json:"label"`
526+ Data []float64 `json:"data"`
527+ }
528+
529+ type assetChartData struct {
530+ Labels []string `json:"labels"`
531+ Datasets []assetChartDataset `json:"datasets"`
532+ }
533+
534+ func appendTaskAssets (task * ormu.Task , taskMap map [string ]interface {}) {
535+ if task == nil || task .Path == "" || task .Status < int64 (ormu .BtStatusDone ) {
536+ return
537+ }
538+ assetPath := filepath .Join (config .GetDataDir (), "backtest" , task .Path , "assets.html" )
539+ if ! utils .Exists (assetPath ) {
540+ return
541+ }
542+ reals , used , err := readAssetLines (assetPath , btCardAssetPoints )
543+ if err != nil || len (reals ) == 0 {
544+ return
545+ }
546+ taskMap ["reals" ] = reals
547+ if len (used ) > 0 {
548+ taskMap ["used" ] = used
549+ }
550+ }
551+
552+ func readAssetLines (file string , maxPoints int ) ([]float64 , []float64 , error ) {
553+ content , err := os .ReadFile (file )
554+ if err != nil {
555+ return nil , nil , err
556+ }
557+ text := string (content )
558+ start := strings .Index (text , "chartData = " )
559+ keyLen := len ("chartData = " )
560+ if start < 0 {
561+ start = strings .Index (text , "chartData=" )
562+ keyLen = len ("chartData=" )
563+ }
564+ if start < 0 {
565+ return nil , nil , fmt .Errorf ("chart data not found" )
566+ }
567+ start += keyLen
568+ end := strings .Index (text [start :], "\n " )
569+ if end < 0 {
570+ end = strings .Index (text [start :], ";" )
571+ }
572+ if end < 0 {
573+ end = strings .Index (text [start :], "</script>" )
574+ }
575+ if end < 0 {
576+ return nil , nil , fmt .Errorf ("chart data not terminated" )
577+ }
578+ jsonStr := strings .TrimSpace (text [start : start + end ])
579+
580+ var data assetChartData
581+ if err := utils2 .UnmarshalString (jsonStr , & data , utils2 .JsonNumDefault ); err != nil {
582+ return nil , nil , err
583+ }
584+
585+ var reals []float64
586+ var used []float64
587+ for _ , ds := range data .Datasets {
588+ switch ds .Label {
589+ case "Real" :
590+ reals = ds .Data
591+ case "Available" :
592+ used = ds .Data
593+ }
594+ }
595+ if len (reals ) == 0 && len (data .Datasets ) > 0 {
596+ reals = data .Datasets [0 ].Data
597+ }
598+
599+ reals = downsampleSeries (reals , maxPoints )
600+ used = downsampleSeries (used , maxPoints )
601+ return reals , used , nil
602+ }
603+
604+ func downsampleSeries (values []float64 , maxPoints int ) []float64 {
605+ if maxPoints <= 0 || len (values ) <= maxPoints {
606+ return values
607+ }
608+ if maxPoints == 1 {
609+ return []float64 {values [len (values )- 1 ]}
610+ }
611+ step := float64 (len (values )- 1 ) / float64 (maxPoints - 1 )
612+ result := make ([]float64 , 0 , maxPoints )
613+ for i := 0 ; i < maxPoints ; i ++ {
614+ idx := int (step * float64 (i ) + 0.5 )
615+ if idx >= len (values ) {
616+ idx = len (values ) - 1
617+ }
618+ result = append (result , values [idx ])
619+ }
620+ return result
621+ }
622+
517623// getBtOptions 获取回测选项列表
518624func getBtOptions (c * fiber.Ctx ) error {
519625 qu , conn , err2 := ormu .Conn ()
0 commit comments