Cannot access new value from MACD Divergence indicator from bot
Cannot access new value from MACD Divergence indicator from bot
07 Mar 2019, 21:46
Hi, I wanted to create a simple bot that's using only this indicator:
[https://ctrader.com/algos/indicators/show/1807]
However, when I backtest the bot, it seems that while indicator itself does add a new entry to its Data property (which is public), but right after it has added a new entry and the bot reaches the OnTick(), the indicator's instance's (initialized at OnStart from the bot) Data does not contain the newly added entry.
I have removed the IsBacktesting() checks from the indicator.
I've debugged the bot and indicator via attaching to ctrader from Visual Studio: here we can see the indicator's code, as it adds a new entry to 'Data':
Right after this, I've placed a breakpoint to the bot's OnTick, where I found that the bot cannot see the 5th item from the Data.
Interestingly enough, it seems this indicator is not the same instance as the indicator I've stopped on a breakpoint.
I have tried raising an event when the indicator adds a new entry to Data and have the bot subscribe to that, but the subscriber method of the bot was never reached then (I'm not sure that cAlgo even supports that anyway).
The source code of the bot and indicator: https://www.justbeamit.com/xbg3v
Thanks,
Dan
Replies
PanagiotisCharalampous
08 Mar 2019, 09:32
Hi daniel.agg,
Thanks for posting in our forum. I downloaded the cBot but there is no source code. Can you please send it with source code? Also you can just post the code here, no need to send files.
Best Regards,
Panagiotis
@PanagiotisCharalampous
daniel.agg
08 Mar 2019, 10:07
That's strange, the .cs files should have been in the corresponding subfolders.
Anyway, here's the bot:
using System; using System.Linq; using cAlgo.API; using cAlgo.API.Indicators; using cAlgo.API.Internals; using cAlgo.Indicators; namespace cAlgo.Robots { [Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.Internet)] public class MACDTestBot : Robot { #region indicator parameters [Parameter("Divergence Ledge", DefaultValue = 3, MinValue = 1, MaxValue = 10, Step = 1)] public int Divergence_Ledge { get; set; } [Parameter("Divergence Extremums", DefaultValue = false)] public bool Divergence_Extremums { get; set; } [Parameter("Divergence Min Length", DefaultValue = 6, MinValue = 4, MaxValue = 10, Step = 1)] public int Divergence_Min_Length { get; set; } [Parameter("Last Points", DefaultValue = 3, MinValue = 1, MaxValue = 10, Step = 1)] public int Last_Points { get; set; } [Parameter("MACD: Short Cycle", DefaultValue = 12)] public int MACD_Short_Cycle { get; set; } [Parameter("MACD: Long Cycle", DefaultValue = 26)] public int MACD_Long_Cycle { get; set; } [Parameter("MACD: Signal Period", DefaultValue = 9)] public int MACD_Signal_Period { get; set; } [Parameter("MACD: Reverse", DefaultValue = false)] public bool MACD_Reverse { get; set; } #endregion private MACDDivergence indicator; private int indicatorDataCount; protected override void OnStart() { indicator = Indicators.GetIndicator<MACDDivergence>( Divergence_Ledge, Divergence_Extremums, Divergence_Min_Length, Last_Points, MACD_Short_Cycle, MACD_Long_Cycle, MACD_Signal_Period, MACD_Reverse); indicatorDataCount = indicator.Data.Count(); } protected override void OnTick() { if(indicatorDataCount != indicator.Data.Count()) { // do something } } protected override void OnStop() { // Put your deinitialization logic here } } }
And here's the indicator:
using System; using cAlgo.API; using cAlgo.API.Internals; using cAlgo.API.Indicators; using cAlgo.Indicators; //using System.Linq; using System.Collections.Generic; namespace cAlgo.Indicators { [Indicator(TimeZone = TimeZones.UTC, IsOverlay = false, AutoRescale = true, ScalePrecision = 7, AccessRights = AccessRights.None)] public class MACDDivergence : Indicator { [Parameter("Divergence Ledge", DefaultValue = 3, MinValue = 1, MaxValue = 10, Step = 1)] public int Divergence_Ledge { get; set; } [Parameter("Divergence Extremums", DefaultValue = false)] public bool Divergence_Extremums { get; set; } [Parameter("Divergence Min Length", DefaultValue = 6, MinValue = 4, MaxValue = 10, Step = 1)] public int Divergence_Min_Length { get; set; } [Parameter("Last Points", DefaultValue = 3, MinValue = 1, MaxValue = 10, Step = 1)] public int Last_Points { get; set; } [Parameter("MACD: Short Cycle", DefaultValue = 12)] public int MACD_Short_Cycle { get; set; } [Parameter("MACD: Long Cycle", DefaultValue = 26)] public int MACD_Long_Cycle { get; set; } [Parameter("MACD: Signal Period", DefaultValue = 9)] public int MACD_Signal_Period { get; set; } [Parameter("MACD: Reverse", DefaultValue = false)] public bool MACD_Reverse { get; set; } [Output("Main", PlotType = PlotType.Histogram, Color = Colors.DeepSkyBlue)] public IndicatorDataSeries Result { get; set; } /// Indicator private MacdHistogram I_MACD; /// Properties string Chart_Object_Title = "MACD Divergence"; private DivergenceType HIGH = DivergenceType.High; private DivergenceType LOW = DivergenceType.Low; private ExtremumActionType for_Update = ExtremumActionType.Update; private ExtremumActionType for_New = ExtremumActionType.New; /// Data to use in Bot private List<Label> High_Extremums = new List<Label>(); private List<Label> Low_Extremums = new List<Label>(); public List<Divergence> Data = new List<Divergence>(); public List<TrendType> Trend = new List<TrendType>(); /// Models public struct Divergence { public DivergenceType T; public Label A; public Label B; public Divergence(DivergenceType _Type = DivergenceType.None, Label Label_A = new Label(), Label Label_B = new Label()) { T = _Type; A = Label_A; B = Label_B; } public string Text { get { return string.Format("- A - {0} - B - {1}", A.Text, B.Text); } } public Colors Color { get { switch (T) { case DivergenceType.High: return Colors.SpringGreen; break; case DivergenceType.Low: return Colors.DeepPink; break; default: return Colors.Yellow; break; } } } } public struct Label { public Point MACD; public Point MS; public Label(Point M1 = new Point(), Point M2 = new Point()) { MACD = M1; MS = M2; } public string Text { get { return string.Format("MACD = {0} MS = {1}", MACD.Text, MS.Text); } } } public struct Point { public int Index; public double Value; public Point(int I = 0, double V = 0) { Index = I; Value = V; } public string Text { get { return string.Format("{0} ({1})", Value.ToString("N6"), Index); } } } public enum ExtremumActionType { Update, New } public enum DivergenceType { High, Low, None } public enum TrendType { Up, Down, None } /// TODO: Init protected override void Initialize() { I_MACD = Indicators.MacdHistogram(MACD_Short_Cycle, MACD_Long_Cycle, MACD_Signal_Period); Print("MACD Divergence Calculator is initialized. Results = {0} Data = {1} Data_Trend = {2}", Result.Count, Data.Count, Trend.Count); } /// TODO: Indicator public override void Calculate(int index) { /// MACD Calc Result[index] = MACD_Reverse ? I_MACD.Histogram[index] - I_MACD.Signal[index] : I_MACD.Signal[index] - I_MACD.Histogram[index]; /// MACD Divergence if (index <= Divergence_Ledge) return; /// TODO: High Divergence Point MACD_Max = Max_MACD(From: index - 1, For_Last_Bars: Divergence_Ledge); Point MS_Max = Max_MS(From: index - 1, For_Last_Bars: Divergence_Ledge); Label Max = new Label(MACD_Max, MS_Max); if (High_Extremums.Count == 0) { if (Is_Max(L: Max, At: index)) Update_Data(Stage: "1 Step: FIRST", _Type: HIGH, New_Extremum: Max, At: index, Action: for_New); } else { Label Last = High_Extremums[High_Extremums.Count - 1]; if (Math.Max(Max.MACD.Index, Max.MS.Index) - Last.MACD.Index <= Divergence_Min_Length) { //if (Last.MACD.Value < MACD(index) && Last.MACD.Index == index - 1 && Max.MACD.Index - Last.MACD.Index <= Divergence_Ledge) //{ // Max.MACD.Value = MACD(index); // Max.MACD.Index = index; // Update_Data(Stage: "3 Step: update NOW", _Type: HIGH, New_Extremum: Max, At: index, Action: for_Update); //} if (Allow_To_Update_High(L: Last, New: Max, At: index)) Update_Data(Stage: "3 Step: update LAST", _Type: HIGH, New_Extremum: Max, At: index, Action: for_Update); } else if (Is_Next_High_Wave(A: Last, B: Max, At: index)) Update_Data(Stage: "4 Step: NEW", _Type: HIGH, New_Extremum: Max, At: index, Action: for_New); } /// TODO: Low Divergence Point MACD_Min = Min_MACD(From: index - 1, For_Last_Bars: Divergence_Ledge); Point MS_Min = Min_MS(From: index - 1, For_Last_Bars: Divergence_Ledge); Label Min = new Label(MACD_Min, MS_Min); if (Low_Extremums.Count == 0) { if (Is_Min(L: Min, At: index)) Update_Data(Stage: "1 Step: FIRST", _Type: LOW, New_Extremum: Min, At: index, Action: for_New); } else { Label Last = Low_Extremums[Low_Extremums.Count - 1]; if (Math.Max(Min.MACD.Index, Min.MS.Index) - Last.MACD.Index <= Divergence_Min_Length) { //if (Last.MACD.Value < MACD(index) && Last.MACD.Index == index - 1 && Min.MACD.Index - Last.MACD.Index <= Divergence_Ledge) //{ // Min.MACD.Value = MACD(index); // Min.MACD.Index = index; // Update_Data(Stage: "3 Step: update NOW", _Type: LOW, New_Extremum: Min, At: index, Action: for_Update); //} if (Allow_To_Update_Low(L: Last, New: Min, At: index)) Update_Data(Stage: "3 Step: update LAST", _Type: LOW, New_Extremum: Min, At: index, Action: for_Update); } else if (Is_Next_Low_Wave(A: Last, B: Min, At: index)) Update_Data(Stage: "4 Step: NEW", _Type: LOW, New_Extremum: Min, At: index, Action: for_New); } /// Update Trend List if (Trend.Count == 0) while (Trend.Count < index) Trend.Add(TrendType.None); if (Data.Count > 0) { Divergence D = Data[Data.Count - 1]; bool T_High = D.T == DivergenceType.High; bool T_Low = D.T == DivergenceType.Low; TrendType T = T_High ? TrendType.Down : (T_Low ? TrendType.Up : TrendType.None); if (Trend.Count < index) Trend.Add(T); else if (Trend.Count - 1 == index) Trend[index] = T; } else { TrendType T = TrendType.None; if (Trend.Count < index) Trend.Add(T); else if (Trend.Count - 1 == index) Trend[index] = T; } } /// Methods /// Info private void Info(string Stage, Label Last, Label Extremum, int At, DivergenceType _Type) { //if (IsBacktesting) // return; Print("{0} - {1} - {2} - {3} - {4} - Last {5} - Extremum {6}", At, MarketSeries.OpenTime[At].Date.ToShortDateString(), Data.Count, _Type.ToString(), Stage, Last.Text, Extremum.Text); } /// Update list Data_Divergence private void Update_Data(string Stage, DivergenceType _Type, Label New_Extremum, int At, ExtremumActionType Action) { if (_Type == HIGH ? High_Extremums.Count > 0 : Low_Extremums.Count > 0) { Label Previous_Extremum = _Type == HIGH ? High_Extremums[High_Extremums.Count - 1] : Low_Extremums[Low_Extremums.Count - 1]; Info(Stage: Stage, Last: Previous_Extremum, Extremum: New_Extremum, At: At, _Type: _Type); if (Action == for_New) if (_Type == HIGH) High_Extremums.Add(New_Extremum); else if (_Type == LOW) Low_Extremums.Add(New_Extremum); Label Last_Extremum = _Type == HIGH ? High_Extremums[High_Extremums.Count - 1] : Low_Extremums[Low_Extremums.Count - 1]; int N_Extremums = _Type == HIGH ? High_Extremums.Count - 1 : Low_Extremums.Count - 1; for (int Index = Math.Max(N_Extremums - Last_Points, 0); Index < N_Extremums; Index++) { Label Current_Extremum = _Type == HIGH ? High_Extremums[Index] : Low_Extremums[Index]; Divergence Last = new Divergence(_Type: _Type, Label_A: Current_Extremum, Label_B: Last_Extremum); Divergence New = new Divergence(_Type: _Type, Label_A: Current_Extremum, Label_B: New_Extremum); if (No_Obstacles(D: New) && Is_Divergence(D: New)) { if (Data.Contains(Last)) { int D_Index = Data.IndexOf(Last); Data[D_Index] = New; Info(Stage: "7 Step: update DATA", Last: Current_Extremum, Extremum: New_Extremum, At: At, _Type: _Type); Draw(D: New, Prefix: D_Index.ToString()); } else if (!Data.Exists(D => D.B.MACD.Index == New.B.MACD.Index)) { Data.Add(New); Info(Stage: "7 Step: new DATA", Last: Current_Extremum, Extremum: New_Extremum, At: At, _Type: _Type); Draw(D: New, Prefix: (Data.Count - 1).ToString()); } } else if (Data.Contains(Last)) { Info(Stage: "7 Step: remove DATA", Last: Current_Extremum, Extremum: New_Extremum, At: At, _Type: _Type); Remove(D: Last, Prefix: Data.IndexOf(Last).ToString()); Data.Remove(Last); } } } else { if (Action == for_New) if (_Type == HIGH) High_Extremums.Add(New_Extremum); else if (_Type == LOW) Low_Extremums.Add(New_Extremum); } if (Action == for_Update) if (_Type == HIGH) High_Extremums[High_Extremums.Count - 1] = New_Extremum; else if (_Type == LOW) Low_Extremums[Low_Extremums.Count - 1] = New_Extremum; } /// Draw and remove chart objects private void Draw(Divergence D, string Prefix) { //if (IsBacktesting) // return; string Name = Prefix + Chart_Object_Title + D.T.ToString() + D.A.MACD.Index.ToString(); int Index_1 = D.A.MACD.Index; int Index_2 = D.B.MACD.Index; double Value_1 = D.A.MACD.Value; double Value_2 = D.B.MACD.Value; Colors Color = D.Color; ChartObjects.DrawLine(Name, Index_1, Value_1, Index_2, Value_2, Color); ChartObjects.DrawText(Name + "A", "•", Index_1, Value_1, VerticalAlignment.Center, HorizontalAlignment.Center, Color); ChartObjects.DrawText(Name + "B", "•", Index_2, Value_2, VerticalAlignment.Center, HorizontalAlignment.Center, Color); Info(Stage: "Draw Divergence:", Last: D.A, Extremum: D.B, At: D.A.MACD.Index, _Type: D.T); } private void Remove(Divergence D, string Prefix) { //if (IsBacktesting) // return; string Name = Prefix + Chart_Object_Title + D.T.ToString() + D.A.MACD.Index.ToString(); ChartObjects.RemoveObject(Name); ChartObjects.RemoveObject(Name + "A"); ChartObjects.RemoveObject(Name + "B"); } /// Helpers private double MACD(int Index) { return Result[Index]; } private bool Is_Divergence(Divergence D) { bool T1 = D.A.MACD.Value >= D.B.MACD.Value && D.A.MS.Value < D.B.MS.Value; bool T2 = D.A.MACD.Value <= D.B.MACD.Value && D.A.MS.Value > D.B.MS.Value; bool T_P = D.T == HIGH ? D.A.MACD.Value > 0 : D.A.MACD.Value < 0; return (T1 || T2) && T_P; } /// Is Max private bool Is_Max(Label L, int At) { return (MACD_Is_Max(P: L.MACD, At: At) && MS_Is_Max(P: L.MS, At: At)); } private bool MACD_Is_Max(Point P, int At) { return P.Value > 0 && P.Value > MACD(At) && P.Value > MACD(P.Index - 1); } private bool MS_Is_Max(Point P, int At) { return P.Value > MS_High(At) && P.Value > MS_High(P.Index - 1); } /// Is Min private bool Is_Min(Label L, int At) { return (MACD_Is_Min(P: L.MACD, At: At) && MS_Is_Min(P: L.MS, At: At)); } private bool MACD_Is_Min(Point P, int At) { return P.Value < 0 && P.Value < MACD(At) && P.Value < MACD(P.Index - 1); } private bool MS_Is_Min(Point P, int At) { return P.Value < MS_High(At) && P.Value < MS_High(P.Index - 1); } /// Allow to update private bool Allow_To_Update_High(Label L, Label New, int At) { if (New.MACD.Index == At) return false; if ((L.MACD.Value < New.MACD.Value && MACD_Is_Max(P: New.MACD, At: At)) || (L.MS.Value < New.MS.Value && MS_Is_Max(P: New.MS, At: At))) return true; return false; } private bool Allow_To_Update_Low(Label L, Label New, int At) { if (New.MACD.Index == At) return false; if ((L.MACD.Value > New.MACD.Value && MACD_Is_Min(P: New.MACD, At: At)) || (L.MS.Value > New.MS.Value && MS_Is_Min(P: New.MS, At: At))) return true; return false; } /// Next Wave private bool Is_Next_High_Wave(Label A, Label B, int At) { bool Was_Lower = MACD_Was_Lower(From: A.MACD.Index, To: B.MACD.Index); bool B_MACD_Max = MACD_Is_Max(P: B.MACD, At: At); bool B_MS_Max = MS_Is_Max(P: B.MS, At: At); bool T1 = A.MACD.Value > 0 && B.MACD.Value > 0 && Was_Lower && B_MACD_Max && B_MS_Max; bool T2 = A.MACD.Value < 0 && B.MACD.Value > 0 && Was_Lower && B_MACD_Max && B_MS_Max; bool T3 = A.MACD.Value > 0 && B.MACD.Value < 0 && A.MS.Value < B.MS.Value && B_MS_Max && MACD(Index: B.MS.Index) < 0; return (T1 || T2 || T3); } private bool Is_Next_Low_Wave(Label A, Label B, int At) { bool Was_Higher = MACD_Was_Higher(From: A.MACD.Index, To: B.MACD.Index); bool B_MACD_Min = MACD_Is_Min(P: B.MACD, At: At); bool B_MS_Min = MS_Is_Min(P: B.MS, At: At); bool T1 = A.MACD.Value < 0 && B.MACD.Value < 0 && Was_Higher && B_MACD_Min && B_MS_Min; bool T2 = A.MACD.Value > 0 && B.MACD.Value < 0 && Was_Higher && B_MACD_Min && B_MS_Min; bool T3 = A.MACD.Value < 0 && B.MACD.Value > 0 && A.MS.Value > B.MS.Value && B_MS_Min && MACD(Index: B.MS.Index) > 0; return (T1 || T2 || T3); } /// Нет препятсвий private bool No_Obstacles(Divergence D) { bool T_MACD = D.T == HIGH ? MACD_Was_Not_Higher(D: D) : MACD_Was_Not_Lower(D: D); bool T_MS = D.T == HIGH ? MS_Was_Not_Higher(D: D) : MS_Was_Not_Lower(D: D); //Info(Stage: string.Format("No O: MACD = {0} MS = {1}", T_MACD, T_MS), D: D, At: D.B.MACD.Index); return (T_MACD && T_MS); } /// MS Not higher or lower than private bool MACD_Was_Not_Higher(Divergence D) { return MACD_Was_Not_Higher(From: D.A.MACD.Index, To: D.B.MACD.Index, Value: Math.Max(D.A.MACD.Value, D.B.MACD.Value)); } private bool MACD_Was_Not_Lower(Divergence D) { return MACD_Was_Not_Lower(From: D.A.MACD.Index, To: D.B.MACD.Index, Value: Math.Min(D.A.MACD.Value, D.B.MACD.Value)); } private bool MACD_Was_Not_Higher(int From, int To, double Value) { if (From + 1 > To) return false; for (int Index = From + 1; Index < To; Index++) if (MACD(Index) > Value) return false; return true; } private bool MACD_Was_Not_Lower(int From, int To, double Value) { if (From + 1 > To) return false; for (int Index = From + 1; Index < To; Index++) if (MACD(Index) < Value) return false; return true; } /// MACD: was lower or higher 2 points private bool MACD_Was_Lower_Zero(int From, int To) { if (From > To) return false; for (int Index = From + 1; Index < To; Index++) if (MACD(Index) < 0) return true; return false; } private bool MACD_Was_Lower(int From, int To) { if (From + 1 > To) return false; double From_Value = MACD(From); double To_Value = MACD(To); for (int Index = From + 1; Index < To; Index++) if (MACD(Index) < From_Value && MACD(Index) < To_Value) return true; return false; } /// MACD: was higher or lower 0 private bool MACD_Was_Higher_Zero(int From, int To) { if (From > To) return false; for (int Index = From + 1; Index < To; Index++) if (MACD(Index) > 0) return true; return false; } private bool MACD_Was_Higher(int From, int To) { if (From + 1 > To) return false; double From_Value = MACD(From); double To_Value = MACD(To); for (int Index = From + 1; Index < To; Index++) if (MACD(Index) > From_Value && MACD(Index) > To_Value) return true; return false; } // MS: high & low private double MS_High(int Index) { return Divergence_Extremums ? MarketSeries.High[Index] : MarketSeries.Close[Index]; } private double MS_Low(int Index) { return Divergence_Extremums ? MarketSeries.Low[Index] : MarketSeries.Close[Index]; } /// MS Not higher or lower than private bool MS_Was_Not_Higher(Divergence D) { return MS_Was_Not_Higher(From: D.A.MS.Index, To: D.B.MS.Index, Value: Math.Max(D.A.MS.Value, D.B.MS.Value)); } private bool MS_Was_Not_Lower(Divergence D) { return MS_Was_Not_Lower(From: D.A.MS.Index, To: D.B.MS.Index, Value: Math.Min(D.A.MS.Value, D.B.MS.Value)); } private bool MS_Was_Not_Higher(int From, int To, double Value) { if (From + 1 > To) return false; for (int Index = From + 1; Index < To; Index++) if (MS_High(Index) > Value) return false; return true; } private bool MS_Was_Not_Lower(int From, int To, double Value) { if (From + 1 > To) return false; for (int Index = From + 1; Index < To; Index++) if (MS_Low(Index) < Value) return false; return true; } /// MS: max & min points private Point Max_MS(int From, int For_Last_Bars) { int Index = From; double Value = MS_High(Index); for (int Bar = 1; Bar <= For_Last_Bars; Bar++) { int New_Index = From - Bar; double New_Value = MS_High(New_Index); if (New_Value > Value) { Value = New_Value; Index = New_Index; } } return new Point(Index, Value); } private Point Min_MS(int From, int For_Last_Bars) { int Index = From; double Value = MS_Low(Index); for (int Bar = 1; Bar <= For_Last_Bars; Bar++) { int New_Index = From - Bar; double New_Value = MS_Low(New_Index); if (New_Value < Value) { Value = New_Value; Index = New_Index; } } return new Point(Index, Value); } /// MACD: max & min points private Point Max_MACD(int From, int For_Last_Bars) { int Index = From; double Value = MACD(Index); for (int Bar = 1; Bar <= For_Last_Bars; Bar++) { int New_Index = From - Bar; double New_Value = MACD(New_Index); if (New_Value < 0) return new Point(Index, Value); if (New_Value > Value) { Value = New_Value; Index = New_Index; } } return new Point(Index, Value); } private Point Min_MACD(int From, int For_Last_Bars) { int Index = From; double Value = MACD(Index); for (int Bar = 1; Bar <= For_Last_Bars; Bar++) { int New_Index = From - Bar; double New_Value = MACD(New_Index); if (New_Value > 0) return new Point(Index, Value); if (New_Value < Value) { Value = New_Value; Index = New_Index; } } return new Point(Index, Value); } } }
@daniel.agg
gyalusdavid
11 Mar 2019, 10:58
RE:
Panagiotis Charalampous said:
Hi daniel.agg,
Thanks for posting in our forum. I downloaded the cBot but there is no source code. Can you please send it with source code? Also you can just post the code here, no need to send files.
Best Regards,
Panagiotis
Dear Panagiotis,
I got almost the same problem, which is quite important, could you please investigate as soon as possible?
Thank you very much, Panagiotis!
Best Regards,
David
@gyalusdavid
PanagiotisCharalampous
12 Mar 2019, 12:49
Hi guys,
I tried to reproduce the problem but could not. If you can isolate the issue into a smaller indicator with more specific reproduction steps I can take another look.
Best Regards,
Panagiotis
@PanagiotisCharalampous
daniel.agg
26 Mar 2019, 18:09
Hi, the problem is solved (using a different indicator). Can you please delete this post?
Thanks in advance,
Dan
@daniel.agg
daniel.agg
07 Mar 2019, 21:51
Sorry, seems like the link to the source code is broken. Reuploaded:
https://drive.google.com/file/d/1CuMe7hKGLAK3XJW0HDQ9RucmDDiEps0S/view?usp=sharing
@daniel.agg