Topics
24 Oct 2020, 15:02
 4
 1545
 1
20 Aug 2020, 12:10
 8
 1443
 1
25 Jun 2020, 12:05
 1908
 3
22 May 2020, 10:18
 3
 1175
 1
14 May 2020, 10:54
 2
 1853
 4
14 May 2020, 10:33
 4
 1303
 1
03 May 2020, 11:42
 2
 1334
 1
03 May 2020, 11:36
 3
 1140
 1
03 May 2020, 11:32
 10
 2368
 5
27 Apr 2020, 18:28
 3
 1072
 1
27 Apr 2020, 18:23
 3
 1128
 1
27 Apr 2020, 12:08
 3
 1087
 1
27 Apr 2020, 12:05
 2
 1127
 1
23 Apr 2020, 11:33
 15
 1859
 1
23 Apr 2020, 11:24
 4
 1383
 1
Replies

afhacker
18 Feb 2019, 12:53

RE:

helex93 said:

No, because it is not thread-safe method.

According to https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.where linq Where method using IEnumerable, and according to cAlgo.API metadata -  Positions interface is IEnumerable

IEnumerable - not thread-safe, same problem with foreach.

Main problem -  avoid the Exception: System.InvalidOperationException: Collection was modified; enumeration operation may not execute.

It occurs, when cBot enumeate Positions collection, and simultaneously - one or many positions is closing (for example by TakeProfit limit)

You can use the lock statement or build your own thread-safe positoins collection by using  .Net thread safe collections.


@afhacker

afhacker
18 Feb 2019, 09:24

RE: RE: RE: RE: RE:

matt_graham_92@hotmail.com said:

matt_graham_92@hotmail.com said:

afhacker said:

Download the compiled "algo" file from here: https://drive.google.com/open?id=1-pXB4XEJy7Eftom0dha21E379xvuucla

https://gyazo.com/347784a6ec02cb92f3dc7c560815f085

It also gives details here, unfortunately the info is in a small box that i can not make bigger by default. So I would have to send like 4 screen shots, and it would just be a mess to read.. here would be the first one..

https://gyazo.com/119cc47637e33437f48c95ab72fc26e6 
2nd: https://gyazo.com/aef035e36755dc2ea41b8b95d6813029
3rd: https://gyazo.com/3697a3921f71e3e6cdc7c41dac0f8f06
4th: https://gyazo.com/e5f795ce4f388524af8f5dd140146d52
5th! https://gyazo.com/bda17a199b63ca89d490ffe80ed331ec

 

 

You can copy the exception detail and post it here, anyway it looks like there is an issue with your selected sound file path for the sound alert.

You can reset the indicator to its first state by deleting its settings file which is located here:

%userprofile%\Documents\cAlgo

Delete the file "AlertPopupSettings_2.0.1.4.xml" and "Alerts_2.0.1.4.db", then test the indicator.


@afhacker

afhacker
14 Feb 2019, 16:00

RE: RE:

Download the compiled "algo" file from here: https://drive.google.com/open?id=1-pXB4XEJy7Eftom0dha21E379xvuucla

using cAlgo.API;
using cAlgo.API.Alert;
using cAlgo.API.Internals;

namespace cAlgo
{
    [Indicator(IsOverlay = true, TimeZone = TimeZones.UTC, AccessRights = AccessRights.FullAccess)]
    public class NewBarAlert : Indicator
    {
        #region Fields

        private int _lastAlertBarIndex;

        #endregion Fields

        #region Overridden Methods

        protected override void Initialize()
        {
            cAlgo.API.Alert.Models.Configuration.Current.Tracer = Print;

            _lastAlertBarIndex = MarketSeries.Close.Count - 1;
        }

        public override void Calculate(int index)
        {
            if (!IsLastBar || index == _lastAlertBarIndex)
            {
                return;
            }

            _lastAlertBarIndex = index;

            string type = MarketSeries.Close[index] > MarketSeries.Open[index] ? "Bullish Bar" : "Bearish Bar";

            Notifications.ShowPopup(TimeFrame, Symbol, type, "New Bar Alert", MarketSeries.Open[index]);
        }

        #endregion Overridden Methods
    }
}

 


@afhacker

afhacker
06 Feb 2019, 17:45

Try my "Advanced Volume" indicator:

using cAlgo.API;
using cAlgo.API.Indicators;
using cAlgo.API.Internals;
using System;

namespace cAlgo
{
    [Indicator(IsOverlay = false, TimeZone = TimeZones.UTC, AccessRights = AccessRights.None, AutoRescale = true)]
    public class AdvancedVolume : Indicator
    {
        #region Fields

        private double _lastPriceValue;

        private MovingAverage _bullishVolumeMa, _bearishVolumeMa;

        #endregion Fields

        #region Parameters

        [Parameter("Use Live Data", DefaultValue = false)]
        public bool UseLiveData { get; set; }

        [Parameter("Show in %", DefaultValue = true)]
        public bool ShowInPercentage { get; set; }

        [Parameter("MA Periods", DefaultValue = 10)]
        public int MaPeriods { get; set; }

        [Parameter("MA Type", DefaultValue = MovingAverageType.Simple)]
        public MovingAverageType MaType { get; set; }

        [Parameter("Use Average For Delta", DefaultValue = false)]
        public bool UseAverageForDelta { get; set; }

        #endregion Parameters

        #region Outputs

        [Output("Bullish Volume", Color = Colors.Lime, PlotType = PlotType.Histogram, IsHistogram = true, Thickness = 5)]
        public IndicatorDataSeries BullishVolume { get; set; }

        [Output("Bearish Volume", Color = Colors.Red, PlotType = PlotType.Histogram, IsHistogram = true, Thickness = 5)]
        public IndicatorDataSeries BearishVolume { get; set; }

        [Output("Bullish Volume MA", Color = Colors.DarkGreen, PlotType = PlotType.Line)]
        public IndicatorDataSeries BullishVolumeMa { get; set; }

        [Output("Bearish Volume MA", Color = Colors.DarkRed, PlotType = PlotType.Line)]
        public IndicatorDataSeries BearishVolumeMa { get; set; }

        [Output("Delta", Color = Colors.Yellow, PlotType = PlotType.Line)]
        public IndicatorDataSeries Delta { get; set; }

        #endregion Outputs

        #region Methods

        protected override void Initialize()
        {
            _lastPriceValue = MarketSeries.Close.LastValue;

            _bullishVolumeMa = Indicators.MovingAverage(BullishVolume, MaPeriods, MaType);
            _bearishVolumeMa = Indicators.MovingAverage(BearishVolume, MaPeriods, MaType);
        }

        public override void Calculate(int index)
        {
            if (IsLastBar && UseLiveData)
            {
                if (MarketSeries.Close.LastValue > _lastPriceValue)
                {
                    double volume = double.IsNaN(BullishVolume[index]) ? 1 : BullishVolume[index] + 1;

                    BullishVolume[index] = ShowInPercentage ? volume / MarketSeries.TickVolume[index] : volume;
                }
                else if (MarketSeries.Close.LastValue < _lastPriceValue)
                {
                    double volume = double.IsNaN(BearishVolume[index]) ? -1 : BearishVolume[index] - 1;

                    BearishVolume[index] = ShowInPercentage ? volume / MarketSeries.TickVolume[index] : volume;
                }

                _lastPriceValue = MarketSeries.Close.LastValue;
            }
            else
            {
                double barRange = MarketSeries.High[index] - MarketSeries.Low[index];

                double percentageAboveBarClose = -(MarketSeries.High[index] - MarketSeries.Close[index]) / barRange;
                double percentageBelowBarClose = (MarketSeries.Close[index] - MarketSeries.Low[index]) / barRange;

                BullishVolume[index] = ShowInPercentage ? percentageBelowBarClose : MarketSeries.TickVolume[index] * percentageBelowBarClose;
                BearishVolume[index] = ShowInPercentage ? percentageAboveBarClose : MarketSeries.TickVolume[index] * percentageAboveBarClose;
            }

            BullishVolumeMa[index] = _bullishVolumeMa.Result[index];
            BearishVolumeMa[index] = _bearishVolumeMa.Result[index];

            Delta[index] = UseAverageForDelta ? BullishVolumeMa[index] - Math.Abs(BearishVolumeMa[index]) : BullishVolume[index] - Math.Abs(BearishVolume[index]);
        }

        #endregion Methods
    }
}

 


@afhacker

afhacker
01 Feb 2019, 21:31 ( Updated at: 21 Dec 2023, 09:21 )

You can use our Trading Time Periods indicator as cycle lines, it's much better than MT4 cycle lines tool.


@afhacker

afhacker
26 Jan 2019, 21:48 ( Updated at: 21 Dec 2023, 09:21 )

It's working fine for me, H1 chart:

Daily chart:


@afhacker

afhacker
26 Jan 2019, 16:26

Try this:

        private void CalculateHeikenAshi(MarketSeries otherSeries, int periods = 1)
        {
            int index = MarketSeries.Close.Count - 1;

            int otherSeriesIndex = otherSeries.OpenTime.GetIndexByTime(MarketSeries.OpenTime[index]);

            double barOhlcSum = otherSeries.Open[otherSeriesIndex] + otherSeries.Low[otherSeriesIndex] +
                otherSeries.High[otherSeriesIndex] + otherSeries.Close[otherSeriesIndex];

            _close[index] = Math.Round(barOhlcSum / 4, Symbol.Digits);

            if (otherSeriesIndex < periods || double.IsNaN(_open[index - 1]))
            {
                _open[index] = Math.Round((otherSeries.Open[otherSeriesIndex] + otherSeries.Close[otherSeriesIndex]) / 2, Symbol.Digits);
                _high[index] = otherSeries.High[otherSeriesIndex];
                _low[index] = otherSeries.Low[otherSeriesIndex];
            }
            else
            {
                _open[index] = Math.Round((_open[index - 1] + _close[index - 1]) / 2, Symbol.Digits);
                _high[index] = Math.Max(otherSeries.High[otherSeriesIndex], Math.Max(_open[index], _close[index]));
                _low[index] = Math.Min(otherSeries.Low[otherSeriesIndex], Math.Min(_open[index], _close[index]));
            }
        }

The _open, _high, _low, and _close are IndicatorDataSeries objects and the "otherSeries" is the other time frame market series object.

 


@afhacker

afhacker
26 Jan 2019, 12:00

I'm building a free multi time frame Heiken Ashi indicator that will plot another time frame Heiken Ashi bars on top of your chart, it will be available for download on our site in the next few days as an update to our current Heiken Ashi MTF indicator (the indicator current version isn't free but the new version will be free).

Check out our Custom Period Candles indicator, it will look like it with an alerting feature on bar color change.


@afhacker

afhacker
16 Dec 2018, 10:16

Use the app domain unhandled exception event:

        protected override void OnStart()
        {
            AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
        }

        private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
        {
            // Log the exception here
        }

 


@afhacker

afhacker
27 Nov 2018, 19:32

I made the indicator.

Download the compiled ".algo" file from here: https://drive.google.com/open?id=1hImBUaSvwPiUQOEmm0BRk9OtvcRPOQmR

Install the indicator file on your cTrader, once it successfully installed check your custom indicators for "MACD Histogram Alert"

The indicator code:

using cAlgo.API;
using cAlgo.API.Alert;
using cAlgo.API.Indicators;

namespace cAlgo
{
    [Indicator(IsOverlay = false, TimeZone = TimeZones.UTC, AccessRights = AccessRights.FullAccess)]
    public class MacdHistogramAlert : Indicator
    {
        #region Fields

        private MacdHistogram _macdHistogram;

        private int _lastAlertBarIndex;

        #endregion Fields

        #region Parameters

        [Parameter()]
        public DataSeries Source { get; set; }

        [Parameter("Long Cycle", DefaultValue = 26)]
        public int LongCycle { get; set; }

        [Parameter("Short Cycle", DefaultValue = 12)]
        public int ShortCycle { get; set; }

        [Parameter("Signal Periods", DefaultValue = 9)]
        public int Periods { get; set; }

        #endregion Parameters

        #region Outputs

        [Output("Histogram > 0", PlotType = PlotType.Histogram, Color = Colors.Green, Thickness = 2)]
        public IndicatorDataSeries HistogramPositive { get; set; }

        [Output("Histogram < 0", PlotType = PlotType.Histogram, Color = Colors.Red, Thickness = 2)]
        public IndicatorDataSeries HistogramNegative { get; set; }

        [Output("Signal", Color = Colors.Purple, LineStyle = LineStyle.Lines)]
        public IndicatorDataSeries Signal { get; set; }

        #endregion Outputs

        #region Methods

        protected override void Initialize()
        {
            _macdHistogram = Indicators.MacdHistogram(Source, LongCycle, ShortCycle, Periods);
        }

        public override void Calculate(int index)
        {
            if (_macdHistogram.Histogram[index] > 0)
            {
                HistogramPositive[index] = _macdHistogram.Histogram[index];
            }

            if (_macdHistogram.Histogram[index] < 0)
            {
                HistogramNegative[index] = _macdHistogram.Histogram[index];
            }

            Signal[index] = _macdHistogram.Signal[index];

            if (_macdHistogram.Histogram[index - 2] <= 0 && _macdHistogram.Histogram[index - 1] > 0)
            {
                TriggerAlert(TradeType.Buy, index);
            }
            else if (_macdHistogram.Histogram[index - 2] >= 0 && _macdHistogram.Histogram[index - 1] < 0)
            {
                TriggerAlert(TradeType.Sell, index);
            }
        }

        private void TriggerAlert(TradeType tradeType, int index)
        {
            if (index == _lastAlertBarIndex || !IsLastBar)
            {
                return;
            }

            string comment = tradeType == TradeType.Buy ? "Histogram is above 0" : "Histogram is below 0";

            Notifications.ShowPopup(TimeFrame, Symbol, MarketSeries.Close[index], "MACD Histogram Alert", tradeType, comment);
        }

        #endregion Methods
    }
}

 


@afhacker

afhacker
27 Nov 2018, 17:20

The library is available in Nuget, you can install it via Nuget package manager or console.

If you don't have Visual Studio you can download the library from Google Drive link and reference it manually via cTrader reference manager.

Did you read the installation instruction?


@afhacker

afhacker
27 Nov 2018, 15:54

RE: RE:

jjcolacicco said:

afhacker said:

First install the library by following this instruction: https://github.com/afhacker/ctrader-alert_popup/wiki/Installation

Then for showing popup follow this instruction: https://github.com/afhacker/ctrader-alert_popup/wiki/Show-Popup

Don't forget to set your indicator access rights to full access otherwise, it will not work.

I think i am missing something.  i have downloaded and installed the Visual editor so i am ready to go with that but i can not find the download for the indicator at your link, https://github.com/afhacker/ctrader-alert_popup/wiki/Installation

Do i need to sign up to GitHub to see the library you mention?

 

Did you installed the library too? I have written two installation method which one you followed? Nuget or manual?


@afhacker

afhacker
27 Nov 2018, 14:01

First install the library by following this instruction: https://github.com/afhacker/ctrader-alert_popup/wiki/Installation

Then for showing popup follow this instruction: https://github.com/afhacker/ctrader-alert_popup/wiki/Show-Popup

Don't forget to set your indicator access rights to full access otherwise, it will not work.


@afhacker

afhacker
27 Nov 2018, 13:30

Try my alert popup window library, you can add popup, sound, email, and telegram alert on your indicator/cBot with just one line of code!

https://ctrader.com/algos/indicators/show/1692

Check the documentation for full example.


@afhacker

afhacker
27 Nov 2018, 08:35

Hi Alexander,

Anytime you call chart class "Draw***" methods the method returns back the object, so suppose you have called the "DrawRectangle" method and you want to make it filled or interactive:

        public override void Calculate(int index)
        {
            if (IsLastBar)
            {
                // Here we are making the color transparent
                Color color = Color.FromArgb(50, Color.Blue);
                // you can use var instead of class name
                ChartRectangle rectangle = Chart.DrawRectangle("rectangle", index - 40, MarketSeries.Low[index - 40], index, MarketSeries.High[index - 40], color, 1, LineStyle.Dots);
                // This will fill the object
                rectangle.IsFilled = true;
                // This will allow the user to modify the object
                rectangle.IsInteractive = true;
            }
        }

The above code only works on cTrader 3.3 or above.

The way Spotware is designed the new chart drawing isn't good enough or at least that's how I think, I liked it to be like this:

        public override void Calculate(int index)
        {
            if (IsLastBar)
            {
                ChartRectangle rectangle = new ChartRectangle
                {
                    Name = "rectangle",
                    FirstBarIndex = index - 40,
                    SecondBarIndex = index,
                    High = MarketSeries.High[index - 40],
                    Low = MarketSeries.Low[index - 40],
                    Color = Color.FromArgb(50, Color.Blue),
                    Thickness = 1,
                    LineStyle = LineStyle.Dots,
                    IsFilled = true,
                    IsInteractive = true
                };

                Chart.Objects.Add(rectangle);
            }
        }

That way we were able to create our own chart object classes by inheriting from current object classes and it was opening the door for more creative ideas.


@afhacker

afhacker
26 Nov 2018, 05:55

Yes its possible and there are different ways to do it, one simple approach will be looking back to history, suppose your bot made 10 trades and after closing the 10th trade your bot will analyze the trades result and it will find out the profitable trades characteristics like in what time of day your bot performed will, in what kind of market (trending or consolidation) it did well,... etc

After that you will use those extra filters on 10 future trades, then you analyze the result of applying those new filters in your 10 future trades if those filters helped you keep applying those filters if didn't you get rid of those filters.

And you keep repeating the above step after every 10 trades, for analyzing you can use the past x trades or all of the past trades.

That was a simple way to do optimization inside your bot but there are other ways like using ML regression models and keep refitting the model after x number of days or weeks.


@afhacker

afhacker
14 Nov 2018, 13:38

Try this: https://drive.google.com/open?id=1jteEpLY-FteEssKfzppcNLPgysEEw5QM

using cAlgo.API;
using cAlgo.API.Alert;
using cAlgo.API.Internals;

namespace cAlgo
{
    [Indicator(IsOverlay = true, TimeZone = TimeZones.UTC, AccessRights = AccessRights.FullAccess)]
    public class BarColorChangeAlert : Indicator
    {
        #region Fields

        private int _lastAlertBarIndex;

        #endregion Fields

        #region Methods

        protected override void Initialize()
        {
            cAlgo.API.Alert.Types.Configuration.Tracer = Print;
        }

        public override void Calculate(int index)
        {
            if (MarketSeries.Close[index] > MarketSeries.Open[index] && MarketSeries.Close[index - 1] < MarketSeries.Open[index - 1])
            {
                TriggerAlert(index, TradeType.Buy);
            }
            else if (MarketSeries.Close[index] < MarketSeries.Open[index] && MarketSeries.Close[index - 1] > MarketSeries.Open[index - 1])
            {
                TriggerAlert(index, TradeType.Sell);
            }
        }

        private void TriggerAlert(int index, TradeType tradeType)
        {
            if (index == _lastAlertBarIndex || !IsLastBar)
            {
                return;
            }

            _lastAlertBarIndex = index;

            string comment = tradeType == TradeType.Buy ? "Bullish Bar" : "Bearish Bar";

            Notifications.ShowPopup(TimeFrame, Symbol, MarketSeries.Close[index], "Bar Color Change Alert", tradeType, comment, MarketSeries.OpenTime[index]);
        }

        #endregion Methods
    }
}

 

Whenever a bar color change happens it shows a popup alert.

You can enable sound, email and Telegram alert on pupop settings if you want to.


@afhacker

afhacker
24 Oct 2018, 07:34

using cAlgo.API;
using cAlgo.API.Internals;

namespace cAlgo
{
    [Indicator(IsOverlay = false, TimeZone = TimeZones.UTC, AccessRights = AccessRights.None, AutoRescale = true)]
    public class AdvancedVolume : Indicator
    {
        #region Fields

        private double _lastPriceValue;

        #endregion Fields

        #region Parameters

        [Parameter("Use Live Data", DefaultValue = false)]
        public bool UseLiveData { get; set; }

        [Parameter("Show in %", DefaultValue = true)]
        public bool ShowInPercentage { get; set; }

        #endregion Parameters

        #region Outputs

        [Output("Bullish Volume", Color = Colors.Lime, PlotType = PlotType.Histogram, Thickness = 5)]
        public IndicatorDataSeries BullishVolume { get; set; }

        [Output("Bearish Volume", Color = Colors.Red, PlotType = PlotType.Histogram, Thickness = 5)]
        public IndicatorDataSeries BearishVolume { get; set; }

        #endregion Outputs

        #region Methods

        protected override void Initialize()
        {
            _lastPriceValue = MarketSeries.Close.LastValue;
        }

        public override void Calculate(int index)
        {
            if (IsLastBar && UseLiveData)
            {
                if (MarketSeries.Close.LastValue > _lastPriceValue)
                {
                    double volume = double.IsNaN(BullishVolume[index]) ? 1 : BullishVolume[index] + 1;

                    BullishVolume[index] = ShowInPercentage ? volume / MarketSeries.TickVolume[index] : volume;
                }
                else if (MarketSeries.Close.LastValue < _lastPriceValue)
                {
                    double volume = double.IsNaN(BearishVolume[index]) ? -1 : BearishVolume[index] - 1;

                    BearishVolume[index] = ShowInPercentage ? volume / MarketSeries.TickVolume[index] : volume;
                }

                _lastPriceValue = MarketSeries.Close.LastValue;
            }
            else
            {
                double barRange = MarketSeries.High[index] - MarketSeries.Low[index];

                double percentageAboveBarClose = (MarketSeries.High[index] - MarketSeries.Close[index]) / barRange;
                double percentageBelowBarClose = -(MarketSeries.Close[index] - MarketSeries.Low[index]) / barRange;

                BullishVolume[index] = ShowInPercentage ? percentageBelowBarClose : MarketSeries.TickVolume[index] * percentageBelowBarClose;
                BearishVolume[index] = ShowInPercentage ? percentageAboveBarClose : MarketSeries.TickVolume[index] * percentageAboveBarClose;
            }
        }


        #endregion Methods
    }
}

 


@afhacker

afhacker
24 Oct 2018, 07:33

using cAlgo.API;
using cAlgo.API.Internals;

namespace cAlgo
{
    [Indicator(IsOverlay = false, TimeZone = TimeZones.UTC, AccessRights = AccessRights.None, AutoRescale = true)]
    public class AdvancedVolume : Indicator
    {
        #region Fields

        private double _lastPriceValue;

        #endregion Fields

        #region Parameters

        [Parameter("Use Live Data", DefaultValue = false)]
        public bool UseLiveData { get; set; }

        [Parameter("Show in %", DefaultValue = true)]
        public bool ShowInPercentage { get; set; }

        #endregion Parameters

        #region Outputs

        [Output("Bullish Volume", Color = Colors.Lime, PlotType = PlotType.Histogram, Thickness = 5)]
        public IndicatorDataSeries BullishVolume { get; set; }

        [Output("Bearish Volume", Color = Colors.Red, PlotType = PlotType.Histogram, Thickness = 5)]
        public IndicatorDataSeries BearishVolume { get; set; }

        #endregion Outputs

        #region Methods

        protected override void Initialize()
        {
            _lastPriceValue = MarketSeries.Close.LastValue;
        }

        public override void Calculate(int index)
        {
            if (IsLastBar && UseLiveData)
            {
                if (MarketSeries.Close.LastValue > _lastPriceValue)
                {
                    double volume = double.IsNaN(BullishVolume[index]) ? 1 : BullishVolume[index] + 1;

                    BullishVolume[index] = ShowInPercentage ? volume / MarketSeries.TickVolume[index] : volume;
                }
                else if (MarketSeries.Close.LastValue < _lastPriceValue)
                {
                    double volume = double.IsNaN(BearishVolume[index]) ? -1 : BearishVolume[index] - 1;

                    BearishVolume[index] = ShowInPercentage ? volume / MarketSeries.TickVolume[index] : volume;
                }

                _lastPriceValue = MarketSeries.Close.LastValue;
            }
            else
            {
                double barRange = MarketSeries.High[index] - MarketSeries.Low[index];

                double percentageAboveBarClose = (MarketSeries.High[index] - MarketSeries.Close[index]) / barRange;
                double percentageBelowBarClose = -(MarketSeries.Close[index] - MarketSeries.Low[index]) / barRange;

                BullishVolume[index] = ShowInPercentage ? percentageBelowBarClose : MarketSeries.TickVolume[index] * percentageBelowBarClose;
                BearishVolume[index] = ShowInPercentage ? percentageAboveBarClose : MarketSeries.TickVolume[index] * percentageAboveBarClose;
            }
        }


        #endregion Methods
    }
}

@afhacker