PPysolate SpineREAD THE CODE · UNDERSTAND THE SYSTEM
课程与章节
源码索引 / CURRENT IMPLEMENTATION

cmd/trade-demo/market.go

158 行 · 点击行号可定位;函数链接随声明位置更新,不手填行号。

跳到函数 / 声明
package main

import (
	"context"
	"encoding/json"
	"fmt"
	"io"
	"sync"
	"time"

	spine "pysolate-spine"
)

type event struct {
	Time      string  `json:"time"`
	ElapsedMS float64 `json:"elapsed_ms"`
	Sequence  int     `json:"seq"`
	Event     string  `json:"event"`
	Tool      string  `json:"tool,omitempty"`
	Data      any     `json:"data,omitempty"`
}

type trace struct {
	mu       sync.Mutex
	start    time.Time
	sequence int
	writer   io.Writer
}

func newTrace(w io.Writer) *trace { return &trace{start: time.Now(), writer: w} }
func (t *trace) log(kind, tool string, data any) {
	t.mu.Lock()
	defer t.mu.Unlock()
	t.write(kind, tool, data)
}
func (t *trace) write(kind, tool string, data any) {
	t.sequence++
	now := time.Now()
	e := event{Time: now.UTC().Format(time.RFC3339Nano), ElapsedMS: float64(now.Sub(t.start).Microseconds()) / 1000, Sequence: t.sequence, Event: kind, Tool: tool, Data: data}
	// This CLI's log writer is stderr; traces never alter runtime decisions.
	_ = json.NewEncoder(t.writer).Encode(e)
}
func (t *trace) closeSource(ch chan string) {
	t.mu.Lock()
	defer t.mu.Unlock()
	close(ch)
	t.write("source.closed", "", nil)
}

type order struct {
	ID       int    `json:"id"`
	Symbol   string `json:"symbol"`
	Quantity int    `json:"quantity"`
	Price    int    `json:"price"`
	Cost     int    `json:"cost"`
}
type market struct {
	mu                                sync.Mutex
	symbol                            string
	price, initialCash, remainingCash int
	allowOrder                        bool
	delay                             time.Duration
	orders                            []order
	attempts                          int
	calls                             map[string]int
	trace                             *trace
}

func newMarket(symbol string, price, cash int, allow bool, delay time.Duration, t *trace) *market {
	return &market{symbol: symbol, price: price, initialCash: cash, remainingCash: cash, allowOrder: allow, delay: delay, calls: map[string]int{}, trace: t}
}
func (m *market) wrap(name string, fn spine.Tool) spine.Tool {
	return func(ctx context.Context, args json.RawMessage) (any, error) {
		m.mu.Lock()
		m.calls[name]++
		m.mu.Unlock()
		m.trace.log("tool.start", name, args)
		value, err := fn(ctx, args)
		if err != nil {
			m.trace.log("tool.error", name, map[string]any{"error": err.Error()})
		} else {
			m.trace.log("tool.end", name, map[string]any{"result": value})
		}
		return value, err
	}
}
func (m *market) waitRead(ctx context.Context) error {
	if m.delay == 0 {
		return ctx.Err()
	}
	timer := time.NewTimer(m.delay)
	defer timer.Stop()
	select {
	case <-timer.C:
		return ctx.Err()
	case <-ctx.Done():
		return ctx.Err()
	}
}
func (m *market) tools() spine.Manifest {
	return spine.Manifest{
		"quote": {Call: m.wrap("quote", func(ctx context.Context, args json.RawMessage) (any, error) {
			var a struct {
				Symbol string `json:"symbol"`
			}
			if err := json.Unmarshal(args, &a); err != nil {
				return nil, err
			}
			if a.Symbol != m.symbol {
				return nil, fmt.Errorf("unknown simulated symbol: %s", a.Symbol)
			}
			if err := m.waitRead(ctx); err != nil {
				return nil, err
			}
			return m.price, nil
		}), AllowEarlyRead: true},
		"cash": {Call: m.wrap("cash", func(ctx context.Context, _ json.RawMessage) (any, error) {
			if err := m.waitRead(ctx); err != nil {
				return nil, err
			}
			// A frozen snapshot is eligible for early reads even if a later buy changes the account.
			return m.initialCash, nil
		}), AllowEarlyRead: true},
		"buy": {Call: m.wrap("buy", func(ctx context.Context, args json.RawMessage) (any, error) {
			m.mu.Lock()
			defer m.mu.Unlock()
			m.attempts++
			if err := ctx.Err(); err != nil {
				return nil, err
			}
			if !m.allowOrder {
				m.trace.log("order.denied", "buy", map[string]any{"reason": "Host approval missing", "account_changed": false})
				return nil, fmt.Errorf("simulated order requires Host -allow-order")
			}
			var a struct {
				Symbol   string `json:"symbol"`
				Quantity int    `json:"quantity"`
			}
			if err := json.Unmarshal(args, &a); err != nil {
				return nil, err
			}
			if a.Symbol != m.symbol || a.Quantity <= 0 {
				return nil, fmt.Errorf("invalid simulated order")
			}
			// Recheck the current account, independently of Python's control flow. Division avoids overflow.
			if a.Quantity > m.remainingCash/m.price {
				m.trace.log("order.denied", "buy", map[string]any{"reason": "insufficient current cash", "account_changed": false})
				return nil, fmt.Errorf("insufficient current cash")
			}
			m.trace.log("order.authorized", "buy", map[string]any{"simulation": true})
			o := order{ID: len(m.orders) + 1, Symbol: a.Symbol, Quantity: a.Quantity, Price: m.price, Cost: a.Quantity * m.price}
			m.remainingCash -= o.Cost
			m.orders = append(m.orders, o)
			m.trace.log("order.committed", "buy", map[string]any{"simulation": true, "order": o, "remaining_cash": m.remainingCash})
			return o, nil
		})},
	}
}