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}") = "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>
|
||||
Reference in New Issue
Block a user