
Em nosso blog, escrevemos muito sobre tecnologias e ferramentas úteis relacionadas à negociação de ações. Uma delas é a plataforma
StockSharp gratuita, que pode ser usada para o desenvolvimento profissional de terminais de negociação e robôs de negociação em C #. Neste artigo, mostraremos como usar a estrutura gráfica incluída no S # .API para criar um terminal de negociação com a capacidade de executar estratégias algorítmicas.
O que é necessário
- Visual Studio 2017 (Comunidade, versão gratuita), nele vamos programar.
- Conexão à negociação na bolsa, nos exemplos deste texto, a interface SMARTcom da ITI Capital é usada .
Criação de projeto
Crie um novo aplicativo WPF no Visual Studio:

Então você precisa adicionar a biblioteca S # .API. Você pode descobrir como fazer isso
na documentação . A melhor opção é instalar usando o Nuget.
Como todos os elementos gráficos do S # .API são baseados no DevExpress, e as bibliotecas do DevExpress vêm com o S # .API, seria tolice não usá-los. Vamos para o editor de janelas MainWindow.xaml:

Substitua Window pelo DXWindow, será necessário usar diferentes esquemas de cores:

O próprio Visual Studio nos oferecerá a inserção das bibliotecas necessárias.
Dividiremos a janela em três partes - na parte superior, haverá uma faixa com botões para configurar conexões e conexões; na parte inferior, haverá uma janela com troncos e, no meio, todos os outros painéis. A maneira mais fácil de quebrar uma janela é com o LayoutControl do DevExpress.
Nas três partes resultantes, adicionaremos os elementos que precisamos.
<dx:DXWindow x:Class="ShellNew.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:dx="http://schemas.devexpress.com/winfx/2008/xaml/core" xmlns:dxlc="http://schemas.devexpress.com/winfx/2008/xaml/layoutcontrol" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800"> <dxlc:LayoutControl Padding="0" Name="LayoutControlRoot" Orientation="Vertical"> <dxlc:LayoutGroup HorizontalAlignment="Stretch" Height="25"> </dxlc:LayoutGroup> <dxlc:LayoutGroup HorizontalAlignment="Stretch" > </dxlc:LayoutGroup> <dxlc:LayoutGroup HorizontalAlignment="Stretch" > </dxlc:LayoutGroup> </dxlc:LayoutControl> </dx:DXWindow>
Configurando uma Conexão com um Conector
Adicione dois botões, um é o botão de configurações de conexão e o segundo é o botão de conexão. Para fazer isso, use o botão SimpleButton do DevExpress. Os botões estarão localizados na parte superior do aplicativo. Em cada botão, colocamos as figuras familiares de
S # .Designer ,
S # .Data e
S # .Terminal .
<dx:DXWindow x:Class="ShellNew.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:dx="http://schemas.devexpress.com/winfx/2008/xaml/core" xmlns:dxlc="http://schemas.devexpress.com/winfx/2008/xaml/layoutcontrol" xmlns:xaml="http://schemas.stocksharp.com/xaml" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800"> <dxlc:LayoutControl Padding="0" Name="LayoutControlRoot" Orientation="Vertical"> <dxlc:LayoutGroup HorizontalAlignment="Stretch" Height="25"> <!-- --> <dxlc:LayoutItem Width="40"> <dx:SimpleButton x:Name="SettingsButton" Click="SettingsButton_Click" > <Image Source="{xaml:ThemedIcons Key=Settings}" Width="16" /> </dx:SimpleButton> </dxlc:LayoutItem> <dxlc:LayoutItem Width="40"> <dx:SimpleButton x:Name="ConnectButton" Click="ConnectButton_Click" > <Image Source="{xaml:ThemedIcons Key=Connect}" Width="16" /> </dx:SimpleButton> </dxlc:LayoutItem> </dxlc:LayoutGroup> <dxlc:LayoutGroup HorizontalAlignment="Stretch" View="Tabs"> <!-- --> </dxlc:LayoutGroup> <dxlc:LayoutGroup HorizontalAlignment="Stretch" > <!-- --> </dxlc:LayoutGroup> </dxlc:LayoutControl> </dx:DXWindow>
No canto superior direito da tela, veremos a seguinte figura:

Clique duas vezes em cada botão para criar manipuladores de eventos para clicar no botão. No código da MainWindow, você deve declarar o conector, bem como o local e o nome do arquivo no qual as configurações do conector serão armazenadas.
public readonly Connector Connector; private const string _dir = "Data"; private static readonly string _settingsFile = $@"{_dir}\connection.xml";
No manipulador de eventos para clicar no botão de configurações do conector, abriremos a janela de configuração do conector e a salvaremos em um arquivo.
private void SettingsButton_Click(object sender, RoutedEventArgs e) { if (Connector.Configure(this)) { new XmlSerializer<SettingsStorage>().Serialize(Connector.Save(), _settingsFile); } }
No construtor, verificaremos se há um diretório e um arquivo com as configurações do conector e, se houver, carregaremos no conector:
A maioria dos objetos .API do S # possui métodos Save and Load que podem ser usados para salvar e carregar esse objeto de um arquivo XML.
No manipulador de métodos, clicando no botão conectar, conectamos o conector.
private void ConnectButton_Click(object sender, RoutedEventArgs e) { Connector.Connect(); }
Agora você pode executar o programa e verificá-lo.
Definindo um tema sombrio
Muitos traders preferem temas obscuros a aplicativos de negociação. Portanto, imediatamente escurecemos o tema do programa. Para você precisar encontrar o arquivo App.xaml:

E substitua Application por gráficos: ExtendedBaseApplication, e o próprio Visual Studio nos oferecerá a inserção das bibliotecas necessárias.
<charting:ExtendedBaseApplication x:Class="ShellNew.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:charting="http://schemas.stocksharp.com/xaml" StartupUri="MainWindow.xaml"> </charting:ExtendedBaseApplication>
E no arquivo App.xaml.cs, você precisa excluir ": Application".
namespace ShellNew {
No construtor de MainWindow, escreva
ApplicationThemeHelper.ApplicationThemeName = Theme.VS2017DarkName;
Código completo no momento:
public partial class MainWindow { public readonly Connector Connector; private const string _dir = "Data"; private static readonly string _settingsFile = $@"{_dir}\connection.xml"; public MainWindow() {
Execute para verificar o tópico obscuro:

Criar barra de ferramentas
Adicione uma pasta na qual armazenaremos todos os controles que criamos e chame-a de XAML. Adicione nosso primeiro UserControll, atribua o nome SecurityGridControl.

Nós adicionamos um elemento SecurityPicker a ele. Ele exibirá as ferramentas disponíveis. Por analogia com a janela principal, usaremos o LayoutControl do DevExpress.
<UserControl x:Class="ShellNew.XAML.SecurityGridControl" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:xaml="http://schemas.stocksharp.com/xaml" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800"> <xaml:SecurityPicker x:Name="SecPicker" /> </UserControl>
Vamos ao designer da janela principal e alteramos a parte central para a exibição de marcadores. Em um dos favoritos, colocaremos o controlador que criamos com o SecurityPicker:
<dxlc:LayoutGroup HorizontalAlignment="Stretch" View="Tabs"> <!-- --> <dxlc:LayoutGroup Header="Securities"> <myxaml:SecurityGridControl x:Name="SecurityPanel" /> </dxlc:LayoutGroup> </dxlc:LayoutGroup>
Agora que temos a barra de ferramentas, precisamos fornecer uma fonte de dados, no nosso caso, é um conector. Você pode simplesmente
MainWindow
SecurityPanel.SecPicker.SecurityProvider = Connector;
no construtor
MainWindow
SecurityPanel.SecPicker.SecurityProvider = Connector;
MainWindow
SecurityPanel.SecPicker.SecurityProvider = Connector;
.
Mas você não deve entupir o MainWindow com código que não se aplica a ele. Portanto, criaremos a variável estática Instance e atribuiremos MainWindow a ela no construtor MainWindow:
… public static MainWindow Instance; … Instance = this; …
Agora, em qualquer lugar do nosso programa, podemos acessar as propriedades do MainWindow através do código MainWindow.Instance.XXX.
No construtor SecurityGridControl, dessa maneira, especificamos o Connector como uma fonte de dados:
public SecurityGridControl() { InitializeComponent(); SecPicker.SecurityProvider = MainWindow.Instance.Connector; }
Execute para verificar:

Adicionando log
O programa, conector ou robô deve ser controlado. Para isso, o S # .API possui uma classe especial do LogManager. Essa classe recebe mensagens de fontes e as passa para ouvintes. No nosso caso, as fontes serão Connector, estratégias, etc., e o ouvinte será um arquivo e um painel de log.
No código MainWindow, declaramos o objeto LogManager e o local em que ele será armazenado:
public readonly LogManager LogManager; private static readonly string _logsDir = $@"{_dir}\Logs\";
No construtor MainWindow, crie um LogManager, defina a fonte do Connector e o arquivo ouvinte para ele:
Por analogia com a barra de ferramentas, crie um painel de log na pasta XAML, adicione outro UserControl. Atribua a ele o nome MonitorControl. Adicione o elemento Monitor a ele.
<UserControl x:Class="ShellNew.XAML.MonitorControl" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:xaml="http://schemas.stocksharp.com/xaml" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800"> <xaml:Monitor x:Name="Monitor" /> </UserControl>
No construtor MonitorControl, defina LogManager como Monitor como um ouvinte:
public MonitorControl() { InitializeComponent(); MainWindow.Instance.LogManager.Listeners.Add(new GuiLogListener(Monitor)); }
Adicione o MonitorControl criado à parte inferior do MainWindow:
<dxlc:LayoutGroup HorizontalAlignment="Stretch" dxlc:LayoutControl.AllowVerticalSizing="True"> <!-- --> <myxaml:MonitorControl x:Name="MonitorControl" /> </dxlc:LayoutGroup>
Execute para verificar:

Crie um painel de vidro
Por analogia com os painéis anteriores, crie um painel de vidro e adicione outro UserControl à pasta XAML. Atribua o nome MarketDepthControl.
No MainWindow já usamos LayoutControl, nesse controle também usaremos LayoutControl. Dividimos o painel em duas partes horizontalmente:
<UserControl x:Class="ShellNew.XAML.MarketDepthControl" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:dxlc="http://schemas.devexpress.com/winfx/2008/xaml/layoutcontrol" mc:Ignorable="d"> <dxlc:LayoutControl Padding="0" Name="LayoutControlRoot" Orientation="Horizontal"> <dxlc:LayoutGroup> <!--Left--> </dxlc:LayoutGroup> <dxlc:LayoutGroup Orientation="Vertical" dxlc:LayoutControl.AllowHorizontalSizing="True"> <!--Rigth--> </dxlc:LayoutGroup> </dxlc:LayoutControl> </UserControl>
Adicione o SecurityPicker ao lado esquerdo - nos encontramos com ele quando criamos a barra de ferramentas.
<dxlc:LayoutGroup> <xaml:SecurityPicker x:Name="SecPicker" SecuritySelected="SecPicker_SecuritySelected" /> </dxlc:LayoutGroup> . : <dxlc:LayoutGroup Orientation="Vertical" dxlc:LayoutControl.AllowHorizontalSizing="True"> <dxlc:LayoutItem VerticalAlignment="Stretch"> <xaml:MarketDepthControl x:Name="MarketDepth" MaxHeight="2000" SelectionChanged="MarketDepth_SelectionChanged" /> </dxlc:LayoutItem> </dxlc:LayoutGroup>
O MarketDepthControl precisa definir um valor MaxHeight, caso contrário, o aplicativo não será iniciado.
Sob o vidro, colocaremos os elementos da tarefa, preço e volume do pedido do portfólio:
<dxlc:LayoutItem Label="Portfolio" Height="20"> <xaml:PortfolioComboBox x:Name="PortfolioComboBox" /> </dxlc:LayoutItem> <dxlc:LayoutItem Label="Price" Height="20"> <dxe:SpinEdit MinValue="0" Name="SpinEditPrice" /> </dxlc:LayoutItem> <dxlc:LayoutItem Label="Volume" Height="20"> <dxe:SpinEdit MinValue="0" Name="SpinEditVolume" /> </dxlc:LayoutItem>
Vale ressaltar a propriedade Label de LayoutItem, que permite definir o texto na frente do elemento. Assim como o elemento SpinEdit do DevExpress, no qual é conveniente definir valores numéricos. Esses elementos têm a seguinte aparência:

Abaixo, colocaremos os botões de compra e venda:
<dxlc:LayoutGroup Orientation="Horizontal" Height="20" VerticalAlignment="Stretch"> <dxlc:LayoutItem VerticalAlignment="Stretch"> <dx:SimpleButton Content="Buy" x:Name="BuyButton" Click="BuyButton_Click" /> </dxlc:LayoutItem> <dxlc:LayoutItem VerticalAlignment="Stretch"> <dx:SimpleButton Content="Sell" x:Name="SelltButton" Click="SelltButton_Click" /> </dxlc:LayoutItem> </dxlc:LayoutGroup>
Código completo:
<UserControl x:Class="ShellNew.XAML.MarketDepthControl" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:dxlc="http://schemas.devexpress.com/winfx/2008/xaml/layoutcontrol" xmlns:dx="http://schemas.devexpress.com/winfx/2008/xaml/core" xmlns:xaml="http://schemas.stocksharp.com/xaml" xmlns:dxe="http://schemas.devexpress.com/winfx/2008/xaml/editors" mc:Ignorable="d"> <dxlc:LayoutControl Padding="0" Name="LayoutControlRoot" Orientation="Horizontal"> <dxlc:LayoutGroup> <xaml:SecurityPicker x:Name="SecPicker" SecuritySelected="SecPicker_SecuritySelected" /> </dxlc:LayoutGroup> <dxlc:LayoutGroup Orientation="Vertical" dxlc:LayoutControl.AllowHorizontalSizing="True"> <dxlc:LayoutItem VerticalAlignment="Stretch"> <xaml:MarketDepthControl x:Name="MarketDepth" MaxHeight="2000" SelectionChanged="MarketDepth_SelectionChanged" /> </dxlc:LayoutItem> <dxlc:LayoutItem Label="Portfolio" Height="20"> <xaml:PortfolioComboBox x:Name="PortfolioComboBox" /> </dxlc:LayoutItem> <dxlc:LayoutItem Label="Price" Height="20"> <dxe:SpinEdit MinValue="0" Name="SpinEditPrice" /> </dxlc:LayoutItem> <dxlc:LayoutItem Label="Volume" Height="20"> <dxe:SpinEdit MinValue="0" Name="SpinEditVolume" /> </dxlc:LayoutItem> <dxlc:LayoutGroup Orientation="Horizontal" Height="20" VerticalAlignment="Stretch"> <dxlc:LayoutItem VerticalAlignment="Stretch"> <dx:SimpleButton Content="Buy" x:Name="BuyButton" Click="BuyButton_Click" /> </dxlc:LayoutItem> <dxlc:LayoutItem VerticalAlignment="Stretch"> <dx:SimpleButton Content="Sell" x:Name="SelltButton" Click="SelltButton_Click" /> </dxlc:LayoutItem> </dxlc:LayoutGroup> </dxlc:LayoutGroup> </dxlc:LayoutControl> </UserControl>
No construtor MarketDepthControl, defina a fonte de ferramentas para SecurityPicker e a fonte de portfólios para PortfolioComboBox; no nosso caso, será Connector:
public MarketDepthControl() { InitializeComponent(); SecPicker.SecurityProvider = MainWindow.Instance.Connector; PortfolioComboBox.Portfolios = new PortfolioDataSource(MainWindow.Instance.Connector); }
Crie um manipulador de eventos de seleção de ferramentas no SecurityPicker. Nele, verificamos se a ferramenta recebida não é igual a zero. Se não for igual a zero, salvamos a ferramenta recebida em uma variável local; será útil para nós ao atualizar o vidro. Em seguida, limpamos e registramos a ferramenta recebida no Conector para receber um copo usando o método RegisterMarketDepth. Usando o método GetMarketDepth, obtemos o vidro atual do instrumento para atualizar o MarketDepthControl com ele.
private Security _selectedSecurity; private void SecPicker_SecuritySelected(Security security) { if (security == null) return; _selectedSecurity = security; MainWindow.Instance.Connector.RegisterMarketDepth(_selectedSecurity); var marketDepth = MainWindow.Instance.Connector.GetMarketDepth(_selectedSecurity); MarketDepth.UpdateDepth(marketDepth); }
Para que o vidro seja atualizado constantemente no construtor MarketDepthControl, inscrevamos o evento de alteração de vidro MarketDepthChanged no conector. No manipulador deste evento, verificaremos a qual ferramenta o vidro recebido pertence e, se ele pertence à ferramenta selecionada no SecurityPicker, atualizamos: MarketDepthControl.
public MarketDepthControl() { InitializeComponent(); SecPicker.SecurityProvider = MainWindow.Instance.Connector; PortfolioComboBox.Portfolios = new PortfolioDataSource(MainWindow.Instance.Connector); MainWindow.Instance.Connector.MarketDepthChanged += Connector_MarketDepthChanged; } private void Connector_MarketDepthChanged(MarketDepth marketDepth) { if (_selectedSecurity == null || marketDepth.Security != _selectedSecurity) return; MarketDepth.UpdateDepth(marketDepth); }
Na parte central do MainWindow, adicione o painel MarketDepthControl criado:
<dxlc:LayoutGroup HorizontalAlignment="Stretch" View="Tabs"> <!-- --> <dxlc:LayoutGroup Header="Securities"> <myxaml:SecurityGridControl x:Name="SecurityPanel" /> </dxlc:LayoutGroup> <dxlc:LayoutGroup Header="Portfolios"> <myxaml:PortfolioGridControl x:Name="PortfolioGridControl" /> </dxlc:LayoutGroup> <dxlc:LayoutGroup Header="Orders"> <myxaml:OrderGridControl x:Name="OrderGridControl" /> </dxlc:LayoutGroup> <dxlc:LayoutGroup Header="MyTrades"> <myxaml:MyTradeGridControl x:Name="MyTradeGridControl" /> </dxlc:LayoutGroup> <dxlc:LayoutGroup Header="MarketDepth"> <myxaml:MarketDepthControl x:Name="MarketDepthControl" /> </dxlc:LayoutGroup> </dxlc:LayoutGroup>
Nesta fase, você pode executar o programa e verificar a operação da atualização de óculos.
Crie um manipulador de eventos para clicar nos botões de compra e venda. Em cada manipulador, criamos um Pedido, nele indicamos a ferramenta selecionada no SecurityPicker, o portfólio selecionado no PortfolioComboBox, volume e preço do SpinEdit correspondente. Registre o aplicativo no Connector usando o método RegisterOrder.
private void BuyButton_Click(object sender, RoutedEventArgs e) { Order order = new Order() { Security = _selectedSecurity, Portfolio = PortfolioComboBox.SelectedPortfolio, Volume = SpinEditVolume.Value, Price = SpinEditPrice.Value, Direction = StockSharp.Messages.Sides.Buy, }; MainWindow.Instance.Connector.RegisterOrder(order); } private void SelltButton_Click(object sender, RoutedEventArgs e) { Order order = new Order() { Security = _selectedSecurity, Portfolio = PortfolioComboBox.SelectedPortfolio, Volume = SpinEditVolume.Value, Price = SpinEditPrice.Value, Direction = StockSharp.Messages.Sides.Sell, }; MainWindow.Instance.Connector.RegisterOrder(order); }
Ambos os processadores diferem apenas na direção do aplicativo.
Vamos alterar o valor de SpinEditPrice pelo preço da cotação selecionada ao selecionar cotações em um copo. Para fazer isso, crie um manipulador de eventos SelectionChanged para MarketDepthControl. No qual atualizaremos o valor de SpinEditPrice ao preço da cotação selecionada, se a cotação selecionada não for igual a zero.
private void MarketDepth_SelectionChanged(object sender, GridSelectionChangedEventArgs e) { if (MarketDepth.SelectedQuote == null) return; SpinEditPrice.Value = MarketDepth.SelectedQuote.Price; }
Execute para verificar:

Salvando dados do mercado
Para salvar portfólios, ferramentas, plataformas, precisamos da classe CsvEntityRegistry. É necessário refazer o local de armazenamento da entidade e chamar o método Init para carregá-los.
_csvEntityRegistry = new CsvEntityRegistry(_csvEntityRegistryDir); _csvEntityRegistry.Init();
Para economizar velas, promoções etc. precisamos de StorageRegistry:
_storageRegistry = new StorageRegistry { DefaultDrive = new LocalMarketDataDrive(_storageRegistryDir), };
Também precisamos do registro SnapshotRegistry de lojas de instantâneos:
_snapshotRegistry = new SnapshotRegistry(_snapshotRegistryDir);
Tudo isso passamos para o Connector quando ele é criado:
Connector = new Connector(_csvEntityRegistry, _storageRegistry, _snapshotRegistry) { IsRestoreSubscriptionOnReconnect = true, StorageAdapter = { DaysLoad = TimeSpan.FromDays(3) }, }; Connector.LookupAll();
Aqui também indicamos que o Conector será reconectado quando a conexão for desconectada e também indicará quantos dias de histórico será baixado. String Connector.LookupAll (); solicita dados disponíveis:
Depois de carregar o aplicativo, indo para a pasta Dados, veremos que novas pastas apareceram:

Ao reconectar, as barras de ferramentas e portfólios já estarão preenchidos.
Aproximamos o final da primeira parte sem problemas. Nesta fase, o programa permite exibir todos os dados de mercado disponíveis para nós. A próxima parte demonstrará o mais delicioso - ou seja, negociar no modo manual e automático.
Para continuar ...Autor : Ivan Zalutsky