diff --git a/.JuliaFormatter.toml b/.JuliaFormatter.toml index 5ef28bd0..323237ba 100644 --- a/.JuliaFormatter.toml +++ b/.JuliaFormatter.toml @@ -1,3 +1 @@ style = "blue" -format_markdown = true -format_docstrings = true diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 2f19f766..682f4e0d 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -15,16 +15,16 @@ jobs: fail-fast: false matrix: version: - - "1.6.7" # LTS + - "1.6.7" # LTS - "1.6" - - "1" # Latest Release + - "1" # Latest Release os: - ubuntu-latest arch: - x64 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - uses: julia-actions/setup-julia@latest with: version: ${{ matrix.version }} @@ -53,10 +53,10 @@ jobs: name: Documentation runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - uses: julia-actions/setup-julia@latest with: - version: '1' + version: "1" - run: | git config --global user.name name git config --global user.email email diff --git a/.github/workflows/DocCleanup.yml b/.github/workflows/DocCleanup.yml index 15680241..f315740e 100644 --- a/.github/workflows/DocCleanup.yml +++ b/.github/workflows/DocCleanup.yml @@ -9,20 +9,20 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout gh-pages branch - uses: actions/checkout@v2 + uses: actions/checkout@v4 with: ref: gh-pages - name: Delete preview and history run: | - git config user.name "Documenter.jl" - git config user.email "documenter@juliadocs.github.io" - git rm -rf "previews/PR$PRNUM" - git commit -m "delete preview" - git branch gh-pages-new $(echo "delete history" | git commit-tree HEAD^{tree}) + git config user.name "Documenter.jl" + git config user.email "documenter@juliadocs.github.io" + git rm -rf "previews/PR$PRNUM" + git commit -m "delete preview" + git branch gh-pages-new $(echo "delete history" | git commit-tree HEAD^{tree}) env: - PRNUM: ${{ github.event.number }} + PRNUM: ${{ github.event.number }} - name: Push changes run: | - git push --force origin gh-pages-new:gh-pages + git push --force origin gh-pages-new:gh-pages diff --git a/.github/workflows/Format.yml b/.github/workflows/Format.yml deleted file mode 100644 index b6614264..00000000 --- a/.github/workflows/Format.yml +++ /dev/null @@ -1,31 +0,0 @@ -name: format-pr -on: - schedule: - - cron: "0 0 * * *" -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: julia-actions/cache@v2 - - name: Install JuliaFormatter and format - run: | - julia -e 'import Pkg; Pkg.add("JuliaFormatter")' - julia -e 'using JuliaFormatter; format(".")' - - # https://github.com/marketplace/actions/create-pull-request - # https://github.com/peter-evans/create-pull-request#reference-example - - name: Create Pull Request - id: cpr - uses: peter-evans/create-pull-request@v3 - with: - token: ${{ secrets.GITHUB_TOKEN }} - commit-message: Format .jl files - title: "Automatic JuliaFormatter.jl run" - branch: auto-juliaformatter-pr - delete-branch: true - labels: formatting, automated pr, no changelog - - name: Check outputs - run: | - echo "Pull Request Number - ${{ steps.cpr.outputs.pull-request-number }}" - echo "Pull Request URL - ${{ steps.cpr.outputs.pull-request-url }}" diff --git a/.github/workflows/FormatCheck.yml b/.github/workflows/FormatCheck.yml new file mode 100644 index 00000000..d062a7a7 --- /dev/null +++ b/.github/workflows/FormatCheck.yml @@ -0,0 +1,44 @@ +name: format-check + +on: + push: + branches: + - "master" + - "release-*" # Match release branches + tags: "*" # Trigger on tag pushes + pull_request: # Trigger on pull requests + +jobs: + build: + runs-on: ${{ matrix.os }} + strategy: + matrix: + julia-version: [1] # Julia versions + julia-arch: [x86] # Architectures + os: [ubuntu-latest] # OS + steps: + # Step 1: Set up Julia + - name: Set up Julia + uses: julia-actions/setup-julia@latest + with: + version: ${{ matrix.julia-version }} + arch: ${{ matrix.julia-arch }} + + # Step 2: Check out code + - name: Checkout code + uses: actions/checkout@v4 + + # Step 3: Install JuliaFormatter and check formatting + - name: Install JuliaFormatter and check formatting + run: | + julia -e ' + using Pkg; + Pkg.add(PackageSpec(name="JuliaFormatter")); + using JuliaFormatter; + result = format(".", verbose=true) + if !result + error("Some files are not properly formatted. Please run `using JuliaFormatter; format(\".\")` locally.") + else + println("All files are properly formatted.") + end + ' diff --git a/NEWS.md b/NEWS.md index d5151860..cb6108df 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,439 +1,386 @@ ### 0.16.1 -* Fix Candlestick plotting. ([#433]) + - Fix Candlestick plotting. ([#433](https://github.com/JuliaStats/TimeSeries.jl/pull/433)) -* Fix labels bug in `TimeArray` plot recipe. ([#430]) - -* Fix double-width char display in `Base.show`. ([#427]) - - -[#433]: https://github.com/JuliaStats/TimeSeries.jl/pull/433 -[#430]: https://github.com/JuliaStats/TimeSeries.jl/issues/430 -[#427]: https://github.com/JuliaStats/TimeSeries.jl/pull/427 + - Fix labels bug in `TimeArray` plot recipe. ([#430](https://github.com/JuliaStats/TimeSeries.jl/issues/430)) + - Fix double-width char display in `Base.show`. ([#427](https://github.com/JuliaStats/TimeSeries.jl/pull/427)) ### 0.16.0 -* Improve performance of `moving` function. (#414) - -* `moving` supports multi-column as input for user-defined function. (#415) - - ```julia - moving(ohlc, 10, dims = 2, colnames = [:A, ...]) do - # given that `ohlc` is a 500x4 `TimeArray`, - # size(A) is (10, 4) - ... - end - ``` - -* The argument `method` of function `merge` is a keyword argument now. (#416) - - ```julia - merge(x, y, method = :outer) - ``` - -* The function `merge` supports variable length input. (#416) - - ```julia - merge(x, y, [zs...], method = :outer) - ``` - -* New function `rename!` for in-place update of column names. (#417) - -* Fix issues of `TimeArray` column names copying. (#418) - -* Fix `@inbounds` handling for `TimeArray`. (#425) - -* `timearray[]` throws `BoundsError` now. (#420) - - ```julia - julia> cl[] - ERROR: BoundsError: attempt to access TimeArray{Float64,1,Date,Array{Float64,1}} - at index [] - ``` - -* `Tables.jl` interface integration. (#382) - -* 2D `getindex` supports. (#423) - - ```julia - ohlc[1:42, [:High, :Low]] - ohlc[42:end, [:High, :Low]] - ohlc[:, [:High, :Low]] - ohlc[42, [:High, :Low]] - ``` + - Improve performance of `moving` function. (#414) + - `moving` supports multi-column as input for user-defined function. (#415) + + ```julia + moving(ohlc, 10; dims=2, colnames=[:A, ...]) do + # given that `ohlc` is a 500x4 `TimeArray`, + # size(A) is (10, 4) + ... + end + ``` + - The argument `method` of function `merge` is a keyword argument now. (#416) + + ```julia + merge(x, y; method=:outer) + ``` + - The function `merge` supports variable length input. (#416) + + ```julia + merge(x, y, [zs...]; method=:outer) + ``` + - New function `rename!` for in-place update of column names. (#417) + - Fix issues of `TimeArray` column names copying. (#418) + - Fix `@inbounds` handling for `TimeArray`. (#425) + - `timearray[]` throws `BoundsError` now. (#420) + + ```julia + julia> cl[] + ERROR: BoundsError: attempt to access TimeArray{Float64,1,Date,Array{Float64,1}} + at index [] + ``` + - `Tables.jl` interface integration. (#382) + - 2D `getindex` supports. (#423) + + ```julia + ohlc[1:42, [:High, :Low]] + ohlc[42:end, [:High, :Low]] + ohlc[:, [:High, :Low]] + ohlc[42, [:High, :Low]] + ``` ### 0.15.0 -* New `TimeArray` constructor for creating a `TimeArray` from existing `TimeArray`. - ([#380]) - - ```julia - TimeArray(ta::TimeArray; timestamp = ..., values = ..., colnames = ..., meta = ...) - ``` - - E.g. - ```julia - julia> meta(cl) - "AAPL" - - julia> cl′ = TimeArray(cl; meta = :AAPL); - - julia> meta(cl′) - :AAPL - ``` - -* `merge` now throws `ArgumentError` on invalid column setup. ([#405]) - -* `percentchange` and `dropnan` now throw `ArgumentError` on invalid `method`. ([#405]) + - New `TimeArray` constructor for creating a `TimeArray` from existing `TimeArray`. + ([#380]) + + ```julia + TimeArray(ta::TimeArray; timestamp=..., values=..., colnames=..., meta=...) + ``` + + E.g. + + ```julia + julia> meta(cl) + "AAPL" + + julia> cl′ = TimeArray(cl; meta=:AAPL); + + julia> meta(cl′) + :AAPL + ``` + - `merge` now throws `ArgumentError` on invalid column setup. ([#405]) + - `percentchange` and `dropnan` now throw `ArgumentError` on invalid `method`. ([#405]) ### 0.14.0 -* Symbol column indexing. ([#377]) - And the String indexing is deprecated. - - E.g. - - ```julia - using MarketData - ohlc[:Close] # and cl["Close"] is deprecated - ``` - -* `Base.getproperty` support. ([#377]) - All the columns can be accessed via the form `ta.column_name`. - - E.g. - - ```julia - using MarketData - ohlc.Open - ``` - - The original `TimeArray` fields getters is available as functions. - - * `timestamp(::TimeArray)` - * `values(::TimeArray)` - * `colnames(::TimeArray)` - * `meta(::TimeArray)` - - ```julia - ohlc.values # this is unavailable due to Base.getproperty support - values(ohlc) # change to this - ``` - -[#377]: https://github.com/JuliaStats/TimeSeries.jl/pull/377 + - Symbol column indexing. ([#377](https://github.com/JuliaStats/TimeSeries.jl/pull/377)) + And the String indexing is deprecated. + + E.g. + + ```julia + using MarketData + ohlc[:Close] # and cl["Close"] is deprecated + ``` + - `Base.getproperty` support. ([#377](https://github.com/JuliaStats/TimeSeries.jl/pull/377)) + All the columns can be accessed via the form `ta.column_name`. + + E.g. + + ```julia + using MarketData + ohlc.Open + ``` + + The original `TimeArray` fields getters is available as functions. + + + `timestamp(::TimeArray)` + + `values(::TimeArray)` + + `colnames(::TimeArray)` + + `meta(::TimeArray)` + + ```julia + ohlc.values # this is unavailable due to Base.getproperty support + values(ohlc) # change to this + ``` ### 0.13.0 -* Julia v0.7/1.0 support. ([#370]) - -[#370]: https://github.com/JuliaStats/TimeSeries.jl/pull/370 - + - Julia v0.7/1.0 support. ([#370](https://github.com/JuliaStats/TimeSeries.jl/pull/370)) ### 0.12.0 -* Support `Base.copy(::TimeArray)`. ([#352]) - -* A new option for `TimeArray` constructor: `unchecked::Bool` to skip - the sanity check of timestamp. ([#361]) - - * `merge()` optimization via the `unchecked` option. ([#363]) - -* Revoking deprecation warning of `==` and redefining its meaning as - 'comparing all fields of two `TimeArray`s'. - Note that if two `TimeArray`s have different dimension, we consider that is - unequal. - ([#356], [#357]) - - ```julia - julia> cl == copy(cl) - true - ``` - - * Also, `isequal` and `hash` is supported now. - + - Support `Base.copy(::TimeArray)`. ([#352](https://github.com/JuliaStats/TimeSeries.jl/pull/352)) + + - A new option for `TimeArray` constructor: `unchecked::Bool` to skip + the sanity check of timestamp. ([#361](https://github.com/JuliaStats/TimeSeries.jl/pull/361)) + + + `merge()` optimization via the `unchecked` option. ([#363](https://github.com/JuliaStats/TimeSeries.jl/pull/363)) + - Revoking deprecation warning of `==` and redefining its meaning as + 'comparing all fields of two `TimeArray`s'. + Note that if two `TimeArray`s have different dimension, we consider that is + unequal. + ([#356](https://github.com/JuliaStats/TimeSeries.jl/pull/356), [#357](https://github.com/JuliaStats/TimeSeries.jl/pull/357)) + ```julia - julia> d = Dict(cl => 42); - - julia> d[cl] - 42 - - julia> d[copy(cl)] - 42 + julia> cl == copy(cl) + true + ``` + + + Also, `isequal` and `hash` is supported now. + + ```julia + julia> d = Dict(cl => 42); + + julia> d[cl] + 42 + + julia> d[copy(cl)] + 42 + ``` + - `diff` supports higher order difference now.([#350](https://github.com/JuliaStats/TimeSeries.jl/pull/350)) + - `diff` support `n` time steps lag now. ([#362](https://github.com/JuliaStats/TimeSeries.jl/pull/362)) + + ```julia + julia> diff(cl, 5) + 495x1 TimeSeries.TimeArray{Float64,1,Date,Array{Float64,1}} 2000-01-10 to 2001-12-31 + │ │ Close │ + ├────────────┼────────┤ + │ 2000-01-10 │ -14.19 │ + │ 2000-01-11 │ -9.75 │ + │ 2000-01-12 │ -16.81 │ + ⋮ + │ 2001-12-27 │ 0.45 │ + │ 2001-12-28 │ 1.76 │ + │ 2001-12-31 │ 0.9 │ + ``` + - New keyword argument for `readtimearray`: `header::Bool`. ([#358](https://github.com/JuliaStats/TimeSeries.jl/pull/358)) + - `TimeArray` supports `all()` and `any()` now. ([#356](https://github.com/JuliaStats/TimeSeries.jl/pull/356), [#359](https://github.com/JuliaStats/TimeSeries.jl/pull/359)) + + ```julia + julia> ta + 3x2 TimeSeries.TimeArray{Int64,2,Date,Array{Int64,2}} 2000-01-03 to 2000-01-05 + │ │ _1 │ _2 │ + ├────────────┼───────┼───────┤ + │ 2000-01-03 │ 1 │ 2 │ + │ 2000-01-04 │ 3 │ 4 │ + │ 2000-01-05 │ 5 │ 6 │ + + julia> all(ta .> 3, 2) + 3x1 TimeSeries.TimeArray{Bool,2,Date,BitArray{2}} 2000-01-03 to 2000-01-05 + │ │ all │ + ├────────────┼───────┤ + │ 2000-01-03 │ false │ + │ 2000-01-04 │ false │ + │ 2000-01-05 │ true │ ``` - -* `diff` supports higher order difference now.([#350]) - -* `diff` support `n` time steps lag now. ([#362]) - - ```julia - julia> diff(cl, 5) - 495x1 TimeSeries.TimeArray{Float64,1,Date,Array{Float64,1}} 2000-01-10 to 2001-12-31 - │ │ Close │ - ├────────────┼────────┤ - │ 2000-01-10 │ -14.19 │ - │ 2000-01-11 │ -9.75 │ - │ 2000-01-12 │ -16.81 │ - ⋮ - │ 2001-12-27 │ 0.45 │ - │ 2001-12-28 │ 1.76 │ - │ 2001-12-31 │ 0.9 │ - ``` - -* New keyword argument for `readtimearray`: `header::Bool`. ([#358]) - -* `TimeArray` supports `all()` and `any()` now. ([#356], [#359]) - - ```julia - julia> ta - 3x2 TimeSeries.TimeArray{Int64,2,Date,Array{Int64,2}} 2000-01-03 to 2000-01-05 - │ │ _1 │ _2 │ - ├────────────┼───────┼───────┤ - │ 2000-01-03 │ 1 │ 2 │ - │ 2000-01-04 │ 3 │ 4 │ - │ 2000-01-05 │ 5 │ 6 │ - - julia> all(ta .> 3, 2) - 3x1 TimeSeries.TimeArray{Bool,2,Date,BitArray{2}} 2000-01-03 to 2000-01-05 - │ │ all │ - ├────────────┼───────┤ - │ 2000-01-03 │ false │ - │ 2000-01-04 │ false │ - │ 2000-01-05 │ true │ - ``` - - -[#350]: https://github.com/JuliaStats/TimeSeries.jl/pull/350 -[#352]: https://github.com/JuliaStats/TimeSeries.jl/pull/352 -[#356]: https://github.com/JuliaStats/TimeSeries.jl/pull/356 -[#357]: https://github.com/JuliaStats/TimeSeries.jl/pull/357 -[#358]: https://github.com/JuliaStats/TimeSeries.jl/pull/358 -[#359]: https://github.com/JuliaStats/TimeSeries.jl/pull/359 -[#361]: https://github.com/JuliaStats/TimeSeries.jl/pull/361 -[#362]: https://github.com/JuliaStats/TimeSeries.jl/pull/362 -[#363]: https://github.com/JuliaStats/TimeSeries.jl/pull/363 - ### 0.11.0 -* Dropping 0.5 support. (issue [#327]) - -* `TimeArray` constructor, `merge`, `update`, `vcat` and `rename` - will throw typed exceptions. (issue [#322]) + - Dropping 0.5 support. (issue [#327](https://github.com/JuliaStats/TimeSeries.jl/pull/327)) -* Signature of `moving` becomes - - ```julia + - `TimeArray` constructor, `merge`, `update`, `vcat` and `rename` + will throw typed exceptions. (issue [#322](https://github.com/JuliaStats/TimeSeries.jl/pull/322)) + - Signature of `moving` becomes + + ```julia moving(f, ta::TimeArray, window; padding=false) - ``` - - , in order to support do-syntax. The original one is deprecated. (issue [#334]) - -* Signature of `upto` becomes - - ```julia + ``` + + , in order to support do-syntax. The original one is deprecated. (issue [#334](https://github.com/JuliaStats/TimeSeries.jl/pull/334)) + - Signature of `upto` becomes + + ```julia moving(f, ta::TimeArray, window; padding=false) - ``` - - , in order to support do-syntax. The original one is deprecated. (issue [#337]) - -* `map` supports callable object. (issue [#339]) - - ```julia - struct T end - (::T)(timestamp, x) = (timestamp, x + 42) - - t = T() - - map(t, ta) - ``` - -* `hcat` support. Given two `TimeArray` which have same timestamp, - - ```julia + ``` + + , in order to support do-syntax. The original one is deprecated. (issue [#337](https://github.com/JuliaStats/TimeSeries.jl/pull/337)) + - `map` supports callable object. (issue [#339](https://github.com/JuliaStats/TimeSeries.jl/pull/339)) + + ```julia + struct T end + (::T)(timestamp, x) = (timestamp, x + 42) + + t = T() + + map(t, ta) + ``` + - `hcat` support. Given two `TimeArray` which have same timestamp, + + ```julia [ta1 ta2] - ``` - - can perform faster than ```merge(ta1, ta2)``` in this case. - (issue [#341]) - -* Support more reduction functions of Base. (issue [#338]) - * `sum` - * `mean` - * `std` - * `var` - - e.g. - ```julia + ``` + + can perform faster than `merge(ta1, ta2)` in this case. + (issue [#341](https://github.com/JuliaStats/TimeSeries.jl/pull/341)) + - Support more reduction functions of Base. (issue [#338](https://github.com/JuliaStats/TimeSeries.jl/pull/338)) + + + `sum` + + `mean` + + `std` + + `var` + + e.g. + + ```julia sum(ta) # same as sum(ta, 1), and it's equivalent to moving(sum, ta, length(ta)) sum(ta, 2) - ``` - -* Support cumulative prod `cumprod`. (issue [#338]) - -* Support `eachindex(ta)`. (issue [#336]) - -[#322]: https://github.com/JuliaStats/TimeSeries.jl/pull/322 -[#327]: https://github.com/JuliaStats/TimeSeries.jl/pull/327 -[#334]: https://github.com/JuliaStats/TimeSeries.jl/pull/334 -[#336]: https://github.com/JuliaStats/TimeSeries.jl/pull/336 -[#337]: https://github.com/JuliaStats/TimeSeries.jl/pull/337 -[#338]: https://github.com/JuliaStats/TimeSeries.jl/pull/338 -[#339]: https://github.com/JuliaStats/TimeSeries.jl/pull/339 -[#341]: https://github.com/JuliaStats/TimeSeries.jl/pull/341 + ``` + - Support cumulative prod `cumprod`. (issue [#338](https://github.com/JuliaStats/TimeSeries.jl/pull/338)) + - Support `eachindex(ta)`. (issue [#336](https://github.com/JuliaStats/TimeSeries.jl/pull/336)) ### 0.10.0 -* add support for time series plotting via RecipesBase dependency (thank you @mkborregaard) -* add StepRange indexing support (issue #311) + - add support for time series plotting via RecipesBase dependency (thank you @mkborregaard) + - add StepRange indexing support (issue #311) ### 0.9.2 -* improve readtimearray to accept IOBuffer (@femtotrader fixes issue #298) + - improve readtimearray to accept IOBuffer (@femtotrader fixes issue #298) ### 0.9.1 -* improve update method and dis-allow updating of empty time arrays (fixes issue #286) + - improve update method and dis-allow updating of empty time arrays (fixes issue #286) ### 0.9.0 -* first version to support julia 0.5 release candidates + - first version to support julia 0.5 release candidates ### 0.8.8 -* merge method deals with meta field values more robustly (fixes issue #164) + - merge method deals with meta field values more robustly (fixes issue #164) ### 0.8.7 -* reexport Base.Dates methods and DataTypes (fixes issue #277) + - reexport Base.Dates methods and DataTypes (fixes issue #277) ### 0.8.6 -* unique column names are enforced (fixes issue #255) + - unique column names are enforced (fixes issue #255) ### 0.8.5 -* update() method creates new TimeArray from existing one, with new timestamp/value pair. -* rename() method creates new TimeArray from existing one, with new column name(s). -* adds tail() and head() methods + - update() method creates new TimeArray from existing one, with new timestamp/value pair. + - rename() method creates new TimeArray from existing one, with new column name(s). + - adds tail() and head() methods ### 0.8.4 -* allows users to show customizable representations for missing values, which are represented as NaN values in the array. + - allows users to show customizable representations for missing values, which are represented as NaN values in the array. ### 0.8.3 -* provides TimeArray constructor without requiring colnames argument (defaults to empty array) + - provides TimeArray constructor without requiring colnames argument (defaults to empty array) ### 0.8.2 -* makes to() and from() more robust by taking zero-length time arrays (@dourouc05 ) + - makes to() and from() more robust by taking zero-length time arrays (@dourouc05 ) ### 0.8.1 -* removes using Base.Dates from outside module @tkelman + - removes using Base.Dates from outside module @tkelman ### 0.8.0 -* deprecated `collapse(ta::TimeArray, f::Function; period::Function=week)` in favour of `collapse(ta::TimeArray, period::Function, timestamp::Function, value::Function=timestamp)` and added support for collapsing 2D TimeArrays + - deprecated `collapse(ta::TimeArray, f::Function; period::Function=week)` in favour of `collapse(ta::TimeArray, period::Function, timestamp::Function, value::Function=timestamp)` and added support for collapsing 2D TimeArrays ### 0.7.4 -* allow math operations between different Number subtypes -* explicitly convert column names to strings in `readtimearray` -* operations between TimeArrays with non-matching meta fields now succeed, with a `Void` meta in the result + - allow math operations between different Number subtypes + - explicitly convert column names to strings in `readtimearray` + - operations between TimeArrays with non-matching meta fields now succeed, with a `Void` meta in the result ### 0.7.3 -* ensure dates are sorted for vcat and map (@dourouc05) + - ensure dates are sorted for vcat and map (@dourouc05) ### 0.7.2 -* map and vcat methods added (thanks again @dourouc05) + - map and vcat methods added (thanks again @dourouc05) ### 0.7.1 -* readtimearray method now allows arbitrary delimiters (thanks @dourouc05) + - readtimearray method now allows arbitrary delimiters (thanks @dourouc05) ### 0.7.0 -* TimeType replaces Union{Date, DateTime} -* meta field in Type downgraded from parameterized to Any -* NaN sentinels added as a kwarg to lag and lead methods -* merge method now supports left, right and outer joins -* percentchange takes method argument as a Symbol vs String -* new methods added including: uniformspaced, uniformspace, dropnan, diff -* findall added to deprecated list in favor of find + - TimeType replaces Union{Date, DateTime} + - meta field in Type downgraded from parameterized to Any + - NaN sentinels added as a kwarg to lag and lead methods + - merge method now supports left, right and outer joins + - percentchange takes method argument as a Symbol vs String + - new methods added including: uniformspaced, uniformspace, dropnan, diff + - findall added to deprecated list in favor of find ### 0.6.7 -* refactors when() method for 30% performance improvement + - refactors when() method for 30% performance improvement ### 0.6.6 -* begin deprecation of by() method, which is being replaced by when() -* when() re-arranges the argument order to TimeArray, Function, Int (or ASCIIString) -* support for ASCIIStrings are now provided for both by() and when() methods + - begin deprecation of by() method, which is being replaced by when() + - when() re-arranges the argument order to TimeArray, Function, Int (or ASCIIString) + - support for ASCIIStrings are now provided for both by() and when() methods ### 0.6.5 -* support added for displaying empty TimeArray -* common scalar -> scalar math functions as unary operators -* adds isnan and isinf -* fixes tests on meta field -* downgrades show tests to pending + - support added for displaying empty TimeArray + - common scalar -> scalar math functions as unary operators + - adds isnan and isinf + - fixes tests on meta field + - downgrades show tests to pending ### 0.6.4 -* replaces Nothing -> nothing and String -> AbstractString + - replaces Nothing -> nothing and String -> AbstractString ### 0.6.3 -* precompile support added -* test/combine.jl and test/split.jl now imports Base.Dates explicitly + - precompile support added + - test/combine.jl and test/split.jl now imports Base.Dates explicitly ### 0.6.2 -* added support for `end` keyword in indices -* added support for lookups via Boolean TimeArrays - e.g. ta[ta["col"] .> 50] -* speedup for lookups via lists of Date/DateTime objects + - added support for `end` keyword in indices + - added support for lookups via Boolean TimeArrays - e.g. ta[ta["col"] .> 50] + - speedup for lookups via lists of Date/DateTime objects ### 0.6.1 -* a phantom release that is actually older than 0.6.0 + - a phantom release that is actually older than 0.6.0 ### 0.6.0 -* first version with support for Julia 0.4 only -* generalized value container from Array to AbstractArray -* implemented new element-wise operators: !, ~, &, |, $, %, !== -* implemented element-wise unary math operators (+, -) -* side note: a previous commit was tagged with v0.6.0 incorrectly, this commit resolves that mistake + - first version with support for Julia 0.4 only + - generalized value container from Array to AbstractArray + - implemented new element-wise operators: !, ~, &, |, $, %, !== + - implemented element-wise unary math operators (+, -) + - side note: a previous commit was tagged with v0.6.0 incorrectly, this commit resolves that mistake ### 0.5.11 -* last version with support for Julia 0.3 -* support for Julia 0.4 dropped, along with the Compat package + - last version with support for Julia 0.3 + - support for Julia 0.4 dropped, along with the Compat package ### 0.5.10 -* changed references of flipud(A) to flipdim(A,1)_ -* changed references of round(x) to iround(Integer,x)_ -* changed references of iround(Integer,x) back to round(Integer,x)_ -* changed references of int(x) to round(Int64, x) -* changed references of float(x) to map(Float64, x) -* changed references of [a] to [a;] in a comprehension found in the by() method -* added Compat package -* substantial speedup for element-wise mathematical operators + - changed references of flipud(A) to flipdim(A,1)_ + - changed references of round(x) to iround(Integer,x)_ + - changed references of iround(Integer,x) back to round(Integer,x)_ + - changed references of int(x) to round(Int64, x) + - changed references of float(x) to map(Float64, x) + - changed references of [a] to [a;] in a comprehension found in the by() method + - added Compat package + - substantial speedup for element-wise mathematical operators ### 0.5.9 -* added kwarg argument `format` to the `readtimearray` method to allow parsing datetime formats that are not -currently supported. -* changed two references to `Range1` to `UnitRange` -* added import of Base.values. I had defined it first and I guess they like it so much they co-opted it. :) + - added kwarg argument `format` to the `readtimearray` method to allow parsing datetime formats that are not + currently supported. + - changed two references to `Range1` to `UnitRange` + - added import of Base.values. I had defined it first and I guess they like it so much they co-opted it. :) ### pre-0.5.8 diff --git a/README.md b/README.md index 8af09436..52503cac 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,4 @@ -TimeSeries.jl -============ +# TimeSeries.jl [![Latest Documentation](https://img.shields.io/badge/docs-dev-blue.svg)](https://JuliaStats.github.io/TimeSeries.jl/dev) [![Stable Documentation](https://img.shields.io/badge/docs-stable-blue.svg)](https://JuliaStats.github.io/TimeSeries.jl/stable) @@ -13,6 +12,7 @@ TimeSeries aims to provide a lightweight framework for working with time series Documentation is provided [here](http://juliastats.github.io/TimeSeries.jl/latest/). ## Installation + Assuming that you already have Julia correctly installed, it suffices to import TimeSeries.jl in the standard way: ```julia @@ -31,4 +31,4 @@ ta = TimeArray(dates, rand(length(dates))) timestamps = DateTime(2018, 1, 1):Hour(1):DateTime(2018, 12, 31) ta = TimeArray(timestamps, rand(length(timestamps))) -``` \ No newline at end of file +``` diff --git a/docs/make.jl b/docs/make.jl index 7a944d29..96d01ecd 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -1,8 +1,7 @@ using Documenter using TimeSeries - -makedocs( +makedocs(; format=Documenter.HTML(; prettyurls=(get(ENV, "CI", nothing) == "true")), sitename="TimeSeries.jl", modules=[TimeSeries], @@ -21,11 +20,9 @@ makedocs( "tables.md", "dotfile.md", "plotting.md", - ] + ], ) -deploydocs( - repo="github.com/JuliaStats/TimeSeries.jl.git", - devbranch="master", - push_preview=true, +deploydocs(; + repo="github.com/JuliaStats/TimeSeries.jl.git", devbranch="master", push_preview=true ) diff --git a/docs/src/apply.md b/docs/src/apply.md index b5fd862a..40cb6b29 100644 --- a/docs/src/apply.md +++ b/docs/src/apply.md @@ -34,7 +34,7 @@ value. To pad the returned `TimeArray` with `NaN` values instead, you can pass `padding=true` as a keyword argument: ```@repl lag -lag(cl[1:3], padding=true) +lag(cl[1:3]; padding=true) ``` ## `lead` diff --git a/docs/src/combine.md b/docs/src/combine.md index 1edde22a..d1a06852 100644 --- a/docs/src/combine.md +++ b/docs/src/combine.md @@ -34,9 +34,9 @@ particular timestamp only exists in one of the series to be merged. For example: ```@repl merge -merge(op[1:3], cl[2:4], method = :left) -merge(op[1:3], cl[2:4], method = :right) -merge(op[1:3], cl[2:4], method = :outer) +merge(op[1:3], cl[2:4]; method=:left) +merge(op[1:3], cl[2:4]; method=:right) +merge(op[1:3], cl[2:4]; method=:outer) ``` The `merge` method allows users to specify the value for the `meta` @@ -49,7 +49,7 @@ value: ```@repl merge meta(AppleCat) -CatApple = merge(CAT, AAPL, meta = 47); +CatApple = merge(CAT, AAPL; meta=47); meta(CatApple) meta(merge(AppleCat, CatApple)) ``` @@ -71,7 +71,7 @@ timestamp. A non-exhaustive list of valid time methods is presented below. | `Dates` method | Time length | -|-----------------|-------------------------------| +|:--------------- |:----------------------------- | | `Dates.quarter` | quarterly | | `day` | daily | | `week` | weekly, starting from Monday. | diff --git a/docs/src/dotfile.md b/docs/src/dotfile.md index bc383d89..4a34932b 100644 --- a/docs/src/dotfile.md +++ b/docs/src/dotfile.md @@ -10,6 +10,7 @@ Here is an handy way to edit it: julia> using TimeSeries julia> edit(joinpath(dirname(pathof(TimeSeries)), ".timeseriesrc.jl")) + ``` ## `DECIMALS` @@ -58,11 +59,11 @@ This output is controlled with `const` values to accommodate difficult to remember unicode numbers: ```julia -const NAN = "NaN" -const NA = "NA" +const NAN = "NaN" +const NA = "NA" const BLACKHOLE = "\u2B24" const DOTCIRCLE = "\u25CC" -const QUESTION = "\u003F" +const QUESTION = "\u003F" MISSING = NAN ``` @@ -76,7 +77,7 @@ at it! Here is an example in REPL with the default: ```julia -julia> lag(cl, padding=true) +julia> lag(cl; padding=true) 500x1 TimeSeries.TimeArray{Float64,1,Date,Array{Float64,1}} 2000-01-03 to 2001-12-31 │ │ Close │ ├────────────┼────────┤ @@ -103,7 +104,7 @@ julia> lag(cl, padding=true) Here is an example in REPL with `NA` selected: ```julia -julia> lag(cl, padding=true) +julia> lag(cl; padding=true) 500x1 TimeSeries.TimeArray{Float64,1,Date,Array{Float64,1}} 2000-01-03 to 2001-12-31 │ │ Close │ ├────────────┼────────┤ @@ -130,7 +131,7 @@ julia> lag(cl, padding=true) Here is an example in REPL with `BLACKHOLE` selected: ```julia -julia> lag(cl, padding=true) +julia> lag(cl; padding=true) 500x1 TimeSeries.TimeArray{Float64,1,Date,Array{Float64,1}} 2000-01-03 to 2001-12-31 │ │ Close │ ├────────────┼────────┤ diff --git a/docs/src/getting_started.md b/docs/src/getting_started.md index c7c2e94d..09983e2f 100644 --- a/docs/src/getting_started.md +++ b/docs/src/getting_started.md @@ -5,6 +5,7 @@ To add it to your Julia packages, simply do the following in REPL: ```julia julia> Pkg.add("TimeSeries") + ``` Throughout this tutorial, we'll be using historical financial data sets, @@ -13,6 +14,7 @@ registered and can be added: ```julia julia> Pkg.add("MarketData") + ``` To create dummy data without using the `MarketData` package, simply use diff --git a/docs/src/indexing.md b/docs/src/indexing.md index d6636e54..3503d87c 100644 --- a/docs/src/indexing.md +++ b/docs/src/indexing.md @@ -7,7 +7,7 @@ Indexing out a time series is done with common bracketing semantics. ### Integer | Example | Description | Indexing value | -|--------------|---------------------------------------|--------------------------------| +|:------------ |:------------------------------------- |:------------------------------ | | `[1]` | First row of data only | single integer | | `[1:3]` | First through third row only | integer range | | `[1:2:10]` | Odd row between first to tenth row | integer range with step | @@ -24,14 +24,14 @@ using MarketData ohlc[1] ohlc[1:3] ohlc[1:2:10] -ohlc[[1:3;8]] +ohlc[[1:3; 8]] ohlc[end] ``` ### Date and DateTime | Example | Description | Indexing value | -|----------------------------------------------|--------------------------------------------|----------------| +|:-------------------------------------------- |:------------------------------------------ |:-------------- | | `[Date(2000, 1, 3)]` | The row containing Jan 3, 2000 timestamp | single Date | | `[[Date(2000, 1, 3), Date(2000, 2, 4)]]` | The rows containing Jan 3 & Feb 4, 2000 | multiple Dates | | `[Date(2000, 1, 3):Day(1):Date(2000, 2, 4)]` | The rows between Jan 3, 2000 & Feb 4, 2000 | range of Date | @@ -53,7 +53,7 @@ ohlc[Date(2000, 1, 3):Day(1):Date(2000, 2, 4)] ### Symbol | Example | Description | Indexing value | -|---------------------|----------------------------------------|------------------| +|:------------------- |:-------------------------------------- |:---------------- | | `[:Open]` | The column named `:Open` | single symbol | | `[:Open, :Close]` | The columns named `:Open` and `:Close` | multiple symbols | | `[[:Open, :Close]]` | The columns named `:Open` and `:Close` | multiple symbols | @@ -75,7 +75,7 @@ ohlc[cols] ## Mixed approach | Example | Description | Indexing value | -|-----------------------------|--------------------------------|-------------------------------| +|:--------------------------- |:------------------------------ |:----------------------------- | | `[1:3, :Open]` | `:Open` column & first 3 rows | single symbol & integer range | | `[:Open][Date(2000, 1, 3)]` | `:Open` column and Jan 3, 2000 | single symbol & Date | diff --git a/docs/src/modify.md b/docs/src/modify.md index 52a6a200..85a49358 100644 --- a/docs/src/modify.md +++ b/docs/src/modify.md @@ -11,14 +11,14 @@ The `rename` method allows the column name(s) to be changed. The `rename!` is used for in-place update. ```@repl base -rename(cl, :Close′) |> first -rename(cl, [:Close′]) |> first -rename(ohlc, [:Open′, :High′, :Low′, :Close′]) |> first -rename(ohlc, :Open => :Open′) |> first -rename(ohlc, :Open => :Open′, :Close => :Close′) |> first -rename(ohlc, Dict(:Open => :Open′, :Close => :Close′)...) |> first -rename(Symbol ∘ uppercase ∘ string, ohlc) |> first -rename(uppercase, ohlc, String) |> first +first(rename(cl, :Close′)) +first(rename(cl, [:Close′])) +first(rename(ohlc, [:Open′, :High′, :Low′, :Close′])) +first(rename(ohlc, :Open => :Open′)) +first(rename(ohlc, :Open => :Open′, :Close => :Close′)) +first(rename(ohlc, Dict(:Open => :Open′, :Close => :Close′)...)) +first(rename(Symbol ∘ uppercase ∘ string, ohlc)) +first(rename(uppercase, ohlc, String)) ``` ```@docs diff --git a/docs/src/operators.md b/docs/src/operators.md index e1a8a672..75b507f5 100644 --- a/docs/src/operators.md +++ b/docs/src/operators.md @@ -21,7 +21,7 @@ likewise excluded here. Base uses `^` to indicate matrix self-multiplication, and so it is not implemented in this context. | Operator | Description | -|----------|----------------------------------------| +|:-------- |:-------------------------------------- | | `.+` | arithmetic element-wise addition | | `.-` | arithmetic element-wise subtraction | | `.*` | arithmetic element-wise multiplication | @@ -39,7 +39,7 @@ and `Int`, `Float`, or `Bool` values. The semantics of an non-dot operators (`>`) is unclear, and such operators are not supported. | Operator | Description | -|----------|-----------------------------------------------| +|:-------- |:--------------------------------------------- | | `.>` | element-wise greater-than comparison | | `.<` | element-wise less-than comparison | | `.==` | element-wise equivalent comparison | @@ -55,7 +55,7 @@ two `TimeArray` objects are provided. Operations between a single `TimeArray` and `Bool` are also supported. | Operator | Description | -|------------|--------------------------| +|:---------- |:------------------------ | | `.&` | element-wise logical AND | | `.\|` | element-wise logical OR | | `.!`, `.~` | element-wise logical NOT | diff --git a/docs/src/plotting.md b/docs/src/plotting.md index f08efbb1..2f97e37f 100644 --- a/docs/src/plotting.md +++ b/docs/src/plotting.md @@ -9,10 +9,10 @@ Here we use the data from Yahoo Fiance as a demo. ```@example plot using Plots, MarketData, TimeSeries -default(show = false) # hide +default(; show=false) # hide ENV["GKSwstype"] = "100" # hide gr() -ta = yahoo(:GOOG, YahooOpt(period1 = now() - Month(1))) +ta = yahoo(:GOOG, YahooOpt(; period1=now() - Month(1))) ``` ## Plotting as multiple series @@ -24,8 +24,10 @@ backend). ```@example plot plot(ta[:Open, :High, :Low, :Close]) -savefig("multi-series.svg"); nothing # hide +savefig("multi-series.svg"); +nothing; # hide ``` + ![](multi-series.svg) ## Plotting candlestick @@ -34,25 +36,31 @@ We have `seriestype = :candlestick` support that requires four columns exist in They are `open`, `high`, `low` and `close` (case-insensitive). ```@example plot -plot(ta, seriestype = :candlestick) -savefig("cs.svg"); nothing # hide +plot(ta; seriestype=:candlestick) +savefig("cs.svg"); +nothing; # hide ``` + ![](cs.svg) ### Other available attributes -- `bar_width::Float64` the valid value is from `0` to `1`. + - `bar_width::Float64` the valid value is from `0` to `1`. ```@example plot -plot(ta, seriestype = :candlestick, bar_width = 0.7) -savefig("bw.svg"); nothing # hide +plot(ta; seriestype=:candlestick, bar_width=0.7) +savefig("bw.svg"); +nothing; # hide ``` + ![](bw.svg) -- `xticks::Int` for controlling the density of x axis labels. + - `xticks::Int` for controlling the density of x axis labels. ```@example plot -plot(ta, seriestype = :candlestick, xticks = 3, xrotation = 60) -savefig("xticks.svg"); nothing # hide +plot(ta; seriestype=:candlestick, xticks=3, xrotation=60) +savefig("xticks.svg"); +nothing; # hide ``` + ![](xticks.svg) diff --git a/docs/src/readwrite.md b/docs/src/readwrite.md index 5283f087..366ceb87 100644 --- a/docs/src/readwrite.md +++ b/docs/src/readwrite.md @@ -24,7 +24,7 @@ provided where users can pass the format of their data. For example: ```julia -ta = readtimearray("close.csv", format="dd/mm/yyyy HH:MM", delim=';') +ta = readtimearray("close.csv"; format="dd/mm/yyyy HH:MM", delim=';') ``` A more robust regex parsing engine is planned so users will not need to diff --git a/docs/src/split.md b/docs/src/split.md index f7858910..6b474a77 100644 --- a/docs/src/split.md +++ b/docs/src/split.md @@ -19,7 +19,7 @@ The period argument holds a valid `Date` method. Below are currently available alternatives. | Dates method | Example | -|--------------------|--------------------------| +|:------------------ |:------------------------ | | `day` | Jan 3, 2000 = 3 | | `dayname` | Jan 3, 2000 = "Monday" | | `week` | Jan 3, 2000 = 1 | diff --git a/docs/src/tables.md b/docs/src/tables.md index ff03fec6..9ad426d6 100644 --- a/docs/src/tables.md +++ b/docs/src/tables.md @@ -1,6 +1,7 @@ # Tables.jl Interface Integration Quoted from the home page of Tables.jl: + > The [Table.jl](https://github.com/JuliaData/Tables.jl) package provides simple, > yet powerful interface functions for working with all kinds tabular data through > predictable access patterns. @@ -19,10 +20,10 @@ In Julia v1.1+, these two functions are supported and baked by `Tables.jl`. ```@repl using MarketData -for row ∈ eachrow(ohlc) - time = row.timestamp - c = row.Close - # ... +for row in eachrow(ohlc) + time = row.timestamp + c = row.Close + # ... end ``` @@ -40,11 +41,10 @@ In this case, user needs to point out the column of time index via the ```@repl df-ta df′ = DataFrames.rename(df, :timestamp => :A); -df′ |> first -TimeArray(df′, timestamp = :A) +first(df′) +TimeArray(df′; timestamp=:A) ``` - ## Save a `TimeArray` via `CSV.jl` ```julia @@ -52,10 +52,9 @@ using CSV CSV.write(filename, ta) ``` - ## Load a `TimeArray` from csv file via `CSV.jl` ```julia using CSV -TimeArray(CSV.File(filename), timestamp = :timestamp) +TimeArray(CSV.File(filename); timestamp=:timestamp) ``` diff --git a/docs/src/timearray.md b/docs/src/timearray.md index b2cd597a..964b3853 100644 --- a/docs/src/timearray.md +++ b/docs/src/timearray.md @@ -65,10 +65,10 @@ TimeArray There are four field getter functions exported. They are named as same as the field names. -- [`timestamp`](@ref) -- [`values`](@ref) -- [`colnames`](@ref) -- [`meta`](@ref) + - [`timestamp`](@ref) + - [`values`](@ref) + - [`colnames`](@ref) + - [`meta`](@ref) ```@docs timestamp diff --git a/src/.timeseriesrc.jl b/src/.timeseriesrc.jl index f3877a74..bc9d60d1 100644 --- a/src/.timeseriesrc.jl +++ b/src/.timeseriesrc.jl @@ -4,11 +4,11 @@ const DECIMALS = 4 ##### showing missing values ###### -const NAN = "NaN" -const NA = "NA" +const NAN = "NaN" +const NA = "NA" const BLACKHOLE = "\u2B24" const DOTCIRCLE = "\u25CC" -const QUESTION = "\u003F" +const QUESTION = "\u003F" # const UNICORN = "\u1F984" # doesn't render well const MISSING = NAN diff --git a/src/TimeSeries.jl b/src/TimeSeries.jl index 727e3be2..3d03dee2 100644 --- a/src/TimeSeries.jl +++ b/src/TimeSeries.jl @@ -11,13 +11,32 @@ using Reexport using Tables using PrettyTables: pretty_table -export TimeArray, AbstractTimeSeries, - when, from, to, findwhen, timestamp, values, colnames, meta, head, tail, - lag, lead, diff, percentchange, moving, upto, - uniformspaced, uniformspace, dropnan, - basecall, - merge, collapse, - readtimearray, writetimearray +export TimeArray, + AbstractTimeSeries, + when, + from, + to, + findwhen, + timestamp, + values, + colnames, + meta, + head, + tail, + lag, + lead, + diff, + percentchange, + moving, + upto, + uniformspaced, + uniformspace, + dropnan, + basecall, + merge, + collapse, + readtimearray, + writetimearray # modify.jl export rename, rename! diff --git a/src/apply.jl b/src/apply.jl index 072e4fa6..2acffa52 100644 --- a/src/apply.jl +++ b/src/apply.jl @@ -6,9 +6,7 @@ import Base.diff ###### lag, lead ################ -function lag(ta::TimeArray{T, N}, n::Int=1; - padding::Bool=false, period::Int=0) where {T, N} - +function lag(ta::TimeArray{T,N}, n::Int=1; padding::Bool=false, period::Int=0) where {T,N} if period != 0 @warn("the period kwarg is deprecated, use lag(ta::TimeArray, period::Int) instead") n = period @@ -16,35 +14,37 @@ function lag(ta::TimeArray{T, N}, n::Int=1; # TODO: apply `unchecked` if padding - paddedvals = [NaN * ones(n,size(ta, 2)); values(ta)[1:end-n, :]] + paddedvals = [NaN * ones(n, size(ta, 2)); values(ta)[1:(end - n), :]] ta = TimeArray(timestamp(ta), paddedvals, colnames(ta), meta(ta)) else - ta = TimeArray(timestamp(ta)[1+n:end], values(ta)[1:end-n, :], colnames(ta), meta(ta)) + ta = TimeArray( + timestamp(ta)[(1 + n):end], values(ta)[1:(end - n), :], colnames(ta), meta(ta) + ) end N == 1 && (ta = ta[colnames(ta)[1]]) return ta - end # lag -function lead(ta::TimeArray{T, N}, n::Int=1; - padding::Bool=false, period::Int=0) where {T, N} - +function lead(ta::TimeArray{T,N}, n::Int=1; padding::Bool=false, period::Int=0) where {T,N} if period != 0 - @warn("the period kwarg is deprecated, use lead(ta::TimeArray, period::Int) instead") - n = period + @warn( + "the period kwarg is deprecated, use lead(ta::TimeArray, period::Int) instead" + ) + n = period end if padding - paddedvals = [values(ta)[1+n:end, :]; NaN*ones(n, length(colnames(ta)))] + paddedvals = [values(ta)[(1 + n):end, :]; NaN * ones(n, length(colnames(ta)))] ta = TimeArray(timestamp(ta), paddedvals, colnames(ta), meta(ta)) else - ta = TimeArray(timestamp(ta)[1:end-n], values(ta)[1+n:end, :], colnames(ta), meta(ta)) + ta = TimeArray( + timestamp(ta)[1:(end - n)], values(ta)[(1 + n):end, :], colnames(ta), meta(ta) + ) end N == 1 && (ta = ta[colnames(ta)[1]]) return ta - end # lead ###### diff ##################### @@ -52,7 +52,7 @@ end # lead function diff(ta::TimeArray, n::Int=1; padding::Bool=false, differences::Int=1) cols = colnames(ta) for d in 1:differences - ta = ta .- lag(ta, n, padding=padding) + ta = ta .- lag(ta, n; padding=padding) end colnames(ta)[:] = cols return ta @@ -60,22 +60,25 @@ end # diff ###### percentchange ############ -function percentchange(ta::TimeArray, returns::Symbol=:simple; - padding::Bool=false, method::AbstractString="") - +function percentchange( + ta::TimeArray, returns::Symbol=:simple; padding::Bool=false, method::AbstractString="" +) if method != "" @warn("the method kwarg is deprecated, use percentchange(ta, :methodname) instead") returns = Symbol(method) end cols = colnames(ta) - ta = returns == :log ? diff(log.(ta), padding=padding) : - returns == :simple ? expm1.(percentchange(ta, :log, padding=padding)) : - throw(ArgumentError("returns must be either :simple or :log")) + ta = if returns == :log + diff(log.(ta); padding=padding) + elseif returns == :simple + expm1.(percentchange(ta, :log; padding=padding)) + else + throw(ArgumentError("returns must be either :simple or :log")) + end colnames(ta)[:] = cols - return ta - + return ta end # percentchange ###### moving ################### @@ -87,87 +90,95 @@ end # percentchange Apply user-defined function `f` to a 1D `TimeArray` with window size `w`. ## Example + To calculate the simple moving average of a time series: ```julia moving(mean, ta, 10) ``` """ -function moving(f, ta::TimeArray{T,1}, window::Integer; - padding::Bool = false) where {T} - ts = padding ? timestamp(ta) : @view(timestamp(ta)[window:end]) - A = values(ta) - vals = map(i -> f(@view A[i:i+(window-1)]), 1:length(ta)-window+1) +function moving(f, ta::TimeArray{T,1}, window::Integer; padding::Bool=false) where {T} + ts = padding ? timestamp(ta) : @view(timestamp(ta)[window:end]) + A = values(ta) + vals = map(i -> f(@view A[i:(i + (window - 1))]), 1:(length(ta) - window + 1)) padding && (vals = [fill(NaN, window - 1); vals]) - TimeArray(ta; timestamp = ts, values = vals) + return TimeArray(ta; timestamp=ts, values=vals) end - """ moving(f, ta::TimeArray{T,2}, w::Integer; padding = false, dims = 1, colnames = [...]) ## Example + In case of `dims = 2`, the user-defined function `f` will get a 2D `Array` as input. ```julia -moving(ohlc, 10, dims = 2, colnames = [:A, ...]) do +moving(ohlc, 10; dims=2, colnames=[:A, ...]) do # given that `ohlc` is a 500x4 `TimeArray`, # size(A) is (10, 4) ... end ``` """ -function moving(f, ta::TimeArray{T,2}, window::Integer; - padding::Bool = false, dims::Integer = 1, - colnames::AbstractVector{Symbol} = _colnames(ta)) where {T} +function moving( + f, + ta::TimeArray{T,2}, + window::Integer; + padding::Bool=false, + dims::Integer=1, + colnames::AbstractVector{Symbol}=_colnames(ta), +) where {T} if !(dims ∈ (1, 2)) throw(ArgumentError("invalid dims $dims")) end - ts = padding ? timestamp(ta) : @view(timestamp(ta)[window:end]) - A = values(ta) + ts = padding ? timestamp(ta) : @view(timestamp(ta)[window:end]) + A = values(ta) if dims == 1 vals = similar(@view(A[window:end, :])) - for i ∈ 1:size(vals, 1), j ∈ 1:size(vals, 2) - vals[i, j] = f(@view(A[i:i+(window-1), j])) + for i in 1:size(vals, 1), j in 1:size(vals, 2) + vals[i, j] = f(@view(A[i:(i + (window - 1)), j])) end else # case of dims = 2 - vals = mapreduce(i -> f(view(A, i-window+1:i, :)), vcat, window:size(A, 1)) + vals = mapreduce(i -> f(view(A, (i - window + 1):i, :)), vcat, window:size(A, 1)) if size(vals, 2) != length(colnames) - throw(DimensionMismatch( - "the output dims should match the lenght of columns, " * - "please set the keyword argument `colnames` properly." - )) + throw( + DimensionMismatch( + "the output dims should match the lenght of columns, " * + "please set the keyword argument `colnames` properly.", + ), + ) end end - padding && (vals = [fill(NaN, (window-1), size(vals, 2)); vals]) - TimeArray(ta; timestamp = ts, values = vals, colnames = colnames) + padding && (vals = [fill(NaN, (window - 1), size(vals, 2)); vals]) + return TimeArray(ta; timestamp=ts, values=vals, colnames=colnames) end ###### upto ##################### -function upto(f, ta::TimeArray{T, 1}) where {T} +function upto(f, ta::TimeArray{T,1}) where {T} vals = zero(values(ta)) - for i=1:length(vals) + for i in 1:length(vals) vals[i] = f(values(ta)[1:i]) end - TimeArray(timestamp(ta), vals, colnames(ta), meta(ta)) + return TimeArray(timestamp(ta), vals, colnames(ta), meta(ta)) end -function upto(f, ta::TimeArray{T, 2}) where {T} +function upto(f, ta::TimeArray{T,2}) where {T} vals = zero(values(ta)) - for i=1:size(vals, 1), j=1:size(vals, 2) + for i in 1:size(vals, 1), j in 1:size(vals, 2) vals[i, j] = f(values(ta)[1:i, j]) end - TimeArray(timestamp(ta), vals, colnames(ta), meta(ta)) + return TimeArray(timestamp(ta), vals, colnames(ta), meta(ta)) end ###### basecall ################# -basecall(ta::TimeArray, f::Function; cnames=colnames(ta)) = - TimeArray(timestamp(ta), f(values(ta)), cnames, meta(ta)) +function basecall(ta::TimeArray, f::Function; cnames=colnames(ta)) + return TimeArray(timestamp(ta), f(values(ta)), cnames, meta(ta)) +end ###### uniform observations ##### @@ -175,25 +186,31 @@ function uniformspaced(ta::TimeArray) gap1 = timestamp(ta)[2] - timestamp(ta)[1] i, n, is_uniform = 2, length(ta), true while is_uniform & (i < n) - is_uniform = gap1 == (timestamp(ta)[i+1] - timestamp(ta)[i]) + is_uniform = gap1 == (timestamp(ta)[i + 1] - timestamp(ta)[i]) i += 1 end return is_uniform end # uniformspaced -function uniformspace(ta::TimeArray{T, N}) where {T, N} - min_gap = minimum(timestamp(ta)[2:end] - timestamp(ta)[1:end-1]) +function uniformspace(ta::TimeArray{T,N}) where {T,N} + min_gap = minimum(timestamp(ta)[2:end] - timestamp(ta)[1:(end - 1)]) newtimestamp = timestamp(ta)[1]:min_gap:timestamp(ta)[end] - emptyta = TimeArray(collect(newtimestamp), zeros(length(newtimestamp), 0), - Symbol[], meta(ta)) - ta = merge(emptyta, ta, method = :left) + emptyta = TimeArray( + collect(newtimestamp), zeros(length(newtimestamp), 0), Symbol[], meta(ta) + ) + ta = merge(emptyta, ta; method=:left) N == 1 && (ta = ta[colnames(ta)[1]]) return ta end # uniformspace ###### dropnan #################### -dropnan(ta::TimeArray, method::Symbol = :all) = - method == :all ? ta[findall(reshape(values(any(.!isnan.(ta), dims = 2)), :))] : - method == :any ? ta[findall(reshape(values(all(.!isnan.(ta), dims = 2)), :))] : - throw(ArgumentError("dropnan method must be :all or :any")) +function dropnan(ta::TimeArray, method::Symbol=:all) + return if method == :all + ta[findall(reshape(values(any(.!isnan.(ta); dims=2)), :))] + elseif method == :any + ta[findall(reshape(values(all(.!isnan.(ta); dims=2)), :))] + else + throw(ArgumentError("dropnan method must be :all or :any")) + end +end diff --git a/src/basemisc.jl b/src/basemisc.jl index 84bc0e39..595199e9 100644 --- a/src/basemisc.jl +++ b/src/basemisc.jl @@ -3,14 +3,13 @@ import Base: cumsum, cumprod, sum, all, any, maximum, minimum import Statistics: mean, std, var -const _tsmap = Dict{Any,Dict{Number,Expr}}() # timestamp map +const _tsmap = Dict{Any,Dict{Number,Expr}}() # timestamp map const _colmap = Dict{Any,Dict{Number,Expr}}() # colanmes map """ To handle :where clause """ -_get_fname(sig::Expr) = - (sig.head == :call) ? sig.args[1] : _get_fname(sig.args[1]) +_get_fname(sig::Expr) = (sig.head == :call) ? sig.args[1] : _get_fname(sig.args[1]) macro _mapbase(sig::Expr, imp::Expr) fname = _get_fname(sig) @@ -38,33 +37,37 @@ macro _mapbase(sig::Expr, imp::Expr) doc = " $sig" fdef = Expr(:function, sig, fbody) - esc(quote - $doc - $fdef - end) + return esc( + quote + $doc + $fdef + end, + ) end # Cumulative functions _tsmap[:cumsum] = Dict(1 => :(timestamp(ta))) _colmap[:cumsum] = Dict(2 => :(colnames(ta))) -@_mapbase cumsum(ta::TimeArray; dims::Integer) cumsum(values(ta), dims = dims) -@_mapbase(cumsum(ta::TimeArray{T,1}, dims::Integer = 1) where{T}, - cumsum(values(ta), dims = dims)) +@_mapbase cumsum(ta::TimeArray; dims::Integer) cumsum(values(ta), dims=dims) +@_mapbase( + cumsum(ta::TimeArray{T,1}, dims::Integer=1) where {T}, cumsum(values(ta), dims=dims) +) _tsmap[:cumprod] = Dict(1 => :(timestamp(ta))) _colmap[:cumprod] = Dict(2 => :(colnames(ta))) -@_mapbase cumprod(ta::TimeArray; dims::Integer) cumprod(values(ta), dims = dims) -@_mapbase(cumprod(ta::TimeArray{T,1}; dims::Integer = 1) where {T}, - cumprod(values(ta), dims = dims)) +@_mapbase cumprod(ta::TimeArray; dims::Integer) cumprod(values(ta), dims=dims) +@_mapbase( + cumprod(ta::TimeArray{T,1}; dims::Integer=1) where {T}, cumprod(values(ta), dims=dims) +) # Reduction functions -@_mapbase sum(ta::TimeArray; dims = 1) sum(values(ta), dims = dims) -@_mapbase all(ta::TimeArray; dims = 1) all(values(ta), dims = dims) -@_mapbase any(ta::TimeArray; dims = 1) any(values(ta), dims = dims) +@_mapbase sum(ta::TimeArray; dims=1) sum(values(ta), dims=dims) +@_mapbase all(ta::TimeArray; dims=1) all(values(ta), dims=dims) +@_mapbase any(ta::TimeArray; dims=1) any(values(ta), dims=dims) -@_mapbase mean(ta::TimeArray; dims = 1) mean(values(ta), dims = dims) -@_mapbase std(ta::TimeArray; dims = 1, kw...) std(values(ta); dims = dims, kw...) -@_mapbase var(ta::TimeArray; dims = 1, kw...) var(values(ta); dims = dims, kw...) +@_mapbase mean(ta::TimeArray; dims=1) mean(values(ta), dims=dims) +@_mapbase std(ta::TimeArray; dims=1, kw...) std(values(ta); dims=dims, kw...) +@_mapbase var(ta::TimeArray; dims=1, kw...) var(values(ta); dims=dims, kw...) -@_mapbase maximum(ta::TimeArray; dims = 1) maximum(values(ta), dims = dims) -@_mapbase minimum(ta::TimeArray; dims = 1) minimum(values(ta), dims = dims) +@_mapbase maximum(ta::TimeArray; dims=1) maximum(values(ta), dims=dims) +@_mapbase minimum(ta::TimeArray; dims=1) minimum(values(ta), dims=dims) diff --git a/src/broadcast.jl b/src/broadcast.jl index 2d212dd6..0a7bd59e 100644 --- a/src/broadcast.jl +++ b/src/broadcast.jl @@ -3,7 +3,7 @@ using Base.Broadcast: Broadcasted, DefaultArrayStyle abstract type AbstractTimeSeriesStyle{N} <: Broadcast.AbstractArrayStyle{N} end struct TimeArrayStyle{N} <: AbstractTimeSeriesStyle{N} end -TimeArrayStyle(::Val{N}) where N = TimeArrayStyle{N}() +TimeArrayStyle(::Val{N}) where {N} = TimeArrayStyle{N}() TimeArrayStyle{M}(::Val{N}) where {N,M} = TimeArrayStyle{N}() # Determin the output type @@ -12,10 +12,9 @@ Base.BroadcastStyle(::Type{<:TimeArray{T,N}}) where {T,N} = TimeArrayStyle{N}() Base.broadcastable(x::AbstractTimeSeries) = x Base.Broadcast.instantiate(bc::Broadcasted{<:TimeArrayStyle}) = - # skip the default axes checking +# skip the default axes checking Broadcast.flatten(bc) - function Base.copy(bc′::Broadcasted{<:TimeArrayStyle}) tas = find_ta(bc′) @@ -31,7 +30,7 @@ function Base.copy(bc′::Broadcasted{<:TimeArrayStyle}) # replace TimeArray objects into Array in the Broadcasted arguments j = 0 args = Any[] - for (i, arg) ∈ enumerate(bc′.args) + for (i, arg) in enumerate(bc′.args) x = if arg isa TimeArray j += 1 if typeof(arg).parameters[2] == 1 # 1D array @@ -45,21 +44,22 @@ function Base.copy(bc′::Broadcasted{<:TimeArrayStyle}) push!(args, x) end - TimeArray(view(timestamp(tas[1]), tstamp_idx[1]), - broadcast(bc′.f, args...), - col′, - meta′) + return TimeArray( + view(timestamp(tas[1]), tstamp_idx[1]), broadcast(bc′.f, args...), col′, meta′ + ) end @inline function check_column_lens(tas::Tuple) - length(tas) <= 1 && return + length(tas) <= 1 && return nothing # if we have more than one TimeArray - lens = Set(i for i ∈ map(ta -> length(colnames(ta)), tas) if i ≠ 1) - length(lens) > 1 && throw( + lens = Set(i for i in map(ta -> length(colnames(ta)), tas) if i ≠ 1) + return length(lens) > 1 && throw( DimensionMismatch( "TimeArrays must have the same number of columns, " * - "or one must be a single column")) + "or one must be a single column", + ), + ) end """ @@ -67,18 +67,18 @@ find all TimeArray in the Broadcasted args and return a tuple of them. """ function find_ta(bc) ret = tuple() - for i ∈ bc.args + for i in bc.args if i isa TimeArray ret = tuple(ret..., i) end end - ret + return ret end -@generated function _new_cnames(args::Vararg{Symbol, N}) where N +@generated function _new_cnames(args::Vararg{Symbol,N}) where {N} expr = :(Symbol(args[1])) - for i ∈ 2:N + for i in 2:N push!(expr.args, "_", :(args[$i])) end - expr + return expr end diff --git a/src/combine.jl b/src/combine.jl index b4fabaf0..beec8692 100644 --- a/src/combine.jl +++ b/src/combine.jl @@ -2,12 +2,19 @@ import Base: merge, hcat, vcat, map ###### merge #################### -function _merge_outer(::Type{IndexType}, ta1::TimeArray{T,N,D}, ta2::TimeArray{T,M,D}, padvalue, meta) where {IndexType,T,N,M,D} - timestamps, new_idx1, new_idx2 = sorted_unique_merge(IndexType, timestamp(ta1), timestamp(ta2)) - vals = fill(convert(T, padvalue), (length(timestamps), length(colnames(ta1)) + length(colnames(ta2)))) +function _merge_outer( + ::Type{IndexType}, ta1::TimeArray{T,N,D}, ta2::TimeArray{T,M,D}, padvalue, meta +) where {IndexType,T,N,M,D} + timestamps, new_idx1, new_idx2 = sorted_unique_merge( + IndexType, timestamp(ta1), timestamp(ta2) + ) + vals = fill( + convert(T, padvalue), + (length(timestamps), length(colnames(ta1)) + length(colnames(ta2))), + ) insertbyidx!(vals, values(ta1), new_idx1) insertbyidx!(vals, values(ta2), new_idx2, size(values(ta1), 2)) - TimeArray(timestamps, vals, [colnames(ta1); colnames(ta2)], meta; unchecked = true) + return TimeArray(timestamps, vals, [colnames(ta1); colnames(ta2)], meta; unchecked=true) end """ @@ -18,48 +25,64 @@ Merge several `TimeArray`s along with the time index. ## Argument -- `method::Symbol`: `:inner`, `:outer`, `:left` or `:right`. + - `method::Symbol`: `:inner`, `:outer`, `:left` or `:right`. """ -function merge(ta1::TimeArray{T,N,D}, ta2::TimeArray{T,M,D}; - method::Symbol = :inner, colnames::Vector = Symbol[], meta = nothing, - padvalue=NaN) where {T,N,M,D} - +function merge( + ta1::TimeArray{T,N,D}, + ta2::TimeArray{T,M,D}; + method::Symbol=:inner, + colnames::Vector=Symbol[], + meta=nothing, + padvalue=NaN, +) where {T,N,M,D} if colnames isa Vector{<:AbstractString} @warn "`merge(...; colname::Vector{<:AbstractString})` is deprecated, " * - "use `merge(...; colnames=Symbol.(colnames))` instead." + "use `merge(...; colnames=Symbol.(colnames))` instead." colnames = Symbol.(colnames) end meta = if _meta(ta1) == _meta(ta2) && meta ≡ nothing _meta(ta1) - elseif typeof(_meta(ta1)) <: AbstractString && typeof(_meta(ta2)) <: AbstractString && meta ≡ nothing + elseif typeof(_meta(ta1)) <: AbstractString && + typeof(_meta(ta2)) <: AbstractString && + meta ≡ nothing string(_meta(ta1), "_", _meta(ta2)) else meta end if method == :inner - idx1, idx2 = overlap(timestamp(ta1), timestamp(ta2)) vals = [values(ta1[idx1]) values(ta2[idx2])] - ta = TimeArray(timestamp(ta1[idx1]), vals, [_colnames(ta1); _colnames(ta2)], meta; unchecked = true) + ta = TimeArray( + timestamp(ta1[idx1]), + vals, + [_colnames(ta1); _colnames(ta2)], + meta; + unchecked=true, + ) elseif method == :left - new_idx2, old_idx2 = overlap(timestamp(ta1), timestamp(ta2)) right_vals = fill(convert(T, padvalue), (length(ta1), length(_colnames(ta2)))) insertbyidx!(right_vals, values(ta2), new_idx2, old_idx2) - ta = TimeArray(timestamp(ta1), [values(ta1) right_vals], [_colnames(ta1); _colnames(ta2)], meta; unchecked = true) + ta = TimeArray( + timestamp(ta1), + [values(ta1) right_vals], + [_colnames(ta1); _colnames(ta2)], + meta; + unchecked=true, + ) elseif method == :right - - ta = merge(ta2, ta1, method = :left; padvalue = padvalue) + ta = merge(ta2, ta1; method=:left, padvalue=padvalue) ncol2 = length(_colnames(ta2)) - vals = [values(ta)[:, (ncol2+1):end] values(ta)[:, 1:ncol2]] - ta = TimeArray(timestamp(ta), vals, [_colnames(ta1); _colnames(ta2)], meta; unchecked = true) + vals = [values(ta)[:, (ncol2 + 1):end] values(ta)[:, 1:ncol2]] + ta = TimeArray( + timestamp(ta), vals, [_colnames(ta1); _colnames(ta2)], meta; unchecked=true + ) elseif method == :outer - ta = if (length(timestamp(ta1)) + length(timestamp(ta2))) > typemax(Int32) _merge_outer(Int64, ta1, ta2, padvalue, meta) else @@ -67,18 +90,22 @@ function merge(ta1::TimeArray{T,N,D}, ta2::TimeArray{T,M,D}; end else - throw(ArgumentError( - "merge method must be one of :inner, :left, :right, :outer")) + throw(ArgumentError("merge method must be one of :inner, :left, :right, :outer")) end return rename!(ta, colnames) - end -merge(x::TimeArray{T}, y::TimeArray{T}, z::TimeArray{T}, a::TimeArray{T}...; - colnames::Vector = Symbol[], kw...) where {T} = - rename!(merge(merge(x, y; kw...), z, a...; kw...), colnames) - +function merge( + x::TimeArray{T}, + y::TimeArray{T}, + z::TimeArray{T}, + a::TimeArray{T}...; + colnames::Vector=Symbol[], + kw..., +) where {T} + return rename!(merge(merge(x, y; kw...), z, a...; kw...), colnames) +end # hcat ########################## @@ -87,17 +114,15 @@ function hcat(x::TimeArray, y::TimeArray) tsy = timestamp(y) if length(tsx) != length(tsx) || tsx != tsy - throw(DimensionMismatch( - "timestamps not consistent, please checkout `merge`.")) + throw(DimensionMismatch("timestamps not consistent, please checkout `merge`.")) end m = ifelse(meta(x) == meta(y), meta(x), nothing) - TimeArray(tsx, [values(x) values(y)], [colnames(x); colnames(y)], m) + return TimeArray(tsx, [values(x) values(y)], [colnames(x); colnames(y)], m) end -hcat(x::TimeArray, y::TimeArray, zs::Vararg{TimeArray}) = - hcat(hcat(x, y), zs...) +hcat(x::TimeArray, y::TimeArray, zs::Vararg{TimeArray}) = hcat(hcat(x, y), zs...) # collapse ###################### @@ -105,16 +130,18 @@ hcat(x::TimeArray, y::TimeArray, zs::Vararg{TimeArray}) = # https://github.com/JuliaLang/julia/blob/b617e8d3a77e49cd5625ca663af88e40f7796f15/stdlib/Dates/src/accessors.jl#L50 # `year` doesn't need this wrapper -for (F, T, P) ∈ ((:quarter, :TimeType, :Quarter), - (:month, :TimeType, :Month), - (:week, :TimeType, :Week), - (:day, :TimeType, :Day), - (:hour, :(Union{DateTime,Time}), :Hour), - (:minute, :(Union{DateTime,Time}), :Minute), - (:second, :(Union{DateTime,Time}), :Second), - (:millisecond, :(Union{DateTime,Time}), :Millisecond), - (:microsecond, :Time, :Microsecond), - (:nanosecond, :Time, :Nanosecond)) +for (F, T, P) in ( + (:quarter, :TimeType, :Quarter), + (:month, :TimeType, :Month), + (:week, :TimeType, :Week), + (:day, :TimeType, :Day), + (:hour, :(Union{DateTime,Time}), :Hour), + (:minute, :(Union{DateTime,Time}), :Minute), + (:second, :(Union{DateTime,Time}), :Second), + (:millisecond, :(Union{DateTime,Time}), :Millisecond), + (:microsecond, :Time, :Microsecond), + (:nanosecond, :Time, :Nanosecond), +) if F === :quarter (VERSION < v"1.6") && continue F = :(Dates.quarter) @@ -122,26 +149,27 @@ for (F, T, P) ∈ ((:quarter, :TimeType, :Quarter), @eval let global collapse wrapper(x::$T) = floor(x, $P(1)) - collapse(ta::TimeArray, ::typeof($F), f::Function, g::Function = f; kw...) = - collapse(ta, wrapper, f, g; kw...) + function collapse(ta::TimeArray, ::typeof($F), f::Function, g::Function=f; kw...) + return collapse(ta, wrapper, f, g; kw...) + end end end -function collapse(ta::TimeArray, period::Function, timestamp::Function, - value::Function = timestamp) - +function collapse( + ta::TimeArray, period::Function, timestamp::Function, value::Function=timestamp +) isempty(ta) && return ta m, n = length(ta), length(colnames(ta)) - ts = _timestamp(ta) - val = _values(ta) - idx = UnitRange{Int}[] + ts = _timestamp(ta) + val = _values(ta) + idx = UnitRange{Int}[] sizehint!(idx, m) t₀ = period(ts[1]) - j = 1 - for i in 1:m-1 - t₁ = period(ts[i+1]) + j = 1 + for i in 1:(m - 1) + t₁ = period(ts[i + 1]) t₀ == t₁ && continue push!(idx, j:i) j = i + 1 @@ -149,14 +177,14 @@ function collapse(ta::TimeArray, period::Function, timestamp::Function, end push!(idx, j:m) - ts′ = [timestamp(@view(ts[i])) for i ∈ idx] + ts′ = [timestamp(@view(ts[i])) for i in idx] val′ = if n == 1 - [value(@view(val[i])) for i ∈ idx] + [value(@view(val[i])) for i in idx] else - [value(@view(val[i, k])) for i ∈ idx, k ∈ 1:n] + [value(@view(val[i, k])) for i in idx, k in 1:n] end - TimeArray(ts′, val′, colnames(ta), meta(ta)) + return TimeArray(ts′, val′, colnames(ta), meta(ta)) end """ @@ -178,8 +206,9 @@ collapse(ta, month, last) collapse(ta, month, last, mean) ``` """ -collapse(ta::TimeArray, period::Period, timestamp::Function, value::Function = timestamp; kw...) = - collapse(ta, x -> floor(x, period), timestamp, value; kw...) +collapse( + ta::TimeArray, period::Period, timestamp::Function, value::Function=timestamp; kw... +) = collapse(ta, x -> floor(x, period), timestamp, value; kw...) # vcat ###################### @@ -224,7 +253,7 @@ function vcat(tas::TimeArray...) end # Concatenate the contents. - ts = vcat([timestamp(ta) for ta in tas]...) + ts = vcat([timestamp(ta) for ta in tas]...) val = vcat([values(ta) for ta in tas]...) order = sortperm(ts) @@ -238,13 +267,13 @@ end # map ###################### @generated function map(f, ta::TimeArray{T,N}) where {T,N} - input_val = (N == 1) ? :(values(ta)[i]) : :(vec(values(ta)[i, :])) + input_val = (N == 1) ? :(values(ta)[i]) : :(vec(values(ta)[i, :])) output_val = (N == 1) ? :(vals[i]) : :(vals[i, :]) output_vals = (N == 1) ? :(vals[order]) : :(vals[order, :]) quote - ts = similar(timestamp(ta)) + ts = similar(timestamp(ta)) vals = similar(values(ta)) for i in eachindex(ta) diff --git a/src/deprecated.jl b/src/deprecated.jl index 5b816232..bb680615 100644 --- a/src/deprecated.jl +++ b/src/deprecated.jl @@ -3,28 +3,29 @@ ############################################################################### @deprecate( - TimeArray(d::AbstractVector{D}, v::AbstractArray{T,N}, - c::Vector{S}, m::Any = nothing; - args...) where {T,N,D<:TimeType,S<:AbstractString}, + TimeArray( + d::AbstractVector{D}, v::AbstractArray{T,N}, c::Vector{S}, m::Any=nothing; args... + ) where {T,N,D<:TimeType,S<:AbstractString}, TimeArray(d, v, Symbol.(c), m; args...) ) @deprecate( - TimeArray(d::D, v::AbstractArray{T,N}, - c::Vector{S}, m::Any = nothing; - args...) where {T,N,D<:TimeType,S<:AbstractString}, + TimeArray( + d::D, v::AbstractArray{T,N}, c::Vector{S}, m::Any=nothing; args... + ) where {T,N,D<:TimeType,S<:AbstractString}, TimeArray(d, v, Symbol.(c), m; args...) ) -@deprecate getindex(ta::TimeArray, s::AbstractString) getindex(ta, Symbol(s)) +@deprecate getindex(ta::TimeArray, s::AbstractString) getindex(ta, Symbol(s)) @deprecate getindex(ta::TimeArray, ss::AbstractString...) getindex(ta, Symbol.(ss)...) -@deprecate rename(ta::TimeArray, col::String) rename(ta::TimeArray, Symbol(col)) +@deprecate rename(ta::TimeArray, col::String) rename(ta::TimeArray, Symbol(col)) @deprecate rename(ta::TimeArray, col::Vector{String}) rename(ta, Symbol.(col)) @deprecate( merge(ta1::TimeArray{T}, ta2::TimeArray, method::Symbol; kw...) where {T}, - merge(ta1, ta2; method = method, kw...)) + merge(ta1, ta2; method=method, kw...) +) ############################################################################### # 0.20.0 @@ -34,7 +35,9 @@ export update @deprecate( update(ta::TimeArray{T,N,D}, tstamp::D, val::AbstractArray{T,N}) where {T,N,D}, - vcat(ta, TimeArray(tstamp, val))) + vcat(ta, TimeArray(tstamp, val)) +) @deprecate( update(ta::TimeArray{T,N,D}, tstamp::D, val::T) where {T,N,D}, - vcat(ta, TimeArray([tstamp], [val]))) + vcat(ta, TimeArray([tstamp], [val])) +) diff --git a/src/modify.jl b/src/modify.jl index 1a6bf56a..6088adcb 100644 --- a/src/modify.jl +++ b/src/modify.jl @@ -11,12 +11,13 @@ Rename the columns of a `TimeArray`. See also [`rename!`](@ref). # Arguments -- `colnametyp` is the input type for the function `f`. The valid value is - `Symbol` or `String`. + + - `colnametyp` is the input type for the function `f`. The valid value is + `Symbol` or `String`. """ function rename(ta::TimeArray, colnames::Vector{Symbol}) length(colnames) == size(ta, 2) || throw(ArgumentError("Colnames length mismatch")) - TimeArray(ta, colnames = colnames, unchecked = true) + return TimeArray(ta; colnames=colnames, unchecked=true) end """ @@ -30,41 +31,44 @@ In-place rename the columns of a `TimeArray`. See also [`rename`](@ref). # Arguments -- `colnametyp` is the input type for the function `f`. The valid value is - `Symbol` or `String`. + + - `colnametyp` is the input type for the function `f`. The valid value is + `Symbol` or `String`. """ function rename!(ta::TimeArray, colnames::Vector{Symbol}) - length(colnames) == size(ta, 2) ? (_colnames(ta)[:] = colnames) : - length(colnames) > 0 && throw(ArgumentError("Colnames length mismatch")) - ta + if length(colnames) == size(ta, 2) + (_colnames(ta)[:] = colnames) + else + length(colnames) > 0 && throw(ArgumentError("Colnames length mismatch")) + end + return ta end # shared interfaces -for f ∈ [:rename, :rename!] +for f in [:rename, :rename!] @eval begin $f(ta::TimeArray, colnames::Symbol) = $f(ta, [colnames]) $f(ta::TimeArray) = throw(MethodError($f, (ta,))) $f(ta::TimeArray, pairs::Pair{Symbol,Symbol}...) = $f(ta, _mapcol(colnames(ta), pairs)) - $f(f::Base.Callable, ta::TimeArray, colnametyp::Type{Symbol} = Symbol) = + $f(f::Base.Callable, ta::TimeArray, colnametyp::Type{Symbol}=Symbol) = $f(ta, map(f, colnames(ta))) $f(f::Base.Callable, ta::TimeArray, colnametyp::Type{String}) = $f(Symbol ∘ f ∘ string, ta) end end - """ utility function for `rename` and `rename!` """ function _mapcol(col, pairs) col′ = copy(col) - for (key, val) ∈ pairs + for (key, val) in pairs i = findfirst(isequal(key), col) if i ≡ nothing # FIXME: `isnothing(i)` in Julia 1.1 throw(ArgumentError("Unknown column `$key`")) end col′[i] = val end - col′ + return col′ end diff --git a/src/plotrecipes.jl b/src/plotrecipes.jl index 219581ff..b2a0f0eb 100644 --- a/src/plotrecipes.jl +++ b/src/plotrecipes.jl @@ -18,8 +18,8 @@ end # Argument -- There are four required columns from `ta::TimeArray`: `open`, `high`, `low` and - `close`. The column names is case-insensitive. + - There are four required columns from `ta::TimeArray`: `open`, `high`, `low` and + `close`. The column names is case-insensitive. # Examples @@ -27,9 +27,10 @@ end julia> using MarketData julia> TimeSeries.Candlestick(ohlcv) + ``` """ -mutable struct Candlestick{D <: TimeType} +mutable struct Candlestick{D<:TimeType} time::Vector{D} open::AbstractVector high::AbstractVector @@ -40,15 +41,15 @@ end Candlestick(ta::TimeArray) = Candlestick(extract_ohlc(ta)...) function extract_ohlc(ta::TimeArray) - C = ["open", "high", "low", "close"] - cols = colnames(ta) + C = ["open", "high", "low", "close"] + cols = colnames(ta) cols′ = lowercase.(string.(colnames(ta))) - V = map(C) do x + V = map(C) do x i = findfirst(isequal(x), cols′) i ≡ nothing && throw(ArgumentError("the TimeArray did not have column `$x`")) values(ta[cols[i]]) end - (timestamp(ta), V...) + return (timestamp(ta), V...) end function HeikinAshi!(cs::Candlestick) # some values here are made too high! @@ -59,20 +60,19 @@ function HeikinAshi!(cs::Candlestick) # some values here are made too high! for i in 2:length(cs.open) cs.close[i] = (cs.open[i] + cs.low[i] + cs.close[i] + cs.high[i]) / 4 - cs.open[i] = (cs.open[i-1] + cs.close[i-1]) / 2 + cs.open[i] = (cs.open[i - 1] + cs.close[i - 1]) / 2 cs.high[i] = maximum([cs.high[i], cs.open[i], cs.close[i]]) cs.low[i] = minimum([cs.low[i], cs.open[i], cs.close[i]]) end end - @recipe function f(cs::Candlestick) st = get(plotattributes, :seriestype, :candlestick) st == :heikinashi && HeikinAshi!(cs) - legend --> false + legend --> false linewidth --> 0.5 - grid --> true + grid --> true xrotation --> 90 bw = get(plotattributes, :bar_width, 0.9) @@ -86,41 +86,19 @@ end xseg, yseg = Vector{Float64}[], Vector{Float64}[] xcenter, ycenter = Float64[], Float64[] - idx = 1:length(cs.time) + idx = 1:length(cs.time) tick = 1 colors = Symbol[] hovers = String[] margin = (1 - bw) / 2 * tick - for (i, o, h, l, c) ∈ zip(idx, cs.open, cs.high, cs.low, cs.close) + for (i, o, h, l, c) in zip(idx, cs.open, cs.high, cs.low, cs.close) x₁, x₂ = i - tick + margin, i - margin x̄ = i - tick / 2 - push!(xseg, [ - x̄, - x̄, - x₁, - x₁, - x̄, - x̄, - x̄, - x₂, - x₂, - x̄, - ]) + push!(xseg, [x̄, x̄, x₁, x₁, x̄, x̄, x̄, x₂, x₂, x̄]) y₁, y₂ = min(o, c), max(o, c) - push!(yseg, [ - l, - y₁, - y₁, - y₂, - y₂, - h, - y₂, - y₂, - y₁, - y₁, - ]) + push!(yseg, [l, y₁, y₁, y₂, y₂, h, y₂, y₂, y₁, y₁]) push!(colors, ifelse(o ≤ c, cols[1], cols[2])) @@ -132,7 +110,9 @@ end xticks = get(plotattributes, :xticks, 1) if xticks isa Integer if xticks != 1 - xticks := (idx .- 0.5, map(x -> (x % xticks) != 1 ? "" : string(cs.time[x]) ,idx)) + xticks := ( + idx .- 0.5, map(x -> (x % xticks) != 1 ? "" : string(cs.time[x]), idx) + ) else xticks := (idx .- 0.5, string.(cs.time)) end @@ -146,20 +126,20 @@ end colors′ = reshape(colors, 1, :) @series begin - seriestype := :shape + seriestype := :shape seriescolor := colors′ - linecolor --> colors′ + linecolor --> colors′ label --> "" xseg, yseg end @series begin - seriestype := :scatter - seriescolor := colors - markersize := 1 + seriestype := :scatter + seriescolor := colors + markersize := 1 markerstrokewidth := 0 label --> "" - hover --> hovers + hover --> hovers xcenter, ycenter end end diff --git a/src/readwrite.jl b/src/readwrite.jl index f225bee8..4e1a7a94 100644 --- a/src/readwrite.jl +++ b/src/readwrite.jl @@ -1,8 +1,9 @@ ###### readtimearray ############ -function readtimearray(source; delim::Char = ',', meta = nothing, - format::AbstractString = "", header::Bool = true) - cfile = readdlm(source, delim, header = header) +function readtimearray( + source; delim::Char=',', meta=nothing, format::AbstractString="", header::Bool=true +) + cfile = readdlm(source, delim; header=header) if header cfile, hd = cfile end @@ -17,21 +18,22 @@ function readtimearray(source; delim::Char = ',', meta = nothing, time = cfile[1:end, 1] if length(time[1]) < 11 # assuming Date not DateTime - tstamps = df === nothing ? - Date[Date(t) for t in time] : - Date[Date(t, df) for t in time] + tstamps = + df === nothing ? Date[Date(t) for t in time] : Date[Date(t, df) for t in time] else - tstamps = df === nothing ? - DateTime[DateTime(t) for t in time] : + tstamps = if df === nothing + DateTime[DateTime(t) for t in time] + else DateTime[DateTime(t, df) for t in time] + end end - vals = insertNaN(cfile[1:end, 2:end]) + vals = insertNaN(cfile[1:end, 2:end]) cnames = header ? Symbol.(hd[2:end]) : gen_colnames(size(cfile, 2) - 1) - TimeArray(tstamps, vals, cnames, meta) + return TimeArray(tstamps, vals, cnames, meta) end # readtimearray -function insertNaN(aa::Array{Any, N}) where {N} +function insertNaN(aa::Array{Any,N}) where {N} for i in 1:size(aa, 1) for j in 1:size(aa, 2) if !isa(aa[i, j], Real) @@ -39,12 +41,16 @@ function insertNaN(aa::Array{Any, N}) where {N} end end end - convert(Array{Float64, N}, aa) + return convert(Array{Float64,N}, aa) end -function writetimearray(ta::TimeArray, fname::AbstractString; - delim::Char = ',', format::AbstractString = "", - header::Bool = true) +function writetimearray( + ta::TimeArray, + fname::AbstractString; + delim::Char=',', + format::AbstractString="", + header::Bool=true, +) open(fname, "w") do io if header strvals = join(colnames(ta), delim) @@ -53,9 +59,11 @@ function writetimearray(ta::TimeArray, fname::AbstractString; for i in eachindex(timestamp(ta)) strvals = replace(join(values(ta)[i, :], delim), "NaN" => "") - strtstamp = isempty(format) ? - string(timestamp(ta)[i]) : + strtstamp = if isempty(format) + string(timestamp(ta)[i]) + else Dates.format(timestamp(ta)[i], format) + end write(io, string(strtstamp, delim, strvals, "\n")) end end diff --git a/src/split.jl b/src/split.jl index 13688d1e..55054127 100644 --- a/src/split.jl +++ b/src/split.jl @@ -1,23 +1,35 @@ # when ############################ -when(ta::TimeArray, period::Function, t::Integer) = - ta[findall(period.(timestamp(ta)) .== t)] -when(ta::TimeArray, period::Function, t::String) = - ta[findall(period.(timestamp(ta)) .== t)] +function when(ta::TimeArray, period::Function, t::Integer) + return ta[findall(period.(timestamp(ta)) .== t)] +end +when(ta::TimeArray, period::Function, t::String) = ta[findall(period.(timestamp(ta)) .== t)] # from, to ###################### -from(ta::TimeArray{T, N, D}, d::D) where {T, N, D} = - length(ta) == 0 ? ta : - d < timestamp(ta)[1] ? ta : - d > timestamp(ta)[end] ? ta[1:0] : +function from(ta::TimeArray{T,N,D}, d::D) where {T,N,D} + return if length(ta) == 0 + ta + elseif d < timestamp(ta)[1] + ta + elseif d > timestamp(ta)[end] + ta[1:0] + else ta[searchsortedfirst(timestamp(ta), d):end] + end +end -to(ta::TimeArray{T, N, D}, d::D) where {T, N, D} = - length(ta) == 0 ? ta : - d < timestamp(ta)[1] ? ta[1:0] : - d > timestamp(ta)[end] ? ta : +function to(ta::TimeArray{T,N,D}, d::D) where {T,N,D} + return if length(ta) == 0 + ta + elseif d < timestamp(ta)[1] + ta[1:0] + elseif d > timestamp(ta)[end] + ta + else ta[1:searchsortedlast(timestamp(ta), d)] + end +end ###### findall ################## @@ -25,7 +37,7 @@ Base.findall(ta::TimeArray{Bool,1}) = findall(values(ta)) Base.findall(f::Function, ta::TimeArray{T,1}) where {T} = findall(f, values(ta)) function Base.findall(f::Function, ta::TimeArray{T,2}) where {T} A = values(ta) - collect(i for i in axes(A, 1) if f(view(A, i, :))) + return collect(i for i in axes(A, 1) if f(view(A, i, :))) end ###### findwhen ################# @@ -43,7 +55,7 @@ findwhen(ta::TimeArray{Bool,1}) = timestamp(ta)[findall(values(ta))] end end - @generated function tail(ta::TimeArray{T,N}, n::Int=6) where {T,N} +@generated function tail(ta::TimeArray{T,N}, n::Int=6) where {T,N} new_values = (N == 1) ? :(values(ta)[start:end]) : :(values(ta)[start:end, :]) quote diff --git a/src/tables.jl b/src/tables.jl index d4a97864..c63835be 100644 --- a/src/tables.jl +++ b/src/tables.jl @@ -8,7 +8,6 @@ using Tables using TableTraits using IteratorInterfaceExtensions - # S: the column names, including the extra column name for time index # N: number of rows # M: number of columns @@ -21,32 +20,36 @@ function TableIter(ta::T) where {T<:TimeArray} col′ = TimeSeries.replace_dupes!([:timestamp; colnames(ta)]) S = Tuple(col′) # TODO: `colnames = @view(col′[2:end])` doesn't work at this moment - TableIter{T,S,N,M}(TimeArray(ta, colnames = col′[2:end], unchecked = true)) + return TableIter{T,S,N,M}(TimeArray(ta; colnames=col′[2:end], unchecked=true)) end data(i::TableIter) = getfield(i, :x) TimeSeries.timestamp(i::TableIter) = timestamp(data(i)) -TimeSeries.colnames(i::TableIter) = colnames(data(i)) +TimeSeries.colnames(i::TableIter) = colnames(data(i)) -Base.getindex(x::TableIter{<:TimeArray}, i::Integer) = - i == 1 ? timestamp(x) : values(data(x)[colnames(x)[i - 1]]) -Base.getindex(x::TableIter{<:TimeArray}, j::Integer, i::Integer) = - i == 1 ? timestamp(x)[j] : values(data(x)[colnames(x)[i - 1]])[j] +function Base.getindex(x::TableIter{<:TimeArray}, i::Integer) + return i == 1 ? timestamp(x) : values(data(x)[colnames(x)[i - 1]]) +end +function Base.getindex(x::TableIter{<:TimeArray}, j::Integer, i::Integer) + return i == 1 ? timestamp(x)[j] : values(data(x)[colnames(x)[i - 1]])[j] +end -Base.length(::TableIter{<:TimeArray,S,N,M}) where {S,N,M} = M +Base.length(::TableIter{<:TimeArray,S,N,M}) where {S,N,M} = M Base.lastindex(::TableIter{<:TimeArray,S,N,M}) where {S,N,M} = M -Base.lastindex(::TableIter{<:TimeArray,S,N,M}, d::Integer) where {S,N,M} = - ifelse(d == 1, N, ifelse(d == 2, M, 1)) +function Base.lastindex(::TableIter{<:TimeArray,S,N,M}, d::Integer) where {S,N,M} + return ifelse(d == 1, N, ifelse(d == 2, M, 1)) +end Base.size(::TableIter{<:TimeArray,S,N,M}) where {S,N,M} = (N, M) Base.propertynames(x::TableIter{<:TimeArray,S}) where {S} = S -Base.getproperty(x::TableIter{<:TimeArray,S}, c::Symbol) where {S} = - c == S[1] ? timestamp(x) : values(getproperty(data(x), c)) +function Base.getproperty(x::TableIter{<:TimeArray,S}, c::Symbol) where {S} + return c == S[1] ? timestamp(x) : values(getproperty(data(x), c)) +end -function Base.iterate(x::TableIter, i::Integer = 1) +function Base.iterate(x::TableIter, i::Integer=1) i > length(x) && return nothing - x[i], i + 1 + return x[i], i + 1 end Tables.istable(::Type{<:AbstractTimeSeries}) = true @@ -55,7 +58,7 @@ Tables.rows(ta::TimeArray) = Tables.rows(Tables.columntable(ta)) Tables.columnaccess(::Type{<:TimeArray}) = true Tables.columns(ta::TimeArray) = TableIter(ta) Tables.columnnames(ta::TimeArray) = Tables.columnnames(TableIter(ta)) -Tables.columnnames(i::TableIter{T, S}) where {T,S} = collect(Symbol, S) +Tables.columnnames(i::TableIter{T,S}) where {T,S} = collect(Symbol, S) Tables.getcolumn(ta::TimeArray, i::Int) = Tables.getcolumn(TableIter(ta), i) Tables.getcolumn(ta::TimeArray, nm::Symbol) = Tables.getcolumn(TableIter(ta), nm) Tables.getcolumn(i::TableIter, n::Int) = i[n] @@ -63,19 +66,21 @@ Tables.getcolumn(i::TableIter, nm::Symbol) = getproperty(i, nm) Tables.schema(ta::AbstractTimeSeries{T,N,D}) where {T,N,D} = Tables.schema(TableIter(ta)) Tables.schema(i::TableIter{T,S}) where {T,S} = Tables.Schema(S, coltypes(data(i))) -coltypes(x::AbstractTimeSeries{T,N,D}) where {T,N,D} = (D, (T for _ ∈ 1:size(x, 2))...) +coltypes(x::AbstractTimeSeries{T,N,D}) where {T,N,D} = (D, (T for _ in 1:size(x, 2))...) TableTraits.isiterabletable(x::TimeArray) = true -IteratorInterfaceExtensions.getiterator(ta::TimeArray) = - Tables.datavaluerows(Tables.columntable(ta)) +function IteratorInterfaceExtensions.getiterator(ta::TimeArray) + return Tables.datavaluerows(Tables.columntable(ta)) +end IteratorInterfaceExtensions.isiterable(ta::TimeArray) = true ############################################################################### # Constructors ############################################################################### -function TimeSeries.TimeArray(x; timestamp::Symbol, timeparser::Base.Callable = identity, - unchecked = false) +function TimeSeries.TimeArray( + x; timestamp::Symbol, timeparser::Base.Callable=identity, unchecked=false +) Tables.istable(x) || throw(ArgumentError("TimeArray requires a table as input")) sch = Tables.schema(x) @@ -85,8 +90,13 @@ function TimeSeries.TimeArray(x; timestamp::Symbol, timeparser::Base.Callable = cols = Tables.columns(x) val = mapreduce(n -> collect(Tables.getcolumn(cols, n)), hcat, names′) - TimeArray(map(timeparser, Tables.getcolumn(cols, timestamp)), val, names′, x; - unchecked = unchecked) + return TimeArray( + map(timeparser, Tables.getcolumn(cols, timestamp)), + val, + names′, + x; + unchecked=unchecked, + ) end ############################################################################### @@ -94,32 +104,28 @@ end ############################################################################### @static if VERSION ≥ v"1.1" - - -@doc """ - eachrow(x::TimeArray) - -Return a row iterator baked by `Tables.rows`. - -# Examples - -```julia -for row ∈ eachrow(ohlc) - time = row.timestamp - price = row.Close -end -``` -""" -Base.eachrow(x::TimeArray) = Tables.rows(x) - -@doc """ - eachcol(x::TimeArray) - -Return a column iterator baked by `Tables.columns`. -""" -Base.eachcol(x::TimeArray) = Tables.columns(x) - - + @doc """ + eachrow(x::TimeArray) + + Return a row iterator baked by `Tables.rows`. + + # Examples + + ```julia + for row ∈ eachrow(ohlc) + time = row.timestamp + price = row.Close + end + ``` + """ + Base.eachrow(x::TimeArray) = Tables.rows(x) + + @doc """ + eachcol(x::TimeArray) + + Return a column iterator baked by `Tables.columns`. + """ + Base.eachcol(x::TimeArray) = Tables.columns(x) end # @static if VERSION ≥ v"1.1" end # TablesIntegration diff --git a/src/timearray.jl b/src/timearray.jl index add52a0a..d0d3032d 100644 --- a/src/timearray.jl +++ b/src/timearray.jl @@ -2,9 +2,23 @@ using Base: @propagate_inbounds -import Base: convert, copy, length, show, getindex, iterate, - lastindex, size, eachindex, ==, isequal, hash, ndims, - getproperty, propertynames, values +import Base: + convert, + copy, + length, + show, + getindex, + iterate, + lastindex, + size, + eachindex, + ==, + isequal, + hash, + ndims, + getproperty, + propertynames, + values abstract type AbstractTimeSeries{T,N,D} end @@ -26,21 +40,17 @@ The third constructor builds a `TimeArray` from a `NamedTuple`. # Arguments -- `timestamp::AbstractVector{<:TimeType}`: a vector of sorted timestamps, + - `timestamp::AbstractVector{<:TimeType}`: a vector of sorted timestamps, -- `timestamp::Symbol`: the column name of the time index from the source table. - The constructor is used for the Tables.jl package integration. - -- `values::AbstractArray`: a data vector or matrix. Its number of rows - should match the length of `timestamp`. - -- `colnames::Vector{Symbol}`: the column names. Its length should match - the column of `values`. - -- `meta::Any`: a user-defined metadata. - -- `timeparser::Callable`: a mapping function for converting the source time index. - For instance, `Dates.unix2datetime` is a common case. + - `timestamp::Symbol`: the column name of the time index from the source table. + The constructor is used for the Tables.jl package integration. + - `values::AbstractArray`: a data vector or matrix. Its number of rows + should match the length of `timestamp`. + - `colnames::Vector{Symbol}`: the column names. Its length should match + the column of `values`. + - `meta::Any`: a user-defined metadata. + - `timeparser::Callable`: a mapping function for converting the source time index. + For instance, `Dates.unix2datetime` is a common case. # Examples @@ -49,36 +59,36 @@ The third constructor builds a `TimeArray` from a `NamedTuple`. col2 = [20.2, 21.2], col3 = [30.2, 31.2]) ta = TimeArray(data; timestamp = :datetime, meta = "Example") - """ struct TimeArray{T,N,D<:TimeType,A<:AbstractArray{T,N}} <: AbstractTimeSeries{T,N,D} - timestamp::Vector{D} values::A colnames::Vector{Symbol} meta::Any function TimeArray{T,N,D,A}( - timestamp::AbstractVector{D}, - values::A, - colnames::Vector{Symbol}, - meta::Any; - unchecked = false) where {T,N,D<:TimeType,A<:AbstractArray{T,N}} + timestamp::AbstractVector{D}, + values::A, + colnames::Vector{Symbol}, + meta::Any; + unchecked=false, + ) where {T,N,D<:TimeType,A<:AbstractArray{T,N}} nrow = size(values, 1) ncol = size(values, 2) colnames = copy(colnames) unchecked && return new(timestamp, values, replace_dupes!(colnames), meta) - nrow != length(timestamp) && throw(DimensionMismatch("values must match length of timestamp")) - ncol != length(colnames) && throw(DimensionMismatch("column names must match width of array")) + nrow != length(timestamp) && + throw(DimensionMismatch("values must match length of timestamp")) + ncol != length(colnames) && + throw(DimensionMismatch("column names must match width of array")) - issorted(timestamp) && return new( - timestamp, values, replace_dupes!(colnames), meta) + issorted(timestamp) && return new(timestamp, values, replace_dupes!(colnames), meta) timestamp_r = reverse(timestamp) - issorted(timestamp_r) && return new( - timestamp_r, reverse(values, dims = 1), replace_dupes!(colnames), meta) + issorted(timestamp_r) && + return new(timestamp_r, reverse(values; dims=1), replace_dupes!(colnames), meta) throw(ArgumentError("timestamps must be monotonic")) end @@ -86,39 +96,60 @@ end ###### outer constructor ######## -TimeArray(d::AbstractVector{D}, v::AbstractArray{T,N}, - c::Vector{Symbol} = gen_colnames(size(v, 2)), - m::Any = nothing; args...) where {T,N,D<:TimeType} = - TimeArray{T,N,D,typeof(v)}(d, v, c, m; args...) +function TimeArray( + d::AbstractVector{D}, + v::AbstractArray{T,N}, + c::Vector{Symbol}=gen_colnames(size(v, 2)), + m::Any=nothing; + args..., +) where {T,N,D<:TimeType} + return TimeArray{T,N,D,typeof(v)}(d, v, c, m; args...) +end -TimeArray(d::D, v::AbstractArray{T,N}, - c::Vector{Symbol} = gen_colnames(size(v, 2)), - m::Any = nothing; args...) where {T,N,D<:TimeType} = - TimeArray{T,N,D,typeof(v)}([d], v, c, m; args...) +function TimeArray( + d::D, + v::AbstractArray{T,N}, + c::Vector{Symbol}=gen_colnames(size(v, 2)), + m::Any=nothing; + args..., +) where {T,N,D<:TimeType} + return TimeArray{T,N,D,typeof(v)}([d], v, c, m; args...) +end -TimeArray(ta::TimeArray; - timestamp = _timestamp(ta), values = _values(ta), - colnames = _colnames(ta), meta = _meta(ta), args...) = - TimeArray(timestamp, values, colnames, meta; args...) +function TimeArray( + ta::TimeArray; + timestamp=_timestamp(ta), + values=_values(ta), + colnames=_colnames(ta), + meta=_meta(ta), + args..., +) + return TimeArray(timestamp, values, colnames, meta; args...) +end -function TimeArray(data::NamedTuple; timestamp::Symbol, meta = nothing, args...) +function TimeArray(data::NamedTuple; timestamp::Symbol, meta=nothing, args...) columns = (key for key in keys(data) if key != timestamp) dat = hcat((data[key] for key in columns)...) - TimeArray(data[timestamp], dat, collect(columns), meta; args...) + return TimeArray(data[timestamp], dat, collect(columns), meta; args...) end ###### conversion ############### -convert(::Type{TimeArray{Float64,N}}, x::TimeArray{Bool,N}) where N = - TimeArray(timestamp(x), Float64.(values(x)), colnames(x), meta(x); unchecked = true) +function convert(::Type{TimeArray{Float64,N}}, x::TimeArray{Bool,N}) where {N} + return TimeArray( + timestamp(x), Float64.(values(x)), colnames(x), meta(x); unchecked=true + ) +end -convert(x::TimeArray{Bool,N}) where N = - convert(TimeArray{Float64,N}, x::TimeArray{Bool,N}) +function convert(x::TimeArray{Bool,N}) where {N} + return convert(TimeArray{Float64,N}, x::TimeArray{Bool,N}) +end ###### copy ############### -copy(ta::TimeArray) = - TimeArray(timestamp(ta), values(ta), colnames(ta), meta(ta); unchecked = true) +function copy(ta::TimeArray) + return TimeArray(timestamp(ta), values(ta), colnames(ta), meta(ta); unchecked=true) +end ###### length ################### @@ -135,7 +166,7 @@ ndims(ta::AbstractTimeSeries{T,N}) where {T,N} = N ###### iteration protocol ######## -@generated function iterate(ta::AbstractTimeSeries{T,N}, i = 1) where {T,N} +@generated function iterate(ta::AbstractTimeSeries{T,N}, i=1) where {T,N} val = (N == 1) ? :(values(ta)[i]) : :(values(ta)[i, :]) quote @@ -157,9 +188,9 @@ Implies ```julia x.timestamp == y.timestamp && -x.values == y.values && -x.colnames == y.colnames && -x.meta == y.meta + x.values == y.values && + x.colnames == y.colnames && + x.meta == y.meta ``` """ == @@ -169,16 +200,17 @@ x.meta == y.meta # 1.0 == 1 # Date(2111, 1, 1) == DateTime(2111, 1, 1) ==(x::TimeArray{T,N}, y::TimeArray{S,M}) where {T,S,N,M} = false -==(x::TimeArray{T,N}, y::TimeArray{S,N}) where {T,S,N} = - all(f -> getfield(x, f) == getfield(y, f), fieldnames(TimeArray)) +function ==(x::TimeArray{T,N}, y::TimeArray{S,N}) where {T,S,N} + return all(f -> getfield(x, f) == getfield(y, f), fieldnames(TimeArray)) +end isequal(x::TimeArray{T,N}, y::TimeArray{S,M}) where {T,S,N,M} = false -isequal(x::TimeArray{T,N}, y::TimeArray{S,N}) where {T,S,N} = - all(f -> isequal(getfield(x, f), getfield(y, f)), fieldnames(TimeArray)) +function isequal(x::TimeArray{T,N}, y::TimeArray{S,N}) where {T,S,N} + return all(f -> isequal(getfield(x, f), getfield(y, f)), fieldnames(TimeArray)) +end # support for Dict -hash(x::TimeArray, h::UInt) = - sum(f -> hash(getfield(x, f), h), fieldnames(TimeArray)) +hash(x::TimeArray, h::UInt) = sum(f -> hash(getfield(x, f), h), fieldnames(TimeArray)) ###### eltype ##################### @@ -188,23 +220,29 @@ Base.eltype(::AbstractTimeSeries{T,2,D}) where {T,D} = Tuple{D,Vector{T}} ###### show ##################### Base.summary(io::IO, ta::TimeArray) = show(io, ta) -function Base.show(io::IO, ta::TimeArray) +function Base.show(io::IO, ta::TimeArray) nrow = size(values(ta), 1) ncol = size(values(ta), 2) print(io, "$(nrow)×$(ncol) $(typeof(ta))") if nrow != 0 print(io, " $(timestamp(ta)[1]) to $(timestamp(ta)[end])") else # e.g. TimeArray(Date[], []) - return + return nothing end end -function Base.show(io::IO, ::MIME"text/plain", ta::TimeArray; allrows = !get(io, :limit, false), allcols = !get(io, :limit, false)) +function Base.show( + io::IO, + ::MIME"text/plain", + ta::TimeArray; + allrows=!get(io, :limit, false), + allcols=!get(io, :limit, false), +) nrow = size(values(ta), 1) ncol = size(values(ta), 2) show(io, ta) # summary line - nrow == 0 && return + nrow == 0 && return nothing println(io) @@ -220,14 +258,16 @@ function Base.show(io::IO, ::MIME"text/plain", ta::TimeArray; allrows = !get(io data = hcat(timestamp(ta), values(ta)) header = vcat("", string.(colnames(ta))) - pretty_table(io, data; - header=header, - newline_at_end = false, - reserved_display_lines = 2, - row_label_alignment = :r, - header_alignment = :l, + return pretty_table( + io, + data; + header=header, + newline_at_end=false, + reserved_display_lines=2, + row_label_alignment=:r, + header_alignment=:l, crop=crop, - vcrop_mode = :middle, + vcrop_mode=:middle, ) end @@ -240,7 +280,7 @@ getindex(ta::TimeArray) = throw(BoundsError(typeof(ta), [])) # single row @propagate_inbounds getindex(ta::TimeArray, n::Integer) = - # avoid conversion to column vector +# avoid conversion to column vector TimeArray(timestamp(ta)[n], values(ta)[n:n, :], colnames(ta), meta(ta)) # single row 1d @@ -252,7 +292,7 @@ getindex(ta::TimeArray) = throw(BoundsError(typeof(ta), [])) TimeArray(timestamp(ta)[r], values(ta)[r, :], colnames(ta), meta(ta)) # range of 1d rows -@propagate_inbounds getindex(ta::TimeArray{T,1}, r::UnitRange{<:Integer}) where T = +@propagate_inbounds getindex(ta::TimeArray{T,1}, r::UnitRange{<:Integer}) where {T} = TimeArray(timestamp(ta)[r], values(ta)[r], colnames(ta), meta(ta)) # array of rows @@ -260,38 +300,38 @@ getindex(ta::TimeArray) = throw(BoundsError(typeof(ta), [])) TimeArray(timestamp(ta)[a], values(ta)[a, :], colnames(ta), meta(ta)) # array of 1d rows -@propagate_inbounds getindex(ta::TimeArray{T,1}, a::AbstractVector{<:Integer}) where T = +@propagate_inbounds getindex(ta::TimeArray{T,1}, a::AbstractVector{<:Integer}) where {T} = TimeArray(timestamp(ta)[a], values(ta)[a], colnames(ta), meta(ta)) # single column by name @propagate_inbounds function getindex(ta::TimeArray, s::Symbol) n = findcol(ta, s) - TimeArray(timestamp(ta), values(ta)[:, n], Symbol[s], meta(ta), unchecked = true) + return TimeArray(timestamp(ta), values(ta)[:, n], Symbol[s], meta(ta); unchecked=true) end # array of columns by name @propagate_inbounds getindex(ta::TimeArray, ss::Symbol...) = getindex(ta, collect(ss)) @propagate_inbounds getindex(ta::TimeArray, ss::Vector{Symbol}) = - TimeArray(ta; values = values(ta)[:, map(s -> findcol(ta, s), ss)], colnames = ss) + TimeArray(ta; values=values(ta)[:, map(s -> findcol(ta, s), ss)], colnames=ss) # ta[rows, cols] -@propagate_inbounds getindex(ta::TimeArray, - rows::Union{AbstractVector{<:Integer},Colon}, - cols::AbstractVector{Symbol}) = - TimeArray( - ta; - timestamp = timestamp(ta)[rows], - values = values(ta)[rows, map(s -> findcol(ta, s), cols)], - colnames = cols, - unchecked = true) +@propagate_inbounds getindex( + ta::TimeArray, + rows::Union{AbstractVector{<:Integer},Colon}, + cols::AbstractVector{Symbol}, +) = TimeArray( + ta; + timestamp=timestamp(ta)[rows], + values=values(ta)[rows, map(s -> findcol(ta, s), cols)], + colnames=cols, + unchecked=true, +) # ta[n, cols] -@propagate_inbounds getindex(ta::TimeArray, n::Integer, cols) = - getindex(ta, [n], cols) +@propagate_inbounds getindex(ta::TimeArray, n::Integer, cols) = getindex(ta, [n], cols) # ta[rows, col] -@propagate_inbounds getindex(ta::TimeArray, rows, col::Symbol) = - getindex(ta, rows, [col]) +@propagate_inbounds getindex(ta::TimeArray, rows, col::Symbol) = getindex(ta, rows, [col]) # ta[n, col] @propagate_inbounds getindex(ta::TimeArray, n::Integer, col::Symbol) = @@ -300,18 +340,19 @@ end # single date @propagate_inbounds function getindex(ta::TimeArray{T,N,D}, d::D) where {T,N,D} idxs = searchsorted(timestamp(ta), d) - length(idxs) == 1 ? ta[idxs[1]] : nothing + return length(idxs) == 1 ? ta[idxs[1]] : nothing end # multiple dates @propagate_inbounds function getindex(ta::TimeArray{T,N,D}, dates::Vector{D}) where {T,N,D} dates = sort(dates) idxs, _ = overlap(timestamp(ta), dates) - ta[idxs] + return ta[idxs] end # StepRange{Date,...} -@propagate_inbounds getindex(ta::TimeArray{T,N,D}, r::StepRange{D}) where {T,N,D} = ta[collect(r)] +@propagate_inbounds getindex(ta::TimeArray{T,N,D}, r::StepRange{D}) where {T,N,D} = + ta[collect(r)] @propagate_inbounds getindex(ta::TimeArray, k::TimeArray{Bool,1}) = ta[findwhen(k)] @@ -319,10 +360,15 @@ end # getindex{T,N}(ta::TimeArray{T,N}, d::DAYOFWEEK) = ta[dayofweek(timestamp(ta)) .== d] # Define end keyword -lastindex(ta::TimeArray, d::Integer = 1) = - (d == 1) ? length(timestamp(ta)) : - (d == 2) ? length(colnames(ta)) : - 1 +function lastindex(ta::TimeArray, d::Integer=1) + return if (d == 1) + length(timestamp(ta)) + elseif (d == 2) + length(colnames(ta)) + else + 1 + end +end eachindex(ta::TimeArray) = Base.OneTo(length(timestamp(ta))) @@ -375,6 +421,6 @@ meta(ta::TimeArray) = getfield(ta, :meta) # internal use, to avoid name collision _timestamp(ta::TimeArray) = getfield(ta, :timestamp) -_values(ta::TimeArray) = getfield(ta, :values) -_colnames(ta::TimeArray) = getfield(ta, :colnames) -_meta(ta::TimeArray) = getfield(ta, :meta) +_values(ta::TimeArray) = getfield(ta, :values) +_colnames(ta::TimeArray) = getfield(ta, :colnames) +_meta(ta::TimeArray) = getfield(ta, :meta) diff --git a/src/utilities.jl b/src/utilities.jl index e81e1c6a..94a60aa2 100644 --- a/src/utilities.jl +++ b/src/utilities.jl @@ -1,6 +1,6 @@ -overlap(ts::Vararg{Vector, 1}) = (Base.OneTo(length(ts[1])),) +overlap(ts::Vararg{Vector,1}) = (Base.OneTo(length(ts[1])),) -function overlap(ts::Vararg{Vector, N}) where {N} +function overlap(ts::Vararg{Vector,N}) where {N} ret = ntuple(_ -> Int[], N) t1 = ts[1] @@ -21,7 +21,7 @@ function overlap(ts::Vararg{Vector, N}) where {N} end end end - ret + return ret end """ @@ -75,14 +75,16 @@ end For each column in src, insert elements from src[i, column] to dst[dstidx[i], column + coloffset]. """ -function insertbyidx!(dst::AbstractArray, src::AbstractArray, dstidx::Vector, coloffset::Int = 0) +function insertbyidx!( + dst::AbstractArray, src::AbstractArray, dstidx::Vector, coloffset::Int=0 +) for c in 1:size(src, 2) cc = c + coloffset for i in 1:length(dstidx) @inbounds dst[dstidx[i], cc] = src[i, c] end end - nothing + return nothing end """ @@ -90,24 +92,26 @@ end For each column in src, insert elements from src[srcidx[i], column] to dst[dstidx[i], column]. """ -function insertbyidx!(dst::AbstractArray, src::AbstractArray, dstidx::Vector, srcidx::Vector) +function insertbyidx!( + dst::AbstractArray, src::AbstractArray, dstidx::Vector, srcidx::Vector +) for c in 1:size(src, 2) for i in 1:length(srcidx) @inbounds dst[dstidx[i], c] = src[srcidx[i], c] end end - nothing + return nothing end @inline function allequal(x) length(x) < 2 && return true e1 = x[1] - @inbounds for i ∈ 2:length(x) + @inbounds for i in 2:length(x) x[i] == e1 || return false end - true + return true end # helper method for inner constructor @@ -120,17 +124,18 @@ function replace_dupes!(cnames::Vector{Symbol}) cnames[d] = Symbol(cnames[d], "_$n") else s = string(cnames[d]) - cnames[d] = Symbol(s[1:length(s)-length(string(n))-1], "_$n") + cnames[d] = Symbol(s[1:(length(s) - length(string(n)) - 1)], "_$n") end end n += 1 end - cnames + return cnames end # helper method for inner constructor -find_dupes_index(A::Vector{Symbol}) = - @inbounds [i for i in eachindex(A) if A[i] ∈ A[1:i-1]] +function find_dupes_index(A::Vector{Symbol}) + @inbounds [i for i in eachindex(A) if A[i] ∈ A[1:(i - 1)]] +end gen_colnames(n::Integer) = gen_colnames(Val{n}()) @@ -138,17 +143,17 @@ gen_colnames(n::Integer) = gen_colnames(Val{n}()) ret = Vector{Symbol}(undef, N) s = "" - for i ∈ 1:N + for i in 1:N s = carry_char(s) ret[i] = Symbol(s) end - ret + return ret end const carry_char_cache = Dict{String,String}("" => "A") -function carry_char(s::String = "") +function carry_char(s::String="") ret = get(carry_char_cache, s, "") (ret != "") && return ret @@ -158,12 +163,12 @@ function carry_char(s::String = "") ret = if c > 'Z' c = 'A' - carry_char(s[1:n-1]) * c + carry_char(s[1:(n - 1)]) * c else - s[1:n-1] * c + s[1:(n - 1)] * c end - carry_char_cache[s] = ret + return carry_char_cache[s] = ret end # helper method for `getindex` @@ -176,16 +181,16 @@ findcol(ta::AbstractTimeSeries, s::Symbol) = findcol(colnames(ta), s) @inline function findcol(cols::Vector{Symbol}, s::Symbol) i = findfirst(isequal(s), cols) (i === nothing) && throw(KeyError(s)) - i + return i end """ Unicode friendly rpad """ function _rpad(s::AbstractString, n::Integer) - x = n - textwidth(s) - (x ≤ 0) && return s - s * (" "^x) + x = n - textwidth(s) + (x ≤ 0) && return s + return s * (" "^x) end _rpad(s::Symbol, n::Integer) = _rpad(string(s), n::Integer) diff --git a/test/apply.jl b/test/apply.jl index ec59a9b6..009975f2 100644 --- a/test/apply.jl +++ b/test/apply.jl @@ -6,279 +6,302 @@ using MarketData using TimeSeries - @testset "apply" begin + @testset "time series methods" begin + @testset "lag takes previous day and timestamps it to next day" begin + ta = lag(cl) + @test values(ta)[1] ≈ 111.94 atol = 0.01 + @test timestamp(ta)[1] == Date(2000, 1, 4) + end + @testset "lag accepts other offset values" begin + ta = lag(cl, 9) + @test timestamp(ta)[1] == Date(2000, 1, 14) + end -@testset "time series methods" begin - @testset "lag takes previous day and timestamps it to next day" begin - ta = lag(cl) - @test values(ta)[1] ≈ 111.94 atol=.01 - @test timestamp(ta)[1] == Date(2000, 1, 4) - end - - @testset "lag accepts other offset values" begin - ta = lag(cl, 9) - @test timestamp(ta)[1] == Date(2000, 1, 14) - end - - @testset "lag operates on 2d arrays" begin - ta = lag(ohlc, 9) - @test timestamp(ta)[1] == Date(2000, 1, 14) - end - - @testset "lag returns 1d from 1d time arrays" begin - @test ndims(values(lag(cl))) == 1 - end - - @testset "lag returns 2d from 2d time arrays" begin - @test ndims(values(lag(ohlc))) == 2 - end - - @testset "lead takes next day and timestamps it to current day" begin - ta = lead(cl) - @test values(ta)[1] ≈ 102.5 atol=.1 - @test timestamp(ta)[1] == Date(2000,1,3) - end + @testset "lag operates on 2d arrays" begin + ta = lag(ohlc, 9) + @test timestamp(ta)[1] == Date(2000, 1, 14) + end - @testset "lead accepts other offset values" begin - ta = lead(cl, 9) - @test values(ta)[1] ≈ 100.44 atol=.1 - @test timestamp(ta)[1] == Date(2000,1,3) - end + @testset "lag returns 1d from 1d time arrays" begin + @test ndims(values(lag(cl))) == 1 + end - @testset "lead operates on 2d arrays" begin - @test timestamp(lead(ohlc, 9))[1] == Date(2000,1,3) - end + @testset "lag returns 2d from 2d time arrays" begin + @test ndims(values(lag(ohlc))) == 2 + end - @testset "lead returns 1d from 1d time arrays" begin - @test ndims(values(lead(cl))) == 1 - end + @testset "lead takes next day and timestamps it to current day" begin + ta = lead(cl) + @test values(ta)[1] ≈ 102.5 atol = 0.1 + @test timestamp(ta)[1] == Date(2000, 1, 3) + end - @testset "lead returns 2d from 2d time arrays" begin - @test ndims(values(lead(ohlc))) == 2 - end + @testset "lead accepts other offset values" begin + ta = lead(cl, 9) + @test values(ta)[1] ≈ 100.44 atol = 0.1 + @test timestamp(ta)[1] == Date(2000, 1, 3) + end - @testset "diff calculates 1st-order differences" begin - @test timestamp(diff(op)) == timestamp(diff(op, padding=false)) - @test values(diff(op)) == values(diff(op, padding=false)) - @test values(diff(op, padding=false))[1] == values(op[2])[1] .- values(op[1])[1] - @test values(diff(op, padding=true))[1] ≡ NaN - @test values(diff(op, padding=true))[2] == values(diff(op))[1] - @test values(diff(op, padding=true))[2] == values(op[2])[1] .- values(op[1])[1] - end + @testset "lead operates on 2d arrays" begin + @test timestamp(lead(ohlc, 9))[1] == Date(2000, 1, 3) + end - @testset "diff calculates 1st-order differences for multi-column ts" begin - @test timestamp(diff(ohlc)) == timestamp(diff(ohlc, padding=false)) - @test values(diff(ohlc)) == values(diff(ohlc, padding=false)) - @test values(diff(ohlc, padding=false))[1,:] == values(ohlc)[2, :] .- values(ohlc)[1, :] - @test values(diff(ohlc, padding=true))[2,:] == values(diff(ohlc))[1, :] - @test values(diff(ohlc, padding=true))[2,:] == values(ohlc)[2, :] .- values(ohlc)[1, :] - @test all(x -> isnan(x), values(diff(ohlc, padding=true))[1, :]) - end + @testset "lead returns 1d from 1d time arrays" begin + @test ndims(values(lead(cl))) == 1 + end - @testset "diff calculates 2nd-order differences" begin - @test timestamp(diff(op, differences=2)) == timestamp(diff(op, padding=false, differences=2)) - @test timestamp(diff(diff(op))) == timestamp(diff(op, padding=false, differences=2)) + @testset "lead returns 2d from 2d time arrays" begin + @test ndims(values(lead(ohlc))) == 2 + end - @test values(diff(op, differences=2)) == values(diff(op, padding=false, differences=2)) - @test values(diff(diff(op))) == values(diff(op, padding=false, differences=2)) + @testset "diff calculates 1st-order differences" begin + @test timestamp(diff(op)) == timestamp(diff(op; padding=false)) + @test values(diff(op)) == values(diff(op; padding=false)) + @test values(diff(op; padding=false))[1] == values(op[2])[1] .- values(op[1])[1] + @test values(diff(op; padding=true))[1] ≡ NaN + @test values(diff(op; padding=true))[2] == values(diff(op))[1] + @test values(diff(op; padding=true))[2] == values(op[2])[1] .- values(op[1])[1] + end - @test values(diff(op, padding=true, differences=2))[3] == values(diff(op, differences=2))[1] - @test values(diff(op, padding=true, differences=2)[2])[1] ≡ NaN - @test values(diff(op, padding=true, differences=2)[1])[1] ≡ NaN - end + @testset "diff calculates 1st-order differences for multi-column ts" begin + @test timestamp(diff(ohlc)) == timestamp(diff(ohlc; padding=false)) + @test values(diff(ohlc)) == values(diff(ohlc; padding=false)) + @test values(diff(ohlc; padding=false))[1, :] == + values(ohlc)[2, :] .- values(ohlc)[1, :] + @test values(diff(ohlc; padding=true))[2, :] == values(diff(ohlc))[1, :] + @test values(diff(ohlc; padding=true))[2, :] == + values(ohlc)[2, :] .- values(ohlc)[1, :] + @test all(x -> isnan(x), values(diff(ohlc; padding=true))[1, :]) + end - @testset "diff calculates 2nd-order differences for multi-column ts" begin - @test timestamp(diff(ohlc, differences=2)) == timestamp(diff(ohlc, padding=false, differences=2)) - @test timestamp(diff(diff(ohlc))) == timestamp(diff(ohlc, padding=false, differences=2)) + @testset "diff calculates 2nd-order differences" begin + @test timestamp(diff(op; differences=2)) == + timestamp(diff(op; padding=false, differences=2)) + @test timestamp(diff(diff(op))) == + timestamp(diff(op; padding=false, differences=2)) - @test values(diff(ohlc, differences=2)) == values(diff(ohlc, padding=false, differences=2)) - @test values(diff(diff(ohlc))) == values(diff(ohlc, padding=false, differences=2)) - @test values(diff(ohlc, padding=true, differences=2))[3, :] == values(diff(ohlc, differences=2))[1, :] + @test values(diff(op; differences=2)) == + values(diff(op; padding=false, differences=2)) + @test values(diff(diff(op))) == values(diff(op; padding=false, differences=2)) - @test all(x -> isnan(x), values(diff(ohlc, padding=true, differences=2))[2,:]) - @test all(x -> isnan(x), values(diff(ohlc, padding=true, differences=2))[1,:]) - end - - @testset "diff calculates 3rd-order differences" begin - @test diff(op, differences=3) |> timestamp == diff(op, padding=false, differences=3) |> timestamp - @test diff(op, differences=3) |> values == diff(op, padding=false, differences=3) |> values - @test diff(diff(diff(op))) |> timestamp == diff(op, padding=false, differences=3) |> timestamp - @test diff(diff(diff(op))) |> values == diff(op, padding=false, differences=3) |> values - @test values(diff(op, padding=true, differences=3))[4] == values(diff(op, differences=3))[1] - @test values(diff(op, padding=true, differences=3))[3] ≡ NaN - @test values(diff(op, padding=true, differences=3))[2] ≡ NaN - @test values(diff(op, padding=true, differences=3))[1] ≡ NaN - end + @test values(diff(op; padding=true, differences=2))[3] == + values(diff(op; differences=2))[1] + @test values(diff(op; padding=true, differences=2)[2])[1] ≡ NaN + @test values(diff(op; padding=true, differences=2)[1])[1] ≡ NaN + end - @testset "diff calculates 3rd-order differences for multi-column ts" begin - @test diff(ohlc, differences=3) |> timestamp == diff(ohlc, padding=false, differences=3) |> timestamp - @test diff(ohlc, differences=3) |> values == diff(ohlc, padding=false, differences=3) |> values - @test diff(diff(diff(ohlc))) |> timestamp == diff(ohlc, padding=false, differences=3) |> timestamp - @test diff(diff(diff(ohlc))) |> values == diff(ohlc, padding=false, differences=3) |> values - @test values(diff(ohlc, padding=true, differences=3))[4, :] == values(diff(ohlc, differences=3))[1, :] - @test all(x -> isnan(x), values(diff(ohlc, padding=true, differences=3))[3,:]) - @test all(x -> isnan(x), values(diff(ohlc, padding=true, differences=3))[2,:]) - @test all(x -> isnan(x), values(diff(ohlc, padding=true, differences=3))[1,:]) - end + @testset "diff calculates 2nd-order differences for multi-column ts" begin + @test timestamp(diff(ohlc; differences=2)) == + timestamp(diff(ohlc; padding=false, differences=2)) + @test timestamp(diff(diff(ohlc))) == + timestamp(diff(ohlc; padding=false, differences=2)) + + @test values(diff(ohlc; differences=2)) == + values(diff(ohlc; padding=false, differences=2)) + @test values(diff(diff(ohlc))) == + values(diff(ohlc; padding=false, differences=2)) + @test values(diff(ohlc; padding=true, differences=2))[3, :] == + values(diff(ohlc; differences=2))[1, :] + + @test all(x -> isnan(x), values(diff(ohlc; padding=true, differences=2))[2, :]) + @test all(x -> isnan(x), values(diff(ohlc; padding=true, differences=2))[1, :]) + end - @testset "diff n lag" begin - let ta = diff(cl, 5) - ans = cl .- lag(cl, 5) + @testset "diff calculates 3rd-order differences" begin + @test timestamp(diff(op; differences=3)) == + timestamp(diff(op; padding=false, differences=3)) + @test values(diff(op; differences=3)) == + values(diff(op; padding=false, differences=3)) + @test timestamp(diff(diff(diff(op)))) == + timestamp(diff(op; padding=false, differences=3)) + @test values(diff(diff(diff(op)))) == + values(diff(op; padding=false, differences=3)) + @test values(diff(op; padding=true, differences=3))[4] == + values(diff(op; differences=3))[1] + @test values(diff(op; padding=true, differences=3))[3] ≡ NaN + @test values(diff(op; padding=true, differences=3))[2] ≡ NaN + @test values(diff(op; padding=true, differences=3))[1] ≡ NaN + end - @test values(ta) == values(ans) - @test timestamp(ta) == timestamp(ans) + @testset "diff calculates 3rd-order differences for multi-column ts" begin + @test timestamp(diff(ohlc; differences=3)) == + timestamp(diff(ohlc; padding=false, differences=3)) + @test values(diff(ohlc; differences=3)) == + values(diff(ohlc; padding=false, differences=3)) + @test timestamp(diff(diff(diff(ohlc)))) == + timestamp(diff(ohlc; padding=false, differences=3)) + @test values(diff(diff(diff(ohlc)))) == + values(diff(ohlc; padding=false, differences=3)) + @test values(diff(ohlc; padding=true, differences=3))[4, :] == + values(diff(ohlc; differences=3))[1, :] + @test all(x -> isnan(x), values(diff(ohlc; padding=true, differences=3))[3, :]) + @test all(x -> isnan(x), values(diff(ohlc; padding=true, differences=3))[2, :]) + @test all(x -> isnan(x), values(diff(ohlc; padding=true, differences=3))[1, :]) end - let ta = diff(ohlc, 5) - ans = ohlc .- lag(ohlc, 5) + @testset "diff n lag" begin + let ta = diff(cl, 5) + ans = cl .- lag(cl, 5) - @test values(ta) == values(ans) - @test timestamp(ta) == timestamp(ans) - end - end # @testset "diff n lag" - - @testset "simple return value" begin - @test values(percentchange(cl, :simple)) == values(percentchange(cl)) - @test values(percentchange(cl)) == values(percentchange(cl, padding=false)) - @test values(percentchange(cl))[1] ≈ (102.5 - 111.94) / 111.94 atol=.01 - @test values(percentchange(ohlc))[1, :] ≈ (values(ohlc)[2, :] - values(ohlc)[1, :]) ./ values(ohlc)[1, :] - @test isnan.(values(percentchange(cl, padding=true))[1]) - @test values(percentchange(cl, padding=true))[2] ≈ (102.5 - 111.94) / 111.94 atol=.01 - @test values(percentchange(ohlc, padding=true))[2, :] ≈ (values(ohlc)[2,:] - values(ohlc)[1,:]) ./ values(ohlc)[1,:] - end + @test values(ta) == values(ans) + @test timestamp(ta) == timestamp(ans) + end - @testset "log return value" begin - @test values(percentchange(cl, :log)) == values(percentchange(cl, :log, padding=false)) - @test values(percentchange(cl, :log))[1] ≈ log(102.5) - log(111.94) atol=.01 - @test values(percentchange(ohlc, :log))[1, :] ≈ log.(values(ohlc)[2,:]) .- log.(values(ohlc)[1,:]) atol=.01 - @test isnan.(values(percentchange(cl, :log, padding=true))[1]) - @test values(percentchange(cl, :log, padding=true))[2] ≈ log(102.5) - log(111.94) - @test values(percentchange(ohlc, :log, padding=true))[2, :] ≈ log.(values(ohlc)[2,:]) .- log.(values(ohlc)[1,:]) - end + let ta = diff(ohlc, 5) + ans = ohlc .- lag(ohlc, 5) - @testset "moving supplies correct window length" begin - let - ta = moving(mean, cl, 10) - @test values(ta) == values(moving(mean, cl, 10, padding=false)) - @test timestamp(ta)[1] == Date(2000, 1, 14) - @test values(ta)[1] ≈ mean(values(cl)[1:10]) - end - let - ta = moving(mean, cl, 10, padding = true) - @test timestamp(ta) == timestamp(cl) - @test values(ta)[1] ≡ NaN - @test values(ta)[10] == values(moving(mean, cl, 10))[1] + @test values(ta) == values(ans) + @test timestamp(ta) == timestamp(ans) + end + end # @testset "diff n lag" + + @testset "simple return value" begin + @test values(percentchange(cl, :simple)) == values(percentchange(cl)) + @test values(percentchange(cl)) == values(percentchange(cl; padding=false)) + @test values(percentchange(cl))[1] ≈ (102.5 - 111.94) / 111.94 atol = 0.01 + @test values(percentchange(ohlc))[1, :] ≈ + (values(ohlc)[2, :] - values(ohlc)[1, :]) ./ values(ohlc)[1, :] + @test isnan.(values(percentchange(cl; padding=true))[1]) + @test values(percentchange(cl; padding=true))[2] ≈ (102.5 - 111.94) / 111.94 atol = + 0.01 + @test values(percentchange(ohlc; padding=true))[2, :] ≈ + (values(ohlc)[2, :] - values(ohlc)[1, :]) ./ values(ohlc)[1, :] end - let - ta = moving(mean, ohlc, 10) - @test values(ta) == values(moving(mean, ohlc, 10, padding=false)) - @test values(ta)[1, :]' ≈ mean(values(ohlc)[1:10, :], dims = 1) - end - let - ta = moving(mean, ohlc, 10, padding = true) - @test all(isnan, values(ta)[1, :]) - @test values(ta)[10, :] == values(moving(mean, ohlc, 10))[1, :] + @testset "log return value" begin + @test values(percentchange(cl, :log)) == + values(percentchange(cl, :log; padding=false)) + @test values(percentchange(cl, :log))[1] ≈ log(102.5) - log(111.94) atol = 0.01 + @test values(percentchange(ohlc, :log))[1, :] ≈ + log.(values(ohlc)[2, :]) .- log.(values(ohlc)[1, :]) atol = 0.01 + @test isnan.(values(percentchange(cl, :log; padding=true))[1]) + @test values(percentchange(cl, :log; padding=true))[2] ≈ + log(102.5) - log(111.94) + @test values(percentchange(ohlc, :log; padding=true))[2, :] ≈ + log.(values(ohlc)[2, :]) .- log.(values(ohlc)[1, :]) end - @testset "moving with do syntax" begin - moving(cl, 10) do x - @test isa(x, AbstractArray{Float64,1}) - x[1] + @testset "moving supplies correct window length" begin + let + ta = moving(mean, cl, 10) + @test values(ta) == values(moving(mean, cl, 10; padding=false)) + @test timestamp(ta)[1] == Date(2000, 1, 14) + @test values(ta)[1] ≈ mean(values(cl)[1:10]) + end + let + ta = moving(mean, cl, 10; padding=true) + @test timestamp(ta) == timestamp(cl) + @test values(ta)[1] ≡ NaN + @test values(ta)[10] == values(moving(mean, cl, 10))[1] end - moving(ohlc, 10) do x - @test isa(x, AbstractArray{Float64,1}) - x[1] + let + ta = moving(mean, ohlc, 10) + @test values(ta) == values(moving(mean, ohlc, 10; padding=false)) + @test values(ta)[1, :]' ≈ mean(values(ohlc)[1:10, :]; dims=1) + end + let + ta = moving(mean, ohlc, 10; padding=true) + @test all(isnan, values(ta)[1, :]) + @test values(ta)[10, :] == values(moving(mean, ohlc, 10))[1, :] end - end - @testset "moving with multi-column" begin - ta = moving(mean, ohlc, 1, dims = 2, colnames = [:mean]) - ans = mean(ohlc, dims = 2) - @test all(values(ta) .== values(ans)) - @test timestamp(ta) == timestamp(ta) + @testset "moving with do syntax" begin + moving(cl, 10) do x + @test isa(x, AbstractArray{Float64,1}) + x[1] + end - ta = moving(ohlc, 10, dims = 2) do A - mean(A, dims = 1) + moving(ohlc, 10) do x + @test isa(x, AbstractArray{Float64,1}) + x[1] + end end - ans = moving(mean, ohlc, 10) - @test values(ta) ≈ values(ans) - @test timestamp(ta) == timestamp(ta) - # with padding - ta = moving(ohlc, 10, dims = 2, padding = true) do A - mean(A, dims = 1) + @testset "moving with multi-column" begin + ta = moving(mean, ohlc, 1; dims=2, colnames=[:mean]) + ans = mean(ohlc; dims=2) + @test all(values(ta) .== values(ans)) + @test timestamp(ta) == timestamp(ta) + + ta = moving(ohlc, 10; dims=2) do A + mean(A; dims=1) + end + ans = moving(mean, ohlc, 10) + @test values(ta) ≈ values(ans) + @test timestamp(ta) == timestamp(ta) + + # with padding + ta = moving(ohlc, 10; dims=2, padding=true) do A + mean(A; dims=1) + end + @test length(ta) == length(ohlc) + @test all(isnan.(values(ta[1:9]))) + + # exceptions + @test_throws ArgumentError moving(mean, ohlc, 24, dims=42) + @test_throws DimensionMismatch moving(mean, ohlc, 24, dims=2) end - @test length(ta) == length(ohlc) - @test all(isnan.(values(ta[1:9]))) - - # exceptions - @test_throws ArgumentError moving(mean, ohlc, 24, dims = 42) - @test_throws DimensionMismatch moving(mean, ohlc, 24, dims = 2) end - end - - @testset "upto method accumulates" begin - @test values(upto(sum, cl))[10] ≈ sum(values(cl)[1:10]) - @test values(upto(mean, cl))[10] ≈ mean(values(cl)[1:10]) - - @test timestamp(upto(sum, cl))[10] == Date(2000, 1, 14) - @test timestamp(upto(mean, cl))[10] == Date(2000, 1, 14) - - # transpose the upto value output from column to row vector but values are identical - @test values(upto(sum, ohlc))[10, :]' ≈ sum(values(ohlc)[1:10, :], dims = 1) - @test values(upto(mean, ohlc))[10, :]' ≈ mean(values(ohlc)[1:10, :], dims = 1) - end -end - - -@testset "basecall works with Base methods" begin - @testset "cumsum works" begin - @test values(basecall(cl, cumsum))[2] == values(cl)[1] + values(cl)[2] - end -end + @testset "upto method accumulates" begin + @test values(upto(sum, cl))[10] ≈ sum(values(cl)[1:10]) + @test values(upto(mean, cl))[10] ≈ mean(values(cl)[1:10]) -@testset "adding/removing missing rows works" begin - uohlc = uniformspace(ohlc[1:8]) + @test timestamp(upto(sum, cl))[10] == Date(2000, 1, 14) + @test timestamp(upto(mean, cl))[10] == Date(2000, 1, 14) - @testset "uniform spacing detection works" begin - @test uniformspaced(datetime1) - @test uniformspaced(uohlc) - @test !uniformspaced(cl) - @test !uniformspaced(ohlc) + # transpose the upto value output from column to row vector but values are identical + @test values(upto(sum, ohlc))[10, :]' ≈ sum(values(ohlc)[1:10, :]; dims=1) + @test values(upto(mean, ohlc))[10, :]' ≈ mean(values(ohlc)[1:10, :]; dims=1) + end end - @testset "forcing uniform spacing works" begin - @test length(uohlc) == 10 - @test values(uohlc[5]) == values(ohlc[5]) - @test all(isnan, values(uohlc[6])) - @test all(isnan, values(uohlc[7])) - @test values(uohlc[8]) == values(ohlc[6]) + @testset "basecall works with Base methods" begin + @testset "cumsum works" begin + @test values(basecall(cl, cumsum))[2] == values(cl)[1] + values(cl)[2] + end end - @testset "dropnan works" begin - nohlc = TimeArray(timestamp(ohlc), copy(values(ohlc)), colnames(ohlc), meta(ohlc)) - values(nohlc)[7:12, 2] .= NaN + @testset "adding/removing missing rows works" begin + uohlc = uniformspace(ohlc[1:8]) - @test timestamp(dropnan(uohlc)) == timestamp(dropnan(uohlc, :all)) - @test values(dropnan(uohlc)) == values(dropnan(uohlc, :all)) + @testset "uniform spacing detection works" begin + @test uniformspaced(datetime1) + @test uniformspaced(uohlc) + @test !uniformspaced(cl) + @test !uniformspaced(ohlc) + end - @test values(dropnan(ohlc, :all)) == values(ohlc) - @test timestamp(dropnan(nohlc, :all)) == timestamp(ohlc) - @test timestamp(dropnan(uohlc, :all)) == timestamp(ohlc[1:8]) - @test values(dropnan(uohlc, :all)) == values(ohlc[1:8]) + @testset "forcing uniform spacing works" begin + @test length(uohlc) == 10 + @test values(uohlc[5]) == values(ohlc[5]) + @test all(isnan, values(uohlc[6])) + @test all(isnan, values(uohlc[7])) + @test values(uohlc[8]) == values(ohlc[6]) + end - @test values(dropnan(ohlc, :any)) == values(ohlc) - @test timestamp(dropnan(nohlc, :any)) == timestamp(ohlc)[[1:6;13:end]] - @test values(dropnan(nohlc, :any)) == values(ohlc)[[1:6;13:end], :] - @test timestamp(dropnan(uohlc, :any)) == timestamp(ohlc[1:8]) - @test values(dropnan(uohlc, :any)) == values(ohlc[1:8]) + @testset "dropnan works" begin + nohlc = TimeArray( + timestamp(ohlc), copy(values(ohlc)), colnames(ohlc), meta(ohlc) + ) + values(nohlc)[7:12, 2] .= NaN + + @test timestamp(dropnan(uohlc)) == timestamp(dropnan(uohlc, :all)) + @test values(dropnan(uohlc)) == values(dropnan(uohlc, :all)) + + @test values(dropnan(ohlc, :all)) == values(ohlc) + @test timestamp(dropnan(nohlc, :all)) == timestamp(ohlc) + @test timestamp(dropnan(uohlc, :all)) == timestamp(ohlc[1:8]) + @test values(dropnan(uohlc, :all)) == values(ohlc[1:8]) + + @test values(dropnan(ohlc, :any)) == values(ohlc) + @test timestamp(dropnan(nohlc, :any)) == timestamp(ohlc)[[1:6; 13:end]] + @test values(dropnan(nohlc, :any)) == values(ohlc)[[1:6; 13:end], :] + @test timestamp(dropnan(uohlc, :any)) == timestamp(ohlc[1:8]) + @test values(dropnan(uohlc, :any)) == values(ohlc[1:8]) + end end -end - - end # @testset "apply" diff --git a/test/basemisc.jl b/test/basemisc.jl index 02a042d6..c5a75ac8 100644 --- a/test/basemisc.jl +++ b/test/basemisc.jl @@ -5,235 +5,229 @@ using MarketData using TimeSeries - @testset "basemisc" begin + @testset "cumulative functions" begin + let ta = cumsum(cl) + @test values(ta) == cumsum(values(cl)) + @test meta(ta) == meta(cl) + end + let ta = cumsum(ohlc; dims=1) + @test values(ta) == cumsum(values(ohlc); dims=1) + @test meta(ta) == meta(ohlc) + end -@testset "cumulative functions" begin - let ta = cumsum(cl) - @test values(ta) == cumsum(values(cl)) - @test meta(ta) == meta(cl) - end + let ta = cumsum(cl; dims=2) + @test values(ta) == cumsum(values(cl); dims=2) + @test meta(ta) == meta(cl) + end - let ta = cumsum(ohlc, dims = 1) - @test values(ta) == cumsum(values(ohlc), dims = 1) - @test meta(ta) == meta(ohlc) - end + let ta = cumsum(ohlc; dims=2) + @test values(ta) == cumsum(values(ohlc); dims=2) + @test meta(ta) == meta(ohlc) + end - let ta = cumsum(cl, dims = 2) - @test values(ta) == cumsum(values(cl), dims = 2) - @test meta(ta) == meta(cl) - end + @test_throws DimensionMismatch cumsum(cl, dims=3) + @test_throws DimensionMismatch cumsum(ohlc, dims=3) - let ta = cumsum(ohlc, dims = 2) - @test values(ta) == cumsum(values(ohlc), dims = 2) - @test meta(ta) == meta(ohlc) - end + let ta = cumprod(cl[1:5]) + @test values(ta) == cumprod(values(cl[1:5])) + @test meta(ta) == meta(cl[1:5]) + end - @test_throws DimensionMismatch cumsum(cl, dims = 3) - @test_throws DimensionMismatch cumsum(ohlc, dims = 3) + let ta = cumprod(ohlc[1:5]; dims=1) + @test values(ta) == cumprod(values(ohlc[1:5]); dims=1) + @test meta(ta) == meta(ohlc[1:5]) + end - let ta = cumprod(cl[1:5]) - @test values(ta) == cumprod(values(cl[1:5])) - @test meta(ta) == meta(cl[1:5]) - end + let ta = cumprod(cl[1:5]; dims=2) + @test values(ta) == cumprod(values(cl[1:5]); dims=2) + @test meta(ta) == meta(cl[1:5]) + end - let ta = cumprod(ohlc[1:5], dims = 1) - @test values(ta) == cumprod(values(ohlc[1:5]), dims = 1) - @test meta(ta) == meta(ohlc[1:5]) - end + let ta = cumprod(ohlc[1:5]; dims=2) + @test values(ta) == cumprod(values(ohlc[1:5]); dims=2) + @test meta(ta) == meta(ohlc[1:5]) + end - let ta = cumprod(cl[1:5], dims = 2) - @test values(ta) == cumprod(values(cl[1:5]), dims = 2) - @test meta(ta) == meta(cl[1:5]) + @test_throws DimensionMismatch cumprod(cl, dims=3) + @test_throws DimensionMismatch cumprod(ohlc, dims=3) end - let ta = cumprod(ohlc[1:5], dims = 2) - @test values(ta) == cumprod(values(ohlc[1:5]), dims = 2) - @test meta(ta) == meta(ohlc[1:5]) + @testset "reduction functions" begin + for (fname, f) in + ([(:sum, sum), (:mean, mean), (:maximum, maximum), (:minimum, minimum)]) + for (name, src) in [(:cl, cl), (:ohlc, ohlc)] + @testset "$fname::$name" begin + let ta = f(src) + @test meta(ta) == meta(src) + @test length(ta) == 1 + @test values(ta) == f(values(src); dims=1) + end + + let ta = f(src; dims=2) + @test meta(ta) == meta(src) + @test length(ta) == length(timestamp(src)) + @test values(ta) == f(values(src); dims=2) + @test colnames(ta) == [fname] + end + + @test_throws DimensionMismatch f(src, dims=3) + end # @testset + end + end # for func + + for (fname, f) in ([(:std, std), (:var, var)]) + for (name, src) in [(:cl, cl), (:ohlc, ohlc)] + @testset "$fname::$name" begin + let ta = f(src) + @test meta(ta) == meta(src) + @test length(ta) == 1 + @test values(ta) == f(values(src); dims=1) + end + + let ta = f(src; dims=2, corrected=false) + @test meta(ta) == meta(src) + @test length(ta) == length(timestamp(src)) + @test values(ta) == f(values(src); dims=2, corrected=false) + @test colnames(ta) == [fname] + end + + @test_throws DimensionMismatch f(src, dims=3) + end # @testset + end + end # for func end - @test_throws DimensionMismatch cumprod(cl, dims = 3) - @test_throws DimensionMismatch cumprod(ohlc, dims = 3) -end - -@testset "reduction functions" begin - for (fname, f) ∈ ([(:sum, sum), (:mean, mean), (:maximum, maximum), (:minimum, minimum)]) - for (name, src) ∈ [(:cl, cl), (:ohlc, ohlc)] - @testset "$fname::$name" begin - let ta = f(src) - @test meta(ta) == meta(src) - @test length(ta) == 1 - @test values(ta) == f(values(src), dims = 1) - end - - let ta = f(src, dims = 2) - @test meta(ta) == meta(src) - @test length(ta) == length(timestamp(src)) - @test values(ta) == f(values(src), dims = 2) - @test colnames(ta) == [fname] - end - - @test_throws DimensionMismatch f(src, dims = 3) - end # @testset - end - end # for func - - for (fname, f) ∈ ([(:std, std), (:var, var)]) - for (name, src) ∈ [(:cl, cl), (:ohlc, ohlc)] - @testset "$fname::$name" begin - let ta = f(src) - @test meta(ta) == meta(src) - @test length(ta) == 1 - @test values(ta) == f(values(src), dims = 1) - end - - let ta = f(src, dims = 2, corrected = false) - @test meta(ta) == meta(src) - @test length(ta) == length(timestamp(src)) - @test values(ta) == f(values(src), dims = 2, corrected = false) - @test colnames(ta) == [fname] - end - - @test_throws DimensionMismatch f(src, dims = 3) - end # @testset - end - end # for func -end - - -@testset "Base.any function" begin - let # single column - """ - julia> any(x .== lag(x)) - 1x1 TimeSeries.TimeArray{Bool,1,Date,BitArray{1}} 2000-01-07 to 2000-01-07 - │ │ _ │ - ├────────────┼───────┤ - │ 2000-01-07 │ false │ - """ - let - ts = timestamp(cl)[1:5] - ta = TimeArray(ts, 1:5) - xs = any(ta .== lag(ta)) - @test timestamp(xs)[1] == ts[end] - @test values(xs)[1] == false - end - - """ - julia> any(x .== lag(x), 2) - 4x1 TimeSeries.TimeArray{Bool,1,Date,BitArray{1}} 2000-01-04 to 2000-01-07 - │ │ any │ - ├────────────┼───────┤ - │ 2000-01-04 │ false │ - │ 2000-01-05 │ true │ - │ 2000-01-06 │ false │ - │ 2000-01-07 │ false │ - """ - let - ts = timestamp(cl)[1:5] - ta = TimeArray(ts, [1, 2, 2, 3, 4]) - xs = any(ta .== lag(ta), dims = 2) - - @test timestamp(xs) == ts[2:end] - @test any(values(xs) .== [false, true, false, false]) - @test colnames(xs) == [:any] - end - end # single column - - let # multi column - """ - julia> x .> 3 - 3x2 TimeSeries.TimeArray{Bool,2,Date,BitArray{2}} 2000-01-03 to 2000-01-05 - │ │ A │ B │ - ├────────────┼───────┼───────┤ - │ 2000-01-03 │ false │ false │ - │ 2000-01-04 │ false │ true │ - │ 2000-01-05 │ true │ true │ - - julia> any(x .> 3, 2) - 3x1 TimeSeries.TimeArray{Bool,2,Date,BitArray{2}} 2000-01-03 to 2000-01-05 - │ │ any │ - ├────────────┼───────┤ - │ 2000-01-03 │ false │ - │ 2000-01-04 │ true │ - │ 2000-01-05 │ true │ - """ - ts = timestamp(cl)[1:3] - ta = TimeArray(ts, [1 2; 3 4; 5 6]) - xs = any(ta .> 3, dims = 2) - - @test timestamp(xs) == ts - @test any(values(xs) .== [false, true, true]) - @test colnames(xs) == [:any] - end # multi column -end # @testset "Base.any" function - - -@testset "Base.all function" begin - let # single column - """ - julia> all(x .== lag(x)) - 1x1 TimeSeries.TimeArray{Bool,1,Date,BitArray{1}} 2000-01-07 to 2000-01-07 - │ │ A │ - ├────────────┼───────┤ - │ 2000-01-07 │ false │ - """ - let - ts = timestamp(cl)[1:5] - ta = TimeArray(ts, 1:5) - xs = all(ta .== lag(ta)) - @test timestamp(xs)[] == ts[end] - @test values(xs)[] == false - end - - """ - julia> all(x .== lag(x), 2) - 4x1 TimeSeries.TimeArray{Bool,1,Date,BitArray{1}} 2000-01-04 to 2000-01-07 - │ │ any │ - ├────────────┼───────┤ - │ 2000-01-04 │ false │ - │ 2000-01-05 │ true │ - │ 2000-01-06 │ false │ - │ 2000-01-07 │ false │ - """ - let - ts = timestamp(cl)[1:5] - ta = TimeArray(ts, [1, 2, 2, 3, 4]) - xs = all(ta .== lag(ta), dims = 2) - - @test timestamp(xs) == ts[2:end] - @test all(values(xs) .== [false, true, false, false]) + @testset "Base.any function" begin + let # single column + """ + julia> any(x .== lag(x)) + 1x1 TimeSeries.TimeArray{Bool,1,Date,BitArray{1}} 2000-01-07 to 2000-01-07 + │ │ _ │ + ├────────────┼───────┤ + │ 2000-01-07 │ false │ + """ + let + ts = timestamp(cl)[1:5] + ta = TimeArray(ts, 1:5) + xs = any(ta .== lag(ta)) + @test timestamp(xs)[1] == ts[end] + @test values(xs)[1] == false + end + + """ + julia> any(x .== lag(x), 2) + 4x1 TimeSeries.TimeArray{Bool,1,Date,BitArray{1}} 2000-01-04 to 2000-01-07 + │ │ any │ + ├────────────┼───────┤ + │ 2000-01-04 │ false │ + │ 2000-01-05 │ true │ + │ 2000-01-06 │ false │ + │ 2000-01-07 │ false │ + """ + let + ts = timestamp(cl)[1:5] + ta = TimeArray(ts, [1, 2, 2, 3, 4]) + xs = any(ta .== lag(ta); dims=2) + + @test timestamp(xs) == ts[2:end] + @test any(values(xs) .== [false, true, false, false]) + @test colnames(xs) == [:any] + end + end # single column + + let # multi column + """ + julia> x .> 3 + 3x2 TimeSeries.TimeArray{Bool,2,Date,BitArray{2}} 2000-01-03 to 2000-01-05 + │ │ A │ B │ + ├────────────┼───────┼───────┤ + │ 2000-01-03 │ false │ false │ + │ 2000-01-04 │ false │ true │ + │ 2000-01-05 │ true │ true │ + + julia> any(x .> 3, 2) + 3x1 TimeSeries.TimeArray{Bool,2,Date,BitArray{2}} 2000-01-03 to 2000-01-05 + │ │ any │ + ├────────────┼───────┤ + │ 2000-01-03 │ false │ + │ 2000-01-04 │ true │ + │ 2000-01-05 │ true │ + """ + ts = timestamp(cl)[1:3] + ta = TimeArray(ts, [1 2; 3 4; 5 6]) + xs = any(ta .> 3; dims=2) + + @test timestamp(xs) == ts + @test any(values(xs) .== [false, true, true]) + @test colnames(xs) == [:any] + end # multi column + end # @testset "Base.any" function + + @testset "Base.all function" begin + let # single column + """ + julia> all(x .== lag(x)) + 1x1 TimeSeries.TimeArray{Bool,1,Date,BitArray{1}} 2000-01-07 to 2000-01-07 + │ │ A │ + ├────────────┼───────┤ + │ 2000-01-07 │ false │ + """ + let + ts = timestamp(cl)[1:5] + ta = TimeArray(ts, 1:5) + xs = all(ta .== lag(ta)) + @test timestamp(xs)[] == ts[end] + @test values(xs)[] == false + end + + """ + julia> all(x .== lag(x), 2) + 4x1 TimeSeries.TimeArray{Bool,1,Date,BitArray{1}} 2000-01-04 to 2000-01-07 + │ │ any │ + ├────────────┼───────┤ + │ 2000-01-04 │ false │ + │ 2000-01-05 │ true │ + │ 2000-01-06 │ false │ + │ 2000-01-07 │ false │ + """ + let + ts = timestamp(cl)[1:5] + ta = TimeArray(ts, [1, 2, 2, 3, 4]) + xs = all(ta .== lag(ta); dims=2) + + @test timestamp(xs) == ts[2:end] + @test all(values(xs) .== [false, true, false, false]) + @test colnames(xs) == [:all] + end + end # single column + + let # multi column + """ + julia> x .> 3 + 3x2 TimeSeries.TimeArray{Bool,2,Date,BitArray{2}} 2000-01-03 to 2000-01-05 + │ │ A │ B │ + ├────────────┼───────┼───────┤ + │ 2000-01-03 │ false │ false │ + │ 2000-01-04 │ false │ true │ + │ 2000-01-05 │ true │ true │ + + julia> all(x .> 3, 2) + 3x1 TimeSeries.TimeArray{Bool,2,Date,BitArray{2}} 2000-01-03 to 2000-01-05 + │ │ all │ + ├────────────┼───────┤ + │ 2000-01-03 │ false │ + │ 2000-01-04 │ false │ + │ 2000-01-05 │ true │ + """ + ts = timestamp(cl)[1:3] + ta = TimeArray(ts, [1 2; 3 4; 5 6]) + xs = all(ta .> 3; dims=2) + + @test timestamp(xs) == ts + @test all(values(xs) .== [false, false, true]) @test colnames(xs) == [:all] - end - end # single column - - let # multi column - """ - julia> x .> 3 - 3x2 TimeSeries.TimeArray{Bool,2,Date,BitArray{2}} 2000-01-03 to 2000-01-05 - │ │ A │ B │ - ├────────────┼───────┼───────┤ - │ 2000-01-03 │ false │ false │ - │ 2000-01-04 │ false │ true │ - │ 2000-01-05 │ true │ true │ - - julia> all(x .> 3, 2) - 3x1 TimeSeries.TimeArray{Bool,2,Date,BitArray{2}} 2000-01-03 to 2000-01-05 - │ │ all │ - ├────────────┼───────┤ - │ 2000-01-03 │ false │ - │ 2000-01-04 │ false │ - │ 2000-01-05 │ true │ - """ - ts = timestamp(cl)[1:3] - ta = TimeArray(ts, [1 2; 3 4; 5 6]) - xs = all(ta .> 3, dims = 2) - - @test timestamp(xs) == ts - @test all(values(xs) .== [false, false, true]) - @test colnames(xs) == [:all] - end # multi column -end # @testset "Base.all" function - - + end # multi column + end # @testset "Base.all" function end # @testset "basemisc" diff --git a/test/broadcast.jl b/test/broadcast.jl index 912b9933..7d4deef5 100644 --- a/test/broadcast.jl +++ b/test/broadcast.jl @@ -4,286 +4,280 @@ using MarketData using TimeSeries - @testset "broadcast" begin - - -@testset "base element-wise operators on TimeArray values" begin - @testset "only values on intersecting Dates computed" begin - let - ta = cl[1:2] ./ op[2:3] - @test values(ta)[1] ≈ 0.94688222 - @test length(ta) == 1 + @testset "base element-wise operators on TimeArray values" begin + @testset "only values on intersecting Dates computed" begin + let + ta = cl[1:2] ./ op[2:3] + @test values(ta)[1] ≈ 0.94688222 + @test length(ta) == 1 + end + + let + ta = cl[1:4] .+ op[4:7] + @test values(ta)[1] ≈ 201.12 atol = 0.01 + @test length(ta) == 1 + end end - let - ta = cl[1:4] .+ op[4:7] - @test values(ta)[1] ≈ 201.12 atol=.01 - @test length(ta) == 1 + @testset "unary operation on TimeArray values" begin + @test values(+cl)[1] == values(cl)[1] + @test values(-cl)[1] == -values(cl)[1] + @test values(.!(cl .== op))[1] == true + @test values(log.(cl))[1] == log.(values(cl)[1]) + @test values(sqrt.(cl))[1] == sqrt.(values(cl)[1]) + @test values(+ohlc)[1, :] == values(ohlc)[1, :] + @test values(-ohlc)[1, :] == -(values(ohlc)[1, :]) + @test values(.!(ohlc .== ohlc))[1, 1] == false + @test values(log.(ohlc))[1, :] == log.(values(ohlc)[1, :]) + @test values(sqrt.(ohlc))[1, :] == sqrt.(values(ohlc)[1, :]) + @test_throws DomainError sqrt.(-ohlc) end - end - @testset "unary operation on TimeArray values" begin - @test values(+cl)[1] == values(cl)[1] - @test values(-cl)[1] == -values(cl)[1] - @test values(.!(cl .== op))[1] == true - @test values(log.(cl))[1] == log.(values(cl)[1]) - @test values(sqrt.(cl))[1] == sqrt.(values(cl)[1]) - @test values(+ohlc)[1, :] == values(ohlc)[1,:] - @test values(-ohlc)[1, :] == -(values(ohlc)[1,:]) - @test values(.!(ohlc .== ohlc))[1, 1] == false - @test values(log.(ohlc))[1, :] == log.(values(ohlc)[1, :]) - @test values(sqrt.(ohlc))[1, :] == sqrt.(values(ohlc)[1, :]) - @test_throws DomainError sqrt.(-ohlc) - end - - @testset "dot operation between TimeArray values and Int/Float and viceversa" begin - @test values(cl .- 100)[1] ≈ 11.94 atol=.01 - @test values(cl .+ 100)[1] ≈ 211.94 atol=.01 - @test values(cl .* 100)[1] ≈ 11194 atol=1 - @test values(cl ./ 100)[1] ≈ 1.1194 atol=.0001 - @test values(cl .^ 2)[1] ≈ 12530.5636 atol=.0001 - @test values(cl .% 2)[1] == values(cl)[1] % 2 - @test values(100 .- cl)[1] ≈ -11.94 atol=.01 - @test values(100 .+ cl)[1] ≈ 211.94 atol=.01 - @test values(100 .* cl)[1] ≈ 11194 atol=.01 - @test values(100 ./ cl)[1] ≈ 0.8933357155619082 - @test values(2 .^ cl)[1] == 4980784073277740581384811358191616 - @test values(2 .% cl)[1] == 2 - @test values(ohlc .- 100)[1,:] == values(ohlc)[1,:] .- 100 - @test values(ohlc .+ 100)[1,:] == values(ohlc)[1,:] .+ 100 - @test values(ohlc .* 100)[1,:] == values(ohlc)[1,:] .* 100 - @test values(ohlc ./ 100)[1,:] == values(ohlc)[1,:] ./ 100 - @test values(ohlc .^ 2)[1,:] == values(ohlc)[1,:] .^ 2 - @test values(ohlc .% 2)[1,:] == values(ohlc)[1,:] .% 2 - @test values(100 .- ohlc)[1,:] == 100 .- values(ohlc)[1,:] - @test values(100 .+ ohlc)[1,:] == 100 .+ values(ohlc)[1,:] - @test values(100 .* ohlc)[1,:] == 100 .* values(ohlc)[1,:] - @test values(100 ./ ohlc)[1,:] == 100 ./ values(ohlc)[1,:] - @test values(2 .^ ohlc)[1,:] == 2 .^ values(ohlc)[1,:] - @test values(2 .% ohlc)[1,:] == 2 .% values(ohlc)[1,:] - end - - @testset "mathematical operations between two same-column-count TimeArrays" begin - @test values(cl .+ op)[1] ≈ 216.82 atol=.01 - @test values(cl .- op)[1] ≈ 7.06 atol=.01 - @test values(cl .* op)[1] ≈ 11740.2672 atol=0.0001 - @test values(cl ./ op)[1] ≈ 1.067315 atol=0.001 - @test values(cl .% op) == values(cl) .% values(op) - @test values(cl .^ op) == values(cl) .^ values(op) - @test values(cl .* (cl.> 200)) == values(cl) .* (values(cl) .> 200) - @test values(round.(cl) .* cl) == round.(Int, values(cl)) .* values(cl) - @test values(ohlc .+ ohlc) == values(ohlc) .+ values(ohlc) - @test values(ohlc .- ohlc) == values(ohlc) .- values(ohlc) - @test values(ohlc .* ohlc) == values(ohlc) .* values(ohlc) - @test values(ohlc ./ ohlc) == values(ohlc) ./ values(ohlc) - @test values(ohlc .% ohlc) == values(ohlc) .% values(ohlc) - @test values(ohlc .^ ohlc) == values(ohlc) .^ values(ohlc) - @test values(ohlc .* (ohlc .> 200)) == values(ohlc) .* (values(ohlc) .> 200) - @test values(round.(ohlc) .* ohlc) == round.(Int, values(ohlc)) .* values(ohlc) - end - - @testset "broadcasted mathematical operations between different-column-count TimeArrays" begin - @test values(ohlc .+ cl) == values(ohlc) .+ values(cl) - @test values(ohlc .- cl) == values(ohlc) .- values(cl) - @test values(ohlc .* cl) == values(ohlc) .* values(cl) - @test values(ohlc ./ cl) == values(ohlc) ./ values(cl) - @test values(ohlc .% cl) == values(ohlc) .% values(cl) - @test values(ohlc .^ cl) == values(ohlc) .^ values(cl) - @test values(ohlc .* (cl.> 200)) == values(ohlc) .* (values(cl) .> 200) - @test values(round.(ohlc) .* cl) == round.(Int, values(ohlc)) .* values(cl) - @test values(cl .+ ohlc) == values(cl) .+ values(ohlc) - @test values(cl .- ohlc) == values(cl) .- values(ohlc) - @test values(cl .* ohlc) == values(cl) .* values(ohlc) - @test values(cl ./ ohlc) == values(cl) ./ values(ohlc) - @test values(cl .% ohlc) == values(cl) .% values(ohlc) - @test values(cl .^ ohlc) == values(cl) .^ values(ohlc) - @test values(cl .* (ohlc .> 200)) == values(cl) .* (values(ohlc) .> 200) - @test values(round.(cl) .* ohlc) == round.(Int, values(cl)) .* values(ohlc) - # One array must have a single column - @test_throws DimensionMismatch (ohlc[:Open, :Close] .+ ohlc) - end - - @testset "comparison operations between TimeArray values and Int/Float (and viceversa)" begin - @test values(cl .> 111.94)[1] == false - @test values(cl .< 111.94)[1] == false - @test values(cl .>= 111.94)[1] == true - @test values(cl .<= 111.94)[1] == true - @test values(cl .== 111.94)[1] == true - @test values(cl .!= 111.94)[1] == false - @test values(111.94 .> cl)[1] == false - @test values(111.94 .< cl)[1] == false - @test values(111.94 .>= cl)[1] == true - @test values(111.94 .<= cl)[1] == true - @test values(111.94 .== cl)[1] == true - @test values(111.94 .!= cl)[1] == false - @test values(ohlc .> 111.94)[1,:] == [false, true, false, false] - @test values(ohlc .< 111.94)[1,:] == [true, false, true, false] - @test values(ohlc .>= 111.94)[1,:] == [false, true, false, true] - @test values(ohlc .<= 111.94)[1,:] == [true, false, true, true] - @test values(ohlc .== 111.94)[1,:] == [false, false, false, true] - @test values(ohlc .!= 111.94)[1,:] == [true, true, true, false] - @test values(111.94 .> ohlc)[1,:] == [true, false, true, false] - @test values(111.94 .< ohlc)[1,:] == [false, true, false, false] - @test values(111.94 .>= ohlc)[1,:] == [true, false, true, true] - @test values(111.94 .<= ohlc)[1,:] == [false, true, false, true] - @test values(111.94 .== ohlc)[1,:] == [false, false, false, true] - @test values(111.94 .!= ohlc)[1,:] == [true, true, true, false] - end - - @testset "comparison operations between TimeArray values and Bool (and viceversa)" begin - @test values((cl .> 111.94) .== true)[1] == false - @test values((cl .> 111.94) .!= true)[1] == true - @test values(true .== (cl .> 111.94))[1] == false - @test values(true .!= (cl .> 111.94))[1] == true - @test values((ohlc .> 111.94).== true)[1,:] == [false, true, false, false] - @test values((ohlc .> 111.94).!= true)[1,:] == [true, false, true, true] - @test values(true .== (ohlc .> 111.94))[1,:] == [false, true, false, false] - @test values(true .!= (ohlc .> 111.94))[1,:] == [true, false, true, true] - end - - @testset "comparison operations between same-column-count TimeArrays" begin - @test values(cl .> op)[1] == true - @test values(cl .< op)[1] == false - @test values(cl .<= op)[1] == false - @test values(cl .>= op)[1] == true - @test values(cl .== op)[1] == false - @test values(cl .!= op)[1] == true - @test values(ohlc .> ohlc)[1,:] == [false, false, false, false] - @test values(ohlc .< ohlc)[1,:] == [false, false, false, false] - @test values(ohlc .<= ohlc)[1,:] == [true, true, true, true] - @test values(ohlc .>= ohlc)[1,:] == [true, true, true, true] - @test values(ohlc .== ohlc)[1,:] == [true, true, true, true] - @test values(ohlc .!= ohlc)[1,:] == [false, false, false, false] - end - - @testset "comparison operations between different-column-count TimeArrays" begin - @test values(ohlc .> cl) == (values(ohlc) .> values(cl)) - @test values(ohlc .< cl) == (values(ohlc) .< values(cl)) - @test values(ohlc .>= cl) == (values(ohlc) .>= values(cl)) - @test values(ohlc .<= cl) == (values(ohlc) .<= values(cl)) - @test values(ohlc .== cl) == (values(ohlc) .== values(cl)) - @test values(ohlc .!= cl) == (values(ohlc) .!= values(cl)) - @test values(cl .> ohlc) == (values(cl) .> values(ohlc)) - @test values(cl .< ohlc) == (values(cl) .< values(ohlc)) - @test values(cl .>= ohlc) == (values(cl) .>= values(ohlc)) - @test values(cl .<= ohlc) == (values(cl) .<= values(ohlc)) - @test values(cl .== ohlc) == (values(cl) .== values(ohlc)) - @test values(cl .!= ohlc) == (values(cl) .!= values(ohlc)) - # One array must have a single column - @test_throws DimensionMismatch (ohlc[:Open, :Close] .== ohlc) - end - - @testset "bitwise elementwise operations between bool and TimeArrays' values" begin - @test values((cl .> 100) .& true)[1] == true - @test values((cl .> 100) .| true)[1] == true - @test values((cl .> 100) .⊻ true)[1] == false - @test values(false .& (cl .> 100))[1] == false - @test values(false .| (cl .> 100))[1] == true - @test values(false .⊻ (cl .> 100))[1] == true - @test values((ohlc .> 100) .& true)[4,:] == [true, true, false, false] - @test values((ohlc .> 100) .| true)[4,:] == [true, true, true, true] - @test values((ohlc .> 100) .⊻ true)[4,:] == [false, false, true, true] - @test values(false .& (ohlc .> 100))[4,:] == [false, false, false, false] - @test values(false .| (ohlc .> 100))[4,:] == [true, true, false, false] - @test values(false .⊻ (ohlc .> 100))[4,:] == [true, true, false, false] - end - - @testset "bitwise elementwise operations between same-column-count TimeArrays' boolean values" begin - @test values((cl .> 100) .& (cl .< 120))[1] == true - @test values((cl .> 100) .| (cl .< 120))[1] == true - @test values((cl .> 100) .⊻ (cl .< 120))[1] == false - @test values((ohlc .> 100) .& (ohlc .< 120))[4,:] == [true, true, false, false] - @test values((ohlc .> 100) .| (ohlc .< 120))[4,:] == [true, true, true, true] - @test values((ohlc .> 100) .⊻ (ohlc .< 120))[4,:] == [false, false, true, true] - @test values((ohlc .> 100) .⊻ (cl .< 120))[4,:] == [false, false, true, true] - end -end - - -@testset "dot call auto-fusion" begin - @testset "single TimeArray" begin - let ta = sin.(log.(2, op)) - @test colnames(ta) == [:Open] - @test timestamp(ta) == timestamp(op) - @test meta(ta) == meta(op) - @test values(ta)[1] == sin(log(2, values(op)[1])) - @test values(ta)[end] == sin(log(2, values(op)[end])) + @testset "dot operation between TimeArray values and Int/Float and viceversa" begin + @test values(cl .- 100)[1] ≈ 11.94 atol = 0.01 + @test values(cl .+ 100)[1] ≈ 211.94 atol = 0.01 + @test values(cl .* 100)[1] ≈ 11194 atol = 1 + @test values(cl ./ 100)[1] ≈ 1.1194 atol = 0.0001 + @test values(cl .^ 2)[1] ≈ 12530.5636 atol = 0.0001 + @test values(cl .% 2)[1] == values(cl)[1] % 2 + @test values(100 .- cl)[1] ≈ -11.94 atol = 0.01 + @test values(100 .+ cl)[1] ≈ 211.94 atol = 0.01 + @test values(100 .* cl)[1] ≈ 11194 atol = 0.01 + @test values(100 ./ cl)[1] ≈ 0.8933357155619082 + @test values(2 .^ cl)[1] == 4980784073277740581384811358191616 + @test values(2 .% cl)[1] == 2 + @test values(ohlc .- 100)[1, :] == values(ohlc)[1, :] .- 100 + @test values(ohlc .+ 100)[1, :] == values(ohlc)[1, :] .+ 100 + @test values(ohlc .* 100)[1, :] == values(ohlc)[1, :] .* 100 + @test values(ohlc ./ 100)[1, :] == values(ohlc)[1, :] ./ 100 + @test values(ohlc .^ 2)[1, :] == values(ohlc)[1, :] .^ 2 + @test values(ohlc .% 2)[1, :] == values(ohlc)[1, :] .% 2 + @test values(100 .- ohlc)[1, :] == 100 .- values(ohlc)[1, :] + @test values(100 .+ ohlc)[1, :] == 100 .+ values(ohlc)[1, :] + @test values(100 .* ohlc)[1, :] == 100 .* values(ohlc)[1, :] + @test values(100 ./ ohlc)[1, :] == 100 ./ values(ohlc)[1, :] + @test values(2 .^ ohlc)[1, :] == 2 .^ values(ohlc)[1, :] + @test values(2 .% ohlc)[1, :] == 2 .% values(ohlc)[1, :] end - f(x, c) = x + c - let ta = f.(cl, 42) - @test colnames(ta) == [:Close] - @test timestamp(ta) == timestamp(cl) - @test meta(ta) == meta(cl) - @test values(ta)[1] == values(cl)[1] + 42 - @test values(ta)[end] == values(cl)[end] + 42 + @testset "mathematical operations between two same-column-count TimeArrays" begin + @test values(cl .+ op)[1] ≈ 216.82 atol = 0.01 + @test values(cl .- op)[1] ≈ 7.06 atol = 0.01 + @test values(cl .* op)[1] ≈ 11740.2672 atol = 0.0001 + @test values(cl ./ op)[1] ≈ 1.067315 atol = 0.001 + @test values(cl .% op) == values(cl) .% values(op) + @test values(cl .^ op) == values(cl) .^ values(op) + @test values(cl .* (cl .> 200)) == values(cl) .* (values(cl) .> 200) + @test values(round.(cl) .* cl) == round.(Int, values(cl)) .* values(cl) + @test values(ohlc .+ ohlc) == values(ohlc) .+ values(ohlc) + @test values(ohlc .- ohlc) == values(ohlc) .- values(ohlc) + @test values(ohlc .* ohlc) == values(ohlc) .* values(ohlc) + @test values(ohlc ./ ohlc) == values(ohlc) ./ values(ohlc) + @test values(ohlc .% ohlc) == values(ohlc) .% values(ohlc) + @test values(ohlc .^ ohlc) == values(ohlc) .^ values(ohlc) + @test values(ohlc .* (ohlc .> 200)) == values(ohlc) .* (values(ohlc) .> 200) + @test values(round.(ohlc) .* ohlc) == round.(Int, values(ohlc)) .* values(ohlc) end - let ta = sin.(log.(2, ohlc)) - @test colnames(ta) == [:Open, :High, :Low, :Close] - @test timestamp(ta) == timestamp(ohlc) - @test meta(ta) == meta(ohlc) - - @test values(ta)[1, 1] == sin(log(2, values(ohlc)[1, 1])) - @test values(ta)[1, 2] == sin(log(2, values(ohlc)[1, 2])) - @test values(ta)[1, 3] == sin(log(2, values(ohlc)[1, 3])) - @test values(ta)[1, 4] == sin(log(2, values(ohlc)[1, 4])) + @testset "broadcasted mathematical operations between different-column-count TimeArrays" begin + @test values(ohlc .+ cl) == values(ohlc) .+ values(cl) + @test values(ohlc .- cl) == values(ohlc) .- values(cl) + @test values(ohlc .* cl) == values(ohlc) .* values(cl) + @test values(ohlc ./ cl) == values(ohlc) ./ values(cl) + @test values(ohlc .% cl) == values(ohlc) .% values(cl) + @test values(ohlc .^ cl) == values(ohlc) .^ values(cl) + @test values(ohlc .* (cl .> 200)) == values(ohlc) .* (values(cl) .> 200) + @test values(round.(ohlc) .* cl) == round.(Int, values(ohlc)) .* values(cl) + @test values(cl .+ ohlc) == values(cl) .+ values(ohlc) + @test values(cl .- ohlc) == values(cl) .- values(ohlc) + @test values(cl .* ohlc) == values(cl) .* values(ohlc) + @test values(cl ./ ohlc) == values(cl) ./ values(ohlc) + @test values(cl .% ohlc) == values(cl) .% values(ohlc) + @test values(cl .^ ohlc) == values(cl) .^ values(ohlc) + @test values(cl .* (ohlc .> 200)) == values(cl) .* (values(ohlc) .> 200) + @test values(round.(cl) .* ohlc) == round.(Int, values(cl)) .* values(ohlc) + # One array must have a single column + @test_throws DimensionMismatch (ohlc[:Open, :Close] .+ ohlc) + end - @test values(ta)[end, 1] == sin(log(2, values(ohlc)[end, 1])) - @test values(ta)[end, 2] == sin(log(2, values(ohlc)[end, 2])) - @test values(ta)[end, 3] == sin(log(2, values(ohlc)[end, 3])) - @test values(ta)[end, 4] == sin(log(2, values(ohlc)[end, 4])) + @testset "comparison operations between TimeArray values and Int/Float (and viceversa)" begin + @test values(cl .> 111.94)[1] == false + @test values(cl .< 111.94)[1] == false + @test values(cl .>= 111.94)[1] == true + @test values(cl .<= 111.94)[1] == true + @test values(cl .== 111.94)[1] == true + @test values(cl .!= 111.94)[1] == false + @test values(111.94 .> cl)[1] == false + @test values(111.94 .< cl)[1] == false + @test values(111.94 .>= cl)[1] == true + @test values(111.94 .<= cl)[1] == true + @test values(111.94 .== cl)[1] == true + @test values(111.94 .!= cl)[1] == false + @test values(ohlc .> 111.94)[1, :] == [false, true, false, false] + @test values(ohlc .< 111.94)[1, :] == [true, false, true, false] + @test values(ohlc .>= 111.94)[1, :] == [false, true, false, true] + @test values(ohlc .<= 111.94)[1, :] == [true, false, true, true] + @test values(ohlc .== 111.94)[1, :] == [false, false, false, true] + @test values(ohlc .!= 111.94)[1, :] == [true, true, true, false] + @test values(111.94 .> ohlc)[1, :] == [true, false, true, false] + @test values(111.94 .< ohlc)[1, :] == [false, true, false, false] + @test values(111.94 .>= ohlc)[1, :] == [true, false, true, true] + @test values(111.94 .<= ohlc)[1, :] == [false, true, false, true] + @test values(111.94 .== ohlc)[1, :] == [false, false, false, true] + @test values(111.94 .!= ohlc)[1, :] == [true, true, true, false] end - end - @testset "TimeArray and Array" begin - let ta = cl[1:4] .+ [1, 2, 3, 4] - @test colnames(ta) == [:Close] - @test timestamp(ta) == timestamp(cl)[1:4] - @test meta(ta) == meta(cl) + @testset "comparison operations between TimeArray values and Bool (and viceversa)" begin + @test values((cl .> 111.94) .== true)[1] == false + @test values((cl .> 111.94) .!= true)[1] == true + @test values(true .== (cl .> 111.94))[1] == false + @test values(true .!= (cl .> 111.94))[1] == true + @test values((ohlc .> 111.94) .== true)[1, :] == [false, true, false, false] + @test values((ohlc .> 111.94) .!= true)[1, :] == [true, false, true, true] + @test values(true .== (ohlc .> 111.94))[1, :] == [false, true, false, false] + @test values(true .!= (ohlc .> 111.94))[1, :] == [true, false, true, true] + end - @test values(ta)[1] == values(cl)[1] + 1 - @test values(ta)[2] == values(cl)[2] + 2 - @test values(ta)[3] == values(cl)[3] + 3 - @test values(ta)[4] == values(cl)[4] + 4 + @testset "comparison operations between same-column-count TimeArrays" begin + @test values(cl .> op)[1] == true + @test values(cl .< op)[1] == false + @test values(cl .<= op)[1] == false + @test values(cl .>= op)[1] == true + @test values(cl .== op)[1] == false + @test values(cl .!= op)[1] == true + @test values(ohlc .> ohlc)[1, :] == [false, false, false, false] + @test values(ohlc .< ohlc)[1, :] == [false, false, false, false] + @test values(ohlc .<= ohlc)[1, :] == [true, true, true, true] + @test values(ohlc .>= ohlc)[1, :] == [true, true, true, true] + @test values(ohlc .== ohlc)[1, :] == [true, true, true, true] + @test values(ohlc .!= ohlc)[1, :] == [false, false, false, false] end - let ta = ohlc[1:4] .+ [1, 2, 3, 4] - @test colnames(ta) == [:Open, :High, :Low, :Close] - @test timestamp(ta) == timestamp(ohlc)[1:4] - @test meta(ta) == meta(ohlc) + @testset "comparison operations between different-column-count TimeArrays" begin + @test values(ohlc .> cl) == (values(ohlc) .> values(cl)) + @test values(ohlc .< cl) == (values(ohlc) .< values(cl)) + @test values(ohlc .>= cl) == (values(ohlc) .>= values(cl)) + @test values(ohlc .<= cl) == (values(ohlc) .<= values(cl)) + @test values(ohlc .== cl) == (values(ohlc) .== values(cl)) + @test values(ohlc .!= cl) == (values(ohlc) .!= values(cl)) + @test values(cl .> ohlc) == (values(cl) .> values(ohlc)) + @test values(cl .< ohlc) == (values(cl) .< values(ohlc)) + @test values(cl .>= ohlc) == (values(cl) .>= values(ohlc)) + @test values(cl .<= ohlc) == (values(cl) .<= values(ohlc)) + @test values(cl .== ohlc) == (values(cl) .== values(ohlc)) + @test values(cl .!= ohlc) == (values(cl) .!= values(ohlc)) + # One array must have a single column + @test_throws DimensionMismatch (ohlc[:Open, :Close] .== ohlc) + end - @test values(ta)[1, 1] == values(ohlc)[1, 1] + 1 - @test values(ta)[1, 2] == values(ohlc)[1, 2] + 1 - @test values(ta)[1, 3] == values(ohlc)[1, 3] + 1 - @test values(ta)[1, 4] == values(ohlc)[1, 4] + 1 + @testset "bitwise elementwise operations between bool and TimeArrays' values" begin + @test values((cl .> 100) .& true)[1] == true + @test values((cl .> 100) .| true)[1] == true + @test values((cl .> 100) .⊻ true)[1] == false + @test values(false .& (cl .> 100))[1] == false + @test values(false .| (cl .> 100))[1] == true + @test values(false .⊻ (cl .> 100))[1] == true + @test values((ohlc .> 100) .& true)[4, :] == [true, true, false, false] + @test values((ohlc .> 100) .| true)[4, :] == [true, true, true, true] + @test values((ohlc .> 100) .⊻ true)[4, :] == [false, false, true, true] + @test values(false .& (ohlc .> 100))[4, :] == [false, false, false, false] + @test values(false .| (ohlc .> 100))[4, :] == [true, true, false, false] + @test values(false .⊻ (ohlc .> 100))[4, :] == [true, true, false, false] end - let arr = [1, 2, 3, 4] - @test_throws DimensionMismatch cl .+ arr + @testset "bitwise elementwise operations between same-column-count TimeArrays' boolean values" begin + @test values((cl .> 100) .& (cl .< 120))[1] == true + @test values((cl .> 100) .| (cl .< 120))[1] == true + @test values((cl .> 100) .⊻ (cl .< 120))[1] == false + @test values((ohlc .> 100) .& (ohlc .< 120))[4, :] == [true, true, false, false] + @test values((ohlc .> 100) .| (ohlc .< 120))[4, :] == [true, true, true, true] + @test values((ohlc .> 100) .⊻ (ohlc .< 120))[4, :] == [false, false, true, true] + @test values((ohlc .> 100) .⊻ (cl .< 120))[4, :] == [false, false, true, true] end end - @testset "custom function" begin - let f(x, y, c) = x - y + c, ta = f.(op, cl, 42) - @test colnames(ta) == [:Open_Close] - @test timestamp(ta) == timestamp(cl) - @test timestamp(ta) == timestamp(op) - @test meta(ta) == meta(op) - @test values(ta)[1] == values(op)[1] - values(cl)[1] + 42 - @test values(ta)[end] == values(op)[end] - values(cl)[end] + 42 + @testset "dot call auto-fusion" begin + @testset "single TimeArray" begin + let ta = sin.(log.(2, op)) + @test colnames(ta) == [:Open] + @test timestamp(ta) == timestamp(op) + @test meta(ta) == meta(op) + @test values(ta)[1] == sin(log(2, values(op)[1])) + @test values(ta)[end] == sin(log(2, values(op)[end])) + end + + f(x, c) = x + c + let ta = f.(cl, 42) + @test colnames(ta) == [:Close] + @test timestamp(ta) == timestamp(cl) + @test meta(ta) == meta(cl) + @test values(ta)[1] == values(cl)[1] + 42 + @test values(ta)[end] == values(cl)[end] + 42 + end + + let ta = sin.(log.(2, ohlc)) + @test colnames(ta) == [:Open, :High, :Low, :Close] + @test timestamp(ta) == timestamp(ohlc) + @test meta(ta) == meta(ohlc) + + @test values(ta)[1, 1] == sin(log(2, values(ohlc)[1, 1])) + @test values(ta)[1, 2] == sin(log(2, values(ohlc)[1, 2])) + @test values(ta)[1, 3] == sin(log(2, values(ohlc)[1, 3])) + @test values(ta)[1, 4] == sin(log(2, values(ohlc)[1, 4])) + + @test values(ta)[end, 1] == sin(log(2, values(ohlc)[end, 1])) + @test values(ta)[end, 2] == sin(log(2, values(ohlc)[end, 2])) + @test values(ta)[end, 3] == sin(log(2, values(ohlc)[end, 3])) + @test values(ta)[end, 4] == sin(log(2, values(ohlc)[end, 4])) + end end - end - @testset "broadcast 2D TimeArray" begin - let A = reshape(values(cl), 500, 1) # 2D, dim -> 500×1 - ta = TimeArray(timestamp(cl), A) .+ ohlc - @test length(colnames(ta)) == 4 - @test timestamp(ta) == timestamp(cl) + @testset "TimeArray and Array" begin + let ta = cl[1:4] .+ [1, 2, 3, 4] + @test colnames(ta) == [:Close] + @test timestamp(ta) == timestamp(cl)[1:4] + @test meta(ta) == meta(cl) + + @test values(ta)[1] == values(cl)[1] + 1 + @test values(ta)[2] == values(cl)[2] + 2 + @test values(ta)[3] == values(cl)[3] + 3 + @test values(ta)[4] == values(cl)[4] + 4 + end + + let ta = ohlc[1:4] .+ [1, 2, 3, 4] + @test colnames(ta) == [:Open, :High, :Low, :Close] + @test timestamp(ta) == timestamp(ohlc)[1:4] + @test meta(ta) == meta(ohlc) + + @test values(ta)[1, 1] == values(ohlc)[1, 1] + 1 + @test values(ta)[1, 2] == values(ohlc)[1, 2] + 1 + @test values(ta)[1, 3] == values(ohlc)[1, 3] + 1 + @test values(ta)[1, 4] == values(ohlc)[1, 4] + 1 + end + + let arr = [1, 2, 3, 4] + @test_throws DimensionMismatch cl .+ arr + end end - end -end # @testset "dot call auto-fusion" + @testset "custom function" begin + let f(x, y, c) = x - y + c, ta = f.(op, cl, 42) + @test colnames(ta) == [:Open_Close] + @test timestamp(ta) == timestamp(cl) + @test timestamp(ta) == timestamp(op) + @test meta(ta) == meta(op) + @test values(ta)[1] == values(op)[1] - values(cl)[1] + 42 + @test values(ta)[end] == values(op)[end] - values(cl)[end] + 42 + end + end + @testset "broadcast 2D TimeArray" begin + let A = reshape(values(cl), 500, 1) # 2D, dim -> 500×1 + ta = TimeArray(timestamp(cl), A) .+ ohlc + @test length(colnames(ta)) == 4 + @test timestamp(ta) == timestamp(cl) + end + end + end # @testset "dot call auto-fusion" end # @testset "broadcast" diff --git a/test/combine.jl b/test/combine.jl index 6deebbfc..1761afe3 100644 --- a/test/combine.jl +++ b/test/combine.jl @@ -5,416 +5,427 @@ using MarketData using TimeSeries - -struct _TestType -end - +struct _TestType end @testset "combine" begin - - -@testset "collapse operations" begin - @testset "collapse squishes correctly" begin - @test values(collapse(cl, week, first))[2] == 97.75 - @test timestamp(collapse(cl, week, first))[2] == Date(2000,1,10) - - @test values(collapse(cl, week, first, last))[2] == 100.44 - @test timestamp(collapse(cl, week, first, last))[2] == Date(2000,1,10) - - @test values(collapse(cl, month, first))[2] == 100.25 - @test timestamp(collapse(cl, month, first))[2] == Date(2000,2,1) - - @test values(collapse(ohlc, week, first))[2, :] == [102.0, 102.25, 94.75, 97.75] - @test timestamp(collapse(ohlc, week, first))[2] == Date(2000,1,10) - - @test values(collapse(ohlc, week, first, last))[2, :] == [100.0, 102.25, 99.38, 100.44] - @test timestamp(collapse(ohlc, week, first, last))[2] == Date(2000,1,10) - - @test values(collapse(ohlc, month, first))[2, :] == [104.0, 105.0, 100.0, 100.25] - @test timestamp(collapse(ohlc, month, first))[2] == Date(2000,2,1) - - # https://github.com/JuliaStats/TimeSeries.jl/issues/498 - if VERSION ≥ v"1.6" - let # quarter + @testset "collapse operations" begin + @testset "collapse squishes correctly" begin + @test values(collapse(cl, week, first))[2] == 97.75 + @test timestamp(collapse(cl, week, first))[2] == Date(2000, 1, 10) + + @test values(collapse(cl, week, first, last))[2] == 100.44 + @test timestamp(collapse(cl, week, first, last))[2] == Date(2000, 1, 10) + + @test values(collapse(cl, month, first))[2] == 100.25 + @test timestamp(collapse(cl, month, first))[2] == Date(2000, 2, 1) + + @test values(collapse(ohlc, week, first))[2, :] == [102.0, 102.25, 94.75, 97.75] + @test timestamp(collapse(ohlc, week, first))[2] == Date(2000, 1, 10) + + @test values(collapse(ohlc, week, first, last))[2, :] == + [100.0, 102.25, 99.38, 100.44] + @test timestamp(collapse(ohlc, week, first, last))[2] == Date(2000, 1, 10) + + @test values(collapse(ohlc, month, first))[2, :] == + [104.0, 105.0, 100.0, 100.25] + @test timestamp(collapse(ohlc, month, first))[2] == Date(2000, 2, 1) + + # https://github.com/JuliaStats/TimeSeries.jl/issues/498 + if VERSION ≥ v"1.6" + let # quarter + ts = [ + Date(2018, 1, 2), + Date(2018, 1, 3), + Date(2019, 1, 5), + Date(2019, 2, 6), + ] + ta = TimeArray(ts, 1:length(ts)) + ta′ = collapse(ta, Dates.quarter, last) + @test values(ta′) == [2, 4] + @test timestamp(ta′) == ts[[2, 4]] + @test ta′ == collapse(ta, Quarter(1), last) + end + end + let # week + ts = [ + Date(2018, 1, 2), Date(2018, 1, 3), Date(2019, 1, 5), Date(2019, 2, 6) + ] + ta = TimeArray(ts, 1:length(ts)) + ta′ = collapse(ta, week, last) + @test values(ta′) == [2, 3, 4] + @test timestamp(ta′) == ts[[2, 3, 4]] + @test ta′ == collapse(ta, Week(1), last) + end + let # month ts = [ - Date(2018, 1, 2), - Date(2018, 1, 3), - Date(2019, 1, 5), - Date(2019, 2, 6), + Date(2018, 1, 2), Date(2018, 1, 3), Date(2019, 1, 10), Date(2019, 2, 5) ] ta = TimeArray(ts, 1:length(ts)) - ta′ = collapse(ta, Dates.quarter, last) - @test values(ta′) == [2, 4] + ta′ = collapse(ta, month, last) + @test values(ta′) == [2, 3, 4] + @test timestamp(ta′) == ts[[2, 3, 4]] + @test ta′ == collapse(ta, Month(1), last) + end + let # day + ts = [ + DateTime(2018, 1, 2, 10), + DateTime(2018, 1, 2, 12), + DateTime(2018, 2, 2, 12), + DateTime(2018, 2, 2, 14), + ] + ta = TimeArray(ts, 1:length(ts)) + ta′ = collapse(ta, day, last) + @test values(ta′) == [2, 4] @test timestamp(ta′) == ts[[2, 4]] - @test ta′ == collapse(ta, Quarter(1), last) + @test ta′ == collapse(ta, Day(1), last) + end + let # hour + ts = [ + DateTime(2018, 1, 2, 10, 0), + DateTime(2018, 1, 2, 12, 0), + DateTime(2018, 2, 2, 12, 0), + DateTime(2018, 2, 2, 12, 30), + ] + ta = TimeArray(ts, 1:length(ts)) + ta′ = collapse(ta, hour, last) + @test values(ta′) == [1, 2, 4] + @test timestamp(ta′) == ts[[1, 2, 4]] + @test ta′ == collapse(ta, Hour(1), last) + end + let # minute + ts = [ + DateTime(2018, 1, 2, 12, 0), + DateTime(2018, 1, 2, 12, 0, 30), + DateTime(2018, 1, 2, 13, 0), + DateTime(2018, 1, 2, 13, 0), + ] + ta = TimeArray(ts, 1:length(ts)) + ta′ = collapse(ta, minute, last) + @test values(ta′) == [2, 4] + @test timestamp(ta′) == ts[[2, 4]] + @test ta′ == collapse(ta, Minute(1), last) + end + let # second + ts = [ + DateTime(2018, 1, 2, 12, 0, 0), + DateTime(2018, 1, 2, 12, 0, 0, 30), + DateTime(2018, 1, 2, 13, 0, 0), + DateTime(2018, 1, 2, 13, 0, 0, 30), + ] + ta = TimeArray(ts, 1:length(ts)) + ta′ = collapse(ta, second, last) + @test values(ta′) == [2, 4] + @test timestamp(ta′) == ts[[2, 4]] + @test ta′ == collapse(ta, Second(1), last) end end - let # week - ts = [ - Date(2018, 1, 2), - Date(2018, 1, 3), - Date(2019, 1, 5), - Date(2019, 2, 6), - ] - ta = TimeArray(ts, 1:length(ts)) - ta′ = collapse(ta, week, last) - @test values(ta′) == [2, 3, 4] - @test timestamp(ta′) == ts[[2, 3, 4]] - @test ta′ == collapse(ta, Week(1), last) - end - let # month - ts = [ - Date(2018, 1, 2), - Date(2018, 1, 3), - Date(2019, 1, 10), - Date(2019, 2, 5), - ] - ta = TimeArray(ts, 1:length(ts)) - ta′ = collapse(ta, month, last) - @test values(ta′) == [2, 3, 4] - @test timestamp(ta′) == ts[[2, 3, 4]] - @test ta′ == collapse(ta, Month(1), last) - end - let # day - ts = [ - DateTime(2018, 1, 2, 10), - DateTime(2018, 1, 2, 12), - DateTime(2018, 2, 2, 12), - DateTime(2018, 2, 2, 14), - ] - ta = TimeArray(ts, 1:length(ts)) - ta′ = collapse(ta, day, last) - @test values(ta′) == [2, 4] - @test timestamp(ta′) == ts[[2, 4]] - @test ta′ == collapse(ta, Day(1), last) - end - let # hour - ts = [ - DateTime(2018, 1, 2, 10, 0), - DateTime(2018, 1, 2, 12, 0), - DateTime(2018, 2, 2, 12, 0), - DateTime(2018, 2, 2, 12, 30), - ] - ta = TimeArray(ts, 1:length(ts)) - ta′ = collapse(ta, hour, last) - @test values(ta′) == [1, 2, 4] - @test timestamp(ta′) == ts[[1, 2, 4]] - @test ta′ == collapse(ta, Hour(1), last) - end - let # minute - ts = [ - DateTime(2018, 1, 2, 12, 0), - DateTime(2018, 1, 2, 12, 0, 30), - DateTime(2018, 1, 2, 13, 0), - DateTime(2018, 1, 2, 13, 0), - ] - ta = TimeArray(ts, 1:length(ts)) - ta′ = collapse(ta, minute, last) - @test values(ta′) == [2, 4] - @test timestamp(ta′) == ts[[2, 4]] - @test ta′ == collapse(ta, Minute(1), last) - end - let # second - ts = [ - DateTime(2018, 1, 2, 12, 0, 0), - DateTime(2018, 1, 2, 12, 0, 0, 30), - DateTime(2018, 1, 2, 13, 0, 0), - DateTime(2018, 1, 2, 13, 0, 0, 30), - ] - ta = TimeArray(ts, 1:length(ts)) - ta′ = collapse(ta, second, last) - @test values(ta′) == [2, 4] - @test timestamp(ta′) == ts[[2, 4]] - @test ta′ == collapse(ta, Second(1), last) - end - end - # https://github.com/JuliaStats/TimeSeries.jl/pull/397 - @testset "Array of String" begin - A = string.(Char.(rand(97:97+25, size(cl)))) - ts = timestamp(cl) - ta = TimeArray(ts, A) + # https://github.com/JuliaStats/TimeSeries.jl/pull/397 + @testset "Array of String" begin + A = string.(Char.(rand(97:(97 + 25), size(cl)))) + ts = timestamp(cl) + ta = TimeArray(ts, A) - let - ta = collapse(ta, week, first) + let + ta = collapse(ta, week, first) - @test values(ta)[2] == A[6] - @test timestamp(ta)[2] == ts[6] + @test values(ta)[2] == A[6] + @test timestamp(ta)[2] == ts[6] + end end - end - - @testset "type promotion" begin - ts = [ - DateTime(2018, 1, 2), - DateTime(2018, 1, 3), - DateTime(2019, 1, 5), - DateTime(2019, 2, 6), - ] - ta = TimeArray(ts, 1:length(ts)) - ta′ = collapse(ta, month, x -> Date(last(x)), last) - @test ta isa TimeArray{Int,1,DateTime,A} where A - @test ta′ isa TimeArray{Int,1,Date,A} where A + @testset "type promotion" begin + ts = [ + DateTime(2018, 1, 2), + DateTime(2018, 1, 3), + DateTime(2019, 1, 5), + DateTime(2019, 2, 6), + ] + ta = TimeArray(ts, 1:length(ts)) + ta′ = collapse(ta, month, x -> Date(last(x)), last) - ta = TimeArray(ts, zeros(4, 10)) - ta′ = collapse(ta, month, x -> Date(last(x)), x -> Int(last(x))) + @test ta isa TimeArray{Int,1,DateTime,A} where {A} + @test ta′ isa TimeArray{Int,1,Date,A} where {A} - @test ta isa TimeArray{Float64,2,DateTime,A} where A - @test ta′ isa TimeArray{Int,2,Date,A} where A - end + ta = TimeArray(ts, zeros(4, 10)) + ta′ = collapse(ta, month, x -> Date(last(x)), x -> Int(last(x))) - @testset "Period supports" begin - ts = [ - Date(2018, 1, 2), - Date(2018, 1, 3), - Date(2019, 1, 5), - Date(2019, 2, 6), - ] - ta = TimeArray(ts, 1:length(ts)) - ta′ = collapse(ta, Month(2), last) - - @test timestamp(ta′) == [Date(2018, 1, 3), Date(2019, 2, 6)] - @test values(ta′) == [2, 4] - end -end - - -@testset "merge works correctly" begin - cl1 = cl[1:3] - op1 = cl[2:4] - aapl = tail(AAPL) - ba = tail(BA) - - @testset "takes colnames kwarg correctly" begin - @test colnames(merge(cl, ohlc[:High, :Low], colnames = [:a, :b, :c])) == [:a, :b, :c] - @test colnames(merge(cl, op, colnames = [:a, :b])) == [:a, :b] - @test_throws ArgumentError merge(cl, op, colnames = [:a]) - @test_throws ArgumentError merge(cl, op, colnames = [:a, :b, :c]) - - for mode ∈ [:inner, :left, :right, :outer] - @test colnames(merge(cl, ohlc[:High, :Low], method = mode, colnames = [:a, :b, :c])) == [:a, :b, :c] - @test colnames(merge(cl, op, method = mode, colnames = [:a, :b])) == [:a, :b] - @test_throws ArgumentError merge(cl, op, method = mode, colnames = [:a]) - @test_throws ArgumentError merge(cl, op, method = mode, colnames = [:a, :b, :c]) + @test ta isa TimeArray{Float64,2,DateTime,A} where {A} + @test ta′ isa TimeArray{Int,2,Date,A} where {A} end - # issue #475 - @test colnames(merge(cl, cl, cl, colnames = [:a, :b, :c])) == [:a, :b, :c] - end + @testset "Period supports" begin + ts = [Date(2018, 1, 2), Date(2018, 1, 3), Date(2019, 1, 5), Date(2019, 2, 6)] + ta = TimeArray(ts, 1:length(ts)) + ta′ = collapse(ta, Month(2), last) - @testset "returns correct alignment with Dates and values" begin - @test values(merge(cl, op)) == values(merge(cl, op, method = :inner)) - @test values(merge(cl,op))[2,1] == values(cl)[2,1] - @test values(merge(cl,op))[2,2] == values(op)[2,1] + @test timestamp(ta′) == [Date(2018, 1, 3), Date(2019, 2, 6)] + @test values(ta′) == [2, 4] + end end - @testset "aligns with disparate sized objects" begin - @test values(merge(cl, op[2:5]))[1,1] == values(cl)[2,1] - @test values(merge(cl, op[2:5]))[1,2] == values(op)[2,1] - @test timestamp(merge(cl, op[2:5]))[1] == Date(2000,1,4) - @test length(merge(cl, op[2:5])) == 4 - - @test length(merge(cl1, op1, method = :inner)) == 2 - @test values(merge(cl1, op1, method = :inner))[2,1] == values(cl1)[3,1] - @test values(merge(cl1, op1, method = :inner))[2,2] == values(op1)[2,1] - - @test length(merge(cl1, op1, method = :left)) == 3 - @test values(merge(cl1, op1, method = :left))[2,1] == values(cl1)[2,1] - @test values(merge(cl1, op1, method = :left))[2,2] == values(op1)[1,1] - @test isnan(values(merge(cl1, op1, method = :left))[1,2]) - - @test length(merge(cl1, op1, method = :right)) == 3 - @test values(merge(cl1, op1, method = :right))[2,1] == values(cl1)[3,1] - @test values(merge(cl1, op1, method = :right))[2,2] == values(op1)[2,1] - @test isnan(values(merge(cl1, op1, method = :right))[3,1]) - - @test length(merge(cl1, op1, method = :outer)) == 4 - @test values(merge(cl1, op1, method = :outer))[2,1] == values(cl1)[2,1] - @test values(merge(cl1, op1, method = :outer))[2,2] == values(op1)[1,1] - @test isnan(values(merge(cl1, op1, method = :outer))[1,2]) - @test isnan(values(merge(cl1, op1, method = :outer))[4,1]) - end + @testset "merge works correctly" begin + cl1 = cl[1:3] + op1 = cl[2:4] + aapl = tail(AAPL) + ba = tail(BA) + + @testset "takes colnames kwarg correctly" begin + @test colnames(merge(cl, ohlc[:High, :Low]; colnames=[:a, :b, :c])) == + [:a, :b, :c] + @test colnames(merge(cl, op; colnames=[:a, :b])) == [:a, :b] + @test_throws ArgumentError merge(cl, op, colnames=[:a]) + @test_throws ArgumentError merge(cl, op, colnames=[:a, :b, :c]) + + for mode in [:inner, :left, :right, :outer] + @test colnames( + merge(cl, ohlc[:High, :Low]; method=mode, colnames=[:a, :b, :c]) + ) == [:a, :b, :c] + @test colnames(merge(cl, op; method=mode, colnames=[:a, :b])) == [:a, :b] + @test_throws ArgumentError merge(cl, op, method=mode, colnames=[:a]) + @test_throws ArgumentError merge(cl, op, method=mode, colnames=[:a, :b, :c]) + end - @testset "column names match the correct values" begin - @test colnames(merge(cl, op[2:5])) == [:Close, :Open] - @test colnames(merge(op[2:5], cl)) == [:Open, :Close] + # issue #475 + @test colnames(merge(cl, cl, cl; colnames=[:a, :b, :c])) == [:a, :b, :c] + end - @test colnames(merge(cl, op[2:5], method = :inner)) == [:Close, :Open] - @test colnames(merge(op[2:5], cl, method = :inner)) == [:Open, :Close] + @testset "returns correct alignment with Dates and values" begin + @test values(merge(cl, op)) == values(merge(cl, op; method=:inner)) + @test values(merge(cl, op))[2, 1] == values(cl)[2, 1] + @test values(merge(cl, op))[2, 2] == values(op)[2, 1] + end - @test colnames(merge(cl, op[2:5], method = :left)) == [:Close, :Open] - @test colnames(merge(op[2:5], cl, method = :left)) == [:Open, :Close] + @testset "aligns with disparate sized objects" begin + @test values(merge(cl, op[2:5]))[1, 1] == values(cl)[2, 1] + @test values(merge(cl, op[2:5]))[1, 2] == values(op)[2, 1] + @test timestamp(merge(cl, op[2:5]))[1] == Date(2000, 1, 4) + @test length(merge(cl, op[2:5])) == 4 + + @test length(merge(cl1, op1; method=:inner)) == 2 + @test values(merge(cl1, op1; method=:inner))[2, 1] == values(cl1)[3, 1] + @test values(merge(cl1, op1; method=:inner))[2, 2] == values(op1)[2, 1] + + @test length(merge(cl1, op1; method=:left)) == 3 + @test values(merge(cl1, op1; method=:left))[2, 1] == values(cl1)[2, 1] + @test values(merge(cl1, op1; method=:left))[2, 2] == values(op1)[1, 1] + @test isnan(values(merge(cl1, op1; method=:left))[1, 2]) + + @test length(merge(cl1, op1; method=:right)) == 3 + @test values(merge(cl1, op1; method=:right))[2, 1] == values(cl1)[3, 1] + @test values(merge(cl1, op1; method=:right))[2, 2] == values(op1)[2, 1] + @test isnan(values(merge(cl1, op1; method=:right))[3, 1]) + + @test length(merge(cl1, op1; method=:outer)) == 4 + @test values(merge(cl1, op1; method=:outer))[2, 1] == values(cl1)[2, 1] + @test values(merge(cl1, op1; method=:outer))[2, 2] == values(op1)[1, 1] + @test isnan(values(merge(cl1, op1; method=:outer))[1, 2]) + @test isnan(values(merge(cl1, op1; method=:outer))[4, 1]) + end - @test colnames(merge(cl, op[2:5], method = :right)) == [:Close, :Open] - @test colnames(merge(op[2:5], cl, method = :right)) == [:Open, :Close] + @testset "column names match the correct values" begin + @test colnames(merge(cl, op[2:5])) == [:Close, :Open] + @test colnames(merge(op[2:5], cl)) == [:Open, :Close] - @test colnames(merge(cl, op[2:5], method = :outer)) == [:Close, :Open] - @test colnames(merge(op[2:5], cl, method = :outer)) == [:Open, :Close] - end + @test colnames(merge(cl, op[2:5]; method=:inner)) == [:Close, :Open] + @test colnames(merge(op[2:5], cl; method=:inner)) == [:Open, :Close] - @testset "unknown method" begin - @test_throws ArgumentError merge(cl, op, method = :unknown) - end + @test colnames(merge(cl, op[2:5]; method=:left)) == [:Close, :Open] + @test colnames(merge(op[2:5], cl; method=:left)) == [:Open, :Close] - @testset "custom missing values" begin - ts1 = TimeArray([Date(2018, 1, 1), Date(2018, 1, 2)], [1, 2]) - ts2 = TimeArray([Date(2018, 1, 2), Date(2018, 1, 3)], [3, 4]) + @test colnames(merge(cl, op[2:5]; method=:right)) == [:Close, :Open] + @test colnames(merge(op[2:5], cl; method=:right)) == [:Open, :Close] - m1 = merge(ts1, ts2, method = :left, padvalue = 0) - @test timestamp(m1) == [Date(2018, 1, 1), Date(2018, 1, 2)] - @test values(m1) == [1 0; 2 3] + @test colnames(merge(cl, op[2:5]; method=:outer)) == [:Close, :Open] + @test colnames(merge(op[2:5], cl; method=:outer)) == [:Open, :Close] + end - m2 = merge(ts1, ts2, method = :right, padvalue = 0) - @test timestamp(m2) == [Date(2018, 1, 2), Date(2018, 1, 3)] - @test values(m2) == [2 3; 0 4] + @testset "unknown method" begin + @test_throws ArgumentError merge(cl, op, method=:unknown) + end - m3 = merge(ts1, ts2, method = :outer, padvalue = 0) - @test timestamp(m3) == [Date(2018, 1, 1), Date(2018, 1, 2), Date(2018, 1, 3)] - @test values(m3) == [1 0; 2 3; 0 4] - end + @testset "custom missing values" begin + ts1 = TimeArray([Date(2018, 1, 1), Date(2018, 1, 2)], [1, 2]) + ts2 = TimeArray([Date(2018, 1, 2), Date(2018, 1, 3)], [3, 4]) - @testset "vararg input" begin - ta1 = merge(op, ohlc, cl) - ta2 = merge(merge(op, ohlc), cl) + m1 = merge(ts1, ts2; method=:left, padvalue=0) + @test timestamp(m1) == [Date(2018, 1, 1), Date(2018, 1, 2)] + @test values(m1) == [1 0; 2 3] - @test timestamp(ta1) == timestamp(ta2) - @test values(ta1) == values(ta2) + m2 = merge(ts1, ts2; method=:right, padvalue=0) + @test timestamp(m2) == [Date(2018, 1, 2), Date(2018, 1, 3)] + @test values(m2) == [2 3; 0 4] - # test keyword arguments passing - ta1 = merge(op, ohlc, cl, method = :outer) - ta2 = merge(merge(op, ohlc, method = :outer), cl, method = :outer) + m3 = merge(ts1, ts2; method=:outer, padvalue=0) + @test timestamp(m3) == [Date(2018, 1, 1), Date(2018, 1, 2), Date(2018, 1, 3)] + @test values(m3) == [1 0; 2 3; 0 4] + end - @test timestamp(ta1) == timestamp(ta2) - @test values(ta1) == values(ta2) - end -end + @testset "vararg input" begin + ta1 = merge(op, ohlc, cl) + ta2 = merge(merge(op, ohlc), cl) + @test timestamp(ta1) == timestamp(ta2) + @test values(ta1) == values(ta2) -@testset "hcat" begin - let ta = [cl op cl] - @test meta(ta) == meta(cl) - @test length(colnames(ta)) == 3 - @test colnames(ta)[1] != colnames(ta)[3] - @test colnames(ta)[1] == :Close - @test colnames(ta)[2] == :Open - @test values(ta) == [values(cl) values(op) values(cl)] - end + # test keyword arguments passing + ta1 = merge(op, ohlc, cl; method=:outer) + ta2 = merge(merge(op, ohlc; method=:outer), cl; method=:outer) - let ta = [cl ohlc] - @test meta(ta) == meta(ta) - @test length(colnames(ta)) == 5 - @test colnames(ta)[1] == :Close - @test colnames(ta)[1] != colnames(ta)[end] - @test values(ta) == [values(cl) values(ohlc)] + @test timestamp(ta1) == timestamp(ta2) + @test values(ta1) == values(ta2) + end end - @test_throws DimensionMismatch [cl[1:3] cl] -end - - -@testset "vcat works correctly" begin - @testset "concatenates time series correctly in 1D" begin - a = TimeArray([Date(2015, 10, 01), Date(2015, 11, 01)], [15, 16], [:Number]) - b = TimeArray([Date(2015, 12, 01)], [17], [:Number]) - c = vcat(a, b) - - @test length(c) == (length(a) + length(b)) - @test colnames(c) == colnames(a) - @test colnames(c) == colnames(b) - @test values(c) == [15, 16, 17] - end + @testset "hcat" begin + let ta = [cl op cl] + @test meta(ta) == meta(cl) + @test length(colnames(ta)) == 3 + @test colnames(ta)[1] != colnames(ta)[3] + @test colnames(ta)[1] == :Close + @test colnames(ta)[2] == :Open + @test values(ta) == [values(cl) values(op) values(cl)] + end - @testset "concatenates time series correctly in 2D" begin - a = TimeArray([Date(2015, 09, 01), Date(2015, 10, 01), Date(2015, 11, 01)], [[15 16]; [17 18]; [19 20]], [:Number1, :Number2]) - b = TimeArray([Date(2015, 12, 01)], [18 18], [:Number1, :Number2]) - c = vcat(a, b) + let ta = [cl ohlc] + @test meta(ta) == meta(ta) + @test length(colnames(ta)) == 5 + @test colnames(ta)[1] == :Close + @test colnames(ta)[1] != colnames(ta)[end] + @test values(ta) == [values(cl) values(ohlc)] + end - @test length(c) == length(a) + length(b) - @test colnames(c) == colnames(a) - @test colnames(c) == colnames(b) - @test values(c) == [[15 16]; [17 18]; [19 20]; [18 18]] + @test_throws DimensionMismatch [cl[1:3] cl] end - @testset "rejects when column names do not match" begin - a = TimeArray([Date(2015, 10, 01), Date(2015, 11, 01)], [15, 16], [:foo]) - b = TimeArray([Date(2015, 12, 01)], [17], [:bar]) + @testset "vcat works correctly" begin + @testset "concatenates time series correctly in 1D" begin + a = TimeArray([Date(2015, 10, 01), Date(2015, 11, 01)], [15, 16], [:Number]) + b = TimeArray([Date(2015, 12, 01)], [17], [:Number]) + c = vcat(a, b) - @test_throws ArgumentError vcat(a, b) - end + @test length(c) == (length(a) + length(b)) + @test colnames(c) == colnames(a) + @test colnames(c) == colnames(b) + @test values(c) == [15, 16, 17] + end - @testset "rejects when metas do not match" begin - a = TimeArray([Date(2015, 10, 01), Date(2015, 11, 01)], [15, 16], [:A], :FirstMeta) - b = TimeArray([Date(2015, 12, 01)], [17], [:A], :SecondMeta) + @testset "concatenates time series correctly in 2D" begin + a = TimeArray( + [Date(2015, 09, 01), Date(2015, 10, 01), Date(2015, 11, 01)], + [[15 16]; [17 18]; [19 20]], + [:Number1, :Number2], + ) + b = TimeArray([Date(2015, 12, 01)], [18 18], [:Number1, :Number2]) + c = vcat(a, b) + + @test length(c) == length(a) + length(b) + @test colnames(c) == colnames(a) + @test colnames(c) == colnames(b) + @test values(c) == [[15 16]; [17 18]; [19 20]; [18 18]] + end - @test_throws ArgumentError vcat(a, b) - end + @testset "rejects when column names do not match" begin + a = TimeArray([Date(2015, 10, 01), Date(2015, 11, 01)], [15, 16], [:foo]) + b = TimeArray([Date(2015, 12, 01)], [17], [:bar]) - @testset "duplicated timestamps order" begin - a = TimeArray([Date(2015, 10, 1), Date(2015, 10, 2), Date(2015, 11, 1)], [15, 16, 17]) - b = TimeArray([Date(2015, 10, 2), Date(2015, 11, 1)], [18, 19]) + @test_throws ArgumentError vcat(a, b) + end - ts = [Date(2015, 10, 1), - Date(2015, 10, 2), Date(2015, 10, 2), - Date(2015, 11, 1), Date(2015, 11, 1)] - ta = vcat(a, b) - @test timestamp(ta) == ts - @test values(ta) == [15, 16, 18, 17, 19] - end + @testset "rejects when metas do not match" begin + a = TimeArray( + [Date(2015, 10, 01), Date(2015, 11, 01)], [15, 16], [:A], :FirstMeta + ) + b = TimeArray([Date(2015, 12, 01)], [17], [:A], :SecondMeta) - @testset "still works when dates are mixed" begin - a = TimeArray([Date(2015, 10, 01), Date(2015, 12, 01)], [15, 17]) - b = TimeArray([Date(2015, 11, 01)], [16]) - c = vcat(a, b) + @test_throws ArgumentError vcat(a, b) + end - @test length(c) == length(a) + length(b) - @test colnames(c) == colnames(a) - @test colnames(c) == colnames(b) - @test values(c) == [15, 16, 17] - @test issorted(timestamp(c)) - end -end + @testset "duplicated timestamps order" begin + a = TimeArray( + [Date(2015, 10, 1), Date(2015, 10, 2), Date(2015, 11, 1)], [15, 16, 17] + ) + b = TimeArray([Date(2015, 10, 2), Date(2015, 11, 1)], [18, 19]) + ts = [ + Date(2015, 10, 1), + Date(2015, 10, 2), + Date(2015, 10, 2), + Date(2015, 11, 1), + Date(2015, 11, 1), + ] + ta = vcat(a, b) + @test timestamp(ta) == ts + @test values(ta) == [15, 16, 18, 17, 19] + end -@testset "map works correctly" begin - @testset "works on both time stamps and 1D values" begin - a = TimeArray([Date(2015, 10, 01), Date(2015, 11, 01)], [15, 16], [:A], :Something) - b = map((timestamp, values) -> (timestamp + Dates.Year(1), values .- 1), a) + @testset "still works when dates are mixed" begin + a = TimeArray([Date(2015, 10, 01), Date(2015, 12, 01)], [15, 17]) + b = TimeArray([Date(2015, 11, 01)], [16]) + c = vcat(a, b) - @test length(b) == length(a) - @test colnames(b) == colnames(a) - @test Dates.year(timestamp(b)[1]) == Dates.year(timestamp(a)[1]) + 1 - @test values(b)[1] == values(a)[1] - 1 - @test meta(b) == meta(a) + @test length(c) == length(a) + length(b) + @test colnames(c) == colnames(a) + @test colnames(c) == colnames(b) + @test values(c) == [15, 16, 17] + @test issorted(timestamp(c)) + end end - @testset "works on both time stamps and 2D values" begin - a = TimeArray([Date(2015, 09, 01), Date(2015, 10, 01), Date(2015, 11, 01)], [[15 16]; [17 18]; [19 20]], [:A, :B]) - b = map((timestamp, values) -> (timestamp + Dates.Year(1), [values[1] + 2, values[2] - 1]), a) - - @test length(b) == length(a) - @test colnames(b) == colnames(a) - @test Dates.year(timestamp(b)[1]) == Dates.year(timestamp(a)[1]) + 1 - @test values(b)[1, 1] == values(a)[1, 1] + 2 - @test values(b)[1, 2] == values(a)[1, 2] - 1 - end + @testset "map works correctly" begin + @testset "works on both time stamps and 1D values" begin + a = TimeArray( + [Date(2015, 10, 01), Date(2015, 11, 01)], [15, 16], [:A], :Something + ) + b = map((timestamp, values) -> (timestamp + Dates.Year(1), values .- 1), a) + + @test length(b) == length(a) + @test colnames(b) == colnames(a) + @test Dates.year(timestamp(b)[1]) == Dates.year(timestamp(a)[1]) + 1 + @test values(b)[1] == values(a)[1] - 1 + @test meta(b) == meta(a) + end - @testset "works with order of elements that varies after modifications" begin - a = TimeArray([Date(2015, 10, 01), Date(2015, 12, 01)], [15, 16], [:A]) - b = map((timestamp, values) -> (timestamp + Dates.Year((timestamp >= Date(2015, 11, 01)) ? -1 : 1), values), a) + @testset "works on both time stamps and 2D values" begin + a = TimeArray( + [Date(2015, 09, 01), Date(2015, 10, 01), Date(2015, 11, 01)], + [[15 16]; [17 18]; [19 20]], + [:A, :B], + ) + b = map( + (timestamp, values) -> + (timestamp + Dates.Year(1), [values[1] + 2, values[2] - 1]), + a, + ) + + @test length(b) == length(a) + @test colnames(b) == colnames(a) + @test Dates.year(timestamp(b)[1]) == Dates.year(timestamp(a)[1]) + 1 + @test values(b)[1, 1] == values(a)[1, 1] + 2 + @test values(b)[1, 2] == values(a)[1, 2] - 1 + end - @test length(b) == length(a) - @test issorted(timestamp(b)) - end + @testset "works with order of elements that varies after modifications" begin + a = TimeArray([Date(2015, 10, 01), Date(2015, 12, 01)], [15, 16], [:A]) + b = map( + (timestamp, values) -> ( + timestamp + Dates.Year((timestamp >= Date(2015, 11, 01)) ? -1 : 1), + values, + ), + a, + ) + + @test length(b) == length(a) + @test issorted(timestamp(b)) + end - @testset "map callable object" begin - (::_TestType)(ts, x) = (ts, x + 42) + @testset "map callable object" begin + (::_TestType)(ts, x) = (ts, x + 42) - ta = map(_TestType(), cl) - @test timestamp(ta) == timestamp(cl) - @test values(ta) == values(cl) .+ 42 - @test meta(ta) == meta(cl) + ta = map(_TestType(), cl) + @test timestamp(ta) == timestamp(cl) + @test values(ta) == values(cl) .+ 42 + @test meta(ta) == meta(cl) + end end -end - - end # @testset "combine" diff --git a/test/meta.jl b/test/meta.jl index ae178670..add84c90 100644 --- a/test/meta.jl +++ b/test/meta.jl @@ -6,144 +6,132 @@ using MarketData using TimeSeries - @testset "meta" begin - - -@testset "construction with and without meta field" begin - nometa = TimeArray(timestamp(cl), values(cl), colnames(cl)) - - @testset "default meta field to nothing" begin - @test meta(nometa) == nothing - end - - @testset "allow objects in meta field" begin - @test meta(mdata) == "Apple" - end -end - - -@testset "get index operations preserve meta" begin - @testset "index by integer row" begin - @test meta(mdata[1]) == "Apple" - end - - @testset "index by integer range" begin - @test meta(mdata[1:2]) == "Apple" - end - - @testset "index by column name" begin - @test meta(mdata[:Close]) == "Apple" - end - - @testset "index by date range" begin - @test meta(mdata[[Date(2000,1,3), Date(2000,1,14)]]) == "Apple" - end -end - - -@testset "split operations preserve meta" begin - @testset "when" begin - @test meta(when(mdata, dayofweek, 1)) == "Apple" + @testset "construction with and without meta field" begin + nometa = TimeArray(timestamp(cl), values(cl), colnames(cl)) + + @testset "default meta field to nothing" begin + @test meta(nometa) == nothing + end + + @testset "allow objects in meta field" begin + @test meta(mdata) == "Apple" + end end - - @testset "from" begin - @test meta(from(mdata, Date(2000,1,1))) == "Apple" + + @testset "get index operations preserve meta" begin + @testset "index by integer row" begin + @test meta(mdata[1]) == "Apple" + end + + @testset "index by integer range" begin + @test meta(mdata[1:2]) == "Apple" + end + + @testset "index by column name" begin + @test meta(mdata[:Close]) == "Apple" + end + + @testset "index by date range" begin + @test meta(mdata[[Date(2000, 1, 3), Date(2000, 1, 14)]]) == "Apple" + end end - @testset "to" begin - @test meta(to(mdata, Date(2000,1,1))) == "Apple" - end -end + @testset "split operations preserve meta" begin + @testset "when" begin + @test meta(when(mdata, dayofweek, 1)) == "Apple" + end + @testset "from" begin + @test meta(from(mdata, Date(2000, 1, 1))) == "Apple" + end -@testset "apply operations preserve meta" begin - @testset "lag" begin - @test meta(lag(mdata)) == "Apple" + @testset "to" begin + @test meta(to(mdata, Date(2000, 1, 1))) == "Apple" + end end - @testset "lead" begin - @test meta(lead(mdata)) == "Apple" - end + @testset "apply operations preserve meta" begin + @testset "lag" begin + @test meta(lag(mdata)) == "Apple" + end - @testset "percentchange" begin - @test meta(percentchange(mdata)) == "Apple" - end + @testset "lead" begin + @test meta(lead(mdata)) == "Apple" + end - @testset "moving" begin - @test meta(moving(mean,mdata,10)) == "Apple" - end + @testset "percentchange" begin + @test meta(percentchange(mdata)) == "Apple" + end - @testset "upto" begin - @test meta(upto(sum, mdata)) == "Apple" - end -end - - -@testset "combine operations preserve meta" begin - @testset "merge when both have identical meta" begin - @test meta(merge(cl, op)) == "AAPL" - @test meta(merge(cl, op, method = :left)) == "AAPL" - @test meta(merge(cl, op, method = :right)) == "AAPL" - @test meta(merge(cl, op, method = :outer)) == "AAPL" - end + @testset "moving" begin + @test meta(moving(mean, mdata, 10)) == "Apple" + end - @testset "merged meta field value concatenates when both objects' meta field values are strings" begin - @test meta(merge(mdata, cl)) == "Apple_AAPL" - @test meta(merge(mdata, cl, method = :left)) == "Apple_AAPL" - @test meta(merge(mdata, cl, method = :right)) == "Apple_AAPL" - @test meta(merge(mdata, cl, method = :outer)) == "Apple_AAPL" + @testset "upto" begin + @test meta(upto(sum, mdata)) == "Apple" + end end - @testset "merge when supplied with meta" begin - @test meta(merge(mdata, mdata, meta=47)) == 47 - @test meta(merge(mdata, mdata, method = :left, meta=47)) == 47 - @test meta(merge(mdata, mdata, method = :right, meta=47)) == 47 - @test meta(merge(mdata, mdata, method = :outer, meta=47)) == 47 - @test meta(merge(mdata, cl, meta=47)) == 47 - @test meta(merge(mdata, cl, method = :left, meta=47)) == 47 - @test meta(merge(mdata, cl, method = :right, meta=47)) == 47 - @test meta(merge(mdata, cl, method = :outer, meta=47)) == 47 - end + @testset "combine operations preserve meta" begin + @testset "merge when both have identical meta" begin + @test meta(merge(cl, op)) == "AAPL" + @test meta(merge(cl, op; method=:left)) == "AAPL" + @test meta(merge(cl, op; method=:right)) == "AAPL" + @test meta(merge(cl, op; method=:outer)) == "AAPL" + end - @testset "merged meta field value for disparate types in meta field defaults to Void" begin - @test meta(merge(mdata, merge(cl, op, meta=47))) == nothing - @test meta(merge(mdata, merge(cl, op, meta=47), method = :left)) == nothing - @test meta(merge(mdata, merge(cl, op, meta=47), method = :right)) == nothing - @test meta(merge(mdata, merge(cl, op, meta=47), method = :outer)) == nothing - end + @testset "merged meta field value concatenates when both objects' meta field values are strings" begin + @test meta(merge(mdata, cl)) == "Apple_AAPL" + @test meta(merge(mdata, cl; method=:left)) == "Apple_AAPL" + @test meta(merge(mdata, cl; method=:right)) == "Apple_AAPL" + @test meta(merge(mdata, cl; method=:outer)) == "Apple_AAPL" + end - @testset "collapse" begin - @test meta(collapse(mdata, week, first)) == "Apple" - end -end + @testset "merge when supplied with meta" begin + @test meta(merge(mdata, mdata; meta=47)) == 47 + @test meta(merge(mdata, mdata; method=:left, meta=47)) == 47 + @test meta(merge(mdata, mdata; method=:right, meta=47)) == 47 + @test meta(merge(mdata, mdata; method=:outer, meta=47)) == 47 + @test meta(merge(mdata, cl; meta=47)) == 47 + @test meta(merge(mdata, cl; method=:left, meta=47)) == 47 + @test meta(merge(mdata, cl; method=:right, meta=47)) == 47 + @test meta(merge(mdata, cl; method=:outer, meta=47)) == 47 + end + @testset "merged meta field value for disparate types in meta field defaults to Void" begin + @test meta(merge(mdata, merge(cl, op; meta=47))) == nothing + @test meta(merge(mdata, merge(cl, op; meta=47); method=:left)) == nothing + @test meta(merge(mdata, merge(cl, op; meta=47); method=:right)) == nothing + @test meta(merge(mdata, merge(cl, op; meta=47); method=:outer)) == nothing + end -@testset "basecall operations preserve meta" begin - @testset "basecall" begin - @test meta(basecall(mdata, cumsum)) == "Apple" + @testset "collapse" begin + @test meta(collapse(mdata, week, first)) == "Apple" + end end -end - - -@testset "mathematical and comparison operations preserve meta" begin - @testset ".+" begin - @test meta(mdata .+ mdata) == "Apple" - @test meta(mdata .+ cl) == nothing + + @testset "basecall operations preserve meta" begin + @testset "basecall" begin + @test meta(basecall(mdata, cumsum)) == "Apple" + end end - @testset ".<" begin - @test meta(mdata .< mdata) == "Apple" - @test meta(mdata .< cl) == nothing + @testset "mathematical and comparison operations preserve meta" begin + @testset ".+" begin + @test meta(mdata .+ mdata) == "Apple" + @test meta(mdata .+ cl) == nothing + end + + @testset ".<" begin + @test meta(mdata .< mdata) == "Apple" + @test meta(mdata .< cl) == nothing + end end -end - - -@testset "readwrite accepts meta argument" begin - @testset "Apple is present" begin - @test meta(mdata) == "Apple" + + @testset "readwrite accepts meta argument" begin + @testset "Apple is present" begin + @test meta(mdata) == "Apple" + end end -end - - end # @testset "meta" diff --git a/test/modify.jl b/test/modify.jl index 4b5b8e8b..0a85d99c 100644 --- a/test/modify.jl +++ b/test/modify.jl @@ -5,124 +5,117 @@ using MarketData using TimeSeries - @testset "modify" begin + @testset "rename" begin + re_ohlc = rename(ohlc, [:a, :b, :c, :d]) + re_cl = rename(cl, [:vector]) + re_cls = rename(cl, :symbol) + + @testset "change colnames with multi-member vector" begin + @test colnames(re_ohlc) == [:a, :b, :c, :d] + @test_throws ArgumentError rename(ohlc, [:a]) + end + @testset "change colnames with single-member vector" begin + @test colnames(re_cl) == [:vector] + @test_throws ArgumentError rename(cl, [:a, :b]) + end + + @testset "change colnames with pair" begin + re_ohlc_2 = rename(ohlc, :Open => :a) + @test colnames(re_ohlc_2) == [:a, :High, :Low, :Close] + @test_throws ArgumentError rename(ohlc, :Unknown => :A) + end -@testset "rename" begin - re_ohlc = rename(ohlc, [:a, :b, :c, :d]) - re_cl = rename(cl, [:vector]) - re_cls = rename(cl, :symbol) - - @testset "change colnames with multi-member vector" begin - @test colnames(re_ohlc) == [:a, :b, :c, :d] - @test_throws ArgumentError rename(ohlc, [:a]) - end - - @testset "change colnames with single-member vector" begin - @test colnames(re_cl) == [:vector] - @test_throws ArgumentError rename(cl, [:a, :b]) - end - - @testset "change colnames with pair" begin - re_ohlc_2 = rename(ohlc, :Open => :a) - @test colnames(re_ohlc_2) == [:a, :High, :Low, :Close] - @test_throws ArgumentError rename(ohlc, :Unknown => :A) - end - - @testset "change colnames with several pairs" begin - re_ohlc_2 = rename(ohlc, :Open => :a, :Close => :d) - @test colnames(re_ohlc_2) == [:a, :High, :Low, :d] - @test_throws MethodError rename(ohlc) - end - - @testset "change colnames with dict" begin - re_ohlc_2 = rename(ohlc, Dict(:Open => :a, :Close => :d)...) - @test colnames(re_ohlc_2) == [:a, :High, :Low, :d] - end - - @testset "change colnames with function" begin - @testset "lambda function" begin - f = colname -> Symbol(uppercase(string(colname))) - re_ohlc_2 = rename(f, ohlc) - @test colnames(re_ohlc_2) == [:OPEN, :HIGH, :LOW, :CLOSE] + @testset "change colnames with several pairs" begin + re_ohlc_2 = rename(ohlc, :Open => :a, :Close => :d) + @test colnames(re_ohlc_2) == [:a, :High, :Low, :d] + @test_throws MethodError rename(ohlc) end - @testset "function composition" begin - f = Symbol ∘ uppercase ∘ string - re_ohlc_2 = rename(f, ohlc) - @test colnames(re_ohlc_2) == [:OPEN, :HIGH, :LOW, :CLOSE] + @testset "change colnames with dict" begin + re_ohlc_2 = rename(ohlc, Dict(:Open => :a, :Close => :d)...) + @test colnames(re_ohlc_2) == [:a, :High, :Low, :d] end - @testset "do block" begin - re_ohlc_2 = rename(ohlc) do x - x |> string |> uppercase |> Symbol + @testset "change colnames with function" begin + @testset "lambda function" begin + f = colname -> Symbol(uppercase(string(colname))) + re_ohlc_2 = rename(f, ohlc) + @test colnames(re_ohlc_2) == [:OPEN, :HIGH, :LOW, :CLOSE] + end + + @testset "function composition" begin + f = Symbol ∘ uppercase ∘ string + re_ohlc_2 = rename(f, ohlc) + @test colnames(re_ohlc_2) == [:OPEN, :HIGH, :LOW, :CLOSE] + end + + @testset "do block" begin + re_ohlc_2 = rename(ohlc) do x + Symbol(uppercase(string(x))) + end + @test colnames(re_ohlc_2) == [:OPEN, :HIGH, :LOW, :CLOSE] end - @test colnames(re_ohlc_2) == [:OPEN, :HIGH, :LOW, :CLOSE] + + @testset "automatic string/symbol" begin + re_ohlc_2 = rename(uppercase, ohlc, String) + @test colnames(re_ohlc_2) == [:OPEN, :HIGH, :LOW, :CLOSE] + end + end + end # @testset "rename" + + @testset "rename!" begin + let + ta = first(ohlc) + cols = [:Open, :High, :Low, :Close] + cols′ = [:A, :B, :C, :D] + rename!(ta, [:A, :B, :C, :D]) + @test colnames(ohlc) == cols + @test colnames(ta) == cols′ + end + + let + ta = first(cl) + rename!(ta, [:A]) + @test colnames(cl) == [:Close] + @test colnames(ta) == [:A] end - @testset "automatic string/symbol" begin - re_ohlc_2 = rename(uppercase, ohlc, String) - @test colnames(re_ohlc_2) == [:OPEN, :HIGH, :LOW, :CLOSE] + let + ta = first(cl) + rename!(ta, :A) + @test colnames(cl) == [:Close] + @test colnames(ta) == [:A] end - end -end # @testset "rename" - - -@testset "rename!" begin - let - ta = first(ohlc) - cols = [:Open, :High, :Low, :Close] - cols′ = [:A, :B, :C, :D] - rename!(ta, [:A, :B, :C, :D]) - @test colnames(ohlc) == cols - @test colnames(ta) == cols′ - end - - let - ta = first(cl) - rename!(ta, [:A]) - @test colnames(cl) == [:Close] - @test colnames(ta) == [:A] - end - - let - ta = first(cl) - rename!(ta, :A) - @test colnames(cl) == [:Close] - @test colnames(ta) == [:A] - end - - let - ta = first(ohlc) - cols = [:Open, :High, :Low, :Close] - cols′ = [:Open′, :High, :Low′, :Close] - rename!(ta, :Open => :Open′, :Low => :Low′) - @test colnames(ohlc) == cols - @test colnames(ta) == cols′ - @test_throws MethodError rename!(ta) - @test_throws ArgumentError rename!(ohlc, :Unknown => :A) - end - - let - ta = first(ohlc) - cols = [:Open, :High, :Low, :Close] - cols′ = [:open, :high, :low, :close] - rename!(Symbol ∘ lowercase ∘ string, ta) - @test colnames(ohlc) == cols - @test colnames(ta) == cols′ - end - - let - ta = first(ohlc) - cols = [:Open, :High, :Low, :Close] - cols′ = [:open, :high, :low, :close] - rename!(lowercase, ta, String) - @test colnames(ohlc) == cols - @test colnames(ta) == cols′ - end -end # @testset "rename!" + let + ta = first(ohlc) + cols = [:Open, :High, :Low, :Close] + cols′ = [:Open′, :High, :Low′, :Close] + rename!(ta, :Open => :Open′, :Low => :Low′) + @test colnames(ohlc) == cols + @test colnames(ta) == cols′ + @test_throws MethodError rename!(ta) + @test_throws ArgumentError rename!(ohlc, :Unknown => :A) + end + let + ta = first(ohlc) + cols = [:Open, :High, :Low, :Close] + cols′ = [:open, :high, :low, :close] + rename!(Symbol ∘ lowercase ∘ string, ta) + @test colnames(ohlc) == cols + @test colnames(ta) == cols′ + end + let + ta = first(ohlc) + cols = [:Open, :High, :Low, :Close] + cols′ = [:open, :high, :low, :close] + rename!(lowercase, ta, String) + @test colnames(ohlc) == cols + @test colnames(ta) == cols′ + end + end # @testset "rename!" end # @testset "modify" diff --git a/test/plotrecipes.jl b/test/plotrecipes.jl index 7941162a..95ba98bf 100644 --- a/test/plotrecipes.jl +++ b/test/plotrecipes.jl @@ -3,20 +3,15 @@ using Test using MarketData using TimeSeries - @testset "plotrecipes" begin + @testset "the Candlestick constructor works" begin + candlestick = TimeSeries.Candlestick(ohlcv) + @test length(candlestick.time) == length(ohlcv) + candlestick = TimeSeries.Candlestick(ohlc) + @test length(candlestick.time) == length(ohlc) -@testset "the Candlestick constructor works" begin - candlestick = TimeSeries.Candlestick(ohlcv) - @test length(candlestick.time) == length(ohlcv) - - candlestick = TimeSeries.Candlestick(ohlc) - @test length(candlestick.time) == length(ohlc) - - @test_throws ArgumentError TimeSeries.Candlestick(cl) - @test_throws ArgumentError TimeSeries.Candlestick(op) -end - - + @test_throws ArgumentError TimeSeries.Candlestick(cl) + @test_throws ArgumentError TimeSeries.Candlestick(op) + end end # @testset "plotrecipes diff --git a/test/readwrite.jl b/test/readwrite.jl index 46e7890f..860dd2cd 100644 --- a/test/readwrite.jl +++ b/test/readwrite.jl @@ -6,128 +6,128 @@ using MarketData using TimeSeries - @testset "readwrite" begin - - -@testset "readwrite parses IO correctly" begin - io = """DateTime,Open - 2010/1/4|9:01:00,7300 - 2010/1/4|9:02:00,7316 - 2010/1/4|9:03:00,7316 - 2010/1/4|9:04:00,7316 - 2010/1/4|9:05:00,7316""" - tm0 = readtimearray(IOBuffer(io), format="yyyy/mm/dd|HH:MM:SS") - - @testset "Input as IOBuffer and specifying DateTime string format for reading" begin - @test length(tm0) == 5 - @test size(values(tm0)) == (5,1) - @test timestamp(tm0)[4] == DateTime(2010,1,4,9,4) - end -end - - -@testset "readwrite parses csv correctly" begin - tm1 = readtimearray( - joinpath(@__DIR__, "data/datetime3.csv"), - format="yyyy/mm/dd|HH:MM:SS") - tm2 = readtimearray( - joinpath(@__DIR__, "data/datetime3.csv"), - format="yyyy/mm/dd|HH:MM:SS", meta="foo") - tm3 = readtimearray( - joinpath(@__DIR__, "data/read_example_delim.csv"), - format="dd/mm/yyyy HH:MM", delim=';') - tm4 = readtimearray( - joinpath(@__DIR__, "data/headless.csv"), - format="yyyy-mm-dd HH:MM:SS", header=false) - - @testset "Specifying DateTime string format for reading" begin - @test length(tm1) == 5 - @test size(values(tm1)) == (5,1) - @test timestamp(tm1)[4] == DateTime(2010,1,4,9,4) - @test_throws( - ArgumentError, - readtimearray(joinpath(dirname(@__FILE__), "data/datetime3.csv"))) - end - - @testset "readtimearray accepts meta field" begin - @test meta(tm2) == "foo" + @testset "readwrite parses IO correctly" begin + io = """DateTime,Open + 2010/1/4|9:01:00,7300 + 2010/1/4|9:02:00,7316 + 2010/1/4|9:03:00,7316 + 2010/1/4|9:04:00,7316 + 2010/1/4|9:05:00,7316""" + tm0 = readtimearray(IOBuffer(io); format="yyyy/mm/dd|HH:MM:SS") + + @testset "Input as IOBuffer and specifying DateTime string format for reading" begin + @test length(tm0) == 5 + @test size(values(tm0)) == (5, 1) + @test timestamp(tm0)[4] == DateTime(2010, 1, 4, 9, 4) + end end - @testset "readtimearray works with arbitrary delimiters" begin - @test length(tm3) == 2 - @test size(values(tm3)) == (2,2) - @test timestamp(tm3)[2] == DateTime(2015,1,1,1,0) - @test values(tm3)[1,1] == 10.42 + @testset "readwrite parses csv correctly" begin + tm1 = readtimearray( + joinpath(@__DIR__, "data/datetime3.csv"); format="yyyy/mm/dd|HH:MM:SS" + ) + tm2 = readtimearray( + joinpath(@__DIR__, "data/datetime3.csv"); + format="yyyy/mm/dd|HH:MM:SS", + meta="foo", + ) + tm3 = readtimearray( + joinpath(@__DIR__, "data/read_example_delim.csv"); + format="dd/mm/yyyy HH:MM", + delim=';', + ) + tm4 = readtimearray( + joinpath(@__DIR__, "data/headless.csv"); + format="yyyy-mm-dd HH:MM:SS", + header=false, + ) + + @testset "Specifying DateTime string format for reading" begin + @test length(tm1) == 5 + @test size(values(tm1)) == (5, 1) + @test timestamp(tm1)[4] == DateTime(2010, 1, 4, 9, 4) + @test_throws( + ArgumentError, + readtimearray(joinpath(dirname(@__FILE__), "data/datetime3.csv")) + ) + end + + @testset "readtimearray accepts meta field" begin + @test meta(tm2) == "foo" + end + + @testset "readtimearray works with arbitrary delimiters" begin + @test length(tm3) == 2 + @test size(values(tm3)) == (2, 2) + @test timestamp(tm3)[2] == DateTime(2015, 1, 1, 1, 0) + @test values(tm3)[1, 1] == 10.42 + end + + @testset "headless csv" begin + @test length(tm4) == 2 + @test size(values(tm4)) == (2, 4) + @test values(tm4) == [1 2 3 4; 5 6 7 8] + end end - @testset "headless csv" begin - @test length(tm4) == 2 - @test size(values(tm4)) == (2, 4) - @test values(tm4) == [1 2 3 4; 5 6 7 8] - end -end + @testset "readwrite parses MarketData objects correctly" begin + @testset "1d values array works" begin + @test typeof(values(cl)) == Array{Float64,1} + end + @testset "2d values array works" begin + @test typeof(values(ohlc)) == Array{Float64,2} + end -@testset "readwrite parses MarketData objects correctly" begin - @testset "1d values array works" begin - @test typeof(values(cl)) == Array{Float64,1} + @testset "timestamp parses to correct type" begin + @test typeof(timestamp(cl)) == Vector{Date} + @test typeof(timestamp(datetime1)) == Vector{DateTime} + end end - @testset "2d values array works" begin - @test typeof(values(ohlc)) == Array{Float64,2} - end + @testset "writetimearray method with default parameters works" begin + mktemp() do filename, _io + uohlc = uniformspace(ohlc) + writetimearray(uohlc, filename) + readback = readtimearray(filename) - @testset "timestamp parses to correct type" begin - @test typeof(timestamp(cl)) == Vector{Date} - @test typeof(timestamp(datetime1)) == Vector{DateTime} + @test colnames(uohlc) == colnames(readback) + @test timestamp(uohlc) == timestamp(readback) + @test isequal(values(uohlc), values(readback)) + end end -end + @testset "writetimearray method with a delimiter works" begin + mktemp() do filename, _io + uohlc = uniformspace(ohlc) + writetimearray(uohlc[1:5], filename; delim=';') + readback = readtimearray(filename; delim=';') -@testset "writetimearray method with default parameters works" begin - mktemp() do filename, _io - uohlc = uniformspace(ohlc) - writetimearray(uohlc, filename) - readback = readtimearray(filename) - - @test colnames(uohlc) == colnames(readback) - @test timestamp(uohlc) == timestamp(readback) - @test isequal(values(uohlc), values(readback)) + @test colnames(uohlc[1:5]) == colnames(readback) + @test timestamp(uohlc[1:5]) == timestamp(readback) + @test isequal(values(uohlc[1:5]), values(readback)) + end end -end -@testset "writetimearray method with a delimiter works" begin - mktemp() do filename, _io - uohlc = uniformspace(ohlc) - writetimearray(uohlc[1:5], filename, delim=';') - readback = readtimearray(filename, delim=';') + @testset "writetimearray method with no header works" begin + mktemp() do filename, _io + uohlc = uniformspace(ohlc) + writetimearray(uohlc[1:5], filename; header=false) + readback = readtimearray(filename; header=false) - @test colnames(uohlc[1:5]) == colnames(readback) - @test timestamp(uohlc[1:5]) == timestamp(readback) - @test isequal(values(uohlc[1:5]), values(readback)) + @test timestamp(uohlc[1:5]) == timestamp(readback) + @test isequal(values(uohlc[1:5]), values(readback)) + end end -end -@testset "writetimearray method with no header works" begin - mktemp() do filename, _io - uohlc = uniformspace(ohlc) - writetimearray(uohlc[1:5], filename, header=false) - readback = readtimearray(filename, header=false) + @testset "writetimearray method with a timestamp format works" begin + mktemp() do filename, _io + uohlc = uniformspace(ohlc) + writetimearray(uohlc[1:5], filename; format="yyyy/mm/dd") + readback = readtimearray(filename; format="yyyy/mm/dd") - @test timestamp(uohlc[1:5]) == timestamp(readback) - @test isequal(values(uohlc[1:5]), values(readback)) + @test timestamp(uohlc[1:5]) == timestamp(readback) + end end -end - -@testset "writetimearray method with a timestamp format works" begin - mktemp() do filename, _io - uohlc = uniformspace(ohlc) - writetimearray(uohlc[1:5], filename, format="yyyy/mm/dd") - readback = readtimearray(filename, format="yyyy/mm/dd") - - @test timestamp(uohlc[1:5]) == timestamp(readback) - end -end - end # @testset "readwrite" diff --git a/test/runtests.jl b/test/runtests.jl index 3698371b..85513609 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -15,11 +15,10 @@ tests = [ "plotrecipes", ] - @testset "TimeSeries" begin @info("Running tests:") - for test ∈ tests + for test in tests @info("\t* $test ...") include("$test.jl") end diff --git a/test/split.jl b/test/split.jl index 553b9da4..f434e1d9 100644 --- a/test/split.jl +++ b/test/split.jl @@ -5,117 +5,122 @@ using MarketData using TimeSeries - @testset "split" begin - - -@testset "find methods" begin - @testset "find returns correct row numbers array" begin - @test timestamp(cl[findall(cl .> op)])[1] == Date(2000, 1, 3) - @test length(findall(cl .> op)) == 244 - end - - @testset "findwhen returns correct Dates array" begin - @test findwhen(cl .> op)[2] == Date(2000, 1, 5) - @test length(findwhen(cl .> op)) == 244 - end - - @testset "findall(f::Function, ta)" begin - @test findall(cl .> 100) == findall(x -> x > 100, cl) - @test findall(cl .> 100) == findall(x -> x[4] > 100, ohlc) + @testset "find methods" begin + @testset "find returns correct row numbers array" begin + @test timestamp(cl[findall(cl .> op)])[1] == Date(2000, 1, 3) + @test length(findall(cl .> op)) == 244 + end + + @testset "findwhen returns correct Dates array" begin + @test findwhen(cl .> op)[2] == Date(2000, 1, 5) + @test length(findwhen(cl .> op)) == 244 + end + + @testset "findall(f::Function, ta)" begin + @test findall(cl .> 100) == findall(x -> x > 100, cl) + @test findall(cl .> 100) == findall(x -> x[4] > 100, ohlc) + end end -end - - -@testset "split date operations" begin - @testset "from and to correctly subset non-zero and zero-length time arrays" begin - @test length(from(cl, Date(2001,12,28))) == 2 - @test length(from(cl, Date(2002,1,1))) == 0 - @test length(from(from(cl, Date(2002,1,1)), Date(2012,1,1))) == 0 - @test length(to(cl, Date(2000,1,4))) == 2 - @test length(to(cl, Date(1999,1,4))) == 0 - @test length(to(to(cl, Date(1999,1,4)), Date(1912,1,1))) == 0 + @testset "split date operations" begin + @testset "from and to correctly subset non-zero and zero-length time arrays" begin + @test length(from(cl, Date(2001, 12, 28))) == 2 + @test length(from(cl, Date(2002, 1, 1))) == 0 + @test length(from(from(cl, Date(2002, 1, 1)), Date(2012, 1, 1))) == 0 + + @test length(to(cl, Date(2000, 1, 4))) == 2 + @test length(to(cl, Date(1999, 1, 4))) == 0 + @test length(to(to(cl, Date(1999, 1, 4)), Date(1912, 1, 1))) == 0 + end + + @testset "when method correctly subset" begin + @test timestamp(when(cl, day, 4))[1] == Date(2000, 1, 4) + @test timestamp(when(cl, dayname, "Friday"))[1] == Date(2000, 1, 7) + @test timestamp(when(cl, week, 5))[1] == Date(2000, 1, 31) + @test timestamp(when(cl, month, 5))[1] == Date(2000, 5, 1) + @test timestamp(when(cl, monthname, "June"))[1] == Date(2000, 6, 1) + @test timestamp(when(cl, year, 2001))[1] == Date(2001, 1, 2) + @test timestamp(when(cl, dayofweek, 1))[1] == Date(2000, 1, 3) + # all the days in the nth week of each month + @test timestamp(when(cl, dayofweekofmonth, 5))[1] == Date(2000, 1, 31) + @test timestamp(when(cl, dayofyear, 365))[1] == Date(2001, 12, 31) + @test timestamp(when(cl, quarterofyear, 4))[1] == Date(2000, 10, 2) + @test timestamp(when(cl, dayofquarter, 1))[1] == Date(2001, 10, 1) + end end - @testset "when method correctly subset" begin - @test timestamp(when(cl, day, 4))[1] == Date(2000,1,4) - @test timestamp(when(cl, dayname, "Friday"))[1] == Date(2000,1,7) - @test timestamp(when(cl, week, 5))[1] == Date(2000,1,31) - @test timestamp(when(cl, month, 5))[1] == Date(2000,5,1) - @test timestamp(when(cl, monthname, "June"))[1] == Date(2000,6,1) - @test timestamp(when(cl, year, 2001))[1] == Date(2001,1,2) - @test timestamp(when(cl, dayofweek, 1))[1] == Date(2000,1,3) - # all the days in the nth week of each month - @test timestamp(when(cl, dayofweekofmonth, 5))[1] == Date(2000,1,31) - @test timestamp(when(cl, dayofyear, 365))[1] == Date(2001,12,31) - @test timestamp(when(cl, quarterofyear, 4))[1] == Date(2000,10,2) - @test timestamp(when(cl, dayofquarter, 1))[1] == Date(2001,10,1) + @testset "head, tail, first and last methods" begin + @testset "head, tail, first and last methods work with default n value on single column TimeArray" begin + @test length(head(cl, 6)) == 6 + @test timestamp(head(cl)) == [ + Date(2000, 1, 3), + Date(2000, 1, 4), + Date(2000, 1, 5), + Date(2000, 1, 6), + Date(2000, 1, 7), + Date(2000, 1, 10), + ] + @test values(head(cl)) == [111.94, 102.5, 104.0, 95.0, 99.5, 97.75] + + @test length(tail(cl, 6)) == 6 + @test timestamp(tail(cl)) == [ + Date(2001, 12, 21), + Date(2001, 12, 24), + Date(2001, 12, 26), + Date(2001, 12, 27), + Date(2001, 12, 28), + Date(2001, 12, 31), + ] + @test values(tail(cl)) == [21.0, 21.36, 21.49, 22.07, 22.43, 21.9] + + @test length(first(cl)) == 1 + @test timestamp(first(cl))[1] == Date(2000, 1, 3) + @test values(first(cl))[1] == 111.94 + @test meta(first(cl)) == "AAPL" + + @test length(last(cl)) == 1 + @test timestamp(last(cl))[1] == Date(2001, 12, 31) + @test values(last(cl))[1] == 21.9 + @test meta(last(cl)) == "AAPL" + end + + @testset "head, tail, first and last methods work with default n value on multi column TimeArray" begin + @test length(head(ohlc)) == 6 + @test values(head(ohlc, 1)) == [104.88 112.5 101.69 111.94] + + @test length(tail(ohlc)) == 6 + @test values(tail(ohlc, 1)) == [22.51 22.66 21.83 21.9] + + @test length(first(ohlc)) == 1 + @test timestamp(first(ohlc))[1] == Date(2000, 1, 3) + @test values(first(ohlc)) == [104.88 112.5 101.69 111.94] + @test meta(first(ohlc)) == "AAPL" + + @test length(last(ohlc)) == 1 + @test timestamp(last(ohlc))[1] == Date(2001, 12, 31) + @test values(last(ohlc)) == [22.51 22.66 21.83 21.9] + @test meta(last(ohlc)) == "AAPL" + end + + @testset "head, tail, first and last methods work with custom periods on single column TimeArray" begin + @test length(head(cl, 2)) == 2 + @test length(head(cl, 500)) == length(cl) + @test length(tail(cl, 2)) == 2 + @test length(tail(cl, 500)) == length(cl) + + @test length(first(cl)) == 1 + @test length(last(cl)) == 1 + end + + @testset "head, tail, first and last methods work with custom periods on multi column TimeArray" begin + @test length(head(ohlc, 2)) == 2 + @test length(head(ohlc, 500)) == length(ohlc) + @test length(tail(ohlc, 2)) == 2 + @test length(tail(ohlc, 500)) == length(ohlc) + + @test length(first(ohlc)) == 1 + @test length(last(ohlc)) == 1 + end end -end - - -@testset "head, tail, first and last methods" begin - @testset "head, tail, first and last methods work with default n value on single column TimeArray" begin - @test length(head(cl,6)) == 6 - @test timestamp(head(cl)) == [Date(2000,1,3), Date(2000,1,4), Date(2000,1,5), - Date(2000,1,6), Date(2000,1,7), Date(2000,1,10)] - @test values(head(cl)) == [111.94, 102.5, 104.0, 95.0, 99.5, 97.75] - - @test length(tail(cl,6)) == 6 - @test timestamp(tail(cl)) == [Date(2001,12,21), Date(2001,12,24), Date(2001,12,26), - Date(2001,12,27), Date(2001,12,28), Date(2001,12,31)] - @test values(tail(cl)) == [21.0, 21.36, 21.49, 22.07, 22.43, 21.9] - - @test length(first(cl)) == 1 - @test timestamp(first(cl))[1] == Date(2000,1,3) - @test values(first(cl))[1] == 111.94 - @test meta(first(cl)) == "AAPL" - - @test length(last(cl)) == 1 - @test timestamp(last(cl))[1] == Date(2001,12,31) - @test values(last(cl))[1] == 21.9 - @test meta(last(cl)) == "AAPL" - end - - @testset "head, tail, first and last methods work with default n value on multi column TimeArray" begin - @test length(head(ohlc)) == 6 - @test values(head(ohlc, 1)) == [104.88 112.5 101.69 111.94] - - @test length(tail(ohlc)) == 6 - @test values(tail(ohlc, 1)) == [22.51 22.66 21.83 21.9] - - @test length(first(ohlc)) == 1 - @test timestamp(first(ohlc))[1] == Date(2000,1,3) - @test values(first(ohlc)) == [104.88 112.5 101.69 111.94] - @test meta(first(ohlc)) == "AAPL" - - @test length(last(ohlc)) == 1 - @test timestamp(last(ohlc))[1] == Date(2001,12,31) - @test values(last(ohlc)) == [22.51 22.66 21.83 21.9] - @test meta(last(ohlc)) == "AAPL" - end - - @testset "head, tail, first and last methods work with custom periods on single column TimeArray" begin - @test length(head(cl, 2)) == 2 - @test length(head(cl, 500)) == length(cl) - @test length(tail(cl, 2)) == 2 - @test length(tail(cl, 500)) == length(cl) - - @test length(first(cl)) == 1 - @test length(last(cl)) == 1 - end - - @testset "head, tail, first and last methods work with custom periods on multi column TimeArray" begin - @test length(head(ohlc, 2)) == 2 - @test length(head(ohlc, 500)) == length(ohlc) - @test length(tail(ohlc, 2)) == 2 - @test length(tail(ohlc, 500)) == length(ohlc) - - @test length(first(ohlc)) == 1 - @test length(last(ohlc)) == 1 - end -end - - end # @testset "split" diff --git a/test/tables.jl b/test/tables.jl index e402a750..a6d1b812 100644 --- a/test/tables.jl +++ b/test/tables.jl @@ -7,261 +7,254 @@ using DataFrames using Tables using CSV - @testset "Tables.jl integration" begin + @testset "interface" begin + @testset "single column" begin + @test Tables.istable(cl) + @test Tables.istable(typeof(cl)) + @test Tables.rowaccess(cl) + @test Tables.rowaccess(typeof(cl)) + @test Tables.columnaccess(cl) + @test Tables.columnaccess(typeof(cl)) + + sch = Tables.schema(cl) + @test sch.names == (:timestamp, :Close) + @test sch.types == (Date, Float64) + end - -@testset "interface" begin - - @testset "single column" begin - @test Tables.istable(cl) - @test Tables.istable(typeof(cl)) - @test Tables.rowaccess(cl) - @test Tables.rowaccess(typeof(cl)) - @test Tables.columnaccess(cl) - @test Tables.columnaccess(typeof(cl)) - - sch = Tables.schema(cl) - @test sch.names == (:timestamp, :Close) - @test sch.types == (Date, Float64) - end - - @testset "multiple column" begin - @test Tables.istable(ohlc) - @test Tables.istable(typeof(ohlc)) - @test Tables.rowaccess(ohlc) - @test Tables.rowaccess(typeof(ohlc)) - @test Tables.columnaccess(ohlc) - @test Tables.columnaccess(typeof(ohlc)) - - sch = Tables.schema(ohlc) - @test sch.names == (:timestamp, :Open, :High, :Low, :Close) - @test sch.types == (Date, Float64, Float64, Float64, Float64) + @testset "multiple column" begin + @test Tables.istable(ohlc) + @test Tables.istable(typeof(ohlc)) + @test Tables.rowaccess(ohlc) + @test Tables.rowaccess(typeof(ohlc)) + @test Tables.columnaccess(ohlc) + @test Tables.columnaccess(typeof(ohlc)) + + sch = Tables.schema(ohlc) + @test sch.names == (:timestamp, :Open, :High, :Low, :Close) + @test sch.types == (Date, Float64, Float64, Float64, Float64) + end end -end + @testset "iterator" begin + @testset "single column" begin + @test Tables.columnnames(cl) == [:timestamp; colnames(cl)] + @test Tables.getcolumn(cl, 1) == timestamp(cl) + @test Tables.getcolumn(cl, :timestamp) == timestamp(cl) + @test Tables.getcolumn(cl, 2) == values(cl) + @test Tables.getcolumn(cl, :Close) == values(cl) + + # column iterator + iters = [Tables.columns(cl)] + if VERSION ≥ v"1.1" + push!(iters, eachcol(cl)) + end + for i in iters + @test size(i) == (length(cl), 2) + @test length(i) == 2 # timestamp and the close columns + @test i[100, 1] == timestamp(cl)[100] + @test i[100, 2] == values(cl)[100] + @test i[end] == values(cl) + @test i[end, 1] == timestamp(cl)[end] + @test i[end, 2] == values(cl)[end] + @test timestamp(i) == timestamp(cl) + @test colnames(i) == colnames(cl) + @test i.Close == values(cl) + @test Tables.getcolumn(i, 1) == timestamp(cl) + @test Tables.getcolumn(i, :timestamp) == timestamp(cl) + @test Tables.getcolumn(i, 2) == values(cl) + @test Tables.getcolumn(i, :Close) == values(cl) + + @test propertynames(i) == (:timestamp, :Close) + end -@testset "iterator" begin - @testset "single column" begin - @test Tables.columnnames(cl) == [:timestamp; colnames(cl)] - @test Tables.getcolumn(cl, 1) == timestamp(cl) - @test Tables.getcolumn(cl, :timestamp) == timestamp(cl) - @test Tables.getcolumn(cl, 2) == values(cl) - @test Tables.getcolumn(cl, :Close) == values(cl) - - # column iterator - iters = [Tables.columns(cl)] - if VERSION ≥ v"1.1" - push!(iters, eachcol(cl)) + # row iterator + iters = [Tables.rows(cl)] + if VERSION ≥ v"1.1" + push!(iters, eachrow(cl)) + end + for iter in iters + for r in iter + @test r.timestamp == timestamp(cl)[1] + @test r.Close == values(cl)[1] + break + end + end end - for i ∈ iters - @test size(i) == (length(cl), 2) - @test length(i) == 2 # timestamp and the close columns - @test i[100, 1] == timestamp(cl)[100] - @test i[100, 2] == values(cl)[100] - @test i[end] == values(cl) - @test i[end, 1] == timestamp(cl)[end] - @test i[end, 2] == values(cl)[end] - @test timestamp(i) == timestamp(cl) - @test colnames(i) == colnames(cl) - @test i.Close == values(cl) - @test Tables.getcolumn(i, 1) == timestamp(cl) - @test Tables.getcolumn(i, :timestamp) == timestamp(cl) - @test Tables.getcolumn(i, 2) == values(cl) - @test Tables.getcolumn(i, :Close) == values(cl) - @test propertynames(i) == (:timestamp, :Close) - end + @testset "multi column" begin + @test Tables.columnnames(ohlc) == [:timestamp; colnames(ohlc)] + @test Tables.getcolumn(ohlc, 1) == timestamp(ohlc) + @test Tables.getcolumn(ohlc, :timestamp) == timestamp(ohlc) + @test Tables.getcolumn(ohlc, 3) == values(ohlc[:High]) + @test Tables.getcolumn(ohlc, :High) == values(ohlc[:High]) + + # column iterator + iters = [Tables.columns(ohlc)] + if VERSION ≥ v"1.1" + push!(iters, eachcol(ohlc)) + end + for i in iters + @test size(i) == (length(ohlc), 5) + @test length(i) == 5 + @test i[100, 1] == timestamp(ohlc)[100] + @test i[100, 3] == values(ohlc)[100, 2] + @test i[end] == values(ohlc[:Close]) + @test i[end, 1] == timestamp(ohlc)[end] + @test i[end, 3] == values(ohlc)[end, 2] + @test timestamp(i) == timestamp(ohlc) + @test colnames(i) == colnames(ohlc) + @test i.Open == values(ohlc.Open) + @test Tables.getcolumn(i, 1) == timestamp(ohlc) + @test Tables.getcolumn(i, :timestamp) == timestamp(ohlc) + @test Tables.getcolumn(i, 3) == values(ohlc[:High]) + @test Tables.getcolumn(i, :High) == values(ohlc[:High]) + + @test propertynames(i) == (:timestamp, :Open, :High, :Low, :Close) + end - # row iterator - iters = [Tables.rows(cl)] - if VERSION ≥ v"1.1" - push!(iters, eachrow(cl)) - end - for iter ∈ iters - for r ∈ iter - @test r.timestamp == timestamp(cl)[1] - @test r.Close == values(cl)[1] - break + # row iterator + iters = [Tables.rows(ohlc)] + if VERSION ≥ v"1.1" + push!(iters, eachrow(ohlc)) + end + for iter in iters + for r in iter + @test r.timestamp == timestamp(ohlc)[1] + @test r.Open == values(ohlc)[1, 1] + @test r.High == values(ohlc)[1, 2] + @test r.Low == values(ohlc)[1, 3] + @test r.Close == values(ohlc)[1, 4] + break + end end end - end - - @testset "multi column" begin - @test Tables.columnnames(ohlc) == [:timestamp; colnames(ohlc)] - @test Tables.getcolumn(ohlc, 1) == timestamp(ohlc) - @test Tables.getcolumn(ohlc, :timestamp) == timestamp(ohlc) - @test Tables.getcolumn(ohlc, 3) == values(ohlc[:High]) - @test Tables.getcolumn(ohlc, :High) == values(ohlc[:High]) - - # column iterator - iters = [Tables.columns(ohlc)] - if VERSION ≥ v"1.1" - push!(iters, eachcol(ohlc)) + end # @testset "iterator" + + @testset "DataFrames.jl" begin + @testset "single column" begin + df = DataFrame(cl) + @test propertynames(df) == [:timestamp; colnames(cl)] + @test df.timestamp == timestamp(cl) + @test df.Close == values(cl.Close) end - for i ∈ iters - @test size(i) == (length(ohlc), 5) - @test length(i) == 5 - @test i[100, 1] == timestamp(ohlc)[100] - @test i[100, 3] == values(ohlc)[100, 2] - @test i[end] == values(ohlc[:Close]) - @test i[end, 1] == timestamp(ohlc)[end] - @test i[end, 3] == values(ohlc)[end, 2] - @test timestamp(i) == timestamp(ohlc) - @test colnames(i) == colnames(ohlc) - @test i.Open == values(ohlc.Open) - @test Tables.getcolumn(i, 1) == timestamp(ohlc) - @test Tables.getcolumn(i, :timestamp) == timestamp(ohlc) - @test Tables.getcolumn(i, 3) == values(ohlc[:High]) - @test Tables.getcolumn(i, :High) == values(ohlc[:High]) - @test propertynames(i) == (:timestamp, :Open, :High, :Low, :Close) + @testset "multi column" begin + df = DataFrame(ohlc) + @test propertynames(df) == [:timestamp; colnames(ohlc)] + @test df.timestamp == timestamp(ohlc) + @test df.Open == values(ohlc.Open) + @test df.High == values(ohlc.High) + @test df.Low == values(ohlc.Low) + @test df.Close == values(ohlc.Close) end - # row iterator - iters = [Tables.rows(ohlc)] - if VERSION ≥ v"1.1" - push!(iters, eachrow(ohlc)) + @testset "column name collision" begin + ta = TimeArray(ohlc; colnames=[:Open, :High, :timestamp, :Close]) + df = DataFrame(ta) + @test propertynames(df) == [:timestamp, :Open, :High, :timestamp_1, :Close] + @test df.timestamp == timestamp(ta) + @test df.Open == values(ta.Open) + @test df.High == values(ta.High) + @test df.timestamp_1 == values(ta.timestamp) + @test df.Close == values(ta.Close) + + # no side effect on column renaming + @test colnames(ta) == [:Open, :High, :timestamp, :Close] end - for iter ∈ iters - for r ∈ iter - @test r.timestamp == timestamp(ohlc)[1] - @test r.Open == values(ohlc)[1, 1] - @test r.High == values(ohlc)[1, 2] - @test r.Low == values(ohlc)[1, 3] - @test r.Close == values(ohlc)[1, 4] - break + + @testset "DataFrame to TimeArray" begin + ts = Date(2018, 1, 1):Day(1):Date(2018, 1, 3) + df = DataFrame(; A=[1.0, 2, 3], B=[4, 5, 6], C=[7, 8, 9], ts=ts) + for unchecked in (true, false) + ta = TimeArray(df; timestamp=:ts, unchecked=unchecked) + + @test timestamp(ta) == ts + @test colnames(ta) == [:A, :B, :C] + @test meta(ta) ≡ df + @test values(ta.A) == [1.0, 2, 3] + @test values(ta.B) == [4, 5.0, 6] + @test values(ta.C) == [7, 8, 9.0] end end - end -end # @testset "iterator" - - -@testset "DataFrames.jl" begin - @testset "single column" begin - df = DataFrame(cl) - @test propertynames(df) == [:timestamp; colnames(cl)] - @test df.timestamp == timestamp(cl) - @test df.Close == values(cl.Close) - end - - @testset "multi column" begin - df = DataFrame(ohlc) - @test propertynames(df) == [:timestamp; colnames(ohlc)] - @test df.timestamp == timestamp(ohlc) - @test df.Open == values(ohlc.Open) - @test df.High == values(ohlc.High) - @test df.Low == values(ohlc.Low) - @test df.Close == values(ohlc.Close) - end - - @testset "column name collision" begin - ta = TimeArray(ohlc, colnames = [:Open, :High, :timestamp, :Close]) - df = DataFrame(ta) - @test propertynames(df) == [:timestamp, :Open, :High, :timestamp_1, :Close] - @test df.timestamp == timestamp(ta) - @test df.Open == values(ta.Open) - @test df.High == values(ta.High) - @test df.timestamp_1 == values(ta.timestamp) - @test df.Close == values(ta.Close) - - # no side effect on column renaming - @test colnames(ta) == [:Open, :High, :timestamp, :Close] - end - - @testset "DataFrame to TimeArray" begin - ts = Date(2018, 1, 1):Day(1):Date(2018, 1, 3) - df = DataFrame(A = [1., 2, 3], - B = [4, 5, 6], - C = [7, 8, 9], - ts = ts) - for unchecked ∈ (true, false) - ta = TimeArray(df; timestamp = :ts, unchecked = unchecked) - - @test timestamp(ta) == ts - @test colnames(ta) == [:A, :B, :C] - @test meta(ta) ≡ df - @test values(ta.A) == [1., 2, 3] - @test values(ta.B) == [4, 5., 6] - @test values(ta.C) == [7, 8, 9.] + end # @testset "DataFrames.jl" + + @testset "CSV.jl" begin + @testset "single column" begin + ta = TimeArray(cl[1:5]; values=[1.1, 2.2, 3.3, 4.4, 5.5]) + io = IOBuffer() + CSV.write(io, ta) + @test String(take!(io)) == + "timestamp,Close\n" * + "2000-01-03,1.1\n" * + "2000-01-04,2.2\n" * + "2000-01-05,3.3\n" * + "2000-01-06,4.4\n" * + "2000-01-07,5.5\n" end - end -end # @testset "DataFrames.jl" - -@testset "CSV.jl" begin - @testset "single column" begin - ta = TimeArray(cl[1:5], values = [1.1, 2.2, 3.3, 4.4, 5.5]) - io = IOBuffer() - CSV.write(io, ta) - @test String(take!(io)) == "timestamp,Close\n" * - "2000-01-03,1.1\n" * - "2000-01-04,2.2\n" * - "2000-01-05,3.3\n" * - "2000-01-06,4.4\n" * - "2000-01-07,5.5\n" - end - - @testset "multi column" begin - ta = TimeArray(ohlc[1:5], values = reshape(1.05:.1:2.95, 5, :)) - io = IOBuffer() - CSV.write(io, ta) - @test String(take!(io)) == "timestamp,Open,High,Low,Close\n" * - "2000-01-03,1.05,1.55,2.05,2.55\n" * - "2000-01-04,1.15,1.65,2.15,2.65\n" * - "2000-01-05,1.25,1.75,2.25,2.75\n" * - "2000-01-06,1.35,1.85,2.35,2.85\n" * - "2000-01-07,1.45,1.95,2.45,2.95\n" - end - - @testset "read csv into TimeArray, single column" begin - file = "timestamp,Close\n" * - "2000-01-03,111.94\n" * - "2000-01-04,102.5\n" * - "2000-01-05,104\n" * - "2000-01-06,95\n" * - "2000-01-07,99.5\n" - io = IOBuffer(file) - csv = CSV.File(io) - ta = TimeArray(csv, timestamp = :timestamp) - ans = cl[1:5] - @test timestamp(ta) == timestamp(ans) - @test values(ta.Close) == values(ans.Close) - @test meta(ta) ≡ csv - end - - @testset "read csv into TimeArray, multi column" begin - file = "timestamp,Open,High,Low,Close\n" * - "2000-01-03,104.88,112.5,101.69,111.94\n" * - "2000-01-04,108.25,110.62,101.19,102.5\n" * - "2000-01-05,103.75,110.56,103,104\n" * - "2000-01-06,106.12,107,95,95\n" * - "2000-01-07,96.5,101,95.5,99.5\n" - io = IOBuffer(file) - csv = CSV.File(io) - ta = TimeArray(csv, timestamp = :timestamp) - ans = ohlc[1:5] - @test timestamp(ta) == Date(2000, 1, 3):Day(1):Date(2000, 1, 7) - @test values(ta.Open) == values(ans.Open) - @test values(ta.High) == values(ans.High) - @test values(ta.Low) == values(ans.Low) - @test values(ta.Close) == values(ans.Close) - @test meta(ta) ≡ csv - end + @testset "multi column" begin + ta = TimeArray(ohlc[1:5]; values=reshape(1.05:0.1:2.95, 5, :)) + io = IOBuffer() + CSV.write(io, ta) + @test String(take!(io)) == + "timestamp,Open,High,Low,Close\n" * + "2000-01-03,1.05,1.55,2.05,2.55\n" * + "2000-01-04,1.15,1.65,2.15,2.65\n" * + "2000-01-05,1.25,1.75,2.25,2.75\n" * + "2000-01-06,1.35,1.85,2.35,2.85\n" * + "2000-01-07,1.45,1.95,2.45,2.95\n" + end - @testset "issue #442" begin - file = "date,high,low,open,close,volume\n" * - "1438992000,50.0,0.00262,50.0,0.00312499,1205.80332085\n" * - "1439078400,0.0041,0.0024,0.00299999,0.00258069,898.12343401\n" * - "1439164800,0.0029022,0.0022,0.00264996,0.00264498,718.36526568\n" * - "1439251200,0.0044,0.002414,0.00264959,0.00395009,3007.27411094\n" - io = IOBuffer(file) - csv = CSV.File(io) - ta = TimeArray(csv, timestamp = :date, timeparser = Date ∘ unix2datetime) - @test timestamp(ta) == Date(2015, 8, 8):Day(1):Date(2015, 8, 11) - @test colnames(ta) == [:high, :low, :open, :close, :volume] - end -end # @testset "CSV.jl" + @testset "read csv into TimeArray, single column" begin + file = + "timestamp,Close\n" * + "2000-01-03,111.94\n" * + "2000-01-04,102.5\n" * + "2000-01-05,104\n" * + "2000-01-06,95\n" * + "2000-01-07,99.5\n" + io = IOBuffer(file) + csv = CSV.File(io) + ta = TimeArray(csv; timestamp=:timestamp) + ans = cl[1:5] + @test timestamp(ta) == timestamp(ans) + @test values(ta.Close) == values(ans.Close) + @test meta(ta) ≡ csv + end + @testset "read csv into TimeArray, multi column" begin + file = + "timestamp,Open,High,Low,Close\n" * + "2000-01-03,104.88,112.5,101.69,111.94\n" * + "2000-01-04,108.25,110.62,101.19,102.5\n" * + "2000-01-05,103.75,110.56,103,104\n" * + "2000-01-06,106.12,107,95,95\n" * + "2000-01-07,96.5,101,95.5,99.5\n" + io = IOBuffer(file) + csv = CSV.File(io) + ta = TimeArray(csv; timestamp=:timestamp) + ans = ohlc[1:5] + @test timestamp(ta) == Date(2000, 1, 3):Day(1):Date(2000, 1, 7) + @test values(ta.Open) == values(ans.Open) + @test values(ta.High) == values(ans.High) + @test values(ta.Low) == values(ans.Low) + @test values(ta.Close) == values(ans.Close) + @test meta(ta) ≡ csv + end + @testset "issue #442" begin + file = + "date,high,low,open,close,volume\n" * + "1438992000,50.0,0.00262,50.0,0.00312499,1205.80332085\n" * + "1439078400,0.0041,0.0024,0.00299999,0.00258069,898.12343401\n" * + "1439164800,0.0029022,0.0022,0.00264996,0.00264498,718.36526568\n" * + "1439251200,0.0044,0.002414,0.00264959,0.00395009,3007.27411094\n" + io = IOBuffer(file) + csv = CSV.File(io) + ta = TimeArray(csv; timestamp=:date, timeparser=Date ∘ unix2datetime) + @test timestamp(ta) == Date(2015, 8, 8):Day(1):Date(2015, 8, 11) + @test colnames(ta) == [:high, :low, :open, :close, :volume] + end + end # @testset "CSV.jl" end # @testset "Tables.jl integration diff --git a/test/timearray.jl b/test/timearray.jl index eab2dac8..a02e2548 100644 --- a/test/timearray.jl +++ b/test/timearray.jl @@ -5,707 +5,743 @@ using MarketData using TimeSeries - @testset "timearray" begin - - -@testset "field extraction methods work" begin - @testset "timestamp, values, colnames and meta" begin - for ta ∈ [cl, op, ohlc] - @test timestamp(ta) isa Vector{Date} - @test values(ta) isa Array{Float64} - @test colnames(ta) isa Vector{Symbol} - @test meta(mdata) == "Apple" + @testset "field extraction methods work" begin + @testset "timestamp, values, colnames and meta" begin + for ta in [cl, op, ohlc] + @test timestamp(ta) isa Vector{Date} + @test values(ta) isa Array{Float64} + @test colnames(ta) isa Vector{Symbol} + @test meta(mdata) == "Apple" + end end end -end + @testset "type constructors allow views" begin + source_rows = 101:121 + source_cols = 1:size(values(AAPL), 2) + tstamps = view(timestamp(AAPL), source_rows) + tvalues = view(values(AAPL), source_rows, source_cols) -@testset "type constructors allow views" begin - source_rows = 101:121 - source_cols = 1:size(values(AAPL), 2) - tstamps = view(timestamp(AAPL), source_rows) - tvalues = view(values(AAPL), source_rows, source_cols) + AAPL1 = TimeArray( + timestamp(AAPL)[source_rows], + values(AAPL)[source_rows, source_cols], + colnames(AAPL), + meta(AAPL), + ) - AAPL1 = TimeArray(timestamp(AAPL)[source_rows], - values(AAPL)[source_rows, source_cols], - colnames(AAPL), meta(AAPL)) + AAPL2 = TimeArray(tstamps, tvalues, colnames(AAPL), meta(AAPL)) - AAPL2 = TimeArray(tstamps, tvalues, colnames(AAPL), meta(AAPL)) + @testset "match first date" begin + @test timestamp(AAPL1)[1] == timestamp(AAPL2)[1] + end - @testset "match first date" begin - @test timestamp(AAPL1)[1] == timestamp(AAPL2)[1] - end + @testset "match first values" begin + @test values(AAPL1)[1] == values(AAPL2)[1] + end - @testset "match first values" begin - @test values(AAPL1)[1] == values(AAPL2)[1] + @testset "match all values" begin + @test values(AAPL1) == values(AAPL2) + end end - @testset "match all values" begin - @test values(AAPL1) == values(AAPL2) - end -end + @testset "type constructors enforce invariants" begin + mangled_stamp = vcat(timestamp(cl)[200:end], timestamp(cl)[1:199]) + @testset "unequal length between values and timestamp fails" begin + @test_throws( + DimensionMismatch, TimeArray(timestamp(cl), values(cl)[2:end], [:Close]) + ) + end -@testset "type constructors enforce invariants" begin - mangled_stamp = vcat(timestamp(cl)[200:end], timestamp(cl)[1:199]) + @testset "unequal length between colnames and array width fails" begin + @test_throws( + DimensionMismatch, TimeArray(timestamp(cl), values(cl), [:Close, :Open]) + ) + end - @testset "unequal length between values and timestamp fails" begin - @test_throws( - DimensionMismatch, - TimeArray(timestamp(cl), values(cl)[2:end], [:Close])) - end + @testset "mangled order of timestamp values fails" begin + @test_throws(ArgumentError, TimeArray(mangled_stamp, values(cl), [:Close])) + end - @testset "unequal length between colnames and array width fails" begin - @test_throws( - DimensionMismatch, - TimeArray(timestamp(cl), values(cl), [:Close, :Open])) - end + @testset "reverse occurs when needed" begin + rev_timestamp = reverse(timestamp(cl); dims=1) + rev_values = reverse(values(cl); dims=1) + ta = TimeArray(rev_timestamp, rev_values, [:Close]) + @test timestamp(ta)[1] == Date(2000, 1, 3) + @test values(ta)[1] == 111.94 + end - @testset "mangled order of timestamp values fails" begin - @test_throws( - ArgumentError, - TimeArray(mangled_stamp, values(cl), [:Close])) - end + @testset "duplicate column names are enumerated by inner constructor" begin + cols = [:a, :b, :c, :a, :a, :b, :d, :e, :e, :e, :e, :f] + ta = TimeArray(timestamp(AAPL), values(AAPL), cols) + @test colnames(ta)[1] == :a + @test colnames(ta)[2] == :b + @test colnames(ta)[3] == :c + @test colnames(ta)[4] == :a_1 + @test colnames(ta)[5] == :a_2 + @test colnames(ta)[6] == :b_1 + @test colnames(ta)[7] == :d + @test colnames(ta)[8] == :e + @test colnames(ta)[9] == :e_1 + @test colnames(ta)[10] == :e_2 + @test colnames(ta)[11] == :e_3 + @test colnames(ta)[12] == :f + end - @testset "reverse occurs when needed" begin - rev_timestamp = reverse(timestamp(cl), dims = 1) - rev_values = reverse(values(cl), dims = 1) - ta = TimeArray(rev_timestamp, rev_values, [:Close]) - @test timestamp(ta)[1] == Date(2000,1,3) - @test values(ta)[1] == 111.94 + @testset "and doesn't when unchecked" begin + ta = TimeArray(mangled_stamp, values(cl); unchecked=true) + @test values(ta) === values(cl) + @test timestamp(ta) === mangled_stamp + end end - @testset "duplicate column names are enumerated by inner constructor" begin - cols = [:a, :b, :c, :a, :a, :b, :d, :e, :e, :e, :e, :f] - ta = TimeArray(timestamp(AAPL), values(AAPL), cols) - @test colnames(ta)[1] == :a - @test colnames(ta)[2] == :b - @test colnames(ta)[3] == :c - @test colnames(ta)[4] == :a_1 - @test colnames(ta)[5] == :a_2 - @test colnames(ta)[6] == :b_1 - @test colnames(ta)[7] == :d - @test colnames(ta)[8] == :e - @test colnames(ta)[9] == :e_1 - @test colnames(ta)[10] == :e_2 - @test colnames(ta)[11] == :e_3 - @test colnames(ta)[12] == :f - end + @testset "construction without colnames" begin + one = TimeArray(timestamp(cl), values(cl)) + multi = TimeArray(timestamp(AAPL), values(AAPL)) + more = TimeArray(timestamp(cl)[1], collect(1:50)') + + @testset "default colnames" begin + @test colnames(one) == [:A] + @test colnames(multi) == [:A, :B, :C, :D, :E, :F, :G, :H, :I, :J, :K, :L] + @test colnames(more) == [ + :A, + :B, + :C, + :D, + :E, + :F, + :G, + :H, + :I, + :J, + :K, + :L, + :M, + :N, + :O, + :P, + :Q, + :R, + :S, + :T, + :U, + :V, + :W, + :X, + :Y, + :Z, + :AA, + :AB, + :AC, + :AD, + :AE, + :AF, + :AG, + :AH, + :AI, + :AJ, + :AK, + :AL, + :AM, + :AN, + :AO, + :AP, + :AQ, + :AR, + :AS, + :AT, + :AU, + :AV, + :AW, + :AX, + ] + end - @testset "and doesn't when unchecked" begin - ta = TimeArray(mangled_stamp, values(cl); unchecked = true) - @test values(ta) === values(cl) - @test timestamp(ta) === mangled_stamp - end -end - -@testset "construction without colnames" begin - one = TimeArray(timestamp(cl), values(cl)) - multi = TimeArray(timestamp(AAPL), values(AAPL)) - more = TimeArray(timestamp(cl)[1], collect(1:50)') - - @testset "default colnames" begin - @test colnames(one) == [:A] - @test colnames(multi) == [:A, :B, :C, :D, :E, :F, :G, :H, :I, :J, :K, :L] - @test colnames(more) == [:A, :B, :C, :D, :E, :F, :G, :H, :I, :J, :K, :L, :M, - :N, :O, :P, :Q, :R, :S, :T, :U, :V, :W, :X, :Y, :Z, - :AA, :AB, :AC, :AD, :AE, :AF, :AG, :AH, :AI, :AJ, :AK, - :AL, :AM, :AN, :AO, :AP, :AQ, :AR, :AS, :AT, :AU, :AV, - :AW, :AX] + @testset "empty colnames forces meta to nothing" begin + @test meta(one) == nothing + @test meta(multi) == nothing + @test meta(more) == nothing + end end - @testset "empty colnames forces meta to nothing" begin - @test meta(one) == nothing - @test meta(multi) == nothing - @test meta(more) == nothing - end -end + @testset "construct with StepRange{Date,Day}" begin + drng = Date(2000, 1, 1):Day(1):Date(2000, 1, 5) + ta = TimeArray(drng, 1:5) + @test timestamp(ta)[1] == first(drng) + @test timestamp(ta)[end] == last(drng) -@testset "construct with StepRange{Date,Day}" begin - drng = Date(2000,1,1):Day(1):Date(2000,1,5) - ta = TimeArray(drng, 1:5) + @test values(ta)[1] == 1 + @test values(ta)[end] == 5 + end - @test timestamp(ta)[1] == first(drng) - @test timestamp(ta)[end] == last(drng) + @testset "construction from existing TimeArray" begin + ts = Date(2018, 1, 1):Day(1):Date(2018, 1, 31) + ta = TimeArray(ts, 1:31, [:x], :Meta) - @test values(ta)[1] == 1 - @test values(ta)[end] == 5 -end + let + ts′ = Date(2018, 3, 1):Day(1):Date(2018, 3, 31) + ta′ = TimeArray(ta; timestamp=ts′) + @test timestamp(ta′) == ts′ + @test values(ta′) == values(ta) + @test colnames(ta′) == colnames(ta) + @test meta(ta′) == meta(ta) + end + let + ta′ = TimeArray(ta; values=2:32) + @test timestamp(ta′) == timestamp(ta) + @test values(ta′) == 2:32 + @test colnames(ta′) == colnames(ta) + @test meta(ta′) == meta(ta) + end -@testset "construction from existing TimeArray" begin - ts = Date(2018, 1, 1):Day(1):Date(2018, 1, 31) - ta = TimeArray(ts, 1:31, [:x], :Meta) + let + ta′ = TimeArray(ta; colnames=[:y]) + @test timestamp(ta′) == timestamp(ta) + @test values(ta′) == values(ta) + @test colnames(ta′) == [:y] + @test meta(ta′) == meta(ta) + end - let - ts′ = Date(2018, 3, 1):Day(1):Date(2018, 3, 31) - ta′ = TimeArray(ta; timestamp = ts′) - @test timestamp(ta′) == ts′ - @test values(ta′) == values(ta) - @test colnames(ta′) == colnames(ta) - @test meta(ta′) == meta(ta) + let + ta′ = TimeArray(ta; meta=:Meta42) + @test timestamp(ta′) == timestamp(ta) + @test values(ta′) == values(ta) + @test colnames(ta′) == colnames(ta) + @test meta(ta′) == :Meta42 + end end - let - ta′ = TimeArray(ta; values = 2:32) - @test timestamp(ta′) == timestamp(ta) - @test values(ta′) == 2:32 - @test colnames(ta′) == colnames(ta) - @test meta(ta′) == meta(ta) + @testset "construct with NamedTuple" begin + data = ( + datetime=[DateTime(2018, 11, 21, 12, 0), DateTime(2018, 11, 21, 13, 0)], + col1=[10.2, 11.2], + col2=[20.2, 21.2], + col3=[30.2, 31.2], + ) + ta = TimeArray(data; timestamp=:datetime, meta="Example") + @test size(ta) == (2, 3) + @test colnames(ta) == [:col1, :col2, :col3] + @test timestamp(ta) == + [DateTime(2018, 11, 21, 12, 0), DateTime(2018, 11, 21, 13, 0)] + @test values(ta) == [10.2 20.2 30.2; 11.2 21.2 31.2] + @test meta(ta) == "Example" + end + + @testset "conversion methods" begin + @testset "convert works " begin + @test convert(TimeArray{Float64,1}, (cl .> op)) isa TimeArray{Float64,1} + @test convert(TimeArray{Float64,2}, (merge(cl .< op, cl .> op))) isa + TimeArray{Float64,2} + @test convert(cl .> op) isa TimeArray{Float64,1} + @test convert(merge(cl .< op, cl .> op)) isa TimeArray{Float64,2} + end end - let - ta′ = TimeArray(ta; colnames = [:y]) - @test timestamp(ta′) == timestamp(ta) - @test values(ta′) == values(ta) - @test colnames(ta′) == [:y] - @test meta(ta′) == meta(ta) - end + @testset "copy methods" begin + cop = copy(op) + cohlc = copy(ohlc) - let - ta′ = TimeArray(ta; meta = :Meta42) - @test timestamp(ta′) == timestamp(ta) - @test values(ta′) == values(ta) - @test colnames(ta′) == colnames(ta) - @test meta(ta′) == :Meta42 - end -end - -@testset "construct with NamedTuple" begin - data = (datetime=[DateTime(2018, 11, 21, 12, 0), DateTime(2018, 11, 21, 13, 0)], col1=[10.2, 11.2], col2=[20.2, 21.2], col3=[30.2, 31.2]) - ta = TimeArray(data; timestamp=:datetime, meta="Example") - @test size(ta) == (2, 3) - @test colnames(ta) == [:col1, :col2, :col3] - @test timestamp(ta) == [DateTime(2018, 11, 21, 12, 0), DateTime(2018, 11, 21, 13, 0)] - @test values(ta) == [10.2 20.2 30.2; 11.2 21.2 31.2] - @test meta(ta) == "Example" -end - -@testset "conversion methods" begin - @testset "convert works " begin - @test convert(TimeArray{Float64,1}, (cl.>op)) isa TimeArray{Float64,1} - @test convert(TimeArray{Float64,2}, (merge(cl.op))) isa TimeArray{Float64,2} - @test convert(cl.>op) isa TimeArray{Float64,1} - @test convert(merge(cl.op)) isa TimeArray{Float64,2} - end -end - -@testset "copy methods" begin - cop = copy(op) - cohlc = copy(ohlc) - - @testset "copy works" begin - @test timestamp(cop) == timestamp(op) - @test values(cop) == values(op) - @test colnames(cop) == colnames(op) - @test meta(cop) == meta(op) - - @test timestamp(cohlc) == timestamp(ohlc) - @test values(cohlc) == values(ohlc) - @test colnames(cohlc) == colnames(ohlc) - @test meta(cohlc) == meta(ohlc) - end -end - -@testset "index by integer works with both 1d and 2d time array" begin - @testset "1d time array" begin - ta = cl[1] - @test timestamp(ta) == [Date(2000,1,3)] - @test values(ta) == [111.94] - @test colnames(ta) == [:Close] - @test meta(ta) == "AAPL" - end + @testset "copy works" begin + @test timestamp(cop) == timestamp(op) + @test values(cop) == values(op) + @test colnames(cop) == colnames(op) + @test meta(cop) == meta(op) - @testset "2d time array" begin - ta = ohlc[1] - @test timestamp(ta) == [Date(2000,1,3)] - @test values(ta) == [104.88 112.5 101.69 111.94] - @test colnames(ta) == [:Open, :High, :Low, :Close] - @test meta(ta) == "AAPL" + @test timestamp(cohlc) == timestamp(ohlc) + @test values(cohlc) == values(ohlc) + @test colnames(cohlc) == colnames(ohlc) + @test meta(cohlc) == meta(ohlc) + end end - @test_throws BoundsError cl[] - @test_throws BoundsError ohlc[] -end + @testset "index by integer works with both 1d and 2d time array" begin + @testset "1d time array" begin + ta = cl[1] + @test timestamp(ta) == [Date(2000, 1, 3)] + @test values(ta) == [111.94] + @test colnames(ta) == [:Close] + @test meta(ta) == "AAPL" + end + @testset "2d time array" begin + ta = ohlc[1] + @test timestamp(ta) == [Date(2000, 1, 3)] + @test values(ta) == [104.88 112.5 101.69 111.94] + @test colnames(ta) == [:Open, :High, :Low, :Close] + @test meta(ta) == "AAPL" + end -@testset "ordered collection methods" begin - @testset "iteration protocol is valid" begin - let # single column - i = 1 - for (t, val) ∈ cl[1:3] - @test t == timestamp(cl)[i] - @test val == values(cl)[i] - i += 1 + @test_throws BoundsError cl[] + @test_throws BoundsError ohlc[] + end + + @testset "ordered collection methods" begin + @testset "iteration protocol is valid" begin + let # single column + i = 1 + for (t, val) in cl[1:3] + @test t == timestamp(cl)[i] + @test val == values(cl)[i] + i += 1 + end end - end - let # multiple column - i = 1 - for (t, val) ∈ ohlc[1:3] - @test t == timestamp(ohlc)[i] - @test val == values(ohlc)[i, :] - i += 1 + let # multiple column + i = 1 + for (t, val) in ohlc[1:3] + @test t == timestamp(ohlc)[i] + @test val == values(ohlc)[i, :] + i += 1 + end end end - end - @testset "end keyword returns correct index" begin - @test timestamp(ohlc[end])[1] == timestamp(ohlc)[end] - end + @testset "end keyword returns correct index" begin + @test timestamp(ohlc[end])[1] == timestamp(ohlc)[end] + end - @testset "getindex on single Int and Date" begin - @test timestamp(ohlc[1]) == [Date(2000,1,3)] - @test timestamp(ohlc[Date(2000,1,3)]) == [Date(2000,1,3)] - end + @testset "getindex on single Int and Date" begin + @test timestamp(ohlc[1]) == [Date(2000, 1, 3)] + @test timestamp(ohlc[Date(2000, 1, 3)]) == [Date(2000, 1, 3)] + end - @testset "getindex on array of Int and Date" begin - @test timestamp(ohlc[[1,10]]) == [Date(2000,1,3), Date(2000,1,14)] - @test timestamp(ohlc[[Date(2000,1,3),Date(2000,1,14)]]) == [Date(2000,1,3), Date(2000,1,14)] - end + @testset "getindex on array of Int and Date" begin + @test timestamp(ohlc[[1, 10]]) == [Date(2000, 1, 3), Date(2000, 1, 14)] + @test timestamp(ohlc[[Date(2000, 1, 3), Date(2000, 1, 14)]]) == + [Date(2000, 1, 3), Date(2000, 1, 14)] + end - @testset "getindex on range of Int and Date" begin - irng = Int8(1):Int8(2):Int8(4) - drng = Date(2000,1,3):Day(1):Date(2000,1,4) - @test timestamp(ohlc[1:2]) == [Date(2000,1,3), Date(2000,1,4)] - @test timestamp(ohlc[1:2:4]) == [Date(2000,1,3), Date(2000,1,5)] - @test timestamp(ohlc[irng]) == [Date(2000,1,3), Date(2000,1,5)] - @test timestamp(ohlc[drng]) == [Date(2000,1,3), Date(2000,1,4)] - end + @testset "getindex on range of Int and Date" begin + irng = Int8(1):Int8(2):Int8(4) + drng = Date(2000, 1, 3):Day(1):Date(2000, 1, 4) + @test timestamp(ohlc[1:2]) == [Date(2000, 1, 3), Date(2000, 1, 4)] + @test timestamp(ohlc[1:2:4]) == [Date(2000, 1, 3), Date(2000, 1, 5)] + @test timestamp(ohlc[irng]) == [Date(2000, 1, 3), Date(2000, 1, 5)] + @test timestamp(ohlc[drng]) == [Date(2000, 1, 3), Date(2000, 1, 4)] + end - @testset "getindex on range of DateTime when only Date is in timestamp" begin - @test_throws( - MethodError, - ohlc[DateTime(2000,1,3,0,0,0)]) - @test_throws( - MethodError, - ohlc[[DateTime(2000,1,3,0,0,0),DateTime(2000,1,14,0,0,0)]]) - @test_throws( - MethodError, - ohlc[DateTime(2000,1,3,0,0,0):Day(1):DateTime(2000,1,4,0,0,0)]) - end + @testset "getindex on range of DateTime when only Date is in timestamp" begin + @test_throws(MethodError, ohlc[DateTime(2000, 1, 3, 0, 0, 0)]) + @test_throws( + MethodError, + ohlc[[DateTime(2000, 1, 3, 0, 0, 0), DateTime(2000, 1, 14, 0, 0, 0)]] + ) + @test_throws( + MethodError, + ohlc[DateTime(2000, 1, 3, 0, 0, 0):Day(1):DateTime(2000, 1, 4, 0, 0, 0)] + ) + end - @testset "getindex on range of Date" begin - @test length(cl[Date(2000,1,1):Day(1):Date(2001,12,31)]) == 500 - end + @testset "getindex on range of Date" begin + @test length(cl[Date(2000, 1, 1):Day(1):Date(2001, 12, 31)]) == 500 + end - @testset "getindex on single column name" begin - idx = Date(2000,1,3):Day(1):Date(2000,1,14) - @test size(values(ohlc[:Open]), 2) == 1 - @test size(values(ohlc[:Open][idx]), 1) == 10 - end + @testset "getindex on single column name" begin + idx = Date(2000, 1, 3):Day(1):Date(2000, 1, 14) + @test size(values(ohlc[:Open]), 2) == 1 + @test size(values(ohlc[:Open][idx]), 1) == 10 + end - @testset "getindex on multiple column name" begin - @test values(ohlc[:Open, :Close])[1] == 104.88 - @test values(ohlc[:Open, :Close])[2] == 108.25 - @test values(ohlc[:Open, :Close])[501] == 111.94 - end + @testset "getindex on multiple column name" begin + @test values(ohlc[:Open, :Close])[1] == 104.88 + @test values(ohlc[:Open, :Close])[2] == 108.25 + @test values(ohlc[:Open, :Close])[501] == 111.94 + end - @testset "getindex on 1d returns 1d object" begin - @test cl[1] isa TimeArray{Float64,1} - @test cl[1:2] isa TimeArray{Float64,1} - end + @testset "getindex on 1d returns 1d object" begin + @test cl[1] isa TimeArray{Float64,1} + @test cl[1:2] isa TimeArray{Float64,1} + end - @testset "getindex on a 1d Boolean TimeArray returns appropriate rows" begin - @test values(ohlc[op .> cl][2]) == values(ohlc[4]) - @test timestamp(ohlc[op[300:end] .> cl][2]) == timestamp(ohlc[303]) - # MethodError, Bool must be 1D-TimeArray - @test_throws MethodError ohlc[merge(op .> cl, op .< cl)] - end + @testset "getindex on a 1d Boolean TimeArray returns appropriate rows" begin + @test values(ohlc[op .> cl][2]) == values(ohlc[4]) + @test timestamp(ohlc[op[300:end] .> cl][2]) == timestamp(ohlc[303]) + # MethodError, Bool must be 1D-TimeArray + @test_throws MethodError ohlc[merge(op .> cl, op .< cl)] + end - @testset "getindex on Vector{Symbol}" begin - hl = [:High, :Low] - ta = ohlc[hl] + @testset "getindex on Vector{Symbol}" begin + hl = [:High, :Low] + ta = ohlc[hl] - @test size(values(ta)) == (length(timestamp(ohlc)), 2) - @test colnames(ta) == hl - end + @test size(values(ta)) == (length(timestamp(ohlc)), 2) + @test colnames(ta) == hl + end - @testset "2D getindex on [Vector{Int}, Vector{Symbol}]" begin - hl = [:High, :Low] - ta = ohlc[1:10, hl] + @testset "2D getindex on [Vector{Int}, Vector{Symbol}]" begin + hl = [:High, :Low] + ta = ohlc[1:10, hl] - @test size(values(ta)) == (10, 2) - @test colnames(ta) == hl + @test size(values(ta)) == (10, 2) + @test colnames(ta) == hl - ta = ohlc[10:end, hl] - @test size(values(ta)) == (length(timestamp(ohlc)) - 9, 2) - @test colnames(ta) == hl + ta = ohlc[10:end, hl] + @test size(values(ta)) == (length(timestamp(ohlc)) - 9, 2) + @test colnames(ta) == hl - ta = ohlc[:, hl] - @test size(values(ta)) == (length(timestamp(ohlc)), 2) - @test colnames(ta) == hl + ta = ohlc[:, hl] + @test size(values(ta)) == (length(timestamp(ohlc)), 2) + @test colnames(ta) == hl - # test KeyError - @test_throws KeyError ohlc[1:2, [:Unknown]] - @test_throws KeyError ohlc[1:end, [:Unknown]] - @test_throws KeyError ohlc[:, [:Unknown]] - end + # test KeyError + @test_throws KeyError ohlc[1:2, [:Unknown]] + @test_throws KeyError ohlc[1:end, [:Unknown]] + @test_throws KeyError ohlc[:, [:Unknown]] + end - @testset "2D getindex on [Integer, Vector{Symbol}]" begin - hl = [:High, :Low] - ta = ohlc[42, hl] - @test size(values(ta)) == (1, 2) - @test colnames(ta) == hl + @testset "2D getindex on [Integer, Vector{Symbol}]" begin + hl = [:High, :Low] + ta = ohlc[42, hl] + @test size(values(ta)) == (1, 2) + @test colnames(ta) == hl - ta = ohlc[end, hl] - @test size(values(ta)) == (1, 2) - @test colnames(ta) == hl + ta = ohlc[end, hl] + @test size(values(ta)) == (1, 2) + @test colnames(ta) == hl - @test_throws KeyError ohlc[42, [:Unknown]] - end + @test_throws KeyError ohlc[42, [:Unknown]] + end - @testset "2D getindex on [Vector{Int}, Symbol]" begin - ta = ohlc[1:42, :Open] - @test size(values(ta)) == (42, 1) - @test colnames(ta) == [:Open] + @testset "2D getindex on [Vector{Int}, Symbol]" begin + ta = ohlc[1:42, :Open] + @test size(values(ta)) == (42, 1) + @test colnames(ta) == [:Open] - ta = ohlc[42:43, :Close] - @test size(values(ta)) == (2, 1) - @test colnames(ta) == [:Close] + ta = ohlc[42:43, :Close] + @test size(values(ta)) == (2, 1) + @test colnames(ta) == [:Close] - @test_throws KeyError ohlc[1:42, :Unknown] - end + @test_throws KeyError ohlc[1:42, :Unknown] + end - @testset "2D getindex on [Integer, Symbol]" begin - ta = ohlc[42, :Open] - @test size(values(ta)) == (1, 1) - @test colnames(ta) == [:Open] + @testset "2D getindex on [Integer, Symbol]" begin + ta = ohlc[42, :Open] + @test size(values(ta)) == (1, 1) + @test colnames(ta) == [:Open] - @test_throws KeyError ohlc[42, :Unknown] - end + @test_throws KeyError ohlc[42, :Unknown] + end - @testset "Base.eachindex" begin - @test eachindex(cl) == 1:length(cl) - @test eachindex(ohlc) == 1:length(ohlc) + @testset "Base.eachindex" begin + @test eachindex(cl) == 1:length(cl) + @test eachindex(ohlc) == 1:length(ohlc) + end end -end + @testset "Base.ndims" begin + @test ndims(cl) == 1 + @test ndims(ohlc) == 2 + end -@testset "Base.ndims" begin - @test ndims(cl) == 1 - @test ndims(ohlc) == 2 -end + @testset "Base.eltype" begin + @test eltype(cl) == Tuple{Date,Float64} + @test eltype(ohlc) == Tuple{Date,Vector{Float64}} + @test eltype(cl .> 1) == Tuple{Date,Bool} + @test eltype(ohlc .> 1) == Tuple{Date,Vector{Bool}} + end -@testset "Base.eltype" begin - @test eltype(cl) == Tuple{Date,Float64} - @test eltype(ohlc) == Tuple{Date,Vector{Float64}} + @testset "Base.collect" begin + @testset "cl" begin + A = collect(cl) + @test A[1] == (Date(2000, 01, 03), 111.94) + @test A[2] == (Date(2000, 01, 04), 102.5) + @test A[3] == (Date(2000, 01, 05), 104.0) + end - @test eltype(cl .> 1) == Tuple{Date,Bool} - @test eltype(ohlc .> 1) == Tuple{Date,Vector{Bool}} -end + @testset "ohlc" begin + A = collect(ohlc) + @test A[1] == (Date(2000, 01, 03), [104.88, 112.5, 101.69, 111.94]) + @test A[2] == (Date(2000, 01, 04), [108.25, 110.62, 101.19, 102.5]) + @test A[3] == (Date(2000, 01, 05), [103.75, 110.56, 103.0, 104.0]) + end + end + @testset "Base.size" begin + @test size(ohlc) == (500, 4) + @test size(ohlc, 1) == 500 + @test size(ohlc, 2) == 4 -@testset "Base.collect" begin - @testset "cl" begin - A = collect(cl) - @test A[1] == (Date(2000, 01, 03), 111.94) - @test A[2] == (Date(2000, 01, 04), 102.5) - @test A[3] == (Date(2000, 01, 05), 104.0) + @test size(cl) == (500,) + @test size(cl, 1) == 500 + @test size(cl, 2) == 1 end - @testset "ohlc" begin - A = collect(ohlc) - @test A[1] == (Date(2000, 01, 03), [104.88, 112.5, 101.69, 111.94]) - @test A[2] == (Date(2000, 01, 04), [108.25, 110.62, 101.19, 102.5]) - @test A[3] == (Date(2000, 01, 05), [103.75, 110.56, 103.0, 104.0]) - end -end + @testset "equal" begin + @test cl == copy(cl) + @test cl ≠ ohlc # rely on fallback definition + @test cl ≠ lag(cl) + @test isequal(cl, copy(cl)) + @test !isequal(cl, ohlc) + @test !isequal(cl, lag(cl)) -@testset "Base.size" begin - @test size(ohlc) == (500, 4) - @test size(ohlc, 1) == 500 - @test size(ohlc, 2) == 4 + ds = collect(DateTime(2017, 12, 25):Day(1):DateTime(2017, 12, 31)) - @test size(cl) == (500,) - @test size(cl, 1) == 500 - @test size(cl, 2) == 1 -end + let # diff colnames + x = TimeArray(ds, 1:7, [:foo]) + y = TimeArray(ds, 1:7, [:bar]) + @test x != y + end + let # Float vs Int + x = TimeArray(ds, 1:7) + y = TimeArray(ds, 1.0:7) + @test x == y + end -@testset "equal" begin - @test cl == copy(cl) - @test cl ≠ ohlc # rely on fallback definition - @test cl ≠ lag(cl) + let # Date vs DateTime + ds2 = collect(Date(2017, 12, 25):Day(1):Date(2017, 12, 31)) + x = TimeArray(ds, 1:7, [:foo], :bar) + y = TimeArray(ds2, 1:7, [:foo], :bar) + @test x == y + end - @test isequal(cl, copy(cl)) - @test !isequal(cl, ohlc) - @test !isequal(cl, lag(cl)) + let # diff meta + x = TimeArray(ds, 1:7, [:foo], :bar) + y = TimeArray(ds, 1:7, [:foo], :baz) + @test x != y + end - ds = DateTime(2017, 12, 25):Day(1):DateTime(2017, 12, 31) |> collect + @testset "hash" begin + @test hash(cl) == hash(copy(cl)) + @test hash(ohlc) == hash(copy(ohlc)) - let # diff colnames - x = TimeArray(ds, 1:7, [:foo]) - y = TimeArray(ds, 1:7, [:bar]) - @test x != y - end + d = Dict(cl => 42) + @test d[cl] == 42 + @test d[copy(cl)] == 42 - let # Float vs Int - x = TimeArray(ds, 1:7) - y = TimeArray(ds, 1.0:7) - @test x == y + d = Dict(ohlc => 24) + @test d[ohlc] == 24 + @test d[copy(ohlc)] == 24 + end end - let # Date vs DateTime - ds2 = Date(2017, 12, 25):Day(1):Date(2017, 12, 31) |> collect - x = TimeArray(ds, 1:7, [:foo], :bar) - y = TimeArray(ds2, 1:7, [:foo], :bar) - @test x == y + # String shown in TimeArray header depends on Julia version + function disptype(ta::TimeArray) + datetype, datatype = eltype(ta).types + datatype = eltype(datatype) + n = ndims(ta) + datastr = repr(datatype) + datestr = repr(datetype) + + if VERSION < v"1.6" + str_repr = "TimeArray{$datastr,$n,$datestr,Array{$datastr,$n}}" + else + # In Julia 1.6 it shows with spaces and aliases + last = n > 1 ? "Matrix{$datastr}" : "Vector{$datastr}" + str_repr = "TimeArray{$datastr, $n, $datestr, $last}" + end + return str_repr end - let # diff meta - x = TimeArray(ds, 1:7, [:foo], :bar) - y = TimeArray(ds, 1:7, [:foo], :baz) - @test x != y - end + @testset "show methods don't throw errors" begin + io = IOBuffer() + let str = sprint(summary, cl) + out = "500×1 $(disptype(cl)) 2000-01-03 to 2001-12-31" + @test str == out + end + show(IOContext(io, :limit => true), MIME("text/plain"), cl) + let str = String(take!(io)) + out = """500×1 $(disptype(cl)) 2000-01-03 to 2001-12-31 + ┌────────────┬────────┐ + │ │ Close │ + ├────────────┼────────┤ + │ 2000-01-03 │ 111.94 │ + │ 2000-01-04 │ 102.5 │ + │ 2000-01-05 │ 104.0 │ + │ 2000-01-06 │ 95.0 │ + │ 2000-01-07 │ 99.5 │ + │ 2000-01-10 │ 97.75 │ + │ 2000-01-11 │ 92.75 │ + │ 2000-01-12 │ 87.19 │ + │ ⋮ │ ⋮ │ + │ 2001-12-20 │ 20.67 │ + │ 2001-12-21 │ 21.0 │ + │ 2001-12-24 │ 21.36 │ + │ 2001-12-26 │ 21.49 │ + │ 2001-12-27 │ 22.07 │ + │ 2001-12-28 │ 22.43 │ + │ 2001-12-31 │ 21.9 │ + └────────────┴────────┘ + 485 rows omitted""" + @test str == out + end - @testset "hash" begin - @test hash(cl) == hash(copy(cl)) - @test hash(ohlc) == hash(copy(ohlc)) + # my edits above seem to work -- now need to do for the rest, JJS 2/22/19 + let str = sprint(summary, ohlc) + out = "500×4 $(disptype(ohlc)) 2000-01-03 to 2001-12-31" + @test str == out + end + show(IOContext(io, :limit => true), MIME("text/plain"), ohlc) + let str = String(take!(io)) + out = """500×4 $(disptype(ohlc)) 2000-01-03 to 2001-12-31 + ┌────────────┬────────┬────────┬────────┬────────┐ + │ │ Open │ High │ Low │ Close │ + ├────────────┼────────┼────────┼────────┼────────┤ + │ 2000-01-03 │ 104.88 │ 112.5 │ 101.69 │ 111.94 │ + │ 2000-01-04 │ 108.25 │ 110.62 │ 101.19 │ 102.5 │ + │ 2000-01-05 │ 103.75 │ 110.56 │ 103.0 │ 104.0 │ + │ 2000-01-06 │ 106.12 │ 107.0 │ 95.0 │ 95.0 │ + │ 2000-01-07 │ 96.5 │ 101.0 │ 95.5 │ 99.5 │ + │ 2000-01-10 │ 102.0 │ 102.25 │ 94.75 │ 97.75 │ + │ 2000-01-11 │ 95.94 │ 99.38 │ 90.5 │ 92.75 │ + │ 2000-01-12 │ 95.0 │ 95.5 │ 86.5 │ 87.19 │ + │ ⋮ │ ⋮ │ ⋮ │ ⋮ │ ⋮ │ + │ 2001-12-20 │ 21.4 │ 21.47 │ 20.62 │ 20.67 │ + │ 2001-12-21 │ 21.01 │ 21.54 │ 20.8 │ 21.0 │ + │ 2001-12-24 │ 20.9 │ 21.45 │ 20.9 │ 21.36 │ + │ 2001-12-26 │ 21.35 │ 22.3 │ 21.14 │ 21.49 │ + │ 2001-12-27 │ 21.58 │ 22.25 │ 21.58 │ 22.07 │ + │ 2001-12-28 │ 21.97 │ 23.0 │ 21.96 │ 22.43 │ + │ 2001-12-31 │ 22.51 │ 22.66 │ 21.83 │ 21.9 │ + └────────────┴────────┴────────┴────────┴────────┘ + 485 rows omitted""" + @test str == out + end - d = Dict(cl => 42) - @test d[cl] == 42 - @test d[copy(cl)] == 42 + let str = sprint(summary, AAPL) + out = "8336×12 $(disptype(AAPL)) 1980-12-12 to 2013-12-31" + @test str == out + end + show(IOContext(io, :limit => true), MIME("text/plain"), AAPL) + let str = String(take!(io)) + out = """8336×12 $(disptype(AAPL)) 1980-12-12 to 2013-12-31 + ┌────────────┬────────┬────────┬────────┬────────┬───────────┬────────────┬───── + │ │ Open │ High │ Low │ Close │ Volume │ ExDividend │ Sp ⋯ + ├────────────┼────────┼────────┼────────┼────────┼───────────┼────────────┼───── + │ 1980-12-12 │ 28.75 │ 28.88 │ 28.75 │ 28.75 │ 2.0939e6 │ 0.0 │ ⋯ + │ 1980-12-15 │ 27.38 │ 27.38 │ 27.25 │ 27.25 │ 785200.0 │ 0.0 │ ⋯ + │ 1980-12-16 │ 25.38 │ 25.38 │ 25.25 │ 25.25 │ 472000.0 │ 0.0 │ ⋯ + │ 1980-12-17 │ 25.88 │ 26.0 │ 25.88 │ 25.88 │ 385900.0 │ 0.0 │ ⋯ + │ 1980-12-18 │ 26.62 │ 26.75 │ 26.62 │ 26.62 │ 327900.0 │ 0.0 │ ⋯ + │ 1980-12-19 │ 28.25 │ 28.38 │ 28.25 │ 28.25 │ 217100.0 │ 0.0 │ ⋯ + │ 1980-12-22 │ 29.62 │ 29.75 │ 29.62 │ 29.62 │ 166800.0 │ 0.0 │ ⋯ + │ 1980-12-23 │ 30.88 │ 31.0 │ 30.88 │ 30.88 │ 209600.0 │ 0.0 │ ⋯ + │ ⋮ │ ⋮ │ ⋮ │ ⋮ │ ⋮ │ ⋮ │ ⋮ │ ⋱ + │ 2013-12-20 │ 545.43 │ 551.61 │ 544.82 │ 549.02 │ 1.55862e7 │ 0.0 │ ⋯ + │ 2013-12-23 │ 568.0 │ 570.72 │ 562.76 │ 570.09 │ 1.79038e7 │ 0.0 │ ⋯ + │ 2013-12-24 │ 569.89 │ 571.88 │ 566.03 │ 567.67 │ 5.9841e6 │ 0.0 │ ⋯ + │ 2013-12-26 │ 568.1 │ 569.5 │ 563.38 │ 563.9 │ 7.286e6 │ 0.0 │ ⋯ + │ 2013-12-27 │ 563.82 │ 564.41 │ 559.5 │ 560.09 │ 8.0673e6 │ 0.0 │ ⋯ + │ 2013-12-30 │ 557.46 │ 560.09 │ 552.32 │ 554.52 │ 9.0582e6 │ 0.0 │ ⋯ + │ 2013-12-31 │ 554.17 │ 561.28 │ 554.0 │ 561.02 │ 7.9673e6 │ 0.0 │ ⋯ + └────────────┴────────┴────────┴────────┴────────┴───────────┴────────────┴───── + 6 columns and 8321 rows omitted""" + @test str == out + end + show(IOContext(io, :limit => true), MIME("text/plain"), AAPL; allcols=true) + let str = String(take!(io)) + out = """8336×12 $(disptype(AAPL)) 1980-12-12 to 2013-12-31 + ┌────────────┬────────┬────────┬────────┬────────┬───────────┬────────────┬────────────┬─────────┬─────────┬─────────┬──────────┬───────────┐ + │ │ Open │ High │ Low │ Close │ Volume │ ExDividend │ SplitRatio │ AdjOpen │ AdjHigh │ AdjLow │ AdjClose │ AdjVolume │ + ├────────────┼────────┼────────┼────────┼────────┼───────────┼────────────┼────────────┼─────────┼─────────┼─────────┼──────────┼───────────┤ + │ 1980-12-12 │ 28.75 │ 28.88 │ 28.75 │ 28.75 │ 2.0939e6 │ 0.0 │ 1.0 │ 3.37658 │ 3.39185 │ 3.37658 │ 3.37658 │ 1.67512e7 │ + │ 1980-12-15 │ 27.38 │ 27.38 │ 27.25 │ 27.25 │ 785200.0 │ 0.0 │ 1.0 │ 3.21568 │ 3.21568 │ 3.20041 │ 3.20041 │ 6.2816e6 │ + │ 1980-12-16 │ 25.38 │ 25.38 │ 25.25 │ 25.25 │ 472000.0 │ 0.0 │ 1.0 │ 2.98079 │ 2.98079 │ 2.96552 │ 2.96552 │ 3.776e6 │ + │ 1980-12-17 │ 25.88 │ 26.0 │ 25.88 │ 25.88 │ 385900.0 │ 0.0 │ 1.0 │ 3.03951 │ 3.05361 │ 3.03951 │ 3.03951 │ 3.0872e6 │ + │ 1980-12-18 │ 26.62 │ 26.75 │ 26.62 │ 26.62 │ 327900.0 │ 0.0 │ 1.0 │ 3.12642 │ 3.14169 │ 3.12642 │ 3.12642 │ 2.6232e6 │ + │ 1980-12-19 │ 28.25 │ 28.38 │ 28.25 │ 28.25 │ 217100.0 │ 0.0 │ 1.0 │ 3.31786 │ 3.33313 │ 3.31786 │ 3.31786 │ 1.7368e6 │ + │ 1980-12-22 │ 29.62 │ 29.75 │ 29.62 │ 29.62 │ 166800.0 │ 0.0 │ 1.0 │ 3.47876 │ 3.49403 │ 3.47876 │ 3.47876 │ 1.3344e6 │ + │ 1980-12-23 │ 30.88 │ 31.0 │ 30.88 │ 30.88 │ 209600.0 │ 0.0 │ 1.0 │ 3.62674 │ 3.64084 │ 3.62674 │ 3.62674 │ 1.6768e6 │ + │ ⋮ │ ⋮ │ ⋮ │ ⋮ │ ⋮ │ ⋮ │ ⋮ │ ⋮ │ ⋮ │ ⋮ │ ⋮ │ ⋮ │ ⋮ │ + │ 2013-12-20 │ 545.43 │ 551.61 │ 544.82 │ 549.02 │ 1.55862e7 │ 0.0 │ 1.0 │ 542.203 │ 548.347 │ 541.597 │ 545.772 │ 1.55862e7 │ + │ 2013-12-23 │ 568.0 │ 570.72 │ 562.76 │ 570.09 │ 1.79038e7 │ 0.0 │ 1.0 │ 564.64 │ 567.344 │ 559.431 │ 566.717 │ 1.79038e7 │ + │ 2013-12-24 │ 569.89 │ 571.88 │ 566.03 │ 567.67 │ 5.9841e6 │ 0.0 │ 1.0 │ 566.519 │ 568.497 │ 562.681 │ 564.312 │ 5.9841e6 │ + │ 2013-12-26 │ 568.1 │ 569.5 │ 563.38 │ 563.9 │ 7.286e6 │ 0.0 │ 1.0 │ 564.739 │ 566.131 │ 560.047 │ 560.564 │ 7.286e6 │ + │ 2013-12-27 │ 563.82 │ 564.41 │ 559.5 │ 560.09 │ 8.0673e6 │ 0.0 │ 1.0 │ 560.484 │ 561.071 │ 556.19 │ 556.777 │ 8.0673e6 │ + │ 2013-12-30 │ 557.46 │ 560.09 │ 552.32 │ 554.52 │ 9.0582e6 │ 0.0 │ 1.0 │ 554.162 │ 556.777 │ 549.053 │ 551.24 │ 9.0582e6 │ + │ 2013-12-31 │ 554.17 │ 561.28 │ 554.0 │ 561.02 │ 7.9673e6 │ 0.0 │ 1.0 │ 550.892 │ 557.96 │ 550.723 │ 557.701 │ 7.9673e6 │ + └────────────┴────────┴────────┴────────┴────────┴───────────┴────────────┴────────────┴─────────┴─────────┴─────────┴──────────┴───────────┘ + 8321 rows omitted""" + @test str == out + end - d = Dict(ohlc => 24) - @test d[ohlc] == 24 - @test d[copy(ohlc)] == 24 - end -end - - -# String shown in TimeArray header depends on Julia version -function disptype(ta::TimeArray) - datetype, datatype = eltype(ta).types - datatype = eltype(datatype) - n = ndims(ta) - datastr = repr(datatype) - datestr = repr(datetype) - - if VERSION < v"1.6" - str_repr = "TimeArray{$datastr,$n,$datestr,Array{$datastr,$n}}" - else - # In Julia 1.6 it shows with spaces and aliases - last = n > 1 ? "Matrix{$datastr}" : "Vector{$datastr}" - str_repr = "TimeArray{$datastr, $n, $datestr, $last}" - end - str_repr -end - -@testset "show methods don't throw errors" begin - io = IOBuffer() - let str = sprint(summary, cl) - out = "500×1 $(disptype(cl)) 2000-01-03 to 2001-12-31" - @test str == out - end - show(IOContext(io, :limit => true), MIME("text/plain"), cl) - let str = String(take!(io)) - out = """500×1 $(disptype(cl)) 2000-01-03 to 2001-12-31 -┌────────────┬────────┐ -│ │ Close │ -├────────────┼────────┤ -│ 2000-01-03 │ 111.94 │ -│ 2000-01-04 │ 102.5 │ -│ 2000-01-05 │ 104.0 │ -│ 2000-01-06 │ 95.0 │ -│ 2000-01-07 │ 99.5 │ -│ 2000-01-10 │ 97.75 │ -│ 2000-01-11 │ 92.75 │ -│ 2000-01-12 │ 87.19 │ -│ ⋮ │ ⋮ │ -│ 2001-12-20 │ 20.67 │ -│ 2001-12-21 │ 21.0 │ -│ 2001-12-24 │ 21.36 │ -│ 2001-12-26 │ 21.49 │ -│ 2001-12-27 │ 22.07 │ -│ 2001-12-28 │ 22.43 │ -│ 2001-12-31 │ 21.9 │ -└────────────┴────────┘ - 485 rows omitted""" - @test str == out - end + let str = sprint(summary, ohlc[1:4]) + out = "4×4 $(disptype(ohlc[1:4])) 2000-01-03 to 2000-01-06" + @test str == out + end + show(io, "text/plain", ohlc[1:4]) + let str = String(take!(io)) + out = """4×4 $(disptype(ohlc[1:4])) 2000-01-03 to 2000-01-06 + ┌────────────┬────────┬────────┬────────┬────────┐ + │ │ Open │ High │ Low │ Close │ + ├────────────┼────────┼────────┼────────┼────────┤ + │ 2000-01-03 │ 104.88 │ 112.5 │ 101.69 │ 111.94 │ + │ 2000-01-04 │ 108.25 │ 110.62 │ 101.19 │ 102.5 │ + │ 2000-01-05 │ 103.75 │ 110.56 │ 103.0 │ 104.0 │ + │ 2000-01-06 │ 106.12 │ 107.0 │ 95.0 │ 95.0 │ + └────────────┴────────┴────────┴────────┴────────┘""" + @test str == out + end - # my edits above seem to work -- now need to do for the rest, JJS 2/22/19 - let str = sprint(summary, ohlc) - out = "500×4 $(disptype(ohlc)) 2000-01-03 to 2001-12-31" - @test str == out - end - show(IOContext(io, :limit => true), MIME("text/plain"), ohlc) - let str = String(take!(io)) - out = """500×4 $(disptype(ohlc)) 2000-01-03 to 2001-12-31 -┌────────────┬────────┬────────┬────────┬────────┐ -│ │ Open │ High │ Low │ Close │ -├────────────┼────────┼────────┼────────┼────────┤ -│ 2000-01-03 │ 104.88 │ 112.5 │ 101.69 │ 111.94 │ -│ 2000-01-04 │ 108.25 │ 110.62 │ 101.19 │ 102.5 │ -│ 2000-01-05 │ 103.75 │ 110.56 │ 103.0 │ 104.0 │ -│ 2000-01-06 │ 106.12 │ 107.0 │ 95.0 │ 95.0 │ -│ 2000-01-07 │ 96.5 │ 101.0 │ 95.5 │ 99.5 │ -│ 2000-01-10 │ 102.0 │ 102.25 │ 94.75 │ 97.75 │ -│ 2000-01-11 │ 95.94 │ 99.38 │ 90.5 │ 92.75 │ -│ 2000-01-12 │ 95.0 │ 95.5 │ 86.5 │ 87.19 │ -│ ⋮ │ ⋮ │ ⋮ │ ⋮ │ ⋮ │ -│ 2001-12-20 │ 21.4 │ 21.47 │ 20.62 │ 20.67 │ -│ 2001-12-21 │ 21.01 │ 21.54 │ 20.8 │ 21.0 │ -│ 2001-12-24 │ 20.9 │ 21.45 │ 20.9 │ 21.36 │ -│ 2001-12-26 │ 21.35 │ 22.3 │ 21.14 │ 21.49 │ -│ 2001-12-27 │ 21.58 │ 22.25 │ 21.58 │ 22.07 │ -│ 2001-12-28 │ 21.97 │ 23.0 │ 21.96 │ 22.43 │ -│ 2001-12-31 │ 22.51 │ 22.66 │ 21.83 │ 21.9 │ -└────────────┴────────┴────────┴────────┴────────┘ - 485 rows omitted""" - @test str == out - end + let str = sprint(show, ohlc[1:0]) + @test str == "0×4 $(disptype(ohlc[1:0]))" + end + let str = sprint(summary, ohlc[1:0]) + @test str == "0×4 $(disptype(ohlc[1:0]))" + end + show(io, "text/plain", ohlc[1:0]) + let str = String(take!(io)) + @test str == "0×4 $(disptype(ohlc[1:0]))" + end - let str = sprint(summary, AAPL) - out = "8336×12 $(disptype(AAPL)) 1980-12-12 to 2013-12-31" - @test str == out - end - show(IOContext(io, :limit => true), MIME("text/plain"), AAPL) - let str = String(take!(io)) - out = """8336×12 $(disptype(AAPL)) 1980-12-12 to 2013-12-31 -┌────────────┬────────┬────────┬────────┬────────┬───────────┬────────────┬───── -│ │ Open │ High │ Low │ Close │ Volume │ ExDividend │ Sp ⋯ -├────────────┼────────┼────────┼────────┼────────┼───────────┼────────────┼───── -│ 1980-12-12 │ 28.75 │ 28.88 │ 28.75 │ 28.75 │ 2.0939e6 │ 0.0 │ ⋯ -│ 1980-12-15 │ 27.38 │ 27.38 │ 27.25 │ 27.25 │ 785200.0 │ 0.0 │ ⋯ -│ 1980-12-16 │ 25.38 │ 25.38 │ 25.25 │ 25.25 │ 472000.0 │ 0.0 │ ⋯ -│ 1980-12-17 │ 25.88 │ 26.0 │ 25.88 │ 25.88 │ 385900.0 │ 0.0 │ ⋯ -│ 1980-12-18 │ 26.62 │ 26.75 │ 26.62 │ 26.62 │ 327900.0 │ 0.0 │ ⋯ -│ 1980-12-19 │ 28.25 │ 28.38 │ 28.25 │ 28.25 │ 217100.0 │ 0.0 │ ⋯ -│ 1980-12-22 │ 29.62 │ 29.75 │ 29.62 │ 29.62 │ 166800.0 │ 0.0 │ ⋯ -│ 1980-12-23 │ 30.88 │ 31.0 │ 30.88 │ 30.88 │ 209600.0 │ 0.0 │ ⋯ -│ ⋮ │ ⋮ │ ⋮ │ ⋮ │ ⋮ │ ⋮ │ ⋮ │ ⋱ -│ 2013-12-20 │ 545.43 │ 551.61 │ 544.82 │ 549.02 │ 1.55862e7 │ 0.0 │ ⋯ -│ 2013-12-23 │ 568.0 │ 570.72 │ 562.76 │ 570.09 │ 1.79038e7 │ 0.0 │ ⋯ -│ 2013-12-24 │ 569.89 │ 571.88 │ 566.03 │ 567.67 │ 5.9841e6 │ 0.0 │ ⋯ -│ 2013-12-26 │ 568.1 │ 569.5 │ 563.38 │ 563.9 │ 7.286e6 │ 0.0 │ ⋯ -│ 2013-12-27 │ 563.82 │ 564.41 │ 559.5 │ 560.09 │ 8.0673e6 │ 0.0 │ ⋯ -│ 2013-12-30 │ 557.46 │ 560.09 │ 552.32 │ 554.52 │ 9.0582e6 │ 0.0 │ ⋯ -│ 2013-12-31 │ 554.17 │ 561.28 │ 554.0 │ 561.02 │ 7.9673e6 │ 0.0 │ ⋯ -└────────────┴────────┴────────┴────────┴────────┴───────────┴────────────┴───── - 6 columns and 8321 rows omitted""" - @test str == out - end - show(IOContext(io, :limit => true), MIME("text/plain"), AAPL; allcols=true) - let str = String(take!(io)) - out = """8336×12 $(disptype(AAPL)) 1980-12-12 to 2013-12-31 -┌────────────┬────────┬────────┬────────┬────────┬───────────┬────────────┬────────────┬─────────┬─────────┬─────────┬──────────┬───────────┐ -│ │ Open │ High │ Low │ Close │ Volume │ ExDividend │ SplitRatio │ AdjOpen │ AdjHigh │ AdjLow │ AdjClose │ AdjVolume │ -├────────────┼────────┼────────┼────────┼────────┼───────────┼────────────┼────────────┼─────────┼─────────┼─────────┼──────────┼───────────┤ -│ 1980-12-12 │ 28.75 │ 28.88 │ 28.75 │ 28.75 │ 2.0939e6 │ 0.0 │ 1.0 │ 3.37658 │ 3.39185 │ 3.37658 │ 3.37658 │ 1.67512e7 │ -│ 1980-12-15 │ 27.38 │ 27.38 │ 27.25 │ 27.25 │ 785200.0 │ 0.0 │ 1.0 │ 3.21568 │ 3.21568 │ 3.20041 │ 3.20041 │ 6.2816e6 │ -│ 1980-12-16 │ 25.38 │ 25.38 │ 25.25 │ 25.25 │ 472000.0 │ 0.0 │ 1.0 │ 2.98079 │ 2.98079 │ 2.96552 │ 2.96552 │ 3.776e6 │ -│ 1980-12-17 │ 25.88 │ 26.0 │ 25.88 │ 25.88 │ 385900.0 │ 0.0 │ 1.0 │ 3.03951 │ 3.05361 │ 3.03951 │ 3.03951 │ 3.0872e6 │ -│ 1980-12-18 │ 26.62 │ 26.75 │ 26.62 │ 26.62 │ 327900.0 │ 0.0 │ 1.0 │ 3.12642 │ 3.14169 │ 3.12642 │ 3.12642 │ 2.6232e6 │ -│ 1980-12-19 │ 28.25 │ 28.38 │ 28.25 │ 28.25 │ 217100.0 │ 0.0 │ 1.0 │ 3.31786 │ 3.33313 │ 3.31786 │ 3.31786 │ 1.7368e6 │ -│ 1980-12-22 │ 29.62 │ 29.75 │ 29.62 │ 29.62 │ 166800.0 │ 0.0 │ 1.0 │ 3.47876 │ 3.49403 │ 3.47876 │ 3.47876 │ 1.3344e6 │ -│ 1980-12-23 │ 30.88 │ 31.0 │ 30.88 │ 30.88 │ 209600.0 │ 0.0 │ 1.0 │ 3.62674 │ 3.64084 │ 3.62674 │ 3.62674 │ 1.6768e6 │ -│ ⋮ │ ⋮ │ ⋮ │ ⋮ │ ⋮ │ ⋮ │ ⋮ │ ⋮ │ ⋮ │ ⋮ │ ⋮ │ ⋮ │ ⋮ │ -│ 2013-12-20 │ 545.43 │ 551.61 │ 544.82 │ 549.02 │ 1.55862e7 │ 0.0 │ 1.0 │ 542.203 │ 548.347 │ 541.597 │ 545.772 │ 1.55862e7 │ -│ 2013-12-23 │ 568.0 │ 570.72 │ 562.76 │ 570.09 │ 1.79038e7 │ 0.0 │ 1.0 │ 564.64 │ 567.344 │ 559.431 │ 566.717 │ 1.79038e7 │ -│ 2013-12-24 │ 569.89 │ 571.88 │ 566.03 │ 567.67 │ 5.9841e6 │ 0.0 │ 1.0 │ 566.519 │ 568.497 │ 562.681 │ 564.312 │ 5.9841e6 │ -│ 2013-12-26 │ 568.1 │ 569.5 │ 563.38 │ 563.9 │ 7.286e6 │ 0.0 │ 1.0 │ 564.739 │ 566.131 │ 560.047 │ 560.564 │ 7.286e6 │ -│ 2013-12-27 │ 563.82 │ 564.41 │ 559.5 │ 560.09 │ 8.0673e6 │ 0.0 │ 1.0 │ 560.484 │ 561.071 │ 556.19 │ 556.777 │ 8.0673e6 │ -│ 2013-12-30 │ 557.46 │ 560.09 │ 552.32 │ 554.52 │ 9.0582e6 │ 0.0 │ 1.0 │ 554.162 │ 556.777 │ 549.053 │ 551.24 │ 9.0582e6 │ -│ 2013-12-31 │ 554.17 │ 561.28 │ 554.0 │ 561.02 │ 7.9673e6 │ 0.0 │ 1.0 │ 550.892 │ 557.96 │ 550.723 │ 557.701 │ 7.9673e6 │ -└────────────┴────────┴────────┴────────┴────────┴───────────┴────────────┴────────────┴─────────┴─────────┴─────────┴──────────┴───────────┘ - 8321 rows omitted""" - @test str == out - end + let str = sprint(show, TimeArray(Date[], [])) + @test str == "0×1 $(disptype(TimeArray(Date[], [])))" + end + show(io, "text/plain", TimeArray(Date[], [])) + let str = String(take!(io)) + @test str == "0×1 $(disptype(TimeArray(Date[], [])))" + end - let str = sprint(summary, ohlc[1:4]) - out = "4×4 $(disptype(ohlc[1:4])) 2000-01-03 to 2000-01-06" - @test str == out - end - show(io, "text/plain", ohlc[1:4]) - let str = String(take!(io)) - out = """4×4 $(disptype(ohlc[1:4])) 2000-01-03 to 2000-01-06 -┌────────────┬────────┬────────┬────────┬────────┐ -│ │ Open │ High │ Low │ Close │ -├────────────┼────────┼────────┼────────┼────────┤ -│ 2000-01-03 │ 104.88 │ 112.5 │ 101.69 │ 111.94 │ -│ 2000-01-04 │ 108.25 │ 110.62 │ 101.19 │ 102.5 │ -│ 2000-01-05 │ 103.75 │ 110.56 │ 103.0 │ 104.0 │ -│ 2000-01-06 │ 106.12 │ 107.0 │ 95.0 │ 95.0 │ -└────────────┴────────┴────────┴────────┴────────┘""" - @test str == out - end + let str = sprint(summary, lag(cl[1:2]; padding=true)) + out = "2×1 $(disptype(lag(cl[1:2], padding=true))) 2000-01-03 to 2000-01-04" + @test str == out + end + show(io, "text/plain", lag(cl[1:2]; padding=true)) + let str = String(take!(io)) + out = """2×1 $(disptype(lag(cl[1:2], padding=true))) 2000-01-03 to 2000-01-04 + ┌────────────┬────────┐ + │ │ Close │ + ├────────────┼────────┤ + │ 2000-01-03 │ NaN │ + │ 2000-01-04 │ 111.94 │ + └────────────┴────────┘""" + @test str == out + end + end # @testset "show methods don't throw errors" + @testset "getproperty" begin + let + @test cl.Close == cl[:Close] + @test_throws KeyError cl.NotFound + end - let str = sprint(show, ohlc[1:0]) - @test str == "0×4 $(disptype(ohlc[1:0]))" - end - let str = sprint(summary, ohlc[1:0]) - @test str == "0×4 $(disptype(ohlc[1:0]))" - end - show(io, "text/plain", ohlc[1:0]) - let str = String(take!(io)) - @test str == "0×4 $(disptype(ohlc[1:0]))" + let + @test ohlc.Open == ohlc[:Open] + @test ohlc.High == ohlc[:High] + @test ohlc.Low == ohlc[:Low] + @test ohlc.Close == ohlc[:Close] + @test_throws KeyError ohlc.NotFound + end end + @testset "colnames should be copied" begin + ts = Date(2019, 1, 1):Day(1):Date(2019, 1, 10) + data = (ts=ts, A=1:10) - let str = sprint(show, TimeArray(Date[], [])) - @test str == "0×1 $(disptype(TimeArray(Date[], [])))" - end - show(io, "text/plain", TimeArray(Date[], [])) - let str = String(take!(io)) - @test str == "0×1 $(disptype(TimeArray(Date[], [])))" - end - - let str = sprint(summary, lag(cl[1:2], padding=true)) - out = "2×1 $(disptype(lag(cl[1:2], padding=true))) 2000-01-03 to 2000-01-04" - @test str == out - end - show(io, "text/plain", lag(cl[1:2], padding=true)) - let str = String(take!(io)) - out = """2×1 $(disptype(lag(cl[1:2], padding=true))) 2000-01-03 to 2000-01-04 -┌────────────┬────────┐ -│ │ Close │ -├────────────┼────────┤ -│ 2000-01-03 │ NaN │ -│ 2000-01-04 │ 111.94 │ -└────────────┴────────┘""" - @test str == out - end -end # @testset "show methods don't throw errors" + ta = TimeArray(data; timestamp=:ts) + ta′ = ta[6:10] + colnames(ta′)[1] = :B -@testset "getproperty" begin - let - @test cl.Close == cl[:Close] - @test_throws KeyError cl.NotFound - end + @test length(ta) == 10 + @test length(ta′) == 5 - let - @test ohlc.Open == ohlc[:Open] - @test ohlc.High == ohlc[:High] - @test ohlc.Low == ohlc[:Low] - @test ohlc.Close == ohlc[:Close] - @test_throws KeyError ohlc.NotFound + @test colnames(ta) == [:A] + @test colnames(ta′) == [:B] end -end - - -@testset "colnames should be copied" begin - ts = Date(2019, 1, 1):Day(1):Date(2019, 1, 10) - data = (ts = ts, A = 1:10) - - ta = TimeArray(data, timestamp = :ts) - ta′ = ta[6:10] - - colnames(ta′)[1] = :B - - @test length(ta) == 10 - @test length(ta′) == 5 - - @test colnames(ta) == [:A] - @test colnames(ta′) == [:B] -end - - end # @testset "timearray" diff --git a/test/timeseriesrc.jl b/test/timeseriesrc.jl index 5e144ab8..aac7e676 100644 --- a/test/timeseriesrc.jl +++ b/test/timeseriesrc.jl @@ -3,42 +3,36 @@ using Test # this line because the const objects are not being exported include(joinpath(dirname(@__FILE__), "..", "src/.timeseriesrc.jl")) - @testset "timeseriesrc" begin - - -@testset "const values are set the package defaults" begin - @testset "DECIMALS" begin - @test DECIMALS == 4 + @testset "const values are set the package defaults" begin + @testset "DECIMALS" begin + @test DECIMALS == 4 + end + + @testset "MISSING" begin + @test MISSING == NAN + end end - @testset "MISSING" begin - @test MISSING == NAN - end -end - - -@testset "const values are correct" begin - @testset "NAN" begin - @test NAN == "NaN" - end + @testset "const values are correct" begin + @testset "NAN" begin + @test NAN == "NaN" + end - @testset "NA" begin - @test NA == "NA" - end + @testset "NA" begin + @test NA == "NA" + end - @testset "BLACKHOLE" begin - @test BLACKHOLE == "\u2B24" - end + @testset "BLACKHOLE" begin + @test BLACKHOLE == "\u2B24" + end - @testset "DOTCIRCLE" begin - @test DOTCIRCLE == "\u25CC" - end + @testset "DOTCIRCLE" begin + @test DOTCIRCLE == "\u25CC" + end - @testset "QUESTION" begin - @test QUESTION == "\u003F" + @testset "QUESTION" begin + @test QUESTION == "\u003F" + end end -end - - end # @testset "timeseriesrc"