package sentio import ( "errors" "git.beejay.kim/Gshopper/sentio/indicator" "math" "time" ) type Strategy interface { Name() string Model() string MarketId() string PositionSymbols() map[Side]string PositionProbabilities() map[Side]float64 Interval() uint8 Cooldown(periods uint8) time.Duration Handle(market Market, probability Probability) error } const ( ATR_MULTIPLIER = 1.5 ATR_PERIOD = 5 ATR_HHV = 4 ATR_STOPLOSS_THRESHOLD = 1 EXTRA_POSITION_THRESHOLD = 1.002 ) type BaseStrategy struct { } func (strategy BaseStrategy) Name() string { panic("implement me") } func (strategy BaseStrategy) Model() string { panic("implement me") } func (strategy BaseStrategy) MarketId() string { return "alpaca" } func (strategy BaseStrategy) PositionSymbols() map[Side]string { panic("implement me") } func (strategy BaseStrategy) PositionProbabilities() map[Side]float64 { panic("implement me") } func (strategy BaseStrategy) Interval() uint8 { return 5 } func (strategy BaseStrategy) Cooldown(periods uint8) time.Duration { return time.Minute * time.Duration(strategy.Interval()*periods) } func (strategy BaseStrategy) Handle(market Market, probability Probability) error { panic("implement me") } func (strategy BaseStrategy) Symbols() []string { var symbols []string for side, s := range strategy.PositionSymbols() { if BASE == side { continue } symbols = append(symbols, s) } return symbols } func (strategy BaseStrategy) CloseAllOrders(market Market) error { var ( symbols = strategy.Symbols() orders []Order err error ) if orders, err = market.Orders(OrderListCriteria{ Status: "open", Symbols: symbols, Nested: true, }); err != nil { return err } for i := range orders { if _, err = market.CloseOrder(orders[i].GetId()); err != nil { return err } } return nil } func (strategy BaseStrategy) AtrStopLoss(market Market, symbols ...string) (map[string]float64, error) { var ( stoplosses map[string]float64 bars map[string][]Bar err error ) if bars, err = market.HistoricalBars(symbols, time.Minute, nil); err != nil { return nil, err } if bars == nil || len(bars) < ATR_PERIOD { return nil, errors.New("AtrStopLoss: could not calculate stoploss for too short timeseries") } stoplosses = make(map[string]float64) for s := range bars { h := make([]float64, len(bars[s])) l := make([]float64, len(bars[s])) c := make([]float64, len(bars[s])) for i := range bars[s] { h[i] = bars[s][i].High l[i] = bars[s][i].Low c[i] = bars[s][i].Close } trailing := indicator.AtrTrailingStopLoss(h, l, c, ATR_PERIOD, ATR_MULTIPLIER, ATR_HHV) stoplosses[s] = trailing[len(trailing)-1] } return stoplosses, nil } func (strategy BaseStrategy) CreateOrder(m Market, t Side, proba Probability, p Portfolio, q map[string]Quote, sl map[string]float64) error { var ( symbol = strategy.PositionSymbols()[t] position Position account MarketAccount has = false threshold float64 size uint ok bool err error ) // ensure portfolio position, has = p.Get(symbol) // define threshold if threshold, ok = strategy.PositionProbabilities()[t]; !ok { threshold = -1 } if threshold == -1 || symbol == "" { return nil } // Prevent Market.CreateOrder when BidPrice less than ATR_STOPLOSS_THRESHOLD if q[symbol].BidPrice/sl[symbol] < ATR_STOPLOSS_THRESHOLD { return nil } if account, err = m.Account(); err != nil { return err } if account.GetCash() < q[symbol].BidPrice { return ErrTooSmallOrder } if !has && proba.Value > threshold { // create a new order if size = uint(math.Floor(account.GetCash() * .7 / q[symbol].BidPrice)); size < 1 { return ErrTooSmallOrder } _, err = m.CreateOrder(symbol, size, sl[symbol]) return err } else if has && position.GetAvgPrice()/position.GetCurrentPrice() > EXTRA_POSITION_THRESHOLD { // create an extra position if size = uint(math.Floor(account.GetCash() * .5 / q[symbol].BidPrice)); size < 1 { return ErrTooSmallOrder } _, err = m.CreateOrder(symbol, size, sl[symbol]) return err } return nil }