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
@@ -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}") = "MultiSmaHmaNotify", "MultiSmaHmaNotify\MultiSmaHmaNotify.csproj", "{e0b9776b-46e4-48d5-91c2-ab7e4bde2f14}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{e0b9776b-46e4-48d5-91c2-ab7e4bde2f14}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{e0b9776b-46e4-48d5-91c2-ab7e4bde2f14}.Debug|Any CPU.Build.0 = Debug|Any CPU
{e0b9776b-46e4-48d5-91c2-ab7e4bde2f14}.Release|Any CPU.ActiveCfg = Release|Any CPU
{e0b9776b-46e4-48d5-91c2-ab7e4bde2f14}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal
@@ -0,0 +1,305 @@
using System;
using System.Collections.Generic;
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
}
[Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.FullAccess)]
public class MultiSmaHmaNotify : Robot
{
#region Parameters
[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("Sound File Path", DefaultValue = @"C:\Windows\Media\Alarm05.wav")]
public string SoundFilePath { get; set; }
[Parameter("Telegram Bot Token", DefaultValue = "8569913524:AAE9RGsvkBPa0yhTFCKBjVeST0fuzdOx5w0")]
public string TelegramBotToken { get; set; }
[Parameter("Telegram Chat ID", DefaultValue = "5171721381")]
public string TelegramChatId { get; set; }
[Parameter("Symbols (CSV)", DefaultValue = "", Group = "Multi Symbol")]
public string SymbolsCsv { get; set; }
#endregion
#region Fields
private HttpClient _httpClient;
private List<SymbolMonitor> _monitors;
#endregion
protected override void OnStart()
{
_httpClient = new HttpClient();
_monitors = new List<SymbolMonitor>();
// Parse Symbols
var symbolNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
if (!string.IsNullOrWhiteSpace(SymbolsCsv))
{
var split = SymbolsCsv.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
foreach (var s in split)
{
symbolNames.Add(s.Trim());
}
}
else
{
// Fallback: Use current symbol if list is empty
symbolNames.Add(SymbolName);
}
foreach (var name in symbolNames)
{
InitializeSymbolMonitor(name);
}
}
private void InitializeSymbolMonitor(string symbolName)
{
Symbol symbol = Symbols.GetSymbol(symbolName);
if (symbol == null)
{
Print($"Error: Symbol '{symbolName}' not found.");
return;
}
// Get Bars for the requested symbol and timeframe matches the instance timeframe
Bars bars = MarketData.GetBars(TimeFrame, symbolName);
// Create Context
var monitor = new SymbolMonitor(this, symbol, bars);
_monitors.Add(monitor);
// Subscribe to BarOpened event (triggers when a bar closes and a new one opens)
bars.BarOpened += (args) => monitor.OnBarOpened();
// Load enough history to calculate indicators immediately
int maxPeriod = Math.Max(SmaPeriod, HmaPeriod);
if (bars.Count < maxPeriod)
{
bars.LoadMoreHistory();
}
}
protected override void OnStop()
{
_httpClient?.Dispose();
}
// Expose helper methods for the inner class
public void Log(string message) => Print(message);
public void PlaySound()
{
try
{
if (!string.IsNullOrWhiteSpace(SoundFilePath))
{
Notifications.PlaySound(SoundFilePath);
}
}
catch (Exception ex)
{
Print("Error playing sound: {0}", ex.Message);
}
}
public void SendTelegram(string message)
{
Task.Run(() => SendTelegramMessageAsync(message));
}
private async Task SendTelegramMessageAsync(string message)
{
if (string.IsNullOrWhiteSpace(TelegramBotToken) || string.IsNullOrWhiteSpace(TelegramChatId))
return;
try
{
string url = $"https://api.telegram.org/bot{TelegramBotToken}/sendMessage?chat_id={TelegramChatId}&text={Uri.EscapeDataString(message)}&parse_mode=HTML";
HttpResponseMessage response = await _httpClient.GetAsync(url);
if (!response.IsSuccessStatusCode)
Print("Telegram Error: {0}", response.StatusCode);
}
catch (Exception ex)
{
Print("Exception sending Telegram message: {0}", ex.Message);
}
}
/// <summary>
/// Inner class to handle state per symbol
/// </summary>
private class SymbolMonitor
{
private readonly MultiSmaHmaNotify _bot;
private readonly Symbol _symbol;
private readonly Bars _bars;
private readonly SimpleMovingAverage _sma;
private readonly HullMovingAverage _hma;
private bool _waitingForSmaRise;
private bool _waitingForSmaFall;
public SymbolMonitor(MultiSmaHmaNotify bot, Symbol symbol, Bars bars)
{
_bot = bot;
_symbol = symbol;
_bars = bars;
// Create indicators based on the specific bars series
_sma = _bot.Indicators.SimpleMovingAverage(_bars.ClosePrices, _bot.SmaPeriod);
_hma = _bot.Indicators.HullMovingAverage(_bars.ClosePrices, _bot.HmaPeriod);
}
public void OnBarOpened()
{
// Ensure we have enough data
if (_bars.Count <= Math.Max(_bot.SmaPeriod, _bot.HmaPeriod) + 1) return;
// Index 1 is the last closed bar, Index 2 is the one before that
double smaClosed = _sma.Result.Last(1);
double smaPrev = _sma.Result.Last(2);
double hmaClosed = _hma.Result.Last(1);
double hmaPrev = _hma.Result.Last(2);
bool isSmaRising = smaClosed > smaPrev;
bool isSmaFalling = smaClosed < smaPrev;
bool isCrossUp = (hmaPrev <= smaPrev) && (hmaClosed > smaClosed);
bool isCrossDown = (hmaPrev >= smaPrev) && (hmaClosed < smaClosed);
bool isHmaAbove = hmaClosed > smaClosed;
bool isHmaBelow = hmaClosed < smaClosed;
// Check Long Logic
if (_bot.AllowedDirection == TradingDirection.Both || _bot.AllowedDirection == TradingDirection.LongOnly)
{
if (isCrossUp)
{
_waitingForSmaFall = false;
if (isSmaRising)
{
TriggerSignal("BUY", "SMA Rising + HMA CrossUp (Immediate)");
_waitingForSmaRise = false;
}
else
{
_waitingForSmaRise = true;
}
}
else if (_waitingForSmaRise)
{
if (!isHmaAbove)
{
_waitingForSmaRise = false; // Invalidated
}
else if (isSmaRising)
{
TriggerSignal("BUY", "SMA Started Rising after HMA CrossUp (Delayed)");
_waitingForSmaRise = false;
}
}
}
else
{
_waitingForSmaRise = false;
}
// Check Short Logic
if (_bot.AllowedDirection == TradingDirection.Both || _bot.AllowedDirection == TradingDirection.ShortOnly)
{
if (isCrossDown)
{
_waitingForSmaRise = false;
if (isSmaFalling)
{
TriggerSignal("SELL", "SMA Falling + HMA CrossDown (Immediate)");
_waitingForSmaFall = false;
}
else
{
_waitingForSmaFall = true;
}
}
else if (_waitingForSmaFall)
{
if (!isHmaBelow)
{
_waitingForSmaFall = false; // Invalidated
}
else if (isSmaFalling)
{
TriggerSignal("SELL", "SMA Started Falling after HMA CrossDown (Delayed)");
_waitingForSmaFall = false;
}
}
}
else
{
_waitingForSmaFall = false;
}
}
private void TriggerSignal(string type, string reason)
{
// Convert Time
string localTime = TimeZoneInfo.ConvertTimeFromUtc(_bot.TimeInUtc, _bot.TimeZone).ToString("yyyy-MM-dd HH:mm:ss");
// Drawing only if this is the chart symbol
if (_symbol.Name == _bot.SymbolName)
{
int index = _bars.Count - 2; // Last closed bar
if (type == "BUY")
_bot.Chart.DrawIcon("BuySignal_" + index, ChartIconType.UpArrow, index, _bars.LowPrices[index] - _symbol.PipSize * 5, Color.Green);
else if (type == "SELL")
_bot.Chart.DrawIcon("SellSignal_" + index, ChartIconType.DownArrow, index, _bars.HighPrices[index] + _symbol.PipSize * 5, Color.Red);
}
string logMessage = $"[{localTime}] {_symbol.Name} ({_bot.TimeFrame}) {type}: {reason}";
_bot.Log(logMessage);
if (!_bot.IsBacktesting)
{
_bot.PlaySound();
string emoji = (type == "BUY" ? "🟢" : (type == "SELL" ? "🔴" : ""));
string htmlMessage = $"{emoji} <b>{_symbol.Name}</b> ({_bot.TimeFrame})\n" +
$"<b>{type} Signal</b>\n" +
$"Reason: <i>{reason}</i>\n" +
$"Time: <code>{localTime}</code>";
_bot.SendTelegram(htmlMessage);
}
}
}
}
}
@@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="cTrader.Automate" Version="*" />
</ItemGroup>
</Project>