Description
Manual Visual Back testing with SL and TP Lines
Modify SL and TP using lines in the Chart.
Add Comments for more features or issues.
REQUIRED: In Chart, disable Positions in Viewing Options, as shown below:
Right Click on Chart > Goto Viewing Options > UNSELECT Positions
Run Bot in Backtesting with Visual Mode.
Click on Buy/Sell to open position.
Click on Close All to Close all open positions.
Click [x] icon on Trade Line to close that position.
Keyboard Shortcuts: b -- Buy, s -- Sell, c -- Close All
Set "Enable Keyboard Shortcuts" to false, to disable Keyboard shortcuts.
On creating a position, TP and SL lines will be created.
These can be moved, to update TP and SL
Use 1,2,3,4,5,6 buttons to move Trading Panel Location
Future TODO: Edit Position
using System;
using System.Linq;
using System.Collections.Generic;
using cAlgo.API;
using cAlgo.API.Internals;
using cAlgo.API.Collections;
namespace cAlgo.Robots
{
[Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
public class ManualTesterWithSLTPLines : Robot
{
[Parameter("Vertical Position", Group = "Panel alignment", DefaultValue = VerticalAlignment.Bottom)]
public VerticalAlignment PanelVerticalAlignment { get; set; }
[Parameter("Horizontal Position", Group = "Panel alignment", DefaultValue = HorizontalAlignment.Right)]
public HorizontalAlignment PanelHorizontalAlignment { get; set; }
[Parameter("Default Lots", Group = "Default trade parameters", DefaultValue = 0.01)]
public double DefaultLots { get; set; }
[Parameter("Default Take Profit (pips)", Group = "Default trade parameters", DefaultValue = 20, MinValue = 1)]
public double DefaultTakeProfitPips { get; set; }
[Parameter("Default Stop Loss (pips)", Group = "Default trade parameters", DefaultValue = 20, MinValue = 1)]
public double DefaultStopLossPips { get; set; }
[Parameter("Enable Keyboard Shortcuts", Group = "Default trade parameters", DefaultValue = true)]
public bool EnableKBShortcuts { get; set; }
List<PositionManager> PositionObjects;
Border TradingPanelBorder;
Canvas canvas;
protected override void OnStart()
{
Positions.Opened += Positions_Opened;
Positions.Closed += Positions_Closed;
Chart.ScrollChanged += Chart_ScrollChanged;
Chart.ObjectsUpdated += Chart_ObjectsUpdated;
Chart.SizeChanged += Chart_SizeChanged;
PositionObjects = new List<PositionManager>();
var tradingPanel = new TradingPanel(this, Symbol, DefaultLots, DefaultStopLossPips, DefaultTakeProfitPips, EnableKBShortcuts);
TradingPanelBorder = new Border
{
VerticalAlignment = PanelVerticalAlignment,
HorizontalAlignment = PanelHorizontalAlignment,
Style = Styles.CreatePanelBackgroundStyle(),
Margin = "20 40 20 20",
Width = 225,
Child = tradingPanel
};
// Panel Location Buttons
StackPanel btnsPanel = new StackPanel();
var grid = new Grid(2, 3);
grid.AddChild(TradePanelPositionBtn("1"), 0, 0);
grid.AddChild(TradePanelPositionBtn("2"), 0, 1);
grid.AddChild(TradePanelPositionBtn("3"), 0, 2);
grid.AddChild(TradePanelPositionBtn("4"), 1, 0);
grid.AddChild(TradePanelPositionBtn("5"), 1, 1);
grid.AddChild(TradePanelPositionBtn("6"), 1, 2);
btnsPanel.HorizontalAlignment = HorizontalAlignment.Center;
btnsPanel.VerticalAlignment = VerticalAlignment.Bottom;
btnsPanel.AddChild(grid);
// Canvas for Position panels
canvas = new Canvas
{
HorizontalAlignment = HorizontalAlignment.Left,
VerticalAlignment = VerticalAlignment.Top,
Width = 0,
Height = Chart.Height,
Opacity = 1
};
Chart.AddControl(TradingPanelBorder);
Chart.AddControl(btnsPanel);
Chart.AddControl(canvas);
}
private void Chart_SizeChanged(ChartSizeEventArgs obj)
{
canvas.Height = Chart.Height;
}
public Button TradePanelPositionBtn(string text)
{
Button btn = new Button
{
Style = Styles.CreateBuyButtonStyle(),
Text = text,
FontSize = 10,
Margin = "1 1 1 1"
};
btn.Click += PositionBtn_Click;
return btn;
}
private void PositionBtn_Click(ButtonClickEventArgs obj)
{
switch (obj.Button.Text)
{
case "1":
TradingPanelBorder.HorizontalAlignment = HorizontalAlignment.Left;
TradingPanelBorder.VerticalAlignment = VerticalAlignment.Top;
break;
case "2":
TradingPanelBorder.HorizontalAlignment = HorizontalAlignment.Center;
TradingPanelBorder.VerticalAlignment = VerticalAlignment.Top;
break;
case "3":
TradingPanelBorder.HorizontalAlignment = HorizontalAlignment.Right;
TradingPanelBorder.VerticalAlignment = VerticalAlignment.Top;
break;
case "4":
TradingPanelBorder.HorizontalAlignment = HorizontalAlignment.Left;
TradingPanelBorder.VerticalAlignment = VerticalAlignment.Bottom;
break;
case "5":
TradingPanelBorder.HorizontalAlignment = HorizontalAlignment.Center;
TradingPanelBorder.VerticalAlignment = VerticalAlignment.Bottom;
break;
case "6":
TradingPanelBorder.HorizontalAlignment = HorizontalAlignment.Right;
TradingPanelBorder.VerticalAlignment = VerticalAlignment.Bottom;
break;
}
}
protected override void OnTick()
{
PositionObjects.ForEach(PO => PO.UpdateText());
}
protected override void OnStop()
{
PositionObjects.ForEach(PO => PO.RemoveObjects());
}
private void Chart_ObjectsUpdated(ChartObjectsUpdatedEventArgs obj)
{
PositionObjects.ForEach(PO => PO.PositionModified());
}
private void Chart_ScrollChanged(ChartScrollEventArgs obj)
{
PositionObjects.ForEach(PO => PO.UpdateText());
}
private void Positions_Closed(PositionClosedEventArgs obj)
{
PositionManager item = PositionObjects.SingleOrDefault(PO => PO.PositionId == obj.Position.Id);
if (item != null)
{
item.RemoveObjects();
PositionObjects.Remove(item);
}
}
private void Positions_Opened(PositionOpenedEventArgs obj)
{
PositionManager PosMgr = new PositionManager(this, Symbol, obj.Position, canvas);
PositionObjects.Add(PosMgr);
}
}
public class PositionManager
{
public readonly int PositionId;
readonly Robot _robot;
readonly Symbol _symbol;
readonly Chart _chart;
readonly Position P;
Canvas _canvas;
TradePositionBlock TradeBlock, SLBlock, TPBlock;
ChartHorizontalLine TradeLine, SLLine, TPLine;
string _preEntryLine = "POS_ENTRY_LINE_";
string _preSLLine = "POS_SL_LINE_";
string _preTPLine = "POS_TP_LINE_";
public PositionManager(Robot robot, Symbol symbol, Position P, Canvas canvas)
{
_robot = robot;
_symbol = symbol;
_chart = robot.Chart;
_canvas = canvas;
this.P = P;
this.PositionId = P.Id;
CreateTradeLine();
CreateSLLine();
CreateTPLine();
}
public double GetWidth()
{
return TradeBlock == null ? 0 : TradeBlock.Width;
}
public void UpdateText()
{
if (TradeBlock != null)
TradeBlock.UpdateTextAndTop();
if (SLBlock != null)
SLBlock.UpdateTextAndTop();
if (TPBlock != null)
TPBlock.UpdateTextAndTop();
}
void CreateTradeLine()
{
Color posColor = P.TradeType == TradeType.Buy ? _chart.ColorSettings.BuyColor : _chart.ColorSettings.SellColor;
TradeLine = _chart.DrawHorizontalLine(_preEntryLine + P.Id, P.EntryPrice, posColor, 1, LineStyle.DotsRare);
TradeLine.Comment = "" + P.Id;
TradeBlock = new TradePositionBlock(_robot, _symbol, P, ObjectType.Trade);
_canvas.AddChild(TradeBlock);
}
void UpdateTradeLine()
{
if (TradeLine != null)
TradeLine.Y = P.EntryPrice;
}
void CreateSLLine()
{
if (P.StopLoss.HasValue)
{
SLLine = _chart.DrawHorizontalLine(_preSLLine + P.Id, P.StopLoss.Value, Color.Red, 1, LineStyle.DotsRare);
SLLine.IsInteractive = true;
SLLine.Comment = "" + P.Id;
SLBlock = new TradePositionBlock(_robot, _symbol, P, ObjectType.SL);
_canvas.AddChild(SLBlock);
}
}
void UpdateSLLine()
{
if (SLLine != null && P.StopLoss.HasValue)
SLLine.Y = P.StopLoss.Value;
}
void CreateTPLine()
{
if (P.TakeProfit.HasValue)
{
TPLine = _chart.DrawHorizontalLine(_preTPLine + P.Id, P.TakeProfit.Value, Color.Green, 1, LineStyle.DotsRare);
TPLine.IsInteractive = true;
TPLine.Comment = "" + P.Id;
TPBlock = new TradePositionBlock(_robot, _symbol, P, ObjectType.TP);
_canvas.AddChild(TPBlock);
}
}
void UpdateTPLine()
{
if (TPLine != null && P.TakeProfit.HasValue)
TPLine.Y = P.TakeProfit.Value;
}
public void RemoveObjects()
{
if (TradeLine != null)
_chart.RemoveObject(TradeLine.Name);
if (SLLine != null)
_chart.RemoveObject(SLLine.Name);
if (TPLine != null)
_chart.RemoveObject(TPLine.Name);
if (TradeBlock != null)
_canvas.RemoveChild(TradeBlock);
if (SLBlock != null)
_canvas.RemoveChild(SLBlock);
if (TPBlock != null)
_canvas.RemoveChild(TPBlock);
}
public void PositionModified()
{
TradeResult TR = null;
if (SLLine != null && P.StopLoss.HasValue && SLLine.Y != P.StopLoss.Value)
{
TR = P.ModifyStopLossPrice(SLLine.Y);
SLLine.Y = TR.IsSuccessful ? SLLine.Y : P.StopLoss.Value;
}
else if (TPLine != null && P.TakeProfit.HasValue && TPLine.Y != P.TakeProfit.Value)
{
TR = P.ModifyTakeProfitPrice(TPLine.Y);
TPLine.Y = TR.IsSuccessful ? TPLine.Y : P.TakeProfit.Value;
}
if (TR != null && !TR.IsSuccessful)
{
_robot.Print("Failed to Modify SL/TP: Error: " + TR.Error);
}
UpdateTradeLine();
UpdateSLLine();
UpdateTPLine();
UpdateText();
}
}
public enum ObjectType
{
Trade,
SL,
TP
}
public class TradePositionBlock : CustomControl
{
TextBlock Text;
Button CloseTrade;
Button EditTrade;
ObjectType _type;
readonly Position _position;
private readonly Robot _robot;
private Chart _chart;
private readonly Symbol _symbol;
public TradePositionBlock(Robot robot, Symbol symbol, Position position, ObjectType type)
{
_robot = robot;
_symbol = symbol;
_position = position;
_chart = _robot.Chart;
_type = type;
switch (_type)
{
case ObjectType.Trade:
AddChild(GetTradeGrid());
break;
case ObjectType.SL:
Text = new TextBlock
{
Text = GetSLText(),
ForegroundColor = Color.White,
BackgroundColor = Color.FromHex("#F05824"),
Padding = "1 1 1 1",
Margin = "1 1 1 1",
Height = 15,
Opacity = 1
};
AddChild(Text);
break;
case ObjectType.TP:
Text = new TextBlock
{
Text = GetTPText(),
ForegroundColor = Color.White,
BackgroundColor = Color.FromHex("#009345"),
Padding = "1 1 1 1",
Margin = "1 1 1 1",
Height = 15,
Opacity = 1
};
AddChild(Text);
break;
}
UpdateTextAndTop();
}
Grid GetTradeGrid()
{
var grid = new Grid(1, 2);
CloseTrade = new Button
{
Style = Styles.CreateCloseButtonStyle(),
Text = "X",
FontSize = 6,
Width = 25,
Height = 15,
Margin = "1 1 1 1"
};
CloseTrade.Click += CloseTrade_Click;
//EditTrade = new Button
//{
// Style = Styles.CreateCloseButtonStyle(),
// Text = "E",
// FontSize = 6,
// Width = 25,
// Height = 15,
// Margin = "1 1 1 1"
//};
Text = new TextBlock
{
Text = GetPositionText(),
ForegroundColor = Color.White,
BackgroundColor = _position.TradeType == TradeType.Buy ? Color.FromHex("009345") : Color.FromHex("#F05824"),
Padding = "1 1 1 1",
Margin = "1 1 1 1",
Height = 15,
Opacity = 1
};
grid.AddChild(Text, 0, 0);
grid.AddChild(CloseTrade, 0, 1);
//grid.AddChild(EditTrade, 0, 2);
return grid;
}
private void CloseTrade_Click(ButtonClickEventArgs obj)
{
TradeResult TR = _position.Close();
if (!TR.IsSuccessful)
_robot.Print("Failed to Close Position: " + _position.Id);
else
_robot.Print(_position.Id + ": Position Closed");
}
public void UpdateTextAndTop()
{
double chartYValue = 0;
string text = "";
switch (_type)
{
case ObjectType.Trade:
chartYValue = _position.EntryPrice;
text = GetPositionText();
break;
case ObjectType.SL:
chartYValue = _position.StopLoss.HasValue ? _position.StopLoss.Value : 0;
text = GetSLText();
break;
case ObjectType.TP:
chartYValue = _position.TakeProfit.HasValue ? _position.TakeProfit.Value : 0;
text = GetTPText();
break;
}
Text.Text = text;
Top = (_chart.TopY - chartYValue) * _chart.Height / (_chart.TopY - _chart.BottomY) + 5;
}
public string GetPositionText()
{
return _position.Id + " | " + _position.Quantity + (_position.TradeType == TradeType.Buy ? " ???? " : " ???? ") + "| " + Math.Round((_position.TradeType == TradeType.Buy ? _symbol.Bid - _position.EntryPrice : _position.EntryPrice - _symbol.Ask) / _symbol.PipSize, 2) + " Pips | " + _robot.Account.Asset.Name + " " + _position.NetProfit;
}
string GetSLText()
{
return _position.Id + " | SL | " + _position.StopLoss + " | " + Math.Round(Math.Abs(_position.StopLoss.Value - _position.EntryPrice) / _symbol.PipSize, 1) + " Pips";
}
string GetTPText()
{
return _position.Id + " | TP | " + _position.TakeProfit + " | " + Math.Round(Math.Abs(_position.TakeProfit.Value - _position.EntryPrice) / _symbol.PipSize, 1) + " Pips";
}
}
public class TradingPanel : CustomControl
{
private const string LotsInputKey = "LotsKey";
private const string TakeProfitInputKey = "TPKey";
private const string StopLossInputKey = "SLKey";
private readonly IDictionary<string, TextBox> _inputMap = new Dictionary<string, TextBox>();
private readonly Robot _robot;
private readonly Symbol _symbol;
public TradingPanel(Robot robot, Symbol symbol, double defaultLots, double defaultStopLossPips, double defaultTakeProfitPips, bool enableKBShortcuts)
{
_robot = robot;
_symbol = symbol;
AddChild(CreateTradingPanel(defaultLots, defaultStopLossPips, defaultTakeProfitPips));
if (enableKBShortcuts)
{
_robot.Chart.AddHotkey(OpenBuyPosition, "b");
_robot.Chart.AddHotkey(OpenSellPosition, "s");
_robot.Chart.AddHotkey(CloseAll, "c");
}
}
void OpenBuyPosition()
{
ExecuteMarketOrderAsync(TradeType.Buy);
}
void OpenSellPosition()
{
ExecuteMarketOrderAsync(TradeType.Sell);
}
private ControlBase CreateTradingPanel(double defaultLots, double defaultStopLossPips, double defaultTakeProfitPips)
{
var mainPanel = new StackPanel();
var header = CreateHeader();
mainPanel.AddChild(header);
var contentPanel = CreateContentPanel(defaultLots, defaultStopLossPips, defaultTakeProfitPips);
mainPanel.AddChild(contentPanel);
return mainPanel;
}
private ControlBase CreateHeader()
{
var headerBorder = new Border
{
BorderThickness = "0 0 0 1",
Style = Styles.CreateCommonBorderStyle()
};
var header = new TextBlock
{
Text = "Quick Trading Panel",
Margin = "10 7",
Style = Styles.CreateHeaderStyle()
};
headerBorder.Child = header;
return headerBorder;
}
private StackPanel CreateContentPanel(double defaultLots, double defaultStopLossPips, double defaultTakeProfitPips)
{
var contentPanel = new StackPanel
{
Margin = 10
};
var grid = new Grid(4, 3);
grid.Columns[1].SetWidthInPixels(5);
var sellButton = CreateTradeButton("SELL (s)", Styles.CreateSellButtonStyle(), TradeType.Sell);
grid.AddChild(sellButton, 0, 0);
var buyButton = CreateTradeButton("BUY (b)", Styles.CreateBuyButtonStyle(), TradeType.Buy);
grid.AddChild(buyButton, 0, 2);
var lotsInput = CreateInputWithLabel("Quantity (Lots)", defaultLots.ToString("F2"), LotsInputKey);
grid.AddChild(lotsInput, 1, 0, 1, 3);
var stopLossInput = CreateInputWithLabel("Stop Loss (Pips)", defaultStopLossPips.ToString("F1"), StopLossInputKey);
grid.AddChild(stopLossInput, 2, 0);
var takeProfitInput = CreateInputWithLabel("Take Profit (Pips)", defaultTakeProfitPips.ToString("F1"), TakeProfitInputKey);
grid.AddChild(takeProfitInput, 2, 2);
var closeAllButton = CreateCloseAllButton();
grid.AddChild(closeAllButton, 3, 0, 1, 3);
contentPanel.AddChild(grid);
return contentPanel;
}
private Button CreateTradeButton(string text, Style style, TradeType tradeType)
{
var tradeButton = new Button
{
Text = text,
Style = style,
Height = 25
};
tradeButton.Click += args => ExecuteMarketOrderAsync(tradeType);
return tradeButton;
}
private ControlBase CreateCloseAllButton()
{
var closeAllBorder = new Border
{
Margin = "0 10 0 0",
BorderThickness = "0 1 0 0",
Style = Styles.CreateCommonBorderStyle()
};
var closeButton = new Button
{
Style = Styles.CreateCloseButtonStyle(),
Text = "Close All (c)",
Margin = "0 10 0 0"
};
closeButton.Click += args => CloseAll();
closeAllBorder.Child = closeButton;
return closeAllBorder;
}
private Panel CreateInputWithLabel(string label, string defaultValue, string inputKey)
{
var stackPanel = new StackPanel
{
Orientation = Orientation.Vertical,
Margin = "0 10 0 0"
};
var textBlock = new TextBlock
{
Text = label
};
var input = new TextBox
{
Margin = "0 5 0 0",
Text = defaultValue,
Style = Styles.CreateInputStyle()
};
_inputMap.Add(inputKey, input);
stackPanel.AddChild(textBlock);
stackPanel.AddChild(input);
return stackPanel;
}
private void ExecuteMarketOrderAsync(TradeType tradeType)
{
var lots = GetValueFromInput(LotsInputKey, 0);
if (lots <= 0)
{
_robot.Print(string.Format("{0} failed, invalid Lots", tradeType));
return;
}
var stopLossPips = GetValueFromInput(StopLossInputKey, 0);
var takeProfitPips = GetValueFromInput(TakeProfitInputKey, 0);
_robot.Print(string.Format("Open position with: LotsParameter: {0}, StopLossPipsParameter: {1}, TakeProfitPipsParameter: {2}", lots, stopLossPips, takeProfitPips));
var volume = _symbol.QuantityToVolumeInUnits(lots);
_robot.ExecuteMarketOrderAsync(tradeType, _symbol.Name, volume, "Trade Panel Sample", stopLossPips, takeProfitPips);
}
private double GetValueFromInput(string inputKey, double defaultValue)
{
double value;
return double.TryParse(_inputMap[inputKey].Text, out value) ? value : defaultValue;
}
private void CloseAll()
{
foreach (var position in _robot.Positions)
_robot.ClosePositionAsync(position);
}
}
public static class Styles
{
public static Style CreatePanelBackgroundStyle()
{
var style = new Style();
style.Set(ControlProperty.CornerRadius, 3);
style.Set(ControlProperty.BackgroundColor, GetColorWithOpacity(Color.FromHex("#292929"), 0.85m), ControlState.DarkTheme);
style.Set(ControlProperty.BackgroundColor, GetColorWithOpacity(Color.FromHex("#FFFFFF"), 0.85m), ControlState.LightTheme);
style.Set(ControlProperty.BorderColor, Color.FromHex("#3C3C3C"), ControlState.DarkTheme);
style.Set(ControlProperty.BorderColor, Color.FromHex("#C3C3C3"), ControlState.LightTheme);
style.Set(ControlProperty.BorderThickness, new Thickness(1));
return style;
}
public static Style CreateCommonBorderStyle()
{
var style = new Style();
style.Set(ControlProperty.BorderColor, GetColorWithOpacity(Color.FromHex("#FFFFFF"), 0.12m), ControlState.DarkTheme);
style.Set(ControlProperty.BorderColor, GetColorWithOpacity(Color.FromHex("#000000"), 0.12m), ControlState.LightTheme);
return style;
}
public static Style CreateHeaderStyle()
{
var style = new Style();
style.Set(ControlProperty.ForegroundColor, GetColorWithOpacity("#FFFFFF", 0.70m), ControlState.DarkTheme);
style.Set(ControlProperty.ForegroundColor, GetColorWithOpacity("#000000", 0.65m), ControlState.LightTheme);
return style;
}
public static Style CreateInputStyle()
{
var style = new Style(DefaultStyles.TextBoxStyle);
style.Set(ControlProperty.BackgroundColor, Color.FromHex("#1A1A1A"), ControlState.DarkTheme);
style.Set(ControlProperty.BackgroundColor, Color.FromHex("#111111"), ControlState.DarkTheme | ControlState.Hover);
style.Set(ControlProperty.BackgroundColor, Color.FromHex("#E7EBED"), ControlState.LightTheme);
style.Set(ControlProperty.BackgroundColor, Color.FromHex("#D6DADC"), ControlState.LightTheme | ControlState.Hover);
style.Set(ControlProperty.CornerRadius, 3);
return style;
}
public static Style CreateBuyButtonStyle()
{
return CreateButtonStyle(Color.FromHex("#009345"), Color.FromHex("#10A651"));
}
public static Style CreateSellButtonStyle()
{
return CreateButtonStyle(Color.FromHex("#F05824"), Color.FromHex("#FF6C36"));
}
public static Style CreateCloseButtonStyle()
{
return CreateButtonStyle(Color.FromHex("#F05824"), Color.FromHex("#FF6C36"));
}
private static Style CreateButtonStyle(Color color, Color hoverColor)
{
var style = new Style(DefaultStyles.ButtonStyle);
style.Set(ControlProperty.BackgroundColor, color, ControlState.DarkTheme);
style.Set(ControlProperty.BackgroundColor, color, ControlState.LightTheme);
style.Set(ControlProperty.BackgroundColor, hoverColor, ControlState.DarkTheme | ControlState.Hover);
style.Set(ControlProperty.BackgroundColor, hoverColor, ControlState.LightTheme | ControlState.Hover);
style.Set(ControlProperty.ForegroundColor, Color.FromHex("#FFFFFF"), ControlState.DarkTheme);
style.Set(ControlProperty.ForegroundColor, Color.FromHex("#FFFFFF"), ControlState.LightTheme);
return style;
}
private static Color GetColorWithOpacity(Color baseColor, decimal opacity)
{
var alpha = (int)Math.Round(byte.MaxValue * opacity, MidpointRounding.AwayFromZero);
return Color.FromArgb(alpha, baseColor);
}
}
}
aksbenz
Joined on 26.09.2020
- Distribution: Free
- Language: C#
- Trading platform: cTrader Automate
- File name: Manual Tester With SL TP Lines.algo
- Rating: 5
- Installs: 2135
- Modified: 02/01/2022 02:26
Comments
Nice, it run stable
Hope will add setup order
This is an excellent technology but I am looking for Bass boat technologies.
Is this indicator working correctly?
Sorry for the rather nerdy question, I haven't been working with the SL and TP Lines system very long so there's a lot I don't understand yet. Still, Visual Backtesting is a bit tricky for me, but I always know that people are willing to help me, I just need to go to the Shagle app, and there will be many of my friends (even strangers there are quite friendly and willing to help) who are willing to explain me the topic that I will not understand. I want to be prepared for their questions, so I'm asking you here.
I hope you won't refuse to help me.
how i move sl acd tp like ctrader aplication?