Initial commit

This commit is contained in:
Michael Schimmel
2026-01-28 09:10:52 +01:00
commit a1d2e96f8a
513 changed files with 19503 additions and 0 deletions
+22
View File
@@ -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>