Создание роботов с помощью S#. Часть 3. Реализация интерфейсов
Теперь приступим к реализации интерфейсов из 2 части статьи “Создание роботов с помощью S#. Часть 2. Базовый класс для всех стратегий”. Здесь западные специалисты рекомендуют вначале реализовывать тестовые версии интерфейсов. И писать сразу UNIT-тесты. Смотрите пример SampleEmulationTesting из библиотеки S#. Но ведь программист — это высшая форма существования разума во Вселенной, и поэтому, в принципе не может ошибаться. Сразу в бой!
QuikTraderBuilderРеализуем создание торговой системы Quik. Основной метод:
Code
public ITrader BuildTrader()
Нужно учитывать, что метод могут одновременно вызвать из разных потоков и создавать только 1 Quik.
Code
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Реализуем получение портфеля по его имени. Основной момент: ждем, когда будет инициализирована сумма на счете, т.к. она нужна для определения размера позиции.
Code
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.
Code
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Определение размера позиции по лимиту открытых позиций и гарантийному обеспечению.
Code
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Чтение и запись состояния стратегии из реестра.
Code
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Получение исторических свечей с сайта финама. Реализацию не выкладываю, т.к. много кода.
Code
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)
{
…
}
Автор статьи — Вадим Чижов