using System;
using System.IO;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using NinjaTrader.Gui;
using NinjaTrader.Gui.Chart;
using NinjaTrader.Gui.Tools;
using NinjaTrader.NinjaScript;
namespace NinjaTrader.NinjaScript.AddOns
{
public class ClickCaptureTool : AddOnBase
{
private NTWindow mainWindow;
private ChartControl chartControl;
private bool capturingEntry;
private TextBox txtEntryPrice, txtEntryTime, txtExitPrice, txtExitTime, txtResultado;
private ComboBox comboDirection, comboTarget;
private CheckBox chkMecha, chkColores, chkCamino, chkAutoSave;
private Button btnCaptureEntry, btnCaptureExit, btnSave, btnReset;
private readonly string csvPath = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolde r.MyDocuments),
"NinjaTrader 8", "OperacionesCapturadas.csv");
protected override void OnStateChange()
{
if (State == State.SetDefaults)
Name = "ClickCaptureTool";
}
protected override void OnWindowCreated(Window window)
{
if (window is ControlCenter)
{
InitWindow();
AddMenuItem();
}
var pi = window.GetType().GetProperty("ChartControl");
if (pi != null && chartControl == null)
chartControl = pi.GetValue(window) as ChartControl;
}
private void AddMenuItem()
{
var controlCenter = Application.Current.Windows.OfType<ControlCenter>( ).FirstOrDefault();
if (controlCenter == null) return;
var toolsMenu = controlCenter.FindResource("MainMenu.Tools") as MenuItem;
if (toolsMenu == null) return;
var item = new MenuItem { Header = "ClickCaptureTool" };
item.Click += (_, __) => InitWindow();
toolsMenu.Items.Add(item);
}
private void InitWindow()
{
if (mainWindow != null && mainWindow.IsVisible)
{
mainWindow.Activate();
return;
}
mainWindow = new NTWindow
{
Caption = "Captura de Operación",
Width = 360,
Height = 480,
Topmost = true
};
mainWindow.Closed += (_, __) => mainWindow = null;
var grid = new Grid { Margin = new Thickness(10) };
for (int i = 0; i < 15; i++)
grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
txtEntryPrice = NewTextBox("Nivel Entrada");
txtEntryTime = NewTextBox("Hora Entrada");
txtExitPrice = NewTextBox("Nivel Salida");
txtExitTime = NewTextBox("Hora Salida");
txtResultado = NewTextBox("Resultado (pts)");
comboDirection = NewCombo(new[] { "Largo", "Corto" });
comboTarget = NewCombo(new[] { "Stop", "Profit" });
chkMecha = NewCheck("Mecha No Echa");
chkColores = NewCheck("Colores");
chkCamino = NewCheck("Camino Limpio 50%");
chkAutoSave = NewCheck("Guardar automáticamente al capturar salida");
btnCaptureEntry = NewButton("Capturar Entrada", (_, __) => StartCapture(true));
btnCaptureExit = NewButton("Capturar Salida", (_, __) => StartCapture(false));
btnSave = NewButton("Guardar en CSV", (_, __) => SaveToCSV());
btnReset = NewButton("Reset", (_, __) => ResetCampos());
AddToGrid(grid, txtEntryPrice, 0);
AddToGrid(grid, txtEntryTime, 1);
AddToGrid(grid, btnCaptureEntry, 2);
AddToGrid(grid, txtExitPrice, 3);
AddToGrid(grid, txtExitTime, 4);
AddToGrid(grid, btnCaptureExit, 5);
AddToGrid(grid, comboDirection, 6);
AddToGrid(grid, comboTarget, 7);
AddToGrid(grid, chkMecha, 8);
AddToGrid(grid, chkColores, 9);
AddToGrid(grid, chkCamino, 10);
AddToGrid(grid, chkAutoSave, 11);
AddToGrid(grid, txtResultado, 12);
AddToGrid(grid, btnSave, 13);
AddToGrid(grid, btnReset, 14);
mainWindow.Content = grid;
mainWindow.Show();
}
private TextBox NewTextBox(string placeholder) =>
new TextBox { Text = placeholder, IsReadOnly = true, Margin = new Thickness(5) };
private Button NewButton(string content, RoutedEventHandler handler)
{
var b = new Button { Content = content, Margin = new Thickness(5) };
b.Click += handler;
return b;
}
private ComboBox NewCombo(string[] items) =>
new ComboBox { ItemsSource = items, SelectedIndex = 0, Margin = new Thickness(5) };
private CheckBox NewCheck(string label) =>
new CheckBox { Content = label, Margin = new Thickness(5) };
private void AddToGrid(Grid grid, UIElement element, int row)
{
Grid.SetRow(element, row);
grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
grid.Children.Add(element);
}
private void StartCapture(bool isEntry)
{
if (chartControl == null)
{
chartControl = Application.Current.Windows
.OfType<Window>()
.Select(w => w.GetType().GetProperty("ChartControl")?.GetValue( w) as ChartControl)
.FirstOrDefault(c => c != null);
}
if (chartControl == null)
{
MessageBox.Show("Abre un gráfico primero.");
return;
}
capturingEntry = isEntry;
Application.Current.Dispatcher.Invoke(() =>
{
chartControl.MouseDown -= OnChartClick;
chartControl.MouseDown += OnChartClick;
});
MessageBox.Show(isEntry ? "Haz clic para capturar ENTRADA" : "Haz clic para capturar SALIDA");
}
// ✅ Método 100% seguro con Dispatcher
private void OnChartClick(object sender, MouseButtonEventArgs e)
{
ChartPanel panel = null;
double yCanvas = 0;
double xCanvas = 0;
// 1. Ejecutar el acceso al gráfico dentro del Dispatcher
Application.Current.Dispatcher.Invoke(() =>
{
try
{
panel = chartControl?.ChartPanels?.FirstOrDefault();
if (panel == null)
throw new Exception("No se encontró el panel del gráfico.");
Point pt = e.GetPosition(panel); // ← seguro aquí
yCanvas = pt.Y;
xCanvas = pt.X + panel.X;
chartControl.MouseDown -= OnChartClick;
double price = panel.Scales.First().GetValueByYWpf(yCanvas);
DateTime time = chartControl.GetTimeByX((int)xCanvas);
if (capturingEntry)
{
txtEntryPrice.Text = price.ToString("F5");
txtEntryTime.Text = time.ToString("HH:mm:ss");
}
else
{
txtExitPrice.Text = price.ToString("F5");
txtExitTime.Text = time.ToString("HH:mm:ss");
CalcularResultado();
if (chkAutoSave.IsChecked == true)
SaveToCSV();
}
MessageBox.Show(capturingEntry ? "Entrada capturada." : "Salida capturada.");
}
catch (Exception ex)
{
MessageBox.Show("Error durante la captura del clic:\n" + ex.Message);
}
});
}
private void CalcularResultado()
{
try
{
double entry = double.Parse(txtEntryPrice.Text);
double exit = double.Parse(txtExitPrice.Text);
double result = (comboDirection.SelectedItem.ToString() == "Largo")
? exit - entry
: entry - exit;
txtResultado.Text = result.ToString("F5");
}
catch
{
txtResultado.Text = "Error";
}
}
private void SaveToCSV()
{
if (string.IsNullOrWhiteSpace(txtEntryPrice.Text) || string.IsNullOrWhiteSpace(txtExitPrice.Text))
{
MessageBox.Show("Debes capturar entrada y salida.");
return;
}
string line = string.Join(",",
DateTime.Now.ToString("s"),
txtEntryPrice.Text,
txtEntryTime.Text,
txtExitPrice.Text,
txtExitTime.Text,
comboDirection.SelectedItem?.ToString(),
comboTarget.SelectedItem?.ToString(),
chkMecha.IsChecked == true ? "Sí" : "No",
chkColores.IsChecked == true ? "Sí" : "No",
chkCamino.IsChecked == true ? "Sí" : "No",
txtResultado.Text
);
try
{
File.AppendAllText(csvPath, line + Environment.NewLine);
MessageBox.Show("Guardado correctamente.");
}
catch (Exception ex)
{
MessageBox.Show("Error al guardar: " + ex.Message);
}
}
private void ResetCampos()
{
txtEntryPrice.Text = "Nivel Entrada";
txtEntryTime.Text = "Hora Entrada";
txtExitPrice.Text = "Nivel Salida";
txtExitTime.Text = "Hora Salida";
txtResultado.Text = "Resultado (pts)";
comboDirection.SelectedIndex = 0;
comboTarget.SelectedIndex = 0;
chkMecha.IsChecked = false;
chkColores.IsChecked = false;
chkCamino.IsChecked = false;
chkAutoSave.IsChecked = false;
}
}
}

Comment