Documentation
¶
Index ¶
- func Middleware(engine *PaperEngine) server.ToolHandlerMiddleware
- type Account
- type Holding
- type LTPProvider
- type Monitor
- type Order
- type PaperEngine
- func (e *PaperEngine) CancelOrder(email, orderID string) (map[string]any, error)
- func (e *PaperEngine) Disable(email string) error
- func (e *PaperEngine) Enable(email string, initialCash float64) error
- func (e *PaperEngine) GetHoldings(email string) (any, error)
- func (e *PaperEngine) GetMargins(email string) (any, error)
- func (e *PaperEngine) GetOrders(email string) (any, error)
- func (e *PaperEngine) GetPositions(email string) (any, error)
- func (e *PaperEngine) IsEnabled(email string) bool
- func (e *PaperEngine) ModifyOrder(email, orderID string, params map[string]any) (map[string]any, error)
- func (e *PaperEngine) PlaceOrder(email string, params map[string]any) (map[string]any, error)
- func (e *PaperEngine) Reset(email string) error
- func (e *PaperEngine) SetDispatcher(d *domain.EventDispatcher)
- func (e *PaperEngine) SetLTPProvider(p LTPProvider)
- func (e *PaperEngine) Status(email string) (map[string]any, error)
- type Position
- type Store
- func (s *Store) DeletePosition(email, exchange, symbol, product string) error
- func (s *Store) DisableAccount(email string) error
- func (s *Store) EnableAccount(email string, initialCash float64) error
- func (s *Store) GetAccount(email string) (*Account, error)
- func (s *Store) GetAllOpenOrders() ([]*Order, error)
- func (s *Store) GetHoldings(email string) ([]*Holding, error)
- func (s *Store) GetOpenOrders(email string) ([]*Order, error)
- func (s *Store) GetOrder(orderID string) (*Order, error)
- func (s *Store) GetOrders(email string) ([]*Order, error)
- func (s *Store) GetPositions(email string) ([]*Position, error)
- func (s *Store) InitTables() error
- func (s *Store) InsertOrder(o *Order) error
- func (s *Store) ResetAccount(email string) error
- func (s *Store) UpdateCashBalance(email string, balance float64) error
- func (s *Store) UpdateOrderStatus(orderID, status string, filledQty int, avgPrice float64) error
- func (s *Store) UpsertHolding(h *Holding) error
- func (s *Store) UpsertPosition(p *Position) error
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Middleware ¶
func Middleware(engine *PaperEngine) server.ToolHandlerMiddleware
Middleware returns MCP tool handler middleware that intercepts order and portfolio tools when the user has paper trading enabled. Non-paper users and unrecognised tools pass through to the real handler.
Types ¶
type Account ¶
type Account struct {
Email string
Enabled bool
InitialCash domain.Money
CashBalance domain.Money
CreatedAt time.Time
ResetAt time.Time
}
Account represents a paper trading account for a user.
InitialCash and CashBalance use the domain.Money value object so the engine fails fast on cross-currency comparisons rather than silently coercing — same Slice 1 pattern as UserLimits.Max*INR. The zero Money (Amount=0, Currency="") is the "empty / not yet funded" sentinel; once EnableAccount runs, both fields hold an INR Money. SQLite REAL columns stay; we bind via .Float64() and rehydrate via domain.NewINR(scanned) on Scan so the wire / persistence shape is unchanged.
type Holding ¶
type Holding struct {
Email string
Exchange string
Tradingsymbol string
Quantity int
AveragePrice domain.Money
LastPrice domain.Money
PnL domain.Money
}
Holding represents a paper trading CNC holding.
All monetary fields are domain.Money (Slice 6a). Same semantic as Position; weighted-average on additional buys via the Money pipeline.
type LTPProvider ¶
LTPProvider fetches last-traded prices for instruments. Instrument format: "EXCHANGE:TRADINGSYMBOL" (e.g. "NSE:RELIANCE").
type Monitor ¶
type Monitor struct {
// contains filtered or unexported fields
}
Monitor polls for OPEN paper orders and fills them when the LTP crosses the limit/trigger price. It runs as a background goroutine.
Wave D Phase 3 Package 3 (Logger sweep): logger is typed as the kc/logger.Logger port. The goroutine uses context.Background() at each log call site — Monitor is a long-lived service with no request ctx; future ctx threading would tie it to a parent app ctx captured by Start.
func NewMonitor ¶
NewMonitor creates a new background monitor for the paper trading engine. Accepts *slog.Logger for caller compatibility (app/wire.go:712); the logger is converted to the kc/logger.Logger port at the boundary.
func (*Monitor) Start ¶
func (m *Monitor) Start()
Start launches the monitor goroutine. Calling Start more than once is a programming error; the redundant goroutine leaks. Callers should create a new Monitor instead.
func (*Monitor) Stop ¶
func (m *Monitor) Stop()
Stop signals the monitor goroutine to exit and waits for it to terminate. It is safe to call Stop multiple times — only the first call closes the stop channel and waits for the loop; subsequent calls are no-ops (sync.Once guard).
If Start was never called, Stop is a pure no-op and returns immediately.
type Order ¶
type Order struct {
OrderID string
Email string
Exchange string
Tradingsymbol string
TransactionType string // BUY/SELL
OrderType string // MARKET/LIMIT/SL/SL-M
Product string // CNC/MIS/NRML
Variety string
Quantity int
Price domain.Money
TriggerPrice domain.Money
Status string // OPEN/COMPLETE/CANCELLED/REJECTED
FilledQuantity int
AveragePrice domain.Money
PlacedAt time.Time
FilledAt time.Time
Tag string
}
Order represents a paper trading order.
All monetary fields use domain.Money (Slice 6b complete):
- Price: user-supplied LIMIT/SL limit price; zero Money is the "MARKET / no price set" sentinel (Slice 2 OrderCheckRequest.Price pattern).
- TriggerPrice: SL / SL-M trigger threshold; zero Money is the "no trigger set" sentinel (LIMIT / MARKET orders never set it).
- AveragePrice: broker-reported fill price; zero Money is the "unfilled" sentinel matching the Slice 6a Position.LastPrice pre-refresh shape.
SQLite REAL columns unchanged; bind via .Float64(), scan via domain.NewINR. JSON wire format unchanged at the orderToMap + trades-list seams (.Float64() at the boundary).
type PaperEngine ¶
type PaperEngine struct {
// contains filtered or unexported fields
}
PaperEngine orchestrates virtual trading logic against a Store.
Wave D Phase 3 Package 3 (Logger sweep): logger is typed as the kc/logger.Logger port. NewEngine accepts *slog.Logger for caller compatibility (app/wire.go:482) and converts at the boundary via logport.NewSlog. Internal log calls use the ctx-aware port API with context.Background() — paper-engine methods don't carry a request ctx today, but the port abstraction positions the package for ctx threading in a later sweep.
func NewEngine ¶
func NewEngine(store *Store, logger *slog.Logger) *PaperEngine
NewEngine creates a new PaperEngine.
func (*PaperEngine) CancelOrder ¶
func (e *PaperEngine) CancelOrder(email, orderID string) (map[string]any, error)
CancelOrder cancels an open paper order.
func (*PaperEngine) Disable ¶
func (e *PaperEngine) Disable(email string) error
Disable deactivates paper trading for the given email.
func (*PaperEngine) Enable ¶
func (e *PaperEngine) Enable(email string, initialCash float64) error
Enable activates paper trading for the given email with the specified initial cash.
func (*PaperEngine) GetHoldings ¶
func (e *PaperEngine) GetHoldings(email string) (any, error)
GetHoldings returns all paper holdings in Kite API format.
func (*PaperEngine) GetMargins ¶
func (e *PaperEngine) GetMargins(email string) (any, error)
GetMargins returns paper margin info in Kite API format.
func (*PaperEngine) GetOrders ¶
func (e *PaperEngine) GetOrders(email string) (any, error)
GetOrders returns all paper orders in Kite API format.
func (*PaperEngine) GetPositions ¶
func (e *PaperEngine) GetPositions(email string) (any, error)
GetPositions returns all paper positions in Kite API format.
func (*PaperEngine) IsEnabled ¶
func (e *PaperEngine) IsEnabled(email string) bool
IsEnabled returns whether paper trading is enabled for the given email.
func (*PaperEngine) ModifyOrder ¶
func (e *PaperEngine) ModifyOrder(email, orderID string, params map[string]any) (map[string]any, error)
ModifyOrder modifies an open paper order's price, quantity, or order type.
func (*PaperEngine) PlaceOrder ¶
PlaceOrder places a paper order. Returns a Kite-compatible response map.
func (*PaperEngine) Reset ¶
func (e *PaperEngine) Reset(email string) error
Reset clears all paper trading data and resets cash to the initial amount.
func (*PaperEngine) SetDispatcher ¶
func (e *PaperEngine) SetDispatcher(d *domain.EventDispatcher)
SetDispatcher wires the paper engine to the shared domain event dispatcher so paper fills emit OrderPlacedEvent + OrderFilledEvent + PositionOpenedEvent into the same pipeline as live trades. Safe to leave nil in tests that don't need the audit trail.
func (*PaperEngine) SetLTPProvider ¶
func (e *PaperEngine) SetLTPProvider(p LTPProvider)
SetLTPProvider sets the LTP provider used for market price lookups.
type Position ¶
type Position struct {
Email string
Exchange string
Tradingsymbol string
Product string
Quantity int // positive=long, negative=short
AveragePrice domain.Money
LastPrice domain.Money
PnL domain.Money
}
Position represents an open paper trading position.
All monetary fields are domain.Money (Slice 6a complete):
- AveragePrice (commit 2): weighted-average via Money.Multiply / Add chains; resets to fillPrice on side-flip.
- LastPrice (commit 1): LTP refreshed from the LTPProvider.
- PnL (commit 3): derived value = Quantity * (LastPrice - AveragePrice). Negative for losing trades; sign preserved through Money.Sub + Money.Multiply.
SQLite REAL columns unchanged; bind via .Float64(), scan via domain.NewINR.
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store provides SQLite persistence for paper trading data.
Wave D Phase 3 Package 3: logger is typed as the kc/logger.Logger port. NewStore takes *slog.Logger for caller compatibility (app/wire.go:478) and converts via logport.NewSlog at the boundary.
func (*Store) DeletePosition ¶
DeletePosition removes a paper position.
func (*Store) DisableAccount ¶
DisableAccount disables paper trading for the given email.
func (*Store) EnableAccount ¶
EnableAccount creates or updates a paper trading account with the given initial cash.
func (*Store) GetAccount ¶
GetAccount retrieves the paper trading account for the given email. Returns nil, nil if no account exists.
SQLite REAL → domain.Money rehydration: the underlying column type is REAL (float64); we scan into local floats and wrap with domain.NewINR so the returned Account presents Money values to engine code. Boundary pattern matches kc/riskguard/limits.go LoadLimits (Slice 1).
func (*Store) GetAllOpenOrders ¶
GetAllOpenOrders returns all OPEN paper orders across all users.
func (*Store) GetHoldings ¶
GetHoldings returns all paper holdings for the given email.
SQLite REAL → domain.Money rehydration mirrors GetPositions.
func (*Store) GetOpenOrders ¶
GetOpenOrders returns all OPEN paper orders for the given email.
func (*Store) GetOrders ¶
GetOrders returns all paper orders for the given email, most recent first.
func (*Store) GetPositions ¶
GetPositions returns all paper positions for the given email.
SQLite REAL → domain.Money rehydration for AveragePrice, LastPrice, PnL. Mirrors the GetAccount pattern from Slice 5.
func (*Store) InitTables ¶
InitTables creates the paper trading tables if they don't already exist.
Foreign-key invariant (DDD aggregate boundary): paper_orders, paper_positions, and paper_holdings are children of the paper_accounts aggregate root. Each child row's email MUST reference an existing paper_accounts(email). The constraint is enforced by SQLite when foreign_keys=ON (set per-connection via the DSN _pragma=foreign_keys(1) — see kc/alerts/db.go:dsnWithFKPragma).
ON DELETE CASCADE: deleting an account row purges all dependent orders / positions / holdings atomically. In practice the application never hard- deletes paper_accounts (DeleteMyAccountUseCase calls Reset+Disable, which preserves the row with enabled=0); the cascade is a defence-in-depth invariant against schema-bypassing DELETEs.
CREATE TABLE IF NOT EXISTS is idempotent only on a fresh schema — existing pre-FK databases (Fly.io prod) keep their old constraint-less tables. New environments and all tests get the FK-enforced schema. Schema-as-truth going forward.
func (*Store) InsertOrder ¶
InsertOrder inserts a new paper trading order.
SQLite REAL boundary: Order.Price + Order.AveragePrice are domain.Money; bind via .Float64() at the SQL parameter sites so column types stay REAL.
func (*Store) ResetAccount ¶
ResetAccount deletes all orders, positions, and holdings for the given email and resets the cash balance to the initial amount.
func (*Store) UpdateCashBalance ¶
UpdateCashBalance updates the cash balance for the given account.
func (*Store) UpdateOrderStatus ¶
UpdateOrderStatus updates the status, filled quantity, and average price of an order.
func (*Store) UpsertHolding ¶
UpsertHolding inserts or updates a paper holding.
SQLite REAL boundary: AveragePrice, LastPrice, PnL are all domain.Money; bind via .Float64() at the SQL parameter sites.
func (*Store) UpsertPosition ¶
UpsertPosition inserts or updates a paper position.
SQLite REAL boundary: AveragePrice, LastPrice, PnL are all domain.Money; bind via .Float64() at the SQL parameter sites so column types stay REAL.