third version

This commit is contained in:
2026-06-08 07:05:01 +00:00
parent 282c841e11
commit 52a935b8b4
20 changed files with 1371 additions and 151 deletions
+38 -30
View File
@@ -246,6 +246,44 @@ func (g *RealGateway) GetPortfolio(ctx context.Context, accountID string) (domai
if err != nil {
return domain.Portfolio{}, err
}
return portfolioFromResponse(resp.PortfolioResponse)
}
func (g *RealGateway) GetOperations(ctx context.Context, accountID string, from, to time.Time) ([]domain.Operation, error) {
if err := ctx.Err(); err != nil {
return nil, err
}
resp, err := retryValue(ctx, g.retryAttempts, g.retryBackoff, func() (*investgo.OperationsResponse, error) {
return g.operations.GetOperations(&investgo.GetOperationsRequest{
AccountId: accountID,
From: from,
To: to,
})
})
if err != nil {
return nil, err
}
return operationsFromResponse(resp.OperationsResponse), nil
}
func operationsFromResponse(resp *pb.OperationsResponse) []domain.Operation {
ops := resp.GetOperations()
out := make([]domain.Operation, 0, len(ops))
for _, op := range ops {
payment := money.MoneyValueToDecimal(op.GetPayment())
out = append(out, domain.Operation{
ID: op.GetId(),
InstrumentUID: op.GetInstrumentUid(),
Type: op.GetOperationType().String(),
Payment: payment,
Commission: operationCommission(op.GetOperationType(), payment),
ExecutedAt: op.GetDate().AsTime().UTC(),
})
}
return out
}
func portfolioFromResponse(resp *pb.PortfolioResponse) (domain.Portfolio, error) {
positions := resp.GetPositions()
holdings := make([]domain.Holding, 0, len(positions))
for _, position := range positions {
@@ -272,36 +310,6 @@ func (g *RealGateway) GetPortfolio(ctx context.Context, accountID string) (domai
}, nil
}
func (g *RealGateway) GetOperations(ctx context.Context, accountID string, from, to time.Time) ([]domain.Operation, error) {
if err := ctx.Err(); err != nil {
return nil, err
}
resp, err := retryValue(ctx, g.retryAttempts, g.retryBackoff, func() (*investgo.OperationsResponse, error) {
return g.operations.GetOperations(&investgo.GetOperationsRequest{
AccountId: accountID,
From: from,
To: to,
})
})
if err != nil {
return nil, err
}
ops := resp.GetOperations()
out := make([]domain.Operation, 0, len(ops))
for _, op := range ops {
payment := money.MoneyValueToDecimal(op.GetPayment())
out = append(out, domain.Operation{
ID: op.GetId(),
InstrumentUID: op.GetInstrumentUid(),
Type: op.GetOperationType().String(),
Payment: payment,
Commission: operationCommission(op.GetOperationType(), payment),
ExecutedAt: op.GetDate().AsTime().UTC(),
})
}
return out, nil
}
func (g *RealGateway) GetServerTime(ctx context.Context) (time.Time, error) {
if err := ctx.Err(); err != nil {
return time.Time{}, err
+124 -4
View File
@@ -1,10 +1,130 @@
package tinvest
import "context"
import (
"context"
"time"
"github.com/russianinvestments/invest-api-go-sdk/investgo"
pb "github.com/russianinvestments/invest-api-go-sdk/proto"
"github.com/shopspring/decimal"
"overnight-trading-bot/internal/domain"
"overnight-trading-bot/internal/money"
)
const sandboxEndpoint = "sandbox-invest-public-api.tinkoff.ru:443"
func NewSandboxGateway(ctx context.Context, opts Options) (*RealGateway, error) {
opts.Endpoint = sandboxEndpoint
return NewRealGateway(ctx, opts)
type SandboxGateway struct {
*RealGateway
sandbox *investgo.SandboxServiceClient
}
func NewSandboxGateway(ctx context.Context, opts Options) (*SandboxGateway, error) {
opts.Endpoint = sandboxEndpoint
realGateway, err := NewRealGateway(ctx, opts)
if err != nil {
return nil, err
}
return &SandboxGateway{
RealGateway: realGateway,
sandbox: realGateway.client.NewSandboxServiceClient(),
}, nil
}
func (g *SandboxGateway) PostLimitOrder(ctx context.Context, accountID, instrumentUID string, side domain.Side, lots int64, price decimal.Decimal, clientOrderID string) (domain.Order, error) {
if err := ctx.Err(); err != nil {
return domain.Order{}, err
}
direction := pb.OrderDirection_ORDER_DIRECTION_BUY
if side == domain.SideSell {
direction = pb.OrderDirection_ORDER_DIRECTION_SELL
}
resp, err := retryValue(ctx, g.retryAttempts, g.retryBackoff, func() (*investgo.PostOrderResponse, error) {
return g.sandbox.PostSandboxOrder(&investgo.PostOrderRequest{
InstrumentId: instrumentUID,
Quantity: lots,
Price: money.DecimalToQuotation(price),
Direction: direction,
AccountId: accountID,
OrderType: pb.OrderType_ORDER_TYPE_LIMIT,
OrderId: clientOrderID,
TimeInForce: pb.TimeInForceType_TIME_IN_FORCE_DAY,
PriceType: pb.PriceType_PRICE_TYPE_CURRENCY,
})
})
if err != nil {
return domain.Order{}, err
}
return orderFromPostResponse(resp.PostOrderResponse, accountID, clientOrderID, side, price), nil
}
func (g *SandboxGateway) CancelOrder(ctx context.Context, accountID, orderID string) error {
if err := ctx.Err(); err != nil {
return err
}
return withRetry(ctx, g.retryAttempts, g.retryBackoff, func() error {
_, err := g.sandbox.CancelSandboxOrder(accountID, orderID)
return err
})
}
func (g *SandboxGateway) GetOrderState(ctx context.Context, accountID, orderID string) (domain.Order, error) {
if err := ctx.Err(); err != nil {
return domain.Order{}, err
}
resp, err := retryValue(ctx, g.retryAttempts, g.retryBackoff, func() (*investgo.GetOrderStateResponse, error) {
return g.sandbox.GetSandboxOrderState(accountID, orderID)
})
if err != nil {
return domain.Order{}, err
}
return orderFromState(resp.OrderState, accountID), nil
}
func (g *SandboxGateway) GetActiveOrders(ctx context.Context, accountID string) ([]domain.Order, error) {
if err := ctx.Err(); err != nil {
return nil, err
}
resp, err := retryValue(ctx, g.retryAttempts, g.retryBackoff, func() (*investgo.GetOrdersResponse, error) {
return g.sandbox.GetSandboxOrders(accountID)
})
if err != nil {
return nil, err
}
states := resp.GetOrders()
out := make([]domain.Order, 0, len(states))
for _, state := range states {
out = append(out, orderFromState(state, accountID))
}
return out, nil
}
func (g *SandboxGateway) GetPortfolio(ctx context.Context, accountID string) (domain.Portfolio, error) {
if err := ctx.Err(); err != nil {
return domain.Portfolio{}, err
}
resp, err := retryValue(ctx, g.retryAttempts, g.retryBackoff, func() (*investgo.PortfolioResponse, error) {
return g.sandbox.GetSandboxPortfolio(accountID, pb.PortfolioRequest_RUB)
})
if err != nil {
return domain.Portfolio{}, err
}
return portfolioFromResponse(resp.PortfolioResponse)
}
func (g *SandboxGateway) GetOperations(ctx context.Context, accountID string, from, to time.Time) ([]domain.Operation, error) {
if err := ctx.Err(); err != nil {
return nil, err
}
resp, err := retryValue(ctx, g.retryAttempts, g.retryBackoff, func() (*investgo.OperationsResponse, error) {
return g.sandbox.GetSandboxOperations(&investgo.GetOperationsRequest{
AccountId: accountID,
From: from,
To: to,
})
})
if err != nil {
return nil, err
}
return operationsFromResponse(resp.OperationsResponse), nil
}