1 module unit.iex; 2 3 import unit_threaded : HiddenTest, writelnUt; 4 5 import iex; 6 7 version(unittest) { 8 /** Check that a string has the provided parameters in unittests. 9 10 Parameters in a URL may be in any order, so a simple equality check is 11 insufficient. 12 */ 13 bool hasParameters(string input, string[] parameters) { 14 import std.algorithm.searching : canFind; 15 foreach (parameter; parameters) { 16 if (! canFind(input, parameter)) return false; 17 } 18 return true; 19 } 20 21 @("hasParameters() checks for substrings") 22 unittest { 23 assert("some-url?one=two&three=four".hasParameters(["one=two","three=four"])); 24 assert(! "some-url?one=two".hasParameters(["three=four"])); 25 } 26 } 27 28 29 @("book() builds an endpoint for a single stock symbol") 30 unittest { 31 auto stock = Stock("AAPL").book(); 32 assert(stock.toURL() == iexPrefix ~ "stock/AAPL/book", stock.toURL()); 33 } 34 35 @("book() builds an endpoint for multiple stock symbols") 36 unittest { 37 auto stock = Stock("AAPL", "BDC").book(); 38 assert(stock.toURL() == iexPrefix ~ "stock/market/batch?symbols=AAPL,BDC&types=book", stock.toURL()); 39 } 40 41 42 @("chart() builds an endpoint for pre-defined date ranges") 43 unittest { 44 auto stock = Stock("AAPL").chart(ChartRange.YearToDate); 45 assert(stock.toURL() == iexPrefix ~ "stock/AAPL/chart/" ~ ChartRange.YTD, 46 stock.toURL()); 47 48 stock = Stock("AAPL") 49 .chart(ChartRange.YearToDate, No.resetAtMidnight, Yes.simplify, 3); 50 51 import std.string : split; 52 auto actual = stock.toURL().split('?'); 53 assert(actual[0] == iexPrefix ~ "stock/AAPL/chart/" ~ ChartRange.YTD, 54 actual[0]); 55 assert(actual[1].hasParameters(["chartSimplify=true", "chartInterval=3"]), 56 actual[1]); 57 } 58 59 @("chart() builds an endpoint for custom dates") 60 unittest { 61 auto stock = Stock("AAPL").chart("20180531"); 62 assert(stock.toURL() == iexPrefix ~ "stock/AAPL/chart/date/20180531", 63 stock.toURL()); 64 } 65 66 @("chart() builds an endpoint for multiple stock symbols") 67 unittest { 68 import std.string : split; 69 auto stock = Stock("AAPL", "BDC").chart("1y"); 70 auto actual = stock.toURL().split('?'); 71 assert(actual[0] == iexPrefix ~ "stock/market/batch", actual[0]); 72 assert(actual[1].hasParameters(["symbols=AAPL,BDC", "types=chart", "range=1y"]), 73 actual[1]); 74 75 stock = Stock("AAPL", "BDC").chart("20180531"); 76 actual = stock.toURL().split('?'); 77 assert(actual[0] == iexPrefix ~ "stock/market/batch", actual[0]); 78 assert(actual[1].hasParameters( 79 ["symbols=AAPL,BDC", "types=chart", "range=20180531"]), 80 actual[1]); 81 } 82 83 @("historicalPrices() is equivalent to chart()") 84 unittest { 85 auto chart = Stock("AAPL").chart(ChartRange.OneYear); 86 auto history = Stock("AAPL").historicalPrices(ChartRange.OneYear); 87 assert(chart == history, history.toURL()); 88 } 89 90 @("timeSeries() is equivalent to chart()") 91 unittest { 92 auto chart = Stock("AAPL").chart(ChartRange.OneYear); 93 auto series = Stock("AAPL").timeSeries(ChartRange.OneYear); 94 assert(chart == series, series.toURL()); 95 } 96 97 98 @("company() builds an endpoint for a single stock symbol") 99 unittest { 100 auto stock = Stock("AAPL").company(); 101 assert(stock.toURL() == iexPrefix ~ "stock/AAPL/company", stock.toURL()); 102 } 103 104 @("company() builds an endpoint for multiple stock symbols") 105 unittest { 106 auto stock = Stock("AAPL", "BDC").company(); 107 assert(stock.toURL() == iexPrefix ~ "stock/market/batch?symbols=AAPL,BDC&types=company", stock.toURL()); 108 } 109 110 111 @("delayedQuote() builds an endpoint for a single stock symbol") 112 unittest { 113 auto stock = Stock("AAPL").delayedQuote(); 114 assert(stock.toURL() == iexPrefix ~ "stock/AAPL/delayed-quote", stock.toURL()); 115 } 116 117 @("delayedQuote() builds an endpoint for multiple stock symbols") 118 unittest { 119 auto stock = Stock("AAPL", "BDC").delayedQuote(); 120 assert(stock.toURL() == 121 iexPrefix ~ "stock/market/batch?symbols=AAPL,BDC&types=delayed-quote", 122 stock.toURL()); 123 } 124 125 126 @("dividends() builds an endpoint for a single stock symbol") 127 unittest { 128 auto stock = Stock("AAPL").dividends(DividendRange.TwoYears); 129 assert(stock.toURL() == iexPrefix ~ "stock/AAPL/dividends/2y", stock.toURL()); 130 } 131 132 @("dividends() builds an endpoint for multiple stock symbols") 133 unittest { 134 import std.string : split; 135 auto stock = Stock("AAPL", "BDC").dividends(DividendRange.TwoYears); 136 auto actual = stock.toURL().split('?'); 137 assert(actual[0] == iexPrefix ~ "stock/market/batch", actual[0]); 138 assert(actual[1].hasParameters(["symbols=AAPL,BDC", "types=dividends", "range=2y"]), 139 actual[1]); 140 } 141 142 143 @("earnings() builds an endpoint for a single stock symbol") 144 unittest { 145 auto stock = Stock("AAPL").earnings(); 146 assert(stock.toURL() == iexPrefix ~ "stock/AAPL/earnings", stock.toURL()); 147 } 148 149 @("earnings() builds an endpoint for multiple stock symbols") 150 unittest { 151 auto stock = Stock("AAPL", "BDC").earnings(); 152 assert(stock.toURL() == 153 iexPrefix ~ "stock/market/batch?symbols=AAPL,BDC&types=earnings", 154 stock.toURL()); 155 } 156 157 158 @("effectiveSpread() builds an endpoint for a single stock symbol") 159 unittest { 160 auto stock = Stock("AAPL").effectiveSpread(); 161 assert(stock.toURL() == iexPrefix ~ "stock/AAPL/effective-spread", 162 stock.toURL()); 163 } 164 165 @("effectiveSpread() builds an endpoint for multiple stock symbols") 166 unittest { 167 auto stock = Stock("AAPL", "BDC").effectiveSpread(); 168 assert(stock.toURL() == 169 iexPrefix ~ "stock/market/batch?symbols=AAPL,BDC&types=effective-spread", 170 stock.toURL()); 171 } 172 173 174 @("financials() builds an endpoint for a single stock symbol") 175 unittest { 176 auto stock = Stock("AAPL").financials(); 177 assert(stock.toURL() == iexPrefix ~ "stock/AAPL/financials", 178 stock.toURL()); 179 } 180 181 @("financials() builds an endpoint for multiple stock symbols") 182 unittest { 183 auto stock = Stock("AAPL", "BDC").financials(); 184 assert(stock.toURL() == 185 iexPrefix ~ "stock/market/batch?symbols=AAPL,BDC&types=financials", 186 stock.toURL()); 187 } 188 189 190 @("thresholdSecurities() builds an endpoint for a single stock symbol") 191 unittest { 192 import std.string : split; 193 auto stock = Stock("AAPL").thresholdSecurities(); 194 assert(stock.toURL() == iexPrefix ~ "stock/AAPL/threshold-securities/", 195 stock.toURL()); 196 197 stock = Stock("market").thresholdSecurities("20180531"); 198 assert(stock.toURL() == 199 iexPrefix ~ "stock/market/threshold-securities/" ~ "20180531", 200 stock.toURL()); 201 } 202 203 @("thresholdSecurities() builds an endpoint as part of a batch") 204 unittest { 205 import std.string : split; 206 auto stock = Stock("AAPL", "BDC") 207 .chart(ChartRange.YTD) 208 .thresholdSecurities(); 209 210 auto actual = stock.toURL().split('?'); 211 assert(actual[0] == iexPrefix ~ "stock/market/batch", actual[0]); 212 assert(actual[1].hasParameters( 213 ["symbols=AAPL,BDC", "types=threshold-securities,chart", "range=ytd"]), 214 actual[1]); 215 } 216 217 218 @("shortInterest() builds an endpoint for a single stock symbol") 219 unittest { 220 import std.string : split; 221 auto stock = Stock("AAPL").shortInterest(); 222 assert(stock.toURL() == iexPrefix ~ "stock/AAPL/short-interest/", 223 stock.toURL()); 224 225 stock = Stock("AAPL").shortInterest("20180531", ResponseFormat.csv); 226 auto actual = stock.toURL().split('?'); 227 assert(actual[0] == iexPrefix ~ "stock/AAPL/short-interest/20180531", 228 actual[0]); 229 assert(actual[1].hasParameters(["format=csv"]), actual[1]); 230 } 231 232 @("shortInterest() builds an endpoint for the market") 233 unittest { 234 auto stock = Stock("").shortInterest(); 235 assert(stock.toURL() == iexPrefix ~ "stock/market/short-interest/", 236 stock.toURL()); 237 238 stock = Stock("market").shortInterest(); 239 assert(stock.toURL() == iexPrefix ~ "stock/market/short-interest/", 240 stock.toURL()); 241 } 242 243 244 @("keyStats() builds an endpoint for a single stock symbol") 245 unittest { 246 auto stock = Stock("AAPL").keyStats(); 247 assert(stock.toURL() == iexPrefix ~ "stock/AAPL/stats", 248 stock.toURL()); 249 } 250 251 @("keyStats() builds an endpoint for multiple stock symbols") 252 unittest { 253 auto stock = Stock("AAPL", "BDC").keyStats(); 254 assert(stock.toURL() == 255 iexPrefix ~ "stock/market/batch?symbols=AAPL,BDC&types=stats", 256 stock.toURL()); 257 } 258 259 260 @("largestTrades() builds an endpoint for a single stock symbol") 261 unittest { 262 auto stock = Stock("AAPL").largestTrades(); 263 assert(stock.toURL() == iexPrefix ~ "stock/AAPL/largest-trades", 264 stock.toURL()); 265 } 266 267 @("largestTrades() builds an endpoint for multiple stock symbols") 268 unittest { 269 auto stock = Stock("AAPL", "BDC").largestTrades(); 270 assert(stock.toURL() == 271 iexPrefix ~ "stock/market/batch?symbols=AAPL,BDC&types=largest-trades", 272 stock.toURL()); 273 } 274 275 276 @("list() builds an endpoint for a single symbol") 277 unittest { 278 import std.string : split; 279 auto stock = Stock("").list(MarketList.MostActive); 280 assert(stock.toURL() == iexPrefix ~ "stock/market/list/mostactive", 281 stock.toURL()); 282 283 stock = Stock("").list(MarketList.MostActive, Yes.displayPercent); 284 auto actual = stock.toURL().split('?'); 285 assert(actual[0] == iexPrefix ~ "stock/market/list/mostactive", actual[0]); 286 assert(actual[1].hasParameters(["displayPercent=true"]), actual[1]); 287 } 288 289 @("list() ignores individual stock symbols passed to it") 290 unittest { 291 auto stock = Stock("AAPL").list(MarketList.MostActive); 292 assert(stock.toURL() == iexPrefix ~ "stock/market/list/mostactive", 293 stock.toURL()); 294 } 295 296 297 @("logo() builds an endpoint for a single symbol") 298 unittest { 299 auto stock = Stock("AAPL").logo(); 300 assert(stock.toURL() == iexPrefix ~ "stock/AAPL/logo", 301 stock.toURL()); 302 } 303 304 @("logo() builds an endpoint for multiple symbols") 305 unittest { 306 auto stock = Stock("AAPL", "BDC").logo(); 307 assert(stock.toURL() == 308 iexPrefix ~ "stock/market/batch?symbols=AAPL,BDC&types=logo", 309 stock.toURL()); 310 } 311 312 313 @("news() builds an endpoint for a single symbol") 314 unittest { 315 auto stock = Stock("AAPL").news(); 316 assert(stock.toURL() == iexPrefix ~ "stock/AAPL/news", stock.toURL()); 317 318 stock = Stock("market").news(); 319 assert(stock.toURL() == iexPrefix ~ "stock/market/news", stock.toURL()); 320 321 stock = Stock("AAPL").news(5); 322 assert(stock.toURL() == iexPrefix ~ "stock/AAPL/news/last/5", stock.toURL()); 323 } 324 325 @("news() builds an endpoint for multiple symbols") 326 unittest { 327 import std.string : split; 328 auto stock = Stock("AAPL", "BDC").news(); 329 auto actual = stock.toURL().split('?'); 330 assert(actual[0] == iexPrefix ~ "stock/market/batch", actual[0]); 331 assert(actual[1].hasParameters(["symbols=AAPL,BDC", "types=news"]), actual[1]); 332 333 stock = Stock("AAPL", "BDC").news(5); 334 actual = stock.toURL().split('?'); 335 assert(actual[0] == iexPrefix ~ "stock/market/batch", actual[0]); 336 assert(actual[1].hasParameters(["symbols=AAPL,BDC", "types=news", "last=5"]), 337 actual[1]); 338 } 339 340 341 @("ohlc() builds an endpoint for a single stock symbol") 342 unittest { 343 auto stock = Stock("AAPL").ohlc(); 344 assert(stock.toURL() == iexPrefix ~ "stock/AAPL/ohlc", stock.toURL()); 345 346 stock = Stock("market").ohlc(); 347 assert(stock.toURL() == iexPrefix ~ "stock/market/ohlc", stock.toURL()); 348 } 349 350 @("ohlc() builds an endpoint for multiple symbols") 351 unittest { 352 import std.string : split; 353 auto stock = Stock("AAPL", "BDC").ohlc(); 354 auto actual = stock.toURL().split('?'); 355 assert(actual[0] == iexPrefix ~ "stock/market/batch", actual[0]); 356 assert(actual[1].hasParameters(["symbols=AAPL,BDC", "types=ohlc"]), actual[1]); 357 } 358 359 @("openclose() is equivalent to ohlc()") 360 unittest { 361 auto ohlc = Stock("AAPL").ohlc(); 362 auto openclose = Stock("AAPL").openclose(); 363 } 364 365 366 @("peers() builds an endpoint for a single stock symbol") 367 unittest { 368 auto stock = Stock("AAPL").peers(); 369 assert(stock.toURL() == iexPrefix ~ "stock/AAPL/peers", stock.toURL()); 370 } 371 372 @("peers() builds an endpoint for multiple symbols") 373 unittest { 374 import std.string : split; 375 auto stock = Stock("AAPL", "BDC").peers(); 376 auto actual = stock.toURL().split('?'); 377 assert(actual[0] == iexPrefix ~ "stock/market/batch", actual[0]); 378 assert(actual[1].hasParameters(["symbols=AAPL,BDC", "types=peers"]), actual[1]); 379 } 380 381 382 @("previous() builds an endpoint for a single stock symbol") 383 unittest { 384 auto stock = Stock("AAPL").previous(); 385 assert(stock.toURL() == iexPrefix ~ "stock/AAPL/previous", stock.toURL()); 386 387 stock = Stock("market").previous(); 388 assert(stock.toURL() == iexPrefix ~ "stock/market/previous", stock.toURL()); 389 } 390 391 @("ohlc() builds an endpoint for multiple symbols") 392 unittest { 393 import std.string : split; 394 auto stock = Stock("AAPL", "BDC").previous(); 395 auto actual = stock.toURL().split('?'); 396 assert(actual[0] == iexPrefix ~ "stock/market/batch", actual[0]); 397 assert(actual[1].hasParameters(["symbols=AAPL,BDC", "types=previous"]), 398 actual[1]); 399 } 400 401 402 @("price() builds an endpoint for a single stock symbol") 403 unittest { 404 auto stock = Stock("AAPL").price(); 405 assert(stock.toURL() == iexPrefix ~ "stock/AAPL/price", stock.toURL()); 406 } 407 408 @("price() builds an endpoint for multiple symbols") 409 unittest { 410 import std.string : split; 411 auto stock = Stock("AAPL", "BDC").price(); 412 auto actual = stock.toURL().split('?'); 413 assert(actual[0] == iexPrefix ~ "stock/market/batch", actual[0]); 414 assert(actual[1].hasParameters(["symbols=AAPL,BDC", "types=price"]), 415 actual[1]); 416 } 417 418 419 @("quote() builds an endpoint for a single stock symbol") 420 unittest { 421 import std.string : split; 422 auto stock = Stock("AAPL").quote(); 423 assert(stock.toURL() == iexPrefix ~ "stock/AAPL/quote", stock.toURL()); 424 425 stock = Stock("AAPL").quote(Yes.displayPercent); 426 auto actual = stock.toURL().split('?'); 427 assert(actual[0] == iexPrefix ~ "stock/AAPL/quote"); 428 assert(actual[1].hasParameters(["displayPercent=true"])); 429 } 430 431 @("quote() builds an endpoint for multiple stock symbols") 432 unittest { 433 import std.string : split; 434 auto stock = Stock("AAPL", "BDC").quote(); 435 assert(stock.toURL() == iexPrefix ~ "stock/market/batch?symbols=AAPL,BDC&types=quote", stock.toURL()); 436 437 stock = Stock("AAPL", "BDC").quote(Yes.displayPercent); 438 auto actual = stock.toURL().split('?'); 439 assert(actual[0] == iexPrefix ~ "stock/market/batch"); 440 assert(actual[1].hasParameters(["symbols=AAPL,BDC", "types=quote", "displayPercent=true"])); 441 } 442 443 444 @("relevant() builds an endpoint for a single stock symbol") 445 unittest { 446 auto stock = Stock("AAPL").relevant(); 447 assert(stock.toURL() == iexPrefix ~ "stock/AAPL/relevant", stock.toURL()); 448 } 449 450 @("relevant() builds an endpoint for multiple symbols") 451 unittest { 452 import std.string : split; 453 auto stock = Stock("AAPL", "BDC").relevant(); 454 auto actual = stock.toURL().split('?'); 455 assert(actual[0] == iexPrefix ~ "stock/market/batch", actual[0]); 456 assert(actual[1].hasParameters(["symbols=AAPL,BDC", "types=relevant"]), actual[1]); 457 } 458 459 460 @("splits() builds an endpoint for a single stock symbol") 461 unittest { 462 auto stock = Stock("AAPL").splits(SplitRange.TwoYears); 463 assert(stock.toURL() == iexPrefix ~ "stock/AAPL/splits/2y", stock.toURL()); 464 } 465 466 @("splits() builds an endpoint for multiple stock symbols") 467 unittest { 468 import std.string : split; 469 auto stock = Stock("AAPL", "BDC").splits(SplitRange.TwoYears); 470 auto actual = stock.toURL().split('?'); 471 assert(actual[0] == iexPrefix ~ "stock/market/batch", actual[0]); 472 assert(actual[1].hasParameters(["symbols=AAPL,BDC", "range=2y", "types=splits"]), 473 actual[1]); 474 } 475 476 477 @("volumeByVenue() builds an endpoint for a single stock symbol") 478 unittest { 479 auto stock = Stock("AAPL").volumeByVenue(); 480 assert(stock.toURL() == iexPrefix ~ "stock/AAPL/volume-by-venue", stock.toURL()); 481 } 482 483 @("volumeByVenue() builds an endpoint for multiple stock symbols") 484 unittest { 485 auto stock = Stock("AAPL", "BDC").volumeByVenue(); 486 assert(stock.toURL() == 487 iexPrefix ~ "stock/market/batch?symbols=AAPL,BDC&types=volume-by-venue", 488 stock.toURL()); 489 } 490 491 492 @("Build single-symbol batch endpoints") 493 unittest { 494 import std.string : split; 495 auto stock = Stock("AAPL") 496 .company() 497 .dividends(DividendRange.FiveYears); 498 499 // We don't have to use the /market endpoint but can, and it keeps the code 500 // simpler (e.g., we could do /stock/AAPL/batch instead). 501 502 auto actual = stock.toURL.split('?'); 503 assert(actual[0] == iexPrefix ~ "stock/market/batch", actual[0]); 504 assert(actual[1].hasParameters( 505 ["symbols=AAPL", "types=company,dividends", "range=5y"]), 506 actual[1]); 507 } 508 509 @("Build multiple-symbol batch endpoints") 510 unittest { 511 import std.string : split; 512 auto stock = Stock("AAPL", "BDC") 513 .quote() 514 .news(5) // Last 5 items. 515 .chart(ChartRange.OneMonth); 516 517 auto actual = stock.toURL().split('?'); 518 assert(actual[0] == iexPrefix ~ "stock/market/batch", actual[0]); 519 assert(actual[1].hasParameters( 520 ["symbols=AAPL,BDC", "types=quote,chart,news", "range=1m", "last=5"]), 521 actual[1]); 522 } 523 524 @("Only use the last-specified value of shared parameters.") 525 /+ TODO: I don't think I like this, but it's better than allowing inconsistency. 526 527 auto stock = Stock("AAPL") 528 .chart() 529 .dividends() 530 .range("1y"); 531 +/ 532 unittest { 533 import std.string : split; 534 auto stock = Stock("AAPL") 535 .chart(ChartRange.TwoYears) 536 .dividends(DividendRange.OneYear); 537 538 auto actual = stock.toURL().split('?'); 539 assert(actual[0] == iexPrefix ~ "stock/market/batch", actual[0]); 540 assert(actual[1].hasParameters(["types=dividends,chart", "range=1y"]), 541 actual[1]); 542 }