<?php
namespace App\Controller;
use Bundles\Orders\Entity\OrderFx;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\JsonResponse;
use App\Entity\User;
use App\Entity\Orders;
use App\Entity\CurrencyPair;
use App\Service\Matcher;
use Psr\Log\LoggerInterface;
use Symfony\Component\Cache\Adapter\AdapterInterface;
use Psr\Cache\CacheItemPoolInterface;
class ApiController extends ParentController {
/** @var array */
protected $errors = [];
protected $data = ['status' => 'success'];
protected $status = 'success';
/** @var User */
protected $user;
protected $helper;
protected $order;
const EXPOSURE_UP = 1;
const EXPOSURE_DOWN = 2;
protected $inputOrder = [
'currency_pair',
'amount',
'exposure_direction',
'date_start',
'date_stop',
'add_periods',
];
protected $inputFilters = [
'start_date_differs' => ['type' => 'int', 'limit' => 15],
'stop_date_differs' => ['type' => 'int', 'limit' => 15],
];
protected $currencyPair;
protected $amount;
protected $exposure;
protected $dateStart;
protected $dateStop;
public function getOrders(Request $request, LoggerInterface $dbLogger) {
if (!$this->validUser($request)) {
return $this->getResponse();
}
$dbLogger->info('getOrders', ['userId' => $this->user->getCustomer()->getId()]);
/** @var OrderFx[] $orders */
$orders = $this->getDoctrine()->getRepository(OrderFx::class)->findBy(['customer' => $this->user->getCustomer()->getId(), 'active' => true]);
if (!empty($orders)) {
$this->setData($orders);
}
return $this->getResponse();
}
public function getOrder(Request $request) {
if (!$this->validUser($request)) {
return $this->getResponse();
}
$orderPublicId = (int)$request->query->get('order_id');
if (empty($orderPublicId)) {
$this->setError('order id is empty');
return $this->getResponse();
}
$filter = ['customer' => $this->user->getCustomer()->getId(), 'publicId' => $orderPublicId, 'active' => true];
/** @var OrderFx $orderRecord */
$orderRecord = $this->getDoctrine()->getRepository(OrderFx::class)->findOneBy($filter);
if (empty($orderRecord)) {
$this->setError('The order info was not found');
return $this->getResponse();
}
$this->setData([$orderRecord]);
return $this->getResponse();
}
public function putOrder(Request $request) {
if (!$this->validUser($request)) {
return $this->getResponse();
}
if (empty($request->query->all())) {
$this->setError('No input data');
return $this->getResponse();
}
$this->inputOrder[] = 'id'; //papildomai privalomas orderio id
foreach ($this->inputOrder as $key) {
$value = $request->query->get($key);
if (empty($value)) {
$this->setError('Required param '.$key.' is empty');
}
$this->data[$key] = strtoupper($value);
}
if (!empty($this->getErrors()) || !$this->isDataValid()) {
return $this->getResponse();
}
$order = $this->order; //isDataValid() istato irasa, jei ji randa patikrinimo metu.
$order->setCurrencyPair($this->currencyPair);
$order->setAmount($this->amount);
$order->setCurrency($this->exposure);
$order->setDateStart($this->dateStart);
$order->setDateStop($this->dateStop);
$order->setEdited(date_create());
$this->getDoctrine()->getManager()->persist($order);
$this->getDoctrine()->getManager()->flush();
$this->data = ['status' => 'success', 'id' => $order->getPublicId()];
return $this->getResponse();
}
public function deleteOrder(Request $request) {
if (!$this->validUser($request)) {
return $this->getResponse();
}
if (empty($request->query->all())) {
$this->setError('No input data');
return $this->getResponse();
}
$this->inputOrder = ['id']; //privalomas tik orderio id
foreach ($this->inputOrder as $key) {
$value = $request->query->get($key);
if (empty($value)) {
$this->setError('Required param '.$key.' is empty');
}
$this->data[$key] = strtoupper($value);
}
if (!empty($this->data['id'])) {
$filter = ['customer' => $this->user->getCustomer()->getId(), 'publicId' => (int)$this->data['id'], 'active' => true];
/** @var OrderFx $orderRecord */
$this->order = $this->getDoctrine()->getRepository(OrderFx::class)->findOneBy($filter);
if (empty($this->order)) {
$this->setError('Order by Id '.$this->data['id'].' is not find in your active order list');
}
}
if (!empty($this->getErrors())) {
return $this->getResponse();
}
$order = $this->order;
$order->setActive(false);
$order->setEdited(date_create());
$this->getDoctrine()->getManager()->persist($order);
$this->getDoctrine()->getManager()->flush();
$this->data = ['status' => 'success', 'deleted_id' => $order->getPublicId()];
return $this->getResponse();
}
public function postOrder(Request $request) {
if (!$this->validUser($request)) {
return $this->getResponse();
}
if (empty($request->query->all())) {
$this->setError('No input data');
return $this->getResponse();
}
foreach ($this->inputOrder as $key) {
$value = $request->query->get($key);
if (empty($value)) {
$this->setError('Required param '.$key.' is empty');
}
$this->data[$key] = strtoupper($value);
}
if (!$this->isDataValid()) {
return $this->getResponse();
}
$order = new OrderFx();
$order->setCustomer($this->user->getCustomer());
$order->setCurrencyPair($this->currencyPair);
$order->setAmountBuy($this->amount);
$order->setCurrency($this->exposure);
$order->setCreated($this->dateStart);
$order->setDateMaturity($this->dateStop);
$order->setCreated(date_create());
// $nextPublicId = $this->getDoctrine()->getRepository(OrderFx::class)->getNextPublicId($order->getCustomer());
// $order->setPublicId($nextPublicId);
// $validator = new \App\Validators\Orders();
// $errors = $validator->validate($order);
// if (empty($errors)) {
$this->getDoctrine()->getManager()->persist($order);
$this->getDoctrine()->getManager()->flush();
$this->data = ['status' => 'success'];//, 'id' => $order->getPublicId()];
// }
return $this->getResponse();
}
public function getLiquidity(LoggerInterface $ordersLogger, CacheItemPoolInterface $cache, Request $request) {
if (!$this->validUser($request)) {
return $this->getResponse();
}
foreach ($this->inputOrder as $key) {
$value = $request->query->get($key);
if (empty($value)) {
$this->setError('Required param '.$key.' is empty');
}
$this->data[$key] = strtoupper($value);
}
$this->data['filter'] = [];
foreach ($this->inputFilters as $key => $params) {
$value = $request->query->get($key);
if (isset($value)) {
if (!empty($params['type']) && $params['type'] == 'int') {
$value = (int)$value;
}
if (!empty($params['limit']) && ($value < 0 || $value > $params['limit'])) {
$this->setError('Filter param '.$key.' value is out of limits (0-'.$params['limit'].')');
} else {
$this->data['filter'][$key] = $value;
}
}
}
if (!$this->isDataValid()) {
return $this->getResponse();
}
$periodsForCheck = $this->getPeriodsForCheck($request);
if (!empty($periodsForCheck)) {
foreach ($periodsForCheck as $period) {
//dump($period);
$order = new OrderFx();
$order->setCustomer($this->user->getCustomer());
$order->setCurrencyPair($this->currencyPair);
$order->setAmount($this->amount);
$order->setCurrency($this->exposure);
$order->setDateStart($period['dateStart']);
$order->setDateStop($period['dateStop']);
try {
$matcher = new Matcher(
$this->container,
$this->getDoctrine(),
$cache,
$ordersLogger,
$this->config,
$this->user,
[$order],
$this->data['filter']);
$matches = $matcher->start(false);
$distribution = [];
$rate = 1;
if (!empty($matches['orders'][0]['distribution'])) {
$distribution = $matches['orders'][0]['distribution'];
$rate = $matches['orders'][0]['rate'];
}
$coveredSumTotal = 0;
$coveredSumInternal = 0;
foreach ($distribution as $forwardType) {
foreach ($forwardType as $item) {
$coveredSumTotal += $item['amount'];
if ($item['entity']->getCustomer()->getId() == $this->user->getCustomer()->getId()) {
$coveredSumInternal += $item['amount'];
}
}
}
$coveredSumTotal = round(($coveredSumTotal * (float)$rate));
if ($coveredSumTotal > $this->amount) {
$coveredSumTotal = $this->amount;
}
$coverage = [
'amount' => [
'internal' => round($coveredSumInternal),
'external' => round($coveredSumTotal - $coveredSumInternal),
'total' => $coveredSumTotal,
'currency' => $order->getCurrencyPair()->getCurrencyNameByNumber($this->exposure),
],
'percentage' => [
'internal' => round($coveredSumInternal / $this->amount * 100, 1),
'external' => round(($coveredSumTotal - $coveredSumInternal) / $this->amount * 100, 1),
'total' => round($coveredSumTotal / $this->amount * 100, 1),
],
];
$period = $order->getDateStart()->format('Y-m-d').' - '.$order->getDateStop()->format('Y-m-d');
$this->data['coverage'][$period] = $coverage;
} catch
(\Exception $e) {
$this->setError('internal error. '.$e->getMessage());
}
}
}
return $this->getResponse();
}
public function getCurrencyPairs(Request $request) {
if (!$this->validUser($request)) {
return $this->getResponse();
}
$this->helper = $this->get('helper');
/** @var OrderFx[] $orders */
$orders = $this->getDoctrine()->getRepository(OrderFx::class)->findBy(['active' => 1]);
if (empty($orders)) {
$this->setError('No any currency pair was found');
return $this->getResponse();
}
$pairs = [];
foreach ($orders as $order) {
$pair = str_replace('/', '', $order->getCurrencyPair()->getName());
if (!in_array($pair, $pairs)) {
$pairs[] = $pair;
}
}
sort($pairs);
$this->data['currency_pairs'] = $pairs;
return $this->getResponse();
}
protected function isDataValid() {
if (!empty($this->data['id'])) {
$filter = ['customer' => $this->user->getCustomer()->getId(), 'publicId' => (int)$this->data['id'], 'active' => true];
/** @var OrderFx $orderRecord */
$this->order = $this->getDoctrine()->getRepository(OrderFx::class)->findOneBy($filter);
if (empty($this->order)) {
$this->setError('Order by Id '.$this->data['id'].' is not found in your active order list');
}
}
$currencies = str_split($this->data['currency_pair'], 3);
if (count($currencies) != 2) {
$this->setError('Wrong currency pair format: '.$this->data['currency_pair'].'. example: EURUSD, GBPUSD');
return $this->getResponse();
}
$currencyPairName = $currencies[0].'/'.$currencies[1];
$this->currencyPair = $this->getDoctrine()->getRepository(CurrencyPair::class)->findOneBy(['name' => $currencyPairName]);
if (empty($this->currencyPair)) {
$this->setError('Currency pair '.$this->data['currency_pair'].' is not supported');
return $this->getResponse();
}
$this->exposure = self::EXPOSURE_UP;
if (!in_array($this->data['exposure_direction'], ['UP', 'DOWN'])) {
$this->setError('exposure_direction value allowed: "UP", "DOWN"');
return $this->getResponse();
}
if ($this->data['exposure_direction'] == 'DOWN') {
$this->exposure = self::EXPOSURE_DOWN;
}
$this->dateStart = date_create($this->data['date_start']);
$this->dateStop = date_create($this->data['date_stop']);
// if (date_create() > $this->dateStart) {
// // $this->setError('Contract order start date must be in future');
// }
$dayOfWeekOfStartDate = $this->dateStart->format('w');
if ($dayOfWeekOfStartDate != 5) {
$this->setError('Order start date weekday is allowed only on fridays. Choose nearest friday date and repeat request');
}
// if (date_create() > $this->dateStop) {
// // $this->setError('Contract order finish date must be in future');
// }
if ($this->dateStart >= $this->dateStop) {
$this->setError('Contract order finish date must be later than start date');
}
$dayOfWeekOfStopDate = $this->dateStop->format('w');
if ($dayOfWeekOfStopDate != 5) {
$this->setError('Order stop date weekday is allowed only on fridays. Choose nearest friday date and repeat request');
}
$this->amount = (int)$this->data['amount'];
if ($this->amount < 10000 || $this->amount > (1000000000)) {
$this->setError('Amount value is out of limits (10 000 - 1 000 000 000)');
}
return empty($this->getErrors());
}
public function pageNotFound() {
$this->setError('Request unknown. Check api endpoint and try again');
return $this->getResponse(404);
}
public function apiDocs() {
return $this->redirect('/api/doc/index.html');
}
protected function validUser(Request $request) {
// look for header "Authorization: Bearer <token>"
if (!$request->headers->has('Authorization')
&& 0 !== strpos($request->headers->get('Authorization'), 'Bearer ')) {
$this->setError('Invalid authorization info');
return false;
}
// skip beyond "Bearer "
$authorizationHeader = $request->headers->get('Authorization');
$apiKey = substr($authorizationHeader, 7);
$ip = $request->getClientIp();
/** @var User $user */
$this->user = $this->getDoctrine()->getRepository(User::class)->findOneBy(['apiKey' => $apiKey]);
if (empty($this->user)) {
$this->setError('Invalid apiKey');
return false;
}
if (strpos($this->user->getAllowedIps(), $ip) === false) {
$this->setError('For this apiKey is not allowed IP: '.$ip);
return false;
}
return true;
}
protected function setData($orders) {
foreach ($orders as $order) {
$order = [
'id' => $order->getPublicId(),
'currency_pair' => str_replace('/', '', $order->getCurrencyPair()->getName()),
'amount' => $order->getAmount(),
'amount_currency' => $order->getCurrencyPair()->getCurrencyNameByNumber(1),
'exposure_direction' => ($order->getCurrency() > 1 ? 'UP' : 'DOWN'),
'date_start' => $order->getDateStart()->format('Y-m-d H:i'),
'date_stop' => $order->getDateStop()->format('Y-m-d H:i'),
'covered' => $order->getReserveAmount(),
];
$this->data['orders'][] = $order;
}
}
protected function getResponse($status = 200) {
if (!empty($this->getErrors())) {
$this->data['status'] = 'error';
$this->data['errors'] = $this->getErrors();
}
return new JsonResponse($this->data, $status);
}
protected function setError($error) {
return $this->errors[] = $error;
}
protected function getErrors() {
return $this->errors;
}
protected function getPeriodsForCheck($request) {
$addPeriods = $request->query->get('add_periods');
if (empty($addPeriods)) {
return [
[
'dateStart' => $this->dateStart,
'dateStop' => $this->dateStop,
],
];
}
$periods = [];
$dateStartString = $this->dateStart->format('Y-m-d');
$dateStopString = $this->dateStop->format('Y-m-d');
$dateStartBeforeWeek = date_create($dateStartString.' -1 week');
if ($dateStartBeforeWeek > date_create()) {
$periods[] = [
'dateStart' => $dateStartBeforeWeek,
'dateStop' => date_create($dateStopString.' -1 week'),
];
}
$periods[] = [
'dateStart' => $this->dateStart,
'dateStop' => $this->dateStop,
];
$periods[] = [
'dateStart' => $this->dateStart,
'dateStop' => date_create($dateStopString.' +1 week'),
];
$periods[] = [
'dateStart' => date_create($dateStartString.' +1 week'),
'dateStop' => $this->dateStop,
];
$periods[] = [
'dateStart' => date_create($dateStartString.' +1 week'),
'dateStop' => date_create($dateStopString.' +1 week'),
];
if (count($periods) < 3) {
$periods[] = [
'dateStart' => date_create($dateStartString.' +2 week'),
'dateStop' => date_create($dateStopString.' +2 week'),
];
}
return $periods;
}
}