6
\$\begingroup\$

If you look at ImportAndPreload method, it looks bad because:

MovingAverageConvergenceDivergence, StochasticRSI, RelativeStrengthIndex and ExponentialMovingAverage. They are all indicators, they all inherit from IndicatorBase. What would happen if I wanted to pass a few more indicators? There would be 10 indicators passed by arguments, which is annoying and it's against S.O.L.I.D. Is there a way to make that method like a variadic function in C++ and pass infinite amount of indicators. By the way, as you can see below, IndicatorBase is generic, which prevents params IndicatorBase<???>[] args.

For example: ImportAndPreload(symbol, interval, macd, stoch, rsi, ema, ...)

I'm also open for other ideas.

MovingAverageConvergenceDivergence macd = new MovingAverageConvergenceDivergence(12, 26, 100);
StochasticRSI stoch = new StochasticRSI(14, 9, 3, 3);
RelativeStrengthIndex rsi = new RelativeStrengthIndex(20);
ExponentialMovingAverage ema = new ExponentialMovingAverage(20);

ImportAndPreload(symbol, interval, macd, stoch, rsi, ema);

// Abstract base class for all indicators:
public abstract class IndicatorBase<TInput, TOutput>
    where TInput: struct
    where TOutput : struct
{
    public abstract TOutput ComputeNextValue(TInput input);
    public abstract void Reset();
}

private void ImportAndPreload(string symbol, KlineInterval interval, MovingAverageConvergenceDivergence macd, StochasticRSI stoch, RelativeStrengthIndex rsi, ExponentialMovingAverage ema)
{
    // Import candles
    List<BinanceKline> candles = Client.GetKlines(symbol, interval, limit: 1000).Data.SkipLast(1).ToList();

    for (int i = 0; i < 3; i++)
    {
        List<BinanceKline> moreCandles2 = Client.GetKlines(symbol, interval, startTime: null, endTime: candles.First().OpenTime, limit: 1000).Data.SkipLast(1).ToList();
        candles.InsertRange(0, moreCandles2);
    }

    // Workaround in case it gets more bigger time to import candles
    List<BinanceKline> moreCandles = Client.GetKlines(symbol, interval, limit: 2).Data.SkipLast(1).ToList();
    candles.AddRange(moreCandles.ExceptUsingJsonCompare(candles));

    // Preload indicators
    for (int i = 0; i < candles.Count; i++)
    {
        macd.ComputeNextValue(candles[i].Close);
        stoch.ComputeNextValue(candles[i].Close);
        rsi.ComputeNextValue(candles[i].Close);
        ema.ComputeNextValue(candles[i].Close);
    }
}
```
\$\endgroup\$
8
\$\begingroup\$

This will depend on whether or not all of the indicators you're interested in passing have the same generic type arguments. If they do then iSR5's solution is the simplest.

If not then you'll need a more complicated solution. On one hand I notice that you're not using any of the outputs of ComputeNextValue; you're only providing inputs to the indicators. Then one possible solution would be to define an interface with the void counterpart of ComputeNextValue which only takes an input and produces no output:

public abstract class IndicatorBase<TInput, TOutput> : IIndicatorBase<TInput>
    where TInput : struct
    where TOutput : struct
{
    public abstract TOutput ComputeNextValue(TInput input);
    public abstract void Reset();

    void IIndicatorBase<TInput>.ComputeNextValue(TInput input) =>
         ((IndicatorBase<TInput, TOutput>)this).ComputeNextValue(input);
}

public interface IIndicatorBase<TInput>
    where TInput: struct
{
    void ComputeNextValue(TInput input);
}

Then ImportAndPreload can become this: (I'm not sure what BinanceKline is, but the input type you're supplying to each indicator is the type of BinanceKline.Close, so that'd be the generic type argument for IIndicatorBase)

private void ImportAndPreload(string symbol, KlineInterval interval, params IIndicatorBase<TypeOfBinanceKlineDotClose>[] indicators)
{
    ...

    // Preload indicators
    for (int i = 0; i < candles.Count; i++)
    {
        foreach (var indicator in indicators)
        {
            indicator.ComputeNextValue(candles[i].Close);
        }
    }
}

...

ImportAndPreload(symbol, interval, macd, stoch, rsi, ema);

This will support any indicator whose input is the type of BinanceKline.Close.

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ Awesome! Thanks a lot, that's what I wanted! \$\endgroup\$ – nop Aug 6 at 17:41
4
\$\begingroup\$

The simplest solution would be using params.

private void ImportAndPreload<TInput, TOutput>(params IndicatorBase<TInput, TOutput>[] foos) { ... }

if you want something more managed, you can use IEnumerable<IndicatorBase<TInput, TOutput>> which would makes uses any type of collections. I would prefer using IEnumereble over params in this case, because you would store all indicators into a collection, which would be easier to handle, move (if needed) or even modify.

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ Isn't that only if you have same TInput and TOutput for all indicators you will pass to params? For example: public class MovingAverageConvergenceDivergence : IndicatorBase<decimal, (decimal Signal, decimal MACD, decimal Histogram)> public class ExponentialMovingAverage : IndicatorBase<decimal, decimal>. They have different TInput/TOutput. \$\endgroup\$ – nop Aug 6 at 17:09
  • 1
    \$\begingroup\$ @nop since its TInput and TOutput are different, then it won't be possible to use this approach. Use Jeff 's method in this case. \$\endgroup\$ – iSR5 Aug 6 at 17:43
  • \$\begingroup\$ yup, thanks for your answer too! \$\endgroup\$ – nop Aug 6 at 19:16

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.