PortfolioUtils.jl (1601B)
1 @testset "Portfolio Return Calculations" begin 2 3 import Dates: Date, Month 4 5 # Create test data: 3 stocks, 12 months 6 dates = repeat(Date(2020,1,1):Month(1):Date(2020,12,1), inner=3) 7 df = DataFrame( 8 datem = dates, 9 permno = repeat([1, 2, 3], 12), 10 ret = rand(36) .* 0.1 .- 0.05, 11 mktcap = repeat([100.0, 200.0, 300.0], 12) 12 ) 13 14 # Equal-weighted returns 15 df_ew = calculate_portfolio_returns(df, :ret, :datem; weighting=:equal) 16 @test nrow(df_ew) == 12 17 @test "port_ret" in names(df_ew) 18 19 # Value-weighted returns 20 df_vw = calculate_portfolio_returns(df, :ret, :datem; 21 weighting=:value, weight_col=:mktcap) 22 @test nrow(df_vw) == 12 23 @test "port_ret" in names(df_vw) 24 25 # Grouped portfolios (e.g., by size group) 26 df.group = repeat([1, 1, 2], 12) 27 df_grouped = calculate_portfolio_returns(df, :ret, :datem; 28 weighting=:value, weight_col=:mktcap, 29 groups=:group) 30 @test nrow(df_grouped) == 24 # 12 months x 2 groups 31 32 # Error cases 33 @test_throws ArgumentError calculate_portfolio_returns(df, :ret, :datem; weighting=:value) 34 @test_throws ArgumentError calculate_portfolio_returns(df, :ret, :datem; weighting=:foo) 35 36 # Missing handling 37 allowmissing!(df, :ret) 38 df.ret[1] = missing 39 df_ew2 = calculate_portfolio_returns(df, :ret, :datem; weighting=:equal) 40 @test nrow(df_ew2) == 12 41 @test !ismissing(df_ew2.port_ret[1]) # should compute from non-missing stocks 42 43 end