JK BullP AutoTrader
JK BullP AutoTrader 是原始 MetaTrader 智能交易系统的移植版本,依赖 Bulls Power 振荡指标。策略比较连续两个 Bulls Power 值:当两根柱子都在零线上方且最新一根低于更早一根时,视为多头动能减弱并做空;当上一根 Bulls Power 跌破零线时做多。多空仓位均使用固定止盈、固定止损以及随盈利逐步收紧的移动止损进行保护。
详情
- 入场条件:当前一根 Bulls Power 高于零且两根前的值更高时做空;当上一根 Bulls Power 低于零时做多。
- 多空方向:双向。
- 出场条件:触发固定止盈、固定止损或移动止损;出现相反信号时反手。
- 止损类型:固定止盈、固定止损、移动止损。
- 默认参数:
BullsPeriod= 13TakeProfitPoints= 350StopLossPoints= 100TrailingStopPoints= 100TrailingStepPoints= 40CandleType= TimeSpan.FromHours(1)
- 筛选:
- 类别:振荡指标
- 方向:双向
- 指标:Bulls Power
- 止损:固定 + 移动
- 复杂度:基础
- 周期:日内/波段(1 小时)
- 季节性:无
- 神经网络:无
- 背离:无
- 风险等级:中等
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Logging;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Port of the JK BullP AutoTrader strategy that trades using the Bulls Power indicator.
/// Sells when Bulls Power weakens above zero and buys when it drops below zero with trailing risk control.
/// </summary>
public class JkBullPowerAutoTraderStrategy : Strategy
{
private readonly StrategyParam<int> _bullsPeriod;
private readonly StrategyParam<decimal> _takeProfitPoints;
private readonly StrategyParam<decimal> _stopLossPoints;
private readonly StrategyParam<decimal> _trailingStopPoints;
private readonly StrategyParam<decimal> _trailingStepPoints;
private readonly StrategyParam<DataType> _candleType;
private BullPower _bullsPower = null!;
private decimal? _prevBulls;
private decimal? _prevPrevBulls;
private decimal? _stopPrice;
private decimal? _takeProfitPrice;
private decimal _entryPrice;
private decimal _priceStep;
/// <summary>
/// Bulls Power indicator length.
/// </summary>
public int BullsPeriod
{
get => _bullsPeriod.Value;
set => _bullsPeriod.Value = value;
}
/// <summary>
/// Take profit distance in price steps.
/// </summary>
public decimal TakeProfitPoints
{
get => _takeProfitPoints.Value;
set => _takeProfitPoints.Value = value;
}
/// <summary>
/// Stop loss distance in price steps.
/// </summary>
public decimal StopLossPoints
{
get => _stopLossPoints.Value;
set => _stopLossPoints.Value = value;
}
/// <summary>
/// Trailing stop activation distance in price steps.
/// </summary>
public decimal TrailingStopPoints
{
get => _trailingStopPoints.Value;
set => _trailingStopPoints.Value = value;
}
/// <summary>
/// Trailing stop step in price steps.
/// </summary>
public decimal TrailingStepPoints
{
get => _trailingStepPoints.Value;
set => _trailingStepPoints.Value = value;
}
/// <summary>
/// Candle type used by the strategy.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes a new instance of the <see cref="JkBullPowerAutoTraderStrategy"/> class.
/// </summary>
public JkBullPowerAutoTraderStrategy()
{
_bullsPeriod = Param(nameof(BullsPeriod), 13)
.SetGreaterThanZero()
.SetDisplay("Bulls Power Period", "Length for Bulls Power indicator", "Indicators")
.SetOptimize(5, 30, 1);
_takeProfitPoints = Param(nameof(TakeProfitPoints), 350m)
.SetNotNegative()
.SetDisplay("Take Profit (pts)", "Take profit distance in price steps", "Risk")
.SetOptimize(50m, 600m, 50m);
_stopLossPoints = Param(nameof(StopLossPoints), 100m)
.SetNotNegative()
.SetDisplay("Stop Loss (pts)", "Stop loss distance in price steps", "Risk")
.SetOptimize(50m, 300m, 25m);
_trailingStopPoints = Param(nameof(TrailingStopPoints), 100m)
.SetNotNegative()
.SetDisplay("Trailing Stop (pts)", "Profit distance that activates trailing", "Risk");
_trailingStepPoints = Param(nameof(TrailingStepPoints), 40m)
.SetNotNegative()
.SetDisplay("Trailing Step (pts)", "Minimal trailing increment", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Type of candles", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevBulls = null;
_prevPrevBulls = null;
_stopPrice = null;
_takeProfitPrice = null;
_entryPrice = 0m;
_priceStep = 0m;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
if (TrailingStopPoints > 0m && TrailingStopPoints <= TrailingStepPoints)
{
this.AddErrorLog("Trailing stop must be greater than trailing step.");
Stop();
return;
}
_priceStep = Security?.PriceStep ?? 1m;
if (_priceStep <= 0m)
_priceStep = 1m;
_bullsPower = new BullPower
{
Length = BullsPeriod
};
_prevBulls = null;
_prevPrevBulls = null;
_stopPrice = null;
_takeProfitPrice = null;
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_bullsPower, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _bullsPower);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal bullsValue)
{
if (candle.State != CandleStates.Finished)
return;
UpdateTrailing(candle);
if (!IsFormedAndOnlineAndAllowTrading())
{
UpdateHistory(bullsValue);
return;
}
CheckRisk(candle);
if (!_bullsPower.IsFormed)
{
UpdateHistory(bullsValue);
return;
}
if (_prevBulls is not decimal prevBulls || _prevPrevBulls is not decimal prevPrevBulls)
{
UpdateHistory(bullsValue);
return;
}
var sellSignal = prevPrevBulls > prevBulls && prevBulls > 0m && bullsValue < prevBulls;
var buySignal = prevPrevBulls < prevBulls && prevBulls < 0m && bullsValue > prevBulls;
if (sellSignal && Position >= 0)
{
// Close any existing long position and establish a short.
var volume = Volume + (Position > 0 ? Position : 0m);
if (volume > 0)
{
SellMarket(volume);
InitializeTargets(false, candle.ClosePrice);
}
}
else if (buySignal && Position <= 0)
{
// Close any existing short position and establish a long.
var volume = Volume + (Position < 0 ? Math.Abs(Position) : 0m);
if (volume > 0)
{
BuyMarket(volume);
InitializeTargets(true, candle.ClosePrice);
}
}
UpdateHistory(bullsValue);
}
private void UpdateTrailing(ICandleMessage candle)
{
if (Position == 0 || TrailingStopPoints <= 0m)
return;
var trailingDistance = TrailingStopPoints * _priceStep;
if (trailingDistance <= 0m)
return;
var trailingStep = TrailingStepPoints * _priceStep;
if (Position > 0)
{
// Update trailing stop for long positions when profit exceeds the trigger distance.
var profit = candle.ClosePrice - _entryPrice;
if (profit <= trailingDistance)
return;
var candidate = candle.ClosePrice - trailingDistance;
if (!_stopPrice.HasValue || candidate > _stopPrice.Value && (trailingStep <= 0m || candidate - _stopPrice.Value >= trailingStep))
_stopPrice = candidate;
}
else
{
// Update trailing stop for short positions when profit exceeds the trigger distance.
var profit = _entryPrice - candle.ClosePrice;
if (profit <= trailingDistance)
return;
var candidate = candle.ClosePrice + trailingDistance;
if (!_stopPrice.HasValue || candidate < _stopPrice.Value && (trailingStep <= 0m || _stopPrice.Value - candidate >= trailingStep))
_stopPrice = candidate;
}
}
private void CheckRisk(ICandleMessage candle)
{
if (Position > 0)
{
// Manage long exits by stop loss, trailing stop, or take profit.
if (_stopPrice.HasValue && candle.LowPrice <= _stopPrice.Value)
{
SellMarket(Position);
ResetTargets();
return;
}
if (_takeProfitPrice.HasValue && candle.HighPrice >= _takeProfitPrice.Value)
{
SellMarket(Position);
ResetTargets();
}
}
else if (Position < 0)
{
// Manage short exits by stop loss, trailing stop, or take profit.
if (_stopPrice.HasValue && candle.HighPrice >= _stopPrice.Value)
{
BuyMarket(-Position);
ResetTargets();
return;
}
if (_takeProfitPrice.HasValue && candle.LowPrice <= _takeProfitPrice.Value)
{
BuyMarket(-Position);
ResetTargets();
}
}
else
{
ResetTargets();
}
}
private void InitializeTargets(bool isLong, decimal entryPrice)
{
_entryPrice = entryPrice;
var stopDistance = StopLossPoints * _priceStep;
var takeDistance = TakeProfitPoints * _priceStep;
_stopPrice = stopDistance > 0m
? isLong ? entryPrice - stopDistance : entryPrice + stopDistance
: null;
_takeProfitPrice = takeDistance > 0m
? isLong ? entryPrice + takeDistance : entryPrice - takeDistance
: null;
}
private void ResetTargets()
{
_stopPrice = null;
_takeProfitPrice = null;
}
private void UpdateHistory(decimal bullsValue)
{
_prevPrevBulls = _prevBulls;
_prevBulls = bullsValue;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import BullPower
from StockSharp.Algo.Strategies import Strategy
class jk_bull_power_auto_trader_strategy(Strategy):
def __init__(self):
super(jk_bull_power_auto_trader_strategy, self).__init__()
self._bulls_period = self.Param("BullsPeriod", 13)
self._tp_points = self.Param("TakeProfitPoints", 350.0)
self._sl_points = self.Param("StopLossPoints", 100.0)
self._trailing_stop_points = self.Param("TrailingStopPoints", 100.0)
self._trailing_step_points = self.Param("TrailingStepPoints", 40.0)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1)))
self._prev_bulls = None
self._prev_prev_bulls = None
self._entry_price = 0.0
self._stop_price = None
self._tp_price = None
self._price_step = 1.0
self._bulls_power = None
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
def OnReseted(self):
super(jk_bull_power_auto_trader_strategy, self).OnReseted()
self._prev_bulls = None
self._prev_prev_bulls = None
self._stop_price = None
self._tp_price = None
self._entry_price = 0.0
self._price_step = 1.0
def OnStarted2(self, time):
super(jk_bull_power_auto_trader_strategy, self).OnStarted2(time)
sec = self.Security
self._price_step = float(sec.PriceStep) if sec is not None and sec.PriceStep is not None else 1.0
if self._price_step <= 0:
self._price_step = 1.0
self._prev_bulls = None
self._prev_prev_bulls = None
self._stop_price = None
self._tp_price = None
self._bulls_power = BullPower()
self._bulls_power.Length = self._bulls_period.Value
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._bulls_power, self._process_candle).Start()
def _process_candle(self, candle, bulls_val):
if candle.State != CandleStates.Finished:
return
bv = float(bulls_val)
self._update_trailing(candle)
if not self.IsFormedAndOnlineAndAllowTrading():
self._update_history(bv)
return
self._check_risk(candle)
if not self._bulls_power.IsFormed:
self._update_history(bv)
return
if self._prev_bulls is None or self._prev_prev_bulls is None:
self._update_history(bv)
return
sell_signal = self._prev_prev_bulls > self._prev_bulls and self._prev_bulls > 0 and bv < self._prev_bulls
buy_signal = self._prev_prev_bulls < self._prev_bulls and self._prev_bulls < 0 and bv > self._prev_bulls
pos = float(self.Position)
vol = float(self.Volume)
if sell_signal and pos >= 0:
volume = vol + (pos if pos > 0 else 0.0)
if volume > 0:
self.SellMarket(volume)
self._init_targets(False, float(candle.ClosePrice))
elif buy_signal and pos <= 0:
volume = vol + (abs(pos) if pos < 0 else 0.0)
if volume > 0:
self.BuyMarket(volume)
self._init_targets(True, float(candle.ClosePrice))
self._update_history(bv)
def _update_trailing(self, candle):
pos = float(self.Position)
if pos == 0 or float(self._trailing_stop_points.Value) <= 0:
return
trailing_distance = float(self._trailing_stop_points.Value) * self._price_step
if trailing_distance <= 0:
return
trailing_step = float(self._trailing_step_points.Value) * self._price_step
close = float(candle.ClosePrice)
if pos > 0:
profit = close - self._entry_price
if profit <= trailing_distance:
return
candidate = close - trailing_distance
if self._stop_price is None or (candidate > self._stop_price and (trailing_step <= 0 or candidate - self._stop_price >= trailing_step)):
self._stop_price = candidate
else:
profit = self._entry_price - close
if profit <= trailing_distance:
return
candidate = close + trailing_distance
if self._stop_price is None or (candidate < self._stop_price and (trailing_step <= 0 or self._stop_price - candidate >= trailing_step)):
self._stop_price = candidate
def _check_risk(self, candle):
pos = float(self.Position)
if pos > 0:
if self._stop_price is not None and float(candle.LowPrice) <= self._stop_price:
self.SellMarket(pos)
self._reset_targets()
return
if self._tp_price is not None and float(candle.HighPrice) >= self._tp_price:
self.SellMarket(pos)
self._reset_targets()
elif pos < 0:
if self._stop_price is not None and float(candle.HighPrice) >= self._stop_price:
self.BuyMarket(abs(pos))
self._reset_targets()
return
if self._tp_price is not None and float(candle.LowPrice) <= self._tp_price:
self.BuyMarket(abs(pos))
self._reset_targets()
else:
self._reset_targets()
def _init_targets(self, is_long, entry_price):
self._entry_price = entry_price
stop_dist = float(self._sl_points.Value) * self._price_step
take_dist = float(self._tp_points.Value) * self._price_step
if stop_dist > 0:
self._stop_price = entry_price - stop_dist if is_long else entry_price + stop_dist
else:
self._stop_price = None
if take_dist > 0:
self._tp_price = entry_price + take_dist if is_long else entry_price - take_dist
else:
self._tp_price = None
def _reset_targets(self):
self._stop_price = None
self._tp_price = None
def _update_history(self, bv):
self._prev_prev_bulls = self._prev_bulls
self._prev_bulls = bv
def CreateClone(self):
return jk_bull_power_auto_trader_strategy()