Procházet zdrojové kódy

alpaca draft strategy

Alexey Kim před 6 měsíci
rodič
revize
0b6234f681

+ 10 - 1
market.go

@@ -1,11 +1,20 @@
 package sentio
 
-import "context"
+import (
+	"context"
+	"errors"
+)
+
+var (
+	ErrMarketClosed = errors.New("market is closed")
+)
 
 type Market interface {
 	Connect(done chan struct{}) (chan MarketConnection, error)
 	Subscribe(symbol string) error
+
 	IsMarketOpened() bool
+	Time() Clock
 
 	Buy(ctx context.Context, symbol string, amount float64) (Order, error)
 	Sell(ctx context.Context, symbol string) (Order, error)

+ 1 - 4
strategy.go

@@ -7,10 +7,7 @@ type Strategy interface {
 	MarketId() string
 	PositionSymbols() map[Side]string
 
-	ShouldClosePositions(portfolio Portfolio, proba float64) []string
-	ShouldOpenPosition(portfolio Portfolio, proba float64) (*string, float64)
-
-	Handle(proba float64, portfolio Portfolio) []StrategyOrder
+	Handle(market Market, proba float64) ([]StrategyOrder, error)
 }
 
 type StrategyOrder struct {

+ 142 - 0
strategy/alpaca/qqq/strategy.go

@@ -0,0 +1,142 @@
+package main
+
+import (
+	"errors"
+	"git.beejay.kim/Gshopper/sentio"
+)
+
+var Strategy = alpacaQQQ{}
+
+type alpacaQQQ struct{}
+
+func (s alpacaQQQ) Name() string {
+	return "Alpaca: QQQ"
+}
+
+func (s alpacaQQQ) Model() string {
+	return "qqq400"
+}
+
+func (s alpacaQQQ) MarketId() string {
+	return "alpaca"
+}
+
+func (s alpacaQQQ) PositionSymbols() map[sentio.Side]string {
+	return map[sentio.Side]string{
+		sentio.LONG: "QQQ",
+	}
+}
+
+func (s alpacaQQQ) Handle(market sentio.Market, proba float64) ([]sentio.StrategyOrder, error) {
+	if !market.IsMarketOpened() {
+		return nil, sentio.ErrMarketClosed
+	}
+
+	var (
+		symbol    string
+		portfolio sentio.Portfolio
+		ok        bool
+		err       error
+	)
+
+	if portfolio, err = market.Portfolio(); err != nil {
+		return nil, err
+	}
+
+	if symbol, ok = s.PositionSymbols()[sentio.LONG]; !ok {
+		return nil, errors.New("incorrect strategy")
+	}
+
+	var (
+		position sentio.Position
+		act      sentio.OrderAction
+		t        = market.Time().Now()
+	)
+
+	if portfolio != nil {
+		position, ok = portfolio.Get(symbol)
+		ok = ok && position.GetSize() != 0
+	} else {
+		ok = false
+	}
+
+	// Close positions before market closed
+	if ok && t.Hour() >= 19 && t.Minute() >= 30 {
+		act = sentio.OrderSell
+		if position.GetSize() < 0 {
+			act = sentio.OrderBuy
+		}
+
+		return []sentio.StrategyOrder{
+			{
+				Symbol: position.GetSymbol(),
+				Action: act,
+				Ratio:  1,
+			},
+		}, nil
+	}
+
+	if proba < 0 {
+		return nil, nil
+	}
+
+	// Close LONG position
+	if ok && position.GetSize() > 0 && proba < 1 {
+		return []sentio.StrategyOrder{
+			{
+				Symbol: position.GetSymbol(),
+				Action: sentio.OrderSell,
+				Ratio:  1,
+			},
+		}, nil
+	}
+
+	// Close SHORT position
+	if ok && position.GetSize() < 0 && proba > 1 {
+		return []sentio.StrategyOrder{
+			{
+				Symbol: position.GetSymbol(),
+				Action: sentio.OrderBuy,
+				Ratio:  1,
+			},
+		}, nil
+	}
+
+	if t.Hour() == 19 && t.Minute() >= 30 {
+		return nil, nil
+	}
+
+	if proba > 1.002 {
+		return []sentio.StrategyOrder{
+			{
+				Symbol: symbol,
+				Action: sentio.OrderBuy,
+				Ratio: func() float64 {
+					if ok {
+						return .3
+					} else {
+						return .6
+					}
+				}(),
+			},
+		}, nil
+	}
+
+	if proba < .998 {
+		return []sentio.StrategyOrder{
+			{
+				Symbol: symbol,
+				Action: sentio.OrderSell,
+				Ratio: func() float64 {
+					if ok {
+						return .3
+					} else {
+						return .6
+					}
+				}(),
+			},
+		}, nil
+	}
+
+	return nil, nil
+}

+ 0 - 128
strategy/btc306.go

@@ -1,128 +0,0 @@
-package main
-
-import (
-	"git.beejay.kim/Gshopper/sentio"
-	"github.com/samber/lo"
-	"time"
-)
-
-var Strategy = _ibkr{
-	clock: sentio.ClockUTC{},
-}
-
-type _ibkr struct {
-	clock sentio.Clock
-}
-
-func (s _ibkr) Name() string {
-	return "BTC: BITO/BITI"
-}
-
-func (s _ibkr) Model() string {
-	return "btc306"
-}
-
-func (s _ibkr) MarketId() string {
-	return "ibkr"
-}
-
-func (s _ibkr) PositionSymbols() map[sentio.Side]string {
-	return map[sentio.Side]string{
-		sentio.LONG:  "521525019", // BITO
-		sentio.SHORT: "569311092", // BITI
-	}
-}
-
-func (s _ibkr) IsOpen() bool {
-	utc := s.clock.Now()
-	h := utc.Hour()
-	m := utc.Minute()
-
-	return (h == 13 && m >= 30 || h > 13) &&
-		h < 19 &&
-		utc.Weekday() != time.Saturday && utc.Weekday() != time.Sunday
-}
-
-func (s _ibkr) ShouldClosePositions(portfolio sentio.Portfolio, proba float64) []string {
-	if !s.IsOpen() {
-		p := lo.Map(portfolio.Positions(), func(item sentio.Position, _ int) string {
-			return item.GetSymbol()
-		})
-
-		symbols := lo.Values(s.PositionSymbols())
-		p = lo.Filter(p, func(item string, _ int) bool {
-			return lo.Contains(symbols, item)
-		})
-
-		return p
-	}
-
-	var (
-		utc     = s.clock.Now()
-		symbols []string
-	)
-
-	if portfolio == nil {
-		return nil
-	}
-
-	for side, symbol := range s.PositionSymbols() {
-		position, ok := portfolio.Get(symbol)
-		if !ok {
-			continue
-		}
-
-		if position.GetSize() <= 0 {
-			continue
-		}
-
-		if utc.Hour() >= 19 {
-			symbols = append(symbols, symbol)
-			continue
-		}
-
-		if sentio.LONG == side && proba < 1 {
-			symbols = append(symbols, symbol)
-			continue
-		}
-
-		if sentio.SHORT == side && proba > 1 {
-			symbols = append(symbols, symbol)
-			continue
-		}
-	}
-
-	return symbols
-}
-
-func (s _ibkr) ShouldOpenPosition(portfolio sentio.Portfolio, proba float64) (*string, float64) {
-	if !s.IsOpen() {
-		return nil, 0
-	}
-
-	if proba < 0 {
-		return nil, 0
-	}
-
-	for side, symbol := range s.PositionSymbols() {
-		position, ok := portfolio.Get(symbol)
-
-		if sentio.LONG == side && proba > 1.002 {
-			if !ok || position.GetSize() <= 0 {
-				return &symbol, .6
-			} else {
-				return &symbol, .3
-			}
-		}
-
-		if sentio.SHORT == side && proba < 0.998 {
-			if !ok || position.GetSize() <= 0 {
-				return &symbol, .8
-			} else {
-				return &symbol, .3
-			}
-		}
-	}
-
-	return nil, 0
-}

+ 0 - 405
strategy/btc306_test.go

@@ -1,405 +0,0 @@
-package main
-
-import (
-	"fmt"
-	"git.beejay.kim/Gshopper/sentio"
-	"github.com/samber/lo"
-	"reflect"
-	"slices"
-	"testing"
-	"time"
-)
-
-func Test__ibkr_IsOpen(t *testing.T) {
-	tests := []struct {
-		time string
-		want bool
-	}{
-		{
-			time: "2024-08-26T13:29:59Z", // 1s before open
-			want: false,
-		},
-		{
-			time: "2024-08-02T15:04:05Z", // Opened
-			want: true,
-		},
-		{
-			time: "2024-08-03T18:34:00Z", // Saturday
-			want: false,
-		},
-		{
-			time: "2024-08-25T15:00:00Z", // Sunday
-			want: false,
-		},
-		{
-			time: "2024-08-26T19:00:05Z", // Closed
-			want: false,
-		},
-	}
-
-	for _, tt := range tests {
-		var (
-			c   ClockFixed
-			s   _ibkr
-			err error
-		)
-
-		if c.fix, err = time.ParseInLocation(time.RFC3339, tt.time, time.UTC); err != nil {
-			t.Errorf("ParseInLocation() = %v", err)
-			return
-		}
-
-		s = _ibkr{
-			clock: c,
-		}
-
-		t.Run(fmt.Sprintf("IBKR:IsOpen:%s", tt.time), func(t *testing.T) {
-			got := s.IsOpen()
-			if got != tt.want {
-				t.Errorf("IsOpen() = %v, want %v", got, tt.want)
-			}
-		})
-	}
-}
-
-func Test__ibkr_ShouldClosePositions(t *testing.T) {
-	type args struct {
-		portfolio sentio.Portfolio
-		proba     float64
-		time      string
-	}
-
-	tests := []struct {
-		args args
-		want []string
-	}{
-		{ // should close SHORT position
-			args: args{
-				portfolio: newStubPortfolio(
-					PositionStub{
-						symbol: "521525019",
-						size:   10,
-						price:  20,
-					},
-					PositionStub{
-						symbol: "569311092",
-						size:   10,
-						price:  8,
-					},
-				),
-				proba: 1.00001,
-				time:  "2024-08-02T15:04:05Z",
-			},
-			want: []string{"569311092"},
-		},
-		{ // should close LONG position
-			args: args{
-				portfolio: newStubPortfolio(
-					PositionStub{
-						symbol: "521525019",
-						size:   10,
-						price:  20,
-					},
-					PositionStub{
-						symbol: "569311092",
-						size:   10,
-						price:  8,
-					},
-				),
-				proba: .9,
-				time:  "2024-08-02T15:04:05Z",
-			},
-			want: []string{"521525019"},
-		},
-		{ // nothing to close
-			args: args{
-				portfolio: newStubPortfolio(),
-				proba:     .9,
-				time:      "2024-08-02T15:04:05Z",
-			},
-			want: nil,
-		},
-		{ // should close both positions
-			args: args{
-				portfolio: newStubPortfolio(
-					PositionStub{
-						symbol: "521525019",
-						size:   10,
-						price:  20,
-					},
-					PositionStub{
-						symbol: "521525020",
-						size:   10,
-						price:  20,
-					},
-					PositionStub{
-						symbol: "569311092",
-						size:   10,
-						price:  8,
-					},
-				),
-				proba: .9,
-				time:  "2024-08-02T19:00:05Z",
-			},
-			want: []string{"521525019", "569311092"},
-		},
-		{ // should close nothing
-			args: args{
-				portfolio: newStubPortfolio(
-					PositionStub{
-						symbol: "569311092",
-						size:   10,
-						price:  8,
-					},
-					PositionStub{
-						symbol: "521525020",
-						size:   10,
-						price:  20,
-					},
-				),
-				proba: .9,
-				time:  "2024-08-02T18:00:05Z",
-			},
-			want: nil,
-		},
-		{ // should close nothing
-			args: args{
-				portfolio: newStubPortfolio(
-					PositionStub{
-						symbol: "521525019",
-						size:   10,
-						price:  20,
-					},
-					PositionStub{
-						symbol: "521525020",
-						size:   10,
-						price:  20,
-					},
-				),
-				proba: 1.2,
-				time:  "2024-08-02T18:00:05Z",
-			},
-			want: nil,
-		},
-		{ // should close all
-			args: args{
-				portfolio: newStubPortfolio(
-					PositionStub{
-						symbol: "521525019",
-						size:   10,
-						price:  20,
-					},
-					PositionStub{
-						symbol: "521525020",
-						size:   10,
-						price:  20,
-					},
-				),
-				proba: 1.2,
-				time:  "2024-08-02T12:00:05Z",
-			},
-			want: []string{"521525019"},
-		},
-		{ // should close nothing
-			args: args{
-				portfolio: newStubPortfolio(
-					PositionStub{
-						symbol: "521525020",
-						size:   10,
-						price:  20,
-					},
-				),
-				proba: 1.1,
-				time:  "2024-08-02T12:00:05Z",
-			},
-			want: nil,
-		},
-		{
-			args: args{
-				portfolio: newStubPortfolio(
-					PositionStub{
-						symbol: "569311092",
-						size:   10938,
-						price:  8.24685045,
-					}),
-				proba: 0.9972525825782994,
-				time:  "2024-09-03T04:00:00Z",
-			},
-			want: []string{"569311092"},
-		},
-	}
-
-	for _, tt := range tests {
-		var (
-			c   ClockFixed
-			s   _ibkr
-			err error
-		)
-
-		if c.fix, err = time.ParseInLocation(time.RFC3339, tt.args.time, time.UTC); err != nil {
-			t.Errorf("ParseInLocation() = %v", err)
-			return
-		}
-
-		s = _ibkr{
-			clock: c,
-		}
-
-		t.Run("IBKR:ShouldClosePositions", func(t *testing.T) {
-			if got := s.ShouldClosePositions(tt.args.portfolio, tt.args.proba); !slices.Equal(got, tt.want) {
-				t.Errorf("ShouldClosePositions() = %v, want %v", got, tt.want)
-			}
-		})
-	}
-}
-
-func Test__ibkr_ShouldOpenPosition(t *testing.T) {
-	type args struct {
-		portfolio sentio.Portfolio
-		proba     float64
-		time      string
-	}
-
-	tests := []struct {
-		args   args
-		symbol *string
-		size   float64
-	}{
-		{
-			args: args{
-				portfolio: newStubPortfolio(),
-				proba:     1.00201,
-				time:      "2024-08-02T18:30:00Z",
-			},
-			symbol: lo.ToPtr("521525019"),
-			size:   .6,
-		},
-		{
-			args: args{
-				portfolio: newStubPortfolio(
-					PositionStub{
-						symbol: "521525019",
-						size:   1,
-						price:  19,
-					},
-				),
-				proba: 1.00201,
-				time:  "2024-08-02T18:30:00Z",
-			},
-			symbol: lo.ToPtr("521525019"),
-			size:   .3,
-		},
-		{
-			args: args{
-				portfolio: newStubPortfolio(
-					PositionStub{
-						symbol: "521525019",
-						size:   -.2,
-						price:  19,
-					},
-				),
-				proba: 1.00201,
-				time:  "2024-08-02T18:30:00Z",
-			},
-			symbol: lo.ToPtr("521525019"),
-			size:   .6,
-		},
-		{
-			args: args{
-				portfolio: newStubPortfolio(
-					PositionStub{
-						symbol: "521525020",
-						size:   3,
-						price:  19,
-					},
-				),
-				proba: 1.00201,
-				time:  "2024-08-02T18:30:00Z",
-			},
-			symbol: lo.ToPtr("521525019"),
-			size:   .6,
-		},
-		{
-			args: args{
-				portfolio: newStubPortfolio(),
-				proba:     .9979,
-				time:      "2024-08-02T18:30:00Z",
-			},
-			symbol: lo.ToPtr("569311092"),
-			size:   .8,
-		},
-		{
-			args: args{
-				portfolio: newStubPortfolio(
-					PositionStub{
-						symbol: "569311092",
-						size:   100,
-						price:  8,
-					},
-				),
-				proba: .9979,
-				time:  "2024-08-02T18:30:00Z",
-			},
-			symbol: lo.ToPtr("569311092"),
-			size:   .3,
-		},
-		{
-			args: args{
-				portfolio: newStubPortfolio(
-					PositionStub{
-						symbol: "521525020",
-						size:   100,
-						price:  8,
-					},
-				),
-				proba: .9979,
-				time:  "2024-08-02T19:00:00Z",
-			},
-			symbol: nil,
-			size:   0,
-		},
-		{
-			args: args{
-				portfolio: newStubPortfolio(
-					PositionStub{
-						symbol: "569311092",
-						size:   100,
-						price:  8,
-					},
-				),
-				proba: -1,
-				time:  "2024-08-02T18:30:00Z",
-			},
-			symbol: nil,
-			size:   0,
-		},
-	}
-
-	for _, tt := range tests {
-		t.Run("IBKR:ShouldOpenPosition", func(t *testing.T) {
-			var (
-				c   ClockFixed
-				s   _ibkr
-				err error
-			)
-
-			if c.fix, err = time.ParseInLocation(time.RFC3339, tt.args.time, time.UTC); err != nil {
-				t.Errorf("ParseInLocation() = %v", err)
-				return
-			}
-
-			s = _ibkr{
-				clock: c,
-			}
-
-			symbol, size := s.ShouldOpenPosition(tt.args.portfolio, tt.args.proba)
-			if !reflect.DeepEqual(symbol, tt.symbol) {
-				t.Errorf("ShouldOpenPosition() symbol = %v, want %v", symbol, tt.symbol)
-			}
-
-			if size != tt.size {
-				t.Errorf("ShouldOpenPosition() size = %v, want %v", size, tt.size)
-			}
-		})
-	}
-}

+ 17 - 27
strategy/ibkr/qqq/qqq.go

@@ -2,7 +2,6 @@ package main
 
 import (
 	"git.beejay.kim/Gshopper/sentio"
-	"time"
 )
 
 var Strategy = _ib_qqq{
@@ -24,15 +23,6 @@ func (strategy _ib_qqq) Model() string {
 func (strategy _ib_qqq) MarketId() string {
 	return "ibkr"
 }
-func (strategy _ib_qqq) IsMarketOpened() bool {
-	utc := strategy.clock.Now()
-	h := utc.Hour()
-	m := utc.Minute()
-
-	return (h == 13 && m >= 30 || h > 13) &&
-		h < 20 &&
-		utc.Weekday() != time.Saturday && utc.Weekday() != time.Sunday
-}
 
 func (strategy _ib_qqq) PositionSymbols() map[sentio.Side]string {
 	return map[sentio.Side]string{
@@ -40,21 +30,21 @@ func (strategy _ib_qqq) PositionSymbols() map[sentio.Side]string {
 	}
 }
 
-func (strategy _ib_qqq) ShouldClosePositions(portfolio sentio.Portfolio, proba float64) []string {
-	panic("implement me")
-}
+func (strategy _ib_qqq) Handle(market sentio.Market, proba float64) ([]sentio.StrategyOrder, error) {
+	if !market.IsMarketOpened() {
+		return nil, sentio.ErrMarketClosed
+	}
 
-func (strategy _ib_qqq) ShouldOpenPosition(portfolio sentio.Portfolio, proba float64) (*string, float64) {
-	panic("implement me")
-}
+	var (
+		utc       = strategy.clock.Now()
+		portfolio sentio.Portfolio
+		err       error
+	)
 
-func (strategy _ib_qqq) Handle(proba float64, portfolio sentio.Portfolio) []sentio.StrategyOrder {
-	if !strategy.IsMarketOpened() {
-		return nil
+	if portfolio, err = market.Portfolio(); err != nil {
+		return nil, err
 	}
 
-	var utc = strategy.clock.Now()
-
 	for _, symbol := range strategy.PositionSymbols() {
 		var (
 			position sentio.Position
@@ -80,7 +70,7 @@ func (strategy _ib_qqq) Handle(proba float64, portfolio sentio.Portfolio) []sent
 					}(),
 					Ratio: 1,
 				},
-			}
+			}, nil
 		}
 
 		if proba < 0 {
@@ -95,7 +85,7 @@ func (strategy _ib_qqq) Handle(proba float64, portfolio sentio.Portfolio) []sent
 					Action: sentio.OrderSell,
 					Ratio:  1,
 				},
-			}
+			}, nil
 		}
 
 		// Close SHORT position
@@ -106,7 +96,7 @@ func (strategy _ib_qqq) Handle(proba float64, portfolio sentio.Portfolio) []sent
 					Action: sentio.OrderBuy,
 					Ratio:  1,
 				},
-			}
+			}, nil
 		}
 
 		if utc.Hour() == 19 && utc.Minute() >= 30 {
@@ -126,7 +116,7 @@ func (strategy _ib_qqq) Handle(proba float64, portfolio sentio.Portfolio) []sent
 						}
 					}(),
 				},
-			}
+			}, nil
 		}
 
 		if proba < .998 {
@@ -142,9 +132,9 @@ func (strategy _ib_qqq) Handle(proba float64, portfolio sentio.Portfolio) []sent
 						}
 					}(),
 				},
-			}
+			}, nil
 		}
 	}
 
-	return nil
+	return nil, nil
 }

+ 0 - 295
strategy/ibkr/qqq/qqq_test.go

@@ -1,295 +0,0 @@
-package main
-
-import (
-	"git.beejay.kim/Gshopper/sentio"
-	"reflect"
-	"testing"
-	"time"
-)
-
-func Test_IBKR_QQQ_IsMarketOpened(t *testing.T) {
-	tests := []struct {
-		name     string
-		datetime string
-		want     bool
-	}{
-		{
-			name:     "1s before open",
-			datetime: "2024-09-05T13:29:59Z",
-			want:     false,
-		},
-		{
-			name:     "just opened",
-			datetime: "2024-09-05T13:30:00Z",
-			want:     true,
-		},
-		{
-			name:     "Saturday",
-			datetime: "2024-09-07T13:30:00Z",
-			want:     false,
-		},
-		{
-			name:     "Sunday",
-			datetime: "2024-09-08T13:30:00Z",
-			want:     false,
-		},
-		{
-			name:     "1s before close",
-			datetime: "2024-09-05T18:59:59Z",
-			want:     true,
-		},
-		{
-			name:     "just closed",
-			datetime: "2024-09-05T20:00:00Z",
-			want:     false,
-		},
-		{
-			name:     "closed",
-			datetime: "2024-09-05T00:30:00Z",
-			want:     false,
-		},
-	}
-
-	for _, tt := range tests {
-		var (
-			strategy _ib_qqq
-			dt       = sentio.StubClock{}
-			err      error
-		)
-
-		if dt.Clock, err = time.ParseInLocation(time.RFC3339, tt.datetime, time.UTC); err != nil {
-			t.Errorf("ParseInLocation() = %v", err)
-			continue
-		}
-
-		strategy = _ib_qqq{clock: dt}
-
-		t.Run(tt.name, func(t *testing.T) {
-			if got := strategy.IsMarketOpened(); got != tt.want {
-				t.Errorf("IsMarketOpened() = %v, want %v", got, tt.want)
-			}
-		})
-	}
-}
-
-func Test__ib_qqq_Handle(t *testing.T) {
-	type args struct {
-		proba     float64
-		portfolio sentio.Portfolio
-		time      string
-	}
-
-	tests := []struct {
-		name string
-		args args
-		want []sentio.StrategyOrder
-	}{
-		{
-			name: "close short position: market reversal",
-			args: args{
-				proba: 1.00001,
-				portfolio: sentio.NewPortfolioStub(
-					sentio.PositionStub{
-						Symbol: "320227571",
-						Size:   -10,
-					},
-				),
-				time: "2024-08-02T15:04:05Z",
-			},
-			want: []sentio.StrategyOrder{
-				{
-					Symbol: "320227571",
-					Action: sentio.OrderBuy,
-					Ratio:  1,
-				},
-			},
-		},
-		{
-			name: "close long position: market reversal",
-			args: args{
-				proba: .999,
-				portfolio: sentio.NewPortfolioStub(
-					sentio.PositionStub{
-						Symbol: "320227571",
-						Size:   255,
-					}),
-				time: "2024-08-02T15:04:05Z",
-			},
-			want: []sentio.StrategyOrder{
-				{
-					Symbol: "320227571",
-					Action: sentio.OrderSell,
-					Ratio:  1,
-				},
-			},
-		},
-		{
-			name: "close position: market closes",
-			args: args{
-				proba: 0.00001,
-				portfolio: sentio.NewPortfolioStub(
-					sentio.PositionStub{
-						Symbol: "320227571",
-						Size:   -10,
-					},
-				),
-				time: "2024-08-02T19:34:05Z",
-			},
-			want: []sentio.StrategyOrder{
-				{
-					Symbol: "320227571",
-					Action: sentio.OrderBuy,
-					Ratio:  1,
-				},
-			},
-		},
-		{
-			name: "close position: market closes",
-			args: args{
-				proba: 1.00201,
-				portfolio: sentio.NewPortfolioStub(
-					sentio.PositionStub{
-						Symbol: "320227571",
-						Size:   -10,
-					},
-				),
-				time: "2024-08-02T19:34:05Z",
-			},
-			want: []sentio.StrategyOrder{
-				{
-					Symbol: "320227571",
-					Action: sentio.OrderBuy,
-					Ratio:  1,
-				},
-			},
-		},
-		{
-			name: "close position: market closes",
-			args: args{
-				proba: 1.00201,
-				portfolio: sentio.NewPortfolioStub(
-					sentio.PositionStub{
-						Symbol: "320227571",
-						Size:   10,
-					},
-				),
-				time: "2024-08-02T19:34:05Z",
-			},
-			want: []sentio.StrategyOrder{
-				{
-					Symbol: "320227571",
-					Action: sentio.OrderSell,
-					Ratio:  1,
-				},
-			},
-		},
-		{
-			name: "open position: long",
-			args: args{
-				proba: 1.00201,
-				time:  "2024-08-02T17:34:05Z",
-			},
-			want: []sentio.StrategyOrder{
-				{
-					Symbol: "320227571",
-					Action: sentio.OrderBuy,
-					Ratio:  .6,
-				},
-			},
-		},
-		{
-			name: "close LONG position",
-			args: args{
-				proba: .99,
-				portfolio: sentio.NewPortfolioStub(
-					sentio.PositionStub{
-						Symbol: "320227571",
-						Size:   10,
-					},
-				),
-				time: "2024-08-02T15:04:05Z",
-			},
-			want: []sentio.StrategyOrder{
-				{
-					Symbol: "320227571",
-					Action: sentio.OrderSell,
-					Ratio:  1,
-				},
-			},
-		},
-		{
-			name: "nothing to close or open",
-			args: args{
-				proba:     1,
-				portfolio: sentio.NewPortfolioStub(),
-				time:      "2024-08-02T15:04:05Z",
-			},
-		},
-		{
-			name: "nothing to close or open",
-			args: args{
-				proba:     1,
-				portfolio: nil,
-				time:      "2024-08-02T15:04:05Z",
-			},
-		},
-		{
-			name: "nothing to close or open",
-			args: args{
-				proba: 1.002,
-				portfolio: sentio.NewPortfolioStub(
-					sentio.PositionStub{
-						Symbol: "569311092",
-						Size:   100,
-					},
-					sentio.PositionStub{
-						Symbol: "521525020",
-						Size:   200,
-					},
-				),
-				time: "2024-08-02T15:04:05Z",
-			},
-		},
-		{
-			name: "open extra LONG position",
-			args: args{
-				proba: 1.002000001,
-				portfolio: sentio.NewPortfolioStub(
-					sentio.PositionStub{
-						Symbol: "320227571",
-						Size:   1,
-					}),
-				time: "2024-08-02T18:30:00Z",
-			},
-			want: []sentio.StrategyOrder{
-				{
-					Symbol: "320227571",
-					Action: sentio.OrderBuy,
-					Ratio:  .3,
-				},
-			},
-		},
-	}
-
-	for _, tt := range tests {
-		var (
-			strategy _ib_qqq
-			dt       = sentio.StubClock{}
-			err      error
-		)
-
-		if dt.Clock, err = time.ParseInLocation(time.RFC3339, tt.args.time, time.UTC); err != nil {
-			t.Errorf("ParseInLocation() = %v", err)
-			continue
-		}
-
-		strategy = _ib_qqq{clock: dt}
-
-		t.Run(tt.name, func(t *testing.T) {
-			got := strategy.Handle(tt.args.proba, tt.args.portfolio)
-			if !reflect.DeepEqual(got, tt.want) {
-				t.Errorf("Handle() got = %v, want %v", got, tt.want)
-			}
-		})
-	}
-}