using System; using System.Collections.Generic; using System.Threading; using cAlgo.API; using cAlgo.API.Internals; namespace cAlgo { [Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)] public class SampleTradingPanel : Robot { [Parameter("Vertical Position", Group = "Panel alignment", DefaultValue = VerticalAlignment.Top)] public VerticalAlignment PanelVerticalAlignment { get; set; } [Parameter("Horizontal Position", Group = "Panel alignment", DefaultValue = HorizontalAlignment.Left)] public HorizontalAlignment PanelHorizontalAlignment { get; set; } [Parameter("Risk_default", Group = "x", DefaultValue = 1.5, MinValue = 0.1, Step = 0.1)] public double Risk { get; set; } [Parameter("Risk_step", Group = "x", DefaultValue = 0.5, MinValue = 0.1)] public double Risk_step { get; set; } protected override void OnStart() { var tradingPanel = new TradingPanel(this, Symbol, Chart, Risk, Risk_step); var border = new Border { VerticalAlignment = PanelVerticalAlignment, HorizontalAlignment = PanelHorizontalAlignment, Style = Styles.CreatePanelBackgroundStyle(), Margin = "20 20 20 20", Width = 245, Child = tradingPanel }; Chart.AddControl(border); Chart.MouseUp += tradingPanel.Chart_MouseUp; Chart.KeyDown += tradingPanel.Chart_KeyDown; } } public class TradingPanel : CustomControl { private TextBox Input { get; set; } private Button Limit { get; set; } private Button Market { get; set; } private readonly Robot _robot; private readonly Symbol _symbol; Chart _chart; private double RiskStep { get; set; } private bool UsePercent { get; set; } LimitOrderState LimitState { get; set; } bool IsLong { get; set; } double LimitStopLoss { get; set; } MarketOrderState MarketState { get; set; } double MarketStopLoss { get; set; } public TradingPanel(Robot robot, Symbol symbol, Chart chart, double riskDef, double riskStep) { _robot = robot; _symbol = symbol; _chart = chart; RiskStep = riskStep; AddChild(CreateContentPanel(riskDef)); } internal void Chart_MouseUp(ChartMouseEventArgs obj) { try { #region Limit if (LimitState == LimitOrderState.DetectingStopPrice) { LimitStopLoss = obj.YValue; LimitState = LimitOrderState.PlacingLimitOrder; Limit.Text = "ORDER"; if (obj.YValue > _symbol.Ask) IsLong = false; else if (obj.YValue < _symbol.Ask) IsLong = true; _chart.DrawHorizontalLine("StopLine", obj.YValue, Color.Red); } else if (LimitState == LimitOrderState.PlacingLimitOrder) { _chart.RemoveObject("StopLine"); double volume; if (UsePercent) { volume = _symbol.VolumeForProportionalRisk(ProportionalAmountType.Balance, double.Parse(Input.Text), Math.Abs((obj.YValue - LimitStopLoss) / _symbol.PipSize)); volume = volume * _robot.Account.FreeMargin / _robot.Account.Balance; volume = _symbol.NormalizeVolumeInUnits(volume, RoundingMode.ToNearest); } else { volume = _symbol.VolumeForFixedRisk(double.Parse(Input.Text), Math.Abs((obj.YValue - LimitStopLoss) / _symbol.PipSize)); volume = volume * _robot.Account.FreeMargin / _robot.Account.Balance; volume = _symbol.NormalizeVolumeInUnits(volume, RoundingMode.ToNearest); } _robot.PlaceLimitOrderAsync(IsLong ? TradeType.Buy : TradeType.Sell, _symbol.Name, volume, obj.YValue, "CustomPanel", Math.Abs(obj.YValue - LimitStopLoss) / _symbol.PipSize, null, (TradeResult result) => { if (result != null && !result.IsSuccessful) { LimitState = LimitOrderState.Default; Limit.Text = "LIMIT"; } }); LimitState = LimitOrderState.PlacingTakeProfit; Limit.Text = "TAKE"; } else if (LimitState == LimitOrderState.PlacingTakeProfit) { _robot.ModifyPendingOrderAsync(_robot.PendingOrders[^1], _robot.PendingOrders[^1].TargetPrice, _robot.PendingOrders[^1].StopLossPips, Math.Abs(obj.YValue - _robot.PendingOrders[^1].TargetPrice) / _symbol.PipSize, (TradeResult result) => { if (result == null || !result.IsSuccessful) { LimitState = LimitOrderState.Default; Limit.Text = "LIMIT"; } }); LimitState = LimitOrderState.Default; Limit.Text = "LIMIT"; } #endregion #region Market if (MarketState == MarketOrderState.DetectingStopPrice) { MarketStopLoss = obj.YValue; double volume; double volume1; if (UsePercent) { volume = _symbol.VolumeForProportionalRisk(ProportionalAmountType.Balance, double.Parse(Input.Text), Math.Abs((_symbol.Ask - MarketStopLoss) / _symbol.PipSize)); volume1 = volume; volume = volume * _robot.Account.FreeMargin / _robot.Account.Balance; volume = _symbol.NormalizeVolumeInUnits(volume, RoundingMode.ToNearest); } else { volume = _symbol.VolumeForFixedRisk(double.Parse(Input.Text), Math.Abs((_symbol.Ask - MarketStopLoss) / _symbol.PipSize)); volume1 = volume; volume = volume * _robot.Account.FreeMargin / _robot.Account.Balance; volume = _symbol.NormalizeVolumeInUnits(volume, RoundingMode.ToNearest); } TradeType type = TradeType.Buy; if (MarketStopLoss > _symbol.Ask) type = TradeType.Sell; var res = _robot.ExecuteMarketOrder(type, _symbol.Name, volume); if (res != null) { _robot.ModifyPositionAsync(res.Position, MarketStopLoss, null, null); _robot.Print($"volume {volume1}"); _robot.Print($"res.Position.VolumeInUnits {res.Position.VolumeInUnits}"); double dif = Math.Abs(volume1 - res.Position.VolumeInUnits) / volume1; _robot.Print($"dif {dif}"); if (dif > 0.2d) { _robot.Print($"Разница между рассчитанным и исполненным объемом {Math.Round(dif,2) * 100}%"); _robot.Notifications.PlaySound(SoundType.Announcement); } } MarketState = MarketOrderState.PlacingTakeProfit; Market.Text = "STOP"; } else if (MarketState == MarketOrderState.PlacingTakeProfit) { var lastPosition = _robot.Positions[^1]; if (lastPosition != null) { double newTakeProfitPrice = obj.YValue; _robot.ModifyPositionAsync(lastPosition, lastPosition.StopLoss, newTakeProfitPrice, (TradeResult result) => { if (result == null || !result.IsSuccessful) { _robot.Print("Failed to modify the take profit: {0}", result.Error); } else { _robot.Print("Take profit successfully modified."); } }); } else { _robot.Print("No open positions found."); } MarketState = MarketOrderState.Default; Limit.Text = "MARKET"; } #endregion } catch { LimitState = LimitOrderState.Default; Limit.Text = "LIMIT"; MarketState = MarketOrderState.Default; Market.Text = "MARKET"; } } internal void Chart_KeyDown(ChartKeyboardEventArgs obj) { if (obj.Key == Key.Escape) { _chart.RemoveObject("StopLine"); LimitState = LimitOrderState.Default; Limit.Text = "LIMIT"; MarketState = MarketOrderState.Default; Market.Text = "MARKET"; } } private Grid CreateContentPanel(double riskDef) { var grid = new Grid(1, 2) { Margin = 10 }; var gridLeft = new Grid(1, 3); Input = CreateInput(riskDef); gridLeft.AddChild(Input, 0, 0); var percentMoneySwitchButton = CreateSwitchButton("$", Styles.CreateGreenButtonStyle()); gridLeft.AddChild(percentMoneySwitchButton, 0, 1); var gridLeftInner = new Grid(2, 1); var plusButton = CreatePlusMinusButton("+", Styles.CreateGreenButtonStyle()); gridLeftInner.AddChild(plusButton, 0, 0); var minusButton = CreatePlusMinusButton("-", Styles.CreateGreenButtonStyle()); gridLeftInner.AddChild(minusButton, 1, 0); gridLeft.AddChild(gridLeftInner, 0, 2); var gridRight = new Grid(1, 2); var limitButton = CreateLimitButton("LIMIT", Styles.CreateGreenButtonStyle()); gridRight.AddChild(limitButton, 0, 0); var marketButton = CreateMarketButton("MARKET", Styles.CreateGreenButtonStyle()); gridRight.AddChild(marketButton, 0, 1); grid.AddChild(gridLeft, 0, 0); grid.AddChild(gridRight, 0, 1); return grid; } private TextBox CreateInput(double riskDef) { var newInput = new TextBox { Margin = "2", Text = riskDef.ToString(), Style = Styles.CreateInputStyle() }; return newInput; } private Button CreateSwitchButton(string text, Style style) { var button = new Button { Text = text, Style = style, Height = 28, Margin = "2" }; button.Click += (button) => { if (button.Button.Text == "%") { button.Button.Text = "$"; UsePercent = false; } else { button.Button.Text = "%"; UsePercent = true; } }; return button; } private Button CreatePlusMinusButton(string text, Style style) { var button = new Button { Text = text, Style = style, Height = 12, VerticalContentAlignment = VerticalAlignment.Center, HorizontalContentAlignment = HorizontalAlignment.Center, Margin = "2" }; if (text == "+") button.Click += args => { if (double.TryParse(Input.Text, out double buf)) Input.Text = (buf + RiskStep).ToString(); }; else button.Click += args => { if (double.TryParse(Input.Text, out double buf)) Input.Text = (buf - RiskStep).ToString(); }; return button; } private Button CreateLimitButton(string text, Style style) { var button = new Button { Text = text, Style = style, VerticalContentAlignment = VerticalAlignment.Center, HorizontalContentAlignment = HorizontalAlignment.Center, Margin = "2" }; button.Click += (button) => { if (LimitState == LimitOrderState.Default) LimitState = LimitOrderState.DetectingStopPrice; MarketState = MarketOrderState.Default; Market.Text = "MARKET"; if (button.Button.Text == "LIMIT") button.Button.Text = "STOP"; }; Limit = button; return button; } private Button CreateMarketButton(string text, Style style) { var button = new Button { Text = text, Style = style, VerticalContentAlignment = VerticalAlignment.Center, HorizontalContentAlignment = HorizontalAlignment.Center, Margin = "2" }; button.Click += (button) => { if (MarketState == MarketOrderState.Default) MarketState = MarketOrderState.DetectingStopPrice; LimitState = LimitOrderState.Default; Limit.Text = "LIMIT"; if (button.Button.Text == "MARKET") button.Button.Text = "STOP"; }; Market = button; return button; } enum LimitOrderState { Default, DetectingStopPrice, PlacingLimitOrder, PlacingTakeProfit } enum MarketOrderState { Default, DetectingStopPrice, PlacingTakeProfit } } 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 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 CreateGreenButtonStyle() { return CreateButtonStyle(Color.FromHex("#009345"), Color.FromHex("#10A651")); } private static Style CreateButtonStyle(Color color, Color hoverColor) { var style = new Style(DefaultStyles.ButtonStyle); style.Set(ControlProperty.Padding, new Thickness(2, 0, 2, 0)); 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); } } }