feat: 完成模拟交易闭环、App 与 A 股行情接入
Checks / verify (push) Failing after 9m52s

统一单入口 Go 工程与前端依赖目录,增加后台供应商配置、候选审核、计划预算、成交冲正、持仓复盘和站内通知。接入策略版本、Python 回测、可配置 A 股公开行情及 zap 文件轮转日志,补充迁移、契约与验证。
This commit is contained in:
2026-10-08 22:58:52 +08:00
parent bffdb3858b
commit 92329ddce2
145 changed files with 18593 additions and 587 deletions
+74
View File
@@ -0,0 +1,74 @@
package market
import (
"context"
"fmt"
"git.sundynix.cn/Blizzard/Veyra/internal/model"
"net/http"
"net/http/httptest"
"testing"
)
func TestMarketPrecisionAndTimestamp(t *testing.T) {
server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, `{"rc":0,"data":{"total":1,"diff":{"0":{"f12":"600000","f13":1,"f14":"fixture","f2":1001,"f124":1700000000,"f152":2}}}}`)
}))
defer server.Close()
client := &Client{http: server.Client()}
rows, e := client.Snapshot(context.Background(), model.ProviderConfig{BaseURL: server.URL, ID: "source", Version: 3})
if e != nil || len(rows) != 1 {
t.Fatalf("snapshot: %v", e)
}
if rows[0].Price != "10.01" || rows[0].ObservedAt.Unix() != 1700000000 || rows[0].SourceVersion != 3 {
t.Fatal("lost integer precision or provider timestamp")
}
}
func TestHistoryOHLCAndDates(t *testing.T) {
response := `{"rc":0,"data":{"code":"600000","name":"fixture","klines":["2025-01-02,10.01,10.11,10.21,9.99,100,1"]}}`
server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Query().Get("fqt") != "0" {
t.Error("adjustment changed")
}
fmt.Fprint(w, response)
}))
defer server.Close()
client := &Client{http: server.Client()}
input := model.ProviderConfig{HistoryBaseURL: server.URL, Version: 4}
data, e := client.History(context.Background(), input, "600000", "SSE", "2025-01-01", "2025-01-03")
if e != nil || len(data.Bars) != 1 || data.Bars[0].Close != "10.11" || data.Adjustment != "unadjusted" {
t.Fatalf("history: %v", e)
}
response = `{"rc":0,"data":{"code":"600000","name":"fixture","klines":["2025-01-02,10.01,10.11,10.00,9.99,100,1"]}}`
if _, e := client.History(context.Background(), input, "600000", "SSE", "2025-01-01", "2025-01-03"); e == nil {
t.Fatal("invalid high accepted")
}
}
func TestSinaQuotesKeepVendorDate(t *testing.T) {
server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/quotes_service/api/json_v2.php/Market_Center.getHQNodeData":
fmt.Fprint(w, `[{"symbol":"sh600000","code":"600000","name":"fixture"}]`)
case "/list=sh600000":
fmt.Fprint(w, `var hq_str_sh600000="fixture,10.000,10.000,10.010,10.100,9.900,10.000,10.010,100,1000,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2025-01-02,15:00:00,00";`)
default:
t.Errorf("unexpected route %s", r.URL.Path)
}
}))
defer server.Close()
client := &Client{http: server.Client()}
rows, e := client.Snapshot(context.Background(), model.ProviderConfig{MarketAdapter: "sina_public", BaseURL: server.URL, QuoteBaseURL: server.URL})
if e != nil || len(rows) != 1 || rows[0].Price != "10.01" || rows[0].ObservedAt.Format("2006-01-02T15:04:05Z") != "2025-01-02T07:00:00Z" {
t.Fatalf("quote validation: %+v, %v", rows, e)
}
}
func TestPublicPricesRejectRounding(t *testing.T) {
for _, v := range []string{"10.001", "1e2", "NaN"} {
if _, e := exactPublicPrice(v); e == nil {
t.Fatalf("rounded or invalid price %s", v)
}
}
if v, e := exactPublicPrice("10.010"); e != nil || v != 1001 {
t.Fatal("exact extra zero rejected")
}
}