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

cmd/trade-demo/main.go

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

跳到函数 / 声明
package main

import (
	"context"
	"encoding/json"
	"flag"
	"fmt"
	"io"
	"os"
	"sort"
	"strings"
	"time"

	spine "pysolate-spine"
)

type config struct {
	wasm, source, mode, memory, symbol, inputs string
	price, cash, limit, quantity               int
	allowOrder, showTransformed                bool
	readDelay, timeLimit                       time.Duration
}

func main() {
	var c config
	flag.StringVar(&c.wasm, "wasm", "dist/spine.wasm", "existing Guest artifact")
	flag.StringVar(&c.source, "source", "examples/trading.py", "Python trading example")
	flag.StringVar(&c.mode, "mode", "plm", "normal, plm, or prefix")
	flag.StringVar(&c.memory, "memory", "fresh", "fresh, copy, or cow (Linux)")
	flag.StringVar(&c.symbol, "symbol", "ACME", "fictional symbol used by the local market")
	flag.IntVar(&c.price, "price", 100, "frozen simulated price, in whole demo units")
	flag.IntVar(&c.cash, "cash", 2000, "initial simulated cash")
	flag.IntVar(&c.limit, "limit", 110, "Python buy-price threshold")
	flag.IntVar(&c.quantity, "quantity", 10, "Python requested quantity")
	flag.StringVar(&c.inputs, "inputs", "", "JSON overrides for Python inputs; never grants Host approval")
	flag.BoolVar(&c.allowOrder, "allow-order", false, "allow in-memory simulated orders; no broker is contacted")
	flag.BoolVar(&c.showTransformed, "show-transformed", false, "print actual Guest-transformed source")
	flag.DurationVar(&c.readDelay, "read-delay", 0, "optional simulated latency of each snapshot read, e.g. 300ms")
	flag.DurationVar(&c.timeLimit, "timeout", 30*time.Second, "whole demo timeout")
	flag.Parse()
	if err := run(c, os.Stdout, os.Stderr); err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
}
func run(c config, stdout, stderr io.Writer) error {
	if c.mode != "normal" && c.mode != "plm" && c.mode != "prefix" {
		return fmt.Errorf("unknown mode %q", c.mode)
	}
	if c.memory != "fresh" && c.memory != "copy" && c.memory != "cow" {
		return fmt.Errorf("unknown memory mode %q", c.memory)
	}
	if c.price <= 0 || c.cash < 0 || c.quantity <= 0 || c.limit < 0 || c.readDelay < 0 || c.timeLimit <= 0 {
		return fmt.Errorf("invalid simulated market settings")
	}
	inputs := map[string]any{"symbol": c.symbol, "limit": c.limit, "quantity": c.quantity}
	if c.inputs != "" {
		if err := json.Unmarshal([]byte(c.inputs), &inputs); err != nil {
			return err
		}
		if inputs == nil {
			return fmt.Errorf("inputs must be a JSON object")
		}
	}
	wasm, err := os.ReadFile(c.wasm)
	if err != nil {
		return err
	}
	source, err := os.ReadFile(c.source)
	if err != nil {
		return err
	}
	t := newTrace(stderr)
	m := newMarket(c.symbol, c.price, c.cash, c.allowOrder, c.readDelay, t)
	t.log("demo.config", "", map[string]any{"simulation": true, "mode": c.mode, "memory": c.memory, "price": c.price, "cash": c.cash, "allow_order": c.allowOrder, "read_delay": c.readDelay.String()})
	ctx, cancel := context.WithTimeout(context.Background(), c.timeLimit)
	defer cancel()
	create := spine.New
	if c.memory == "copy" {
		create = spine.NewPrepared
	} else if c.memory == "cow" {
		create = spine.NewPreparedCOW
	}
	manifest := m.tools()
	earlyReads := make([]string, 0, len(manifest))
	for name, spec := range manifest {
		if spec.AllowEarlyRead {
			earlyReads = append(earlyReads, name)
		}
	}
	sort.Strings(earlyReads)
	t.log("runner.create", "", nil)
	runner, err := create(ctx, wasm, manifest)
	if err != nil {
		return err
	}
	defer runner.Close(context.Background())
	t.log("runner.ready", "", map[string]any{"early_reads": earlyReads, "buy_early": manifest["buy"].AllowEarlyRead})
	var output spine.Output
	if c.mode == "prefix" {
		chunks := make(chan string)
		producerDone := make(chan struct{})
		producerCtx, stopProducer := context.WithCancel(ctx)
		go func() {
			defer close(producerDone)
			defer t.closeSource(chunks)
			for i, chunk := range strings.SplitAfter(string(source), "\n") {
				if chunk == "" {
					continue
				}
				t.log("source.send", "", map[string]any{"chunk": i + 1, "text": chunk})
				select {
				case chunks <- chunk:
				case <-producerCtx.Done():
					t.log("source.cancelled", "", nil)
					return
				}
			}
		}()
		output, err = runner.RunPrefix(ctx, chunks, inputs)
		stopProducer()
		<-producerDone
	} else {
		t.log("source.complete", "", map[string]any{"bytes": len(source)})
		execute := runner.Run
		if c.mode == "plm" {
			execute = runner.RunPLM
		}
		output, err = execute(ctx, string(source), inputs)
	}
	t.log("run.finished", "", map[string]any{"transformed": output.Transformed != "", "error": errorText(err)})
	if c.showTransformed {
		fmt.Fprintln(stderr, "--- actual transformed source ---")
		if output.Transformed == "" {
			fmt.Fprintln(stderr, "(no AST rewrite)")
		} else {
			fmt.Fprintln(stderr, output.Transformed)
		}
	}
	if output.Stdout != "" {
		t.log("guest.stdout", "", output.Stdout)
	}
	if err != nil {
		return err
	}
	report := struct {
		Simulation bool            `json:"simulation"`
		Mode       string          `json:"mode"`
		Result     json.RawMessage `json:"result"`
		Calls      map[string]int  `json:"tool_calls"`
		Attempts   int             `json:"buy_attempts"`
		Orders     []order         `json:"orders"`
		Remaining  int             `json:"remaining_cash"`
	}{true, c.mode, output.Value, m.calls, m.attempts, append([]order{}, m.orders...), m.remainingCash}
	return json.NewEncoder(stdout).Encode(report)
}
func errorText(err error) string {
	if err == nil {
		return ""
	}
	return err.Error()
}