Теперь приступим к реализации интерфейсов из 2 части статьи “Создание роботов с помощью S#. Часть 2. Базовый класс для всех стратегий”. Здесь западные специалисты рекомендуют вначале реализовывать тестовые версии интерфейсов. И писать сразу UNIT-тесты. Смотрите пример SampleEmulationTesting из библиотеки S#. Но ведь программист — это высшая форма существования разума во Вселенной, и поэтому, в принципе не может ошибаться. Сразу в бой!
QuikTraderBuilder Реализуем создание торговой системы Quik. Основной метод:
public ITrader BuildTrader()
Нужно учитывать, что метод могут одновременно вызвать из разных потоков и создавать только 1 Quik.
public sealed class QuikTraderBuilder : ITraderBuilder, ILogSource
{
private object lockObject = new object();
private Task task;
private bool isConnected;
public string Path { get; set; }
public string Login { get; set; }
public string Password { get; set; }
public string DdeServer { get; set; }
public string DllName { get; set; }
private string title;
public string Title
{
get { return title ?? Login; }
set { title = value; }
}
public ITrader Trader { get; private set; }
public ICandleManager CandleManager { get; private set; }
public event Action IsConnectedChanged;
public ITrader BuildTrader()
{
lock (lockObject)
{
if (task == null)
task = Task.Factory.StartNew(CreateTrader);
}
task.Wait();
return Trader;
}
private void CreateTrader()
{
QuikTerminal terminal = RunTerminalInternal();
QuikTrader quikTrader;
WriteLog(ErrorTypes.None, "Создаем шлюз взаимодействия с системой Quik.");
if (!string.IsNullOrEmpty(DdeServer))
{
if (!string.IsNullOrEmpty(DllName))
quikTrader = new QuikTrader(terminal.DirectoryName, DdeServer, DllName);
else
quikTrader = new QuikTrader(terminal.DirectoryName, DdeServer);
}
else
{
quikTrader = new QuikTrader(terminal.DirectoryName);
}
quikTrader.SecuritiesTable.Columns.Add(DdeSecurityColumns.MarginBuy);
quikTrader.SecuritiesTable.Columns.Add(DdeSecurityColumns.MinPrice);
quikTrader.SecuritiesTable.Columns.Add(DdeSecurityColumns.MaxPrice);
quikTrader.ReConnectionSettings.Interval = TimeSpan.FromSeconds(10);
quikTrader.ReConnectionSettings.WorkingTime = Exchange.Rts.WorkingTime;
quikTrader.Connected += () =>
{
UpdateIsConnected(null);
if (!quikTrader.IsExportRunning)
{
WriteLog(ErrorTypes.None, "Запускаем экспорт данных.");
quikTrader.StartExport();
Verify(quikTrader.Terminal);
}
};
quikTrader.ConnectionError += ex => UpdateIsConnected(ex);
quikTrader.Disconnected += () => UpdateIsConnected(null);
Trader = quikTrader;
CandleManager = new CandleManager(quikTrader);
WriteLog(ErrorTypes.None, "Производим подключение.");
quikTrader.Connect();
}
private void UpdateIsConnected(Exception ex)
{
var trader = Trader;
bool isConnectedNew = trader != null && trader.IsConnected;
if (isConnected != isConnectedNew)
{
isConnected = isConnectedNew;
WriteLog(ErrorTypes.None, isConnectedNew ? "Соединение подключено." : "Соединение отключено.");
RaiseIsConnectedChanged();
if (ex != null) WriteLog(ErrorTypes.Error, ex.Message);
}
}
private void RaiseIsConnectedChanged()
{
var handler = IsConnectedChanged;
if (handler != null) handler();
}
public void RunTerminal()
{
RunTerminalInternal();
}
private QuikTerminal RunTerminalInternal()
{
QuikTerminal terminal = QuikTerminal.Get(Path);
if (!terminal.IsLaunched)
terminal.Launch();
if (!terminal.IsConnected)
terminal.Login(Login, Password);
return terminal;
}
private void Verify(QuikTerminal terminal)
{
var errors = terminal.GetTableSettings();
foreach (var error in errors)
{
string message = string.Format("Таблица {0}. {1}", error.Table.Caption, error.Error.Message);
WriteLog(ErrorTypes.Warning, message);
}
}
public void Dispose()
{
if (task != null)
task.Dispose();
if (CandleManager != null)
CandleManager.Dispose();
if (Trader != null)
Trader.Dispose();
}
#region ILogSource
public Ecng.Collections.INotifyList<ILogSource> Childs
{
get { return null; }
}
private Guid id = Guid.NewGuid();
public Guid Id
{
get { return id; }
}
public event Action<LogMessage> Log;
public string Name
{
get { return Title; }
}
public ILogSource Parent
{
get { return null; }
}
private void RaiseLog(LogMessage logMessage)
{
var handler = Log;
if (handler != null)
handler(logMessage);
}
private void WriteLog(ErrorTypes errorType, string message)
{
LogMessage logMessage = new LogMessage(this, DateTime.Now, errorType, message);
RaiseLog(logMessage);
}
#endregion
}
Создание других торговых систем оставим читателю в качестве домашнего задания.
PortfolioSelector Реализуем получение портфеля по его имени. Основной момент: ждем, когда будет инициализирована сумма на счете, т.к. она нужна для определения размера позиции.
public class PortfolioSelector : IPortfolioSelector
{
private string title;
public string Title
{
get { return title ?? PortfolioName; }
set { title = value; }
}
public string PortfolioName { get; set; }
public Portfolio GetPortfolio(ITrader trader)
{
Portfolio result = FindPortfolio(trader);
if (result != null)
return result;
ManualResetEvent manualResetEvent = new ManualResetEvent(false);
Action<IEnumerable<Portfolio>> onPortfoliosChanged = p =>
{
if (FindPortfolio(trader) != null)
manualResetEvent.Set();
};
trader.NewPortfolios += onPortfoliosChanged;
trader.PortfoliosChanged += onPortfoliosChanged;
manualResetEvent.WaitOne(TimeSpan.FromSeconds(30));
trader.NewPortfolios -= onPortfoliosChanged;
trader.PortfoliosChanged -= onPortfoliosChanged;
result = FindPortfolio(trader);
return result;
}
private Portfolio FindPortfolio(ITrader trader)
{
return trader.Portfolios.FirstOrDefault(p => p.Name == PortfolioName && p.BeginAmount.Value != 0);
}
}
SecuritySelector Поиск инструмента по его коду. Реализация аналогична PortfolioSelector.
public class SecuritySelector : ISecuritySelector
{
private string title;
public string Title
{
get { return title ?? SecurityCode; }
set { title = value; }
}
public string SecurityCode { get; set; }
public Security GetSecurity(ITrader trader)
{
Security result = FindSecurity(trader);
if (result != null)
return result;
ManualResetEvent manualResetEvent = new ManualResetEvent(false);
Action<IEnumerable<Security>> onNewSecurities = s =>
{
if (FindSecurity(trader) != null)
manualResetEvent.Set();
};
trader.NewSecurities += onNewSecurities;
manualResetEvent.WaitOne(TimeSpan.FromSeconds(30));
trader.NewSecurities -= onNewSecurities;
result = FindSecurity(trader);
return result;
}
private Security FindSecurity(ITrader trader)
{
return trader.Securities.FirstOrDefault(s => s.Code == SecurityCode);
}
}
MarginVolumeSizer Определение размера позиции по лимиту открытых позиций и гарантийному обеспечению.
public class MarginVolumeSizer : IVolumeSizer
{
public double Ratio { get; set; }
public decimal MaxCapital { get; set; }
public MarginVolumeSizer()
{
MaxCapital = decimal.MaxValue;
}
public int GetVolume(Portfolio portfolio, Security security)
{
decimal capital = Math.Min(portfolio.BeginAmount.Value, MaxCapital);
int quantity = (int)(capital * (decimal)Ratio / security.MarginBuy);
return quantity;
}
}
RegistrySettingsProvider Чтение и запись состояния стратегии из реестра.
public class RegistrySettingsProvider : ISettingsProvider
{
public string SubKey { get; set; }
public RegistrySettingsProvider()
{
SubKey = @"Software\FinDirector";
}
public string ReadSetting(string name)
{
using (RegistryKey registryKey = Registry.CurrentUser.CreateSubKey(SubKey))
{
return (string)registryKey.GetValue(name);
}
}
public void WriteSetting(string name, string value)
{
using (RegistryKey registryKey = Registry.CurrentUser.CreateSubKey(SubKey))
{
registryKey.SetValue(name, value);
}
}
}
FinamHistoryCandleProvider Получение исторических свечей с сайта финама. Реализацию не выкладываю, т.к. много кода.
public class FinamHistoryCandleProvider : IHistoryCandleProvider
{
public int FinamSecurityCode { get; set; }
public TimeSpan TimeFrame { get; set; }
public FinamHistoryCandleProvider()
{
FinamSecurityCode = 17455;
TimeFrame = TimeSpan.FromHours(1);
}
public List<TimeFrameCandle> GetHistoryCandles(DateTime beginDate, DateTime endDate)
{
…
}
:[:::right Автор статьи — Вадим Чижов
Kommentare (12)
Anmelden oder Konto erstellen, Melden Sie sich an oder registrieren Sie sich, um einen Kommentar zu hinterlassen
Спасибо за статьи) Пытаюсь разбираться. Завис на реализации QuikTraderBuilder. Никак не разберусь почему монитор не получает сообщения от QuikTraderBuilder, видимо что-то с реализацией ILogSource.
Буду рад помощи)
Да я вроде так и делаю но почемуто все вообще виснет. Может проблема в версии 4.0.23? Вы какую версию используете?
ps: да и в логфайл сообщения от трейдера также не попадают( Мне кажется что гуи синхронизация здесь непричем. Скорее проблема с реализацией ILogSourse. Хотя могу только догадыватся так как не профи) FinDirector, подскажите, у вас то все работает или всетаки чтото меняли после публикации своих статей?
Тоже использую 4.0.23. Все работает. Могу только посоветовать создать новый пустой проект и наладить в нем логи, и в уже имеющемся проекте отключить логи. Тогда и разберетесь, что именно и почему не работает.
Даже не знаю в чем точто была проблема но все заработало. FinDirector, с вашего позволения могу выложить проект-шаблон целиком.
Добрый день! Попытался реализовать описанный выше каркас - появились ошибки реализации интерфейсов(ITraderBuilder, ILogSource). версия библиотеки 4.1.6. Можете ли вы поделиться реализацией проекта-шаблона или подсказать в чем там были траблы?
Присоединяюсь к serq, спасибо за статьи. Может все же выложите реализацию получения свечей с финама. Повис на этом уже который день...
Пока, я просто выбираю руками нужный инструмент и слушаю tcpmon'ом запрос браузра и оттуда достаю код.
Подскажите, пожалуйста, как получить список всех доступных кодов инструментов?
Огромное спасибо!
Смотрю, многие, в том числе и я, пользовались приведённым кодом, как основой. У многих в цитируемом тексте присутствует WriteLog :) Небольшое замечание: для подключения источника логирования не нужно реализовывать ILogSource. Достаточно унаследоваться от BaseLogReceiver<ВашКласс> и тогда можно замечательно использовать метод AddLog, и методы предоставляемые для него классом LoggingHelper: AddErrorLog, AddInfoLog и т.п. Правда для полного логирования необходимо добавить ещё
по вкусу :)
Т.е., вместо того, чтобы пытаться продавать техподдержку и обучение того, чем нельзя пользоватьсяновичкам (и, соответственно, не находит широкого использования) на продажу небесплатного продукта с бесплатной техподдержкой-обучением-раскруткой , которым все легко пользуются и с более широким вовлечением волонтеров-разработчиков.
VassilSanych - коварный и лукавый волонтёр-разработчик, который хочет бесплатно поучаствовать в развитии проекта StockSharp, поломав давно установившийся и отлаженный статус-кво!