/*
* Neiro Technology 2011-2014
*/
package psconsole;
import java.awt.Color;
import java.awt.EventQueue;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import java.util.StringTokenizer;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import psconsole.ServerConsole.Terminal;
import textsockets.PSPackage;
/**
* Менеджер задач. Содержит список активных и исторических задач.
* Автоматом экспортирует всё в xml.
* @author deathNC
*/
public class TaskManager {
public TaskManager(MainForm form) {
this.form = form;
taskList = new LinkedList<>();
startThreads();
}
private void startThreads() {
// -- запуск потока-менеджера исполнения задач
Thread t = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
threadManagerTaskExecuting();
} catch (Exception e) {
e.printStackTrace();
}
}
}
});
t.setDaemon(true);
t.start();
}
/**
* Нельзя вызывать данный метод! Он вызывается автоматически
* потоком-менеджером задач
*/
private void threadManagerTaskExecuting() throws Exception {
Thread.sleep(200);
int i = 0;
int stat;
long tm = form.timeCurrent;
boolean needForRefresh = false;
synchronized (taskList) {
Iterator<Task> iter = taskList.iterator();
while ( (64 > i++) && iter.hasNext()) {
Task task = iter.next();
// -- отправка задач на исполнение
// -- по типу запуска определение, пора ли запускать
stat = 0;
if (task.startNow) {
// если с момента старта не прошло более 30 сек, отправляем
if (tm - task.timeStart > 30000) {
stat = 1;
if (task.status == TaskStatus.TS_Sending) {
task.status = TaskStatus.TS_Finished;
needForRefresh = true;
}
}
} else {
// если за 60 секунд
if (task.timeStart - tm > 60000) stat = -1;
else if (task.timeStart - tm < 5000) stat = 1;
}
if (stat == 0) {
if (task.status != TaskStatus.TS_Sending)
needForRefresh = true;
task.status = TaskStatus.TS_Sending;
} else if (stat < 0) {
if (task.status != TaskStatus.TS_Waiting)
needForRefresh = true;
task.status = TaskStatus.TS_Waiting;
} else if (task.status != TaskStatus.TS_Running) {
if (task.status != TaskStatus.TS_Finished)
needForRefresh = true;
task.status = TaskStatus.TS_Finished;
}
if (stat != 0) continue;
String s = task.getOrders();
for (TaskAccInfo inf : task.accountList) {
if (inf.status != TaskAccInfo.TS_Sending) continue;
// если задача не отправлена, отправляем
Terminal conn = form.server.get(inf.accID);
if (conn == null) continue;
if (tm - inf.lastTimeSending < 3000) continue; // чтобы не спамить по 10 раз в сек
inf.lastTimeSending = tm;
// упаковка задачи
PSPackage pkg = new PSPackage();
pkg.set("cmd", "execute-task");
pkg.set("id", task.id);
pkg.set("tm", Long.toString(task.timeStart/1000));
pkg.set("startNow", Boolean.toString(task.startNow));
pkg.set("orders", s);
conn.sendPackage(pkg);
}
}
}
// -- если требуется обновление таблицы задач
if (needForRefresh) {
try {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
form.taskModel.refreshTaskList(TaskManager.this);
}
});
} catch (Exception e) {}
}
}
/**
* Добавление нового элемента в список задач
* @param task новый элемент
*/
public void addTask(Task task) {
synchronized (taskList) {
taskList.addFirst(task);
}
}
/**
* Проверка принадлежнасти ордера задаче по комментарию (OrderComment()).
* @param comment комментарий ордера MT4
* @return null, если ордер не идентифицирован, иначе - структура с задачей
* и номером ордера в списке
*/
public ReciveOrderCheck checkTaskOrder(String comment) {
int i1 = comment.indexOf('{');
int i2 = comment.indexOf('}', i1);
if (i1 < 0 || i2 < 0) return null;
StringTokenizer st = new StringTokenizer(comment.substring(i1 + 1, i2), ";");
String id = st.nextToken();
Iterator<Task> i = taskList.descendingIterator();
while (i.hasNext()) {
Task task = i.next();
if (id.equals(task.id)) {
try {
ReciveOrderCheck ans = new ReciveOrderCheck();
ans.task = task;
ans.index = Integer.parseInt(st.nextToken());
return ans;
} catch (Exception e) {}
return null;
}
}
return null;
}
/**
* Поиск задачи по идентификатору
* @param id идентификатор
* @return null, если задача не найдена
*/
public Task taskByID(String id) {
Iterator<Task> i = taskList.descendingIterator();
while (i.hasNext()) {
Task task = i.next();
if (task.id.equals(id)) return task;
}
return null;
}
/**
* Полный список задач. И уже выполненных, и исполняемых, и запланированных.
* Сортировка идёт в обратном порядке - новые элементы добавляются не в
* конец списка, а в начало.
* <b>Внимание!</b> Обращения к списку должны быть засинхронизированы - объект
* taskList не является thread-safe!
*/
public final LinkedList<Task> taskList;
/**
* Класс главной формы приложения. Использование разрешено только через
* поток диспетчеризации сообщений (синхронно с form)
*/
private final MainForm form;
public static Random rand = new Random();
/**
* Описание задачи. Содержит список ордеров и идентификационные данные.
*/
public static class Task {
public Task(String id) {
this.id = id;
orderList = new ArrayList<>();
accountList = new ArrayList<>();
startNow = false;
status = TaskStatus.TS_Waiting;
color = new Color(220 + rand.nextInt(35),
220 + rand.nextInt(35), 220 + rand.nextInt(35));
}
/**
* Экспорт данной задачи в xml-node
* @param node ветвь, выделенная исключительно только под данную задачу
*/
public void exportToNode(Element node) {
Document doc = node.getOwnerDocument();
Element item = doc.createElement("parameters");
item.setAttribute("name", name);
item.setAttribute("id", id);
item.setAttribute("status", status.name());
item.setAttribute("time", Long.toString(timeStart));
node.appendChild(item);
// -- accounts data export
item = doc.createElement("accounts");
node.appendChild(item);
for (TaskAccInfo accInfo : accountList) {
Element accNode = doc.createElement("acc");
accNode.setAttribute("id", Long.toString(accInfo.accID));
accNode.setAttribute("status", Integer.toString(accInfo.status));
item.appendChild(accNode);
}
// -- orders data export
Element root = doc.createElement("orders");
node.appendChild(root);
for (TaskOrder o : orderList) {
item = doc.createElement("o");
item.setAttribute("smb", o.symbol);
item.setAttribute("type", o.type.name());
item.setAttribute("size", Double.toString(o.size));
item.setAttribute("dev", Integer.toString(o.deviation));
item.setAttribute("sl", Integer.toString(o.stoploss));
item.setAttribute("tp", Integer.toString(o.takeprofit));
item.setAttribute("trail", Integer.toString(o.trailing));
item.setAttribute("time", Integer.toString(o.time));
item.setAttribute("useBreakeven", Boolean.toString(o.useBreakeven));
root.appendChild(item);
}
}
/**
* Импорт всех данных, касаемых данной задачи. Внимание - id задачи не
* импортируется! id задаётся через конструктор!
* @param node ветвь, содержащая в себе отдельную задачу
*/
public void importFromNode(Element node) {
orderList.clear();
accountList.clear();
Element item;
// -- parameters
NodeList nl = node.getElementsByTagName("parameters");
if (nl.getLength() > 0) {
item = (Element)nl.item(0);
try {
name = item.getAttribute("name");
timeStart = Long.parseLong(item.getAttribute("time"));
String buffer = item.getAttribute("status");
if (!buffer.isEmpty()) status = TaskStatus.valueOf(buffer);
} catch (Exception e) { e.printStackTrace(); }
}
// -- account data
nl = node.getElementsByTagName("accounts");
if (nl.getLength() > 0) {
item = (Element)nl.item(0);
nl = item.getElementsByTagName("acc");
for (int i = 0; i < nl.getLength(); ++i) {
try {
Element a = (Element)nl.item(i);
TaskAccInfo acc = new TaskAccInfo(Long.parseLong(a.getAttribute("id")));
acc.status = Integer.parseInt(a.getAttribute("status"));
accountList.add(acc);
} catch (Exception e) { e.printStackTrace(); }
}
}
// -- orders
nl = node.getElementsByTagName("orders");
if (nl.getLength() > 0) {
item = (Element)nl.item(0);
nl = item.getElementsByTagName("o");
for (int i = 0; i < nl.getLength(); ++i) {
item = (Element)nl.item(i);
try {
TaskOrder o = new TaskOrder(item.getAttribute("smb"));
o.type = TaskOrder.Type.valueOf(item.getAttribute("type"));
o.size = Double.parseDouble(item.getAttribute("size"));
o.deviation = Integer.parseInt(item.getAttribute("dev"));
o.stoploss = Integer.parseInt(item.getAttribute("sl"));
o.takeprofit = Integer.parseInt(item.getAttribute("tp"));
o.trailing = Integer.parseInt(item.getAttribute("trail"));
o.time = Integer.parseInt(item.getAttribute("time"));
o.useBreakeven = Boolean.parseBoolean(item.getAttribute("useBreakeven"));
orderList.add(o);
} catch (Exception e) { e.printStackTrace(); }
}
}
}
/**
* Получить список ордеров одной строкой. Нужно для отправки в MT4
* @return
*/
public String getOrders() {
StringBuilder sb = new StringBuilder();
for (TaskOrder order : orderList) {
sb.append(order.deviation); sb.append(',');
sb.append(order.size); sb.append(',');
sb.append(order.symbol); sb.append(',');
sb.append(order.stoploss); sb.append(',');
sb.append(order.takeprofit); sb.append(',');
sb.append(order.time); sb.append(',');
sb.append(order.trailing); sb.append(',');
sb.append(order.type.toString()); sb.append(',');
sb.append(order.useBreakeven); sb.append(',');
sb.append(";");
}
return sb.toString();
}
/**
* Импорт задачи из xml-элемента node. Так же сразу создаёт экземпляр
* класса Task.
* @param node ветвь, выделенная именно под данную задачу
* @return null, если в содержимом ветви есть ошибки
*/
public static Task createFromNode(Element node) {
Element item;
String id = null;
// -- parameters
NodeList nl = node.getElementsByTagName("parameters");
if (nl.getLength() > 0) {
item = (Element)nl.item(0);
try { id = item.getAttribute("id"); }
catch (Exception e) { e.printStackTrace(); }
}
if (id == null) return null;
Task task = new Task(id);
task.importFromNode(node);
return task;
}
@Override
public String toString() {
return name;
}
/**
* Список ордеров в данной задаче
*/
public final List<TaskOrder> orderList;
/**
* Список торговых счетов, на которых задача должна выполниться
*/
public final List<TaskAccInfo> accountList;
/**
* Имя задачи (не имеет значения)
*/
public String name;
/**
* Уникальный идентификатор задачи
*/
public final String id;
/**
* Время отправки ордеров на рынок
*/
public long timeStart;
/**
* Флаг: исполнять задачу сразу, если true
*/
public boolean startNow;
/**
* Цвет фона для ордеров данной задачи (в таблице real-time торговли)
*/
public final Color color;
/**
* Статус задачи
*/
public TaskStatus status;
}
/**
* Описание ордера в задаче. Такой ордер описывает только приказ на
* открытие, и никак не описывает открытые или исторические ордера!
*/
public static class TaskOrder {
/**
* Все поля заполняются стандартными данными
* @param symbol валютная пара
*/
public TaskOrder(String symbol) {
this.symbol = symbol;
type = Type.Both;
size = 15;
deviation = 90;
stoploss = 110;
takeprofit = 750;
trailing = 85;
time = 35;
useBreakeven = true;
}
public String symbol;
/**
* Тип сделки
*/
public Type type;
/**
* Размер сделки в процентах от текущего депозита.
*/
public double size;
/**
* Отклонение от текущей рыночной цены. Указывается в пунктах
*/
public int deviation;
/**
* Уровень стоплоса. Указывается в пунктах
*/
public int stoploss;
/**
* Уровень тейкпрофита. Указывается в пунктах.
*/
public int takeprofit; // pips
/**
* Трейлинг-стоп. Указывается в пунктах.
*/
public int trailing; // pips
/**
* Время существования сделки. Указывается в секундах.
*/
public int time; // seconds
/**
* Использовать уровень безубытка. Это значит, что будет установлен
* минимально возможный Stoploss, по преодолению которого ордер
* закроется с profit'ом равным нулю.
*/
public boolean useBreakeven;
@Override
public String toString() {
return symbol;
}
public static enum Type { Buy, Sell, Both }
}
/**
* Класс предназначен для информирования о том, на каких счетах следует
* запускать задачу, и на каких счетах она уже запущена
*/
public static class TaskAccInfo {
/**
* Статус задачи: отправка в торговый терминал
*/
public final static int TS_Sending = 0;
/**
* Статус задачи: запущена в торговом терминале
*/
public final static int TS_Started = 1;
/**
* Статус задачи: завершена в торговом терминале
*/
public final static int TS_Finished = 2;
public TaskAccInfo(long accountID) {
this.accID = accountID;
status = TS_Sending;
lastTimeSending = 0;
}
/**
* Идентификатор торгового счёта
*/
public final long accID;
/**
* Статус исполнения задачи
*/
public int status;
/**
* Время последней попытки отправки
*/
public long lastTimeSending;
}
/**
* Результат функции checkTaskOrder().
*/
public static class ReciveOrderCheck {
Task task = null;
int index = -1;
}
/**
* Статуст задачи в целом
*/
public enum TaskStatus {
/**
* Ожидание - состояние, в котором система ждёт указанног овремени
* отправки ордеров. Вернее, ожидание закончится за минуту до указанного
* времени, и начнётся отправка ордеров.
*/
TS_Waiting ("Ожидание"),
/**
* Отправка ордеров длится 30 секунд для моментальных задач, и 55 для
* отложенных. При этом, для отложенных отправка выполняется не после
* указанного времени, и за минуту до указанного времени.
*/
TS_Sending ("Отправка"),
/**
* Означает, что mql-робот принял пакет ордеров и начал его обработку.
*/
TS_Running ("Выполняется"),
/**
* Означает, что задача завершена. Либо об этом оповестил mql, либо
* эта задача вовсе не попала в торговый терминал - она в любом случае
* будет завершённой.
*/
TS_Finished ("Завершена");
private TaskStatus(String description) {
this.description = description;
}
/**
* Описание статуса (для вывода на экран)
*/
public final String description;
}
}