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
+78
View File
@@ -0,0 +1,78 @@
package repository
import (
"context"
"git.sundynix.cn/Blizzard/Veyra/internal/apperror"
"git.sundynix.cn/Blizzard/Veyra/internal/model"
"github.com/google/uuid"
"github.com/jackc/pgx/v5/pgxpool"
"time"
)
type MarketRepository struct{ pool *pgxpool.Pool }
func NewMarketRepository(pool *pgxpool.Pool) *MarketRepository { return &MarketRepository{pool} }
func (r *MarketRepository) List(ctx context.Context, query string) ([]model.Instrument, error) {
rows, e := r.pool.Query(ctx, `SELECT id,symbol,exchange,name,price_cents,observed_at,received_at,source_id,source_version FROM veyra_instrument WHERE symbol LIKE $1 OR name LIKE $1 ORDER BY (price_cents>0) DESC,symbol LIMIT 100`, "%"+query+"%")
if e != nil {
return nil, mapError(e)
}
defer rows.Close()
list := make([]model.Instrument, 0)
for rows.Next() {
var v model.Instrument
if e := rows.Scan(&v.ID, &v.Symbol, &v.Exchange, &v.Name, &v.PriceCents, &v.ObservedAt, &v.ReceivedAt, &v.SourceID, &v.SourceVersion); e != nil {
return nil, mapError(e)
}
v.Price = model.FormatMoney(v.PriceCents)
list = append(list, v)
}
return list, mapError(rows.Err())
}
func (r *MarketRepository) Save(ctx context.Context, c model.ProviderConfig, list []model.Instrument, started time.Time, message string) error {
tx, e := r.pool.Begin(ctx)
if e != nil {
return mapError(e)
}
defer tx.Rollback(context.Background())
var version int64
var enabled bool
if e := tx.QueryRow(ctx, `SELECT version,enabled FROM veyra_provider_config WHERE id=$1 FOR SHARE`, c.ID).Scan(&version, &enabled); e != nil {
return mapError(e)
}
if !enabled || version != c.Version {
return apperror.ErrConflict
}
status := "COMPLETED"
if message != "" {
status = "FAILED"
list = nil
}
for _, v := range list {
_, e := tx.Exec(ctx, `INSERT INTO veyra_instrument(id,symbol,exchange,name,price_cents,observed_at,source_id,source_version) VALUES($1,$2,$3,$4,$5,$6,$7,$8) ON CONFLICT(exchange,symbol) DO UPDATE SET name=excluded.name,price_cents=excluded.price_cents,observed_at=excluded.observed_at,received_at=now(),source_id=excluded.source_id,source_version=excluded.source_version WHERE veyra_instrument.observed_at<=excluded.observed_at`, uuid.NewString(), v.Symbol, v.Exchange, v.Name, v.PriceCents, v.ObservedAt, c.ID, c.Version)
if e != nil {
return mapError(e)
}
}
_, e = tx.Exec(ctx, `INSERT INTO veyra_market_sync(id,source_id,source_version,instrument_count,status,message,started_at) VALUES($1,$2,$3,$4,$5,$6,$7)`, uuid.NewString(), c.ID, c.Version, len(list), status, message, started)
if e != nil {
return mapError(e)
}
return mapError(tx.Commit(ctx))
}
func (r *MarketRepository) Tasks(ctx context.Context) ([]model.MarketSync, error) {
rows, e := r.pool.Query(ctx, `SELECT id,source_id,source_version,instrument_count,status,message,started_at,finished_at FROM veyra_market_sync ORDER BY finished_at DESC,id DESC LIMIT 20`)
if e != nil {
return nil, mapError(e)
}
defer rows.Close()
list := make([]model.MarketSync, 0)
for rows.Next() {
var v model.MarketSync
if e := rows.Scan(&v.ID, &v.SourceID, &v.SourceVersion, &v.InstrumentCount, &v.Status, &v.Message, &v.StartedAt, &v.FinishedAt); e != nil {
return nil, mapError(e)
}
list = append(list, v)
}
return list, mapError(rows.Err())
}