Skip to content

Commit

Permalink
Временный копипаст
Browse files Browse the repository at this point in the history
  • Loading branch information
denismosolov committed Oct 13, 2020
1 parent 2a1bdc0 commit 0b26082
Show file tree
Hide file tree
Showing 5 changed files with 234 additions and 12 deletions.
8 changes: 7 additions & 1 deletion index.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
declare(strict_types=1);

use Oliver\{Application,Logger};
use Oliver\Reply\{Stocks,Orders,MarketOrderBuyStock,MarketOrderSellStock,Ping,ICanDo,Introduction,Repeat};
use Oliver\Reply\{Stocks,Orders,MarketOrderBuyStock,MarketOrderSellStock,Ping,ICanDo,Introduction,Repeat,Order};
use jamesRUS52\TinkoffInvest\{TIClient,TISiteEnum,TIException};
use Symfony\Component\DependencyInjection\{ContainerBuilder,Reference};

Expand Down Expand Up @@ -42,6 +42,11 @@ function main($event, $context): array
->register(Orders::class, Orders::class)
->addArgument(new Reference(TIClient::class))
;
$containerBuilder
->register(Order::class, Order::class)
->addArgument(new Reference(TIClient::class))
->addArgument(new Reference(Logger::class))
;
$containerBuilder
->register(MarketOrderBuyStock::class, MarketOrderBuyStock::class)
->addArgument(new Reference(TIClient::class))
Expand All @@ -61,6 +66,7 @@ function main($event, $context): array
new Repeat(),
$containerBuilder->get(Stocks::class),
$containerBuilder->get(Orders::class),
$containerBuilder->get(Order::class),
$containerBuilder->get(MarketOrderBuyStock::class),
$containerBuilder->get(MarketOrderSellStock::class),
);
Expand Down
3 changes: 3 additions & 0 deletions src/Reply/MarketOrderBuyStock.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
use jamesRUS52\TinkoffInvest\TIOrder;
use Psr\Log\LoggerInterface;

/**
* @deprecated use Reply\Order instead
*/
class MarketOrderBuyStock implements ReplyInterface
{
/**
Expand Down
3 changes: 3 additions & 0 deletions src/Reply/MarketOrderSellStock.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
use jamesRUS52\TinkoffInvest\TIOrder;
use Psr\Log\LoggerInterface;

/**
* @deprecated use Reply\Order instead
*/
// @todo: refactor,create abstract class with common methods
// MarketOrderSellStock and MarketOrderBuyStock
class MarketOrderSellStock implements ReplyInterface
Expand Down
208 changes: 208 additions & 0 deletions src/Reply/Order.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
<?php

declare(strict_types=1);

namespace Oliver\Reply;

use jamesRUS52\TinkoffInvest\TIClient;
use jamesRUS52\TinkoffInvest\TIInstrument;
use jamesRUS52\TinkoffInvest\TIOperationEnum;
use jamesRUS52\TinkoffInvest\TIException;
use jamesRUS52\TinkoffInvest\TIOrder;
use Psr\Log\LoggerInterface;

class Order implements ReplyInterface
{
/**
* Tinkoff Invest API Client
*/
private $client;

// @todo: better dependency injection
private $logger;

public const OPERATION_BUY = 'buy'; // @todo: use TIOperationEnum::BUY
public const OPERATION_SELL = 'sell';
public const UNIT_LOT = 'lot';

public function __construct(TIClient $client, LoggerInterface $logger)
{
$this->client = $client;
$this->logger = $logger;
}

public function handle(array $event): array
{
$intents = $event['request']['nlu']['intents'] ?? [];
$context = $event['state']['session']['context'] ?? [];
if (isset($intents['order'])) {
// @todo: choose a better design pattern
// ex. Reply/Order/(Buy|Sell)(Confirm...|Exec|Cancel) + validation
// buy usd

// @todo: class, read from intents or read from context
$slots = $intents['order']['slots']; // shortcut

$operation = $slots['operation']['value'] ?? '';
$unit = $slots['unit']['value'] ?? '';
$figi = $slots['figi']['value'] ?? '';
$amount = $slots['amount']['value'] ?? 0;
$buyLot = $operation === self::OPERATION_BUY && $unit === self::UNIT_LOT && $figi;
if ($buyLot) {
// @todo: validation
$instrument = $this->client->getInstrumentByFigi($figi);
$text = sprintf('заявка на покупку %s по рыночной цене,', $instrument->getName());
$text .= sprintf('тикер: %s,', $instrument->getTicker());
if ($unit === 'share') {
$text .= sprintf('количество акций: %d,', $amount);
} elseif ($unit === 'lot') {
$text .= sprintf('количество лотов: %d,', $amount);
} else {
throw new \Exception('Неизвестная единица измерения: ' . $unit);
}
$text .= 'для подтверждения скажите подтверждаю, для отмены скажите нет.';
return [
'session_state' => [
'text' => $text,
'context' => [
'order' => [
'operation' => $operation,
'figi' => $figi,
'type' => $instrument->getType(),
'amount' => $amount,
'unit' => $unit,
'ticker' => $instrument->getTicker(),
'name' => $instrument->getName(),
],
],
],
'response' => [
'text' => $text,
'tts' => $text,
'end_session' => false,
],
'version' => '1.0',
];
}
} else if (isset($intents['YANDEX.CONFIRM']) && isset($context['order'])) {
$operation = $context['order']['operation'] ?? '';
$unit = $context['order']['unit'] ?? '';
$figi = $context['order']['figi'] ?? '';
$amount = $context['order']['amount'] ?? 0;
// @todo: validation
$buyLot = $operation === self::OPERATION_BUY && $unit === self::UNIT_LOT && $figi;
if ($buyLot) {
try {
$order = $this->client->sendOrder(
$figi,
$amount,
TIOperationEnum::BUY
);
$text = $this->checkStatus($order); // @todo: refactor, move to a separate class
} catch (TIException $te) {
$text = $this->checkException($te);
}
$newContext = $context;
unset($newContext['order']);
return [
'session_state' => [
'text' => $text,
'context' => $newContext,
],
'response' => [
'text' => $text,
'tts' => $text,
'end_session' => false,
],
'version' => '1.0',
];
}
} else {
return [];
}
}

/**
* @todo: move to a separate class, can be re-used for limit orders
*/
private function checkStatus(TIOrder $order): string
{
switch ($order->getStatus()) {
// [ New, PartiallyFill, Fill, Cancelled, Replaced, PendingCancel, Rejected, PendingReplace, PendingNew ]
case 'Fill':
return 'заявка исполнена,'; // @fixme: $order->getPrice() всегда возвращает null
// @todo: добавь информацию о комиссии брокера
// @todo: добавь информацию о цене
case 'New':
return 'заявка на покупку создана,';
case 'PendingNew':
return 'заявка на покупку отправлена,';
case 'Rejected':
$text = 'заявка на покупку отклонена системой,';
// ОШИБКА: (579) Для выбранного финансового инструмента цена должна быть не меньше 126.02
$this->logger->debug($order->getRejectReason());
$this->logger->debug($order->getMessage());
if (
$order->getRejectReason() === 'Unknown' &&
preg_match('/ОШИБКА:\s+\(\d+\)/', $order->getMessage())
) {
$parts = false;
$parts = preg_split('/ОШИБКА:\s+\(\d+\)/', $order->getMessage());
if (is_array($parts)) {
$text .= end($parts);
} else {
$text .= 'неизвестная ошибка,';
}
}
// @todo: Specified security is not found [...]
return $text;
default:
// @todo: add test case
return 'произошло что-то непонятное, проверьте свои заявки и акции,';
}
}

/**
* @todo: move to a separate class, can be re-used for limit orders
*/
private function checkException(TIException $te): string
{
$this->logger->debug(
'Исключительная ситуация',
['exception' => $te]
);
$text = 'заявка на покупку отклонена системой,';
// Недостаточно активов для сделки [OrderNotAvailable]
if (preg_match('/\[OrderNotAvailable\]/', $te->getMessage())) {
$text = preg_replace('/\[OrderNotAvailable\]/', '', $te->getMessage());
if (is_null($text)) {
// @todo: ????
$text = 'неизвестная ошибка,';
}
if (preg_match('/Недостаточно активов для сделки/i', $text)) {
// @todo: test case
$text = 'недостаточно активов для сделки, ';
$text .= 'пополните счёт и попробуйте снова, ';
}
// Недостаточно заявок в стакане для тикера TCS [OrderBookException]
} elseif (preg_match('/\[OrderBookException\]/', $te->getMessage())) {
$text = preg_replace('/\[OrderBookException\]/', '', $te->getMessage());
if (is_null($text)) {
// @todo: ????
$text = 'неизвестная ошибка,';
}
if (preg_match('/Недостаточно заявок в стакане для тикера/i', $text)) {
// @todo: test case
$text = 'недостаточно заявок в стакане, ';
$text .= 'похоже биржа закрыта, попробуйте позже ';
}
} elseif (preg_match('/\[VALIDATION_ERROR\]/', $te->getMessage())) {
if (preg_match('/has invalid scale/', $te->getMessage())) {
$text .= 'недопустимый шаг цены, узнайте минимальный шаг цены для этого инструмента на бирже,';
}
} else {
$text = 'ошибка при взаимодействии с биржей, попробуйте позже,';
}
return $text;
}
}
24 changes: 13 additions & 11 deletions tests/Reply/OrderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ public function testBuy1LotUSDRUBConfirmation(): void
'start' => 3,
'end' => 4,
],
'value' => 'lot',
'value' => Order::UNIT_LOT,
],
'figi' => [
'type' => 'FIGI',
Expand Down Expand Up @@ -189,14 +189,15 @@ public function testBuy1LotUSDRUBOrder(): void
],
'state' => [
'session' => [
'text' => '',
'text' => '', // @todo: add
'context' => [
'order' => [
'operation' => 'buy',
'figi' => self::FIGI_USDRUB,
'type' => 'currency',
'type' => 'Currency',
'amount' => 1,
'unit' => 'lot',
'unit' => Order::UNIT_LOT,
'ticker' => 'USD000UTSTOM', // @todo: const
'name' => 'Доллар США',
],
],
Expand Down Expand Up @@ -259,7 +260,7 @@ public function testSell1LotUSDRUBConfirmation(): void
'start' => 3,
'end' => 4,
],
'value' => 'lot',
'value' => Order::UNIT_LOT,
],
'figi' => [
'type' => 'FIGI',
Expand All @@ -275,7 +276,7 @@ public function testSell1LotUSDRUBConfirmation(): void
'start' => 0,
'end' => 1,
],
'value' => 'sell',
'value' => Order::OPERATION_SELL,
],
]
]
Expand Down Expand Up @@ -344,14 +345,15 @@ public function testSell1LotUSDRUBOrder(): void
],
'state' => [
'session' => [
'text' => '',
'text' => '', // @todo: add
'context' => [
'order' => [
'operation' => 'sell',
'operation' => Order::OPERATION_SELL,
'figi' => self::FIGI_USDRUB,
'type' => 'currency',
'type' => 'Currency',
'amount' => 1,
'unit' => 'lot',
'unit' => Order::UNIT_LOT,
'ticker' => 'USD000UTSTOM', // @todo: const
'name' => 'Доллар США',
],
],
Expand All @@ -370,7 +372,7 @@ public function testSell1LotUSDRUBOrder(): void
->with(
$this->equalTo(self::FIGI_USDRUB),
$this->equalTo(1),
$this->equalTo(TIOperationEnum::BUY),
$this->equalTo(TIOperationEnum::SELL),
$this->equalTo(null) // I wonder if it works?
)->willReturn($order);
$logger = new Logger('');
Expand Down

0 comments on commit 0b26082

Please sign in to comment.