Browse Source

grid strategy

main
strange 7 months ago
parent
commit
2764111556
  1. 8
      api/pub.proto
  2. 12
      cmd/test/test.go
  3. 11
      internal/admin/repoitory/user_repository.go
  4. 33
      internal/admin/service/auth_service.go
  5. 6
      internal/exchange/exchange_service.go
  6. 6
      internal/trading/backtest/trade_account.go
  7. 6
      internal/trading/trading_grpc_server.go
  8. 8
      internal/trading/trading_service.go
  9. 3
      pkg/data/entity/user.go
  10. 4
      pkg/session/session.go
  11. 97
      pkg/strategy/bollgrid.go
  12. 87
      pkg/strategy/grid.go
  13. 2
      pkg/strategy/sig_strategy_registry.go
  14. 4
      pkg/strategy/strategy.go
  15. 6
      pkg/strategy/super_trend_bos_waves.go
  16. 3
      pkg/types/signal.go
  17. 2
      pkg/utils/misc/concurrent.go
  18. 2
      pkg/utils/misc/condition.go
  19. 2
      pkg/utils/misc/tuple.go

8
api/pub.proto

@ -248,6 +248,10 @@ message RpcSubject {
int64 uid = 1; int64 uid = 1;
string account = 2; string account = 2;
int64 time = 3; // ms int64 time = 3; // ms
map<string, string> extra = 4; Role role = 4;
map<string, string> extra = 15;
}
enum Role {
User = 0; //
Admin = 1; //
} }

12
cmd/test/test.go

@ -1,10 +1,7 @@
package main package main
import ( import (
"fmt"
"path/filepath"
"sig-pub/pkg/zlog" "sig-pub/pkg/zlog"
"strings"
"github.com/govalues/decimal" "github.com/govalues/decimal"
) )
@ -12,14 +9,7 @@ import (
func main() { func main() {
// curl -H 'Content-Type: application/json' --data-binary "@vmdata.json" -X POST http://localhost:8428/api/v1/import // curl -H 'Content-Type: application/json' --data-binary "@vmdata.json" -X POST http://localhost:8428/api/v1/import
// testDecimalScale() testDecimalScale()
file := "config.toml"
ext := filepath.Ext(file)
filename, _ := strings.CutSuffix(file, ext)
filename2 := strings.Replace(file, ext, "", -1)
fmt.Println(ext)
fmt.Println(filename)
fmt.Println(filename2)
} }
type BTC struct { type BTC struct {

11
internal/admin/repoitory/user_repository.go

@ -29,16 +29,17 @@ func (dao *UserRepository) FindByAccount(account string) (user *entity.User, err
} }
func (dao *UserRepository) NextUid() (nextUid int64, err error) { func (dao *UserRepository) NextUid() (nextUid int64, err error) {
maxUid := int64(0) var maxUid *int64
err = dao.db.Select(&maxUid, "uid=(select userid from t_user)") err = dao.db.Select(&maxUid, "select max(userid) from t_user")
if err != nil { if err != nil {
return return
} }
if maxUid == 0 { if maxUid == nil || *maxUid == 0 {
maxUid = 10000 maxUid = new(int64)
*maxUid = 10000
} }
nextUid = maxUid + rands.RandN(10) nextUid = *maxUid + rands.RandN(10)
return return
} }

33
internal/admin/service/auth_service.go

@ -2,13 +2,16 @@ package service
import ( import (
"encoding/base64" "encoding/base64"
"fmt"
"net/http" "net/http"
"sig-pub/api/pb"
"sig-pub/internal/admin/args" "sig-pub/internal/admin/args"
repository "sig-pub/internal/admin/repoitory" repository "sig-pub/internal/admin/repoitory"
"sig-pub/pkg/config" "sig-pub/pkg/config"
"sig-pub/pkg/data/entity" "sig-pub/pkg/data/entity"
"sig-pub/pkg/resp" "sig-pub/pkg/resp"
"sig-pub/pkg/session" "sig-pub/pkg/session"
"strconv"
"time" "time"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@ -16,7 +19,7 @@ import (
type AuthService struct { type AuthService struct {
repo *repository.UserRepository repo *repository.UserRepository
aesKeyBytes []byte aesKey []byte
} }
func NewAuthService(repo *repository.UserRepository) *AuthService { func NewAuthService(repo *repository.UserRepository) *AuthService {
@ -30,10 +33,12 @@ func (svc *AuthService) Route(group *gin.RouterGroup) {
if err != nil { if err != nil {
panic(err) panic(err)
} }
svc.aesKeyBytes, err = base64.StdEncoding.DecodeString(aesKey) svc.aesKey, err = base64.StdEncoding.DecodeString(aesKey)
if err != nil { if err != nil {
panic(err) panic(err)
} }
group.POST("login", svc.Login) // 登录接口
} }
func (s *AuthService) Login(ctx *gin.Context) { func (s *AuthService) Login(ctx *gin.Context) {
@ -51,12 +56,8 @@ func (s *AuthService) Login(ctx *gin.Context) {
ctx.JSON(http.StatusInternalServerError, resp.Fail(err.Error())) ctx.JSON(http.StatusInternalServerError, resp.Fail(err.Error()))
return return
} }
if len(req.Password) < 6 {
ctx.JSON(http.StatusBadRequest, gin.H{"error": "password length must be at least 6"})
return
}
if user == nil { if user == nil || user.UserId == 0 {
// 不存在注册 // 不存在注册
now := time.Now().UnixMilli() now := time.Now().UnixMilli()
uid, err := s.repo.NextUid() uid, err := s.repo.NextUid()
@ -67,8 +68,9 @@ func (s *AuthService) Login(ctx *gin.Context) {
user = &entity.User{ user = &entity.User{
UserId: uid, UserId: uid,
Account: req.Account, Account: req.Account,
Username: req.Account, Username: fmt.Sprintf("sig-%s", strconv.FormatInt(uid, 36)),
Password: req.Password, Password: req.Password,
Role: pb.Role_User,
CreateAt: now, CreateAt: now,
UpdateAt: now, UpdateAt: now,
} }
@ -77,7 +79,6 @@ func (s *AuthService) Login(ctx *gin.Context) {
ctx.JSON(http.StatusInternalServerError, resp.Fail(err.Error())) ctx.JSON(http.StatusInternalServerError, resp.Fail(err.Error()))
return return
} }
// return nil, errors.New("not found account " + req.Account)
} }
// verify password login // verify password login
if req.Password != user.Password { if req.Password != user.Password {
@ -86,10 +87,14 @@ func (s *AuthService) Login(ctx *gin.Context) {
} }
// generate token // generate token
rpcSubject := session.NewRpcSubject(user.UserId, user.Account, nil) rpcSubject := session.NewRpcSubject(user.UserId, user.Account, user.Role, nil)
_ = rpcSubject token, err := session.RpcSubjectGenToken(s.aesKey, rpcSubject)
// session.RpcSubjectGenToken() if err != nil {
ctx.JSON(http.StatusInternalServerError, resp.Fail(err.Error()))
// res := &auth.ResLogin{Token: token, Subject: subject}
return return
}
ctx.JSON(http.StatusOK, resp.Success(resp.H{
"token": token,
"subject": rpcSubject,
}))
} }

6
internal/exchange/exchange_service.go

@ -13,7 +13,7 @@ import (
"sig-pub/pkg/publish" "sig-pub/pkg/publish"
"sig-pub/pkg/types" "sig-pub/pkg/types"
"sig-pub/pkg/utils/collect" "sig-pub/pkg/utils/collect"
"sig-pub/pkg/utils/lang" "sig-pub/pkg/utils/misc"
"sig-pub/pkg/utils/retry" "sig-pub/pkg/utils/retry"
"sig-pub/pkg/utils/times" "sig-pub/pkg/utils/times"
"sig-pub/pkg/zlog" "sig-pub/pkg/zlog"
@ -815,8 +815,8 @@ func (svc *ExchangeService) HistoryKline(arg *pb.SeriesRange, recvBranch int, re
// 检查k线是否连续进行补齐 // 检查k线是否连续进行补齐
if err = svc.paddingKlinesIfNotSeries(exchange, arg.InstId, interval, klines, if err = svc.paddingKlinesIfNotSeries(exchange, arg.InstId, interval, klines,
lang.Ternary(arg.Desc, nil, prevLastK), misc.Ternary(arg.Desc, nil, prevLastK),
lang.Ternary(arg.Desc, prevFirstK, nil), misc.Ternary(arg.Desc, prevFirstK, nil),
); err != nil { ); err != nil {
return return
} }

6
internal/trading/backtest/trade_account.go

@ -7,7 +7,7 @@ import (
"sig-pub/pkg/types/decimals" "sig-pub/pkg/types/decimals"
"sig-pub/pkg/utils/collect" "sig-pub/pkg/utils/collect"
"sig-pub/pkg/utils/conver" "sig-pub/pkg/utils/conver"
"sig-pub/pkg/utils/lang" "sig-pub/pkg/utils/misc"
"sig-pub/pkg/zlog" "sig-pub/pkg/zlog"
"time" "time"
@ -200,8 +200,8 @@ func (a *BacktestTradeAccount) CloseTradeOrder(ticket trade.TradeTicket) (err er
entryTrades++ entryTrades++
entryPxs += trade.Price entryPxs += trade.Price
order.EntryFee += trade.Fee order.EntryFee += trade.Fee
order.EntryTime = lang.Ternary(order.EntryTime == 0, trade.Ctime, min(order.EntryTime, trade.Ctime)) order.EntryTime = misc.Ternary(order.EntryTime == 0, trade.Ctime, min(order.EntryTime, trade.Ctime))
order.PeakPx = lang.Ternary( order.PeakPx = misc.Ternary(
trade.Side == types.SideLong, trade.Side == types.SideLong,
max(order.PeakPx, trade.PeakPx), max(order.PeakPx, trade.PeakPx),
min(order.PeakPx, trade.PeakPx), min(order.PeakPx, trade.PeakPx),

6
internal/trading/trading_grpc_server.go

@ -6,7 +6,7 @@ import (
"sig-pub/api/pb" "sig-pub/api/pb"
"sig-pub/pkg/types" "sig-pub/pkg/types"
"sig-pub/pkg/utils/collect" "sig-pub/pkg/utils/collect"
"sig-pub/pkg/utils/lang" "sig-pub/pkg/utils/misc"
"sig-pub/pkg/utils/times" "sig-pub/pkg/utils/times"
"sig-pub/pkg/zlog" "sig-pub/pkg/zlog"
@ -58,7 +58,7 @@ func (svr *TradingGrpcServer) IndicatorMetas(ctx context.Context, req *pb.ReqInd
Name: input.Name, Name: input.Name,
Desc: input.Desc, Desc: input.Desc,
Type: int32(input.Type), Type: int32(input.Type),
Default: lang.Ternary(input.Default == nil, "", fmt.Sprintf("%v", input.Default)), Default: misc.Ternary(input.Default == nil, "", fmt.Sprintf("%v", input.Default)),
Options: collect.Mapping(input.Options, func(opt types.InputOption) *pb.InputOption { Options: collect.Mapping(input.Options, func(opt types.InputOption) *pb.InputOption {
return &pb.InputOption{ return &pb.InputOption{
Name: opt.Name, Name: opt.Name,
@ -191,7 +191,7 @@ func (svr *TradingGrpcServer) BacktestLog(ctx context.Context, req *pb.ReqBackte
// BacktestRace 交易计划参数调试回测 // BacktestRace 交易计划参数调试回测
func (svr *TradingGrpcServer) BacktestRace(ctx context.Context, req *pb.ReqBacktestRace) (rsp *pb.RspBacktestRace, err error) { func (svr *TradingGrpcServer) BacktestRace(ctx context.Context, req *pb.ReqBacktestRace) (rsp *pb.RspBacktestRace, err error) {
rsp = new(pb.RspBacktestRace) rsp = new(pb.RspBacktestRace)
lang.SafeGo(func() { misc.SafeGo(func() {
c := context.Background() c := context.Background()
err := svr.tradingService.BacktestRace(c, req) err := svr.tradingService.BacktestRace(c, req)
if err != nil { if err != nil {

8
internal/trading/trading_service.go

@ -15,7 +15,7 @@ import (
"sig-pub/pkg/trade" "sig-pub/pkg/trade"
"sig-pub/pkg/types" "sig-pub/pkg/types"
"sig-pub/pkg/utils/collect" "sig-pub/pkg/utils/collect"
"sig-pub/pkg/utils/lang" "sig-pub/pkg/utils/misc"
"sig-pub/pkg/utils/times" "sig-pub/pkg/utils/times"
"sig-pub/pkg/zlog" "sig-pub/pkg/zlog"
"sort" "sort"
@ -285,7 +285,7 @@ func (svc *TradingService) IndicatorSeries(ctx context.Context, indicatorName st
sr.WindowExtra = uint32(max(0, candlePeriods-1)) + uint32(appros) sr.WindowExtra = uint32(max(0, candlePeriods-1)) + uint32(appros)
indicatorStates := indicator.Meta().State // 指标导出状态 indicatorStates := indicator.Meta().State // 指标导出状态
digit = lang.Ternary(digit > 0 && digit <= 10, digit, 6) // 保留小数位数 digit = misc.Ternary(digit > 0 && digit <= 10, digit, 6) // 保留小数位数
pow := math.Pow(10, float64(digit)) pow := math.Pow(10, float64(digit))
matrix = make([]float64, 0, 200) matrix = make([]float64, 0, 200)
times = make([]int64, 0, 200) times = make([]int64, 0, 200)
@ -358,9 +358,9 @@ func (svc *TradingService) StrategySeries(ctx context.Context, req *pb.ReqStrate
// todo 多币种回测 // todo 多币种回测
return return
} }
side := lang.Ternary(sigSide == types.SideLong, pb.Side_BUY, pb.Side_SELL) side := misc.Ternary(sigSide == types.SideLong, pb.Side_BUY, pb.Side_SELL)
rsp.Signal = append(rsp.Signal, int32(side)) rsp.Signal = append(rsp.Signal, int32(side))
rsp.Direction = append(rsp.Direction, lang.Ternary[int32](k.OpenF64() > k.CloseF64(), 1, 2)) rsp.Direction = append(rsp.Direction, misc.Ternary[int32](k.OpenF64() > k.CloseF64(), 1, 2))
rsp.Prices = append(rsp.Prices, k.CloseF64()) rsp.Prices = append(rsp.Prices, k.CloseF64())
// 处理k线时间 // 处理k线时间
ktime := k.Ts ktime := k.Ts

3
pkg/data/entity/user.go

@ -1,5 +1,7 @@
package entity package entity
import "sig-pub/api/pb"
// User 用户 // User 用户
type User struct { type User struct {
UserId int64 `gorm:"column:userid;primaryKey;" json:"userId"` // 用户id UserId int64 `gorm:"column:userid;primaryKey;" json:"userId"` // 用户id
@ -7,6 +9,7 @@ type User struct {
Username string `gorm:"column:username" json:"username"` // 用户名 Username string `gorm:"column:username" json:"username"` // 用户名
Password string `gorm:"column:password" json:"password"` // 密码 Password string `gorm:"column:password" json:"password"` // 密码
Avatar string `gorm:"column:avatar" json:"avatar"` // 头像 Avatar string `gorm:"column:avatar" json:"avatar"` // 头像
Role pb.Role `gorm:"column:role" json:"role"` // 角色 0:普通用户 1:管理员
CreateAt int64 `gorm:"column:create_at" json:"createAt"` // 创建时间 CreateAt int64 `gorm:"column:create_at" json:"createAt"` // 创建时间
UpdateAt int64 `gorm:"column:update_at" json:"updateAt"` // 更新时间 UpdateAt int64 `gorm:"column:update_at" json:"updateAt"` // 更新时间
} }

4
pkg/session/session.go

@ -9,8 +9,8 @@ import (
"google.golang.org/protobuf/proto" "google.golang.org/protobuf/proto"
) )
func NewRpcSubject(userId int64, account string, extra map[string]string) (subject *pb.RpcSubject) { func NewRpcSubject(userId int64, account string, role pb.Role, extra map[string]string) (subject *pb.RpcSubject) {
subject = &pb.RpcSubject{Uid: userId, Account: account, Time: time.Now().UnixMilli(), Extra: extra} subject = &pb.RpcSubject{Uid: userId, Account: account, Role: role, Time: time.Now().UnixMilli(), Extra: extra}
return return
} }

97
pkg/strategy/bollgrid.go

@ -0,0 +1,97 @@
package strategy
import (
"sig-pub/pkg/types"
)
// BollGrid 布林带网格策略
// 基于布林带中轨(SMA)和标准差(StdDev)构建动态网格
// 利用布林带指标计算出的中轨和上轨反推标准差
// 当价格下穿下方网格线时做多
// 当价格上穿上方网格线时做空
type BollGrid struct {
period int16 // 布林带周期
gridStep float64 // 网格间距(标准差倍数)
gridSize int16 // 单侧网格数量
}
func (s *BollGrid) New() ISigStrategy {
return &BollGrid{}
}
func (s *BollGrid) Meta() StrategyMeta {
return StrategyMeta{
Name: "BollGrid",
Desc: "基于布林带标准差的动态网格策略",
Input: []types.InputArg{
{Name: "period", Type: types.InputTypeUInt, Desc: "布林带周期", Default: 20},
{Name: "gridStep", Type: types.InputTypeUFloat, Desc: "网格间距(标准差倍数)", Default: 1.0},
{Name: "gridSize", Type: types.InputTypeUInt, Desc: "单侧网格数量", Default: 3},
},
}
}
func (s *BollGrid) Init(input types.Input) (err error) {
s.period = input.Int16("period")
s.gridStep = input.Float("gridStep")
s.gridSize = input.Int16("gridSize")
return
}
func (s *BollGrid) CandlePeriods(ctx ISingleSigStrategyContext) int16 {
return max(
ctx.Indicator("BOLL", s.period).CandlePeriods(),
2, // 需要前一根K线判断交叉
)
}
func (s *BollGrid) Update(ctx ISingleSigStrategyContext) (side types.Side) {
// 获取指标数据
// BOLL指标 Calculate 返回值为 mb (中轨)
bollInd := ctx.Indicator("BOLL", s.period)
mb := bollInd.Get(0)
ub := bollInd.State("ub", 0) // 上轨 (mb + 2*sigma)
// 计算标准差 sigma
// 默认 BOLL 实现中,ub = mb + 2 * sigma
sigma := (ub - mb) / 2.0
if sigma == 0 {
return types.SideNone
}
// 获取前一根指标数据用于判断交叉
mbPrev := bollInd.Get(1)
ubPrev := bollInd.State("ub", 1)
sigmaPrev := (ubPrev - mbPrev) / 2.0
// 获取K线收盘价
closeP := ctx.Get(0).CloseF64()
closePrev := ctx.Get(1).CloseF64()
// 遍历网格层级
for i := int16(1); i <= s.gridSize; i++ {
stepMul := float64(i) * s.gridStep
// 下方网格线: MB - i * step * sigma
lower := mb - sigma*stepMul
lowerPrev := mbPrev - sigmaPrev*stepMul
// 价格下穿下方网格线 -> 买入信号
// Close[1] >= Lower[1] && Close[0] < Lower[0]
if closePrev >= lowerPrev && closeP < lower {
return types.SideLong
}
// 上方网格线: MB + i * step * sigma
upper := mb + sigma*stepMul
upperPrev := mbPrev + sigmaPrev*stepMul
// 价格上穿上方网格线 -> 卖出信号
// Close[1] <= Upper[1] && Close[0] > Upper[0]
if closePrev <= upperPrev && closeP > upper {
return types.SideShort
}
}
return
}

87
pkg/strategy/grid.go

@ -0,0 +1,87 @@
package strategy
import (
"sig-pub/pkg/types"
)
// Grid 网格策略
// 基于均线和ATR构建动态网格
// 当价格下穿下方网格线时做多
// 当价格上穿上方网格线时做空
type Grid struct {
period int16 // 均线和ATR周期
gridStep float64 // 网格间距(ATR倍数)
gridSize int16 // 单侧网格数量
}
func (s *Grid) New() ISigStrategy {
return &Grid{}
}
func (s *Grid) Meta() StrategyMeta {
return StrategyMeta{
Name: "Grid",
Desc: "基于ATR的动态网格策略",
Input: []types.InputArg{
{Name: "period", Type: types.InputTypeUInt, Desc: "EMA和ATR周期", Default: 20},
{Name: "gridStep", Type: types.InputTypeUFloat, Desc: "网格间距(ATR倍数)", Default: 1.0},
{Name: "gridSize", Type: types.InputTypeUInt, Desc: "单侧网格数量", Default: 5},
},
}
}
func (s *Grid) Init(input types.Input) (err error) {
s.period = input.Int16("period")
s.gridStep = input.Float("gridStep")
s.gridSize = input.Int16("gridSize")
return
}
func (s *Grid) CandlePeriods(ctx ISingleSigStrategyContext) int16 {
return max(
ctx.Indicator("EMA", s.period).CandlePeriods(),
ctx.Indicator("ATR", s.period).CandlePeriods(),
2, // 需要前一根K线判断交叉
)
}
func (s *Grid) Update(ctx ISingleSigStrategyContext) (side types.Side) {
// 获取指标数据
ema := ctx.Indicator("EMA", s.period).Get(0)
atr := ctx.Indicator("ATR", s.period).Get(0)
// 获取前一根指标数据用于判断交叉
emaPrev := ctx.Indicator("EMA", s.period).Get(1)
atrPrev := ctx.Indicator("ATR", s.period).Get(1)
// 获取K线收盘价
closeP := ctx.Get(0).CloseF64()
closePrev := ctx.Get(1).CloseF64()
// 遍历网格层级
for i := int16(1); i <= s.gridSize; i++ {
step := float64(i) * s.gridStep
// 下方网格线
lower := ema - atr*step
lowerPrev := emaPrev - atrPrev*step
// 价格下穿下方网格线 -> 买入信号
// Close[1] >= Lower[1] && Close[0] < Lower[0]
if closePrev >= lowerPrev && closeP < lower {
return types.SideLong
}
// 上方网格线
upper := ema + atr*step
upperPrev := emaPrev + atrPrev*step
// 价格上穿上方网格线 -> 卖出信号
// Close[1] <= Upper[1] && Close[0] > Upper[0]
if closePrev <= upperPrev && closeP > upper {
return types.SideShort
}
}
return
}

2
pkg/strategy/sig_strategy_registry.go

@ -29,6 +29,8 @@ func (r *SigStrategyRegistry) Init() (err error) {
r.MustRegistStrategy(&SuperTrend2Macd{}) r.MustRegistStrategy(&SuperTrend2Macd{})
r.MustRegistStrategy(&SuperTrendMacdRSI{}) r.MustRegistStrategy(&SuperTrendMacdRSI{})
r.MustRegistStrategy(&TrendTrackV1{}) r.MustRegistStrategy(&TrendTrackV1{})
r.MustRegistStrategy(&Grid{})
r.MustRegistStrategy(&BollGrid{})
return return
} }

4
pkg/strategy/strategy.go

@ -5,7 +5,7 @@ import (
"sig-pub/api/pb" "sig-pub/api/pb"
"sig-pub/pkg/types" "sig-pub/pkg/types"
"sig-pub/pkg/utils/collect" "sig-pub/pkg/utils/collect"
"sig-pub/pkg/utils/lang" "sig-pub/pkg/utils/misc"
"strings" "strings"
) )
@ -34,7 +34,7 @@ const (
func DriverIntervalKey(instId string, exchange pb.ExchangeType, completed bool, intervals ...types.Interval) string { func DriverIntervalKey(instId string, exchange pb.ExchangeType, completed bool, intervals ...types.Interval) string {
types.IntervalsSort(intervals) types.IntervalsSort(intervals)
strIntervals := collect.Mapping(intervals, func(interval types.Interval) string { return string(interval) }) strIntervals := collect.Mapping(intervals, func(interval types.Interval) string { return string(interval) })
pubKey := fmt.Sprintf("/interval/%s/%s/%s/%d", instId, exchange.String(), strings.Join(strIntervals, ","), lang.Ternary(completed, 1, 0)) pubKey := fmt.Sprintf("/interval/%s/%s/%s/%d", instId, exchange.String(), strings.Join(strIntervals, ","), misc.Ternary(completed, 1, 0))
return pubKey return pubKey
} }

6
pkg/strategy/super_trend_bos_waves.go

@ -2,7 +2,7 @@ package strategy
import ( import (
"sig-pub/pkg/types" "sig-pub/pkg/types"
"sig-pub/pkg/utils/lang" "sig-pub/pkg/utils/misc"
) )
type SupertrendBOSWaves struct { type SupertrendBOSWaves struct {
@ -77,9 +77,9 @@ func (s *SupertrendBOSWaves) Update(ctx ISingleSigStrategyContext) (side types.S
// Standard supertrend logic // Standard supertrend logic
prevSupertrend := supertrend prevSupertrend := supertrend
if direction == 1 { if direction == 1 {
supertrend = lang.Ternary(close < prevSupertrend, upperBand, max(lowerBand, prevSupertrend)) supertrend = misc.Ternary(close < prevSupertrend, upperBand, max(lowerBand, prevSupertrend))
} else { } else {
supertrend = lang.Ternary(close > prevSupertrend, lowerBand, min(upperBand, prevSupertrend)) supertrend = misc.Ternary(close > prevSupertrend, lowerBand, min(upperBand, prevSupertrend))
} }
s.prevDirection = direction s.prevDirection = direction

3
pkg/types/signal.go

@ -3,6 +3,7 @@ package types
type Side int32 type Side int32
const ( const (
SideNone Side = 0 // NONE
SideLong Side = 1 // LONG SideLong Side = 1 // LONG
SideShort Side = 2 // SHORT SideShort Side = 2 // SHORT
) )
@ -19,7 +20,7 @@ func (side Side) Opposite() Side {
case SideShort: case SideShort:
return SideLong return SideLong
default: default:
return 0 return SideNone
} }
} }

2
pkg/utils/lang/concurrent.go → pkg/utils/misc/concurrent.go

@ -1,4 +1,4 @@
package lang package misc
import ( import (
"runtime/debug" "runtime/debug"

2
pkg/utils/lang/condition.go → pkg/utils/misc/condition.go

@ -1,4 +1,4 @@
package lang package misc
// Ternary is a 1 line if/else statement. // Ternary is a 1 line if/else statement.
func Ternary[T any](condition bool, ifOutput T, elseOutput T) T { func Ternary[T any](condition bool, ifOutput T, elseOutput T) T {

2
pkg/utils/lang/tuple.go → pkg/utils/misc/tuple.go

@ -1,4 +1,4 @@
package lang package misc
type Tuple2[V0, V1 any] struct { type Tuple2[V0, V1 any] struct {
V0 V0 V0 V0
Loading…
Cancel
Save