Initial commit
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 16
|
||||
VisualStudioVersion = 16.0.30011.22
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SmaHmaBot", "SmaHmaBot\SmaHmaBot.csproj", "{7c1a7ee9-4442-4a60-b965-f5dae20e9381}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{7c1a7ee9-4442-4a60-b965-f5dae20e9381}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{7c1a7ee9-4442-4a60-b965-f5dae20e9381}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{7c1a7ee9-4442-4a60-b965-f5dae20e9381}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{7c1a7ee9-4442-4a60-b965-f5dae20e9381}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -0,0 +1,259 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using cAlgo.API;
|
||||
using cAlgo.API.Indicators;
|
||||
using cAlgo.API.Internals;
|
||||
|
||||
namespace cAlgo.Robots
|
||||
{
|
||||
public enum TradingDirection
|
||||
{
|
||||
Both,
|
||||
LongOnly,
|
||||
ShortOnly
|
||||
}
|
||||
|
||||
public enum PendingSignalState
|
||||
{
|
||||
None,
|
||||
WaitingForBuyEntry,
|
||||
WaitingForSellEntry
|
||||
}
|
||||
|
||||
// Continuous Trend-State Bot with risk management and SMA distance exit.
|
||||
[Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.FullAccess)]
|
||||
public class SmaHmaBot : Robot
|
||||
{
|
||||
#region Parameters
|
||||
|
||||
[Parameter("Risk Percentage (%)", DefaultValue = 2.0, MinValue = 0.1, MaxValue = 10.0)]
|
||||
public double RiskPercentage { get; set; }
|
||||
|
||||
[Parameter("Min SL Distance (Pips)", DefaultValue = 40, MinValue = 1)]
|
||||
public double MinStopLossPips { get; set; }
|
||||
|
||||
[Parameter("Max SMA Distance (Pips)", DefaultValue = 100, MinValue = 1)]
|
||||
public double MaxSmaDistancePips { get; set; }
|
||||
|
||||
[Parameter("SMA Period", DefaultValue = 200, MinValue = 1)]
|
||||
public int SmaPeriod { get; set; }
|
||||
|
||||
[Parameter("HMA Period", DefaultValue = 250, MinValue = 1)]
|
||||
public int HmaPeriod { get; set; }
|
||||
|
||||
[Parameter("Trading Direction", DefaultValue = TradingDirection.Both)]
|
||||
public TradingDirection AllowedDirection { get; set; }
|
||||
|
||||
[Parameter("BE Offset (Pips)", DefaultValue = 0.5, MinValue = 0)]
|
||||
public double BreakEvenOffsetPips { get; set; }
|
||||
|
||||
[Parameter("Telegram Bot Token", DefaultValue = "")]
|
||||
public string TelegramBotToken { get; set; }
|
||||
|
||||
[Parameter("Telegram Chat ID", DefaultValue = "")]
|
||||
public string TelegramChatId { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Fields
|
||||
|
||||
private SimpleMovingAverage _sma;
|
||||
private HullMovingAverage _hma;
|
||||
private HttpClient _httpClient;
|
||||
|
||||
private PendingSignalState _signalState = PendingSignalState.None;
|
||||
private double _extremumSinceSignal;
|
||||
private const string PositionLabel = "SmaHmaContinuousBot";
|
||||
|
||||
#endregion
|
||||
|
||||
protected override void OnStart()
|
||||
{
|
||||
_sma = Indicators.SimpleMovingAverage(Bars.ClosePrices, SmaPeriod);
|
||||
_hma = Indicators.HullMovingAverage(Bars.ClosePrices, HmaPeriod);
|
||||
_httpClient = new HttpClient();
|
||||
|
||||
Print("Bot started. Max SMA Distance: {0} pips", MaxSmaDistancePips);
|
||||
}
|
||||
|
||||
protected override void OnStop()
|
||||
{
|
||||
_httpClient?.Dispose();
|
||||
}
|
||||
|
||||
protected override void OnTick()
|
||||
{
|
||||
ManageBreakEven();
|
||||
ManageProfitTaking();
|
||||
}
|
||||
|
||||
protected override void OnBarClosed()
|
||||
{
|
||||
if (Bars.Count <= Math.Max(SmaPeriod, HmaPeriod) + 2) return;
|
||||
|
||||
UpdateTrendAndSignalState();
|
||||
HandleEntryLogic();
|
||||
}
|
||||
|
||||
private void UpdateTrendAndSignalState()
|
||||
{
|
||||
double smaClosed = _sma.Result.Last(0);
|
||||
double smaPrev = _sma.Result.Last(1);
|
||||
double hmaClosed = _hma.Result.Last(0);
|
||||
|
||||
bool isSmaRising = (smaClosed > smaPrev);
|
||||
bool isSmaFalling = (smaClosed < smaPrev);
|
||||
bool isHmaAboveSma = (hmaClosed > smaClosed);
|
||||
bool isHmaBelowSma = (hmaClosed < smaClosed);
|
||||
|
||||
var activePosition = Positions.Find(PositionLabel, SymbolName);
|
||||
|
||||
if ((activePosition != null))
|
||||
{
|
||||
if (((activePosition.TradeType == TradeType.Buy) && (isHmaBelowSma)) ||
|
||||
((activePosition.TradeType == TradeType.Sell) && (isHmaAboveSma)))
|
||||
{
|
||||
ClosePosition(activePosition);
|
||||
_signalState = PendingSignalState.None;
|
||||
}
|
||||
}
|
||||
|
||||
if (activePosition == null)
|
||||
{
|
||||
if ((AllowedDirection != TradingDirection.ShortOnly) && (isHmaAboveSma) && (isSmaRising))
|
||||
{
|
||||
if (_signalState != PendingSignalState.WaitingForBuyEntry)
|
||||
{
|
||||
_signalState = PendingSignalState.WaitingForBuyEntry;
|
||||
_extremumSinceSignal = Bars.LowPrices.Last(0);
|
||||
}
|
||||
}
|
||||
else if ((AllowedDirection != TradingDirection.LongOnly) && (isHmaBelowSma) && (isSmaFalling))
|
||||
{
|
||||
if (_signalState != PendingSignalState.WaitingForSellEntry)
|
||||
{
|
||||
_signalState = PendingSignalState.WaitingForSellEntry;
|
||||
_extremumSinceSignal = Bars.HighPrices.Last(0);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_signalState = PendingSignalState.None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleEntryLogic()
|
||||
{
|
||||
if (_signalState == PendingSignalState.None) return;
|
||||
|
||||
double currentClose = Bars.ClosePrices.Last(0);
|
||||
double smaValue = _sma.Result.Last(0);
|
||||
|
||||
if (_signalState == PendingSignalState.WaitingForBuyEntry)
|
||||
{
|
||||
_extremumSinceSignal = Math.Min(_extremumSinceSignal, Bars.LowPrices.Last(0));
|
||||
if ((Bars.HighPrices.Last(2) < Bars.LowPrices.Last(0)) && (currentClose <= smaValue))
|
||||
{
|
||||
ExecuteEntry(TradeType.Buy, _extremumSinceSignal);
|
||||
}
|
||||
}
|
||||
else if (_signalState == PendingSignalState.WaitingForSellEntry)
|
||||
{
|
||||
_extremumSinceSignal = Math.Max(_extremumSinceSignal, Bars.HighPrices.Last(0));
|
||||
if ((Bars.LowPrices.Last(2) > Bars.HighPrices.Last(0)) && (currentClose >= smaValue))
|
||||
{
|
||||
ExecuteEntry(TradeType.Sell, _extremumSinceSignal);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ExecuteEntry(TradeType type, double stopLossPrice)
|
||||
{
|
||||
double entryPrice = (type == TradeType.Buy) ? Symbol.Ask : Symbol.Bid;
|
||||
double slDistPips = Math.Abs(entryPrice - stopLossPrice) / Symbol.PipSize;
|
||||
|
||||
if (slDistPips < MinStopLossPips)
|
||||
{
|
||||
slDistPips = MinStopLossPips;
|
||||
stopLossPrice = (type == TradeType.Buy)
|
||||
? (entryPrice - MinStopLossPips * Symbol.PipSize)
|
||||
: (entryPrice + MinStopLossPips * Symbol.PipSize);
|
||||
}
|
||||
|
||||
double riskAmount = Account.Balance * (RiskPercentage / 100.0);
|
||||
double volume = Symbol.VolumeForFixedRisk(riskAmount, slDistPips);
|
||||
volume = Symbol.NormalizeVolumeInUnits(volume, RoundingMode.ToNearest);
|
||||
|
||||
if (volume < Symbol.VolumeInUnitsMin) return;
|
||||
|
||||
_signalState = PendingSignalState.None;
|
||||
var result = ExecuteMarketOrder(type, SymbolName, volume, PositionLabel, null, null);
|
||||
|
||||
if (result.IsSuccessful)
|
||||
{
|
||||
ModifyPosition(result.Position, stopLossPrice, null, ProtectionType.Absolute);
|
||||
TriggerNotification(type.ToString().ToUpper() + " ENTRY", $"Vol: {volume}, SL: {stopLossPrice}");
|
||||
}
|
||||
}
|
||||
|
||||
private void ManageProfitTaking()
|
||||
{
|
||||
foreach (var position in Positions.Where(p => p.Label == PositionLabel))
|
||||
{
|
||||
double smaValue = _sma.Result.Last(0);
|
||||
double currentPrice = (position.TradeType == TradeType.Buy) ? Symbol.Bid : Symbol.Ask;
|
||||
double smaDistPips = Math.Abs(currentPrice - smaValue) / Symbol.PipSize;
|
||||
|
||||
// Close if position in profit AND too far from SMA
|
||||
if ((position.GrossProfit > 0) && (smaDistPips > MaxSmaDistancePips))
|
||||
{
|
||||
ClosePosition(position);
|
||||
TriggerNotification("TAKE PROFIT", $"Price far from SMA ({smaDistPips:F1} pips)");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ManageBreakEven()
|
||||
{
|
||||
foreach (var position in Positions.Where(p => p.Label == PositionLabel))
|
||||
{
|
||||
if (position.StopLoss == null) continue;
|
||||
|
||||
double initialRisk = Math.Abs(position.EntryPrice - position.StopLoss.Value);
|
||||
double currentProfitDist = (position.TradeType == TradeType.Buy) ? (Symbol.Bid - position.EntryPrice) : (position.EntryPrice - Symbol.Ask);
|
||||
|
||||
if (currentProfitDist > initialRisk)
|
||||
{
|
||||
double targetSl = (position.TradeType == TradeType.Buy)
|
||||
? (position.EntryPrice + BreakEvenOffsetPips * Symbol.PipSize)
|
||||
: (position.EntryPrice - BreakEvenOffsetPips * Symbol.PipSize);
|
||||
|
||||
bool isAlreadyBE = (position.TradeType == TradeType.Buy) ? (position.StopLoss >= targetSl) : (position.StopLoss <= targetSl);
|
||||
|
||||
if (!isAlreadyBE) ModifyPosition(position, targetSl, null, ProtectionType.Absolute);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void TriggerNotification(string type, string reason)
|
||||
{
|
||||
if (IsBacktesting) return;
|
||||
string msg = $"<b>{Symbol.Name}</b>\n{type}\n{reason}";
|
||||
Task.Run(() => SendTelegramMessageAsync(msg));
|
||||
}
|
||||
|
||||
private async Task SendTelegramMessageAsync(string message)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(TelegramBotToken)) return;
|
||||
try
|
||||
{
|
||||
string url = $"https://api.telegram.org/bot{TelegramBotToken}/sendMessage?chat_id={TelegramChatId}&text={Uri.EscapeDataString(message)}&parse_mode=HTML";
|
||||
await _httpClient.GetAsync(url);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="cTrader.Automate" Version="*" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
Reference in New Issue
Block a user