Project P085

Stock chart

Daily prices in whole cents drawn as a chart: the best single buy and sell, which is the best run of daily changes (Kadane); every rise taken; and the best profit from at most k trades by dynamic programming, with the trades listed.

2 modules · 2 recorded sessionstext-menu UI in the terminalupdated 2026-10-15

Every screen below was recorded under CPython. When this page was built, the EML interpreter replayed each session from the same input and printed the same bytes.

About

Daily prices in whole cents, drawn as a chart. The program finds the best single buy and sell, which is the same as the best run of daily changes; the profit from taking every rise; and the best profit from at most k trades, with the trades listed. It starts with 16 days of prices.

  • main.eml - the menu, reading prices, the chart and the reports
  • market.eml - the best trade, the best run, daily changes, every rise, and the best k trades

How each part works:

  • Best single trade, as in the corpus case stock-buy-sell-profit: one pass, keeping the cheapest price so far. Not trading is allowed, so when nothing rises the best is 0.
  • Best run, as in the corpus case max-subarray-kadane: the largest sum of consecutive daily changes, keeping where the run starts. A trade's profit is the sum of the changes it holds through, so with any rise the two are the same. When every change is a fall, Kadane gives the smallest fall, while the best trade is still 0.
  • Every rise: as many trades as you like means taking each day's rise.
  • At most k trades, one share at a time, by dynamic programming over the days: the most made by day d with at most j trades is either the most by the day before, or a sale on day d of a share bought on an earlier day b, after the most made with j - 1 trades by day b. The trades are read back from the choices made.
  • The chart scales each price to a bar of 1 to 40 # from the lowest to the highest, in whole numbers.

What is checked: menu choices 1 to 5; 1 to 5 trades; 2 to 40 prices, each above 0 and up to 99999.99, with at most two decimals, separated by spaces. An empty answer cancels.

Sessions: sessions/basic.in gives: - the chart, and the best trade: buy on day 3, sell on day 15, 5.20 a share, the same as the best run of changes; - at most 1, 2 and 5 trades: 5.20, 7.55 and 10.00, the last being every rise; - prices that only fall (no trade, the smallest fall as the best run) and prices that stay flat.

sessions/bad-input.in gives: - menu choices 0 and x; - 0, 6 and abc trades; - one price, a word, a negative price, 0, three decimals, "12." and 41 prices - each refused, then cancelled.

Built on the verified corpus cases stock-buy-sell-profit (the cheapest price so far) and max-subarray-kadane (the best run, and why it differs when nothing rises).

Recorded sessions

What the screen shows while someone uses the program. Each typed line appears after its prompt, the way a terminal shows it.

bad-input

interpreter: byte-equal
== Stock chart ==
Daily prices: the best single trade, the best run of changes, and the best k trades.

1) chart  2) best trade  3) best k trades  4) new prices  5) quit
choice> 0
Pick a number from 1 to 5.

1) chart  2) best trade  3) best k trades  4) new prices  5) quit
choice> x
Pick a number from 1 to 5.

1) chart  2) best trade  3) best k trades  4) new prices  5) quit
choice> 3
at most how many trades (1 to 5)> 0
Type a whole number from 1 to 5.
at most how many trades (1 to 5)> 6
Type a whole number from 1 to 5.
at most how many trades (1 to 5)> abc
Type a whole number from 1 to 5.
at most how many trades (1 to 5)> 
Cancelled.

1) chart  2) best trade  3) best k trades  4) new prices  5) quit
choice> 4
prices, oldest first (2 to 40, like 12.40 13 11.85)> 12
Type 2 to 40 prices above 0, up to 99999.99, separated by spaces.
prices, oldest first (2 to 40, like 12.40 13 11.85)> 12 abc
Type 2 to 40 prices above 0, up to 99999.99, separated by spaces.
prices, oldest first (2 to 40, like 12.40 13 11.85)> 12 -3
Type 2 to 40 prices above 0, up to 99999.99, separated by spaces.
prices, oldest first (2 to 40, like 12.40 13 11.85)> 12 0
Type 2 to 40 prices above 0, up to 99999.99, separated by spaces.
prices, oldest first (2 to 40, like 12.40 13 11.85)> 12.345 13
Type 2 to 40 prices above 0, up to 99999.99, separated by spaces.
prices, oldest first (2 to 40, like 12.40 13 11.85)> 12. 13
Type 2 to 40 prices above 0, up to 99999.99, separated by spaces.
prices, oldest first (2 to 40, like 12.40 13 11.85)> 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
Type 2 to 40 prices above 0, up to 99999.99, separated by spaces.
prices, oldest first (2 to 40, like 12.40 13 11.85)> 
Cancelled.

1) chart  2) best trade  3) best k trades  4) new prices  5) quit
choice> 5
Bye.
What was typed (17 lines)
0
x
3
0
6
abc

4
12
12 abc
12 -3
12 0
12.345 13
12. 13
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1

5

basic

interpreter: byte-equal
== Stock chart ==
Daily prices: the best single trade, the best run of changes, and the best k trades.

1) chart  2) best trade  3) best k trades  4) new prices  5) quit
choice> 1
  day  1  12.50  ######
  day  2  13.10  ##########
  day  3  11.80  #
  day  4  12.25  ####
  day  5  14.05  #################
  day  6  13.90  ################
  day  7  15.30  ###########################
  day  8  14.80  #######################
  day  9  12.95  #########
  day 10  13.50  #############
  day 11  16.10  #################################
  day 12  15.75  ##############################
  day 13  14.40  ####################
  day 14  15.20  ##########################
  day 15  17.00  ########################################
  day 16  16.55  ####################################

1) chart  2) best trade  3) best k trades  4) new prices  5) quit
choice> 2
Best single trade: buy on day 3 at 11.80, sell on day 15 at 17.00 - 5.20 a share.
The best run of daily changes is days 3 to 15, adding up to 5.20.
The same thing: a trade's profit is the sum of the daily changes it holds through.
Trading every rise, as often as you like: 10.00.

1) chart  2) best trade  3) best k trades  4) new prices  5) quit
choice> 3
at most how many trades (1 to 5)> 1
  buy day 3 at 11.80, sell day 15 at 17.00: 5.20
At most 1 trade, one share at a time: 5.20 in all.

1) chart  2) best trade  3) best k trades  4) new prices  5) quit
choice> 3
at most how many trades (1 to 5)> 2
  buy day 3 at 11.80, sell day 7 at 15.30: 3.50
  buy day 9 at 12.95, sell day 15 at 17.00: 4.05
At most 2 trades, one share at a time: 7.55 in all.

1) chart  2) best trade  3) best k trades  4) new prices  5) quit
choice> 3
at most how many trades (1 to 5)> 5
  buy day 1 at 12.50, sell day 2 at 13.10: 0.60
  buy day 3 at 11.80, sell day 5 at 14.05: 2.25
  buy day 6 at 13.90, sell day 7 at 15.30: 1.40
  buy day 9 at 12.95, sell day 11 at 16.10: 3.15
  buy day 13 at 14.40, sell day 15 at 17.00: 2.60
At most 5 trades, one share at a time: 10.00 in all.

1) chart  2) best trade  3) best k trades  4) new prices  5) quit
choice> 4
prices, oldest first (2 to 40, like 12.40 13 11.85)> 10 9 8.5 7
4 days of prices.

1) chart  2) best trade  3) best k trades  4) new prices  5) quit
choice> 2
No single trade makes money: the price only falls (or stays). Not trading is the best.
The best run of daily changes is days 2 to 3, adding up to -0.50.
Every daily change is a fall, so the best run is the smallest fall - while the best trade is not to trade.
Trading every rise, as often as you like: 0.00.

1) chart  2) best trade  3) best k trades  4) new prices  5) quit
choice> 3
at most how many trades (1 to 5)> 2
No trade makes money.

1) chart  2) best trade  3) best k trades  4) new prices  5) quit
choice> 4
prices, oldest first (2 to 40, like 12.40 13 11.85)> 5 5 5
3 days of prices.

1) chart  2) best trade  3) best k trades  4) new prices  5) quit
choice> 2
No single trade makes money: the price only falls (or stays). Not trading is the best.
The best run of daily changes is days 1 to 2, adding up to 0.00.
No day rises, so the best run adds up to nothing - and the best trade is not to trade.
Trading every rise, as often as you like: 0.00.

1) chart  2) best trade  3) best k trades  4) new prices  5) quit
choice> 1
  day  1  5.00  #
  day  2  5.00  #
  day  3  5.00  #

1) chart  2) best trade  3) best k trades  4) new prices  5) quit
choice> 5
Bye.
What was typed (18 lines)
1
2
3
1
3
2
3
5
4
10 9 8.5 7
2
3
2
4
5 5 5
2
1
5

Modules

The program as written, entry module first. Each module transpiles to its own Python file, which is what eml project run executes.

main.eml(entry)

eml
# P085 stock chart: daily prices drawn as a chart; the best single trade,
# which is the best run of daily changes; and the best profit from up to k
# trades, with the trades listed.
import market

def trim(s):
    0 => i
    len(s) => j
    while i < j and s[i] == " ":
        i + 1 => i
    while j > i and s[j - 1] == " ":
        j - 1 => j
    return s[i:j]

def cents_of(s):
    # "12.40", "12.4" or "12" as whole cents (up to 99999.99), or -1.
    0 => k
    while k < len(s) and s[k] != ".":
        k + 1 => k
    s[0:k] => whole
    "" => part
    if k < len(s):
        s[k + 1:len(s)] => part
    if len(whole) == 0 or len(whole) > 5 or len(part) > 2 or (k < len(s) and len(part) == 0):
        return -1
    0 => n
    for c in whole + part:
        if not (c >= "0" and c <= "9"):
            return -1
        n * 10 + int(c) => n
    if len(part) == 0:
        return n * 100
    if len(part) == 1:
        return n * 10
    return n

def money(c):
    if c < 0:
        return "-" + money(0 - c)
    0 => w
    c => r
    while r >= 100:
        r - 100 => r
        w + 1 => w
    if r < 10:
        return str(w) + ".0" + str(r)
    return str(w) + "." + str(r)

def prices_of(s):
    [] => out
    "" => w
    for c in s + " ":
        if c == " " or c == ",":
            if w != "":
                cents_of(w) => v
                if v <= 0:
                    return []
                out + [v] => out
            "" => w
        else:
            w + c => w
    return out

def chart(ps):
    # One row per day, a bar of up to 40 # from the lowest price to the
    # highest.
    ps[0] => lo
    ps[0] => hi
    for p in ps:
        if p < lo:
            p => lo
        if p > hi:
            p => hi
    for d in [0:len(ps) - 1]:
        1 => n
        if hi > lo:
            # 1 + floor((price - lowest) x 39 / (highest - lowest)), in
            # whole numbers
            (ps[d] - lo) * 39 => r
            while r >= hi - lo:
                r - (hi - lo) => r
                n + 1 => n
        "" => bar
        for i in [1:n]:
            bar + "#" => bar
        "" => day
        if d + 1 < 10:
            " " => day
        ("  day " + day + str(d + 1) + "  " + money(ps[d]) + "  " + bar) ^0

def best(ps):
    market.best_trade(ps) => t
    if t[0] == 0:
        "No single trade makes money: the price only falls (or stays). Not trading is the best." ^0
    else:
        ("Best single trade: buy on day " + str(t[1] + 1) + " at " + money(ps[t[1]]) + ", sell on day " + str(t[2] + 1) + " at " + money(ps[t[2]]) + " - " + money(t[0]) + " a share.") ^0
    market.changes(ps) => cs
    market.best_run(cs) => r
    ("The best run of daily changes is days " + str(r[1] + 1) + " to " + str(r[2] + 2) + ", adding up to " + money(r[0]) + ".") ^0
    if r[0] > 0:
        "The same thing: a trade's profit is the sum of the daily changes it holds through." ^0
    elif r[0] == 0:
        "No day rises, so the best run adds up to nothing - and the best trade is not to trade." ^0
    else:
        "Every daily change is a fall, so the best run is the smallest fall - while the best trade is not to trade." ^0
    ("Trading every rise, as often as you like: " + money(market.every_rise(ps)) + ".") ^0

def trades(ps):
    while True:
        trim(input("at most how many trades (1 to 5)> ")) => s
        if s == "":
            "Cancelled." ^0
            return
        if s == "1" or s == "2" or s == "3" or s == "4" or s == "5":
            int(s) => k
            market.best_k(ps, k) => r
            if len(r[1]) == 0:
                "No trade makes money." ^0
                return
            for t in r[1]:
                ("  buy day " + str(t[0] + 1) + " at " + money(ps[t[0]]) + ", sell day " + str(t[1] + 1) + " at " + money(ps[t[1]]) + ": " + money(ps[t[1]] - ps[t[0]])) ^0
            "trades" => word
            if k == 1:
                "trade" => word
            ("At most " + str(k) + " " + word + ", one share at a time: " + money(r[0]) + " in all.") ^0
            return
        "Type a whole number from 1 to 5." ^0

def new_prices(ps):
    while True:
        trim(input("prices, oldest first (2 to 40, like 12.40 13 11.85)> ")) => s
        if s == "":
            "Cancelled." ^0
            return ps
        prices_of(s) => out
        if len(out) >= 2 and len(out) <= 40:
            (str(len(out)) + " days of prices.") ^0
            return out
        "Type 2 to 40 prices above 0, up to 99999.99, separated by spaces." ^0

[1250, 1310, 1180, 1225, 1405, 1390, 1530, 1480, 1295, 1350, 1610, 1575, 1440, 1520, 1700, 1655] => prices

"== Stock chart ==" ^0
"Daily prices: the best single trade, the best run of changes, and the best k trades." ^0
True => running
while running:
    "" ^0
    "1) chart  2) best trade  3) best k trades  4) new prices  5) quit" ^0
    trim(input("choice> ")) => choice
    if choice == "1":
        chart(prices)
    elif choice == "2":
        best(prices)
    elif choice == "3":
        trades(prices)
    elif choice == "4":
        new_prices(prices) => prices
    elif choice == "5":
        False => running
    else:
        "Pick a number from 1 to 5." ^0
"Bye." ^0
Python projection (main.py)
import market

def trim(s):
    i = 0
    j = len(s)
    while i < j and s[i] == " ":
        i = i + 1
    while j > i and s[j - 1] == " ":
        j = j - 1
    return s[i:j]

def cents_of(s):
    k = 0
    while k < len(s) and s[k] != ".":
        k = k + 1
    whole = s[0:k]
    part = ""
    if k < len(s):
        part = s[k + 1:len(s)]
    if len(whole) == 0 or len(whole) > 5 or len(part) > 2 or k < len(s) and len(part) == 0:
        return -1
    n = 0
    for c in whole + part:
        if not (c >= "0" and c <= "9"):
            return -1
        n = n * 10 + int(c)
    if len(part) == 0:
        return n * 100
    if len(part) == 1:
        return n * 10
    return n

def money(c):
    if c < 0:
        return "-" + money(0 - c)
    w = 0
    r = c
    while r >= 100:
        r = r - 100
        w = w + 1
    if r < 10:
        return str(w) + ".0" + str(r)
    return str(w) + "." + str(r)

def prices_of(s):
    out = []
    w = ""
    for c in s + " ":
        if c == " " or c == ",":
            if w != "":
                v = cents_of(w)
                if v <= 0:
                    return []
                out = out + [v]
            w = ""
        else:
            w = w + c
    return out

def chart(ps):
    lo = ps[0]
    hi = ps[0]
    for p in ps:
        if p < lo:
            lo = p
        if p > hi:
            hi = p
    for d in range(0, len(ps)):
        n = 1
        if hi > lo:
            r = (ps[d] - lo) * 39
            while r >= hi - lo:
                r = r - (hi - lo)
                n = n + 1
        bar = ""
        for i in range(1, n+1):
            bar = bar + "#"
        day = ""
        if d + 1 < 10:
            day = " "
        print("  day " + day + str(d + 1) + "  " + money(ps[d]) + "  " + bar)

def best(ps):
    t = market.best_trade(ps)
    if t[0] == 0:
        print("No single trade makes money: the price only falls (or stays). Not trading is the best.")
    else:
        print("Best single trade: buy on day " + str(t[1] + 1) + " at " + money(ps[t[1]]) + ", sell on day " + str(t[2] + 1) + " at " + money(ps[t[2]]) + " - " + money(t[0]) + " a share.")
    cs = market.changes(ps)
    r = market.best_run(cs)
    print("The best run of daily changes is days " + str(r[1] + 1) + " to " + str(r[2] + 2) + ", adding up to " + money(r[0]) + ".")
    if r[0] > 0:
        print("The same thing: a trade's profit is the sum of the daily changes it holds through.")
    elif r[0] == 0:
        print("No day rises, so the best run adds up to nothing - and the best trade is not to trade.")
    else:
        print("Every daily change is a fall, so the best run is the smallest fall - while the best trade is not to trade.")
    print("Trading every rise, as often as you like: " + money(market.every_rise(ps)) + ".")

def trades(ps):
    while True:
        s = trim(input("at most how many trades (1 to 5)> "))
        if s == "":
            print("Cancelled.")
            return
        if s == "1" or s == "2" or s == "3" or s == "4" or s == "5":
            k = int(s)
            r = market.best_k(ps, k)
            if len(r[1]) == 0:
                print("No trade makes money.")
                return
            for t in r[1]:
                print("  buy day " + str(t[0] + 1) + " at " + money(ps[t[0]]) + ", sell day " + str(t[1] + 1) + " at " + money(ps[t[1]]) + ": " + money(ps[t[1]] - ps[t[0]]))
            word = "trades"
            if k == 1:
                word = "trade"
            print("At most " + str(k) + " " + word + ", one share at a time: " + money(r[0]) + " in all.")
            return
        print("Type a whole number from 1 to 5.")

def new_prices(ps):
    while True:
        s = trim(input("prices, oldest first (2 to 40, like 12.40 13 11.85)> "))
        if s == "":
            print("Cancelled.")
            return ps
        out = prices_of(s)
        if len(out) >= 2 and len(out) <= 40:
            print(str(len(out)) + " days of prices.")
            return out
        print("Type 2 to 40 prices above 0, up to 99999.99, separated by spaces.")

prices = [1250, 1310, 1180, 1225, 1405, 1390, 1530, 1480, 1295, 1350, 1610, 1575, 1440, 1520, 1700, 1655]
print("== Stock chart ==")
print("Daily prices: the best single trade, the best run of changes, and the best k trades.")
running = True
while running:
    print("")
    print("1) chart  2) best trade  3) best k trades  4) new prices  5) quit")
    choice = trim(input("choice> "))
    if choice == "1":
        chart(prices)
    elif choice == "2":
        best(prices)
    elif choice == "3":
        trades(prices)
    elif choice == "4":
        prices = new_prices(prices)
    elif choice == "5":
        running = False
    else:
        print("Pick a number from 1 to 5.")
print("Bye.")

market.eml

eml
# P085 stock chart - a run of daily prices in whole cents: the best single
# buy and sell, the best run of daily changes, and the best profit from up
# to k trades, each holding at most one share at a time.

def best_trade(prices):
    # The corpus case stock-buy-sell-profit: one pass keeping the cheapest
    # price so far. Returns [profit, buy day, sell day] (days from 0), or
    # [0, -1, -1] when no trade makes money - not trading is allowed.
    0 => best
    -1 => buy
    -1 => sell
    0 => low
    for d in [1:len(prices) - 1]:
        if prices[d] - prices[low] > best:
            prices[d] - prices[low] => best
            low => buy
            d => sell
        if prices[d] < prices[low]:
            d => low
    return [best, buy, sell]

def best_run(xs):
    # The corpus case max-subarray-kadane, keeping where the run starts:
    # the largest sum of consecutive numbers (at least one), and its first
    # and last index. With every number negative it is the least negative.
    xs[0] => best
    0 => bs
    0 => be
    xs[0] => cur
    0 => cs
    for i in [1:len(xs) - 1]:
        if cur + xs[i] < xs[i]:
            xs[i] => cur
            i => cs
        else:
            cur + xs[i] => cur
        if cur > best:
            cur => best
            cs => bs
            i => be
    return [best, bs, be]

def changes(prices):
    [0] * (len(prices) - 1) => out
    for d in [1:len(prices) - 1]:
        prices[d] - prices[d - 1] => out[d - 1]
    return out

def every_rise(prices):
    # As many trades as you like: take every rise from one day to the next.
    0 => s
    for c in changes(prices):
        if c > 0:
            s + c => s
    return s

def best_k(prices, k):
    # At most k trades, by dynamic programming over the days. best[j][d] is
    # the most that can be made by day d with at most j trades: either day
    # d does nothing, or it sells a share bought on an earlier day b, with
    # best[j - 1][b] made before buying. `hold` keeps the best of
    # best[j - 1][b] - price[b] so far. Returns [profit, trades as [buy day,
    # sell day]], the trades read back from the choices made.
    len(prices) => n
    # best[j][d]: the most made by day d with at most j trades closed
    [0] * (k + 1) => best
    for j in [0:k]:
        [0] * n => best[j]
    # came[j][d]: the buy day of the trade closing on day d, or -1 if day d
    # closed none
    [0] * (k + 1) => came
    for j in [0:k]:
        [-1] * n => came[j]
    for j in [1:k]:
        0 - prices[0] => hold
        0 => hold_day
        for d in [1:n - 1]:
            best[j][d - 1] => keep
            prices[d] + hold => sell
            if sell > keep:
                sell => best[j][d]
                hold_day => came[j][d]
            else:
                keep => best[j][d]
            if best[j - 1][d] - prices[d] > hold:
                best[j - 1][d] - prices[d] => hold
                d => hold_day
    [] => trades
    k => j
    n - 1 => d
    while j > 0 and d > 0:
        if came[j][d] == -1:
            d - 1 => d
        else:
            [[came[j][d], d]] + trades => trades
            came[j][d] => d
            j - 1 => j
    return [best[k][n - 1], trades]
Python projection (market.py)
def best_trade(prices):
    best = 0
    buy = -1
    sell = -1
    low = 0
    for d in range(1, len(prices)):
        if prices[d] - prices[low] > best:
            best = prices[d] - prices[low]
            buy = low
            sell = d
        if prices[d] < prices[low]:
            low = d
    return [best, buy, sell]

def best_run(xs):
    best = xs[0]
    bs = 0
    be = 0
    cur = xs[0]
    cs = 0
    for i in range(1, len(xs)):
        if cur + xs[i] < xs[i]:
            cur = xs[i]
            cs = i
        else:
            cur = cur + xs[i]
        if cur > best:
            best = cur
            bs = cs
            be = i
    return [best, bs, be]

def changes(prices):
    out = [0] * (len(prices) - 1)
    for d in range(1, len(prices)):
        out[d - 1] = prices[d] - prices[d - 1]
    return out

def every_rise(prices):
    s = 0
    for c in changes(prices):
        if c > 0:
            s = s + c
    return s

def best_k(prices, k):
    n = len(prices)
    best = [0] * (k + 1)
    for j in range(0, k+1):
        best[j] = [0] * n
    came = [0] * (k + 1)
    for j in range(0, k+1):
        came[j] = [-1] * n
    for j in range(1, k+1):
        hold = 0 - prices[0]
        hold_day = 0
        for d in range(1, n):
            keep = best[j][d - 1]
            sell = prices[d] + hold
            if sell > keep:
                best[j][d] = sell
                came[j][d] = hold_day
            else:
                best[j][d] = keep
            if best[j - 1][d] - prices[d] > hold:
                hold = best[j - 1][d] - prices[d]
                hold_day = d
    trades = []
    j = k
    d = n - 1
    while j > 0 and d > 0:
        if came[j][d] == -1:
            d = d - 1
        else:
            trades = [[came[j][d], d]] + trades
            d = came[j][d]
            j = j - 1
    return [best[k][n - 1], trades]

Built on these corpus cases

ΣEML 2026

Efficient New Language — a high-density semantic overlay for humans and AI agents.

This site’s demos run on the real EML toolchain, in your browser.

© 2026 EveMissLab (一言諾科技有限公司) / Neo.K. All rights reserved.