Here are the annual returns for a hypothetical S&P 500 stock index fund for each calendar year from 1928 to 2017, expressed as a multiplier. So in 1928 you might say "the index went up by 37.88%" which I've represented here by 1.3788.

1.3788, 0.8809, 0.7152, 0.5293, 0.8485, 1.4659, 0.9406, 1.4137, 1.2792, 0.6141,  1.2521, 0.9455, 0.8471, 0.8214, 1.1243,  1.1945, 1.138, 1.3072, 0.8813, 1,  0.9935, 1.1026, 1.2178, 1.1646, 1.1178,  0.9338, 1.4502, 1.264, 1.0262, 0.8569,  1.3806, 1.0848, 0.9703, 1.2313, 0.8819,  1.1889, 1.1297, 1.0906, 0.8691, 1.2009,  1.0766, 0.8864, 1.001, 1.1079, 1.1563,  0.8263, 0.7028, 1.3155, 1.1915, 0.885,  1.0106, 1.1231, 1.2577, 0.9027, 1.1476,  1.1727, 1.014, 1.2633, 1.1462, 1.0203,  1.124, 1.2725, 0.9344, 1.2631, 1.0446,  1.0706, 0.9846, 1.3411, 1.2026, 1.3101,  1.2667, 1.1953, 0.8986, 0.8696, 0.7663,  1.2638, 1.0899, 1.03, 1.1362, 1.0353,  0.6151, 1.2345, 1.1278, 1, 1.1341,  1.296, 1.1139, 0.9927, 1.0954, 1.1942

Source: https://www.macrotrends.net/2526/sp-500-historical-annual-returns

Challenge

Given as inputs:

  • the array of annual returns (but see Rule 2. below)
  • an array \$X\$ of \$1\le K \le 90\$ positive numbers (the amounts to invest in each year of the investment period)

write a program or function that outputs what would have been the "best" \$K\$ consecutive year period to have invested the amounts in \$X\$, where:

  • each amount is invested at the beginning of a year.
  • anything left after each year is reinvested at the beginning of each subsequent year.
  • "best" means the largest amount at the end of the \$K\$ year period.

Rules

  1. This is , so the fewest bytes in each language wins. Standard rules apply. Explanations encouraged.

  2. If you don't like the way I've represented the array of annual returns, you can change them to any other array of 90 numbers that you prefer.

  3. You can output the best \$K\$ year period in any consistent manner (e.g. 0-indexed or 1-indexed, the first and/or the last year to have invested, etc.) but you need to say what your output represents if it isn't obvious.

  4. In case of a tie, output any or all correct answers.

Example calculation

Suppose \$X = [ 10000, 20000, 30000 ]\$.

If you had invested these amounts in 1928, 1929, and 1930 (the 1-indices 1, 2, and 3) you would have ended up with 42743.10. Sad.

But if you had invested those amounts in 2014, 2015, and 2016 (the 1-indices 87, 88, and 89) you would have ended up with 66722.66. A bit better.

It turns out the best three year period to have invested these amounts would have been 1995, 1996, and 1997 (1-indices 68, 69, and 70), resulting in 91942.91. Nice.

Test cases

1-indexed, first year of optimal period

[ 1, 2, 3 ]                          ->  68
[ 1 ]                                ->   6
* any array of length 90 *           ->   1
[ 1, 1 ]                             ->  27
[ 1, 2 ]                             ->   8
[ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ]  ->  62
[ 1, 2, 3, 4, 5, 4, 3, 2, 1 ]        ->  64
* 1 repeated 20 times *              ->  53
  • Rule 2 says we can replace them with any other array of 90 numbers. I assume it isn't valid to replace it with an array that is 1 followed by 89 0s, so that 1 is always the correct answer? Or did you mean "take whatever input you want, but the number that gets output has to match the test cases"? – Kamil Drakari Nov 21 at 19:06
  • 1
    @KamilDrakari you can do any 1-1 transformation you like. You do need to end up with the actual correct answer, though. The idea is that someone might prefer cumulative returns, or those returns minus 1. It's there for flexibility. But if there's also some hidden golfing opportunity, so be it. – ngm Nov 21 at 19:35
  • @ngm okay so it's "you can encode this input in whatever way you want" rather than "your answer only needs to work for a specific value for this input, and you get to choose what that is" – Kamil Drakari Nov 21 at 19:37
  • @KamilDrakari yes, although not quite whatever way you want - you are still limited to an array with 90 elements. – ngm Nov 21 at 19:47
  • "So in 1927 you might say..." - I think you mean 1928. – Jonathan Allan Nov 21 at 20:27

Jelly, 9 bytes

PÐƤ⁹L¤ƤḋM

A dyadic link which yields a list of all the maximal starting 1-based indices.

Try it online!

(or ṡL}PÐƤ€ḋM)

How?

PÐƤ⁹L¤ƤḋM - Link: list of returns, list of nominal investments
      Ƥ   - for infixes of (the returns)...
          - ...of length:
     ¤    -               nilad followed by links as a nilad:
   ⁹      -                 chain's right argument (nominals)
    L     -                 length
          - ...do:
 ÐƤ       -        for post-fixes:
P         -          product
       ḋ  - dot-product (each) with (the nominals)
        M - maximal indices

Japt, 25 bytes

ãVl)míV ®rÈ+YÌ *Yg}0
bUrw

Try it online!

0-indexed output.

Explanation, with U as the list of returns and V as the list of investments:

ã                       :Get subsections of U
 Vl)                    : with the same length as V
    m                   :For each of those sections
     í                  : pair each element with
      V                 : the corresponding element from V
        ®          0    :Calculate the investment results of each by:
         r        }     : for each year:
          È+            :  add the result of the previous year
            YÌ          :  to this year's investment
               *Yg      :  and multiply by this year's return

b                       :Get the index of
 Urw                    : the maximum

Bonus cheating answer:

Japt, 13 bytes

OvUÎò3 ®n dÃq

Try it online!

Takes the list of returns as this array, which is an array of 90 strings where each string contains a base-10 integer padded to 1929 digits; I believe this technically meets the requirements. The "Try it" link only has the first element of the "returns" list because the full thing broke the permalink generator, but that's the only one that gets used. You can paste in the whole thing from the pastebin if you want.

Explanation:

As mentioned, only the first element of the "returns" array is actually used; every other element is 0 in the pastebin and ignored by the program. The first element is generated by this, which takes the string representation of a Japt program, turns each character into a 3 digit number, and joins those numbers into a new string of just digits. The code being encoded is this, which is basically my main answer adjusted to hard-code the returns. Then the "answer" program is just:

  UÎ             :Get the first element
    ò3           :Cut it into slices of length 3
       ®n        :Convert those strings to numbers
          dà    :Convert those numbers to characters
            q    :Join all the characters into one string
Ov               :Execute it as Japt
  • I'm not sure if the 13 byte version follows the default rules (encoding actual code into flexible inputs seems like a loophole), but I like it anyway! – ngm Nov 22 at 2:13
  • @ngm I mean, I called it a cheat answer and tacked it on to a real answer for a reason. I was mainly just trying to stretch the limits of "any other array of 90 numbers that you prefer". – Kamil Drakari Nov 22 at 5:04

JavaScript (ES6), 73 bytes

Takes input as (annual_returns)(X). The result is 0-indexed.

a=>x=>a.map((_,i)=>x.map((c,j)=>(t=(t+c)*a[i+j])>m&&(r=i,m=t),t=0),m=0)|r

Try it online!

J, 48 bytes

1+[:(i.>./)[:({:@[*{.@[+*/@])/"2[(|.@,."1)#@[]\]

Try it online!

ungolfed

1 + [: (i. >./) [: ({:@[ * {.@[ + */@])/"2 [ |.@,."1 #@[ ]\ ]

how

Take input list (eg, 123) on left, data on right.

#@[ ]\ ] Chop up the data into infixes whose length matches the input:

1.3788 0.8809 0.7152
       0.8809 0.7152 0.5293
              0.7152 0.5293 0.8485

[ ,."1 now zip each of those groups of 3 with the input list

1      2      3
1.3788 0.8809 0.7152
       1      2      3
       0.8809 0.7152 0.5293
              1      2      3
              0.7152 0.5293 0.8485

|.@ and reverse them:

3      2      1
0.7152 0.8809 1.3788

(verb to insert)/"2 Between the items of each zipped group of 3, insert the verb in parentheses.

3             2             1
        VERB          VERB
0.7152        0.8809        1.3788

({:@[ * {.@[ + */@]) is the inserted verb, which will reduce each item of the list of zipped groups of threes down to one number, which is the amount earned over that group of years:

          2. plus the first      1. */] = product of right args
             (top) item on left
              +-+     +--------+
              |2|  +  |    1   |
              +-+     |    *   |     
                      | 1.3788 |
             *        +--------+

          0.8809  

          3. Multiply that sum by the bottom
             number on the left.

(i. >./) Take the list thus reduced and find the index of the max.

1 + [: And add one

Jelly, 11 bytes

ṡ⁹L¤U×\€UḋM

Try it online!

The return factor on a deposit is the cumulative product of the return factors in the years until the end of the K year period. If we're allowed to take the input in reverse and output a year number as 2018-[end date], the 10 bytes solution ×\⁹L¤Ƥ×S€M works.

ṡ                All overlapping slices of left input of length:
 ⁹L¤               length(right input)
                 Calculate cumulative returns list for each possible year:
    U              Reverse each element of the result
     ×\€           Take the cumulative product
        U          Reverse again
         ḋ      Dot product with the deposits list.
                The final balance when invested in each year.
          M     Get all indices of maximum values
  • ṡL} saves a byte over ṡ⁹L¤ (my alternative 9 byter, ṡL}PÐƤ€ḋM is effectively a more golfed version) – Jonathan Allan Nov 22 at 8:07
  • @JonathanAllan I'm still learning Jelly. How exactly does } work-- is it always equivalent to ⁹[monad]¤? – lirtosiast Nov 22 at 8:35
  • 1
    No, it's not exactly equivalent. The } quick treats the monad to its left as if it were a dyad and uses the right argument as the left input to that dyad (doing nothing with the left argument), as such the parsing of code containing L} is as if that were a dyad, while the parsing of the code containing ⁹L¤ is as if that were a nilad - if you try the replacement in Erik's code it wont work as the next part of the chain is a dyad (while the next in yours is a monad). – Jonathan Allan Nov 22 at 9:26

Python 2, 97 bytes

lambda r,v:max(range(len(r)-len(v)+1),key=lambda i:reduce(lambda x,(a,b):(x+a)*b,zip(v,r[i:]),0))

Try it online!

Returns 0-indexed

  • The last item of r should be 1.1942. Also, len(r) is 90. – Erik the Outgolfer Nov 21 at 16:41
  • @EriktheOutgolfer Fixed – TFeld Nov 22 at 8:02

05AB1E, 21 20 bytes

Œsgùεø0©\vy`s®+*©]Zk

Way too long..

0-indexed.

Try it online or verify all test cases.

Explanation:

Π                 # Get all sublists of the (implicit) input-list of stock-changes
 s                 # Swap to take the second (implicit) input-list of investments
  g                # And take its length
   ù               # Only leave sublists of that size
ε                  # Map each to:
 ø                 #  Create pairs with the second (implicit) input-list of investments
  0©               #  Store 0 in the register
    \              #  Discard the 0 from the stack
     vy            #  Loop `y` over the pairs
       `           #   Pop and push both values of the pair to the stack
        s          #   Swap them
         ®+        #   Add the value from the register to the investment amount
           *       #   And multiply it by the current number of the sublist
            ©      #   And then replace the value in the register with this
             ]     # Close both the loop and map
              Z    # Take the max of the mapped sublists (without popping the list)
               k   # Determine the index of that max (and output implicitly)

Jelly, 15 bytes

ṡ⁹L¤ż€+Ṫ×Ḣʋƒ€0M

Try it online!

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.