|
@@ -0,0 +1,392 @@
|
|
|
+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(
|
|
|
+ position_stub{
|
|
|
+ symbol: "521525019",
|
|
|
+ size: 10,
|
|
|
+ price: 20,
|
|
|
+ },
|
|
|
+ position_stub{
|
|
|
+ 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(
|
|
|
+ position_stub{
|
|
|
+ symbol: "521525019",
|
|
|
+ size: 10,
|
|
|
+ price: 20,
|
|
|
+ },
|
|
|
+ position_stub{
|
|
|
+ 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(
|
|
|
+ position_stub{
|
|
|
+ symbol: "521525019",
|
|
|
+ size: 10,
|
|
|
+ price: 20,
|
|
|
+ },
|
|
|
+ position_stub{
|
|
|
+ symbol: "521525020",
|
|
|
+ size: 10,
|
|
|
+ price: 20,
|
|
|
+ },
|
|
|
+ position_stub{
|
|
|
+ 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(
|
|
|
+ position_stub{
|
|
|
+ symbol: "569311092",
|
|
|
+ size: 10,
|
|
|
+ price: 8,
|
|
|
+ },
|
|
|
+ position_stub{
|
|
|
+ symbol: "521525020",
|
|
|
+ size: 10,
|
|
|
+ price: 20,
|
|
|
+ },
|
|
|
+ ),
|
|
|
+ proba: .9,
|
|
|
+ time: "2024-08-02T18:00:05Z",
|
|
|
+ },
|
|
|
+ want: nil,
|
|
|
+ },
|
|
|
+ { // should close nothing
|
|
|
+ args: args{
|
|
|
+ portfolio: newStubPortfolio(
|
|
|
+ position_stub{
|
|
|
+ symbol: "521525019",
|
|
|
+ size: 10,
|
|
|
+ price: 20,
|
|
|
+ },
|
|
|
+ position_stub{
|
|
|
+ symbol: "521525020",
|
|
|
+ size: 10,
|
|
|
+ price: 20,
|
|
|
+ },
|
|
|
+ ),
|
|
|
+ proba: 1.2,
|
|
|
+ time: "2024-08-02T18:00:05Z",
|
|
|
+ },
|
|
|
+ want: nil,
|
|
|
+ },
|
|
|
+ { // should close all
|
|
|
+ args: args{
|
|
|
+ portfolio: newStubPortfolio(
|
|
|
+ position_stub{
|
|
|
+ symbol: "521525019",
|
|
|
+ size: 10,
|
|
|
+ price: 20,
|
|
|
+ },
|
|
|
+ position_stub{
|
|
|
+ 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(
|
|
|
+ position_stub{
|
|
|
+ symbol: "521525020",
|
|
|
+ size: 10,
|
|
|
+ price: 20,
|
|
|
+ },
|
|
|
+ ),
|
|
|
+ proba: 1.1,
|
|
|
+ time: "2024-08-02T12:00:05Z",
|
|
|
+ },
|
|
|
+ want: nil,
|
|
|
+ },
|
|
|
+ }
|
|
|
+
|
|
|
+ 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(
|
|
|
+ position_stub{
|
|
|
+ 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(
|
|
|
+ position_stub{
|
|
|
+ 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(
|
|
|
+ position_stub{
|
|
|
+ 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(
|
|
|
+ position_stub{
|
|
|
+ symbol: "569311092",
|
|
|
+ size: 100,
|
|
|
+ price: 8,
|
|
|
+ },
|
|
|
+ ),
|
|
|
+ proba: .9979,
|
|
|
+ time: "2024-08-02T18:30:00Z",
|
|
|
+ },
|
|
|
+ symbol: lo.ToPtr("569311092"),
|
|
|
+ size: .3,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ args: args{
|
|
|
+ portfolio: newStubPortfolio(
|
|
|
+ position_stub{
|
|
|
+ symbol: "521525020",
|
|
|
+ size: 100,
|
|
|
+ price: 8,
|
|
|
+ },
|
|
|
+ ),
|
|
|
+ proba: .9979,
|
|
|
+ time: "2024-08-02T19:00:00Z",
|
|
|
+ },
|
|
|
+ symbol: nil,
|
|
|
+ size: 0,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ args: args{
|
|
|
+ portfolio: newStubPortfolio(
|
|
|
+ position_stub{
|
|
|
+ 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)
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+}
|