Skip to content
This repository has been archived by the owner. It is now read-only.

Commit

Permalink
Add interfaces, page for errors
Browse files Browse the repository at this point in the history
  • Loading branch information
UncleSema committed May 24, 2022
1 parent 6240ca3 commit 64960aa
Show file tree
Hide file tree
Showing 20 changed files with 913 additions and 420 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@
3. Добавить уникальное имя стратегии, чтобы выбирать её через UI.

Примеры
стратегий: [на стакане](https://github.com/UncleSema/ttb/blob/main/src/main/java/ru/unclesema/ttb/strategy/orderbook/OrderBookStrategyImpl.java)
стратегий: [на стакане](https://github.com/UncleSema/ttb/blob/main/src/main/java/ru/unclesema/ttb/strategy/orderbook/AsksBidsStrategy.java)
,
[на свечах](https://github.com/UncleSema/ttb/blob/main/src/main/java/ru/unclesema/ttb/strategy/rsi/RsiStrategy.java).

Expand Down
4 changes: 2 additions & 2 deletions src/main/java/ru/unclesema/ttb/ApplicationModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import org.springframework.stereotype.Component;
import ru.unclesema.ttb.strategy.CandleStrategy;
import ru.unclesema.ttb.strategy.Strategy;
import ru.unclesema.ttb.strategy.orderbook.OrderBookStrategyImpl;
import ru.unclesema.ttb.strategy.orderbook.AsksBidsStrategy;
import ru.unclesema.ttb.strategy.rsi.RsiStrategy;

import java.util.List;
Expand All @@ -30,7 +30,7 @@ public Object deserializeKey(String key, DeserializationContext ctxt) {

@Bean
public List<Strategy> availableStrategies() {
return List.of(new OrderBookStrategyImpl(), new RsiStrategy());
return List.of(new AsksBidsStrategy(), new RsiStrategy());
}

@Bean
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/ru/unclesema/ttb/client/InvestClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
import ru.unclesema.ttb.MarketSubscriber;
import ru.unclesema.ttb.model.User;
import ru.unclesema.ttb.model.UserMode;
import ru.unclesema.ttb.service.AnalyzeService;
import ru.unclesema.ttb.service.analyze.AnalyzeService;
import ru.unclesema.ttb.utility.Utility;

import java.math.BigDecimal;
Expand Down
17 changes: 17 additions & 0 deletions src/main/java/ru/unclesema/ttb/config/ExceptionResolver.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package ru.unclesema.ttb.config;

import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;

@Component
public class ExceptionResolver implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
return new ModelAndView("error-page", Map.of("msg", ex.getMessage()));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
Expand Down Expand Up @@ -69,10 +68,4 @@ public String sellAll(String accountId) {
service.sellAll(accountId);
return "redirect:/" + accountId;
}

@GetMapping("/app-error")
public String exceptionHandler(Model model, Exception e) {
model.addAttribute("msg", e.getMessage());
return "error-page";
}
}
271 changes: 16 additions & 255 deletions src/main/java/ru/unclesema/ttb/service/ApplicationService.java

Large diffs are not rendered by default.

323 changes: 323 additions & 0 deletions src/main/java/ru/unclesema/ttb/service/ApplicationServiceImpl.java

Large diffs are not rendered by default.

35 changes: 35 additions & 0 deletions src/main/java/ru/unclesema/ttb/service/analyze/AnalyzeService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package ru.unclesema.ttb.service.analyze;

import ru.tinkoff.piapi.contract.v1.Candle;
import ru.tinkoff.piapi.contract.v1.LastPrice;
import ru.tinkoff.piapi.contract.v1.Operation;
import ru.tinkoff.piapi.contract.v1.OrderDirection;
import ru.unclesema.ttb.model.User;

import java.math.BigDecimal;
import java.util.List;

/**
* Сервис, отвечающий за режим анализа.
*/
public interface AnalyzeService {
/**
* Метод добавляет новую операцию, совершенную во время симуляции работы стратегии
*/
void processOrder(User user, String figi, long quantity, BigDecimal price, OrderDirection direction);

/**
* @return Совершённые стратегией операции
*/
List<Operation> getOperations(User user);

/**
* @return Цену последней добавленной свечи
*/
LastPrice getLastPrice(User user, String figi);

/**
* Добавить новую свечу
*/
void processNewCandle(User user, Candle candle);
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package ru.unclesema.ttb.service;
package ru.unclesema.ttb.service.analyze;

import com.google.protobuf.Timestamp;
import org.springframework.stereotype.Service;
Expand All @@ -16,14 +16,15 @@
* Сервис, отвечающий за режим анализа.
*/
@Service
public class AnalyzeService {
public class AnalyzeServiceImpl implements AnalyzeService {
private final Map<User, List<Operation>> operations = new HashMap<>();
private final Map<User, Map<String, LastPrice>> lastPriceByUser = new HashMap<>();
private final Map<User, Timestamp> timeByUser = new HashMap<>();

/**
* Метод добавляет новую операцию, совершенную во время симуляции работы стратегии
*/
@Override
public void processOrder(User user, String figi, long quantity, BigDecimal price, OrderDirection direction) {
OperationType operationType = (direction == OrderDirection.ORDER_DIRECTION_BUY ? OperationType.OPERATION_TYPE_BUY : OperationType.OPERATION_TYPE_SELL);
operations.computeIfAbsent(user, u -> new ArrayList<>());
Expand All @@ -39,15 +40,17 @@ public void processOrder(User user, String figi, long quantity, BigDecimal price
}

/**
* @return Соверщенные стратегией операции
* @return Совершённые стратегией операции
*/
@Override
public List<Operation> getOperations(User user) {
return operations.getOrDefault(user, List.of());
}

/**
* @return Цену последней добавленной свечи
*/
@Override
public LastPrice getLastPrice(User user, String figi) {
return lastPriceByUser
.getOrDefault(user, Map.of())
Expand All @@ -57,6 +60,7 @@ public LastPrice getLastPrice(User user, String figi) {
/**
* Метод добавляет новую свечу
*/
@Override
public void processNewCandle(User user, Candle candle) {
addLastPrice(user, Utility.toLastPrice(candle));
addTimestampForUser(user, candle.getTime());
Expand Down
163 changes: 39 additions & 124 deletions src/main/java/ru/unclesema/ttb/service/front/FrontService.java
Original file line number Diff line number Diff line change
@@ -1,190 +1,105 @@
package ru.unclesema.ttb.service.front;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import ru.tinkoff.piapi.contract.v1.Instrument;
import ru.tinkoff.piapi.contract.v1.MoneyValue;
import ru.tinkoff.piapi.contract.v1.Operation;
import ru.tinkoff.piapi.contract.v1.OperationType;
import ru.unclesema.ttb.client.InvestClient;
import ru.unclesema.ttb.model.User;
import ru.unclesema.ttb.service.PriceService;
import ru.unclesema.ttb.service.UserService;
import ru.unclesema.ttb.strategy.CandleStrategy;
import ru.unclesema.ttb.strategy.Strategy;
import ru.unclesema.ttb.utility.Utility;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
* Сервис для работы с UI
*/
@Service
@RequiredArgsConstructor
@Slf4j
public class FrontService {
private final List<Strategy> availableStrategies;
private final List<CandleStrategy> availableCandleStrategies;
private final PriceService priceService;
private final UserService userService;
private final InvestClient investClient;

public interface FrontService {
/**
* @return всех существующих пользователей
*/
public List<User> getAllUsers() {
return userService.getAllUsers();
}
List<User> getAllUsers();

/**
* @return пользователь с заданным accountId
* @throws IllegalArgumentException, если пользователь не найден
*/
public User findUser(String accountId) {
return userService.findUserByAccountId(accountId);
}
User findUser(String accountId);

/**
* @return имя заданного инструмента
*/
public String getInstrumentName(User user, String figi) {
return getInstrument(user, figi).getName();
}
String getInstrumentName(User user, String figi);

/**
* @return заданный инструмент
*/
public Instrument getInstrument(User user, String figi) {
return investClient.getInstrument(user, figi);
}
Instrument getInstrument(User user, String figi);

/**
* Конвертирует <code>LastPrice</code> в <code>String</code>
*/
public String lastPriceToString(User user, BigDecimal quantity, String figi) {
String currency = getInstrument(user, figi).getCurrency();
BigDecimal lastPrice = priceService.getLastPrice(user, figi);
return lastPrice.multiply(quantity).doubleValue() + " " + currency.toUpperCase();
}
String lastPriceToString(User user, BigDecimal quantity, String figi);

/**
* Конвертирует <code>MoneyValue</code> в <code>String</code>
*/
public String moneyValueToString(User user, String figi, MoneyValue value) {
Instrument instrument = getInstrument(user, figi);
return Utility.toBigDecimal(value).doubleValue() + " " + instrument.getCurrency().toUpperCase();
}
String moneyValueToString(User user, String figi, MoneyValue value);

/**
* @return отчёт по стратегии
*/
public StrategyStatement getStatement(User user) {
Map<String, BigDecimal> benefitByCurrency = new HashMap<>();
// Обрабатываем ещё не проданные бумаги
for (var entry : priceService.getRemainingInstruments(user).entrySet()) {
var instrument = investClient.getInstrument(user, entry.getKey());
var amount = entry.getValue();
var benefit = benefitByCurrency.getOrDefault(instrument.getCurrency(), BigDecimal.ZERO);
benefit = benefit.add(priceService.getLastPrice(user, instrument.getFigi()).multiply(BigDecimal.valueOf(amount)));
benefitByCurrency.put(instrument.getCurrency(), benefit);
}
var operations = getOperations(user);
// Обрабатываем уже совершённые операции
for (var op : operations) {
if (op.getInstrumentType().equalsIgnoreCase("currency")) continue;
var instrument = getInstrument(user, op.getFigi());
var benefit = benefitByCurrency.getOrDefault(instrument.getCurrency(), BigDecimal.ZERO);
var payment = Utility.toBigDecimal(op.getPayment()).abs();
if (op.getOperationType() == OperationType.OPERATION_TYPE_BUY) {
benefit = benefit.subtract(payment);
} else if (op.getOperationType() == OperationType.OPERATION_TYPE_SELL) {
benefit = benefit.add(payment);
} else if (op.getOperationType() == OperationType.OPERATION_TYPE_BROKER_FEE) {
benefit = benefit.subtract(payment);
} else {
log.error("Неизвестная операция {}", op.getOperationType());
}
benefitByCurrency.put(instrument.getCurrency(), benefit);
}
return new StrategyStatement(benefitByCurrency, operations);
}
StrategyStatement getStatement(User user);

/**
* Конвертирует заработанные стратегией средства в <code>String</code>
*/
public String printBenefits(User user) {
var statement = getStatement(user);
var benefitByCurrency = statement.benefitByCurrency();
if (benefitByCurrency.isEmpty()) {
return "пока ничего :(";
}
return benefitByCurrency.entrySet()
.stream()
.map(entry -> entry.getValue().doubleValue() + " " + entry.getKey().toUpperCase())
.collect(Collectors.joining(", "));
}
String printBenefits(User user);

/**
* Конвертирует <code>OperationType</code> в <code>String</code>
*/
public String operationTypeToString(OperationType operationType) {
if (operationType == OperationType.OPERATION_TYPE_BUY) {
return "Покупка";
}
if (operationType == OperationType.OPERATION_TYPE_SELL) {
return "Продажа";
}
if (operationType == OperationType.OPERATION_TYPE_INPUT) {
return "Пополнение";
}
if (operationType == OperationType.OPERATION_TYPE_OUTPUT) {
return "Снятие";
}
if (operationType == OperationType.OPERATION_TYPE_BROKER_FEE) {
return "Комиссия брокера";
}
return operationType.name();
}
String operationTypeToString(OperationType operationType);

/**
* @return возвращает дату операции
*/
public LocalDateTime getDate(Operation op) {
return LocalDateTime.ofInstant(Utility.toInstant(op.getDate()), ZoneId.systemDefault());
}
LocalDateTime getDate(Operation op);

public Map<String, Long> getRemainingInstruments(User user) {
return priceService.getRemainingInstruments(user);
}
/**
* Получение купленных, но ещё не проданных стратегией, бумаг.
*/
Map<String, Long> getRemainingInstruments(User user);

public boolean isActive(User user) {
return userService.isActive(user);
}
/**
* Проверка на то, что пользователь активный
*/
boolean isActive(User user);

public List<Strategy> getAvailableStrategies() {
return availableStrategies;
}
/**
* Получить доступные для выбора стратегии.
*/
List<Strategy> getAvailableStrategies();

public List<CandleStrategy> getAvailableCandleStrategies() {
return availableCandleStrategies;
}
/**
* Получить доступные для выбора стратегии, использующие свечи.
*/
List<CandleStrategy> getAvailableCandleStrategies();

public List<Operation> getOperations(User user) {
return investClient.getOperations(user);
}
/**
* Получить все операции, произведенных с начала работы приложения по текущий момент.
*/
List<Operation> getOperations(User user);

public BigDecimal getBalance(User user) {
return priceService.getBalance(user);
}
/**
* Получить количество потраченных стратегией рублей.
*/
BigDecimal getBalance(User user);

public boolean contains(String accountId) {
return userService.contains(accountId);
}
/**
* Проверка на то, что пользователь с данным <code>accountId</code> существует.
*/
boolean contains(String accountId);
}
Loading

0 comments on commit 64960aa

Please sign in to comment.