first version
This commit is contained in:
@@ -0,0 +1,182 @@
|
||||
package tinvest
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/shopspring/decimal"
|
||||
|
||||
"overnight-trading-bot/internal/domain"
|
||||
)
|
||||
|
||||
var ErrNotFound = errors.New("not found")
|
||||
|
||||
type Gateway interface {
|
||||
GetInstrument(ctx context.Context, ticker, classCode string) (domain.Instrument, error)
|
||||
GetCandles(ctx context.Context, instrumentUID string, interval string, from, to time.Time) ([]domain.Candle, error)
|
||||
GetOrderBook(ctx context.Context, instrumentUID string, depth int32) (domain.OrderBook, error)
|
||||
GetTradingStatus(ctx context.Context, instrumentUID string) (domain.TradingStatus, error)
|
||||
PostLimitOrder(ctx context.Context, accountID, instrumentUID string, side domain.Side, lots int64, price decimal.Decimal, clientOrderID string) (domain.Order, error)
|
||||
CancelOrder(ctx context.Context, accountID, orderID string) error
|
||||
GetOrderState(ctx context.Context, accountID, orderID string) (domain.Order, error)
|
||||
GetActiveOrders(ctx context.Context, accountID string) ([]domain.Order, error)
|
||||
GetPortfolio(ctx context.Context, accountID string) (domain.Portfolio, error)
|
||||
GetOperations(ctx context.Context, accountID string, from, to time.Time) ([]domain.Operation, error)
|
||||
GetServerTime(ctx context.Context) (time.Time, error)
|
||||
}
|
||||
|
||||
type FakeGateway struct {
|
||||
mu sync.Mutex
|
||||
Instruments map[string]domain.Instrument
|
||||
Candles map[string][]domain.Candle
|
||||
OrderBooks map[string]domain.OrderBook
|
||||
Statuses map[string]domain.TradingStatus
|
||||
Orders map[string]domain.Order
|
||||
Portfolio domain.Portfolio
|
||||
Operations []domain.Operation
|
||||
ServerTime time.Time
|
||||
}
|
||||
|
||||
func NewFakeGateway() *FakeGateway {
|
||||
return &FakeGateway{
|
||||
Instruments: make(map[string]domain.Instrument),
|
||||
Candles: make(map[string][]domain.Candle),
|
||||
OrderBooks: make(map[string]domain.OrderBook),
|
||||
Statuses: make(map[string]domain.TradingStatus),
|
||||
Orders: make(map[string]domain.Order),
|
||||
Portfolio: domain.Portfolio{
|
||||
Equity: decimal.NewFromInt(100_000),
|
||||
Cash: decimal.NewFromInt(100_000),
|
||||
CheckedAt: time.Now().UTC(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (f *FakeGateway) GetInstrument(_ context.Context, ticker, classCode string) (domain.Instrument, error) {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
for _, instrument := range f.Instruments {
|
||||
if instrument.Ticker == ticker && instrument.ClassCode == classCode {
|
||||
return instrument, nil
|
||||
}
|
||||
}
|
||||
return domain.Instrument{}, ErrNotFound
|
||||
}
|
||||
|
||||
func (f *FakeGateway) GetCandles(_ context.Context, instrumentUID string, _ string, from, to time.Time) ([]domain.Candle, error) {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
var out []domain.Candle
|
||||
for _, candle := range f.Candles[instrumentUID] {
|
||||
if !candle.TradeDate.Before(from) && !candle.TradeDate.After(to) {
|
||||
out = append(out, candle)
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (f *FakeGateway) GetOrderBook(_ context.Context, instrumentUID string, _ int32) (domain.OrderBook, error) {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
book, ok := f.OrderBooks[instrumentUID]
|
||||
if !ok {
|
||||
return domain.OrderBook{}, ErrNotFound
|
||||
}
|
||||
return book, nil
|
||||
}
|
||||
|
||||
func (f *FakeGateway) GetTradingStatus(_ context.Context, instrumentUID string) (domain.TradingStatus, error) {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
status, ok := f.Statuses[instrumentUID]
|
||||
if !ok {
|
||||
return domain.TradingStatusNormal, nil
|
||||
}
|
||||
return status, nil
|
||||
}
|
||||
|
||||
func (f *FakeGateway) PostLimitOrder(_ context.Context, accountID, instrumentUID string, side domain.Side, lots int64, price decimal.Decimal, clientOrderID string) (domain.Order, error) {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
order := domain.Order{
|
||||
ClientOrderID: clientOrderID,
|
||||
BrokerOrderID: "fake-" + clientOrderID,
|
||||
AccountIDHash: accountID,
|
||||
InstrumentUID: instrumentUID,
|
||||
Side: side,
|
||||
OrderType: domain.OrderTypeLimit,
|
||||
LimitPrice: price,
|
||||
QuantityLots: lots,
|
||||
Status: domain.OrderStatusSent,
|
||||
RawStateJSON: "{}",
|
||||
CreatedAt: time.Now().UTC(),
|
||||
UpdatedAt: time.Now().UTC(),
|
||||
}
|
||||
f.Orders[order.BrokerOrderID] = order
|
||||
return order, nil
|
||||
}
|
||||
|
||||
func (f *FakeGateway) CancelOrder(_ context.Context, _ string, orderID string) error {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
order, ok := f.Orders[orderID]
|
||||
if !ok {
|
||||
return ErrNotFound
|
||||
}
|
||||
order.Status = domain.OrderStatusCancelled
|
||||
order.UpdatedAt = time.Now().UTC()
|
||||
f.Orders[orderID] = order
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *FakeGateway) GetOrderState(_ context.Context, _ string, orderID string) (domain.Order, error) {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
order, ok := f.Orders[orderID]
|
||||
if !ok {
|
||||
return domain.Order{}, ErrNotFound
|
||||
}
|
||||
return order, nil
|
||||
}
|
||||
|
||||
func (f *FakeGateway) GetActiveOrders(_ context.Context, _ string) ([]domain.Order, error) {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
out := make([]domain.Order, 0)
|
||||
for _, order := range f.Orders {
|
||||
if order.Status == domain.OrderStatusSent || order.Status == domain.OrderStatusPartiallyFilled {
|
||||
out = append(out, order)
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (f *FakeGateway) GetPortfolio(_ context.Context, _ string) (domain.Portfolio, error) {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
f.Portfolio.CheckedAt = time.Now().UTC()
|
||||
return f.Portfolio, nil
|
||||
}
|
||||
|
||||
func (f *FakeGateway) GetOperations(_ context.Context, _ string, from, to time.Time) ([]domain.Operation, error) {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
var out []domain.Operation
|
||||
for _, op := range f.Operations {
|
||||
if !op.ExecutedAt.Before(from) && !op.ExecutedAt.After(to) {
|
||||
out = append(out, op)
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (f *FakeGateway) GetServerTime(context.Context) (time.Time, error) {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
if f.ServerTime.IsZero() {
|
||||
return time.Now().UTC(), nil
|
||||
}
|
||||
return f.ServerTime, nil
|
||||
}
|
||||
Reference in New Issue
Block a user