Browse Source

indicator/strategy use stream series

main
strange 10 months ago
parent
commit
1f492c6710
  1. 4
      README.md
  2. 2
      api/pub.proto
  3. 2
      api/trading.proto
  4. 4
      internal/exchange/exchange_service.go
  5. 20
      internal/trading/backtest/backtest.go
  6. 3
      internal/trading/sig/indicator_context.go
  7. 4
      internal/trading/trading_grpc_server.go
  8. 153
      internal/trading/trading_service.go
  9. 4
      pkg/indicator/atr.go
  10. 4
      pkg/indicator/indicator.go
  11. 5
      pkg/indicator/rsi.go
  12. 4
      pkg/indicator/sam.go
  13. 4
      pkg/strategy/cross_star.go
  14. 4
      pkg/strategy/gold_x.go
  15. 2
      pkg/strategy/sig_strategy.go
  16. 4
      pkg/strategy/super_trend.go
  17. 14
      pkg/trade/account.go
  18. 78
      pkg/trade/close_strategy.go
  19. 10
      pkg/trade/error.go
  20. 40
      pkg/trade/trade_account.go
  21. 12
      pkg/trade/trade_strategy.go
  22. 51
      pkg/trade/types.go

4
README.md

@ -51,12 +51,16 @@ kline时间窗口
Dragonfly redis替换
CCXT – [加密货币交易所交易库](https://github.com/ccxt/ccxt)
### 绘图框架
tradingview advanced-charts:
- https://cn.tradingview.com/advanced-charts/
tradingview lightweight-charts:
- https://tradingview.github.io/lightweight-charts/
- https://github.com/tradingview/lightweight-charts
- widgets: https://www.tradingview.com/widget-docs/widgets/charts/symbol-overview/
- free: https://www.tradingview.com/free-charting-libraries/
d3js:
- https://d3js.org/
night-vision:

2
api/pub.proto

@ -146,6 +146,6 @@ message SeriesRange {
bool live = 8; // k线, before和after为0时是否追加实时k线
bool desc = 9; // ,
uint32 window = 10; // k线条数
uint32 windowExtra = 10; // k线条数
uint32 limit = 11; // 0, limit则返回错误
}

2
api/trading.proto

@ -44,8 +44,6 @@ message ReqStrategySeries {
message RspStrategySeries {
repeated Side signal = 1; // 0.sell,1.buy
repeated int64 times = 2;
repeated bool wins = 3; // k线价格方向是否正确
double winRate = 4;
}
message ReqBacktest {

4
internal/exchange/exchange_service.go

@ -732,8 +732,8 @@ func (svc *ExchangeService) CalcSeriesRange(arg *pb.SeriesRange) (after, before,
}
}
// 额外拉取
if arg.Window > 0 {
before = max(intervalAdder(before, -int64(arg.Window)), KlineBefore0)
if arg.WindowExtra > 0 {
before = max(intervalAdder(before, -int64(arg.WindowExtra)), KlineBefore0)
}
if before > after {
err = fmt.Errorf("time range invalid: before must less then after")

20
internal/trading/backtest/backtest.go

@ -40,7 +40,7 @@ func (b *Backtest) RunTradingPlan(ctx context.Context, tradingPlan *sig.TradingP
instId := plan.InstId
sigStrategy := tradingPlan.GetSigStrategy()
maxWindow := sigStrategy.MaxWindow()
maxWindow := int(sigStrategy.RequiredSeries())
if maxWindow < 0 || maxWindow > indicator.MaxWindow {
err = fmt.Errorf("invalid window %d 0-%d, planId=%d", maxWindow, indicator.MaxWindow, plan.Id)
return
@ -53,15 +53,15 @@ func (b *Backtest) RunTradingPlan(ctx context.Context, tradingPlan *sig.TradingP
closeManager.SetDynamicParams(0.1, 0.02, 0)
seriesRange := &pb.SeriesRange{
Exchange: exchange,
InstId: instId,
Interval: string(interval),
Before: stime,
After: etime,
Open: false,
Live: false,
Desc: false,
Window: uint32(maxWindow),
Exchange: exchange,
InstId: instId,
Interval: string(interval),
Before: stime,
After: etime,
Open: false,
Live: false,
Desc: false,
WindowExtra: uint32(maxWindow),
}
// fetch history klines via stream
req := &pb.ReqHistoryKlineStream{Series: seriesRange}

3
internal/trading/sig/indicator_context.go

@ -60,12 +60,13 @@ func (c *IndicatorContext) Series(offset, count int16) (klines series.Klines) {
offset += c.offset
ks, ok := c.kSeries.Series(offset, count)
if !ok {
zlog.Warningf("get kline series offset out of range: offset=%d, count=%d, lastTs=%d", offset, count, c.kSeries.LastTs())
zlog.Warningf("get kline series offset out of range: offset=%d, count=%d, length=%d, lastTs=%d", offset, count, c.kSeries.Length(), c.kSeries.LastTs())
panic(fmt.Errorf("get kline series offset out of range: offset=%d, count=%d", offset, count))
}
return ks
}
// Deprecated: 用流处理(exchange rpc stream)
type HistoryIndicatorContext struct {
IOffsetIndicatorContext
exchangeClient pb.ExchangeServiceClient

4
internal/trading/trading_grpc_server.go

@ -22,7 +22,7 @@ func (svr *TradingGrpcServer) Init() (err error) {
}
func (svr *TradingGrpcServer) IndicatorSeries(ctx context.Context, req *pb.ReqIndicatorSeries) (rsp *pb.RspIndicatorSeries, err error) {
matrix, times, err := svr.tradingService.IndicatorSeries(req.Indicator, req.Window, req.Series)
matrix, times, err := svr.tradingService.IndicatorSeries(ctx, req.Indicator, req.Window, req.Series)
if err != nil {
return
}
@ -34,7 +34,7 @@ func (svr *TradingGrpcServer) IndicatorSeries(ctx context.Context, req *pb.ReqIn
func (svr *TradingGrpcServer) StrategySeries(ctx context.Context, req *pb.ReqStrategySeries) (rsp *pb.RspStrategySeries, err error) {
rsp = &pb.RspStrategySeries{}
err = svr.tradingService.StrategySeries(req, rsp)
err = svr.tradingService.StrategySeries(ctx, req, rsp)
return
}

153
internal/trading/trading_service.go

@ -3,6 +3,7 @@ package trading
import (
"context"
"fmt"
"io"
"sig-pub/api/pb"
"sig-pub/pkg/client"
"sig-pub/pkg/data"
@ -13,12 +14,14 @@ import (
"sig-pub/pkg/types"
"sig-pub/pkg/utils/collect"
"sig-pub/pkg/utils/lang"
"sig-pub/pkg/utils/times"
"sig-pub/pkg/zlog"
"sig-pub/internal/trading/backtest"
"sig-pub/internal/trading/sig"
"github.com/bytedance/sonic"
"google.golang.org/grpc"
)
type TradingService struct {
@ -153,8 +156,48 @@ func (svc *TradingService) getTradingPlan(plan *entity.TradePlan, sigKlineSeries
// }()
}
// fetchHistoryKlineSeries 请求k线数据流式处理
func (svc *TradingService) fetchHistoryKlineSeries(ctx context.Context, sr *pb.SeriesRange, recvFn func(k *types.Kline) error) (err error) {
// fetch history klines via stream
req := &pb.ReqHistoryKlineStream{Series: sr}
stream, err := svc.exchangeClient.HistoryKlineStream(ctx, req, grpc.UseCompressor("snappy"))
if err != nil {
return
}
var msg *pb.RspHistoryKlineStream
recvTimes, recvTotal := 0, 0
watch := times.NewWatch()
for {
select {
case <-ctx.Done():
err = ctx.Err()
return
default:
}
msg, err = stream.Recv()
if err == io.EOF {
err = nil
break
}
if err != nil {
return
}
recvTimes++
recvTotal += len(msg.Klines)
for _, k := range msg.Klines {
kline := new(types.Kline)
kline.ParsePBKline(sr.Exchange, k)
if err = recvFn(kline); err != nil {
return
}
}
}
zlog.Debugf("fetch history kline series: recv=%d, total=%d, use %s", recvTimes, recvTotal, watch.ElapsedFmt("."))
return
}
// IndicatorSeries 获取指标实时或历史序列数据, 闭区间
func (svc *TradingService) IndicatorSeries(indicatorName string, window uint32, sr *pb.SeriesRange) (matrix []float64, times []int64, err error) {
func (svc *TradingService) IndicatorSeries(ctx context.Context, indicatorName string, window uint32, sr *pb.SeriesRange) (matrix []float64, times []int64, err error) {
// indicatorName string, exchange pb.ExchangeType, instId string, interval types.Interval, window int
indicator, ok := svc.indicatorReg.IndicatorW(indicatorName)
if !ok {
@ -168,42 +211,35 @@ func (svc *TradingService) IndicatorSeries(indicatorName string, window uint32,
return
}
// before, after, count := sr.Before, sr.After, sr.Count
// var indCtx IOffsetIndicatorContext
// // 查询实时指标数据
// if before == 0 && after == 0 {
// klineSeries, err1 := svc.klineStore.GetKlineSeires(sr.Exchange, sr.InstId, interval)
// if err1 != nil {
// err = err1
// return
// }
// // recover todo out of range
// indCtx = NewIndicatorContext(klineSeries)
// }
// 查询历史指标数据
sr.Window = window
indCtx := sig.NewHistoryIndicatorContext(svc.exchangeClient)
totalK := 0
if totalK, err = indCtx.Init(sr); err != nil {
return
}
count := totalK - int(window)
requiredSeries := int(indicator.RequiredSeries(int16(window)))
sr.WindowExtra = uint32(requiredSeries)
kSeries := sig.NewKlineSeries(sr.Exchange, sr.InstId, types.Interval(sr.Interval))
indicatorContext := sig.NewIndicatorContext(kSeries)
matrix = make([]float64, 0, sr.Count)
times = make([]int64, 0, sr.Count)
for i := count - 1; i >= 0; i-- {
indCtx.SetOffset(int16(i))
vector := indicator.Calculate(indCtx, int16(window))
matrix = make([]float64, 0, 200)
times = make([]int64, 0, 200)
err = svc.fetchHistoryKlineSeries(ctx, sr, func(k *types.Kline) (err error) {
if lastTs, serial := kSeries.Update(k); !serial {
err = fmt.Errorf("kline not series: %s(%s), interval=%s, lastTs=%d", sr.InstId, sr.Exchange, interval, lastTs)
return
}
if kSeries.Length() < requiredSeries {
return
}
vector := indicator.Calculate(indicatorContext, int16(window))
matrix = append(matrix, vector)
times = append(times, indCtx.Get(0).Ts)
times = append(times, indicatorContext.Get(0).Ts)
return
})
if err != nil {
return
}
return
}
// StrategySeries 简单策略信号测试
// todo 去掉 HistoryIndicatorContext, 像backtest使用stream来一个算一个
func (svc *TradingService) StrategySeries(req *pb.ReqStrategySeries, rsp *pb.RspStrategySeries) (err error) {
func (svc *TradingService) StrategySeries(ctx context.Context, req *pb.ReqStrategySeries, rsp *pb.RspStrategySeries) (err error) {
// sigStrategy
sigStrategy, ok := svc.strategyReg.NewSigStrategy(req.SigStrategy)
if !ok {
@ -222,54 +258,31 @@ func (svc *TradingService) StrategySeries(req *pb.ReqStrategySeries, rsp *pb.Rsp
return
}
// klineSeries, err1 := svc.klineStore.GetKlineSeires(req.Exchange, req.InstId, interval)
// if err1 != nil {
// err = err1
// return
// }
// recover todo out of range
requiredSeries := int(sigStrategy.RequiredSeries())
sr := req.Series
exchange := sr.Exchange
series := sig.NewKlineSeries(exchange, sr.InstId, interval)
count, totalK := 0, 0
indctx := sig.NewIndicatorContext(series)
_ = indctx
indicatorContext := sig.NewHistoryIndicatorContext(svc.exchangeClient)
req.Series.Window += indicator.MaxWindow
if totalK, err = indicatorContext.Init(req.Series); err != nil {
return
}
count = totalK - indicator.MaxWindow
sr.WindowExtra = uint32(requiredSeries)
kSeries := sig.NewKlineSeries(sr.Exchange, sr.InstId, interval)
indicatorContext := sig.NewIndicatorContext(kSeries)
strategyContext := sig.NewStrategyContext(indicatorContext, svc.indicatorReg)
for i := count - 1; i >= 0; i-- {
strategyContext.SetOffset(int16(i))
err = svc.fetchHistoryKlineSeries(ctx, sr, func(k *types.Kline) (err error) {
if lastTs, serial := kSeries.Update(k); !serial {
err = fmt.Errorf("kline not series: %s(%s), interval=%s, lastTs=%d", sr.InstId, sr.Exchange, interval, lastTs)
return
}
if kSeries.Length() < requiredSeries {
return
}
sigSide := sigStrategy.Update(strategyContext)
if sigSide == types.SideLong || sigSide == types.SideShort {
if sigSide.IsValid() {
side := lang.Ternary(sigSide == types.SideLong, pb.Side_BUY, pb.Side_SELL)
signalK := strategyContext.Get(0)
rsp.Signal = append(rsp.Signal, side)
rsp.Times = append(rsp.Times, signalK.Ts)
{
// todo delete
win := false
if indicatorContext.GetOffset() > 0 {
nextK := indicatorContext.Get(-1)
if side == pb.Side_BUY {
win = nextK.Close.Cmp(signalK.Close) > 0
} else {
win = nextK.Close.Cmp(signalK.Close) < 0
}
}
rsp.Wins = append(rsp.Wins, win)
}
rsp.Times = append(rsp.Times, k.Ts)
}
return
})
if err != nil {
return
}
// 信号点胜率判断
wins := collect.Filter(rsp.Wins, func(_ int, win bool) bool { return win })
rsp.WinRate = float64(len(wins)) / float64(len(rsp.Wins))
return
}

4
pkg/indicator/atr.go

@ -12,6 +12,10 @@ func (c *ATR) Name() string {
return "atr"
}
func (c *ATR) RequiredSeries(window int16) int16 {
return window + 1
}
// Calculate 计算单根k线rsi指标
func (c *ATR) Calculate(ctx IIndicatorContext, window int16) (vector float64) {
klineSeries := ctx.Series(0, int16(window)+1)

4
pkg/indicator/base.go → pkg/indicator/indicator.go

@ -17,7 +17,11 @@ type IIndicator interface {
// IIndicator 窗口指标基础计算接口
type IWindowIndicator interface {
// Name 指标名称
Name() string
// RequiredSeries 计算窗口大小的指标值需要的K线数量
RequiredSeries(window int16) int16
// Calculate 计算窗口大小的指标值
Calculate(ctx IIndicatorContext, window int16) (vector float64)
}

5
pkg/indicator/rsi.go

@ -11,11 +11,14 @@ import (
type RSI struct {
}
// indicator interface
func (c *RSI) Name() string {
return "rsi"
}
func (c *RSI) RequiredSeries(window int16) int16 {
return window
}
// Calculate 计算单根k线rsi指标
func (c *RSI) Calculate(ctx IIndicatorContext, window int16) (vector float64) {
// 读k线, 计算

4
pkg/indicator/sam.go

@ -14,6 +14,10 @@ func (c *SMA) Name() string {
return "sma"
}
func (c *SMA) RequiredSeries(window int16) int16 {
return window
}
// Calculate 计算单根k线sma指标
func (c *SMA) Calculate(ctx IIndicatorContext, window int16) (vector float64) {
closeSeries := ctx.Series(0, window).Close()

4
pkg/strategy/cross_star.go

@ -39,8 +39,8 @@ func (s *CrossStar) Init(param StrategyParam) (err error) { // 校验参数, 并
return
}
func (s *CrossStar) MaxWindow() int {
return int(1)
func (s *CrossStar) RequiredSeries() int16 {
return 1
}
func (s *CrossStar) Update(ctx ISigStrategyContext) (side types.Side) {

4
pkg/strategy/gold_x.go

@ -41,8 +41,8 @@ func (s *GoldX) Init(param StrategyParam) (err error) { // 校验参数, 并根
return
}
func (s *GoldX) MaxWindow() int {
return int(max(s.long, s.short))
func (s *GoldX) RequiredSeries() int16 {
return max(s.long, s.short) + 1
}
func (s *GoldX) Update(ctx ISigStrategyContext) (side types.Side) {

2
pkg/strategy/sig_strategy.go

@ -10,7 +10,7 @@ import (
type ISigStrategy interface {
New() ISigStrategy
Meta() StrategyMeta
MaxWindow() int // 需要的最大数据窗口数, 回测时用, 若不定义则取最大窗口值
RequiredSeries() int16 // 需要的最小数据k线数, 回测时用, 若不定义则取最大窗口值
Init(param StrategyParam) (err error) // 校验参数, 并根据参数初始化策略
Update(ctx ISigStrategyContext) (side types.Side)
}

4
pkg/strategy/super_trend.go

@ -64,8 +64,8 @@ func (s *SupertrendBOSWaves) Init(param StrategyParam) (err error) { // 校验
return
}
func (s *SupertrendBOSWaves) MaxWindow() int {
return int(s.atrLength + 1)
func (s *SupertrendBOSWaves) RequiredSeries() int16 {
return s.atrLength + 1
}
func (s *SupertrendBOSWaves) Update(ctx ISigStrategyContext) (side types.Side) {

14
pkg/trade/account.go

@ -1,14 +0,0 @@
package trade
import (
"sig-pub/api/pb"
"github.com/govalues/decimal"
)
type IAccountHolder interface {
Exchange() pb.ExchangeType
GetValueCash() decimal.Decimal // 获取账户总市值
GetTradeCash() decimal.Decimal // 获取可交易空闲资金
GetOpenTrades() []int64 // 获取未平仓交易单
}

78
pkg/trade/close_strategy.go

@ -1,24 +1,84 @@
package trade
import (
"sig-pub/pkg/types"
"sig-pub/pkg/types/decimals"
)
type ICloseStrategy interface {
OnKline(k types.Kline, pos *Position) (closePos bool, cause Cause)
OnPrice(price float64, pos *Position) (closePos bool, cause Cause)
OnSigStrategySingal(sigSide types.Side, pos *Position) (closePos bool, cause Cause)
}
// 平仓策略参数
type CloseStrategyParam struct {
StopLossPct float64 // 固定止损 static stoploss
TakeProfitPct float64 // 固定止盈 static take profit
TrailMinProfit float64 // 启动移动止损的最小盈利阈值(例如达到 1% 后才开始追踪) minimum profit (fraction) before trailing activates (e.g. 0.01 = 1%);
TrailingPct float64 // 移动止损百分比(例如 0.02 表示从最高价回撤 2% 时触发追踪止损) trailing stop percent (e.g. 0.02 = 2%);
ProfitRetracePct float64 // close when profit retraces more than this fraction of peak profit;基于最高利润回撤触发平仓(例如从最高利润回撤超过 30% 则平仓)。
StopLossPct float64 `json:"stopLossPct"` // 固定止损 static stoploss
TakeProfitPct float64 `json:"takeProfitPct"` // 固定止盈 static take profit
TrailMinProfit float64 `json:"trailMinProfit"` // 启动移动止损的最小盈利阈值(例如达到 1% 后才开始追踪) minimum profit (fraction) before trailing activates (e.g. 0.01 = 1%);
TrailingPct float64 `json:"trailingPct"` // 移动止损百分比(例如 0.02 表示从最高价回撤 2% 时触发追踪止损) trailing stop percent (e.g. 0.02 = 2%);
ProfitRetracePct float64 `json:"profitRetracePct"` // close when profit retraces more than this fraction of peak profit;基于最高利润回撤触发平仓(例如从最高利润回撤超过 30% 则平仓)。
CloseOnSideReverse bool `json:"closeOnSideReverse"` // 交易信号和持单方向相反时是否进行平仓
Fee bool `json:"fee"` // 计算止盈止损时是否包含手续费
}
// CloseStrategy 平仓策略
type CloseStrategy struct {
CloseStrategyParam
}
func NewCloseStrategy(param CloseStrategyParam) {
func NewCloseStrategy(param CloseStrategyParam) *CloseStrategy {
return &CloseStrategy{
CloseStrategyParam: param,
}
}
// Update 当k线更新判断是否关闭仓位
func (s *CloseStrategy) OnKline(k types.Kline, pos *Position) (closePos bool, cause Cause) {
closePrice := decimals.MustToFloat64(k.Close)
// update peak px
if pos.Side == types.SideLong && closePrice > pos.PeakPx {
pos.PeakPx = closePrice
}
if pos.Side == types.SideShort && closePrice < pos.PeakPx {
pos.PeakPx = closePrice
}
return s.OnPrice(closePrice, pos)
}
// OnPriceUpdate 当价格更新判断是否关闭仓位
func (s *CloseStrategy) OnPriceUpdate(price float64, position *string) (closePos bool) {
// OnPrice 当k线更新判断是否关闭仓位
func (s *CloseStrategy) OnPrice(price float64, pos *Position) (closePos bool, cause Cause) {
if !pos.Side.IsValid() {
return
}
// side long:
if pos.Side == types.SideLong {
// 固定止损
if s.StopLossPct > 0 && price <= pos.EntryPx*(1-s.StopLossPct) {
return true, CauseStoploss
}
// 固定止盈
if s.TakeProfitPct > 0 && price >= pos.EntryPx*(1+s.TakeProfitPct) {
return true, CauseTakeprofit
}
// todo dynamic trailing
return
}
// side short:
if s.StopLossPct > 0 && price >= pos.EntryPx*(1+s.StopLossPct) {
return true, CauseStoploss
}
if s.TakeProfitPct > 0 && price <= pos.EntryPx*(1-s.TakeProfitPct) {
return true, CauseTakeprofit
}
return
}
// OnSigStrategySingal 根据策略信号尝试平掉相反方向的仓位。例如策略返回 SELL 时,平掉 BUY 持仓
func (s *CloseStrategy) OnSigStrategySingal(sigSide types.Side, pos *Position) (closePos bool, cause Cause) {
if !s.CloseOnSideReverse {
return
}
return sigSide != pos.Side, CauseStoploss
}

10
pkg/trade/error.go

@ -1,10 +0,0 @@
package trade
type ErrorCode int32
const (
_ ErrorCode = iota
ErrorCodeArgs // 参数有误
ErrorCodeValue // 值有误
ErrorCodeKline // 错误k线
)

40
pkg/trade/trade_account.go

@ -0,0 +1,40 @@
package trade
import (
"sig-pub/pkg/data/entity"
"sig-pub/pkg/types"
"github.com/govalues/decimal"
)
// sig -> close strategy
// sig -> risk strategy -> trade strategy -> tarde account
type ITradeAccount interface {
// 根据当前价格对仓位进行 mark-to-market,返回账户净值
GetCurrentEquity() decimal.Decimal
// 返回当前仓位的名义总敞口(绝对值)
GetCurrentExposure() decimal.Decimal
// 获取可交易空闲资金
GetCash() decimal.Decimal
// 获取未平仓交易单
ListOpenPosition() []*Position
// 获取交易订单
GetTradeOrder(tradeId int64) Trade
// 判断在给定价格下是否可以开仓(基于 MaxPosPct 和 MaxExposurePct)
CanOpen(instId string, side types.Side, qty, price float64) bool
// 直接用市价下单(简化),qty为基础货币数量
ApplyMarketOrder(instId string, side types.Side, qty float64, price float64, ts int64) (t *entity.TradeOrder, ok bool)
// 将仓位进行平仓
ClosePosition()
}
type OkxTradeAccount struct {
ITradeAccount
}

12
pkg/trade/trade_strategy.go

@ -0,0 +1,12 @@
package trade
// ITradeStrategy 下单策略
// 根据购买信号和账户信息生成下单参数
type ITradeStrategy interface {
}
type TradeStrategyParam struct {
}
type TradeStrategy struct {
}

51
pkg/trade/types.go

@ -0,0 +1,51 @@
package trade
import "sig-pub/pkg/types"
// Position 持仓仓位
type Position struct {
TradeId int64 // 交易订单id
Status int32 // 1.交易中 2.持仓中 3.已平仓
Side types.Side // 交易方向
Qty float64 // 交易量
EntryPx float64 // 入场价格
EntryTs int64 // 入场时间
PeakPx float64 // highest (for long) or lowest (for short) observed price since entry
Fee float64 // 手续费
FeeRate float64 // 手续费率
}
type Trade struct {
Id int64 // 交易id
Side types.Side // 交易方向
Qty float64 // 交易量
Price float64 // 开仓价格
Fee float64 // 开仓手续费
Ts int64 // 开仓时间
ClosePrice float64 // 平仓价格
CloseFee float64 // 平仓手续费
CloseTs int64 // 平仓时间
CloseCause string // 平仓原因 ["stoploss", "takeprofit", "trailing", "retrace", "signal"](“止损”、“止盈”、“动态跟踪”、“回撤”、“信号”)
Pnl float64 // 盈利/亏损 pnl = (t.ClosePrice-t.Price)*t.Qty - t.Fee - t.CloseFee
HoldTime string // 持仓时间
}
type Cause int32
const (
_ Cause = iota
CauseStoploss // 固定止损
CauseTakeprofit // 固定止盈
CauseReverseSingal // 策略反向信号
)
func (c Cause) String() string {
switch c {
default:
return ""
case CauseStoploss:
return "stoploss"
case CauseTakeprofit:
return "takeprofit"
}
}
Loading…
Cancel
Save