commit a1d2e96f8a1e227271526c17d04fc585fe15775e Author: Michael Schimmel Date: Wed Jan 28 09:10:52 2026 +0100 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b13ca30 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +API +CloudJournal +Data +Export diff --git a/Settings/DAX open breakout, GER40 m15 long.cbotset b/Settings/DAX open breakout, GER40 m15 long.cbotset new file mode 100644 index 0000000..008255b --- /dev/null +++ b/Settings/DAX open breakout, GER40 m15 long.cbotset @@ -0,0 +1,22 @@ +{ + "Chart": { + "Symbol": "GER40", + "Period": "m15" + }, + "Parameters": { + "direction": "1", + "entryhour": "9", + "entryminute": "0", + "cancelhour": "14", + "closehour": "18", + "kclen": "14", + "kcmult": "1", + "atrmult": "3.3", + "slmult": "2.8000000000000003", + "tpmult": "2.8000000000000003", + "filtertrend": "True", + "malength": "240", + "compounding": "False", + "risk": "2" + } +} \ No newline at end of file diff --git a/Settings/DAX open breakout, GER40 m15 short.cbotset b/Settings/DAX open breakout, GER40 m15 short.cbotset new file mode 100644 index 0000000..567bbab --- /dev/null +++ b/Settings/DAX open breakout, GER40 m15 short.cbotset @@ -0,0 +1,22 @@ +{ + "Chart": { + "Symbol": "GER40", + "Period": "m15" + }, + "Parameters": { + "direction": "-1", + "entryhour": "9", + "entryminute": "0", + "cancelhour": "14", + "closehour": "18", + "kclen": "14", + "kcmult": "1", + "atrmult": "3.2", + "slmult": "2.7", + "tpmult": "2.1", + "filtertrend": "True", + "malength": "220", + "compounding": "False", + "risk": "2" + } +} \ No newline at end of file diff --git a/Settings/DAX open breakout, GER40 m15.cbotset b/Settings/DAX open breakout, GER40 m15.cbotset new file mode 100644 index 0000000..f4c7f01 --- /dev/null +++ b/Settings/DAX open breakout, GER40 m15.cbotset @@ -0,0 +1,21 @@ +{ + "Chart": { + "Symbol": "GER40", + "Period": "m15" + }, + "Parameters": { + "entryhour": 9, + "entryminute": 0, + "cancelhour": 14, + "closehour": 18, + "kclen": 14, + "kcmult": 1.0, + "atrmult": 3.2, + "slmult": 2.8, + "tpmult": 2.5, + "filtertrend": true, + "malength": 230, + "compounding": false, + "risk": 2 + } +} \ No newline at end of file diff --git a/Settings/DAX open breakout, GER40 m30 long.cbotset b/Settings/DAX open breakout, GER40 m30 long.cbotset new file mode 100644 index 0000000..6a21948 --- /dev/null +++ b/Settings/DAX open breakout, GER40 m30 long.cbotset @@ -0,0 +1,22 @@ +{ + "Chart": { + "Symbol": "GER40", + "Period": "m30" + }, + "Parameters": { + "direction": "1", + "entryhour": "9", + "entryminute": "0", + "cancelhour": "14", + "closehour": "18", + "kclen": "10", + "kcmult": "0.8", + "atrmult": "2.5", + "slmult": "2.5", + "tpmult": "2.6", + "filtertrend": "True", + "malength": "260", + "compounding": "False", + "risk": "2" + } +} \ No newline at end of file diff --git a/Settings/DAX open breakout, GER40 m30 short.cbotset b/Settings/DAX open breakout, GER40 m30 short.cbotset new file mode 100644 index 0000000..e64d8ed --- /dev/null +++ b/Settings/DAX open breakout, GER40 m30 short.cbotset @@ -0,0 +1,22 @@ +{ + "Chart": { + "Symbol": "GER40", + "Period": "m30" + }, + "Parameters": { + "direction": "-1", + "entryhour": "9", + "entryminute": "0", + "cancelhour": "14", + "closehour": "18", + "kclen": "10", + "kcmult": "1.3", + "atrmult": "2.5", + "slmult": "2.6", + "tpmult": "2.1", + "filtertrend": "True", + "malength": "200", + "compounding": "False", + "risk": "2" + } +} \ No newline at end of file diff --git a/Sources/Common/MSLib.sln b/Sources/Common/MSLib.sln new file mode 100644 index 0000000..b4eb1b6 --- /dev/null +++ b/Sources/Common/MSLib.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30114.105 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MSLib", "MSLib\MSLib.csproj", "{81C5B946-7D77-4DDD-B9E0-9AE6E47ADE2E}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {81C5B946-7D77-4DDD-B9E0-9AE6E47ADE2E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {81C5B946-7D77-4DDD-B9E0-9AE6E47ADE2E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {81C5B946-7D77-4DDD-B9E0-9AE6E47ADE2E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {81C5B946-7D77-4DDD-B9E0-9AE6E47ADE2E}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/Sources/Common/MSLib/LowPassFilter.cs b/Sources/Common/MSLib/LowPassFilter.cs new file mode 100644 index 0000000..6cfd73b --- /dev/null +++ b/Sources/Common/MSLib/LowPassFilter.cs @@ -0,0 +1,450 @@ +using System; +using System.Collections.Generic; // Required for List and Queue +using System.Linq; // Required for .Any() and .ToArray() +using System.Diagnostics; // For Debug.WriteLine, if needed for warnings/errors + +namespace MS +{ + // ICalculation interface defines a contract for updatable and resettable calculation objects. + // This allows different types of indicators or filters to be used interchangeably + // if they adhere to this structure. + public interface ICalculation + { + // Update processes a new data point (inputValue). + // inputValue: The new data point to incorporate into the calculation. + // commitState: If true, the internal state of the calculator is permanently updated. + // If false, the calculation is performed as a "peek" or "what-if" scenario + // without altering the persistent state of the calculator. + // Returns: The calculated value (e.g., median, MMI, filtered output) as a double. + // May return double.NaN if the calculation cannot be performed (e.g., insufficient data). + double Update(double inputValue, bool commitState = true); + + // Reset re-initializes the calculator to its default state. + // This typically involves clearing any stored historical data or state variables, + // making the calculator ready for a new sequence of data. + void Reset(); + } + + // Median class calculates a rolling median over a specified period. + // The median is the middle value of a data set sorted in ascending order. + // This implementation allows for committing state changes or calculating a "what-if" median. + public class Median : ICalculation + { + private readonly int _period; // The number of data points in the rolling window. + private readonly List _windowValues; // Stores the sorted values within the current window. Used for efficient median calculation. + private readonly Queue _windowOrder; // Stores the values in insertion order to manage the rolling window (FIFO). + + // Constructor for the Median calculator. + // period: The number of data points to include in the median calculation window. Must be positive. + public Median(int period) + { + if (period <= 0) + throw new ArgumentOutOfRangeException(nameof(period), "Period must be positive."); + + _period = period; + _windowValues = new List(_period); // Initialize with capacity for performance. + _windowOrder = new Queue(_period); // Initialize with capacity. + } + + // Updates the median calculation with a new data point (inputValue). + // If commitState is true, the inputValue is added to the window, and the oldest value is removed if the window is full. + // The median is then calculated from this updated (persisted) window. + // If commitState is false, the median is calculated based on a temporary window: + // - If the persisted window is full, it simulates replacing the oldest values with inputValue (current logic uses P-1 smallest). + // - If the persisted window is not full, it adds inputValue to the current persisted values. + // This temporary calculation does not alter the actual persisted state of _windowValues or _windowOrder. + public double Update(double inputValue, bool commitState = true) + { + List tempWindowForCalc = new List(); // Temporary list for performing the median calculation. + + if (commitState) // Persist the new value and update the window. + { + _windowOrder.Enqueue(inputValue); // Add to FIFO queue to track age. + int insertIndex = _windowValues.BinarySearch(inputValue); // Find position in sorted list. + if (insertIndex < 0) insertIndex = ~insertIndex; // If not found, get complement for insertion index. + _windowValues.Insert(insertIndex, inputValue); // Insert into sorted list. + + if (_windowOrder.Count > _period) // If window size exceeds period. + { + double oldestValue = _windowOrder.Dequeue(); // Remove oldest from FIFO queue. + _windowValues.Remove(oldestValue); // Remove oldest from sorted list (can be slow if list is large). + } + tempWindowForCalc.AddRange(_windowValues); // Calculation based on the actual, updated window. + } + else // Calculate "what-if" median without persisting state. + { + if (_windowValues.Any()) // If there are existing values. + { + if (_windowValues.Count == _period) // If the persisted window is full. + { + // Current "what-if" logic: takes the _period-1 smallest values from the current window + // and adds the new inputValue. This might not always reflect replacing the "oldest" actual item. + for (int i = 0; i < _period - 1; ++i) tempWindowForCalc.Add(_windowValues[i]); + } + else // Persisted window is not yet full. + { + tempWindowForCalc.AddRange(_windowValues); // Use all current values. + } + } + tempWindowForCalc.Add(inputValue); // Add the new input value to the temporary set. + } + + if (!tempWindowForCalc.Any()) // If no data in the temporary window. + { + return double.NaN; // Cannot calculate median. + } + + tempWindowForCalc.Sort(); // Median calculation requires a sorted list. + + int n = tempWindowForCalc.Count; + double calculatedMedian; + + if (n == 0) return double.NaN; // Should be caught by .Any() check above. + + if (n % 2 == 0) // Even number of elements. + { + if (n >= 2) { // Median is the average of the two middle elements. + calculatedMedian = (tempWindowForCalc[n / 2 - 1] + tempWindowForCalc[n / 2]) / 2.0; + } else { + return double.NaN; // Not enough elements for a meaningful even median. + } + } + else // Odd number of elements. + { + calculatedMedian = tempWindowForCalc[n / 2]; // Median is the middle element. + } + return calculatedMedian; + } + + // Resets the Median calculator to its initial state. + // Clears all stored values from the calculation window. + public void Reset() + { + _windowValues.Clear(); // Empties the sorted list of values. + _windowOrder.Clear(); // Empties the queue tracking insertion order. + } + } + + // MMI (Market Meanness Index) class calculates a rolling index indicating if a market is trending or mean-reverting. + // It uses a Median calculator internally. + // The MMI value is typically between 0 and 100. + public class MMI : ICalculation + { + private readonly int _period; // The period for MMI and its internal Median calculation. + private readonly Median _medianCalculator; // Internal Median calculator instance. + private readonly Queue _priceWindow; // Stores raw price values in insertion order for MMI logic. + + // Constructor for the MMI calculator. + // period: The number of data points for the MMI window. Must be at least 2. + public MMI(int period) + { + if (period < 2) // MMI calculation requires at least 2 data points to compare. + throw new ArgumentOutOfRangeException(nameof(period), "Period must be at least 2 for MMI calculation."); + + _period = period; + _medianCalculator = new Median(period); // Initialize the Median helper. + _priceWindow = new Queue(period); // Initialize price queue. + } + + // Updates the MMI calculation with a new data point (inputValue). + // It first updates its internal Median calculator. + // Then, it updates its own price window based on commitState. + // Finally, it calculates MMI based on the median and the price window. + // MMI is calculated as 100 * (nl + nh) / (period - 1), where: + // nl: count of prices > median and > previous price. + // nh: count of prices < median and < previous price. + // Returns NaN if the median is NaN or if the price window for MMI isn't full (_period size). + public double Update(double inputValue, bool commitState = true) + { + // Update the median, passing through the commitState. + double calculatedMedian = _medianCalculator.Update(inputValue, commitState); + + double[] pricesForMmiLoop; // Array to hold prices for the MMI loop. + + if (commitState) // Persist inputValue in MMI's price window. + { + _priceWindow.Enqueue(inputValue); + if (_priceWindow.Count > _period) + { + _priceWindow.Dequeue(); // Maintain window size. + } + pricesForMmiLoop = _priceWindow.ToArray(); // Use the actual, updated price window. + } + else // "What-if" scenario for MMI price window. + { + List tempList = new List(); + if (_priceWindow.Any()) + { + if (_priceWindow.Count == _period) // If full, simulate replacing the oldest. + { + var tempQueue = new Queue(_priceWindow.Skip(1)); // Create a new queue, skipping the oldest. + tempQueue.Enqueue(inputValue); // Add the new value. + pricesForMmiLoop = tempQueue.ToArray(); + } + else // Not full, just add to current MMI prices. + { + tempList.AddRange(_priceWindow); + tempList.Add(inputValue); + pricesForMmiLoop = tempList.ToArray(); + } + } + else // Price window is empty. + { + tempList.Add(inputValue); + pricesForMmiLoop = tempList.ToArray(); + } + } + + // MMI can only be calculated if median is valid and the price window for the loop is full. + if (double.IsNaN(calculatedMedian) || pricesForMmiLoop.Length != _period) + { + return double.NaN; + } + + int nl = 0; // Counter for "trending up" conditions. + int nh = 0; // Counter for "trending down" conditions. + // Loop starts from 1 because it compares price[i] with price[i-1]. + for (int i = 1; i < _period; i++) + { + double priceInWindow = pricesForMmiLoop[i]; + double prevPriceInWindow = pricesForMmiLoop[i - 1]; + // Condition for nl: price is above median and current price is greater than previous price. + if (priceInWindow > calculatedMedian && priceInWindow > prevPriceInWindow) nl++; + // Condition for nh: price is below median and current price is less than previous price. + else if (priceInWindow < calculatedMedian && priceInWindow < prevPriceInWindow) nh++; + } + // Denominator (_period - 1) is safe because constructor enforces _period >= 2. + return 100.0 * (nl + nh) / (_period - 1); + } + + // Resets the MMI calculator to its initial state. + // This involves resetting its internal Median calculator and clearing its own price window. + public void Reset() + { + _medianCalculator.Reset(); // Reset the dependent Median calculator. + _priceWindow.Clear(); // Clear the MMI's specific price data. + } + } + + // LowPassFilter class implements a 2nd order IIR (Infinite Impulse Response) digital filter. + // It can be configured as various types of low-pass filters (Butterworth, Bessel, Chebyshev) + // using static factory methods. + // The filter equation is: + // y[n] = b0*x[n] + b1*x[n-1] + b2*x[n-2] - a1*y[n-1] - a2*y[n-2] + public class LowPassFilter : ICalculation + { + // Filter coefficients for the numerator (feedforward) + private readonly double _b0, _b1, _b2; + // Filter coefficients for the denominator (feedback), signs are for the direct subtraction in formula. + private readonly double _a1, _a2; + + // State variables storing previous input values. + private double _x1; // x[n-1] + private double _x2; // x[n-2] + // State variables storing previous output values. + private double _y1; // y[n-1] + private double _y2; // y[n-2] + + // Constructor to initialize the filter with specific coefficients. + // Used by the static factory methods or for custom filter designs. + // b0_coeff, b1_coeff, b2_coeff: Numerator (feedforward) coefficients. + // a1_coeff, a2_coeff: Denominator (feedback) coefficients for the form y[n] = ... - a1*y[n-1] - a2*y[n-2]. + public LowPassFilter(double b0_coeff, double b1_coeff, double b2_coeff, double a1_coeff, double a2_coeff) + { + _b0 = b0_coeff; + _b1 = b1_coeff; + _b2 = b2_coeff; + _a1 = a1_coeff; // Corresponds to a1 in y[n] = ... - a1*y[n-1] + _a2 = a2_coeff; // Corresponds to a2 in y[n] = ... - a2*y[n-2] + Reset(); // Initialize state variables to zero. + } + + // Factory method to create a 2nd order Butterworth low-pass filter. + // Butterworth filters are known for their maximally flat passband response. + // cutoffFrequency: The -3dB cutoff frequency of the filter. + // samplingFrequency: The sampling frequency of the input signal. + public static LowPassFilter CreateButterworthLowPass(double cutoffFrequency, double samplingFrequency) + { + if (cutoffFrequency <= 0) + throw new ArgumentOutOfRangeException(nameof(cutoffFrequency), "Cutoff frequency must be positive."); + if (samplingFrequency <= 0) + throw new ArgumentOutOfRangeException(nameof(samplingFrequency), "Sampling frequency must be positive."); + // Cutoff frequency must be less than Nyquist frequency (samplingFrequency / 2.0). + if (cutoffFrequency >= samplingFrequency / 2.0) + throw new ArgumentOutOfRangeException(nameof(cutoffFrequency), $"Cutoff frequency ({cutoffFrequency}) must be strictly less than half the sampling frequency ({samplingFrequency / 2.0})."); + + double b0_calc, b1_calc, b2_calc, a1_calc, a2_calc; + // Prewarp the cutoff frequency for the bilinear transform. + // C = tan(pi * fc / fs) + double C = Math.Tan(Math.PI * cutoffFrequency / samplingFrequency); + + if (Math.Abs(C) < 1e-12) // Handle extremely low cutoff (approaching DC). + { + b0_calc = 0; b1_calc = 0; b2_calc = 0; // Effectively blocks AC signals. + // Coefficients for y[n] = 2*y[n-1] - y[n-2] (integrator-like behavior for output). + a1_calc = -2.0; // So that -a1_calc*y[n-1] becomes +2*y[n-1]. + a2_calc = 1.0; // So that -a2_calc*y[n-2] becomes -1*y[n-2]. + } + else + { + double sqrt2 = Math.Sqrt(2.0); + // Denominator factor for Butterworth coefficients after bilinear transform. + double D_butter = 1.0 / (1.0 + sqrt2 * C + C * C); + + // Numerator coefficients + b0_calc = C * C * D_butter; + b1_calc = 2.0 * b0_calc; + b2_calc = b0_calc; + + // Denominator coefficients (for 1 + a1_std*z^-1 + a2_std*z^-2) + // These are directly used as _a1, _a2 in the filter update equation. + a1_calc = 2.0 * (C * C - 1.0) * D_butter; + a2_calc = (1.0 - sqrt2 * C + C * C) * D_butter; + } + return new LowPassFilter(b0_calc, b1_calc, b2_calc, a1_calc, a2_calc); + } + + // Factory method to create a 2nd order Bessel low-pass filter. + // Bessel filters are known for their maximally flat group delay (linear phase response in passband). + // cutoffFrequency: The -3dB cutoff frequency. Note: Bessel filter design often refers to other frequency points. + // This implementation aims for -3dB at the specified cutoffFrequency. + // samplingFrequency: The sampling frequency of the input signal. + public static LowPassFilter CreateBesselLowPass(double cutoffFrequency, double samplingFrequency) + { + if (cutoffFrequency <= 0) throw new ArgumentOutOfRangeException(nameof(cutoffFrequency), "Cutoff frequency must be positive."); + if (samplingFrequency <= 0) throw new ArgumentOutOfRangeException(nameof(samplingFrequency), "Sampling frequency must be positive."); + if (cutoffFrequency >= samplingFrequency / 2.0) throw new ArgumentOutOfRangeException(nameof(cutoffFrequency), $"Cutoff frequency ({cutoffFrequency}) must be strictly less than half the sampling frequency ({samplingFrequency / 2.0})."); + + // C_bessel = tan(pi * fc / fs), prewarped frequency factor. + double C_bessel = Math.Tan(Math.PI * cutoffFrequency / samplingFrequency); + if (Math.Abs(C_bessel) < 1e-12) // Handle low cutoff. + { + return new LowPassFilter(0, 0, 0, -2.0, 1.0); // Similar to Butterworth low cutoff. + } + + // Coefficients derived from analog Bessel prototype (s^2 + 3s + 3), + // frequency scaled for -3dB at cutoffFrequency, then transformed using bilinear method. + // omega_c_analog_warped = 2 * fs * tan(pi * fc / fs) + double omega_c_analog_warped = 2.0 * samplingFrequency * C_bessel; + // k_bessel_norm_3db is a scaling factor for the Bessel prototype's natural frequency + // to achieve the -3dB point at the desired warped analog cutoff. Value is approx. 1.3617. + double k_bessel_norm_3db = 1.3617070504; // Sqrt(3 * (Sqrt(5.0) - 1.0) / 2.0) + double omega_0_scaled = omega_c_analog_warped / k_bessel_norm_3db; // Scaled analog natural frequency for prototype. + + // Analog prototype denominator: s^2 + A_s*s + B_s + // For Bessel n=2, scaled: s^2 + (3*omega_0_scaled)*s + (3*omega_0_scaled^2) + double as_coeff_analog = 3.0 * omega_0_scaled; // Coefficient for s-term. + double bs_coeff_analog = 3.0 * omega_0_scaled * omega_0_scaled; // Constant term (and numerator for DC gain 1). + + // K_T_bilinear = 2 * fs, factor in bilinear transform s = K_T * (1-z^-1)/(1+z^-1) (if K_T is used for 2/T_sample) + // Here, K_T_bilinear is 2*fs. + double K_T_bilinear = 2.0 * samplingFrequency; + + // Denominator after bilinear transform of H(s) = bs_coeff_analog / (s^2 + as_coeff_analog*s + bs_coeff_analog) + // gives A0_prime_denom = (K_T^2) + as_coeff_analog*K_T + bs_coeff_analog for normalization. + double A0_prime_denom = K_T_bilinear * K_T_bilinear + as_coeff_analog * K_T_bilinear + bs_coeff_analog; + if (Math.Abs(A0_prime_denom) < 1e-12) throw new InvalidOperationException("Denominator term A0_prime_denom is zero in Bessel filter calculation."); + + // Digital filter coefficients b_i = Bi_prime / A0_prime_denom, a_i = Ai_prime_std / A0_prime_denom + double b0_calc = bs_coeff_analog / A0_prime_denom; + double b1_calc = (2.0 * bs_coeff_analog) / A0_prime_denom; // From bilinear transform structure. + double b2_calc = bs_coeff_analog / A0_prime_denom; + + // Denominator coefficients for 1 + a1_std*z^-1 + a2_std*z^-2 form. + double a1_calc = (2.0 * bs_coeff_analog - 2.0 * K_T_bilinear * K_T_bilinear) / A0_prime_denom; + double a2_calc = (K_T_bilinear * K_T_bilinear - as_coeff_analog * K_T_bilinear + bs_coeff_analog) / A0_prime_denom; + + return new LowPassFilter(b0_calc, b1_calc, b2_calc, a1_calc, a2_calc); + } + + // Factory method to create a 2nd order Chebyshev Type I low-pass filter. + // Chebyshev Type I filters have a steeper roll-off than Butterworth, but exhibit ripple in the passband. + // cutoffFrequency: The frequency at which the passband ripple ends (e.g., gain drops by rippleDb). + // samplingFrequency: The sampling frequency of the input signal. + // rippleDb: The passband ripple in decibels (e.g., 0.5 dB, 1 dB). Must be positive. + public static LowPassFilter CreateChebyshevLowPass(double cutoffFrequency, double samplingFrequency, double rippleDb) + { + if (cutoffFrequency <= 0) throw new ArgumentOutOfRangeException(nameof(cutoffFrequency), "Cutoff frequency must be positive."); + if (samplingFrequency <= 0) throw new ArgumentOutOfRangeException(nameof(samplingFrequency), "Sampling frequency must be positive."); + if (cutoffFrequency >= samplingFrequency / 2.0) throw new ArgumentOutOfRangeException(nameof(cutoffFrequency), $"Cutoff frequency ({cutoffFrequency}) must be strictly less than half the sampling frequency ({samplingFrequency / 2.0})."); + if (rippleDb <= 0) throw new ArgumentOutOfRangeException(nameof(rippleDb), "Passband ripple must be positive."); + + // C_cheby = tan(pi * fc / fs), prewarped frequency factor. + double C_cheby = Math.Tan(Math.PI * cutoffFrequency / samplingFrequency); + if (Math.Abs(C_cheby) < 1e-12) // Handle low cutoff. + { + return new LowPassFilter(0,0,0, -2.0, 1.0); // Similar to Butterworth low cutoff. + } + + // Epsilon is derived from the passband ripple. + double epsilon = Math.Sqrt(Math.Pow(10, rippleDb / 10.0) - 1.0); + int N_order = 2; // Filter order is fixed at 2. + + // Parameters for locating analog prototype poles on an ellipse. + // alpha_val determines the eccentricity of the ellipse. + double alpha_val = Math.Asinh(1.0 / epsilon) / N_order; + // For N=2, poles are at s_k = -sigma_k +/- j*omega_k. + // These are scaled by C_cheby (warped cutoff frequency) for the specific filter. + double sin_pi_over_2N = Math.Sin(Math.PI / (2.0 * N_order)); // sin(pi/4) for N=2 + double cos_pi_over_2N = Math.Cos(Math.PI / (2.0 * N_order)); // cos(pi/4) for N=2 + + double sigma_k_scaled = Math.Sinh(alpha_val) * sin_pi_over_2N * C_cheby; + double omega_k_scaled = Math.Cosh(alpha_val) * cos_pi_over_2N * C_cheby; + + // Analog prototype denominator: s^2 + A_s*s + B_s + // Derived from poles: (s + sigma_k_scaled)^2 + omega_k_scaled^2 + double as_coeff_analog = 2.0 * sigma_k_scaled; // s-term coefficient. + double bs_coeff_analog = sigma_k_scaled * sigma_k_scaled + omega_k_scaled * omega_k_scaled; // Constant term. + + // Analog numerator: For Chebyshev Type I, N=2 (even order), DC gain H(0) = 1. + // So, numerator constant of analog prototype is bs_coeff_analog. + double num_s_coeff = bs_coeff_analog; + + double K_T_bilinear = 2.0 * samplingFrequency; // Factor for bilinear transform. + + // Denominator for normalization after bilinear transform. + double A0_prime_denom = K_T_bilinear * K_T_bilinear + as_coeff_analog * K_T_bilinear + bs_coeff_analog; + if (Math.Abs(A0_prime_denom) < 1e-12) throw new InvalidOperationException("Denominator term A0_prime_denom is zero in Chebyshev filter calculation."); + + // Digital filter coefficients. + double b0_calc = num_s_coeff / A0_prime_denom; + double b1_calc = (2.0 * num_s_coeff) / A0_prime_denom; + double b2_calc = num_s_coeff / A0_prime_denom; + + double a1_calc = (2.0 * bs_coeff_analog - 2.0 * K_T_bilinear * K_T_bilinear) / A0_prime_denom; + double a2_calc = (K_T_bilinear * K_T_bilinear - as_coeff_analog * K_T_bilinear + bs_coeff_analog) / A0_prime_denom; + + return new LowPassFilter(b0_calc, b1_calc, b2_calc, a1_calc, a2_calc); + } + + // Updates the filter with a new input sample (inputValue). + // Applies the difference equation: + // y[n] = b0*x[n] + b1*x[n-1] + b2*x[n-2] - a1*y[n-1] - a2*y[n-2] + // If commitState is true, the internal state variables (previous inputs and outputs) are updated. + // If false, the output is calculated using the current state, but the state is not advanced. + public double Update(double inputValue, bool commitState = true) + { + // Calculate the current output y[n] based on current input x[n] and previous states. + double output = _b0 * inputValue + _b1 * _x1 + _b2 * _x2 - _a1 * _y1 - _a2 * _y2; + + if (commitState) // If true, update the filter's internal state. + { + // Shift previous inputs: x[n-2] = x[n-1], x[n-1] = x[n] (current inputValue). + _x2 = _x1; + _x1 = inputValue; + // Shift previous outputs: y[n-2] = y[n-1], y[n-1] = y[n] (current calculated output). + _y2 = _y1; + _y1 = output; + } + return output; // Return the filtered output. + } + + // Resets the filter's internal state variables to zero. + // This clears any memory of previous input or output values, useful for starting a new filtering sequence. + public void Reset() + { + _x1 = 0; _x2 = 0; // Reset previous input states. + _y1 = 0; _y2 = 0; // Reset previous output states. + } + } +} \ No newline at end of file diff --git a/Sources/Common/MSLib/MSLib.csproj b/Sources/Common/MSLib/MSLib.csproj new file mode 100644 index 0000000..bafd05b --- /dev/null +++ b/Sources/Common/MSLib/MSLib.csproj @@ -0,0 +1,9 @@ + + + + net6.0 + enable + enable + + + diff --git a/Sources/Common/MSLib/ZorroBinaryFiles.cs b/Sources/Common/MSLib/ZorroBinaryFiles.cs new file mode 100644 index 0000000..4d84c14 --- /dev/null +++ b/Sources/Common/MSLib/ZorroBinaryFiles.cs @@ -0,0 +1,286 @@ +using System; +using System.Collections.Generic; // Required for List +using System.IO; // For File operations +using System.Runtime.InteropServices; // Required for StructLayout and Marshal + +namespace Zorro +{ + /// + /// Provides generic methods for loading and saving arrays of structures + /// from/to binary files using marshalling. + /// This class is intended for internal use within this assembly. + /// + internal static class BinaryFileHandler + { + public static void SaveArrayAsBlock(string filePath, T[] data) where T : struct + { + if (data == null || data.Length == 0) + { + File.WriteAllBytes(filePath, Array.Empty()); + return; + } + + int structSize = Marshal.SizeOf(typeof(T)); + byte[] byteArray = new byte[data.Length * structSize]; + IntPtr buffer = Marshal.AllocHGlobal(structSize); + try + { + for (int i = 0; i < data.Length; i++) + { + Marshal.StructureToPtr(data[i], buffer, false); + Marshal.Copy(buffer, byteArray, i * structSize, structSize); + } + File.WriteAllBytes(filePath, byteArray); + } + finally + { + Marshal.FreeHGlobal(buffer); + } + } + + public static T[]? LoadArrayAsBlock(string filePath) where T : struct + { + if (!File.Exists(filePath)) + { + return null; + } + + byte[] byteArray = File.ReadAllBytes(filePath); + if (byteArray.Length == 0) + { + return Array.Empty(); + } + + int structSize = Marshal.SizeOf(typeof(T)); + + if (byteArray.Length % structSize != 0) + { + throw new ArgumentException($"File size ({byteArray.Length} bytes) is not a valid multiple of the structure size ({structSize} bytes) for type {typeof(T).Name}. The file may be corrupted or not in the expected format."); + } + + int itemCount = byteArray.Length / structSize; + T[] dataArray = new T[itemCount]; + IntPtr buffer = Marshal.AllocHGlobal(structSize); + try + { + for (int i = 0; i < itemCount; i++) + { + Marshal.Copy(byteArray, i * structSize, buffer, structSize); + dataArray[i] = Marshal.PtrToStructure(buffer); + } + } + finally + { + Marshal.FreeHGlobal(buffer); + } + return dataArray; + } + } + + [StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Ansi)] + public struct ZorroT1 + { + public double OADate; + public float Value; + + /// + /// Returns the time as a UTC DateTime object. + /// Note: DateTime.FromOADate returns a DateTime with Kind=Unspecified. + /// We specify it as UTC as Zorro DATEs are GMT/UTC. + /// + public DateTime TimeUtc => DateTime.SpecifyKind(DateTime.FromOADate(OADate), DateTimeKind.Utc); + + public ZorroT1(double oaDate, float value) + { + OADate = oaDate; + Value = value; + } + public ZorroT1(DateTime dateTime, float value) + { + // Ensure the DateTime is converted to its UTC equivalent for OADate + OADate = dateTime.ToUniversalTime().ToOADate(); + Value = value; + } + + public static void SaveToFile(string filePath, ZorroT1[] data) => BinaryFileHandler.SaveArrayAsBlock(filePath, data); + public static ZorroT1[]? LoadFromFile(string filePath) => BinaryFileHandler.LoadArrayAsBlock(filePath); + } + + [StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Ansi)] + public struct ZorroT2 + { + public double OADate; + public float Price; + public float Volume; + + public DateTime TimeUtc => DateTime.SpecifyKind(DateTime.FromOADate(OADate), DateTimeKind.Utc); + + public ZorroT2(double oaDate, float price, float volume) + { + OADate = oaDate; + Price = price; + Volume = volume; + } + public ZorroT2(DateTime dateTime, float price, float volume) + { + OADate = dateTime.ToUniversalTime().ToOADate(); + Price = price; + Volume = volume; + } + + public static void SaveToFile(string filePath, ZorroT2[] data) => BinaryFileHandler.SaveArrayAsBlock(filePath, data); + public static ZorroT2[]? LoadFromFile(string filePath) => BinaryFileHandler.LoadArrayAsBlock(filePath); + } + + [StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Ansi)] + public struct ZorroT6 + { + public double OADate; + public float High; + public float Low; + public float Open; + public float Close; + public float AdditionalValue1; + public float AdditionalValue2; + + public DateTime TimeUtc => DateTime.SpecifyKind(DateTime.FromOADate(OADate), DateTimeKind.Utc); + + public ZorroT6(double oaDate, float high, float low, float open, float close, float additionalValue1, float additionalValue2) + { + OADate = oaDate; + High = high; + Low = low; + Open = open; + Close = close; + AdditionalValue1 = additionalValue1; + AdditionalValue2 = additionalValue2; + } + public ZorroT6(DateTime dateTime, float high, float low, float open, float close, float additionalValue1, float additionalValue2) + { + OADate = dateTime.ToUniversalTime().ToOADate(); + High = high; + Low = low; + Open = open; + Close = close; + AdditionalValue1 = additionalValue1; + AdditionalValue2 = additionalValue2; + } + + public static void SaveToFile(string filePath, ZorroT6[] data) => BinaryFileHandler.SaveArrayAsBlock(filePath, data); + public static ZorroT6[]? LoadFromFile(string filePath) => BinaryFileHandler.LoadArrayAsBlock(filePath); + } + + // --- Builder Classes for Incremental Array Construction --- + + public class ZorroT1ArrayBuilder + { + private readonly List _items; + + public ZorroT1ArrayBuilder(int initialCapacity = 0) + { + _items = initialCapacity > 0 ? new List(initialCapacity) : new List(); + } + + public ZorroT1ArrayBuilder Add(DateTime time, float value) + { + // Constructor of ZorroT1 now handles UTC conversion + _items.Add(new ZorroT1(time, value)); + return this; + } + + public ZorroT1ArrayBuilder Add(double oaDate, float value) + { + _items.Add(new ZorroT1(oaDate, value)); + return this; + } + + public ZorroT1ArrayBuilder Add(ZorroT1 item) + { + _items.Add(item); + return this; + } + + public ZorroT1[] ToArray() => _items.ToArray(); + public void Clear() => _items.Clear(); + public int Count => _items.Count; + + public void SaveToFile(string filePath) + { + ZorroT1.SaveToFile(filePath, _items.ToArray()); + } + } + + public class ZorroT2ArrayBuilder + { + private readonly List _items; + + public ZorroT2ArrayBuilder(int initialCapacity = 0) + { + _items = initialCapacity > 0 ? new List(initialCapacity) : new List(); + } + + public ZorroT2ArrayBuilder Add(DateTime time, float price, float volume) + { + _items.Add(new ZorroT2(time, price, volume)); + return this; + } + + public ZorroT2ArrayBuilder Add(double oaDate, float price, float volume) + { + _items.Add(new ZorroT2(oaDate, price, volume)); + return this; + } + + public ZorroT2ArrayBuilder Add(ZorroT2 item) + { + _items.Add(item); + return this; + } + + public ZorroT2[] ToArray() => _items.ToArray(); + public void Clear() => _items.Clear(); + public int Count => _items.Count; + + public void SaveToFile(string filePath) + { + ZorroT2.SaveToFile(filePath, _items.ToArray()); + } + } + + public class ZorroT6ArrayBuilder + { + private readonly List _items; + + public ZorroT6ArrayBuilder(int initialCapacity = 0) + { + _items = initialCapacity > 0 ? new List(initialCapacity) : new List(); + } + + public ZorroT6ArrayBuilder Add(DateTime time, float high, float low, float open, float close, float additionalValue1, float additionalValue2) + { + _items.Add(new ZorroT6(time, high, low, open, close, additionalValue1, additionalValue2)); + return this; + } + + public ZorroT6ArrayBuilder Add(double oaDate, float high, float low, float open, float close, float additionalValue1, float additionalValue2) + { + _items.Add(new ZorroT6(oaDate, high, low, open, close, additionalValue1, additionalValue2)); + return this; + } + + public ZorroT6ArrayBuilder Add(ZorroT6 item) + { + _items.Add(item); + return this; + } + + public ZorroT6[] ToArray() => _items.ToArray(); + public void Clear() => _items.Clear(); + public int Count => _items.Count; + + public void SaveToFile(string filePath) + { + ZorroT6.SaveToFile(filePath, _items.ToArray()); + } + } +} \ No newline at end of file diff --git a/Sources/Common/MSLib/bin/Debug/net6.0/MSLib.deps.json b/Sources/Common/MSLib/bin/Debug/net6.0/MSLib.deps.json new file mode 100644 index 0000000..0c157d0 --- /dev/null +++ b/Sources/Common/MSLib/bin/Debug/net6.0/MSLib.deps.json @@ -0,0 +1,23 @@ +{ + "runtimeTarget": { + "name": ".NETCoreApp,Version=v6.0", + "signature": "" + }, + "compilationOptions": {}, + "targets": { + ".NETCoreApp,Version=v6.0": { + "MSLib/1.0.0": { + "runtime": { + "MSLib.dll": {} + } + } + } + }, + "libraries": { + "MSLib/1.0.0": { + "type": "project", + "serviceable": false, + "sha512": "" + } + } +} \ No newline at end of file diff --git a/Sources/Common/MSLib/bin/Debug/net6.0/MSLib.dll b/Sources/Common/MSLib/bin/Debug/net6.0/MSLib.dll new file mode 100644 index 0000000..acf6f9a Binary files /dev/null and b/Sources/Common/MSLib/bin/Debug/net6.0/MSLib.dll differ diff --git a/Sources/Common/MSLib/bin/Debug/net6.0/MSLib.pdb b/Sources/Common/MSLib/bin/Debug/net6.0/MSLib.pdb new file mode 100644 index 0000000..d1c06a6 Binary files /dev/null and b/Sources/Common/MSLib/bin/Debug/net6.0/MSLib.pdb differ diff --git a/Sources/Common/MSLib/obj/Debug/net6.0/.NETCoreApp,Version=v6.0.AssemblyAttributes.cs b/Sources/Common/MSLib/obj/Debug/net6.0/.NETCoreApp,Version=v6.0.AssemblyAttributes.cs new file mode 100644 index 0000000..32c95f9 --- /dev/null +++ b/Sources/Common/MSLib/obj/Debug/net6.0/.NETCoreApp,Version=v6.0.AssemblyAttributes.cs @@ -0,0 +1,4 @@ +// +using System; +using System.Reflection; +[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v6.0", FrameworkDisplayName = "")] diff --git a/Sources/Common/MSLib/obj/Debug/net6.0/MSLib.AssemblyInfo.cs b/Sources/Common/MSLib/obj/Debug/net6.0/MSLib.AssemblyInfo.cs new file mode 100644 index 0000000..ab39a41 --- /dev/null +++ b/Sources/Common/MSLib/obj/Debug/net6.0/MSLib.AssemblyInfo.cs @@ -0,0 +1,22 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +using System; +using System.Reflection; + +[assembly: System.Reflection.AssemblyCompanyAttribute("MSLib")] +[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] +[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0")] +[assembly: System.Reflection.AssemblyProductAttribute("MSLib")] +[assembly: System.Reflection.AssemblyTitleAttribute("MSLib")] +[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] + +// Generated by the MSBuild WriteCodeFragment class. + diff --git a/Sources/Common/MSLib/obj/Debug/net6.0/MSLib.AssemblyInfoInputs.cache b/Sources/Common/MSLib/obj/Debug/net6.0/MSLib.AssemblyInfoInputs.cache new file mode 100644 index 0000000..243db22 --- /dev/null +++ b/Sources/Common/MSLib/obj/Debug/net6.0/MSLib.AssemblyInfoInputs.cache @@ -0,0 +1 @@ +5bd6cf3423e8e65d9421c371ad2c6b9a30b6eb1d diff --git a/Sources/Common/MSLib/obj/Debug/net6.0/MSLib.GeneratedMSBuildEditorConfig.editorconfig b/Sources/Common/MSLib/obj/Debug/net6.0/MSLib.GeneratedMSBuildEditorConfig.editorconfig new file mode 100644 index 0000000..4db8300 --- /dev/null +++ b/Sources/Common/MSLib/obj/Debug/net6.0/MSLib.GeneratedMSBuildEditorConfig.editorconfig @@ -0,0 +1,10 @@ +is_global = true +build_property.TargetFramework = net6.0 +build_property.TargetPlatformMinVersion = +build_property.UsingMicrosoftNETSdkWeb = +build_property.ProjectTypeGuids = +build_property.InvariantGlobalization = +build_property.PlatformNeutralAssembly = +build_property._SupportedPlatformList = Linux,macOS,Windows +build_property.RootNamespace = MSLib +build_property.ProjectDir = C:\Users\Brummel\Documents\cAlgo\Sources\Common\MSLib\ diff --git a/Sources/Common/MSLib/obj/Debug/net6.0/MSLib.GlobalUsings.g.cs b/Sources/Common/MSLib/obj/Debug/net6.0/MSLib.GlobalUsings.g.cs new file mode 100644 index 0000000..ac22929 --- /dev/null +++ b/Sources/Common/MSLib/obj/Debug/net6.0/MSLib.GlobalUsings.g.cs @@ -0,0 +1,8 @@ +// +global using global::System; +global using global::System.Collections.Generic; +global using global::System.IO; +global using global::System.Linq; +global using global::System.Net.Http; +global using global::System.Threading; +global using global::System.Threading.Tasks; diff --git a/Sources/Common/MSLib/obj/Debug/net6.0/MSLib.assets.cache b/Sources/Common/MSLib/obj/Debug/net6.0/MSLib.assets.cache new file mode 100644 index 0000000..d7e1628 Binary files /dev/null and b/Sources/Common/MSLib/obj/Debug/net6.0/MSLib.assets.cache differ diff --git a/Sources/Common/MSLib/obj/Debug/net6.0/MSLib.csproj.AssemblyReference.cache b/Sources/Common/MSLib/obj/Debug/net6.0/MSLib.csproj.AssemblyReference.cache new file mode 100644 index 0000000..6092d5c Binary files /dev/null and b/Sources/Common/MSLib/obj/Debug/net6.0/MSLib.csproj.AssemblyReference.cache differ diff --git a/Sources/Common/MSLib/obj/Debug/net6.0/MSLib.csproj.CoreCompileInputs.cache b/Sources/Common/MSLib/obj/Debug/net6.0/MSLib.csproj.CoreCompileInputs.cache new file mode 100644 index 0000000..5a1c6bb --- /dev/null +++ b/Sources/Common/MSLib/obj/Debug/net6.0/MSLib.csproj.CoreCompileInputs.cache @@ -0,0 +1 @@ +375eb7427a13a6a1a036f49ce73cc2a66452e118 diff --git a/Sources/Common/MSLib/obj/Debug/net6.0/MSLib.csproj.FileListAbsolute.txt b/Sources/Common/MSLib/obj/Debug/net6.0/MSLib.csproj.FileListAbsolute.txt new file mode 100644 index 0000000..71d3a6b --- /dev/null +++ b/Sources/Common/MSLib/obj/Debug/net6.0/MSLib.csproj.FileListAbsolute.txt @@ -0,0 +1,12 @@ +C:\Users\Brummel\Documents\cAlgo\Sources\Common\MSLib\bin\Debug\net6.0\MSLib.deps.json +C:\Users\Brummel\Documents\cAlgo\Sources\Common\MSLib\bin\Debug\net6.0\MSLib.dll +C:\Users\Brummel\Documents\cAlgo\Sources\Common\MSLib\bin\Debug\net6.0\MSLib.pdb +C:\Users\Brummel\Documents\cAlgo\Sources\Common\MSLib\obj\Debug\net6.0\MSLib.csproj.AssemblyReference.cache +C:\Users\Brummel\Documents\cAlgo\Sources\Common\MSLib\obj\Debug\net6.0\MSLib.GeneratedMSBuildEditorConfig.editorconfig +C:\Users\Brummel\Documents\cAlgo\Sources\Common\MSLib\obj\Debug\net6.0\MSLib.AssemblyInfoInputs.cache +C:\Users\Brummel\Documents\cAlgo\Sources\Common\MSLib\obj\Debug\net6.0\MSLib.AssemblyInfo.cs +C:\Users\Brummel\Documents\cAlgo\Sources\Common\MSLib\obj\Debug\net6.0\MSLib.csproj.CoreCompileInputs.cache +C:\Users\Brummel\Documents\cAlgo\Sources\Common\MSLib\obj\Debug\net6.0\MSLib.dll +C:\Users\Brummel\Documents\cAlgo\Sources\Common\MSLib\obj\Debug\net6.0\refint\MSLib.dll +C:\Users\Brummel\Documents\cAlgo\Sources\Common\MSLib\obj\Debug\net6.0\MSLib.pdb +C:\Users\Brummel\Documents\cAlgo\Sources\Common\MSLib\obj\Debug\net6.0\ref\MSLib.dll diff --git a/Sources/Common/MSLib/obj/Debug/net6.0/MSLib.dll b/Sources/Common/MSLib/obj/Debug/net6.0/MSLib.dll new file mode 100644 index 0000000..acf6f9a Binary files /dev/null and b/Sources/Common/MSLib/obj/Debug/net6.0/MSLib.dll differ diff --git a/Sources/Common/MSLib/obj/Debug/net6.0/MSLib.pdb b/Sources/Common/MSLib/obj/Debug/net6.0/MSLib.pdb new file mode 100644 index 0000000..d1c06a6 Binary files /dev/null and b/Sources/Common/MSLib/obj/Debug/net6.0/MSLib.pdb differ diff --git a/Sources/Common/MSLib/obj/Debug/net6.0/ref/MSLib.dll b/Sources/Common/MSLib/obj/Debug/net6.0/ref/MSLib.dll new file mode 100644 index 0000000..00ff9d3 Binary files /dev/null and b/Sources/Common/MSLib/obj/Debug/net6.0/ref/MSLib.dll differ diff --git a/Sources/Common/MSLib/obj/Debug/net6.0/refint/MSLib.dll b/Sources/Common/MSLib/obj/Debug/net6.0/refint/MSLib.dll new file mode 100644 index 0000000..00ff9d3 Binary files /dev/null and b/Sources/Common/MSLib/obj/Debug/net6.0/refint/MSLib.dll differ diff --git a/Sources/Common/MSLib/obj/MSLib.csproj.nuget.dgspec.json b/Sources/Common/MSLib/obj/MSLib.csproj.nuget.dgspec.json new file mode 100644 index 0000000..103cb00 --- /dev/null +++ b/Sources/Common/MSLib/obj/MSLib.csproj.nuget.dgspec.json @@ -0,0 +1,60 @@ +{ + "format": 1, + "restore": { + "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Common\\MSLib\\MSLib.csproj": {} + }, + "projects": { + "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Common\\MSLib\\MSLib.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Common\\MSLib\\MSLib.csproj", + "projectName": "MSLib", + "projectPath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Common\\MSLib\\MSLib.csproj", + "packagesPath": "C:\\Users\\Brummel\\.nuget\\packages\\", + "outputPath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Common\\MSLib\\obj\\", + "projectStyle": "PackageReference", + "configFilePaths": [ + "C:\\Users\\Brummel\\AppData\\Roaming\\NuGet\\NuGet.Config" + ], + "originalTargetFrameworks": [ + "net6.0" + ], + "sources": { + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net6.0": { + "targetAlias": "net6.0", + "projectReferences": {} + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + } + }, + "frameworks": { + "net6.0": { + "targetAlias": "net6.0", + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\6.0.200\\RuntimeIdentifierGraph.json" + } + } + } + } +} \ No newline at end of file diff --git a/Sources/Common/MSLib/obj/MSLib.csproj.nuget.g.props b/Sources/Common/MSLib/obj/MSLib.csproj.nuget.g.props new file mode 100644 index 0000000..0c973b0 --- /dev/null +++ b/Sources/Common/MSLib/obj/MSLib.csproj.nuget.g.props @@ -0,0 +1,15 @@ + + + + True + NuGet + $(MSBuildThisFileDirectory)project.assets.json + $(UserProfile)\.nuget\packages\ + C:\Users\Brummel\.nuget\packages\ + PackageReference + 6.1.0 + + + + + \ No newline at end of file diff --git a/Sources/Common/MSLib/obj/MSLib.csproj.nuget.g.targets b/Sources/Common/MSLib/obj/MSLib.csproj.nuget.g.targets new file mode 100644 index 0000000..35a7576 --- /dev/null +++ b/Sources/Common/MSLib/obj/MSLib.csproj.nuget.g.targets @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/Sources/Common/MSLib/obj/project.assets.json b/Sources/Common/MSLib/obj/project.assets.json new file mode 100644 index 0000000..aec2b59 --- /dev/null +++ b/Sources/Common/MSLib/obj/project.assets.json @@ -0,0 +1,65 @@ +{ + "version": 3, + "targets": { + "net6.0": {} + }, + "libraries": {}, + "projectFileDependencyGroups": { + "net6.0": [] + }, + "packageFolders": { + "C:\\Users\\Brummel\\.nuget\\packages\\": {} + }, + "project": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Common\\MSLib\\MSLib.csproj", + "projectName": "MSLib", + "projectPath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Common\\MSLib\\MSLib.csproj", + "packagesPath": "C:\\Users\\Brummel\\.nuget\\packages\\", + "outputPath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Common\\MSLib\\obj\\", + "projectStyle": "PackageReference", + "configFilePaths": [ + "C:\\Users\\Brummel\\AppData\\Roaming\\NuGet\\NuGet.Config" + ], + "originalTargetFrameworks": [ + "net6.0" + ], + "sources": { + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net6.0": { + "targetAlias": "net6.0", + "projectReferences": {} + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + } + }, + "frameworks": { + "net6.0": { + "targetAlias": "net6.0", + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\6.0.200\\RuntimeIdentifierGraph.json" + } + } + } +} \ No newline at end of file diff --git a/Sources/Common/MSLib/obj/project.nuget.cache b/Sources/Common/MSLib/obj/project.nuget.cache new file mode 100644 index 0000000..701bd15 --- /dev/null +++ b/Sources/Common/MSLib/obj/project.nuget.cache @@ -0,0 +1,8 @@ +{ + "version": 2, + "dgSpecHash": "Oo9DgV2PMMwBLALF97djBTxT9Wgbo3xT86Zo8aWAwrxLBxF4zrxrwtxR5/TCIXpFczNFbEDKftQZxvjl9Y6BHQ==", + "success": true, + "projectFilePath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Common\\MSLib\\MSLib.csproj", + "expectedPackageFiles": [], + "logs": [] +} \ No newline at end of file diff --git a/Sources/Indicators/Bar times.algo b/Sources/Indicators/Bar times.algo new file mode 100644 index 0000000..061a565 Binary files /dev/null and b/Sources/Indicators/Bar times.algo differ diff --git a/Sources/Indicators/Bar times/Bar times.sln b/Sources/Indicators/Bar times/Bar times.sln new file mode 100644 index 0000000..9b30d8e --- /dev/null +++ b/Sources/Indicators/Bar times/Bar times.sln @@ -0,0 +1,20 @@ + +Microsoft Visual Studio Solution File, Format Version 11.00 +# Visual Studio 2010 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bar times", "Bar times\Bar times.csproj", "{373DED72-A17B-4903-8EC9-FA9589B99D5D}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {373DED72-A17B-4903-8EC9-FA9589B99D5D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {373DED72-A17B-4903-8EC9-FA9589B99D5D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {373DED72-A17B-4903-8EC9-FA9589B99D5D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {373DED72-A17B-4903-8EC9-FA9589B99D5D}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/Sources/Indicators/Bar times/Bar times/Bar times.cs b/Sources/Indicators/Bar times/Bar times/Bar times.cs new file mode 100644 index 0000000..a84fb3c --- /dev/null +++ b/Sources/Indicators/Bar times/Bar times/Bar times.cs @@ -0,0 +1,47 @@ +// ------------------------------------------------------------------------------------------------- +// +// This code is a cAlgo API MACD Crossover indicator provided by njardim@email.com on Dec 2015. +// +// Based on the original MACD Crossover indicator from cTrade. +// +// ------------------------------------------------------------------------------------------------- + +using System; +using cAlgo.API; +using cAlgo.API.Internals; +using cAlgo.API.Indicators; +using cAlgo.Indicators; + +namespace cAlgo +{ + [Indicator(IsOverlay = false, TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)] + public class BarTimes : Indicator + { + [Output("Signal", LineColor = "Blue", PlotType = PlotType.Line, LineStyle = LineStyle.Lines, Thickness = 1)] + public IndicatorDataSeries Signal { get; set; } + + public IndicatorDataSeries Times { get; set; } + private MovingAverage TimesMa; + + protected override void Initialize() + { + Times = CreateDataSeries(); + TimesMa = Indicators.MovingAverage(Times, 200, MovingAverageType.Simple ); + } + + public override void Calculate(int index) + { + if( index>0 ) + { + var t0 = Bars.OpenTimes[index]; + var t1 = Bars.OpenTimes[index-1]; + if( t0.ToLocalTime().Date == t1.ToLocalTime().Date ) + Times[index] = (t0-t1).TotalMinutes; + else + Times[index] = 0; + } + + Signal[index] = Times[index]>0? TimesMa.Result[index] / Times[index] : 0; + } + } +} diff --git a/Sources/Indicators/Bar times/Bar times/Bar times.csproj b/Sources/Indicators/Bar times/Bar times/Bar times.csproj new file mode 100644 index 0000000..e33466c --- /dev/null +++ b/Sources/Indicators/Bar times/Bar times/Bar times.csproj @@ -0,0 +1,24 @@ + + + + net6.0 + False + False + + + 7.2 + Debug + AnyCPU + Properties + cAlgo + Bar times + 512 + + + + + + + + + \ No newline at end of file diff --git a/Sources/Indicators/Bar times/Bar times/Properties/AssemblyInfo.cs b/Sources/Indicators/Bar times/Bar times/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..aca3ca9 --- /dev/null +++ b/Sources/Indicators/Bar times/Bar times/Properties/AssemblyInfo.cs @@ -0,0 +1,20 @@ +using System.Diagnostics; +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("Bar times")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyProduct("Bar times")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +[assembly: ComVisible(false)] + +[assembly: Guid("35f19c43-c7cc-4f53-b8e0-61caaf9cd8e8")] + +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] +#if DEBUG + [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations)] +#endif \ No newline at end of file diff --git a/Sources/Indicators/BarCloseAlert.algo b/Sources/Indicators/BarCloseAlert.algo new file mode 100644 index 0000000..4edd7b7 Binary files /dev/null and b/Sources/Indicators/BarCloseAlert.algo differ diff --git a/Sources/Indicators/BarCloseAlert/BarCloseAlert.sln b/Sources/Indicators/BarCloseAlert/BarCloseAlert.sln new file mode 100644 index 0000000..517ad0f --- /dev/null +++ b/Sources/Indicators/BarCloseAlert/BarCloseAlert.sln @@ -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}") = "BarCloseAlert", "BarCloseAlert\BarCloseAlert.csproj", "{5de69e58-aff2-4b56-be9b-3de3b4fc7e82}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {5de69e58-aff2-4b56-be9b-3de3b4fc7e82}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5de69e58-aff2-4b56-be9b-3de3b4fc7e82}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5de69e58-aff2-4b56-be9b-3de3b4fc7e82}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5de69e58-aff2-4b56-be9b-3de3b4fc7e82}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/Sources/Indicators/BarCloseAlert/BarCloseAlert/BarCloseAlert.cs b/Sources/Indicators/BarCloseAlert/BarCloseAlert/BarCloseAlert.cs new file mode 100644 index 0000000..6d77497 --- /dev/null +++ b/Sources/Indicators/BarCloseAlert/BarCloseAlert/BarCloseAlert.cs @@ -0,0 +1,72 @@ +using System; +using cAlgo.API; +using cAlgo.API.Internals; +using cAlgo.API.Indicators; + +namespace cAlgo.Indicators +{ + [Indicator(IsOverlay = true, TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)] + public class BarCloseAlert : Indicator + { + private HullMovingAverage _hma; + + [Parameter("Bar Close Sound File", Group = "Sounds", DefaultValue = "C:\\Windows\\Media\\Alarm07.wav")] + public string BarCloseSoundFileName { get; set; } + + [Parameter("HMA Sound File", Group = "Sounds", DefaultValue = "C:\\Windows\\Media\\Alarm08.wav")] + public string HmaSoundFileName { get; set; } + + [Parameter("HMA Period", Group = "HMA Settings", DefaultValue = 200)] + public int HmaPeriod { get; set; } + + protected override void Initialize() + { + _hma = Indicators.HullMovingAverage(Bars.ClosePrices, HmaPeriod); + Bars.BarClosed += OnBarsClosed; + } + + private void OnBarsClosed(BarClosedEventArgs obj) + { + // 1. Play Bar Close Sound + if (!string.IsNullOrEmpty(BarCloseSoundFileName)) + { + Notifications.PlaySound(BarCloseSoundFileName); + } + + // 2. Check HMA High/Low + // We need enough bars for Last(0), Last(1), Last(2) + if (Bars.Count < 3 || string.IsNullOrEmpty(HmaSoundFileName)) + { + return; + } + + // Adjusted Indexing per user requirement: + // Last(0) is treated as the bar that just closed. + double hmaJustClosed = _hma.Result.Last(0); + double hmaPrev = _hma.Result.Last(1); + double hmaPrePrev = _hma.Result.Last(2); + + // Check for High Point (Peak): Rising then Falling + // Shape: / \ + bool isHigh = (hmaPrePrev < hmaPrev) && (hmaPrev > hmaJustClosed); + + // Check for Low Point (Valley): Falling then Rising + // Shape: \ / + bool isLow = (hmaPrePrev > hmaPrev) && (hmaPrev < hmaJustClosed); + + if (isHigh || isLow) + { + Notifications.PlaySound(HmaSoundFileName); + } + } + + public override void Calculate(int index) + { + } + + protected override void OnDestroy() + { + Bars.BarClosed -= OnBarsClosed; + } + } +} \ No newline at end of file diff --git a/Sources/Indicators/BarCloseAlert/BarCloseAlert/BarCloseAlert.csproj b/Sources/Indicators/BarCloseAlert/BarCloseAlert/BarCloseAlert.csproj new file mode 100644 index 0000000..da1fef1 --- /dev/null +++ b/Sources/Indicators/BarCloseAlert/BarCloseAlert/BarCloseAlert.csproj @@ -0,0 +1,9 @@ + + + net6.0 + + + + + + diff --git a/Sources/Indicators/Candlestick Patterns DEMO.algo b/Sources/Indicators/Candlestick Patterns DEMO.algo new file mode 100644 index 0000000..84ced26 Binary files /dev/null and b/Sources/Indicators/Candlestick Patterns DEMO.algo differ diff --git a/Sources/Indicators/Candlestick Patterns DEMO/Candlestick Patterns DEMO.sln b/Sources/Indicators/Candlestick Patterns DEMO/Candlestick Patterns DEMO.sln new file mode 100644 index 0000000..b7a72d8 --- /dev/null +++ b/Sources/Indicators/Candlestick Patterns DEMO/Candlestick Patterns DEMO.sln @@ -0,0 +1,20 @@ + +Microsoft Visual Studio Solution File, Format Version 11.00 +# Visual Studio 2010 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Candlestick Patterns DEMO", "Candlestick Patterns DEMO\Candlestick Patterns DEMO.csproj", "{B9F710D0-7DD5-4E04-B2E9-98C1878E7674}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {B9F710D0-7DD5-4E04-B2E9-98C1878E7674}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B9F710D0-7DD5-4E04-B2E9-98C1878E7674}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B9F710D0-7DD5-4E04-B2E9-98C1878E7674}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B9F710D0-7DD5-4E04-B2E9-98C1878E7674}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/Sources/Indicators/Candlestick Patterns DEMO/Candlestick Patterns DEMO/Candlestick Patterns DEMO.cs b/Sources/Indicators/Candlestick Patterns DEMO/Candlestick Patterns DEMO/Candlestick Patterns DEMO.cs new file mode 100644 index 0000000..0b8fa89 --- /dev/null +++ b/Sources/Indicators/Candlestick Patterns DEMO/Candlestick Patterns DEMO/Candlestick Patterns DEMO.cs @@ -0,0 +1,504 @@ +using System; +using System.IO; +using cAlgo.API; +using cAlgo.API.Internals; +using cAlgo.API.Indicators; +using cAlgo.Indicators; + +namespace cAlgo +{ + [Indicator(IsOverlay = true, TimeZone = TimeZones.UTC, AccessRights = AccessRights.FileSystem)] + public class CP : Indicator + { + #region Parameters + + [Parameter("Show names", DefaultValue = false)] + public bool Name_Status { get; set; } + + [Parameter("Show more info", DefaultValue = true)] + public bool Add_info { get; set; } + + [Parameter("Show Doji", DefaultValue = true)] + public bool Show_Doji { get; set; } + + [Parameter("Show Hammer", DefaultValue = true)] + public bool Show_Hammer { get; set; } + + [Parameter("MACD Long Cycle", DefaultValue = 26)] + public int LongCycle { get; set; } + + [Parameter("MACD Short Cycle", DefaultValue = 12)] + public int ShortCycle { get; set; } + + [Parameter("MACD Signal Periods", DefaultValue = 9)] + public int Periods { get; set; } + + [Parameter("Doji", DefaultValue = "✝")] + public string Doji_s { get; set; } + [Parameter("Hammer", DefaultValue = "☨")] + public string Hammer_s { get; set; } + + #endregion Parameters + + private MacdHistogram macd; + + private const VerticalAlignment vAlign = VerticalAlignment.Top; + private const HorizontalAlignment hAlign = HorizontalAlignment.Center; + + private const double n = 0.618; + + private const string UpArrow = "▲"; + private const string DownArrow = "▼"; + + private string Pattern_name; + + private double offset; + + private struct Candle + { + #region Data + + public double High; + public double Close; + public double Open; + public double Low; + + #endregion Data + + #region Functions + + public bool IsFallCandle() + { + if (Close < Open) + return true; + else + return false; + } + public bool IsRiseCandle() + { + if (Open < Close) + return true; + else + return false; + } + + public double Median() + { + return (High + Low) / 2; + } + + #endregion Functions + } + + private enum PatternType : int + { + Doji = 1, + Hammer = 2 + } + + protected override void Initialize() + { + macd = Indicators.MacdHistogram(MarketSeries.Close, LongCycle, ShortCycle, Periods); + offset = Symbol.PipSize * 5; + } + + private void DrawText(int index, int _Type) + { + var high = MarketSeries.High[index]; + var low = MarketSeries.Low[index]; + + int x = index; + + double h_y = high + offset; + double h_d_y = h_y + offset * 2.5; + double h_t_y = h_d_y + offset; + + double l_y = low - offset * 2.5; + double l_d_y = l_y - offset; + + + if (TimeFrame == TimeFrame.Minute) + { + h_y = high + offset / 2; + h_d_y = h_y + offset / 2; + h_t_y = h_d_y + offset; + + l_y = low - offset / 2; + l_d_y = l_y - offset / 2; + } + + + + if (TimeFrame == TimeFrame.Minute15) + { + h_y = high + offset / 1.5; + h_d_y = h_y + offset; + h_t_y = h_d_y + offset; + + l_y = low - offset * 1.5; + l_d_y = l_y - offset * 1.1; + } + + + if (TimeFrame == TimeFrame.Hour) + { + h_y = high + offset; + h_d_y = h_y + offset * 3; + h_t_y = h_d_y + offset; + + l_y = low - offset * 3; + l_d_y = l_y - offset * 3; + } + + if (TimeFrame == TimeFrame.Hour4) + { + h_y = high + offset; + h_d_y = h_y + offset * 5; + h_t_y = h_d_y + offset; + + l_y = low - offset * 5; + l_d_y = l_y - offset * 5; + } + + if (TimeFrame == TimeFrame.Daily) + { + h_y = high + offset * 3; + h_d_y = h_y + offset * 12; + h_t_y = h_d_y + offset; + + l_y = low - offset * 12; + l_d_y = l_y - offset * 12; + } + + if (TimeFrame == TimeFrame.Weekly) + { + h_y = high + offset * 6; + h_d_y = h_y + offset * 24; + h_t_y = h_d_y + offset; + + l_y = low - offset * 24; + l_d_y = l_y - offset * 24; + } + + if (TimeFrame == TimeFrame.Monthly) + { + h_y = high + offset * 24; + h_d_y = h_y + offset * 64; + h_t_y = h_d_y + offset; + + l_y = low - offset * 64; + l_d_y = l_y - offset * 64; + } + + string f_ObjName; + string s_ObjName; + + switch (_Type) + { + case (int)PatternType.Doji: + + f_ObjName = string.Format("Doji {0}", index); + s_ObjName = string.Format("Doji | {0}", index); + + ChartObjects.DrawText(f_ObjName, "Doji", x, h_d_y, vAlign, hAlign, Colors.White); + ChartObjects.DrawText(s_ObjName, "\n|", x, h_y, vAlign, hAlign, Colors.White); + + break; + + case (int)PatternType.Hammer: + + f_ObjName = string.Format("Hammer {0}", index); + s_ObjName = string.Format("Hammer | {0}", index); + + ChartObjects.DrawText(f_ObjName, "Hammer", x, h_d_y, vAlign, hAlign, Colors.White); + ChartObjects.DrawText(s_ObjName, "\n|", x, h_y, vAlign, hAlign, Colors.White); + + break; + + } + } + + private void DrawSymbols(int index, int _Type) + { + var high = MarketSeries.High[index]; + var low = MarketSeries.Low[index]; + + int x = index; + + double h_y = high + offset; + double h_d_y = h_y + offset * 2.5; + double h_t_y = h_d_y + offset; + + double l_y = low - offset * 2.5; + double l_d_y = l_y - offset; + + + if (TimeFrame == TimeFrame.Minute) + { + h_y = high + offset / 2; + h_d_y = h_y + offset / 2; + h_t_y = h_d_y + offset; + + l_y = low - offset / 2; + l_d_y = l_y - offset / 2; + } + + + + if (TimeFrame == TimeFrame.Minute15) + { + h_y = high + offset / 1.5; + h_d_y = h_y + offset; + h_t_y = h_d_y + offset; + + l_y = low - offset * 1.5; + l_d_y = l_y - offset * 1.1; + } + + + if (TimeFrame == TimeFrame.Hour) + { + h_y = high + offset; + h_d_y = h_y + offset * 3; + h_t_y = h_d_y + offset; + + l_y = low - offset * 3; + l_d_y = l_y - offset * 3; + } + + if (TimeFrame == TimeFrame.Hour4) + { + h_y = high + offset; + h_d_y = h_y + offset * 5; + h_t_y = h_d_y + offset; + + l_y = low - offset * 5; + l_d_y = l_y - offset * 5; + } + + if (TimeFrame == TimeFrame.Daily) + { + h_y = high + offset * 3; + h_d_y = h_y + offset * 12; + h_t_y = h_d_y + offset; + + l_y = low - offset * 12; + l_d_y = l_y - offset * 12; + } + + if (TimeFrame == TimeFrame.Weekly) + { + h_y = high + offset * 6; + h_d_y = h_y + offset * 24; + h_t_y = h_d_y + offset; + + l_y = low - offset * 24; + l_d_y = l_y - offset * 24; + } + + if (TimeFrame == TimeFrame.Monthly) + { + h_y = high + offset * 24; + h_d_y = h_y + offset * 64; + h_t_y = h_d_y + offset; + + l_y = low - offset * 64; + l_d_y = l_y - offset * 64; + } + + string f_ObjName; + string s_ObjName; + + switch (_Type) + { + case (int)PatternType.Doji: + + f_ObjName = string.Format("Doji {0}", index); + s_ObjName = string.Format("Doji | {0}", index); + + ChartObjects.DrawText(f_ObjName, Doji_s, x, h_d_y, vAlign, hAlign, Colors.White); + ChartObjects.DrawText(s_ObjName, "\n|", x, h_y, vAlign, hAlign, Colors.White); + + break; + + case (int)PatternType.Hammer: + + f_ObjName = string.Format("Hammer {0}", index); + s_ObjName = string.Format("Hammer | {0}", index); + + ChartObjects.DrawText(f_ObjName, Hammer_s, x, h_d_y, vAlign, hAlign, Colors.DarkOrange); + ChartObjects.DrawText(s_ObjName, "\n|", x, h_y, vAlign, hAlign, Colors.White); + + break; + + } + } + + + private bool Size(Candle candle) + { + if (candle.IsFallCandle()) + { + if ((candle.Open - candle.Close) > (candle.High - candle.Low) * n) + return true; + } + else if (candle.IsRiseCandle()) + { + if ((candle.Close - candle.Open) > (candle.High - candle.Low) * n) + return true; + } + + return false; + } + + private void Text_Info(int index, string Pattern_name) + { + if (Add_info == true) + { + + #region Pattern + + ChartObjects.DrawText("PATTERN", "LAST PATTERN : " + Pattern_name, StaticPosition.TopLeft, Colors.White); + + #endregion Pattern + + #region MACD + + if (MACD_Info(index) == "UP") + { + ChartObjects.DrawText("MACD", "\nMACD : ", StaticPosition.TopLeft, Colors.White); + ChartObjects.DrawText("ARROW MACD", "\n\t" + UpArrow, StaticPosition.TopLeft, Colors.DodgerBlue); + + } + else if (MACD_Info(index) == "DOWN") + { + ChartObjects.DrawText("MACD", "\nMACD : ", StaticPosition.TopLeft, Colors.White); + ChartObjects.DrawText("ARROW MACD", "\n\t" + DownArrow, StaticPosition.TopLeft, Colors.Crimson); + } + + #endregion MACD + + } + } + + private string MACD_Info(int index) + { + if (macd.Histogram[index] > 0) + { + return "UP"; + } + + if (macd.Histogram[index] < 0) + { + return "DOWN"; + } + return "ZERO"; + } + + + private bool Doji(Candle candle) + { + + if (candle.IsFallCandle() & (candle.Close != candle.Low)) + { + if ((candle.High - candle.Low) > 12 * (candle.Open - candle.Close)) + return true; + } + if (candle.IsRiseCandle() & (candle.Close != candle.High)) + { + if ((candle.High - candle.Low) > 12 * (candle.Close - candle.Open)) + return true; + } + return false; + } + + private bool Hammer(Candle candle) + { + if (candle.IsFallCandle() & (candle.Close == candle.Low)) + { + if ((candle.Median() > candle.Open) & (candle.Median() > candle.Close)) + { + if ((candle.Median() - candle.Low) > 1.618 * (candle.Open - candle.Close)) + return true; + } + } + + if (candle.IsRiseCandle() & (candle.Close == candle.High)) + { + if ((candle.Median() < candle.Open) & (candle.Median() < candle.Close)) + { + if ((candle.High - candle.Median()) > 1.618 * (candle.Close - candle.Open)) + return true; + } + } + + return false; + } + + public override void Calculate(int index) + { + #region structs + + Candle candle = new Candle(); + + #endregion structs + + #region variables + + int candle_index = 0; + + candle.High = MarketSeries.High.Last(candle_index); + candle.Open = MarketSeries.Open.Last(candle_index); + candle.Close = MarketSeries.Close.Last(candle_index); + candle.Low = MarketSeries.Low.Last(candle_index); + + #endregion variables + + #region Patterns + + // Doji + + if (Doji(candle)) + { + if (Show_Doji == true) + { + Pattern_name = "Doji"; + if (Name_Status == true) + { + DrawText(index, (int)PatternType.Doji); + } + else + { + DrawSymbols(index, (int)PatternType.Doji); + } + } + } + + //Hammer + + if (Hammer(candle)) + { + if (Show_Hammer == true) + { + Pattern_name = "Hammer"; + if (Name_Status == true) + { + DrawText(index, (int)PatternType.Hammer); + } + else + { + DrawSymbols(index, (int)PatternType.Hammer); + } + } + } + + #endregion Patterns + + #region Text + + Text_Info(index, Pattern_name); + + #endregion Text + } + } +} diff --git a/Sources/Indicators/Candlestick Patterns DEMO/Candlestick Patterns DEMO/Candlestick Patterns DEMO.csproj b/Sources/Indicators/Candlestick Patterns DEMO/Candlestick Patterns DEMO/Candlestick Patterns DEMO.csproj new file mode 100644 index 0000000..bfeb7d6 --- /dev/null +++ b/Sources/Indicators/Candlestick Patterns DEMO/Candlestick Patterns DEMO/Candlestick Patterns DEMO.csproj @@ -0,0 +1,58 @@ + + + + + Debug + AnyCPU + {B9F710D0-7DD5-4E04-B2E9-98C1878E7674} + {DD87C1B2-3799-4CA2-93B6-5288EE928820};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + Library + Properties + cAlgo + Candlestick Patterns DEMO + v4.0 + Client + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + False + ..\..\..\..\API\cAlgo.API.dll + + + + + + + + + \ No newline at end of file diff --git a/Sources/Indicators/Candlestick Patterns DEMO/Candlestick Patterns DEMO/Properties/AssemblyInfo.cs b/Sources/Indicators/Candlestick Patterns DEMO/Candlestick Patterns DEMO/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..814d8b2 --- /dev/null +++ b/Sources/Indicators/Candlestick Patterns DEMO/Candlestick Patterns DEMO/Properties/AssemblyInfo.cs @@ -0,0 +1,16 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("Candlestick Patterns DEMO")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyProduct("Candlestick Patterns DEMO")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +[assembly: ComVisible(false)] + +[assembly: Guid("35f19c43-c7cc-4f53-b8e0-61caaf9cd8e8")] + +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] \ No newline at end of file diff --git a/Sources/Indicators/Correlation with angle v1.1.algo b/Sources/Indicators/Correlation with angle v1.1.algo new file mode 100644 index 0000000..4f167e9 Binary files /dev/null and b/Sources/Indicators/Correlation with angle v1.1.algo differ diff --git a/Sources/Indicators/Correlation with angle v1.1/Correlation with angle v1.1.sln b/Sources/Indicators/Correlation with angle v1.1/Correlation with angle v1.1.sln new file mode 100644 index 0000000..f4ad4f5 --- /dev/null +++ b/Sources/Indicators/Correlation with angle v1.1/Correlation with angle v1.1.sln @@ -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}") = "Correlation with angle v1.1", "Correlation with angle v1.1\Correlation with angle v1.1.csproj", "{0e2c43ed-8f8d-446b-848e-ed1fe9ef49a2}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {0e2c43ed-8f8d-446b-848e-ed1fe9ef49a2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0e2c43ed-8f8d-446b-848e-ed1fe9ef49a2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0e2c43ed-8f8d-446b-848e-ed1fe9ef49a2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0e2c43ed-8f8d-446b-848e-ed1fe9ef49a2}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/Sources/Indicators/Correlation with angle v1.1/Correlation with angle v1.1/Correlation with angle v1.1.cs b/Sources/Indicators/Correlation with angle v1.1/Correlation with angle v1.1/Correlation with angle v1.1.cs new file mode 100644 index 0000000..901975e --- /dev/null +++ b/Sources/Indicators/Correlation with angle v1.1/Correlation with angle v1.1/Correlation with angle v1.1.cs @@ -0,0 +1,249 @@ +using System; +using System.Linq; +using System.Threading; +using cAlgo.API; +using cAlgo.API.Indicators; +using cAlgo.API.Internals; +using cAlgo.Indicators; +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace cAlgo +{ + [Indicator(IsOverlay = false, TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)] + public class Correlationwithanglev11 : Indicator + { + [Parameter("Symbol Selection Method", DefaultValue = SymbolSelectionMethodType.SymbolList, Group = "Symbole Choice \n(Uncomment code for more than 6 Symbole)")] + public SymbolSelectionMethodType SymbolSelectionMethod { get; set; } + public enum SymbolSelectionMethodType + { + WatchList, + SymbolList + } + + [Parameter("Symbol List 1 / WatchList Name 1:", DefaultValue = "XAUUSD", Group = "Symbol Management (5 Symbol / List Maximum)")] + public string TradedSymbols1 { get; set; } + [Parameter("Symbol List 2 / WatchList Name 2:", DefaultValue = "SpotBrent", Group = "Symbol Management (5 Symbol / List Maximum)")] + public string TradedSymbols2 { get; set; } + + [Parameter("ExtraLevel", DefaultValue = 50, Group = "Angle Level")] + public int ExtraLevel { get; set; } + + [Parameter("PeriodA ngle", DefaultValue = 14, Group = "Angle Setting")] + public int PeriodAngle { get; set; } + [Parameter("Loockback Periods Angle", DefaultValue = 1, Group = "Angle Setting")] + public int LookbackAngle { get; set; } + [Parameter("Price Smooth Type", DefaultValue = MovingAverageType.Weighted, Group = "Angle Setting")] + public MovingAverageType MaTypeAngle { get; set; } + + [Parameter("History Diff Angle", DefaultValue = 1, Group = "Base Setting")] + public int HistoryTextLookback { get; set; } + + [Parameter("ShowSignals", DefaultValue = true, Group = "Histogram Setting")] + public bool ShowSignal { get; set; } + [Parameter("Sensibility Histogram", DefaultValue = 0.3, Group = "Histogram Setting")] + public double SensibilityHisto { get; set; } + + [Parameter("Signal Periods", DefaultValue = 55, Group = "Histogram Setting")] + public int SignalPeriods { get; set; } + [Parameter("Price Smooth Type", DefaultValue = MovingAverageType.Weighted, Group = "Histogram Setting")] + public MovingAverageType MaTypeSignal { get; set; } + + [Output("List 1 : Symb n° 1", LineColor = "Lime", PlotType = PlotType.Line, LineStyle = LineStyle.Solid, Thickness = 1)] + public IndicatorDataSeries SymbN1 { get; set; } + [Output("List 1 : Symb n° 2", LineColor = "Lime", PlotType = PlotType.Line, LineStyle = LineStyle.Solid, Thickness = 1)] + public IndicatorDataSeries SymbN2 { get; set; } + [Output("List 1 : Symb n° 3", LineColor = "Lime", PlotType = PlotType.Line, LineStyle = LineStyle.Solid, Thickness = 1)] + public IndicatorDataSeries SymbN3 { get; set; } + [Output("List 1 : Symb n° 4", LineColor = "Lime", PlotType = PlotType.Line, LineStyle = LineStyle.Solid, Thickness = 1)] + public IndicatorDataSeries SymbN4 { get; set; } + [Output("List 1 : Symb n° 5", LineColor = "Lime", PlotType = PlotType.Line, LineStyle = LineStyle.Solid, Thickness = 1)] + public IndicatorDataSeries SymbN5 { get; set; } + + [Output("List 2 : Symb n° 6", LineColor = "Red", PlotType = PlotType.Line, LineStyle = LineStyle.Solid, Thickness = 1)] + public IndicatorDataSeries SymbN6 { get; set; } + [Output("List 2 : Symb n° 7", LineColor = "Red", PlotType = PlotType.Line, LineStyle = LineStyle.Solid, Thickness = 1)] + public IndicatorDataSeries SymbN7 { get; set; } + [Output("List 2 : Symb n° 8", LineColor = "Red", PlotType = PlotType.Line, LineStyle = LineStyle.Solid, Thickness = 1)] + public IndicatorDataSeries SymbN8 { get; set; } + [Output("List 2 : Symb n° 9", LineColor = "Red", PlotType = PlotType.Line, LineStyle = LineStyle.Solid, Thickness = 1)] + public IndicatorDataSeries SymbN9 { get; set; } + [Output("List 2 : Symb n° 10", LineColor = "Red", PlotType = PlotType.Line, LineStyle = LineStyle.Solid, Thickness = 1)] + public IndicatorDataSeries SymbN10 { get; set; } + + [Output("LevelHigh", LineColor = "Gold", PlotType = PlotType.Line, LineStyle = LineStyle.Solid, Thickness = 1)] + public IndicatorDataSeries LevelHigh { get; set; } + [Output("LevelMid", LineColor = "White", PlotType = PlotType.Line, LineStyle = LineStyle.Solid, Thickness = 1)] + public IndicatorDataSeries LevelMid { get; set; } + [Output("LevelLow", LineColor = "Gold", PlotType = PlotType.Line, LineStyle = LineStyle.Solid, Thickness = 1)] + public IndicatorDataSeries LevelLow { get; set; } + + private MovingAverage[] angleIndex1, angleIndex2; + private AverageTrueRange[] atrIndex1, atrIndex2; + + private Symbol[] TradeList1, TradeList2; + private Bars[] bars1, bars2; + private IndicatorDataSeries[] ResPaireWatchlist1, ResPaireWatchlist2; + private int[] indexBars1, indexBars2; + + protected override void Initialize() + { + if (SymbolSelectionMethod == SymbolSelectionMethodType.WatchList) + { + // Get the trade list from the watchlist 1 provided by the user + foreach (Watchlist w in Watchlists) + { + if (w.Name == TradedSymbols1) + { + TradeList1 = Symbols.GetSymbols(w.SymbolNames.ToArray()); + } + } + // Get the trade list from the watchlist 2 provided by the user + foreach (Watchlist w in Watchlists) + { + if (w.Name == TradedSymbols2) + { + TradeList2 = Symbols.GetSymbols(w.SymbolNames.ToArray()); + } + } + Print(); + Print("Watchlist 1 : [ {0} ] = {1} symbols || Watchlist 2 : [ {2} ] = {3} symbols", Bars.ToString().Substring(0, 3), TradeList1.Length, Bars.ToString().Substring(3, 3), TradeList2.Length); + Print(); + } + else if (SymbolSelectionMethod == SymbolSelectionMethodType.SymbolList) + { + // Get the trade list from the SymbolList provided by the user + string[] SymbolList = TradedSymbols1.Split(' '); + TradeList1 = Symbols.GetSymbols(SymbolList); + + // Get the trade list from the SymbolList provided by the user + string[] SymbolList2 = TradedSymbols2.Split(' '); + TradeList2 = Symbols.GetSymbols(SymbolList2); + } + + //Create Indicators for Watchlist 1 + angleIndex1 = new MovingAverage[TradeList1.Length]; + atrIndex1 = new AverageTrueRange[TradeList1.Length]; + bars1 = new Bars[TradeList1.Length]; + indexBars1 = new int[TradeList1.Length]; + ResPaireWatchlist1 = new IndicatorDataSeries[TradeList1.Length]; + + //Create Indicators for Watchlist 2 + angleIndex2 = new MovingAverage[TradeList2.Length]; + atrIndex2 = new AverageTrueRange[TradeList2.Length]; + bars2 = new Bars[TradeList2.Length]; + indexBars2 = new int[TradeList2.Length]; + ResPaireWatchlist2 = new IndicatorDataSeries[TradeList2.Length]; + + //Initialize Indicators for Watchlist 1 + int i = 0; + foreach (var symbol in TradeList1) + { + Print("Watchlist : [ {0} ] => {1} symbols = {2}", Bars.SymbolName, (i + 1), symbol.Name); + //Load bars of watchlist 1 + bars1[i] = MarketData.GetBars(TimeFrame, symbol.Name); + while (bars1[i].OpenTimes[0] > Bars.OpenTimes[0]) + bars1[i].LoadMoreHistory(); + + //Load Futurs DataSerie watchlist 1 (calculation in Calculate(int index)) + ResPaireWatchlist1[i] = CreateDataSeries(); + + //Load Indicators watchlist 1 + + atrIndex1[i] = Indicators.AverageTrueRange(bars1[i], 500, MovingAverageType.Simple); + angleIndex1[i] = Indicators.MovingAverage(bars1[i].ClosePrices, PeriodAngle, MaTypeAngle); + i++; + } + //Initialize Indicators for Watchlist 2 + Print(); + + int j = 0; + foreach (var symbol2 in TradeList2) + { + Print("Watchlist : [ {0} ] => {1} symbols = {2}", Bars.SymbolName, (j + 1), symbol2.Name); + //Load bars of watchlist 2 + bars2[j] = MarketData.GetBars(TimeFrame, symbol2.Name); + while (bars2[j].OpenTimes[0] > Bars.OpenTimes[0]) + bars2[j].LoadMoreHistory(); + + //Load Futurs DataSerie watchlist 2 (calculation in Calculate(int index)) + ResPaireWatchlist2[j] = CreateDataSeries(); + + //Load Indicators watchlist 2 + atrIndex2[j] = Indicators.AverageTrueRange(bars2[j], 500, MovingAverageType.Simple); + angleIndex2[j] = Indicators.MovingAverage(bars2[j].ClosePrices, PeriodAngle, MaTypeAngle); + j++; + } + Print(); + } + public override void Calculate(int index) + { + //Plot Static level on indicator + LevelHigh[index] = ExtraLevel; + LevelMid[index] = 0; + LevelLow[index] = 0 - ExtraLevel; + + if (index < PeriodAngle) + return; + + //Calculate angle by symbol list 1 + for (int i = 0; i < TradeList1.Length; i++) + { + indexBars1[i] = bars1[i].OpenTimes.GetIndexByTime(Bars.OpenTimes[index]); + ResPaireWatchlist1[i][index] = GetCalculationAngle(angleIndex1[i].Result[indexBars1[i]], angleIndex1[i].Result[indexBars1[i] - LookbackAngle], atrIndex1[i].Result[indexBars1[i]]); + } + + //Remove Static text of difference into index and index - HistoryTextLookback + if (IndicatorArea.FindAllObjects(ChartObjectType.Text).Length > 0) + IndicatorArea.RemoveAllObjects(); + //Calculate difference into index and index - HistoryTextLookback list 1 + for (int i = 0; i < TradeList1.Length; i++) + IndicatorArea.DrawText((TradeList1[i]).ToString() + index, TradeList1[i] + " : " + ResPaireWatchlist1[i][index].ToString("F2"), index + 2, double.IsNaN(ResPaireWatchlist1[i][index]) ? 0 : ResPaireWatchlist1[i][index], ResPaireWatchlist1[i][index] > ResPaireWatchlist1[i][index - HistoryTextLookback] ? Color.Lime : Color.Red); + + //Output the result like symbolList number != output number list 1 + int j = TradeList1.Length - 1; + SymbN1[index] = ResPaireWatchlist1[0 < j ? 0 : j][index]; + SymbN2[index] = ResPaireWatchlist1[1 < j ? 1 : j][index]; + SymbN3[index] = ResPaireWatchlist1[2 < j ? 2 : j][index]; + SymbN4[index] = ResPaireWatchlist1[3 < j ? 3 : j][index]; + SymbN5[index] = ResPaireWatchlist1[4 < j ? 4 : j][index]; + + //Calculate angle by symbol list 2 + for (int i = 0; i < TradeList2.Length; i++) + { + indexBars2[i] = bars2[i].OpenTimes.GetIndexByTime(Bars.OpenTimes[index]); + ResPaireWatchlist2[i][index] = GetCalculationAngle(angleIndex2[i].Result[indexBars2[i]], angleIndex2[i].Result[indexBars2[i] - LookbackAngle], atrIndex2[i].Result[indexBars2[i]]); + } + + //Calculate difference into index and index - HistoryTextLookback list 2 + for (int i = 0; i < TradeList2.Length; i++) + IndicatorArea.DrawText((TradeList2[i]).ToString() + index, TradeList2[i] + " : " + ResPaireWatchlist2[i][index].ToString("F2"), index + 2, double.IsNaN(ResPaireWatchlist2[i][index]) ? 0 : ResPaireWatchlist2[i][index], ResPaireWatchlist2[i][index] > ResPaireWatchlist2[i][index - HistoryTextLookback] ? Color.Lime : Color.Red); + + //Output the result like symbolList number != output number list 2 + int k = TradeList2.Length - 1; + SymbN6[index] = ResPaireWatchlist2[0 < k ? 0 : k][index]; + SymbN7[index] = ResPaireWatchlist2[1 < k ? 1 : k][index]; + SymbN8[index] = ResPaireWatchlist2[2 < k ? 2 : k][index]; + SymbN9[index] = ResPaireWatchlist2[3 < k ? 3 : k][index]; + SymbN10[index] = ResPaireWatchlist2[4 < k ? 4 : k][index]; + + } + + //Function for angle calculation with atr normalization + public double GetCalculationAngle(double priceSmooth, double priceSmoothLoockBack, double atr) + { + var _momentumpositive = priceSmooth - priceSmoothLoockBack; + var _momentumnegative = priceSmoothLoockBack - priceSmooth; + var _momentum = priceSmooth > priceSmoothLoockBack + ? _momentumpositive / atr + : _momentumnegative / atr; + var _hypothenuse = Math.Sqrt((_momentum * _momentum) + (LookbackAngle * LookbackAngle)); + var _cos = (LookbackAngle / _hypothenuse); + var _angle = priceSmooth > priceSmoothLoockBack + ? (0 + (Math.Acos(_cos) * 100)) + : (0 - (Math.Acos(_cos) * 100)); + return _angle; + } + } +} diff --git a/Sources/Indicators/Correlation with angle v1.1/Correlation with angle v1.1/Correlation with angle v1.1.csproj b/Sources/Indicators/Correlation with angle v1.1/Correlation with angle v1.1/Correlation with angle v1.1.csproj new file mode 100644 index 0000000..4040d8c --- /dev/null +++ b/Sources/Indicators/Correlation with angle v1.1/Correlation with angle v1.1/Correlation with angle v1.1.csproj @@ -0,0 +1,9 @@ + + + net6.0 + + + + + + diff --git a/Sources/Indicators/Correlation with angle v1.1/msAngle of MultiSymbol.cs b/Sources/Indicators/Correlation with angle v1.1/msAngle of MultiSymbol.cs new file mode 100644 index 0000000..c1cd75e --- /dev/null +++ b/Sources/Indicators/Correlation with angle v1.1/msAngle of MultiSymbol.cs @@ -0,0 +1,182 @@ +using System; +using System.Linq; +using System.Threading; +using cAlgo.API; +using cAlgo.API.Indicators; +using cAlgo.API.Internals; +using cAlgo.Indicators; +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace cAlgo +{ + //[Cloud("Fast Smooth", "Slow Smooth", FirstColor = "Green", SecondColor = "Red", Opacity = 0.1)] + [Levels(0)] + [Indicator(IsOverlay = false, TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)] + public class AngleOfMultiSymbol : Indicator + { + [Parameter("Symbol Selection Method", DefaultValue = SymbolSelectionMethodType.SymbolList, Group = "Symbole Choice \n(Uncomment code for more than 6 Symbole)")] + public SymbolSelectionMethodType SymbolSelectionMethod { get; set; } + [Parameter("Symbol List", DefaultValue = "EURUSD GBPUSD AUDUSD USDCHF", Group = "Symbole Choice \n(Uncomment code for more than 6 Symbole)")] + public string TradedSymbols { get; set; } + [Parameter("Watchlist Name", DefaultValue = "My Watchlist", Group = "Symbole Choice \n(Uncomment code for more than 6 Symbole)")] + public string WatchlistName { get; set; } + + public enum SymbolSelectionMethodType + { + CurrentChart, + SymbolList, + WatchList + } + + [Parameter("History Diff Angle", DefaultValue = 1, Group = "Base Setting")] + public int HistoryTextLookback { get; set; } + [Parameter("Tf", DefaultValue = "Hour1", Group = "Base Setting")] + public TimeFrame Tf { get; set; } + [Parameter("Price Smooth Period (255)", DefaultValue = 255, Group = "Angle Setting")] + public int SmoothPeriods { get; set; } + [Parameter("Price Smooth Type", DefaultValue = MovingAverageType.Weighted, Group = "Angle Setting")] + public MovingAverageType MaType { get; set; } + [Parameter("Loockback Periods Angle", DefaultValue = 1, Group = "Angle Setting")] + public int LookbackPeriodsAngle { get; set; } + [Parameter("Sensitivity (1.0)", DefaultValue = 1, Group = "Angle Setting")] + public double Sensitivity { get; set; } + + [Output("Symb n° 1", LineColor = "White", PlotType = PlotType.Line, LineStyle = LineStyle.Solid, Thickness = 1)] + public IndicatorDataSeries SymbN1 { get; set; } + [Output("Symb n° 2", LineColor = "Lime", PlotType = PlotType.Line, LineStyle = LineStyle.Solid, Thickness = 1)] + public IndicatorDataSeries SymbN2 { get; set; } + [Output("Symb n° 3", LineColor = "Green", PlotType = PlotType.Line, LineStyle = LineStyle.Solid, Thickness = 1)] + public IndicatorDataSeries SymbN3 { get; set; } + [Output("Symb n° 4", LineColor = "DeepSkyBlue", PlotType = PlotType.Line, LineStyle = LineStyle.Solid, Thickness = 1)] + public IndicatorDataSeries SymbN4 { get; set; } + [Output("Symb n° 5", LineColor = "Red", PlotType = PlotType.Line, LineStyle = LineStyle.Solid, Thickness = 1)] + public IndicatorDataSeries SymbN5 { get; set; } + [Output("Symb n° 6", LineColor = "Magenta", PlotType = PlotType.Line, LineStyle = LineStyle.Solid, Thickness = 1)] + public IndicatorDataSeries SymbN6 { get; set; } + + /*Uncomment For more Symbole on the chart + [Output("Symb n° 7", LineColor = "Green", PlotType = PlotType.Line, LineStyle = LineStyle.Solid, Thickness = 1)] + public IndicatorDataSeries SymbN7 { get; set; } + [Output("Symb n° 8", LineColor = "DeepSkyBlue", PlotType = PlotType.Line, LineStyle = LineStyle.Solid, Thickness = 1)] + public IndicatorDataSeries SymbN8 { get; set; } + [Output("Symb n° 9", LineColor = "Red", PlotType = PlotType.Line, LineStyle = LineStyle.Solid, Thickness = 1)] + public IndicatorDataSeries SymbN9 { get; set; } + [Output("Symb n° 10", LineColor = "Magenta", PlotType = PlotType.Line, LineStyle = LineStyle.Solid, Thickness = 1)] + public IndicatorDataSeries SymbN10 { get; set; } + */ + + private MovingAverage[] ma; + private AverageTrueRange[] atr; + + private Symbol[] TradeList; + private Bars[] bars; + private IndicatorDataSeries[] Sources, ResSymbol; + private int[] indexBars; + + protected override void Initialize() + { + if (SymbolSelectionMethod == SymbolSelectionMethodType.WatchList) + { + // Get the trade list from the watchlist provided by the user + foreach (Watchlist w in Watchlists) + { + if (w.Name == WatchlistName) + { + TradeList = Symbols.GetSymbols(w.SymbolNames.ToArray()); + } + } + } + else if (SymbolSelectionMethod == SymbolSelectionMethodType.SymbolList) + { + // Get the trade list from the sysmbol list provided by the user + string[] SymbolList = TradedSymbols.ToUpper().Split(' '); + + TradeList = Symbols.GetSymbols(SymbolList); + } + else + { + TradeList = new Symbol[1]; + TradeList[0] = Symbol; + } + + atr = new AverageTrueRange[TradeList.Length]; + ma = new MovingAverage[TradeList.Length]; + bars = new Bars[TradeList.Length]; + indexBars = new int[TradeList.Length]; + Sources = new IndicatorDataSeries[TradeList.Length]; + ResSymbol = new IndicatorDataSeries[TradeList.Length]; + + Print("{0} traded symbols: ", TradeList.Length); + + int i = 0; + foreach (var symbol in TradeList) + { + Print(symbol.Name); + + bars[i] = MarketData.GetBars(Tf, symbol.Name); + if (bars[i].OpenTimes[0] > Bars.OpenTimes[0]) + bars[i].LoadMoreHistory(); + //Load indicators on start up EP5-ATR + Sources[i] = CreateDataSeries(); + ResSymbol[i] = CreateDataSeries(); + atr[i] = Indicators.AverageTrueRange(bars[i], 500, MovingAverageType.Simple); + + ma[i] = Indicators.MovingAverage(Sources[i], SmoothPeriods, MaType); + i++; + } + } + + public override void Calculate(int index) + { + if (index < SmoothPeriods) + return; + + for (int i = 0; i < TradeList.Length; i++) + { + //indexBars[i] = GetIndexByDate(bars[i], Bars.OpenTimes[index]); + + indexBars[i] = bars[i].OpenTimes.GetIndexByTime(Bars.OpenTimes[index]); + Sources[i][index] = bars[i].ClosePrices[indexBars[i]]; + ResSymbol[i][index] = GetCalculationSymbol(ma[i].Result[index], ma[i].Result[index - LookbackPeriodsAngle], atr[i].Result[indexBars[i]]); + } + + SymbN1[index] = ResSymbol[0][index]; + SymbN2[index] = ResSymbol[1][index]; + SymbN3[index] = ResSymbol[2][index]; + SymbN4[index] = ResSymbol[3][index]; + SymbN5[index] = ResSymbol[4][index]; + SymbN6[index] = ResSymbol[5][index]; + + /* Uncomment For More Symbols on chart + SymbN7[index] = ResSymbol[2][index]; + SymbN8[index] = ResSymbol[3][index]; + SymbN9[index] = ResSymbol[4][index]; + SymbN10[index] = ResSymbol[5][index]; + */ + + IndicatorArea.RemoveAllObjects(); + + for (int i = 0; i < TradeList.Length; i++) + { + IndicatorArea.DrawText((TradeList[i]).ToString() + index, TradeList[i] + " : " + ResSymbol[i][index].ToString("F2"), index, ResSymbol[i][index], ResSymbol[i][index] > ResSymbol[i][index - HistoryTextLookback] ? Color.Lime : Color.Red); + } + + } + public double GetCalculationSymbol(double priceSmooth, double priceSmoothLoockBack, double atr) + { + var _momentumpositive = priceSmooth - priceSmoothLoockBack; + var _momentumnegative = priceSmoothLoockBack - priceSmooth; + var _momentum = priceSmooth > priceSmoothLoockBack + ? _momentumpositive / atr + : _momentumnegative / atr; + var _hypothenuse = Math.Sqrt((_momentum * _momentum) + (LookbackPeriodsAngle * LookbackPeriodsAngle)); + var _cos = (LookbackPeriodsAngle / _hypothenuse); + var _angle = priceSmooth > priceSmoothLoockBack + ? (0 + (Math.Acos(_cos) * 100)) * Sensitivity + : (0 - (Math.Acos(_cos) * 100)) * Sensitivity; + return _angle; + } + } +} diff --git a/Sources/Indicators/Correlation with angle v1.1/msAngle of MultiSymbol.csproj b/Sources/Indicators/Correlation with angle v1.1/msAngle of MultiSymbol.csproj new file mode 100644 index 0000000..4040d8c --- /dev/null +++ b/Sources/Indicators/Correlation with angle v1.1/msAngle of MultiSymbol.csproj @@ -0,0 +1,9 @@ + + + net6.0 + + + + + + diff --git a/Sources/Indicators/Difference.algo b/Sources/Indicators/Difference.algo new file mode 100644 index 0000000..aee3263 Binary files /dev/null and b/Sources/Indicators/Difference.algo differ diff --git a/Sources/Indicators/Difference/Difference.sln b/Sources/Indicators/Difference/Difference.sln new file mode 100644 index 0000000..869f942 --- /dev/null +++ b/Sources/Indicators/Difference/Difference.sln @@ -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}") = "Difference", "Difference\Difference.csproj", "{3ebc950e-1111-4055-9211-b51d7d4ac02e}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {3ebc950e-1111-4055-9211-b51d7d4ac02e}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3ebc950e-1111-4055-9211-b51d7d4ac02e}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3ebc950e-1111-4055-9211-b51d7d4ac02e}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3ebc950e-1111-4055-9211-b51d7d4ac02e}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/Sources/Indicators/Difference/Difference/Difference.cs b/Sources/Indicators/Difference/Difference/Difference.cs new file mode 100644 index 0000000..1ce1293 --- /dev/null +++ b/Sources/Indicators/Difference/Difference/Difference.cs @@ -0,0 +1,32 @@ +using System; +using cAlgo.API; +using cAlgo.API.Internals; + +namespace cAlgo.Indicators +{ + // Calculates the difference or absolute distance between two data sources + [Indicator(IsOverlay = false, TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)] + public class DifferenceIndicator : Indicator + { + [Parameter("Source 1")] + public DataSeries Source1 { get; set; } + + [Parameter("Source 2")] + public DataSeries Source2 { get; set; } + + [Parameter("Use Absolute Distance", DefaultValue = false)] + public bool UseAbsoluteDistance { get; set; } + + [Output("Result", LineColor = "MainColor", PlotType = PlotType.Line, Thickness = 1)] + public IndicatorDataSeries Result { get; set; } + + // Core calculation logic + public override void Calculate(int index) + { + double diff = Source1[index] - Source2[index]; + + // Result is either raw difference or absolute distance + Result[index] = UseAbsoluteDistance ? Math.Abs(diff) : diff; + } + } +} \ No newline at end of file diff --git a/Sources/Indicators/Difference/Difference/Difference.csproj b/Sources/Indicators/Difference/Difference/Difference.csproj new file mode 100644 index 0000000..da1fef1 --- /dev/null +++ b/Sources/Indicators/Difference/Difference/Difference.csproj @@ -0,0 +1,9 @@ + + + net6.0 + + + + + + diff --git a/Sources/Indicators/Economic Events On Chart.algo b/Sources/Indicators/Economic Events On Chart.algo new file mode 100644 index 0000000..bd94e70 Binary files /dev/null and b/Sources/Indicators/Economic Events On Chart.algo differ diff --git a/Sources/Indicators/Economic Events On Chart/.gitattributes b/Sources/Indicators/Economic Events On Chart/.gitattributes new file mode 100644 index 0000000..1ff0c42 --- /dev/null +++ b/Sources/Indicators/Economic Events On Chart/.gitattributes @@ -0,0 +1,63 @@ +############################################################################### +# Set default behavior to automatically normalize line endings. +############################################################################### +* text=auto + +############################################################################### +# Set default behavior for command prompt diff. +# +# This is need for earlier builds of msysgit that does not have it on by +# default for csharp files. +# Note: This is only used by command line +############################################################################### +#*.cs diff=csharp + +############################################################################### +# Set the merge driver for project and solution files +# +# Merging from the command prompt will add diff markers to the files if there +# are conflicts (Merging from VS is not affected by the settings below, in VS +# the diff markers are never inserted). Diff markers may cause the following +# file extensions to fail to load in VS. An alternative would be to treat +# these files as binary and thus will always conflict and require user +# intervention with every merge. To do so, just uncomment the entries below +############################################################################### +#*.sln merge=binary +#*.csproj merge=binary +#*.vbproj merge=binary +#*.vcxproj merge=binary +#*.vcproj merge=binary +#*.dbproj merge=binary +#*.fsproj merge=binary +#*.lsproj merge=binary +#*.wixproj merge=binary +#*.modelproj merge=binary +#*.sqlproj merge=binary +#*.wwaproj merge=binary + +############################################################################### +# behavior for image files +# +# image files are treated as binary by default. +############################################################################### +#*.jpg binary +#*.png binary +#*.gif binary + +############################################################################### +# diff behavior for common document formats +# +# Convert binary document formats to text before diffing them. This feature +# is only available from the command line. Turn it on by uncommenting the +# entries below. +############################################################################### +#*.doc diff=astextplain +#*.DOC diff=astextplain +#*.docx diff=astextplain +#*.DOCX diff=astextplain +#*.dot diff=astextplain +#*.DOT diff=astextplain +#*.pdf diff=astextplain +#*.PDF diff=astextplain +#*.rtf diff=astextplain +#*.RTF diff=astextplain diff --git a/Sources/Indicators/Economic Events On Chart/.gitignore b/Sources/Indicators/Economic Events On Chart/.gitignore new file mode 100644 index 0000000..9491a2f --- /dev/null +++ b/Sources/Indicators/Economic Events On Chart/.gitignore @@ -0,0 +1,363 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Oo]ut/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd \ No newline at end of file diff --git a/Sources/Indicators/Economic Events On Chart/Economic Events On Chart.sln b/Sources/Indicators/Economic Events On Chart/Economic Events On Chart.sln new file mode 100644 index 0000000..d5b245c --- /dev/null +++ b/Sources/Indicators/Economic Events On Chart/Economic Events On Chart.sln @@ -0,0 +1,20 @@ + +Microsoft Visual Studio Solution File, Format Version 11.00 +# Visual Studio 2010 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Economic Events On Chart", "Economic Events On Chart\Economic Events On Chart.csproj", "{7D9A4560-D021-4B46-8D58-83DCBEBBE654}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {7D9A4560-D021-4B46-8D58-83DCBEBBE654}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7D9A4560-D021-4B46-8D58-83DCBEBBE654}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7D9A4560-D021-4B46-8D58-83DCBEBBE654}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7D9A4560-D021-4B46-8D58-83DCBEBBE654}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/Sources/Indicators/Economic Events On Chart/Economic Events On Chart/Economic Events On Chart.cs b/Sources/Indicators/Economic Events On Chart/Economic Events On Chart/Economic Events On Chart.cs new file mode 100644 index 0000000..b831773 --- /dev/null +++ b/Sources/Indicators/Economic Events On Chart/Economic Events On Chart/Economic Events On Chart.cs @@ -0,0 +1,345 @@ +using System; +using cAlgo.API; +using System.Collections.Generic; +using System.Xml; +using System.Net; +using System.Xml.Serialization; +using System.IO; +using System.Text; +using System.Linq; +using System.Globalization; + +namespace cAlgo +{ + [Indicator(IsOverlay = true, TimeZone = TimeZones.UTC, AccessRights = AccessRights.Internet)] + public class EconomicEventsOnChart : Indicator + { + private Color _colorHighImpact, _colorMediumImpact, _colorLowImpact, _colorOthers; + + private TextBlock _textBlock; + + [Parameter("Data URI", DefaultValue = "https://nfs.faireconomy.media/ff_calendar_thisweek.xml", Group = "General")] + public string DataUri { get; set; } + + [Parameter("Only Symbol Events", DefaultValue = true, Group = "General")] + public bool OnlySymbolEvents { get; set; } + + [Parameter("Show Past Events", DefaultValue = true, Group = "General")] + public bool ShowPastEvents { get; set; } + + [Parameter("Show", DefaultValue = true, Group = "High Impact")] + public bool ShowHighImpact { get; set; } + + [Parameter("Color", DefaultValue = "Red", Group = "High Impact")] + public string ColorHighImpact { get; set; } + + [Parameter("Style", DefaultValue = LineStyle.Solid, Group = "High Impact")] + public LineStyle LineStyleHighImpact { get; set; } + + [Parameter("Thickness", DefaultValue = 1, Group = "High Impact")] + public int ThicknessHighImpact { get; set; } + + [Parameter("Show", DefaultValue = true, Group = "Medium Impact")] + public bool ShowMediumImpact { get; set; } + + [Parameter("Color", DefaultValue = "Gold", Group = "Medium Impact")] + public string ColorMediumImpact { get; set; } + + [Parameter("Style", DefaultValue = LineStyle.Solid, Group = "Medium Impact")] + public LineStyle LineStyleMediumImpact { get; set; } + + [Parameter("Thickness", DefaultValue = 1, Group = "Medium Impact")] + public int ThicknessMediumImpact { get; set; } + + [Parameter("Show", DefaultValue = true, Group = "Low Impact")] + public bool ShowLowImpact { get; set; } + + [Parameter("Color", DefaultValue = "Yellow", Group = "Low Impact")] + public string ColorLowImpact { get; set; } + + [Parameter("Style", DefaultValue = LineStyle.Solid, Group = "Low Impact")] + public LineStyle LineStyleLowImpact { get; set; } + + [Parameter("Thickness", DefaultValue = 1, Group = "Low Impact")] + public int ThicknessLowImpact { get; set; } + + [Parameter("Show", DefaultValue = false, Group = "Others")] + public bool ShowOthers { get; set; } + + [Parameter("Color", DefaultValue = "Gray", Group = "Others")] + public string ColorOthers { get; set; } + + [Parameter("Style", DefaultValue = LineStyle.Solid, Group = "Others")] + public LineStyle LineStyleOthers { get; set; } + + [Parameter("Thickness", DefaultValue = 1, Group = "Others")] + public int ThicknessOthers { get; set; } + + [Parameter("Show", DefaultValue = true, Group = "Text Block")] + public bool ShowTextBlock { get; set; } + + [Parameter("Background Color", DefaultValue = "#969696", Group = "Text Block")] + public string TextBlockBackgroundColor { get; set; } + + [Parameter("Color", DefaultValue = "White", Group = "Text Block")] + public string TextBlockColor { get; set; } + + [Parameter("Horizontal Alignment", DefaultValue = HorizontalAlignment.Center, Group = "Text Block")] + public HorizontalAlignment TextBlockHorizontalAlignment { get; set; } + + [Parameter("Vertical Alignment", DefaultValue = VerticalAlignment.Bottom, Group = "Text Block")] + public VerticalAlignment TextBlockVerticalAlignment { get; set; } + + [Parameter("Text Alignment", DefaultValue = TextAlignment.Center, Group = "Text Block")] + public TextAlignment TextBlockTextAlignment { get; set; } + + [Parameter("Font Weight", DefaultValue = FontWeight.Bold, Group = "Text Block")] + public FontWeight TextBlockFontWeight { get; set; } + + protected override void Initialize() + { + RemoveEventLines(); + + if (ShowTextBlock) + { + _textBlock = new TextBlock + { + IsVisible = false, + HorizontalAlignment = TextBlockHorizontalAlignment, + VerticalAlignment = TextBlockVerticalAlignment, + BackgroundColor = GetColor(TextBlockBackgroundColor), + ForegroundColor = GetColor(TextBlockColor), + TextAlignment = TextBlockTextAlignment, + FontWeight = TextBlockFontWeight, + Padding = 5 + }; + + Chart.AddControl(_textBlock); + + Chart.ObjectHoverChanged += Chart_ObjectHoverChanged; + } + + _colorHighImpact = GetColor(ColorHighImpact); + _colorMediumImpact = GetColor(ColorMediumImpact); + _colorLowImpact = GetColor(ColorLowImpact); + _colorOthers = GetColor(ColorOthers); + + var events = GetNewsEvents(); + + DisplayEvents(events); + } + + private void Chart_ObjectHoverChanged(ChartObjectHoverChangedEventArgs obj) + { + if (!obj.IsObjectHovered || obj.ChartObject == null || string.IsNullOrWhiteSpace(obj.ChartObject.Name) || !obj.ChartObject.Name.EndsWith("Event", StringComparison.OrdinalIgnoreCase)) + { + _textBlock.IsVisible = false; + + return; + } + + _textBlock.Text = string.Format("{0} | {1}", obj.ChartObject.Name.Replace(" | Event", string.Empty), obj.ChartObject.Comment); + + _textBlock.IsVisible = true; + } + + public override void Calculate(int index) + { + } + + private IEnumerable GetNewsEvents() + { + using (var webClient = new WebClient()) + { + var data = webClient.DownloadString(DataUri); + + return GetNewsEventsFromXml(data); + } + } + + private IEnumerable GetNewsEventsFromXml(string xml) + { + var xmlSerializer = new XmlSerializer(typeof(WeeklyEvents)); + + var stream = new StringReader(xml); + + var weeklyEvents = xmlSerializer.Deserialize(stream) as WeeklyEvents; + + foreach (var newsEvent in weeklyEvents.Events) + { + var timeString = string.Format("{0} {1}", newsEvent.UtcDate, newsEvent.UtcTime); + + DateTimeOffset time; + + if (DateTimeOffset.TryParseExact(timeString, "MM-dd-yyyy h:mmtt", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out time)) + { + newsEvent.Time = time; + } + } + + return weeklyEvents.Events; + } + + private void DisplayEvents(IEnumerable events) + { + foreach (var newsEvent in events) + { + if (!newsEvent.Time.HasValue + || (newsEvent.Impact == NewsEventImpact.High && !ShowHighImpact) + || (newsEvent.Impact == NewsEventImpact.Medium && !ShowMediumImpact) + || (newsEvent.Impact == NewsEventImpact.Low && !ShowLowImpact) + || ((newsEvent.Impact == NewsEventImpact.None || newsEvent.Impact == NewsEventImpact.Holiday) && !ShowOthers) + || (OnlySymbolEvents && !IsEventRelatedToSymbol(newsEvent.Currency)) + || (!ShowPastEvents && newsEvent.Time < Server.TimeInUtc)) continue; + + var lineSettings = GetLineSettings(newsEvent.Impact); + + var eventLine = Chart.DrawVerticalLine(string.Format("{0} | {1} | {2} | Event", newsEvent.Title, newsEvent.Currency, newsEvent.Impact), newsEvent.Time.Value.UtcDateTime, lineSettings.Color, lineSettings.Thickness, lineSettings.Style); + + var stringBuilder = new StringBuilder(); + + if (!string.IsNullOrWhiteSpace(newsEvent.Forecast)) + { + stringBuilder.Append(string.Format("Forecast: {0} | ", newsEvent.Forecast)); + } + + if (!string.IsNullOrWhiteSpace(newsEvent.Previous)) + { + stringBuilder.Append(string.Format("Previous: {0} | ", newsEvent.Previous)); + } + + if (newsEvent.Time.HasValue) + { + var time = newsEvent.Time.Value.ToOffset(Application.UserTimeOffset); + + stringBuilder.Append(string.Format("Time: {0:s}", time)); + } + + eventLine.Comment = stringBuilder.ToString(); + eventLine.IsInteractive = true; + eventLine.IsLocked = true; + } + } + + private bool IsEventRelatedToSymbol(string eventCurrency) + { + return SymbolName.StartsWith(eventCurrency, StringComparison.OrdinalIgnoreCase) || SymbolName.EndsWith(eventCurrency, StringComparison.OrdinalIgnoreCase); + } + + private Color GetColor(string colorString, int alpha = 255) + { + var color = colorString[0] == '#' ? Color.FromHex(colorString) : Color.FromName(colorString); + + return Color.FromArgb(alpha, color); + } + + private LineSettings GetLineSettings(NewsEventImpact impact) + { + switch (impact) + { + case NewsEventImpact.High: + return new LineSettings + { + Color = _colorHighImpact, + Style = LineStyleHighImpact, + Thickness = ThicknessHighImpact + }; + + case NewsEventImpact.Medium: + return new LineSettings + { + Color = _colorMediumImpact, + Style = LineStyleMediumImpact, + Thickness = ThicknessMediumImpact + }; + + case NewsEventImpact.Low: + return new LineSettings + { + Color = _colorLowImpact, + Style = LineStyleLowImpact, + Thickness = ThicknessLowImpact + }; + + default: + return new LineSettings + { + Color = _colorOthers, + Style = LineStyleOthers, + Thickness = ThicknessOthers + }; + } + } + + private void RemoveEventLines() + { + var chartObjects = Chart.Objects.ToArray(); + + foreach (var chartObject in chartObjects) + { + if (chartObject.ObjectType != ChartObjectType.VerticalLine || !chartObject.IsInteractive || string.IsNullOrEmpty(chartObject.Name) || !chartObject.Name.EndsWith("Event", StringComparison.OrdinalIgnoreCase)) + { + continue; + } + + Chart.RemoveObject(chartObject.Name); + } + } + } + + [XmlRoot("weeklyevents")] + public class WeeklyEvents + { + [XmlElement("event")] + public List Events { get; set; } + } + + public class NewsEvent + { + [XmlElement("title")] + public string Title { get; set; } + + [XmlElement("country")] + public string Currency { get; set; } + + [XmlElement("date")] + public string UtcDate { get; set; } + + [XmlElement("time")] + public string UtcTime { get; set; } + + [XmlIgnore] + public DateTimeOffset? Time { get; set; } + + [XmlElement("impact")] + public NewsEventImpact Impact { get; set; } + + [XmlElement("previous")] + public string Previous { get; set; } + + [XmlElement("forecast")] + public string Forecast { get; set; } + } + + public enum NewsEventImpact + { + None, + + High, + + Medium, + + Low, + + Holiday + } + + public struct LineSettings + { + public Color Color { get; set; } + + public LineStyle Style { get; set; } + + public int Thickness { get; set; } + } +} \ No newline at end of file diff --git a/Sources/Indicators/Economic Events On Chart/Economic Events On Chart/Economic Events On Chart.csproj b/Sources/Indicators/Economic Events On Chart/Economic Events On Chart/Economic Events On Chart.csproj new file mode 100644 index 0000000..b2d03cf --- /dev/null +++ b/Sources/Indicators/Economic Events On Chart/Economic Events On Chart/Economic Events On Chart.csproj @@ -0,0 +1,60 @@ + + + + + 7.2 + Debug + AnyCPU + {7D9A4560-D021-4B46-8D58-83DCBEBBE654} + {DD87C1B2-3799-4CA2-93B6-5288EE928820};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + Library + Properties + cAlgo + Economic Events On Chart + v4.0 + Client + 512 + True + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + False + ..\..\..\..\API\cAlgo.API.dll + + + + + + + + + \ No newline at end of file diff --git a/Sources/Indicators/Economic Events On Chart/Economic Events On Chart/Properties/AssemblyInfo.cs b/Sources/Indicators/Economic Events On Chart/Economic Events On Chart/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..5334d9f --- /dev/null +++ b/Sources/Indicators/Economic Events On Chart/Economic Events On Chart/Properties/AssemblyInfo.cs @@ -0,0 +1,20 @@ +using System.Diagnostics; +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("Economic Events On Chart")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyProduct("Economic Events On Chart")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +[assembly: ComVisible(false)] + +[assembly: Guid("35f19c43-c7cc-4f53-b8e0-61caaf9cd8e8")] + +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] +#if DEBUG + [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations)] +#endif \ No newline at end of file diff --git a/Sources/Indicators/HmaClusterDisplay.algo b/Sources/Indicators/HmaClusterDisplay.algo new file mode 100644 index 0000000..17e0f49 Binary files /dev/null and b/Sources/Indicators/HmaClusterDisplay.algo differ diff --git a/Sources/Indicators/HmaClusterDisplay/.vscode/tasks.json b/Sources/Indicators/HmaClusterDisplay/.vscode/tasks.json new file mode 100644 index 0000000..215166e --- /dev/null +++ b/Sources/Indicators/HmaClusterDisplay/.vscode/tasks.json @@ -0,0 +1 @@ +{"version":"2.0.0","tasks":[{"label":"build","command":"dotnet","type":"process","args":["build","${workspaceFolder}","/property:GenerateFullPaths=true","/consoleLoggerParameters:NoSummary"],"problemMatcher":"$msCompile"}]} \ No newline at end of file diff --git a/Sources/Indicators/HmaClusterDisplay/HmaClusterDisplay.sln b/Sources/Indicators/HmaClusterDisplay/HmaClusterDisplay.sln new file mode 100644 index 0000000..6dfb4d0 --- /dev/null +++ b/Sources/Indicators/HmaClusterDisplay/HmaClusterDisplay.sln @@ -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}") = "HmaClusterDisplay", "HmaClusterDisplay\HmaClusterDisplay.csproj", "{9a7d3632-3200-43b4-9d1a-032be8e9a37e}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {9a7d3632-3200-43b4-9d1a-032be8e9a37e}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9a7d3632-3200-43b4-9d1a-032be8e9a37e}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9a7d3632-3200-43b4-9d1a-032be8e9a37e}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9a7d3632-3200-43b4-9d1a-032be8e9a37e}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/Sources/Indicators/HmaClusterDisplay/HmaClusterDisplay/HmaClusterDisplay.cs b/Sources/Indicators/HmaClusterDisplay/HmaClusterDisplay/HmaClusterDisplay.cs new file mode 100644 index 0000000..390e899 --- /dev/null +++ b/Sources/Indicators/HmaClusterDisplay/HmaClusterDisplay/HmaClusterDisplay.cs @@ -0,0 +1,135 @@ +using System; +using System.IO; +using System.Linq; +using cAlgo.API; +using cAlgo.API.Indicators; +using cAlgo.API.Internals; + +namespace cAlgo +{ + [Indicator(IsOverlay = true, TimeZone = TimeZones.UTC, AccessRights = AccessRights.FullAccess)] + public class HmaClusterDisplay : Indicator + { + #region Parameters + + [Parameter("Config File Path", DefaultValue = @"\\COFFEE\cTrader\HmaCluster.txt")] + public string ConfigFilePath { get; set; } + + #endregion + + #region Outputs + + // SMA with Length SmaBias, Col=#FF01FF01, thickness=2 + [Output("SMA Bias", LineColor = "#FF01FF01", Thickness = 2)] + public IndicatorDataSeries SmaBiasOutput { get; set; } + + // HMA with Length HmaBias, Col=#FFFF6666, thickness=2 + [Output("HMA Bias", LineColor = "#FFFF6666", Thickness = 2)] + public IndicatorDataSeries HmaBiasOutput { get; set; } + + // HMA with Length HmaCluster, Col=White, thickness=2 + [Output("HMA Cluster", LineColor = "White", Thickness = 2)] + public IndicatorDataSeries HmaClusterOutput { get; set; } + + #endregion + + #region Variables + + private SimpleMovingAverage _smaBiasIndicator; + private HullMovingAverage _hmaBiasIndicator; + private HullMovingAverage _hmaClusterIndicator; + + // Default values from prompt header + private int _smaBiasLength = 200; + private int _hmaBiasLength = 250; + private int _hmaClusterLength = 25; + + #endregion + + protected override void Initialize() + { + LoadConfiguration(); + + // Initialize indicators with loaded or default values + _smaBiasIndicator = Indicators.SimpleMovingAverage(Bars.ClosePrices, _smaBiasLength); + _hmaBiasIndicator = Indicators.HullMovingAverage(Bars.ClosePrices, _hmaBiasLength); + _hmaClusterIndicator = Indicators.HullMovingAverage(Bars.ClosePrices, _hmaClusterLength); + + Print($"Initialized: {_smaBiasLength} SMA | {_hmaBiasLength} HMA Bias | {_hmaClusterLength} HMA Cluster"); + } + + public override void Calculate(int index) + { + if (_smaBiasIndicator != null) + SmaBiasOutput[index] = _smaBiasIndicator.Result[index]; + + if (_hmaBiasIndicator != null) + HmaBiasOutput[index] = _hmaBiasIndicator.Result[index]; + + if (_hmaClusterIndicator != null) + HmaClusterOutput[index] = _hmaClusterIndicator.Result[index]; + } + + private void LoadConfiguration() + { + if (!File.Exists(ConfigFilePath)) + { + Print($"Config file not found at {ConfigFilePath}. Using defaults."); + return; + } + + try + { + var lines = File.ReadAllLines(ConfigFilePath); + var currentSymbol = SymbolName; + // cTrader ShortName returns "m5", "h1", etc. which matches your file format + var currentTimeframe = TimeFrame.ShortName; + + foreach (var line in lines) + { + var trimmedLine = line.Trim(); + + // Skip comments and empty lines + if (string.IsNullOrWhiteSpace(trimmedLine) || trimmedLine.StartsWith("#")) + continue; + + // Split by whitespace + var columns = trimmedLine.Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries); + + // Ensure we have enough columns (need at least up to index 6) + if (columns.Length < 7) + continue; + + var cfgSymbol = columns[0]; + var cfgTimeframe = columns[1]; + + // Check for match (case-insensitive) + if (string.Equals(cfgSymbol, currentSymbol, StringComparison.OrdinalIgnoreCase) && + string.Equals(cfgTimeframe, currentTimeframe, StringComparison.OrdinalIgnoreCase)) + { + // Parse values + // Col 4: SmaBias + // Col 5: HmaBias + // Col 6: HmaCluster + if (int.TryParse(columns[4], out int sBias) && + int.TryParse(columns[5], out int hBias) && + int.TryParse(columns[6], out int hCluster)) + { + _smaBiasLength = sBias; + _hmaBiasLength = hBias; + _hmaClusterLength = hCluster; + Print($"Configuration found for {currentSymbol} {currentTimeframe}"); + return; // Stop searching after match + } + } + } + + Print($"No specific config found for {currentSymbol} {currentTimeframe}. Using defaults."); + } + catch (Exception ex) + { + Print($"Error reading config file: {ex.Message}. Using defaults."); + } + } + } +} \ No newline at end of file diff --git a/Sources/Indicators/HmaClusterDisplay/HmaClusterDisplay/HmaClusterDisplay.csproj b/Sources/Indicators/HmaClusterDisplay/HmaClusterDisplay/HmaClusterDisplay.csproj new file mode 100644 index 0000000..da1fef1 --- /dev/null +++ b/Sources/Indicators/HmaClusterDisplay/HmaClusterDisplay/HmaClusterDisplay.csproj @@ -0,0 +1,9 @@ + + + net6.0 + + + + + + diff --git a/Sources/Indicators/HmaClusterDisplay/HmaClusterDisplay/obj/Debug/net6.0/.NETCoreApp,Version=v6.0.AssemblyAttributes.cs b/Sources/Indicators/HmaClusterDisplay/HmaClusterDisplay/obj/Debug/net6.0/.NETCoreApp,Version=v6.0.AssemblyAttributes.cs new file mode 100644 index 0000000..32c95f9 --- /dev/null +++ b/Sources/Indicators/HmaClusterDisplay/HmaClusterDisplay/obj/Debug/net6.0/.NETCoreApp,Version=v6.0.AssemblyAttributes.cs @@ -0,0 +1,4 @@ +// +using System; +using System.Reflection; +[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v6.0", FrameworkDisplayName = "")] diff --git a/Sources/Indicators/HmaClusterDisplay/HmaClusterDisplay/obj/Debug/net6.0/HmaClusterDisplay.AssemblyInfo.cs b/Sources/Indicators/HmaClusterDisplay/HmaClusterDisplay/obj/Debug/net6.0/HmaClusterDisplay.AssemblyInfo.cs new file mode 100644 index 0000000..c44f687 --- /dev/null +++ b/Sources/Indicators/HmaClusterDisplay/HmaClusterDisplay/obj/Debug/net6.0/HmaClusterDisplay.AssemblyInfo.cs @@ -0,0 +1,22 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +using System; +using System.Reflection; + +[assembly: System.Reflection.AssemblyCompanyAttribute("HmaClusterDisplay")] +[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] +[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0")] +[assembly: System.Reflection.AssemblyProductAttribute("HmaClusterDisplay")] +[assembly: System.Reflection.AssemblyTitleAttribute("HmaClusterDisplay")] +[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] + +// Generated by the MSBuild WriteCodeFragment class. + diff --git a/Sources/Indicators/HmaClusterDisplay/HmaClusterDisplay/obj/Debug/net6.0/HmaClusterDisplay.AssemblyInfoInputs.cache b/Sources/Indicators/HmaClusterDisplay/HmaClusterDisplay/obj/Debug/net6.0/HmaClusterDisplay.AssemblyInfoInputs.cache new file mode 100644 index 0000000..fb3298c --- /dev/null +++ b/Sources/Indicators/HmaClusterDisplay/HmaClusterDisplay/obj/Debug/net6.0/HmaClusterDisplay.AssemblyInfoInputs.cache @@ -0,0 +1 @@ +c64656f30d6316c2781214813c004e91eeb999a2 diff --git a/Sources/Indicators/HmaClusterDisplay/HmaClusterDisplay/obj/Debug/net6.0/HmaClusterDisplay.GeneratedMSBuildEditorConfig.editorconfig b/Sources/Indicators/HmaClusterDisplay/HmaClusterDisplay/obj/Debug/net6.0/HmaClusterDisplay.GeneratedMSBuildEditorConfig.editorconfig new file mode 100644 index 0000000..eb62e09 --- /dev/null +++ b/Sources/Indicators/HmaClusterDisplay/HmaClusterDisplay/obj/Debug/net6.0/HmaClusterDisplay.GeneratedMSBuildEditorConfig.editorconfig @@ -0,0 +1,10 @@ +is_global = true +build_property.TargetFramework = net6.0 +build_property.TargetPlatformMinVersion = +build_property.UsingMicrosoftNETSdkWeb = +build_property.ProjectTypeGuids = +build_property.InvariantGlobalization = +build_property.PlatformNeutralAssembly = +build_property._SupportedPlatformList = Linux,macOS,Windows +build_property.RootNamespace = HmaClusterDisplay +build_property.ProjectDir = C:\Users\Brummel\Documents\cAlgo\Sources\Indicators\HmaClusterDisplay\HmaClusterDisplay\ diff --git a/Sources/Indicators/HmaClusterDisplay/HmaClusterDisplay/obj/Debug/net6.0/HmaClusterDisplay.assets.cache b/Sources/Indicators/HmaClusterDisplay/HmaClusterDisplay/obj/Debug/net6.0/HmaClusterDisplay.assets.cache new file mode 100644 index 0000000..49943a9 Binary files /dev/null and b/Sources/Indicators/HmaClusterDisplay/HmaClusterDisplay/obj/Debug/net6.0/HmaClusterDisplay.assets.cache differ diff --git a/Sources/Indicators/HmaClusterDisplay/HmaClusterDisplay/obj/Debug/net6.0/HmaClusterDisplay.csproj.AssemblyReference.cache b/Sources/Indicators/HmaClusterDisplay/HmaClusterDisplay/obj/Debug/net6.0/HmaClusterDisplay.csproj.AssemblyReference.cache new file mode 100644 index 0000000..50043b7 Binary files /dev/null and b/Sources/Indicators/HmaClusterDisplay/HmaClusterDisplay/obj/Debug/net6.0/HmaClusterDisplay.csproj.AssemblyReference.cache differ diff --git a/Sources/Indicators/HmaClusterDisplay/HmaClusterDisplay/obj/HmaClusterDisplay.csproj.nuget.dgspec.json b/Sources/Indicators/HmaClusterDisplay/HmaClusterDisplay/obj/HmaClusterDisplay.csproj.nuget.dgspec.json new file mode 100644 index 0000000..3d07a8d --- /dev/null +++ b/Sources/Indicators/HmaClusterDisplay/HmaClusterDisplay/obj/HmaClusterDisplay.csproj.nuget.dgspec.json @@ -0,0 +1,66 @@ +{ + "format": 1, + "restore": { + "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Indicators\\HmaClusterDisplay\\HmaClusterDisplay\\HmaClusterDisplay.csproj": {} + }, + "projects": { + "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Indicators\\HmaClusterDisplay\\HmaClusterDisplay\\HmaClusterDisplay.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Indicators\\HmaClusterDisplay\\HmaClusterDisplay\\HmaClusterDisplay.csproj", + "projectName": "HmaClusterDisplay", + "projectPath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Indicators\\HmaClusterDisplay\\HmaClusterDisplay\\HmaClusterDisplay.csproj", + "packagesPath": "C:\\Users\\Brummel\\.nuget\\packages\\", + "outputPath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Indicators\\HmaClusterDisplay\\HmaClusterDisplay\\obj\\", + "projectStyle": "PackageReference", + "configFilePaths": [ + "C:\\Users\\Brummel\\AppData\\Roaming\\NuGet\\NuGet.Config" + ], + "originalTargetFrameworks": [ + "net6.0" + ], + "sources": { + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net6.0": { + "targetAlias": "net6.0", + "projectReferences": {} + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + } + }, + "frameworks": { + "net6.0": { + "targetAlias": "net6.0", + "dependencies": { + "cTrader.Automate": { + "target": "Package", + "version": "[*, )" + } + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\6.0.200\\RuntimeIdentifierGraph.json" + } + } + } + } +} \ No newline at end of file diff --git a/Sources/Indicators/HmaClusterDisplay/HmaClusterDisplay/obj/HmaClusterDisplay.csproj.nuget.g.props b/Sources/Indicators/HmaClusterDisplay/HmaClusterDisplay/obj/HmaClusterDisplay.csproj.nuget.g.props new file mode 100644 index 0000000..0b161ac --- /dev/null +++ b/Sources/Indicators/HmaClusterDisplay/HmaClusterDisplay/obj/HmaClusterDisplay.csproj.nuget.g.props @@ -0,0 +1,21 @@ + + + + True + NuGet + $(MSBuildThisFileDirectory)project.assets.json + $(UserProfile)\.nuget\packages\ + C:\Users\Brummel\.nuget\packages\ + PackageReference + 6.1.0 + + + + + + + + + C:\Users\Brummel\.nuget\packages\ctrader.automate\1.0.14 + + \ No newline at end of file diff --git a/Sources/Indicators/HmaClusterDisplay/HmaClusterDisplay/obj/HmaClusterDisplay.csproj.nuget.g.targets b/Sources/Indicators/HmaClusterDisplay/HmaClusterDisplay/obj/HmaClusterDisplay.csproj.nuget.g.targets new file mode 100644 index 0000000..46f142c --- /dev/null +++ b/Sources/Indicators/HmaClusterDisplay/HmaClusterDisplay/obj/HmaClusterDisplay.csproj.nuget.g.targets @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Sources/Indicators/HmaClusterDisplay/HmaClusterDisplay/obj/project.assets.json b/Sources/Indicators/HmaClusterDisplay/HmaClusterDisplay/obj/project.assets.json new file mode 100644 index 0000000..4e3616e --- /dev/null +++ b/Sources/Indicators/HmaClusterDisplay/HmaClusterDisplay/obj/project.assets.json @@ -0,0 +1,139 @@ +{ + "version": 3, + "targets": { + "net6.0": { + "cTrader.Automate/1.0.14": { + "type": "package", + "compile": { + "lib/net6.0/cAlgo.API.dll": {} + }, + "runtime": { + "lib/net6.0/cAlgo.API.dll": {} + }, + "build": { + "build/cTrader.Automate.props": {}, + "build/cTrader.Automate.targets": {} + } + } + } + }, + "libraries": { + "cTrader.Automate/1.0.14": { + "sha512": "eNwE7WL90MGBKb5MuLAtZLdQy0vxkI5EVhLWAQ9S83EAdYkGAzdccvGFLk2oqmtSGBtD+gEpyrJUW/ej4dI4jw==", + "type": "package", + "path": "ctrader.automate/1.0.14", + "hasTools": true, + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "build/cTrader.Automate.props", + "build/cTrader.Automate.targets", + "ctrader.automate.1.0.14.nupkg.sha512", + "ctrader.automate.nuspec", + "eula.md", + "icon.png", + "lib/net40/cAlgo.API.dll", + "lib/net40/cAlgo.API.xml", + "lib/net6.0/cAlgo.API.dll", + "lib/net6.0/cAlgo.API.xml", + "tools/net472/Core.AlgoFormat.Compose.Reflection.dll", + "tools/net472/Core.AlgoFormat.Writer.dll", + "tools/net472/Core.AlgoFormat.dll", + "tools/net472/Core.Domain.Primitives.dll", + "tools/net472/Newtonsoft.Json.dll", + "tools/net472/System.Buffers.dll", + "tools/net472/System.Collections.Immutable.dll", + "tools/net472/System.Memory.dll", + "tools/net472/System.Numerics.Vectors.dll", + "tools/net472/System.Reflection.Metadata.dll", + "tools/net472/System.Reflection.MetadataLoadContext.dll", + "tools/net472/System.Runtime.CompilerServices.Unsafe.dll", + "tools/net472/cTrader.Automate.Sdk.Tasks.dll", + "tools/net6.0/Core.AlgoFormat.Compose.Reflection.dll", + "tools/net6.0/Core.AlgoFormat.Writer.dll", + "tools/net6.0/Core.AlgoFormat.dll", + "tools/net6.0/Core.Connection.Protobuf.Common.dll", + "tools/net6.0/Core.Domain.Primitives.dll", + "tools/net6.0/Microsoft.Win32.SystemEvents.dll", + "tools/net6.0/Newtonsoft.Json.dll", + "tools/net6.0/System.Drawing.Common.dll", + "tools/net6.0/System.Reflection.MetadataLoadContext.dll", + "tools/net6.0/System.Security.Permissions.dll", + "tools/net6.0/System.Windows.Extensions.dll", + "tools/net6.0/cTrader.Automate.Sdk.Tasks.dll", + "tools/net6.0/protobuf-net.Core.dll", + "tools/net6.0/protobuf-net.dll", + "tools/net6.0/runtimes/unix/lib/net6.0/System.Drawing.Common.dll", + "tools/net6.0/runtimes/win/lib/net6.0/Microsoft.Win32.SystemEvents.dll", + "tools/net6.0/runtimes/win/lib/net6.0/System.Drawing.Common.dll", + "tools/net6.0/runtimes/win/lib/net6.0/System.Windows.Extensions.dll" + ] + } + }, + "projectFileDependencyGroups": { + "net6.0": [ + "cTrader.Automate >= *" + ] + }, + "packageFolders": { + "C:\\Users\\Brummel\\.nuget\\packages\\": {} + }, + "project": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Indicators\\HmaClusterDisplay\\HmaClusterDisplay\\HmaClusterDisplay.csproj", + "projectName": "HmaClusterDisplay", + "projectPath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Indicators\\HmaClusterDisplay\\HmaClusterDisplay\\HmaClusterDisplay.csproj", + "packagesPath": "C:\\Users\\Brummel\\.nuget\\packages\\", + "outputPath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Indicators\\HmaClusterDisplay\\HmaClusterDisplay\\obj\\", + "projectStyle": "PackageReference", + "configFilePaths": [ + "C:\\Users\\Brummel\\AppData\\Roaming\\NuGet\\NuGet.Config" + ], + "originalTargetFrameworks": [ + "net6.0" + ], + "sources": { + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net6.0": { + "targetAlias": "net6.0", + "projectReferences": {} + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + } + }, + "frameworks": { + "net6.0": { + "targetAlias": "net6.0", + "dependencies": { + "cTrader.Automate": { + "target": "Package", + "version": "[*, )" + } + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\6.0.200\\RuntimeIdentifierGraph.json" + } + } + } +} \ No newline at end of file diff --git a/Sources/Indicators/HmaClusterDisplay/HmaClusterDisplay/obj/project.nuget.cache b/Sources/Indicators/HmaClusterDisplay/HmaClusterDisplay/obj/project.nuget.cache new file mode 100644 index 0000000..14266fc --- /dev/null +++ b/Sources/Indicators/HmaClusterDisplay/HmaClusterDisplay/obj/project.nuget.cache @@ -0,0 +1,10 @@ +{ + "version": 2, + "dgSpecHash": "yHH60uYu212/ZwKBjFEs3QIJ5FpzVGBMwmFDYLX2rhH2Cs1IBW+Oz5ZVjZ6S4fBauvoGBarOvV07SnxXEk/83g==", + "success": true, + "projectFilePath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Indicators\\HmaClusterDisplay\\HmaClusterDisplay\\HmaClusterDisplay.csproj", + "expectedPackageFiles": [ + "C:\\Users\\Brummel\\.nuget\\packages\\ctrader.automate\\1.0.14\\ctrader.automate.1.0.14.nupkg.sha512" + ], + "logs": [] +} \ No newline at end of file diff --git a/Sources/Indicators/HmaClusterSR.algo b/Sources/Indicators/HmaClusterSR.algo new file mode 100644 index 0000000..329c2c4 Binary files /dev/null and b/Sources/Indicators/HmaClusterSR.algo differ diff --git a/Sources/Indicators/HmaClusterSR/HmaClusterSR.sln b/Sources/Indicators/HmaClusterSR/HmaClusterSR.sln new file mode 100644 index 0000000..6d7fb14 --- /dev/null +++ b/Sources/Indicators/HmaClusterSR/HmaClusterSR.sln @@ -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}") = "HmaClusterSR", "HmaClusterSR\HmaClusterSR.csproj", "{31e3888d-35c4-4e2d-8ad6-4fc04fd7ebb7}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {31e3888d-35c4-4e2d-8ad6-4fc04fd7ebb7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {31e3888d-35c4-4e2d-8ad6-4fc04fd7ebb7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {31e3888d-35c4-4e2d-8ad6-4fc04fd7ebb7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {31e3888d-35c4-4e2d-8ad6-4fc04fd7ebb7}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/Sources/Indicators/HmaClusterSR/HmaClusterSR/HmaClusterSR.cs b/Sources/Indicators/HmaClusterSR/HmaClusterSR/HmaClusterSR.cs new file mode 100644 index 0000000..5495496 --- /dev/null +++ b/Sources/Indicators/HmaClusterSR/HmaClusterSR/HmaClusterSR.cs @@ -0,0 +1,875 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +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 +{ + [Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)] + public class HmaClusterBot : Robot + { + #region Parameters + + [Parameter("Symbols (CSV)", DefaultValue = "", Group = "Multi Symbol")] + public string SymbolsCsv { get; set; } + + [Parameter("Risk % of Balance", DefaultValue = 1.0, MinValue = 0.1, MaxValue = 10.0, Group = "Risk")] + public double RiskPercent { get; set; } + + [Parameter("Entry Significance %", DefaultValue = 20.0, MinValue = 1.0, Group = "Thresholds")] + public double EntrySigThreshold { get; set; } + + [Parameter("Stop Loss Significance %", DefaultValue = 10.0, MinValue = 1.0, Group = "Thresholds")] + public double SlSigThreshold { get; set; } + + [Parameter("SMA Bias Period", DefaultValue = 200, Group = "Bias")] + public int SmaBiasPeriod { get; set; } + + [Parameter("HMA Bias Period", DefaultValue = 250, Group = "Bias")] + public int HmaBiasPeriod { get; set; } + + [Parameter("Use Bias Deviation Filter", DefaultValue = true, Group = "Bias Filter")] + public bool UseBiasDeviationFilter { get; set; } + + [Parameter("Bias Deviation Avg Period", DefaultValue = 500, Group = "Bias Filter")] + public int BiasDeviationAvgPeriod { get; set; } + + [Parameter("Use Dynamic Position Management", DefaultValue = true, Group = "Management")] + public bool UseDynamicPositionManagement { get; set; } + + [Parameter("Close Profit on Bias Flip", DefaultValue = false, Group = "Management")] + public bool CloseProfitOnBiasFlip { get; set; } + + [Parameter("HMA Cluster Period", DefaultValue = 25, Group = "Clusters")] + public int HmaClusterPeriod { get; set; } + + [Parameter("Cluster Max Points", DefaultValue = 2000, Group = "Clusters")] + public int MaxPoints { get; set; } + + [Parameter("Cluster Decay (Bars)", DefaultValue = 1000, Group = "Clusters")] + public int DecayPeriod { get; set; } + + // --- Telegram Parameters --- + [Parameter("Send Telegram Only", DefaultValue = false, Group = "Telegram")] + public bool SendTelegramOnly { get; set; } + + [Parameter("Telegram Bot Token", DefaultValue = "8569913524:AAE9RGsvkBPa0yhTFCKBjVeST0fuzdOx5w0", Group = "Telegram")] + public string TelegramBotToken { get; set; } + + [Parameter("Telegram Chat ID", DefaultValue = "5171721381", Group = "Telegram")] + public string TelegramChatId { get; set; } + + #endregion + + private readonly List _strategies = new List(); + private static readonly HttpClient _httpClient = new HttpClient(); + + protected override void OnStart() + { + try + { + if (string.IsNullOrWhiteSpace(SymbolsCsv)) + { + Print("CSV is empty. Running in Single-Symbol Mode on Chart Symbol."); + + var config = new StrategyConfig + { + SymbolName = SymbolName, + EntrySigThreshold = EntrySigThreshold, + SlSigThreshold = SlSigThreshold, + SmaBiasPeriod = SmaBiasPeriod, + HmaBiasPeriod = HmaBiasPeriod, + HmaClusterPeriod = HmaClusterPeriod, + UseBiasDeviationFilter = UseBiasDeviationFilter, + BiasDeviationAvgPeriod = BiasDeviationAvgPeriod, + UseDynamicPositionManagement = UseDynamicPositionManagement, + CloseProfitOnBiasFlip = CloseProfitOnBiasFlip, + SendTelegramOnly = SendTelegramOnly, + RiskPercent = RiskPercent, + MaxPoints = MaxPoints, + DecayPeriod = DecayPeriod + }; + + var strategy = new ClusterStrategy(this, Symbol, Bars, config, _httpClient, TelegramBotToken, TelegramChatId, BroadcastError); + _strategies.Add(strategy); + strategy.Start(); + } + else + { + Print("CSV detected. Running in Multi-Symbol Mode."); + ParseCsvAndCreateStrategies(); + } + } + catch (Exception ex) + { + BroadcastError($"CRITICAL STARTUP ERROR: {ex.Message}"); + Stop(); + } + } + + protected override void OnStop() + { + foreach (var strategy in _strategies) + { + strategy.Stop(); + } + } + + protected override void OnError(Error error) + { + BroadcastError($"TRADING ERROR [{error.Code}]: {error.ToString}"); + } + + public void BroadcastError(string message) + { + Print(message); + + if (!string.IsNullOrWhiteSpace(TelegramBotToken) && !string.IsNullOrWhiteSpace(TelegramChatId)) + { + string formattedMsg = $"⚠️ ERROR @ {DateTime.UtcNow:HH:mm:ss} UTC\n\n{message}"; + _ = SendTelegramRawAsync(TelegramBotToken, TelegramChatId, formattedMsg); + } + } + + private async Task SendTelegramRawAsync(string token, string chatId, string message) + { + try + { + string url = $"https://api.telegram.org/bot{token}/sendMessage?chat_id={chatId}&text={Uri.EscapeDataString(message)}&parse_mode=HTML"; + await _httpClient.GetAsync(url); + } + catch (Exception ex) + { + Print("FAILED TO SEND TELEGRAM ERROR: " + ex.Message); + } + } + + private void ParseCsvAndCreateStrategies() + { + var normalizedCsv = SymbolsCsv.Replace("\n", ",").Replace("\r", ","); + + var tokens = normalizedCsv.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) + .Select(t => t.Trim()) + .Where(t => !string.IsNullOrEmpty(t)) + .ToArray(); + + int paramsPerSymbol = 10; + + if (tokens.Length % paramsPerSymbol != 0) + { + string err = $"CSV Token count ({tokens.Length}) is not a multiple of {paramsPerSymbol}. Check format."; + BroadcastError(err); + } + + for (int i = 0; i < tokens.Length; i += paramsPerSymbol) + { + if (i + paramsPerSymbol > tokens.Length) break; + + string symName = tokens[i]; + + try + { + Symbol symbol = Symbols.GetSymbol(symName); + if (symbol == null) + { + BroadcastError($"Symbol '{symName}' not found in cTrader."); + continue; + } + + Bars bars = MarketData.GetBars(TimeFrame, symName); + + var config = new StrategyConfig + { + SymbolName = symName, + EntrySigThreshold = double.Parse(tokens[i + 1], CultureInfo.InvariantCulture), + SlSigThreshold = double.Parse(tokens[i + 2], CultureInfo.InvariantCulture), + SmaBiasPeriod = int.Parse(tokens[i + 3], CultureInfo.InvariantCulture), + HmaBiasPeriod = int.Parse(tokens[i + 4], CultureInfo.InvariantCulture), + HmaClusterPeriod = int.Parse(tokens[i + 5], CultureInfo.InvariantCulture), + UseBiasDeviationFilter = bool.Parse(tokens[i + 6]), + BiasDeviationAvgPeriod = int.Parse(tokens[i + 7], CultureInfo.InvariantCulture), + UseDynamicPositionManagement = bool.Parse(tokens[i + 8]), + SendTelegramOnly = bool.Parse(tokens[i + 9]), + + // Default values for parameters not in CSV yet + CloseProfitOnBiasFlip = CloseProfitOnBiasFlip, + RiskPercent = RiskPercent, + MaxPoints = MaxPoints, + DecayPeriod = DecayPeriod + }; + + var strategy = new ClusterStrategy(this, symbol, bars, config, _httpClient, TelegramBotToken, TelegramChatId, BroadcastError); + _strategies.Add(strategy); + strategy.Start(); + + Print("Initialized Strategy for {0}", symName); + } + catch (Exception ex) + { + BroadcastError($"Config Error for '{symName}': {ex.Message}"); + } + } + } + } + + public class StrategyConfig + { + public string SymbolName { get; set; } + public double EntrySigThreshold { get; set; } + public double SlSigThreshold { get; set; } + public int SmaBiasPeriod { get; set; } + public int HmaBiasPeriod { get; set; } + public int HmaClusterPeriod { get; set; } + public bool UseBiasDeviationFilter { get; set; } + public int BiasDeviationAvgPeriod { get; set; } + public bool UseDynamicPositionManagement { get; set; } + public bool CloseProfitOnBiasFlip { get; set; } + public bool SendTelegramOnly { get; set; } + public double RiskPercent { get; set; } + public int MaxPoints { get; set; } + public int DecayPeriod { get; set; } + } + + public class ClusterStrategy + { + #region Types & Fields + + private enum PointType { Peak, Trough } + private enum Bias { Long, Short, Neutral } + + private struct ExtremumPoint + { + public double Price; + public int Index; + public PointType Type; + } + + public struct ClusterLevel + { + public double Price; + public double Significance; + } + + private readonly Robot _robot; + private readonly Symbol _symbol; + private readonly Bars _bars; + private readonly StrategyConfig _config; + private readonly HttpClient _httpClient; + private readonly string _botToken; + private readonly string _chatId; + private readonly Action _errorCallback; + + private SimpleMovingAverage _smaBias; + private HullMovingAverage _hmaBias; + private HullMovingAverage _hmaCluster; + + private readonly Queue _deviationQueue = new Queue(); + private double _runningDeviationSum; + + private bool _deviationConditionMetInCurrentCycle; + private bool _isTradingAllowedBasedOnPrevCycle; + + private readonly List _extremaPoints = new(); + private readonly List _amplitudes = new(); + + private double _trendExtremum; + private int _trendExtremumIndex; + private double _lastExtremumPrice; + private bool? _isUpTrend; + private double _currentDynamicRange; + + private const string Label = "HmaClusterBot"; + + #endregion + + public ClusterStrategy( + Robot robot, + Symbol symbol, + Bars bars, + StrategyConfig config, + HttpClient httpClient, + string token, + string chatId, + Action errorCallback) + { + _robot = robot; + _symbol = symbol; + _bars = bars; + _config = config; + _httpClient = httpClient; + _botToken = token; + _chatId = chatId; + _errorCallback = errorCallback; + } + + public void Start() + { + int requiredBars = Math.Max(_config.SmaBiasPeriod, _config.HmaBiasPeriod); + requiredBars = Math.Max(requiredBars, _config.BiasDeviationAvgPeriod) + 10; + + while (_bars.Count < requiredBars) + { + int loaded = _bars.LoadMoreHistory(); + if (loaded == 0) + { + _errorCallback?.Invoke($"Not enough history for {_config.SymbolName}. Loaded: {_bars.Count}, Req: {requiredBars}"); + return; + } + } + + _smaBias = _robot.Indicators.SimpleMovingAverage(_bars.ClosePrices, _config.SmaBiasPeriod); + _hmaBias = _robot.Indicators.HullMovingAverage(_bars.ClosePrices, _config.HmaBiasPeriod); + _hmaCluster = _robot.Indicators.HullMovingAverage(_bars.ClosePrices, _config.HmaClusterPeriod); + + _currentDynamicRange = 5.0 * _symbol.PipSize; + _deviationConditionMetInCurrentCycle = false; + _isTradingAllowedBasedOnPrevCycle = false; + + int startIndex = Math.Max(_config.SmaBiasPeriod, _config.HmaBiasPeriod); + startIndex = Math.Max(startIndex, _config.BiasDeviationAvgPeriod); + + for (int i = startIndex; i < _bars.Count; i++) + { + UpdateFilterState(i); + UpdateClusterData(i); + } + + _bars.BarOpened += OnBarOpened; + } + + public void Stop() + { + _bars.BarOpened -= OnBarOpened; + } + + private void OnBarOpened(BarOpenedEventArgs obj) + { + try + { + int index = _bars.Count - 2; + if (index < _config.BiasDeviationAvgPeriod) return; + + UpdateFilterState(index); + UpdateClusterData(index); + + var clusters = CalculateClusters(index); + + // 1. Dynamic SL/TP Management + if (!_config.SendTelegramOnly && _config.UseDynamicPositionManagement && clusters.Count >= 2) + { + ManagePositions(clusters); + } + + var currentBias = GetCurrentBias(index); + + // 2. Check for Profit Close on Bias Flip + if (!_config.SendTelegramOnly && _config.CloseProfitOnBiasFlip) + { + CloseReversedPositions(currentBias); + } + + // 3. New Entry Logic + ManageOrders(currentBias, index, clusters); + } + catch (Exception ex) + { + _errorCallback?.Invoke($"Runtime Error ({_config.SymbolName}): {ex.Message}\n{ex.StackTrace}"); + } + } + + private void CloseReversedPositions(Bias currentBias) + { + if (currentBias == Bias.Neutral) return; + + foreach (var pos in _robot.Positions) + { + if (pos.SymbolName != _config.SymbolName || pos.Label != Label) continue; + if (pos.NetProfit <= 0) continue; + + bool close = false; + + if (currentBias == Bias.Short && pos.TradeType == TradeType.Buy) + close = true; + else if (currentBias == Bias.Long && pos.TradeType == TradeType.Sell) + close = true; + + if (close) + { + var result = _robot.ClosePosition(pos); + if (result.IsSuccessful) + { + string msg = $"🔒 CLOSE PROFIT (Bias Flip) @ {_config.SymbolName}\n" + + $"Profit: {pos.NetProfit:F2}"; + _ = SendTelegramMessageAsync(msg); + } + } + } + } + + private void UpdateFilterState(int index) + { + double currHma = _hmaBias.Result[index]; + double currSma = _smaBias.Result[index]; + double prevHma = _hmaBias.Result[index - 1]; + double prevSma = _smaBias.Result[index - 1]; + + double currentBiasDeviation = Math.Abs(currHma - currSma); + + _deviationQueue.Enqueue(currentBiasDeviation); + _runningDeviationSum += currentBiasDeviation; + + if (_deviationQueue.Count > _config.BiasDeviationAvgPeriod) + { + double removed = _deviationQueue.Dequeue(); + _runningDeviationSum -= removed; + } + + double averageDeviation = (_deviationQueue.Count > 0) + ? _runningDeviationSum / _deviationQueue.Count + : 0.0; + + bool currHmaAbove = currHma > currSma; + bool prevHmaAbove = prevHma > prevSma; + + if (currHmaAbove != prevHmaAbove) + { + _isTradingAllowedBasedOnPrevCycle = _deviationConditionMetInCurrentCycle; + _deviationConditionMetInCurrentCycle = false; + } + + if (currentBiasDeviation > averageDeviation) + { + _deviationConditionMetInCurrentCycle = true; + } + } + + private Bias GetCurrentBias(int index) + { + double hma = _hmaBias.Result[index]; + double sma = _smaBias.Result[index]; + double prevSma = _smaBias.Result[index - 1]; + + if ((hma > sma) && (sma > prevSma)) return Bias.Long; + if ((hma < sma) && (sma < prevSma)) return Bias.Short; + + return Bias.Neutral; + } + + private void ManagePositions(List clusters) + { + double pNow = _symbol.Bid; + + foreach (var pos in _robot.Positions) + { + if (pos.Label != Label || pos.SymbolName != _config.SymbolName) continue; + + ClusterLevel? newSl = null; + ClusterLevel? newTp = null; + + if (pos.TradeType == TradeType.Buy) + { + double highestSlPrice = double.MinValue; + double highestTpPrice = double.MinValue; + + foreach (var c in clusters) + { + if (c.Price < pNow && c.Significance < _config.SlSigThreshold && c.Price > highestSlPrice) + { + newSl = c; + highestSlPrice = c.Price; + } + if (c.Price > pNow && c.Significance >= _config.EntrySigThreshold && c.Price > highestTpPrice) + { + newTp = c; + highestTpPrice = c.Price; + } + } + + double proposedSl = (newSl.HasValue) ? newSl.Value.Price : (pos.StopLoss ?? 0); + double proposedTp = (newTp.HasValue) ? newTp.Value.Price : (pos.TakeProfit ?? 0); + + bool modifySl = pos.StopLoss.HasValue && (proposedSl > pos.StopLoss.Value + _symbol.TickSize); + if (!pos.StopLoss.HasValue && newSl.HasValue) modifySl = true; + + bool modifyTp = newTp.HasValue && Math.Abs(proposedTp - (pos.TakeProfit ?? 0)) > _symbol.TickSize; + + if (modifySl || modifyTp) + { + double finalSl = modifySl ? proposedSl : pos.StopLoss ?? 0; + double finalTp = modifyTp ? proposedTp : pos.TakeProfit ?? 0; + + if (finalSl < _symbol.Bid && (finalTp == 0 || finalTp > _symbol.Bid)) + { + _robot.ModifyPosition(pos, finalSl, finalTp, ProtectionType.Absolute); + } + } + } + else // Sell + { + double lowestSlPrice = double.MaxValue; + double lowestTpPrice = double.MaxValue; + + foreach (var c in clusters) + { + if (c.Price > pNow && c.Significance < _config.SlSigThreshold && c.Price < lowestSlPrice) + { + newSl = c; + lowestSlPrice = c.Price; + } + if (c.Price < pNow && c.Significance >= _config.EntrySigThreshold && c.Price < lowestTpPrice) + { + newTp = c; + lowestTpPrice = c.Price; + } + } + + double proposedSl = (newSl.HasValue) ? newSl.Value.Price : (pos.StopLoss ?? 0); + double proposedTp = (newTp.HasValue) ? newTp.Value.Price : (pos.TakeProfit ?? 0); + + bool modifySl = pos.StopLoss.HasValue && (proposedSl < pos.StopLoss.Value - _symbol.TickSize); + if (!pos.StopLoss.HasValue && newSl.HasValue) modifySl = true; + + bool modifyTp = newTp.HasValue && Math.Abs(proposedTp - (pos.TakeProfit ?? 0)) > _symbol.TickSize; + + if (modifySl || modifyTp) + { + double finalSl = modifySl ? proposedSl : pos.StopLoss ?? 0; + double finalTp = modifyTp ? proposedTp : pos.TakeProfit ?? 0; + + if (finalSl > _symbol.Ask && (finalTp == 0 || finalTp < _symbol.Ask)) + { + _robot.ModifyPosition(pos, finalSl, finalTp, ProtectionType.Absolute); + } + } + } + } + } + + private void ManageOrders(Bias bias, int index, List clusters) + { + CleanupOrders(bias); + + if (_config.UseBiasDeviationFilter && !_isTradingAllowedBasedOnPrevCycle) + return; + + if (bias == Bias.Neutral) return; + + if (!_config.SendTelegramOnly) + { + bool hasLong = _robot.Positions.Any(p => p.SymbolName == _config.SymbolName && p.Label == Label && p.TradeType == TradeType.Buy); + bool hasShort = _robot.Positions.Any(p => p.SymbolName == _config.SymbolName && p.Label == Label && p.TradeType == TradeType.Sell); + + if ((bias == Bias.Long && hasLong) || (bias == Bias.Short && hasShort)) + return; + } + + if (clusters.Count < 2) return; + + double pNow = _bars.ClosePrices[index]; + + if (bias == Bias.Short) + ProcessShortSetup(clusters, pNow); + else if (bias == Bias.Long) + ProcessLongSetup(clusters, pNow); + } + + private void CleanupOrders(Bias currentBias) + { + if (_config.SendTelegramOnly) return; + + foreach (var order in _robot.PendingOrders) + { + if (order.Label != Label || order.SymbolName != _config.SymbolName) continue; + + if (currentBias == Bias.Long && order.TradeType == TradeType.Sell) + _robot.CancelPendingOrder(order); + else if (currentBias == Bias.Short && order.TradeType == TradeType.Buy) + _robot.CancelPendingOrder(order); + else if (currentBias == Bias.Neutral) + _robot.CancelPendingOrder(order); + } + } + + private void ProcessShortSetup(List clusters, double pNow) + { + ClusterLevel? entry = null; + ClusterLevel? sl = null; + ClusterLevel? tp = null; + + double highestEntryPrice = double.MinValue; + double lowestSlPrice = double.MaxValue; + double lowestTpPrice = double.MaxValue; + + foreach (var c in clusters) + { + if (c.Price > pNow) + { + if ((c.Significance >= _config.EntrySigThreshold) && (c.Price > highestEntryPrice)) + { + entry = c; + highestEntryPrice = c.Price; + } + if ((c.Significance < _config.SlSigThreshold) && (c.Price < lowestSlPrice)) + { + sl = c; + lowestSlPrice = c.Price; + } + } + } + + if (entry == null || sl == null) return; + + foreach (var c in clusters) + { + if ((c.Price < entry.Value.Price) && (c.Significance >= _config.EntrySigThreshold)) + { + if (c.Price < lowestTpPrice) + { + tp = c; + lowestTpPrice = c.Price; + } + } + } + + if (tp != null && (sl.Value.Price > entry.Value.Price)) + { + double risk = sl.Value.Price - entry.Value.Price; + double reward = entry.Value.Price - tp.Value.Price; + + if (risk <= reward) + UpdateOrPlaceLimitOrder(TradeType.Sell, entry.Value.Price, sl.Value.Price, tp.Value.Price); + } + } + + private void ProcessLongSetup(List clusters, double pNow) + { + ClusterLevel? entry = null; + ClusterLevel? sl = null; + ClusterLevel? tp = null; + + double lowestEntryPrice = double.MaxValue; + double highestSlPrice = double.MinValue; + double highestTpPrice = double.MinValue; + + foreach (var c in clusters) + { + if (c.Price < pNow) + { + if ((c.Significance >= _config.EntrySigThreshold) && (c.Price < lowestEntryPrice)) + { + entry = c; + lowestEntryPrice = c.Price; + } + if ((c.Significance < _config.SlSigThreshold) && (c.Price > highestSlPrice)) + { + sl = c; + highestSlPrice = c.Price; + } + } + } + + if (entry == null || sl == null) return; + + foreach (var c in clusters) + { + if ((c.Price > entry.Value.Price) && (c.Significance >= _config.EntrySigThreshold)) + { + if (c.Price > highestTpPrice) + { + tp = c; + highestTpPrice = c.Price; + } + } + } + + if (tp != null && (sl.Value.Price < entry.Value.Price)) + { + double risk = entry.Value.Price - sl.Value.Price; + double reward = tp.Value.Price - entry.Value.Price; + + if (risk <= reward) + UpdateOrPlaceLimitOrder(TradeType.Buy, entry.Value.Price, sl.Value.Price, tp.Value.Price); + } + } + + private void UpdateOrPlaceLimitOrder(TradeType type, double entry, double sl, double tp) + { + double slDistPips = Math.Abs(entry - sl) / _symbol.PipSize; + if (slDistPips <= 0) return; + + double riskAmount = _robot.Account.Balance * (_config.RiskPercent / 100.0); + double volume = _symbol.VolumeForFixedRisk(riskAmount, slDistPips); + + volume = _symbol.NormalizeVolumeInUnits(volume, RoundingMode.Down); + if (volume < _symbol.VolumeInUnitsMin) return; + + double lots = _symbol.VolumeInUnitsToQuantity(volume); + + if (_config.SendTelegramOnly) + { + string directionStr = type == TradeType.Buy ? "BUY" : "SELL"; + string directionIcon = type == TradeType.Buy ? "📈" : "📉"; + string msg = $"{directionIcon} {directionStr} Signal @ {_config.SymbolName}\n\n" + + $"Entry: {entry}\n" + + $"SL: {sl}\n" + + $"TP: {tp}\n" + + $"Vol: {lots:F2} Lots"; + + _ = SendTelegramMessageAsync(msg); + return; + } + + var existingOrder = _robot.PendingOrders.FirstOrDefault(o => o.SymbolName == _config.SymbolName && o.Label == Label && o.TradeType == type); + + if (existingOrder != null) + { + bool volumeChanged = Math.Abs(existingOrder.VolumeInUnits - volume) > _symbol.VolumeInUnitsStep; + bool entryChanged = Math.Abs(existingOrder.TargetPrice - entry) > _symbol.TickSize; + bool slChanged = Math.Abs((existingOrder.StopLoss ?? 0) - sl) > _symbol.TickSize; + bool tpChanged = Math.Abs((existingOrder.TakeProfit ?? 0) - tp) > _symbol.TickSize; + + if (volumeChanged || entryChanged || slChanged || tpChanged) + { + _robot.ModifyPendingOrder(existingOrder, entry, sl, tp, ProtectionType.Absolute, null, volume); + } + } + else + { + _robot.Print("[SIGNAL] {0} {1} | Entry: {2} | SL: {3} | TP: {4} | Vol: {5:F2} Lots", + (type == TradeType.Buy ? "BUY" : "SELL"), _config.SymbolName, entry, sl, tp, lots); + + _robot.PlaceLimitOrder(type, _config.SymbolName, volume, entry, Label, sl, tp, ProtectionType.Absolute); + } + } + + private async Task SendTelegramMessageAsync(string message) + { + if (_robot.IsBacktesting) return; + if (string.IsNullOrWhiteSpace(_botToken) || string.IsNullOrWhiteSpace(_chatId)) return; + + try + { + string url = $"https://api.telegram.org/bot{_botToken}/sendMessage?chat_id={_chatId}&text={Uri.EscapeDataString(message)}&parse_mode=HTML"; + HttpResponseMessage response = await _httpClient.GetAsync(url); + + if (!response.IsSuccessStatusCode) + { + _errorCallback?.Invoke($"Telegram Error: {response.StatusCode}"); + } + } + catch (Exception ex) + { + _errorCallback?.Invoke($"Telegram Exception: {ex.Message}"); + } + } + + private void UpdateClusterData(int index) + { + double currentHma = _hmaCluster.Result[index]; + double prevHma = _hmaCluster.Result[index - 1]; + bool currentDirectionUp = (currentHma > prevHma); + + if (_isUpTrend == null) + { + _isUpTrend = currentDirectionUp; + SetExtremum(index); + _lastExtremumPrice = _trendExtremum; + return; + } + + if (currentDirectionUp != _isUpTrend) + { + double amp = Math.Abs(_trendExtremum - _lastExtremumPrice); + if (amp > 0) + { + _amplitudes.Add(amp); + if (_amplitudes.Count > 50) _amplitudes.RemoveAt(0); + + double sum = 0; + for (int i = 0; i < _amplitudes.Count; i++) sum += _amplitudes[i]; + _currentDynamicRange = (sum / _amplitudes.Count) * 0.5; + } + _extremaPoints.Add(new ExtremumPoint { Price = _trendExtremum, Index = _trendExtremumIndex, Type = _isUpTrend.Value ? PointType.Peak : PointType.Trough }); + if (_extremaPoints.Count > _config.MaxPoints) _extremaPoints.RemoveAt(0); + + _lastExtremumPrice = _trendExtremum; + _isUpTrend = currentDirectionUp; + SetExtremum(index); + } + else + { + UpdateExtremum(index); + } + } + + private void SetExtremum(int index) + { + _trendExtremum = _isUpTrend.Value ? _bars.HighPrices[index] : _bars.LowPrices[index]; + _trendExtremumIndex = index; + } + + private void UpdateExtremum(int index) + { + if (_isUpTrend.Value && (_bars.HighPrices[index] > _trendExtremum)) + { + _trendExtremum = _bars.HighPrices[index]; + _trendExtremumIndex = index; + } + else if (!_isUpTrend.Value && (_bars.LowPrices[index] < _trendExtremum)) + { + _trendExtremum = _bars.LowPrices[index]; + _trendExtremumIndex = index; + } + } + + private List CalculateClusters(int currentIndex) + { + int count = _extremaPoints.Count; + if (count < 2) return new List(); + + double currentPrice = _bars.ClosePrices[currentIndex]; + double range = _currentDynamicRange; + + // Score-First / Global Density Approach (Matches Indicator) + var candidates = _extremaPoints + .Select(p => { + double weightedScore = _extremaPoints + .Where(other => Math.Abs(other.Price - p.Price) <= range) + .Sum(other => GetWeight(other, currentIndex, currentPrice)); + + return new { Price = p.Price, Score = weightedScore }; + }) + .OrderByDescending(z => z.Score) + .ToList(); + + var topZones = new List(); + foreach (var zone in candidates) + { + // Filter Overlap + if (!topZones.Any(z => Math.Abs(z.Price - zone.Price) < range)) + { + // Normalize Score relative to total Weight of all points (Matches Indicator Normalization) + double totalWeightSum = _extremaPoints.Sum(p => GetWeight(p, currentIndex, currentPrice)); + double sigPercent = (totalWeightSum > 0) ? (zone.Score / totalWeightSum) * 100.0 : 0; + + topZones.Add(new ClusterLevel { Price = zone.Price, Significance = sigPercent }); + } + } + + return topZones; + } + + private double GetWeight(ExtremumPoint point, int currentIndex, double currentPrice) + { + double weight = Math.Max(0.0, 1.0 - ((double)(currentIndex - point.Index) / _config.DecayPeriod)); + if ((point.Type == PointType.Peak) && (point.Price < currentPrice)) weight *= 2.0; + else if ((point.Type == PointType.Trough) && (point.Price > currentPrice)) weight *= 2.0; + return weight; + } + } +} \ No newline at end of file diff --git a/Sources/Indicators/HmaClusterSR/HmaClusterSR/HmaClusterSR.csproj b/Sources/Indicators/HmaClusterSR/HmaClusterSR/HmaClusterSR.csproj new file mode 100644 index 0000000..da1fef1 --- /dev/null +++ b/Sources/Indicators/HmaClusterSR/HmaClusterSR/HmaClusterSR.csproj @@ -0,0 +1,9 @@ + + + net6.0 + + + + + + diff --git a/Sources/Indicators/HmaMitigationCount/HmaMitigationCount.sln b/Sources/Indicators/HmaMitigationCount/HmaMitigationCount.sln new file mode 100644 index 0000000..15e2f62 --- /dev/null +++ b/Sources/Indicators/HmaMitigationCount/HmaMitigationCount.sln @@ -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}") = "HmaMitigationCount", "HmaMitigationCount\HmaMitigationCount.csproj", "{a7f42ba9-479c-418c-83b4-228416c67510}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {a7f42ba9-479c-418c-83b4-228416c67510}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {a7f42ba9-479c-418c-83b4-228416c67510}.Debug|Any CPU.Build.0 = Debug|Any CPU + {a7f42ba9-479c-418c-83b4-228416c67510}.Release|Any CPU.ActiveCfg = Release|Any CPU + {a7f42ba9-479c-418c-83b4-228416c67510}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/Sources/Indicators/HmaMitigationCount/HmaMitigationCount/HmaMitigationCount.cs b/Sources/Indicators/HmaMitigationCount/HmaMitigationCount/HmaMitigationCount.cs new file mode 100644 index 0000000..1ee97f0 --- /dev/null +++ b/Sources/Indicators/HmaMitigationCount/HmaMitigationCount/HmaMitigationCount.cs @@ -0,0 +1,162 @@ +using cAlgo.API; +using cAlgo.API.Indicators; +using cAlgo.API.Internals; +using System.Collections.Generic; +using System.Linq; +using System; + +namespace cAlgo.Indicators +{ + [Indicator(IsOverlay = true, TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)] + public class HmaMitigationCount : Indicator + { + [Parameter("HMA Period", DefaultValue = 20, MinValue = 2)] + public int HmaPeriod { get; set; } + + [Parameter("Session Close Time (UTC)", DefaultValue = "17:30")] + public TimeSpan SessionCloseTimeUtc { get; set; } + + [Output("HMA", LineColor = "Orange")] + public IndicatorDataSeries HmaOutput { get; set; } + + private HullMovingAverage _hma; + private Stack _peakStack; + private Stack _troughStack; + private int lblCnt = 0; + private int mitigatedPeaks = 0; + private int mitigatedTroughs = 0; + private int dir = 0; + + private DateTime _currentSessionEndTimeUtc = DateTime.MinValue; + + protected override void Initialize() + { + _hma = Indicators.HullMovingAverage(Bars.ClosePrices, HmaPeriod); + _peakStack = new Stack(); + _troughStack = new Stack(); + } + + public override void Calculate(int index) + { + HmaOutput[index] = _hma.Result[index]; + DateTime barOpenTime = Bars.OpenTimes[index]; // This is UTC + + // --- Session Initialization (run once) --- + if (_currentSessionEndTimeUtc == DateTime.MinValue) + { + // Find the session close time for the *current* bar's day + DateTime sessionCloseToday = barOpenTime.Date.Add(SessionCloseTimeUtc); + + // If the bar is already *after* today's close, the session ends *tomorrow* + if (barOpenTime >= sessionCloseToday) + { + _currentSessionEndTimeUtc = sessionCloseToday.AddDays(1); + } + // If the bar is *before* today's close, the session ends *today* + else + { + _currentSessionEndTimeUtc = sessionCloseToday; + } + } + + // --- Session Check --- + // Check if the current bar has crossed the session boundary + if (barOpenTime >= _currentSessionEndTimeUtc) + { + // New session starts, clear stacks + _peakStack.Clear(); + _troughStack.Clear(); + //Chart.RemoveAllObjects(); // Optional: Clear old session text + + // Set the *next* session end time, looping in case of market gaps (weekends) + while (barOpenTime >= _currentSessionEndTimeUtc) + { + _currentSessionEndTimeUtc = _currentSessionEndTimeUtc.AddDays(1); + } + } + + // Stop logic from running on the forming bar + if (IsLastBar) + { + return; + } + + // We need at least 3 bars to identify a peak/trough at index - 1 + if (index < 2) + { + return; + } + + // --- 1. Identify new peaks/troughs at index - 1 AND check mitigation --- + // All HMA values are now based on closed bars (index, index-1, index-2). + double prevHma = _hma.Result[index - 1]; + double prevHma2 = _hma.Result[index - 2]; + double currentHma = _hma.Result[index]; + + // A peak at index-1 + bool isPeak = (prevHma > prevHma2) && (prevHma > currentHma); + // A trough at index-1 + bool isTrough = (prevHma < prevHma2) && (prevHma < currentHma); + + if (isPeak) + { + // New Peak: Check if it mitigates any *previous Peaks* + if( dir < 0 ) + { + mitigatedPeaks = 0; + lblCnt++; + } + + // A new peak (prevHma) mitigates old peaks that are *lower* + while (_peakStack.Count > 0 && prevHma >= _peakStack.Peek()) + { + _peakStack.Pop(); // Pop the mitigated (lower) peak + mitigatedPeaks++; + } + + if (mitigatedPeaks > 0) + { + // Draw mitigation count above the peak bar + // The remaining count is the peaks *higher* than the new one + string label = $"Mit: {mitigatedPeaks} / Rem: {_peakStack.Count}"; + double yPos = Bars.HighPrices[index - 1] + Symbol.PipSize * 5; + Chart.DrawText($"peak_mit_{lblCnt}", label, index - 1, yPos, Color.Red); + dir = 1; + } + + // Add the new peak to its own stack + _peakStack.Push(prevHma); + } + + if (isTrough) + { + // New Trough: Check if it mitigates any *previous Troughs* + if( dir > 0 ) + { + mitigatedTroughs = 0; + lblCnt++; + } + + // A new trough (prevHma) mitigates old troughs that are *higher* + while (_troughStack.Count > 0 && prevHma <= _troughStack.Peek()) + { + _troughStack.Pop(); // Pop the mitigated (higher) trough + mitigatedTroughs++; + } + + if (mitigatedTroughs > 0) + { + // Draw mitigation count below the trough bar + // The remaining count is the troughs *lower* than the new one + string label = $"Mit: {mitigatedTroughs} / Rem: {_troughStack.Count}"; + double yPos = Bars.LowPrices[index - 1] - Symbol.PipSize * 5; + Chart.DrawText($"trough_mit_{lblCnt}", label, index - 1, yPos, Color.Lime); + dir = -1; + } + + // Add the new trough to its own stack + _troughStack.Push(prevHma); + } + } + } +} \ No newline at end of file diff --git a/Sources/Indicators/HmaMitigationCount/HmaMitigationCount/HmaMitigationCount.csproj b/Sources/Indicators/HmaMitigationCount/HmaMitigationCount/HmaMitigationCount.csproj new file mode 100644 index 0000000..da1fef1 --- /dev/null +++ b/Sources/Indicators/HmaMitigationCount/HmaMitigationCount/HmaMitigationCount.csproj @@ -0,0 +1,9 @@ + + + net6.0 + + + + + + diff --git a/Sources/Indicators/HmaMitigationSignals.algo b/Sources/Indicators/HmaMitigationSignals.algo new file mode 100644 index 0000000..5286de2 Binary files /dev/null and b/Sources/Indicators/HmaMitigationSignals.algo differ diff --git a/Sources/Indicators/HmaMitigationSignals/.vscode/tasks.json b/Sources/Indicators/HmaMitigationSignals/.vscode/tasks.json new file mode 100644 index 0000000..215166e --- /dev/null +++ b/Sources/Indicators/HmaMitigationSignals/.vscode/tasks.json @@ -0,0 +1 @@ +{"version":"2.0.0","tasks":[{"label":"build","command":"dotnet","type":"process","args":["build","${workspaceFolder}","/property:GenerateFullPaths=true","/consoleLoggerParameters:NoSummary"],"problemMatcher":"$msCompile"}]} \ No newline at end of file diff --git a/Sources/Indicators/HmaMitigationSignals/HmaMitigationSignals.sln b/Sources/Indicators/HmaMitigationSignals/HmaMitigationSignals.sln new file mode 100644 index 0000000..e5eee12 --- /dev/null +++ b/Sources/Indicators/HmaMitigationSignals/HmaMitigationSignals.sln @@ -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}") = "HmaMitigationSignals", "HmaMitigationSignals\HmaMitigationSignals.csproj", "{46b07fe6-e609-40d3-a15f-e381ff0f058e}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {46b07fe6-e609-40d3-a15f-e381ff0f058e}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {46b07fe6-e609-40d3-a15f-e381ff0f058e}.Debug|Any CPU.Build.0 = Debug|Any CPU + {46b07fe6-e609-40d3-a15f-e381ff0f058e}.Release|Any CPU.ActiveCfg = Release|Any CPU + {46b07fe6-e609-40d3-a15f-e381ff0f058e}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/Sources/Indicators/HmaMitigationSignals/HmaMitigationSignals/HmaMitigationSignals.cs b/Sources/Indicators/HmaMitigationSignals/HmaMitigationSignals/HmaMitigationSignals.cs new file mode 100644 index 0000000..b0bbac9 --- /dev/null +++ b/Sources/Indicators/HmaMitigationSignals/HmaMitigationSignals/HmaMitigationSignals.cs @@ -0,0 +1,204 @@ +using cAlgo.API; +using cAlgo.API.Indicators; +using cAlgo.API.Internals; +using System.Collections.Generic; +using System.Linq; +using System; + +namespace cAlgo.Indicators +{ + [Indicator(IsOverlay = true, TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)] + public class HmaMitigationSignals : Indicator + { + [Parameter("HMA Period", DefaultValue = 20, MinValue = 2)] + public int HmaPeriod { get; set; } + + // Use the chart's TimeZone for this input + [Parameter("Session Close Time (Chart TZ)", DefaultValue = "17:30")] + public TimeSpan SessionCloseTime { get; set; } + + [Parameter("Enable Alerts", DefaultValue = true)] + public bool EnableAlerts { get; set; } + + [Parameter("Alert Sound Path", DefaultValue = "")] + public string AlertSoundPath { get; set; } + + [Output("HMA", LineColor = "Orange")] + public IndicatorDataSeries HmaOutput { get; set; } + + private HullMovingAverage _hma; + private Stack _peakStack; + private Stack _troughStack; + + private DateTime _currentSessionEndTimeUtc = DateTime.MinValue; + + // State flags for entry setup + private bool _buySetupActive = false; + private bool _sellSetupActive = false; + + protected override void Initialize() + { + _hma = Indicators.HullMovingAverage(Bars.ClosePrices, HmaPeriod); + _peakStack = new Stack(); + _troughStack = new Stack(); + } + + public override void Calculate(int index) + { + HmaOutput[index] = _hma.Result[index]; + DateTime barOpenTime = Bars.OpenTimes[index]; // This is UTC + + // --- Session Initialization (run once) --- + if (_currentSessionEndTimeUtc == DateTime.MinValue) + { + // Convert the current UTC bar time to the chart's local time zone + DateTime barOpenTimeChartTz = TimeZoneInfo.ConvertTimeFromUtc(barOpenTime, this.TimeZone); + + // Create the session close time for "today" *in the chart's timezone* + DateTime sessionCloseChartTz = barOpenTimeChartTz.Date.Add(SessionCloseTime); + + // Convert this chart-timezone-based closing time back to UTC + DateTime sessionCloseTodayUtc = TimeZoneInfo.ConvertTimeToUtc( sessionCloseChartTz, this.TimeZone); + + if (barOpenTime >= sessionCloseTodayUtc) + { + // Bar is after today's close. Session end is tomorrow (chart time). + // Add 1 day *in chart time* (handles DST) + DateTime nextSessionEndChartTz = sessionCloseChartTz.AddDays(1); + // Convert back to UTC + _currentSessionEndTimeUtc = TimeZoneInfo.ConvertTime(nextSessionEndChartTz, this.TimeZone, TimeZoneInfo.Utc); + } + else + { + // Bar is before today's close. Session end is today (chart time). + _currentSessionEndTimeUtc = sessionCloseTodayUtc; + } + } + + // --- Session Check --- + // Check if the current bar has crossed the session boundary + if (barOpenTime >= _currentSessionEndTimeUtc) + { + // New session starts, clear stacks and drawings + _peakStack.Clear(); + _troughStack.Clear(); + //Chart.RemoveAllObjects(); // Clear old session text and icons + + // Reset setup flags + _buySetupActive = false; + _sellSetupActive = false; + + // Set the *next* session end time, looping in case of market gaps (weekends) + while (barOpenTime >= _currentSessionEndTimeUtc) + { + // Convert current UTC end time to chart time + DateTime currentSessionEndChartTz = TimeZoneInfo.ConvertTimeFromUtc(_currentSessionEndTimeUtc, this.TimeZone); + // Add 1 day *in chart time* (handles DST) + DateTime nextSessionEndChartTz = currentSessionEndChartTz.AddDays(1); + // Convert back to UTC for the next comparison + _currentSessionEndTimeUtc = TimeZoneInfo.ConvertTime(nextSessionEndChartTz, this.TimeZone, TimeZoneInfo.Utc); + } + } + + // Stop signal logic from running on the forming bar (which causes repainting) + if (IsLastBar) + { + return; + } + + // We need at least 3 bars to identify a peak/trough at index - 1 + if (index < 2) + { + return; + } + + // --- 1. Identify new peaks/troughs at index - 1 AND check for entry --- + // All HMA values are now based on closed bars (index, index-1, index-2). + double prevHma = _hma.Result[index - 1]; + double prevHma2 = _hma.Result[index - 2]; + double currentHma = _hma.Result[index]; + + // A peak at index-1 + bool isPeak = (prevHma > prevHma2) && (prevHma > currentHma); + // A trough at index-1 + bool isTrough = (prevHma < prevHma2) && (prevHma < currentHma); + + if (isPeak) + { + // Check if a sell setup was active *before* pushing the new peak + if (_sellSetupActive) + { + // Draw sell icon at the peak (index - 1) + Chart.DrawIcon($"sell_{index - 1}", ChartIconType.DownArrow, index - 1, Bars.HighPrices[index - 1] + Symbol.PipSize * 10, Color.Red); + _sellSetupActive = false; // Reset setup + } + + _peakStack.Push(prevHma); + // Draw count above the peak bar + string peakLabel = $"{_peakStack.Count}"; + double yPos = Bars.HighPrices[index - 1] + Symbol.PipSize * 5; + Chart.DrawText($"peak_{index - 1}", peakLabel, index - 1, yPos, Color.Red); + } + + if (isTrough) + { + // Check if a buy setup was active *before* pushing the new trough + if (_buySetupActive) + { + // Draw buy icon at the trough (index - 1) + Chart.DrawIcon($"buy_{index - 1}", ChartIconType.UpArrow, index - 1, Bars.LowPrices[index - 1] - Symbol.PipSize * 10, Color.Lime); + _buySetupActive = false; // Reset setup + } + + _troughStack.Push(prevHma); + // Draw count below the trough bar + string troughLabel = $"{_troughStack.Count}"; + double yPos = Bars.LowPrices[index - 1] - Symbol.PipSize * 5; + Chart.DrawText($"trough_{index - 1}", troughLabel, index - 1, yPos, Color.Lime); + } + + // --- 2. Check for mitigation (Setup) based on the hma value --- + // Mitigation is checked against the closed bar 'currentHma' (at index). + + // Check peak mitigation (testing highs) + bool peakMitigationTriggeredSetup = false; + while (_peakStack.Count > 0 && currentHma >= _peakStack.Peek()) + { + _peakStack.Pop(); + if (_peakStack.Count <= 1) + { + peakMitigationTriggeredSetup = true; + } + } + if (peakMitigationTriggeredSetup) + { + // Alert only on the first bar the setup becomes active + if (EnableAlerts && !_sellSetupActive && !string.IsNullOrEmpty(AlertSoundPath)) + { + Notifications.PlaySound(AlertSoundPath); + } + _sellSetupActive = true; // Activate sell setup + } + + // Check trough mitigation (testing lows) + bool troughMitigationTriggeredSetup = false; + while (_troughStack.Count > 0 && currentHma <= _troughStack.Peek()) + { + _troughStack.Pop(); + if (_troughStack.Count <= 1) + { + troughMitigationTriggeredSetup = true; + } + } + if (troughMitigationTriggeredSetup) + { + // Alert only on the first bar the setup becomes active + if (EnableAlerts && !_buySetupActive && !string.IsNullOrEmpty(AlertSoundPath)) + { + Notifications.PlaySound(AlertSoundPath); + } + _buySetupActive = true; // Activate buy setup + } + } + } +} \ No newline at end of file diff --git a/Sources/Indicators/HmaMitigationSignals/HmaMitigationSignals/HmaMitigationSignals.csproj b/Sources/Indicators/HmaMitigationSignals/HmaMitigationSignals/HmaMitigationSignals.csproj new file mode 100644 index 0000000..da1fef1 --- /dev/null +++ b/Sources/Indicators/HmaMitigationSignals/HmaMitigationSignals/HmaMitigationSignals.csproj @@ -0,0 +1,9 @@ + + + net6.0 + + + + + + diff --git a/Sources/Indicators/HmaMitigationSignals/HmaMitigationSignals/obj/Debug/net6.0/.NETCoreApp,Version=v6.0.AssemblyAttributes.cs b/Sources/Indicators/HmaMitigationSignals/HmaMitigationSignals/obj/Debug/net6.0/.NETCoreApp,Version=v6.0.AssemblyAttributes.cs new file mode 100644 index 0000000..32c95f9 --- /dev/null +++ b/Sources/Indicators/HmaMitigationSignals/HmaMitigationSignals/obj/Debug/net6.0/.NETCoreApp,Version=v6.0.AssemblyAttributes.cs @@ -0,0 +1,4 @@ +// +using System; +using System.Reflection; +[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v6.0", FrameworkDisplayName = "")] diff --git a/Sources/Indicators/HmaMitigationSignals/HmaMitigationSignals/obj/Debug/net6.0/HmaMitigationSignals.AssemblyInfo.cs b/Sources/Indicators/HmaMitigationSignals/HmaMitigationSignals/obj/Debug/net6.0/HmaMitigationSignals.AssemblyInfo.cs new file mode 100644 index 0000000..87c1693 --- /dev/null +++ b/Sources/Indicators/HmaMitigationSignals/HmaMitigationSignals/obj/Debug/net6.0/HmaMitigationSignals.AssemblyInfo.cs @@ -0,0 +1,22 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +using System; +using System.Reflection; + +[assembly: System.Reflection.AssemblyCompanyAttribute("HmaMitigationSignals")] +[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] +[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0")] +[assembly: System.Reflection.AssemblyProductAttribute("HmaMitigationSignals")] +[assembly: System.Reflection.AssemblyTitleAttribute("HmaMitigationSignals")] +[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] + +// Generated by the MSBuild WriteCodeFragment class. + diff --git a/Sources/Indicators/HmaMitigationSignals/HmaMitigationSignals/obj/Debug/net6.0/HmaMitigationSignals.AssemblyInfoInputs.cache b/Sources/Indicators/HmaMitigationSignals/HmaMitigationSignals/obj/Debug/net6.0/HmaMitigationSignals.AssemblyInfoInputs.cache new file mode 100644 index 0000000..9f3e929 --- /dev/null +++ b/Sources/Indicators/HmaMitigationSignals/HmaMitigationSignals/obj/Debug/net6.0/HmaMitigationSignals.AssemblyInfoInputs.cache @@ -0,0 +1 @@ +eba8fecb039ecb3c0a142d0f2a9b3f551d940de1 diff --git a/Sources/Indicators/HmaMitigationSignals/HmaMitigationSignals/obj/Debug/net6.0/HmaMitigationSignals.GeneratedMSBuildEditorConfig.editorconfig b/Sources/Indicators/HmaMitigationSignals/HmaMitigationSignals/obj/Debug/net6.0/HmaMitigationSignals.GeneratedMSBuildEditorConfig.editorconfig new file mode 100644 index 0000000..073d1ae --- /dev/null +++ b/Sources/Indicators/HmaMitigationSignals/HmaMitigationSignals/obj/Debug/net6.0/HmaMitigationSignals.GeneratedMSBuildEditorConfig.editorconfig @@ -0,0 +1,10 @@ +is_global = true +build_property.TargetFramework = net6.0 +build_property.TargetPlatformMinVersion = +build_property.UsingMicrosoftNETSdkWeb = +build_property.ProjectTypeGuids = +build_property.InvariantGlobalization = +build_property.PlatformNeutralAssembly = +build_property._SupportedPlatformList = Linux,macOS,Windows +build_property.RootNamespace = HmaMitigationSignals +build_property.ProjectDir = C:\Users\Brummel\Documents\cAlgo\Sources\Indicators\HmaMitigationSignals\HmaMitigationSignals\ diff --git a/Sources/Indicators/HmaMitigationSignals/HmaMitigationSignals/obj/Debug/net6.0/HmaMitigationSignals.assets.cache b/Sources/Indicators/HmaMitigationSignals/HmaMitigationSignals/obj/Debug/net6.0/HmaMitigationSignals.assets.cache new file mode 100644 index 0000000..55fe793 Binary files /dev/null and b/Sources/Indicators/HmaMitigationSignals/HmaMitigationSignals/obj/Debug/net6.0/HmaMitigationSignals.assets.cache differ diff --git a/Sources/Indicators/HmaMitigationSignals/HmaMitigationSignals/obj/Debug/net6.0/HmaMitigationSignals.csproj.AssemblyReference.cache b/Sources/Indicators/HmaMitigationSignals/HmaMitigationSignals/obj/Debug/net6.0/HmaMitigationSignals.csproj.AssemblyReference.cache new file mode 100644 index 0000000..50043b7 Binary files /dev/null and b/Sources/Indicators/HmaMitigationSignals/HmaMitigationSignals/obj/Debug/net6.0/HmaMitigationSignals.csproj.AssemblyReference.cache differ diff --git a/Sources/Indicators/HmaMitigationSignals/HmaMitigationSignals/obj/HmaMitigationSignals.csproj.nuget.dgspec.json b/Sources/Indicators/HmaMitigationSignals/HmaMitigationSignals/obj/HmaMitigationSignals.csproj.nuget.dgspec.json new file mode 100644 index 0000000..f587421 --- /dev/null +++ b/Sources/Indicators/HmaMitigationSignals/HmaMitigationSignals/obj/HmaMitigationSignals.csproj.nuget.dgspec.json @@ -0,0 +1,66 @@ +{ + "format": 1, + "restore": { + "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Indicators\\HmaMitigationSignals\\HmaMitigationSignals\\HmaMitigationSignals.csproj": {} + }, + "projects": { + "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Indicators\\HmaMitigationSignals\\HmaMitigationSignals\\HmaMitigationSignals.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Indicators\\HmaMitigationSignals\\HmaMitigationSignals\\HmaMitigationSignals.csproj", + "projectName": "HmaMitigationSignals", + "projectPath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Indicators\\HmaMitigationSignals\\HmaMitigationSignals\\HmaMitigationSignals.csproj", + "packagesPath": "C:\\Users\\Brummel\\.nuget\\packages\\", + "outputPath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Indicators\\HmaMitigationSignals\\HmaMitigationSignals\\obj\\", + "projectStyle": "PackageReference", + "configFilePaths": [ + "C:\\Users\\Brummel\\AppData\\Roaming\\NuGet\\NuGet.Config" + ], + "originalTargetFrameworks": [ + "net6.0" + ], + "sources": { + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net6.0": { + "targetAlias": "net6.0", + "projectReferences": {} + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + } + }, + "frameworks": { + "net6.0": { + "targetAlias": "net6.0", + "dependencies": { + "cTrader.Automate": { + "target": "Package", + "version": "[*, )" + } + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\6.0.200\\RuntimeIdentifierGraph.json" + } + } + } + } +} \ No newline at end of file diff --git a/Sources/Indicators/HmaMitigationSignals/HmaMitigationSignals/obj/HmaMitigationSignals.csproj.nuget.g.props b/Sources/Indicators/HmaMitigationSignals/HmaMitigationSignals/obj/HmaMitigationSignals.csproj.nuget.g.props new file mode 100644 index 0000000..0b161ac --- /dev/null +++ b/Sources/Indicators/HmaMitigationSignals/HmaMitigationSignals/obj/HmaMitigationSignals.csproj.nuget.g.props @@ -0,0 +1,21 @@ + + + + True + NuGet + $(MSBuildThisFileDirectory)project.assets.json + $(UserProfile)\.nuget\packages\ + C:\Users\Brummel\.nuget\packages\ + PackageReference + 6.1.0 + + + + + + + + + C:\Users\Brummel\.nuget\packages\ctrader.automate\1.0.14 + + \ No newline at end of file diff --git a/Sources/Indicators/HmaMitigationSignals/HmaMitigationSignals/obj/HmaMitigationSignals.csproj.nuget.g.targets b/Sources/Indicators/HmaMitigationSignals/HmaMitigationSignals/obj/HmaMitigationSignals.csproj.nuget.g.targets new file mode 100644 index 0000000..46f142c --- /dev/null +++ b/Sources/Indicators/HmaMitigationSignals/HmaMitigationSignals/obj/HmaMitigationSignals.csproj.nuget.g.targets @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Sources/Indicators/HmaMitigationSignals/HmaMitigationSignals/obj/project.assets.json b/Sources/Indicators/HmaMitigationSignals/HmaMitigationSignals/obj/project.assets.json new file mode 100644 index 0000000..592a61b --- /dev/null +++ b/Sources/Indicators/HmaMitigationSignals/HmaMitigationSignals/obj/project.assets.json @@ -0,0 +1,139 @@ +{ + "version": 3, + "targets": { + "net6.0": { + "cTrader.Automate/1.0.14": { + "type": "package", + "compile": { + "lib/net6.0/cAlgo.API.dll": {} + }, + "runtime": { + "lib/net6.0/cAlgo.API.dll": {} + }, + "build": { + "build/cTrader.Automate.props": {}, + "build/cTrader.Automate.targets": {} + } + } + } + }, + "libraries": { + "cTrader.Automate/1.0.14": { + "sha512": "eNwE7WL90MGBKb5MuLAtZLdQy0vxkI5EVhLWAQ9S83EAdYkGAzdccvGFLk2oqmtSGBtD+gEpyrJUW/ej4dI4jw==", + "type": "package", + "path": "ctrader.automate/1.0.14", + "hasTools": true, + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "build/cTrader.Automate.props", + "build/cTrader.Automate.targets", + "ctrader.automate.1.0.14.nupkg.sha512", + "ctrader.automate.nuspec", + "eula.md", + "icon.png", + "lib/net40/cAlgo.API.dll", + "lib/net40/cAlgo.API.xml", + "lib/net6.0/cAlgo.API.dll", + "lib/net6.0/cAlgo.API.xml", + "tools/net472/Core.AlgoFormat.Compose.Reflection.dll", + "tools/net472/Core.AlgoFormat.Writer.dll", + "tools/net472/Core.AlgoFormat.dll", + "tools/net472/Core.Domain.Primitives.dll", + "tools/net472/Newtonsoft.Json.dll", + "tools/net472/System.Buffers.dll", + "tools/net472/System.Collections.Immutable.dll", + "tools/net472/System.Memory.dll", + "tools/net472/System.Numerics.Vectors.dll", + "tools/net472/System.Reflection.Metadata.dll", + "tools/net472/System.Reflection.MetadataLoadContext.dll", + "tools/net472/System.Runtime.CompilerServices.Unsafe.dll", + "tools/net472/cTrader.Automate.Sdk.Tasks.dll", + "tools/net6.0/Core.AlgoFormat.Compose.Reflection.dll", + "tools/net6.0/Core.AlgoFormat.Writer.dll", + "tools/net6.0/Core.AlgoFormat.dll", + "tools/net6.0/Core.Connection.Protobuf.Common.dll", + "tools/net6.0/Core.Domain.Primitives.dll", + "tools/net6.0/Microsoft.Win32.SystemEvents.dll", + "tools/net6.0/Newtonsoft.Json.dll", + "tools/net6.0/System.Drawing.Common.dll", + "tools/net6.0/System.Reflection.MetadataLoadContext.dll", + "tools/net6.0/System.Security.Permissions.dll", + "tools/net6.0/System.Windows.Extensions.dll", + "tools/net6.0/cTrader.Automate.Sdk.Tasks.dll", + "tools/net6.0/protobuf-net.Core.dll", + "tools/net6.0/protobuf-net.dll", + "tools/net6.0/runtimes/unix/lib/net6.0/System.Drawing.Common.dll", + "tools/net6.0/runtimes/win/lib/net6.0/Microsoft.Win32.SystemEvents.dll", + "tools/net6.0/runtimes/win/lib/net6.0/System.Drawing.Common.dll", + "tools/net6.0/runtimes/win/lib/net6.0/System.Windows.Extensions.dll" + ] + } + }, + "projectFileDependencyGroups": { + "net6.0": [ + "cTrader.Automate >= *" + ] + }, + "packageFolders": { + "C:\\Users\\Brummel\\.nuget\\packages\\": {} + }, + "project": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Indicators\\HmaMitigationSignals\\HmaMitigationSignals\\HmaMitigationSignals.csproj", + "projectName": "HmaMitigationSignals", + "projectPath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Indicators\\HmaMitigationSignals\\HmaMitigationSignals\\HmaMitigationSignals.csproj", + "packagesPath": "C:\\Users\\Brummel\\.nuget\\packages\\", + "outputPath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Indicators\\HmaMitigationSignals\\HmaMitigationSignals\\obj\\", + "projectStyle": "PackageReference", + "configFilePaths": [ + "C:\\Users\\Brummel\\AppData\\Roaming\\NuGet\\NuGet.Config" + ], + "originalTargetFrameworks": [ + "net6.0" + ], + "sources": { + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net6.0": { + "targetAlias": "net6.0", + "projectReferences": {} + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + } + }, + "frameworks": { + "net6.0": { + "targetAlias": "net6.0", + "dependencies": { + "cTrader.Automate": { + "target": "Package", + "version": "[*, )" + } + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\6.0.200\\RuntimeIdentifierGraph.json" + } + } + } +} \ No newline at end of file diff --git a/Sources/Indicators/HmaMitigationSignals/HmaMitigationSignals/obj/project.nuget.cache b/Sources/Indicators/HmaMitigationSignals/HmaMitigationSignals/obj/project.nuget.cache new file mode 100644 index 0000000..cca9cd0 --- /dev/null +++ b/Sources/Indicators/HmaMitigationSignals/HmaMitigationSignals/obj/project.nuget.cache @@ -0,0 +1,10 @@ +{ + "version": 2, + "dgSpecHash": "DxEeUy28ZBdzEtfthwL77wzQ4ke6Qq+9ihU8hGHHq9Zkz9SR9yfNrOVC7zcjvC2fluF4I2zsexRZHVqmVVc3IA==", + "success": true, + "projectFilePath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Indicators\\HmaMitigationSignals\\HmaMitigationSignals\\HmaMitigationSignals.csproj", + "expectedPackageFiles": [ + "C:\\Users\\Brummel\\.nuget\\packages\\ctrader.automate\\1.0.14\\ctrader.automate.1.0.14.nupkg.sha512" + ], + "logs": [] +} \ No newline at end of file diff --git a/Sources/Indicators/HmaSmaCrossoverSignal.algo b/Sources/Indicators/HmaSmaCrossoverSignal.algo new file mode 100644 index 0000000..d728292 Binary files /dev/null and b/Sources/Indicators/HmaSmaCrossoverSignal.algo differ diff --git a/Sources/Indicators/HmaSmaCrossoverSignal/HmaSmaCrossoverSignal.sln b/Sources/Indicators/HmaSmaCrossoverSignal/HmaSmaCrossoverSignal.sln new file mode 100644 index 0000000..ed32b08 --- /dev/null +++ b/Sources/Indicators/HmaSmaCrossoverSignal/HmaSmaCrossoverSignal.sln @@ -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}") = "HmaSmaCrossoverSignal", "HmaSmaCrossoverSignal\HmaSmaCrossoverSignal.csproj", "{82d7192b-57bd-41f4-b4a0-b60264330377}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {82d7192b-57bd-41f4-b4a0-b60264330377}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {82d7192b-57bd-41f4-b4a0-b60264330377}.Debug|Any CPU.Build.0 = Debug|Any CPU + {82d7192b-57bd-41f4-b4a0-b60264330377}.Release|Any CPU.ActiveCfg = Release|Any CPU + {82d7192b-57bd-41f4-b4a0-b60264330377}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/Sources/Indicators/HmaSmaCrossoverSignal/HmaSmaCrossoverSignal/HmaSmaCrossoverSignal.cs b/Sources/Indicators/HmaSmaCrossoverSignal/HmaSmaCrossoverSignal/HmaSmaCrossoverSignal.cs new file mode 100644 index 0000000..2296db4 --- /dev/null +++ b/Sources/Indicators/HmaSmaCrossoverSignal/HmaSmaCrossoverSignal/HmaSmaCrossoverSignal.cs @@ -0,0 +1,101 @@ +using cAlgo.API; +using cAlgo.API.Indicators; + +namespace cAlgo.Indicators +{ + // Indicator in a separate window, shows HMA A (fast HMA) and trade signals. + [Indicator(IsOverlay = false, TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)] + public class HmaSmaCrossoverSignal : Indicator + { + [Parameter("HMA A Period (Fast)", DefaultValue = 20, Group = "Periods")] + public int HmaAPeriod { get; set; } + + [Parameter("HMA B Period (Slow)", DefaultValue = 250, Group = "Periods")] + public int HmaBPeriod { get; set; } + + [Parameter("SMA C Period (Base)", DefaultValue = 200, Group = "Periods")] + public int SmaCPeriod { get; set; } + + // Output for HMA A to provide context for signals + [Output("HMA A", LineColor = "Cyan")] + public IndicatorDataSeries HmaAOutput { get; set; } + + // Output for Buy signals + [Output("Buy Signal", LineColor = "Green", PlotType = PlotType.Points, Thickness = 8)] + public IndicatorDataSeries BuySignal { get; set; } + + // Output for Sell signals + [Output("Sell Signal", LineColor = "Red", PlotType = PlotType.Points, Thickness = 8)] + public IndicatorDataSeries SellSignal { get; set; } + + // Internal indicator references + private HullMovingAverage _hmaA; + private HullMovingAverage _hmaB; + private SimpleMovingAverage _smaC; + + protected override void Initialize() + { + // Initialize the internal indicators using the close prices + _hmaA = Indicators.HullMovingAverage(Bars.ClosePrices, HmaAPeriod); + _hmaB = Indicators.HullMovingAverage(Bars.ClosePrices, HmaBPeriod); + _smaC = Indicators.SimpleMovingAverage(Bars.ClosePrices, SmaCPeriod); + } + + public override void Calculate(int index) + { + // Assign HMA A value to the output series for the current index + HmaAOutput[index] = _hmaA.Result[index]; + + // We need at least 3 bars (index, index-1, index-2) for peak/trough detection at index-1 + if (index < 2) + { + BuySignal[index] = double.NaN; + SellSignal[index] = double.NaN; + return; + } + + // Get HMA A values for peak/trough detection + double hmaA_curr = _hmaA.Result[index]; + double hmaA_prev = _hmaA.Result[index - 1]; + double hmaA_prev2 = _hmaA.Result[index - 2]; + + // Get values for signal conditions (at index - 1) + double hmaB_prev = _hmaB.Result[index - 1]; + double smaC_prev = _smaC.Result[index - 1]; + + // Detect peak or trough at index - 1 (the last closed bar confirmed by the current bar) + // A peak occurs if the middle bar (index-1) is higher than its neighbors (index-2 and index) + bool isPeak = (hmaA_prev2 < hmaA_prev) && (hmaA_prev > hmaA_curr); + // A trough occurs if the middle bar (index-1) is lower than its neighbors + bool isTrough = (hmaA_prev2 > hmaA_prev) && (hmaA_prev < hmaA_curr); + + // Initialize signals for the current bar (index) to NaN + BuySignal[index] = double.NaN; + SellSignal[index] = double.NaN; + + // Reset signals at index - 1 (to handle repainting if the current bar 'index' changes) + BuySignal[index - 1] = double.NaN; + SellSignal[index - 1] = double.NaN; + + // --- Sell Signal Condition --- + // B < C (Slow HMA below Base SMA) + // A > C (Fast HMA above Base SMA) + // A forms a peak (at index - 1) + if ((hmaB_prev < smaC_prev) && (hmaA_prev > smaC_prev) && isPeak) + { + // Place the signal dot at the peak (index - 1) at the HMA A level + SellSignal[index - 1] = hmaA_prev; + } + + // --- Buy Signal Condition --- + // B > C (Slow HMA above Base SMA) + // A < C (Fast HMA below Base SMA) + // A forms a trough (at index - 1) + if ((hmaB_prev > smaC_prev) && (hmaA_prev < smaC_prev) && isTrough) + { + // Place the signal dot at the trough (index - 1) at the HMA A level + BuySignal[index - 1] = hmaA_prev; + } + } + } +} \ No newline at end of file diff --git a/Sources/Indicators/HmaSmaCrossoverSignal/HmaSmaCrossoverSignal/HmaSmaCrossoverSignal.csproj b/Sources/Indicators/HmaSmaCrossoverSignal/HmaSmaCrossoverSignal/HmaSmaCrossoverSignal.csproj new file mode 100644 index 0000000..c8ec96f --- /dev/null +++ b/Sources/Indicators/HmaSmaCrossoverSignal/HmaSmaCrossoverSignal/HmaSmaCrossoverSignal.csproj @@ -0,0 +1,9 @@ + + + + net6.0 + + + + + \ No newline at end of file diff --git a/Sources/Indicators/HmaSmaCrossoverSignal/HmaSmaCrossoverSignal/obj/Debug/net6.0/.NETCoreApp,Version=v6.0.AssemblyAttributes.cs b/Sources/Indicators/HmaSmaCrossoverSignal/HmaSmaCrossoverSignal/obj/Debug/net6.0/.NETCoreApp,Version=v6.0.AssemblyAttributes.cs new file mode 100644 index 0000000..32c95f9 --- /dev/null +++ b/Sources/Indicators/HmaSmaCrossoverSignal/HmaSmaCrossoverSignal/obj/Debug/net6.0/.NETCoreApp,Version=v6.0.AssemblyAttributes.cs @@ -0,0 +1,4 @@ +// +using System; +using System.Reflection; +[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v6.0", FrameworkDisplayName = "")] diff --git a/Sources/Indicators/HmaSmaCrossoverSignal/HmaSmaCrossoverSignal/obj/Debug/net6.0/HmaSmaCrossoverSignal.AssemblyInfo.cs b/Sources/Indicators/HmaSmaCrossoverSignal/HmaSmaCrossoverSignal/obj/Debug/net6.0/HmaSmaCrossoverSignal.AssemblyInfo.cs new file mode 100644 index 0000000..47a7a17 --- /dev/null +++ b/Sources/Indicators/HmaSmaCrossoverSignal/HmaSmaCrossoverSignal/obj/Debug/net6.0/HmaSmaCrossoverSignal.AssemblyInfo.cs @@ -0,0 +1,22 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +using System; +using System.Reflection; + +[assembly: System.Reflection.AssemblyCompanyAttribute("HmaSmaCrossoverSignal")] +[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] +[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0")] +[assembly: System.Reflection.AssemblyProductAttribute("HmaSmaCrossoverSignal")] +[assembly: System.Reflection.AssemblyTitleAttribute("HmaSmaCrossoverSignal")] +[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] + +// Generated by the MSBuild WriteCodeFragment class. + diff --git a/Sources/Indicators/HmaSmaCrossoverSignal/HmaSmaCrossoverSignal/obj/Debug/net6.0/HmaSmaCrossoverSignal.AssemblyInfoInputs.cache b/Sources/Indicators/HmaSmaCrossoverSignal/HmaSmaCrossoverSignal/obj/Debug/net6.0/HmaSmaCrossoverSignal.AssemblyInfoInputs.cache new file mode 100644 index 0000000..f9e0068 --- /dev/null +++ b/Sources/Indicators/HmaSmaCrossoverSignal/HmaSmaCrossoverSignal/obj/Debug/net6.0/HmaSmaCrossoverSignal.AssemblyInfoInputs.cache @@ -0,0 +1 @@ +68c010eb8d891632f96c629d00071703b797f10f diff --git a/Sources/Indicators/HmaSmaCrossoverSignal/HmaSmaCrossoverSignal/obj/Debug/net6.0/HmaSmaCrossoverSignal.GeneratedMSBuildEditorConfig.editorconfig b/Sources/Indicators/HmaSmaCrossoverSignal/HmaSmaCrossoverSignal/obj/Debug/net6.0/HmaSmaCrossoverSignal.GeneratedMSBuildEditorConfig.editorconfig new file mode 100644 index 0000000..71b7f07 --- /dev/null +++ b/Sources/Indicators/HmaSmaCrossoverSignal/HmaSmaCrossoverSignal/obj/Debug/net6.0/HmaSmaCrossoverSignal.GeneratedMSBuildEditorConfig.editorconfig @@ -0,0 +1,10 @@ +is_global = true +build_property.TargetFramework = net6.0 +build_property.TargetPlatformMinVersion = +build_property.UsingMicrosoftNETSdkWeb = +build_property.ProjectTypeGuids = +build_property.InvariantGlobalization = +build_property.PlatformNeutralAssembly = +build_property._SupportedPlatformList = Linux,macOS,Windows +build_property.RootNamespace = HmaSmaCrossoverSignal +build_property.ProjectDir = C:\Users\Brummel\Documents\cAlgo\Sources\Indicators\HmaSmaCrossoverSignal\HmaSmaCrossoverSignal\ diff --git a/Sources/Indicators/HmaSmaCrossoverSignal/HmaSmaCrossoverSignal/obj/Debug/net6.0/HmaSmaCrossoverSignal.assets.cache b/Sources/Indicators/HmaSmaCrossoverSignal/HmaSmaCrossoverSignal/obj/Debug/net6.0/HmaSmaCrossoverSignal.assets.cache new file mode 100644 index 0000000..c8cec84 Binary files /dev/null and b/Sources/Indicators/HmaSmaCrossoverSignal/HmaSmaCrossoverSignal/obj/Debug/net6.0/HmaSmaCrossoverSignal.assets.cache differ diff --git a/Sources/Indicators/HmaSmaCrossoverSignal/HmaSmaCrossoverSignal/obj/Debug/net6.0/HmaSmaCrossoverSignal.csproj.AssemblyReference.cache b/Sources/Indicators/HmaSmaCrossoverSignal/HmaSmaCrossoverSignal/obj/Debug/net6.0/HmaSmaCrossoverSignal.csproj.AssemblyReference.cache new file mode 100644 index 0000000..50043b7 Binary files /dev/null and b/Sources/Indicators/HmaSmaCrossoverSignal/HmaSmaCrossoverSignal/obj/Debug/net6.0/HmaSmaCrossoverSignal.csproj.AssemblyReference.cache differ diff --git a/Sources/Indicators/HmaSmaCrossoverSignal/HmaSmaCrossoverSignal/obj/HmaSmaCrossoverSignal.csproj.nuget.dgspec.json b/Sources/Indicators/HmaSmaCrossoverSignal/HmaSmaCrossoverSignal/obj/HmaSmaCrossoverSignal.csproj.nuget.dgspec.json new file mode 100644 index 0000000..1cf2a99 --- /dev/null +++ b/Sources/Indicators/HmaSmaCrossoverSignal/HmaSmaCrossoverSignal/obj/HmaSmaCrossoverSignal.csproj.nuget.dgspec.json @@ -0,0 +1,66 @@ +{ + "format": 1, + "restore": { + "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Indicators\\HmaSmaCrossoverSignal\\HmaSmaCrossoverSignal\\HmaSmaCrossoverSignal.csproj": {} + }, + "projects": { + "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Indicators\\HmaSmaCrossoverSignal\\HmaSmaCrossoverSignal\\HmaSmaCrossoverSignal.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Indicators\\HmaSmaCrossoverSignal\\HmaSmaCrossoverSignal\\HmaSmaCrossoverSignal.csproj", + "projectName": "HmaSmaCrossoverSignal", + "projectPath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Indicators\\HmaSmaCrossoverSignal\\HmaSmaCrossoverSignal\\HmaSmaCrossoverSignal.csproj", + "packagesPath": "C:\\Users\\Brummel\\.nuget\\packages\\", + "outputPath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Indicators\\HmaSmaCrossoverSignal\\HmaSmaCrossoverSignal\\obj\\", + "projectStyle": "PackageReference", + "configFilePaths": [ + "C:\\Users\\Brummel\\AppData\\Roaming\\NuGet\\NuGet.Config" + ], + "originalTargetFrameworks": [ + "net6.0" + ], + "sources": { + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net6.0": { + "targetAlias": "net6.0", + "projectReferences": {} + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + } + }, + "frameworks": { + "net6.0": { + "targetAlias": "net6.0", + "dependencies": { + "cTrader.Automate": { + "target": "Package", + "version": "[*, )" + } + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\6.0.200\\RuntimeIdentifierGraph.json" + } + } + } + } +} \ No newline at end of file diff --git a/Sources/Indicators/HmaSmaCrossoverSignal/HmaSmaCrossoverSignal/obj/HmaSmaCrossoverSignal.csproj.nuget.g.props b/Sources/Indicators/HmaSmaCrossoverSignal/HmaSmaCrossoverSignal/obj/HmaSmaCrossoverSignal.csproj.nuget.g.props new file mode 100644 index 0000000..0b161ac --- /dev/null +++ b/Sources/Indicators/HmaSmaCrossoverSignal/HmaSmaCrossoverSignal/obj/HmaSmaCrossoverSignal.csproj.nuget.g.props @@ -0,0 +1,21 @@ + + + + True + NuGet + $(MSBuildThisFileDirectory)project.assets.json + $(UserProfile)\.nuget\packages\ + C:\Users\Brummel\.nuget\packages\ + PackageReference + 6.1.0 + + + + + + + + + C:\Users\Brummel\.nuget\packages\ctrader.automate\1.0.14 + + \ No newline at end of file diff --git a/Sources/Indicators/HmaSmaCrossoverSignal/HmaSmaCrossoverSignal/obj/HmaSmaCrossoverSignal.csproj.nuget.g.targets b/Sources/Indicators/HmaSmaCrossoverSignal/HmaSmaCrossoverSignal/obj/HmaSmaCrossoverSignal.csproj.nuget.g.targets new file mode 100644 index 0000000..46f142c --- /dev/null +++ b/Sources/Indicators/HmaSmaCrossoverSignal/HmaSmaCrossoverSignal/obj/HmaSmaCrossoverSignal.csproj.nuget.g.targets @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Sources/Indicators/HmaSmaCrossoverSignal/HmaSmaCrossoverSignal/obj/project.assets.json b/Sources/Indicators/HmaSmaCrossoverSignal/HmaSmaCrossoverSignal/obj/project.assets.json new file mode 100644 index 0000000..036fc56 --- /dev/null +++ b/Sources/Indicators/HmaSmaCrossoverSignal/HmaSmaCrossoverSignal/obj/project.assets.json @@ -0,0 +1,139 @@ +{ + "version": 3, + "targets": { + "net6.0": { + "cTrader.Automate/1.0.14": { + "type": "package", + "compile": { + "lib/net6.0/cAlgo.API.dll": {} + }, + "runtime": { + "lib/net6.0/cAlgo.API.dll": {} + }, + "build": { + "build/cTrader.Automate.props": {}, + "build/cTrader.Automate.targets": {} + } + } + } + }, + "libraries": { + "cTrader.Automate/1.0.14": { + "sha512": "eNwE7WL90MGBKb5MuLAtZLdQy0vxkI5EVhLWAQ9S83EAdYkGAzdccvGFLk2oqmtSGBtD+gEpyrJUW/ej4dI4jw==", + "type": "package", + "path": "ctrader.automate/1.0.14", + "hasTools": true, + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "build/cTrader.Automate.props", + "build/cTrader.Automate.targets", + "ctrader.automate.1.0.14.nupkg.sha512", + "ctrader.automate.nuspec", + "eula.md", + "icon.png", + "lib/net40/cAlgo.API.dll", + "lib/net40/cAlgo.API.xml", + "lib/net6.0/cAlgo.API.dll", + "lib/net6.0/cAlgo.API.xml", + "tools/net472/Core.AlgoFormat.Compose.Reflection.dll", + "tools/net472/Core.AlgoFormat.Writer.dll", + "tools/net472/Core.AlgoFormat.dll", + "tools/net472/Core.Domain.Primitives.dll", + "tools/net472/Newtonsoft.Json.dll", + "tools/net472/System.Buffers.dll", + "tools/net472/System.Collections.Immutable.dll", + "tools/net472/System.Memory.dll", + "tools/net472/System.Numerics.Vectors.dll", + "tools/net472/System.Reflection.Metadata.dll", + "tools/net472/System.Reflection.MetadataLoadContext.dll", + "tools/net472/System.Runtime.CompilerServices.Unsafe.dll", + "tools/net472/cTrader.Automate.Sdk.Tasks.dll", + "tools/net6.0/Core.AlgoFormat.Compose.Reflection.dll", + "tools/net6.0/Core.AlgoFormat.Writer.dll", + "tools/net6.0/Core.AlgoFormat.dll", + "tools/net6.0/Core.Connection.Protobuf.Common.dll", + "tools/net6.0/Core.Domain.Primitives.dll", + "tools/net6.0/Microsoft.Win32.SystemEvents.dll", + "tools/net6.0/Newtonsoft.Json.dll", + "tools/net6.0/System.Drawing.Common.dll", + "tools/net6.0/System.Reflection.MetadataLoadContext.dll", + "tools/net6.0/System.Security.Permissions.dll", + "tools/net6.0/System.Windows.Extensions.dll", + "tools/net6.0/cTrader.Automate.Sdk.Tasks.dll", + "tools/net6.0/protobuf-net.Core.dll", + "tools/net6.0/protobuf-net.dll", + "tools/net6.0/runtimes/unix/lib/net6.0/System.Drawing.Common.dll", + "tools/net6.0/runtimes/win/lib/net6.0/Microsoft.Win32.SystemEvents.dll", + "tools/net6.0/runtimes/win/lib/net6.0/System.Drawing.Common.dll", + "tools/net6.0/runtimes/win/lib/net6.0/System.Windows.Extensions.dll" + ] + } + }, + "projectFileDependencyGroups": { + "net6.0": [ + "cTrader.Automate >= *" + ] + }, + "packageFolders": { + "C:\\Users\\Brummel\\.nuget\\packages\\": {} + }, + "project": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Indicators\\HmaSmaCrossoverSignal\\HmaSmaCrossoverSignal\\HmaSmaCrossoverSignal.csproj", + "projectName": "HmaSmaCrossoverSignal", + "projectPath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Indicators\\HmaSmaCrossoverSignal\\HmaSmaCrossoverSignal\\HmaSmaCrossoverSignal.csproj", + "packagesPath": "C:\\Users\\Brummel\\.nuget\\packages\\", + "outputPath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Indicators\\HmaSmaCrossoverSignal\\HmaSmaCrossoverSignal\\obj\\", + "projectStyle": "PackageReference", + "configFilePaths": [ + "C:\\Users\\Brummel\\AppData\\Roaming\\NuGet\\NuGet.Config" + ], + "originalTargetFrameworks": [ + "net6.0" + ], + "sources": { + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net6.0": { + "targetAlias": "net6.0", + "projectReferences": {} + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + } + }, + "frameworks": { + "net6.0": { + "targetAlias": "net6.0", + "dependencies": { + "cTrader.Automate": { + "target": "Package", + "version": "[*, )" + } + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\6.0.200\\RuntimeIdentifierGraph.json" + } + } + } +} \ No newline at end of file diff --git a/Sources/Indicators/HmaSmaCrossoverSignal/HmaSmaCrossoverSignal/obj/project.nuget.cache b/Sources/Indicators/HmaSmaCrossoverSignal/HmaSmaCrossoverSignal/obj/project.nuget.cache new file mode 100644 index 0000000..0cfe061 --- /dev/null +++ b/Sources/Indicators/HmaSmaCrossoverSignal/HmaSmaCrossoverSignal/obj/project.nuget.cache @@ -0,0 +1,10 @@ +{ + "version": 2, + "dgSpecHash": "gsY+QQDmIPx+sSjbH7sxPisXr6pkw57LnDv8vmwyNX36zT16qTTfyBOGiCeYbT44fvyFC/YvqEHJXGdREbyY6w==", + "success": true, + "projectFilePath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Indicators\\HmaSmaCrossoverSignal\\HmaSmaCrossoverSignal\\HmaSmaCrossoverSignal.csproj", + "expectedPackageFiles": [ + "C:\\Users\\Brummel\\.nuget\\packages\\ctrader.automate\\1.0.14\\ctrader.automate.1.0.14.nupkg.sha512" + ], + "logs": [] +} \ No newline at end of file diff --git a/Sources/Indicators/ImbalanceAlert.algo b/Sources/Indicators/ImbalanceAlert.algo new file mode 100644 index 0000000..c1fa991 Binary files /dev/null and b/Sources/Indicators/ImbalanceAlert.algo differ diff --git a/Sources/Indicators/ImbalanceAlert/ImbalanceAlert.sln b/Sources/Indicators/ImbalanceAlert/ImbalanceAlert.sln new file mode 100644 index 0000000..7bfe896 --- /dev/null +++ b/Sources/Indicators/ImbalanceAlert/ImbalanceAlert.sln @@ -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}") = "ImbalanceAlert", "ImbalanceAlert\ImbalanceAlert.csproj", "{c36a0d9f-f01d-43c6-8718-c23e761197a5}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {c36a0d9f-f01d-43c6-8718-c23e761197a5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {c36a0d9f-f01d-43c6-8718-c23e761197a5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {c36a0d9f-f01d-43c6-8718-c23e761197a5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {c36a0d9f-f01d-43c6-8718-c23e761197a5}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/Sources/Indicators/ImbalanceAlert/ImbalanceAlert/ImbalanceAlert.cs b/Sources/Indicators/ImbalanceAlert/ImbalanceAlert/ImbalanceAlert.cs new file mode 100644 index 0000000..befe35b --- /dev/null +++ b/Sources/Indicators/ImbalanceAlert/ImbalanceAlert/ImbalanceAlert.cs @@ -0,0 +1,170 @@ +using System; +using cAlgo.API; +using cAlgo.API.Internals; + +namespace cAlgo +{ + [Indicator(IsOverlay = true, TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)] + public class ImbalanceAlert : Indicator + { + #region Parameters + + // Alert Settings + [Parameter("Send Email", DefaultValue = true, Group = "Alert Settings")] + public bool SendEmail { get; set; } + + [Parameter("Sender Email", DefaultValue = "junk001@mycelium.de", Group = "Alert Settings")] + public string SenderEmail { get; set; } + + [Parameter("Recipient Email", DefaultValue = "junk001@mycelium.de", Group = "Alert Settings")] + public string RecipientEmail { get; set; } + + [Parameter("Play Sound", DefaultValue = true, Group = "Alert Settings")] + public bool PlaySound { get; set; } + + [Parameter("Sound File Path", DefaultValue = "C:\\Windows\\Media\\Alarm07.wav", Group = "Alert Settings")] + public string SoundFilePath { get; set; } + + // Filters + [Parameter("Min Gap Size (Pips)", DefaultValue = 1.0, Group = "Filters")] + public double MinGapSizePips { get; set; } + + // Visuals + [Parameter("Draw Boxes", DefaultValue = true, Group = "Visuals")] + public bool DrawBoxes { get; set; } + + [Parameter("Bullish Color", DefaultValue = "Green", Group = "Visuals")] + public string BullishColorName { get; set; } + + [Parameter("Bearish Color", DefaultValue = "Red", Group = "Visuals")] + public string BearishColorName { get; set; } + + #endregion + + #region Fields + + private int _lastAlertBarIndex = -1; + private Color _bullColor; + private Color _bearColor; + + #endregion + + protected override void Initialize() + { + // 50% Transparency (Alpha 127 out of 255) + _bullColor = Color.FromArgb(127, Color.FromName(BullishColorName)); + _bearColor = Color.FromArgb(127, Color.FromName(BearishColorName)); + } + + public override void Calculate(int index) + { + // Need at least 3 completed bars (index-3 to index-1) + if (index < 3) return; + + int cIndex = index - 1; // The closed bar confirming the gap + int aIndex = index - 3; // The origin bar + + double highA = Bars.HighPrices[aIndex]; + double lowA = Bars.LowPrices[aIndex]; + + double highC = Bars.HighPrices[cIndex]; + double lowC = Bars.LowPrices[cIndex]; + + // Check Bullish FVG (Undervalued) + // Gap is between High of A and Low of C + if (lowC > highA) + { + // Bottom: HighA, Top: LowC + ProcessGap(true, aIndex, cIndex, highA, lowC, index); + } + // Check Bearish FVG (Overvalued) + // Gap is between Low of A and High of C + else if (highC < lowA) + { + // Bottom: HighC, Top: LowA (Bugfix: Parameter order swapped) + ProcessGap(false, aIndex, cIndex, highC, lowA, index); + } + } + + private void ProcessGap(bool isBullish, int startBarIdx, int endBarIdx, double bottomPrice, double topPrice, int currentIndex) + { + double gapSize = topPrice - bottomPrice; + + // Check against min size + if (gapSize < MinGapSizePips * Symbol.PipSize) return; + + // Visualization + if (DrawBoxes) + { + string objName = $"FVG_{startBarIdx}"; + var color = isBullish ? _bullColor : _bearColor; + + // Draw rectangle + var rect = Chart.DrawRectangle(objName, startBarIdx, bottomPrice, endBarIdx, topPrice, color, 1, LineStyle.Solid); + rect.IsFilled = true; + } + + // Alert Logic + if (IsLastBar && RunningMode == RunningMode.RealTime) + { + if (_lastAlertBarIndex != currentIndex) + { + TriggerAlerts(isBullish, topPrice, bottomPrice); + _lastAlertBarIndex = currentIndex; + } + } + } + + private void TriggerAlerts(bool isBullish, double top, double bottom) + { + string type = isBullish ? "BULLISH" : "BEARISH"; + string message = $"{Symbol.Name} ({TimeFrame}): {type} Imbalance Detected. Range: {bottom} - {top}"; + + // 1. Sound Alert + if (PlaySound) + { + if (!string.IsNullOrWhiteSpace(SoundFilePath)) + { + try + { + Notifications.PlaySound(SoundFilePath); + } + catch + { + Notifications.PlaySound(SoundType.Doorbell); + } + } + } + + // 2. Email Alert + if (SendEmail) + { + SendAlertEmail(type, top, bottom); + } + + Print(message); + } + + private void SendAlertEmail(string type, double top, double bottom) + { + try + { + string subject = $"{Symbol.Name} ({TimeFrame}): {type} Imbalance Detected"; + string body = $@" +

Imbalance Manifested

+

Symbol: {Symbol.Name}

+

Timeframe: {TimeFrame}

+

Direction: {type}

+

Gap Range: {bottom} - {top}

+

Time: {Server.Time.ToUniversalTime()} UTC

+ "; + + Notifications.SendEmail(SenderEmail, RecipientEmail, subject, body); + } + catch (Exception ex) + { + Print($"Failed to send email: {ex.Message}"); + } + } + } +} \ No newline at end of file diff --git a/Sources/Indicators/ImbalanceAlert/ImbalanceAlert/ImbalanceAlert.csproj b/Sources/Indicators/ImbalanceAlert/ImbalanceAlert/ImbalanceAlert.csproj new file mode 100644 index 0000000..da1fef1 --- /dev/null +++ b/Sources/Indicators/ImbalanceAlert/ImbalanceAlert/ImbalanceAlert.csproj @@ -0,0 +1,9 @@ + + + net6.0 + + + + + + diff --git a/Sources/Indicators/Kairi Relative Index.algo b/Sources/Indicators/Kairi Relative Index.algo new file mode 100644 index 0000000..d00ded5 Binary files /dev/null and b/Sources/Indicators/Kairi Relative Index.algo differ diff --git a/Sources/Indicators/Kairi Relative Index/Kairi Relative Index.sln b/Sources/Indicators/Kairi Relative Index/Kairi Relative Index.sln new file mode 100644 index 0000000..99476f3 --- /dev/null +++ b/Sources/Indicators/Kairi Relative Index/Kairi Relative Index.sln @@ -0,0 +1,20 @@ + +Microsoft Visual Studio Solution File, Format Version 11.00 +# Visual Studio 2010 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kairi Relative Index", "Kairi Relative Index\Kairi Relative Index.csproj", "{C3FBC874-301E-41CA-A6FE-A1059BA1C86E}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {C3FBC874-301E-41CA-A6FE-A1059BA1C86E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C3FBC874-301E-41CA-A6FE-A1059BA1C86E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C3FBC874-301E-41CA-A6FE-A1059BA1C86E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C3FBC874-301E-41CA-A6FE-A1059BA1C86E}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/Sources/Indicators/Kairi Relative Index/Kairi Relative Index/Kairi Relative Index.cs b/Sources/Indicators/Kairi Relative Index/Kairi Relative Index/Kairi Relative Index.cs new file mode 100644 index 0000000..b8c2de3 --- /dev/null +++ b/Sources/Indicators/Kairi Relative Index/Kairi Relative Index/Kairi Relative Index.cs @@ -0,0 +1,33 @@ +using cAlgo.API; +using cAlgo.API.Indicators; + +namespace cAlgo +{ + [Indicator(IsOverlay = false, AccessRights = AccessRights.None)] + public class KairiRelativeIndex : Indicator + { + [Parameter("Source")] + public DataSeries Source { get; set; } + + [Parameter("Periods", DefaultValue = 14)] + public int Periods { get; set; } + + [Parameter("MA Type", DefaultValue = MovingAverageType.Simple)] + public MovingAverageType MaType { get; set; } + + [Output("KRI", LineColor = "Red")] + public IndicatorDataSeries KRI { get; set; } + + private MovingAverage MA; + + protected override void Initialize() + { + MA = Indicators.MovingAverage(Source, Periods, MaType); + } + + public override void Calculate(int index) + { + KRI[index] = ((Source[index] - MA.Result[index]) / MA.Result[index]) * 100; + } + } +} diff --git a/Sources/Indicators/Kairi Relative Index/Kairi Relative Index/Kairi Relative Index.csproj b/Sources/Indicators/Kairi Relative Index/Kairi Relative Index/Kairi Relative Index.csproj new file mode 100644 index 0000000..4d38494 --- /dev/null +++ b/Sources/Indicators/Kairi Relative Index/Kairi Relative Index/Kairi Relative Index.csproj @@ -0,0 +1,60 @@ + + + + + 7.2 + Debug + AnyCPU + {C3FBC874-301E-41CA-A6FE-A1059BA1C86E} + {DD87C1B2-3799-4CA2-93B6-5288EE928820};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + Library + Properties + cAlgo + Kairi Relative Index + v4.0 + Client + 512 + True + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + False + ..\..\..\..\API\cAlgo.API.dll + + + + + + + + + \ No newline at end of file diff --git a/Sources/Indicators/Kairi Relative Index/Kairi Relative Index/Properties/AssemblyInfo.cs b/Sources/Indicators/Kairi Relative Index/Kairi Relative Index/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..afde0ef --- /dev/null +++ b/Sources/Indicators/Kairi Relative Index/Kairi Relative Index/Properties/AssemblyInfo.cs @@ -0,0 +1,20 @@ +using System.Diagnostics; +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("Kairi Relative Index")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyProduct("Kairi Relative Index")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +[assembly: ComVisible(false)] + +[assembly: Guid("35f19c43-c7cc-4f53-b8e0-61caaf9cd8e8")] + +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] +#if DEBUG + [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations)] +#endif \ No newline at end of file diff --git a/Sources/Indicators/MACD Crossover (7).algo b/Sources/Indicators/MACD Crossover (7).algo new file mode 100644 index 0000000..8be90ba Binary files /dev/null and b/Sources/Indicators/MACD Crossover (7).algo differ diff --git a/Sources/Indicators/MACD Crossover (7)/MACD Crossover (7).sln b/Sources/Indicators/MACD Crossover (7)/MACD Crossover (7).sln new file mode 100644 index 0000000..e4663d1 --- /dev/null +++ b/Sources/Indicators/MACD Crossover (7)/MACD Crossover (7).sln @@ -0,0 +1,20 @@ + +Microsoft Visual Studio Solution File, Format Version 11.00 +# Visual Studio 2010 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MACD Crossover (7)", "MACD Crossover (7)\MACD Crossover (7).csproj", "{373DED72-A17B-4903-8EC9-FA9589B99D5D}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {373DED72-A17B-4903-8EC9-FA9589B99D5D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {373DED72-A17B-4903-8EC9-FA9589B99D5D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {373DED72-A17B-4903-8EC9-FA9589B99D5D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {373DED72-A17B-4903-8EC9-FA9589B99D5D}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/Sources/Indicators/MACD Crossover (7)/MACD Crossover (7)/MACD Crossover (7).cs b/Sources/Indicators/MACD Crossover (7)/MACD Crossover (7)/MACD Crossover (7).cs new file mode 100644 index 0000000..51c8ae2 --- /dev/null +++ b/Sources/Indicators/MACD Crossover (7)/MACD Crossover (7)/MACD Crossover (7).cs @@ -0,0 +1,155 @@ +// ------------------------------------------------------------------------------------------------- +// +// This code is a cAlgo API MACD Crossover indicator provided by njardim@email.com on Dec 2015. +// +// Based on the original MACD Crossover indicator from cTrade. +// +// ------------------------------------------------------------------------------------------------- +/* + * Indicator was modified by Telegram @Fibonacci2011 on Jan 2020. + * + * Modifications: + * + * 2020/1/21 Added MAType. Levels and Lookback feature + * 2020/1/21 Added tick volume feature + * 2020/1/22 Replaced lookback with MA smoothing feature + * 2020/1/22 Added zero lag feature + * 2020/2/10 Removed Tick Volume, error in logic + +This is a modified version of the traditional MACD indicator. + +Ma Smoothing attempts to show longer-term momentum and cycles better than the standard MACD. + +Cut-Off levels indicate levels where MACD and signal line difference is greater than the level. + +Tick volume feature takes into consideration the changes in tick volume. Increasing +Tick Volume Factor will increasi tick volume feature relative weight + + +===================================================================================================== + SETTINGS +===================================================================================================== + +Traditional MACD: +MACD periods: 12 & 26 // Signal period: 9 // MA Type: Exponential + +Linda Raschke: +MACD periods: 3 & 10 // Signal period: 16 // MA Type: Simple + +Awesome Oscillator: +MACD periods: 5 & 34 // Signal period:N/A // MA Type: Simple + +*/ +// ------------------------------------------------------------------------------------------------- + +using System; +using cAlgo.API; +using cAlgo.API.Internals; +using cAlgo.API.Indicators; +using cAlgo.Indicators; + +namespace cAlgo +{ + [Indicator(IsOverlay = false, TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)] + public class MACDCrossover : Indicator + { + + // public MACDCrossover MACDCrossover; + + [Parameter()] + public DataSeries Source { get; set; } + + [Parameter("MA Type", Group = "--- MA Type ---", DefaultValue = MovingAverageType.Simple)] + public MovingAverageType MAType { get; set; } + + [Parameter("Long Cycle", Group = "--- Period ---", DefaultValue = 10)] + public int LongCycle { get; set; } + + [Parameter("Short Cycle", Group = "--- Period ---", DefaultValue = 3)] + public int ShortCycle { get; set; } + + [Parameter("Signal Periods", Group = "--- Period ---", DefaultValue = 16)] + public int Periods { get; set; } + + [Parameter("Scale by bar time span (on tick charts)", Group = "--- Period ---", DefaultValue = true)] + public bool ScaleByTimeSpan { get; set; } + + [Output("Histogram", PlotType = PlotType.Line, LineColor = "Transparent", Thickness = 1)] + public IndicatorDataSeries Histogram { get; set; } + + [Output("Histogram UpSlope", PlotType = PlotType.Histogram, LineColor = "Green", Thickness = 5)] + public IndicatorDataSeries HistogramRisingPositive { get; set; } + + [Output("Histogram UpDownSlope", PlotType = PlotType.Histogram, LineColor = "Aquamarine", Thickness = 5)] + public IndicatorDataSeries HistogramRisingNegative { get; set; } + + [Output("Histogram DownSlope", PlotType = PlotType.Histogram, LineColor = "DarkRed", Thickness = 5)] + public IndicatorDataSeries HistogramFallingNegative { get; set; } + + [Output("Histogram DownUpSlope", PlotType = PlotType.Histogram, LineColor = "Pink", Thickness = 5)] + public IndicatorDataSeries HistogramFallingPositive { get; set; } + + [Output("Signal", LineColor = "Yellow", PlotType = PlotType.DiscontinuousLine, LineStyle = LineStyle.Lines, Thickness = 1)] + public IndicatorDataSeries Signal { get; set; } + + public IndicatorDataSeries Times { get; set; } + + private MovingAverage SlowMa; + private MovingAverage FastMa; + private MovingAverage SignalMa; + private MovingAverage TimesMa; + + public IndicatorDataSeries macd; + + protected override void Initialize() + { + SlowMa = Indicators.MovingAverage(Source, LongCycle, MAType); + FastMa = Indicators.MovingAverage(Source, ShortCycle, MAType); + + macd = CreateDataSeries(); + Times = CreateDataSeries(); + SignalMa = Indicators.MovingAverage(macd, Periods, MAType); + TimesMa = Indicators.MovingAverage(Times, 200, MovingAverageType.Simple ); + } + + public override void Calculate(int index) + { + if( index>0 ) + { + var t0 = Bars.OpenTimes[index]; + var t1 = Bars.OpenTimes[index-1]; + if( t0.ToLocalTime().Date == t1.ToLocalTime().Date ) + Times[index] = (t0-t1).TotalMinutes; + else + Times[index] = 0; + } + + macd[index] = (FastMa.Result[index] - SlowMa.Result[index]); + Histogram[index] = macd[index]; + if( ScaleByTimeSpan && Times[index]>0) + Histogram[index] *= TimesMa.Result[index] / Times[index]; + + Signal[index] = SignalMa.Result[index]; + + HistogramRisingPositive[index] = 0; + HistogramRisingNegative[index] = 0; + HistogramFallingPositive[index] = 0; + HistogramFallingNegative[index] = 0; + + if (Histogram[index] > 0) + { + if (Histogram.IsRising()) + HistogramRisingPositive[index] = Histogram[index]; + else + HistogramRisingNegative[index] = Histogram[index]; + } + else if (Histogram[index] < 0) + { + if (Histogram.IsRising()) + HistogramFallingPositive[index] = Histogram[index]; + else + HistogramFallingNegative[index] = Histogram[index]; + } + } + } +} diff --git a/Sources/Indicators/MACD Crossover (7)/MACD Crossover (7)/MACD Crossover (7).csproj b/Sources/Indicators/MACD Crossover (7)/MACD Crossover (7)/MACD Crossover (7).csproj new file mode 100644 index 0000000..6b327b8 --- /dev/null +++ b/Sources/Indicators/MACD Crossover (7)/MACD Crossover (7)/MACD Crossover (7).csproj @@ -0,0 +1,24 @@ + + + + net6.0 + False + False + + + 7.2 + Debug + AnyCPU + Properties + cAlgo + MACD Crossover (7) + 512 + + + + + + + + + \ No newline at end of file diff --git a/Sources/Indicators/MACD Crossover (7)/MACD Crossover (7)/MACD Crossover (7).csproj.backup b/Sources/Indicators/MACD Crossover (7)/MACD Crossover (7)/MACD Crossover (7).csproj.backup new file mode 100644 index 0000000..d9c3f61 --- /dev/null +++ b/Sources/Indicators/MACD Crossover (7)/MACD Crossover (7)/MACD Crossover (7).csproj.backup @@ -0,0 +1,64 @@ + + + + + 7.2 + Debug + AnyCPU + {373DED72-A17B-4903-8EC9-FA9589B99D5D} + {DD87C1B2-3799-4CA2-93B6-5288EE928820};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + Library + Properties + cAlgo + MACD Crossover (7) + v4.6.1 + + + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + CS0108,CS0162,CS0109,CS0219,CS0169,CS0628 + false + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + CS0108,CS0162,CS0109,CS0219,CS0169,CS0628 + false + + + + + + + + + + False + ..\..\..\..\API\cAlgo.API.dll + + + + + + + + + \ No newline at end of file diff --git a/Sources/Indicators/MACD Crossover (7)/MACD Crossover (7)/Properties/AssemblyInfo.cs b/Sources/Indicators/MACD Crossover (7)/MACD Crossover (7)/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..e15f025 --- /dev/null +++ b/Sources/Indicators/MACD Crossover (7)/MACD Crossover (7)/Properties/AssemblyInfo.cs @@ -0,0 +1,20 @@ +using System.Diagnostics; +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("MACD Crossover (7)")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyProduct("MACD Crossover (7)")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +[assembly: ComVisible(false)] + +[assembly: Guid("35f19c43-c7cc-4f53-b8e0-61caaf9cd8e8")] + +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] +#if DEBUG + [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations)] +#endif \ No newline at end of file diff --git a/Sources/Indicators/MACD Crossover (7)/MACD Crossover (7)/bin/Release/net6.0/MACD Crossover (7).algo b/Sources/Indicators/MACD Crossover (7)/MACD Crossover (7)/bin/Release/net6.0/MACD Crossover (7).algo new file mode 100644 index 0000000..5967884 Binary files /dev/null and b/Sources/Indicators/MACD Crossover (7)/MACD Crossover (7)/bin/Release/net6.0/MACD Crossover (7).algo differ diff --git a/Sources/Indicators/MACD Crossover (7)/MACD Crossover (7)/bin/Release/net6.0/MACD Crossover (7).algo.metadata b/Sources/Indicators/MACD Crossover (7)/MACD Crossover (7)/bin/Release/net6.0/MACD Crossover (7).algo.metadata new file mode 100644 index 0000000..3aef87f --- /dev/null +++ b/Sources/Indicators/MACD Crossover (7)/MACD Crossover (7)/bin/Release/net6.0/MACD Crossover (7).algo.metadata @@ -0,0 +1,132 @@ +[ + { + "Flags": "Indicator", + "Guid": "00000000-0000-0000-0000-000000000000", + "Group": "Custom", + "FriendlyName": "MACDCrossover", + "ShortName": null, + "TypeName": "cAlgo.MACDCrossover", + "AssemblyName": "MACD Crossover (7), Version=1.0.0.0, Culture=neutral, PublicKeyToken=null", + "TimeZone": "UTC", + "IsOverlay": false, + "IsPercentage": false, + "ScalePrecision": null, + "Levels": [], + "DefaultSymbolName": null, + "DefaultTimeFrame": null, + "Video": null, + "Lines": [ + { + "Name": "Histogram UpSlope", + "Style": "Solid", + "PlotType": "Histogram", + "Thickness": 5.0, + "Color": "#008000FF", + "PropertyName": "HistogramRisingPositive", + "IsUsedInAutoScale": true, + "IsColorCustomizable": true + }, + { + "Name": "Histogram UpDownSlope", + "Style": "Solid", + "PlotType": "Histogram", + "Thickness": 5.0, + "Color": "#7FFFD4FF", + "PropertyName": "HistogramRisingNegative", + "IsUsedInAutoScale": true, + "IsColorCustomizable": true + }, + { + "Name": "Histogram DownSlope", + "Style": "Solid", + "PlotType": "Histogram", + "Thickness": 5.0, + "Color": "#8B0000FF", + "PropertyName": "HistogramFallingNegative", + "IsUsedInAutoScale": true, + "IsColorCustomizable": true + }, + { + "Name": "Histogram DownUpSlope", + "Style": "Solid", + "PlotType": "Histogram", + "Thickness": 5.0, + "Color": "#FFC0CBFF", + "PropertyName": "HistogramFallingPositive", + "IsUsedInAutoScale": true, + "IsColorCustomizable": true + }, + { + "Name": "Signal", + "Style": "Lines", + "PlotType": "DiscontinuousLine", + "Thickness": 1.0, + "Color": "#FFFF00FF", + "PropertyName": "Signal", + "IsUsedInAutoScale": true, + "IsColorCustomizable": true + } + ], + "Clouds": [], + "Parameters": [ + { + "DefaultValue": "Close", + "ParameterType": "DataSeries", + "PropertyName": "Source", + "FriendlyName": "Source", + "GroupName": null + }, + { + "EnumValues": { + "Simple": 0, + "Exponential": 1, + "TimeSeries": 2, + "Triangular": 3, + "VIDYA": 4, + "Weighted": 5, + "WilderSmoothing": 6, + "Hull": 7 + }, + "DefaultValue": 0, + "ParameterType": "Enum", + "PropertyName": "MAType", + "FriendlyName": "MA Type", + "GroupName": "--- MA Type ---" + }, + { + "MinValue": -2147483648, + "MaxValue": 2147483647, + "Step": 1, + "DefaultValue": 10, + "ParameterType": "Integer", + "PropertyName": "LongCycle", + "FriendlyName": "Long Cycle", + "GroupName": "--- Period ---" + }, + { + "MinValue": -2147483648, + "MaxValue": 2147483647, + "Step": 1, + "DefaultValue": 3, + "ParameterType": "Integer", + "PropertyName": "ShortCycle", + "FriendlyName": "Short Cycle", + "GroupName": "--- Period ---" + }, + { + "MinValue": -2147483648, + "MaxValue": 2147483647, + "Step": 1, + "DefaultValue": 16, + "ParameterType": "Integer", + "PropertyName": "Periods", + "FriendlyName": "Signal Periods", + "GroupName": "--- Period ---" + } + ], + "Capabilities": [], + "CustomAttributes": [], + "Sets": [], + "AddIndicatorsToChart": false + } +] \ No newline at end of file diff --git a/Sources/Indicators/MACD Crossover (7)/MACD Crossover (7)/bin/Release/net6.0/MACD Crossover (7).deps.json b/Sources/Indicators/MACD Crossover (7)/MACD Crossover (7)/bin/Release/net6.0/MACD Crossover (7).deps.json new file mode 100644 index 0000000..8ae490d --- /dev/null +++ b/Sources/Indicators/MACD Crossover (7)/MACD Crossover (7)/bin/Release/net6.0/MACD Crossover (7).deps.json @@ -0,0 +1,41 @@ +{ + "runtimeTarget": { + "name": ".NETCoreApp,Version=v6.0", + "signature": "" + }, + "compilationOptions": {}, + "targets": { + ".NETCoreApp,Version=v6.0": { + "MACD Crossover (7)/1.0.0": { + "dependencies": { + "cTrader.Automate": "1.0.9" + }, + "runtime": { + "MACD Crossover (7).dll": {} + } + }, + "cTrader.Automate/1.0.9": { + "runtime": { + "lib/net6.0/cAlgo.API.dll": { + "assemblyVersion": "1.0.0.0", + "fileVersion": "1.0.0.0" + } + } + } + } + }, + "libraries": { + "MACD Crossover (7)/1.0.0": { + "type": "project", + "serviceable": false, + "sha512": "" + }, + "cTrader.Automate/1.0.9": { + "type": "package", + "serviceable": true, + "sha512": "sha512-F2rQGQi6h9OVGfd4ePfa8nr1qU+WOqTFYnpm3hr6E+ibtgUamuzTelupnxNuPCBbuBHuxyvrCL+4emiOfdAIRg==", + "path": "ctrader.automate/1.0.9", + "hashPath": "ctrader.automate.1.0.9.nupkg.sha512" + } + } +} \ No newline at end of file diff --git a/Sources/Indicators/MACD Crossover (7)/MACD Crossover (7)/bin/Release/net6.0/MACD Crossover (7).dll b/Sources/Indicators/MACD Crossover (7)/MACD Crossover (7)/bin/Release/net6.0/MACD Crossover (7).dll new file mode 100644 index 0000000..ffe38c7 Binary files /dev/null and b/Sources/Indicators/MACD Crossover (7)/MACD Crossover (7)/bin/Release/net6.0/MACD Crossover (7).dll differ diff --git a/Sources/Indicators/MACD Crossover (7)/MACD Crossover (7)/bin/Release/net6.0/MACD Crossover (7).pdb b/Sources/Indicators/MACD Crossover (7)/MACD Crossover (7)/bin/Release/net6.0/MACD Crossover (7).pdb new file mode 100644 index 0000000..9581cad Binary files /dev/null and b/Sources/Indicators/MACD Crossover (7)/MACD Crossover (7)/bin/Release/net6.0/MACD Crossover (7).pdb differ diff --git a/Sources/Indicators/MACD Crossover (7)/MACD Crossover (7)/bin/Release/net6.0/publish/MACD Crossover (7).deps.json b/Sources/Indicators/MACD Crossover (7)/MACD Crossover (7)/bin/Release/net6.0/publish/MACD Crossover (7).deps.json new file mode 100644 index 0000000..8ae490d --- /dev/null +++ b/Sources/Indicators/MACD Crossover (7)/MACD Crossover (7)/bin/Release/net6.0/publish/MACD Crossover (7).deps.json @@ -0,0 +1,41 @@ +{ + "runtimeTarget": { + "name": ".NETCoreApp,Version=v6.0", + "signature": "" + }, + "compilationOptions": {}, + "targets": { + ".NETCoreApp,Version=v6.0": { + "MACD Crossover (7)/1.0.0": { + "dependencies": { + "cTrader.Automate": "1.0.9" + }, + "runtime": { + "MACD Crossover (7).dll": {} + } + }, + "cTrader.Automate/1.0.9": { + "runtime": { + "lib/net6.0/cAlgo.API.dll": { + "assemblyVersion": "1.0.0.0", + "fileVersion": "1.0.0.0" + } + } + } + } + }, + "libraries": { + "MACD Crossover (7)/1.0.0": { + "type": "project", + "serviceable": false, + "sha512": "" + }, + "cTrader.Automate/1.0.9": { + "type": "package", + "serviceable": true, + "sha512": "sha512-F2rQGQi6h9OVGfd4ePfa8nr1qU+WOqTFYnpm3hr6E+ibtgUamuzTelupnxNuPCBbuBHuxyvrCL+4emiOfdAIRg==", + "path": "ctrader.automate/1.0.9", + "hashPath": "ctrader.automate.1.0.9.nupkg.sha512" + } + } +} \ No newline at end of file diff --git a/Sources/Indicators/MACD Crossover (7)/MACD Crossover (7)/bin/Release/net6.0/publish/MACD Crossover (7).dll b/Sources/Indicators/MACD Crossover (7)/MACD Crossover (7)/bin/Release/net6.0/publish/MACD Crossover (7).dll new file mode 100644 index 0000000..ffe38c7 Binary files /dev/null and b/Sources/Indicators/MACD Crossover (7)/MACD Crossover (7)/bin/Release/net6.0/publish/MACD Crossover (7).dll differ diff --git a/Sources/Indicators/MACD Crossover (7)/MACD Crossover (7)/bin/Release/net6.0/publish/MACD Crossover (7).pdb b/Sources/Indicators/MACD Crossover (7)/MACD Crossover (7)/bin/Release/net6.0/publish/MACD Crossover (7).pdb new file mode 100644 index 0000000..9581cad Binary files /dev/null and b/Sources/Indicators/MACD Crossover (7)/MACD Crossover (7)/bin/Release/net6.0/publish/MACD Crossover (7).pdb differ diff --git a/Sources/Indicators/MACD Crossover (7)/MACD Crossover (7)/bin/Release/net6.0/publish/cAlgo.API.dll b/Sources/Indicators/MACD Crossover (7)/MACD Crossover (7)/bin/Release/net6.0/publish/cAlgo.API.dll new file mode 100644 index 0000000..fc0d146 Binary files /dev/null and b/Sources/Indicators/MACD Crossover (7)/MACD Crossover (7)/bin/Release/net6.0/publish/cAlgo.API.dll differ diff --git a/Sources/Indicators/MACD Crossover (7)/MACD Crossover (7)/obj/MACD Crossover (7).csproj.nuget.dgspec.json b/Sources/Indicators/MACD Crossover (7)/MACD Crossover (7)/obj/MACD Crossover (7).csproj.nuget.dgspec.json new file mode 100644 index 0000000..e9ff3f2 --- /dev/null +++ b/Sources/Indicators/MACD Crossover (7)/MACD Crossover (7)/obj/MACD Crossover (7).csproj.nuget.dgspec.json @@ -0,0 +1,66 @@ +{ + "format": 1, + "restore": { + "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Indicators\\MACD Crossover (7)\\MACD Crossover (7)\\MACD Crossover (7).csproj": {} + }, + "projects": { + "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Indicators\\MACD Crossover (7)\\MACD Crossover (7)\\MACD Crossover (7).csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Indicators\\MACD Crossover (7)\\MACD Crossover (7)\\MACD Crossover (7).csproj", + "projectName": "MACD Crossover (7)", + "projectPath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Indicators\\MACD Crossover (7)\\MACD Crossover (7)\\MACD Crossover (7).csproj", + "packagesPath": "C:\\Users\\Brummel\\.nuget\\packages\\", + "outputPath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Indicators\\MACD Crossover (7)\\MACD Crossover (7)\\obj\\", + "projectStyle": "PackageReference", + "configFilePaths": [ + "C:\\Users\\Brummel\\AppData\\Roaming\\NuGet\\NuGet.Config" + ], + "originalTargetFrameworks": [ + "net6.0" + ], + "sources": { + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net6.0": { + "targetAlias": "net6.0", + "projectReferences": {} + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + } + }, + "frameworks": { + "net6.0": { + "targetAlias": "net6.0", + "dependencies": { + "cTrader.Automate": { + "target": "Package", + "version": "[1.*, )" + } + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\6.0.200\\RuntimeIdentifierGraph.json" + } + } + } + } +} \ No newline at end of file diff --git a/Sources/Indicators/MACD Crossover (7)/MACD Crossover (7)/obj/MACD Crossover (7).csproj.nuget.g.props b/Sources/Indicators/MACD Crossover (7)/MACD Crossover (7)/obj/MACD Crossover (7).csproj.nuget.g.props new file mode 100644 index 0000000..2f5501b --- /dev/null +++ b/Sources/Indicators/MACD Crossover (7)/MACD Crossover (7)/obj/MACD Crossover (7).csproj.nuget.g.props @@ -0,0 +1,21 @@ + + + + True + NuGet + $(MSBuildThisFileDirectory)project.assets.json + $(UserProfile)\.nuget\packages\ + C:\Users\Brummel\.nuget\packages\ + PackageReference + 6.1.0 + + + + + + + + + C:\Users\Brummel\.nuget\packages\ctrader.automate\1.0.9 + + \ No newline at end of file diff --git a/Sources/Indicators/MACD Crossover (7)/MACD Crossover (7)/obj/MACD Crossover (7).csproj.nuget.g.targets b/Sources/Indicators/MACD Crossover (7)/MACD Crossover (7)/obj/MACD Crossover (7).csproj.nuget.g.targets new file mode 100644 index 0000000..611004a --- /dev/null +++ b/Sources/Indicators/MACD Crossover (7)/MACD Crossover (7)/obj/MACD Crossover (7).csproj.nuget.g.targets @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Sources/Indicators/MACD Crossover (7)/MACD Crossover (7)/obj/Release/MACD Crossover (7).csproj.FileListAbsolute.txt b/Sources/Indicators/MACD Crossover (7)/MACD Crossover (7)/obj/Release/MACD Crossover (7).csproj.FileListAbsolute.txt new file mode 100644 index 0000000..e69de29 diff --git a/Sources/Indicators/MACD Crossover (7)/MACD Crossover (7)/obj/Release/net6.0/.NETCoreApp,Version=v6.0.AssemblyAttributes.cs b/Sources/Indicators/MACD Crossover (7)/MACD Crossover (7)/obj/Release/net6.0/.NETCoreApp,Version=v6.0.AssemblyAttributes.cs new file mode 100644 index 0000000..32c95f9 --- /dev/null +++ b/Sources/Indicators/MACD Crossover (7)/MACD Crossover (7)/obj/Release/net6.0/.NETCoreApp,Version=v6.0.AssemblyAttributes.cs @@ -0,0 +1,4 @@ +// +using System; +using System.Reflection; +[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v6.0", FrameworkDisplayName = "")] diff --git a/Sources/Indicators/MACD Crossover (7)/MACD Crossover (7)/obj/Release/net6.0/MACD Crossover (7).GeneratedMSBuildEditorConfig.editorconfig b/Sources/Indicators/MACD Crossover (7)/MACD Crossover (7)/obj/Release/net6.0/MACD Crossover (7).GeneratedMSBuildEditorConfig.editorconfig new file mode 100644 index 0000000..08b7b92 --- /dev/null +++ b/Sources/Indicators/MACD Crossover (7)/MACD Crossover (7)/obj/Release/net6.0/MACD Crossover (7).GeneratedMSBuildEditorConfig.editorconfig @@ -0,0 +1,10 @@ +is_global = true +build_property.TargetFramework = net6.0 +build_property.TargetPlatformMinVersion = +build_property.UsingMicrosoftNETSdkWeb = +build_property.ProjectTypeGuids = +build_property.InvariantGlobalization = +build_property.PlatformNeutralAssembly = +build_property._SupportedPlatformList = Linux,macOS,Windows +build_property.RootNamespace = cAlgo +build_property.ProjectDir = C:\Users\Brummel\Documents\cAlgo\Sources\Indicators\MACD Crossover (7)\MACD Crossover (7)\ diff --git a/Sources/Indicators/MACD Crossover (7)/MACD Crossover (7)/obj/Release/net6.0/MACD Crossover (7).assets.cache b/Sources/Indicators/MACD Crossover (7)/MACD Crossover (7)/obj/Release/net6.0/MACD Crossover (7).assets.cache new file mode 100644 index 0000000..718dfc4 Binary files /dev/null and b/Sources/Indicators/MACD Crossover (7)/MACD Crossover (7)/obj/Release/net6.0/MACD Crossover (7).assets.cache differ diff --git a/Sources/Indicators/MACD Crossover (7)/MACD Crossover (7)/obj/Release/net6.0/MACD Crossover (7).csproj.AssemblyReference.cache b/Sources/Indicators/MACD Crossover (7)/MACD Crossover (7)/obj/Release/net6.0/MACD Crossover (7).csproj.AssemblyReference.cache new file mode 100644 index 0000000..ca6c1fd Binary files /dev/null and b/Sources/Indicators/MACD Crossover (7)/MACD Crossover (7)/obj/Release/net6.0/MACD Crossover (7).csproj.AssemblyReference.cache differ diff --git a/Sources/Indicators/MACD Crossover (7)/MACD Crossover (7)/obj/Release/net6.0/MACD Crossover (7).csproj.CoreCompileInputs.cache b/Sources/Indicators/MACD Crossover (7)/MACD Crossover (7)/obj/Release/net6.0/MACD Crossover (7).csproj.CoreCompileInputs.cache new file mode 100644 index 0000000..772ddb5 --- /dev/null +++ b/Sources/Indicators/MACD Crossover (7)/MACD Crossover (7)/obj/Release/net6.0/MACD Crossover (7).csproj.CoreCompileInputs.cache @@ -0,0 +1 @@ +8b1c4478054cdd89cfbdb0145247239631117c52 diff --git a/Sources/Indicators/MACD Crossover (7)/MACD Crossover (7)/obj/Release/net6.0/MACD Crossover (7).csproj.FileListAbsolute.txt b/Sources/Indicators/MACD Crossover (7)/MACD Crossover (7)/obj/Release/net6.0/MACD Crossover (7).csproj.FileListAbsolute.txt new file mode 100644 index 0000000..bac60d3 --- /dev/null +++ b/Sources/Indicators/MACD Crossover (7)/MACD Crossover (7)/obj/Release/net6.0/MACD Crossover (7).csproj.FileListAbsolute.txt @@ -0,0 +1,10 @@ +C:\Users\Brummel\Documents\cAlgo\Sources\Indicators\MACD Crossover (7)\MACD Crossover (7)\bin\Release\net6.0\MACD Crossover (7).deps.json +C:\Users\Brummel\Documents\cAlgo\Sources\Indicators\MACD Crossover (7)\MACD Crossover (7)\bin\Release\net6.0\MACD Crossover (7).dll +C:\Users\Brummel\Documents\cAlgo\Sources\Indicators\MACD Crossover (7)\MACD Crossover (7)\bin\Release\net6.0\MACD Crossover (7).pdb +C:\Users\Brummel\Documents\cAlgo\Sources\Indicators\MACD Crossover (7)\MACD Crossover (7)\obj\Release\net6.0\MACD Crossover (7).csproj.AssemblyReference.cache +C:\Users\Brummel\Documents\cAlgo\Sources\Indicators\MACD Crossover (7)\MACD Crossover (7)\obj\Release\net6.0\MACD Crossover (7).GeneratedMSBuildEditorConfig.editorconfig +C:\Users\Brummel\Documents\cAlgo\Sources\Indicators\MACD Crossover (7)\MACD Crossover (7)\obj\Release\net6.0\MACD Crossover (7).csproj.CoreCompileInputs.cache +C:\Users\Brummel\Documents\cAlgo\Sources\Indicators\MACD Crossover (7)\MACD Crossover (7)\obj\Release\net6.0\MACD Crossover (7).dll +C:\Users\Brummel\Documents\cAlgo\Sources\Indicators\MACD Crossover (7)\MACD Crossover (7)\obj\Release\net6.0\refint\MACD Crossover (7).dll +C:\Users\Brummel\Documents\cAlgo\Sources\Indicators\MACD Crossover (7)\MACD Crossover (7)\obj\Release\net6.0\MACD Crossover (7).pdb +C:\Users\Brummel\Documents\cAlgo\Sources\Indicators\MACD Crossover (7)\MACD Crossover (7)\obj\Release\net6.0\ref\MACD Crossover (7).dll diff --git a/Sources/Indicators/MACD Crossover (7)/MACD Crossover (7)/obj/Release/net6.0/MACD Crossover (7).dll b/Sources/Indicators/MACD Crossover (7)/MACD Crossover (7)/obj/Release/net6.0/MACD Crossover (7).dll new file mode 100644 index 0000000..ffe38c7 Binary files /dev/null and b/Sources/Indicators/MACD Crossover (7)/MACD Crossover (7)/obj/Release/net6.0/MACD Crossover (7).dll differ diff --git a/Sources/Indicators/MACD Crossover (7)/MACD Crossover (7)/obj/Release/net6.0/MACD Crossover (7).pdb b/Sources/Indicators/MACD Crossover (7)/MACD Crossover (7)/obj/Release/net6.0/MACD Crossover (7).pdb new file mode 100644 index 0000000..9581cad Binary files /dev/null and b/Sources/Indicators/MACD Crossover (7)/MACD Crossover (7)/obj/Release/net6.0/MACD Crossover (7).pdb differ diff --git a/Sources/Indicators/MACD Crossover (7)/MACD Crossover (7)/obj/Release/net6.0/PublishOutputs.d8d4e7c099.txt b/Sources/Indicators/MACD Crossover (7)/MACD Crossover (7)/obj/Release/net6.0/PublishOutputs.d8d4e7c099.txt new file mode 100644 index 0000000..e482eef --- /dev/null +++ b/Sources/Indicators/MACD Crossover (7)/MACD Crossover (7)/obj/Release/net6.0/PublishOutputs.d8d4e7c099.txt @@ -0,0 +1,4 @@ +C:\Users\Brummel\Documents\cAlgo\Sources\Indicators\MACD Crossover (7)\MACD Crossover (7)\bin\Release\net6.0\publish\MACD Crossover (7).dll +C:\Users\Brummel\Documents\cAlgo\Sources\Indicators\MACD Crossover (7)\MACD Crossover (7)\bin\Release\net6.0\publish\MACD Crossover (7).deps.json +C:\Users\Brummel\Documents\cAlgo\Sources\Indicators\MACD Crossover (7)\MACD Crossover (7)\bin\Release\net6.0\publish\MACD Crossover (7).pdb +C:\Users\Brummel\Documents\cAlgo\Sources\Indicators\MACD Crossover (7)\MACD Crossover (7)\bin\Release\net6.0\publish\cAlgo.API.dll diff --git a/Sources/Indicators/MACD Crossover (7)/MACD Crossover (7)/obj/Release/net6.0/ref/MACD Crossover (7).dll b/Sources/Indicators/MACD Crossover (7)/MACD Crossover (7)/obj/Release/net6.0/ref/MACD Crossover (7).dll new file mode 100644 index 0000000..b741e2a Binary files /dev/null and b/Sources/Indicators/MACD Crossover (7)/MACD Crossover (7)/obj/Release/net6.0/ref/MACD Crossover (7).dll differ diff --git a/Sources/Indicators/MACD Crossover (7)/MACD Crossover (7)/obj/Release/net6.0/refint/MACD Crossover (7).dll b/Sources/Indicators/MACD Crossover (7)/MACD Crossover (7)/obj/Release/net6.0/refint/MACD Crossover (7).dll new file mode 100644 index 0000000..b741e2a Binary files /dev/null and b/Sources/Indicators/MACD Crossover (7)/MACD Crossover (7)/obj/Release/net6.0/refint/MACD Crossover (7).dll differ diff --git a/Sources/Indicators/MACD Crossover (7)/MACD Crossover (7)/obj/project.assets.json b/Sources/Indicators/MACD Crossover (7)/MACD Crossover (7)/obj/project.assets.json new file mode 100644 index 0000000..a19a355 --- /dev/null +++ b/Sources/Indicators/MACD Crossover (7)/MACD Crossover (7)/obj/project.assets.json @@ -0,0 +1,136 @@ +{ + "version": 3, + "targets": { + "net6.0": { + "cTrader.Automate/1.0.9": { + "type": "package", + "compile": { + "lib/net6.0/cAlgo.API.dll": {} + }, + "runtime": { + "lib/net6.0/cAlgo.API.dll": {} + }, + "build": { + "build/cTrader.Automate.props": {}, + "build/cTrader.Automate.targets": {} + } + } + } + }, + "libraries": { + "cTrader.Automate/1.0.9": { + "sha512": "F2rQGQi6h9OVGfd4ePfa8nr1qU+WOqTFYnpm3hr6E+ibtgUamuzTelupnxNuPCBbuBHuxyvrCL+4emiOfdAIRg==", + "type": "package", + "path": "ctrader.automate/1.0.9", + "hasTools": true, + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "build/cTrader.Automate.props", + "build/cTrader.Automate.targets", + "ctrader.automate.1.0.9.nupkg.sha512", + "ctrader.automate.nuspec", + "eula.md", + "icon.png", + "lib/net40/cAlgo.API.dll", + "lib/net40/cAlgo.API.xml", + "lib/net6.0/cAlgo.API.dll", + "lib/net6.0/cAlgo.API.xml", + "tools/net472/Core.AlgoFormat.Compose.Reflection.dll", + "tools/net472/Core.AlgoFormat.Writer.dll", + "tools/net472/Core.AlgoFormat.dll", + "tools/net472/Core.Domain.Primitives.dll", + "tools/net472/Newtonsoft.Json.dll", + "tools/net472/System.Buffers.dll", + "tools/net472/System.Collections.Immutable.dll", + "tools/net472/System.Memory.dll", + "tools/net472/System.Numerics.Vectors.dll", + "tools/net472/System.Reflection.Metadata.dll", + "tools/net472/System.Reflection.MetadataLoadContext.dll", + "tools/net472/System.Runtime.CompilerServices.Unsafe.dll", + "tools/net472/cTrader.Automate.Sdk.Tasks.dll", + "tools/net6.0/Core.AlgoFormat.Compose.Reflection.dll", + "tools/net6.0/Core.AlgoFormat.Writer.dll", + "tools/net6.0/Core.AlgoFormat.dll", + "tools/net6.0/Core.Domain.Primitives.dll", + "tools/net6.0/Microsoft.Win32.SystemEvents.dll", + "tools/net6.0/Newtonsoft.Json.dll", + "tools/net6.0/System.Drawing.Common.dll", + "tools/net6.0/System.Reflection.MetadataLoadContext.dll", + "tools/net6.0/System.Security.Permissions.dll", + "tools/net6.0/System.Windows.Extensions.dll", + "tools/net6.0/cTrader.Automate.Sdk.Tasks.dll", + "tools/net6.0/runtimes/unix/lib/net6.0/System.Drawing.Common.dll", + "tools/net6.0/runtimes/win/lib/net6.0/Microsoft.Win32.SystemEvents.dll", + "tools/net6.0/runtimes/win/lib/net6.0/System.Drawing.Common.dll", + "tools/net6.0/runtimes/win/lib/net6.0/System.Windows.Extensions.dll" + ] + } + }, + "projectFileDependencyGroups": { + "net6.0": [ + "cTrader.Automate >= 1.*" + ] + }, + "packageFolders": { + "C:\\Users\\Brummel\\.nuget\\packages\\": {} + }, + "project": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Indicators\\MACD Crossover (7)\\MACD Crossover (7)\\MACD Crossover (7).csproj", + "projectName": "MACD Crossover (7)", + "projectPath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Indicators\\MACD Crossover (7)\\MACD Crossover (7)\\MACD Crossover (7).csproj", + "packagesPath": "C:\\Users\\Brummel\\.nuget\\packages\\", + "outputPath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Indicators\\MACD Crossover (7)\\MACD Crossover (7)\\obj\\", + "projectStyle": "PackageReference", + "configFilePaths": [ + "C:\\Users\\Brummel\\AppData\\Roaming\\NuGet\\NuGet.Config" + ], + "originalTargetFrameworks": [ + "net6.0" + ], + "sources": { + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net6.0": { + "targetAlias": "net6.0", + "projectReferences": {} + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + } + }, + "frameworks": { + "net6.0": { + "targetAlias": "net6.0", + "dependencies": { + "cTrader.Automate": { + "target": "Package", + "version": "[1.*, )" + } + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\6.0.200\\RuntimeIdentifierGraph.json" + } + } + } +} \ No newline at end of file diff --git a/Sources/Indicators/MACD Crossover (7)/MACD Crossover (7)/obj/project.nuget.cache b/Sources/Indicators/MACD Crossover (7)/MACD Crossover (7)/obj/project.nuget.cache new file mode 100644 index 0000000..8d633ec --- /dev/null +++ b/Sources/Indicators/MACD Crossover (7)/MACD Crossover (7)/obj/project.nuget.cache @@ -0,0 +1,10 @@ +{ + "version": 2, + "dgSpecHash": "yMGrNe3fSoOGtiz6KSiUvXuOVjyIqkEcHNE7aWnXF14zQSbGwqVbP2ubdcaxcLHLIR45SieCJ9HedMTgx/OgPA==", + "success": true, + "projectFilePath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Indicators\\MACD Crossover (7)\\MACD Crossover (7)\\MACD Crossover (7).csproj", + "expectedPackageFiles": [ + "C:\\Users\\Brummel\\.nuget\\packages\\ctrader.automate\\1.0.9\\ctrader.automate.1.0.9.nupkg.sha512" + ], + "logs": [] +} \ No newline at end of file diff --git a/Sources/Indicators/MS_LowPassFilterDemo.algo b/Sources/Indicators/MS_LowPassFilterDemo.algo new file mode 100644 index 0000000..b608d5f Binary files /dev/null and b/Sources/Indicators/MS_LowPassFilterDemo.algo differ diff --git a/Sources/Indicators/MS_LowPassFilterDemo/.vscode/tasks.json b/Sources/Indicators/MS_LowPassFilterDemo/.vscode/tasks.json new file mode 100644 index 0000000..215166e --- /dev/null +++ b/Sources/Indicators/MS_LowPassFilterDemo/.vscode/tasks.json @@ -0,0 +1 @@ +{"version":"2.0.0","tasks":[{"label":"build","command":"dotnet","type":"process","args":["build","${workspaceFolder}","/property:GenerateFullPaths=true","/consoleLoggerParameters:NoSummary"],"problemMatcher":"$msCompile"}]} \ No newline at end of file diff --git a/Sources/Indicators/MS_LowPassFilterDemo/MS_LowPassFilterDemo.sln b/Sources/Indicators/MS_LowPassFilterDemo/MS_LowPassFilterDemo.sln new file mode 100644 index 0000000..cf474d4 --- /dev/null +++ b/Sources/Indicators/MS_LowPassFilterDemo/MS_LowPassFilterDemo.sln @@ -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}") = "MS_LowPassFilterDemo", "MS_LowPassFilterDemo\MS_LowPassFilterDemo.csproj", "{d1f3b25b-4dc2-4868-81ff-9675263fbcc3}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {d1f3b25b-4dc2-4868-81ff-9675263fbcc3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {d1f3b25b-4dc2-4868-81ff-9675263fbcc3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {d1f3b25b-4dc2-4868-81ff-9675263fbcc3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {d1f3b25b-4dc2-4868-81ff-9675263fbcc3}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/Sources/Indicators/MS_LowPassFilterDemo/MS_LowPassFilterDemo/MS_LowPassFilterDemo.cs b/Sources/Indicators/MS_LowPassFilterDemo/MS_LowPassFilterDemo/MS_LowPassFilterDemo.cs new file mode 100644 index 0000000..4b387e3 --- /dev/null +++ b/Sources/Indicators/MS_LowPassFilterDemo/MS_LowPassFilterDemo/MS_LowPassFilterDemo.cs @@ -0,0 +1,205 @@ +using cAlgo.API; +using cAlgo.API.Indicators; +using cAlgo.API.Internals; +using System; +using MS; // Required for LowPassFilter and MMI from your provided file + +namespace cAlgo.Robots +{ + [Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)] + public class BesselFilterAtrSLBot : Robot + { + [Parameter("Cutoff Period", DefaultValue = 20, MinValue = 3)] // Renamed from FilterPeriodParameter + public int CutoffPeriodParameter { get; set; } + + [Parameter("ATR Period", DefaultValue = 14, MinValue = 1)] + public int AtrPeriodParameter { get; set; } + + [Parameter("MMI Period", DefaultValue = 20, MinValue = 2)] + public int MmiPeriodParameter { get; set; } + + [Parameter("Take Profit Factor (x SL)", DefaultValue = 2.0, MinValue = 0.1, Step = 0.1)] + public double TakeProfitFactor { get; set; } + + [Parameter("Volume (Lots)", DefaultValue = 0.01, MinValue = 0.01, Step = 0.01)] + public double VolumeInLots { get; set; } + + [Parameter("Bot Label", DefaultValue = "BesselMMIBotV12")] // V12 + public string BotLabel { get; set; } + + private LowPassFilter besselFilter; + private IndicatorDataSeries filterOutput; + private AverageTrueRange atr; + + private MS.MMI mmiCalculator; + private IndicatorDataSeries mmiOutput; + + private const double SamplingFrequency = 1.0; // Sampling frequency is 1 sample per bar + + protected override void OnStart() + { + // Calculate cutoffFrequency based on the new CutoffPeriodParameter + // samplingFrequency is fixed at 1.0 + // cutoffFrequency = 1.0 / CutoffPeriodParameter + double actualCutoffFrequency = 1.0 / CutoffPeriodParameter; + + // Validate the calculated cutoff frequency. + // It must be positive and strictly less than Nyquist frequency (SamplingFrequency / 2.0, which is 0.5). + // For CutoffPeriodParameter >= 3: + // 1.0 / CutoffPeriodParameter > 0 is true. + // 1.0 / CutoffPeriodParameter < 0.5 implies CutoffPeriodParameter > 2, which is true. + // The check is kept for robustness. + if (actualCutoffFrequency <= 0 || actualCutoffFrequency >= SamplingFrequency / 2.0) + { + Print($"Error: Invalid CutoffPeriodParameter ({CutoffPeriodParameter}). " + + $"The resulting Cutoff Frequency ({actualCutoffFrequency:F4}) must be > 0 and < {SamplingFrequency / 2.0}. " + + $"This implies CutoffPeriodParameter must be > 2."); + Stop(); + return; + } + + besselFilter = LowPassFilter.CreateBesselLowPass(actualCutoffFrequency, SamplingFrequency); + filterOutput = CreateDataSeries(); + atr = Indicators.AverageTrueRange(AtrPeriodParameter, MovingAverageType.Simple); + + mmiCalculator = new MS.MMI(MmiPeriodParameter); + mmiOutput = CreateDataSeries(); + + besselFilter.Reset(); + mmiCalculator.Reset(); + + for (int i = 0; i < Bars.Count; i++) + { + double historicalClose = Bars.ClosePrices[i]; + + double filterVal = besselFilter.Update(historicalClose); + filterOutput[i] = filterVal; + + double mmiVal = mmiCalculator.Update(historicalClose); + mmiOutput[i] = mmiVal; + } + } + + protected override void OnBar() + { + if (Bars.Count < 4) + { + Print($"Bars.Count = {Bars.Count}. Waiting for more history (need Bars.Count >= 4 for Last(3) access)."); + return; + } + + // Check if enough bars have loaded for ATR calculation. + // AtrPeriodParameter is the period for ATR, not related to CutoffPeriodParameter directly for this check. + if (Bars.Count <= AtrPeriodParameter) + { + Print($"Bars.Count = {Bars.Count}. Waiting for more bars for ATR calculation (AtrPeriod: {AtrPeriodParameter})."); + return; + } + + double currentAtrValue = atr.Result.Last(1); + if (double.IsNaN(currentAtrValue) || currentAtrValue <= 0) + { + Print($"ATR value is not valid (NaN or <=0): {currentAtrValue:F5} for bar index {Bars.Count - 2}. Trade checks skipped."); + return; + } + + double closeOfRecentlyClosedBar = Bars.ClosePrices.Last(1); + + double currentFilterValue = besselFilter.Update(closeOfRecentlyClosedBar); + filterOutput[Bars.Count - 2] = currentFilterValue; + + double currentMmiValue = mmiCalculator.Update(closeOfRecentlyClosedBar); + mmiOutput[Bars.Count - 2] = currentMmiValue; + + double filterVal_L1_Recent = filterOutput.Last(1); + double filterVal_L2_TurnPoint = filterOutput.Last(2); + double filterVal_L3_Prior = filterOutput.Last(3); + + if (double.IsNaN(filterVal_L1_Recent) || double.IsNaN(filterVal_L2_TurnPoint) || double.IsNaN(filterVal_L3_Prior)) + { + Print($"Filter values contain NaN. Waiting for filter to stabilize. L1: {filterVal_L1_Recent}, L2: {filterVal_L2_TurnPoint}, L3: {filterVal_L3_Prior}"); + return; + } + + bool isLowPoint = filterVal_L3_Prior > filterVal_L2_TurnPoint && filterVal_L2_TurnPoint < filterVal_L1_Recent; + bool isHighPoint = filterVal_L3_Prior < filterVal_L2_TurnPoint && filterVal_L2_TurnPoint > filterVal_L1_Recent; + + if (isLowPoint || isHighPoint) + { + double mmi_L1_Recent = mmiOutput.Last(1); + double mmi_L2_Prior = mmiOutput.Last(2); + + if (double.IsNaN(mmi_L1_Recent) || double.IsNaN(mmi_L2_Prior)) + { + Print($"MMI values for comparison contain NaN. MMI_L1_Recent: {mmi_L1_Recent}, MMI_L2_Prior: {mmi_L2_Prior}. Trade condition not met."); + return; + } + + bool isMmiFalling = mmi_L2_Prior > mmi_L1_Recent; + + if (isMmiFalling) + { + Print($"MMI is falling (Prior L2: {mmi_L2_Prior:F2} > Recent L1: {mmi_L1_Recent:F2}). Bessel signal confirmed."); + if (isLowPoint) + { + ClosePositions(TradeType.Sell); + if (Positions.FindAll(BotLabel, SymbolName, TradeType.Buy).Length == 0) + { + ExecuteOrder(TradeType.Buy, "Long Entry (Bessel Low, MMI Falling)", currentAtrValue); + } + } + else // isHighPoint + { + ClosePositions(TradeType.Buy); + if (Positions.FindAll(BotLabel, SymbolName, TradeType.Sell).Length == 0) + { + ExecuteOrder(TradeType.Sell, "Short Entry (Bessel High, MMI Falling)", currentAtrValue); + } + } + } + else + { + Print($"MMI is not falling (Prior L2: {mmi_L2_Prior:F2}, Recent L1: {mmi_L1_Recent:F2}). Trade condition not met."); + } + } + } + + private void ExecuteOrder(TradeType tradeType, string comment, double validatedAtrValue) + { + double volume = Symbol.QuantityToVolumeInUnits(VolumeInLots); + double stopLossDistanceInPrice = 4 * validatedAtrValue; + double stopLossInPips = stopLossDistanceInPrice / Symbol.PipSize; + + if (stopLossInPips < 1.0) + { + Print($"Calculated Stop Loss in pips ({stopLossInPips:F2}) is less than 1. Adjusting to 1 pip for order '{comment}'. ATR ({validatedAtrValue:F5}), PipSize ({Symbol.PipSize:F5})."); + stopLossInPips = 1.0; + } + + double? takeProfitInPips = null; + if (TakeProfitFactor > 0) + { + takeProfitInPips = stopLossInPips * TakeProfitFactor; + if (takeProfitInPips < 1.0) + { + takeProfitInPips = 1.0; + Print($"Calculated Take Profit in pips ({stopLossInPips * TakeProfitFactor:F2}) is less than 1. Adjusting to 1 pip."); + } + } + + ExecuteMarketOrder(tradeType, SymbolName, volume, BotLabel, stopLossInPips, takeProfitInPips, comment); + } + + private void ClosePositions(TradeType tradeType) + { + foreach (var position in Positions.FindAll(BotLabel, SymbolName, tradeType)) + { + ClosePosition(position); + } + } + + protected override void OnStop() + { + } + } +} \ No newline at end of file diff --git a/Sources/Indicators/MS_LowPassFilterDemo/MS_LowPassFilterDemo/MS_LowPassFilterDemo.csproj b/Sources/Indicators/MS_LowPassFilterDemo/MS_LowPassFilterDemo/MS_LowPassFilterDemo.csproj new file mode 100644 index 0000000..866383b --- /dev/null +++ b/Sources/Indicators/MS_LowPassFilterDemo/MS_LowPassFilterDemo/MS_LowPassFilterDemo.csproj @@ -0,0 +1,14 @@ + + + + net6.0 + + + + + + + ..\..\..\Common\MSLib\obj\Debug\net6.0\MSLib.dll + + + \ No newline at end of file diff --git a/Sources/Indicators/MS_LowPassFilterDemo/MS_LowPassFilterDemo/obj/Debug/net6.0/.NETCoreApp,Version=v6.0.AssemblyAttributes.cs b/Sources/Indicators/MS_LowPassFilterDemo/MS_LowPassFilterDemo/obj/Debug/net6.0/.NETCoreApp,Version=v6.0.AssemblyAttributes.cs new file mode 100644 index 0000000..32c95f9 --- /dev/null +++ b/Sources/Indicators/MS_LowPassFilterDemo/MS_LowPassFilterDemo/obj/Debug/net6.0/.NETCoreApp,Version=v6.0.AssemblyAttributes.cs @@ -0,0 +1,4 @@ +// +using System; +using System.Reflection; +[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v6.0", FrameworkDisplayName = "")] diff --git a/Sources/Indicators/MS_LowPassFilterDemo/MS_LowPassFilterDemo/obj/Debug/net6.0/MS_LowPassFilterDemo.AssemblyInfo.cs b/Sources/Indicators/MS_LowPassFilterDemo/MS_LowPassFilterDemo/obj/Debug/net6.0/MS_LowPassFilterDemo.AssemblyInfo.cs new file mode 100644 index 0000000..60b590e --- /dev/null +++ b/Sources/Indicators/MS_LowPassFilterDemo/MS_LowPassFilterDemo/obj/Debug/net6.0/MS_LowPassFilterDemo.AssemblyInfo.cs @@ -0,0 +1,22 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +using System; +using System.Reflection; + +[assembly: System.Reflection.AssemblyCompanyAttribute("MS_LowPassFilterDemo")] +[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] +[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0")] +[assembly: System.Reflection.AssemblyProductAttribute("MS_LowPassFilterDemo")] +[assembly: System.Reflection.AssemblyTitleAttribute("MS_LowPassFilterDemo")] +[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] + +// Generated by the MSBuild WriteCodeFragment class. + diff --git a/Sources/Indicators/MS_LowPassFilterDemo/MS_LowPassFilterDemo/obj/Debug/net6.0/MS_LowPassFilterDemo.AssemblyInfoInputs.cache b/Sources/Indicators/MS_LowPassFilterDemo/MS_LowPassFilterDemo/obj/Debug/net6.0/MS_LowPassFilterDemo.AssemblyInfoInputs.cache new file mode 100644 index 0000000..dd186c5 --- /dev/null +++ b/Sources/Indicators/MS_LowPassFilterDemo/MS_LowPassFilterDemo/obj/Debug/net6.0/MS_LowPassFilterDemo.AssemblyInfoInputs.cache @@ -0,0 +1 @@ +bcc33b8311974b4171c0c2609349fa752b073072 diff --git a/Sources/Indicators/MS_LowPassFilterDemo/MS_LowPassFilterDemo/obj/Debug/net6.0/MS_LowPassFilterDemo.GeneratedMSBuildEditorConfig.editorconfig b/Sources/Indicators/MS_LowPassFilterDemo/MS_LowPassFilterDemo/obj/Debug/net6.0/MS_LowPassFilterDemo.GeneratedMSBuildEditorConfig.editorconfig new file mode 100644 index 0000000..5685d8b --- /dev/null +++ b/Sources/Indicators/MS_LowPassFilterDemo/MS_LowPassFilterDemo/obj/Debug/net6.0/MS_LowPassFilterDemo.GeneratedMSBuildEditorConfig.editorconfig @@ -0,0 +1,10 @@ +is_global = true +build_property.TargetFramework = net6.0 +build_property.TargetPlatformMinVersion = +build_property.UsingMicrosoftNETSdkWeb = +build_property.ProjectTypeGuids = +build_property.InvariantGlobalization = +build_property.PlatformNeutralAssembly = +build_property._SupportedPlatformList = Linux,macOS,Windows +build_property.RootNamespace = MS_LowPassFilterDemo +build_property.ProjectDir = C:\Users\Brummel\Documents\cAlgo\Sources\Indicators\MS_LowPassFilterDemo\MS_LowPassFilterDemo\ diff --git a/Sources/Indicators/MS_LowPassFilterDemo/MS_LowPassFilterDemo/obj/Debug/net6.0/MS_LowPassFilterDemo.assets.cache b/Sources/Indicators/MS_LowPassFilterDemo/MS_LowPassFilterDemo/obj/Debug/net6.0/MS_LowPassFilterDemo.assets.cache new file mode 100644 index 0000000..17656a9 Binary files /dev/null and b/Sources/Indicators/MS_LowPassFilterDemo/MS_LowPassFilterDemo/obj/Debug/net6.0/MS_LowPassFilterDemo.assets.cache differ diff --git a/Sources/Indicators/MS_LowPassFilterDemo/MS_LowPassFilterDemo/obj/Debug/net6.0/MS_LowPassFilterDemo.csproj.AssemblyReference.cache b/Sources/Indicators/MS_LowPassFilterDemo/MS_LowPassFilterDemo/obj/Debug/net6.0/MS_LowPassFilterDemo.csproj.AssemblyReference.cache new file mode 100644 index 0000000..76e6da8 Binary files /dev/null and b/Sources/Indicators/MS_LowPassFilterDemo/MS_LowPassFilterDemo/obj/Debug/net6.0/MS_LowPassFilterDemo.csproj.AssemblyReference.cache differ diff --git a/Sources/Indicators/MS_LowPassFilterDemo/MS_LowPassFilterDemo/obj/MS_LowPassFilterDemo.csproj.nuget.dgspec.json b/Sources/Indicators/MS_LowPassFilterDemo/MS_LowPassFilterDemo/obj/MS_LowPassFilterDemo.csproj.nuget.dgspec.json new file mode 100644 index 0000000..b0d3c2e --- /dev/null +++ b/Sources/Indicators/MS_LowPassFilterDemo/MS_LowPassFilterDemo/obj/MS_LowPassFilterDemo.csproj.nuget.dgspec.json @@ -0,0 +1,66 @@ +{ + "format": 1, + "restore": { + "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Indicators\\MS_LowPassFilterDemo\\MS_LowPassFilterDemo\\MS_LowPassFilterDemo.csproj": {} + }, + "projects": { + "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Indicators\\MS_LowPassFilterDemo\\MS_LowPassFilterDemo\\MS_LowPassFilterDemo.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Indicators\\MS_LowPassFilterDemo\\MS_LowPassFilterDemo\\MS_LowPassFilterDemo.csproj", + "projectName": "MS_LowPassFilterDemo", + "projectPath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Indicators\\MS_LowPassFilterDemo\\MS_LowPassFilterDemo\\MS_LowPassFilterDemo.csproj", + "packagesPath": "C:\\Users\\Brummel\\.nuget\\packages\\", + "outputPath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Indicators\\MS_LowPassFilterDemo\\MS_LowPassFilterDemo\\obj\\", + "projectStyle": "PackageReference", + "configFilePaths": [ + "C:\\Users\\Brummel\\AppData\\Roaming\\NuGet\\NuGet.Config" + ], + "originalTargetFrameworks": [ + "net6.0" + ], + "sources": { + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net6.0": { + "targetAlias": "net6.0", + "projectReferences": {} + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + } + }, + "frameworks": { + "net6.0": { + "targetAlias": "net6.0", + "dependencies": { + "cTrader.Automate": { + "target": "Package", + "version": "[*, )" + } + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\6.0.200\\RuntimeIdentifierGraph.json" + } + } + } + } +} \ No newline at end of file diff --git a/Sources/Indicators/MS_LowPassFilterDemo/MS_LowPassFilterDemo/obj/MS_LowPassFilterDemo.csproj.nuget.g.props b/Sources/Indicators/MS_LowPassFilterDemo/MS_LowPassFilterDemo/obj/MS_LowPassFilterDemo.csproj.nuget.g.props new file mode 100644 index 0000000..120d227 --- /dev/null +++ b/Sources/Indicators/MS_LowPassFilterDemo/MS_LowPassFilterDemo/obj/MS_LowPassFilterDemo.csproj.nuget.g.props @@ -0,0 +1,21 @@ + + + + True + NuGet + $(MSBuildThisFileDirectory)project.assets.json + $(UserProfile)\.nuget\packages\ + C:\Users\Brummel\.nuget\packages\ + PackageReference + 6.1.0 + + + + + + + + + C:\Users\Brummel\.nuget\packages\ctrader.automate\1.0.12 + + \ No newline at end of file diff --git a/Sources/Indicators/MS_LowPassFilterDemo/MS_LowPassFilterDemo/obj/MS_LowPassFilterDemo.csproj.nuget.g.targets b/Sources/Indicators/MS_LowPassFilterDemo/MS_LowPassFilterDemo/obj/MS_LowPassFilterDemo.csproj.nuget.g.targets new file mode 100644 index 0000000..29b3c4c --- /dev/null +++ b/Sources/Indicators/MS_LowPassFilterDemo/MS_LowPassFilterDemo/obj/MS_LowPassFilterDemo.csproj.nuget.g.targets @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Sources/Indicators/MS_LowPassFilterDemo/MS_LowPassFilterDemo/obj/project.assets.json b/Sources/Indicators/MS_LowPassFilterDemo/MS_LowPassFilterDemo/obj/project.assets.json new file mode 100644 index 0000000..ac27895 --- /dev/null +++ b/Sources/Indicators/MS_LowPassFilterDemo/MS_LowPassFilterDemo/obj/project.assets.json @@ -0,0 +1,139 @@ +{ + "version": 3, + "targets": { + "net6.0": { + "cTrader.Automate/1.0.12": { + "type": "package", + "compile": { + "lib/net6.0/cAlgo.API.dll": {} + }, + "runtime": { + "lib/net6.0/cAlgo.API.dll": {} + }, + "build": { + "build/cTrader.Automate.props": {}, + "build/cTrader.Automate.targets": {} + } + } + } + }, + "libraries": { + "cTrader.Automate/1.0.12": { + "sha512": "h+HPnbqPTkoSeojoYwWWOo5Jm9ExkmrmsU/WbF/idcgO2auKAPILBghZRP6Qt5mAcE/40YCRMNHT45/kzzs5Sg==", + "type": "package", + "path": "ctrader.automate/1.0.12", + "hasTools": true, + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "build/cTrader.Automate.props", + "build/cTrader.Automate.targets", + "ctrader.automate.1.0.12.nupkg.sha512", + "ctrader.automate.nuspec", + "eula.md", + "icon.png", + "lib/net40/cAlgo.API.dll", + "lib/net40/cAlgo.API.xml", + "lib/net6.0/cAlgo.API.dll", + "lib/net6.0/cAlgo.API.xml", + "tools/net472/Core.AlgoFormat.Compose.Reflection.dll", + "tools/net472/Core.AlgoFormat.Writer.dll", + "tools/net472/Core.AlgoFormat.dll", + "tools/net472/Core.Domain.Primitives.dll", + "tools/net472/Newtonsoft.Json.dll", + "tools/net472/System.Buffers.dll", + "tools/net472/System.Collections.Immutable.dll", + "tools/net472/System.Memory.dll", + "tools/net472/System.Numerics.Vectors.dll", + "tools/net472/System.Reflection.Metadata.dll", + "tools/net472/System.Reflection.MetadataLoadContext.dll", + "tools/net472/System.Runtime.CompilerServices.Unsafe.dll", + "tools/net472/cTrader.Automate.Sdk.Tasks.dll", + "tools/net6.0/Core.AlgoFormat.Compose.Reflection.dll", + "tools/net6.0/Core.AlgoFormat.Writer.dll", + "tools/net6.0/Core.AlgoFormat.dll", + "tools/net6.0/Core.Connection.Protobuf.Common.dll", + "tools/net6.0/Core.Domain.Primitives.dll", + "tools/net6.0/Microsoft.Win32.SystemEvents.dll", + "tools/net6.0/Newtonsoft.Json.dll", + "tools/net6.0/System.Drawing.Common.dll", + "tools/net6.0/System.Reflection.MetadataLoadContext.dll", + "tools/net6.0/System.Security.Permissions.dll", + "tools/net6.0/System.Windows.Extensions.dll", + "tools/net6.0/cTrader.Automate.Sdk.Tasks.dll", + "tools/net6.0/protobuf-net.Core.dll", + "tools/net6.0/protobuf-net.dll", + "tools/net6.0/runtimes/unix/lib/net6.0/System.Drawing.Common.dll", + "tools/net6.0/runtimes/win/lib/net6.0/Microsoft.Win32.SystemEvents.dll", + "tools/net6.0/runtimes/win/lib/net6.0/System.Drawing.Common.dll", + "tools/net6.0/runtimes/win/lib/net6.0/System.Windows.Extensions.dll" + ] + } + }, + "projectFileDependencyGroups": { + "net6.0": [ + "cTrader.Automate >= *" + ] + }, + "packageFolders": { + "C:\\Users\\Brummel\\.nuget\\packages\\": {} + }, + "project": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Indicators\\MS_LowPassFilterDemo\\MS_LowPassFilterDemo\\MS_LowPassFilterDemo.csproj", + "projectName": "MS_LowPassFilterDemo", + "projectPath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Indicators\\MS_LowPassFilterDemo\\MS_LowPassFilterDemo\\MS_LowPassFilterDemo.csproj", + "packagesPath": "C:\\Users\\Brummel\\.nuget\\packages\\", + "outputPath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Indicators\\MS_LowPassFilterDemo\\MS_LowPassFilterDemo\\obj\\", + "projectStyle": "PackageReference", + "configFilePaths": [ + "C:\\Users\\Brummel\\AppData\\Roaming\\NuGet\\NuGet.Config" + ], + "originalTargetFrameworks": [ + "net6.0" + ], + "sources": { + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net6.0": { + "targetAlias": "net6.0", + "projectReferences": {} + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + } + }, + "frameworks": { + "net6.0": { + "targetAlias": "net6.0", + "dependencies": { + "cTrader.Automate": { + "target": "Package", + "version": "[*, )" + } + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\6.0.200\\RuntimeIdentifierGraph.json" + } + } + } +} \ No newline at end of file diff --git a/Sources/Indicators/MS_LowPassFilterDemo/MS_LowPassFilterDemo/obj/project.nuget.cache b/Sources/Indicators/MS_LowPassFilterDemo/MS_LowPassFilterDemo/obj/project.nuget.cache new file mode 100644 index 0000000..110c62c --- /dev/null +++ b/Sources/Indicators/MS_LowPassFilterDemo/MS_LowPassFilterDemo/obj/project.nuget.cache @@ -0,0 +1,10 @@ +{ + "version": 2, + "dgSpecHash": "ZUN9dJK6IO89CJta6N5Xb6zI5P/TwW3ITVnZJdBrfsSAyQZJNs0epgLJUeOFB7BtelfX4t45/QcE1YswvR+aYw==", + "success": true, + "projectFilePath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Indicators\\MS_LowPassFilterDemo\\MS_LowPassFilterDemo\\MS_LowPassFilterDemo.csproj", + "expectedPackageFiles": [ + "C:\\Users\\Brummel\\.nuget\\packages\\ctrader.automate\\1.0.12\\ctrader.automate.1.0.12.nupkg.sha512" + ], + "logs": [] +} \ No newline at end of file diff --git a/Sources/Indicators/MS_MMI-Gemini.algo b/Sources/Indicators/MS_MMI-Gemini.algo new file mode 100644 index 0000000..8e97c83 Binary files /dev/null and b/Sources/Indicators/MS_MMI-Gemini.algo differ diff --git a/Sources/Indicators/MS_MMI-Gemini/.vscode/tasks.json b/Sources/Indicators/MS_MMI-Gemini/.vscode/tasks.json new file mode 100644 index 0000000..215166e --- /dev/null +++ b/Sources/Indicators/MS_MMI-Gemini/.vscode/tasks.json @@ -0,0 +1 @@ +{"version":"2.0.0","tasks":[{"label":"build","command":"dotnet","type":"process","args":["build","${workspaceFolder}","/property:GenerateFullPaths=true","/consoleLoggerParameters:NoSummary"],"problemMatcher":"$msCompile"}]} \ No newline at end of file diff --git a/Sources/Indicators/MS_MMI-Gemini/MS_MMI-Gemini.sln b/Sources/Indicators/MS_MMI-Gemini/MS_MMI-Gemini.sln new file mode 100644 index 0000000..9042903 --- /dev/null +++ b/Sources/Indicators/MS_MMI-Gemini/MS_MMI-Gemini.sln @@ -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}") = "MS_MMI-Gemini", "MS_MMI-Gemini\MS_MMI-Gemini.csproj", "{79df7f5d-d387-4a9d-92f8-9072b4d06c29}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {79df7f5d-d387-4a9d-92f8-9072b4d06c29}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {79df7f5d-d387-4a9d-92f8-9072b4d06c29}.Debug|Any CPU.Build.0 = Debug|Any CPU + {79df7f5d-d387-4a9d-92f8-9072b4d06c29}.Release|Any CPU.ActiveCfg = Release|Any CPU + {79df7f5d-d387-4a9d-92f8-9072b4d06c29}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/Sources/Indicators/MS_MMI-Gemini/MS_MMI-Gemini/MS_MMI-Gemini.cs b/Sources/Indicators/MS_MMI-Gemini/MS_MMI-Gemini/MS_MMI-Gemini.cs new file mode 100644 index 0000000..56834af --- /dev/null +++ b/Sources/Indicators/MS_MMI-Gemini/MS_MMI-Gemini/MS_MMI-Gemini.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using cAlgo.API; +using cAlgo.API.Internals; +using cAlgo.API.Indicators; + +using MS; + +namespace cAlgo +{ + [Indicator(IsOverlay = false, TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)] + public class MarketMeannessIndex : Indicator + { + [Parameter("Time Period", DefaultValue = 20, MinValue = 2)] + public int TimePeriod { get; set; } + + [Parameter("Source")] + public DataSeries Source { get; set; } + + [Output("MMI", LineColor = "Orange")] + public IndicatorDataSeries Result { get; set; } + + // Instance of the MMI class to handle all MMI calculations. + private MMI _mmiCalculator; + private int _lastIndex = -1; + + protected override void Initialize() + { + // Initialize the MMI calculator with the specified TimePeriod. + _mmiCalculator = new MMI(TimePeriod); // Use new class name MMI + } + + public override void Calculate(int index) + { + Result[index] = _mmiCalculator.Update(Source[index], index > _lastIndex); + _lastIndex = index; + } + } +} \ No newline at end of file diff --git a/Sources/Indicators/MS_MMI-Gemini/MS_MMI-Gemini/MS_MMI-Gemini.csproj b/Sources/Indicators/MS_MMI-Gemini/MS_MMI-Gemini/MS_MMI-Gemini.csproj new file mode 100644 index 0000000..866383b --- /dev/null +++ b/Sources/Indicators/MS_MMI-Gemini/MS_MMI-Gemini/MS_MMI-Gemini.csproj @@ -0,0 +1,14 @@ + + + + net6.0 + + + + + + + ..\..\..\Common\MSLib\obj\Debug\net6.0\MSLib.dll + + + \ No newline at end of file diff --git a/Sources/Indicators/MS_MMI-Gemini/MS_MMI-Gemini/obj/Debug/net6.0/.NETCoreApp,Version=v6.0.AssemblyAttributes.cs b/Sources/Indicators/MS_MMI-Gemini/MS_MMI-Gemini/obj/Debug/net6.0/.NETCoreApp,Version=v6.0.AssemblyAttributes.cs new file mode 100644 index 0000000..32c95f9 --- /dev/null +++ b/Sources/Indicators/MS_MMI-Gemini/MS_MMI-Gemini/obj/Debug/net6.0/.NETCoreApp,Version=v6.0.AssemblyAttributes.cs @@ -0,0 +1,4 @@ +// +using System; +using System.Reflection; +[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v6.0", FrameworkDisplayName = "")] diff --git a/Sources/Indicators/MS_MMI-Gemini/MS_MMI-Gemini/obj/Debug/net6.0/MS_MMI-Gemini.AssemblyInfo.cs b/Sources/Indicators/MS_MMI-Gemini/MS_MMI-Gemini/obj/Debug/net6.0/MS_MMI-Gemini.AssemblyInfo.cs new file mode 100644 index 0000000..5ee631a --- /dev/null +++ b/Sources/Indicators/MS_MMI-Gemini/MS_MMI-Gemini/obj/Debug/net6.0/MS_MMI-Gemini.AssemblyInfo.cs @@ -0,0 +1,22 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +using System; +using System.Reflection; + +[assembly: System.Reflection.AssemblyCompanyAttribute("MS_MMI-Gemini")] +[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] +[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0")] +[assembly: System.Reflection.AssemblyProductAttribute("MS_MMI-Gemini")] +[assembly: System.Reflection.AssemblyTitleAttribute("MS_MMI-Gemini")] +[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] + +// Generated by the MSBuild WriteCodeFragment class. + diff --git a/Sources/Indicators/MS_MMI-Gemini/MS_MMI-Gemini/obj/Debug/net6.0/MS_MMI-Gemini.AssemblyInfoInputs.cache b/Sources/Indicators/MS_MMI-Gemini/MS_MMI-Gemini/obj/Debug/net6.0/MS_MMI-Gemini.AssemblyInfoInputs.cache new file mode 100644 index 0000000..b983df7 --- /dev/null +++ b/Sources/Indicators/MS_MMI-Gemini/MS_MMI-Gemini/obj/Debug/net6.0/MS_MMI-Gemini.AssemblyInfoInputs.cache @@ -0,0 +1 @@ +6212e91709e6f354c7deb7d22a8e0ffdd8bd7ce9 diff --git a/Sources/Indicators/MS_MMI-Gemini/MS_MMI-Gemini/obj/Debug/net6.0/MS_MMI-Gemini.GeneratedMSBuildEditorConfig.editorconfig b/Sources/Indicators/MS_MMI-Gemini/MS_MMI-Gemini/obj/Debug/net6.0/MS_MMI-Gemini.GeneratedMSBuildEditorConfig.editorconfig new file mode 100644 index 0000000..8a9195f --- /dev/null +++ b/Sources/Indicators/MS_MMI-Gemini/MS_MMI-Gemini/obj/Debug/net6.0/MS_MMI-Gemini.GeneratedMSBuildEditorConfig.editorconfig @@ -0,0 +1,10 @@ +is_global = true +build_property.TargetFramework = net6.0 +build_property.TargetPlatformMinVersion = +build_property.UsingMicrosoftNETSdkWeb = +build_property.ProjectTypeGuids = +build_property.InvariantGlobalization = +build_property.PlatformNeutralAssembly = +build_property._SupportedPlatformList = Linux,macOS,Windows +build_property.RootNamespace = MS_MMI-Gemini +build_property.ProjectDir = C:\Users\Brummel\Documents\cAlgo\Sources\Indicators\MS_MMI-Gemini\MS_MMI-Gemini\ diff --git a/Sources/Indicators/MS_MMI-Gemini/MS_MMI-Gemini/obj/Debug/net6.0/MS_MMI-Gemini.assets.cache b/Sources/Indicators/MS_MMI-Gemini/MS_MMI-Gemini/obj/Debug/net6.0/MS_MMI-Gemini.assets.cache new file mode 100644 index 0000000..59fab5b Binary files /dev/null and b/Sources/Indicators/MS_MMI-Gemini/MS_MMI-Gemini/obj/Debug/net6.0/MS_MMI-Gemini.assets.cache differ diff --git a/Sources/Indicators/MS_MMI-Gemini/MS_MMI-Gemini/obj/Debug/net6.0/MS_MMI-Gemini.csproj.AssemblyReference.cache b/Sources/Indicators/MS_MMI-Gemini/MS_MMI-Gemini/obj/Debug/net6.0/MS_MMI-Gemini.csproj.AssemblyReference.cache new file mode 100644 index 0000000..5fa6f84 Binary files /dev/null and b/Sources/Indicators/MS_MMI-Gemini/MS_MMI-Gemini/obj/Debug/net6.0/MS_MMI-Gemini.csproj.AssemblyReference.cache differ diff --git a/Sources/Indicators/MS_MMI-Gemini/MS_MMI-Gemini/obj/MS_MMI-Gemini.csproj.nuget.dgspec.json b/Sources/Indicators/MS_MMI-Gemini/MS_MMI-Gemini/obj/MS_MMI-Gemini.csproj.nuget.dgspec.json new file mode 100644 index 0000000..dda5145 --- /dev/null +++ b/Sources/Indicators/MS_MMI-Gemini/MS_MMI-Gemini/obj/MS_MMI-Gemini.csproj.nuget.dgspec.json @@ -0,0 +1,66 @@ +{ + "format": 1, + "restore": { + "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Indicators\\MS_MMI-Gemini\\MS_MMI-Gemini\\MS_MMI-Gemini.csproj": {} + }, + "projects": { + "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Indicators\\MS_MMI-Gemini\\MS_MMI-Gemini\\MS_MMI-Gemini.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Indicators\\MS_MMI-Gemini\\MS_MMI-Gemini\\MS_MMI-Gemini.csproj", + "projectName": "MS_MMI-Gemini", + "projectPath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Indicators\\MS_MMI-Gemini\\MS_MMI-Gemini\\MS_MMI-Gemini.csproj", + "packagesPath": "C:\\Users\\Brummel\\.nuget\\packages\\", + "outputPath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Indicators\\MS_MMI-Gemini\\MS_MMI-Gemini\\obj\\", + "projectStyle": "PackageReference", + "configFilePaths": [ + "C:\\Users\\Brummel\\AppData\\Roaming\\NuGet\\NuGet.Config" + ], + "originalTargetFrameworks": [ + "net6.0" + ], + "sources": { + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net6.0": { + "targetAlias": "net6.0", + "projectReferences": {} + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + } + }, + "frameworks": { + "net6.0": { + "targetAlias": "net6.0", + "dependencies": { + "cTrader.Automate": { + "target": "Package", + "version": "[*, )" + } + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\6.0.200\\RuntimeIdentifierGraph.json" + } + } + } + } +} \ No newline at end of file diff --git a/Sources/Indicators/MS_MMI-Gemini/MS_MMI-Gemini/obj/MS_MMI-Gemini.csproj.nuget.g.props b/Sources/Indicators/MS_MMI-Gemini/MS_MMI-Gemini/obj/MS_MMI-Gemini.csproj.nuget.g.props new file mode 100644 index 0000000..120d227 --- /dev/null +++ b/Sources/Indicators/MS_MMI-Gemini/MS_MMI-Gemini/obj/MS_MMI-Gemini.csproj.nuget.g.props @@ -0,0 +1,21 @@ + + + + True + NuGet + $(MSBuildThisFileDirectory)project.assets.json + $(UserProfile)\.nuget\packages\ + C:\Users\Brummel\.nuget\packages\ + PackageReference + 6.1.0 + + + + + + + + + C:\Users\Brummel\.nuget\packages\ctrader.automate\1.0.12 + + \ No newline at end of file diff --git a/Sources/Indicators/MS_MMI-Gemini/MS_MMI-Gemini/obj/MS_MMI-Gemini.csproj.nuget.g.targets b/Sources/Indicators/MS_MMI-Gemini/MS_MMI-Gemini/obj/MS_MMI-Gemini.csproj.nuget.g.targets new file mode 100644 index 0000000..29b3c4c --- /dev/null +++ b/Sources/Indicators/MS_MMI-Gemini/MS_MMI-Gemini/obj/MS_MMI-Gemini.csproj.nuget.g.targets @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Sources/Indicators/MS_MMI-Gemini/MS_MMI-Gemini/obj/project.assets.json b/Sources/Indicators/MS_MMI-Gemini/MS_MMI-Gemini/obj/project.assets.json new file mode 100644 index 0000000..1291cf1 --- /dev/null +++ b/Sources/Indicators/MS_MMI-Gemini/MS_MMI-Gemini/obj/project.assets.json @@ -0,0 +1,139 @@ +{ + "version": 3, + "targets": { + "net6.0": { + "cTrader.Automate/1.0.12": { + "type": "package", + "compile": { + "lib/net6.0/cAlgo.API.dll": {} + }, + "runtime": { + "lib/net6.0/cAlgo.API.dll": {} + }, + "build": { + "build/cTrader.Automate.props": {}, + "build/cTrader.Automate.targets": {} + } + } + } + }, + "libraries": { + "cTrader.Automate/1.0.12": { + "sha512": "h+HPnbqPTkoSeojoYwWWOo5Jm9ExkmrmsU/WbF/idcgO2auKAPILBghZRP6Qt5mAcE/40YCRMNHT45/kzzs5Sg==", + "type": "package", + "path": "ctrader.automate/1.0.12", + "hasTools": true, + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "build/cTrader.Automate.props", + "build/cTrader.Automate.targets", + "ctrader.automate.1.0.12.nupkg.sha512", + "ctrader.automate.nuspec", + "eula.md", + "icon.png", + "lib/net40/cAlgo.API.dll", + "lib/net40/cAlgo.API.xml", + "lib/net6.0/cAlgo.API.dll", + "lib/net6.0/cAlgo.API.xml", + "tools/net472/Core.AlgoFormat.Compose.Reflection.dll", + "tools/net472/Core.AlgoFormat.Writer.dll", + "tools/net472/Core.AlgoFormat.dll", + "tools/net472/Core.Domain.Primitives.dll", + "tools/net472/Newtonsoft.Json.dll", + "tools/net472/System.Buffers.dll", + "tools/net472/System.Collections.Immutable.dll", + "tools/net472/System.Memory.dll", + "tools/net472/System.Numerics.Vectors.dll", + "tools/net472/System.Reflection.Metadata.dll", + "tools/net472/System.Reflection.MetadataLoadContext.dll", + "tools/net472/System.Runtime.CompilerServices.Unsafe.dll", + "tools/net472/cTrader.Automate.Sdk.Tasks.dll", + "tools/net6.0/Core.AlgoFormat.Compose.Reflection.dll", + "tools/net6.0/Core.AlgoFormat.Writer.dll", + "tools/net6.0/Core.AlgoFormat.dll", + "tools/net6.0/Core.Connection.Protobuf.Common.dll", + "tools/net6.0/Core.Domain.Primitives.dll", + "tools/net6.0/Microsoft.Win32.SystemEvents.dll", + "tools/net6.0/Newtonsoft.Json.dll", + "tools/net6.0/System.Drawing.Common.dll", + "tools/net6.0/System.Reflection.MetadataLoadContext.dll", + "tools/net6.0/System.Security.Permissions.dll", + "tools/net6.0/System.Windows.Extensions.dll", + "tools/net6.0/cTrader.Automate.Sdk.Tasks.dll", + "tools/net6.0/protobuf-net.Core.dll", + "tools/net6.0/protobuf-net.dll", + "tools/net6.0/runtimes/unix/lib/net6.0/System.Drawing.Common.dll", + "tools/net6.0/runtimes/win/lib/net6.0/Microsoft.Win32.SystemEvents.dll", + "tools/net6.0/runtimes/win/lib/net6.0/System.Drawing.Common.dll", + "tools/net6.0/runtimes/win/lib/net6.0/System.Windows.Extensions.dll" + ] + } + }, + "projectFileDependencyGroups": { + "net6.0": [ + "cTrader.Automate >= *" + ] + }, + "packageFolders": { + "C:\\Users\\Brummel\\.nuget\\packages\\": {} + }, + "project": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Indicators\\MS_MMI-Gemini\\MS_MMI-Gemini\\MS_MMI-Gemini.csproj", + "projectName": "MS_MMI-Gemini", + "projectPath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Indicators\\MS_MMI-Gemini\\MS_MMI-Gemini\\MS_MMI-Gemini.csproj", + "packagesPath": "C:\\Users\\Brummel\\.nuget\\packages\\", + "outputPath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Indicators\\MS_MMI-Gemini\\MS_MMI-Gemini\\obj\\", + "projectStyle": "PackageReference", + "configFilePaths": [ + "C:\\Users\\Brummel\\AppData\\Roaming\\NuGet\\NuGet.Config" + ], + "originalTargetFrameworks": [ + "net6.0" + ], + "sources": { + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net6.0": { + "targetAlias": "net6.0", + "projectReferences": {} + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + } + }, + "frameworks": { + "net6.0": { + "targetAlias": "net6.0", + "dependencies": { + "cTrader.Automate": { + "target": "Package", + "version": "[*, )" + } + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\6.0.200\\RuntimeIdentifierGraph.json" + } + } + } +} \ No newline at end of file diff --git a/Sources/Indicators/MS_MMI-Gemini/MS_MMI-Gemini/obj/project.nuget.cache b/Sources/Indicators/MS_MMI-Gemini/MS_MMI-Gemini/obj/project.nuget.cache new file mode 100644 index 0000000..f6a5e37 --- /dev/null +++ b/Sources/Indicators/MS_MMI-Gemini/MS_MMI-Gemini/obj/project.nuget.cache @@ -0,0 +1,10 @@ +{ + "version": 2, + "dgSpecHash": "elBf61JcK4g2x3cqEBsppIXUJSF6GeLSDQQLuujEWh0gPIwtNN2wYTVF2iOPwt/aBLgdzQWsCtyEVbtTTANXcQ==", + "success": true, + "projectFilePath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Indicators\\MS_MMI-Gemini\\MS_MMI-Gemini\\MS_MMI-Gemini.csproj", + "expectedPackageFiles": [ + "C:\\Users\\Brummel\\.nuget\\packages\\ctrader.automate\\1.0.12\\ctrader.automate.1.0.12.nupkg.sha512" + ], + "logs": [] +} \ No newline at end of file diff --git a/Sources/Indicators/MS_Trend_BlackBook.algo b/Sources/Indicators/MS_Trend_BlackBook.algo new file mode 100644 index 0000000..89d07ff Binary files /dev/null and b/Sources/Indicators/MS_Trend_BlackBook.algo differ diff --git a/Sources/Indicators/MultiTimeframeSignalAggregator.algo b/Sources/Indicators/MultiTimeframeSignalAggregator.algo new file mode 100644 index 0000000..ea8914e Binary files /dev/null and b/Sources/Indicators/MultiTimeframeSignalAggregator.algo differ diff --git a/Sources/Indicators/MultiTimeframeSignalAggregator/.vscode/tasks.json b/Sources/Indicators/MultiTimeframeSignalAggregator/.vscode/tasks.json new file mode 100644 index 0000000..215166e --- /dev/null +++ b/Sources/Indicators/MultiTimeframeSignalAggregator/.vscode/tasks.json @@ -0,0 +1 @@ +{"version":"2.0.0","tasks":[{"label":"build","command":"dotnet","type":"process","args":["build","${workspaceFolder}","/property:GenerateFullPaths=true","/consoleLoggerParameters:NoSummary"],"problemMatcher":"$msCompile"}]} \ No newline at end of file diff --git a/Sources/Indicators/MultiTimeframeSignalAggregator/MultiTimeframeSignalAggregator.sln b/Sources/Indicators/MultiTimeframeSignalAggregator/MultiTimeframeSignalAggregator.sln new file mode 100644 index 0000000..2cd43d9 --- /dev/null +++ b/Sources/Indicators/MultiTimeframeSignalAggregator/MultiTimeframeSignalAggregator.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30011.22 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HmaSmaCrossoverSignal", "..\HmaSmaCrossoverSignal\HmaSmaCrossoverSignal\HmaSmaCrossoverSignal.csproj", "{e8c2d7a1-30b4-4506-8340-eb539cf7beb6}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MultiTimeframeSignalAggregator", "MultiTimeframeSignalAggregator\MultiTimeframeSignalAggregator.csproj", "{14f3d6c2-2294-4116-a68c-4e6230c98b1c}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {14f3d6c2-2294-4116-a68c-4e6230c98b1c}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {14f3d6c2-2294-4116-a68c-4e6230c98b1c}.Debug|Any CPU.Build.0 = Debug|Any CPU + {14f3d6c2-2294-4116-a68c-4e6230c98b1c}.Release|Any CPU.ActiveCfg = Release|Any CPU + {14f3d6c2-2294-4116-a68c-4e6230c98b1c}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal + diff --git a/Sources/Indicators/MultiTimeframeSignalAggregator/MultiTimeframeSignalAggregator/MultiTimeframeSignalAggregator.cs b/Sources/Indicators/MultiTimeframeSignalAggregator/MultiTimeframeSignalAggregator/MultiTimeframeSignalAggregator.cs new file mode 100644 index 0000000..c4a0132 --- /dev/null +++ b/Sources/Indicators/MultiTimeframeSignalAggregator/MultiTimeframeSignalAggregator/MultiTimeframeSignalAggregator.cs @@ -0,0 +1,156 @@ +using System; +using cAlgo.API; +using cAlgo.API.Indicators; +// using cAlgo.API.Extensions; // Removed faulty namespace reference + +namespace cAlgo.Indicators +{ + // Aggregates signals from HmaSmaCrossoverSignal on two timeframes + [Indicator(IsOverlay = false, TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)] + public class MultiTimeframeSignalAggregator : Indicator + { + [Parameter("HTF TimeFrame (B)", DefaultValue = "m2")] + public TimeFrame HtfTimeFrame { get; set; } + + [Parameter("HMA A Period (Fast)", DefaultValue = 20, Group = "Indicator Settings")] + public int HmaAPeriod { get; set; } + + [Parameter("HMA B Period (Slow)", DefaultValue = 250, Group = "Indicator Settings")] + public int HmaBPeriod { get; set; } + + [Parameter("SMA C Period (Base)", DefaultValue = 200, Group = "Indicator Settings")] + public int SmaCPeriod { get; set; } + + // Output for final Buy signals + [Output("Buy Signal", LineColor = "Yellow", PlotType = PlotType.Points, Thickness = 8)] + public IndicatorDataSeries BuySignal { get; set; } + + // Output for final Sell signals + [Output("Sell Signal", LineColor = "Red", PlotType = PlotType.Points, Thickness = 8)] + public IndicatorDataSeries SellSignal { get; set; } + + // Indicator A (Current Chart) + private HmaSmaCrossoverSignal _indicatorA; + + // Indicator B (Higher Timeframe) + private HmaSmaCrossoverSignal _indicatorB; + private Bars _htfBars; + + // State variables to track the last signal on the HTF + private DateTime _lastHtfBuyTime = DateTime.MinValue; + private DateTime _lastHtfSellTime = DateTime.MinValue; + private int _lastHtfIndexChecked = -1; + + protected override void Initialize() + { + // Get the Bars for the higher timeframe + _htfBars = MarketData.GetBars(HtfTimeFrame); + + // Initialize Indicator A (current timeframe) + _indicatorA = Indicators.GetIndicator(HmaAPeriod, HmaBPeriod, SmaCPeriod); + + // Initialize Indicator B (higher timeframe) + _indicatorB = Indicators.GetIndicator(_htfBars, HmaAPeriod, HmaBPeriod, SmaCPeriod); + } + + public override void Calculate(int index) + { + // Reset signals for the current bar (index) and the previous bar (index - 1) + // This handles the repainting nature of the underlying signal + BuySignal[index] = double.NaN; + SellSignal[index] = double.NaN; + if (index > 0) + { + BuySignal[index - 1] = double.NaN; + SellSignal[index - 1] = double.NaN; + } + + // Need at least 2 bars for index-1 signals + if (index < 2) + { + return; + } + + // --- 1. Update HTF (Indicator B) State --- + + // Manual replacement for GetIndexByTime + int htfIndex = GetHtfIndex(Bars.OpenTimes[index]); + if (htfIndex == -1) + { + // Not enough HTF data + return; + } + + // Scan HTF bars from where we left off up to the current htfIndex + // We scan efficiently, only checking new bars + int scanFromIndex = Math.Max(0, _lastHtfIndexChecked + 1); + int scanToIndex = htfIndex; + + for (int i = scanFromIndex; i <= scanToIndex; i++) + { + // The signal indicator needs at least 2 bars (i and i-1) to check for signals + if (i < 2) continue; + + // Check for confirmed signals at (i-1) + if (!double.IsNaN(_indicatorB.BuySignal[i - 1])) + { + _lastHtfBuyTime = _htfBars.OpenTimes[i - 1]; + } + if (!double.IsNaN(_indicatorB.SellSignal[i - 1])) + { + _lastHtfSellTime = _htfBars.OpenTimes[i - 1]; + } + } + // Remember how far we've scanned + _lastHtfIndexChecked = scanToIndex; + + // --- 2. Check Current TF (Indicator A) Signal --- + + // Get the confirmed signal from the *previous* bar (index - 1) + double currentBuySignal = _indicatorA.BuySignal[index - 1]; + double currentSellSignal = _indicatorA.SellSignal[index - 1]; + + bool hasCurrentBuy = !double.IsNaN(currentBuySignal); + bool hasCurrentSell = !double.IsNaN(currentSellSignal); + + if (!hasCurrentBuy && !hasCurrentSell) + { + // No signal on current TF + return; + } + + // --- 3. Apply Aggregation Logic --- + + // Buy signal: Current TF shows Buy AND the last HTF signal was also a Buy + if (hasCurrentBuy && (_lastHtfBuyTime > _lastHtfSellTime)) + { + // Place the signal at index - 1, using the value from Indicator A + BuySignal[index - 1] = currentBuySignal; + } + + // Sell signal: Current TF shows Sell AND the last HTF signal was also a Sell + if (hasCurrentSell && (_lastHtfSellTime > _lastHtfBuyTime)) + { + // Place the signal at index - 1, using the value from Indicator A + SellSignal[index - 1] = currentSellSignal; + } + } + + // Manual implementation of GetIndexByTime + // Finds the index of the bar that was open at the specified time + private int GetHtfIndex(DateTime time) + { + // Loop backwards for efficiency, assuming time is recent + for (int i = _htfBars.Count - 1; i >= 0; i--) + { + // If the bar's open time is at or before the requested time, this is the bar + if (_htfBars.OpenTimes[i] <= time) + { + return i; + } + } + // Time is before the first bar + return -1; + } + } +} \ No newline at end of file diff --git a/Sources/Indicators/MultiTimeframeSignalAggregator/MultiTimeframeSignalAggregator/MultiTimeframeSignalAggregator.csproj b/Sources/Indicators/MultiTimeframeSignalAggregator/MultiTimeframeSignalAggregator/MultiTimeframeSignalAggregator.csproj new file mode 100644 index 0000000..74027d2 --- /dev/null +++ b/Sources/Indicators/MultiTimeframeSignalAggregator/MultiTimeframeSignalAggregator/MultiTimeframeSignalAggregator.csproj @@ -0,0 +1,12 @@ + + + + net6.0 + + + + + + + + \ No newline at end of file diff --git a/Sources/Indicators/MultiTimeframeSignalAggregator/MultiTimeframeSignalAggregator/obj/Debug/net10.0/.NETCoreApp,Version=v10.0.AssemblyAttributes.cs b/Sources/Indicators/MultiTimeframeSignalAggregator/MultiTimeframeSignalAggregator/obj/Debug/net10.0/.NETCoreApp,Version=v10.0.AssemblyAttributes.cs new file mode 100644 index 0000000..9e27e66 --- /dev/null +++ b/Sources/Indicators/MultiTimeframeSignalAggregator/MultiTimeframeSignalAggregator/obj/Debug/net10.0/.NETCoreApp,Version=v10.0.AssemblyAttributes.cs @@ -0,0 +1,4 @@ +// +using System; +using System.Reflection; +[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v10.0", FrameworkDisplayName = "")] diff --git a/Sources/Indicators/MultiTimeframeSignalAggregator/MultiTimeframeSignalAggregator/obj/Debug/net10.0/MultiTimeframeSignalAggregator.AssemblyInfo.cs b/Sources/Indicators/MultiTimeframeSignalAggregator/MultiTimeframeSignalAggregator/obj/Debug/net10.0/MultiTimeframeSignalAggregator.AssemblyInfo.cs new file mode 100644 index 0000000..85f33e4 --- /dev/null +++ b/Sources/Indicators/MultiTimeframeSignalAggregator/MultiTimeframeSignalAggregator/obj/Debug/net10.0/MultiTimeframeSignalAggregator.AssemblyInfo.cs @@ -0,0 +1,22 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +using System; +using System.Reflection; + +[assembly: System.Reflection.AssemblyCompanyAttribute("MultiTimeframeSignalAggregator")] +[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] +[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0")] +[assembly: System.Reflection.AssemblyProductAttribute("MultiTimeframeSignalAggregator")] +[assembly: System.Reflection.AssemblyTitleAttribute("MultiTimeframeSignalAggregator")] +[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] + +// Von der MSBuild WriteCodeFragment-Klasse generiert. + diff --git a/Sources/Indicators/MultiTimeframeSignalAggregator/MultiTimeframeSignalAggregator/obj/Debug/net10.0/MultiTimeframeSignalAggregator.AssemblyInfoInputs.cache b/Sources/Indicators/MultiTimeframeSignalAggregator/MultiTimeframeSignalAggregator/obj/Debug/net10.0/MultiTimeframeSignalAggregator.AssemblyInfoInputs.cache new file mode 100644 index 0000000..8edc280 --- /dev/null +++ b/Sources/Indicators/MultiTimeframeSignalAggregator/MultiTimeframeSignalAggregator/obj/Debug/net10.0/MultiTimeframeSignalAggregator.AssemblyInfoInputs.cache @@ -0,0 +1 @@ +6f0108d5c6b0163cb6f7beb9819d3362ef1dbdcd diff --git a/Sources/Indicators/MultiTimeframeSignalAggregator/MultiTimeframeSignalAggregator/obj/Debug/net10.0/MultiTimeframeSignalAggregator.GeneratedMSBuildEditorConfig.editorconfig b/Sources/Indicators/MultiTimeframeSignalAggregator/MultiTimeframeSignalAggregator/obj/Debug/net10.0/MultiTimeframeSignalAggregator.GeneratedMSBuildEditorConfig.editorconfig new file mode 100644 index 0000000..e3c9e8a --- /dev/null +++ b/Sources/Indicators/MultiTimeframeSignalAggregator/MultiTimeframeSignalAggregator/obj/Debug/net10.0/MultiTimeframeSignalAggregator.GeneratedMSBuildEditorConfig.editorconfig @@ -0,0 +1,10 @@ +is_global = true +build_property.TargetFramework = net10.0 +build_property.TargetPlatformMinVersion = +build_property.UsingMicrosoftNETSdkWeb = +build_property.ProjectTypeGuids = +build_property.InvariantGlobalization = +build_property.PlatformNeutralAssembly = +build_property._SupportedPlatformList = Linux,macOS,Windows +build_property.RootNamespace = MultiTimeframeSignalAggregator +build_property.ProjectDir = c:\Users\Brummel\Documents\cAlgo\Sources\Indicators\MultiTimeframeSignalAggregator\MultiTimeframeSignalAggregator\ diff --git a/Sources/Indicators/MultiTimeframeSignalAggregator/MultiTimeframeSignalAggregator/obj/Debug/net10.0/MultiTimeframeSignalAggregator.GlobalUsings.g.cs b/Sources/Indicators/MultiTimeframeSignalAggregator/MultiTimeframeSignalAggregator/obj/Debug/net10.0/MultiTimeframeSignalAggregator.GlobalUsings.g.cs new file mode 100644 index 0000000..ac22929 --- /dev/null +++ b/Sources/Indicators/MultiTimeframeSignalAggregator/MultiTimeframeSignalAggregator/obj/Debug/net10.0/MultiTimeframeSignalAggregator.GlobalUsings.g.cs @@ -0,0 +1,8 @@ +// +global using global::System; +global using global::System.Collections.Generic; +global using global::System.IO; +global using global::System.Linq; +global using global::System.Net.Http; +global using global::System.Threading; +global using global::System.Threading.Tasks; diff --git a/Sources/Indicators/MultiTimeframeSignalAggregator/MultiTimeframeSignalAggregator/obj/Debug/net6.0/.NETCoreApp,Version=v6.0.AssemblyAttributes.cs b/Sources/Indicators/MultiTimeframeSignalAggregator/MultiTimeframeSignalAggregator/obj/Debug/net6.0/.NETCoreApp,Version=v6.0.AssemblyAttributes.cs new file mode 100644 index 0000000..32c95f9 --- /dev/null +++ b/Sources/Indicators/MultiTimeframeSignalAggregator/MultiTimeframeSignalAggregator/obj/Debug/net6.0/.NETCoreApp,Version=v6.0.AssemblyAttributes.cs @@ -0,0 +1,4 @@ +// +using System; +using System.Reflection; +[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v6.0", FrameworkDisplayName = "")] diff --git a/Sources/Indicators/MultiTimeframeSignalAggregator/MultiTimeframeSignalAggregator/obj/Debug/net6.0/MultiTimeframeSignalAggregator.AssemblyInfo.cs b/Sources/Indicators/MultiTimeframeSignalAggregator/MultiTimeframeSignalAggregator/obj/Debug/net6.0/MultiTimeframeSignalAggregator.AssemblyInfo.cs new file mode 100644 index 0000000..fd7866f --- /dev/null +++ b/Sources/Indicators/MultiTimeframeSignalAggregator/MultiTimeframeSignalAggregator/obj/Debug/net6.0/MultiTimeframeSignalAggregator.AssemblyInfo.cs @@ -0,0 +1,22 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +using System; +using System.Reflection; + +[assembly: System.Reflection.AssemblyCompanyAttribute("MultiTimeframeSignalAggregator")] +[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] +[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0")] +[assembly: System.Reflection.AssemblyProductAttribute("MultiTimeframeSignalAggregator")] +[assembly: System.Reflection.AssemblyTitleAttribute("MultiTimeframeSignalAggregator")] +[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] + +// Generated by the MSBuild WriteCodeFragment class. + diff --git a/Sources/Indicators/MultiTimeframeSignalAggregator/MultiTimeframeSignalAggregator/obj/Debug/net6.0/MultiTimeframeSignalAggregator.AssemblyInfoInputs.cache b/Sources/Indicators/MultiTimeframeSignalAggregator/MultiTimeframeSignalAggregator/obj/Debug/net6.0/MultiTimeframeSignalAggregator.AssemblyInfoInputs.cache new file mode 100644 index 0000000..8edc280 --- /dev/null +++ b/Sources/Indicators/MultiTimeframeSignalAggregator/MultiTimeframeSignalAggregator/obj/Debug/net6.0/MultiTimeframeSignalAggregator.AssemblyInfoInputs.cache @@ -0,0 +1 @@ +6f0108d5c6b0163cb6f7beb9819d3362ef1dbdcd diff --git a/Sources/Indicators/MultiTimeframeSignalAggregator/MultiTimeframeSignalAggregator/obj/Debug/net6.0/MultiTimeframeSignalAggregator.GeneratedMSBuildEditorConfig.editorconfig b/Sources/Indicators/MultiTimeframeSignalAggregator/MultiTimeframeSignalAggregator/obj/Debug/net6.0/MultiTimeframeSignalAggregator.GeneratedMSBuildEditorConfig.editorconfig new file mode 100644 index 0000000..61ea2e1 --- /dev/null +++ b/Sources/Indicators/MultiTimeframeSignalAggregator/MultiTimeframeSignalAggregator/obj/Debug/net6.0/MultiTimeframeSignalAggregator.GeneratedMSBuildEditorConfig.editorconfig @@ -0,0 +1,10 @@ +is_global = true +build_property.TargetFramework = net6.0 +build_property.TargetPlatformMinVersion = +build_property.UsingMicrosoftNETSdkWeb = +build_property.ProjectTypeGuids = +build_property.InvariantGlobalization = +build_property.PlatformNeutralAssembly = +build_property._SupportedPlatformList = Linux,macOS,Windows +build_property.RootNamespace = MultiTimeframeSignalAggregator +build_property.ProjectDir = C:\Users\Brummel\Documents\cAlgo\Sources\Indicators\MultiTimeframeSignalAggregator\MultiTimeframeSignalAggregator\ diff --git a/Sources/Indicators/MultiTimeframeSignalAggregator/MultiTimeframeSignalAggregator/obj/Debug/net6.0/MultiTimeframeSignalAggregator.assets.cache b/Sources/Indicators/MultiTimeframeSignalAggregator/MultiTimeframeSignalAggregator/obj/Debug/net6.0/MultiTimeframeSignalAggregator.assets.cache new file mode 100644 index 0000000..c6f45e3 Binary files /dev/null and b/Sources/Indicators/MultiTimeframeSignalAggregator/MultiTimeframeSignalAggregator/obj/Debug/net6.0/MultiTimeframeSignalAggregator.assets.cache differ diff --git a/Sources/Indicators/MultiTimeframeSignalAggregator/MultiTimeframeSignalAggregator/obj/Debug/net6.0/MultiTimeframeSignalAggregator.csproj.AssemblyReference.cache b/Sources/Indicators/MultiTimeframeSignalAggregator/MultiTimeframeSignalAggregator/obj/Debug/net6.0/MultiTimeframeSignalAggregator.csproj.AssemblyReference.cache new file mode 100644 index 0000000..50043b7 Binary files /dev/null and b/Sources/Indicators/MultiTimeframeSignalAggregator/MultiTimeframeSignalAggregator/obj/Debug/net6.0/MultiTimeframeSignalAggregator.csproj.AssemblyReference.cache differ diff --git a/Sources/Indicators/MultiTimeframeSignalAggregator/MultiTimeframeSignalAggregator/obj/MultiTimeframeSignalAggregator.csproj.nuget.dgspec.json b/Sources/Indicators/MultiTimeframeSignalAggregator/MultiTimeframeSignalAggregator/obj/MultiTimeframeSignalAggregator.csproj.nuget.dgspec.json new file mode 100644 index 0000000..a9d9750 --- /dev/null +++ b/Sources/Indicators/MultiTimeframeSignalAggregator/MultiTimeframeSignalAggregator/obj/MultiTimeframeSignalAggregator.csproj.nuget.dgspec.json @@ -0,0 +1,128 @@ +{ + "format": 1, + "restore": { + "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Indicators\\MultiTimeframeSignalAggregator\\MultiTimeframeSignalAggregator\\MultiTimeframeSignalAggregator.csproj": {} + }, + "projects": { + "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Indicators\\HmaSmaCrossoverSignal\\HmaSmaCrossoverSignal\\HmaSmaCrossoverSignal.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Indicators\\HmaSmaCrossoverSignal\\HmaSmaCrossoverSignal\\HmaSmaCrossoverSignal.csproj", + "projectName": "HmaSmaCrossoverSignal", + "projectPath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Indicators\\HmaSmaCrossoverSignal\\HmaSmaCrossoverSignal\\HmaSmaCrossoverSignal.csproj", + "packagesPath": "C:\\Users\\Brummel\\.nuget\\packages\\", + "outputPath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Indicators\\HmaSmaCrossoverSignal\\HmaSmaCrossoverSignal\\obj\\", + "projectStyle": "PackageReference", + "configFilePaths": [ + "C:\\Users\\Brummel\\AppData\\Roaming\\NuGet\\NuGet.Config" + ], + "originalTargetFrameworks": [ + "net6.0" + ], + "sources": { + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net6.0": { + "targetAlias": "net6.0", + "projectReferences": {} + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + } + }, + "frameworks": { + "net6.0": { + "targetAlias": "net6.0", + "dependencies": { + "cTrader.Automate": { + "target": "Package", + "version": "[*, )" + } + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\6.0.200\\RuntimeIdentifierGraph.json" + } + } + }, + "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Indicators\\MultiTimeframeSignalAggregator\\MultiTimeframeSignalAggregator\\MultiTimeframeSignalAggregator.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Indicators\\MultiTimeframeSignalAggregator\\MultiTimeframeSignalAggregator\\MultiTimeframeSignalAggregator.csproj", + "projectName": "MultiTimeframeSignalAggregator", + "projectPath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Indicators\\MultiTimeframeSignalAggregator\\MultiTimeframeSignalAggregator\\MultiTimeframeSignalAggregator.csproj", + "packagesPath": "C:\\Users\\Brummel\\.nuget\\packages\\", + "outputPath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Indicators\\MultiTimeframeSignalAggregator\\MultiTimeframeSignalAggregator\\obj\\", + "projectStyle": "PackageReference", + "configFilePaths": [ + "C:\\Users\\Brummel\\AppData\\Roaming\\NuGet\\NuGet.Config" + ], + "originalTargetFrameworks": [ + "net6.0" + ], + "sources": { + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net6.0": { + "targetAlias": "net6.0", + "projectReferences": { + "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Indicators\\HmaSmaCrossoverSignal\\HmaSmaCrossoverSignal\\HmaSmaCrossoverSignal.csproj": { + "projectPath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Indicators\\HmaSmaCrossoverSignal\\HmaSmaCrossoverSignal\\HmaSmaCrossoverSignal.csproj" + } + } + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + } + }, + "frameworks": { + "net6.0": { + "targetAlias": "net6.0", + "dependencies": { + "cTrader.Automate": { + "target": "Package", + "version": "[*, )" + } + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\6.0.200\\RuntimeIdentifierGraph.json" + } + } + } + } +} \ No newline at end of file diff --git a/Sources/Indicators/MultiTimeframeSignalAggregator/MultiTimeframeSignalAggregator/obj/MultiTimeframeSignalAggregator.csproj.nuget.g.props b/Sources/Indicators/MultiTimeframeSignalAggregator/MultiTimeframeSignalAggregator/obj/MultiTimeframeSignalAggregator.csproj.nuget.g.props new file mode 100644 index 0000000..0b161ac --- /dev/null +++ b/Sources/Indicators/MultiTimeframeSignalAggregator/MultiTimeframeSignalAggregator/obj/MultiTimeframeSignalAggregator.csproj.nuget.g.props @@ -0,0 +1,21 @@ + + + + True + NuGet + $(MSBuildThisFileDirectory)project.assets.json + $(UserProfile)\.nuget\packages\ + C:\Users\Brummel\.nuget\packages\ + PackageReference + 6.1.0 + + + + + + + + + C:\Users\Brummel\.nuget\packages\ctrader.automate\1.0.14 + + \ No newline at end of file diff --git a/Sources/Indicators/MultiTimeframeSignalAggregator/MultiTimeframeSignalAggregator/obj/MultiTimeframeSignalAggregator.csproj.nuget.g.targets b/Sources/Indicators/MultiTimeframeSignalAggregator/MultiTimeframeSignalAggregator/obj/MultiTimeframeSignalAggregator.csproj.nuget.g.targets new file mode 100644 index 0000000..46f142c --- /dev/null +++ b/Sources/Indicators/MultiTimeframeSignalAggregator/MultiTimeframeSignalAggregator/obj/MultiTimeframeSignalAggregator.csproj.nuget.g.targets @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Sources/Indicators/MultiTimeframeSignalAggregator/MultiTimeframeSignalAggregator/obj/project.assets.json b/Sources/Indicators/MultiTimeframeSignalAggregator/MultiTimeframeSignalAggregator/obj/project.assets.json new file mode 100644 index 0000000..bc15bae --- /dev/null +++ b/Sources/Indicators/MultiTimeframeSignalAggregator/MultiTimeframeSignalAggregator/obj/project.assets.json @@ -0,0 +1,162 @@ +{ + "version": 3, + "targets": { + "net6.0": { + "cTrader.Automate/1.0.14": { + "type": "package", + "compile": { + "lib/net6.0/cAlgo.API.dll": {} + }, + "runtime": { + "lib/net6.0/cAlgo.API.dll": {} + }, + "build": { + "build/cTrader.Automate.props": {}, + "build/cTrader.Automate.targets": {} + } + }, + "HmaSmaCrossoverSignal/1.0.0": { + "type": "project", + "framework": ".NETCoreApp,Version=v6.0", + "dependencies": { + "cTrader.Automate": "0.0.0" + }, + "compile": { + "bin/placeholder/HmaSmaCrossoverSignal.dll": {} + }, + "runtime": { + "bin/placeholder/HmaSmaCrossoverSignal.dll": {} + } + } + } + }, + "libraries": { + "cTrader.Automate/1.0.14": { + "sha512": "eNwE7WL90MGBKb5MuLAtZLdQy0vxkI5EVhLWAQ9S83EAdYkGAzdccvGFLk2oqmtSGBtD+gEpyrJUW/ej4dI4jw==", + "type": "package", + "path": "ctrader.automate/1.0.14", + "hasTools": true, + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "build/cTrader.Automate.props", + "build/cTrader.Automate.targets", + "ctrader.automate.1.0.14.nupkg.sha512", + "ctrader.automate.nuspec", + "eula.md", + "icon.png", + "lib/net40/cAlgo.API.dll", + "lib/net40/cAlgo.API.xml", + "lib/net6.0/cAlgo.API.dll", + "lib/net6.0/cAlgo.API.xml", + "tools/net472/Core.AlgoFormat.Compose.Reflection.dll", + "tools/net472/Core.AlgoFormat.Writer.dll", + "tools/net472/Core.AlgoFormat.dll", + "tools/net472/Core.Domain.Primitives.dll", + "tools/net472/Newtonsoft.Json.dll", + "tools/net472/System.Buffers.dll", + "tools/net472/System.Collections.Immutable.dll", + "tools/net472/System.Memory.dll", + "tools/net472/System.Numerics.Vectors.dll", + "tools/net472/System.Reflection.Metadata.dll", + "tools/net472/System.Reflection.MetadataLoadContext.dll", + "tools/net472/System.Runtime.CompilerServices.Unsafe.dll", + "tools/net472/cTrader.Automate.Sdk.Tasks.dll", + "tools/net6.0/Core.AlgoFormat.Compose.Reflection.dll", + "tools/net6.0/Core.AlgoFormat.Writer.dll", + "tools/net6.0/Core.AlgoFormat.dll", + "tools/net6.0/Core.Connection.Protobuf.Common.dll", + "tools/net6.0/Core.Domain.Primitives.dll", + "tools/net6.0/Microsoft.Win32.SystemEvents.dll", + "tools/net6.0/Newtonsoft.Json.dll", + "tools/net6.0/System.Drawing.Common.dll", + "tools/net6.0/System.Reflection.MetadataLoadContext.dll", + "tools/net6.0/System.Security.Permissions.dll", + "tools/net6.0/System.Windows.Extensions.dll", + "tools/net6.0/cTrader.Automate.Sdk.Tasks.dll", + "tools/net6.0/protobuf-net.Core.dll", + "tools/net6.0/protobuf-net.dll", + "tools/net6.0/runtimes/unix/lib/net6.0/System.Drawing.Common.dll", + "tools/net6.0/runtimes/win/lib/net6.0/Microsoft.Win32.SystemEvents.dll", + "tools/net6.0/runtimes/win/lib/net6.0/System.Drawing.Common.dll", + "tools/net6.0/runtimes/win/lib/net6.0/System.Windows.Extensions.dll" + ] + }, + "HmaSmaCrossoverSignal/1.0.0": { + "type": "project", + "path": "../../HmaSmaCrossoverSignal/HmaSmaCrossoverSignal/HmaSmaCrossoverSignal.csproj", + "msbuildProject": "../../HmaSmaCrossoverSignal/HmaSmaCrossoverSignal/HmaSmaCrossoverSignal.csproj" + } + }, + "projectFileDependencyGroups": { + "net6.0": [ + "HmaSmaCrossoverSignal >= 1.0.0", + "cTrader.Automate >= *" + ] + }, + "packageFolders": { + "C:\\Users\\Brummel\\.nuget\\packages\\": {} + }, + "project": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Indicators\\MultiTimeframeSignalAggregator\\MultiTimeframeSignalAggregator\\MultiTimeframeSignalAggregator.csproj", + "projectName": "MultiTimeframeSignalAggregator", + "projectPath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Indicators\\MultiTimeframeSignalAggregator\\MultiTimeframeSignalAggregator\\MultiTimeframeSignalAggregator.csproj", + "packagesPath": "C:\\Users\\Brummel\\.nuget\\packages\\", + "outputPath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Indicators\\MultiTimeframeSignalAggregator\\MultiTimeframeSignalAggregator\\obj\\", + "projectStyle": "PackageReference", + "configFilePaths": [ + "C:\\Users\\Brummel\\AppData\\Roaming\\NuGet\\NuGet.Config" + ], + "originalTargetFrameworks": [ + "net6.0" + ], + "sources": { + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net6.0": { + "targetAlias": "net6.0", + "projectReferences": { + "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Indicators\\HmaSmaCrossoverSignal\\HmaSmaCrossoverSignal\\HmaSmaCrossoverSignal.csproj": { + "projectPath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Indicators\\HmaSmaCrossoverSignal\\HmaSmaCrossoverSignal\\HmaSmaCrossoverSignal.csproj" + } + } + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + } + }, + "frameworks": { + "net6.0": { + "targetAlias": "net6.0", + "dependencies": { + "cTrader.Automate": { + "target": "Package", + "version": "[*, )" + } + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\6.0.200\\RuntimeIdentifierGraph.json" + } + } + } +} \ No newline at end of file diff --git a/Sources/Indicators/MultiTimeframeSignalAggregator/MultiTimeframeSignalAggregator/obj/project.nuget.cache b/Sources/Indicators/MultiTimeframeSignalAggregator/MultiTimeframeSignalAggregator/obj/project.nuget.cache new file mode 100644 index 0000000..14547d9 --- /dev/null +++ b/Sources/Indicators/MultiTimeframeSignalAggregator/MultiTimeframeSignalAggregator/obj/project.nuget.cache @@ -0,0 +1,10 @@ +{ + "version": 2, + "dgSpecHash": "l9ej8bY5Nt/RoGXI8NmU4NSEfOvDahP0rhvkoxUdx179x2+rf5i3gpg/Ke460Mq9eCvmMBPmNfVR12kmifXGaA==", + "success": true, + "projectFilePath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Indicators\\MultiTimeframeSignalAggregator\\MultiTimeframeSignalAggregator\\MultiTimeframeSignalAggregator.csproj", + "expectedPackageFiles": [ + "C:\\Users\\Brummel\\.nuget\\packages\\ctrader.automate\\1.0.14\\ctrader.automate.1.0.14.nupkg.sha512" + ], + "logs": [] +} \ No newline at end of file diff --git a/Sources/Indicators/Parabolic Stop.algo b/Sources/Indicators/Parabolic Stop.algo new file mode 100644 index 0000000..3f2709c Binary files /dev/null and b/Sources/Indicators/Parabolic Stop.algo differ diff --git a/Sources/Indicators/Parabolic Stop/Parabolic Stop.sln b/Sources/Indicators/Parabolic Stop/Parabolic Stop.sln new file mode 100644 index 0000000..76b2901 --- /dev/null +++ b/Sources/Indicators/Parabolic Stop/Parabolic Stop.sln @@ -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}") = "Parabolic Stop", "Parabolic Stop\Parabolic Stop.csproj", "{fbbf853c-ecf6-4c65-b8cf-c9b8291d74f7}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {fbbf853c-ecf6-4c65-b8cf-c9b8291d74f7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {fbbf853c-ecf6-4c65-b8cf-c9b8291d74f7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {fbbf853c-ecf6-4c65-b8cf-c9b8291d74f7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {fbbf853c-ecf6-4c65-b8cf-c9b8291d74f7}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/Sources/Indicators/Parabolic Stop/Parabolic Stop/Parabolic Stop.cs b/Sources/Indicators/Parabolic Stop/Parabolic Stop/Parabolic Stop.cs new file mode 100644 index 0000000..fbe35a5 --- /dev/null +++ b/Sources/Indicators/Parabolic Stop/Parabolic Stop/Parabolic Stop.cs @@ -0,0 +1,74 @@ +using cAlgo.API; +using cAlgo.API.Indicators; + +namespace cAlgo +{ + + [Indicator(IsOverlay = true, TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)] + public class ParabolicStop : Indicator + { + + [Parameter("Start", DefaultValue = 0.02)] + public double Start { get; set; } + + [Parameter("Maximum", DefaultValue = 0.2)] + public double Maximum { get; set; } + + [Parameter("Length", DefaultValue = 20)] + public int Length { get; set; } + + [Parameter("Multiplier", DefaultValue = 4)] + public int Multiplier { get; set; } + + private ParabolicSAR _sar; + private SimpleMovingAverage _smaClose; + private AverageTrueRange _atr; + + private double _max; + private double _min; + private bool _isUptrend; + private double _vstop; + bool isUptrendPrev; + double vstopPrev; + + // Erstelle die IndicatorDataSeries mit Output-Attribut und LineColor + [Output("Stop Line", LineColor = "Aqua", PlotType = PlotType.Line)] + public IndicatorDataSeries _stopLine { get; set; } + + [Output("SMA Line", LineColor = "White", PlotType = PlotType.Line)] + public IndicatorDataSeries _smaLine { get; set; } + + protected override void Initialize() + { + _sar = Indicators.ParabolicSAR(Bars, Start, Maximum); + _smaClose = Indicators.SimpleMovingAverage(Bars.ClosePrices, 3 * Length); + _atr = Indicators.AverageTrueRange(Bars, Length, MovingAverageType.Exponential); + + } + + public override void Calculate(int index) + { + double smaClose = _smaClose.Result[index]; + + double max1 = System.Math.Max(_max, Bars.ClosePrices[index]); + double min1 = System.Math.Min(_min, Bars.ClosePrices[index]); + + double stop = isUptrendPrev ? max1 - Multiplier * _atr.Result[index] : min1 + Multiplier * _atr.Result[index]; + + double vstop1 = isUptrendPrev ? System.Math.Min(System.Math.Max(vstopPrev, stop), _sar.Result[index]) : System.Math.Max(System.Math.Min(vstopPrev, stop), _sar.Result[index]); + + isUptrendPrev = _isUptrend; + _isUptrend = Bars.ClosePrices[index] - vstop1 >= 0; + bool isTrendChanged = _isUptrend != isUptrendPrev; + + _max = isTrendChanged ? Bars.ClosePrices[index] : max1; + _min = isTrendChanged ? Bars.ClosePrices[index] : min1; + + vstopPrev = _vstop; + _vstop = isTrendChanged ? (_isUptrend ? System.Math.Max(_max - Multiplier * _atr.Result[index], _sar.Result[index]) : System.Math.Min(_min + Multiplier * _atr.Result[index], _sar.Result[index])) : vstop1; + + _stopLine[index] = _vstop; + _smaLine[index] = smaClose; + } + } +} \ No newline at end of file diff --git a/Sources/Indicators/Parabolic Stop/Parabolic Stop/Parabolic Stop.csproj b/Sources/Indicators/Parabolic Stop/Parabolic Stop/Parabolic Stop.csproj new file mode 100644 index 0000000..da1fef1 --- /dev/null +++ b/Sources/Indicators/Parabolic Stop/Parabolic Stop/Parabolic Stop.csproj @@ -0,0 +1,9 @@ + + + net6.0 + + + + + + diff --git a/Sources/Indicators/Sample Alligator.algo b/Sources/Indicators/Sample Alligator.algo new file mode 100644 index 0000000..c01e571 Binary files /dev/null and b/Sources/Indicators/Sample Alligator.algo differ diff --git a/Sources/Indicators/Sample Alligator/Sample Alligator.sln b/Sources/Indicators/Sample Alligator/Sample Alligator.sln new file mode 100644 index 0000000..b8e84c1 --- /dev/null +++ b/Sources/Indicators/Sample Alligator/Sample Alligator.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.31729.503 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample Alligator", "Sample Alligator\Sample Alligator.csproj", "{D11E711B-6CE5-42A2-BD07-F25907198272}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {D11E711B-6CE5-42A2-BD07-F25907198272}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D11E711B-6CE5-42A2-BD07-F25907198272}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D11E711B-6CE5-42A2-BD07-F25907198272}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D11E711B-6CE5-42A2-BD07-F25907198272}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {F5EF3993-F32A-48D0-A3D9-C1F8B25B02A8} + EndGlobalSection +EndGlobal diff --git a/Sources/Indicators/Sample Alligator/Sample Alligator/Sample Alligator.cs b/Sources/Indicators/Sample Alligator/Sample Alligator/Sample Alligator.cs new file mode 100644 index 0000000..6dece75 --- /dev/null +++ b/Sources/Indicators/Sample Alligator/Sample Alligator/Sample Alligator.cs @@ -0,0 +1,63 @@ +// ------------------------------------------------------------------------------------------------- +// +// This code is a cTrader Automate API example. +// +// All changes to this file might be lost on the next application update. +// If you are going to modify this file please make a copy using the "Duplicate" command. +// +// ------------------------------------------------------------------------------------------------- + +using cAlgo.API; +using cAlgo.API.Indicators; + +namespace cAlgo +{ + [Indicator(IsOverlay = true, TimeZone = TimeZones.UTC, AutoRescale = false, AccessRights = AccessRights.None)] + public class SampleAlligator : Indicator + { + [Parameter("Periods", Group = "Jaws", DefaultValue = 13)] + public int JawsPeriods { get; set; } + + [Parameter("Shift", Group = "Jaws", DefaultValue = 8)] + public int JawsShift { get; set; } + + [Parameter("Periods", Group = "Teeth", DefaultValue = 8)] + public int TeethPeriods { get; set; } + + [Parameter("Shift", Group = "Teeth", DefaultValue = 5)] + public int TeethShift { get; set; } + + [Parameter("Periods", Group = "Lips", DefaultValue = 5)] + public int LipsPeriods { get; set; } + + [Parameter("Shift", Group = "Lips", DefaultValue = 3)] + public int LipsShift { get; set; } + + [Output("Jaws", LineColor = "Blue")] + public IndicatorDataSeries Jaws { get; set; } + + [Output("Teeth", LineColor = "Red")] + public IndicatorDataSeries Teeth { get; set; } + + [Output("Lips", LineColor = "Lime")] + public IndicatorDataSeries Lips { get; set; } + + private WellesWilderSmoothing jawsMa; + private WellesWilderSmoothing teethMa; + private WellesWilderSmoothing lipsMa; + + protected override void Initialize() + { + jawsMa = Indicators.WellesWilderSmoothing(Bars.MedianPrices, JawsPeriods); + teethMa = Indicators.WellesWilderSmoothing(Bars.MedianPrices, TeethPeriods); + lipsMa = Indicators.WellesWilderSmoothing(Bars.MedianPrices, LipsPeriods); + } + + public override void Calculate(int index) + { + Jaws[index + JawsShift] = jawsMa.Result[index]; + Teeth[index + TeethShift] = teethMa.Result[index]; + Lips[index + LipsShift] = lipsMa.Result[index]; + } + } +} \ No newline at end of file diff --git a/Sources/Indicators/Sample Alligator/Sample Alligator/Sample Alligator.csproj b/Sources/Indicators/Sample Alligator/Sample Alligator/Sample Alligator.csproj new file mode 100644 index 0000000..6d836c9 --- /dev/null +++ b/Sources/Indicators/Sample Alligator/Sample Alligator/Sample Alligator.csproj @@ -0,0 +1,9 @@ + + + net6.0 + + + + + + \ No newline at end of file diff --git a/Sources/Indicators/Sample Bears Power.algo b/Sources/Indicators/Sample Bears Power.algo new file mode 100644 index 0000000..7421891 Binary files /dev/null and b/Sources/Indicators/Sample Bears Power.algo differ diff --git a/Sources/Indicators/Sample Bears Power/Sample Bears Power.sln b/Sources/Indicators/Sample Bears Power/Sample Bears Power.sln new file mode 100644 index 0000000..eae0b40 --- /dev/null +++ b/Sources/Indicators/Sample Bears Power/Sample Bears Power.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.31729.503 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample Bears Power", "Sample Bears Power\Sample Bears Power.csproj", "{A47D013C-CD13-4116-B515-982BB6DB8193}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {A47D013C-CD13-4116-B515-982BB6DB8193}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A47D013C-CD13-4116-B515-982BB6DB8193}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A47D013C-CD13-4116-B515-982BB6DB8193}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A47D013C-CD13-4116-B515-982BB6DB8193}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {A737E7DA-C31D-464C-A4C9-2892B58E2BD2} + EndGlobalSection +EndGlobal diff --git a/Sources/Indicators/Sample Bears Power/Sample Bears Power/Sample Bears Power.cs b/Sources/Indicators/Sample Bears Power/Sample Bears Power/Sample Bears Power.cs new file mode 100644 index 0000000..dfad65d --- /dev/null +++ b/Sources/Indicators/Sample Bears Power/Sample Bears Power/Sample Bears Power.cs @@ -0,0 +1,42 @@ +// ------------------------------------------------------------------------------------------------- +// +// This code is a cTrader Automate API example. +// +// All changes to this file might be lost on the next application update. +// If you are going to modify this file please make a copy using the "Duplicate" command. +// +// ------------------------------------------------------------------------------------------------- + +using cAlgo.API; +using cAlgo.API.Indicators; + +namespace cAlgo +{ + [Indicator(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)] + public class SampleBearsPower : Indicator + { + [Parameter("Source")] + public DataSeries Source { get; set; } + + [Parameter(DefaultValue = 13, MinValue = 2)] + public int Periods { get; set; } + + [Parameter("MA Type", DefaultValue = MovingAverageType.Exponential)] + public MovingAverageType MAType { get; set; } + + [Output("Result", LineColor = "Orange", PlotType = PlotType.Histogram)] + public IndicatorDataSeries Result { get; set; } + + private MovingAverage movingAverage; + + protected override void Initialize() + { + movingAverage = Indicators.MovingAverage(Source, Periods, MAType); + } + + public override void Calculate(int index) + { + Result[index] = Bars.LowPrices[index] - movingAverage.Result[index]; + } + } +} diff --git a/Sources/Indicators/Sample Bears Power/Sample Bears Power/Sample Bears Power.csproj b/Sources/Indicators/Sample Bears Power/Sample Bears Power/Sample Bears Power.csproj new file mode 100644 index 0000000..6d836c9 --- /dev/null +++ b/Sources/Indicators/Sample Bears Power/Sample Bears Power/Sample Bears Power.csproj @@ -0,0 +1,9 @@ + + + net6.0 + + + + + + \ No newline at end of file diff --git a/Sources/Indicators/Sample Bulls Power.algo b/Sources/Indicators/Sample Bulls Power.algo new file mode 100644 index 0000000..ea487dc Binary files /dev/null and b/Sources/Indicators/Sample Bulls Power.algo differ diff --git a/Sources/Indicators/Sample Bulls Power/Sample Bulls Power.sln b/Sources/Indicators/Sample Bulls Power/Sample Bulls Power.sln new file mode 100644 index 0000000..61afa95 --- /dev/null +++ b/Sources/Indicators/Sample Bulls Power/Sample Bulls Power.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.31729.503 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample Bulls Power", "Sample Bulls Power\Sample Bulls Power.csproj", "{DEFFA144-B39B-4DC8-89F6-79B28D235012}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {DEFFA144-B39B-4DC8-89F6-79B28D235012}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DEFFA144-B39B-4DC8-89F6-79B28D235012}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DEFFA144-B39B-4DC8-89F6-79B28D235012}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DEFFA144-B39B-4DC8-89F6-79B28D235012}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {8BE518C6-1636-48D4-BA7A-CAE50E5D6353} + EndGlobalSection +EndGlobal diff --git a/Sources/Indicators/Sample Bulls Power/Sample Bulls Power/Sample Bulls Power.cs b/Sources/Indicators/Sample Bulls Power/Sample Bulls Power/Sample Bulls Power.cs new file mode 100644 index 0000000..a3f006a --- /dev/null +++ b/Sources/Indicators/Sample Bulls Power/Sample Bulls Power/Sample Bulls Power.cs @@ -0,0 +1,42 @@ +// ------------------------------------------------------------------------------------------------- +// +// This code is a cTrader Automate API example. +// +// All changes to this file might be lost on the next application update. +// If you are going to modify this file please make a copy using the "Duplicate" command. +// +// ------------------------------------------------------------------------------------------------- + +using cAlgo.API; +using cAlgo.API.Indicators; + +namespace cAlgo +{ + [Indicator(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)] + public class SampleBullsPower : Indicator + { + [Parameter("Source")] + public DataSeries Source { get; set; } + + [Parameter(DefaultValue = 13, MinValue = 2)] + public int Periods { get; set; } + + [Parameter("MA Type", DefaultValue = MovingAverageType.Exponential)] + public MovingAverageType MAType { get; set; } + + [Output("Result", LineColor = "Orange", PlotType = PlotType.Histogram)] + public IndicatorDataSeries Result { get; set; } + + private MovingAverage movingAverage; + + protected override void Initialize() + { + movingAverage = Indicators.MovingAverage(Source, Periods, MAType); + } + + public override void Calculate(int index) + { + Result[index] = Bars.HighPrices[index] - movingAverage.Result[index]; + } + } +} diff --git a/Sources/Indicators/Sample Bulls Power/Sample Bulls Power/Sample Bulls Power.csproj b/Sources/Indicators/Sample Bulls Power/Sample Bulls Power/Sample Bulls Power.csproj new file mode 100644 index 0000000..6d836c9 --- /dev/null +++ b/Sources/Indicators/Sample Bulls Power/Sample Bulls Power/Sample Bulls Power.csproj @@ -0,0 +1,9 @@ + + + net6.0 + + + + + + \ No newline at end of file diff --git a/Sources/Indicators/Sample DeMarker.algo b/Sources/Indicators/Sample DeMarker.algo new file mode 100644 index 0000000..b8c3b8a Binary files /dev/null and b/Sources/Indicators/Sample DeMarker.algo differ diff --git a/Sources/Indicators/Sample DeMarker/Sample DeMarker.sln b/Sources/Indicators/Sample DeMarker/Sample DeMarker.sln new file mode 100644 index 0000000..c9bced6 --- /dev/null +++ b/Sources/Indicators/Sample DeMarker/Sample DeMarker.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.31729.503 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample DeMarker", "Sample DeMarker\Sample DeMarker.csproj", "{5D4E7D9A-43FC-4F05-901D-546347D93E4E}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {5D4E7D9A-43FC-4F05-901D-546347D93E4E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5D4E7D9A-43FC-4F05-901D-546347D93E4E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5D4E7D9A-43FC-4F05-901D-546347D93E4E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5D4E7D9A-43FC-4F05-901D-546347D93E4E}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {36F1F62C-8815-4939-B2A2-1FAF0FA9B2FF} + EndGlobalSection +EndGlobal diff --git a/Sources/Indicators/Sample DeMarker/Sample DeMarker/Sample DeMarker.cs b/Sources/Indicators/Sample DeMarker/Sample DeMarker/Sample DeMarker.cs new file mode 100644 index 0000000..c36a315 --- /dev/null +++ b/Sources/Indicators/Sample DeMarker/Sample DeMarker/Sample DeMarker.cs @@ -0,0 +1,49 @@ +// ------------------------------------------------------------------------------------------------- +// +// This code is a cTrader Automate API example. +// +// All changes to this file might be lost on the next application update. +// If you are going to modify this file please make a copy using the "Duplicate" command. +// +// ------------------------------------------------------------------------------------------------- + +using System; +using cAlgo.API; +using cAlgo.API.Indicators; + +namespace cAlgo +{ + [Indicator(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)] + public class SampleDeMarker : Indicator + { + [Parameter(DefaultValue = 14)] + public int Periods { get; set; } + + [Output("DMark", LineColor = "Turquoise")] + public IndicatorDataSeries DMark { get; set; } + + private IndicatorDataSeries deMin; + private IndicatorDataSeries deMax; + private MovingAverage deMinMA; + private MovingAverage deMaxMA; + + protected override void Initialize() + { + deMin = CreateDataSeries(); + deMax = CreateDataSeries(); + deMinMA = Indicators.MovingAverage(deMin, Periods, MovingAverageType.Simple); + deMaxMA = Indicators.MovingAverage(deMax, Periods, MovingAverageType.Simple); + } + + public override void Calculate(int index) + { + deMin[index] = Math.Max(Bars.LowPrices[index - 1] - Bars.LowPrices[index], 0); + deMax[index] = Math.Max(Bars.HighPrices[index] - Bars.HighPrices[index - 1], 0); + + var min = deMinMA.Result[index]; + var max = deMaxMA.Result[index]; + + DMark[index] = max / (min + max); + } + } +} diff --git a/Sources/Indicators/Sample DeMarker/Sample DeMarker/Sample DeMarker.csproj b/Sources/Indicators/Sample DeMarker/Sample DeMarker/Sample DeMarker.csproj new file mode 100644 index 0000000..6d836c9 --- /dev/null +++ b/Sources/Indicators/Sample DeMarker/Sample DeMarker/Sample DeMarker.csproj @@ -0,0 +1,9 @@ + + + net6.0 + + + + + + \ No newline at end of file diff --git a/Sources/Indicators/Sample EMA.algo b/Sources/Indicators/Sample EMA.algo new file mode 100644 index 0000000..eb998ca Binary files /dev/null and b/Sources/Indicators/Sample EMA.algo differ diff --git a/Sources/Indicators/Sample EMA/Sample EMA.sln b/Sources/Indicators/Sample EMA/Sample EMA.sln new file mode 100644 index 0000000..bd79b9f --- /dev/null +++ b/Sources/Indicators/Sample EMA/Sample EMA.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.31729.503 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample EMA", "Sample EMA\Sample EMA.csproj", "{F76AABC5-E32A-4B1F-9FBF-EAA686B5F769}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {F76AABC5-E32A-4B1F-9FBF-EAA686B5F769}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F76AABC5-E32A-4B1F-9FBF-EAA686B5F769}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F76AABC5-E32A-4B1F-9FBF-EAA686B5F769}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F76AABC5-E32A-4B1F-9FBF-EAA686B5F769}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {244A7EFF-8CB3-4A89-A5CB-E9224A234C17} + EndGlobalSection +EndGlobal diff --git a/Sources/Indicators/Sample EMA/Sample EMA/Sample EMA.cs b/Sources/Indicators/Sample EMA/Sample EMA/Sample EMA.cs new file mode 100644 index 0000000..f51e502 --- /dev/null +++ b/Sources/Indicators/Sample EMA/Sample EMA/Sample EMA.cs @@ -0,0 +1,43 @@ +// ------------------------------------------------------------------------------------------------- +// +// This code is a cTrader Automate API example. +// +// All changes to this file might be lost on the next application update. +// If you are going to modify this file please make a copy using the "Duplicate" command. +// +// ------------------------------------------------------------------------------------------------- + +using cAlgo.API; + +namespace cAlgo +{ + [Indicator(IsOverlay = true, TimeZone = TimeZones.UTC, AutoRescale = false, AccessRights = AccessRights.None)] + public class SampleEMA : Indicator + { + [Parameter("Source")] + public DataSeries Source { get; set; } + + [Parameter("Periods", DefaultValue = 14)] + public int Periods { get; set; } + + [Output("Main", LineColor = "Turquoise")] + public IndicatorDataSeries Result { get; set; } + + private double exp; + + protected override void Initialize() + { + exp = 2.0 / (Periods + 1); + } + + public override void Calculate(int index) + { + var previousValue = Result[index - 1]; + + if (double.IsNaN(previousValue)) + Result[index] = Source[index]; + else + Result[index] = Source[index] * exp + previousValue * (1 - exp); + } + } +} diff --git a/Sources/Indicators/Sample EMA/Sample EMA/Sample EMA.csproj b/Sources/Indicators/Sample EMA/Sample EMA/Sample EMA.csproj new file mode 100644 index 0000000..6d836c9 --- /dev/null +++ b/Sources/Indicators/Sample EMA/Sample EMA/Sample EMA.csproj @@ -0,0 +1,9 @@ + + + net6.0 + + + + + + \ No newline at end of file diff --git a/Sources/Indicators/Sample Envelopes Cloud.algo b/Sources/Indicators/Sample Envelopes Cloud.algo new file mode 100644 index 0000000..7236bdd Binary files /dev/null and b/Sources/Indicators/Sample Envelopes Cloud.algo differ diff --git a/Sources/Indicators/Sample Envelopes Cloud/Sample Envelopes Cloud.sln b/Sources/Indicators/Sample Envelopes Cloud/Sample Envelopes Cloud.sln new file mode 100644 index 0000000..7110814 --- /dev/null +++ b/Sources/Indicators/Sample Envelopes Cloud/Sample Envelopes Cloud.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.31729.503 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample Envelopes Cloud", "Sample Envelopes Cloud\Sample Envelopes Cloud.csproj", "{C0EC0427-9DF4-4210-8082-017AA80C774A}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {C0EC0427-9DF4-4210-8082-017AA80C774A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C0EC0427-9DF4-4210-8082-017AA80C774A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C0EC0427-9DF4-4210-8082-017AA80C774A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C0EC0427-9DF4-4210-8082-017AA80C774A}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {B08A4524-33BD-4715-B7D2-5A9DCB221E43} + EndGlobalSection +EndGlobal diff --git a/Sources/Indicators/Sample Envelopes Cloud/Sample Envelopes Cloud/Sample Envelopes Cloud.cs b/Sources/Indicators/Sample Envelopes Cloud/Sample Envelopes Cloud/Sample Envelopes Cloud.cs new file mode 100644 index 0000000..b4c9b70 --- /dev/null +++ b/Sources/Indicators/Sample Envelopes Cloud/Sample Envelopes Cloud/Sample Envelopes Cloud.cs @@ -0,0 +1,39 @@ +using cAlgo.API; +using cAlgo.API.Indicators; + +namespace cAlgo +{ + [Cloud(UpperBandLineName, LowerBandLineName)] + [Indicator(IsOverlay = true, TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)] + public class SampleEnvelopesCloud : Indicator + { + private const string UpperBandLineName = "UpperBand"; + private const string LowerBandLineName = "LowerBand"; + + [Parameter(DefaultValue = 14)] + public int Period { get; set; } + + [Parameter(DefaultValue = 0.1)] + public double Deviation { get; set; } + + [Output(UpperBandLineName, LineColor = "#B268BCFF")] + public IndicatorDataSeries UpperBand { get; set; } + + [Output(LowerBandLineName, LineColor = "#B2FF5861")] + public IndicatorDataSeries LowerBand { get; set; } + + private MovingAverage _movingAverage; + + protected override void Initialize() + { + _movingAverage = Indicators.MovingAverage(Bars.ClosePrices, Period, MovingAverageType.Simple); + } + + public override void Calculate(int index) + { + var maValue = _movingAverage.Result[index]; + UpperBand[index] = maValue * (1 + Deviation / 100); + LowerBand[index] = maValue * (1 - Deviation / 100); + } + } +} diff --git a/Sources/Indicators/Sample Envelopes Cloud/Sample Envelopes Cloud/Sample Envelopes Cloud.csproj b/Sources/Indicators/Sample Envelopes Cloud/Sample Envelopes Cloud/Sample Envelopes Cloud.csproj new file mode 100644 index 0000000..4d61a27 --- /dev/null +++ b/Sources/Indicators/Sample Envelopes Cloud/Sample Envelopes Cloud/Sample Envelopes Cloud.csproj @@ -0,0 +1,9 @@ + + + net6.0 + + + + + + \ No newline at end of file diff --git a/Sources/Indicators/Sample Price Channels.algo b/Sources/Indicators/Sample Price Channels.algo new file mode 100644 index 0000000..b68b624 Binary files /dev/null and b/Sources/Indicators/Sample Price Channels.algo differ diff --git a/Sources/Indicators/Sample Price Channels/Sample Price Channels.sln b/Sources/Indicators/Sample Price Channels/Sample Price Channels.sln new file mode 100644 index 0000000..7428ac6 --- /dev/null +++ b/Sources/Indicators/Sample Price Channels/Sample Price Channels.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.31729.503 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample Price Channels", "Sample Price Channels\Sample Price Channels.csproj", "{352AA4CE-9530-4132-B500-F2809A0EEBB7}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {352AA4CE-9530-4132-B500-F2809A0EEBB7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {352AA4CE-9530-4132-B500-F2809A0EEBB7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {352AA4CE-9530-4132-B500-F2809A0EEBB7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {352AA4CE-9530-4132-B500-F2809A0EEBB7}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {AE838552-C7FC-4153-AD99-5C69B0E8E315} + EndGlobalSection +EndGlobal diff --git a/Sources/Indicators/Sample Price Channels/Sample Price Channels/Sample Price Channels.cs b/Sources/Indicators/Sample Price Channels/Sample Price Channels/Sample Price Channels.cs new file mode 100644 index 0000000..8f4f1a9 --- /dev/null +++ b/Sources/Indicators/Sample Price Channels/Sample Price Channels/Sample Price Channels.cs @@ -0,0 +1,46 @@ +// ------------------------------------------------------------------------------------------------- +// +// This code is a cTrader Automate API example. +// +// All changes to this file might be lost on the next application update. +// If you are going to modify this file please make a copy using the "Duplicate" command. +// +// ------------------------------------------------------------------------------------------------- + +using System; +using cAlgo.API; + +namespace cAlgo +{ + [Indicator(IsOverlay = true, TimeZone = TimeZones.UTC, AutoRescale = false, AccessRights = AccessRights.None)] + public class SamplePriceChannels : Indicator + { + [Parameter(DefaultValue = 20)] + public int Periods { get; set; } + + [Output("Upper", LineColor = "Pink")] + public IndicatorDataSeries Upper { get; set; } + + [Output("Lower", LineColor = "Pink")] + public IndicatorDataSeries Lower { get; set; } + + [Output("Center", LineColor = "Pink")] + public IndicatorDataSeries Center { get; set; } + + public override void Calculate(int index) + { + double upper = double.MinValue; + double lower = double.MaxValue; + + for (int i = index - Periods; i <= index - 1; i++) + { + upper = Math.Max(Bars.HighPrices[i], upper); + lower = Math.Min(Bars.LowPrices[i], lower); + } + + Upper[index] = upper; + Lower[index] = lower; + Center[index] = (upper + lower) / 2; + } + } +} diff --git a/Sources/Indicators/Sample Price Channels/Sample Price Channels/Sample Price Channels.csproj b/Sources/Indicators/Sample Price Channels/Sample Price Channels/Sample Price Channels.csproj new file mode 100644 index 0000000..6d836c9 --- /dev/null +++ b/Sources/Indicators/Sample Price Channels/Sample Price Channels/Sample Price Channels.csproj @@ -0,0 +1,9 @@ + + + net6.0 + + + + + + \ No newline at end of file diff --git a/Sources/Indicators/Sample Reference SMA.algo b/Sources/Indicators/Sample Reference SMA.algo new file mode 100644 index 0000000..157bf47 Binary files /dev/null and b/Sources/Indicators/Sample Reference SMA.algo differ diff --git a/Sources/Indicators/Sample Reference SMA/Sample Reference SMA.sln b/Sources/Indicators/Sample Reference SMA/Sample Reference SMA.sln new file mode 100644 index 0000000..69028b1 --- /dev/null +++ b/Sources/Indicators/Sample Reference SMA/Sample Reference SMA.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.31729.503 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample Reference SMA", "Sample Reference SMA\Sample Reference SMA.csproj", "{379F2DE2-039B-418A-96D2-D12A54957B53}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {379F2DE2-039B-418A-96D2-D12A54957B53}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {379F2DE2-039B-418A-96D2-D12A54957B53}.Debug|Any CPU.Build.0 = Debug|Any CPU + {379F2DE2-039B-418A-96D2-D12A54957B53}.Release|Any CPU.ActiveCfg = Release|Any CPU + {379F2DE2-039B-418A-96D2-D12A54957B53}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {E36CB531-4E18-4E12-9076-292B58A6D9E9} + EndGlobalSection +EndGlobal diff --git a/Sources/Indicators/Sample Reference SMA/Sample Reference SMA/Sample Reference SMA.cs b/Sources/Indicators/Sample Reference SMA/Sample Reference SMA/Sample Reference SMA.cs new file mode 100644 index 0000000..633f1cb --- /dev/null +++ b/Sources/Indicators/Sample Reference SMA/Sample Reference SMA/Sample Reference SMA.cs @@ -0,0 +1,38 @@ +// ------------------------------------------------------------------------------------------------- +// +// This code is a cTrader Automate API example. +// +// All changes to this file might be lost on the next application update. +// If you are going to modify this file please make a copy using the "Duplicate" command. +// +// ------------------------------------------------------------------------------------------------- + +using cAlgo.API; + +namespace cAlgo +{ + [Indicator(IsOverlay = true, TimeZone = TimeZones.UTC, AutoRescale = false, AccessRights = AccessRights.None)] + public class SampleReferenceSMA : Indicator + { + [Parameter("Source")] + public DataSeries Source { get; set; } + + [Parameter(DefaultValue = 14)] + public int SmaPeriod { get; set; } + + [Output("Referenced SMA Output")] + public IndicatorDataSeries refSMA { get; set; } + + private SampleSMA sma; + + protected override void Initialize() + { + sma = Indicators.GetIndicator(Source, SmaPeriod); + } + + public override void Calculate(int index) + { + refSMA[index] = sma.Result[index]; + } + } +} diff --git a/Sources/Indicators/Sample Reference SMA/Sample Reference SMA/Sample Reference SMA.csproj b/Sources/Indicators/Sample Reference SMA/Sample Reference SMA/Sample Reference SMA.csproj new file mode 100644 index 0000000..eaf6065 --- /dev/null +++ b/Sources/Indicators/Sample Reference SMA/Sample Reference SMA/Sample Reference SMA.csproj @@ -0,0 +1,13 @@ + + + net6.0 + + + + + + + + + + \ No newline at end of file diff --git a/Sources/Indicators/Sample SMA.algo b/Sources/Indicators/Sample SMA.algo new file mode 100644 index 0000000..28fe32f Binary files /dev/null and b/Sources/Indicators/Sample SMA.algo differ diff --git a/Sources/Indicators/Sample SMA/Sample SMA.sln b/Sources/Indicators/Sample SMA/Sample SMA.sln new file mode 100644 index 0000000..1305b29 --- /dev/null +++ b/Sources/Indicators/Sample SMA/Sample SMA.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.31729.503 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample SMA", "Sample SMA\Sample SMA.csproj", "{A84A345B-D154-47A0-B384-D178681525D7}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {A84A345B-D154-47A0-B384-D178681525D7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A84A345B-D154-47A0-B384-D178681525D7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A84A345B-D154-47A0-B384-D178681525D7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A84A345B-D154-47A0-B384-D178681525D7}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {4C1B609B-0732-4915-AC38-A0C170900740} + EndGlobalSection +EndGlobal diff --git a/Sources/Indicators/Sample SMA/Sample SMA/Sample SMA.cs b/Sources/Indicators/Sample SMA/Sample SMA/Sample SMA.cs new file mode 100644 index 0000000..7889bf2 --- /dev/null +++ b/Sources/Indicators/Sample SMA/Sample SMA/Sample SMA.cs @@ -0,0 +1,36 @@ +// ------------------------------------------------------------------------------------------------- +// +// This code is a cTrader Automate API example. +// +// All changes to this file might be lost on the next application update. +// If you are going to modify this file please make a copy using the "Duplicate" command. +// +// ------------------------------------------------------------------------------------------------- + +using cAlgo.API; + +namespace cAlgo +{ + [Indicator(IsOverlay = true, TimeZone = TimeZones.UTC, AutoRescale = false, AccessRights = AccessRights.None)] + public class SampleSMA : Indicator + { + [Parameter("Source")] + public DataSeries Source { get; set; } + + [Parameter(DefaultValue = 14)] + public int Periods { get; set; } + + [Output("Main", LineColor = "Turquoise")] + public IndicatorDataSeries Result { get; set; } + + public override void Calculate(int index) + { + var sum = 0.0; + + for (var i = index - Periods + 1; i <= index; i++) + sum += Source[i]; + + Result[index] = sum / Periods; + } + } +} diff --git a/Sources/Indicators/Sample SMA/Sample SMA/Sample SMA.csproj b/Sources/Indicators/Sample SMA/Sample SMA/Sample SMA.csproj new file mode 100644 index 0000000..6d836c9 --- /dev/null +++ b/Sources/Indicators/Sample SMA/Sample SMA/Sample SMA.csproj @@ -0,0 +1,9 @@ + + + net6.0 + + + + + + \ No newline at end of file diff --git a/Sources/Indicators/Sample Standard Deviation.algo b/Sources/Indicators/Sample Standard Deviation.algo new file mode 100644 index 0000000..b8df7c2 Binary files /dev/null and b/Sources/Indicators/Sample Standard Deviation.algo differ diff --git a/Sources/Indicators/Sample Standard Deviation/Sample Standard Deviation.sln b/Sources/Indicators/Sample Standard Deviation/Sample Standard Deviation.sln new file mode 100644 index 0000000..a31a193 --- /dev/null +++ b/Sources/Indicators/Sample Standard Deviation/Sample Standard Deviation.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.31729.503 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample Standard Deviation", "Sample Standard Deviation\Sample Standard Deviation.csproj", "{9A613E47-9DBB-415C-9AF9-DC10C3B588CC}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {9A613E47-9DBB-415C-9AF9-DC10C3B588CC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9A613E47-9DBB-415C-9AF9-DC10C3B588CC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9A613E47-9DBB-415C-9AF9-DC10C3B588CC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9A613E47-9DBB-415C-9AF9-DC10C3B588CC}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {A1B1BB6D-BE3B-4D5A-9FD9-83E6085D984A} + EndGlobalSection +EndGlobal diff --git a/Sources/Indicators/Sample Standard Deviation/Sample Standard Deviation/Sample Standard Deviation.cs b/Sources/Indicators/Sample Standard Deviation/Sample Standard Deviation/Sample Standard Deviation.cs new file mode 100644 index 0000000..4517eda --- /dev/null +++ b/Sources/Indicators/Sample Standard Deviation/Sample Standard Deviation/Sample Standard Deviation.cs @@ -0,0 +1,49 @@ +// ------------------------------------------------------------------------------------------------- +// +// This code is a cTrader Automate API example. +// +// All changes to this file might be lost on the next application update. +// If you are going to modify this file please make a copy using the "Duplicate" command. +// +// ------------------------------------------------------------------------------------------------- + +using System; +using cAlgo.API; +using cAlgo.API.Indicators; + +namespace cAlgo +{ + [Indicator(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)] + public class SampleStandardDeviation : Indicator + { + [Parameter("Source")] + public DataSeries Source { get; set; } + + [Parameter(DefaultValue = 14, MinValue = 2)] + public int Periods { get; set; } + + [Parameter("MA Type", DefaultValue = MovingAverageType.Simple)] + public MovingAverageType MAType { get; set; } + + [Output("Result", LineColor = "Orange")] + public IndicatorDataSeries Result { get; set; } + + private MovingAverage movingAverage; + + protected override void Initialize() + { + movingAverage = Indicators.MovingAverage(Source, Periods, MAType); + } + + public override void Calculate(int index) + { + var average = movingAverage.Result[index]; + var sum = 0.0; + + for (var period = 0; period < Periods; period++) + sum += Math.Pow(Source[index - period] - average, 2.0); + + Result[index] = Math.Sqrt(sum / Periods); + } + } +} diff --git a/Sources/Indicators/Sample Standard Deviation/Sample Standard Deviation/Sample Standard Deviation.csproj b/Sources/Indicators/Sample Standard Deviation/Sample Standard Deviation/Sample Standard Deviation.csproj new file mode 100644 index 0000000..6d836c9 --- /dev/null +++ b/Sources/Indicators/Sample Standard Deviation/Sample Standard Deviation/Sample Standard Deviation.csproj @@ -0,0 +1,9 @@ + + + net6.0 + + + + + + \ No newline at end of file diff --git a/Sources/Indicators/SmaHmaCrossoverAlert.algo b/Sources/Indicators/SmaHmaCrossoverAlert.algo new file mode 100644 index 0000000..02032c4 Binary files /dev/null and b/Sources/Indicators/SmaHmaCrossoverAlert.algo differ diff --git a/Sources/Indicators/SmaHmaCrossoverAlert/SmaHmaCrossoverAlert.sln b/Sources/Indicators/SmaHmaCrossoverAlert/SmaHmaCrossoverAlert.sln new file mode 100644 index 0000000..3052e9b --- /dev/null +++ b/Sources/Indicators/SmaHmaCrossoverAlert/SmaHmaCrossoverAlert.sln @@ -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}") = "SmaHmaCrossoverAlert", "SmaHmaCrossoverAlert\SmaHmaCrossoverAlert.csproj", "{a1056547-1f1b-47ec-bc9f-dbee33be00dc}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {a1056547-1f1b-47ec-bc9f-dbee33be00dc}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {a1056547-1f1b-47ec-bc9f-dbee33be00dc}.Debug|Any CPU.Build.0 = Debug|Any CPU + {a1056547-1f1b-47ec-bc9f-dbee33be00dc}.Release|Any CPU.ActiveCfg = Release|Any CPU + {a1056547-1f1b-47ec-bc9f-dbee33be00dc}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/Sources/Indicators/SmaHmaCrossoverAlert/SmaHmaCrossoverAlert/SmaHmaCrossoverAlert.cs b/Sources/Indicators/SmaHmaCrossoverAlert/SmaHmaCrossoverAlert/SmaHmaCrossoverAlert.cs new file mode 100644 index 0000000..819ac73 --- /dev/null +++ b/Sources/Indicators/SmaHmaCrossoverAlert/SmaHmaCrossoverAlert/SmaHmaCrossoverAlert.cs @@ -0,0 +1,118 @@ +using System; +using cAlgo.API; +using cAlgo.API.Internals; +using cAlgo.API.Indicators; + +namespace cAlgo +{ + [Indicator(IsOverlay = true, TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)] + public class SmaHmaCrossoverAlert : Indicator + { + #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("Sound File Path", DefaultValue = @"C:\Windows\Media\Alarm05.wav")] + public string SoundFilePath { get; set; } + + [Parameter("Source")] + public DataSeries Source { get; set; } + + #endregion + + #region Outputs + + [Output("SMA", LineColor = "#FF01FF01", PlotType = PlotType.Line, Thickness = 2)] + public IndicatorDataSeries SmaResult { get; set; } + + [Output("HMA", LineColor = "#FFFF6666", PlotType = PlotType.Line, Thickness = 2)] + public IndicatorDataSeries HmaResult { get; set; } + + #endregion + + #region Fields + + private SimpleMovingAverage _sma; + private HullMovingAverage _hma; + private int _lastAlertBarIndex; + + #endregion + + protected override void Initialize() + { + _sma = Indicators.SimpleMovingAverage(Source, SmaPeriod); + _hma = Indicators.HullMovingAverage(Source, HmaPeriod); + _lastAlertBarIndex = -1; + } + + public override void Calculate(int index) + { + // Update indicator values + SmaResult[index] = _sma.Result[index]; + HmaResult[index] = _hma.Result[index]; + + // Ensure sufficient data exists + if ((index <= SmaPeriod) || (index <= HmaPeriod)) + { + return; + } + + // Processing alerts only for the last bar (real-time) to prevent playing sounds during backfill + if (!IsLastBar) + { + return; + } + + // Prevent multiple alerts on the same bar + if (index == _lastAlertBarIndex) + { + return; + } + + double smaCurr = _sma.Result[index]; + double smaPrev = _sma.Result[index - 1]; + double hmaCurr = _hma.Result[index]; + double hmaPrev = _hma.Result[index - 1]; + + // Condition 1: SMA Slope + bool isSmaRising = smaCurr > smaPrev; + bool isSmaFalling = smaCurr < smaPrev; + + // Condition 2: Crossover + // CrossUp: Previous HMA was below/equal SMA, Current HMA is above SMA + bool isCrossUp = (hmaPrev <= smaPrev) && (hmaCurr > smaCurr); + + // CrossDown: Previous HMA was above/equal SMA, Current HMA is below SMA + bool isCrossDown = (hmaPrev >= smaPrev) && (hmaCurr < smaCurr); + + // Signal Evaluation + if ((isSmaRising) && (isCrossUp)) + { + PlayAlert(index); + } + else if ((isSmaFalling) && (isCrossDown)) + { + PlayAlert(index); + } + } + + private void PlayAlert(int index) + { + _lastAlertBarIndex = index; + + // Try/Catch block to prevent indicator crash if file is missing + try + { + Notifications.PlaySound(SoundFilePath); + } + catch (Exception ex) + { + Print("Error playing sound: {0}", ex.Message); + } + } + } +} \ No newline at end of file diff --git a/Sources/Indicators/SmaHmaCrossoverAlert/SmaHmaCrossoverAlert/SmaHmaCrossoverAlert.csproj b/Sources/Indicators/SmaHmaCrossoverAlert/SmaHmaCrossoverAlert/SmaHmaCrossoverAlert.csproj new file mode 100644 index 0000000..da1fef1 --- /dev/null +++ b/Sources/Indicators/SmaHmaCrossoverAlert/SmaHmaCrossoverAlert/SmaHmaCrossoverAlert.csproj @@ -0,0 +1,9 @@ + + + net6.0 + + + + + + diff --git a/Sources/Plugins/MS_StartAllBots.algo b/Sources/Plugins/MS_StartAllBots.algo new file mode 100644 index 0000000..71e4831 Binary files /dev/null and b/Sources/Plugins/MS_StartAllBots.algo differ diff --git a/Sources/Plugins/MS_StartAllBots/MS_StartAllBots.sln b/Sources/Plugins/MS_StartAllBots/MS_StartAllBots.sln new file mode 100644 index 0000000..f1e32bd --- /dev/null +++ b/Sources/Plugins/MS_StartAllBots/MS_StartAllBots.sln @@ -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}") = "MS_StartAllBots", "MS_StartAllBots\MS_StartAllBots.csproj", "{9242f644-de9a-43ea-9aae-3cfb338b375f}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {9242f644-de9a-43ea-9aae-3cfb338b375f}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9242f644-de9a-43ea-9aae-3cfb338b375f}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9242f644-de9a-43ea-9aae-3cfb338b375f}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9242f644-de9a-43ea-9aae-3cfb338b375f}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/Sources/Plugins/MS_StartAllBots/MS_StartAllBots/MS_StartAllBots.cs b/Sources/Plugins/MS_StartAllBots/MS_StartAllBots/MS_StartAllBots.cs new file mode 100644 index 0000000..a343a68 --- /dev/null +++ b/Sources/Plugins/MS_StartAllBots/MS_StartAllBots/MS_StartAllBots.cs @@ -0,0 +1,67 @@ +using System; +using System.Linq; +using cAlgo.API; // Required for ChartManager, ChartFrame, Chart, IChartRobot +using cAlgo.API.Collections; +using cAlgo.API.Indicators; +using cAlgo.API.Internals; + +namespace cAlgo.Plugins +{ + [Plugin(AccessRights = AccessRights.None)] + public class MS_StartAllBots : Plugin + { + private const string TargetBotName = "MS_ExportTickData"; + + protected override void OnStart() + { + // Access the static ChartManager to get all open chart frames. + // ChartManager.ChartFrames returns an IReadOnlyList. + //var chartFrames = ChartManager; + + // Iterate through each chart frame. + foreach (var frame in ChartManager) + { + if( frame is ChartFrame ) + { + var chartFrame = frame as ChartFrame; + + // Each ChartFrame has a Chart property. + var chart = chartFrame.Chart; + + // It's good practice to ensure the chart object is not null. + if (chart != null) + { + // Access all cBot instances (IChartRobot) attached to the current chart. + // Chart.Robots returns an IReadOnlyList. + var chartRobots = chart.Robots; + + // Iterate through each cBot instance on the current chart. + foreach (var chartRobot in chartRobots) + { + // Check if the cBot's template name matches the target name + // and if the cBot instance is not currently running. + if (chartRobot.Name == TargetBotName) + { + RobotState state = chartRobot.State; + if( state == RobotState.Stopped ) + { + var ppath = chartRobot.Parameters.First( p => p.Name == "DirectoryName" ); + if( ppath != null ) + { + ppath.Value = "\\\\COFFEE\\TickData\\Pepperstone"; + chartRobot.Start(); + } + } + } + } + } + } + } + } + + protected override void OnStop() + { + // Handle Plugin stop here if needed. + } + } +} \ No newline at end of file diff --git a/Sources/Plugins/MS_StartAllBots/MS_StartAllBots/MS_StartAllBots.csproj b/Sources/Plugins/MS_StartAllBots/MS_StartAllBots/MS_StartAllBots.csproj new file mode 100644 index 0000000..da1fef1 --- /dev/null +++ b/Sources/Plugins/MS_StartAllBots/MS_StartAllBots/MS_StartAllBots.csproj @@ -0,0 +1,9 @@ + + + net6.0 + + + + + + diff --git a/Sources/Plugins/Webhook.algo b/Sources/Plugins/Webhook.algo new file mode 100644 index 0000000..26cd160 Binary files /dev/null and b/Sources/Plugins/Webhook.algo differ diff --git a/Sources/Plugins/Webhook/Webhook.sln b/Sources/Plugins/Webhook/Webhook.sln new file mode 100644 index 0000000..85efe57 --- /dev/null +++ b/Sources/Plugins/Webhook/Webhook.sln @@ -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}") = "Webhook", "Webhook\Webhook.csproj", "{4b67b9be-4382-4e2b-ad2d-372da3d81a9a}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {4b67b9be-4382-4e2b-ad2d-372da3d81a9a}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4b67b9be-4382-4e2b-ad2d-372da3d81a9a}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4b67b9be-4382-4e2b-ad2d-372da3d81a9a}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4b67b9be-4382-4e2b-ad2d-372da3d81a9a}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/Sources/Plugins/Webhook/Webhook/Webhook.cs b/Sources/Plugins/Webhook/Webhook/Webhook.cs new file mode 100644 index 0000000..b7e6953 --- /dev/null +++ b/Sources/Plugins/Webhook/Webhook/Webhook.cs @@ -0,0 +1,308 @@ +/* +Webhook setup: + +1. cmd as admin: + > netsh http add urlacl url=http://+:80/ctrader user=RAKI\Brummel + +2. Windows Defender Firewall, Outgoing Rules: + New rule, Ports-->TCP-->local port = 80, remote ports = all + +3. FritzBox: + New "MyFritz-Freigabe", HTTP-Server, Deviceport 80, external port 80, Folder "ctrader" + +Result: +Webhook in Tradingview: http://raki.1k9zmiax0jpc2iys.myfritz.net:80/ctrader +*/ + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Net; +using System.Resources; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Globalization; +using System.Text.Json; +using cAlgo.API; +using cAlgo.API.Collections; +using cAlgo.API.Indicators; +using cAlgo.API.Internals; + +namespace cAlgo.Plugins; + +[Plugin(AccessRights = AccessRights.FullAccess)] +public class Webhook : Plugin +{ + private HttpListener listener; + private string lastMsg = ""; + private bool msgProcessed = false; + private readonly CancellationTokenSource cancellationTokenSource = new(); + private Task task; + private TradeInfo tradeInfo; + private Symbol symbol; + private readonly List charts = new(); + + private class TradeInfo + { + public string Symbol { get; set; } + public double StopLoss { get; set; } + public int Risk { get; set; } + public double Buy { get; set; } + public double Sell { get; set; } + public double Open { get; set; } + public double Low { get; set; } + public double High { get; set; } + public double Close { get; set; } + public DateTime ReceiveTime { get; set; } + public List Charts { get; set; } + } + + protected override void OnStart() + { + listener = new HttpListener(); + //listener.Prefixes.Add("http://localhost:3000/"); + listener.Prefixes.Add("http://+:80/ctrader/"); + listener.Start(); + + var cancellationToken = cancellationTokenSource.Token; + + task = Task.Run(() => + { + while( !cancellationToken.IsCancellationRequested ) + { + // Wait for a request to come in + var context = listener.GetContext(); + var request = context.Request; + + // Read the request body + using (var reader = new StreamReader(request.InputStream, request.ContentEncoding)) + { + string m = reader.ReadToEnd(); + + Monitor.Enter(this); + try + { + if( m != lastMsg ) + { + lastMsg = m; + msgProcessed = false; + } + } + finally + { + Monitor.Exit(this); + } + } + + // Send a response + var response = context.Response; + response.StatusCode = 200; + response.ContentType = "text/plain"; + var buffer = Encoding.UTF8.GetBytes("OK - cTrader"); + response.ContentLength64 = buffer.Length; + response.OutputStream.Write(buffer, 0, buffer.Length); + response.OutputStream.Close(); + } + }, cancellationToken); + + Timer.Start(TimeSpan.FromSeconds(1)); + Positions.Opened += (a) => { UpdateStops(); }; + } + + protected override void OnTimer() + { + ProcessMsg(); + } + + protected override void OnStop() + { + Timer.Stop(); + cancellationTokenSource.Cancel(); + task.Wait(); + listener.Stop(); + } + + protected bool UpdateTradeInfo() + { + string m = ""; + + Monitor.Enter(this); + try + { + if( !msgProcessed ) + { + msgProcessed = true; + m = lastMsg; + } + } + finally + { + Monitor.Exit(this); + } + + if( m== "" ) + return false; + + Print( "Received: {0}", m ); + tradeInfo = JsonSerializer.Deserialize(m); + tradeInfo.ReceiveTime = DateTime.Now; + symbol = Symbols.GetSymbol(tradeInfo.Symbol); + if( symbol == null ) + { + Print( "Error: unknown symbol {0}", tradeInfo.Symbol ); + return false; + } + + tradeInfo.StopLoss = Math.Round( tradeInfo.StopLoss, symbol.Digits ); + tradeInfo.Buy = Math.Round( tradeInfo.Buy, symbol.Digits ); + tradeInfo.Sell = Math.Round( tradeInfo.Sell, symbol.Digits ); + + charts.Clear(); + foreach (var frame in ChartManager) + if (frame is ChartFrame chartFrame) + if( chartFrame.Chart.Symbol == symbol ) + charts.Add( chartFrame.Chart ); + + foreach (var chart in charts ) + chart.DrawStaticText("Message", tradeInfo.ReceiveTime.ToString("HH:mm:ss")+ ": "+m, VerticalAlignment.Top, HorizontalAlignment.Right, chart.ColorSettings.ForegroundColor); + + return true; + } + + protected void ProcessMsg() + { + if( UpdateTradeInfo() ) + { + foreach (var chart in charts ) + { + chart.DrawHorizontalLine( "SL", tradeInfo.StopLoss, Color.White ); + if( tradeInfo.Buy > 0 ) + chart.DrawIcon("Buy" + chart.Bars.Count, ChartIconType.UpArrow, chart.Bars.Count - 1, chart.Bars.ClosePrices[chart.Bars.Count - 1], Color.Green); + if( tradeInfo.Sell > 0 ) + chart.DrawIcon("Sell" + chart.Bars.Count, ChartIconType.DownArrow, chart.Bars.Count - 1, chart.Bars.ClosePrices[chart.Bars.Count - 1], Color.Red); + } + + ManageOrders(); + UpdateStops(); + } + } + + private PendingOrder pendingOrder; + + private double CalculateVolume(double StopLoss) + { + var maxAmountRisked = Account.Equity * ((double)tradeInfo.Risk / 100); + return symbol.NormalizeVolumeInUnits(maxAmountRisked / (StopLoss/symbol.PipSize * symbol.PipValue), RoundingMode.Down); + + } + + protected void ManageOrders() + { + return; + + if( Positions.Count > 0 || tradeInfo.Symbol != "GER40" ) + return; + + if( tradeInfo.Buy > 0 ) + { + double buyPrice = Math.Round( tradeInfo.Buy+symbol.Spread, symbol.Digits ); + double stopLossPrice = Math.Round( tradeInfo.StopLoss-symbol.Spread, symbol.Digits ); + + if( pendingOrder != null ) + if( pendingOrder.TradeType != TradeType.Buy || pendingOrder.TargetPrice != buyPrice ) + { + pendingOrder.Cancel(); + pendingOrder = null; + } + + if( pendingOrder == null ) + { + TradeResult tradeResult = PlaceStopOrder( + TradeType.Buy, tradeInfo.Symbol, + 2, // CalculateVolume( buyPrice-stopLossPrice ), + buyPrice, "Managed long", + (buyPrice-stopLossPrice)/symbol.PipSize, null); + + if( tradeResult.IsSuccessful ) + pendingOrder = tradeResult.PendingOrder; + } + } + else if( tradeInfo.Sell > 0 ) + { + double sellPrice = Math.Round( tradeInfo.Sell-symbol.Spread, symbol.Digits ); + double stopLossPrice = Math.Round( tradeInfo.StopLoss+symbol.Spread, symbol.Digits ); + + if( pendingOrder != null ) + if( pendingOrder.TradeType != TradeType.Sell || pendingOrder.TargetPrice != sellPrice ) + { + pendingOrder.Cancel(); + pendingOrder = null; + } + + if( pendingOrder == null ) + { + TradeResult tradeResult = PlaceStopOrder( + TradeType.Sell, tradeInfo.Symbol, + 2, // CalculateVolume( stopLossPrice-sellPrice ), + sellPrice, "Managed short", + (stopLossPrice-sellPrice)/symbol.PipSize, null); + + if( tradeResult.IsSuccessful ) + pendingOrder = tradeResult.PendingOrder; + } + } + } + + protected void UpdateStops() + { + foreach (var position in Positions) + { + if (position.Symbol != symbol) + continue; + + if( position.TradeType == TradeType.Buy ) + { + double sl = Math.Round( tradeInfo.StopLoss-symbol.Spread, symbol.Digits ); + + if( !position.StopLoss.HasValue || position.StopLoss < sl ) + ModifyPosition(position, sl, position.TakeProfit); +/* + if( tradeInfo.Close < sl ) + { + position.Close(); + continue; + } + + double be = Math.Round( position.EntryPrice+symbol.Spread, symbol.Digits ); + double sl_be = Math.Min( sl, be ); + + if( !position.StopLoss.HasValue || position.StopLoss < sl_be ) + ModifyPosition(position, sl_be, position.TakeProfit); +*/ +} + else + { + double sl = Math.Round( tradeInfo.StopLoss+symbol.Spread, symbol.Digits ); + + if( !position.StopLoss.HasValue || position.StopLoss > sl ) + ModifyPosition(position, sl, position.TakeProfit); +/* + if( tradeInfo.Close > sl ) + { + position.Close(); + continue; + } + + double be = Math.Round( position.EntryPrice-symbol.Spread, symbol.Digits ); + double sl_be = Math.Max( sl, be ); + + if( !position.StopLoss.HasValue || position.StopLoss > sl_be ) + ModifyPosition(position, sl_be, position.TakeProfit); +*/ + } + } + } +} \ No newline at end of file diff --git a/Sources/Plugins/Webhook/Webhook/Webhook.csproj b/Sources/Plugins/Webhook/Webhook/Webhook.csproj new file mode 100644 index 0000000..da1fef1 --- /dev/null +++ b/Sources/Plugins/Webhook/Webhook/Webhook.csproj @@ -0,0 +1,9 @@ + + + net6.0 + + + + + + diff --git a/Sources/Robots/HMACrossBot.algo b/Sources/Robots/HMACrossBot.algo new file mode 100644 index 0000000..adfe42e Binary files /dev/null and b/Sources/Robots/HMACrossBot.algo differ diff --git a/Sources/Robots/HMACrossBot/.vscode/tasks.json b/Sources/Robots/HMACrossBot/.vscode/tasks.json new file mode 100644 index 0000000..215166e --- /dev/null +++ b/Sources/Robots/HMACrossBot/.vscode/tasks.json @@ -0,0 +1 @@ +{"version":"2.0.0","tasks":[{"label":"build","command":"dotnet","type":"process","args":["build","${workspaceFolder}","/property:GenerateFullPaths=true","/consoleLoggerParameters:NoSummary"],"problemMatcher":"$msCompile"}]} \ No newline at end of file diff --git a/Sources/Robots/HMACrossBot/HMACrossBot.sln b/Sources/Robots/HMACrossBot/HMACrossBot.sln new file mode 100644 index 0000000..45a23bc --- /dev/null +++ b/Sources/Robots/HMACrossBot/HMACrossBot.sln @@ -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}") = "HMACrossBot", "HMACrossBot\HMACrossBot.csproj", "{7acd261c-177a-4980-a562-d7b8d3f6feb4}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {7acd261c-177a-4980-a562-d7b8d3f6feb4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7acd261c-177a-4980-a562-d7b8d3f6feb4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7acd261c-177a-4980-a562-d7b8d3f6feb4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7acd261c-177a-4980-a562-d7b8d3f6feb4}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/Sources/Robots/HMACrossBot/HMACrossBot/HMACrossBot.cs b/Sources/Robots/HMACrossBot/HMACrossBot/HMACrossBot.cs new file mode 100644 index 0000000..b591c87 --- /dev/null +++ b/Sources/Robots/HMACrossBot/HMACrossBot/HMACrossBot.cs @@ -0,0 +1,148 @@ +using System; +using System.Linq; // Required for .Where() and .Count() +using cAlgo.API; +using cAlgo.API.Indicators; +using cAlgo.API.Internals; + +namespace cAlgo.Robots +{ + // Strategy based on two HMAs (fast and slow) + [Robot(AccessRights = AccessRights.None, TimeZone = TimeZones.UTC)] + public class HMACrossBot : Robot + { + [Parameter("Fast HMA Period", DefaultValue = 20, MinValue = 2)] + public int FastHmaPeriod { get; set; } + + [Parameter("Slow HMA Period", DefaultValue = 25, MinValue = 2)] + public int SlowHmaPeriod { get; set; } + + [Parameter("Max Positions (per side)", DefaultValue = 3, MinValue = 1)] + public int MaxPositions { get; set; } + + [Parameter("Volume (Units)", DefaultValue = 0.1, MinValue = 0.1)] + public double VolumeInUnits { get; set; } + + private HullMovingAverage _fastHma; + private HullMovingAverage _slowHma; + + // A unique label to identify positions opened by this cBot instance + private string _botLabel; + + // Initialize indicators and check parameters + protected override void OnStart() + { + if (FastHmaPeriod >= SlowHmaPeriod) + { + Print("Error: Fast HMA Period must be less than Slow HMA Period for this logic."); + Stop(); + return; + } + + _fastHma = Indicators.HullMovingAverage(Bars.ClosePrices, FastHmaPeriod); + _slowHma = Indicators.HullMovingAverage(Bars.ClosePrices, SlowHmaPeriod); + + // Generate a unique label for this cBot instance + _botLabel = $"HMACrossBot_{SymbolName}_{TimeFrame}_{InstanceId}"; + } + + // Main logic executed at the close of each bar + protected override void OnBar() + { + // Ensure we have enough data to calculate HMA direction + if (_slowHma.Result.Count < 3) + { + return; + } + + // --- 1. Get HMA values --- + // Use index 1 (the most recently closed bar) as OnBar fires at the close. + double fastHmaCurrent = _fastHma.Result.Last(1); + double slowHmaCurrent = _slowHma.Result.Last(1); + double slowHmaPrevious = _slowHma.Result.Last(2); + + // --- 2. Check Exit Conditions (Close First) --- + + // Close all LONG positions if fast HMA crosses below or equals slow HMA + if (fastHmaCurrent <= slowHmaCurrent) + { + ClosePositions(TradeType.Buy); + } + + // Close all SHORT positions if fast HMA crosses above or equals slow HMA + // (Corrected: Use TradeType.Sell, not .Short) + if (fastHmaCurrent >= slowHmaCurrent) + { + ClosePositions(TradeType.Sell); + } + + // --- 3. Check Entry Conditions (Open After) --- + + // Get current positions count (check only positions managed by this bot) + int longPositionsCount = Positions.Count(p => p.Label == _botLabel && p.SymbolName == SymbolName && p.TradeType == TradeType.Buy); + // (Corrected: Use TradeType.Sell, not .Short) + int shortPositionsCount = Positions.Count(p => p.Label == _botLabel && p.SymbolName == SymbolName && p.TradeType == TradeType.Sell); + + // LONG Entry: fast > slow AND slow is rising + if ((fastHmaCurrent > slowHmaCurrent) && (slowHmaCurrent > slowHmaPrevious)) + { + if (longPositionsCount < MaxPositions) + { + ExecuteTrade(TradeType.Buy); + } + } + + // SHORT Entry: fast < slow AND slow is falling + if ((fastHmaCurrent < slowHmaCurrent) && (slowHmaCurrent < slowHmaPrevious)) + { + if (shortPositionsCount < MaxPositions) + { + // (Corrected: Use TradeType.Sell, not .Short) + ExecuteTrade(TradeType.Sell); + } + } + } + + // Helper to execute a trade with normalized volume + private void ExecuteTrade(TradeType tradeType) + { + // Normalize volume to the symbol's requirements + double volumeToTrade = Symbol.NormalizeVolumeInUnits(VolumeInUnits, RoundingMode.ToNearest); + + // Ensure minimum volume is met + if (volumeToTrade < Symbol.VolumeInUnitsMin) + { + volumeToTrade = Symbol.VolumeInUnitsMin; + } + + ExecuteMarketOrder(tradeType, SymbolName, volumeToTrade, _botLabel); + } + + // Helper to close all positions of a specific type + private void ClosePositions(TradeType tradeType) + { + // Find all positions for the specified trade type and label + // (Corrected: Removed 'IsClosing' check, as it's not on Position interface) + var positionsToClose = Positions + .Where(p => p.Label == _botLabel && + p.SymbolName == SymbolName && + p.TradeType == tradeType) + .ToList(); + + foreach (var position in positionsToClose) + { + // Asynchronously close each position + ClosePositionAsync(position); + } + } + + protected override void OnStop() + { + // cBot is stopping + } + + protected override void OnTick() + { + // All logic is handled OnBar in this strategy + } + } +} \ No newline at end of file diff --git a/Sources/Robots/HMACrossBot/HMACrossBot/HMACrossBot.csproj b/Sources/Robots/HMACrossBot/HMACrossBot/HMACrossBot.csproj new file mode 100644 index 0000000..da1fef1 --- /dev/null +++ b/Sources/Robots/HMACrossBot/HMACrossBot/HMACrossBot.csproj @@ -0,0 +1,9 @@ + + + net6.0 + + + + + + diff --git a/Sources/Robots/HMACrossBot/HMACrossBot/obj/Debug/net6.0/.NETCoreApp,Version=v6.0.AssemblyAttributes.cs b/Sources/Robots/HMACrossBot/HMACrossBot/obj/Debug/net6.0/.NETCoreApp,Version=v6.0.AssemblyAttributes.cs new file mode 100644 index 0000000..32c95f9 --- /dev/null +++ b/Sources/Robots/HMACrossBot/HMACrossBot/obj/Debug/net6.0/.NETCoreApp,Version=v6.0.AssemblyAttributes.cs @@ -0,0 +1,4 @@ +// +using System; +using System.Reflection; +[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v6.0", FrameworkDisplayName = "")] diff --git a/Sources/Robots/HMACrossBot/HMACrossBot/obj/Debug/net6.0/HMACrossBot.AssemblyInfo.cs b/Sources/Robots/HMACrossBot/HMACrossBot/obj/Debug/net6.0/HMACrossBot.AssemblyInfo.cs new file mode 100644 index 0000000..9731d22 --- /dev/null +++ b/Sources/Robots/HMACrossBot/HMACrossBot/obj/Debug/net6.0/HMACrossBot.AssemblyInfo.cs @@ -0,0 +1,22 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +using System; +using System.Reflection; + +[assembly: System.Reflection.AssemblyCompanyAttribute("HMACrossBot")] +[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] +[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0")] +[assembly: System.Reflection.AssemblyProductAttribute("HMACrossBot")] +[assembly: System.Reflection.AssemblyTitleAttribute("HMACrossBot")] +[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] + +// Generated by the MSBuild WriteCodeFragment class. + diff --git a/Sources/Robots/HMACrossBot/HMACrossBot/obj/Debug/net6.0/HMACrossBot.AssemblyInfoInputs.cache b/Sources/Robots/HMACrossBot/HMACrossBot/obj/Debug/net6.0/HMACrossBot.AssemblyInfoInputs.cache new file mode 100644 index 0000000..92984ff --- /dev/null +++ b/Sources/Robots/HMACrossBot/HMACrossBot/obj/Debug/net6.0/HMACrossBot.AssemblyInfoInputs.cache @@ -0,0 +1 @@ +f7a58568308eab12ad2da8f697114353004279fa diff --git a/Sources/Robots/HMACrossBot/HMACrossBot/obj/Debug/net6.0/HMACrossBot.GeneratedMSBuildEditorConfig.editorconfig b/Sources/Robots/HMACrossBot/HMACrossBot/obj/Debug/net6.0/HMACrossBot.GeneratedMSBuildEditorConfig.editorconfig new file mode 100644 index 0000000..1172ac7 --- /dev/null +++ b/Sources/Robots/HMACrossBot/HMACrossBot/obj/Debug/net6.0/HMACrossBot.GeneratedMSBuildEditorConfig.editorconfig @@ -0,0 +1,10 @@ +is_global = true +build_property.TargetFramework = net6.0 +build_property.TargetPlatformMinVersion = +build_property.UsingMicrosoftNETSdkWeb = +build_property.ProjectTypeGuids = +build_property.InvariantGlobalization = +build_property.PlatformNeutralAssembly = +build_property._SupportedPlatformList = Linux,macOS,Windows +build_property.RootNamespace = HMACrossBot +build_property.ProjectDir = C:\Users\Brummel\Documents\cAlgo\Sources\Robots\HMACrossBot\HMACrossBot\ diff --git a/Sources/Robots/HMACrossBot/HMACrossBot/obj/Debug/net6.0/HMACrossBot.assets.cache b/Sources/Robots/HMACrossBot/HMACrossBot/obj/Debug/net6.0/HMACrossBot.assets.cache new file mode 100644 index 0000000..8e38137 Binary files /dev/null and b/Sources/Robots/HMACrossBot/HMACrossBot/obj/Debug/net6.0/HMACrossBot.assets.cache differ diff --git a/Sources/Robots/HMACrossBot/HMACrossBot/obj/Debug/net6.0/HMACrossBot.csproj.AssemblyReference.cache b/Sources/Robots/HMACrossBot/HMACrossBot/obj/Debug/net6.0/HMACrossBot.csproj.AssemblyReference.cache new file mode 100644 index 0000000..50043b7 Binary files /dev/null and b/Sources/Robots/HMACrossBot/HMACrossBot/obj/Debug/net6.0/HMACrossBot.csproj.AssemblyReference.cache differ diff --git a/Sources/Robots/HMACrossBot/HMACrossBot/obj/HMACrossBot.csproj.nuget.dgspec.json b/Sources/Robots/HMACrossBot/HMACrossBot/obj/HMACrossBot.csproj.nuget.dgspec.json new file mode 100644 index 0000000..eb178b0 --- /dev/null +++ b/Sources/Robots/HMACrossBot/HMACrossBot/obj/HMACrossBot.csproj.nuget.dgspec.json @@ -0,0 +1,66 @@ +{ + "format": 1, + "restore": { + "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\HMACrossBot\\HMACrossBot\\HMACrossBot.csproj": {} + }, + "projects": { + "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\HMACrossBot\\HMACrossBot\\HMACrossBot.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\HMACrossBot\\HMACrossBot\\HMACrossBot.csproj", + "projectName": "HMACrossBot", + "projectPath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\HMACrossBot\\HMACrossBot\\HMACrossBot.csproj", + "packagesPath": "C:\\Users\\Brummel\\.nuget\\packages\\", + "outputPath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\HMACrossBot\\HMACrossBot\\obj\\", + "projectStyle": "PackageReference", + "configFilePaths": [ + "C:\\Users\\Brummel\\AppData\\Roaming\\NuGet\\NuGet.Config" + ], + "originalTargetFrameworks": [ + "net6.0" + ], + "sources": { + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net6.0": { + "targetAlias": "net6.0", + "projectReferences": {} + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + } + }, + "frameworks": { + "net6.0": { + "targetAlias": "net6.0", + "dependencies": { + "cTrader.Automate": { + "target": "Package", + "version": "[*, )" + } + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\6.0.200\\RuntimeIdentifierGraph.json" + } + } + } + } +} \ No newline at end of file diff --git a/Sources/Robots/HMACrossBot/HMACrossBot/obj/HMACrossBot.csproj.nuget.g.props b/Sources/Robots/HMACrossBot/HMACrossBot/obj/HMACrossBot.csproj.nuget.g.props new file mode 100644 index 0000000..0b161ac --- /dev/null +++ b/Sources/Robots/HMACrossBot/HMACrossBot/obj/HMACrossBot.csproj.nuget.g.props @@ -0,0 +1,21 @@ + + + + True + NuGet + $(MSBuildThisFileDirectory)project.assets.json + $(UserProfile)\.nuget\packages\ + C:\Users\Brummel\.nuget\packages\ + PackageReference + 6.1.0 + + + + + + + + + C:\Users\Brummel\.nuget\packages\ctrader.automate\1.0.14 + + \ No newline at end of file diff --git a/Sources/Robots/HMACrossBot/HMACrossBot/obj/HMACrossBot.csproj.nuget.g.targets b/Sources/Robots/HMACrossBot/HMACrossBot/obj/HMACrossBot.csproj.nuget.g.targets new file mode 100644 index 0000000..46f142c --- /dev/null +++ b/Sources/Robots/HMACrossBot/HMACrossBot/obj/HMACrossBot.csproj.nuget.g.targets @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Sources/Robots/HMACrossBot/HMACrossBot/obj/project.assets.json b/Sources/Robots/HMACrossBot/HMACrossBot/obj/project.assets.json new file mode 100644 index 0000000..5e04cdc --- /dev/null +++ b/Sources/Robots/HMACrossBot/HMACrossBot/obj/project.assets.json @@ -0,0 +1,139 @@ +{ + "version": 3, + "targets": { + "net6.0": { + "cTrader.Automate/1.0.14": { + "type": "package", + "compile": { + "lib/net6.0/cAlgo.API.dll": {} + }, + "runtime": { + "lib/net6.0/cAlgo.API.dll": {} + }, + "build": { + "build/cTrader.Automate.props": {}, + "build/cTrader.Automate.targets": {} + } + } + } + }, + "libraries": { + "cTrader.Automate/1.0.14": { + "sha512": "eNwE7WL90MGBKb5MuLAtZLdQy0vxkI5EVhLWAQ9S83EAdYkGAzdccvGFLk2oqmtSGBtD+gEpyrJUW/ej4dI4jw==", + "type": "package", + "path": "ctrader.automate/1.0.14", + "hasTools": true, + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "build/cTrader.Automate.props", + "build/cTrader.Automate.targets", + "ctrader.automate.1.0.14.nupkg.sha512", + "ctrader.automate.nuspec", + "eula.md", + "icon.png", + "lib/net40/cAlgo.API.dll", + "lib/net40/cAlgo.API.xml", + "lib/net6.0/cAlgo.API.dll", + "lib/net6.0/cAlgo.API.xml", + "tools/net472/Core.AlgoFormat.Compose.Reflection.dll", + "tools/net472/Core.AlgoFormat.Writer.dll", + "tools/net472/Core.AlgoFormat.dll", + "tools/net472/Core.Domain.Primitives.dll", + "tools/net472/Newtonsoft.Json.dll", + "tools/net472/System.Buffers.dll", + "tools/net472/System.Collections.Immutable.dll", + "tools/net472/System.Memory.dll", + "tools/net472/System.Numerics.Vectors.dll", + "tools/net472/System.Reflection.Metadata.dll", + "tools/net472/System.Reflection.MetadataLoadContext.dll", + "tools/net472/System.Runtime.CompilerServices.Unsafe.dll", + "tools/net472/cTrader.Automate.Sdk.Tasks.dll", + "tools/net6.0/Core.AlgoFormat.Compose.Reflection.dll", + "tools/net6.0/Core.AlgoFormat.Writer.dll", + "tools/net6.0/Core.AlgoFormat.dll", + "tools/net6.0/Core.Connection.Protobuf.Common.dll", + "tools/net6.0/Core.Domain.Primitives.dll", + "tools/net6.0/Microsoft.Win32.SystemEvents.dll", + "tools/net6.0/Newtonsoft.Json.dll", + "tools/net6.0/System.Drawing.Common.dll", + "tools/net6.0/System.Reflection.MetadataLoadContext.dll", + "tools/net6.0/System.Security.Permissions.dll", + "tools/net6.0/System.Windows.Extensions.dll", + "tools/net6.0/cTrader.Automate.Sdk.Tasks.dll", + "tools/net6.0/protobuf-net.Core.dll", + "tools/net6.0/protobuf-net.dll", + "tools/net6.0/runtimes/unix/lib/net6.0/System.Drawing.Common.dll", + "tools/net6.0/runtimes/win/lib/net6.0/Microsoft.Win32.SystemEvents.dll", + "tools/net6.0/runtimes/win/lib/net6.0/System.Drawing.Common.dll", + "tools/net6.0/runtimes/win/lib/net6.0/System.Windows.Extensions.dll" + ] + } + }, + "projectFileDependencyGroups": { + "net6.0": [ + "cTrader.Automate >= *" + ] + }, + "packageFolders": { + "C:\\Users\\Brummel\\.nuget\\packages\\": {} + }, + "project": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\HMACrossBot\\HMACrossBot\\HMACrossBot.csproj", + "projectName": "HMACrossBot", + "projectPath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\HMACrossBot\\HMACrossBot\\HMACrossBot.csproj", + "packagesPath": "C:\\Users\\Brummel\\.nuget\\packages\\", + "outputPath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\HMACrossBot\\HMACrossBot\\obj\\", + "projectStyle": "PackageReference", + "configFilePaths": [ + "C:\\Users\\Brummel\\AppData\\Roaming\\NuGet\\NuGet.Config" + ], + "originalTargetFrameworks": [ + "net6.0" + ], + "sources": { + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net6.0": { + "targetAlias": "net6.0", + "projectReferences": {} + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + } + }, + "frameworks": { + "net6.0": { + "targetAlias": "net6.0", + "dependencies": { + "cTrader.Automate": { + "target": "Package", + "version": "[*, )" + } + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\6.0.200\\RuntimeIdentifierGraph.json" + } + } + } +} \ No newline at end of file diff --git a/Sources/Robots/HMACrossBot/HMACrossBot/obj/project.nuget.cache b/Sources/Robots/HMACrossBot/HMACrossBot/obj/project.nuget.cache new file mode 100644 index 0000000..271cf55 --- /dev/null +++ b/Sources/Robots/HMACrossBot/HMACrossBot/obj/project.nuget.cache @@ -0,0 +1,10 @@ +{ + "version": 2, + "dgSpecHash": "K2rUaOKUIqRJ+x6ANzVtKPk1+58QBQ2T6CNqLozyqXgru9j1i7/TzPvsHUDY/xQrYcV+KOxXpy8WirqosKokuQ==", + "success": true, + "projectFilePath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\HMACrossBot\\HMACrossBot\\HMACrossBot.csproj", + "expectedPackageFiles": [ + "C:\\Users\\Brummel\\.nuget\\packages\\ctrader.automate\\1.0.14\\ctrader.automate.1.0.14.nupkg.sha512" + ], + "logs": [] +} \ No newline at end of file diff --git a/Sources/Robots/Hma20CandleStartegy_V1.algo b/Sources/Robots/Hma20CandleStartegy_V1.algo new file mode 100644 index 0000000..135d0fc Binary files /dev/null and b/Sources/Robots/Hma20CandleStartegy_V1.algo differ diff --git a/Sources/Robots/Hma20CandleStartegy_V1/.vscode/tasks.json b/Sources/Robots/Hma20CandleStartegy_V1/.vscode/tasks.json new file mode 100644 index 0000000..215166e --- /dev/null +++ b/Sources/Robots/Hma20CandleStartegy_V1/.vscode/tasks.json @@ -0,0 +1 @@ +{"version":"2.0.0","tasks":[{"label":"build","command":"dotnet","type":"process","args":["build","${workspaceFolder}","/property:GenerateFullPaths=true","/consoleLoggerParameters:NoSummary"],"problemMatcher":"$msCompile"}]} \ No newline at end of file diff --git a/Sources/Robots/Hma20CandleStartegy_V1/Hma20CandleStartegy_V1.sln b/Sources/Robots/Hma20CandleStartegy_V1/Hma20CandleStartegy_V1.sln new file mode 100644 index 0000000..50c5570 --- /dev/null +++ b/Sources/Robots/Hma20CandleStartegy_V1/Hma20CandleStartegy_V1.sln @@ -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}") = "Hma20CandleStartegy_V1", "Hma20CandleStartegy_V1\Hma20CandleStartegy_V1.csproj", "{f2e9a40a-78e5-4641-8770-59be963064e1}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {f2e9a40a-78e5-4641-8770-59be963064e1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {f2e9a40a-78e5-4641-8770-59be963064e1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {f2e9a40a-78e5-4641-8770-59be963064e1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {f2e9a40a-78e5-4641-8770-59be963064e1}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/Sources/Robots/Hma20CandleStartegy_V1/Hma20CandleStartegy_V1/Hma20CandleStartegy_V1.cs b/Sources/Robots/Hma20CandleStartegy_V1/Hma20CandleStartegy_V1/Hma20CandleStartegy_V1.cs new file mode 100644 index 0000000..c4e3568 --- /dev/null +++ b/Sources/Robots/Hma20CandleStartegy_V1/Hma20CandleStartegy_V1/Hma20CandleStartegy_V1.cs @@ -0,0 +1,130 @@ +using cAlgo.API; +using cAlgo.API.Indicators; +using System; + +namespace cAlgo.Robots +{ + // A simple trading strategy based on HMA slope and candle patterns. + [Robot(AccessRights = AccessRights.None)] + public class HmaBiasStrategy : Robot + { + [Parameter("HMA Period", DefaultValue = 20, Group = "Parameters")] + public int HmaPeriod { get; set; } + + [Parameter("ATR Period", DefaultValue = 14, Group = "Parameters")] + public int AtrPeriod { get; set; } + + [Parameter("ATR Multiplier for SL", DefaultValue = 4.0, Group = "Parameters")] + public double AtrMultiplier { get; set; } + + [Parameter("Volume (Units)", DefaultValue = 10000, MinValue = 1000, Group = "Parameters")] + public double VolumeInUnits { get; set; } + + [Parameter("Wick Ratio (Lower > Upper * Ratio)", DefaultValue = 2.0, Group = "Parameters")] + public double WickRatio { get; set; } + + [Parameter("Trade Label", DefaultValue = "HmaBiasStrategy", Group = "Parameters")] + public string Label { get; set; } + + // Hull Moving Average for trend direction and momentum. + private HullMovingAverage _hma; + // Average True Range for Stop Loss calculation. + private AverageTrueRange _atr; + + // Holds the currently open position. + private Position _position; + // Tracks the bar index when a position was opened. + private int _entryBarIndex; + + // Initialize indicators and state variables. + protected override void OnStart() + { + _hma = Indicators.HullMovingAverage(Bars.ClosePrices, HmaPeriod); + _atr = Indicators.AverageTrueRange(AtrPeriod, MovingAverageType.Simple); + _position = null; + _entryBarIndex = -1; + } + + // Main logic, executed on every bar close. + protected override void OnBar() + { + // First, check if we need to close the existing position. + if ((_position != null) && (Bars.Count - 1 > _entryBarIndex)) + { + ClosePosition(_position); + _position = null; + } + + // If no position is open, check for a new entry signal. + if (_position == null) + { + // Ensure we have enough data for the lookback. + if (Bars.Count < HmaPeriod + 5) + { + return; + } + + // --- Bias Calculation on PREVIOUS bar --- + var hmaPrev = _hma.Result.Last(2); + var hmaPrev2 = _hma.Result.Last(3); + var hmaPrev3 = _hma.Result.Last(4); + + var slopeOnPrevBar = hmaPrev - hmaPrev2; + var slopeOnPrevBar2 = hmaPrev2 - hmaPrev3; + + // Check if the bias was bullish on the bar that just closed before this one. + bool previousBarHadBullishBias = (slopeOnPrevBar > 0) && (slopeOnPrevBar > slopeOnPrevBar2); + + if (previousBarHadBullishBias) + { + // --- Signal Check on CURRENT bar --- + var currentBar = Bars.LastBar; + var previousBar = Bars.Last(1); // This is the bar that had the bias + bool signalTriggered = false; + + // Signal 1: Current bar's close is above the previous bar's high. + if (currentBar.Close > previousBar.High) + { + signalTriggered = true; + } + + // Signal 2: Current bar is a bullish candle with a long lower wick. + bool isBullishCandle = currentBar.Close > currentBar.Open; + if ((!signalTriggered) && (isBullishCandle)) + { + double lowerWick = currentBar.Open - currentBar.Low; + double upperWick = currentBar.High - currentBar.Close; + if (lowerWick > (upperWick * WickRatio)) + { + signalTriggered = true; + } + } + + // --- Execute Trade --- + if (signalTriggered) + { + var volumeInUnits = Symbol.NormalizeVolumeInUnits(VolumeInUnits, RoundingMode.ToNearest); + var stopLossPips = _atr.Result.LastValue * AtrMultiplier / Symbol.PipSize; + var result = ExecuteMarketOrder(TradeType.Buy, Symbol.Name, volumeInUnits, Label, stopLossPips, null); + + if (result.IsSuccessful) + { + _position = result.Position; + _entryBarIndex = Bars.Count - 1; + } + } + } + } + } + + // Ensure any open position is closed when the cBot stops. + protected override void OnStop() + { + if (_position != null) + { + ClosePosition(_position); + _position = null; + } + } + } +} \ No newline at end of file diff --git a/Sources/Robots/Hma20CandleStartegy_V1/Hma20CandleStartegy_V1/Hma20CandleStartegy_V1.csproj b/Sources/Robots/Hma20CandleStartegy_V1/Hma20CandleStartegy_V1/Hma20CandleStartegy_V1.csproj new file mode 100644 index 0000000..da1fef1 --- /dev/null +++ b/Sources/Robots/Hma20CandleStartegy_V1/Hma20CandleStartegy_V1/Hma20CandleStartegy_V1.csproj @@ -0,0 +1,9 @@ + + + net6.0 + + + + + + diff --git a/Sources/Robots/Hma20CandleStartegy_V1/Hma20CandleStartegy_V1/obj/Debug/net6.0/.NETCoreApp,Version=v6.0.AssemblyAttributes.cs b/Sources/Robots/Hma20CandleStartegy_V1/Hma20CandleStartegy_V1/obj/Debug/net6.0/.NETCoreApp,Version=v6.0.AssemblyAttributes.cs new file mode 100644 index 0000000..32c95f9 --- /dev/null +++ b/Sources/Robots/Hma20CandleStartegy_V1/Hma20CandleStartegy_V1/obj/Debug/net6.0/.NETCoreApp,Version=v6.0.AssemblyAttributes.cs @@ -0,0 +1,4 @@ +// +using System; +using System.Reflection; +[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v6.0", FrameworkDisplayName = "")] diff --git a/Sources/Robots/Hma20CandleStartegy_V1/Hma20CandleStartegy_V1/obj/Debug/net6.0/Hma20CandleStartegy_V1.AssemblyInfo.cs b/Sources/Robots/Hma20CandleStartegy_V1/Hma20CandleStartegy_V1/obj/Debug/net6.0/Hma20CandleStartegy_V1.AssemblyInfo.cs new file mode 100644 index 0000000..d4bfd27 --- /dev/null +++ b/Sources/Robots/Hma20CandleStartegy_V1/Hma20CandleStartegy_V1/obj/Debug/net6.0/Hma20CandleStartegy_V1.AssemblyInfo.cs @@ -0,0 +1,22 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +using System; +using System.Reflection; + +[assembly: System.Reflection.AssemblyCompanyAttribute("Hma20CandleStartegy_V1")] +[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] +[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0")] +[assembly: System.Reflection.AssemblyProductAttribute("Hma20CandleStartegy_V1")] +[assembly: System.Reflection.AssemblyTitleAttribute("Hma20CandleStartegy_V1")] +[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] + +// Generated by the MSBuild WriteCodeFragment class. + diff --git a/Sources/Robots/Hma20CandleStartegy_V1/Hma20CandleStartegy_V1/obj/Debug/net6.0/Hma20CandleStartegy_V1.AssemblyInfoInputs.cache b/Sources/Robots/Hma20CandleStartegy_V1/Hma20CandleStartegy_V1/obj/Debug/net6.0/Hma20CandleStartegy_V1.AssemblyInfoInputs.cache new file mode 100644 index 0000000..66c1358 --- /dev/null +++ b/Sources/Robots/Hma20CandleStartegy_V1/Hma20CandleStartegy_V1/obj/Debug/net6.0/Hma20CandleStartegy_V1.AssemblyInfoInputs.cache @@ -0,0 +1 @@ +ae0e2f4e15affc77969c0b7d1a4875435b51d9bc diff --git a/Sources/Robots/Hma20CandleStartegy_V1/Hma20CandleStartegy_V1/obj/Debug/net6.0/Hma20CandleStartegy_V1.GeneratedMSBuildEditorConfig.editorconfig b/Sources/Robots/Hma20CandleStartegy_V1/Hma20CandleStartegy_V1/obj/Debug/net6.0/Hma20CandleStartegy_V1.GeneratedMSBuildEditorConfig.editorconfig new file mode 100644 index 0000000..2e081dd --- /dev/null +++ b/Sources/Robots/Hma20CandleStartegy_V1/Hma20CandleStartegy_V1/obj/Debug/net6.0/Hma20CandleStartegy_V1.GeneratedMSBuildEditorConfig.editorconfig @@ -0,0 +1,10 @@ +is_global = true +build_property.TargetFramework = net6.0 +build_property.TargetPlatformMinVersion = +build_property.UsingMicrosoftNETSdkWeb = +build_property.ProjectTypeGuids = +build_property.InvariantGlobalization = +build_property.PlatformNeutralAssembly = +build_property._SupportedPlatformList = Linux,macOS,Windows +build_property.RootNamespace = Hma20CandleStartegy_V1 +build_property.ProjectDir = C:\Users\Brummel\Documents\cAlgo\Sources\Robots\Hma20CandleStartegy_V1\Hma20CandleStartegy_V1\ diff --git a/Sources/Robots/Hma20CandleStartegy_V1/Hma20CandleStartegy_V1/obj/Debug/net6.0/Hma20CandleStartegy_V1.assets.cache b/Sources/Robots/Hma20CandleStartegy_V1/Hma20CandleStartegy_V1/obj/Debug/net6.0/Hma20CandleStartegy_V1.assets.cache new file mode 100644 index 0000000..c3001a0 Binary files /dev/null and b/Sources/Robots/Hma20CandleStartegy_V1/Hma20CandleStartegy_V1/obj/Debug/net6.0/Hma20CandleStartegy_V1.assets.cache differ diff --git a/Sources/Robots/Hma20CandleStartegy_V1/Hma20CandleStartegy_V1/obj/Debug/net6.0/Hma20CandleStartegy_V1.csproj.AssemblyReference.cache b/Sources/Robots/Hma20CandleStartegy_V1/Hma20CandleStartegy_V1/obj/Debug/net6.0/Hma20CandleStartegy_V1.csproj.AssemblyReference.cache new file mode 100644 index 0000000..bea28c3 Binary files /dev/null and b/Sources/Robots/Hma20CandleStartegy_V1/Hma20CandleStartegy_V1/obj/Debug/net6.0/Hma20CandleStartegy_V1.csproj.AssemblyReference.cache differ diff --git a/Sources/Robots/Hma20CandleStartegy_V1/Hma20CandleStartegy_V1/obj/Hma20CandleStartegy_V1.csproj.nuget.dgspec.json b/Sources/Robots/Hma20CandleStartegy_V1/Hma20CandleStartegy_V1/obj/Hma20CandleStartegy_V1.csproj.nuget.dgspec.json new file mode 100644 index 0000000..e3931fd --- /dev/null +++ b/Sources/Robots/Hma20CandleStartegy_V1/Hma20CandleStartegy_V1/obj/Hma20CandleStartegy_V1.csproj.nuget.dgspec.json @@ -0,0 +1,66 @@ +{ + "format": 1, + "restore": { + "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\Hma20CandleStartegy_V1\\Hma20CandleStartegy_V1\\Hma20CandleStartegy_V1.csproj": {} + }, + "projects": { + "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\Hma20CandleStartegy_V1\\Hma20CandleStartegy_V1\\Hma20CandleStartegy_V1.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\Hma20CandleStartegy_V1\\Hma20CandleStartegy_V1\\Hma20CandleStartegy_V1.csproj", + "projectName": "Hma20CandleStartegy_V1", + "projectPath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\Hma20CandleStartegy_V1\\Hma20CandleStartegy_V1\\Hma20CandleStartegy_V1.csproj", + "packagesPath": "C:\\Users\\Brummel\\.nuget\\packages\\", + "outputPath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\Hma20CandleStartegy_V1\\Hma20CandleStartegy_V1\\obj\\", + "projectStyle": "PackageReference", + "configFilePaths": [ + "C:\\Users\\Brummel\\AppData\\Roaming\\NuGet\\NuGet.Config" + ], + "originalTargetFrameworks": [ + "net6.0" + ], + "sources": { + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net6.0": { + "targetAlias": "net6.0", + "projectReferences": {} + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + } + }, + "frameworks": { + "net6.0": { + "targetAlias": "net6.0", + "dependencies": { + "cTrader.Automate": { + "target": "Package", + "version": "[*, )" + } + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\6.0.200\\RuntimeIdentifierGraph.json" + } + } + } + } +} \ No newline at end of file diff --git a/Sources/Robots/Hma20CandleStartegy_V1/Hma20CandleStartegy_V1/obj/Hma20CandleStartegy_V1.csproj.nuget.g.props b/Sources/Robots/Hma20CandleStartegy_V1/Hma20CandleStartegy_V1/obj/Hma20CandleStartegy_V1.csproj.nuget.g.props new file mode 100644 index 0000000..d551163 --- /dev/null +++ b/Sources/Robots/Hma20CandleStartegy_V1/Hma20CandleStartegy_V1/obj/Hma20CandleStartegy_V1.csproj.nuget.g.props @@ -0,0 +1,21 @@ + + + + True + NuGet + $(MSBuildThisFileDirectory)project.assets.json + $(UserProfile)\.nuget\packages\ + C:\Users\Brummel\.nuget\packages\ + PackageReference + 6.1.0 + + + + + + + + + C:\Users\Brummel\.nuget\packages\ctrader.automate\1.0.13 + + \ No newline at end of file diff --git a/Sources/Robots/Hma20CandleStartegy_V1/Hma20CandleStartegy_V1/obj/Hma20CandleStartegy_V1.csproj.nuget.g.targets b/Sources/Robots/Hma20CandleStartegy_V1/Hma20CandleStartegy_V1/obj/Hma20CandleStartegy_V1.csproj.nuget.g.targets new file mode 100644 index 0000000..13634e8 --- /dev/null +++ b/Sources/Robots/Hma20CandleStartegy_V1/Hma20CandleStartegy_V1/obj/Hma20CandleStartegy_V1.csproj.nuget.g.targets @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Sources/Robots/Hma20CandleStartegy_V1/Hma20CandleStartegy_V1/obj/project.assets.json b/Sources/Robots/Hma20CandleStartegy_V1/Hma20CandleStartegy_V1/obj/project.assets.json new file mode 100644 index 0000000..d4c456d --- /dev/null +++ b/Sources/Robots/Hma20CandleStartegy_V1/Hma20CandleStartegy_V1/obj/project.assets.json @@ -0,0 +1,139 @@ +{ + "version": 3, + "targets": { + "net6.0": { + "cTrader.Automate/1.0.13": { + "type": "package", + "compile": { + "lib/net6.0/cAlgo.API.dll": {} + }, + "runtime": { + "lib/net6.0/cAlgo.API.dll": {} + }, + "build": { + "build/cTrader.Automate.props": {}, + "build/cTrader.Automate.targets": {} + } + } + } + }, + "libraries": { + "cTrader.Automate/1.0.13": { + "sha512": "teFQQhvFb8/1XQsvvGXNlUIkwZ6Ab+FZHqJ8kXiR2AW8IJA0EIue2dRHNglmlRdqSwnMhocSS9EkPZBTXxsHLQ==", + "type": "package", + "path": "ctrader.automate/1.0.13", + "hasTools": true, + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "build/cTrader.Automate.props", + "build/cTrader.Automate.targets", + "ctrader.automate.1.0.13.nupkg.sha512", + "ctrader.automate.nuspec", + "eula.md", + "icon.png", + "lib/net40/cAlgo.API.dll", + "lib/net40/cAlgo.API.xml", + "lib/net6.0/cAlgo.API.dll", + "lib/net6.0/cAlgo.API.xml", + "tools/net472/Core.AlgoFormat.Compose.Reflection.dll", + "tools/net472/Core.AlgoFormat.Writer.dll", + "tools/net472/Core.AlgoFormat.dll", + "tools/net472/Core.Domain.Primitives.dll", + "tools/net472/Newtonsoft.Json.dll", + "tools/net472/System.Buffers.dll", + "tools/net472/System.Collections.Immutable.dll", + "tools/net472/System.Memory.dll", + "tools/net472/System.Numerics.Vectors.dll", + "tools/net472/System.Reflection.Metadata.dll", + "tools/net472/System.Reflection.MetadataLoadContext.dll", + "tools/net472/System.Runtime.CompilerServices.Unsafe.dll", + "tools/net472/cTrader.Automate.Sdk.Tasks.dll", + "tools/net6.0/Core.AlgoFormat.Compose.Reflection.dll", + "tools/net6.0/Core.AlgoFormat.Writer.dll", + "tools/net6.0/Core.AlgoFormat.dll", + "tools/net6.0/Core.Connection.Protobuf.Common.dll", + "tools/net6.0/Core.Domain.Primitives.dll", + "tools/net6.0/Microsoft.Win32.SystemEvents.dll", + "tools/net6.0/Newtonsoft.Json.dll", + "tools/net6.0/System.Drawing.Common.dll", + "tools/net6.0/System.Reflection.MetadataLoadContext.dll", + "tools/net6.0/System.Security.Permissions.dll", + "tools/net6.0/System.Windows.Extensions.dll", + "tools/net6.0/cTrader.Automate.Sdk.Tasks.dll", + "tools/net6.0/protobuf-net.Core.dll", + "tools/net6.0/protobuf-net.dll", + "tools/net6.0/runtimes/unix/lib/net6.0/System.Drawing.Common.dll", + "tools/net6.0/runtimes/win/lib/net6.0/Microsoft.Win32.SystemEvents.dll", + "tools/net6.0/runtimes/win/lib/net6.0/System.Drawing.Common.dll", + "tools/net6.0/runtimes/win/lib/net6.0/System.Windows.Extensions.dll" + ] + } + }, + "projectFileDependencyGroups": { + "net6.0": [ + "cTrader.Automate >= *" + ] + }, + "packageFolders": { + "C:\\Users\\Brummel\\.nuget\\packages\\": {} + }, + "project": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\Hma20CandleStartegy_V1\\Hma20CandleStartegy_V1\\Hma20CandleStartegy_V1.csproj", + "projectName": "Hma20CandleStartegy_V1", + "projectPath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\Hma20CandleStartegy_V1\\Hma20CandleStartegy_V1\\Hma20CandleStartegy_V1.csproj", + "packagesPath": "C:\\Users\\Brummel\\.nuget\\packages\\", + "outputPath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\Hma20CandleStartegy_V1\\Hma20CandleStartegy_V1\\obj\\", + "projectStyle": "PackageReference", + "configFilePaths": [ + "C:\\Users\\Brummel\\AppData\\Roaming\\NuGet\\NuGet.Config" + ], + "originalTargetFrameworks": [ + "net6.0" + ], + "sources": { + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net6.0": { + "targetAlias": "net6.0", + "projectReferences": {} + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + } + }, + "frameworks": { + "net6.0": { + "targetAlias": "net6.0", + "dependencies": { + "cTrader.Automate": { + "target": "Package", + "version": "[*, )" + } + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\6.0.200\\RuntimeIdentifierGraph.json" + } + } + } +} \ No newline at end of file diff --git a/Sources/Robots/Hma20CandleStartegy_V1/Hma20CandleStartegy_V1/obj/project.nuget.cache b/Sources/Robots/Hma20CandleStartegy_V1/Hma20CandleStartegy_V1/obj/project.nuget.cache new file mode 100644 index 0000000..7ae9eae --- /dev/null +++ b/Sources/Robots/Hma20CandleStartegy_V1/Hma20CandleStartegy_V1/obj/project.nuget.cache @@ -0,0 +1,10 @@ +{ + "version": 2, + "dgSpecHash": "KAVywmsuuoI+N5Yoz0EqytZDCBvv/kG4rq4cxtRVij8d4jb5sQvxFq6zhsdVSKst9ebEbkC7kERvAUjuJ6BsPw==", + "success": true, + "projectFilePath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\Hma20CandleStartegy_V1\\Hma20CandleStartegy_V1\\Hma20CandleStartegy_V1.csproj", + "expectedPackageFiles": [ + "C:\\Users\\Brummel\\.nuget\\packages\\ctrader.automate\\1.0.13\\ctrader.automate.1.0.13.nupkg.sha512" + ], + "logs": [] +} \ No newline at end of file diff --git a/Sources/Robots/HmaClusterBot.algo b/Sources/Robots/HmaClusterBot.algo new file mode 100644 index 0000000..c70f58c Binary files /dev/null and b/Sources/Robots/HmaClusterBot.algo differ diff --git a/Sources/Robots/HmaClusterBot/.vscode/tasks.json b/Sources/Robots/HmaClusterBot/.vscode/tasks.json new file mode 100644 index 0000000..215166e --- /dev/null +++ b/Sources/Robots/HmaClusterBot/.vscode/tasks.json @@ -0,0 +1 @@ +{"version":"2.0.0","tasks":[{"label":"build","command":"dotnet","type":"process","args":["build","${workspaceFolder}","/property:GenerateFullPaths=true","/consoleLoggerParameters:NoSummary"],"problemMatcher":"$msCompile"}]} \ No newline at end of file diff --git a/Sources/Robots/HmaClusterBot/HmaClusterBot.sln b/Sources/Robots/HmaClusterBot/HmaClusterBot.sln new file mode 100644 index 0000000..da49bf6 --- /dev/null +++ b/Sources/Robots/HmaClusterBot/HmaClusterBot.sln @@ -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}") = "HmaClusterBot", "HmaClusterBot\HmaClusterBot.csproj", "{5ca67298-283e-4125-b2b7-5ca1cceeb315}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {5ca67298-283e-4125-b2b7-5ca1cceeb315}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5ca67298-283e-4125-b2b7-5ca1cceeb315}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5ca67298-283e-4125-b2b7-5ca1cceeb315}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5ca67298-283e-4125-b2b7-5ca1cceeb315}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/Sources/Robots/HmaClusterBot/HmaClusterBot/HmaClusterBot.cs b/Sources/Robots/HmaClusterBot/HmaClusterBot/HmaClusterBot.cs new file mode 100644 index 0000000..6a1f1b9 --- /dev/null +++ b/Sources/Robots/HmaClusterBot/HmaClusterBot/HmaClusterBot.cs @@ -0,0 +1,936 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +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 +{ + [Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)] + public class HmaClusterBot : Robot + { + #region Parameters + + [Parameter("Symbols (CSV)", DefaultValue = "", Group = "Multi Symbol")] + public string SymbolsCsv { get; set; } + + [Parameter("Risk % of Balance", DefaultValue = 1.0, MinValue = 0.1, MaxValue = 10.0, Group = "Risk")] + public double RiskPercent { get; set; } + + [Parameter("Entry Significance %", DefaultValue = 20.0, MinValue = 1.0, Group = "Thresholds")] + public double EntrySigThreshold { get; set; } + + [Parameter("Stop Loss Significance %", DefaultValue = 10.0, MinValue = 1.0, Group = "Thresholds")] + public double SlSigThreshold { get; set; } + + [Parameter("SMA Bias Period", DefaultValue = 200, Group = "Bias")] + public int SmaBiasPeriod { get; set; } + + [Parameter("HMA Bias Period", DefaultValue = 250, Group = "Bias")] + public int HmaBiasPeriod { get; set; } + + [Parameter("Use Bias Deviation Filter", DefaultValue = true, Group = "Bias Filter")] + public bool UseBiasDeviationFilter { get; set; } + + [Parameter("Bias Deviation Avg Period", DefaultValue = 500, Group = "Bias Filter")] + public int BiasDeviationAvgPeriod { get; set; } + + [Parameter("Use Dynamic Position Management", DefaultValue = true, Group = "Management")] + public bool UseDynamicPositionManagement { get; set; } + + [Parameter("Close Profit on Bias Flip", DefaultValue = false, Group = "Management")] + public bool CloseProfitOnBiasFlip { get; set; } + + [Parameter("HMA Cluster Period", DefaultValue = 25, Group = "Clusters")] + public int HmaClusterPeriod { get; set; } + + [Parameter("Cluster Max Points", DefaultValue = 2000, Group = "Clusters")] + public int MaxPoints { get; set; } + + [Parameter("Cluster Decay (Bars)", DefaultValue = 1000, Group = "Clusters")] + public int DecayPeriod { get; set; } + + // --- Telegram Parameters --- + [Parameter("Send Telegram Only", DefaultValue = false, Group = "Telegram")] + public bool SendTelegramOnly { get; set; } + + [Parameter("Telegram Bot Token", DefaultValue = "", Group = "Telegram")] + public string TelegramBotToken { get; set; } + + [Parameter("Telegram Chat ID", DefaultValue = "", Group = "Telegram")] + public string TelegramChatId { get; set; } + + #endregion + + private readonly List _strategies = new List(); + private static readonly HttpClient _httpClient = new HttpClient(); + + protected override void OnStart() + { + try + { + if (string.IsNullOrWhiteSpace(SymbolsCsv)) + { + Print("CSV is empty. Running in Single-Symbol Mode on Chart Symbol."); + + var config = new StrategyConfig + { + SymbolName = SymbolName, + EntrySigThreshold = EntrySigThreshold, + SlSigThreshold = SlSigThreshold, + SmaBiasPeriod = SmaBiasPeriod, + HmaBiasPeriod = HmaBiasPeriod, + HmaClusterPeriod = HmaClusterPeriod, + UseBiasDeviationFilter = UseBiasDeviationFilter, + BiasDeviationAvgPeriod = BiasDeviationAvgPeriod, + UseDynamicPositionManagement = UseDynamicPositionManagement, + CloseProfitOnBiasFlip = CloseProfitOnBiasFlip, + SendTelegramOnly = SendTelegramOnly, + RiskPercent = RiskPercent, + MaxPoints = MaxPoints, + DecayPeriod = DecayPeriod + }; + + var strategy = new ClusterStrategy(this, Symbol, Bars, config, _httpClient, TelegramBotToken, TelegramChatId, BroadcastError); + _strategies.Add(strategy); + strategy.Start(); + } + else + { + Print("CSV detected. Running in Multi-Symbol Mode."); + ParseCsvAndCreateStrategies(); + } + } + catch (Exception ex) + { + BroadcastError($"CRITICAL STARTUP ERROR: {ex.Message}"); + Stop(); + } + } + + protected override void OnStop() + { + foreach (var strategy in _strategies) + { + strategy.Stop(); + } + } + + protected override void OnError(Error error) + { + BroadcastError($"TRADING ERROR [{error.Code}]: {error.ToString}"); + } + + public void BroadcastError(string message) + { + Print(message); + + if (!string.IsNullOrWhiteSpace(TelegramBotToken) && !string.IsNullOrWhiteSpace(TelegramChatId)) + { + string formattedMsg = $"⚠️ ERROR @ {DateTime.UtcNow:HH:mm:ss} UTC\n\n{message}"; + _ = SendTelegramRawAsync(TelegramBotToken, TelegramChatId, formattedMsg); + } + } + + private async Task SendTelegramRawAsync(string token, string chatId, string message) + { + try + { + string url = $"https://api.telegram.org/bot{token}/sendMessage?chat_id={chatId}&text={Uri.EscapeDataString(message)}&parse_mode=HTML"; + await _httpClient.GetAsync(url); + } + catch (Exception ex) + { + Print("FAILED TO SEND TELEGRAM ERROR: " + ex.Message); + } + } + + private void ParseCsvAndCreateStrategies() + { + var normalizedCsv = SymbolsCsv.Replace("\n", ",").Replace("\r", ","); + + var tokens = normalizedCsv.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) + .Select(t => t.Trim()) + .Where(t => !string.IsNullOrEmpty(t)) + .ToArray(); + + int paramsPerSymbol = 10; + + if (tokens.Length % paramsPerSymbol != 0) + { + string err = $"CSV Token count ({tokens.Length}) is not a multiple of {paramsPerSymbol}. Check format."; + BroadcastError(err); + } + + for (int i = 0; i < tokens.Length; i += paramsPerSymbol) + { + if (i + paramsPerSymbol > tokens.Length) break; + + string symName = tokens[i]; + + try + { + Symbol symbol = Symbols.GetSymbol(symName); + if (symbol == null) + { + BroadcastError($"Symbol '{symName}' not found in cTrader."); + continue; + } + + Bars bars = MarketData.GetBars(TimeFrame, symName); + + var config = new StrategyConfig + { + SymbolName = symName, + EntrySigThreshold = double.Parse(tokens[i + 1], CultureInfo.InvariantCulture), + SlSigThreshold = double.Parse(tokens[i + 2], CultureInfo.InvariantCulture), + SmaBiasPeriod = int.Parse(tokens[i + 3], CultureInfo.InvariantCulture), + HmaBiasPeriod = int.Parse(tokens[i + 4], CultureInfo.InvariantCulture), + HmaClusterPeriod = int.Parse(tokens[i + 5], CultureInfo.InvariantCulture), + UseBiasDeviationFilter = bool.Parse(tokens[i + 6]), + BiasDeviationAvgPeriod = int.Parse(tokens[i + 7], CultureInfo.InvariantCulture), + UseDynamicPositionManagement = bool.Parse(tokens[i + 8]), + SendTelegramOnly = bool.Parse(tokens[i + 9]), + + // Defaults from global params + CloseProfitOnBiasFlip = CloseProfitOnBiasFlip, + RiskPercent = RiskPercent, + MaxPoints = MaxPoints, + DecayPeriod = DecayPeriod + }; + + var strategy = new ClusterStrategy(this, symbol, bars, config, _httpClient, TelegramBotToken, TelegramChatId, BroadcastError); + _strategies.Add(strategy); + strategy.Start(); + + Print("Initialized Strategy for {0}", symName); + } + catch (Exception ex) + { + BroadcastError($"Config Error for '{symName}': {ex.Message}"); + } + } + } + } + + public class StrategyConfig + { + public string SymbolName { get; set; } + public double EntrySigThreshold { get; set; } + public double SlSigThreshold { get; set; } + public int SmaBiasPeriod { get; set; } + public int HmaBiasPeriod { get; set; } + public int HmaClusterPeriod { get; set; } + public bool UseBiasDeviationFilter { get; set; } + public int BiasDeviationAvgPeriod { get; set; } + public bool UseDynamicPositionManagement { get; set; } + public bool CloseProfitOnBiasFlip { get; set; } + public bool SendTelegramOnly { get; set; } + public double RiskPercent { get; set; } + public int MaxPoints { get; set; } + public int DecayPeriod { get; set; } + } + + public class ClusterStrategy + { + #region Types & Fields + + private enum PointType { Peak, Trough } + private enum Bias { Long, Short, Neutral } + + private struct ExtremumPoint + { + public double Price; + public int Index; + public PointType Type; + } + + public struct ClusterLevel + { + public double Price; + public double Significance; + } + + private readonly Robot _robot; + private readonly Symbol _symbol; + private readonly Bars _bars; + private readonly StrategyConfig _config; + private readonly HttpClient _httpClient; + private readonly string _botToken; + private readonly string _chatId; + private readonly Action _errorCallback; + + private SimpleMovingAverage _smaBias; + private HullMovingAverage _hmaBias; + private HullMovingAverage _hmaCluster; + + private readonly Queue _deviationQueue = new Queue(); + private double _runningDeviationSum; + + private bool _deviationConditionMetInCurrentCycle; + private bool _isTradingAllowedBasedOnPrevCycle; + + private readonly List _extremaPoints = new(); + private readonly List _amplitudes = new(); + + private double _trendExtremum; + private int _trendExtremumIndex; + private double _lastExtremumPrice; + private bool? _isUpTrend; + private double _currentDynamicRange; + + private const string Label = "HmaClusterBot"; + + #endregion + + public ClusterStrategy( + Robot robot, + Symbol symbol, + Bars bars, + StrategyConfig config, + HttpClient httpClient, + string token, + string chatId, + Action errorCallback) + { + _robot = robot; + _symbol = symbol; + _bars = bars; + _config = config; + _httpClient = httpClient; + _botToken = token; + _chatId = chatId; + _errorCallback = errorCallback; + } + + public void Start() + { + int requiredBars = Math.Max(_config.SmaBiasPeriod, _config.HmaBiasPeriod); + requiredBars = Math.Max(requiredBars, _config.BiasDeviationAvgPeriod) + 10; + + while (_bars.Count < requiredBars) + { + int loaded = _bars.LoadMoreHistory(); + if (loaded == 0) + { + _errorCallback?.Invoke($"Not enough history for {_config.SymbolName}. Loaded: {_bars.Count}, Req: {requiredBars}"); + return; + } + } + + _smaBias = _robot.Indicators.SimpleMovingAverage(_bars.ClosePrices, _config.SmaBiasPeriod); + _hmaBias = _robot.Indicators.HullMovingAverage(_bars.ClosePrices, _config.HmaBiasPeriod); + _hmaCluster = _robot.Indicators.HullMovingAverage(_bars.ClosePrices, _config.HmaClusterPeriod); + + _currentDynamicRange = 5.0 * _symbol.PipSize; + _deviationConditionMetInCurrentCycle = false; + _isTradingAllowedBasedOnPrevCycle = false; + + int startIndex = Math.Max(_config.SmaBiasPeriod, _config.HmaBiasPeriod); + startIndex = Math.Max(startIndex, _config.BiasDeviationAvgPeriod); + + for (int i = startIndex; i < _bars.Count; i++) + { + UpdateFilterState(i); + UpdateClusterData(i); + } + + _bars.BarOpened += OnBarOpened; + } + + public void Stop() + { + _bars.BarOpened -= OnBarOpened; + } + + private void OnBarOpened(BarOpenedEventArgs obj) + { + try + { + int index = _bars.Count - 2; + if (index < _config.BiasDeviationAvgPeriod) return; + + UpdateFilterState(index); + UpdateClusterData(index); + + var clusters = CalculateClusters(index); + + // 1. Dynamic SL/TP Management + if (!_config.SendTelegramOnly && _config.UseDynamicPositionManagement && clusters.Count >= 2) + { + ManagePositions(clusters); + } + + var currentBias = GetCurrentBias(index); + + // 2. Check for Profit Close on Bias Flip + if (!_config.SendTelegramOnly && _config.CloseProfitOnBiasFlip) + { + CloseReversedPositions(currentBias); + } + + // 3. New Entry Logic with FIXED Orphan-Cleanup and Validations + ManageOrders(currentBias, index, clusters); + } + catch (Exception ex) + { + _errorCallback?.Invoke($"Runtime Error ({_config.SymbolName}): {ex.Message}\n{ex.StackTrace}"); + } + } + + private void CloseReversedPositions(Bias currentBias) + { + if (currentBias == Bias.Neutral) return; + + foreach (var pos in _robot.Positions) + { + if (pos.SymbolName != _config.SymbolName || pos.Label != Label) continue; + if (pos.NetProfit <= 0) continue; + + bool close = false; + + if (currentBias == Bias.Short && pos.TradeType == TradeType.Buy) + close = true; + else if (currentBias == Bias.Long && pos.TradeType == TradeType.Sell) + close = true; + + if (close) + { + var result = _robot.ClosePosition(pos); + if (result.IsSuccessful) + { + string msg = $"🔒 CLOSE PROFIT (Bias Flip) @ {_config.SymbolName}\n" + + $"Profit: {pos.NetProfit:F2}"; + _ = SendTelegramMessageAsync(msg); + } + } + } + } + + private void UpdateFilterState(int index) + { + double currHma = _hmaBias.Result[index]; + double currSma = _smaBias.Result[index]; + double prevHma = _hmaBias.Result[index - 1]; + double prevSma = _smaBias.Result[index - 1]; + + double currentBiasDeviation = Math.Abs(currHma - currSma); + + _deviationQueue.Enqueue(currentBiasDeviation); + _runningDeviationSum += currentBiasDeviation; + + if (_deviationQueue.Count > _config.BiasDeviationAvgPeriod) + { + double removed = _deviationQueue.Dequeue(); + _runningDeviationSum -= removed; + } + + double averageDeviation = (_deviationQueue.Count > 0) + ? _runningDeviationSum / _deviationQueue.Count + : 0.0; + + bool currHmaAbove = currHma > currSma; + bool prevHmaAbove = prevHma > prevSma; + + if (currHmaAbove != prevHmaAbove) + { + _isTradingAllowedBasedOnPrevCycle = _deviationConditionMetInCurrentCycle; + _deviationConditionMetInCurrentCycle = false; + } + + if (currentBiasDeviation > averageDeviation) + { + _deviationConditionMetInCurrentCycle = true; + } + } + + private Bias GetCurrentBias(int index) + { + double hma = _hmaBias.Result[index]; + double sma = _smaBias.Result[index]; + double prevSma = _smaBias.Result[index - 1]; + + if ((hma > sma) && (sma > prevSma)) return Bias.Long; + if ((hma < sma) && (sma < prevSma)) return Bias.Short; + + return Bias.Neutral; + } + + private void ManagePositions(List clusters) + { + double pNow = _symbol.Bid; + + foreach (var pos in _robot.Positions) + { + if (pos.Label != Label || pos.SymbolName != _config.SymbolName) continue; + + ClusterLevel? newSl = null; + ClusterLevel? newTp = null; + + if (pos.TradeType == TradeType.Buy) + { + double highestSlPrice = double.MinValue; + double highestTpPrice = double.MinValue; + + foreach (var c in clusters) + { + if (c.Price < pNow && c.Significance < _config.SlSigThreshold && c.Price > highestSlPrice) + { + newSl = c; + highestSlPrice = c.Price; + } + if (c.Price > pNow && c.Significance >= _config.EntrySigThreshold && c.Price > highestTpPrice) + { + newTp = c; + highestTpPrice = c.Price; + } + } + + double proposedSl = (newSl.HasValue) ? newSl.Value.Price : (pos.StopLoss ?? 0); + double proposedTp = (newTp.HasValue) ? newTp.Value.Price : (pos.TakeProfit ?? 0); + + bool modifySl = pos.StopLoss.HasValue && (proposedSl > pos.StopLoss.Value + _symbol.TickSize); + if (!pos.StopLoss.HasValue && newSl.HasValue) modifySl = true; + + bool modifyTp = newTp.HasValue && Math.Abs(proposedTp - (pos.TakeProfit ?? 0)) > _symbol.TickSize; + + if (modifySl || modifyTp) + { + double finalSl = modifySl ? proposedSl : pos.StopLoss ?? 0; + double finalTp = modifyTp ? proposedTp : pos.TakeProfit ?? 0; + + if (finalSl < _symbol.Bid && (finalTp == 0 || finalTp > _symbol.Bid)) + { + _robot.ModifyPosition(pos, finalSl, finalTp, ProtectionType.Absolute); + } + } + } + else // Sell + { + double lowestSlPrice = double.MaxValue; + double lowestTpPrice = double.MaxValue; + + foreach (var c in clusters) + { + if (c.Price > pNow && c.Significance < _config.SlSigThreshold && c.Price < lowestSlPrice) + { + newSl = c; + lowestSlPrice = c.Price; + } + if (c.Price < pNow && c.Significance >= _config.EntrySigThreshold && c.Price < lowestTpPrice) + { + newTp = c; + lowestTpPrice = c.Price; + } + } + + double proposedSl = (newSl.HasValue) ? newSl.Value.Price : (pos.StopLoss ?? 0); + double proposedTp = (newTp.HasValue) ? newTp.Value.Price : (pos.TakeProfit ?? 0); + + bool modifySl = pos.StopLoss.HasValue && (proposedSl < pos.StopLoss.Value - _symbol.TickSize); + if (!pos.StopLoss.HasValue && newSl.HasValue) modifySl = true; + + bool modifyTp = newTp.HasValue && Math.Abs(proposedTp - (pos.TakeProfit ?? 0)) > _symbol.TickSize; + + if (modifySl || modifyTp) + { + double finalSl = modifySl ? proposedSl : pos.StopLoss ?? 0; + double finalTp = modifyTp ? proposedTp : pos.TakeProfit ?? 0; + + if (finalSl > _symbol.Ask && (finalTp == 0 || finalTp < _symbol.Ask)) + { + _robot.ModifyPosition(pos, finalSl, finalTp, ProtectionType.Absolute); + } + } + } + } + } + + private void ManageOrders(Bias bias, int index, List clusters) + { + // Standard Cleanup: Orders in wrong direction (Bias Change) + CleanupWrongBiasOrders(bias); + + if (_config.UseBiasDeviationFilter && !_isTradingAllowedBasedOnPrevCycle) + return; + + if (bias == Bias.Neutral) return; + + if (!_config.SendTelegramOnly) + { + bool hasLong = _robot.Positions.Any(p => p.SymbolName == _config.SymbolName && p.Label == Label && p.TradeType == TradeType.Buy); + bool hasShort = _robot.Positions.Any(p => p.SymbolName == _config.SymbolName && p.Label == Label && p.TradeType == TradeType.Sell); + + // --- FIX 1: Open Positions Cleanup --- + // If we are already invested, we ensure no pending orders for the same direction are lingering around. + if (bias == Bias.Long && hasLong) + { + CancelPendingOrders(TradeType.Buy); + return; + } + if (bias == Bias.Short && hasShort) + { + CancelPendingOrders(TradeType.Sell); + return; + } + } + + if (clusters.Count < 2) return; + + double pNow = _bars.ClosePrices[index]; + + if (bias == Bias.Short) + ProcessShortSetup(clusters, pNow); + else if (bias == Bias.Long) + ProcessLongSetup(clusters, pNow); + } + + private void CancelPendingOrders(TradeType type) + { + foreach (var order in _robot.PendingOrders) + { + if (order.SymbolName == _config.SymbolName && order.Label == Label && order.TradeType == type) + { + _robot.CancelPendingOrder(order); + } + } + } + + private void CleanupWrongBiasOrders(Bias currentBias) + { + if (_config.SendTelegramOnly) return; + + foreach (var order in _robot.PendingOrders) + { + if (order.Label != Label || order.SymbolName != _config.SymbolName) continue; + + if (currentBias == Bias.Long && order.TradeType == TradeType.Sell) + _robot.CancelPendingOrder(order); + else if (currentBias == Bias.Short && order.TradeType == TradeType.Buy) + _robot.CancelPendingOrder(order); + else if (currentBias == Bias.Neutral) + _robot.CancelPendingOrder(order); + } + } + + private void ProcessShortSetup(List clusters, double pNow) + { + ClusterLevel? entry = null; + ClusterLevel? sl = null; + ClusterLevel? tp = null; + + double highestEntryPrice = double.MinValue; + double lowestSlPrice = double.MaxValue; + double lowestTpPrice = double.MaxValue; + + foreach (var c in clusters) + { + if (c.Price > pNow) + { + if ((c.Significance >= _config.EntrySigThreshold) && (c.Price > highestEntryPrice)) + { + entry = c; + highestEntryPrice = c.Price; + } + if ((c.Significance < _config.SlSigThreshold) && (c.Price < lowestSlPrice)) + { + sl = c; + lowestSlPrice = c.Price; + } + } + } + + if (entry == null || sl == null) return; + + foreach (var c in clusters) + { + if ((c.Price < entry.Value.Price) && (c.Significance >= _config.EntrySigThreshold)) + { + if (c.Price < lowestTpPrice) + { + tp = c; + lowestTpPrice = c.Price; + } + } + } + + if (tp != null && (sl.Value.Price > entry.Value.Price)) + { + double risk = sl.Value.Price - entry.Value.Price; + double reward = entry.Value.Price - tp.Value.Price; + + if (risk <= reward) + UpdateOrPlaceLimitOrder(TradeType.Sell, entry.Value.Price, sl.Value.Price, tp.Value.Price); + } + } + + private void ProcessLongSetup(List clusters, double pNow) + { + ClusterLevel? entry = null; + ClusterLevel? sl = null; + ClusterLevel? tp = null; + + double lowestEntryPrice = double.MaxValue; + double highestSlPrice = double.MinValue; + double highestTpPrice = double.MinValue; + + foreach (var c in clusters) + { + if (c.Price < pNow) + { + if ((c.Significance >= _config.EntrySigThreshold) && (c.Price < lowestEntryPrice)) + { + entry = c; + lowestEntryPrice = c.Price; + } + if ((c.Significance < _config.SlSigThreshold) && (c.Price > highestSlPrice)) + { + sl = c; + highestSlPrice = c.Price; + } + } + } + + if (entry == null || sl == null) return; + + foreach (var c in clusters) + { + if ((c.Price > entry.Value.Price) && (c.Significance >= _config.EntrySigThreshold)) + { + if (c.Price > highestTpPrice) + { + tp = c; + highestTpPrice = c.Price; + } + } + } + + if (tp != null && (sl.Value.Price < entry.Value.Price)) + { + double risk = entry.Value.Price - sl.Value.Price; + double reward = tp.Value.Price - entry.Value.Price; + + if (risk <= reward) + UpdateOrPlaceLimitOrder(TradeType.Buy, entry.Value.Price, sl.Value.Price, tp.Value.Price); + } + } + + private void UpdateOrPlaceLimitOrder(TradeType type, double entry, double sl, double tp) + { + var existingOrder = _robot.PendingOrders.FirstOrDefault(o => o.SymbolName == _config.SymbolName && o.Label == Label && o.TradeType == type); + + // --- FIX 4: Market Proximity Check --- + // If the limit price is invalid (e.g. Buy Limit above Ask), we must abort/cancel. + bool priceInvalid = false; + double buffer = _symbol.PipSize; + + if (type == TradeType.Buy && entry >= (_symbol.Ask - buffer)) priceInvalid = true; + if (type == TradeType.Sell && entry <= (_symbol.Bid + buffer)) priceInvalid = true; + + if (priceInvalid) + { + if (existingOrder != null) _robot.CancelPendingOrder(existingOrder); + return; + } + + // --- FIX 3: SL Distance Check --- + double slDistPips = Math.Abs(entry - sl) / _symbol.PipSize; + if (slDistPips <= 0) + { + if (existingOrder != null) _robot.CancelPendingOrder(existingOrder); + return; + } + + // --- FIX 2: Volume Check --- + double riskAmount = _robot.Account.Balance * (_config.RiskPercent / 100.0); + double volume = _symbol.VolumeForFixedRisk(riskAmount, slDistPips); + volume = _symbol.NormalizeVolumeInUnits(volume, RoundingMode.Down); + + if (volume < _symbol.VolumeInUnitsMin) + { + if (existingOrder != null) _robot.CancelPendingOrder(existingOrder); + return; + } + + double lots = _symbol.VolumeInUnitsToQuantity(volume); + + if (_config.SendTelegramOnly) + { + string directionStr = type == TradeType.Buy ? "BUY" : "SELL"; + string directionIcon = type == TradeType.Buy ? "📈" : "📉"; + string msg = $"{directionIcon} {directionStr} Signal @ {_config.SymbolName}\n\n" + + $"Entry: {entry}\n" + + $"SL: {sl}\n" + + $"TP: {tp}\n" + + $"Vol: {lots:F2} Lots"; + + _ = SendTelegramMessageAsync(msg); + return; + } + + if (existingOrder != null) + { + bool volumeChanged = Math.Abs(existingOrder.VolumeInUnits - volume) > _symbol.VolumeInUnitsStep; + bool entryChanged = Math.Abs(existingOrder.TargetPrice - entry) > _symbol.TickSize; + bool slChanged = Math.Abs((existingOrder.StopLoss ?? 0) - sl) > _symbol.TickSize; + bool tpChanged = Math.Abs((existingOrder.TakeProfit ?? 0) - tp) > _symbol.TickSize; + + if (volumeChanged || entryChanged || slChanged || tpChanged) + { + var result = _robot.ModifyPendingOrder(existingOrder, entry, sl, tp, ProtectionType.Absolute, null, volume); + if (!result.IsSuccessful) + { + // Fallback: If modify fails (e.g. spread jump), cancel it to avoid stale orders + _robot.CancelPendingOrder(existingOrder); + } + } + } + else + { + _robot.Print("[SIGNAL] {0} {1} | Entry: {2} | SL: {3} | TP: {4} | Vol: {5:F2} Lots", + (type == TradeType.Buy ? "BUY" : "SELL"), _config.SymbolName, entry, sl, tp, lots); + + _robot.PlaceLimitOrder(type, _config.SymbolName, volume, entry, Label, sl, tp, ProtectionType.Absolute); + } + } + + private async Task SendTelegramMessageAsync(string message) + { + if (_robot.IsBacktesting) return; + if (string.IsNullOrWhiteSpace(_botToken) || string.IsNullOrWhiteSpace(_chatId)) return; + + try + { + string url = $"https://api.telegram.org/bot{_botToken}/sendMessage?chat_id={_chatId}&text={Uri.EscapeDataString(message)}&parse_mode=HTML"; + HttpResponseMessage response = await _httpClient.GetAsync(url); + + if (!response.IsSuccessStatusCode) + { + _errorCallback?.Invoke($"Telegram Error: {response.StatusCode}"); + } + } + catch (Exception ex) + { + _errorCallback?.Invoke($"Telegram Exception: {ex.Message}"); + } + } + + private void UpdateClusterData(int index) + { + double currentHma = _hmaCluster.Result[index]; + double prevHma = _hmaCluster.Result[index - 1]; + bool currentDirectionUp = (currentHma > prevHma); + + if (_isUpTrend == null) + { + _isUpTrend = currentDirectionUp; + SetExtremum(index); + _lastExtremumPrice = _trendExtremum; + return; + } + + if (currentDirectionUp != _isUpTrend) + { + double amp = Math.Abs(_trendExtremum - _lastExtremumPrice); + if (amp > 0) + { + _amplitudes.Add(amp); + if (_amplitudes.Count > 50) _amplitudes.RemoveAt(0); + + double sum = 0; + for (int i = 0; i < _amplitudes.Count; i++) sum += _amplitudes[i]; + _currentDynamicRange = (sum / _amplitudes.Count) * 0.5; + } + _extremaPoints.Add(new ExtremumPoint { Price = _trendExtremum, Index = _trendExtremumIndex, Type = _isUpTrend.Value ? PointType.Peak : PointType.Trough }); + if (_extremaPoints.Count > _config.MaxPoints) _extremaPoints.RemoveAt(0); + + _lastExtremumPrice = _trendExtremum; + _isUpTrend = currentDirectionUp; + SetExtremum(index); + } + else + { + UpdateExtremum(index); + } + } + + private void SetExtremum(int index) + { + _trendExtremum = _isUpTrend.Value ? _bars.HighPrices[index] : _bars.LowPrices[index]; + _trendExtremumIndex = index; + } + + private void UpdateExtremum(int index) + { + if (_isUpTrend.Value && (_bars.HighPrices[index] > _trendExtremum)) + { + _trendExtremum = _bars.HighPrices[index]; + _trendExtremumIndex = index; + } + else if (!_isUpTrend.Value && (_bars.LowPrices[index] < _trendExtremum)) + { + _trendExtremum = _bars.LowPrices[index]; + _trendExtremumIndex = index; + } + } + + private List CalculateClusters(int currentIndex) + { + int count = _extremaPoints.Count; + if (count == 0) return new List(); + + double currentPrice = _bars.ClosePrices[currentIndex]; + double[] weights = new double[count]; + double totalWeightSum = 0; + + for (int i = 0; i < count; i++) + { + weights[i] = GetWeight(_extremaPoints[i], currentIndex, currentPrice); + totalWeightSum += weights[i]; + } + + if (totalWeightSum == 0) return new List(); + + var zones = new List(); + for (int i = count - 1; i >= 0; i--) + { + var p = _extremaPoints[i]; + + bool exists = false; + for (int j = 0; j < zones.Count; j++) + { + if (Math.Abs(zones[j].Price - p.Price) < _currentDynamicRange) + { + exists = true; + break; + } + } + if (exists) continue; + + double score = 0; + for (int k = 0; k < count; k++) + { + if (Math.Abs(_extremaPoints[k].Price - p.Price) <= _currentDynamicRange) + { + score += weights[k]; + } + } + + zones.Add(new ClusterLevel { Price = p.Price, Significance = (score / totalWeightSum) * 100.0 }); + } + return zones; + } + + private double GetWeight(ExtremumPoint point, int currentIndex, double currentPrice) + { + double weight = Math.Max(0.0, 1.0 - ((double)(currentIndex - point.Index) / _config.DecayPeriod)); + if ((point.Type == PointType.Peak) && (point.Price < currentPrice)) weight *= 2.0; + else if ((point.Type == PointType.Trough) && (point.Price > currentPrice)) weight *= 2.0; + return weight; + } + } +} \ No newline at end of file diff --git a/Sources/Robots/HmaClusterBot/HmaClusterBot/HmaClusterBot.csproj b/Sources/Robots/HmaClusterBot/HmaClusterBot/HmaClusterBot.csproj new file mode 100644 index 0000000..c8ec96f --- /dev/null +++ b/Sources/Robots/HmaClusterBot/HmaClusterBot/HmaClusterBot.csproj @@ -0,0 +1,9 @@ + + + + net6.0 + + + + + \ No newline at end of file diff --git a/Sources/Robots/HmaClusterBot/HmaClusterBot/obj/Debug/net6.0/.NETCoreApp,Version=v6.0.AssemblyAttributes.cs b/Sources/Robots/HmaClusterBot/HmaClusterBot/obj/Debug/net6.0/.NETCoreApp,Version=v6.0.AssemblyAttributes.cs new file mode 100644 index 0000000..32c95f9 --- /dev/null +++ b/Sources/Robots/HmaClusterBot/HmaClusterBot/obj/Debug/net6.0/.NETCoreApp,Version=v6.0.AssemblyAttributes.cs @@ -0,0 +1,4 @@ +// +using System; +using System.Reflection; +[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v6.0", FrameworkDisplayName = "")] diff --git a/Sources/Robots/HmaClusterBot/HmaClusterBot/obj/Debug/net6.0/HmaClusterBot.AssemblyInfo.cs b/Sources/Robots/HmaClusterBot/HmaClusterBot/obj/Debug/net6.0/HmaClusterBot.AssemblyInfo.cs new file mode 100644 index 0000000..72bd866 --- /dev/null +++ b/Sources/Robots/HmaClusterBot/HmaClusterBot/obj/Debug/net6.0/HmaClusterBot.AssemblyInfo.cs @@ -0,0 +1,22 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +using System; +using System.Reflection; + +[assembly: System.Reflection.AssemblyCompanyAttribute("HmaClusterBot")] +[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] +[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0")] +[assembly: System.Reflection.AssemblyProductAttribute("HmaClusterBot")] +[assembly: System.Reflection.AssemblyTitleAttribute("HmaClusterBot")] +[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] + +// Generated by the MSBuild WriteCodeFragment class. + diff --git a/Sources/Robots/HmaClusterBot/HmaClusterBot/obj/Debug/net6.0/HmaClusterBot.AssemblyInfoInputs.cache b/Sources/Robots/HmaClusterBot/HmaClusterBot/obj/Debug/net6.0/HmaClusterBot.AssemblyInfoInputs.cache new file mode 100644 index 0000000..f3fa1ee --- /dev/null +++ b/Sources/Robots/HmaClusterBot/HmaClusterBot/obj/Debug/net6.0/HmaClusterBot.AssemblyInfoInputs.cache @@ -0,0 +1 @@ +296f6e29070e0927c857564d4a04976a31621cde diff --git a/Sources/Robots/HmaClusterBot/HmaClusterBot/obj/Debug/net6.0/HmaClusterBot.GeneratedMSBuildEditorConfig.editorconfig b/Sources/Robots/HmaClusterBot/HmaClusterBot/obj/Debug/net6.0/HmaClusterBot.GeneratedMSBuildEditorConfig.editorconfig new file mode 100644 index 0000000..5f6e32e --- /dev/null +++ b/Sources/Robots/HmaClusterBot/HmaClusterBot/obj/Debug/net6.0/HmaClusterBot.GeneratedMSBuildEditorConfig.editorconfig @@ -0,0 +1,10 @@ +is_global = true +build_property.TargetFramework = net6.0 +build_property.TargetPlatformMinVersion = +build_property.UsingMicrosoftNETSdkWeb = +build_property.ProjectTypeGuids = +build_property.InvariantGlobalization = +build_property.PlatformNeutralAssembly = +build_property._SupportedPlatformList = Linux,macOS,Windows +build_property.RootNamespace = HmaClusterBot +build_property.ProjectDir = C:\Users\Brummel\Documents\cAlgo\Sources\Robots\HmaClusterBot\HmaClusterBot\ diff --git a/Sources/Robots/HmaClusterBot/HmaClusterBot/obj/Debug/net6.0/HmaClusterBot.assets.cache b/Sources/Robots/HmaClusterBot/HmaClusterBot/obj/Debug/net6.0/HmaClusterBot.assets.cache new file mode 100644 index 0000000..dad4a4e Binary files /dev/null and b/Sources/Robots/HmaClusterBot/HmaClusterBot/obj/Debug/net6.0/HmaClusterBot.assets.cache differ diff --git a/Sources/Robots/HmaClusterBot/HmaClusterBot/obj/Debug/net6.0/HmaClusterBot.csproj.AssemblyReference.cache b/Sources/Robots/HmaClusterBot/HmaClusterBot/obj/Debug/net6.0/HmaClusterBot.csproj.AssemblyReference.cache new file mode 100644 index 0000000..50043b7 Binary files /dev/null and b/Sources/Robots/HmaClusterBot/HmaClusterBot/obj/Debug/net6.0/HmaClusterBot.csproj.AssemblyReference.cache differ diff --git a/Sources/Robots/HmaClusterBot/HmaClusterBot/obj/HmaClusterBot.csproj.nuget.dgspec.json b/Sources/Robots/HmaClusterBot/HmaClusterBot/obj/HmaClusterBot.csproj.nuget.dgspec.json new file mode 100644 index 0000000..69eb6f5 --- /dev/null +++ b/Sources/Robots/HmaClusterBot/HmaClusterBot/obj/HmaClusterBot.csproj.nuget.dgspec.json @@ -0,0 +1,66 @@ +{ + "format": 1, + "restore": { + "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\HmaClusterBot\\HmaClusterBot\\HmaClusterBot.csproj": {} + }, + "projects": { + "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\HmaClusterBot\\HmaClusterBot\\HmaClusterBot.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\HmaClusterBot\\HmaClusterBot\\HmaClusterBot.csproj", + "projectName": "HmaClusterBot", + "projectPath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\HmaClusterBot\\HmaClusterBot\\HmaClusterBot.csproj", + "packagesPath": "C:\\Users\\Brummel\\.nuget\\packages\\", + "outputPath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\HmaClusterBot\\HmaClusterBot\\obj\\", + "projectStyle": "PackageReference", + "configFilePaths": [ + "C:\\Users\\Brummel\\AppData\\Roaming\\NuGet\\NuGet.Config" + ], + "originalTargetFrameworks": [ + "net6.0" + ], + "sources": { + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net6.0": { + "targetAlias": "net6.0", + "projectReferences": {} + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + } + }, + "frameworks": { + "net6.0": { + "targetAlias": "net6.0", + "dependencies": { + "cTrader.Automate": { + "target": "Package", + "version": "[*, )" + } + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\6.0.200\\RuntimeIdentifierGraph.json" + } + } + } + } +} \ No newline at end of file diff --git a/Sources/Robots/HmaClusterBot/HmaClusterBot/obj/HmaClusterBot.csproj.nuget.g.props b/Sources/Robots/HmaClusterBot/HmaClusterBot/obj/HmaClusterBot.csproj.nuget.g.props new file mode 100644 index 0000000..0b161ac --- /dev/null +++ b/Sources/Robots/HmaClusterBot/HmaClusterBot/obj/HmaClusterBot.csproj.nuget.g.props @@ -0,0 +1,21 @@ + + + + True + NuGet + $(MSBuildThisFileDirectory)project.assets.json + $(UserProfile)\.nuget\packages\ + C:\Users\Brummel\.nuget\packages\ + PackageReference + 6.1.0 + + + + + + + + + C:\Users\Brummel\.nuget\packages\ctrader.automate\1.0.14 + + \ No newline at end of file diff --git a/Sources/Robots/HmaClusterBot/HmaClusterBot/obj/HmaClusterBot.csproj.nuget.g.targets b/Sources/Robots/HmaClusterBot/HmaClusterBot/obj/HmaClusterBot.csproj.nuget.g.targets new file mode 100644 index 0000000..46f142c --- /dev/null +++ b/Sources/Robots/HmaClusterBot/HmaClusterBot/obj/HmaClusterBot.csproj.nuget.g.targets @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Sources/Robots/HmaClusterBot/HmaClusterBot/obj/project.assets.json b/Sources/Robots/HmaClusterBot/HmaClusterBot/obj/project.assets.json new file mode 100644 index 0000000..f728640 --- /dev/null +++ b/Sources/Robots/HmaClusterBot/HmaClusterBot/obj/project.assets.json @@ -0,0 +1,139 @@ +{ + "version": 3, + "targets": { + "net6.0": { + "cTrader.Automate/1.0.14": { + "type": "package", + "compile": { + "lib/net6.0/cAlgo.API.dll": {} + }, + "runtime": { + "lib/net6.0/cAlgo.API.dll": {} + }, + "build": { + "build/cTrader.Automate.props": {}, + "build/cTrader.Automate.targets": {} + } + } + } + }, + "libraries": { + "cTrader.Automate/1.0.14": { + "sha512": "eNwE7WL90MGBKb5MuLAtZLdQy0vxkI5EVhLWAQ9S83EAdYkGAzdccvGFLk2oqmtSGBtD+gEpyrJUW/ej4dI4jw==", + "type": "package", + "path": "ctrader.automate/1.0.14", + "hasTools": true, + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "build/cTrader.Automate.props", + "build/cTrader.Automate.targets", + "ctrader.automate.1.0.14.nupkg.sha512", + "ctrader.automate.nuspec", + "eula.md", + "icon.png", + "lib/net40/cAlgo.API.dll", + "lib/net40/cAlgo.API.xml", + "lib/net6.0/cAlgo.API.dll", + "lib/net6.0/cAlgo.API.xml", + "tools/net472/Core.AlgoFormat.Compose.Reflection.dll", + "tools/net472/Core.AlgoFormat.Writer.dll", + "tools/net472/Core.AlgoFormat.dll", + "tools/net472/Core.Domain.Primitives.dll", + "tools/net472/Newtonsoft.Json.dll", + "tools/net472/System.Buffers.dll", + "tools/net472/System.Collections.Immutable.dll", + "tools/net472/System.Memory.dll", + "tools/net472/System.Numerics.Vectors.dll", + "tools/net472/System.Reflection.Metadata.dll", + "tools/net472/System.Reflection.MetadataLoadContext.dll", + "tools/net472/System.Runtime.CompilerServices.Unsafe.dll", + "tools/net472/cTrader.Automate.Sdk.Tasks.dll", + "tools/net6.0/Core.AlgoFormat.Compose.Reflection.dll", + "tools/net6.0/Core.AlgoFormat.Writer.dll", + "tools/net6.0/Core.AlgoFormat.dll", + "tools/net6.0/Core.Connection.Protobuf.Common.dll", + "tools/net6.0/Core.Domain.Primitives.dll", + "tools/net6.0/Microsoft.Win32.SystemEvents.dll", + "tools/net6.0/Newtonsoft.Json.dll", + "tools/net6.0/System.Drawing.Common.dll", + "tools/net6.0/System.Reflection.MetadataLoadContext.dll", + "tools/net6.0/System.Security.Permissions.dll", + "tools/net6.0/System.Windows.Extensions.dll", + "tools/net6.0/cTrader.Automate.Sdk.Tasks.dll", + "tools/net6.0/protobuf-net.Core.dll", + "tools/net6.0/protobuf-net.dll", + "tools/net6.0/runtimes/unix/lib/net6.0/System.Drawing.Common.dll", + "tools/net6.0/runtimes/win/lib/net6.0/Microsoft.Win32.SystemEvents.dll", + "tools/net6.0/runtimes/win/lib/net6.0/System.Drawing.Common.dll", + "tools/net6.0/runtimes/win/lib/net6.0/System.Windows.Extensions.dll" + ] + } + }, + "projectFileDependencyGroups": { + "net6.0": [ + "cTrader.Automate >= *" + ] + }, + "packageFolders": { + "C:\\Users\\Brummel\\.nuget\\packages\\": {} + }, + "project": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\HmaClusterBot\\HmaClusterBot\\HmaClusterBot.csproj", + "projectName": "HmaClusterBot", + "projectPath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\HmaClusterBot\\HmaClusterBot\\HmaClusterBot.csproj", + "packagesPath": "C:\\Users\\Brummel\\.nuget\\packages\\", + "outputPath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\HmaClusterBot\\HmaClusterBot\\obj\\", + "projectStyle": "PackageReference", + "configFilePaths": [ + "C:\\Users\\Brummel\\AppData\\Roaming\\NuGet\\NuGet.Config" + ], + "originalTargetFrameworks": [ + "net6.0" + ], + "sources": { + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net6.0": { + "targetAlias": "net6.0", + "projectReferences": {} + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + } + }, + "frameworks": { + "net6.0": { + "targetAlias": "net6.0", + "dependencies": { + "cTrader.Automate": { + "target": "Package", + "version": "[*, )" + } + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\6.0.200\\RuntimeIdentifierGraph.json" + } + } + } +} \ No newline at end of file diff --git a/Sources/Robots/HmaClusterBot/HmaClusterBot/obj/project.nuget.cache b/Sources/Robots/HmaClusterBot/HmaClusterBot/obj/project.nuget.cache new file mode 100644 index 0000000..4172436 --- /dev/null +++ b/Sources/Robots/HmaClusterBot/HmaClusterBot/obj/project.nuget.cache @@ -0,0 +1,10 @@ +{ + "version": 2, + "dgSpecHash": "NEYKNJ+0pRHXWSIUJ8meJtMDF84xyxPwtc1chms3WBz34cQ0viYha1RgvvteKYETOCh8y4l1tk9Xm9OxaOeitA==", + "success": true, + "projectFilePath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\HmaClusterBot\\HmaClusterBot\\HmaClusterBot.csproj", + "expectedPackageFiles": [ + "C:\\Users\\Brummel\\.nuget\\packages\\ctrader.automate\\1.0.14\\ctrader.automate.1.0.14.nupkg.sha512" + ], + "logs": [] +} \ No newline at end of file diff --git a/Sources/Robots/HmaClusterSR.algo b/Sources/Robots/HmaClusterSR.algo new file mode 100644 index 0000000..e7234d1 Binary files /dev/null and b/Sources/Robots/HmaClusterSR.algo differ diff --git a/Sources/Robots/HmaKcBot.algo b/Sources/Robots/HmaKcBot.algo new file mode 100644 index 0000000..6562343 Binary files /dev/null and b/Sources/Robots/HmaKcBot.algo differ diff --git a/Sources/Robots/HmaKcBot/.vscode/tasks.json b/Sources/Robots/HmaKcBot/.vscode/tasks.json new file mode 100644 index 0000000..215166e --- /dev/null +++ b/Sources/Robots/HmaKcBot/.vscode/tasks.json @@ -0,0 +1 @@ +{"version":"2.0.0","tasks":[{"label":"build","command":"dotnet","type":"process","args":["build","${workspaceFolder}","/property:GenerateFullPaths=true","/consoleLoggerParameters:NoSummary"],"problemMatcher":"$msCompile"}]} \ No newline at end of file diff --git a/Sources/Robots/HmaKcBot/HmaKcBot.sln b/Sources/Robots/HmaKcBot/HmaKcBot.sln new file mode 100644 index 0000000..2dc08ae --- /dev/null +++ b/Sources/Robots/HmaKcBot/HmaKcBot.sln @@ -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}") = "HmaKcBot", "HmaKcBot\HmaKcBot.csproj", "{335ef48b-c961-4e40-97d0-9890a4d69d46}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {335ef48b-c961-4e40-97d0-9890a4d69d46}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {335ef48b-c961-4e40-97d0-9890a4d69d46}.Debug|Any CPU.Build.0 = Debug|Any CPU + {335ef48b-c961-4e40-97d0-9890a4d69d46}.Release|Any CPU.ActiveCfg = Release|Any CPU + {335ef48b-c961-4e40-97d0-9890a4d69d46}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/Sources/Robots/HmaKcBot/HmaKcBot/HmaKcBot.cs b/Sources/Robots/HmaKcBot/HmaKcBot/HmaKcBot.cs new file mode 100644 index 0000000..d466f62 --- /dev/null +++ b/Sources/Robots/HmaKcBot/HmaKcBot/HmaKcBot.cs @@ -0,0 +1,114 @@ +using cAlgo.API; +using cAlgo.API.Indicators; +using System; + +namespace cAlgo.Robots +{ + [Robot(AccessRights = AccessRights.None)] + public class HmaKcBot : Robot + { + [Parameter("HMA Period", DefaultValue = 250)] + public int HmaPeriod { get; set; } + + [Parameter("KC MA Period", DefaultValue = 20)] + public int KcMaPeriod { get; set; } + + [Parameter("KC ATR Period", DefaultValue = 20)] + public int KcAtrPeriod { get; set; } + + [Parameter("KC Multiplier", DefaultValue = 2.0)] + public double KcMultiplier { get; set; } + + [Parameter("Risk Percentage", DefaultValue = 1.0, MinValue = 0.1, MaxValue = 10.0)] + public double RiskPercentage { get; set; } + + [Parameter("Stop Loss (pips)", DefaultValue = 20, MinValue = 1)] + public double StopLossPips { get; set; } + + private HullMovingAverage _hma; + private KeltnerChannels _kc; + private const string Label = "HmaKcBot"; + + protected override void OnStart() + { + _hma = Indicators.HullMovingAverage(Bars.ClosePrices, HmaPeriod); + _kc = Indicators.KeltnerChannels(KcMaPeriod, MovingAverageType.Simple, KcAtrPeriod, MovingAverageType.Hull, KcMultiplier); + } + + protected override void OnBar() + { + var hmaCurrent = _hma.Result.Last(1); + var hmaPrevious = _hma.Result.Last(2); + var hmaOld = _hma.Result.Last(3); + + // Sell Condition + if (hmaPrevious > hmaCurrent && hmaPrevious > hmaOld) + { + ClosePositions(TradeType.Buy); + var position = Positions.Find(Label, SymbolName, TradeType.Sell); + if (position == null) + { + var volumeInUnits = Symbol.VolumeForProportionalRisk(ProportionalAmountType.Equity, RiskPercentage, StopLossPips); + var normalizedVolume = Symbol.NormalizeVolumeInUnits(volumeInUnits, RoundingMode.ToNearest); + + if (normalizedVolume > 0) + { + ExecuteMarketOrder(TradeType.Sell, SymbolName, normalizedVolume, Label, StopLossPips, null); + } + } + } + + // Buy Condition + if (hmaPrevious < hmaCurrent && hmaPrevious < hmaOld) + { + ClosePositions(TradeType.Sell); + var position = Positions.Find(Label, SymbolName, TradeType.Buy); + if (position == null) + { + var volumeInUnits = Symbol.VolumeForProportionalRisk(ProportionalAmountType.Equity, RiskPercentage, StopLossPips); + var normalizedVolume = Symbol.NormalizeVolumeInUnits(volumeInUnits, RoundingMode.ToNearest); + + if (normalizedVolume > 0) + { + ExecuteMarketOrder(TradeType.Buy, SymbolName, normalizedVolume, Label, StopLossPips, null); + } + } + } + + TrailStopLoss(); + } + + private void TrailStopLoss() + { + foreach (var position in Positions.FindAll(Label, SymbolName)) + { + if (position.TradeType == TradeType.Buy) + { + var newStopLoss = Math.Round(_kc.Bottom.Last(1), Symbol.Digits); + if (position.StopLoss == null || newStopLoss > position.StopLoss) + { + // Updated ModifyPosition to include ProtectionType.Absolute. + ModifyPosition(position, newStopLoss, position.TakeProfit, ProtectionType.Absolute); + } + } + else if (position.TradeType == TradeType.Sell) + { + var newStopLoss = Math.Round(_kc.Top.Last(1), Symbol.Digits); + if (position.StopLoss == null || newStopLoss < position.StopLoss) + { + // Updated ModifyPosition to include ProtectionType.Absolute. + ModifyPosition(position, newStopLoss, position.TakeProfit, ProtectionType.Absolute); + } + } + } + } + + private void ClosePositions(TradeType tradeType) + { + foreach (var position in Positions.FindAll(Label, SymbolName, tradeType)) + { + ClosePosition(position); + } + } + } +} \ No newline at end of file diff --git a/Sources/Robots/HmaKcBot/HmaKcBot/HmaKcBot.csproj b/Sources/Robots/HmaKcBot/HmaKcBot/HmaKcBot.csproj new file mode 100644 index 0000000..da1fef1 --- /dev/null +++ b/Sources/Robots/HmaKcBot/HmaKcBot/HmaKcBot.csproj @@ -0,0 +1,9 @@ + + + net6.0 + + + + + + diff --git a/Sources/Robots/HmaKcBot/HmaKcBot/obj/Debug/net6.0/.NETCoreApp,Version=v6.0.AssemblyAttributes.cs b/Sources/Robots/HmaKcBot/HmaKcBot/obj/Debug/net6.0/.NETCoreApp,Version=v6.0.AssemblyAttributes.cs new file mode 100644 index 0000000..32c95f9 --- /dev/null +++ b/Sources/Robots/HmaKcBot/HmaKcBot/obj/Debug/net6.0/.NETCoreApp,Version=v6.0.AssemblyAttributes.cs @@ -0,0 +1,4 @@ +// +using System; +using System.Reflection; +[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v6.0", FrameworkDisplayName = "")] diff --git a/Sources/Robots/HmaKcBot/HmaKcBot/obj/Debug/net6.0/HmaKcBot.AssemblyInfo.cs b/Sources/Robots/HmaKcBot/HmaKcBot/obj/Debug/net6.0/HmaKcBot.AssemblyInfo.cs new file mode 100644 index 0000000..56ee436 --- /dev/null +++ b/Sources/Robots/HmaKcBot/HmaKcBot/obj/Debug/net6.0/HmaKcBot.AssemblyInfo.cs @@ -0,0 +1,22 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +using System; +using System.Reflection; + +[assembly: System.Reflection.AssemblyCompanyAttribute("HmaKcBot")] +[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] +[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0")] +[assembly: System.Reflection.AssemblyProductAttribute("HmaKcBot")] +[assembly: System.Reflection.AssemblyTitleAttribute("HmaKcBot")] +[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] + +// Generated by the MSBuild WriteCodeFragment class. + diff --git a/Sources/Robots/HmaKcBot/HmaKcBot/obj/Debug/net6.0/HmaKcBot.AssemblyInfoInputs.cache b/Sources/Robots/HmaKcBot/HmaKcBot/obj/Debug/net6.0/HmaKcBot.AssemblyInfoInputs.cache new file mode 100644 index 0000000..f4c5c57 --- /dev/null +++ b/Sources/Robots/HmaKcBot/HmaKcBot/obj/Debug/net6.0/HmaKcBot.AssemblyInfoInputs.cache @@ -0,0 +1 @@ +c085c96e76c2bfbc661649df310079c6fc9608f0 diff --git a/Sources/Robots/HmaKcBot/HmaKcBot/obj/Debug/net6.0/HmaKcBot.GeneratedMSBuildEditorConfig.editorconfig b/Sources/Robots/HmaKcBot/HmaKcBot/obj/Debug/net6.0/HmaKcBot.GeneratedMSBuildEditorConfig.editorconfig new file mode 100644 index 0000000..adf838d --- /dev/null +++ b/Sources/Robots/HmaKcBot/HmaKcBot/obj/Debug/net6.0/HmaKcBot.GeneratedMSBuildEditorConfig.editorconfig @@ -0,0 +1,10 @@ +is_global = true +build_property.TargetFramework = net6.0 +build_property.TargetPlatformMinVersion = +build_property.UsingMicrosoftNETSdkWeb = +build_property.ProjectTypeGuids = +build_property.InvariantGlobalization = +build_property.PlatformNeutralAssembly = +build_property._SupportedPlatformList = Linux,macOS,Windows +build_property.RootNamespace = HmaKcBot +build_property.ProjectDir = C:\Users\Brummel\Documents\cAlgo\Sources\Robots\HmaKcBot\HmaKcBot\ diff --git a/Sources/Robots/HmaKcBot/HmaKcBot/obj/Debug/net6.0/HmaKcBot.assets.cache b/Sources/Robots/HmaKcBot/HmaKcBot/obj/Debug/net6.0/HmaKcBot.assets.cache new file mode 100644 index 0000000..afd6281 Binary files /dev/null and b/Sources/Robots/HmaKcBot/HmaKcBot/obj/Debug/net6.0/HmaKcBot.assets.cache differ diff --git a/Sources/Robots/HmaKcBot/HmaKcBot/obj/Debug/net6.0/HmaKcBot.csproj.AssemblyReference.cache b/Sources/Robots/HmaKcBot/HmaKcBot/obj/Debug/net6.0/HmaKcBot.csproj.AssemblyReference.cache new file mode 100644 index 0000000..76e6da8 Binary files /dev/null and b/Sources/Robots/HmaKcBot/HmaKcBot/obj/Debug/net6.0/HmaKcBot.csproj.AssemblyReference.cache differ diff --git a/Sources/Robots/HmaKcBot/HmaKcBot/obj/HmaKcBot.csproj.nuget.dgspec.json b/Sources/Robots/HmaKcBot/HmaKcBot/obj/HmaKcBot.csproj.nuget.dgspec.json new file mode 100644 index 0000000..54c62f5 --- /dev/null +++ b/Sources/Robots/HmaKcBot/HmaKcBot/obj/HmaKcBot.csproj.nuget.dgspec.json @@ -0,0 +1,66 @@ +{ + "format": 1, + "restore": { + "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\HmaKcBot\\HmaKcBot\\HmaKcBot.csproj": {} + }, + "projects": { + "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\HmaKcBot\\HmaKcBot\\HmaKcBot.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\HmaKcBot\\HmaKcBot\\HmaKcBot.csproj", + "projectName": "HmaKcBot", + "projectPath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\HmaKcBot\\HmaKcBot\\HmaKcBot.csproj", + "packagesPath": "C:\\Users\\Brummel\\.nuget\\packages\\", + "outputPath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\HmaKcBot\\HmaKcBot\\obj\\", + "projectStyle": "PackageReference", + "configFilePaths": [ + "C:\\Users\\Brummel\\AppData\\Roaming\\NuGet\\NuGet.Config" + ], + "originalTargetFrameworks": [ + "net6.0" + ], + "sources": { + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net6.0": { + "targetAlias": "net6.0", + "projectReferences": {} + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + } + }, + "frameworks": { + "net6.0": { + "targetAlias": "net6.0", + "dependencies": { + "cTrader.Automate": { + "target": "Package", + "version": "[*, )" + } + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\6.0.200\\RuntimeIdentifierGraph.json" + } + } + } + } +} \ No newline at end of file diff --git a/Sources/Robots/HmaKcBot/HmaKcBot/obj/HmaKcBot.csproj.nuget.g.props b/Sources/Robots/HmaKcBot/HmaKcBot/obj/HmaKcBot.csproj.nuget.g.props new file mode 100644 index 0000000..120d227 --- /dev/null +++ b/Sources/Robots/HmaKcBot/HmaKcBot/obj/HmaKcBot.csproj.nuget.g.props @@ -0,0 +1,21 @@ + + + + True + NuGet + $(MSBuildThisFileDirectory)project.assets.json + $(UserProfile)\.nuget\packages\ + C:\Users\Brummel\.nuget\packages\ + PackageReference + 6.1.0 + + + + + + + + + C:\Users\Brummel\.nuget\packages\ctrader.automate\1.0.12 + + \ No newline at end of file diff --git a/Sources/Robots/HmaKcBot/HmaKcBot/obj/HmaKcBot.csproj.nuget.g.targets b/Sources/Robots/HmaKcBot/HmaKcBot/obj/HmaKcBot.csproj.nuget.g.targets new file mode 100644 index 0000000..29b3c4c --- /dev/null +++ b/Sources/Robots/HmaKcBot/HmaKcBot/obj/HmaKcBot.csproj.nuget.g.targets @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Sources/Robots/HmaKcBot/HmaKcBot/obj/project.assets.json b/Sources/Robots/HmaKcBot/HmaKcBot/obj/project.assets.json new file mode 100644 index 0000000..73f1439 --- /dev/null +++ b/Sources/Robots/HmaKcBot/HmaKcBot/obj/project.assets.json @@ -0,0 +1,139 @@ +{ + "version": 3, + "targets": { + "net6.0": { + "cTrader.Automate/1.0.12": { + "type": "package", + "compile": { + "lib/net6.0/cAlgo.API.dll": {} + }, + "runtime": { + "lib/net6.0/cAlgo.API.dll": {} + }, + "build": { + "build/cTrader.Automate.props": {}, + "build/cTrader.Automate.targets": {} + } + } + } + }, + "libraries": { + "cTrader.Automate/1.0.12": { + "sha512": "h+HPnbqPTkoSeojoYwWWOo5Jm9ExkmrmsU/WbF/idcgO2auKAPILBghZRP6Qt5mAcE/40YCRMNHT45/kzzs5Sg==", + "type": "package", + "path": "ctrader.automate/1.0.12", + "hasTools": true, + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "build/cTrader.Automate.props", + "build/cTrader.Automate.targets", + "ctrader.automate.1.0.12.nupkg.sha512", + "ctrader.automate.nuspec", + "eula.md", + "icon.png", + "lib/net40/cAlgo.API.dll", + "lib/net40/cAlgo.API.xml", + "lib/net6.0/cAlgo.API.dll", + "lib/net6.0/cAlgo.API.xml", + "tools/net472/Core.AlgoFormat.Compose.Reflection.dll", + "tools/net472/Core.AlgoFormat.Writer.dll", + "tools/net472/Core.AlgoFormat.dll", + "tools/net472/Core.Domain.Primitives.dll", + "tools/net472/Newtonsoft.Json.dll", + "tools/net472/System.Buffers.dll", + "tools/net472/System.Collections.Immutable.dll", + "tools/net472/System.Memory.dll", + "tools/net472/System.Numerics.Vectors.dll", + "tools/net472/System.Reflection.Metadata.dll", + "tools/net472/System.Reflection.MetadataLoadContext.dll", + "tools/net472/System.Runtime.CompilerServices.Unsafe.dll", + "tools/net472/cTrader.Automate.Sdk.Tasks.dll", + "tools/net6.0/Core.AlgoFormat.Compose.Reflection.dll", + "tools/net6.0/Core.AlgoFormat.Writer.dll", + "tools/net6.0/Core.AlgoFormat.dll", + "tools/net6.0/Core.Connection.Protobuf.Common.dll", + "tools/net6.0/Core.Domain.Primitives.dll", + "tools/net6.0/Microsoft.Win32.SystemEvents.dll", + "tools/net6.0/Newtonsoft.Json.dll", + "tools/net6.0/System.Drawing.Common.dll", + "tools/net6.0/System.Reflection.MetadataLoadContext.dll", + "tools/net6.0/System.Security.Permissions.dll", + "tools/net6.0/System.Windows.Extensions.dll", + "tools/net6.0/cTrader.Automate.Sdk.Tasks.dll", + "tools/net6.0/protobuf-net.Core.dll", + "tools/net6.0/protobuf-net.dll", + "tools/net6.0/runtimes/unix/lib/net6.0/System.Drawing.Common.dll", + "tools/net6.0/runtimes/win/lib/net6.0/Microsoft.Win32.SystemEvents.dll", + "tools/net6.0/runtimes/win/lib/net6.0/System.Drawing.Common.dll", + "tools/net6.0/runtimes/win/lib/net6.0/System.Windows.Extensions.dll" + ] + } + }, + "projectFileDependencyGroups": { + "net6.0": [ + "cTrader.Automate >= *" + ] + }, + "packageFolders": { + "C:\\Users\\Brummel\\.nuget\\packages\\": {} + }, + "project": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\HmaKcBot\\HmaKcBot\\HmaKcBot.csproj", + "projectName": "HmaKcBot", + "projectPath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\HmaKcBot\\HmaKcBot\\HmaKcBot.csproj", + "packagesPath": "C:\\Users\\Brummel\\.nuget\\packages\\", + "outputPath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\HmaKcBot\\HmaKcBot\\obj\\", + "projectStyle": "PackageReference", + "configFilePaths": [ + "C:\\Users\\Brummel\\AppData\\Roaming\\NuGet\\NuGet.Config" + ], + "originalTargetFrameworks": [ + "net6.0" + ], + "sources": { + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net6.0": { + "targetAlias": "net6.0", + "projectReferences": {} + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + } + }, + "frameworks": { + "net6.0": { + "targetAlias": "net6.0", + "dependencies": { + "cTrader.Automate": { + "target": "Package", + "version": "[*, )" + } + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\6.0.200\\RuntimeIdentifierGraph.json" + } + } + } +} \ No newline at end of file diff --git a/Sources/Robots/HmaKcBot/HmaKcBot/obj/project.nuget.cache b/Sources/Robots/HmaKcBot/HmaKcBot/obj/project.nuget.cache new file mode 100644 index 0000000..656182d --- /dev/null +++ b/Sources/Robots/HmaKcBot/HmaKcBot/obj/project.nuget.cache @@ -0,0 +1,10 @@ +{ + "version": 2, + "dgSpecHash": "XgjSBy1e5g3yI21pAC15ZU+Ik0YPC1U9+c5CtniqfcpL0zXKjYStMBHb3A6jnU4FFMSg8LtqczM9QrWa3Q/x0w==", + "success": true, + "projectFilePath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\HmaKcBot\\HmaKcBot\\HmaKcBot.csproj", + "expectedPackageFiles": [ + "C:\\Users\\Brummel\\.nuget\\packages\\ctrader.automate\\1.0.12\\ctrader.automate.1.0.12.nupkg.sha512" + ], + "logs": [] +} \ No newline at end of file diff --git a/Sources/Robots/HmaSmaCrossoverBot.algo b/Sources/Robots/HmaSmaCrossoverBot.algo new file mode 100644 index 0000000..880fca2 Binary files /dev/null and b/Sources/Robots/HmaSmaCrossoverBot.algo differ diff --git a/Sources/Robots/HmaSmaCrossoverBot/.vscode/tasks.json b/Sources/Robots/HmaSmaCrossoverBot/.vscode/tasks.json new file mode 100644 index 0000000..215166e --- /dev/null +++ b/Sources/Robots/HmaSmaCrossoverBot/.vscode/tasks.json @@ -0,0 +1 @@ +{"version":"2.0.0","tasks":[{"label":"build","command":"dotnet","type":"process","args":["build","${workspaceFolder}","/property:GenerateFullPaths=true","/consoleLoggerParameters:NoSummary"],"problemMatcher":"$msCompile"}]} \ No newline at end of file diff --git a/Sources/Robots/HmaSmaCrossoverBot/HmaSmaCrossoverBot.sln b/Sources/Robots/HmaSmaCrossoverBot/HmaSmaCrossoverBot.sln new file mode 100644 index 0000000..2495992 --- /dev/null +++ b/Sources/Robots/HmaSmaCrossoverBot/HmaSmaCrossoverBot.sln @@ -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}") = "HmaSmaCrossoverBot", "HmaSmaCrossoverBot\HmaSmaCrossoverBot.csproj", "{b3f19675-15c1-4ecd-87db-b4e345e3d6f5}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {b3f19675-15c1-4ecd-87db-b4e345e3d6f5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {b3f19675-15c1-4ecd-87db-b4e345e3d6f5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {b3f19675-15c1-4ecd-87db-b4e345e3d6f5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {b3f19675-15c1-4ecd-87db-b4e345e3d6f5}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/Sources/Robots/HmaSmaCrossoverBot/HmaSmaCrossoverBot/HmaSmaCrossoverBot.cs b/Sources/Robots/HmaSmaCrossoverBot/HmaSmaCrossoverBot/HmaSmaCrossoverBot.cs new file mode 100644 index 0000000..3429511 --- /dev/null +++ b/Sources/Robots/HmaSmaCrossoverBot/HmaSmaCrossoverBot/HmaSmaCrossoverBot.cs @@ -0,0 +1,198 @@ +using cAlgo.API; +using cAlgo.API.Indicators; +using System; + +namespace cAlgo.Robots +{ + [Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)] + public class HmaSmaCrossoverBot : Robot + { + [Parameter("HMA A Period (Fast)", DefaultValue = 20, Group = "Periods")] + public int HmaAPeriod { get; set; } + + [Parameter("HMA B Period (Slow)", DefaultValue = 250, Group = "Periods")] + public int HmaBPeriod { get; set; } + + [Parameter("SMA C Period (Base)", DefaultValue = 200, Group = "Periods")] + public int SmaCPeriod { get; set; } + + [Parameter("Lot Size", DefaultValue = 0.1, Group = "Volume")] + public double LotSize { get; set; } + + [Parameter("Max Positions", DefaultValue = 4, Group = "Volume")] + public int MaxPositions { get; set; } + + [Parameter("Stop Loss (Pips)", DefaultValue = 50, Group = "Protection")] + public double StopLossPips { get; set; } + + // Label for position management + private const string BotLabel = "HmaSmaCrossoverBot"; + + private HullMovingAverage _hmaA; + private HullMovingAverage _hmaB; + private SimpleMovingAverage _smaC; + private double _volumeInUnits; + private TimeZoneInfo _germanZone; // Added for time filter + + protected override void OnStart() + { + _hmaA = Indicators.HullMovingAverage(Bars.ClosePrices, HmaAPeriod); + _hmaB = Indicators.HullMovingAverage(Bars.ClosePrices, HmaBPeriod); + _smaC = Indicators.SimpleMovingAverage(Bars.ClosePrices, SmaCPeriod); + + // Convert LotSize to normalized VolumeInUnits + double preciseVolume = Symbol.QuantityToVolumeInUnits(LotSize); + _volumeInUnits = Symbol.NormalizeVolumeInUnits(preciseVolume, RoundingMode.ToNearest); + + if (_volumeInUnits < Symbol.VolumeInUnitsMin) + { + _volumeInUnits = Symbol.VolumeInUnitsMin; + Print($"Warning: Requested volume {preciseVolume} is below minimum. Using minimum: {_volumeInUnits} units."); + } + + // Initialize German TimeZone + try + { + // "W. Europe Standard Time" is the ID for Berlin, Amsterdam, Rome (CET/CEST) + _germanZone = TimeZoneInfo.FindSystemTimeZoneById("W. Europe Standard Time"); + } + catch (TimeZoneNotFoundException) + { + Print("Error: TimeZone 'W. Europe Standard Time' not found. Using Server TimeZone as fallback for time filter (might be incorrect)."); + _germanZone = TimeZone; // Fallback to server zone (which is UTC here) + } + catch (Exception ex) + { + Print($"Error initializing TimeZone: {ex.Message}. Using Server TimeZone as fallback."); + _germanZone = TimeZone; + } + } + + protected override void OnBar() + { + // Ensure indicators have enough data + 3 bars for peak/trough logic + int requiredBars = Math.Max(HmaAPeriod, Math.Max(HmaBPeriod, SmaCPeriod)) + 3; + if (Bars.Count < requiredBars) + { + return; + } + + // Check exit conditions first + HandleExits(); + + // Check entry conditions + HandleEntries(); + } + + // Checks and executes exit logic based on rules + private void HandleExits() + { + // Get current and previous values (based on closed bars) + // Last(0) = current closed bar, Last(1) = previous bar + double hmaB_curr = _hmaB.Result.Last(0); + double smaC_curr = _smaC.Result.Last(0); + double hmaB_prev = _hmaB.Result.Last(1); + + double low_curr = Bars.LowPrices.Last(0); + double low_prev = Bars.LowPrices.Last(1); + double high_curr = Bars.HighPrices.Last(0); + double high_prev = Bars.HighPrices.Last(1); + + // --- Long Exit Conditions --- + // 1. B < C (at current close) + // 2. Low crosses below B (Currentbar.Low < Current.B AND PreviousBar.Low >= Previous.B) + bool longExitSignal = (hmaB_curr < smaC_curr) || + ((low_curr < hmaB_curr) && (low_prev >= hmaB_prev)); + + // --- Short Exit Conditions --- + // 1. B > C (at current close) + // 2. High crosses above B (Currentbar.High > Current.B AND PreviousBar.High <= Previous.B) + bool shortExitSignal = (hmaB_curr > smaC_curr) || + ((high_curr > hmaB_curr) && (high_prev <= hmaB_prev)); + + // Find all positions managed by this bot for the current symbol + var botPositions = GetPositions(null); + + foreach (var position in botPositions) + { + if (position.TradeType == TradeType.Buy && longExitSignal) + { + ClosePosition(position); + } + else if (position.TradeType == TradeType.Sell && shortExitSignal) + { + ClosePosition(position); + } + } + } + + // Checks and executes entry logic based on indicator signals + private void HandleEntries() + { + // --- Time Filter --- + // Use TimeInUtc for a reliable UTC baseline (as Robot TimeZone is UTC) + DateTime germanTime = TimeZoneInfo.ConvertTimeFromUtc(TimeInUtc, _germanZone); + + // Only allow new trades between 7:00 (inclusive) and 17:00 (exclusive) German time + if (germanTime.Hour < 7 || germanTime.Hour >= 17) + { + return; // Outside trading hours + } + // --- End Time Filter --- + + // Get values for signal bar (i-1) and confirmation bar (i) + // Last(0) = i (confirmation bar, just closed) + // Last(1) = i-1 (signal bar) + // Last(2) = i-2 (previous bar) + + double hmaA_confirm = _hmaA.Result.Last(0); + double hmaA_signal = _hmaA.Result.Last(1); + double hmaA_prev = _hmaA.Result.Last(2); + + // Get filter values at the signal bar (i-1) + double hmaB_signal = _hmaB.Result.Last(1); + double smaC_signal = _smaC.Result.Last(1); + + // Peak/Trough detection at i-1 (Last(1)), confirmed by i (Last(0)) + bool isPeak = (hmaA_prev < hmaA_signal) && (hmaA_signal > hmaA_confirm); + bool isTrough = (hmaA_prev > hmaA_signal) && (hmaA_signal < hmaA_confirm); + + // --- Sell Signal Condition (at i-1) --- + // 1. B < C + // 2. A > C + // 3. A forms a peak + if ((hmaB_signal < smaC_signal) && (hmaA_signal > smaC_signal) && isPeak) + { + if (GetPositions(TradeType.Sell).Length < MaxPositions) + { + ExecuteMarketOrder(TradeType.Sell, SymbolName, _volumeInUnits, BotLabel, StopLossPips, null); + } + } + + // --- Buy Signal Condition (at i-1) --- + // 1. B > C + // 2. A < C + // 3. A forms a trough + if ((hmaB_signal > smaC_signal) && (hmaA_signal < smaC_signal) && isTrough) + { + if (GetPositions(TradeType.Buy).Length < MaxPositions) + { + ExecuteMarketOrder(TradeType.Buy, SymbolName, _volumeInUnits, BotLabel, StopLossPips, null); + } + } + } + + // Helper to get positions only for the current symbol and managed by this bot + private Position[] GetPositions(TradeType? tradeType) + { + if (tradeType == null) + { + // Get all Buy or Sell positions for this bot/symbol + return Positions.FindAll(BotLabel, SymbolName); + } + + // Get specific TradeType positions for this bot/symbol + return Positions.FindAll(BotLabel, SymbolName, tradeType.Value); + } + } +} \ No newline at end of file diff --git a/Sources/Robots/HmaSmaCrossoverBot/HmaSmaCrossoverBot/HmaSmaCrossoverBot.csproj b/Sources/Robots/HmaSmaCrossoverBot/HmaSmaCrossoverBot/HmaSmaCrossoverBot.csproj new file mode 100644 index 0000000..da1fef1 --- /dev/null +++ b/Sources/Robots/HmaSmaCrossoverBot/HmaSmaCrossoverBot/HmaSmaCrossoverBot.csproj @@ -0,0 +1,9 @@ + + + net6.0 + + + + + + diff --git a/Sources/Robots/HmaSmaCrossoverBot/HmaSmaCrossoverBot/obj/Debug/net6.0/.NETCoreApp,Version=v6.0.AssemblyAttributes.cs b/Sources/Robots/HmaSmaCrossoverBot/HmaSmaCrossoverBot/obj/Debug/net6.0/.NETCoreApp,Version=v6.0.AssemblyAttributes.cs new file mode 100644 index 0000000..32c95f9 --- /dev/null +++ b/Sources/Robots/HmaSmaCrossoverBot/HmaSmaCrossoverBot/obj/Debug/net6.0/.NETCoreApp,Version=v6.0.AssemblyAttributes.cs @@ -0,0 +1,4 @@ +// +using System; +using System.Reflection; +[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v6.0", FrameworkDisplayName = "")] diff --git a/Sources/Robots/HmaSmaCrossoverBot/HmaSmaCrossoverBot/obj/Debug/net6.0/HmaSmaCrossoverBot.AssemblyInfo.cs b/Sources/Robots/HmaSmaCrossoverBot/HmaSmaCrossoverBot/obj/Debug/net6.0/HmaSmaCrossoverBot.AssemblyInfo.cs new file mode 100644 index 0000000..7c1148f --- /dev/null +++ b/Sources/Robots/HmaSmaCrossoverBot/HmaSmaCrossoverBot/obj/Debug/net6.0/HmaSmaCrossoverBot.AssemblyInfo.cs @@ -0,0 +1,22 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +using System; +using System.Reflection; + +[assembly: System.Reflection.AssemblyCompanyAttribute("HmaSmaCrossoverBot")] +[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] +[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0")] +[assembly: System.Reflection.AssemblyProductAttribute("HmaSmaCrossoverBot")] +[assembly: System.Reflection.AssemblyTitleAttribute("HmaSmaCrossoverBot")] +[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] + +// Generated by the MSBuild WriteCodeFragment class. + diff --git a/Sources/Robots/HmaSmaCrossoverBot/HmaSmaCrossoverBot/obj/Debug/net6.0/HmaSmaCrossoverBot.AssemblyInfoInputs.cache b/Sources/Robots/HmaSmaCrossoverBot/HmaSmaCrossoverBot/obj/Debug/net6.0/HmaSmaCrossoverBot.AssemblyInfoInputs.cache new file mode 100644 index 0000000..0462cb7 --- /dev/null +++ b/Sources/Robots/HmaSmaCrossoverBot/HmaSmaCrossoverBot/obj/Debug/net6.0/HmaSmaCrossoverBot.AssemblyInfoInputs.cache @@ -0,0 +1 @@ +4f125f90c408d20a05e42f8a6dc598f9c14b8e6f diff --git a/Sources/Robots/HmaSmaCrossoverBot/HmaSmaCrossoverBot/obj/Debug/net6.0/HmaSmaCrossoverBot.GeneratedMSBuildEditorConfig.editorconfig b/Sources/Robots/HmaSmaCrossoverBot/HmaSmaCrossoverBot/obj/Debug/net6.0/HmaSmaCrossoverBot.GeneratedMSBuildEditorConfig.editorconfig new file mode 100644 index 0000000..db5a751 --- /dev/null +++ b/Sources/Robots/HmaSmaCrossoverBot/HmaSmaCrossoverBot/obj/Debug/net6.0/HmaSmaCrossoverBot.GeneratedMSBuildEditorConfig.editorconfig @@ -0,0 +1,10 @@ +is_global = true +build_property.TargetFramework = net6.0 +build_property.TargetPlatformMinVersion = +build_property.UsingMicrosoftNETSdkWeb = +build_property.ProjectTypeGuids = +build_property.InvariantGlobalization = +build_property.PlatformNeutralAssembly = +build_property._SupportedPlatformList = Linux,macOS,Windows +build_property.RootNamespace = HmaSmaCrossoverBot +build_property.ProjectDir = C:\Users\Brummel\Documents\cAlgo\Sources\Robots\HmaSmaCrossoverBot\HmaSmaCrossoverBot\ diff --git a/Sources/Robots/HmaSmaCrossoverBot/HmaSmaCrossoverBot/obj/Debug/net6.0/HmaSmaCrossoverBot.assets.cache b/Sources/Robots/HmaSmaCrossoverBot/HmaSmaCrossoverBot/obj/Debug/net6.0/HmaSmaCrossoverBot.assets.cache new file mode 100644 index 0000000..a1c39b0 Binary files /dev/null and b/Sources/Robots/HmaSmaCrossoverBot/HmaSmaCrossoverBot/obj/Debug/net6.0/HmaSmaCrossoverBot.assets.cache differ diff --git a/Sources/Robots/HmaSmaCrossoverBot/HmaSmaCrossoverBot/obj/Debug/net6.0/HmaSmaCrossoverBot.csproj.AssemblyReference.cache b/Sources/Robots/HmaSmaCrossoverBot/HmaSmaCrossoverBot/obj/Debug/net6.0/HmaSmaCrossoverBot.csproj.AssemblyReference.cache new file mode 100644 index 0000000..50043b7 Binary files /dev/null and b/Sources/Robots/HmaSmaCrossoverBot/HmaSmaCrossoverBot/obj/Debug/net6.0/HmaSmaCrossoverBot.csproj.AssemblyReference.cache differ diff --git a/Sources/Robots/HmaSmaCrossoverBot/HmaSmaCrossoverBot/obj/HmaSmaCrossoverBot.csproj.nuget.dgspec.json b/Sources/Robots/HmaSmaCrossoverBot/HmaSmaCrossoverBot/obj/HmaSmaCrossoverBot.csproj.nuget.dgspec.json new file mode 100644 index 0000000..7bbd24b --- /dev/null +++ b/Sources/Robots/HmaSmaCrossoverBot/HmaSmaCrossoverBot/obj/HmaSmaCrossoverBot.csproj.nuget.dgspec.json @@ -0,0 +1,66 @@ +{ + "format": 1, + "restore": { + "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\HmaSmaCrossoverBot\\HmaSmaCrossoverBot\\HmaSmaCrossoverBot.csproj": {} + }, + "projects": { + "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\HmaSmaCrossoverBot\\HmaSmaCrossoverBot\\HmaSmaCrossoverBot.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\HmaSmaCrossoverBot\\HmaSmaCrossoverBot\\HmaSmaCrossoverBot.csproj", + "projectName": "HmaSmaCrossoverBot", + "projectPath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\HmaSmaCrossoverBot\\HmaSmaCrossoverBot\\HmaSmaCrossoverBot.csproj", + "packagesPath": "C:\\Users\\Brummel\\.nuget\\packages\\", + "outputPath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\HmaSmaCrossoverBot\\HmaSmaCrossoverBot\\obj\\", + "projectStyle": "PackageReference", + "configFilePaths": [ + "C:\\Users\\Brummel\\AppData\\Roaming\\NuGet\\NuGet.Config" + ], + "originalTargetFrameworks": [ + "net6.0" + ], + "sources": { + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net6.0": { + "targetAlias": "net6.0", + "projectReferences": {} + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + } + }, + "frameworks": { + "net6.0": { + "targetAlias": "net6.0", + "dependencies": { + "cTrader.Automate": { + "target": "Package", + "version": "[*, )" + } + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\6.0.200\\RuntimeIdentifierGraph.json" + } + } + } + } +} \ No newline at end of file diff --git a/Sources/Robots/HmaSmaCrossoverBot/HmaSmaCrossoverBot/obj/HmaSmaCrossoverBot.csproj.nuget.g.props b/Sources/Robots/HmaSmaCrossoverBot/HmaSmaCrossoverBot/obj/HmaSmaCrossoverBot.csproj.nuget.g.props new file mode 100644 index 0000000..0b161ac --- /dev/null +++ b/Sources/Robots/HmaSmaCrossoverBot/HmaSmaCrossoverBot/obj/HmaSmaCrossoverBot.csproj.nuget.g.props @@ -0,0 +1,21 @@ + + + + True + NuGet + $(MSBuildThisFileDirectory)project.assets.json + $(UserProfile)\.nuget\packages\ + C:\Users\Brummel\.nuget\packages\ + PackageReference + 6.1.0 + + + + + + + + + C:\Users\Brummel\.nuget\packages\ctrader.automate\1.0.14 + + \ No newline at end of file diff --git a/Sources/Robots/HmaSmaCrossoverBot/HmaSmaCrossoverBot/obj/HmaSmaCrossoverBot.csproj.nuget.g.targets b/Sources/Robots/HmaSmaCrossoverBot/HmaSmaCrossoverBot/obj/HmaSmaCrossoverBot.csproj.nuget.g.targets new file mode 100644 index 0000000..46f142c --- /dev/null +++ b/Sources/Robots/HmaSmaCrossoverBot/HmaSmaCrossoverBot/obj/HmaSmaCrossoverBot.csproj.nuget.g.targets @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Sources/Robots/HmaSmaCrossoverBot/HmaSmaCrossoverBot/obj/project.assets.json b/Sources/Robots/HmaSmaCrossoverBot/HmaSmaCrossoverBot/obj/project.assets.json new file mode 100644 index 0000000..0df71b6 --- /dev/null +++ b/Sources/Robots/HmaSmaCrossoverBot/HmaSmaCrossoverBot/obj/project.assets.json @@ -0,0 +1,139 @@ +{ + "version": 3, + "targets": { + "net6.0": { + "cTrader.Automate/1.0.14": { + "type": "package", + "compile": { + "lib/net6.0/cAlgo.API.dll": {} + }, + "runtime": { + "lib/net6.0/cAlgo.API.dll": {} + }, + "build": { + "build/cTrader.Automate.props": {}, + "build/cTrader.Automate.targets": {} + } + } + } + }, + "libraries": { + "cTrader.Automate/1.0.14": { + "sha512": "eNwE7WL90MGBKb5MuLAtZLdQy0vxkI5EVhLWAQ9S83EAdYkGAzdccvGFLk2oqmtSGBtD+gEpyrJUW/ej4dI4jw==", + "type": "package", + "path": "ctrader.automate/1.0.14", + "hasTools": true, + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "build/cTrader.Automate.props", + "build/cTrader.Automate.targets", + "ctrader.automate.1.0.14.nupkg.sha512", + "ctrader.automate.nuspec", + "eula.md", + "icon.png", + "lib/net40/cAlgo.API.dll", + "lib/net40/cAlgo.API.xml", + "lib/net6.0/cAlgo.API.dll", + "lib/net6.0/cAlgo.API.xml", + "tools/net472/Core.AlgoFormat.Compose.Reflection.dll", + "tools/net472/Core.AlgoFormat.Writer.dll", + "tools/net472/Core.AlgoFormat.dll", + "tools/net472/Core.Domain.Primitives.dll", + "tools/net472/Newtonsoft.Json.dll", + "tools/net472/System.Buffers.dll", + "tools/net472/System.Collections.Immutable.dll", + "tools/net472/System.Memory.dll", + "tools/net472/System.Numerics.Vectors.dll", + "tools/net472/System.Reflection.Metadata.dll", + "tools/net472/System.Reflection.MetadataLoadContext.dll", + "tools/net472/System.Runtime.CompilerServices.Unsafe.dll", + "tools/net472/cTrader.Automate.Sdk.Tasks.dll", + "tools/net6.0/Core.AlgoFormat.Compose.Reflection.dll", + "tools/net6.0/Core.AlgoFormat.Writer.dll", + "tools/net6.0/Core.AlgoFormat.dll", + "tools/net6.0/Core.Connection.Protobuf.Common.dll", + "tools/net6.0/Core.Domain.Primitives.dll", + "tools/net6.0/Microsoft.Win32.SystemEvents.dll", + "tools/net6.0/Newtonsoft.Json.dll", + "tools/net6.0/System.Drawing.Common.dll", + "tools/net6.0/System.Reflection.MetadataLoadContext.dll", + "tools/net6.0/System.Security.Permissions.dll", + "tools/net6.0/System.Windows.Extensions.dll", + "tools/net6.0/cTrader.Automate.Sdk.Tasks.dll", + "tools/net6.0/protobuf-net.Core.dll", + "tools/net6.0/protobuf-net.dll", + "tools/net6.0/runtimes/unix/lib/net6.0/System.Drawing.Common.dll", + "tools/net6.0/runtimes/win/lib/net6.0/Microsoft.Win32.SystemEvents.dll", + "tools/net6.0/runtimes/win/lib/net6.0/System.Drawing.Common.dll", + "tools/net6.0/runtimes/win/lib/net6.0/System.Windows.Extensions.dll" + ] + } + }, + "projectFileDependencyGroups": { + "net6.0": [ + "cTrader.Automate >= *" + ] + }, + "packageFolders": { + "C:\\Users\\Brummel\\.nuget\\packages\\": {} + }, + "project": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\HmaSmaCrossoverBot\\HmaSmaCrossoverBot\\HmaSmaCrossoverBot.csproj", + "projectName": "HmaSmaCrossoverBot", + "projectPath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\HmaSmaCrossoverBot\\HmaSmaCrossoverBot\\HmaSmaCrossoverBot.csproj", + "packagesPath": "C:\\Users\\Brummel\\.nuget\\packages\\", + "outputPath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\HmaSmaCrossoverBot\\HmaSmaCrossoverBot\\obj\\", + "projectStyle": "PackageReference", + "configFilePaths": [ + "C:\\Users\\Brummel\\AppData\\Roaming\\NuGet\\NuGet.Config" + ], + "originalTargetFrameworks": [ + "net6.0" + ], + "sources": { + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net6.0": { + "targetAlias": "net6.0", + "projectReferences": {} + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + } + }, + "frameworks": { + "net6.0": { + "targetAlias": "net6.0", + "dependencies": { + "cTrader.Automate": { + "target": "Package", + "version": "[*, )" + } + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\6.0.200\\RuntimeIdentifierGraph.json" + } + } + } +} \ No newline at end of file diff --git a/Sources/Robots/HmaSmaCrossoverBot/HmaSmaCrossoverBot/obj/project.nuget.cache b/Sources/Robots/HmaSmaCrossoverBot/HmaSmaCrossoverBot/obj/project.nuget.cache new file mode 100644 index 0000000..77b4a60 --- /dev/null +++ b/Sources/Robots/HmaSmaCrossoverBot/HmaSmaCrossoverBot/obj/project.nuget.cache @@ -0,0 +1,10 @@ +{ + "version": 2, + "dgSpecHash": "o2zbiJnQ5+mDJlvuxYW8+Qmxo/HzYxOwFc7BUoYdtbMdhQvLGFQJ9gkt6PTVAeX05JrpmDeuNnfj0iTviIs+iQ==", + "success": true, + "projectFilePath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\HmaSmaCrossoverBot\\HmaSmaCrossoverBot\\HmaSmaCrossoverBot.csproj", + "expectedPackageFiles": [ + "C:\\Users\\Brummel\\.nuget\\packages\\ctrader.automate\\1.0.14\\ctrader.automate.1.0.14.nupkg.sha512" + ], + "logs": [] +} \ No newline at end of file diff --git a/Sources/Robots/HmaStructureBot.algo b/Sources/Robots/HmaStructureBot.algo new file mode 100644 index 0000000..bbcd495 Binary files /dev/null and b/Sources/Robots/HmaStructureBot.algo differ diff --git a/Sources/Robots/HmaStructureBot/.vscode/tasks.json b/Sources/Robots/HmaStructureBot/.vscode/tasks.json new file mode 100644 index 0000000..215166e --- /dev/null +++ b/Sources/Robots/HmaStructureBot/.vscode/tasks.json @@ -0,0 +1 @@ +{"version":"2.0.0","tasks":[{"label":"build","command":"dotnet","type":"process","args":["build","${workspaceFolder}","/property:GenerateFullPaths=true","/consoleLoggerParameters:NoSummary"],"problemMatcher":"$msCompile"}]} \ No newline at end of file diff --git a/Sources/Robots/HmaStructureBot/HmaStructureBot.sln b/Sources/Robots/HmaStructureBot/HmaStructureBot.sln new file mode 100644 index 0000000..5e683b7 --- /dev/null +++ b/Sources/Robots/HmaStructureBot/HmaStructureBot.sln @@ -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}") = "HmaStructureBot", "HmaStructureBot\HmaStructureBot.csproj", "{9f6f411d-ba95-4e92-853b-10e6a1a8c98c}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {9f6f411d-ba95-4e92-853b-10e6a1a8c98c}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9f6f411d-ba95-4e92-853b-10e6a1a8c98c}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9f6f411d-ba95-4e92-853b-10e6a1a8c98c}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9f6f411d-ba95-4e92-853b-10e6a1a8c98c}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/Sources/Robots/HmaStructureBot/HmaStructureBot/HmaStructureBot.cs b/Sources/Robots/HmaStructureBot/HmaStructureBot/HmaStructureBot.cs new file mode 100644 index 0000000..b5e1831 --- /dev/null +++ b/Sources/Robots/HmaStructureBot/HmaStructureBot/HmaStructureBot.cs @@ -0,0 +1,227 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using cAlgo.API; +using cAlgo.API.Indicators; +using cAlgo.API.Internals; + +namespace cAlgo.Robots +{ + [Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)] + public class HmaStructureBot : Robot + { + // --- Parameters --- + [Parameter("HMA Period", DefaultValue = 25)] + public int HmaPeriod { get; set; } + + [Parameter("Risk % per Trade", DefaultValue = 1.0, MinValue = 0.01)] + public double RiskPercent { get; set; } + + [Parameter("Order Label", DefaultValue = "HMA_Struct")] + public string OrderLabel { get; set; } + + // Expiration removed + + // --- Indicators --- + private HullMovingAverage _hma; + + // --- State Management --- + private DateTime _lastSetupTime; + + // --- Data Structures --- + private enum ExtremumType { High, Low } + + private struct Extremum + { + public ExtremumType Type; + public double Value; + public int Index; // Index relative to logic + public DateTime Time; // Unique identifier for the extremum + } + + protected override void OnStart() + { + _hma = Indicators.HullMovingAverage(Bars.ClosePrices, HmaPeriod); + } + + protected override void OnBarClosed() + { + // 1. Manage existing pending orders + ManageExistingOrders(); + + // 2. Find Extrema + var extrema = GetExtrema(200, 5); + + if (extrema.Count < 5) + return; + + // 3. Check for duplicate execution on the same setup + // The setup is defined by the latest extremum (extrema[0]) + if (extrema[0].Time == _lastSetupTime) + return; + + bool orderPlaced = false; + + // --- Short Logic --- + // Last extremum must be a Trough (T0) + if (extrema[0].Type == ExtremumType.Low) + { + var t0 = extrema[0].Value; + var h0 = extrema[1].Value; + var t1 = extrema[2].Value; + var h1 = extrema[3].Value; + var t2 = extrema[4].Value; + + // Condition: T1 < T2 and H0 < T2 + if (t1 < t2 && h0 < t2) + { + double entryPrice = 0.5 * (t2 + h0); + double stopLossPrice = h1; + double takeProfitPrice = Math.Max(t0, t1); + + PlaceStructureOrder(TradeType.Sell, entryPrice, stopLossPrice, takeProfitPrice); + orderPlaced = true; + } + } + // --- Long Logic --- + // Last extremum must be a Peak (H0) + else if (extrema[0].Type == ExtremumType.High) + { + var h0 = extrema[0].Value; + var t0 = extrema[1].Value; + var h1 = extrema[2].Value; + var t1 = extrema[3].Value; + var h2 = extrema[4].Value; + + // Condition: H1 > H2 and T0 > H2 + if (h1 > h2 && t0 > h2) + { + double entryPrice = 0.5 * (h2 + t0); + double stopLossPrice = t1; + double takeProfitPrice = Math.Min(h0, h1); + + PlaceStructureOrder(TradeType.Buy, entryPrice, stopLossPrice, takeProfitPrice); + orderPlaced = true; + } + } + + // 4. Mark Setup as handled if we placed an order + if (orderPlaced) + { + _lastSetupTime = extrema[0].Time; + } + } + + private void PlaceStructureOrder(TradeType type, double entryPrice, double slPrice, double tpPrice) + { + // Calculate Risk + double riskAmount = Account.Balance * (RiskPercent / 100.0); + + // Note: VolumeForFixedRisk still requires SL in Pips calculation + double slDistancePips = Math.Abs(entryPrice - slPrice) / Symbol.PipSize; + + if (slDistancePips <= 0) return; + + double volume = Symbol.VolumeForFixedRisk(riskAmount, slDistancePips); + volume = Symbol.NormalizeVolumeInUnits(volume, RoundingMode.Down); + + // Place Limit Order using Absolute Prices + // Expiry is null (GTC - Good Till Cancelled) + PlaceLimitOrder(type, SymbolName, volume, entryPrice, OrderLabel, slPrice, tpPrice, ProtectionType.Absolute, null); + } + + private void ManageExistingOrders() + { + foreach (var order in PendingOrders.ToArray()) + { + if (order.Label != OrderLabel || order.SymbolName != SymbolName) + continue; + + var barLow = Bars.LowPrices.Last(0); + var barHigh = Bars.HighPrices.Last(0); + bool isCancelled = false; + + // --- Check SL Hit --- + if (order.StopLoss.HasValue) + { + if (order.TradeType == TradeType.Buy) + { + if (barLow <= order.StopLoss.Value) + { + order.Cancel(); + isCancelled = true; + } + } + else // Sell + { + if (barHigh >= order.StopLoss.Value) + { + order.Cancel(); + isCancelled = true; + } + } + } + + if (isCancelled) continue; + + // --- Check TP Extension --- + if (order.TakeProfit.HasValue) + { + if (order.TradeType == TradeType.Sell) + { + // If price went lower than TP, move TP to Low + if (barLow < order.TakeProfit.Value) + { + ModifyPendingOrder(order, order.TargetPrice, order.StopLoss, barLow, order.ProtectionType); + } + } + else // Buy + { + // If price went higher than TP, move TP to High + if (barHigh > order.TakeProfit.Value) + { + ModifyPendingOrder(order, order.TargetPrice, order.StopLoss, barHigh, order.ProtectionType); + } + } + } + } + } + + private List GetExtrema(int searchDepth, int minCount) + { + var list = new List(); + var hma = _hma.Result; + + // i starts at 1 to compare with confirmed i+1 and i-1 (where i-1 is closer to closed bar Last(0)) + for (int i = 1; i < searchDepth && i < hma.Count - 1; i++) + { + double current = hma.Last(i); + double prev = hma.Last(i + 1); // Older + double next = hma.Last(i - 1); // Newer + + if (current > prev && current > next) + { + list.Add(new Extremum { + Type = ExtremumType.High, + Value = current, + Index = i, + Time = Bars.OpenTimes.Last(i) + }); + } + else if (current < prev && current < next) + { + list.Add(new Extremum { + Type = ExtremumType.Low, + Value = current, + Index = i, + Time = Bars.OpenTimes.Last(i) + }); + } + + if (list.Count >= minCount) + break; + } + return list; + } + } +} \ No newline at end of file diff --git a/Sources/Robots/HmaStructureBot/HmaStructureBot/HmaStructureBot.csproj b/Sources/Robots/HmaStructureBot/HmaStructureBot/HmaStructureBot.csproj new file mode 100644 index 0000000..da1fef1 --- /dev/null +++ b/Sources/Robots/HmaStructureBot/HmaStructureBot/HmaStructureBot.csproj @@ -0,0 +1,9 @@ + + + net6.0 + + + + + + diff --git a/Sources/Robots/HmaStructureBot/HmaStructureBot/obj/Debug/net6.0/.NETCoreApp,Version=v6.0.AssemblyAttributes.cs b/Sources/Robots/HmaStructureBot/HmaStructureBot/obj/Debug/net6.0/.NETCoreApp,Version=v6.0.AssemblyAttributes.cs new file mode 100644 index 0000000..32c95f9 --- /dev/null +++ b/Sources/Robots/HmaStructureBot/HmaStructureBot/obj/Debug/net6.0/.NETCoreApp,Version=v6.0.AssemblyAttributes.cs @@ -0,0 +1,4 @@ +// +using System; +using System.Reflection; +[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v6.0", FrameworkDisplayName = "")] diff --git a/Sources/Robots/HmaStructureBot/HmaStructureBot/obj/Debug/net6.0/HmaStructureBot.AssemblyInfo.cs b/Sources/Robots/HmaStructureBot/HmaStructureBot/obj/Debug/net6.0/HmaStructureBot.AssemblyInfo.cs new file mode 100644 index 0000000..1b7a9a6 --- /dev/null +++ b/Sources/Robots/HmaStructureBot/HmaStructureBot/obj/Debug/net6.0/HmaStructureBot.AssemblyInfo.cs @@ -0,0 +1,22 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +using System; +using System.Reflection; + +[assembly: System.Reflection.AssemblyCompanyAttribute("HmaStructureBot")] +[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] +[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0")] +[assembly: System.Reflection.AssemblyProductAttribute("HmaStructureBot")] +[assembly: System.Reflection.AssemblyTitleAttribute("HmaStructureBot")] +[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] + +// Generated by the MSBuild WriteCodeFragment class. + diff --git a/Sources/Robots/HmaStructureBot/HmaStructureBot/obj/Debug/net6.0/HmaStructureBot.AssemblyInfoInputs.cache b/Sources/Robots/HmaStructureBot/HmaStructureBot/obj/Debug/net6.0/HmaStructureBot.AssemblyInfoInputs.cache new file mode 100644 index 0000000..cfb48c4 --- /dev/null +++ b/Sources/Robots/HmaStructureBot/HmaStructureBot/obj/Debug/net6.0/HmaStructureBot.AssemblyInfoInputs.cache @@ -0,0 +1 @@ +7c0c361c93e8c6d8fca2b9bb1c31f57269d6fb5c diff --git a/Sources/Robots/HmaStructureBot/HmaStructureBot/obj/Debug/net6.0/HmaStructureBot.GeneratedMSBuildEditorConfig.editorconfig b/Sources/Robots/HmaStructureBot/HmaStructureBot/obj/Debug/net6.0/HmaStructureBot.GeneratedMSBuildEditorConfig.editorconfig new file mode 100644 index 0000000..04388ad --- /dev/null +++ b/Sources/Robots/HmaStructureBot/HmaStructureBot/obj/Debug/net6.0/HmaStructureBot.GeneratedMSBuildEditorConfig.editorconfig @@ -0,0 +1,10 @@ +is_global = true +build_property.TargetFramework = net6.0 +build_property.TargetPlatformMinVersion = +build_property.UsingMicrosoftNETSdkWeb = +build_property.ProjectTypeGuids = +build_property.InvariantGlobalization = +build_property.PlatformNeutralAssembly = +build_property._SupportedPlatformList = Linux,macOS,Windows +build_property.RootNamespace = HmaStructureBot +build_property.ProjectDir = C:\Users\Brummel\Documents\cAlgo\Sources\Robots\HmaStructureBot\HmaStructureBot\ diff --git a/Sources/Robots/HmaStructureBot/HmaStructureBot/obj/Debug/net6.0/HmaStructureBot.assets.cache b/Sources/Robots/HmaStructureBot/HmaStructureBot/obj/Debug/net6.0/HmaStructureBot.assets.cache new file mode 100644 index 0000000..92fcb42 Binary files /dev/null and b/Sources/Robots/HmaStructureBot/HmaStructureBot/obj/Debug/net6.0/HmaStructureBot.assets.cache differ diff --git a/Sources/Robots/HmaStructureBot/HmaStructureBot/obj/Debug/net6.0/HmaStructureBot.csproj.AssemblyReference.cache b/Sources/Robots/HmaStructureBot/HmaStructureBot/obj/Debug/net6.0/HmaStructureBot.csproj.AssemblyReference.cache new file mode 100644 index 0000000..50043b7 Binary files /dev/null and b/Sources/Robots/HmaStructureBot/HmaStructureBot/obj/Debug/net6.0/HmaStructureBot.csproj.AssemblyReference.cache differ diff --git a/Sources/Robots/HmaStructureBot/HmaStructureBot/obj/HmaStructureBot.csproj.nuget.dgspec.json b/Sources/Robots/HmaStructureBot/HmaStructureBot/obj/HmaStructureBot.csproj.nuget.dgspec.json new file mode 100644 index 0000000..721d957 --- /dev/null +++ b/Sources/Robots/HmaStructureBot/HmaStructureBot/obj/HmaStructureBot.csproj.nuget.dgspec.json @@ -0,0 +1,66 @@ +{ + "format": 1, + "restore": { + "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\HmaStructureBot\\HmaStructureBot\\HmaStructureBot.csproj": {} + }, + "projects": { + "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\HmaStructureBot\\HmaStructureBot\\HmaStructureBot.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\HmaStructureBot\\HmaStructureBot\\HmaStructureBot.csproj", + "projectName": "HmaStructureBot", + "projectPath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\HmaStructureBot\\HmaStructureBot\\HmaStructureBot.csproj", + "packagesPath": "C:\\Users\\Brummel\\.nuget\\packages\\", + "outputPath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\HmaStructureBot\\HmaStructureBot\\obj\\", + "projectStyle": "PackageReference", + "configFilePaths": [ + "C:\\Users\\Brummel\\AppData\\Roaming\\NuGet\\NuGet.Config" + ], + "originalTargetFrameworks": [ + "net6.0" + ], + "sources": { + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net6.0": { + "targetAlias": "net6.0", + "projectReferences": {} + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + } + }, + "frameworks": { + "net6.0": { + "targetAlias": "net6.0", + "dependencies": { + "cTrader.Automate": { + "target": "Package", + "version": "[*, )" + } + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\6.0.200\\RuntimeIdentifierGraph.json" + } + } + } + } +} \ No newline at end of file diff --git a/Sources/Robots/HmaStructureBot/HmaStructureBot/obj/HmaStructureBot.csproj.nuget.g.props b/Sources/Robots/HmaStructureBot/HmaStructureBot/obj/HmaStructureBot.csproj.nuget.g.props new file mode 100644 index 0000000..0b161ac --- /dev/null +++ b/Sources/Robots/HmaStructureBot/HmaStructureBot/obj/HmaStructureBot.csproj.nuget.g.props @@ -0,0 +1,21 @@ + + + + True + NuGet + $(MSBuildThisFileDirectory)project.assets.json + $(UserProfile)\.nuget\packages\ + C:\Users\Brummel\.nuget\packages\ + PackageReference + 6.1.0 + + + + + + + + + C:\Users\Brummel\.nuget\packages\ctrader.automate\1.0.14 + + \ No newline at end of file diff --git a/Sources/Robots/HmaStructureBot/HmaStructureBot/obj/HmaStructureBot.csproj.nuget.g.targets b/Sources/Robots/HmaStructureBot/HmaStructureBot/obj/HmaStructureBot.csproj.nuget.g.targets new file mode 100644 index 0000000..46f142c --- /dev/null +++ b/Sources/Robots/HmaStructureBot/HmaStructureBot/obj/HmaStructureBot.csproj.nuget.g.targets @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Sources/Robots/HmaStructureBot/HmaStructureBot/obj/project.assets.json b/Sources/Robots/HmaStructureBot/HmaStructureBot/obj/project.assets.json new file mode 100644 index 0000000..4b75b33 --- /dev/null +++ b/Sources/Robots/HmaStructureBot/HmaStructureBot/obj/project.assets.json @@ -0,0 +1,139 @@ +{ + "version": 3, + "targets": { + "net6.0": { + "cTrader.Automate/1.0.14": { + "type": "package", + "compile": { + "lib/net6.0/cAlgo.API.dll": {} + }, + "runtime": { + "lib/net6.0/cAlgo.API.dll": {} + }, + "build": { + "build/cTrader.Automate.props": {}, + "build/cTrader.Automate.targets": {} + } + } + } + }, + "libraries": { + "cTrader.Automate/1.0.14": { + "sha512": "eNwE7WL90MGBKb5MuLAtZLdQy0vxkI5EVhLWAQ9S83EAdYkGAzdccvGFLk2oqmtSGBtD+gEpyrJUW/ej4dI4jw==", + "type": "package", + "path": "ctrader.automate/1.0.14", + "hasTools": true, + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "build/cTrader.Automate.props", + "build/cTrader.Automate.targets", + "ctrader.automate.1.0.14.nupkg.sha512", + "ctrader.automate.nuspec", + "eula.md", + "icon.png", + "lib/net40/cAlgo.API.dll", + "lib/net40/cAlgo.API.xml", + "lib/net6.0/cAlgo.API.dll", + "lib/net6.0/cAlgo.API.xml", + "tools/net472/Core.AlgoFormat.Compose.Reflection.dll", + "tools/net472/Core.AlgoFormat.Writer.dll", + "tools/net472/Core.AlgoFormat.dll", + "tools/net472/Core.Domain.Primitives.dll", + "tools/net472/Newtonsoft.Json.dll", + "tools/net472/System.Buffers.dll", + "tools/net472/System.Collections.Immutable.dll", + "tools/net472/System.Memory.dll", + "tools/net472/System.Numerics.Vectors.dll", + "tools/net472/System.Reflection.Metadata.dll", + "tools/net472/System.Reflection.MetadataLoadContext.dll", + "tools/net472/System.Runtime.CompilerServices.Unsafe.dll", + "tools/net472/cTrader.Automate.Sdk.Tasks.dll", + "tools/net6.0/Core.AlgoFormat.Compose.Reflection.dll", + "tools/net6.0/Core.AlgoFormat.Writer.dll", + "tools/net6.0/Core.AlgoFormat.dll", + "tools/net6.0/Core.Connection.Protobuf.Common.dll", + "tools/net6.0/Core.Domain.Primitives.dll", + "tools/net6.0/Microsoft.Win32.SystemEvents.dll", + "tools/net6.0/Newtonsoft.Json.dll", + "tools/net6.0/System.Drawing.Common.dll", + "tools/net6.0/System.Reflection.MetadataLoadContext.dll", + "tools/net6.0/System.Security.Permissions.dll", + "tools/net6.0/System.Windows.Extensions.dll", + "tools/net6.0/cTrader.Automate.Sdk.Tasks.dll", + "tools/net6.0/protobuf-net.Core.dll", + "tools/net6.0/protobuf-net.dll", + "tools/net6.0/runtimes/unix/lib/net6.0/System.Drawing.Common.dll", + "tools/net6.0/runtimes/win/lib/net6.0/Microsoft.Win32.SystemEvents.dll", + "tools/net6.0/runtimes/win/lib/net6.0/System.Drawing.Common.dll", + "tools/net6.0/runtimes/win/lib/net6.0/System.Windows.Extensions.dll" + ] + } + }, + "projectFileDependencyGroups": { + "net6.0": [ + "cTrader.Automate >= *" + ] + }, + "packageFolders": { + "C:\\Users\\Brummel\\.nuget\\packages\\": {} + }, + "project": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\HmaStructureBot\\HmaStructureBot\\HmaStructureBot.csproj", + "projectName": "HmaStructureBot", + "projectPath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\HmaStructureBot\\HmaStructureBot\\HmaStructureBot.csproj", + "packagesPath": "C:\\Users\\Brummel\\.nuget\\packages\\", + "outputPath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\HmaStructureBot\\HmaStructureBot\\obj\\", + "projectStyle": "PackageReference", + "configFilePaths": [ + "C:\\Users\\Brummel\\AppData\\Roaming\\NuGet\\NuGet.Config" + ], + "originalTargetFrameworks": [ + "net6.0" + ], + "sources": { + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net6.0": { + "targetAlias": "net6.0", + "projectReferences": {} + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + } + }, + "frameworks": { + "net6.0": { + "targetAlias": "net6.0", + "dependencies": { + "cTrader.Automate": { + "target": "Package", + "version": "[*, )" + } + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\6.0.200\\RuntimeIdentifierGraph.json" + } + } + } +} \ No newline at end of file diff --git a/Sources/Robots/HmaStructureBot/HmaStructureBot/obj/project.nuget.cache b/Sources/Robots/HmaStructureBot/HmaStructureBot/obj/project.nuget.cache new file mode 100644 index 0000000..d0d8d9a --- /dev/null +++ b/Sources/Robots/HmaStructureBot/HmaStructureBot/obj/project.nuget.cache @@ -0,0 +1,10 @@ +{ + "version": 2, + "dgSpecHash": "+LfUhmmRyp/da3uLv4sl9ySOIaNqbWkyyWrG6dFoxe2QtHmfBoTXxKOgzeDDVjvn++es3OTLB7yxGQK8b5/qMg==", + "success": true, + "projectFilePath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\HmaStructureBot\\HmaStructureBot\\HmaStructureBot.csproj", + "expectedPackageFiles": [ + "C:\\Users\\Brummel\\.nuget\\packages\\ctrader.automate\\1.0.14\\ctrader.automate.1.0.14.nupkg.sha512" + ], + "logs": [] +} \ No newline at end of file diff --git a/Sources/Robots/HmaTrendTrader.algo b/Sources/Robots/HmaTrendTrader.algo new file mode 100644 index 0000000..060be51 Binary files /dev/null and b/Sources/Robots/HmaTrendTrader.algo differ diff --git a/Sources/Robots/HmaTrendTrader/.vscode/tasks.json b/Sources/Robots/HmaTrendTrader/.vscode/tasks.json new file mode 100644 index 0000000..215166e --- /dev/null +++ b/Sources/Robots/HmaTrendTrader/.vscode/tasks.json @@ -0,0 +1 @@ +{"version":"2.0.0","tasks":[{"label":"build","command":"dotnet","type":"process","args":["build","${workspaceFolder}","/property:GenerateFullPaths=true","/consoleLoggerParameters:NoSummary"],"problemMatcher":"$msCompile"}]} \ No newline at end of file diff --git a/Sources/Robots/HmaTrendTrader/HmaTrendTrader.sln b/Sources/Robots/HmaTrendTrader/HmaTrendTrader.sln new file mode 100644 index 0000000..ea80567 --- /dev/null +++ b/Sources/Robots/HmaTrendTrader/HmaTrendTrader.sln @@ -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}") = "HmaTrendTrader", "HmaTrendTrader\HmaTrendTrader.csproj", "{3730b511-c6f3-4c3c-9b25-cec622450d0e}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {3730b511-c6f3-4c3c-9b25-cec622450d0e}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3730b511-c6f3-4c3c-9b25-cec622450d0e}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3730b511-c6f3-4c3c-9b25-cec622450d0e}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3730b511-c6f3-4c3c-9b25-cec622450d0e}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/Sources/Robots/HmaTrendTrader/HmaTrendTrader/HmaTrendTrader.cs b/Sources/Robots/HmaTrendTrader/HmaTrendTrader/HmaTrendTrader.cs new file mode 100644 index 0000000..3bca62e --- /dev/null +++ b/Sources/Robots/HmaTrendTrader/HmaTrendTrader/HmaTrendTrader.cs @@ -0,0 +1,513 @@ +using System; +using System.Linq; // Added for .Distinct() and .Any() +using System.Collections.Generic; // Added for List +using cAlgo.API; +using cAlgo.API.Indicators; +using cAlgo.API.Internals; + +namespace cAlgo.Robots +{ + // Defines the trading direction + public enum TradeDirection + { + Both, + Long, + Short + } + + [Robot(AccessRights = AccessRights.None)] + public class HmaTrendTrader : Robot + { + [Parameter("Trade Direction", DefaultValue = TradeDirection.Both)] + public TradeDirection TradeDir { get; set; } + + [Parameter("HMA Period", DefaultValue = 25, MinValue = 2)] + public int HmaPeriod { get; set; } + + [Parameter("Max Positions (per Direction)", DefaultValue = 3, MinValue = 1)] + public int MaxPositions { get; set; } + + [Parameter("Risk (Pips)", DefaultValue = 125, MinValue = 1)] + public double RiskPips { get; set; } + + [Parameter("Volume (Lots)", DefaultValue = 0.1, MinValue = 0.01, Step = 0.01)] + public double VolumeLots { get; set; } + + [Parameter("Enable Grid Take Profit", DefaultValue = true)] + public bool EnableGridTakeProfit { get; set; } + + [Parameter("Enable Opposite Exit", DefaultValue = true)] + public bool EnableOppositeExit { get; set; } + + [Parameter("Enable Volume Scaling", DefaultValue = false)] + public bool EnableVolumeScaling { get; set; } + + [Parameter("Cooldown Bars", DefaultValue = 1, MinValue = 0)] + public int CooldownBars { get; set; } + + private HullMovingAverage _hma; + private double _volumeInUnits; + + // Bias state updated by OnBarClosed + private bool _isShortBias = false; + private bool _isLongBias = false; + + // Cooldown tracking + private int _lastLongExitBarIndex = -1; + private int _lastShortExitBarIndex = -1; + + // Extremum Tracking stores HMA values + private double? _lastPeakPrice = null; + private double? _lastTroughPrice = null; + + // NEW: Index tracking for comparison + private int _lastPeakIndex = -1; + private int _lastTroughIndex = -1; + + protected override void OnStart() + { + _hma = Indicators.HullMovingAverage(Bars.ClosePrices, HmaPeriod); + + _volumeInUnits = Symbol.QuantityToVolumeInUnits(VolumeLots); + _volumeInUnits = Symbol.NormalizeVolumeInUnits(_volumeInUnits, RoundingMode.ToNearest); + + Positions.Closed += OnPositionsClosed; + + Print("HmaTrendTrader started. Managing existing positions:"); + + var positionsToLog = new List(); + + if (TradeDir == TradeDirection.Long || TradeDir == TradeDirection.Both) + { + // Get all existing Long positions for this symbol + positionsToLog.AddRange(GetManagedPositions().Where(p => p.TradeType == TradeType.Buy)); + } + if (TradeDir == TradeDirection.Short || TradeDir == TradeDirection.Both) + { + // Get all existing Short positions for this symbol + positionsToLog.AddRange(GetManagedPositions().Where(p => p.TradeType == TradeType.Sell)); + } + + // Use Distinct() in case TradeDir == Both + foreach (var pos in positionsToLog.Distinct()) + { + Print($" -> Takeover: {pos.TradeType} ID {pos.Id}, Vol: {pos.VolumeInUnits}, Entry: {pos.EntryPrice}, SL: {pos.StopLoss}"); + } + + if (!positionsToLog.Any()) + { + Print(" -> No existing positions found for management."); + } + + // Added: Initial status report after takeover + LogStatusReport(); + } + + // Added: Event handler for position closures (Cooldown logic) + private void OnPositionsClosed(PositionClosedEventArgs args) + { + var position = args.Position; + + // Only track positions managed by this bot + if (position.SymbolName == Symbol.Name) + { + // Store the index of the bar *during which* the exit occurred + int exitBarIndex = Bars.Count - 1; + + if (position.TradeType == TradeType.Buy) + { + _lastLongExitBarIndex = exitBarIndex; + } + else if (position.TradeType == TradeType.Sell) + { + _lastShortExitBarIndex = exitBarIndex; + } + } + } + + protected override void OnTick() + { + HandleEntry(); + HandleExit(); + HandleExtremumCleanup(); // Cleanup logic runs continuously + } + + private void HandleExtremumCleanup() + { + // --- CLEANUP SHORT EXTREMUM (TROUGH) --- + // Trigger: Price hits Trough level + if (_lastTroughPrice.HasValue && Symbol.Bid <= _lastTroughPrice.Value) + { + // Reset Condition: Only reset if the Peak is newer (structural validation) + bool isPeakNewer = _lastPeakPrice.HasValue && (_lastPeakIndex > _lastTroughIndex); + + if (isPeakNewer) + { + Print($"Trough cleanup hit at {_lastTroughPrice}. Resetting Trough (Peak is newer)."); + _lastTroughPrice = null; + _lastTroughIndex = -1; + } + // Optional: We could implement aggressive cleanup here if no Peak exists, + // but sticking strictly to the user's structural rule. + } + + // --- CLEANUP LONG EXTREMUM (PEAK) --- + // Trigger: Price hits Peak level + if (_lastPeakPrice.HasValue && Symbol.Ask >= _lastPeakPrice.Value) + { + // Reset Condition: Only reset if the Trough is newer (structural validation) + bool isTroughNewer = _lastTroughPrice.HasValue && (_lastTroughIndex > _lastPeakIndex); + + if (isTroughNewer) + { + Print($"Peak cleanup hit at {_lastPeakPrice}. Resetting Peak (Trough is newer)."); + _lastPeakPrice = null; + _lastPeakIndex = -1; + } + } + } + + protected override void OnBarClosed() + { + bool wasLongBias = _isLongBias; + bool wasShortBias = _isShortBias; + + // Data for HMA + var hma_T1 = _hma.Result.Last(0); + var hma_T2 = _hma.Result.Last(1); + + _isShortBias = hma_T1 < hma_T2; + _isLongBias = hma_T1 > hma_T2; + + // --- TRACKING EXTREMA (HMA Value at Pivot) --- + + // Index of the bar that just closed (used as the creation index for the extremum) + int currentBarIndex = Bars.Count - 1; + + // 1. Flip from Long to Short (Peak confirmed) + if (wasLongBias && !_isLongBias) + { + _lastPeakPrice = hma_T2; + _lastPeakIndex = currentBarIndex; + } + + // 2. Flip from Short to Long (Trough confirmed) + if (wasShortBias && !_isShortBias) + { + _lastTroughPrice = hma_T2; + _lastTroughIndex = currentBarIndex; + } + + // --- EXIT LOGIC (Grid) --- + + if (EnableGridTakeProfit) + { + if (wasLongBias && !_isLongBias) + { + if (TradeDir == TradeDirection.Short || TradeDir == TradeDirection.Both) + { + var shorts = GetManagedPositions().Where(p => p.TradeType == TradeType.Sell).ToList(); + + if (shorts.Count > 0 && shorts.Sum(p => p.GrossProfit) > 0) + { + Print($"HMA Peak detected. Closing {shorts.Count} Short positions (Total PnL > 0)."); + foreach (var pos in shorts) ClosePosition(pos); + } + } + } + + if (wasShortBias && !_isShortBias) + { + if (TradeDir == TradeDirection.Long || TradeDir == TradeDirection.Both) + { + var longs = GetManagedPositions().Where(p => p.TradeType == TradeType.Buy).ToList(); + + if (longs.Count > 0 && longs.Sum(p => p.GrossProfit) > 0) + { + Print($"HMA Trough detected. Closing {longs.Count} Long positions (Total PnL > 0)."); + foreach (var pos in longs) ClosePosition(pos); + } + } + } + } + + HandleBreakevenMove(); + LogStatusReport(); + } + + private void HandleEntry() + { + if (TradeDir == TradeDirection.Long || TradeDir == TradeDirection.Both) HandleLongEntry(); + if (TradeDir == TradeDirection.Short || TradeDir == TradeDirection.Both) HandleShortEntry(); + } + + private void HandleShortEntry() + { + // Cooldown check + if (_lastShortExitBarIndex > -1 && (Bars.Count - 1 < _lastShortExitBarIndex + CooldownBars)) return; + + // Conditions + if (!_isShortBias) return; + if (Bars.HighPrices.Last(1) > Bars.HighPrices.Last(2)) return; + if (Symbol.Bid <= Bars.HighPrices.Last(1)) return; + if (GetAtRiskPositionsCount(TradeType.Sell) > 0) return; + + int positionCount = GetManagedPositionsCount(TradeType.Sell); + if (positionCount >= MaxPositions) return; + + // Volume + double volumeToExecute = _volumeInUnits; + if (EnableVolumeScaling) volumeToExecute = _volumeInUnits * (positionCount + 1); + volumeToExecute = NormalizeVolume(volumeToExecute); + if (volumeToExecute == 0) return; + + var result = ExecuteMarketOrder(TradeType.Sell, Symbol.Name, volumeToExecute, null, RiskPips, null); + + if (result.IsSuccessful) + { + // TP LOGIC: Set TP for the new position to the last Trough price + if (_lastTroughPrice.HasValue) + { + TrySetTakeProfit( + result.Position, + _lastTroughPrice.Value, + $"TP set for new Short {result.Position.Id} at Trough price: {_lastTroughPrice}"); + } + else + { + Print($"INFO: Short Entry {result.Position.Id} was placed, but _lastTroughPrice is NULL. TP not set."); + } + + if (EnableOppositeExit) ManageOppositePositions(TradeType.Sell); + } + } + + private void HandleLongEntry() + { + if (_lastLongExitBarIndex > -1 && (Bars.Count - 1 < _lastLongExitBarIndex + CooldownBars)) return; + if (!_isLongBias) return; + if (Bars.LowPrices.Last(1) < Bars.LowPrices.Last(2)) return; + if (Symbol.Ask >= Bars.LowPrices.Last(1)) return; + if (GetAtRiskPositionsCount(TradeType.Buy) > 0) return; + + int positionCount = GetManagedPositionsCount(TradeType.Buy); + if (positionCount >= MaxPositions) return; + + double volumeToExecute = _volumeInUnits; + if (EnableVolumeScaling) volumeToExecute = _volumeInUnits * (positionCount + 1); + + volumeToExecute = NormalizeVolume(volumeToExecute); + if (volumeToExecute == 0) return; + + var result = ExecuteMarketOrder(TradeType.Buy, Symbol.Name, volumeToExecute, null, RiskPips, null); + + if (result.IsSuccessful) + { + // TP LOGIC: Set TP for the new position to the last Peak price + if (_lastPeakPrice.HasValue) + { + TrySetTakeProfit( + result.Position, + _lastPeakPrice.Value, + $"TP set for new Long {result.Position.Id} at Peak price: {_lastPeakPrice}"); + } + else + { + Print($"INFO: Long Entry {result.Position.Id} was placed, but _lastPeakPrice is NULL. TP not set."); + } + + if (EnableOppositeExit) ManageOppositePositions(TradeType.Buy); + } + } + + private void TrySetTakeProfit(Position position, double tpPrice, string logMessage) + { + tpPrice = Math.Round(tpPrice, Symbol.Digits); + + try + { + ModifyPosition(position, position.StopLoss, tpPrice, ProtectionType.Absolute); + Print(logMessage); + } + catch (Exception ex) + { + Print($"ERROR: Failed to set TP for {position.Id} to {tpPrice}. Reason: {ex.Message}"); + } + } + + private void ManageOppositePositions(TradeType newEntryDirection) + { + TradeType oppositeType = (newEntryDirection == TradeType.Buy) ? TradeType.Sell : TradeType.Buy; + var oppositePositions = GetManagedPositions().Where(p => p.TradeType == oppositeType).ToList(); + + if (!oppositePositions.Any()) return; + + if (oppositePositions.Sum(p => p.GrossProfit) > 0) + { + Print($"Fresh Entry ({newEntryDirection}) -> Basket Close {oppositeType}."); + foreach (var pos in oppositePositions) ClosePosition(pos); + return; + } + + foreach (var pos in oppositePositions) + { + if (pos.GrossProfit > 0) ClosePosition(pos); + else SetScratchTpOrClose(pos); + } + } + + private void HandleBreakevenMove() + { + var lastClosedHigh = Bars.HighPrices.Last(0); + var lastClosedLow = Bars.LowPrices.Last(0); + var buffer = 2 * Symbol.Spread; + + foreach (var pos in GetManagedPositions()) + { + if (pos.TradeType == TradeType.Sell && (TradeDir == TradeDirection.Short || TradeDir == TradeDirection.Both)) + { + if (pos.StopLoss.HasValue == false || pos.StopLoss > pos.EntryPrice) + { + if (pos.EntryPrice > (lastClosedHigh + buffer)) + { + var newSL = pos.EntryPrice - Symbol.Spread; + if (!pos.StopLoss.HasValue || Math.Abs(pos.StopLoss.Value - newSL) > Symbol.PipSize * 0.1) + ModifyPosition(pos, newSL, pos.TakeProfit, ProtectionType.Absolute); + } + } + } + else if (pos.TradeType == TradeType.Buy && (TradeDir == TradeDirection.Long || TradeDir == TradeDirection.Both)) + { + if (pos.StopLoss.HasValue == false || pos.StopLoss < pos.EntryPrice) + { + if (pos.EntryPrice < (lastClosedLow - buffer)) + { + var newSL = pos.EntryPrice + Symbol.Spread; + if (!pos.StopLoss.HasValue || Math.Abs(pos.StopLoss.Value - newSL) > Symbol.PipSize * 0.1) + ModifyPosition(pos, newSL, pos.TakeProfit, ProtectionType.Absolute); + } + } + } + } + } + + private void HandleExit() + { + if (_isLongBias) + { + if (TradeDir == TradeDirection.Short || TradeDir == TradeDirection.Both) HandleLossPreventionExit(TradeType.Sell); + } + if (_isShortBias) + { + if (TradeDir == TradeDirection.Long || TradeDir == TradeDirection.Both) HandleLossPreventionExit(TradeType.Buy); + } + } + + private void HandleLossPreventionExit(TradeType tradeType) + { + foreach (var pos in GetManagedPositions().Where(p => p.TradeType == tradeType && p.GrossProfit <= 0)) + { + SetScratchTpOrClose(pos); + } + } + + private void SetScratchTpOrClose(Position pos) + { + double scratchTp; + double currentPrice; + bool isBuy = pos.TradeType == TradeType.Buy; + + if (isBuy) + { + scratchTp = pos.EntryPrice + Symbol.Spread; + currentPrice = Symbol.Bid; + } + else + { + scratchTp = pos.EntryPrice - Symbol.Spread; + currentPrice = Symbol.Ask; + } + + scratchTp = Math.Round(scratchTp, Symbol.Digits); + + if ((isBuy && currentPrice >= scratchTp) || (!isBuy && currentPrice <= scratchTp)) + { + ClosePosition(pos); + return; + } + + if (pos.TakeProfit.HasValue && Math.Abs(pos.TakeProfit.Value - scratchTp) < Symbol.PipSize) return; + + try + { + ModifyPosition(pos, pos.StopLoss, scratchTp, ProtectionType.Absolute); + } + catch (Exception) {} + } + + private double NormalizeVolume(double vol) + { + vol = Symbol.NormalizeVolumeInUnits(vol, RoundingMode.ToNearest); + if (vol < Symbol.VolumeInUnitsMin) vol = Symbol.VolumeInUnitsMin; + if (vol > Symbol.VolumeInUnitsMax) vol = Symbol.VolumeInUnitsMax; + return vol; + } + + private void LogStatusReport() + { + int longPositions = 0, shortPositions = 0, longAtRisk = 0, shortAtRisk = 0; + + if (TradeDir != TradeDirection.Short) + { + longPositions = GetManagedPositionsCount(TradeType.Buy); + longAtRisk = GetAtRiskPositionsCount(TradeType.Buy); + } + if (TradeDir != TradeDirection.Long) + { + shortPositions = GetManagedPositionsCount(TradeType.Sell); + shortAtRisk = GetAtRiskPositionsCount(TradeType.Sell); + } + + var activePositions = GetActiveManagedPositions(); + double openPips = activePositions.Sum(p => p.Pips); + double todayPips = History.Where(t => t.ClosingTime.Date == Server.Time.Date && t.SymbolName == Symbol.Name).Sum(t => t.Pips); + + string swingInfo = ""; + if (_lastPeakPrice.HasValue) swingInfo += $"PeakHMA: {_lastPeakPrice} "; + if (_lastTroughPrice.HasValue) swingInfo += $"TroughHMA: {_lastTroughPrice}"; + + Print($"Status: Open: {openPips:F1} Pips | Day: {todayPips:F1} Pips | L:{longPositions}({longAtRisk}) S:{shortPositions}({shortAtRisk}) | {swingInfo}"); + } + + private System.Collections.Generic.IEnumerable GetManagedPositions() + { + return Positions.Where(p => p.SymbolName == Symbol.Name); + } + + private List GetActiveManagedPositions() + { + var managedPositions = new List(); + if (TradeDir != TradeDirection.Short) managedPositions.AddRange(GetManagedPositions().Where(p => p.TradeType == TradeType.Buy)); + if (TradeDir != TradeDirection.Long) managedPositions.AddRange(GetManagedPositions().Where(p => p.TradeType == TradeType.Sell)); + return managedPositions.Distinct().ToList(); + } + + private int GetManagedPositionsCount(TradeType tradeType) => GetManagedPositions().Count(p => p.TradeType == tradeType); + + private int GetAtRiskPositionsCount(TradeType tradeType) + { + if (tradeType == TradeType.Sell) + return GetManagedPositions().Count(p => p.TradeType == TradeType.Sell && p.StopLoss.HasValue && p.StopLoss > p.EntryPrice); + else + return GetManagedPositions().Count(p => p.TradeType == TradeType.Buy && p.StopLoss.HasValue && p.StopLoss < p.EntryPrice); + } + + protected override void OnStop() + { + if (Positions != null) + { + Positions.Closed -= OnPositionsClosed; + } + } + } +} \ No newline at end of file diff --git a/Sources/Robots/HmaTrendTrader/HmaTrendTrader/HmaTrendTrader.csproj b/Sources/Robots/HmaTrendTrader/HmaTrendTrader/HmaTrendTrader.csproj new file mode 100644 index 0000000..da1fef1 --- /dev/null +++ b/Sources/Robots/HmaTrendTrader/HmaTrendTrader/HmaTrendTrader.csproj @@ -0,0 +1,9 @@ + + + net6.0 + + + + + + diff --git a/Sources/Robots/HmaTrendTrader/HmaTrendTrader/obj/Debug/net6.0/.NETCoreApp,Version=v6.0.AssemblyAttributes.cs b/Sources/Robots/HmaTrendTrader/HmaTrendTrader/obj/Debug/net6.0/.NETCoreApp,Version=v6.0.AssemblyAttributes.cs new file mode 100644 index 0000000..32c95f9 --- /dev/null +++ b/Sources/Robots/HmaTrendTrader/HmaTrendTrader/obj/Debug/net6.0/.NETCoreApp,Version=v6.0.AssemblyAttributes.cs @@ -0,0 +1,4 @@ +// +using System; +using System.Reflection; +[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v6.0", FrameworkDisplayName = "")] diff --git a/Sources/Robots/HmaTrendTrader/HmaTrendTrader/obj/Debug/net6.0/HmaTrendTrader.AssemblyInfo.cs b/Sources/Robots/HmaTrendTrader/HmaTrendTrader/obj/Debug/net6.0/HmaTrendTrader.AssemblyInfo.cs new file mode 100644 index 0000000..dab0451 --- /dev/null +++ b/Sources/Robots/HmaTrendTrader/HmaTrendTrader/obj/Debug/net6.0/HmaTrendTrader.AssemblyInfo.cs @@ -0,0 +1,22 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +using System; +using System.Reflection; + +[assembly: System.Reflection.AssemblyCompanyAttribute("HmaTrendTrader")] +[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] +[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0")] +[assembly: System.Reflection.AssemblyProductAttribute("HmaTrendTrader")] +[assembly: System.Reflection.AssemblyTitleAttribute("HmaTrendTrader")] +[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] + +// Generated by the MSBuild WriteCodeFragment class. + diff --git a/Sources/Robots/HmaTrendTrader/HmaTrendTrader/obj/Debug/net6.0/HmaTrendTrader.AssemblyInfoInputs.cache b/Sources/Robots/HmaTrendTrader/HmaTrendTrader/obj/Debug/net6.0/HmaTrendTrader.AssemblyInfoInputs.cache new file mode 100644 index 0000000..d97f190 --- /dev/null +++ b/Sources/Robots/HmaTrendTrader/HmaTrendTrader/obj/Debug/net6.0/HmaTrendTrader.AssemblyInfoInputs.cache @@ -0,0 +1 @@ +f6adf706ad1e2096cedfd7bdb9e4f4226c670f98 diff --git a/Sources/Robots/HmaTrendTrader/HmaTrendTrader/obj/Debug/net6.0/HmaTrendTrader.GeneratedMSBuildEditorConfig.editorconfig b/Sources/Robots/HmaTrendTrader/HmaTrendTrader/obj/Debug/net6.0/HmaTrendTrader.GeneratedMSBuildEditorConfig.editorconfig new file mode 100644 index 0000000..35605aa --- /dev/null +++ b/Sources/Robots/HmaTrendTrader/HmaTrendTrader/obj/Debug/net6.0/HmaTrendTrader.GeneratedMSBuildEditorConfig.editorconfig @@ -0,0 +1,10 @@ +is_global = true +build_property.TargetFramework = net6.0 +build_property.TargetPlatformMinVersion = +build_property.UsingMicrosoftNETSdkWeb = +build_property.ProjectTypeGuids = +build_property.InvariantGlobalization = +build_property.PlatformNeutralAssembly = +build_property._SupportedPlatformList = Linux,macOS,Windows +build_property.RootNamespace = HmaTrendTrader +build_property.ProjectDir = C:\Users\Brummel\Documents\cAlgo\Sources\Robots\HmaTrendTrader\HmaTrendTrader\ diff --git a/Sources/Robots/HmaTrendTrader/HmaTrendTrader/obj/Debug/net6.0/HmaTrendTrader.assets.cache b/Sources/Robots/HmaTrendTrader/HmaTrendTrader/obj/Debug/net6.0/HmaTrendTrader.assets.cache new file mode 100644 index 0000000..6c11353 Binary files /dev/null and b/Sources/Robots/HmaTrendTrader/HmaTrendTrader/obj/Debug/net6.0/HmaTrendTrader.assets.cache differ diff --git a/Sources/Robots/HmaTrendTrader/HmaTrendTrader/obj/Debug/net6.0/HmaTrendTrader.csproj.AssemblyReference.cache b/Sources/Robots/HmaTrendTrader/HmaTrendTrader/obj/Debug/net6.0/HmaTrendTrader.csproj.AssemblyReference.cache new file mode 100644 index 0000000..50043b7 Binary files /dev/null and b/Sources/Robots/HmaTrendTrader/HmaTrendTrader/obj/Debug/net6.0/HmaTrendTrader.csproj.AssemblyReference.cache differ diff --git a/Sources/Robots/HmaTrendTrader/HmaTrendTrader/obj/HmaTrendTrader.csproj.nuget.dgspec.json b/Sources/Robots/HmaTrendTrader/HmaTrendTrader/obj/HmaTrendTrader.csproj.nuget.dgspec.json new file mode 100644 index 0000000..b4df631 --- /dev/null +++ b/Sources/Robots/HmaTrendTrader/HmaTrendTrader/obj/HmaTrendTrader.csproj.nuget.dgspec.json @@ -0,0 +1,66 @@ +{ + "format": 1, + "restore": { + "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\HmaTrendTrader\\HmaTrendTrader\\HmaTrendTrader.csproj": {} + }, + "projects": { + "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\HmaTrendTrader\\HmaTrendTrader\\HmaTrendTrader.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\HmaTrendTrader\\HmaTrendTrader\\HmaTrendTrader.csproj", + "projectName": "HmaTrendTrader", + "projectPath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\HmaTrendTrader\\HmaTrendTrader\\HmaTrendTrader.csproj", + "packagesPath": "C:\\Users\\Brummel\\.nuget\\packages\\", + "outputPath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\HmaTrendTrader\\HmaTrendTrader\\obj\\", + "projectStyle": "PackageReference", + "configFilePaths": [ + "C:\\Users\\Brummel\\AppData\\Roaming\\NuGet\\NuGet.Config" + ], + "originalTargetFrameworks": [ + "net6.0" + ], + "sources": { + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net6.0": { + "targetAlias": "net6.0", + "projectReferences": {} + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + } + }, + "frameworks": { + "net6.0": { + "targetAlias": "net6.0", + "dependencies": { + "cTrader.Automate": { + "target": "Package", + "version": "[*, )" + } + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\6.0.200\\RuntimeIdentifierGraph.json" + } + } + } + } +} \ No newline at end of file diff --git a/Sources/Robots/HmaTrendTrader/HmaTrendTrader/obj/HmaTrendTrader.csproj.nuget.g.props b/Sources/Robots/HmaTrendTrader/HmaTrendTrader/obj/HmaTrendTrader.csproj.nuget.g.props new file mode 100644 index 0000000..0b161ac --- /dev/null +++ b/Sources/Robots/HmaTrendTrader/HmaTrendTrader/obj/HmaTrendTrader.csproj.nuget.g.props @@ -0,0 +1,21 @@ + + + + True + NuGet + $(MSBuildThisFileDirectory)project.assets.json + $(UserProfile)\.nuget\packages\ + C:\Users\Brummel\.nuget\packages\ + PackageReference + 6.1.0 + + + + + + + + + C:\Users\Brummel\.nuget\packages\ctrader.automate\1.0.14 + + \ No newline at end of file diff --git a/Sources/Robots/HmaTrendTrader/HmaTrendTrader/obj/HmaTrendTrader.csproj.nuget.g.targets b/Sources/Robots/HmaTrendTrader/HmaTrendTrader/obj/HmaTrendTrader.csproj.nuget.g.targets new file mode 100644 index 0000000..46f142c --- /dev/null +++ b/Sources/Robots/HmaTrendTrader/HmaTrendTrader/obj/HmaTrendTrader.csproj.nuget.g.targets @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Sources/Robots/HmaTrendTrader/HmaTrendTrader/obj/project.assets.json b/Sources/Robots/HmaTrendTrader/HmaTrendTrader/obj/project.assets.json new file mode 100644 index 0000000..26f0b99 --- /dev/null +++ b/Sources/Robots/HmaTrendTrader/HmaTrendTrader/obj/project.assets.json @@ -0,0 +1,139 @@ +{ + "version": 3, + "targets": { + "net6.0": { + "cTrader.Automate/1.0.14": { + "type": "package", + "compile": { + "lib/net6.0/cAlgo.API.dll": {} + }, + "runtime": { + "lib/net6.0/cAlgo.API.dll": {} + }, + "build": { + "build/cTrader.Automate.props": {}, + "build/cTrader.Automate.targets": {} + } + } + } + }, + "libraries": { + "cTrader.Automate/1.0.14": { + "sha512": "eNwE7WL90MGBKb5MuLAtZLdQy0vxkI5EVhLWAQ9S83EAdYkGAzdccvGFLk2oqmtSGBtD+gEpyrJUW/ej4dI4jw==", + "type": "package", + "path": "ctrader.automate/1.0.14", + "hasTools": true, + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "build/cTrader.Automate.props", + "build/cTrader.Automate.targets", + "ctrader.automate.1.0.14.nupkg.sha512", + "ctrader.automate.nuspec", + "eula.md", + "icon.png", + "lib/net40/cAlgo.API.dll", + "lib/net40/cAlgo.API.xml", + "lib/net6.0/cAlgo.API.dll", + "lib/net6.0/cAlgo.API.xml", + "tools/net472/Core.AlgoFormat.Compose.Reflection.dll", + "tools/net472/Core.AlgoFormat.Writer.dll", + "tools/net472/Core.AlgoFormat.dll", + "tools/net472/Core.Domain.Primitives.dll", + "tools/net472/Newtonsoft.Json.dll", + "tools/net472/System.Buffers.dll", + "tools/net472/System.Collections.Immutable.dll", + "tools/net472/System.Memory.dll", + "tools/net472/System.Numerics.Vectors.dll", + "tools/net472/System.Reflection.Metadata.dll", + "tools/net472/System.Reflection.MetadataLoadContext.dll", + "tools/net472/System.Runtime.CompilerServices.Unsafe.dll", + "tools/net472/cTrader.Automate.Sdk.Tasks.dll", + "tools/net6.0/Core.AlgoFormat.Compose.Reflection.dll", + "tools/net6.0/Core.AlgoFormat.Writer.dll", + "tools/net6.0/Core.AlgoFormat.dll", + "tools/net6.0/Core.Connection.Protobuf.Common.dll", + "tools/net6.0/Core.Domain.Primitives.dll", + "tools/net6.0/Microsoft.Win32.SystemEvents.dll", + "tools/net6.0/Newtonsoft.Json.dll", + "tools/net6.0/System.Drawing.Common.dll", + "tools/net6.0/System.Reflection.MetadataLoadContext.dll", + "tools/net6.0/System.Security.Permissions.dll", + "tools/net6.0/System.Windows.Extensions.dll", + "tools/net6.0/cTrader.Automate.Sdk.Tasks.dll", + "tools/net6.0/protobuf-net.Core.dll", + "tools/net6.0/protobuf-net.dll", + "tools/net6.0/runtimes/unix/lib/net6.0/System.Drawing.Common.dll", + "tools/net6.0/runtimes/win/lib/net6.0/Microsoft.Win32.SystemEvents.dll", + "tools/net6.0/runtimes/win/lib/net6.0/System.Drawing.Common.dll", + "tools/net6.0/runtimes/win/lib/net6.0/System.Windows.Extensions.dll" + ] + } + }, + "projectFileDependencyGroups": { + "net6.0": [ + "cTrader.Automate >= *" + ] + }, + "packageFolders": { + "C:\\Users\\Brummel\\.nuget\\packages\\": {} + }, + "project": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\HmaTrendTrader\\HmaTrendTrader\\HmaTrendTrader.csproj", + "projectName": "HmaTrendTrader", + "projectPath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\HmaTrendTrader\\HmaTrendTrader\\HmaTrendTrader.csproj", + "packagesPath": "C:\\Users\\Brummel\\.nuget\\packages\\", + "outputPath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\HmaTrendTrader\\HmaTrendTrader\\obj\\", + "projectStyle": "PackageReference", + "configFilePaths": [ + "C:\\Users\\Brummel\\AppData\\Roaming\\NuGet\\NuGet.Config" + ], + "originalTargetFrameworks": [ + "net6.0" + ], + "sources": { + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net6.0": { + "targetAlias": "net6.0", + "projectReferences": {} + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + } + }, + "frameworks": { + "net6.0": { + "targetAlias": "net6.0", + "dependencies": { + "cTrader.Automate": { + "target": "Package", + "version": "[*, )" + } + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\6.0.200\\RuntimeIdentifierGraph.json" + } + } + } +} \ No newline at end of file diff --git a/Sources/Robots/HmaTrendTrader/HmaTrendTrader/obj/project.nuget.cache b/Sources/Robots/HmaTrendTrader/HmaTrendTrader/obj/project.nuget.cache new file mode 100644 index 0000000..c3ed89a --- /dev/null +++ b/Sources/Robots/HmaTrendTrader/HmaTrendTrader/obj/project.nuget.cache @@ -0,0 +1,10 @@ +{ + "version": 2, + "dgSpecHash": "EFmTahP3Dahr3ofgCrrTANByFY6zuVMJxQqTR1zpEqqpJCPx4tg5yc8kuKpi6MJDjxPFMt1JynivpUAWATS4sQ==", + "success": true, + "projectFilePath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\HmaTrendTrader\\HmaTrendTrader\\HmaTrendTrader.csproj", + "expectedPackageFiles": [ + "C:\\Users\\Brummel\\.nuget\\packages\\ctrader.automate\\1.0.14\\ctrader.automate.1.0.14.nupkg.sha512" + ], + "logs": [] +} \ No newline at end of file diff --git a/Sources/Robots/MS_ExportM1.algo b/Sources/Robots/MS_ExportM1.algo new file mode 100644 index 0000000..0cb2074 Binary files /dev/null and b/Sources/Robots/MS_ExportM1.algo differ diff --git a/Sources/Robots/MS_ExportM1/.vscode/tasks.json b/Sources/Robots/MS_ExportM1/.vscode/tasks.json new file mode 100644 index 0000000..215166e --- /dev/null +++ b/Sources/Robots/MS_ExportM1/.vscode/tasks.json @@ -0,0 +1 @@ +{"version":"2.0.0","tasks":[{"label":"build","command":"dotnet","type":"process","args":["build","${workspaceFolder}","/property:GenerateFullPaths=true","/consoleLoggerParameters:NoSummary"],"problemMatcher":"$msCompile"}]} \ No newline at end of file diff --git a/Sources/Robots/MS_ExportM1/MS_ExportM1.sln b/Sources/Robots/MS_ExportM1/MS_ExportM1.sln new file mode 100644 index 0000000..bf13415 --- /dev/null +++ b/Sources/Robots/MS_ExportM1/MS_ExportM1.sln @@ -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}") = "MS_ExportM1", "MS_ExportM1\MS_ExportM1.csproj", "{f253903a-7911-406c-9c10-f6bf23ab3657}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {f253903a-7911-406c-9c10-f6bf23ab3657}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {f253903a-7911-406c-9c10-f6bf23ab3657}.Debug|Any CPU.Build.0 = Debug|Any CPU + {f253903a-7911-406c-9c10-f6bf23ab3657}.Release|Any CPU.ActiveCfg = Release|Any CPU + {f253903a-7911-406c-9c10-f6bf23ab3657}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/Sources/Robots/MS_ExportM1/MS_ExportM1/MS_ExportM1.cs b/Sources/Robots/MS_ExportM1/MS_ExportM1/MS_ExportM1.cs new file mode 100644 index 0000000..de296f4 --- /dev/null +++ b/Sources/Robots/MS_ExportM1/MS_ExportM1/MS_ExportM1.cs @@ -0,0 +1,193 @@ +using System; +using System.IO; +using System.IO.Compression; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using cAlgo.API; + +namespace cAlgo.Robots +{ + [Robot(AccessRights = AccessRights.FullAccess, AddIndicators = true)] + public class MS_ExportM1 : Robot + { + // The M1Record struct defines the binary format for a single M1 bar. + [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 56)] + private struct M1Record + { + public double OADateTime; + public long Open; + public long High; + public long Low; + public long Close; + public long Spread; + public int TickVolume; + public int Digits; + } + + [Parameter("Output Path", DefaultValue = "\\\\COFFEE\\TickData\\Pepperstone", Group = "Output Settings")] + public string DirectoryName { get; set; } + + // New parameter to control the batch file size in kilobytes. + [Parameter("Max File Size (kB)", DefaultValue = 10000, Group = "Output Settings", MinValue = 1)] + public int MaxFileSizeKb { get; set; } + + private Dictionary> _monthlyData; + private double _priceFactor; + + protected override void OnStart() + { + if (!IsBacktesting) + { + Print("This cBot only operates in Backtesting mode."); + Stop(); + return; + } + + if (string.IsNullOrWhiteSpace(DirectoryName)) + { + Print("Error: Output path is not specified."); + Stop(); + return; + } + + _monthlyData = new Dictionary>(); + _priceFactor = Math.Pow(10, Symbol.Digits); + Directory.CreateDirectory(DirectoryName); + Print("Starting M1 data accumulation..."); + } + + protected override void OnBar() + { + var barIndex = Bars.Count - 2; + if (barIndex < 0) return; + + var bar = Bars[barIndex]; + var monthKey = $"{Symbol.Name}_{bar.OpenTime:yyyy_MM}"; + + if (!_monthlyData.ContainsKey(monthKey)) + { + _monthlyData[monthKey] = new List(); + } + + var record = new M1Record + { + OADateTime = bar.OpenTime.ToOADate(), + Open = (long)(bar.Open * _priceFactor), + High = (long)(bar.High * _priceFactor), + Low = (long)(bar.Low * _priceFactor), + Close = (long)(bar.Close * _priceFactor), + Spread = (long)(Symbol.Spread * _priceFactor), + TickVolume = (int)bar.TickVolume, + Digits = Symbol.Digits + }; + + _monthlyData[monthKey].Add(record); + } + + protected override void OnTick() + { + // Not used + } + + // The OnStop method contains the new batching logic. + protected override void OnStop() + { + Print("Backtest finished. Writing accumulated data to batched files..."); + + if (_monthlyData == null || _monthlyData.Count == 0) + { + Print("No data was accumulated to write."); + return; + } + + // The keys (e.g., "EURUSD_2023_01") are sorted to process months chronologically. + var sortedMonthKeys = _monthlyData.Keys.ToList(); + sortedMonthKeys.Sort(); + + var recordBatch = new List(); + string startMonthKey = null; + long maxFileSizeBytes = MaxFileSizeKb * 1024; + var recordSize = Marshal.SizeOf(typeof(M1Record)); + + for (var i = 0; i < sortedMonthKeys.Count; i++) + { + var currentMonthKey = sortedMonthKeys[i]; + + // Set the start month for the new batch. + if (startMonthKey == null) + { + startMonthKey = currentMonthKey; + } + + // Add the entire month's data to the current batch. + recordBatch.AddRange(_monthlyData[currentMonthKey]); + + long currentBatchSizeBytes = recordBatch.Count * recordSize; + bool isLastMonth = (i == sortedMonthKeys.Count - 1); + + // Write the file if the size limit is exceeded OR if it's the last month. + // This ensures a month's data is never split. + if (currentBatchSizeBytes >= maxFileSizeBytes || isLastMonth) + { + var symbol = Symbol.Name; + var startDate = startMonthKey.Substring(symbol.Length + 1).Replace('_', '-'); + var endDate = currentMonthKey.Substring(symbol.Length + 1).Replace('_', '-'); + var fileNameBase = $"{symbol}_{startDate}_{endDate}"; + + WriteCompressedM1File(fileNameBase, recordBatch); + + // Reset for the next batch. + recordBatch.Clear(); + startMonthKey = null; + } + } + + Print("M1 data export completed successfully."); + } + + private void WriteCompressedM1File(string fileNameBase, List records) + { + // The path for the final archive file with an .m1 extension. + var archiveFilePath = Path.Combine(DirectoryName, $"{fileNameBase}.m1"); + // The name for the binary data file inside the archive. + var entryFileName = $"{fileNameBase}.m1"; + + var recordSize = Marshal.SizeOf(typeof(M1Record)); + var data = new byte[records.Count * recordSize]; + var handle = default(GCHandle); + + try + { + // This loop serializes the list of structs into a single byte array. + for (var i = 0; i < records.Count; i++) + { + var record = records[i]; + handle = GCHandle.Alloc(record, GCHandleType.Pinned); + Marshal.Copy(handle.AddrOfPinnedObject(), data, i * recordSize, recordSize); + handle.Free(); + } + } + finally + { + if (handle.IsAllocated) handle.Free(); + } + + // Create the ZIP archive file. + using (var fileStream = new FileStream(archiveFilePath, FileMode.Create)) + using (var archive = new ZipArchive(fileStream, ZipArchiveMode.Create)) + { + // Create a new entry inside the archive for the binary data. + var zipEntry = archive.CreateEntry(entryFileName, CompressionLevel.Optimal); + + // Open the entry's stream and write the serialized byte array to it. + using (var entryStream = zipEntry.Open()) + { + entryStream.Write(data, 0, data.Length); + } + } + + Print($"Created archive: {archiveFilePath} (Uncompressed Size: {data.Length / 1024.0:F2} kB)"); + } + } +} \ No newline at end of file diff --git a/Sources/Robots/MS_ExportM1/MS_ExportM1/MS_ExportM1.csproj b/Sources/Robots/MS_ExportM1/MS_ExportM1/MS_ExportM1.csproj new file mode 100644 index 0000000..da1fef1 --- /dev/null +++ b/Sources/Robots/MS_ExportM1/MS_ExportM1/MS_ExportM1.csproj @@ -0,0 +1,9 @@ + + + net6.0 + + + + + + diff --git a/Sources/Robots/MS_ExportM1/MS_ExportM1/obj/Debug/net6.0/.NETCoreApp,Version=v6.0.AssemblyAttributes.cs b/Sources/Robots/MS_ExportM1/MS_ExportM1/obj/Debug/net6.0/.NETCoreApp,Version=v6.0.AssemblyAttributes.cs new file mode 100644 index 0000000..32c95f9 --- /dev/null +++ b/Sources/Robots/MS_ExportM1/MS_ExportM1/obj/Debug/net6.0/.NETCoreApp,Version=v6.0.AssemblyAttributes.cs @@ -0,0 +1,4 @@ +// +using System; +using System.Reflection; +[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v6.0", FrameworkDisplayName = "")] diff --git a/Sources/Robots/MS_ExportM1/MS_ExportM1/obj/Debug/net6.0/MS_ExportM1.AssemblyInfo.cs b/Sources/Robots/MS_ExportM1/MS_ExportM1/obj/Debug/net6.0/MS_ExportM1.AssemblyInfo.cs new file mode 100644 index 0000000..f8ca53f --- /dev/null +++ b/Sources/Robots/MS_ExportM1/MS_ExportM1/obj/Debug/net6.0/MS_ExportM1.AssemblyInfo.cs @@ -0,0 +1,22 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +using System; +using System.Reflection; + +[assembly: System.Reflection.AssemblyCompanyAttribute("MS_ExportM1")] +[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] +[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0")] +[assembly: System.Reflection.AssemblyProductAttribute("MS_ExportM1")] +[assembly: System.Reflection.AssemblyTitleAttribute("MS_ExportM1")] +[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] + +// Generated by the MSBuild WriteCodeFragment class. + diff --git a/Sources/Robots/MS_ExportM1/MS_ExportM1/obj/Debug/net6.0/MS_ExportM1.AssemblyInfoInputs.cache b/Sources/Robots/MS_ExportM1/MS_ExportM1/obj/Debug/net6.0/MS_ExportM1.AssemblyInfoInputs.cache new file mode 100644 index 0000000..7bd95d5 --- /dev/null +++ b/Sources/Robots/MS_ExportM1/MS_ExportM1/obj/Debug/net6.0/MS_ExportM1.AssemblyInfoInputs.cache @@ -0,0 +1 @@ +8ce4c412fa4edea093f10e5541b067b21cdaf492 diff --git a/Sources/Robots/MS_ExportM1/MS_ExportM1/obj/Debug/net6.0/MS_ExportM1.GeneratedMSBuildEditorConfig.editorconfig b/Sources/Robots/MS_ExportM1/MS_ExportM1/obj/Debug/net6.0/MS_ExportM1.GeneratedMSBuildEditorConfig.editorconfig new file mode 100644 index 0000000..1360d65 --- /dev/null +++ b/Sources/Robots/MS_ExportM1/MS_ExportM1/obj/Debug/net6.0/MS_ExportM1.GeneratedMSBuildEditorConfig.editorconfig @@ -0,0 +1,10 @@ +is_global = true +build_property.TargetFramework = net6.0 +build_property.TargetPlatformMinVersion = +build_property.UsingMicrosoftNETSdkWeb = +build_property.ProjectTypeGuids = +build_property.InvariantGlobalization = +build_property.PlatformNeutralAssembly = +build_property._SupportedPlatformList = Linux,macOS,Windows +build_property.RootNamespace = MS_ExportM1 +build_property.ProjectDir = C:\Users\Brummel\Documents\cAlgo\Sources\Robots\MS_ExportM1\MS_ExportM1\ diff --git a/Sources/Robots/MS_ExportM1/MS_ExportM1/obj/Debug/net6.0/MS_ExportM1.assets.cache b/Sources/Robots/MS_ExportM1/MS_ExportM1/obj/Debug/net6.0/MS_ExportM1.assets.cache new file mode 100644 index 0000000..87a5970 Binary files /dev/null and b/Sources/Robots/MS_ExportM1/MS_ExportM1/obj/Debug/net6.0/MS_ExportM1.assets.cache differ diff --git a/Sources/Robots/MS_ExportM1/MS_ExportM1/obj/Debug/net6.0/MS_ExportM1.csproj.AssemblyReference.cache b/Sources/Robots/MS_ExportM1/MS_ExportM1/obj/Debug/net6.0/MS_ExportM1.csproj.AssemblyReference.cache new file mode 100644 index 0000000..76e6da8 Binary files /dev/null and b/Sources/Robots/MS_ExportM1/MS_ExportM1/obj/Debug/net6.0/MS_ExportM1.csproj.AssemblyReference.cache differ diff --git a/Sources/Robots/MS_ExportM1/MS_ExportM1/obj/MS_ExportM1.csproj.nuget.dgspec.json b/Sources/Robots/MS_ExportM1/MS_ExportM1/obj/MS_ExportM1.csproj.nuget.dgspec.json new file mode 100644 index 0000000..b4c50df --- /dev/null +++ b/Sources/Robots/MS_ExportM1/MS_ExportM1/obj/MS_ExportM1.csproj.nuget.dgspec.json @@ -0,0 +1,66 @@ +{ + "format": 1, + "restore": { + "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\MS_ExportM1\\MS_ExportM1\\MS_ExportM1.csproj": {} + }, + "projects": { + "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\MS_ExportM1\\MS_ExportM1\\MS_ExportM1.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\MS_ExportM1\\MS_ExportM1\\MS_ExportM1.csproj", + "projectName": "MS_ExportM1", + "projectPath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\MS_ExportM1\\MS_ExportM1\\MS_ExportM1.csproj", + "packagesPath": "C:\\Users\\Brummel\\.nuget\\packages\\", + "outputPath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\MS_ExportM1\\MS_ExportM1\\obj\\", + "projectStyle": "PackageReference", + "configFilePaths": [ + "C:\\Users\\Brummel\\AppData\\Roaming\\NuGet\\NuGet.Config" + ], + "originalTargetFrameworks": [ + "net6.0" + ], + "sources": { + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net6.0": { + "targetAlias": "net6.0", + "projectReferences": {} + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + } + }, + "frameworks": { + "net6.0": { + "targetAlias": "net6.0", + "dependencies": { + "cTrader.Automate": { + "target": "Package", + "version": "[*, )" + } + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\6.0.200\\RuntimeIdentifierGraph.json" + } + } + } + } +} \ No newline at end of file diff --git a/Sources/Robots/MS_ExportM1/MS_ExportM1/obj/MS_ExportM1.csproj.nuget.g.props b/Sources/Robots/MS_ExportM1/MS_ExportM1/obj/MS_ExportM1.csproj.nuget.g.props new file mode 100644 index 0000000..120d227 --- /dev/null +++ b/Sources/Robots/MS_ExportM1/MS_ExportM1/obj/MS_ExportM1.csproj.nuget.g.props @@ -0,0 +1,21 @@ + + + + True + NuGet + $(MSBuildThisFileDirectory)project.assets.json + $(UserProfile)\.nuget\packages\ + C:\Users\Brummel\.nuget\packages\ + PackageReference + 6.1.0 + + + + + + + + + C:\Users\Brummel\.nuget\packages\ctrader.automate\1.0.12 + + \ No newline at end of file diff --git a/Sources/Robots/MS_ExportM1/MS_ExportM1/obj/MS_ExportM1.csproj.nuget.g.targets b/Sources/Robots/MS_ExportM1/MS_ExportM1/obj/MS_ExportM1.csproj.nuget.g.targets new file mode 100644 index 0000000..29b3c4c --- /dev/null +++ b/Sources/Robots/MS_ExportM1/MS_ExportM1/obj/MS_ExportM1.csproj.nuget.g.targets @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Sources/Robots/MS_ExportM1/MS_ExportM1/obj/project.assets.json b/Sources/Robots/MS_ExportM1/MS_ExportM1/obj/project.assets.json new file mode 100644 index 0000000..433cfd0 --- /dev/null +++ b/Sources/Robots/MS_ExportM1/MS_ExportM1/obj/project.assets.json @@ -0,0 +1,139 @@ +{ + "version": 3, + "targets": { + "net6.0": { + "cTrader.Automate/1.0.12": { + "type": "package", + "compile": { + "lib/net6.0/cAlgo.API.dll": {} + }, + "runtime": { + "lib/net6.0/cAlgo.API.dll": {} + }, + "build": { + "build/cTrader.Automate.props": {}, + "build/cTrader.Automate.targets": {} + } + } + } + }, + "libraries": { + "cTrader.Automate/1.0.12": { + "sha512": "h+HPnbqPTkoSeojoYwWWOo5Jm9ExkmrmsU/WbF/idcgO2auKAPILBghZRP6Qt5mAcE/40YCRMNHT45/kzzs5Sg==", + "type": "package", + "path": "ctrader.automate/1.0.12", + "hasTools": true, + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "build/cTrader.Automate.props", + "build/cTrader.Automate.targets", + "ctrader.automate.1.0.12.nupkg.sha512", + "ctrader.automate.nuspec", + "eula.md", + "icon.png", + "lib/net40/cAlgo.API.dll", + "lib/net40/cAlgo.API.xml", + "lib/net6.0/cAlgo.API.dll", + "lib/net6.0/cAlgo.API.xml", + "tools/net472/Core.AlgoFormat.Compose.Reflection.dll", + "tools/net472/Core.AlgoFormat.Writer.dll", + "tools/net472/Core.AlgoFormat.dll", + "tools/net472/Core.Domain.Primitives.dll", + "tools/net472/Newtonsoft.Json.dll", + "tools/net472/System.Buffers.dll", + "tools/net472/System.Collections.Immutable.dll", + "tools/net472/System.Memory.dll", + "tools/net472/System.Numerics.Vectors.dll", + "tools/net472/System.Reflection.Metadata.dll", + "tools/net472/System.Reflection.MetadataLoadContext.dll", + "tools/net472/System.Runtime.CompilerServices.Unsafe.dll", + "tools/net472/cTrader.Automate.Sdk.Tasks.dll", + "tools/net6.0/Core.AlgoFormat.Compose.Reflection.dll", + "tools/net6.0/Core.AlgoFormat.Writer.dll", + "tools/net6.0/Core.AlgoFormat.dll", + "tools/net6.0/Core.Connection.Protobuf.Common.dll", + "tools/net6.0/Core.Domain.Primitives.dll", + "tools/net6.0/Microsoft.Win32.SystemEvents.dll", + "tools/net6.0/Newtonsoft.Json.dll", + "tools/net6.0/System.Drawing.Common.dll", + "tools/net6.0/System.Reflection.MetadataLoadContext.dll", + "tools/net6.0/System.Security.Permissions.dll", + "tools/net6.0/System.Windows.Extensions.dll", + "tools/net6.0/cTrader.Automate.Sdk.Tasks.dll", + "tools/net6.0/protobuf-net.Core.dll", + "tools/net6.0/protobuf-net.dll", + "tools/net6.0/runtimes/unix/lib/net6.0/System.Drawing.Common.dll", + "tools/net6.0/runtimes/win/lib/net6.0/Microsoft.Win32.SystemEvents.dll", + "tools/net6.0/runtimes/win/lib/net6.0/System.Drawing.Common.dll", + "tools/net6.0/runtimes/win/lib/net6.0/System.Windows.Extensions.dll" + ] + } + }, + "projectFileDependencyGroups": { + "net6.0": [ + "cTrader.Automate >= *" + ] + }, + "packageFolders": { + "C:\\Users\\Brummel\\.nuget\\packages\\": {} + }, + "project": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\MS_ExportM1\\MS_ExportM1\\MS_ExportM1.csproj", + "projectName": "MS_ExportM1", + "projectPath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\MS_ExportM1\\MS_ExportM1\\MS_ExportM1.csproj", + "packagesPath": "C:\\Users\\Brummel\\.nuget\\packages\\", + "outputPath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\MS_ExportM1\\MS_ExportM1\\obj\\", + "projectStyle": "PackageReference", + "configFilePaths": [ + "C:\\Users\\Brummel\\AppData\\Roaming\\NuGet\\NuGet.Config" + ], + "originalTargetFrameworks": [ + "net6.0" + ], + "sources": { + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net6.0": { + "targetAlias": "net6.0", + "projectReferences": {} + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + } + }, + "frameworks": { + "net6.0": { + "targetAlias": "net6.0", + "dependencies": { + "cTrader.Automate": { + "target": "Package", + "version": "[*, )" + } + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\6.0.200\\RuntimeIdentifierGraph.json" + } + } + } +} \ No newline at end of file diff --git a/Sources/Robots/MS_ExportM1/MS_ExportM1/obj/project.nuget.cache b/Sources/Robots/MS_ExportM1/MS_ExportM1/obj/project.nuget.cache new file mode 100644 index 0000000..c3ef36c --- /dev/null +++ b/Sources/Robots/MS_ExportM1/MS_ExportM1/obj/project.nuget.cache @@ -0,0 +1,10 @@ +{ + "version": 2, + "dgSpecHash": "GRpjVCmBWHgsHRIXVcYSIT2iekBcWzeywsXH//Ydh7cqImEi7KvxnFiQ+DXtKZNmE7kFdxuMJjhjI8Ai+rK6Xw==", + "success": true, + "projectFilePath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\MS_ExportM1\\MS_ExportM1\\MS_ExportM1.csproj", + "expectedPackageFiles": [ + "C:\\Users\\Brummel\\.nuget\\packages\\ctrader.automate\\1.0.12\\ctrader.automate.1.0.12.nupkg.sha512" + ], + "logs": [] +} \ No newline at end of file diff --git a/Sources/Robots/MS_ExportTickData.algo b/Sources/Robots/MS_ExportTickData.algo new file mode 100644 index 0000000..0f85559 Binary files /dev/null and b/Sources/Robots/MS_ExportTickData.algo differ diff --git a/Sources/Robots/MS_ExportTickData/.vscode/tasks.json b/Sources/Robots/MS_ExportTickData/.vscode/tasks.json new file mode 100644 index 0000000..215166e --- /dev/null +++ b/Sources/Robots/MS_ExportTickData/.vscode/tasks.json @@ -0,0 +1 @@ +{"version":"2.0.0","tasks":[{"label":"build","command":"dotnet","type":"process","args":["build","${workspaceFolder}","/property:GenerateFullPaths=true","/consoleLoggerParameters:NoSummary"],"problemMatcher":"$msCompile"}]} \ No newline at end of file diff --git a/Sources/Robots/MS_ExportTickData/MS_ExportTickData.sln b/Sources/Robots/MS_ExportTickData/MS_ExportTickData.sln new file mode 100644 index 0000000..3061f11 --- /dev/null +++ b/Sources/Robots/MS_ExportTickData/MS_ExportTickData.sln @@ -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}") = "MS_ExportTickData", "MS_ExportTickData\MS_ExportTickData.csproj", "{e689f11c-ce60-494c-a1f3-b269c1dfa127}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {e689f11c-ce60-494c-a1f3-b269c1dfa127}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {e689f11c-ce60-494c-a1f3-b269c1dfa127}.Debug|Any CPU.Build.0 = Debug|Any CPU + {e689f11c-ce60-494c-a1f3-b269c1dfa127}.Release|Any CPU.ActiveCfg = Release|Any CPU + {e689f11c-ce60-494c-a1f3-b269c1dfa127}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/Sources/Robots/MS_ExportTickData/MS_ExportTickData/MS_ExportTickData.cs b/Sources/Robots/MS_ExportTickData/MS_ExportTickData/MS_ExportTickData.cs new file mode 100644 index 0000000..4a6ec47 --- /dev/null +++ b/Sources/Robots/MS_ExportTickData/MS_ExportTickData/MS_ExportTickData.cs @@ -0,0 +1,446 @@ +/* +---------------------------------------------------------------------------------------------------- +Module: TickDataSaverCustomBinary +Version: 3.18 (Simplified yearly-to-monthly migration: delete yearly on success, no marker file) +Author: cTrader Coding Assistant +Date: May 27, 2025 + +Description: +This cTrader cBot captures tick data. Data is cached and written to monthly files. +Output directory is a FULL PATH. If directory doesn't exist, bot stops. + +One-Time Yearly to Monthly File Migration (OnStart, for years 2017-2025): +- Converts existing "Symbol_YYYY.tab" or "Symbol_YYYY.tab_zip" files into + new uncompressed "Symbol_YYYY_MM.tab" files. Successfully migrated yearly files are DELETED. + If monthly files for a year already exist, any corresponding yearly file is deleted. + WARNING: This conversion can be memory-intensive for very large yearly files. + +Post-Migration Operating Modes & Extensions: (Unchanged) +- Live Trading Output: ".tab-live" files (monthly, uncompressed). +- Historical Master Files: + - ".tab" for the current system month (uncompressed, actively updated). + - ".tab_zip" for months older than current system month (archived, compressed). +- Backtesting Output: + - ".tab_zip" for months older than current system month (if zip doesn't already exist). + - ".tab" (uncompressed) for the current system month. +... +---------------------------------------------------------------------------------------------------- +*/ +using System; +using System.IO; +using System.IO.Compression; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text.RegularExpressions; +using cAlgo.API; +using cAlgo.API.Internals; + +namespace cAlgo.Robots +{ + [Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.FullAccess)] + public class TickDataSaverCustomBinary : Robot + { + [Parameter("Output path", DefaultValue = "", Group = "Output Settings")] + public string DirectoryName { get; set; } + + [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 16)] + private struct TickDataRecord + { + public double OADateTime; + public float Ask; + public float Bid; + public TickDataRecord(DateTime dateTime, float ask, float bid) + { OADateTime = dateTime.ToOADate(); Ask = ask; Bid = bid; } + public DateTime RecordTime => DateTime.FromOADate(OADateTime); + } + + private List _tickCache; + private int _cachedDataYear = 0; + private int _cachedDataMonth = 0; + private string _dataFolderPath; + private int _systemYearAtStart; + private int _systemMonthAtStart; + private string _outputCurrentModeFileExtension; + private HashSet _sessionOutputMonthsInitialized; + private bool _isDirectoryValid = false; + + private DateTime GetLastTickTimeFromFile(string filePath) { /* ... (as in v3.17, unchanged) ... */ + if (!_isDirectoryValid) return DateTime.MinValue; + try + { + if (!File.Exists(filePath)) return DateTime.MinValue; + using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) + { + if (fs.Length < Marshal.SizeOf(typeof(TickDataRecord))) return DateTime.MinValue; + fs.Seek(-Marshal.SizeOf(typeof(TickDataRecord)), SeekOrigin.End); + using (BinaryReader reader = new BinaryReader(fs)) + { + double oaDate = reader.ReadDouble(); + return DateTime.FromOADate(oaDate); + } + } + } + catch (Exception ex) { Print($"Error reading last tick time from {filePath}: {ex.Message}"); return DateTime.MinValue; } + } + + private void CompressFileAndRemoveOriginal(string sourceTabFilePath, string targetZipFilePath, string entryNameInZip) { /* ... (as in v3.17, unchanged) ... */ + if (!_isDirectoryValid || !File.Exists(sourceTabFilePath)) + { Print($"Compression skipped: Source file '{sourceTabFilePath}' not found or directory invalid."); return; } + Print($"Attempting to compress '{sourceTabFilePath}' to '{targetZipFilePath}'."); + try + { + if (File.Exists(targetZipFilePath)) { File.Delete(targetZipFilePath); } + using (FileStream originalFileStream = new FileStream(sourceTabFilePath, FileMode.Open, FileAccess.Read, FileShare.None)) + using (FileStream compressedFileStream = new FileStream(targetZipFilePath, FileMode.CreateNew)) + using (ZipArchive archive = new ZipArchive(compressedFileStream, ZipArchiveMode.Create)) + { + ZipArchiveEntry entry = archive.CreateEntry(entryNameInZip); + using (Stream entryStream = entry.Open()) { originalFileStream.CopyTo(entryStream); } + } + Print($"Successfully compressed '{sourceTabFilePath}' to '{targetZipFilePath}'."); + try { File.Delete(sourceTabFilePath); Print($"Deleted original file '{sourceTabFilePath}'."); } + catch (Exception exDel) { Print($"Error deleting original file '{sourceTabFilePath}' after compression: {exDel.Message}"); } + } + catch (IOException ioEx) when (File.Exists(targetZipFilePath) && ioEx.Message.Contains("already exists")) + { Print($"Compressed file '{targetZipFilePath}' conflict during CreateNew. Error: {ioEx.Message}"); } + catch (Exception ex) + { + Print($"Error during compression of '{sourceTabFilePath}': {ex.Message}"); + if (File.Exists(targetZipFilePath) ) { try { using(var fs = File.OpenRead(targetZipFilePath)) { if (fs.Length == 0) File.Delete(targetZipFilePath); } Print("Cleaned up potentially incomplete zip file on error."); } catch {} } + } + } + + private void ArchivePreExistingOldTabFile(int yearToProcess, int monthToProcess) { /* ... (as in v3.17, unchanged, operates on monthly .tab) ... */ + if (!_isDirectoryValid) return; + string tabFileNameOnly = $"{Symbol.Name}_{yearToProcess}_{monthToProcess:D2}.tab"; + string tabFilePath = Path.Combine(_dataFolderPath, tabFileNameOnly); + string zipFilePath = Path.Combine(_dataFolderPath, $"{Symbol.Name}_{yearToProcess}_{monthToProcess:D2}.tab_zip"); + + if (File.Exists(tabFilePath)) + { + if (!File.Exists(zipFilePath)) + { + Print($"Found pre-existing .tab file for old month {yearToProcess}-{monthToProcess:D2}: '{tabFilePath}'. Archiving..."); + CompressFileAndRemoveOriginal(tabFilePath, zipFilePath, tabFileNameOnly); + } else { + Print($"Archive file '{zipFilePath}' already exists. Deleting redundant pre-existing .tab file '{tabFilePath}'."); + try { File.Delete(tabFilePath); } catch (Exception exDel) { Print($"Error deleting redundant .tab file '{tabFilePath}': {exDel.Message}"); } + } + } + } + + private List ReadAllRecordsFromStream(Stream stream) { /* ... (as in v3.17, unchanged) ... */ + var records = new List(); + using (var reader = new BinaryReader(stream)) + { + while (reader.BaseStream.Position < reader.BaseStream.Length) { + try { + records.Add(new TickDataRecord(DateTime.FromOADate(reader.ReadDouble()), reader.ReadSingle(), reader.ReadSingle())); + } catch (EndOfStreamException) { break; } + catch (Exception exRead) { Print($"Error reading record stream: {exRead.Message}"); break;} + } + } + return records; + } + + // Temporary one-time migration from Symbol_YYYY.ext to Symbol_YYYY_MM.tab + private void MigrateYearlyFilesToMonthlyIfNeeded() + { + if (!_isDirectoryValid) return; + Print("Starting one-time check to migrate old yearly files (2017-2025) to monthly .tab format..."); + + for (int yearToMigrate = 2017; yearToMigrate <= 2025; yearToMigrate++) + { + string yearlyTabFileName = $"{Symbol.Name}_{yearToMigrate}.tab"; // Entry name if from zip + string yearlyTabFilePath = Path.Combine(_dataFolderPath, yearlyTabFileName); + string yearlyZipFilePath = Path.Combine(_dataFolderPath, $"{Symbol.Name}_{yearToMigrate}.tab_zip"); + + // Check if monthly files for this year ALREADY exist. + bool monthlyFilesAlreadyExistForThisYear = false; + for (int m = 1; m <= 12; m++) + { + if (File.Exists(Path.Combine(_dataFolderPath, $"{Symbol.Name}_{yearToMigrate}_{m:D2}.tab")) || + File.Exists(Path.Combine(_dataFolderPath, $"{Symbol.Name}_{yearToMigrate}_{m:D2}.tab_zip"))) + { + monthlyFilesAlreadyExistForThisYear = true; + break; + } + } + + if (monthlyFilesAlreadyExistForThisYear) + { + Print($"Monthly files for year {yearToMigrate} already exist."); + // If monthly files exist, delete any remaining original yearly file for cleanup + if (File.Exists(yearlyTabFilePath)) + { + Print($"Deleting obsolete yearly file: {yearlyTabFilePath}"); + try { File.Delete(yearlyTabFilePath); } catch (Exception ex) { Print($"Error deleting {yearlyTabFilePath}: {ex.Message}"); } + } + if (File.Exists(yearlyZipFilePath)) + { + Print($"Deleting obsolete yearly archive: {yearlyZipFilePath}"); + try { File.Delete(yearlyZipFilePath); } catch (Exception ex) { Print($"Error deleting {yearlyZipFilePath}: {ex.Message}"); } + } + continue; // Skip to next year + } + + // If we reach here, no monthly files for yearToMigrate exist yet. + // Now, try to find and process the yearly source file. + List recordsForYear = null; + string sourceYearlyFileProcessed = null; // Path of the file that was read + + if (File.Exists(yearlyZipFilePath)) + { + Print($"Found yearly archive {yearlyZipFilePath} for migration. Decompressing..."); + sourceYearlyFileProcessed = yearlyZipFilePath; + try { + using (FileStream zipToOpen = new FileStream(yearlyZipFilePath, FileMode.Open, FileAccess.Read, FileShare.Read)) + using (ZipArchive archive = new ZipArchive(zipToOpen, ZipArchiveMode.Read)) { + // Assume the entry name inside the zip is the original .tab filename + ZipArchiveEntry entry = archive.Entries.FirstOrDefault(e => e.Name.Equals(yearlyTabFileName, StringComparison.OrdinalIgnoreCase)); + if (entry != null) { + using (Stream entryStream = entry.Open()) { recordsForYear = ReadAllRecordsFromStream(entryStream); } + } else { Print($"Entry '{yearlyTabFileName}' not found in {yearlyZipFilePath}."); } + } + } catch (Exception ex) { Print($"Error decompressing or reading {yearlyZipFilePath}: {ex.Message}"); } + } + else if (File.Exists(yearlyTabFilePath)) + { + Print($"Found yearly .tab file {yearlyTabFilePath} for migration. Reading..."); + sourceYearlyFileProcessed = yearlyTabFilePath; + try { + using (FileStream fs = File.OpenRead(yearlyTabFilePath)) { recordsForYear = ReadAllRecordsFromStream(fs); } + } catch (Exception ex) { Print($"Error reading {yearlyTabFilePath}: {ex.Message}"); } + } + + if (recordsForYear == null || !recordsForYear.Any()) + { + if (sourceYearlyFileProcessed != null) Print($"No records in {sourceYearlyFileProcessed} or read error for year {yearToMigrate}. Source file will not be deleted automatically if it was not processed."); + else Print($"No yearly file (.tab or .tab_zip) found for {yearToMigrate} to convert."); + continue; // Move to next year + } + + Print($"Distributing {recordsForYear.Count} records from year {yearToMigrate} (source: {Path.GetFileName(sourceYearlyFileProcessed)}) into monthly .tab files. WARNING: This may be memory intensive."); + var monthlyGroups = recordsForYear.GroupBy(r => r.RecordTime.Month); + bool conversionSuccessAllMonths = true; + + foreach (var group in monthlyGroups) + { + int month = group.Key; + string monthlyTabTargetName = $"{Symbol.Name}_{yearToMigrate}_{month:D2}.tab"; + string monthlyTabTargetPath = Path.Combine(_dataFolderPath, monthlyTabTargetName); + List monthRecords = group.OrderBy(r => r.RecordTime).ToList(); + + Print($"Writing {monthRecords.Count} records for {yearToMigrate}-{month:D2} to {monthlyTabTargetPath} (Mode: Create)."); + if (!WriteRawCacheDataToFile(monthlyTabTargetPath, FileMode.Create, monthRecords)) + { + conversionSuccessAllMonths = false; + Print($"Failed to write {monthlyTabTargetPath}. Yearly migration for {yearToMigrate} will be considered incomplete."); + break; + } + } + + if (conversionSuccessAllMonths) + { + Print($"Successfully converted all months for year {yearToMigrate} from {sourceYearlyFileProcessed}. Deleting original yearly file."); + try { File.Delete(sourceYearlyFileProcessed); } + catch (Exception ex) { Print($"Error deleting original yearly file {sourceYearlyFileProcessed} after successful conversion: {ex.Message}."); } + } + else + { + Print($"Conversion of {sourceYearlyFileProcessed} for year {yearToMigrate} was incomplete due to errors. Original yearly file NOT deleted."); + } + } + Print("One-time yearly to monthly file migration check finished."); + } + + private void UpdateHistoricalTabFile(int yearToUpdate, int monthToUpdate) { /* ... (as in v3.17, unchanged) ... */ + if (!_isDirectoryValid) { Print($"Skipping UpdateHistoricalTabFile for {yearToUpdate}-{monthToUpdate:D2} as output directory is invalid."); return; } + Print($"Attempting to update historical .tab file for month {yearToUpdate}-{monthToUpdate:D2} in: '{_dataFolderPath}'"); + string historyTabFilePath = Path.Combine(_dataFolderPath, $"{Symbol.Name}_{yearToUpdate}_{monthToUpdate:D2}.tab"); + string zipFilePath = Path.ChangeExtension(historyTabFilePath, ".tab_zip"); + if (File.Exists(zipFilePath)) { Print($"Skipping update for {historyTabFilePath} as a compressed version '{zipFilePath}' already exists (month likely archived)."); return; } + DateTime lastTickTimeInTabFile = GetLastTickTimeFromFile(historyTabFilePath); + if (lastTickTimeInTabFile != DateTime.MinValue) Print($"Last tick in {historyTabFilePath} is at: {lastTickTimeInTabFile:yyyy-MM-dd HH:mm:ss.fff} UTC"); + else Print($"No existing .tab file found for {yearToUpdate}-{monthToUpdate:D2} or it's empty: {historyTabFilePath}"); + Ticks serverTicks = MarketData.GetTicks(SymbolName); + List recordsToAppendToTab = new List(); + DateTime earliestTickAvailableInCollection = DateTime.MaxValue; + if (serverTicks.Any()) { earliestTickAvailableInCollection = serverTicks.OrderBy(t => t.Time).First().Time; Print($"Earliest tick from server: {earliestTickAvailableInCollection:yyyy-MM-dd HH:mm:ss.fff} UTC"); } + if (lastTickTimeInTabFile != DateTime.MinValue && earliestTickAvailableInCollection > lastTickTimeInTabFile) { + Print($"Attempting to load more history for .tab. Last in file: {lastTickTimeInTabFile:yyyy-MM-dd HH:mm:ss.fff} UTC, earliest from server: {earliestTickAvailableInCollection:yyyy-MM-dd HH:mm:ss.fff} UTC"); + const int maxLoadAttempts = 100; + int totalLoadedHistorically = 0; + for (int i = 0; i < maxLoadAttempts; i++) { + int newlyLoadedCount = 0; + try { newlyLoadedCount = serverTicks.LoadMoreHistory(); } catch(Exception e) { Print($"Error during LoadMoreHistory: {e.Message}"); break; } + if (newlyLoadedCount == 0 && i > 0) { Print("LoadMoreHistory returned 0 new ticks in this attempt."); break; } + totalLoadedHistorically += newlyLoadedCount; + if (!serverTicks.Any()) { Print("Ticks collection became empty after LoadMoreHistory."); break; } + earliestTickAvailableInCollection = serverTicks.OrderBy(t => t.Time).First().Time; + Print($"Total {totalLoadedHistorically} ticks potentially loaded via LoadMoreHistory. Earliest tick now: {earliestTickAvailableInCollection:yyyy-MM-dd HH:mm:ss.fff} UTC"); + if (earliestTickAvailableInCollection <= lastTickTimeInTabFile) { Print("Gap to .tab file successfully bridged or covered by loaded history."); break; } + if (i == maxLoadAttempts - 1) Print("Max history load attempts reached."); + } + if (earliestTickAvailableInCollection > lastTickTimeInTabFile) { + string msg = $"Gap to .tab file for {yearToUpdate}-{monthToUpdate:D2} might still exist. Last in .tab: {lastTickTimeInTabFile:yyyy-MM-dd HH:mm:ss.fff} UTC, Earliest from server: {earliestTickAvailableInCollection:yyyy-MM-dd HH:mm:ss.fff} UTC"; + Print($"WARNING: {msg}"); MessageBox.Show($"{msg}. Run backtest to rebuild history database.", $"Warning for {Symbol.Name}", MessageBoxButton.OK, MessageBoxImage.Warning, MessageBoxResult.OK); + } else if (earliestTickAvailableInCollection != DateTime.MaxValue) { Print($"History load seems to cover up to or before last tick in .tab file for {yearToUpdate}-{monthToUpdate:D2}."); } + } + var newHistoricalTicks = serverTicks.Where(t => t.Time.Year == yearToUpdate && t.Time.Month == monthToUpdate && t.Time > lastTickTimeInTabFile).OrderBy(t => t.Time); + foreach (Tick tick in newHistoricalTicks) recordsToAppendToTab.Add(new TickDataRecord(tick.Time, (float)tick.Ask, (float)tick.Bid)); + if (recordsToAppendToTab.Any()) { + Print($"Found {recordsToAppendToTab.Count} new historical records to append to {historyTabFilePath}."); + try { + using (FileStream fs = new FileStream(historyTabFilePath, FileMode.Append, FileAccess.Write, FileShare.Read)) + using (BinaryWriter writer = new BinaryWriter(fs)) { foreach (var record in recordsToAppendToTab) { writer.Write(record.OADateTime); writer.Write(record.Ask); writer.Write(record.Bid); } writer.Flush(); fs.Flush(true); } + Print($"Successfully appended {recordsToAppendToTab.Count} records to {historyTabFilePath}."); + } catch (Exception ex) { Print($"Error appending to historical .tab file {historyTabFilePath}: {ex.Message}"); } + } else { Print($"No new historical records found to append to .tab file for {yearToUpdate}-{monthToUpdate:D2}."); } + } + + // Helper method to write a list of TickDataRecord to a specific file with a specific mode + private bool WriteRawCacheDataToFile(string filePath, FileMode mode, List cacheToFlush) { /* ... (as in v3.17, unchanged) ... */ + if (!_isDirectoryValid) { Print($"WriteRawCacheDataToFile: Skipped writing to '{filePath}' as output directory is invalid."); return false; } + if (cacheToFlush == null || !cacheToFlush.Any()) { Print($"WriteRawCacheDataToFile: Cache provided for '{filePath}' is empty or null. No data written."); return true; } + Print($"Attempting to write {cacheToFlush.Count} records to '{filePath}' using {mode}."); + try { + string fileDirectory = Path.GetDirectoryName(filePath); + if (!string.IsNullOrEmpty(fileDirectory) && !Directory.Exists(fileDirectory)) { Print($"Attempting to create directory for file: {fileDirectory}"); Directory.CreateDirectory(fileDirectory); } + using (FileStream fileStream = new FileStream(filePath, mode, FileAccess.Write, FileShare.Read)) + using (BinaryWriter writer = new BinaryWriter(fileStream)) { foreach (var recordInCache in cacheToFlush) { writer.Write(recordInCache.OADateTime); writer.Write(recordInCache.Ask); writer.Write(recordInCache.Bid); } writer.Flush(); fileStream.Flush(true); } + Print($"{cacheToFlush.Count} records successfully written to '{filePath}'."); + return true; + } catch (DirectoryNotFoundException dnfe) { Print($"DIRECTORY NOT FOUND while writing to '{filePath}': {dnfe.Message}. Marking directory as invalid."); _isDirectoryValid = false; return false; } + catch (IOException ex) { Print($"IO Error writing cache to '{filePath}' using {mode}: {ex.Message}"); return false; } + catch (Exception ex) { Print($"Unexpected error writing cache to '{filePath}' using {mode}: {ex.Message}"); return false; } + } + + protected override void OnStart() { /* ... (as in v3.17, with MigrateYearlyFilesToMonthlyIfNeeded call first) ... */ + _tickCache = new List(); + _sessionOutputMonthsInitialized = new HashSet(); + _dataFolderPath = DirectoryName; + + if (string.IsNullOrWhiteSpace(_dataFolderPath)) { Print("FATAL ERROR: The 'Output path' parameter is not set."); _isDirectoryValid = false; Stop(); return; } + if (!Directory.Exists(_dataFolderPath)) { Print($"FATAL ERROR: The specified Output path does not exist: '{_dataFolderPath}'. Stopping cBot."); _isDirectoryValid = false; Stop(); return; } + else { _isDirectoryValid = true; Print($"Using output directory: '{_dataFolderPath}'"); } + + _systemYearAtStart = DateTime.Now.Year; + _systemMonthAtStart = DateTime.Now.Month; + _outputCurrentModeFileExtension = IsBacktesting ? ".tab" : ".tab-live"; + + Print("Custom binary tick data saver with caching started."); + Print($"Actual system date at start (for context): {_systemYearAtStart}-{_systemMonthAtStart:D2}"); + Print("Operating Mode: {0}. File extension for output files this session: '{1}'", IsBacktesting ? "Backtesting" : "Live", _outputCurrentModeFileExtension); + + if (_isDirectoryValid) + { + MigrateYearlyFilesToMonthlyIfNeeded(); // Perform one-time migration first + + if (!IsBacktesting) + { + Print("Live mode: Checking for pre-existing old monthly .tab files to archive..."); + for (int year = 2017; year <= _systemYearAtStart; year++) { + for (int month = 1; month <= 12; month++) { + if (year < _systemYearAtStart || (year == _systemYearAtStart && month < _systemMonthAtStart)) { + ArchivePreExistingOldTabFile(year, month); + } + } + } + Print($"Live mode: Initial update of historical .tab file for month {_systemYearAtStart}-{_systemMonthAtStart:D2}..."); + UpdateHistoricalTabFile(_systemYearAtStart, _systemMonthAtStart); + Print("Live mode: Re-initializing cache for .tab-live session file."); + _tickCache.Clear(); _cachedDataYear = 0; _cachedDataMonth = 0; _sessionOutputMonthsInitialized.Clear(); + Print("In live mode, current month's .tab-live data will be flushed on {0} bar close.", TimeFrame); + Print("Hourly timer starting for background updates of current month's .tab historical file."); + Timer.Start(3600); + } + } else { Print("File operations (conversion, live startup) skipped due to invalid output directory."); } + } + + protected override void OnTimer() { /* ... (as in v3.17, unchanged) ... */ + if (!_isDirectoryValid || IsBacktesting) return; + int currentYear = DateTime.Now.Year; + int currentMonth = DateTime.Now.Month; + Print($"Hourly timer triggered at {Server.Time:yyyy-MM-dd HH:mm:ss} UTC. Attempting to update historical .tab file for month {currentYear}-{currentMonth:D2}..."); + UpdateHistoricalTabFile(currentYear, currentMonth); + } + + protected override void OnBar() { /* ... (as in v3.17, unchanged) ... */ + if (!_isDirectoryValid || IsBacktesting) return; + int currentProcessingYear = Server.Time.Year; + int currentProcessingMonth = Server.Time.Month; + if (_cachedDataYear == currentProcessingYear && _cachedDataMonth == currentProcessingMonth && _tickCache.Any()) { + Print($"Bar closed on {TimeFrame}. Flushing cache to '{_outputCurrentModeFileExtension}' for live month {_cachedDataYear}-{_cachedDataMonth:D2} ({_tickCache.Count} records) at {Server.Time:yyyy-MM-dd HH:mm:ss.fff} UTC."); + if (FlushOutputCacheToFile(_cachedDataYear, _cachedDataMonth)) { _tickCache.Clear(); Print("Cache successfully flushed and cleared on bar close."); } + else { Print($"Failed to write cache to '{_outputCurrentModeFileExtension}' for month {_cachedDataYear}-{_cachedDataMonth:D2} on bar close. Cache ({_tickCache.Count} records) retained.");} + } + } + + protected override void OnTick() { /* ... (as in v3.17, unchanged) ... */ + if (!_isDirectoryValid) return; + DateTime tickTime = Server.Time; + float askPrice = (float)Symbol.Ask; + float bidPrice = (float)Symbol.Bid; + int yearOfTick = tickTime.Year; + int monthOfTick = tickTime.Month; + var record = new TickDataRecord(tickTime, askPrice, bidPrice); + bool monthOrYearChanged = (_cachedDataYear != 0 && (_cachedDataYear != yearOfTick || _cachedDataMonth != monthOfTick)); + if (monthOrYearChanged && _tickCache.Any()) { + Print($"Date change for output cache (from {_cachedDataYear}-{_cachedDataMonth:D2} to {yearOfTick}-{monthOfTick:D2}). Flushing cache for {_cachedDataYear}-{_cachedDataMonth:D2} ({_tickCache.Count} records)."); + if (FlushOutputCacheToFile(_cachedDataYear, _cachedDataMonth)) _tickCache.Clear(); + else { Print($"CRITICAL: Failed to flush output cache for {_cachedDataYear}-{_cachedDataMonth:D2}. Data remains in cache. Tick for {yearOfTick}-{monthOfTick:D2} dropped."); return; } + } + _cachedDataYear = yearOfTick; + _cachedDataMonth = monthOfTick; + _tickCache.Add(record); + } + + private bool FlushOutputCacheToFile(int yearToFlush, int monthToFlush) { /* ... (as in v3.17, unchanged) ... */ + if (!_isDirectoryValid) { Print($"Skipping FlushOutputCacheToFile for {yearToFlush}-{monthToFlush:D2}: directory invalid."); return false; } + if (!_tickCache.Any()) { Print($"FlushOutputCacheToFile called for {yearToFlush}-{monthToFlush:D2}, but cache is empty."); return true; } + string fileKey = $"{yearToFlush}{monthToFlush:D2}"; + bool success = false; + if (IsBacktesting) { + string uncompressedTabFileName = $"{Symbol.Name}_{yearToFlush}_{monthToFlush:D2}.tab"; + string uncompressedTabFilePath = Path.Combine(_dataFolderPath, uncompressedTabFileName); + string compressedZipFileName = $"{Symbol.Name}_{yearToFlush}_{monthToFlush:D2}.tab_zip"; + string compressedZipFilePath = Path.Combine(_dataFolderPath, compressedZipFileName); + bool isOlderThanCurrentSystemMonth = yearToFlush < _systemYearAtStart || (yearToFlush == _systemYearAtStart && monthToFlush < _systemMonthAtStart); + if (isOlderThanCurrentSystemMonth) { + if (File.Exists(compressedZipFilePath)) { Print($"Backtesting: Compressed file '{compressedZipFilePath}' for older month {yearToFlush}-{monthToFlush:D2} already exists. No new file created."); success = true; } + else { + Print($"Backtesting: Generating data for older month {yearToFlush}-{monthToFlush:D2} to temporary file '{uncompressedTabFilePath}' for compression."); + if (WriteRawCacheDataToFile(uncompressedTabFilePath, FileMode.Create, _tickCache)) { CompressFileAndRemoveOriginal(uncompressedTabFilePath, compressedZipFilePath, uncompressedTabFileName); success = true; } + else { Print($"Backtesting: Failed to write .tab for {yearToFlush}-{monthToFlush:D2}. Compression skipped."); } + } + } else { + Print($"Backtesting: Generating uncompressed .tab file for current/future system month {yearToFlush}-{monthToFlush:D2}: '{uncompressedTabFilePath}'."); + success = WriteRawCacheDataToFile(uncompressedTabFilePath, FileMode.Create, _tickCache); + } + } else { + string outputFileName = $"{Symbol.Name}_{yearToFlush}_{monthToFlush:D2}{_outputCurrentModeFileExtension}"; + string outputFilePath = Path.Combine(_dataFolderPath, outputFileName); + FileMode mode = _sessionOutputMonthsInitialized.Contains(fileKey) ? FileMode.Append : FileMode.Create; + Print($"Mode for output file {outputFilePath} (Live .tab-live): {mode}."); + success = WriteRawCacheDataToFile(outputFilePath, mode, _tickCache); + if (success && mode == FileMode.Create) { _sessionOutputMonthsInitialized.Add(fileKey); } + } + return success; + } + + protected override void OnStop() { /* ... (as in v3.17, unchanged) ... */ + Print("Stopping cBot..."); + if (!IsBacktesting && _isDirectoryValid) { Timer.Stop(); Print("Hourly timer for .tab file updates stopped."); } + if (_isDirectoryValid && _tickCache.Any()) { + Print($"OnStop: Flushing remaining {_tickCache.Count} records from output cache for month {_cachedDataYear}-{_cachedDataMonth:D2} (Target ext: '{_outputCurrentModeFileExtension}')."); + if (FlushOutputCacheToFile(_cachedDataYear, _cachedDataMonth)) { _tickCache.Clear(); Print("Output cache successfully flushed and cleared."); } + else { Print($"OnStop: Failed to flush remaining output cache for month {_cachedDataYear}-{_cachedDataMonth:D2}. {_tickCache.Count} records may be lost."); } + } else if (!_isDirectoryValid) { Print("OnStop: No data flushed as output directory was invalid."); } + else { Print("OnStop: No data in output cache to flush."); } + _sessionOutputMonthsInitialized.Clear(); + Print("TickDataSaverCustomBinary stopped."); + } + } +} \ No newline at end of file diff --git a/Sources/Robots/MS_ExportTickData/MS_ExportTickData/MS_ExportTickData.csproj b/Sources/Robots/MS_ExportTickData/MS_ExportTickData/MS_ExportTickData.csproj new file mode 100644 index 0000000..866383b --- /dev/null +++ b/Sources/Robots/MS_ExportTickData/MS_ExportTickData/MS_ExportTickData.csproj @@ -0,0 +1,14 @@ + + + + net6.0 + + + + + + + ..\..\..\Common\MSLib\obj\Debug\net6.0\MSLib.dll + + + \ No newline at end of file diff --git a/Sources/Robots/MS_ExportTickData/MS_ExportTickData/obj/Debug/net6.0/.NETCoreApp,Version=v6.0.AssemblyAttributes.cs b/Sources/Robots/MS_ExportTickData/MS_ExportTickData/obj/Debug/net6.0/.NETCoreApp,Version=v6.0.AssemblyAttributes.cs new file mode 100644 index 0000000..32c95f9 --- /dev/null +++ b/Sources/Robots/MS_ExportTickData/MS_ExportTickData/obj/Debug/net6.0/.NETCoreApp,Version=v6.0.AssemblyAttributes.cs @@ -0,0 +1,4 @@ +// +using System; +using System.Reflection; +[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v6.0", FrameworkDisplayName = "")] diff --git a/Sources/Robots/MS_ExportTickData/MS_ExportTickData/obj/Debug/net6.0/MS_ExportTickData.AssemblyInfo.cs b/Sources/Robots/MS_ExportTickData/MS_ExportTickData/obj/Debug/net6.0/MS_ExportTickData.AssemblyInfo.cs new file mode 100644 index 0000000..78d6cb3 --- /dev/null +++ b/Sources/Robots/MS_ExportTickData/MS_ExportTickData/obj/Debug/net6.0/MS_ExportTickData.AssemblyInfo.cs @@ -0,0 +1,22 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +using System; +using System.Reflection; + +[assembly: System.Reflection.AssemblyCompanyAttribute("MS_ExportTickData")] +[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] +[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0")] +[assembly: System.Reflection.AssemblyProductAttribute("MS_ExportTickData")] +[assembly: System.Reflection.AssemblyTitleAttribute("MS_ExportTickData")] +[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] + +// Generated by the MSBuild WriteCodeFragment class. + diff --git a/Sources/Robots/MS_ExportTickData/MS_ExportTickData/obj/Debug/net6.0/MS_ExportTickData.AssemblyInfoInputs.cache b/Sources/Robots/MS_ExportTickData/MS_ExportTickData/obj/Debug/net6.0/MS_ExportTickData.AssemblyInfoInputs.cache new file mode 100644 index 0000000..f614a0b --- /dev/null +++ b/Sources/Robots/MS_ExportTickData/MS_ExportTickData/obj/Debug/net6.0/MS_ExportTickData.AssemblyInfoInputs.cache @@ -0,0 +1 @@ +c3d257cf2f7ddc396be5a92ae68365f0f6daba64 diff --git a/Sources/Robots/MS_ExportTickData/MS_ExportTickData/obj/Debug/net6.0/MS_ExportTickData.GeneratedMSBuildEditorConfig.editorconfig b/Sources/Robots/MS_ExportTickData/MS_ExportTickData/obj/Debug/net6.0/MS_ExportTickData.GeneratedMSBuildEditorConfig.editorconfig new file mode 100644 index 0000000..8eb2649 --- /dev/null +++ b/Sources/Robots/MS_ExportTickData/MS_ExportTickData/obj/Debug/net6.0/MS_ExportTickData.GeneratedMSBuildEditorConfig.editorconfig @@ -0,0 +1,10 @@ +is_global = true +build_property.TargetFramework = net6.0 +build_property.TargetPlatformMinVersion = +build_property.UsingMicrosoftNETSdkWeb = +build_property.ProjectTypeGuids = +build_property.InvariantGlobalization = +build_property.PlatformNeutralAssembly = +build_property._SupportedPlatformList = Linux,macOS,Windows +build_property.RootNamespace = MS_ExportTickData +build_property.ProjectDir = C:\Users\Brummel\Documents\cAlgo\Sources\Robots\MS_ExportTickData\MS_ExportTickData\ diff --git a/Sources/Robots/MS_ExportTickData/MS_ExportTickData/obj/Debug/net6.0/MS_ExportTickData.assets.cache b/Sources/Robots/MS_ExportTickData/MS_ExportTickData/obj/Debug/net6.0/MS_ExportTickData.assets.cache new file mode 100644 index 0000000..e97909c Binary files /dev/null and b/Sources/Robots/MS_ExportTickData/MS_ExportTickData/obj/Debug/net6.0/MS_ExportTickData.assets.cache differ diff --git a/Sources/Robots/MS_ExportTickData/MS_ExportTickData/obj/Debug/net6.0/MS_ExportTickData.csproj.AssemblyReference.cache b/Sources/Robots/MS_ExportTickData/MS_ExportTickData/obj/Debug/net6.0/MS_ExportTickData.csproj.AssemblyReference.cache new file mode 100644 index 0000000..f4d0916 Binary files /dev/null and b/Sources/Robots/MS_ExportTickData/MS_ExportTickData/obj/Debug/net6.0/MS_ExportTickData.csproj.AssemblyReference.cache differ diff --git a/Sources/Robots/MS_ExportTickData/MS_ExportTickData/obj/MS_ExportTickData.csproj.nuget.dgspec.json b/Sources/Robots/MS_ExportTickData/MS_ExportTickData/obj/MS_ExportTickData.csproj.nuget.dgspec.json new file mode 100644 index 0000000..c3b3f9a --- /dev/null +++ b/Sources/Robots/MS_ExportTickData/MS_ExportTickData/obj/MS_ExportTickData.csproj.nuget.dgspec.json @@ -0,0 +1,66 @@ +{ + "format": 1, + "restore": { + "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\MS_ExportTickData\\MS_ExportTickData\\MS_ExportTickData.csproj": {} + }, + "projects": { + "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\MS_ExportTickData\\MS_ExportTickData\\MS_ExportTickData.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\MS_ExportTickData\\MS_ExportTickData\\MS_ExportTickData.csproj", + "projectName": "MS_ExportTickData", + "projectPath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\MS_ExportTickData\\MS_ExportTickData\\MS_ExportTickData.csproj", + "packagesPath": "C:\\Users\\Brummel\\.nuget\\packages\\", + "outputPath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\MS_ExportTickData\\MS_ExportTickData\\obj\\", + "projectStyle": "PackageReference", + "configFilePaths": [ + "C:\\Users\\Brummel\\AppData\\Roaming\\NuGet\\NuGet.Config" + ], + "originalTargetFrameworks": [ + "net6.0" + ], + "sources": { + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net6.0": { + "targetAlias": "net6.0", + "projectReferences": {} + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + } + }, + "frameworks": { + "net6.0": { + "targetAlias": "net6.0", + "dependencies": { + "cTrader.Automate": { + "target": "Package", + "version": "[*, )" + } + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\6.0.200\\RuntimeIdentifierGraph.json" + } + } + } + } +} \ No newline at end of file diff --git a/Sources/Robots/MS_ExportTickData/MS_ExportTickData/obj/MS_ExportTickData.csproj.nuget.g.props b/Sources/Robots/MS_ExportTickData/MS_ExportTickData/obj/MS_ExportTickData.csproj.nuget.g.props new file mode 100644 index 0000000..120d227 --- /dev/null +++ b/Sources/Robots/MS_ExportTickData/MS_ExportTickData/obj/MS_ExportTickData.csproj.nuget.g.props @@ -0,0 +1,21 @@ + + + + True + NuGet + $(MSBuildThisFileDirectory)project.assets.json + $(UserProfile)\.nuget\packages\ + C:\Users\Brummel\.nuget\packages\ + PackageReference + 6.1.0 + + + + + + + + + C:\Users\Brummel\.nuget\packages\ctrader.automate\1.0.12 + + \ No newline at end of file diff --git a/Sources/Robots/MS_ExportTickData/MS_ExportTickData/obj/MS_ExportTickData.csproj.nuget.g.targets b/Sources/Robots/MS_ExportTickData/MS_ExportTickData/obj/MS_ExportTickData.csproj.nuget.g.targets new file mode 100644 index 0000000..29b3c4c --- /dev/null +++ b/Sources/Robots/MS_ExportTickData/MS_ExportTickData/obj/MS_ExportTickData.csproj.nuget.g.targets @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Sources/Robots/MS_ExportTickData/MS_ExportTickData/obj/project.assets.json b/Sources/Robots/MS_ExportTickData/MS_ExportTickData/obj/project.assets.json new file mode 100644 index 0000000..e8fe508 --- /dev/null +++ b/Sources/Robots/MS_ExportTickData/MS_ExportTickData/obj/project.assets.json @@ -0,0 +1,139 @@ +{ + "version": 3, + "targets": { + "net6.0": { + "cTrader.Automate/1.0.12": { + "type": "package", + "compile": { + "lib/net6.0/cAlgo.API.dll": {} + }, + "runtime": { + "lib/net6.0/cAlgo.API.dll": {} + }, + "build": { + "build/cTrader.Automate.props": {}, + "build/cTrader.Automate.targets": {} + } + } + } + }, + "libraries": { + "cTrader.Automate/1.0.12": { + "sha512": "h+HPnbqPTkoSeojoYwWWOo5Jm9ExkmrmsU/WbF/idcgO2auKAPILBghZRP6Qt5mAcE/40YCRMNHT45/kzzs5Sg==", + "type": "package", + "path": "ctrader.automate/1.0.12", + "hasTools": true, + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "build/cTrader.Automate.props", + "build/cTrader.Automate.targets", + "ctrader.automate.1.0.12.nupkg.sha512", + "ctrader.automate.nuspec", + "eula.md", + "icon.png", + "lib/net40/cAlgo.API.dll", + "lib/net40/cAlgo.API.xml", + "lib/net6.0/cAlgo.API.dll", + "lib/net6.0/cAlgo.API.xml", + "tools/net472/Core.AlgoFormat.Compose.Reflection.dll", + "tools/net472/Core.AlgoFormat.Writer.dll", + "tools/net472/Core.AlgoFormat.dll", + "tools/net472/Core.Domain.Primitives.dll", + "tools/net472/Newtonsoft.Json.dll", + "tools/net472/System.Buffers.dll", + "tools/net472/System.Collections.Immutable.dll", + "tools/net472/System.Memory.dll", + "tools/net472/System.Numerics.Vectors.dll", + "tools/net472/System.Reflection.Metadata.dll", + "tools/net472/System.Reflection.MetadataLoadContext.dll", + "tools/net472/System.Runtime.CompilerServices.Unsafe.dll", + "tools/net472/cTrader.Automate.Sdk.Tasks.dll", + "tools/net6.0/Core.AlgoFormat.Compose.Reflection.dll", + "tools/net6.0/Core.AlgoFormat.Writer.dll", + "tools/net6.0/Core.AlgoFormat.dll", + "tools/net6.0/Core.Connection.Protobuf.Common.dll", + "tools/net6.0/Core.Domain.Primitives.dll", + "tools/net6.0/Microsoft.Win32.SystemEvents.dll", + "tools/net6.0/Newtonsoft.Json.dll", + "tools/net6.0/System.Drawing.Common.dll", + "tools/net6.0/System.Reflection.MetadataLoadContext.dll", + "tools/net6.0/System.Security.Permissions.dll", + "tools/net6.0/System.Windows.Extensions.dll", + "tools/net6.0/cTrader.Automate.Sdk.Tasks.dll", + "tools/net6.0/protobuf-net.Core.dll", + "tools/net6.0/protobuf-net.dll", + "tools/net6.0/runtimes/unix/lib/net6.0/System.Drawing.Common.dll", + "tools/net6.0/runtimes/win/lib/net6.0/Microsoft.Win32.SystemEvents.dll", + "tools/net6.0/runtimes/win/lib/net6.0/System.Drawing.Common.dll", + "tools/net6.0/runtimes/win/lib/net6.0/System.Windows.Extensions.dll" + ] + } + }, + "projectFileDependencyGroups": { + "net6.0": [ + "cTrader.Automate >= *" + ] + }, + "packageFolders": { + "C:\\Users\\Brummel\\.nuget\\packages\\": {} + }, + "project": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\MS_ExportTickData\\MS_ExportTickData\\MS_ExportTickData.csproj", + "projectName": "MS_ExportTickData", + "projectPath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\MS_ExportTickData\\MS_ExportTickData\\MS_ExportTickData.csproj", + "packagesPath": "C:\\Users\\Brummel\\.nuget\\packages\\", + "outputPath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\MS_ExportTickData\\MS_ExportTickData\\obj\\", + "projectStyle": "PackageReference", + "configFilePaths": [ + "C:\\Users\\Brummel\\AppData\\Roaming\\NuGet\\NuGet.Config" + ], + "originalTargetFrameworks": [ + "net6.0" + ], + "sources": { + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net6.0": { + "targetAlias": "net6.0", + "projectReferences": {} + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + } + }, + "frameworks": { + "net6.0": { + "targetAlias": "net6.0", + "dependencies": { + "cTrader.Automate": { + "target": "Package", + "version": "[*, )" + } + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\6.0.200\\RuntimeIdentifierGraph.json" + } + } + } +} \ No newline at end of file diff --git a/Sources/Robots/MS_ExportTickData/MS_ExportTickData/obj/project.nuget.cache b/Sources/Robots/MS_ExportTickData/MS_ExportTickData/obj/project.nuget.cache new file mode 100644 index 0000000..6311bea --- /dev/null +++ b/Sources/Robots/MS_ExportTickData/MS_ExportTickData/obj/project.nuget.cache @@ -0,0 +1,10 @@ +{ + "version": 2, + "dgSpecHash": "HudTK3f0N+tNjBJb2A2PyUN32ap0Za5MJN0GkKma6X3CtPxIznL+XMezLx70VyeNs6v0IekOGrY2sHeH8g7KIQ==", + "success": true, + "projectFilePath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\MS_ExportTickData\\MS_ExportTickData\\MS_ExportTickData.csproj", + "expectedPackageFiles": [ + "C:\\Users\\Brummel\\.nuget\\packages\\ctrader.automate\\1.0.12\\ctrader.automate.1.0.12.nupkg.sha512" + ], + "logs": [] +} \ No newline at end of file diff --git a/Sources/Robots/MS_HistoryToZorro.algo b/Sources/Robots/MS_HistoryToZorro.algo new file mode 100644 index 0000000..fecdcc6 Binary files /dev/null and b/Sources/Robots/MS_HistoryToZorro.algo differ diff --git a/Sources/Robots/MS_HistoryToZorro/.vscode/tasks.json b/Sources/Robots/MS_HistoryToZorro/.vscode/tasks.json new file mode 100644 index 0000000..215166e --- /dev/null +++ b/Sources/Robots/MS_HistoryToZorro/.vscode/tasks.json @@ -0,0 +1 @@ +{"version":"2.0.0","tasks":[{"label":"build","command":"dotnet","type":"process","args":["build","${workspaceFolder}","/property:GenerateFullPaths=true","/consoleLoggerParameters:NoSummary"],"problemMatcher":"$msCompile"}]} \ No newline at end of file diff --git a/Sources/Robots/MS_HistoryToZorro/MS_HistoryToZorro.sln b/Sources/Robots/MS_HistoryToZorro/MS_HistoryToZorro.sln new file mode 100644 index 0000000..ef8c727 --- /dev/null +++ b/Sources/Robots/MS_HistoryToZorro/MS_HistoryToZorro.sln @@ -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}") = "MS_HistoryToZorro", "MS_HistoryToZorro\MS_HistoryToZorro.csproj", "{95abf5e0-ec18-4558-8e7c-15f99fdc7ec6}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {95abf5e0-ec18-4558-8e7c-15f99fdc7ec6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {95abf5e0-ec18-4558-8e7c-15f99fdc7ec6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {95abf5e0-ec18-4558-8e7c-15f99fdc7ec6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {95abf5e0-ec18-4558-8e7c-15f99fdc7ec6}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/Sources/Robots/MS_HistoryToZorro/MS_HistoryToZorro/MS_HistoryToZorro.cs b/Sources/Robots/MS_HistoryToZorro/MS_HistoryToZorro/MS_HistoryToZorro.cs new file mode 100644 index 0000000..89d0de7 --- /dev/null +++ b/Sources/Robots/MS_HistoryToZorro/MS_HistoryToZorro/MS_HistoryToZorro.cs @@ -0,0 +1,174 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text; +using cAlgo.API; +using cAlgo.API.Internals; +using Zorro; // For ZorroT6 and ZorroT6ArrayBuilder from your ZorroBinaryFiles.cs + +namespace cAlgo.Robots +{ + [Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.FullAccess)] + public class BacktestYearlyDualExporterWithPrefix : Robot + { + [Parameter("Output Directory Path", DefaultValue = "C:\\Temp\\YearlyDualM1Export")] + public string OutputDirectory { get; set; } + + [Parameter("Filename Prefix", DefaultValue = "cTrader")] + public string FileNamePrefix { get; set; } + + private Dictionary> _yearlyCsvData; + private Dictionary _yearlyT6Builders; + private string _symbolSpecificOutputPath; + + protected override void OnStart() + { + _yearlyCsvData = new Dictionary>(); + _yearlyT6Builders = new Dictionary(); + + // Ensure Bars.Count is sufficient for .Last(1) access in OnBar, + // though OnBar typically isn't called until there are enough bars. + if (Bars.Count < 2 && IsBacktesting) // Check specifically for backtesting context + { + Print("Warning: Not enough bars loaded at OnStart to determine initial data for .Last(1). Will proceed and rely on OnBar calls."); + } + + + try + { + Directory.CreateDirectory(OutputDirectory); + _symbolSpecificOutputPath = Path.Combine(OutputDirectory, Symbol.Name); + Directory.CreateDirectory(_symbolSpecificOutputPath); + } + catch (Exception ex) + { + Print($"Error creating output directories: {ex.Message}. Stopping cBot."); + Stop(); + return; + } + + Print($"cBot started. Data for symbol {Symbol.Name} ({TimeFrame}) during the backtest period will be collected."); + Print($"Output will be saved to yearly files in: {_symbolSpecificOutputPath} with prefix '{FileNamePrefix}'"); + } + + protected override void OnBar() + { + // OnBar is called when a new bar opens. + // We need data from the bar that just *closed*. + // This corresponds to index [Bars.Count - 2] or .Last(1) on DataSeries. + // Check if enough bars exist. In backtesting, OnBar implies at least 2 bars are loaded + // (1 closed, 1 opening). + if (Bars.Count < 2) + { + // This should not happen in a normal OnBar sequence during backtesting + // after the history has started loading. + Print("Warning: OnBar called with Bars.Count < 2. Skipping this bar to prevent index error."); + return; + } + + // Correctly define barCloseTime as the OpenTime of the current, newly forming bar. + // This is the actual closing moment of the previous bar. + DateTime barCloseTime = Bars.OpenTimes.LastValue; // OpenTime of bar at index Bars.Count - 1 + + // The year for file splitting should be based on the timestamp of the bar being recorded. + // If barCloseTime represents the *end* of the period for lastBar, its year is appropriate. + int currentBarYear = barCloseTime.Year; + + // --- Ensure collections exist for the current year --- + if (!_yearlyCsvData.ContainsKey(currentBarYear)) + { + _yearlyCsvData[currentBarYear] = new List(); + _yearlyCsvData[currentBarYear].Add("Timestamp,Open,High,Low,Close,Volume,Spread"); + + _yearlyT6Builders[currentBarYear] = new ZorroT6ArrayBuilder(); + + Print($"Started collecting data for year: {currentBarYear}. Filenames will use prefix '{FileNamePrefix}'."); + } + + // --- Data points from the bar that just closed (using .Last(1) for each series) --- + float open = (float)Bars.OpenPrices.Last(1); // Data from bar at index Bars.Count - 2 + float high = (float)Bars.HighPrices.Last(1); // Data from bar at index Bars.Count - 2 + float low = (float)Bars.LowPrices.Last(1); // Data from bar at index Bars.Count - 2 + float close = (float)Bars.ClosePrices.Last(1); // Data from bar at index Bars.Count - 2 + float tickVolume = (float)Bars.TickVolumes.Last(1); // Data from bar at index Bars.Count - 2 + float spreadPlaceholder = 0.0f; + + // --- Populate CSV Data for the current year --- + string timestampStr = barCloseTime.ToString("yyyy-MM-dd HH:mm:ss"); + string openStr = open.ToString(CultureInfo.InvariantCulture); + string highStr = high.ToString(CultureInfo.InvariantCulture); + string lowStr = low.ToString(CultureInfo.InvariantCulture); + string closeStr = close.ToString(CultureInfo.InvariantCulture); + string volumeStrCsv = tickVolume.ToString(CultureInfo.InvariantCulture); + string spreadStrCsv = spreadPlaceholder.ToString("F1", CultureInfo.InvariantCulture); + + _yearlyCsvData[currentBarYear].Add($"{timestampStr},{openStr},{highStr},{lowStr},{closeStr},{volumeStrCsv},{spreadStrCsv}"); + + // --- Populate T6 Data for the current year --- + _yearlyT6Builders[currentBarYear].Add(barCloseTime, high, low, open, close, spreadPlaceholder, tickVolume); + } + + protected override void OnStop() + { + Print("Backtest finished. Saving collected data to yearly files..."); + + // --- Save CSV data for each year --- + foreach (var yearEntry in _yearlyCsvData) + { + int year = yearEntry.Key; + List csvLines = yearEntry.Value; + string csvFileName = $"{FileNamePrefix}_{Symbol.Name}_{year}.csv"; + string csvFilePath = Path.Combine(_symbolSpecificOutputPath, csvFileName); + + if (csvLines.Count > 1) + { + Print($"Saving {csvLines.Count -1} data records to CSV: {csvFilePath}"); + try + { + File.WriteAllLines(csvFilePath, csvLines, Encoding.UTF8); + Print($"Successfully saved CSV: {csvFilePath}"); + } + catch (Exception ex) + { + Print($"Error writing CSV file '{csvFilePath}': {ex.Message}"); + } + } + else + { + Print($"No data (or only header) collected for CSV for year {year}. Skipping file: {csvFilePath}"); + } + } + + // --- Save T6 binary data for each year --- + foreach (var yearEntry in _yearlyT6Builders) + { + int year = yearEntry.Key; + ZorroT6ArrayBuilder t6Builder = yearEntry.Value; + string t6FileName = $"{FileNamePrefix}_{Symbol.Name}_{year}.t6"; + string t6FilePath = Path.Combine(_symbolSpecificOutputPath, t6FileName); + + if (t6Builder.Count > 0) + { + Print($"Saving {t6Builder.Count} records to T6 binary: {t6FilePath}"); + try + { + t6Builder.SaveToFile(t6FilePath); + Print($"Successfully saved T6: {t6FilePath}"); + } + catch (Exception ex) + { + Print($"Error writing T6 binary file '{t6FilePath}': {ex.Message}"); + } + } + else + { + Print($"No data collected for T6 for year {year}. Skipping file: {t6FilePath}"); + } + } + + Print("Yearly data export process completed."); + } + } +} \ No newline at end of file diff --git a/Sources/Robots/MS_HistoryToZorro/MS_HistoryToZorro/MS_HistoryToZorro.csproj b/Sources/Robots/MS_HistoryToZorro/MS_HistoryToZorro/MS_HistoryToZorro.csproj new file mode 100644 index 0000000..866383b --- /dev/null +++ b/Sources/Robots/MS_HistoryToZorro/MS_HistoryToZorro/MS_HistoryToZorro.csproj @@ -0,0 +1,14 @@ + + + + net6.0 + + + + + + + ..\..\..\Common\MSLib\obj\Debug\net6.0\MSLib.dll + + + \ No newline at end of file diff --git a/Sources/Robots/MS_HistoryToZorro/MS_HistoryToZorro/obj/Debug/net6.0/.NETCoreApp,Version=v6.0.AssemblyAttributes.cs b/Sources/Robots/MS_HistoryToZorro/MS_HistoryToZorro/obj/Debug/net6.0/.NETCoreApp,Version=v6.0.AssemblyAttributes.cs new file mode 100644 index 0000000..32c95f9 --- /dev/null +++ b/Sources/Robots/MS_HistoryToZorro/MS_HistoryToZorro/obj/Debug/net6.0/.NETCoreApp,Version=v6.0.AssemblyAttributes.cs @@ -0,0 +1,4 @@ +// +using System; +using System.Reflection; +[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v6.0", FrameworkDisplayName = "")] diff --git a/Sources/Robots/MS_HistoryToZorro/MS_HistoryToZorro/obj/Debug/net6.0/MS_HistoryToZorro.AssemblyInfo.cs b/Sources/Robots/MS_HistoryToZorro/MS_HistoryToZorro/obj/Debug/net6.0/MS_HistoryToZorro.AssemblyInfo.cs new file mode 100644 index 0000000..bd9f428 --- /dev/null +++ b/Sources/Robots/MS_HistoryToZorro/MS_HistoryToZorro/obj/Debug/net6.0/MS_HistoryToZorro.AssemblyInfo.cs @@ -0,0 +1,22 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +using System; +using System.Reflection; + +[assembly: System.Reflection.AssemblyCompanyAttribute("MS_HistoryToZorro")] +[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] +[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0")] +[assembly: System.Reflection.AssemblyProductAttribute("MS_HistoryToZorro")] +[assembly: System.Reflection.AssemblyTitleAttribute("MS_HistoryToZorro")] +[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] + +// Generated by the MSBuild WriteCodeFragment class. + diff --git a/Sources/Robots/MS_HistoryToZorro/MS_HistoryToZorro/obj/Debug/net6.0/MS_HistoryToZorro.AssemblyInfoInputs.cache b/Sources/Robots/MS_HistoryToZorro/MS_HistoryToZorro/obj/Debug/net6.0/MS_HistoryToZorro.AssemblyInfoInputs.cache new file mode 100644 index 0000000..077a0e1 --- /dev/null +++ b/Sources/Robots/MS_HistoryToZorro/MS_HistoryToZorro/obj/Debug/net6.0/MS_HistoryToZorro.AssemblyInfoInputs.cache @@ -0,0 +1 @@ +f9f65a61ac9aabeac45ca62c3dc6c6cbe416ef73 diff --git a/Sources/Robots/MS_HistoryToZorro/MS_HistoryToZorro/obj/Debug/net6.0/MS_HistoryToZorro.GeneratedMSBuildEditorConfig.editorconfig b/Sources/Robots/MS_HistoryToZorro/MS_HistoryToZorro/obj/Debug/net6.0/MS_HistoryToZorro.GeneratedMSBuildEditorConfig.editorconfig new file mode 100644 index 0000000..fca47a3 --- /dev/null +++ b/Sources/Robots/MS_HistoryToZorro/MS_HistoryToZorro/obj/Debug/net6.0/MS_HistoryToZorro.GeneratedMSBuildEditorConfig.editorconfig @@ -0,0 +1,10 @@ +is_global = true +build_property.TargetFramework = net6.0 +build_property.TargetPlatformMinVersion = +build_property.UsingMicrosoftNETSdkWeb = +build_property.ProjectTypeGuids = +build_property.InvariantGlobalization = +build_property.PlatformNeutralAssembly = +build_property._SupportedPlatformList = Linux,macOS,Windows +build_property.RootNamespace = MS_HistoryToZorro +build_property.ProjectDir = C:\Users\Brummel\Documents\cAlgo\Sources\Robots\MS_HistoryToZorro\MS_HistoryToZorro\ diff --git a/Sources/Robots/MS_HistoryToZorro/MS_HistoryToZorro/obj/Debug/net6.0/MS_HistoryToZorro.assets.cache b/Sources/Robots/MS_HistoryToZorro/MS_HistoryToZorro/obj/Debug/net6.0/MS_HistoryToZorro.assets.cache new file mode 100644 index 0000000..549d37e Binary files /dev/null and b/Sources/Robots/MS_HistoryToZorro/MS_HistoryToZorro/obj/Debug/net6.0/MS_HistoryToZorro.assets.cache differ diff --git a/Sources/Robots/MS_HistoryToZorro/MS_HistoryToZorro/obj/Debug/net6.0/MS_HistoryToZorro.csproj.AssemblyReference.cache b/Sources/Robots/MS_HistoryToZorro/MS_HistoryToZorro/obj/Debug/net6.0/MS_HistoryToZorro.csproj.AssemblyReference.cache new file mode 100644 index 0000000..2548430 Binary files /dev/null and b/Sources/Robots/MS_HistoryToZorro/MS_HistoryToZorro/obj/Debug/net6.0/MS_HistoryToZorro.csproj.AssemblyReference.cache differ diff --git a/Sources/Robots/MS_HistoryToZorro/MS_HistoryToZorro/obj/MS_HistoryToZorro.csproj.nuget.dgspec.json b/Sources/Robots/MS_HistoryToZorro/MS_HistoryToZorro/obj/MS_HistoryToZorro.csproj.nuget.dgspec.json new file mode 100644 index 0000000..52dc2c7 --- /dev/null +++ b/Sources/Robots/MS_HistoryToZorro/MS_HistoryToZorro/obj/MS_HistoryToZorro.csproj.nuget.dgspec.json @@ -0,0 +1,66 @@ +{ + "format": 1, + "restore": { + "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\MS_HistoryToZorro\\MS_HistoryToZorro\\MS_HistoryToZorro.csproj": {} + }, + "projects": { + "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\MS_HistoryToZorro\\MS_HistoryToZorro\\MS_HistoryToZorro.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\MS_HistoryToZorro\\MS_HistoryToZorro\\MS_HistoryToZorro.csproj", + "projectName": "MS_HistoryToZorro", + "projectPath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\MS_HistoryToZorro\\MS_HistoryToZorro\\MS_HistoryToZorro.csproj", + "packagesPath": "C:\\Users\\Brummel\\.nuget\\packages\\", + "outputPath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\MS_HistoryToZorro\\MS_HistoryToZorro\\obj\\", + "projectStyle": "PackageReference", + "configFilePaths": [ + "C:\\Users\\Brummel\\AppData\\Roaming\\NuGet\\NuGet.Config" + ], + "originalTargetFrameworks": [ + "net6.0" + ], + "sources": { + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net6.0": { + "targetAlias": "net6.0", + "projectReferences": {} + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + } + }, + "frameworks": { + "net6.0": { + "targetAlias": "net6.0", + "dependencies": { + "cTrader.Automate": { + "target": "Package", + "version": "[*, )" + } + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\6.0.200\\RuntimeIdentifierGraph.json" + } + } + } + } +} \ No newline at end of file diff --git a/Sources/Robots/MS_HistoryToZorro/MS_HistoryToZorro/obj/MS_HistoryToZorro.csproj.nuget.g.props b/Sources/Robots/MS_HistoryToZorro/MS_HistoryToZorro/obj/MS_HistoryToZorro.csproj.nuget.g.props new file mode 100644 index 0000000..120d227 --- /dev/null +++ b/Sources/Robots/MS_HistoryToZorro/MS_HistoryToZorro/obj/MS_HistoryToZorro.csproj.nuget.g.props @@ -0,0 +1,21 @@ + + + + True + NuGet + $(MSBuildThisFileDirectory)project.assets.json + $(UserProfile)\.nuget\packages\ + C:\Users\Brummel\.nuget\packages\ + PackageReference + 6.1.0 + + + + + + + + + C:\Users\Brummel\.nuget\packages\ctrader.automate\1.0.12 + + \ No newline at end of file diff --git a/Sources/Robots/MS_HistoryToZorro/MS_HistoryToZorro/obj/MS_HistoryToZorro.csproj.nuget.g.targets b/Sources/Robots/MS_HistoryToZorro/MS_HistoryToZorro/obj/MS_HistoryToZorro.csproj.nuget.g.targets new file mode 100644 index 0000000..29b3c4c --- /dev/null +++ b/Sources/Robots/MS_HistoryToZorro/MS_HistoryToZorro/obj/MS_HistoryToZorro.csproj.nuget.g.targets @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Sources/Robots/MS_HistoryToZorro/MS_HistoryToZorro/obj/project.assets.json b/Sources/Robots/MS_HistoryToZorro/MS_HistoryToZorro/obj/project.assets.json new file mode 100644 index 0000000..26360fb --- /dev/null +++ b/Sources/Robots/MS_HistoryToZorro/MS_HistoryToZorro/obj/project.assets.json @@ -0,0 +1,139 @@ +{ + "version": 3, + "targets": { + "net6.0": { + "cTrader.Automate/1.0.12": { + "type": "package", + "compile": { + "lib/net6.0/cAlgo.API.dll": {} + }, + "runtime": { + "lib/net6.0/cAlgo.API.dll": {} + }, + "build": { + "build/cTrader.Automate.props": {}, + "build/cTrader.Automate.targets": {} + } + } + } + }, + "libraries": { + "cTrader.Automate/1.0.12": { + "sha512": "h+HPnbqPTkoSeojoYwWWOo5Jm9ExkmrmsU/WbF/idcgO2auKAPILBghZRP6Qt5mAcE/40YCRMNHT45/kzzs5Sg==", + "type": "package", + "path": "ctrader.automate/1.0.12", + "hasTools": true, + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "build/cTrader.Automate.props", + "build/cTrader.Automate.targets", + "ctrader.automate.1.0.12.nupkg.sha512", + "ctrader.automate.nuspec", + "eula.md", + "icon.png", + "lib/net40/cAlgo.API.dll", + "lib/net40/cAlgo.API.xml", + "lib/net6.0/cAlgo.API.dll", + "lib/net6.0/cAlgo.API.xml", + "tools/net472/Core.AlgoFormat.Compose.Reflection.dll", + "tools/net472/Core.AlgoFormat.Writer.dll", + "tools/net472/Core.AlgoFormat.dll", + "tools/net472/Core.Domain.Primitives.dll", + "tools/net472/Newtonsoft.Json.dll", + "tools/net472/System.Buffers.dll", + "tools/net472/System.Collections.Immutable.dll", + "tools/net472/System.Memory.dll", + "tools/net472/System.Numerics.Vectors.dll", + "tools/net472/System.Reflection.Metadata.dll", + "tools/net472/System.Reflection.MetadataLoadContext.dll", + "tools/net472/System.Runtime.CompilerServices.Unsafe.dll", + "tools/net472/cTrader.Automate.Sdk.Tasks.dll", + "tools/net6.0/Core.AlgoFormat.Compose.Reflection.dll", + "tools/net6.0/Core.AlgoFormat.Writer.dll", + "tools/net6.0/Core.AlgoFormat.dll", + "tools/net6.0/Core.Connection.Protobuf.Common.dll", + "tools/net6.0/Core.Domain.Primitives.dll", + "tools/net6.0/Microsoft.Win32.SystemEvents.dll", + "tools/net6.0/Newtonsoft.Json.dll", + "tools/net6.0/System.Drawing.Common.dll", + "tools/net6.0/System.Reflection.MetadataLoadContext.dll", + "tools/net6.0/System.Security.Permissions.dll", + "tools/net6.0/System.Windows.Extensions.dll", + "tools/net6.0/cTrader.Automate.Sdk.Tasks.dll", + "tools/net6.0/protobuf-net.Core.dll", + "tools/net6.0/protobuf-net.dll", + "tools/net6.0/runtimes/unix/lib/net6.0/System.Drawing.Common.dll", + "tools/net6.0/runtimes/win/lib/net6.0/Microsoft.Win32.SystemEvents.dll", + "tools/net6.0/runtimes/win/lib/net6.0/System.Drawing.Common.dll", + "tools/net6.0/runtimes/win/lib/net6.0/System.Windows.Extensions.dll" + ] + } + }, + "projectFileDependencyGroups": { + "net6.0": [ + "cTrader.Automate >= *" + ] + }, + "packageFolders": { + "C:\\Users\\Brummel\\.nuget\\packages\\": {} + }, + "project": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\MS_HistoryToZorro\\MS_HistoryToZorro\\MS_HistoryToZorro.csproj", + "projectName": "MS_HistoryToZorro", + "projectPath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\MS_HistoryToZorro\\MS_HistoryToZorro\\MS_HistoryToZorro.csproj", + "packagesPath": "C:\\Users\\Brummel\\.nuget\\packages\\", + "outputPath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\MS_HistoryToZorro\\MS_HistoryToZorro\\obj\\", + "projectStyle": "PackageReference", + "configFilePaths": [ + "C:\\Users\\Brummel\\AppData\\Roaming\\NuGet\\NuGet.Config" + ], + "originalTargetFrameworks": [ + "net6.0" + ], + "sources": { + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net6.0": { + "targetAlias": "net6.0", + "projectReferences": {} + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + } + }, + "frameworks": { + "net6.0": { + "targetAlias": "net6.0", + "dependencies": { + "cTrader.Automate": { + "target": "Package", + "version": "[*, )" + } + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\6.0.200\\RuntimeIdentifierGraph.json" + } + } + } +} \ No newline at end of file diff --git a/Sources/Robots/MS_HistoryToZorro/MS_HistoryToZorro/obj/project.nuget.cache b/Sources/Robots/MS_HistoryToZorro/MS_HistoryToZorro/obj/project.nuget.cache new file mode 100644 index 0000000..b6988ba --- /dev/null +++ b/Sources/Robots/MS_HistoryToZorro/MS_HistoryToZorro/obj/project.nuget.cache @@ -0,0 +1,10 @@ +{ + "version": 2, + "dgSpecHash": "qCbXJQB185SwlDQtO1/3MEY82hhK0tNdCN1LG+SQT8gWU4mcxCk3Ng160ITJ+LhnH965XESK835MzjYBT8VANg==", + "success": true, + "projectFilePath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\MS_HistoryToZorro\\MS_HistoryToZorro\\MS_HistoryToZorro.csproj", + "expectedPackageFiles": [ + "C:\\Users\\Brummel\\.nuget\\packages\\ctrader.automate\\1.0.12\\ctrader.automate.1.0.12.nupkg.sha512" + ], + "logs": [] +} \ No newline at end of file diff --git a/Sources/Robots/MS_IncrementalExport.algo b/Sources/Robots/MS_IncrementalExport.algo new file mode 100644 index 0000000..2b203fc Binary files /dev/null and b/Sources/Robots/MS_IncrementalExport.algo differ diff --git a/Sources/Robots/MS_IncrementalExport/.vscode/tasks.json b/Sources/Robots/MS_IncrementalExport/.vscode/tasks.json new file mode 100644 index 0000000..215166e --- /dev/null +++ b/Sources/Robots/MS_IncrementalExport/.vscode/tasks.json @@ -0,0 +1 @@ +{"version":"2.0.0","tasks":[{"label":"build","command":"dotnet","type":"process","args":["build","${workspaceFolder}","/property:GenerateFullPaths=true","/consoleLoggerParameters:NoSummary"],"problemMatcher":"$msCompile"}]} \ No newline at end of file diff --git a/Sources/Robots/MS_IncrementalExport/MS_IncrementalExport.sln b/Sources/Robots/MS_IncrementalExport/MS_IncrementalExport.sln new file mode 100644 index 0000000..031852f --- /dev/null +++ b/Sources/Robots/MS_IncrementalExport/MS_IncrementalExport.sln @@ -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}") = "MS_IncrementalExport", "MS_IncrementalExport\MS_IncrementalExport.csproj", "{c3b052a5-8e47-4bd1-99ba-f114dbdcfa8f}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {c3b052a5-8e47-4bd1-99ba-f114dbdcfa8f}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {c3b052a5-8e47-4bd1-99ba-f114dbdcfa8f}.Debug|Any CPU.Build.0 = Debug|Any CPU + {c3b052a5-8e47-4bd1-99ba-f114dbdcfa8f}.Release|Any CPU.ActiveCfg = Release|Any CPU + {c3b052a5-8e47-4bd1-99ba-f114dbdcfa8f}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/Sources/Robots/MS_IncrementalExport/MS_IncrementalExport/MS_IncrementalExport.cs b/Sources/Robots/MS_IncrementalExport/MS_IncrementalExport/MS_IncrementalExport.cs new file mode 100644 index 0000000..c3dbe3e --- /dev/null +++ b/Sources/Robots/MS_IncrementalExport/MS_IncrementalExport/MS_IncrementalExport.cs @@ -0,0 +1,186 @@ +/* * MS_IncrementalExport + * High-performance M1 and Tick data exporter. + * Naming: {Symbol}_{Start-YYYY-MM}_{End-YYYY-MM}.{ext} + */ + +using System; +using System.IO; +using System.IO.Compression; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using cAlgo.API; + +namespace cAlgo.Robots +{ + public enum ExportMode { M1, Tick } + + [Robot(AccessRights = AccessRights.FullAccess, AddIndicators = true)] + public class MS_IncrementalExport : Robot + { + [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 56)] + private struct M1Record + { + public double OADateTime; // Must be at Offset 0 + public long Open, High, Low, Close, Spread; + public int TickVolume, Digits; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 16)] + private struct TickDataRecord + { + public double OADateTime; // Must be at Offset 0 + public float Ask; + public float Bid; + } + + [Parameter("Export Mode", DefaultValue = ExportMode.M1)] + public ExportMode Mode { get; set; } + + [Parameter("Output Path", DefaultValue = "/mnt/Data")] + public string DirectoryName { get; set; } + + [Parameter("Existing File Path", DefaultValue = "")] + public string ExistingFilePath { get; set; } + + [Parameter("Max File Size (kB)", DefaultValue = 10000)] + public int MaxFileSizeKb { get; set; } + + private List _m1Buffer = new List(); + private List _tickBuffer = new List(); + private double _lastTimestamp = double.MinValue; + private double _priceFactor; + + protected override void OnStart() + { + _priceFactor = Math.Pow(10, Symbol.Digits); + + if (!string.IsNullOrWhiteSpace(ExistingFilePath) && File.Exists(ExistingFilePath)) + { + LoadExistingData(ExistingFilePath); + } + } + + protected override void OnTick() + { + if (Mode != ExportMode.Tick) return; + + var time = Server.TimeInUtc.ToOADate(); + if (time <= _lastTimestamp) return; + + _tickBuffer.Add(new TickDataRecord + { + OADateTime = time, + Ask = (float)Symbol.Ask, + Bid = (float)Symbol.Bid + }); + } + + protected override void OnBar() + { + if (Mode != ExportMode.M1) return; + + var barIndex = Bars.Count - 2; + if (barIndex < 0) return; + + var bar = Bars[barIndex]; + var barTime = bar.OpenTime.ToOADate(); + + if (barTime <= _lastTimestamp) return; + + _m1Buffer.Add(new M1Record + { + OADateTime = barTime, + Open = (long)(bar.Open * _priceFactor), + High = (long)(bar.High * _priceFactor), + Low = (long)(bar.Low * _priceFactor), + Close = (long)(bar.Close * _priceFactor), + Spread = (long)(Symbol.Spread * _priceFactor), + TickVolume = (int)bar.TickVolume, + Digits = Symbol.Digits + }); + } + + protected override void OnStop() + { + if (Mode == ExportMode.M1) + ProcessExport(_m1Buffer, "m1"); + else + ProcessExport(_tickBuffer, "tick"); + } + + private void ProcessExport(List buffer, string extension) where T : unmanaged + { + if (buffer.Count == 0) return; + + int recordSize = Marshal.SizeOf(); + long maxBytes = (long)MaxFileSizeKb * 1024; + int batchSize = (int)(maxBytes / recordSize); + + for (int i = 0; i < buffer.Count; i += batchSize) + { + var count = Math.Min(batchSize, buffer.Count - i); + var batch = buffer.GetRange(i, count); + WriteBatch(batch, extension); + } + } + + private void WriteBatch(List records, string ext) where T : unmanaged + { + ReadOnlySpan span = CollectionsMarshal.AsSpan(records); + + // Extract timestamps via Cast to interpret the first 8 bytes as double + double start = MemoryMarshal.Cast(span)[0]; + double end = MemoryMarshal.Cast(span.Slice(records.Count - 1))[0]; + + string startStr = DateTime.FromOADate(start).ToString("yyyy-MM"); + string endStr = DateTime.FromOADate(end).ToString("yyyy-MM"); + + string fileName = $"{Symbol.Name}_{startStr}_{endStr}.{ext}"; + string archivePath = Path.Combine(DirectoryName, fileName); + + ReadOnlySpan byteSpan = MemoryMarshal.Cast(span); + + using var fileStream = new FileStream(archivePath, FileMode.Create); + using var archive = new ZipArchive(fileStream, ZipArchiveMode.Create); + var entry = archive.CreateEntry($"{Symbol.Name}.bin", CompressionLevel.Fastest); + + using var entryStream = entry.Open(); + entryStream.Write(byteSpan); + + Print("Saved: {0}", archivePath); + } + + private void LoadExistingData(string path) + { + try + { + using var archive = ZipFile.OpenRead(path); + var entry = archive.Entries.FirstOrDefault(); + if (entry == null) return; + + using var stream = entry.Open(); + using var ms = new MemoryStream(); + stream.CopyTo(ms); + + byte[] data = ms.ToArray(); + if (Mode == ExportMode.M1) + { + var records = MemoryMarshal.Cast(data); + foreach (var r in records) if (r.OADateTime > _lastTimestamp) _lastTimestamp = r.OADateTime; + } + else + { + var records = MemoryMarshal.Cast(data); + foreach (var r in records) if (r.OADateTime > _lastTimestamp) _lastTimestamp = r.OADateTime; + } + + Print("Loaded existing data. Last timestamp: {0}", DateTime.FromOADate(_lastTimestamp)); + } + catch (Exception ex) + { + Print("Error loading existing data: {0}", ex.Message); + } + } + } +} \ No newline at end of file diff --git a/Sources/Robots/MS_IncrementalExport/MS_IncrementalExport/MS_IncrementalExport.csproj b/Sources/Robots/MS_IncrementalExport/MS_IncrementalExport/MS_IncrementalExport.csproj new file mode 100644 index 0000000..da1fef1 --- /dev/null +++ b/Sources/Robots/MS_IncrementalExport/MS_IncrementalExport/MS_IncrementalExport.csproj @@ -0,0 +1,9 @@ + + + net6.0 + + + + + + diff --git a/Sources/Robots/MS_IncrementalExport/MS_IncrementalExport/obj/Debug/net6.0/.NETCoreApp,Version=v6.0.AssemblyAttributes.cs b/Sources/Robots/MS_IncrementalExport/MS_IncrementalExport/obj/Debug/net6.0/.NETCoreApp,Version=v6.0.AssemblyAttributes.cs new file mode 100644 index 0000000..32c95f9 --- /dev/null +++ b/Sources/Robots/MS_IncrementalExport/MS_IncrementalExport/obj/Debug/net6.0/.NETCoreApp,Version=v6.0.AssemblyAttributes.cs @@ -0,0 +1,4 @@ +// +using System; +using System.Reflection; +[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v6.0", FrameworkDisplayName = "")] diff --git a/Sources/Robots/MS_IncrementalExport/MS_IncrementalExport/obj/Debug/net6.0/MS_IncrementalExport.AssemblyInfo.cs b/Sources/Robots/MS_IncrementalExport/MS_IncrementalExport/obj/Debug/net6.0/MS_IncrementalExport.AssemblyInfo.cs new file mode 100644 index 0000000..5c11940 --- /dev/null +++ b/Sources/Robots/MS_IncrementalExport/MS_IncrementalExport/obj/Debug/net6.0/MS_IncrementalExport.AssemblyInfo.cs @@ -0,0 +1,22 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +using System; +using System.Reflection; + +[assembly: System.Reflection.AssemblyCompanyAttribute("MS_IncrementalExport")] +[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] +[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0")] +[assembly: System.Reflection.AssemblyProductAttribute("MS_IncrementalExport")] +[assembly: System.Reflection.AssemblyTitleAttribute("MS_IncrementalExport")] +[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] + +// Generated by the MSBuild WriteCodeFragment class. + diff --git a/Sources/Robots/MS_IncrementalExport/MS_IncrementalExport/obj/Debug/net6.0/MS_IncrementalExport.AssemblyInfoInputs.cache b/Sources/Robots/MS_IncrementalExport/MS_IncrementalExport/obj/Debug/net6.0/MS_IncrementalExport.AssemblyInfoInputs.cache new file mode 100644 index 0000000..85aaab4 --- /dev/null +++ b/Sources/Robots/MS_IncrementalExport/MS_IncrementalExport/obj/Debug/net6.0/MS_IncrementalExport.AssemblyInfoInputs.cache @@ -0,0 +1 @@ +5773c38d4319cb7f76ca5b658c23774f3fba8ad5 diff --git a/Sources/Robots/MS_IncrementalExport/MS_IncrementalExport/obj/Debug/net6.0/MS_IncrementalExport.GeneratedMSBuildEditorConfig.editorconfig b/Sources/Robots/MS_IncrementalExport/MS_IncrementalExport/obj/Debug/net6.0/MS_IncrementalExport.GeneratedMSBuildEditorConfig.editorconfig new file mode 100644 index 0000000..e820204 --- /dev/null +++ b/Sources/Robots/MS_IncrementalExport/MS_IncrementalExport/obj/Debug/net6.0/MS_IncrementalExport.GeneratedMSBuildEditorConfig.editorconfig @@ -0,0 +1,10 @@ +is_global = true +build_property.TargetFramework = net6.0 +build_property.TargetPlatformMinVersion = +build_property.UsingMicrosoftNETSdkWeb = +build_property.ProjectTypeGuids = +build_property.InvariantGlobalization = +build_property.PlatformNeutralAssembly = +build_property._SupportedPlatformList = Linux,macOS,Windows +build_property.RootNamespace = MS_IncrementalExport +build_property.ProjectDir = C:\Users\Brummel\Documents\cAlgo\Sources\Robots\MS_IncrementalExport\MS_IncrementalExport\ diff --git a/Sources/Robots/MS_IncrementalExport/MS_IncrementalExport/obj/Debug/net6.0/MS_IncrementalExport.assets.cache b/Sources/Robots/MS_IncrementalExport/MS_IncrementalExport/obj/Debug/net6.0/MS_IncrementalExport.assets.cache new file mode 100644 index 0000000..5ac7128 Binary files /dev/null and b/Sources/Robots/MS_IncrementalExport/MS_IncrementalExport/obj/Debug/net6.0/MS_IncrementalExport.assets.cache differ diff --git a/Sources/Robots/MS_IncrementalExport/MS_IncrementalExport/obj/Debug/net6.0/MS_IncrementalExport.csproj.AssemblyReference.cache b/Sources/Robots/MS_IncrementalExport/MS_IncrementalExport/obj/Debug/net6.0/MS_IncrementalExport.csproj.AssemblyReference.cache new file mode 100644 index 0000000..50043b7 Binary files /dev/null and b/Sources/Robots/MS_IncrementalExport/MS_IncrementalExport/obj/Debug/net6.0/MS_IncrementalExport.csproj.AssemblyReference.cache differ diff --git a/Sources/Robots/MS_IncrementalExport/MS_IncrementalExport/obj/MS_IncrementalExport.csproj.nuget.dgspec.json b/Sources/Robots/MS_IncrementalExport/MS_IncrementalExport/obj/MS_IncrementalExport.csproj.nuget.dgspec.json new file mode 100644 index 0000000..06fc674 --- /dev/null +++ b/Sources/Robots/MS_IncrementalExport/MS_IncrementalExport/obj/MS_IncrementalExport.csproj.nuget.dgspec.json @@ -0,0 +1,66 @@ +{ + "format": 1, + "restore": { + "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\MS_IncrementalExport\\MS_IncrementalExport\\MS_IncrementalExport.csproj": {} + }, + "projects": { + "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\MS_IncrementalExport\\MS_IncrementalExport\\MS_IncrementalExport.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\MS_IncrementalExport\\MS_IncrementalExport\\MS_IncrementalExport.csproj", + "projectName": "MS_IncrementalExport", + "projectPath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\MS_IncrementalExport\\MS_IncrementalExport\\MS_IncrementalExport.csproj", + "packagesPath": "C:\\Users\\Brummel\\.nuget\\packages\\", + "outputPath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\MS_IncrementalExport\\MS_IncrementalExport\\obj\\", + "projectStyle": "PackageReference", + "configFilePaths": [ + "C:\\Users\\Brummel\\AppData\\Roaming\\NuGet\\NuGet.Config" + ], + "originalTargetFrameworks": [ + "net6.0" + ], + "sources": { + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net6.0": { + "targetAlias": "net6.0", + "projectReferences": {} + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + } + }, + "frameworks": { + "net6.0": { + "targetAlias": "net6.0", + "dependencies": { + "cTrader.Automate": { + "target": "Package", + "version": "[*, )" + } + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\6.0.200\\RuntimeIdentifierGraph.json" + } + } + } + } +} \ No newline at end of file diff --git a/Sources/Robots/MS_IncrementalExport/MS_IncrementalExport/obj/MS_IncrementalExport.csproj.nuget.g.props b/Sources/Robots/MS_IncrementalExport/MS_IncrementalExport/obj/MS_IncrementalExport.csproj.nuget.g.props new file mode 100644 index 0000000..0b161ac --- /dev/null +++ b/Sources/Robots/MS_IncrementalExport/MS_IncrementalExport/obj/MS_IncrementalExport.csproj.nuget.g.props @@ -0,0 +1,21 @@ + + + + True + NuGet + $(MSBuildThisFileDirectory)project.assets.json + $(UserProfile)\.nuget\packages\ + C:\Users\Brummel\.nuget\packages\ + PackageReference + 6.1.0 + + + + + + + + + C:\Users\Brummel\.nuget\packages\ctrader.automate\1.0.14 + + \ No newline at end of file diff --git a/Sources/Robots/MS_IncrementalExport/MS_IncrementalExport/obj/MS_IncrementalExport.csproj.nuget.g.targets b/Sources/Robots/MS_IncrementalExport/MS_IncrementalExport/obj/MS_IncrementalExport.csproj.nuget.g.targets new file mode 100644 index 0000000..46f142c --- /dev/null +++ b/Sources/Robots/MS_IncrementalExport/MS_IncrementalExport/obj/MS_IncrementalExport.csproj.nuget.g.targets @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Sources/Robots/MS_IncrementalExport/MS_IncrementalExport/obj/project.assets.json b/Sources/Robots/MS_IncrementalExport/MS_IncrementalExport/obj/project.assets.json new file mode 100644 index 0000000..d4dd616 --- /dev/null +++ b/Sources/Robots/MS_IncrementalExport/MS_IncrementalExport/obj/project.assets.json @@ -0,0 +1,139 @@ +{ + "version": 3, + "targets": { + "net6.0": { + "cTrader.Automate/1.0.14": { + "type": "package", + "compile": { + "lib/net6.0/cAlgo.API.dll": {} + }, + "runtime": { + "lib/net6.0/cAlgo.API.dll": {} + }, + "build": { + "build/cTrader.Automate.props": {}, + "build/cTrader.Automate.targets": {} + } + } + } + }, + "libraries": { + "cTrader.Automate/1.0.14": { + "sha512": "eNwE7WL90MGBKb5MuLAtZLdQy0vxkI5EVhLWAQ9S83EAdYkGAzdccvGFLk2oqmtSGBtD+gEpyrJUW/ej4dI4jw==", + "type": "package", + "path": "ctrader.automate/1.0.14", + "hasTools": true, + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "build/cTrader.Automate.props", + "build/cTrader.Automate.targets", + "ctrader.automate.1.0.14.nupkg.sha512", + "ctrader.automate.nuspec", + "eula.md", + "icon.png", + "lib/net40/cAlgo.API.dll", + "lib/net40/cAlgo.API.xml", + "lib/net6.0/cAlgo.API.dll", + "lib/net6.0/cAlgo.API.xml", + "tools/net472/Core.AlgoFormat.Compose.Reflection.dll", + "tools/net472/Core.AlgoFormat.Writer.dll", + "tools/net472/Core.AlgoFormat.dll", + "tools/net472/Core.Domain.Primitives.dll", + "tools/net472/Newtonsoft.Json.dll", + "tools/net472/System.Buffers.dll", + "tools/net472/System.Collections.Immutable.dll", + "tools/net472/System.Memory.dll", + "tools/net472/System.Numerics.Vectors.dll", + "tools/net472/System.Reflection.Metadata.dll", + "tools/net472/System.Reflection.MetadataLoadContext.dll", + "tools/net472/System.Runtime.CompilerServices.Unsafe.dll", + "tools/net472/cTrader.Automate.Sdk.Tasks.dll", + "tools/net6.0/Core.AlgoFormat.Compose.Reflection.dll", + "tools/net6.0/Core.AlgoFormat.Writer.dll", + "tools/net6.0/Core.AlgoFormat.dll", + "tools/net6.0/Core.Connection.Protobuf.Common.dll", + "tools/net6.0/Core.Domain.Primitives.dll", + "tools/net6.0/Microsoft.Win32.SystemEvents.dll", + "tools/net6.0/Newtonsoft.Json.dll", + "tools/net6.0/System.Drawing.Common.dll", + "tools/net6.0/System.Reflection.MetadataLoadContext.dll", + "tools/net6.0/System.Security.Permissions.dll", + "tools/net6.0/System.Windows.Extensions.dll", + "tools/net6.0/cTrader.Automate.Sdk.Tasks.dll", + "tools/net6.0/protobuf-net.Core.dll", + "tools/net6.0/protobuf-net.dll", + "tools/net6.0/runtimes/unix/lib/net6.0/System.Drawing.Common.dll", + "tools/net6.0/runtimes/win/lib/net6.0/Microsoft.Win32.SystemEvents.dll", + "tools/net6.0/runtimes/win/lib/net6.0/System.Drawing.Common.dll", + "tools/net6.0/runtimes/win/lib/net6.0/System.Windows.Extensions.dll" + ] + } + }, + "projectFileDependencyGroups": { + "net6.0": [ + "cTrader.Automate >= *" + ] + }, + "packageFolders": { + "C:\\Users\\Brummel\\.nuget\\packages\\": {} + }, + "project": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\MS_IncrementalExport\\MS_IncrementalExport\\MS_IncrementalExport.csproj", + "projectName": "MS_IncrementalExport", + "projectPath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\MS_IncrementalExport\\MS_IncrementalExport\\MS_IncrementalExport.csproj", + "packagesPath": "C:\\Users\\Brummel\\.nuget\\packages\\", + "outputPath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\MS_IncrementalExport\\MS_IncrementalExport\\obj\\", + "projectStyle": "PackageReference", + "configFilePaths": [ + "C:\\Users\\Brummel\\AppData\\Roaming\\NuGet\\NuGet.Config" + ], + "originalTargetFrameworks": [ + "net6.0" + ], + "sources": { + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net6.0": { + "targetAlias": "net6.0", + "projectReferences": {} + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + } + }, + "frameworks": { + "net6.0": { + "targetAlias": "net6.0", + "dependencies": { + "cTrader.Automate": { + "target": "Package", + "version": "[*, )" + } + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\6.0.200\\RuntimeIdentifierGraph.json" + } + } + } +} \ No newline at end of file diff --git a/Sources/Robots/MS_IncrementalExport/MS_IncrementalExport/obj/project.nuget.cache b/Sources/Robots/MS_IncrementalExport/MS_IncrementalExport/obj/project.nuget.cache new file mode 100644 index 0000000..fc111a6 --- /dev/null +++ b/Sources/Robots/MS_IncrementalExport/MS_IncrementalExport/obj/project.nuget.cache @@ -0,0 +1,10 @@ +{ + "version": 2, + "dgSpecHash": "yjqqJ1FuNh4unJj0bFrEA5zuzdz+mayZe5I9PxwxfOVpWAKWRwhTHWIw6G7wF7IgsOktZDLcmYJEDZ4N/mWgGw==", + "success": true, + "projectFilePath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\MS_IncrementalExport\\MS_IncrementalExport\\MS_IncrementalExport.csproj", + "expectedPackageFiles": [ + "C:\\Users\\Brummel\\.nuget\\packages\\ctrader.automate\\1.0.14\\ctrader.automate.1.0.14.nupkg.sha512" + ], + "logs": [] +} \ No newline at end of file diff --git a/Sources/Robots/MS_SimpleExport.algo b/Sources/Robots/MS_SimpleExport.algo new file mode 100644 index 0000000..5f53be1 Binary files /dev/null and b/Sources/Robots/MS_SimpleExport.algo differ diff --git a/Sources/Robots/MS_SimpleExport/MS_SimpleExport.sln b/Sources/Robots/MS_SimpleExport/MS_SimpleExport.sln new file mode 100644 index 0000000..44a9446 --- /dev/null +++ b/Sources/Robots/MS_SimpleExport/MS_SimpleExport.sln @@ -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}") = "MS_SimpleExport", "MS_SimpleExport\MS_SimpleExport.csproj", "{51a6e49d-15ea-4fdd-8fac-5d03bfb41241}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {51a6e49d-15ea-4fdd-8fac-5d03bfb41241}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {51a6e49d-15ea-4fdd-8fac-5d03bfb41241}.Debug|Any CPU.Build.0 = Debug|Any CPU + {51a6e49d-15ea-4fdd-8fac-5d03bfb41241}.Release|Any CPU.ActiveCfg = Release|Any CPU + {51a6e49d-15ea-4fdd-8fac-5d03bfb41241}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/Sources/Robots/MS_SimpleExport/MS_SimpleExport/MS_SimpleExport.cs b/Sources/Robots/MS_SimpleExport/MS_SimpleExport/MS_SimpleExport.cs new file mode 100644 index 0000000..675d067 --- /dev/null +++ b/Sources/Robots/MS_SimpleExport/MS_SimpleExport/MS_SimpleExport.cs @@ -0,0 +1,108 @@ +using System; +using System.IO; +using System.IO.Compression; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using cAlgo.API; + +namespace cAlgo.Robots +{ + public enum ExportMode { M1, Tick } + + [Robot(AccessRights = AccessRights.FullAccess)] + public class MS_SimpleExport : Robot + { + [StructLayout(LayoutKind.Sequential, Pack = 1)] + private struct M1Record { + public double Time; + public double O, H, L, C; + public float S; + public int V; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + private struct TickRecord { + public double Time; + public double Ask, Bid; + } + + [Parameter("Export Mode", DefaultValue = ExportMode.M1)] + public ExportMode Mode { get; set; } + + [Parameter("Output File (ZIP)", DefaultValue = "")] + public string OutputFile { get; set; } + + [Parameter("Is Probing", DefaultValue = false)] + public bool IsProbing { get; set; } + + private List _m1Buffer = new List(); + private List _tickBuffer = new List(); + + protected override void OnStart() { + // No price factor needed for double/float export + } + + protected override void OnTick() { + if (Mode != ExportMode.Tick) return; + + if (IsProbing) { + Print("DATA_FOUND"); + Stop(); + return; + } + + _tickBuffer.Add(new TickRecord { + Time = Server.TimeInUtc.ToOADate(), + Ask = Symbol.Ask, + Bid = Symbol.Bid + }); + } + + protected override void OnBar() { + if (Mode != ExportMode.M1) return; + + if (IsProbing) { + Print("DATA_FOUND"); + Stop(); + return; + } + + var bar = Bars.Last(1); + _m1Buffer.Add(new M1Record { + Time = bar.OpenTime.ToOADate(), + O = bar.Open, + H = bar.High, + L = bar.Low, + C = bar.Close, + S = (float)Symbol.Spread, + V = (int)bar.TickVolume + }); + } + + protected override void OnStop() { + if (IsProbing || string.IsNullOrEmpty(OutputFile)) return; + + try { + using var fileStream = new FileStream(OutputFile, FileMode.Create); + using var archive = new ZipArchive(fileStream, ZipArchiveMode.Create); + var entry = archive.CreateEntry($"{Symbol.Name}.bin", CompressionLevel.Fastest); + + using var entryStream = entry.Open(); + + if (Mode == ExportMode.M1) { + ReadOnlySpan span = CollectionsMarshal.AsSpan(_m1Buffer); + ReadOnlySpan byteSpan = MemoryMarshal.Cast(span); + if (!byteSpan.IsEmpty) entryStream.Write(byteSpan); + } else { + ReadOnlySpan span = CollectionsMarshal.AsSpan(_tickBuffer); + ReadOnlySpan byteSpan = MemoryMarshal.Cast(span); + if (!byteSpan.IsEmpty) entryStream.Write(byteSpan); + } + + Print("EXPORT_SUCCESS"); + } catch (Exception ex) { + Print("ERROR: {0}", ex.Message); + } + } + } +} \ No newline at end of file diff --git a/Sources/Robots/MS_SimpleExport/MS_SimpleExport/MS_SimpleExport.csproj b/Sources/Robots/MS_SimpleExport/MS_SimpleExport/MS_SimpleExport.csproj new file mode 100644 index 0000000..da1fef1 --- /dev/null +++ b/Sources/Robots/MS_SimpleExport/MS_SimpleExport/MS_SimpleExport.csproj @@ -0,0 +1,9 @@ + + + net6.0 + + + + + + diff --git a/Sources/Robots/Multi-MA-Bot.algo b/Sources/Robots/Multi-MA-Bot.algo new file mode 100644 index 0000000..0b901b4 Binary files /dev/null and b/Sources/Robots/Multi-MA-Bot.algo differ diff --git a/Sources/Robots/Multi-MA-Bot/.vscode/tasks.json b/Sources/Robots/Multi-MA-Bot/.vscode/tasks.json new file mode 100644 index 0000000..215166e --- /dev/null +++ b/Sources/Robots/Multi-MA-Bot/.vscode/tasks.json @@ -0,0 +1 @@ +{"version":"2.0.0","tasks":[{"label":"build","command":"dotnet","type":"process","args":["build","${workspaceFolder}","/property:GenerateFullPaths=true","/consoleLoggerParameters:NoSummary"],"problemMatcher":"$msCompile"}]} \ No newline at end of file diff --git a/Sources/Robots/Multi-MA-Bot/Multi-MA-Bot.sln b/Sources/Robots/Multi-MA-Bot/Multi-MA-Bot.sln new file mode 100644 index 0000000..4a2ec64 --- /dev/null +++ b/Sources/Robots/Multi-MA-Bot/Multi-MA-Bot.sln @@ -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}") = "Multi-MA-Bot", "Multi-MA-Bot\Multi-MA-Bot.csproj", "{a62c61af-44d7-443c-9f8a-242c717d8d45}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {a62c61af-44d7-443c-9f8a-242c717d8d45}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {a62c61af-44d7-443c-9f8a-242c717d8d45}.Debug|Any CPU.Build.0 = Debug|Any CPU + {a62c61af-44d7-443c-9f8a-242c717d8d45}.Release|Any CPU.ActiveCfg = Release|Any CPU + {a62c61af-44d7-443c-9f8a-242c717d8d45}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/Sources/Robots/Multi-MA-Bot/Multi-MA-Bot/Multi-MA-Bot.cs b/Sources/Robots/Multi-MA-Bot/Multi-MA-Bot/Multi-MA-Bot.cs new file mode 100644 index 0000000..80d8398 --- /dev/null +++ b/Sources/Robots/Multi-MA-Bot/Multi-MA-Bot/Multi-MA-Bot.cs @@ -0,0 +1,208 @@ +using cAlgo.API; +using cAlgo.API.Indicators; +using System; +using System.Linq; + +namespace cAlgo.Robots +{ + [Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)] + public class KeltnerSlopeTickBot : Robot + { + [Parameter("HMA Period", DefaultValue = 250, Group = "Indicators")] + public int HmaPeriod { get; set; } + + [Parameter("SMA Period", DefaultValue = 200, Group = "Indicators")] + public int SmaPeriod { get; set; } + + [Parameter("KC Centerline Period", DefaultValue = 20, Group = "Indicators")] + public int KeltnerSmaPeriod { get; set; } + + [Parameter("KC ATR Period", DefaultValue = 20, Group = "Indicators")] + public int KeltnerAtrPeriod { get; set; } + + [Parameter("KC ATR MA Type", DefaultValue = MovingAverageType.Hull, Group = "Indicators")] + public MovingAverageType KeltnerAtrMaType { get; set; } + + [Parameter("KC Band Distance", DefaultValue = 2.0, Group = "Indicators")] + public double KeltnerBandDistance { get; set; } + + [Parameter("Equity Risk %", DefaultValue = 1.0, Group = "Trading")] + public double EquityRiskPercent { get; set; } + + [Parameter("Max Open Positions", DefaultValue = 10, Group = "Trading")] + public int MaxPositions { get; set; } + + [Parameter("Drawing History (Bars)", DefaultValue = 500, Group = "Display")] + public int DrawingHistory { get; set; } + + private HullMovingAverage _hma; + private SimpleMovingAverage _sma; + private KeltnerChannels _keltnerChannels; + + // KORREKTUR: Variablen zum Speichern des vorherigen Tick-Preises. + private double _previousAsk; + private double _previousBid; + + private const string BotLabelPrefix = "KeltnerBot"; + private const string HmaLinePrefix = "Bot_HMA"; + private const string SmaLinePrefix = "Bot_SMA"; + private const string KcTopLinePrefix = "Bot_KC_Top"; + private const string KcMainLinePrefix = "Bot_KC_Main"; + private const string KcBottomLinePrefix = "Bot_KC_Bottom"; + + protected override void OnStart() + { + _hma = Indicators.HullMovingAverage(Bars.ClosePrices, HmaPeriod); + _sma = Indicators.SimpleMovingAverage(Bars.ClosePrices, SmaPeriod); + _keltnerChannels = Indicators.KeltnerChannels(KeltnerSmaPeriod, MovingAverageType.Simple, KeltnerAtrPeriod, KeltnerAtrMaType, KeltnerBandDistance); + + Positions.Opened += OnPositionOpened; + + // KORREKTUR: Initialisiert die Preis-Variablen, um eine falsche Auslösung beim ersten Tick zu vermeiden. + _previousAsk = Symbol.Ask; + _previousBid = Symbol.Bid; + } + + protected override void OnStop() + { + Chart.RemoveAllObjects(); + } + + protected override void OnBar() + { + DrawIndicators(); + RemoveOldSegments(); + } + + protected override void OnTick() + { + if (Positions.Count >= MaxPositions) + { + return; + } + + var hmaSlope = _hma.Result.Last(1) - _hma.Result.Last(2); + var smaSlope = _sma.Result.Last(1) - _sma.Result.Last(2); + var keltnerMainSlope = _keltnerChannels.Main.Last(1) - _keltnerChannels.Main.Last(2); + + var kcMain = _keltnerChannels.Main.Last(1); + + // Long-Entry-Logik + bool isUpTrend = hmaSlope > 0 && smaSlope > 0; + // KORREKTUR: Verwendet den gespeicherten _previousAsk für einen echten Tick-basierten Kreuzungsvergleich. + bool isBuyCross = _previousAsk <= kcMain && Symbol.Ask > kcMain; + + if (isUpTrend && isBuyCross) + { + var stopLossPrice = _keltnerChannels.Bottom.Last(1); + var takeProfitPrice = _keltnerChannels.Top.Last(1); + + if (stopLossPrice >= Symbol.Ask) + { + UpdatePreviousPrices(); + return; + } + + var stopLossInPips = (Symbol.Ask - stopLossPrice) / Symbol.PipSize; + var takeProfitInPips = (takeProfitPrice - Symbol.Ask) / Symbol.PipSize; + + double? volumeInUnits = Symbol.VolumeForProportionalRisk(ProportionalAmountType.Equity, EquityRiskPercent / MaxPositions / 100, stopLossPrice); + if (volumeInUnits.HasValue) + { + var normalizedVolume = Symbol.NormalizeVolumeInUnits(volumeInUnits.Value, RoundingMode.Down); + if (normalizedVolume > 0) + { + ExecuteMarketOrder(TradeType.Buy, SymbolName, normalizedVolume, $"{BotLabelPrefix}_Buy", stopLossInPips, takeProfitInPips); + } + } + } + + // Short-Entry-Logik + bool isDownTrend = hmaSlope < 0 && smaSlope < 0; + // KORREKTUR: Verwendet den gespeicherten _previousBid für einen echten Tick-basierten Kreuzungsvergleich. + bool isSellCross = _previousBid >= kcMain && Symbol.Bid < kcMain; + + if (isDownTrend && isSellCross) + { + var stopLossPrice = _keltnerChannels.Top.Last(1); + var takeProfitPrice = _keltnerChannels.Bottom.Last(1); + + if (stopLossPrice <= Symbol.Bid) + { + UpdatePreviousPrices(); + return; + } + + var stopLossInPips = (stopLossPrice - Symbol.Bid) / Symbol.PipSize; + var takeProfitInPips = (Symbol.Bid - takeProfitPrice) / Symbol.PipSize; + + double? volumeInUnits = Symbol.VolumeForProportionalRisk(ProportionalAmountType.Equity, EquityRiskPercent / MaxPositions / 100, stopLossPrice); + if (volumeInUnits.HasValue) + { + var normalizedVolume = Symbol.NormalizeVolumeInUnits(volumeInUnits.Value, RoundingMode.Down); + if (normalizedVolume > 0) + { + ExecuteMarketOrder(TradeType.Sell, SymbolName, normalizedVolume, $"{BotLabelPrefix}_Sell", stopLossInPips, takeProfitInPips); + } + } + } + + // KORREKTUR: Speichert die aktuellen Preise für den nächsten Tick. + UpdatePreviousPrices(); + } + + private void UpdatePreviousPrices() + { + _previousAsk = Symbol.Ask; + _previousBid = Symbol.Bid; + } + + private void OnPositionOpened(PositionOpenedEventArgs args) + { + var position = args.Position; + + if (!position.Label.StartsWith(BotLabelPrefix)) + { + return; + } + + if (!position.StopLoss.HasValue || !position.TakeProfit.HasValue) + { + Print("KRITISCHER FEHLER: Position {0} ({1}) wurde ohne StopLoss oder TakeProfit eröffnet. Bot wird gestoppt.", position.Id, position.Label); + Stop(); + } + } + + private void DrawIndicators() + { + // Da Indikatorwerte sich nur auf Bar-Basis ändern, ist eine Aktualisierung pro Tick nicht nötig. + // Die Ausführung im OnBar spart daher Rechenleistung. + var previousBarTime = Bars.OpenTimes.Last(2); + var currentBarTime = Bars.OpenTimes.Last(1); + string uniqueSuffix = $"_{currentBarTime:o}"; + + Chart.DrawTrendLine(HmaLinePrefix + uniqueSuffix, previousBarTime, _hma.Result.Last(2), currentBarTime, _hma.Result.Last(1), Color.Purple); + Chart.DrawTrendLine(SmaLinePrefix + uniqueSuffix, previousBarTime, _sma.Result.Last(2), currentBarTime, _sma.Result.Last(1), Color.Orange); + Chart.DrawTrendLine(KcTopLinePrefix + uniqueSuffix, previousBarTime, _keltnerChannels.Top.Last(2), currentBarTime, _keltnerChannels.Top.Last(1), Color.Blue); + Chart.DrawTrendLine(KcMainLinePrefix + uniqueSuffix, previousBarTime, _keltnerChannels.Main.Last(2), currentBarTime, _keltnerChannels.Main.Last(1), Color.Blue, 2, LineStyle.Dots); + Chart.DrawTrendLine(KcBottomLinePrefix + uniqueSuffix, previousBarTime, _keltnerChannels.Bottom.Last(2), currentBarTime, _keltnerChannels.Bottom.Last(1), Color.Blue); + } + + private void RemoveOldSegments() + { + if (Bars.Count <= DrawingHistory) + { + return; + } + + var oldBarTime = Bars.OpenTimes.Last(DrawingHistory); + string oldUniqueSuffix = $"_{oldBarTime:o}"; + + Chart.RemoveObject(HmaLinePrefix + oldUniqueSuffix); + Chart.RemoveObject(SmaLinePrefix + oldUniqueSuffix); + Chart.RemoveObject(KcTopLinePrefix + oldUniqueSuffix); + Chart.RemoveObject(KcMainLinePrefix + oldUniqueSuffix); + Chart.RemoveObject(KcBottomLinePrefix + oldUniqueSuffix); + } + } +} \ No newline at end of file diff --git a/Sources/Robots/Multi-MA-Bot/Multi-MA-Bot/Multi-MA-Bot.csproj b/Sources/Robots/Multi-MA-Bot/Multi-MA-Bot/Multi-MA-Bot.csproj new file mode 100644 index 0000000..da1fef1 --- /dev/null +++ b/Sources/Robots/Multi-MA-Bot/Multi-MA-Bot/Multi-MA-Bot.csproj @@ -0,0 +1,9 @@ + + + net6.0 + + + + + + diff --git a/Sources/Robots/Multi-MA-Bot/Multi-MA-Bot/obj/Debug/net6.0/.NETCoreApp,Version=v6.0.AssemblyAttributes.cs b/Sources/Robots/Multi-MA-Bot/Multi-MA-Bot/obj/Debug/net6.0/.NETCoreApp,Version=v6.0.AssemblyAttributes.cs new file mode 100644 index 0000000..32c95f9 --- /dev/null +++ b/Sources/Robots/Multi-MA-Bot/Multi-MA-Bot/obj/Debug/net6.0/.NETCoreApp,Version=v6.0.AssemblyAttributes.cs @@ -0,0 +1,4 @@ +// +using System; +using System.Reflection; +[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v6.0", FrameworkDisplayName = "")] diff --git a/Sources/Robots/Multi-MA-Bot/Multi-MA-Bot/obj/Debug/net6.0/Multi-MA-Bot.AssemblyInfo.cs b/Sources/Robots/Multi-MA-Bot/Multi-MA-Bot/obj/Debug/net6.0/Multi-MA-Bot.AssemblyInfo.cs new file mode 100644 index 0000000..a2d0e3a --- /dev/null +++ b/Sources/Robots/Multi-MA-Bot/Multi-MA-Bot/obj/Debug/net6.0/Multi-MA-Bot.AssemblyInfo.cs @@ -0,0 +1,22 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +using System; +using System.Reflection; + +[assembly: System.Reflection.AssemblyCompanyAttribute("Multi-MA-Bot")] +[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] +[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0")] +[assembly: System.Reflection.AssemblyProductAttribute("Multi-MA-Bot")] +[assembly: System.Reflection.AssemblyTitleAttribute("Multi-MA-Bot")] +[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] + +// Generated by the MSBuild WriteCodeFragment class. + diff --git a/Sources/Robots/Multi-MA-Bot/Multi-MA-Bot/obj/Debug/net6.0/Multi-MA-Bot.AssemblyInfoInputs.cache b/Sources/Robots/Multi-MA-Bot/Multi-MA-Bot/obj/Debug/net6.0/Multi-MA-Bot.AssemblyInfoInputs.cache new file mode 100644 index 0000000..eae11af --- /dev/null +++ b/Sources/Robots/Multi-MA-Bot/Multi-MA-Bot/obj/Debug/net6.0/Multi-MA-Bot.AssemblyInfoInputs.cache @@ -0,0 +1 @@ +f0e854385f2f3e45c8832fc57c8188402c93f659 diff --git a/Sources/Robots/Multi-MA-Bot/Multi-MA-Bot/obj/Debug/net6.0/Multi-MA-Bot.GeneratedMSBuildEditorConfig.editorconfig b/Sources/Robots/Multi-MA-Bot/Multi-MA-Bot/obj/Debug/net6.0/Multi-MA-Bot.GeneratedMSBuildEditorConfig.editorconfig new file mode 100644 index 0000000..577a726 --- /dev/null +++ b/Sources/Robots/Multi-MA-Bot/Multi-MA-Bot/obj/Debug/net6.0/Multi-MA-Bot.GeneratedMSBuildEditorConfig.editorconfig @@ -0,0 +1,10 @@ +is_global = true +build_property.TargetFramework = net6.0 +build_property.TargetPlatformMinVersion = +build_property.UsingMicrosoftNETSdkWeb = +build_property.ProjectTypeGuids = +build_property.InvariantGlobalization = +build_property.PlatformNeutralAssembly = +build_property._SupportedPlatformList = Linux,macOS,Windows +build_property.RootNamespace = Multi-MA-Bot +build_property.ProjectDir = C:\Users\Brummel\Documents\cAlgo\Sources\Robots\Multi-MA-Bot\Multi-MA-Bot\ diff --git a/Sources/Robots/Multi-MA-Bot/Multi-MA-Bot/obj/Debug/net6.0/Multi-MA-Bot.assets.cache b/Sources/Robots/Multi-MA-Bot/Multi-MA-Bot/obj/Debug/net6.0/Multi-MA-Bot.assets.cache new file mode 100644 index 0000000..aa2e987 Binary files /dev/null and b/Sources/Robots/Multi-MA-Bot/Multi-MA-Bot/obj/Debug/net6.0/Multi-MA-Bot.assets.cache differ diff --git a/Sources/Robots/Multi-MA-Bot/Multi-MA-Bot/obj/Debug/net6.0/Multi-MA-Bot.csproj.AssemblyReference.cache b/Sources/Robots/Multi-MA-Bot/Multi-MA-Bot/obj/Debug/net6.0/Multi-MA-Bot.csproj.AssemblyReference.cache new file mode 100644 index 0000000..76e6da8 Binary files /dev/null and b/Sources/Robots/Multi-MA-Bot/Multi-MA-Bot/obj/Debug/net6.0/Multi-MA-Bot.csproj.AssemblyReference.cache differ diff --git a/Sources/Robots/Multi-MA-Bot/Multi-MA-Bot/obj/Multi-MA-Bot.csproj.nuget.dgspec.json b/Sources/Robots/Multi-MA-Bot/Multi-MA-Bot/obj/Multi-MA-Bot.csproj.nuget.dgspec.json new file mode 100644 index 0000000..4e5fb21 --- /dev/null +++ b/Sources/Robots/Multi-MA-Bot/Multi-MA-Bot/obj/Multi-MA-Bot.csproj.nuget.dgspec.json @@ -0,0 +1,66 @@ +{ + "format": 1, + "restore": { + "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\Multi-MA-Bot\\Multi-MA-Bot\\Multi-MA-Bot.csproj": {} + }, + "projects": { + "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\Multi-MA-Bot\\Multi-MA-Bot\\Multi-MA-Bot.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\Multi-MA-Bot\\Multi-MA-Bot\\Multi-MA-Bot.csproj", + "projectName": "Multi-MA-Bot", + "projectPath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\Multi-MA-Bot\\Multi-MA-Bot\\Multi-MA-Bot.csproj", + "packagesPath": "C:\\Users\\Brummel\\.nuget\\packages\\", + "outputPath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\Multi-MA-Bot\\Multi-MA-Bot\\obj\\", + "projectStyle": "PackageReference", + "configFilePaths": [ + "C:\\Users\\Brummel\\AppData\\Roaming\\NuGet\\NuGet.Config" + ], + "originalTargetFrameworks": [ + "net6.0" + ], + "sources": { + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net6.0": { + "targetAlias": "net6.0", + "projectReferences": {} + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + } + }, + "frameworks": { + "net6.0": { + "targetAlias": "net6.0", + "dependencies": { + "cTrader.Automate": { + "target": "Package", + "version": "[*, )" + } + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\6.0.200\\RuntimeIdentifierGraph.json" + } + } + } + } +} \ No newline at end of file diff --git a/Sources/Robots/Multi-MA-Bot/Multi-MA-Bot/obj/Multi-MA-Bot.csproj.nuget.g.props b/Sources/Robots/Multi-MA-Bot/Multi-MA-Bot/obj/Multi-MA-Bot.csproj.nuget.g.props new file mode 100644 index 0000000..120d227 --- /dev/null +++ b/Sources/Robots/Multi-MA-Bot/Multi-MA-Bot/obj/Multi-MA-Bot.csproj.nuget.g.props @@ -0,0 +1,21 @@ + + + + True + NuGet + $(MSBuildThisFileDirectory)project.assets.json + $(UserProfile)\.nuget\packages\ + C:\Users\Brummel\.nuget\packages\ + PackageReference + 6.1.0 + + + + + + + + + C:\Users\Brummel\.nuget\packages\ctrader.automate\1.0.12 + + \ No newline at end of file diff --git a/Sources/Robots/Multi-MA-Bot/Multi-MA-Bot/obj/Multi-MA-Bot.csproj.nuget.g.targets b/Sources/Robots/Multi-MA-Bot/Multi-MA-Bot/obj/Multi-MA-Bot.csproj.nuget.g.targets new file mode 100644 index 0000000..29b3c4c --- /dev/null +++ b/Sources/Robots/Multi-MA-Bot/Multi-MA-Bot/obj/Multi-MA-Bot.csproj.nuget.g.targets @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Sources/Robots/Multi-MA-Bot/Multi-MA-Bot/obj/project.assets.json b/Sources/Robots/Multi-MA-Bot/Multi-MA-Bot/obj/project.assets.json new file mode 100644 index 0000000..bcecff3 --- /dev/null +++ b/Sources/Robots/Multi-MA-Bot/Multi-MA-Bot/obj/project.assets.json @@ -0,0 +1,139 @@ +{ + "version": 3, + "targets": { + "net6.0": { + "cTrader.Automate/1.0.12": { + "type": "package", + "compile": { + "lib/net6.0/cAlgo.API.dll": {} + }, + "runtime": { + "lib/net6.0/cAlgo.API.dll": {} + }, + "build": { + "build/cTrader.Automate.props": {}, + "build/cTrader.Automate.targets": {} + } + } + } + }, + "libraries": { + "cTrader.Automate/1.0.12": { + "sha512": "h+HPnbqPTkoSeojoYwWWOo5Jm9ExkmrmsU/WbF/idcgO2auKAPILBghZRP6Qt5mAcE/40YCRMNHT45/kzzs5Sg==", + "type": "package", + "path": "ctrader.automate/1.0.12", + "hasTools": true, + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "build/cTrader.Automate.props", + "build/cTrader.Automate.targets", + "ctrader.automate.1.0.12.nupkg.sha512", + "ctrader.automate.nuspec", + "eula.md", + "icon.png", + "lib/net40/cAlgo.API.dll", + "lib/net40/cAlgo.API.xml", + "lib/net6.0/cAlgo.API.dll", + "lib/net6.0/cAlgo.API.xml", + "tools/net472/Core.AlgoFormat.Compose.Reflection.dll", + "tools/net472/Core.AlgoFormat.Writer.dll", + "tools/net472/Core.AlgoFormat.dll", + "tools/net472/Core.Domain.Primitives.dll", + "tools/net472/Newtonsoft.Json.dll", + "tools/net472/System.Buffers.dll", + "tools/net472/System.Collections.Immutable.dll", + "tools/net472/System.Memory.dll", + "tools/net472/System.Numerics.Vectors.dll", + "tools/net472/System.Reflection.Metadata.dll", + "tools/net472/System.Reflection.MetadataLoadContext.dll", + "tools/net472/System.Runtime.CompilerServices.Unsafe.dll", + "tools/net472/cTrader.Automate.Sdk.Tasks.dll", + "tools/net6.0/Core.AlgoFormat.Compose.Reflection.dll", + "tools/net6.0/Core.AlgoFormat.Writer.dll", + "tools/net6.0/Core.AlgoFormat.dll", + "tools/net6.0/Core.Connection.Protobuf.Common.dll", + "tools/net6.0/Core.Domain.Primitives.dll", + "tools/net6.0/Microsoft.Win32.SystemEvents.dll", + "tools/net6.0/Newtonsoft.Json.dll", + "tools/net6.0/System.Drawing.Common.dll", + "tools/net6.0/System.Reflection.MetadataLoadContext.dll", + "tools/net6.0/System.Security.Permissions.dll", + "tools/net6.0/System.Windows.Extensions.dll", + "tools/net6.0/cTrader.Automate.Sdk.Tasks.dll", + "tools/net6.0/protobuf-net.Core.dll", + "tools/net6.0/protobuf-net.dll", + "tools/net6.0/runtimes/unix/lib/net6.0/System.Drawing.Common.dll", + "tools/net6.0/runtimes/win/lib/net6.0/Microsoft.Win32.SystemEvents.dll", + "tools/net6.0/runtimes/win/lib/net6.0/System.Drawing.Common.dll", + "tools/net6.0/runtimes/win/lib/net6.0/System.Windows.Extensions.dll" + ] + } + }, + "projectFileDependencyGroups": { + "net6.0": [ + "cTrader.Automate >= *" + ] + }, + "packageFolders": { + "C:\\Users\\Brummel\\.nuget\\packages\\": {} + }, + "project": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\Multi-MA-Bot\\Multi-MA-Bot\\Multi-MA-Bot.csproj", + "projectName": "Multi-MA-Bot", + "projectPath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\Multi-MA-Bot\\Multi-MA-Bot\\Multi-MA-Bot.csproj", + "packagesPath": "C:\\Users\\Brummel\\.nuget\\packages\\", + "outputPath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\Multi-MA-Bot\\Multi-MA-Bot\\obj\\", + "projectStyle": "PackageReference", + "configFilePaths": [ + "C:\\Users\\Brummel\\AppData\\Roaming\\NuGet\\NuGet.Config" + ], + "originalTargetFrameworks": [ + "net6.0" + ], + "sources": { + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net6.0": { + "targetAlias": "net6.0", + "projectReferences": {} + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + } + }, + "frameworks": { + "net6.0": { + "targetAlias": "net6.0", + "dependencies": { + "cTrader.Automate": { + "target": "Package", + "version": "[*, )" + } + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\6.0.200\\RuntimeIdentifierGraph.json" + } + } + } +} \ No newline at end of file diff --git a/Sources/Robots/Multi-MA-Bot/Multi-MA-Bot/obj/project.nuget.cache b/Sources/Robots/Multi-MA-Bot/Multi-MA-Bot/obj/project.nuget.cache new file mode 100644 index 0000000..39c63ab --- /dev/null +++ b/Sources/Robots/Multi-MA-Bot/Multi-MA-Bot/obj/project.nuget.cache @@ -0,0 +1,10 @@ +{ + "version": 2, + "dgSpecHash": "s24EynUesZSOJz4qpScvhH/N2ttt5lPGd0hG4jf8I3vVmHtQs2Uc8fTWUsUUbz6o6oRXLPvvXBfrOfuejLIKog==", + "success": true, + "projectFilePath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\Multi-MA-Bot\\Multi-MA-Bot\\Multi-MA-Bot.csproj", + "expectedPackageFiles": [ + "C:\\Users\\Brummel\\.nuget\\packages\\ctrader.automate\\1.0.12\\ctrader.automate.1.0.12.nupkg.sha512" + ], + "logs": [] +} \ No newline at end of file diff --git a/Sources/Robots/MultiSmaHmaNotify.algo b/Sources/Robots/MultiSmaHmaNotify.algo new file mode 100644 index 0000000..41c36be Binary files /dev/null and b/Sources/Robots/MultiSmaHmaNotify.algo differ diff --git a/Sources/Robots/MultiSmaHmaNotify/MultiSmaHmaNotify.sln b/Sources/Robots/MultiSmaHmaNotify/MultiSmaHmaNotify.sln new file mode 100644 index 0000000..28ed823 --- /dev/null +++ b/Sources/Robots/MultiSmaHmaNotify/MultiSmaHmaNotify.sln @@ -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 diff --git a/Sources/Robots/MultiSmaHmaNotify/MultiSmaHmaNotify/MultiSmaHmaNotify.cs b/Sources/Robots/MultiSmaHmaNotify/MultiSmaHmaNotify/MultiSmaHmaNotify.cs new file mode 100644 index 0000000..8ab9398 --- /dev/null +++ b/Sources/Robots/MultiSmaHmaNotify/MultiSmaHmaNotify/MultiSmaHmaNotify.cs @@ -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 _monitors; + + #endregion + + protected override void OnStart() + { + _httpClient = new HttpClient(); + _monitors = new List(); + + // Parse Symbols + var symbolNames = new HashSet(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); + } + } + + /// + /// Inner class to handle state per symbol + /// + 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} {_symbol.Name} ({_bot.TimeFrame})\n" + + $"{type} Signal\n" + + $"Reason: {reason}\n" + + $"Time: {localTime}"; + + _bot.SendTelegram(htmlMessage); + } + } + } + } +} \ No newline at end of file diff --git a/Sources/Robots/MultiSmaHmaNotify/MultiSmaHmaNotify/MultiSmaHmaNotify.csproj b/Sources/Robots/MultiSmaHmaNotify/MultiSmaHmaNotify/MultiSmaHmaNotify.csproj new file mode 100644 index 0000000..da1fef1 --- /dev/null +++ b/Sources/Robots/MultiSmaHmaNotify/MultiSmaHmaNotify/MultiSmaHmaNotify.csproj @@ -0,0 +1,9 @@ + + + net6.0 + + + + + + diff --git a/Sources/Robots/MultiTimeframeIndicatorBot_v1.algo b/Sources/Robots/MultiTimeframeIndicatorBot_v1.algo new file mode 100644 index 0000000..fe49e20 Binary files /dev/null and b/Sources/Robots/MultiTimeframeIndicatorBot_v1.algo differ diff --git a/Sources/Robots/MultiTimeframeIndicatorBot_v1/.vscode/tasks.json b/Sources/Robots/MultiTimeframeIndicatorBot_v1/.vscode/tasks.json new file mode 100644 index 0000000..215166e --- /dev/null +++ b/Sources/Robots/MultiTimeframeIndicatorBot_v1/.vscode/tasks.json @@ -0,0 +1 @@ +{"version":"2.0.0","tasks":[{"label":"build","command":"dotnet","type":"process","args":["build","${workspaceFolder}","/property:GenerateFullPaths=true","/consoleLoggerParameters:NoSummary"],"problemMatcher":"$msCompile"}]} \ No newline at end of file diff --git a/Sources/Robots/MultiTimeframeIndicatorBot_v1/MultiTimeframeIndicatorBot_v1.sln b/Sources/Robots/MultiTimeframeIndicatorBot_v1/MultiTimeframeIndicatorBot_v1.sln new file mode 100644 index 0000000..e4839e7 --- /dev/null +++ b/Sources/Robots/MultiTimeframeIndicatorBot_v1/MultiTimeframeIndicatorBot_v1.sln @@ -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}") = "MultiTimeframeIndicatorBot_v1", "MultiTimeframeIndicatorBot_v1\MultiTimeframeIndicatorBot_v1.csproj", "{67d006b6-e88a-4f89-b154-988c64d96db5}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {67d006b6-e88a-4f89-b154-988c64d96db5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {67d006b6-e88a-4f89-b154-988c64d96db5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {67d006b6-e88a-4f89-b154-988c64d96db5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {67d006b6-e88a-4f89-b154-988c64d96db5}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/Sources/Robots/MultiTimeframeIndicatorBot_v1/MultiTimeframeIndicatorBot_v1/MultiTimeframeIndicatorBot_v1.cs b/Sources/Robots/MultiTimeframeIndicatorBot_v1/MultiTimeframeIndicatorBot_v1/MultiTimeframeIndicatorBot_v1.cs new file mode 100644 index 0000000..4b6e298 --- /dev/null +++ b/Sources/Robots/MultiTimeframeIndicatorBot_v1/MultiTimeframeIndicatorBot_v1/MultiTimeframeIndicatorBot_v1.cs @@ -0,0 +1,392 @@ +using System; +using cAlgo.API; +using cAlgo.API.Collections; +using cAlgo.API.Indicators; +using cAlgo.API.Internals; +using System.Collections.Generic; +using System.Linq; + +namespace cAlgo.Robots +{ + [Robot(AccessRights = AccessRights.None, AddIndicators = true)] + public class MultiTimeframeIndicatorBot : Robot + { + #region Identity & Parameters + private const string BotLabel = "MultiTimeframeIndicatorBot_v1"; + + [Parameter("Fast MA Period", DefaultValue = 250, Group = "Periods")] + public int FastMaPeriod { get; set; } + + [Parameter("Slow MA Period", DefaultValue = 200, Group = "Periods")] + public int SlowMaPeriod { get; set; } + + [Parameter("Swing MA Period", DefaultValue = 20, Group = "Periods")] + public int SwingMaPeriod { get; set; } + + [Parameter("Max Risk", DefaultValue = 100.0, Group = "Risk")] + public double MaxRisk { get; set; } + + [Parameter("Min Stop Loss (Pips)", DefaultValue = 10.0, Group = "Risk Management", MinValue = 1)] + public double MinStopLossPips { get; set; } + + [Parameter("Take Profit % of Range", DefaultValue = 75.0, Group = "Risk Management", MinValue = 0.1)] + public double TakeProfitPercentage { get; set; } + + [Parameter("Stop Loss % of Range", DefaultValue = 50.0, Group = "Risk Management", MinValue = 0.1)] + public double StopLossPercentage { get; set; } + + [Parameter("Entry Offset % of Range", DefaultValue = 5.0, Group = "Risk Management", MinValue = 0)] + public double EntryOffsetPercentage { get; set; } + + [Parameter("Pyramiding Threshold (Pips)", DefaultValue = 20.0, Group = "Pyramiding")] + public double PyramidingThresholdPips { get; set; } + + [Parameter("Max Pyramiding Positions", DefaultValue = 3, Group = "Pyramiding")] + public int MaxPyramidingPositions { get; set; } + + [Parameter("Use Trailing Stop", DefaultValue = true, Group = "Risk Management")] + public bool UseTrailingStop { get; set; } + #endregion + + #region Fields + // Holds custom management data for a single trade. + private class TradeInfo + { + public double StopLossPrice { get; set; } + public double? TakeProfitPrice { get; set; } + public double InitialStopLossPips { get; set; } + } + + // Stores management data for all open positions, keyed by position ID. + private readonly Dictionary _managedTrades = new Dictionary(); + private TradeInfo _pendingTradeInfo; + + private enum MarketStructureBias { Neutral, Bullish, Bearish } + + private Bars _dailyBars; + private Color _originalColor; + private HullMovingAverage _fastMaDaily, _swingMaDaily, _fastMaPrimary, _swingMaPrimary; + private SimpleMovingAverage _slowMaDaily, _slowMaPrimary; + private Stack _highestHighs, _lowestLows; + private bool isBullishBias; + private bool isBearishBias; + private double _hh, _ll; + private MarketStructureBias _structureBias = MarketStructureBias.Neutral; + #endregion + + protected override void OnStart() + { + _dailyBars = MarketData.GetBars(TimeFrame.Daily); + _fastMaDaily = Indicators.HullMovingAverage(_dailyBars.ClosePrices, FastMaPeriod); + _slowMaDaily = Indicators.SimpleMovingAverage(_dailyBars.ClosePrices, SlowMaPeriod); + _swingMaDaily = Indicators.HullMovingAverage(_dailyBars.ClosePrices, SwingMaPeriod); + _fastMaPrimary = Indicators.HullMovingAverage(Bars.ClosePrices, FastMaPeriod); + _slowMaPrimary = Indicators.SimpleMovingAverage(Bars.ClosePrices, SlowMaPeriod); + _swingMaPrimary = Indicators.HullMovingAverage(Bars.ClosePrices, SwingMaPeriod); + + _highestHighs = new Stack(); + _lowestLows = new Stack(); + _originalColor = Chart.ColorSettings.GridLinesColor; + + Positions.Opened += OnPositionOpened; + Positions.Closed += OnPositionClosed; + } + + protected override void OnStop() + { + Chart.ColorSettings.GridLinesColor = _originalColor; + Positions.Opened -= OnPositionOpened; + Positions.Closed -= OnPositionClosed; + } + + protected override void OnTick() + { + ManageOpenTrades(); + } + + protected override void OnBar() + { + if (ManagePositions()) return; + + if (Positions.Find(BotLabel, Symbol.Name) != null) return; + if (Bars.Count < 5) return; + + if (_hh == 0 || _ll == 0) + { + _hh = Bars.HighPrices.Last(1); + _ll = Bars.LowPrices.Last(1); + } + + if (Bars.HighPrices.Last(1) > _hh) _hh = Bars.HighPrices.Last(1); + if (Bars.LowPrices.Last(1) < _ll) _ll = Bars.LowPrices.Last(1); + + bool isSwingLow = _swingMaPrimary.Result.Last(3) > _swingMaPrimary.Result.Last(2) && _swingMaPrimary.Result.Last(2) < _swingMaPrimary.Result.Last(1); + bool isSwingHigh = _swingMaPrimary.Result.Last(3) < _swingMaPrimary.Result.Last(2) && _swingMaPrimary.Result.Last(2) > _swingMaPrimary.Result.Last(1); + + if (isSwingLow) { _lowestLows.Push(_ll); _hh = Bars.HighPrices.Last(1); } + if (isSwingHigh) { _highestHighs.Push(_hh); _ll = Bars.LowPrices.Last(1); } + + var dailyIndex = _dailyBars.OpenTimes.GetIndexByTime(Bars.Last(0).OpenTime); + if (dailyIndex < 1) return; + + isBullishBias = _fastMaDaily.Result[dailyIndex] > _fastMaDaily.Result[dailyIndex - 1] && _slowMaDaily.Result[dailyIndex] > _slowMaDaily.Result[dailyIndex - 1] && _fastMaPrimary.Result.Last(1) > _fastMaPrimary.Result.Last(2) && _slowMaPrimary.Result.Last(1) > _slowMaPrimary.Result.Last(2); + isBearishBias = _fastMaDaily.Result[dailyIndex] < _fastMaDaily.Result[dailyIndex - 1] && _slowMaDaily.Result[dailyIndex] < _slowMaDaily.Result[dailyIndex - 1] && _fastMaPrimary.Result.Last(1) < _fastMaPrimary.Result.Last(2) && _slowMaPrimary.Result.Last(1) < _slowMaPrimary.Result.Last(2); + + VisualizeBias(isBullishBias, isBearishBias); + + bool buySignal = _highestHighs.Count > 0 && _lowestLows.Count > 0 && Bars.HighPrices.Last(1) > _highestHighs.Peek(); + bool sellSignal = _highestHighs.Count > 0 && _lowestLows.Count > 0 && Bars.LowPrices.Last(1) < _lowestLows.Peek(); + + if (buySignal) _structureBias = MarketStructureBias.Bullish; + else if (sellSignal) _structureBias = MarketStructureBias.Bearish; + + if (isBullishBias && buySignal && _structureBias == MarketStructureBias.Bullish) + { + var lowPoint = _lowestLows.Peek(); + var highPoint = Math.Max(_hh, _highestHighs.Peek()); + var range = highPoint - lowPoint; + if (range > 0) + { + var entryPrice = lowPoint + range * (EntryOffsetPercentage / 100.0); + var stopLoss = lowPoint - range * (StopLossPercentage / 100.0); + double? takeProfit = UseTrailingStop ? (double?)null : lowPoint + range * (TakeProfitPercentage / 100.0); + + bool isValidOrder = (takeProfit.HasValue && takeProfit > entryPrice && entryPrice > stopLoss) || (!takeProfit.HasValue && entryPrice > stopLoss); + if (isValidOrder) + { + var volume = CalculateVolumeInUnits(entryPrice, stopLoss); + if (volume >= Symbol.VolumeInUnitsMin) + { + _pendingTradeInfo = new TradeInfo { StopLossPrice = stopLoss, TakeProfitPrice = takeProfit, InitialStopLossPips = (entryPrice - stopLoss) / Symbol.PipSize }; + CancelAndPlaceOrder(TradeType.Buy, entryPrice, volume); + } + } + } + } + else if (isBearishBias && sellSignal && _structureBias == MarketStructureBias.Bearish) + { + var highPoint = _highestHighs.Peek(); + var lowPoint = Math.Min(_ll, _lowestLows.Peek()); + var range = highPoint - lowPoint; + if (range > 0) + { + var entryPrice = highPoint - range * (EntryOffsetPercentage / 100.0); + var stopLoss = highPoint + range * (StopLossPercentage / 100.0); + double? takeProfit = UseTrailingStop ? (double?)null : highPoint - range * (TakeProfitPercentage / 100.0); + + bool isValidOrder = (takeProfit.HasValue && stopLoss > entryPrice && entryPrice > takeProfit) || (!takeProfit.HasValue && stopLoss > entryPrice); + if (isValidOrder) + { + var volume = CalculateVolumeInUnits(entryPrice, stopLoss); + if (volume >= Symbol.VolumeInUnitsMin) + { + _pendingTradeInfo = new TradeInfo { StopLossPrice = stopLoss, TakeProfitPrice = takeProfit, InitialStopLossPips = (stopLoss - entryPrice) / Symbol.PipSize }; + CancelAndPlaceOrder(TradeType.Sell, entryPrice, volume); + } + } + } + } + + while (_highestHighs.Count > 0 && _highestHighs.Peek() < Bars.HighPrices.Last(1)) { _highestHighs.Pop(); } + while (_lowestLows.Count > 0 && _lowestLows.Peek() > Bars.LowPrices.Last(1)) { _lowestLows.Pop(); } + + VisualizePeekValues(); + } + + private void OnPositionOpened(PositionOpenedEventArgs args) + { + var position = args.Position; + if (position.Label == BotLabel && position.SymbolName == Symbol.Name) + { + if (_pendingTradeInfo != null && !_managedTrades.ContainsKey(position.Id)) + { + _managedTrades[position.Id] = _pendingTradeInfo; + Print($"Managing new position {position.Id} with custom SL: {_pendingTradeInfo.StopLossPrice:F5}"); + _pendingTradeInfo = null; + } + } + } + + private void OnPositionClosed(PositionClosedEventArgs args) + { + if (_managedTrades.Remove(args.Position.Id)) + { + Print($"Stopped managing closed position {args.Position.Id}. Reason: {args.Reason}"); + } + } + + private void ManageOpenTrades() + { + var managedPositionIds = _managedTrades.Keys.ToList(); + foreach (var positionId in managedPositionIds) + { + var position = Positions.FirstOrDefault(p => p.Id == positionId); + if (position == null) + { + _managedTrades.Remove(positionId); + continue; + } + + var tradeInfo = _managedTrades[positionId]; + if ((position.TradeType == TradeType.Buy && Symbol.Bid <= tradeInfo.StopLossPrice) || (position.TradeType == TradeType.Sell && Symbol.Ask >= tradeInfo.StopLossPrice)) + { + ClosePosition(position); + Print($"Position {position.Id} hit custom SL at {tradeInfo.StopLossPrice:F5}. Closing."); + continue; + } + + if (!UseTrailingStop && tradeInfo.TakeProfitPrice.HasValue) + { + if ((position.TradeType == TradeType.Buy && Symbol.Bid >= tradeInfo.TakeProfitPrice.Value) || (position.TradeType == TradeType.Sell && Symbol.Ask <= tradeInfo.TakeProfitPrice.Value)) + { + ClosePosition(position); + Print($"Position {position.Id} hit custom TP at {tradeInfo.TakeProfitPrice.Value:F5}. Closing."); + continue; + } + } + + if (UseTrailingStop) + { + double newStopLossPrice; + if (position.TradeType == TradeType.Buy) + { + newStopLossPrice = Symbol.Bid - tradeInfo.InitialStopLossPips * Symbol.PipSize; + if (newStopLossPrice > tradeInfo.StopLossPrice) + { + tradeInfo.StopLossPrice = newStopLossPrice; + } + } + else // Sell + { + newStopLossPrice = Symbol.Ask + tradeInfo.InitialStopLossPips * Symbol.PipSize; + if (newStopLossPrice < tradeInfo.StopLossPrice) + { + tradeInfo.StopLossPrice = newStopLossPrice; + } + } + } + } + } + + private void CancelAndPlaceOrder(TradeType tradeType, double entry, double volumeInUnits) + { + var existingOrder = PendingOrders.FirstOrDefault(o => o.Label == BotLabel && o.SymbolName == Symbol.Name); + if (existingOrder != null) + { + CancelPendingOrder(existingOrder); + } + PlaceLimitOrder(tradeType, Symbol.Name, volumeInUnits, entry, BotLabel, null, null, ProtectionType.Absolute); + } + + private double CalculateVolumeInUnits(double entryPrice, double stopLossPrice) + { + var stopLossInPips = Math.Abs(entryPrice - stopLossPrice) / Symbol.PipSize; + + if (stopLossInPips < MinStopLossPips) + { + Print($"Trade volume calculation skipped. Calculated SL of {stopLossInPips:F2} pips is below the minimum of {MinStopLossPips} pips."); + return 0; + } + + if (stopLossInPips <= 0) return 0; + + var volume = Symbol.VolumeForFixedRisk(MaxRisk, stopLossInPips); + return Symbol.NormalizeVolumeInUnits(volume, RoundingMode.Down); + } + + private bool ManagePositions() + { + var openPositions = Positions.Where(p => p.Label == BotLabel && p.SymbolName == Symbol.Name && _managedTrades.ContainsKey(p.Id)).ToList(); + if (!openPositions.Any()) return false; + if (openPositions.Count >= MaxPyramidingPositions) return true; + + var threshold = PyramidingThresholdPips * Symbol.PipSize; + var tradeType = openPositions.First().TradeType; + + bool canPyramid = tradeType == TradeType.Buy ? openPositions.All(p => Symbol.Ask > p.EntryPrice + threshold) : openPositions.All(p => Symbol.Bid < p.EntryPrice - threshold); + if (!canPyramid) return true; + + Print("Pyramiding condition met. Scaling in."); + + double totalRisk = 0; + foreach (var pos in openPositions) + { + var tradeInfo = _managedTrades[pos.Id]; + var stopLossInPips = Math.Abs(pos.EntryPrice - tradeInfo.StopLossPrice) / Symbol.PipSize; + totalRisk += Symbol.AmountRisked(pos.VolumeInUnits, stopLossInPips); + } + + if (totalRisk <= 0) + { + Print("Cannot pyramid. Calculated risk is zero."); + return true; + } + + var lastPosition = openPositions.OrderBy(p => p.EntryTime).Last(); + var lastTradeInfo = _managedTrades[lastPosition.Id]; + var stopLossPipsForNewPosition = lastTradeInfo.InitialStopLossPips; + + var originalPosition = openPositions.OrderBy(p => p.EntryTime).First(); + var originalTradeInfo = _managedTrades[originalPosition.Id]; + + foreach (var pos in openPositions) + { + _managedTrades[pos.Id].StopLossPrice = pos.EntryPrice; + } + Print($"{openPositions.Count} existing position(s) moved to custom break-even."); + + var volumeForNewPosition = Symbol.VolumeForFixedRisk(totalRisk, stopLossPipsForNewPosition); + var normalizedVolume = Symbol.NormalizeVolumeInUnits(volumeForNewPosition, RoundingMode.Down); + + if (normalizedVolume < MinStopLossPips) + { + Print($"Calculated pyramid volume ({normalizedVolume}) is below minimum. Aborting scale-in."); + return true; + } + + var currentPrice = tradeType == TradeType.Buy ? Symbol.Ask : Symbol.Bid; + var newSlPrice = tradeType == TradeType.Buy ? currentPrice - stopLossPipsForNewPosition * Symbol.PipSize : currentPrice + stopLossPipsForNewPosition * Symbol.PipSize; + + var newTradeInfo = new TradeInfo + { + StopLossPrice = newSlPrice, + TakeProfitPrice = UseTrailingStop ? (double?)null : originalTradeInfo.TakeProfitPrice, + InitialStopLossPips = stopLossPipsForNewPosition + }; + + var result = ExecuteMarketOrder(tradeType, Symbol.Name, normalizedVolume, BotLabel, null, null, "Pyramid Position"); + if (result.IsSuccessful) + { + _managedTrades[result.Position.Id] = newTradeInfo; + Print($"Managing new pyramid position {result.Position.Id} with custom SL: {newTradeInfo.StopLossPrice:F5}"); + } + else + { + Print($"Failed to open pyramid position. Error: {result.Error}"); + } + + return true; + } + + private void VisualizeBias(bool isBullish, bool isBearish) + { + Chart.ColorSettings.GridLinesColor = (isBullish || isBearish) ? _originalColor : Chart.ColorSettings.BackgroundColor; + } + + private void VisualizePeekValues() + { + const string highLineName = "peekHighLine", lowLineName = "peekLowLine"; + Chart.RemoveObject(highLineName); Chart.RemoveObject(lowLineName); + if (isBullishBias) + { + if (_hh > 0) Chart.DrawHorizontalLine(highLineName, _hh, Color.Crimson, 1, LineStyle.Dots); + if (_lowestLows.Count > 0) Chart.DrawHorizontalLine(lowLineName, _lowestLows.Peek(), Color.CornflowerBlue, 1, LineStyle.Dots); + } + else if (isBearishBias) + { + if (_highestHighs.Count > 0) Chart.DrawHorizontalLine(highLineName, _highestHighs.Peek(), Color.Crimson, 1, LineStyle.Dots); + if (_ll > 0) Chart.DrawHorizontalLine(lowLineName, _ll, Color.CornflowerBlue, 1, LineStyle.Dots); + } + } + } +} \ No newline at end of file diff --git a/Sources/Robots/MultiTimeframeIndicatorBot_v1/MultiTimeframeIndicatorBot_v1/MultiTimeframeIndicatorBot_v1.csproj b/Sources/Robots/MultiTimeframeIndicatorBot_v1/MultiTimeframeIndicatorBot_v1/MultiTimeframeIndicatorBot_v1.csproj new file mode 100644 index 0000000..da1fef1 --- /dev/null +++ b/Sources/Robots/MultiTimeframeIndicatorBot_v1/MultiTimeframeIndicatorBot_v1/MultiTimeframeIndicatorBot_v1.csproj @@ -0,0 +1,9 @@ + + + net6.0 + + + + + + diff --git a/Sources/Robots/MultiTimeframeIndicatorBot_v1/MultiTimeframeIndicatorBot_v1/obj/Debug/net6.0/.NETCoreApp,Version=v6.0.AssemblyAttributes.cs b/Sources/Robots/MultiTimeframeIndicatorBot_v1/MultiTimeframeIndicatorBot_v1/obj/Debug/net6.0/.NETCoreApp,Version=v6.0.AssemblyAttributes.cs new file mode 100644 index 0000000..32c95f9 --- /dev/null +++ b/Sources/Robots/MultiTimeframeIndicatorBot_v1/MultiTimeframeIndicatorBot_v1/obj/Debug/net6.0/.NETCoreApp,Version=v6.0.AssemblyAttributes.cs @@ -0,0 +1,4 @@ +// +using System; +using System.Reflection; +[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v6.0", FrameworkDisplayName = "")] diff --git a/Sources/Robots/MultiTimeframeIndicatorBot_v1/MultiTimeframeIndicatorBot_v1/obj/Debug/net6.0/MultiTimeframeIndicatorBot_v1.AssemblyInfo.cs b/Sources/Robots/MultiTimeframeIndicatorBot_v1/MultiTimeframeIndicatorBot_v1/obj/Debug/net6.0/MultiTimeframeIndicatorBot_v1.AssemblyInfo.cs new file mode 100644 index 0000000..82e0e11 --- /dev/null +++ b/Sources/Robots/MultiTimeframeIndicatorBot_v1/MultiTimeframeIndicatorBot_v1/obj/Debug/net6.0/MultiTimeframeIndicatorBot_v1.AssemblyInfo.cs @@ -0,0 +1,22 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +using System; +using System.Reflection; + +[assembly: System.Reflection.AssemblyCompanyAttribute("MultiTimeframeIndicatorBot_v1")] +[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] +[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0")] +[assembly: System.Reflection.AssemblyProductAttribute("MultiTimeframeIndicatorBot_v1")] +[assembly: System.Reflection.AssemblyTitleAttribute("MultiTimeframeIndicatorBot_v1")] +[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] + +// Generated by the MSBuild WriteCodeFragment class. + diff --git a/Sources/Robots/MultiTimeframeIndicatorBot_v1/MultiTimeframeIndicatorBot_v1/obj/Debug/net6.0/MultiTimeframeIndicatorBot_v1.AssemblyInfoInputs.cache b/Sources/Robots/MultiTimeframeIndicatorBot_v1/MultiTimeframeIndicatorBot_v1/obj/Debug/net6.0/MultiTimeframeIndicatorBot_v1.AssemblyInfoInputs.cache new file mode 100644 index 0000000..915a8f6 --- /dev/null +++ b/Sources/Robots/MultiTimeframeIndicatorBot_v1/MultiTimeframeIndicatorBot_v1/obj/Debug/net6.0/MultiTimeframeIndicatorBot_v1.AssemblyInfoInputs.cache @@ -0,0 +1 @@ +9b2672a8d2900c6fca08ca32e8f3d901ef4db0e4 diff --git a/Sources/Robots/MultiTimeframeIndicatorBot_v1/MultiTimeframeIndicatorBot_v1/obj/Debug/net6.0/MultiTimeframeIndicatorBot_v1.GeneratedMSBuildEditorConfig.editorconfig b/Sources/Robots/MultiTimeframeIndicatorBot_v1/MultiTimeframeIndicatorBot_v1/obj/Debug/net6.0/MultiTimeframeIndicatorBot_v1.GeneratedMSBuildEditorConfig.editorconfig new file mode 100644 index 0000000..164d5a3 --- /dev/null +++ b/Sources/Robots/MultiTimeframeIndicatorBot_v1/MultiTimeframeIndicatorBot_v1/obj/Debug/net6.0/MultiTimeframeIndicatorBot_v1.GeneratedMSBuildEditorConfig.editorconfig @@ -0,0 +1,10 @@ +is_global = true +build_property.TargetFramework = net6.0 +build_property.TargetPlatformMinVersion = +build_property.UsingMicrosoftNETSdkWeb = +build_property.ProjectTypeGuids = +build_property.InvariantGlobalization = +build_property.PlatformNeutralAssembly = +build_property._SupportedPlatformList = Linux,macOS,Windows +build_property.RootNamespace = MultiTimeframeIndicatorBot_v1 +build_property.ProjectDir = C:\Users\Brummel\Documents\cAlgo\Sources\Robots\MultiTimeframeIndicatorBot_v1\MultiTimeframeIndicatorBot_v1\ diff --git a/Sources/Robots/MultiTimeframeIndicatorBot_v1/MultiTimeframeIndicatorBot_v1/obj/Debug/net6.0/MultiTimeframeIndicatorBot_v1.assets.cache b/Sources/Robots/MultiTimeframeIndicatorBot_v1/MultiTimeframeIndicatorBot_v1/obj/Debug/net6.0/MultiTimeframeIndicatorBot_v1.assets.cache new file mode 100644 index 0000000..b97156d Binary files /dev/null and b/Sources/Robots/MultiTimeframeIndicatorBot_v1/MultiTimeframeIndicatorBot_v1/obj/Debug/net6.0/MultiTimeframeIndicatorBot_v1.assets.cache differ diff --git a/Sources/Robots/MultiTimeframeIndicatorBot_v1/MultiTimeframeIndicatorBot_v1/obj/Debug/net6.0/MultiTimeframeIndicatorBot_v1.csproj.AssemblyReference.cache b/Sources/Robots/MultiTimeframeIndicatorBot_v1/MultiTimeframeIndicatorBot_v1/obj/Debug/net6.0/MultiTimeframeIndicatorBot_v1.csproj.AssemblyReference.cache new file mode 100644 index 0000000..4696add Binary files /dev/null and b/Sources/Robots/MultiTimeframeIndicatorBot_v1/MultiTimeframeIndicatorBot_v1/obj/Debug/net6.0/MultiTimeframeIndicatorBot_v1.csproj.AssemblyReference.cache differ diff --git a/Sources/Robots/MultiTimeframeIndicatorBot_v1/MultiTimeframeIndicatorBot_v1/obj/MultiTimeframeIndicatorBot_v1.csproj.nuget.dgspec.json b/Sources/Robots/MultiTimeframeIndicatorBot_v1/MultiTimeframeIndicatorBot_v1/obj/MultiTimeframeIndicatorBot_v1.csproj.nuget.dgspec.json new file mode 100644 index 0000000..15b3b8c --- /dev/null +++ b/Sources/Robots/MultiTimeframeIndicatorBot_v1/MultiTimeframeIndicatorBot_v1/obj/MultiTimeframeIndicatorBot_v1.csproj.nuget.dgspec.json @@ -0,0 +1,66 @@ +{ + "format": 1, + "restore": { + "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\MultiTimeframeIndicatorBot_v1\\MultiTimeframeIndicatorBot_v1\\MultiTimeframeIndicatorBot_v1.csproj": {} + }, + "projects": { + "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\MultiTimeframeIndicatorBot_v1\\MultiTimeframeIndicatorBot_v1\\MultiTimeframeIndicatorBot_v1.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\MultiTimeframeIndicatorBot_v1\\MultiTimeframeIndicatorBot_v1\\MultiTimeframeIndicatorBot_v1.csproj", + "projectName": "MultiTimeframeIndicatorBot_v1", + "projectPath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\MultiTimeframeIndicatorBot_v1\\MultiTimeframeIndicatorBot_v1\\MultiTimeframeIndicatorBot_v1.csproj", + "packagesPath": "C:\\Users\\Brummel\\.nuget\\packages\\", + "outputPath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\MultiTimeframeIndicatorBot_v1\\MultiTimeframeIndicatorBot_v1\\obj\\", + "projectStyle": "PackageReference", + "configFilePaths": [ + "C:\\Users\\Brummel\\AppData\\Roaming\\NuGet\\NuGet.Config" + ], + "originalTargetFrameworks": [ + "net6.0" + ], + "sources": { + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net6.0": { + "targetAlias": "net6.0", + "projectReferences": {} + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + } + }, + "frameworks": { + "net6.0": { + "targetAlias": "net6.0", + "dependencies": { + "cTrader.Automate": { + "target": "Package", + "version": "[*, )" + } + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\6.0.200\\RuntimeIdentifierGraph.json" + } + } + } + } +} \ No newline at end of file diff --git a/Sources/Robots/MultiTimeframeIndicatorBot_v1/MultiTimeframeIndicatorBot_v1/obj/MultiTimeframeIndicatorBot_v1.csproj.nuget.g.props b/Sources/Robots/MultiTimeframeIndicatorBot_v1/MultiTimeframeIndicatorBot_v1/obj/MultiTimeframeIndicatorBot_v1.csproj.nuget.g.props new file mode 100644 index 0000000..d551163 --- /dev/null +++ b/Sources/Robots/MultiTimeframeIndicatorBot_v1/MultiTimeframeIndicatorBot_v1/obj/MultiTimeframeIndicatorBot_v1.csproj.nuget.g.props @@ -0,0 +1,21 @@ + + + + True + NuGet + $(MSBuildThisFileDirectory)project.assets.json + $(UserProfile)\.nuget\packages\ + C:\Users\Brummel\.nuget\packages\ + PackageReference + 6.1.0 + + + + + + + + + C:\Users\Brummel\.nuget\packages\ctrader.automate\1.0.13 + + \ No newline at end of file diff --git a/Sources/Robots/MultiTimeframeIndicatorBot_v1/MultiTimeframeIndicatorBot_v1/obj/MultiTimeframeIndicatorBot_v1.csproj.nuget.g.targets b/Sources/Robots/MultiTimeframeIndicatorBot_v1/MultiTimeframeIndicatorBot_v1/obj/MultiTimeframeIndicatorBot_v1.csproj.nuget.g.targets new file mode 100644 index 0000000..13634e8 --- /dev/null +++ b/Sources/Robots/MultiTimeframeIndicatorBot_v1/MultiTimeframeIndicatorBot_v1/obj/MultiTimeframeIndicatorBot_v1.csproj.nuget.g.targets @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Sources/Robots/MultiTimeframeIndicatorBot_v1/MultiTimeframeIndicatorBot_v1/obj/project.assets.json b/Sources/Robots/MultiTimeframeIndicatorBot_v1/MultiTimeframeIndicatorBot_v1/obj/project.assets.json new file mode 100644 index 0000000..5978a9f --- /dev/null +++ b/Sources/Robots/MultiTimeframeIndicatorBot_v1/MultiTimeframeIndicatorBot_v1/obj/project.assets.json @@ -0,0 +1,139 @@ +{ + "version": 3, + "targets": { + "net6.0": { + "cTrader.Automate/1.0.13": { + "type": "package", + "compile": { + "lib/net6.0/cAlgo.API.dll": {} + }, + "runtime": { + "lib/net6.0/cAlgo.API.dll": {} + }, + "build": { + "build/cTrader.Automate.props": {}, + "build/cTrader.Automate.targets": {} + } + } + } + }, + "libraries": { + "cTrader.Automate/1.0.13": { + "sha512": "teFQQhvFb8/1XQsvvGXNlUIkwZ6Ab+FZHqJ8kXiR2AW8IJA0EIue2dRHNglmlRdqSwnMhocSS9EkPZBTXxsHLQ==", + "type": "package", + "path": "ctrader.automate/1.0.13", + "hasTools": true, + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "build/cTrader.Automate.props", + "build/cTrader.Automate.targets", + "ctrader.automate.1.0.13.nupkg.sha512", + "ctrader.automate.nuspec", + "eula.md", + "icon.png", + "lib/net40/cAlgo.API.dll", + "lib/net40/cAlgo.API.xml", + "lib/net6.0/cAlgo.API.dll", + "lib/net6.0/cAlgo.API.xml", + "tools/net472/Core.AlgoFormat.Compose.Reflection.dll", + "tools/net472/Core.AlgoFormat.Writer.dll", + "tools/net472/Core.AlgoFormat.dll", + "tools/net472/Core.Domain.Primitives.dll", + "tools/net472/Newtonsoft.Json.dll", + "tools/net472/System.Buffers.dll", + "tools/net472/System.Collections.Immutable.dll", + "tools/net472/System.Memory.dll", + "tools/net472/System.Numerics.Vectors.dll", + "tools/net472/System.Reflection.Metadata.dll", + "tools/net472/System.Reflection.MetadataLoadContext.dll", + "tools/net472/System.Runtime.CompilerServices.Unsafe.dll", + "tools/net472/cTrader.Automate.Sdk.Tasks.dll", + "tools/net6.0/Core.AlgoFormat.Compose.Reflection.dll", + "tools/net6.0/Core.AlgoFormat.Writer.dll", + "tools/net6.0/Core.AlgoFormat.dll", + "tools/net6.0/Core.Connection.Protobuf.Common.dll", + "tools/net6.0/Core.Domain.Primitives.dll", + "tools/net6.0/Microsoft.Win32.SystemEvents.dll", + "tools/net6.0/Newtonsoft.Json.dll", + "tools/net6.0/System.Drawing.Common.dll", + "tools/net6.0/System.Reflection.MetadataLoadContext.dll", + "tools/net6.0/System.Security.Permissions.dll", + "tools/net6.0/System.Windows.Extensions.dll", + "tools/net6.0/cTrader.Automate.Sdk.Tasks.dll", + "tools/net6.0/protobuf-net.Core.dll", + "tools/net6.0/protobuf-net.dll", + "tools/net6.0/runtimes/unix/lib/net6.0/System.Drawing.Common.dll", + "tools/net6.0/runtimes/win/lib/net6.0/Microsoft.Win32.SystemEvents.dll", + "tools/net6.0/runtimes/win/lib/net6.0/System.Drawing.Common.dll", + "tools/net6.0/runtimes/win/lib/net6.0/System.Windows.Extensions.dll" + ] + } + }, + "projectFileDependencyGroups": { + "net6.0": [ + "cTrader.Automate >= *" + ] + }, + "packageFolders": { + "C:\\Users\\Brummel\\.nuget\\packages\\": {} + }, + "project": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\MultiTimeframeIndicatorBot_v1\\MultiTimeframeIndicatorBot_v1\\MultiTimeframeIndicatorBot_v1.csproj", + "projectName": "MultiTimeframeIndicatorBot_v1", + "projectPath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\MultiTimeframeIndicatorBot_v1\\MultiTimeframeIndicatorBot_v1\\MultiTimeframeIndicatorBot_v1.csproj", + "packagesPath": "C:\\Users\\Brummel\\.nuget\\packages\\", + "outputPath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\MultiTimeframeIndicatorBot_v1\\MultiTimeframeIndicatorBot_v1\\obj\\", + "projectStyle": "PackageReference", + "configFilePaths": [ + "C:\\Users\\Brummel\\AppData\\Roaming\\NuGet\\NuGet.Config" + ], + "originalTargetFrameworks": [ + "net6.0" + ], + "sources": { + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net6.0": { + "targetAlias": "net6.0", + "projectReferences": {} + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + } + }, + "frameworks": { + "net6.0": { + "targetAlias": "net6.0", + "dependencies": { + "cTrader.Automate": { + "target": "Package", + "version": "[*, )" + } + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\6.0.200\\RuntimeIdentifierGraph.json" + } + } + } +} \ No newline at end of file diff --git a/Sources/Robots/MultiTimeframeIndicatorBot_v1/MultiTimeframeIndicatorBot_v1/obj/project.nuget.cache b/Sources/Robots/MultiTimeframeIndicatorBot_v1/MultiTimeframeIndicatorBot_v1/obj/project.nuget.cache new file mode 100644 index 0000000..4e1b60e --- /dev/null +++ b/Sources/Robots/MultiTimeframeIndicatorBot_v1/MultiTimeframeIndicatorBot_v1/obj/project.nuget.cache @@ -0,0 +1,10 @@ +{ + "version": 2, + "dgSpecHash": "SM9Rn42GTn/x8ySYHA/b4QOq7C9kj6vyI6bzhdQj9r0Xxv3G7phcupg31/F7ZfMQ65LVxaqU5zCmidKth1N4+Q==", + "success": true, + "projectFilePath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\MultiTimeframeIndicatorBot_v1\\MultiTimeframeIndicatorBot_v1\\MultiTimeframeIndicatorBot_v1.csproj", + "expectedPackageFiles": [ + "C:\\Users\\Brummel\\.nuget\\packages\\ctrader.automate\\1.0.13\\ctrader.automate.1.0.13.nupkg.sha512" + ], + "logs": [] +} \ No newline at end of file diff --git a/Sources/Robots/RoundNumberBreakout.algo b/Sources/Robots/RoundNumberBreakout.algo new file mode 100644 index 0000000..305324d Binary files /dev/null and b/Sources/Robots/RoundNumberBreakout.algo differ diff --git a/Sources/Robots/RoundNumberBreakout/.vscode/tasks.json b/Sources/Robots/RoundNumberBreakout/.vscode/tasks.json new file mode 100644 index 0000000..215166e --- /dev/null +++ b/Sources/Robots/RoundNumberBreakout/.vscode/tasks.json @@ -0,0 +1 @@ +{"version":"2.0.0","tasks":[{"label":"build","command":"dotnet","type":"process","args":["build","${workspaceFolder}","/property:GenerateFullPaths=true","/consoleLoggerParameters:NoSummary"],"problemMatcher":"$msCompile"}]} \ No newline at end of file diff --git a/Sources/Robots/RoundNumberBreakout/RoundNumberBreakout.sln b/Sources/Robots/RoundNumberBreakout/RoundNumberBreakout.sln new file mode 100644 index 0000000..8e535e5 --- /dev/null +++ b/Sources/Robots/RoundNumberBreakout/RoundNumberBreakout.sln @@ -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}") = "RoundNumberBreakout", "RoundNumberBreakout\RoundNumberBreakout.csproj", "{1339461f-b88a-4077-b07b-184bd48cdf2e}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {1339461f-b88a-4077-b07b-184bd48cdf2e}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1339461f-b88a-4077-b07b-184bd48cdf2e}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1339461f-b88a-4077-b07b-184bd48cdf2e}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1339461f-b88a-4077-b07b-184bd48cdf2e}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/Sources/Robots/RoundNumberBreakout/RoundNumberBreakout/RoundNumberBreakout.cs b/Sources/Robots/RoundNumberBreakout/RoundNumberBreakout/RoundNumberBreakout.cs new file mode 100644 index 0000000..c94a382 --- /dev/null +++ b/Sources/Robots/RoundNumberBreakout/RoundNumberBreakout/RoundNumberBreakout.cs @@ -0,0 +1,209 @@ +using System; +using System.Linq; +using cAlgo.API; +using cAlgo.API.Internals; + +namespace cAlgo.Robots +{ + [Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)] + public class RoundNumberBreakout : Robot + { + #region Parameters + + [Parameter("Order Volume (Lots)", DefaultValue = 1.0)] + public double VolumeInLots { get; set; } + + [Parameter("Round Number Step (n)", DefaultValue = 50, MinValue = 0.00001)] + public double RoundNumberStep { get; set; } + + [Parameter("SL Factor", DefaultValue = 1.0)] + public double SlFactor { get; set; } + + [Parameter("TP Factor", DefaultValue = 2.0)] + public double TpFactor { get; set; } + + [Parameter("Label", DefaultValue = "RN_Breakout")] + public string Label { get; set; } + + [Parameter("Max Lookback Bars", DefaultValue = 200, Group = "Optimization")] + public int MaxLookback { get; set; } + + #endregion + + #region Fields + + private double _volumeInUnits; + + #endregion + + protected override void OnStart() + { + _volumeInUnits = Symbol.QuantityToVolumeInUnits(VolumeInLots); + } + + protected override void OnBarClosed() + { + double closeCurrent = Bars.ClosePrices.Last(0); + double closePrev = Bars.ClosePrices.Last(1); + + // --- BUY LOGIC --- + double potentialLowerRn = Math.Floor(closeCurrent / RoundNumberStep) * RoundNumberStep; + + if (closeCurrent > potentialLowerRn && closePrev < potentialLowerRn) + { + // 1. Check duplicate RN + // 2. Check virtual history + if (!IsSameAsLastEntry(TradeType.Buy, potentialLowerRn) && + WasLastVirtualTradeSuccessful(TradeType.Buy, potentialLowerRn)) + { + CancelPendingOrders(); + PlaceTrade(TradeType.Buy, potentialLowerRn); + } + } + + // --- SELL LOGIC --- + double potentialUpperRn = Math.Ceiling(closeCurrent / RoundNumberStep) * RoundNumberStep; + + if (closeCurrent < potentialUpperRn && closePrev > potentialUpperRn) + { + // 1. Check duplicate RN + // 2. Check virtual history + if (!IsSameAsLastEntry(TradeType.Sell, potentialUpperRn) && + WasLastVirtualTradeSuccessful(TradeType.Sell, potentialUpperRn)) + { + CancelPendingOrders(); + PlaceTrade(TradeType.Sell, potentialUpperRn); + } + } + } + + private void PlaceTrade(TradeType type, double entryPrice) + { + double slPrice, tpPrice; + CalculateLevels(type, entryPrice, out slPrice, out tpPrice); + + entryPrice = Math.Round(entryPrice, Symbol.Digits); + + PlaceLimitOrder(type, SymbolName, _volumeInUnits, entryPrice, Label, slPrice, tpPrice, ProtectionType.Absolute); + } + + /// + /// Checks if the last executed trade in the given direction was opened at the same RN level. + /// + private bool IsSameAsLastEntry(TradeType type, double currentRn) + { + // We look at actual History to see what we really traded last + var lastTrade = History + .Where(t => t.Label == Label && t.SymbolName == SymbolName && t.TradeType == type) + .OrderByDescending(t => t.EntryTime) // Important: Order by EntryTime, not ClosingTime + .FirstOrDefault(); + + if (lastTrade == null) + return false; + + // Use a small epsilon for double comparison (TickSize / 2) + double epsilon = Symbol.TickSize / 2; + return Math.Abs(lastTrade.EntryPrice - currentRn) < epsilon; + } + + /// + /// Simulates the outcome of the PREVIOUS signal of the same type. + /// + private bool WasLastVirtualTradeSuccessful(TradeType type, double currentRnLevel) + { + int startIndex = 2; + int foundSignalIndex = -1; + double foundRnLevel = 0; + + // 1. Find the previous signal backwards + for (int i = startIndex; i < Math.Min(Bars.Count, MaxLookback); i++) + { + double c = Bars.ClosePrices.Last(i); + double cPrev = Bars.ClosePrices.Last(i + 1); + + if (type == TradeType.Buy) + { + double rn = Math.Floor(c / RoundNumberStep) * RoundNumberStep; + if (c > rn && cPrev < rn) + { + foundSignalIndex = i; + foundRnLevel = rn; + break; + } + } + else // Sell + { + double rn = Math.Ceiling(c / RoundNumberStep) * RoundNumberStep; + if (c < rn && cPrev > rn) + { + foundSignalIndex = i; + foundRnLevel = rn; + break; + } + } + } + + if (foundSignalIndex == -1) return true; + + // 2. Simulate forward + double sl, tp; + CalculateLevels(type, foundRnLevel, out sl, out tp); + + bool isFilled = false; + + for (int j = foundSignalIndex - 1; j >= 0; j--) + { + double high = Bars.HighPrices.Last(j); + double low = Bars.LowPrices.Last(j); + + if (!isFilled) + { + if (type == TradeType.Buy) { if (low <= foundRnLevel) isFilled = true; } + else { if (high >= foundRnLevel) isFilled = true; } + } + + if (isFilled) + { + if (type == TradeType.Buy) + { + if (low <= sl) return false; // LOSS + if (high >= tp) return true; // WIN + } + else + { + if (high >= sl) return false; // LOSS + if (low <= tp) return true; // WIN + } + } + } + + return true; + } + + private void CalculateLevels(TradeType type, double entryPrice, out double sl, out double tp) + { + double riskAmount = RoundNumberStep * SlFactor; + double rewardAmount = RoundNumberStep * TpFactor; + + if (type == TradeType.Buy) + { + sl = Math.Round(entryPrice - riskAmount, Symbol.Digits); + tp = Math.Round(entryPrice + rewardAmount, Symbol.Digits); + } + else + { + sl = Math.Round(entryPrice + riskAmount, Symbol.Digits); + tp = Math.Round(entryPrice - rewardAmount, Symbol.Digits); + } + } + + private void CancelPendingOrders() + { + var ordersToCancel = PendingOrders.Where(o => o.Label == Label && o.SymbolName == SymbolName); + foreach (var order in ordersToCancel) + { + CancelPendingOrder(order); + } + } + } +} \ No newline at end of file diff --git a/Sources/Robots/RoundNumberBreakout/RoundNumberBreakout/RoundNumberBreakout.csproj b/Sources/Robots/RoundNumberBreakout/RoundNumberBreakout/RoundNumberBreakout.csproj new file mode 100644 index 0000000..da1fef1 --- /dev/null +++ b/Sources/Robots/RoundNumberBreakout/RoundNumberBreakout/RoundNumberBreakout.csproj @@ -0,0 +1,9 @@ + + + net6.0 + + + + + + diff --git a/Sources/Robots/RoundNumberBreakout/RoundNumberBreakout/obj/Debug/net6.0/.NETCoreApp,Version=v6.0.AssemblyAttributes.cs b/Sources/Robots/RoundNumberBreakout/RoundNumberBreakout/obj/Debug/net6.0/.NETCoreApp,Version=v6.0.AssemblyAttributes.cs new file mode 100644 index 0000000..32c95f9 --- /dev/null +++ b/Sources/Robots/RoundNumberBreakout/RoundNumberBreakout/obj/Debug/net6.0/.NETCoreApp,Version=v6.0.AssemblyAttributes.cs @@ -0,0 +1,4 @@ +// +using System; +using System.Reflection; +[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v6.0", FrameworkDisplayName = "")] diff --git a/Sources/Robots/RoundNumberBreakout/RoundNumberBreakout/obj/Debug/net6.0/RoundNumberBreakout.AssemblyInfo.cs b/Sources/Robots/RoundNumberBreakout/RoundNumberBreakout/obj/Debug/net6.0/RoundNumberBreakout.AssemblyInfo.cs new file mode 100644 index 0000000..52b4da3 --- /dev/null +++ b/Sources/Robots/RoundNumberBreakout/RoundNumberBreakout/obj/Debug/net6.0/RoundNumberBreakout.AssemblyInfo.cs @@ -0,0 +1,22 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +using System; +using System.Reflection; + +[assembly: System.Reflection.AssemblyCompanyAttribute("RoundNumberBreakout")] +[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] +[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0")] +[assembly: System.Reflection.AssemblyProductAttribute("RoundNumberBreakout")] +[assembly: System.Reflection.AssemblyTitleAttribute("RoundNumberBreakout")] +[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] + +// Generated by the MSBuild WriteCodeFragment class. + diff --git a/Sources/Robots/RoundNumberBreakout/RoundNumberBreakout/obj/Debug/net6.0/RoundNumberBreakout.AssemblyInfoInputs.cache b/Sources/Robots/RoundNumberBreakout/RoundNumberBreakout/obj/Debug/net6.0/RoundNumberBreakout.AssemblyInfoInputs.cache new file mode 100644 index 0000000..98ce71d --- /dev/null +++ b/Sources/Robots/RoundNumberBreakout/RoundNumberBreakout/obj/Debug/net6.0/RoundNumberBreakout.AssemblyInfoInputs.cache @@ -0,0 +1 @@ +97fa14048a771c104ce90affe74e8b1e2d87bfd2 diff --git a/Sources/Robots/RoundNumberBreakout/RoundNumberBreakout/obj/Debug/net6.0/RoundNumberBreakout.GeneratedMSBuildEditorConfig.editorconfig b/Sources/Robots/RoundNumberBreakout/RoundNumberBreakout/obj/Debug/net6.0/RoundNumberBreakout.GeneratedMSBuildEditorConfig.editorconfig new file mode 100644 index 0000000..e6b3908 --- /dev/null +++ b/Sources/Robots/RoundNumberBreakout/RoundNumberBreakout/obj/Debug/net6.0/RoundNumberBreakout.GeneratedMSBuildEditorConfig.editorconfig @@ -0,0 +1,10 @@ +is_global = true +build_property.TargetFramework = net6.0 +build_property.TargetPlatformMinVersion = +build_property.UsingMicrosoftNETSdkWeb = +build_property.ProjectTypeGuids = +build_property.InvariantGlobalization = +build_property.PlatformNeutralAssembly = +build_property._SupportedPlatformList = Linux,macOS,Windows +build_property.RootNamespace = RoundNumberBreakout +build_property.ProjectDir = C:\Users\Brummel\Documents\cAlgo\Sources\Robots\RoundNumberBreakout\RoundNumberBreakout\ diff --git a/Sources/Robots/RoundNumberBreakout/RoundNumberBreakout/obj/Debug/net6.0/RoundNumberBreakout.assets.cache b/Sources/Robots/RoundNumberBreakout/RoundNumberBreakout/obj/Debug/net6.0/RoundNumberBreakout.assets.cache new file mode 100644 index 0000000..f7948b1 Binary files /dev/null and b/Sources/Robots/RoundNumberBreakout/RoundNumberBreakout/obj/Debug/net6.0/RoundNumberBreakout.assets.cache differ diff --git a/Sources/Robots/RoundNumberBreakout/RoundNumberBreakout/obj/Debug/net6.0/RoundNumberBreakout.csproj.AssemblyReference.cache b/Sources/Robots/RoundNumberBreakout/RoundNumberBreakout/obj/Debug/net6.0/RoundNumberBreakout.csproj.AssemblyReference.cache new file mode 100644 index 0000000..50043b7 Binary files /dev/null and b/Sources/Robots/RoundNumberBreakout/RoundNumberBreakout/obj/Debug/net6.0/RoundNumberBreakout.csproj.AssemblyReference.cache differ diff --git a/Sources/Robots/RoundNumberBreakout/RoundNumberBreakout/obj/RoundNumberBreakout.csproj.nuget.dgspec.json b/Sources/Robots/RoundNumberBreakout/RoundNumberBreakout/obj/RoundNumberBreakout.csproj.nuget.dgspec.json new file mode 100644 index 0000000..38c191a --- /dev/null +++ b/Sources/Robots/RoundNumberBreakout/RoundNumberBreakout/obj/RoundNumberBreakout.csproj.nuget.dgspec.json @@ -0,0 +1,66 @@ +{ + "format": 1, + "restore": { + "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\RoundNumberBreakout\\RoundNumberBreakout\\RoundNumberBreakout.csproj": {} + }, + "projects": { + "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\RoundNumberBreakout\\RoundNumberBreakout\\RoundNumberBreakout.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\RoundNumberBreakout\\RoundNumberBreakout\\RoundNumberBreakout.csproj", + "projectName": "RoundNumberBreakout", + "projectPath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\RoundNumberBreakout\\RoundNumberBreakout\\RoundNumberBreakout.csproj", + "packagesPath": "C:\\Users\\Brummel\\.nuget\\packages\\", + "outputPath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\RoundNumberBreakout\\RoundNumberBreakout\\obj\\", + "projectStyle": "PackageReference", + "configFilePaths": [ + "C:\\Users\\Brummel\\AppData\\Roaming\\NuGet\\NuGet.Config" + ], + "originalTargetFrameworks": [ + "net6.0" + ], + "sources": { + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net6.0": { + "targetAlias": "net6.0", + "projectReferences": {} + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + } + }, + "frameworks": { + "net6.0": { + "targetAlias": "net6.0", + "dependencies": { + "cTrader.Automate": { + "target": "Package", + "version": "[*, )" + } + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\6.0.200\\RuntimeIdentifierGraph.json" + } + } + } + } +} \ No newline at end of file diff --git a/Sources/Robots/RoundNumberBreakout/RoundNumberBreakout/obj/RoundNumberBreakout.csproj.nuget.g.props b/Sources/Robots/RoundNumberBreakout/RoundNumberBreakout/obj/RoundNumberBreakout.csproj.nuget.g.props new file mode 100644 index 0000000..0b161ac --- /dev/null +++ b/Sources/Robots/RoundNumberBreakout/RoundNumberBreakout/obj/RoundNumberBreakout.csproj.nuget.g.props @@ -0,0 +1,21 @@ + + + + True + NuGet + $(MSBuildThisFileDirectory)project.assets.json + $(UserProfile)\.nuget\packages\ + C:\Users\Brummel\.nuget\packages\ + PackageReference + 6.1.0 + + + + + + + + + C:\Users\Brummel\.nuget\packages\ctrader.automate\1.0.14 + + \ No newline at end of file diff --git a/Sources/Robots/RoundNumberBreakout/RoundNumberBreakout/obj/RoundNumberBreakout.csproj.nuget.g.targets b/Sources/Robots/RoundNumberBreakout/RoundNumberBreakout/obj/RoundNumberBreakout.csproj.nuget.g.targets new file mode 100644 index 0000000..46f142c --- /dev/null +++ b/Sources/Robots/RoundNumberBreakout/RoundNumberBreakout/obj/RoundNumberBreakout.csproj.nuget.g.targets @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Sources/Robots/RoundNumberBreakout/RoundNumberBreakout/obj/project.assets.json b/Sources/Robots/RoundNumberBreakout/RoundNumberBreakout/obj/project.assets.json new file mode 100644 index 0000000..84b281e --- /dev/null +++ b/Sources/Robots/RoundNumberBreakout/RoundNumberBreakout/obj/project.assets.json @@ -0,0 +1,139 @@ +{ + "version": 3, + "targets": { + "net6.0": { + "cTrader.Automate/1.0.14": { + "type": "package", + "compile": { + "lib/net6.0/cAlgo.API.dll": {} + }, + "runtime": { + "lib/net6.0/cAlgo.API.dll": {} + }, + "build": { + "build/cTrader.Automate.props": {}, + "build/cTrader.Automate.targets": {} + } + } + } + }, + "libraries": { + "cTrader.Automate/1.0.14": { + "sha512": "eNwE7WL90MGBKb5MuLAtZLdQy0vxkI5EVhLWAQ9S83EAdYkGAzdccvGFLk2oqmtSGBtD+gEpyrJUW/ej4dI4jw==", + "type": "package", + "path": "ctrader.automate/1.0.14", + "hasTools": true, + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "build/cTrader.Automate.props", + "build/cTrader.Automate.targets", + "ctrader.automate.1.0.14.nupkg.sha512", + "ctrader.automate.nuspec", + "eula.md", + "icon.png", + "lib/net40/cAlgo.API.dll", + "lib/net40/cAlgo.API.xml", + "lib/net6.0/cAlgo.API.dll", + "lib/net6.0/cAlgo.API.xml", + "tools/net472/Core.AlgoFormat.Compose.Reflection.dll", + "tools/net472/Core.AlgoFormat.Writer.dll", + "tools/net472/Core.AlgoFormat.dll", + "tools/net472/Core.Domain.Primitives.dll", + "tools/net472/Newtonsoft.Json.dll", + "tools/net472/System.Buffers.dll", + "tools/net472/System.Collections.Immutable.dll", + "tools/net472/System.Memory.dll", + "tools/net472/System.Numerics.Vectors.dll", + "tools/net472/System.Reflection.Metadata.dll", + "tools/net472/System.Reflection.MetadataLoadContext.dll", + "tools/net472/System.Runtime.CompilerServices.Unsafe.dll", + "tools/net472/cTrader.Automate.Sdk.Tasks.dll", + "tools/net6.0/Core.AlgoFormat.Compose.Reflection.dll", + "tools/net6.0/Core.AlgoFormat.Writer.dll", + "tools/net6.0/Core.AlgoFormat.dll", + "tools/net6.0/Core.Connection.Protobuf.Common.dll", + "tools/net6.0/Core.Domain.Primitives.dll", + "tools/net6.0/Microsoft.Win32.SystemEvents.dll", + "tools/net6.0/Newtonsoft.Json.dll", + "tools/net6.0/System.Drawing.Common.dll", + "tools/net6.0/System.Reflection.MetadataLoadContext.dll", + "tools/net6.0/System.Security.Permissions.dll", + "tools/net6.0/System.Windows.Extensions.dll", + "tools/net6.0/cTrader.Automate.Sdk.Tasks.dll", + "tools/net6.0/protobuf-net.Core.dll", + "tools/net6.0/protobuf-net.dll", + "tools/net6.0/runtimes/unix/lib/net6.0/System.Drawing.Common.dll", + "tools/net6.0/runtimes/win/lib/net6.0/Microsoft.Win32.SystemEvents.dll", + "tools/net6.0/runtimes/win/lib/net6.0/System.Drawing.Common.dll", + "tools/net6.0/runtimes/win/lib/net6.0/System.Windows.Extensions.dll" + ] + } + }, + "projectFileDependencyGroups": { + "net6.0": [ + "cTrader.Automate >= *" + ] + }, + "packageFolders": { + "C:\\Users\\Brummel\\.nuget\\packages\\": {} + }, + "project": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\RoundNumberBreakout\\RoundNumberBreakout\\RoundNumberBreakout.csproj", + "projectName": "RoundNumberBreakout", + "projectPath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\RoundNumberBreakout\\RoundNumberBreakout\\RoundNumberBreakout.csproj", + "packagesPath": "C:\\Users\\Brummel\\.nuget\\packages\\", + "outputPath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\RoundNumberBreakout\\RoundNumberBreakout\\obj\\", + "projectStyle": "PackageReference", + "configFilePaths": [ + "C:\\Users\\Brummel\\AppData\\Roaming\\NuGet\\NuGet.Config" + ], + "originalTargetFrameworks": [ + "net6.0" + ], + "sources": { + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net6.0": { + "targetAlias": "net6.0", + "projectReferences": {} + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + } + }, + "frameworks": { + "net6.0": { + "targetAlias": "net6.0", + "dependencies": { + "cTrader.Automate": { + "target": "Package", + "version": "[*, )" + } + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\6.0.200\\RuntimeIdentifierGraph.json" + } + } + } +} \ No newline at end of file diff --git a/Sources/Robots/RoundNumberBreakout/RoundNumberBreakout/obj/project.nuget.cache b/Sources/Robots/RoundNumberBreakout/RoundNumberBreakout/obj/project.nuget.cache new file mode 100644 index 0000000..054e790 --- /dev/null +++ b/Sources/Robots/RoundNumberBreakout/RoundNumberBreakout/obj/project.nuget.cache @@ -0,0 +1,10 @@ +{ + "version": 2, + "dgSpecHash": "G99Qvg+Rf0hVw9376X2H9VP5tcy5DRCmAUc/rfz0z3MvZ72Ic/Ky+j1cLsZrJITjv8ekw+Bo/R3kXzpAqUmmGg==", + "success": true, + "projectFilePath": "C:\\Users\\Brummel\\Documents\\cAlgo\\Sources\\Robots\\RoundNumberBreakout\\RoundNumberBreakout\\RoundNumberBreakout.csproj", + "expectedPackageFiles": [ + "C:\\Users\\Brummel\\.nuget\\packages\\ctrader.automate\\1.0.14\\ctrader.automate.1.0.14.nupkg.sha512" + ], + "logs": [] +} \ No newline at end of file diff --git a/Sources/Robots/SL-Manager.algo b/Sources/Robots/SL-Manager.algo new file mode 100644 index 0000000..e747f1d Binary files /dev/null and b/Sources/Robots/SL-Manager.algo differ diff --git a/Sources/Robots/SL-Manager/SL-Manager.sln b/Sources/Robots/SL-Manager/SL-Manager.sln new file mode 100644 index 0000000..50a6dd7 --- /dev/null +++ b/Sources/Robots/SL-Manager/SL-Manager.sln @@ -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}") = "SL-Manager", "SL-Manager\SL-Manager.csproj", "{9c5ce502-1255-4944-9785-d0997d792830}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {9c5ce502-1255-4944-9785-d0997d792830}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9c5ce502-1255-4944-9785-d0997d792830}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9c5ce502-1255-4944-9785-d0997d792830}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9c5ce502-1255-4944-9785-d0997d792830}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/Sources/Robots/SL-Manager/SL-Manager/SL-Manager.cs b/Sources/Robots/SL-Manager/SL-Manager/SL-Manager.cs new file mode 100644 index 0000000..cbd62c2 --- /dev/null +++ b/Sources/Robots/SL-Manager/SL-Manager/SL-Manager.cs @@ -0,0 +1,149 @@ +using System; +using System.Collections.Generic; +using cAlgo.API; +using cAlgo.API.Internals; + +namespace cAlgo.Robots +{ + [Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)] + public class SimpleLossCloser : Robot + { + [Parameter("Max Loss Amount (EUR)", DefaultValue = 0.95, MinValue = 0.01)] + public double MaxLossAmount { get; set; } + + [Parameter("Use Profit Protection (Rule 2)", DefaultValue = true)] + public bool UseProfitProtection { get; set; } + + [Parameter("Use Drawdown Close (Rule 3)", DefaultValue = true)] + public bool UseDrawdownCloseRule { get; set; } + + // Stores the peak profit (G) achieved for each position (Position.Id) + private readonly Dictionary _maxProfits = new Dictionary(); + + // Stores the max drawdown (D) (lowest negative profit) for each position + private readonly Dictionary _maxDrawdowns = new Dictionary(); + + protected override void OnStart() + { + // Subscribe to position close events to clean up the dictionaries + Positions.Closed += OnPositionClosed; + } + + protected override void OnTick() + { + // Iterate over all open positions for the current symbol + foreach (var position in Positions) + { + if (position.SymbolName != SymbolName) + { + continue; // Only manage positions for the cBot's symbol + } + + // --- Update G (Max Profit) and D (Max Drawdown) --- + _maxProfits.TryGetValue(position.Id, out double maxProfit); + _maxDrawdowns.TryGetValue(position.Id, out double maxDrawdown); + + double currentProfit = position.NetProfit; + + // Update max profit (G) if current profit is higher + if (currentProfit > maxProfit) + { + maxProfit = currentProfit; + _maxProfits[position.Id] = maxProfit; // Store new peak + } + + // Update max drawdown (D) if current profit is lower (more negative) + if (currentProfit < maxDrawdown) + { + maxDrawdown = currentProfit; + _maxDrawdowns[position.Id] = maxDrawdown; // Store new low + } + + // --- Rule 1: Max Loss Close (Hard Stop) --- + // Check if the position has a loss and if that loss exceeds the threshold + if (currentProfit < 0 && Math.Abs(currentProfit) > MaxLossAmount) + { + ClosePosition(position); + continue; // Position is closed, process next position + } + + // --- Rule 2: Profit Protection (Trailing) --- + if (UseProfitProtection) + { + // Check profit protection trigger: + // 1. Max profit (G) must be greater than the MaxLossAmount threshold. + // 2. Current profit must have dropped to 50% (or less) of the max profit (G). + if ((maxProfit > MaxLossAmount) && (currentProfit <= (0.5 * maxProfit))) + { + ClosePosition(position); + continue; // Position is closed, process next position + } + } + + // --- Rule 3: Drawdown Close (New) --- + // If D (absolute value of max drawdown) > G (max profit) AND position is currently in profit + if (UseDrawdownCloseRule) + { + // maxDrawdown is negative (e.g., -5), maxProfit is positive (e.g., 3) + // We check if Abs(-5) > 3, and if current profit is positive. + if ((currentProfit > 0) && (Math.Abs(maxDrawdown) > maxProfit) && (Math.Min(Math.Abs(maxDrawdown), maxProfit) > 0.25 * MaxLossAmount)) + { + ClosePosition(position); + continue; // Position is closed, process next position + } + } + + // --- Rule 4: Set initial Stop Loss (Original Rule 2) --- + // If position is still open and has no SL, set the emergency SL. + if (position.StopLoss == null) + { + // Check required symbol properties before calculation + if (Symbol.PipValue == 0 || Symbol.LotSize == 0) + { + Print("Warning: PipValue or LotSize is zero. Cannot calculate currency-based SL for Position {0}.", position.Id); + continue; // Skip SL calculation for this tick/position + } + + // Convert position volume from units to lots + double positionLots = Symbol.VolumeInUnitsToQuantity(position.VolumeInUnits); + + // Calculate the value of 1 pip for this specific position's quantity + double positionPipValue = Symbol.PipValue * positionLots; + + if (positionPipValue == 0) continue; // Avoid division by zero + + // Calculate the required Stop Loss distance in Pips + double targetLossAmount = 3 * MaxLossAmount; + double slPips = targetLossAmount / positionPipValue; + + double slPrice; + if (position.TradeType == TradeType.Buy) + { + slPrice = position.EntryPrice - (slPips * Symbol.PipSize); + } + else // Sell + { + slPrice = position.EntryPrice + (slPips * Symbol.PipSize); + } + + slPrice = Math.Round(slPrice, Symbol.Digits); + + ModifyPosition(position, slPrice, position.TakeProfit, ProtectionType.Absolute); + } + } + } + + private void OnPositionClosed(PositionClosedEventArgs args) + { + // Remove the position from tracking when it's closed + _maxProfits.Remove(args.Position.Id); + _maxDrawdowns.Remove(args.Position.Id); + } + + protected override void OnStop() + { + // Unsubscribe from events on cBot stop + Positions.Closed -= OnPositionClosed; + } + } +} \ No newline at end of file diff --git a/Sources/Robots/SL-Manager/SL-Manager/SL-Manager.csproj b/Sources/Robots/SL-Manager/SL-Manager/SL-Manager.csproj new file mode 100644 index 0000000..da1fef1 --- /dev/null +++ b/Sources/Robots/SL-Manager/SL-Manager/SL-Manager.csproj @@ -0,0 +1,9 @@ + + + net6.0 + + + + + + diff --git a/Sources/Robots/SmaHmaBot.algo b/Sources/Robots/SmaHmaBot.algo new file mode 100644 index 0000000..42a6068 Binary files /dev/null and b/Sources/Robots/SmaHmaBot.algo differ diff --git a/Sources/Robots/SmaHmaBot/SmaHmaBot.sln b/Sources/Robots/SmaHmaBot/SmaHmaBot.sln new file mode 100644 index 0000000..2765596 --- /dev/null +++ b/Sources/Robots/SmaHmaBot/SmaHmaBot.sln @@ -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 diff --git a/Sources/Robots/SmaHmaBot/SmaHmaBot/SmaHmaBot.cs b/Sources/Robots/SmaHmaBot/SmaHmaBot/SmaHmaBot.cs new file mode 100644 index 0000000..8af7dbb --- /dev/null +++ b/Sources/Robots/SmaHmaBot/SmaHmaBot/SmaHmaBot.cs @@ -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 = $"{Symbol.Name}\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 { } + } + } +} \ No newline at end of file diff --git a/Sources/Robots/SmaHmaBot/SmaHmaBot/SmaHmaBot.csproj b/Sources/Robots/SmaHmaBot/SmaHmaBot/SmaHmaBot.csproj new file mode 100644 index 0000000..da1fef1 --- /dev/null +++ b/Sources/Robots/SmaHmaBot/SmaHmaBot/SmaHmaBot.csproj @@ -0,0 +1,9 @@ + + + net6.0 + + + + + + diff --git a/Sources/Robots/SmaHmaNotification.algo b/Sources/Robots/SmaHmaNotification.algo new file mode 100644 index 0000000..5204714 Binary files /dev/null and b/Sources/Robots/SmaHmaNotification.algo differ diff --git a/Sources/Robots/SmaHmaNotification/SmaHmaNotification.sln b/Sources/Robots/SmaHmaNotification/SmaHmaNotification.sln new file mode 100644 index 0000000..2da1a5f --- /dev/null +++ b/Sources/Robots/SmaHmaNotification/SmaHmaNotification.sln @@ -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}") = "SmaHmaNotification", "SmaHmaNotification\SmaHmaNotification.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 diff --git a/Sources/Robots/SmaHmaNotification/SmaHmaNotification/SmaHmaNotification.cs b/Sources/Robots/SmaHmaNotification/SmaHmaNotification/SmaHmaNotification.cs new file mode 100644 index 0000000..e5e1e43 --- /dev/null +++ b/Sources/Robots/SmaHmaNotification/SmaHmaNotification/SmaHmaNotification.cs @@ -0,0 +1,230 @@ +using System; +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 SmaHmaNotification : 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; } + + #endregion + + #region Fields + + private SimpleMovingAverage _sma; + private HullMovingAverage _hma; + private HttpClient _httpClient; + + private bool _waitingForSmaRise; + private bool _waitingForSmaFall; + + #endregion + + protected override void OnStart() + { + _sma = Indicators.SimpleMovingAverage(Bars.ClosePrices, SmaPeriod); + _hma = Indicators.HullMovingAverage(Bars.ClosePrices, HmaPeriod); + + _httpClient = new HttpClient(); + + // TriggerSignal("START", $"Bot started. Direction: {AllowedDirection}"); + } + + protected override void OnStop() + { + _httpClient?.Dispose(); + } + + protected override void OnBarClosed() + { + if (Bars.Count <= Math.Max(SmaPeriod, HmaPeriod) + 1) + { + return; + } + + double smaClosed = _sma.Result.Last(0); + double smaPrev = _sma.Result.Last(1); + + double hmaClosed = _hma.Result.Last(0); + double hmaPrev = _hma.Result.Last(1); + + 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; + + if (AllowedDirection == TradingDirection.Both || 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; + } + else if (isSmaRising) + { + TriggerSignal("BUY", "SMA Started Rising after HMA CrossUp (Delayed)"); + _waitingForSmaRise = false; + } + } + } + else + { + _waitingForSmaRise = false; + } + + if (AllowedDirection == TradingDirection.Both || 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; + } + else if (isSmaFalling) + { + TriggerSignal("SELL", "SMA Started Falling after HMA CrossDown (Delayed)"); + _waitingForSmaFall = false; + } + } + } + else + { + _waitingForSmaFall = false; + } + } + + private void TriggerSignal(string type, string reason) + { + int index = Bars.Count - 1; + // Use converted Server Time to respect robot's TimeZone even in backtest. + string localTime = TimeZoneInfo.ConvertTimeFromUtc(TimeInUtc, TimeZone).ToString("yyyy-MM-dd HH:mm:ss"); + + if (type == "BUY") + { + Chart.DrawIcon("BuySignal_" + index, ChartIconType.UpArrow, index, Bars.LowPrices[index] - Symbol.PipSize * 5, Color.Green); + } + else if (type == "SELL") + { + Chart.DrawIcon("SellSignal_" + index, ChartIconType.DownArrow, index, Bars.HighPrices[index] + Symbol.PipSize * 5, Color.Red); + } + + string logMessage = $"[{localTime}] {Symbol.Name} ({TimeFrame}) {type}: {reason}"; + Print(logMessage); + + // Suppress external notifications during backtesting. + if (!IsBacktesting) + { + PlaySoundAlert(); + + string emoji = (type == "BUY" ? "🟢" : (type == "SELL" ? "🔴" : "ℹ")); + string htmlMessage = $"{emoji} {Symbol.Name} ({TimeFrame})\n" + + $"{type} Signal\n" + + $"Reason: {reason}\n" + + $"Time: {localTime}"; + + Task.Run(() => SendTelegramMessageAsync(htmlMessage)); + } + } + + private void PlaySoundAlert() + { + try + { + if (!string.IsNullOrWhiteSpace(SoundFilePath)) + { + Notifications.PlaySound(SoundFilePath); + } + } + catch (Exception ex) + { + Print("Error playing sound: {0}", ex.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); + } + } + } +} \ No newline at end of file diff --git a/Sources/Robots/SmaHmaNotification/SmaHmaNotification/SmaHmaNotification.csproj b/Sources/Robots/SmaHmaNotification/SmaHmaNotification/SmaHmaNotification.csproj new file mode 100644 index 0000000..da1fef1 --- /dev/null +++ b/Sources/Robots/SmaHmaNotification/SmaHmaNotification/SmaHmaNotification.csproj @@ -0,0 +1,9 @@ + + + net6.0 + + + + + + diff --git a/Sources/Robots/Web Sockets Sample.algo b/Sources/Robots/Web Sockets Sample.algo new file mode 100644 index 0000000..ec2fa3d Binary files /dev/null and b/Sources/Robots/Web Sockets Sample.algo differ