diff --git a/CHANGELOG.md b/CHANGELOG.md index b83cb4a..27bc260 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## v0.6.0-beta + +Added universal parameters to all endpoints with the ability to change format to CSV and HTML (beta). + ## v0.5.0-beta Added indices->quotes to parallelize and speed up multiple index quotes. diff --git a/README.md b/README.md index e335bbb..479bc90 100644 --- a/README.md +++ b/README.md @@ -72,6 +72,21 @@ $status = $client->utilities->api_status(); $headers = $client->utilities->headers(); ``` +### Universal Parameters + +All endpoints (other than utilities) supports universal parameters. + +For instance, you can change the format to CSV + +``` +$option_chain = $client->options->option_chain( + symbol: 'AAPL', + expiration: '2025-01-17', + side: Side::CALL, + parameters: new Parameters(format: Format::CSV), +); +``` + ## Testing ```bash diff --git a/src/ClientBase.php b/src/ClientBase.php index 56ba489..bcd6e7d 100644 --- a/src/ClientBase.php +++ b/src/ClientBase.php @@ -31,7 +31,7 @@ public function setGuzzle(GuzzleClient $guzzleClient): void /** * @throws \Throwable */ - public function executeInParallel(array $calls): array + public function execute_in_parallel(array $calls): array { $promises = []; foreach ($calls as $call) { @@ -59,8 +59,9 @@ protected function async($method, array $arguments = []): PromiseInterface public function execute($method, array $arguments = []): object { try { + $format = array_key_exists('format', $arguments) ? $arguments['format'] : 'json'; $response = $this->guzzle->get($method, [ - 'headers' => $this->headers(), + 'headers' => $this->headers($format), 'query' => $arguments, ]); } catch (\GuzzleHttp\Exception\ClientException $e) { @@ -70,22 +71,37 @@ public function execute($method, array $arguments = []): object }; } - $json_response = (string)$response->getBody(); + switch ($format) { + case 'csv': + case 'html': + $object_response = (object)array( + $arguments['format'] => (string)$response->getBody() + ); + break; - $response = json_decode($json_response); + case 'json': + default: + $json_response = (string)$response->getBody(); - if (isset($response->s) && $response->s === 'error') { - throw new ApiException(message: $response->errmsg, response: $response); + $object_response = json_decode($json_response); + + if (isset($object_response->s) && $object_response->s === 'error') { + throw new ApiException(message: $object_response->errmsg, response: $response); + } } - return $response; + return $object_response; } - protected function headers(): array + protected function headers(string $format = 'json'): array { return [ 'Host' => self::API_HOST, - 'Accept' => 'application/json', + 'Accept' => match ($format) { + 'json' => 'application/json', + 'csv' => 'text/csv', + 'html' => 'text/html', + }, 'Authorization' => "Bearer $this->token", ]; } diff --git a/src/Endpoints/Indices.php b/src/Endpoints/Indices.php index a15f7e3..bde5d6f 100644 --- a/src/Endpoints/Indices.php +++ b/src/Endpoints/Indices.php @@ -4,14 +4,18 @@ use GuzzleHttp\Exception\GuzzleException; use MarketDataApp\Client; +use MarketDataApp\Endpoints\Requests\Parameters; use MarketDataApp\Endpoints\Responses\Indices\Candles; use MarketDataApp\Endpoints\Responses\Indices\Quote; use MarketDataApp\Endpoints\Responses\Indices\Quotes; use MarketDataApp\Exceptions\ApiException; +use MarketDataApp\Traits\UniversalParameters; class Indices { + use UniversalParameters; + private Client $client; public const BASE_URL = "v1/indices/"; @@ -28,32 +32,40 @@ public function __construct($client) * * @param bool $fifty_two_week Enable the output of 52-week high and 52-week low data in the quote output. * + * @param Parameters|null $parameters Universal parameters for all methods (such as format). + * * @throws GuzzleException|ApiException */ - public function quote(string $symbol, bool $fifty_two_week = false): Quote - { - return new Quote($this->client->execute(self::BASE_URL . "quotes/$symbol", ['52week' => $fifty_two_week])); + public function quote( + string $symbol, + bool $fifty_two_week = false, + ?Parameters $parameters = null + ): Quote { + return new Quote($this->execute("quotes/$symbol", ['52week' => $fifty_two_week], $parameters)); } - /** * Get a real-time price quote for a multiple indices by doing parallel requests. * * @param array $symbols The ticker symbols to return in the response. * @param bool $fifty_two_week Enable the output of 52-week high and 52-week low data in the quote output. + * @param Parameters|null $parameters Universal parameters for all methods (such as format). * * @throws \Throwable */ - public function quotes(array $symbols, bool $fifty_two_week = false): Quotes - { + public function quotes( + array $symbols, + bool $fifty_two_week = false, + ?Parameters $parameters = null + ): Quotes { // Execute standard quotes in parallel $calls = []; foreach ($symbols as $symbol) { - $calls[] = [self::BASE_URL . "quotes/$symbol", ['52week' => $fifty_two_week]]; + $calls[] = ["quotes/$symbol", ['52week' => $fifty_two_week]]; } - return new Quotes($this->client->executeInParallel($calls)); + return new Quotes($this->execute_in_parallel($calls, $parameters)); } /** @@ -78,6 +90,8 @@ public function quotes(array $symbols, bool $fifty_two_week = false): Quotes * @param int|null $countback Will fetch a number of candles before (to the left of) to. If you use from, countback * is not required. * + * @param Parameters|null $parameters Universal parameters for all methods (such as format). + * * @throws ApiException|GuzzleException */ public function candles( @@ -85,10 +99,10 @@ public function candles( string $from, string $to = null, string $resolution = 'D', - int $countback = null + int $countback = null, + ?Parameters $parameters = null ): Candles { - return new Candles($this->client->execute(self::BASE_URL . "candles/{$resolution}/{$symbol}/", - compact('from', 'to', 'countback') - )); + return new Candles($this->execute("candles/{$resolution}/{$symbol}/", compact('from', 'to', 'countback'), + $parameters)); } } diff --git a/src/Endpoints/Markets.php b/src/Endpoints/Markets.php index 9c20f96..1ecb01e 100644 --- a/src/Endpoints/Markets.php +++ b/src/Endpoints/Markets.php @@ -4,12 +4,16 @@ use GuzzleHttp\Exception\GuzzleException; use MarketDataApp\Client; +use MarketDataApp\Endpoints\Requests\Parameters; use MarketDataApp\Endpoints\Responses\Markets\Statuses; use MarketDataApp\Exceptions\ApiException; +use MarketDataApp\Traits\UniversalParameters; class Markets { + use UniversalParameters; + private Client $client; public const BASE_URL = "v1/markets/"; @@ -37,6 +41,8 @@ public function __construct($client) * @param int|null $countback Countback will fetch a number of dates before to If you use from, countback is not * required. * + * @param Parameters|null $parameters Universal parameters for all methods (such as format). + * * @throws GuzzleException|ApiException */ public function status( @@ -44,9 +50,10 @@ public function status( string $date = null, string $from = null, string $to = null, - int $countback = null + int $countback = null, + ?Parameters $parameters = null ): Statuses { - return new Statuses($this->client->execute(self::BASE_URL . "status/", - compact('country', 'date', 'from', 'to', 'countback'))); + return new Statuses($this->execute("status/", + compact('country', 'date', 'from', 'to', 'countback'), $parameters)); } } diff --git a/src/Endpoints/MutualFunds.php b/src/Endpoints/MutualFunds.php index 567681a..95be4f6 100644 --- a/src/Endpoints/MutualFunds.php +++ b/src/Endpoints/MutualFunds.php @@ -4,12 +4,16 @@ use GuzzleHttp\Exception\GuzzleException; use MarketDataApp\Client; +use MarketDataApp\Endpoints\Requests\Parameters; use MarketDataApp\Endpoints\Responses\MutualFunds\Candles; use MarketDataApp\Exceptions\ApiException; +use MarketDataApp\Traits\UniversalParameters; class MutualFunds { + use UniversalParameters; + private Client $client; public const BASE_URL = "v1/funds/"; @@ -40,6 +44,8 @@ public function __construct($client) * @param int|null $countback Will fetch a number of candles before (to the left of) to. If you use from, countback * is not required. * + * @param Parameters|null $parameters Universal parameters for all methods (such as format). + * * @return Candles * @throws GuzzleException|ApiException */ @@ -49,9 +55,10 @@ public function candles( string $to = null, string $resolution = 'D', int $countback = null, + ?Parameters $parameters = null ): Candles { - return new Candles($this->client->execute(self::BASE_URL . "candles/{$resolution}/{$symbol}/", - compact('from', 'to', 'countback') + return new Candles($this->execute("candles/{$resolution}/{$symbol}/", + compact('from', 'to', 'countback'), $parameters )); } } diff --git a/src/Endpoints/Options.php b/src/Endpoints/Options.php index 1709779..93467ae 100644 --- a/src/Endpoints/Options.php +++ b/src/Endpoints/Options.php @@ -4,6 +4,7 @@ use GuzzleHttp\Exception\GuzzleException; use MarketDataApp\Client; +use MarketDataApp\Endpoints\Requests\Parameters; use MarketDataApp\Endpoints\Responses\Options\Expirations; use MarketDataApp\Endpoints\Responses\Options\Lookup; use MarketDataApp\Endpoints\Responses\Options\OptionChains; @@ -13,10 +14,13 @@ use MarketDataApp\Enums\Range; use MarketDataApp\Enums\Side; use MarketDataApp\Exceptions\ApiException; +use MarketDataApp\Traits\UniversalParameters; class Options { + use UniversalParameters; + private Client $client; public const BASE_URL = "v1/options/"; @@ -38,12 +42,18 @@ public function __construct($client) * day. If date is omitted the expiration dates will be from the current trading day during market hours or from the * last trading day when the market is closed. Accepted timestamp inputs: ISO 8601, unix, spreadsheet. * + * @param Parameters|null $parameters Universal parameters for all methods (such as format). + * * @throws ApiException|GuzzleException */ - public function expirations(string $symbol, int $strike = null, string $date = null): Expirations - { - return new Expirations($this->client->execute(self::BASE_URL . "expirations/$symbol", - compact('strike', 'date'))); + public function expirations( + string $symbol, + int $strike = null, + string $date = null, + ?Parameters $parameters = null + ): Expirations { + return new Expirations($this->execute("expirations/$symbol", + compact('strike', 'date'), $parameters)); } /** @@ -55,12 +65,14 @@ public function expirations(string $symbol, int $strike = null, string $date = n * - (3) expiration date * - (4) option side (i.e. put or call). * + * @param Parameters|null $parameters Universal parameters for all methods (such as format). + * * This endpoint will translate the user's input into a valid OCC option symbol. * Example: "AAPL 7/28/23 $200 Call". */ - public function lookup(string $input): Lookup + public function lookup(string $input, ?Parameters $parameters = null): Lookup { - return new Lookup($this->client->execute(self::BASE_URL . "lookup/" . $input)); + return new Lookup($this->execute("lookup/" . $input, [], $parameters)); } /** @@ -75,12 +87,18 @@ public function lookup(string $input): Lookup * is omitted the expiration dates will be from the current trading day during market hours or from the last trading * day when the market is closed. Accepted timestamp inputs: ISO 8601, unix, spreadsheet. * + * @param Parameters|null $parameters Universal parameters for all methods (such as format). + * * @throws ApiException|GuzzleException */ - public function strikes(string $symbol, string $expiration = null, string $date = null): Strikes - { - return new Strikes($this->client->execute(self::BASE_URL . "strikes/$symbol", - compact('expiration', 'date'))); + public function strikes( + string $symbol, + string $expiration = null, + string $date = null, + ?Parameters $parameters = null + ): Strikes { + return new Strikes($this->execute("strikes/$symbol", + compact('expiration', 'date'), $parameters)); } /** @@ -196,6 +214,8 @@ public function strikes(string $symbol, string $expiration = null, string $date * @param int|null $min_volume Limit the option chain to options with a volume transacted greater than or equal to * the number provided. * + * @param Parameters|null $parameters Universal parameters for all methods (such as format). + * * @throws GuzzleException|ApiException */ public function option_chain( @@ -224,8 +244,9 @@ public function option_chain( float $max_bid_ask_spread_pct = null, int $min_open_interest = null, int $min_volume = null, + ?Parameters $parameters = null ): OptionChains { - return new OptionChains($this->client->execute(self::BASE_URL . "chain/$symbol", [ + return new OptionChains($this->execute("chain/$symbol", [ 'date' => $date, 'expiration' => $expiration instanceof Expiration ? $expiration->value : $expiration, 'from' => $from, @@ -250,7 +271,7 @@ public function option_chain( 'maxBidAskSpreadPct' => $max_bid_ask_spread_pct, 'minOpenInterest' => $min_open_interest, 'minVolume' => $min_volume, - ])); + ], $parameters)); } /** @@ -273,11 +294,18 @@ public function option_chain( * When the market is closed the quote will be from the last trading day. Accepted timestamp inputs: ISO 8601, unix, * spreadsheet. * + * @param Parameters|null $parameters Universal parameters for all methods (such as format). + * * @throws ApiException|GuzzleException */ - public function quotes(string $option_symbol, string $date = null, string $from = null, string $to = null): Quotes - { - return new Quotes($this->client->execute(self::BASE_URL . "quotes/$option_symbol/", - compact('date', 'from', 'to'))); + public function quotes( + string $option_symbol, + string $date = null, + string $from = null, + string $to = null, + ?Parameters $parameters = null + ): Quotes { + return new Quotes($this->execute("quotes/$option_symbol/", + compact('date', 'from', 'to'), $parameters)); } } diff --git a/src/Endpoints/Requests/Parameters.php b/src/Endpoints/Requests/Parameters.php new file mode 100644 index 0000000..f5efbd7 --- /dev/null +++ b/src/Endpoints/Requests/Parameters.php @@ -0,0 +1,13 @@ +isJson()) { + return; + } + // Convert the response to this object. $this->status = $response->s; diff --git a/src/Endpoints/Responses/Indices/Quote.php b/src/Endpoints/Responses/Indices/Quote.php index 7f1c557..c4fdd5b 100644 --- a/src/Endpoints/Responses/Indices/Quote.php +++ b/src/Endpoints/Responses/Indices/Quote.php @@ -3,8 +3,9 @@ namespace MarketDataApp\Endpoints\Responses\Indices; use Carbon\Carbon; +use MarketDataApp\Endpoints\Responses\ResponseBase; -class Quote +class Quote extends ResponseBase { // Will always be ok when there is data for the symbol requested. @@ -34,6 +35,11 @@ class Quote public function __construct(object $response) { + parent::__construct($response); + if (!$this->isJson()) { + return; + } + $this->status = $response->s; if ($this->status === "ok") { $this->symbol = $response->symbol[0]; diff --git a/src/Endpoints/Responses/Markets/Statuses.php b/src/Endpoints/Responses/Markets/Statuses.php index f401eb6..8161929 100644 --- a/src/Endpoints/Responses/Markets/Statuses.php +++ b/src/Endpoints/Responses/Markets/Statuses.php @@ -3,8 +3,9 @@ namespace MarketDataApp\Endpoints\Responses\Markets; use Carbon\Carbon; +use MarketDataApp\Endpoints\Responses\ResponseBase; -class Statuses +class Statuses extends ResponseBase { // Will always be ok when there is data for the dates requested. @@ -15,6 +16,10 @@ class Statuses public function __construct(object $response) { + parent::__construct($response); + if (!$this->isJson()) { + return; + } // Convert the response to this object. $this->status = $response->s; diff --git a/src/Endpoints/Responses/MutualFunds/Candles.php b/src/Endpoints/Responses/MutualFunds/Candles.php index 4caa9ed..31384e4 100644 --- a/src/Endpoints/Responses/MutualFunds/Candles.php +++ b/src/Endpoints/Responses/MutualFunds/Candles.php @@ -3,8 +3,9 @@ namespace MarketDataApp\Endpoints\Responses\MutualFunds; use Carbon\Carbon; +use MarketDataApp\Endpoints\Responses\ResponseBase; -class Candles +class Candles extends ResponseBase { // Will always be ok when there is data for the candles requested. @@ -19,6 +20,11 @@ class Candles public function __construct(object $response) { + parent::__construct($response); + if (!$this->isJson()) { + return; + } + // Convert the response to this object. $this->status = $response->s; diff --git a/src/Endpoints/Responses/Options/Expirations.php b/src/Endpoints/Responses/Options/Expirations.php index d6987a2..2ac6d07 100644 --- a/src/Endpoints/Responses/Options/Expirations.php +++ b/src/Endpoints/Responses/Options/Expirations.php @@ -3,8 +3,9 @@ namespace MarketDataApp\Endpoints\Responses\Options; use Carbon\Carbon; +use MarketDataApp\Endpoints\Responses\ResponseBase; -class Expirations +class Expirations extends ResponseBase { // Status will always be ok when there is strike data for the underlying/expirations requested. @@ -29,6 +30,11 @@ class Expirations public function __construct(object $response) { + parent::__construct($response); + if (!$this->isJson()) { + return; + } + // Convert the response to this object. $this->status = $response->s; diff --git a/src/Endpoints/Responses/Options/Lookup.php b/src/Endpoints/Responses/Options/Lookup.php index 7da7f63..2e39d37 100644 --- a/src/Endpoints/Responses/Options/Lookup.php +++ b/src/Endpoints/Responses/Options/Lookup.php @@ -2,7 +2,9 @@ namespace MarketDataApp\Endpoints\Responses\Options; -class Lookup +use MarketDataApp\Endpoints\Responses\ResponseBase; + +class Lookup extends ResponseBase { // Status will always be ok when the OCC option symbol is successfully generated. public string $status; @@ -13,6 +15,11 @@ class Lookup public function __construct(object $response) { + parent::__construct($response); + if (!$this->isJson()) { + return; + } + // Convert the response to this object. $this->status = $response->s; $this->option_symbol = $response->optionSymbol;} diff --git a/src/Endpoints/Responses/Options/OptionChainStrike.php b/src/Endpoints/Responses/Options/OptionChainStrike.php index b314d56..7ef3df6 100644 --- a/src/Endpoints/Responses/Options/OptionChainStrike.php +++ b/src/Endpoints/Responses/Options/OptionChainStrike.php @@ -70,19 +70,19 @@ public function __construct( public float|null $implied_volatility, // The delta of the option. - public float $delta, + public float|null $delta, // The gamma of the option. - public float $gamma, + public float|null $gamma, // The theta of the option. - public float $theta, + public float|null $theta, // The vega of the option. - public float $vega, + public float|null $vega, // The rho of the option. - public float $rho, + public float|null $rho, // The date/time of the quote. public Carbon $updated, diff --git a/src/Endpoints/Responses/Options/OptionChains.php b/src/Endpoints/Responses/Options/OptionChains.php index cfe05fa..c545a2b 100644 --- a/src/Endpoints/Responses/Options/OptionChains.php +++ b/src/Endpoints/Responses/Options/OptionChains.php @@ -3,9 +3,10 @@ namespace MarketDataApp\Endpoints\Responses\Options; use Carbon\Carbon; +use MarketDataApp\Endpoints\Responses\ResponseBase; use MarketDataApp\Enums\Side; -class OptionChains +class OptionChains extends ResponseBase { // Status will always be ok when there is the quote requested. @@ -22,6 +23,11 @@ class OptionChains public function __construct(object $response) { + parent::__construct($response); + if (!$this->isJson()) { + return; + } + // Convert the response to this object. $this->status = $response->s; diff --git a/src/Endpoints/Responses/Options/Quotes.php b/src/Endpoints/Responses/Options/Quotes.php index 517fcb8..41125da 100644 --- a/src/Endpoints/Responses/Options/Quotes.php +++ b/src/Endpoints/Responses/Options/Quotes.php @@ -3,8 +3,9 @@ namespace MarketDataApp\Endpoints\Responses\Options; use Carbon\Carbon; +use MarketDataApp\Endpoints\Responses\ResponseBase; -class Quotes +class Quotes extends ResponseBase { // Status will always be ok when there is data for the quote requested. @@ -21,6 +22,11 @@ class Quotes public function __construct(object $response) { + parent::__construct($response); + if (!$this->isJson()) { + return; + } + // Convert the response to this object. $this->status = $response->s; diff --git a/src/Endpoints/Responses/Options/Strikes.php b/src/Endpoints/Responses/Options/Strikes.php index 293fbf2..e492348 100644 --- a/src/Endpoints/Responses/Options/Strikes.php +++ b/src/Endpoints/Responses/Options/Strikes.php @@ -3,8 +3,9 @@ namespace MarketDataApp\Endpoints\Responses\Options; use Carbon\Carbon; +use MarketDataApp\Endpoints\Responses\ResponseBase; -class Strikes +class Strikes extends ResponseBase { // Will always be ok when there is data for the candles requested. public string $status; @@ -32,6 +33,11 @@ class Strikes public function __construct(object $response) { + parent::__construct($response); + if (!$this->isJson()) { + return; + } + // Convert the response to this object. $this->status = $response->s; diff --git a/src/Endpoints/Responses/ResponseBase.php b/src/Endpoints/Responses/ResponseBase.php new file mode 100644 index 0000000..6b40123 --- /dev/null +++ b/src/Endpoints/Responses/ResponseBase.php @@ -0,0 +1,46 @@ +csv)) { + $this->csv = $response->csv; + } + + if (isset($response->html)) { + $this->html = $response->html; + } + } + + public function getCsv(): string + { + return $this->csv; + } + + public function getHtml(): string + { + return $this->html; + } + + public function isJson(): bool + { + return empty($this->csv) && empty($this->html); + } + + public function isHtml(): bool + { + return !empty($this->html); + } + + public function isCsv(): bool + { + return !empty($this->csv); + } +} diff --git a/src/Endpoints/Responses/Stocks/BulkCandles.php b/src/Endpoints/Responses/Stocks/BulkCandles.php index 1fc0ead..ac398c2 100644 --- a/src/Endpoints/Responses/Stocks/BulkCandles.php +++ b/src/Endpoints/Responses/Stocks/BulkCandles.php @@ -3,8 +3,9 @@ namespace MarketDataApp\Endpoints\Responses\Stocks; use Carbon\Carbon; +use MarketDataApp\Endpoints\Responses\ResponseBase; -class BulkCandles +class BulkCandles extends ResponseBase { // Will always be ok when there is data for the candles requested. @@ -15,6 +16,11 @@ class BulkCandles public function __construct(object $response) { + parent::__construct($response); + if (!$this->isJson()) { + return; + } + // Convert the response to this object. $this->status = $response->s; diff --git a/src/Endpoints/Responses/Stocks/BulkQuotes.php b/src/Endpoints/Responses/Stocks/BulkQuotes.php index 59c6eba..4d5e805 100644 --- a/src/Endpoints/Responses/Stocks/BulkQuotes.php +++ b/src/Endpoints/Responses/Stocks/BulkQuotes.php @@ -3,8 +3,9 @@ namespace MarketDataApp\Endpoints\Responses\Stocks; use Carbon\Carbon; +use MarketDataApp\Endpoints\Responses\ResponseBase; -class BulkQuotes +class BulkQuotes extends ResponseBase { // Will always be ok when there is data for the symbol requested. @@ -15,6 +16,11 @@ class BulkQuotes public function __construct(object $response) { + parent::__construct($response); + if (!$this->isJson()) { + return; + } + // Convert the response to this object. $this->status = $response->s; diff --git a/src/Endpoints/Responses/Stocks/Candles.php b/src/Endpoints/Responses/Stocks/Candles.php index 56500a6..a390b84 100644 --- a/src/Endpoints/Responses/Stocks/Candles.php +++ b/src/Endpoints/Responses/Stocks/Candles.php @@ -3,8 +3,9 @@ namespace MarketDataApp\Endpoints\Responses\Stocks; use Carbon\Carbon; +use MarketDataApp\Endpoints\Responses\ResponseBase; -class Candles +class Candles extends ResponseBase { // Will always be ok when there is data for the candles requested. @@ -19,6 +20,11 @@ class Candles public function __construct(object $response) { + parent::__construct($response); + if (!$this->isJson()) { + return; + } + // Convert the response to this object. $this->status = $response->s; diff --git a/src/Endpoints/Responses/Stocks/Earnings.php b/src/Endpoints/Responses/Stocks/Earnings.php index e90eabd..c1ab5b4 100644 --- a/src/Endpoints/Responses/Stocks/Earnings.php +++ b/src/Endpoints/Responses/Stocks/Earnings.php @@ -3,8 +3,9 @@ namespace MarketDataApp\Endpoints\Responses\Stocks; use Carbon\Carbon; +use MarketDataApp\Endpoints\Responses\ResponseBase; -class Earnings +class Earnings extends ResponseBase { // Will always be ok when there is data for the symbol requested. @@ -15,6 +16,11 @@ class Earnings public function __construct(object $response) { + parent::__construct($response); + if (!$this->isJson()) { + return; + } + // Convert the response to this object. $this->status = $response->s; diff --git a/src/Endpoints/Responses/Stocks/News.php b/src/Endpoints/Responses/Stocks/News.php index b7eb0ce..f9dc383 100644 --- a/src/Endpoints/Responses/Stocks/News.php +++ b/src/Endpoints/Responses/Stocks/News.php @@ -3,8 +3,9 @@ namespace MarketDataApp\Endpoints\Responses\Stocks; use Carbon\Carbon; +use MarketDataApp\Endpoints\Responses\ResponseBase; -class News +class News extends ResponseBase { // Will always be ok when there is data for the symbol requested. @@ -33,6 +34,11 @@ class News public function __construct(object $response) { + parent::__construct($response); + if (!$this->isJson()) { + return; + } + // Convert the response to this object. $this->status = $response->s; diff --git a/src/Endpoints/Responses/Stocks/Quote.php b/src/Endpoints/Responses/Stocks/Quote.php index 3268197..06b3eb2 100644 --- a/src/Endpoints/Responses/Stocks/Quote.php +++ b/src/Endpoints/Responses/Stocks/Quote.php @@ -3,8 +3,9 @@ namespace MarketDataApp\Endpoints\Responses\Stocks; use Carbon\Carbon; +use MarketDataApp\Endpoints\Responses\ResponseBase; -class Quote +class Quote extends ResponseBase { // Will always be ok when there is data for the symbol requested. @@ -55,6 +56,11 @@ class Quote public function __construct(object $response) { + parent::__construct($response); + if (!$this->isJson()) { + return; + } + // Convert the response to this object. $this->status = $response->s; $this->symbol = $response->symbol[0]; diff --git a/src/Endpoints/Stocks.php b/src/Endpoints/Stocks.php index 2cf2119..203fa04 100644 --- a/src/Endpoints/Stocks.php +++ b/src/Endpoints/Stocks.php @@ -4,6 +4,7 @@ use GuzzleHttp\Exception\GuzzleException; use MarketDataApp\Client; +use MarketDataApp\Endpoints\Requests\Parameters; use MarketDataApp\Endpoints\Responses\Stocks\BulkCandles; use MarketDataApp\Endpoints\Responses\Stocks\BulkQuotes; use MarketDataApp\Endpoints\Responses\Stocks\Candles; @@ -12,10 +13,13 @@ use MarketDataApp\Endpoints\Responses\Stocks\Quote; use MarketDataApp\Endpoints\Responses\Stocks\Quotes; use MarketDataApp\Exceptions\ApiException; +use MarketDataApp\Traits\UniversalParameters; class Stocks { + use UniversalParameters; + private Client $client; public const BASE_URL = "v1/stocks/"; @@ -46,6 +50,8 @@ public function __construct($client) * @param bool $adjust_splits Adjust historical data for historical splits and reverse splits. Market Data uses * the CRSP methodology for adjustment. Daily candles default: true. * + * @param Parameters|null $parameters Universal parameters for all methods (such as format). + * * @return BulkCandles * @throws ApiException * @throws GuzzleException @@ -56,6 +62,7 @@ public function bulkCandles( bool $snapshot = false, string $date = null, bool $adjust_splits = false, + ?Parameters $parameters = null ): BulkCandles { if (empty($symbols) && !$snapshot) { throw new \InvalidArgumentException('Either symbols or snapshot must be set'); @@ -63,14 +70,14 @@ public function bulkCandles( $symbols = implode(',', array_map('trim', $symbols)); - return new BulkCandles($this->client->execute(self::BASE_URL . "bulkcandles/{$resolution}/", + return new BulkCandles($this->execute("bulkcandles/{$resolution}/", [ 'symbols' => $symbols, 'snapshot' => $snapshot, 'date' => $date, 'adjustsplits' => $adjust_splits ] - )); + , $parameters)); } /** @@ -115,6 +122,8 @@ public function bulkCandles( * All data is currently returned unadjusted for dividends. Market Data uses the CRSP methodology for adjustment. * Daily candles default: true. Intraday candles default: false. * + * @param Parameters|null $parameters Universal parameters for all methods (such as format). + * * @return Candles * @throws GuzzleException|ApiException */ @@ -129,8 +138,9 @@ public function candles( string $country = null, bool $adjust_splits = false, bool $adjust_dividends = false, + ?Parameters $parameters = null ): Candles { - return new Candles($this->client->execute(self::BASE_URL . "candles/{$resolution}/{$symbol}/", [ + return new Candles($this->execute("candles/{$resolution}/{$symbol}/", [ 'from' => $from, 'to' => $to, 'countback' => $countback, @@ -140,22 +150,25 @@ public function candles( 'adjustsplits' => $adjust_splits, 'adjustdividends' => $adjust_dividends ] - )); + , $parameters)); } /** * Get a real-time price quote for a stock. * * @param string $symbol The company's ticker symbol. + * * @param bool $fifty_two_week Enable the output of 52-week high and 52-week low data in the quote output. By * default this parameter is false if omitted. * + * @param Parameters|null $parameters Universal parameters for all methods (such as format). + * * @throws GuzzleException|ApiException */ - public function quote(string $symbol, bool $fifty_two_week = false): Quote + public function quote(string $symbol, bool $fifty_two_week = false, ?Parameters $parameters = null): Quote { - return new Quote($this->client->execute(self::BASE_URL . "quotes/{$symbol}", - ['52week' => $fifty_two_week])); + return new Quote($this->execute("quotes/{$symbol}", + ['52week' => $fifty_two_week], $parameters)); } /** @@ -163,24 +176,25 @@ public function quote(string $symbol, bool $fifty_two_week = false): Quote * * @param array $symbols The ticker symbols to return in the response. * @param bool $fifty_two_week Enable the output of 52-week high and 52-week low data in the quote output. + * @param Parameters|null $parameters Universal parameters for all methods (such as format). * * @throws \Throwable */ - public function quotes(array $symbols, bool $fifty_two_week = false): Quotes + public function quotes(array $symbols, bool $fifty_two_week = false, ?Parameters $parameters = null): Quotes { // Execute standard quotes in parallel $calls = []; foreach ($symbols as $symbol) { - $calls[] = [self::BASE_URL . "quotes/$symbol", ['52week' => $fifty_two_week]]; + $calls[] = ["quotes/$symbol", ['52week' => $fifty_two_week]]; } - return new Quotes($this->client->executeInParallel($calls)); + return new Quotes($this->execute_in_parallel($calls, $parameters)); } /** * Get a real-time price quote for a multiple stocks in a single API request. * - * The bulkquotes endpoint is designed to return hundreds of symbols at once or full market snapshots. Response + * The bulkQuotes endpoint is designed to return hundreds of symbols at once or full market snapshots. Response * times for less than 50 symbols will be quicker using the standard quotes endpoint and sending your requests in * parallel. * @@ -190,17 +204,19 @@ public function quotes(array $symbols, bool $fifty_two_week = false): Quotes * @param bool $snapshot Returns a full market snapshot with quotes for all symbols when set to true. The symbols * parameter may be omitted if the snapshot parameter is set. * + * @param Parameters|null $parameters Universal parameters for all methods (such as format). + * * @throws GuzzleException * @throws \Exception */ - public function bulkQuotes(array $symbols = [], bool $snapshot = false): BulkQuotes + public function bulkQuotes(array $symbols = [], bool $snapshot = false, ?Parameters $parameters = null): BulkQuotes { if (empty($symbols) && !$snapshot) { throw new \InvalidArgumentException('Either symbols or snapshot must be set'); } - return new BulkQuotes($this->client->execute(self::BASE_URL . "bulkquotes", - ['symbols' => implode(',', $symbols), 'snapshot' => $snapshot])); + return new BulkQuotes($this->execute("bulkquotes", + ['symbols' => implode(',', $symbols), 'snapshot' => $snapshot], $parameters)); } /** @@ -209,17 +225,22 @@ public function bulkQuotes(array $symbols = [], bool $snapshot = false): BulkQuo * Premium subscription required. * * @param string $symbol The company's ticker symbol. + * * @param string|null $from The earliest earnings report to include in the output. If you use countback, from is not * required. * * @param string|null $to The latest earnings report to include in the output. + * * @param int|null $countback Countback will fetch a specific number of earnings reports before to. If you use from, * countback is not required. * * @param string|null $date Retrieve a specific earnings report by date. + * * @param string|null $datekey Retrieve a specific earnings report by date and quarter. Example: 2023-Q4. This * allows you to retrieve a 4th quarter value without knowing the company's specific fiscal year. * + * @param Parameters|null $parameters Universal parameters for all methods (such as format). + * * @return Earnings * @throws ApiException * @throws GuzzleException @@ -230,14 +251,15 @@ public function earnings( string $to = null, int $countback = null, string $date = null, - string $datekey = null + string $datekey = null, + ?Parameters $parameters = null ): Earnings { if (is_null($from) && (is_null($countback) || is_null($to))) { throw new \InvalidArgumentException('Either `from` or `countback` and `to` must be set'); } - return new Earnings($this->client->execute(self::BASE_URL . "earnings/{$symbol}", - compact('from', 'to', 'countback', 'date', 'datekey'))); + return new Earnings($this->execute("earnings/{$symbol}", + compact('from', 'to', 'countback', 'date', 'datekey'), $parameters)); } /** @@ -246,12 +268,18 @@ public function earnings( * CAUTION: This endpoint is in beta. * * @param string $symbol The ticker symbol of the stock. + * * @param string|null $from The earliest news to include in the output. If you use countback, from is not required. + * * @param string|null $to The latest news to include in the output. + * * @param int|null $countback Countback will fetch a specific number of news before to. If you use from, countback * is not required. * * @param string|null $date Retrieve news for a specific day. + * + * @param Parameters|null $parameters Universal parameters for all methods (such as format). + * * @throws \InvalidArgumentException */ public function news( @@ -260,14 +288,13 @@ public function news( string $to = null, int $countback = null, string $date = null, + ?Parameters $parameters = null ): News { if (is_null($from) && (is_null($countback) || is_null($to))) { throw new \InvalidArgumentException('Either `from` or `countback` and `to` must be set'); } - return new News($this->client->execute(self::BASE_URL . "news/{$symbol}", - compact('from', 'to', 'countback', 'date'))); + return new News($this->execute("news/{$symbol}", + compact('from', 'to', 'countback', 'date'), $parameters)); } - - } diff --git a/src/Enums/Format.php b/src/Enums/Format.php new file mode 100644 index 0000000..37ccb55 --- /dev/null +++ b/src/Enums/Format.php @@ -0,0 +1,11 @@ +client->execute(self::BASE_URL . $method, + array_merge($arguments, [ + 'format' => $parameters->format->value + ]) + ); + } + + /** + * @throws \Throwable + */ + protected function execute_in_parallel(array $calls, ?Parameters $parameters = null): array + { + if(is_null($parameters)) $parameters = new Parameters(); + + for($i = 0; $i < count($calls); $i++) { + $calls[$i][0] = self::BASE_URL . $calls[$i][0]; + $calls[$i][1]['format'] = $parameters->format->value; + } + + return $this->client->execute_in_parallel($calls); + } +} diff --git a/tests/Integration/IndicesTest.php b/tests/Integration/IndicesTest.php index 7b2ef62..561130a 100644 --- a/tests/Integration/IndicesTest.php +++ b/tests/Integration/IndicesTest.php @@ -5,8 +5,10 @@ use Carbon\Carbon; use GuzzleHttp\Exception\GuzzleException; use MarketDataApp\Client; +use MarketDataApp\Endpoints\Requests\Parameters; use MarketDataApp\Endpoints\Responses\Indices\Candles; use MarketDataApp\Endpoints\Responses\Indices\Quote; +use MarketDataApp\Enums\Format; use PHPUnit\Framework\TestCase; class IndicesTest extends TestCase @@ -35,6 +37,13 @@ public function testQuote_success() $this->assertInstanceOf(Carbon::class, $response->updated); } + public function testQuote_csv_success() + { + $response = $this->client->indices->quote(symbol: "VIX", parameters: new Parameters(format: Format::CSV)); + $this->assertInstanceOf(Quote::class, $response); + $this->assertEquals('string', gettype($response->getCsv())); + } + public function testQuotes_success() { @@ -93,6 +102,4 @@ public function testCandles_noData() $this->assertInstanceOf(Carbon::class, $response->next_time); $this->assertInstanceOf(Carbon::class, $response->prev_time); } - - } diff --git a/tests/Integration/MutualFundsTest.php b/tests/Integration/MutualFundsTest.php index 4739ee3..b2ec8e8 100644 --- a/tests/Integration/MutualFundsTest.php +++ b/tests/Integration/MutualFundsTest.php @@ -4,8 +4,10 @@ use Carbon\Carbon; use MarketDataApp\Client; +use MarketDataApp\Endpoints\Requests\Parameters; use MarketDataApp\Endpoints\Responses\MutualFunds\Candle; use MarketDataApp\Endpoints\Responses\MutualFunds\Candles; +use MarketDataApp\Enums\Format; use PHPUnit\Framework\TestCase; class MutualFundsTest extends TestCase @@ -41,4 +43,19 @@ public function testCandles_success() $this->assertEquals('double', gettype($response->candles[0]->open)); $this->assertInstanceOf(Carbon::class, $response->candles[0]->timestamp); } + + public function testCandles_csv_success() + { + $response = $this->client->mutual_funds->candles( + symbol: 'VFINX', + from: '2022-09-01', + to: '2022-09-05', + resolution: 'D', + parameters: new Parameters(format: Format::CSV) + ); + + // Verify that the response is an object of the correct type. + $this->assertInstanceOf(Candles::class, $response); + $this->assertEquals('string', gettype($response->getCsv())); + } } diff --git a/tests/Integration/OptionsTest.php b/tests/Integration/OptionsTest.php index 89fde95..b6f9bc0 100644 --- a/tests/Integration/OptionsTest.php +++ b/tests/Integration/OptionsTest.php @@ -4,6 +4,7 @@ use Carbon\Carbon; use MarketDataApp\Client; +use MarketDataApp\Endpoints\Requests\Parameters; use MarketDataApp\Endpoints\Responses\Options\Expirations; use MarketDataApp\Endpoints\Responses\Options\Lookup; use MarketDataApp\Endpoints\Responses\Options\OptionChainStrike; @@ -12,6 +13,7 @@ use MarketDataApp\Endpoints\Responses\Options\Quotes; use MarketDataApp\Endpoints\Responses\Options\Strikes; use MarketDataApp\Enums\Expiration; +use MarketDataApp\Enums\Format; use MarketDataApp\Enums\Side; use PHPUnit\Framework\TestCase; @@ -38,6 +40,16 @@ public function testExpirations_success() $this->assertInstanceOf(Carbon::class, $response->expirations[0]); } + public function testExpirations_csv_success() + { + $response = $this->client->options->expirations( + symbol: 'AAPL', parameters: new Parameters(format: Format::CSV) + ); + + // Verify that the response is an object of the correct type. + $this->assertInstanceOf(Expirations::class, $response); + $this->assertEquals('string', gettype($response->getCsv())); + } public function testLookup_success() { @@ -62,6 +74,19 @@ public function testStrikes_success() $this->assertNotEmpty(array_pop($response->dates)); } + public function testStrikes_csv_success() + { + $response = $this->client->options->strikes( + symbol: 'AAPL', + date: '2023-01-03', + parameters: new Parameters(format: Format::CSV), + ); + + // Verify that the response is an object of the correct type. + $this->assertInstanceOf(Strikes::class, $response); + $this->assertEquals('string', gettype($response->getCsv())); + } + public function testQuotes_success() { $response = $this->client->options->quotes('AAPL250117C00150000'); @@ -94,6 +119,17 @@ public function testQuotes_success() $this->assertInstanceOf(Carbon::class, $response->quotes[0]->updated); } + public function testQuotes_csv_success() + { + $response = $this->client->options->quotes( + option_symbol: 'AAPL250117C00150000', + parameters: new Parameters(format: Format::CSV), + ); + + $this->assertInstanceOf(Quotes::class, $response); + $this->assertEquals('string', gettype($response->getCsv())); + } + public function testOptionChain_success() { $response = $this->client->options->option_chain( @@ -132,12 +168,27 @@ public function testOptionChain_success() $this->assertEquals('double', gettype($option_strike->extrinsic_value)); $this->assertEquals('double', gettype($option_strike->implied_volatility)); $this->assertTrue(in_array(gettype($option_strike->delta), ['double', 'NULL'])); - $this->assertEquals('double', gettype($option_strike->gamma)); - $this->assertEquals('double', gettype($option_strike->theta)); - $this->assertEquals('double', gettype($option_strike->vega)); - $this->assertEquals('double', gettype($option_strike->rho)); + $this->assertTrue(in_array(gettype($option_strike->gamma), ['double', 'NULL'])); + $this->assertTrue(in_array(gettype($option_strike->theta), ['double', 'NULL'])); + $this->assertTrue(in_array(gettype($option_strike->vega), ['double', 'NULL'])); + $this->assertTrue(in_array(gettype($option_strike->rho), ['double', 'NULL'])); $this->assertEquals('double', gettype($option_strike->underlying_price)); } + + public function testOptionChain_csv_success() + { + $response = $this->client->options->option_chain( + symbol: 'AAPL', + expiration: '2025-01-17', + side: Side::CALL, + parameters: new Parameters(format: Format::CSV), + ); + + // Verify that the response is an object of the correct type. + $this->assertInstanceOf(OptionChains::class, $response); + $this->assertEquals('string', gettype($response->getCsv())); + } + public function testOptionChain_expirationEnum_success() { $response = $this->client->options->option_chain( @@ -176,10 +227,10 @@ public function testOptionChain_expirationEnum_success() $this->assertEquals('double', gettype($option_strike->extrinsic_value)); $this->assertEquals('double', gettype($option_strike->implied_volatility)); $this->assertTrue(in_array(gettype($option_strike->delta), ['double', 'NULL'])); - $this->assertEquals('double', gettype($option_strike->gamma)); - $this->assertEquals('double', gettype($option_strike->theta)); - $this->assertEquals('double', gettype($option_strike->vega)); - $this->assertEquals('double', gettype($option_strike->rho)); + $this->assertTrue(in_array(gettype($option_strike->gamma), ['double', 'NULL'])); + $this->assertTrue(in_array(gettype($option_strike->theta), ['double', 'NULL'])); + $this->assertTrue(in_array(gettype($option_strike->vega), ['double', 'NULL'])); + $this->assertTrue(in_array(gettype($option_strike->rho), ['double', 'NULL'])); $this->assertEquals('double', gettype($option_strike->underlying_price)); } } diff --git a/tests/Integration/StocksTest.php b/tests/Integration/StocksTest.php index c4de3a5..8fb86a5 100644 --- a/tests/Integration/StocksTest.php +++ b/tests/Integration/StocksTest.php @@ -5,6 +5,7 @@ use Carbon\Carbon; use GuzzleHttp\Exception\GuzzleException; use MarketDataApp\Client; +use MarketDataApp\Endpoints\Requests\Parameters; use MarketDataApp\Endpoints\Responses\Stocks\BulkCandles; use MarketDataApp\Endpoints\Responses\Stocks\BulkQuote; use MarketDataApp\Endpoints\Responses\Stocks\BulkQuotes; @@ -12,6 +13,7 @@ use MarketDataApp\Endpoints\Responses\Stocks\Candles; use MarketDataApp\Endpoints\Responses\Stocks\Earnings; use MarketDataApp\Endpoints\Responses\Stocks\Quote; +use MarketDataApp\Enums\Format; use MarketDataApp\Exceptions\ApiException; use PHPUnit\Framework\TestCase; @@ -54,6 +56,21 @@ public function testCandles_success() $this->assertInstanceOf(Carbon::class, $response->candles[0]->timestamp); } + public function testCandles_csv_success() + { + $response = $this->client->stocks->candles( + symbol: "AAPL", + from: '2022-09-01', + to: '2022-09-05', + resolution: 'D', + parameters: new Parameters(format: Format::CSV) + ); + + // Verify that the response is an object of the correct type. + $this->assertInstanceOf(Candles::class, $response); + $this->assertEquals('string', gettype($response->getCsv())); + } + /** * @throws GuzzleException|ApiException */ @@ -78,6 +95,22 @@ public function testBulkCandles_success() $this->assertInstanceOf(Carbon::class, $response->candles[0]->timestamp); } + /** + * @throws GuzzleException|ApiException + */ + public function testBulkCandles_csv_success() + { + $response = $this->client->stocks->bulkCandles( + symbols: ["AAPL"], + resolution: 'D', + parameters: new Parameters(format: Format::CSV) + ); + + // Verify that the response is an object of the correct type. + $this->assertInstanceOf(BulkCandles::class, $response); + $this->assertEquals('string', gettype($response->getCsv())); + } + public function testQuote_success() { $response = $this->client->stocks->quote('AAPL'); @@ -99,6 +132,17 @@ public function testQuote_success() $this->assertInstanceOf(Carbon::class, $response->updated); } + public function testQuote_csv_success() + { + $response = $this->client->stocks->quote( + symbol: 'AAPL', + parameters: new Parameters(format: Format::CSV) + ); + + $this->assertInstanceOf(Quote::class, $response); + $this->assertEquals('string', gettype($response->getCsv())); + } + public function testQuotes_success() { $response = $this->client->stocks->quotes(['AAPL']); @@ -144,6 +188,19 @@ public function testBulkQuotes_success() $this->assertInstanceOf(Carbon::class, $response->quotes[0]->updated); } + /** + * @throws \Throwable + */ + public function testBulkQuotes_csv_success() + { + $response = $this->client->stocks->bulkQuotes( + symbols: ['AAPL'], + parameters: new Parameters(format: Format::CSV) + ); + $this->assertInstanceOf(BulkQuotes::class, $response); + $this->assertNotEmpty($response->getCsv()); + } + public function testEarnings_success() { $response = $this->client->stocks->earnings(symbol: 'AAPL', from: '2023-01-01'); @@ -165,4 +222,16 @@ public function testEarnings_success() $this->assertEquals('double', gettype($response->earnings[0]->surprise_eps_pct)); $this->assertInstanceOf(Carbon::class, $response->earnings[0]->updated); } + + public function testEarnings_csv_success() + { + $response = $this->client->stocks->earnings( + symbol: 'AAPL', + from: '2023-01-01', + parameters: new Parameters(format: Format::CSV) + ); + + $this->assertInstanceOf(Earnings::class, $response); + $this->assertNotEmpty($response->getCsv()); + } } diff --git a/tests/Unit/IndicesTest.php b/tests/Unit/IndicesTest.php index 2e8cec8..ec293d7 100644 --- a/tests/Unit/IndicesTest.php +++ b/tests/Unit/IndicesTest.php @@ -8,10 +8,12 @@ use GuzzleHttp\Psr7\Request; use GuzzleHttp\Psr7\Response; use MarketDataApp\Client; +use MarketDataApp\Endpoints\Requests\Parameters; use MarketDataApp\Endpoints\Responses\Indices\Candle; use MarketDataApp\Endpoints\Responses\Indices\Candles; use MarketDataApp\Endpoints\Responses\Indices\Quote; use MarketDataApp\Endpoints\Responses\Indices\Quotes; +use MarketDataApp\Enums\Format; use MarketDataApp\Exceptions\ApiException; use MarketDataApp\Tests\Traits\MockResponses; use PHPUnit\Framework\TestCase; @@ -67,6 +69,23 @@ public function testQuote_success() $this->assertEquals(Carbon::parse($mocked_response['updated'][0]), $response->updated); } + public function testQuote_csv_success() + { + $mocked_response = 's, symbol, last, change, changepct, 52weekHigh, 52weekLow, updated'; + $this->setMockResponses([new Response(200, [], $mocked_response)]); + + $response = $this->client->indices->quote(symbol: "DJI", parameters: new Parameters(Format::CSV)); + $this->assertEquals($mocked_response, $response->getCsv()); + } + + public function testQuote_HTML_success() + { + $mocked_response = '
Hello World
'; + $this->setMockResponses([new Response(200, [], $mocked_response)]); + + $response = $this->client->indices->quote(symbol: "DJI", parameters: new Parameters(Format::HTML)); + $this->assertEquals($mocked_response, $response->getHtml()); + } /** @@ -163,6 +182,28 @@ public function testCandles_fromTo_success() } } + + /** + * @throws GuzzleException|ApiException + */ + public function testCandles_csv_success() + { + $mocked_response = "s, c, h, l, o, t\r\n"; + $this->setMockResponses([new Response(200, [], $mocked_response)]); + + $response = $this->client->indices->candles( + symbol: "DJI", + from: '2022-09-01', + to: '2022-09-05', + resolution: 'D', + parameters: new Parameters(format: Format::CSV) + ); + + // Verify that the response is an object of the correct type. + $this->assertInstanceOf(Candles::class, $response); + $this->assertEquals($mocked_response, $response->getCsv()); + } + /** * @throws GuzzleException */ @@ -210,6 +251,8 @@ public function testCandles_noDataNextTimePrevTime_success() // Verify that the response is an object of the correct type. $this->assertInstanceOf(Candles::class, $response); $this->assertEmpty($response->candles); + $this->assertFalse($response->isCsv()); + $this->assertFalse($response->isHtml()); $this->assertEquals($mocked_response['s'], $response->status); $this->assertEquals(Carbon::parse($mocked_response['nextTime']), $response->next_time); $this->assertEquals(Carbon::parse($mocked_response['prevTime']), $response->next_time); diff --git a/tests/Unit/MarketsTest.php b/tests/Unit/MarketsTest.php index 2c8e488..12212d3 100644 --- a/tests/Unit/MarketsTest.php +++ b/tests/Unit/MarketsTest.php @@ -5,8 +5,10 @@ use Carbon\Carbon; use GuzzleHttp\Psr7\Response; use MarketDataApp\Client; +use MarketDataApp\Endpoints\Requests\Parameters; use MarketDataApp\Endpoints\Responses\Markets\Status; use MarketDataApp\Endpoints\Responses\Markets\Statuses; +use MarketDataApp\Enums\Format; use MarketDataApp\Tests\Traits\MockResponses; use PHPUnit\Framework\TestCase; @@ -48,4 +50,18 @@ public function testStatus_success() $this->assertEquals($mocked_response['status'][$i], $response->statuses[$i]->status); } } + public function testStatus_csv_success() + { + $mocked_response = 's, date, status'; + $this->setMockResponses([new Response(200, [], $mocked_response)]); + + $response = $this->client->markets->status( + date: '1680580800', + parameters: new Parameters(Format::CSV) + ); + + // Verify that the response is an object of the correct type. + $this->assertInstanceOf(Statuses::class, $response); + $this->assertEquals($mocked_response, $response->getCsv()); + } } diff --git a/tests/Unit/MutualFundsTest.php b/tests/Unit/MutualFundsTest.php index 04cccce..dd440a8 100644 --- a/tests/Unit/MutualFundsTest.php +++ b/tests/Unit/MutualFundsTest.php @@ -6,8 +6,10 @@ use GuzzleHttp\Exception\GuzzleException; use GuzzleHttp\Psr7\Response; use MarketDataApp\Client; +use MarketDataApp\Endpoints\Requests\Parameters; use MarketDataApp\Endpoints\Responses\MutualFunds\Candle; use MarketDataApp\Endpoints\Responses\MutualFunds\Candles; +use MarketDataApp\Enums\Format; use MarketDataApp\Exceptions\ApiException; use MarketDataApp\Tests\Traits\MockResponses; use PHPUnit\Framework\TestCase; @@ -59,6 +61,23 @@ public function testCandles_fromTo_success() $this->assertEquals(Carbon::parse($mocked_response['t'][$i]), $response->candles[$i]->timestamp); } } + public function testCandles_csv_success() + { + $mocked_response = "s, t, o, h, l, c\r\n"; + $this->setMockResponses([new Response(200, [], $mocked_response)]); + + $response = $this->client->mutual_funds->candles( + symbol: 'VFINX', + from: '2022-09-01', + to: '2022-09-05', + resolution: 'D', + parameters: new Parameters(Format::CSV) + ); + + // Verify that the response is an object of the correct type. + $this->assertInstanceOf(Candles::class, $response); + $this->assertEquals($mocked_response, $response->getCsv()); + } /** * @throws GuzzleException|ApiException diff --git a/tests/Unit/OptionsTest.php b/tests/Unit/OptionsTest.php index 286f1d6..09789a3 100644 --- a/tests/Unit/OptionsTest.php +++ b/tests/Unit/OptionsTest.php @@ -5,6 +5,7 @@ use Carbon\Carbon; use GuzzleHttp\Psr7\Response; use MarketDataApp\Client; +use MarketDataApp\Endpoints\Requests\Parameters; use MarketDataApp\Endpoints\Responses\Options\Expirations; use MarketDataApp\Endpoints\Responses\Options\Lookup; use MarketDataApp\Endpoints\Responses\Options\OptionChainStrike; @@ -12,6 +13,7 @@ use MarketDataApp\Endpoints\Responses\Options\Quote; use MarketDataApp\Endpoints\Responses\Options\Quotes; use MarketDataApp\Endpoints\Responses\Options\Strikes; +use MarketDataApp\Enums\Format; use MarketDataApp\Enums\Side; use MarketDataApp\Tests\Traits\MockResponses; use PHPUnit\Framework\TestCase; @@ -52,6 +54,21 @@ public function testExpirations_success() } } + public function testExpirations_csv_success() + { + $mocked_response = "s, expirations, updated\r\n"; + $this->setMockResponses([new Response(200, [], $mocked_response)]); + + $response = $this->client->options->expirations( + symbol: 'AAPL', + parameters: new Parameters(format: Format::CSV) + ); + + // Verify that the response is an object of the correct type. + $this->assertInstanceOf(Expirations::class, $response); + $this->assertEquals($mocked_response, $response->getCsv()); + } + public function testExpirations_noData_success() { $mocked_response = [ @@ -85,11 +102,24 @@ public function testLookup_success() $this->assertEquals($mocked_response['optionSymbol'], $response->option_symbol); } + + public function testLookup_csv_success() + { + $mocked_response = "s, optionSymbol\r\n"; + $this->setMockResponses([new Response(200, [], $mocked_response)]); + + $response = $this->client->options->lookup('AAPL 7/28/23 $200 Call', new Parameters(format: Format::CSV)); + + // Verify that the response is an object of the correct type. + $this->assertInstanceOf(Lookup::class, $response); + $this->assertEquals($mocked_response, $response->getCsv()); + } + public function testStrikes_success() { $mocked_response = [ - 's' => 'ok', - 'updated' => 1663704000, + 's' => 'ok', + 'updated' => 1663704000, '2023-01-20' => [ 30.0, 35.0 @@ -109,6 +139,23 @@ public function testStrikes_success() $this->assertEquals($mocked_response['2023-01-20'], $response->dates['2023-01-20']); } + public function testStrikes_csv_success() + { + $mocked_response = "s, updated, 2023-01-20\r\n"; + $this->setMockResponses([new Response(200, [], $mocked_response)]); + + $response = $this->client->options->strikes( + symbol: 'AAPL', + expiration: '2023-01-20', + date: '2023-01-03', + parameters: new Parameters(Format::CSV), + ); + + // Verify that the response is an object of the correct type. + $this->assertInstanceOf(Strikes::class, $response); + $this->assertEquals($mocked_response, $response->getCsv()); + } + public function testStrikes_noData_success() { $mocked_response = [ @@ -190,6 +237,21 @@ public function testQuotes_success() } } + public function testQuotes_csv_success() + { + $mocked_response = "s, optionSymbol, ask...\r\n"; + $this->setMockResponses([new Response(200, [], $mocked_response)]); + + $response = $this->client->options->quotes( + option_symbol: 'AAPL250117C00150000', + parameters: new Parameters(Format::CSV) + ); + + // Verify that the response is an object of the correct type. + $this->assertInstanceOf(Quotes::class, $response); + $this->assertEquals($mocked_response, $response->getCsv()); + } + public function testQuotes_noData_success() { $mocked_response = [ @@ -286,6 +348,24 @@ public function testOptionChain_success() } } + + + public function testOptionChain_csv_success() + { + $mocked_response = "s, optionSymbol, underlying...\r\n"; + $this->setMockResponses([new Response(200, [], $mocked_response)]); + + $response = $this->client->options->option_chain( + symbol: 'AAPL', + side: Side::CALL, + parameters: new Parameters(Format::CSV) + ); + + // Verify that the response is an object of the correct type. + $this->assertInstanceOf(OptionChains::class, $response); + $this->assertEquals($mocked_response, $response->getCsv()); + } + public function testOptionChain_noData_success() { $mocked_response = [ diff --git a/tests/Unit/StocksTest.php b/tests/Unit/StocksTest.php index a1b6057..adf1040 100644 --- a/tests/Unit/StocksTest.php +++ b/tests/Unit/StocksTest.php @@ -9,6 +9,7 @@ use GuzzleHttp\Psr7\Response; use InvalidArgumentException; use MarketDataApp\Client; +use MarketDataApp\Endpoints\Requests\Parameters; use MarketDataApp\Endpoints\Responses\Stocks\BulkCandles; use MarketDataApp\Endpoints\Responses\Stocks\BulkQuote; use MarketDataApp\Endpoints\Responses\Stocks\BulkQuotes; @@ -19,6 +20,7 @@ use MarketDataApp\Endpoints\Responses\Stocks\News; use MarketDataApp\Endpoints\Responses\Stocks\Quote; use MarketDataApp\Endpoints\Responses\Stocks\Quotes; +use MarketDataApp\Enums\Format; use MarketDataApp\Exceptions\ApiException; use MarketDataApp\Tests\Traits\MockResponses; use PHPUnit\Framework\TestCase; @@ -106,6 +108,24 @@ public function testCandles_fromTo_success() } } + public function testCandles_csv_success() + { + $mocked_response = "s, c, h, l, o, v, t"; + $this->setMockResponses([new Response(200, [], $mocked_response)]); + + $response = $this->client->stocks->candles( + symbol: "AAPL", + from: '2022-09-01', + to: '2022-09-05', + resolution: 'D', + parameters: new Parameters(format: Format::CSV) + ); + + // Verify that the response is an object of the correct type. + $this->assertInstanceOf(Candles::class, $response); + $this->assertEquals($mocked_response, $response->getCsv()); + } + /** * @throws GuzzleException|ApiException */ @@ -160,13 +180,13 @@ public function testCandles_noDataNextTime_success() public function testBulkCandles_success() { $mocked_response = [ - 's' => 'ok', - 'c' => [22.84, 23.93], - 'h' => [23.27, 24.68], - 'l' => [22.26, 22.67], - 'o' => [22.41, 24.08], - 'v' => [123123, 66959442], - 't' => [1659326400, 1659412800] + 's' => 'ok', + 'c' => [22.84, 23.93], + 'h' => [23.27, 24.68], + 'l' => [22.26, 22.67], + 'o' => [22.41, 24.08], + 'v' => [123123, 66959442], + 't' => [1659326400, 1659412800] ]; $this->setMockResponses([new Response(200, [], json_encode($mocked_response))]); @@ -191,6 +211,22 @@ public function testBulkCandles_success() } } + public function testBulkCandles_csv_success() + { + $mocked_response = "s, c, h, l, o, v, t"; + $this->setMockResponses([new Response(200, [], $mocked_response)]); + + $response = $this->client->stocks->bulkCandles( + symbols: ["AAPL", "MSFT"], + resolution: 'D', + parameters: new Parameters(format: Format::CSV) + ); + + // Verify that the response is an object of the correct type. + $this->assertInstanceOf(BulkCandles::class, $response); + $this->assertEquals($mocked_response, $response->getCsv()); + } + /** * @throws GuzzleException|ApiException */ @@ -247,6 +283,21 @@ public function testQuote_success() $this->assertEquals(Carbon::parse($mocked_response['updated'][0]), $quote->updated); } + public function testQuote_csv_success() + { + $mocked_response = "a, b, c"; + $this->setMockResponses([ + new Response(200, [], $mocked_response), + ]); + $quote = $this->client->stocks->quote( + symbol: 'AAPL', + parameters: new Parameters(format: Format::CSV) + ); + + $this->assertInstanceOf(Quote::class, $quote); + $this->assertEquals($mocked_response, $quote->getCsv()); + } + public function testQuote_52week_success() { $mocked_response = $this->aapl_mocked_response; @@ -351,6 +402,20 @@ public function testBulkQuotes_success() $this->assertEquals(Carbon::parse($mocked_response['updated'][$i]), $response->quotes[$i]->updated); } } + + public function testBulkQuotes_csv_success() + { + $mocked_response = "a, b, c"; + $this->setMockResponses([new Response(200, [], $mocked_response)]); + + $response = $this->client->stocks->bulkQuotes( + symbols: ['AAPL', 'NFLX'], + parameters: new Parameters(format: Format::CSV) + ); + $this->assertInstanceOf(BulkQuotes::class, $response); + $this->assertEquals($mocked_response, $response->getCsv()); + } + /** * @throws \Throwable */ @@ -454,7 +519,8 @@ public function testEarnings_success() $this->assertEquals($mocked_response['fiscalYear'][$i], $response->earnings[$i]->fiscal_year); $this->assertEquals($mocked_response['fiscalQuarter'][$i], $response->earnings[$i]->fiscal_quarter); $this->assertEquals(Carbon::parse($mocked_response['date'][$i]), $response->earnings[$i]->date); - $this->assertEquals(Carbon::parse($mocked_response['reportDate'][$i]), $response->earnings[$i]->report_date); + $this->assertEquals(Carbon::parse($mocked_response['reportDate'][$i]), + $response->earnings[$i]->report_date); $this->assertEquals($mocked_response['reportTime'][$i], $response->earnings[$i]->report_time); $this->assertEquals($mocked_response['currency'][$i], $response->earnings[$i]->currency); $this->assertEquals($mocked_response['reportedEPS'][$i], $response->earnings[$i]->reported_eps); @@ -465,6 +531,20 @@ public function testEarnings_success() } } + public function testEarnings_csv_success() + { + $mocked_response = "s, symbol, fiscalYear..."; + $this->setMockResponses([new Response(200, [], $mocked_response)]); + $response = $this->client->stocks->earnings( + symbol: 'AAPL', + from: '2023-01-01', + parameters: new Parameters(format: Format::CSV) + ); + + $this->assertInstanceOf(Earnings::class, $response); + $this->assertEquals($mocked_response, $response->getCsv()); + } + /** * @throws GuzzleException|ApiException */ @@ -474,7 +554,6 @@ public function testEarnings_noFromOrCountback_throwsException() $this->client->stocks->earnings('AAPL'); } - public function testNews_success() { $mocked_response = [ @@ -497,6 +576,20 @@ public function testNews_success() $this->assertEquals(Carbon::parse($mocked_response['publicationDate']), $news->publication_date); } + public function testNews_csv_success() + { + $mocked_response = "s, symbol, headline..."; + $this->setMockResponses([new Response(200, [], $mocked_response)]); + $news = $this->client->stocks->news( + symbol: 'AAPL', + from: '2023-01-01', + parameters: new Parameters(format: Format::CSV) + ); + + $this->assertInstanceOf(News::class, $news); + $this->assertEquals($mocked_response, $news->getCsv()); + } + public function testNews_noFromOrCountback_throwsException() { $this->expectException(\InvalidArgumentException::class);