| @@ -0,0 +1,3 @@ | |||||
| /.drone.yml | |||||
| /docker | |||||
| /docker-volumes | |||||
| @@ -1 +1,2 @@ | |||||
| .idea | |||||
| .idea | |||||
| docker-volumes/ | |||||
| @@ -3,4 +3,13 @@ services: | |||||
| php: | php: | ||||
| image: arbitry:local | image: arbitry:local | ||||
| volumes: | volumes: | ||||
| - ./project:/srv/project | |||||
| - ./project:/projects/main | |||||
| mongo: | |||||
| command: "--wiredTigerCacheSizeGB 0.25" | |||||
| environment: | |||||
| MONGO_INITDB_ROOT_USERNAME: root | |||||
| MONGO_INITDB_ROOT_PASSWORD: q12asd3 | |||||
| MONGO_INITDB_DATABASE: arbitry | |||||
| volumes: | |||||
| - ./docker-volumes/mongo:/data/db | |||||
| @@ -3,4 +3,9 @@ services: | |||||
| php: | php: | ||||
| environment: | environment: | ||||
| TZ: UTC | TZ: UTC | ||||
| TERM: xterm | |||||
| TERM: xterm | |||||
| mongo: | |||||
| image: mongo | |||||
| restart: unless-stopped | |||||
| ports: | |||||
| - 27017:27017 | |||||
| @@ -7,10 +7,23 @@ ENV TERM xterm | |||||
| RUN apk add --update --no-cache \ | RUN apk add --update --no-cache \ | ||||
| libcurl \ | libcurl \ | ||||
| bash \ | bash \ | ||||
| curl | |||||
| curl \ | |||||
| git \ | |||||
| alpine-sdk \ | |||||
| openssl-dev | |||||
| ENV EXT_MONGODB_VERSION=1.7.4 | |||||
| RUN docker-php-source extract \ | |||||
| && git clone --branch $EXT_MONGODB_VERSION --depth 1 https://github.com/mongodb/mongo-php-driver.git /usr/src/php/ext/mongodb \ | |||||
| && cd /usr/src/php/ext/mongodb && git submodule update --init \ | |||||
| && docker-php-ext-install mongodb | |||||
| RUN docker-php-ext-install bcmath | RUN docker-php-ext-install bcmath | ||||
| COPY --from=composer /usr/bin/composer /usr/bin/composer | COPY --from=composer /usr/bin/composer /usr/bin/composer | ||||
| COPY ./project /projects/main | COPY ./project /projects/main | ||||
| WORKDIR /projects/main | |||||
| @@ -19,3 +19,5 @@ APP_SECRET=2cb22b0116249ce29b70a2b0ab83a8e5 | |||||
| #TRUSTED_PROXIES=127.0.0.0/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16 | #TRUSTED_PROXIES=127.0.0.0/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16 | ||||
| #TRUSTED_HOSTS='^(localhost|example\.com)$' | #TRUSTED_HOSTS='^(localhost|example\.com)$' | ||||
| ###< symfony/framework-bundle ### | ###< symfony/framework-bundle ### | ||||
| DATABASE_URL=mongodb://root:q12asd3@mongo:27017/?authenticationDatabase=admin | |||||
| @@ -5,6 +5,7 @@ | |||||
| "php": "^7.2.5", | "php": "^7.2.5", | ||||
| "ext-ctype": "*", | "ext-ctype": "*", | ||||
| "ext-iconv": "*", | "ext-iconv": "*", | ||||
| "mongodb/mongodb": "^1.6", | |||||
| "myclabs/php-enum": "^1.7", | "myclabs/php-enum": "^1.7", | ||||
| "nyholm/psr7": "^1.2", | "nyholm/psr7": "^1.2", | ||||
| "psr/http-client": "^1.0", | "psr/http-client": "^1.0", | ||||
| @@ -4,8 +4,76 @@ | |||||
| "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", | ||||
| "This file is @generated automatically" | "This file is @generated automatically" | ||||
| ], | ], | ||||
| "content-hash": "4e5589a47c5b0dc0e0cd3d924e0621aa", | |||||
| "content-hash": "2ab113991ae6dde7f7448cf4e4e7f8d9", | |||||
| "packages": [ | "packages": [ | ||||
| { | |||||
| "name": "mongodb/mongodb", | |||||
| "version": "1.6.0", | |||||
| "source": { | |||||
| "type": "git", | |||||
| "url": "https://github.com/mongodb/mongo-php-library.git", | |||||
| "reference": "dc43ba25fb593d6a2988e6a535b6f5386eda5b15" | |||||
| }, | |||||
| "dist": { | |||||
| "type": "zip", | |||||
| "url": "https://api.github.com/repos/mongodb/mongo-php-library/zipball/dc43ba25fb593d6a2988e6a535b6f5386eda5b15", | |||||
| "reference": "dc43ba25fb593d6a2988e6a535b6f5386eda5b15", | |||||
| "shasum": "" | |||||
| }, | |||||
| "require": { | |||||
| "ext-hash": "*", | |||||
| "ext-json": "*", | |||||
| "ext-mongodb": "^1.7", | |||||
| "php": "^5.6 || ^7.0" | |||||
| }, | |||||
| "require-dev": { | |||||
| "phpunit/phpunit": "^5.7.27 || ^6.4 || ^8.3", | |||||
| "sebastian/comparator": "^1.0 || ^2.0 || ^3.0", | |||||
| "squizlabs/php_codesniffer": "^3.4", | |||||
| "symfony/phpunit-bridge": "^4.4@dev" | |||||
| }, | |||||
| "type": "library", | |||||
| "extra": { | |||||
| "branch-alias": { | |||||
| "dev-master": "1.6.x-dev" | |||||
| } | |||||
| }, | |||||
| "autoload": { | |||||
| "psr-4": { | |||||
| "MongoDB\\": "src/" | |||||
| }, | |||||
| "files": [ | |||||
| "src/functions.php" | |||||
| ] | |||||
| }, | |||||
| "notification-url": "https://packagist.org/downloads/", | |||||
| "license": [ | |||||
| "Apache-2.0" | |||||
| ], | |||||
| "authors": [ | |||||
| { | |||||
| "name": "Andreas Braun", | |||||
| "email": "andreas.braun@mongodb.com" | |||||
| }, | |||||
| { | |||||
| "name": "Jeremy Mikola", | |||||
| "email": "jmikola@gmail.com" | |||||
| }, | |||||
| { | |||||
| "name": "Katherine Walker", | |||||
| "email": "katherine.walker@mongodb.com" | |||||
| } | |||||
| ], | |||||
| "description": "MongoDB driver library", | |||||
| "homepage": "https://jira.mongodb.org/browse/PHPLIB", | |||||
| "keywords": [ | |||||
| "database", | |||||
| "driver", | |||||
| "mongodb", | |||||
| "persistence" | |||||
| ], | |||||
| "time": "2020-02-04T18:16:35+00:00" | |||||
| }, | |||||
| { | { | ||||
| "name": "myclabs/php-enum", | "name": "myclabs/php-enum", | ||||
| "version": "1.7.6", | "version": "1.7.6", | ||||
| @@ -11,6 +11,11 @@ services: | |||||
| autowire: true # Automatically injects dependencies in your services. | autowire: true # Automatically injects dependencies in your services. | ||||
| autoconfigure: true # Automatically registers your services as commands, event subscribers, etc. | autoconfigure: true # Automatically registers your services as commands, event subscribers, etc. | ||||
| _instanceof: | |||||
| App\Exchanges\ExchangeInterface: | |||||
| tags: | |||||
| - { name: 'arbitry.exchange'} | |||||
| # makes classes in src/ available to be used as services | # makes classes in src/ available to be used as services | ||||
| # this creates a service per class whose id is the fully-qualified class name | # this creates a service per class whose id is the fully-qualified class name | ||||
| App\: | App\: | ||||
| @@ -29,4 +34,17 @@ services: | |||||
| $privateKey: 'test' | $privateKey: 'test' | ||||
| Http\Client\HttpClient: | Http\Client\HttpClient: | ||||
| class: Nyholm\HttpClient\Client | |||||
| class: Nyholm\HttpClient\Client | |||||
| MongoDB\Client: | |||||
| factory: ['App\Infrastructure\MongoClientFactory', 'create'] | |||||
| arguments: | |||||
| - '%env(resolve:DATABASE_URL)%' | |||||
| App\Infrastructure\Arbitration\Inner\ChainProvider: ~ | |||||
| App\Arbitration\Inner\ChainProviderInterface: '@App\Infrastructure\Arbitration\Inner\ChainProvider' | |||||
| App\Exchanges\ExchangeProvider: | |||||
| arguments: | |||||
| - !tagged arbitry.exchange | |||||
| @@ -0,0 +1,22 @@ | |||||
| <?php | |||||
| declare(strict_types=1); | |||||
| namespace App\Arbitration\Inner; | |||||
| use App\Exchanges\ExchangeInterface; | |||||
| interface ChainProviderInterface | |||||
| { | |||||
| /** | |||||
| * @param ExchangeInterface $exchange | |||||
| * @return ArbitrationChain[] | |||||
| */ | |||||
| public function getChainsForExchange(ExchangeInterface $exchange): array; | |||||
| public function addChain(ExchangeInterface $exchange, ArbitrationChain $chain): void; | |||||
| public function removeChain(ExchangeInterface $exchange, ArbitrationChain $chain): void; | |||||
| } | |||||
| @@ -7,8 +7,10 @@ namespace App\Command; | |||||
| use App\Arbitration\Inner\Stuff\ArbitrationMarketChainsFinder; | use App\Arbitration\Inner\Stuff\ArbitrationMarketChainsFinder; | ||||
| use App\Exchanges\ExchangeProvider; | |||||
| use App\Exchanges\Kuna\KunaExchange; | use App\Exchanges\Kuna\KunaExchange; | ||||
| use App\Exchanges\MarketInterface; | use App\Exchanges\MarketInterface; | ||||
| use App\Infrastructure\Arbitration\Inner\ChainProvider; | |||||
| use Symfony\Component\Console\Command\Command; | use Symfony\Component\Console\Command\Command; | ||||
| use Symfony\Component\Console\Input\InputInterface; | use Symfony\Component\Console\Input\InputInterface; | ||||
| use Symfony\Component\Console\Output\OutputInterface; | use Symfony\Component\Console\Output\OutputInterface; | ||||
| @@ -17,26 +19,34 @@ class TestCommand extends Command | |||||
| { | { | ||||
| protected static $defaultName = 'test:test'; | protected static $defaultName = 'test:test'; | ||||
| /** | |||||
| * @var KunaExchange | |||||
| */ | |||||
| private KunaExchange $kunaExchange; | |||||
| private ExchangeProvider $exchangeProvider; | |||||
| private ChainProvider $chainProvider; | |||||
| public function __construct( | public function __construct( | ||||
| KunaExchange $kunaExchange | |||||
| ExchangeProvider $exchangeProvider, | |||||
| ChainProvider $chainProvider | |||||
| ) | ) | ||||
| { | { | ||||
| parent::__construct(self::$defaultName); | parent::__construct(self::$defaultName); | ||||
| $this->kunaExchange = $kunaExchange; | |||||
| $this->exchangeProvider = $exchangeProvider; | |||||
| $this->chainProvider = $chainProvider; | |||||
| } | } | ||||
| public function execute(InputInterface $input, OutputInterface $output) | public function execute(InputInterface $input, OutputInterface $output) | ||||
| { | { | ||||
| $finder = new ArbitrationMarketChainsFinder(); | $finder = new ArbitrationMarketChainsFinder(); | ||||
| $chains = $finder->findChainsInExchange($this->kunaExchange, 5); | |||||
| $exchange = $this->exchangeProvider->getExchanges()[0]; | |||||
| // $chains = $finder->findChainsInExchange($exchange, 3); | |||||
| // | |||||
| // foreach ($chains as $chain) { | |||||
| // $this->chainProvider->addChain($exchange, $chain); | |||||
| // } | |||||
| foreach ($chains as $chain) { | |||||
| $fetchedChains = $this->chainProvider->getChainsForExchange($exchange); | |||||
| foreach ($fetchedChains as $chain) { | |||||
| echo implode(' -> ', array_map( | echo implode(' -> ', array_map( | ||||
| function (MarketInterface $market) { | function (MarketInterface $market) { | ||||
| return $market->getPair()->getBase() . '/' . $market->getPair()->getQuote(); | return $market->getPair()->getBase() . '/' . $market->getPair()->getQuote(); | ||||
| @@ -9,6 +9,7 @@ namespace App\Exchanges; | |||||
| interface ExchangeInterface | interface ExchangeInterface | ||||
| { | { | ||||
| public function getName(): string; | |||||
| /** | /** | ||||
| * @return MarketInterface[] | * @return MarketInterface[] | ||||
| */ | */ | ||||
| @@ -0,0 +1,33 @@ | |||||
| <?php | |||||
| declare(strict_types=1); | |||||
| namespace App\Exchanges; | |||||
| use IteratorAggregate; | |||||
| class ExchangeProvider | |||||
| { | |||||
| /** | |||||
| * @var IteratorAggregate | |||||
| */ | |||||
| private IteratorAggregate $exchanges; | |||||
| public function __construct( | |||||
| IteratorAggregate $exchanges | |||||
| ) | |||||
| { | |||||
| $this->exchanges = $exchanges; | |||||
| } | |||||
| /** | |||||
| * @return ExchangeInterface[] | |||||
| */ | |||||
| public function getExchanges(): array | |||||
| { | |||||
| return iterator_to_array($this->exchanges->getIterator()); | |||||
| } | |||||
| } | |||||
| @@ -28,13 +28,22 @@ class KunaExchange implements ExchangeInterface | |||||
| */ | */ | ||||
| public function getMarkets(): array | public function getMarkets(): array | ||||
| { | { | ||||
| $data = $this->apiExecutor->getMarkets(); | |||||
| static $markets; | |||||
| $markets = []; | |||||
| foreach ($data as $marketData) { | |||||
| $markets[] = KunaMarket::createFromApiData($marketData, $this->apiExecutor); | |||||
| if (empty($markets)) { | |||||
| $data = $this->apiExecutor->getMarkets(); | |||||
| $markets = []; | |||||
| foreach ($data as $marketData) { | |||||
| $markets[] = KunaMarket::createFromApiData($marketData, $this->apiExecutor); | |||||
| } | |||||
| } | } | ||||
| return $markets; | return $markets; | ||||
| } | } | ||||
| public function getName(): string | |||||
| { | |||||
| return 'kuna'; | |||||
| } | |||||
| } | } | ||||
| @@ -0,0 +1,99 @@ | |||||
| <?php | |||||
| declare(strict_types=1); | |||||
| namespace App\Infrastructure\Arbitration\Inner; | |||||
| use App\Arbitration\Inner\ArbitrationChain; | |||||
| use App\Arbitration\Inner\ChainProviderInterface; | |||||
| use App\Exchanges\ExchangeInterface; | |||||
| use App\Exchanges\MarketInterface; | |||||
| use MongoDB\Client; | |||||
| use MongoDB\Collection; | |||||
| class ChainProvider implements ChainProviderInterface | |||||
| { | |||||
| private Collection $collection; | |||||
| public function __construct( | |||||
| Client $client | |||||
| ) | |||||
| { | |||||
| $this->collection = $client->selectCollection('arbitry', 'inner_arbitration_chains'); | |||||
| } | |||||
| /** | |||||
| * @inheritDoc | |||||
| */ | |||||
| public function getChainsForExchange(ExchangeInterface $exchange): array | |||||
| { | |||||
| $rows = $this->collection->find(['exchange' => $exchange->getName()]); | |||||
| $markets = $exchange->getMarkets(); | |||||
| $chains = []; | |||||
| foreach ($rows as $row) { | |||||
| $chainMarkets = []; | |||||
| foreach ($row['chain'] ?? [] as $chainData) { | |||||
| foreach ($markets as $market) { | |||||
| if ((string)$market->getPair() === $chainData) { | |||||
| $chainMarkets[] = $market; | |||||
| } | |||||
| } | |||||
| } | |||||
| if (!empty($chainMarkets)) { | |||||
| $chains[] = new ArbitrationChain(...$chainMarkets); | |||||
| } | |||||
| } | |||||
| return $chains; | |||||
| } | |||||
| protected function calculateChainHash(ExchangeInterface $exchange, ArbitrationChain $chain): string | |||||
| { | |||||
| return md5( | |||||
| $exchange->getName() | |||||
| . ';' | |||||
| . implode( | |||||
| ';', | |||||
| array_map( | |||||
| static function (MarketInterface $market): string | |||||
| { | |||||
| return (string)$market->getPair(); | |||||
| }, | |||||
| $chain->getMarkets() | |||||
| ) | |||||
| ) | |||||
| ); | |||||
| } | |||||
| public function addChain(ExchangeInterface $exchange, ArbitrationChain $chain): void | |||||
| { | |||||
| $this | |||||
| ->collection | |||||
| ->insertOne([ | |||||
| '_id' => $this->calculateChainHash($exchange, $chain), | |||||
| 'exchange' => $exchange->getName(), | |||||
| 'chain' => array_map( | |||||
| static function (MarketInterface $market): string | |||||
| { | |||||
| return (string)$market->getPair(); | |||||
| }, | |||||
| $chain->getMarkets() | |||||
| ) | |||||
| ]); | |||||
| } | |||||
| public function removeChain(ExchangeInterface $exchange, ArbitrationChain $chain): void | |||||
| { | |||||
| $this | |||||
| ->collection | |||||
| ->deleteOne([ | |||||
| '_id' => $this->calculateChainHash($exchange, $chain) | |||||
| ]); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,18 @@ | |||||
| <?php | |||||
| declare(strict_types=1); | |||||
| namespace App\Infrastructure; | |||||
| use MongoDB\Client; | |||||
| class MongoClientFactory | |||||
| { | |||||
| public static function create(string $uri): Client | |||||
| { | |||||
| return new Client($uri); | |||||
| } | |||||
| } | |||||
| @@ -27,4 +27,9 @@ class CurrencyPair | |||||
| { | { | ||||
| return $this->quote; | return $this->quote; | ||||
| } | } | ||||
| public function __toString() | |||||
| { | |||||
| return $this->base . '/' . $this->quote; | |||||
| } | |||||
| } | } | ||||
| @@ -1,4 +1,7 @@ | |||||
| { | { | ||||
| "mongodb/mongodb": { | |||||
| "version": "1.6.0" | |||||
| }, | |||||
| "myclabs/php-enum": { | "myclabs/php-enum": { | ||||
| "version": "1.7.6" | "version": "1.7.6" | ||||
| }, | }, | ||||