You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
134 lines
4.4 KiB
134 lines
4.4 KiB
package trade |
|
|
|
import ( |
|
"fmt" |
|
"sig-pub/pkg/types" |
|
"sig-pub/pkg/types/decimals" |
|
) |
|
|
|
// Exit 止盈止损策略(trading service 管理) |
|
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 `json:"stopLossPct"` // 固定止损 static stoploss |
|
TakeProfitPct float64 `json:"takeProfitPct"` // 固定止盈 static take profit |
|
ProfitRetracePcts [][]float64 `json:"profitRetracePcts"` // 基于最高利润回撤触发平仓 (例如 [[0.01, 0.3], [0.02, 0.2]] 最高利润超过1%时30%回撤则触发平仓,最高利润超过2%时20%回撤就触发平仓) |
|
CloseOnSideReverse bool `json:"closeOnSideReverse"` // 交易信号和持单方向相反时是否进行平仓 |
|
Fee bool `json:"fee"` // 计算止盈止损时是否包含手续费 |
|
} |
|
|
|
// CloseStrategy 平仓策略 |
|
type CloseStrategy struct { |
|
CloseStrategyParam |
|
} |
|
|
|
func NewCloseStrategy(param CloseStrategyParam) (cs *CloseStrategy, err error) { |
|
if param.StopLossPct < 0 { |
|
err = fmt.Errorf("stopLossPct can't less zero") |
|
return |
|
} |
|
cs = &CloseStrategy{CloseStrategyParam: param} |
|
return |
|
} |
|
|
|
// Update 当k线更新判断是否关闭仓位 |
|
func (s *CloseStrategy) OnKline(k types.Kline, pos *Position) (closePos bool, cause Cause) { |
|
closePrice := decimals.MustToFloat64(k.Close) |
|
return s.OnPrice(closePrice, pos) |
|
} |
|
|
|
// OnPrice 当k线更新判断是否关闭仓位 |
|
func (s *CloseStrategy) OnPrice(price float64, pos *Position) (closePos bool, cause Cause) { |
|
if !pos.Side.IsValid() { |
|
return |
|
} |
|
// update peak px |
|
if pos.Side == types.SideLong && (price > pos.PeakPx) { |
|
pos.PeakPx = price |
|
} |
|
if pos.Side == types.SideShort && (price < pos.PeakPx) { |
|
pos.PeakPx = price |
|
} |
|
|
|
entry := pos.EntryPx |
|
// side long: |
|
if pos.Side == types.SideLong { |
|
// 固定止损 |
|
if s.StopLossPct > 0 && price <= entry*(1-s.StopLossPct) { |
|
return true, CauseCloseStoploss |
|
} |
|
// 固定止盈 |
|
if s.TakeProfitPct > 0 && price >= entry*(1+s.TakeProfitPct) { |
|
return true, CauseCloseTakeprofit |
|
} |
|
// 基于最高利润动态止盈 |
|
if len(s.ProfitRetracePcts) > 0 { |
|
// peak profit fraction |
|
peakProfit := (pos.PeakPx - entry) / entry |
|
minProfitToTrail, trailingPct := float64(0), float64(0) |
|
for _, profit := range s.ProfitRetracePcts { |
|
if len(profit) != 2 { |
|
continue |
|
} |
|
_minProfitToTrail := profit[0] // 启动最高利润回撤的最小盈利阈值 |
|
_trailingPct := profit[1] // 基于最高利润回撤触发平仓 |
|
if peakProfit >= _minProfitToTrail && _minProfitToTrail > minProfitToTrail { |
|
minProfitToTrail = _minProfitToTrail |
|
trailingPct = _trailingPct |
|
} |
|
} |
|
if minProfitToTrail > 0 && trailingPct > 0 { |
|
trail := entry + (pos.PeakPx-entry)*(1-trailingPct) |
|
if price <= trail { |
|
return true, CauseCloseTrailing |
|
} |
|
} |
|
} |
|
return |
|
} |
|
|
|
// side short: |
|
if s.StopLossPct > 0 && price >= pos.EntryPx*(1+s.StopLossPct) { |
|
return true, CauseCloseStoploss |
|
} |
|
if s.TakeProfitPct > 0 && price <= pos.EntryPx*(1-s.TakeProfitPct) { |
|
return true, CauseCloseTakeprofit |
|
} |
|
// 基于最高利润动态止盈 |
|
if len(s.ProfitRetracePcts) > 0 { |
|
// peak profit fraction |
|
peakProfit := (entry - pos.PeakPx) / entry |
|
minProfitToTrail, trailingPct := float64(0), float64(0) |
|
for _, profit := range s.ProfitRetracePcts { |
|
if len(profit) != 2 { |
|
continue |
|
} |
|
_minProfitToTrail := profit[0] // 启动最高利润回撤的最小盈利阈值 |
|
_trailingPct := profit[1] // 基于最高利润回撤触发平仓 |
|
if peakProfit >= _minProfitToTrail && _minProfitToTrail >= minProfitToTrail { |
|
minProfitToTrail = _minProfitToTrail |
|
trailingPct = _trailingPct |
|
} |
|
} |
|
if minProfitToTrail > 0 && trailingPct > 0 { |
|
trail := entry - (entry-pos.PeakPx)*(1+trailingPct) |
|
if price >= trail { |
|
return true, CauseCloseTrailing |
|
} |
|
} |
|
} |
|
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, CauseCloseReverseSingal |
|
}
|
|
|