This program simulates a simple stock market where the player trades shares in five fictional companies (AAA through EEE) starting with $10,000 in capital. Each trading day the player enters buy or sell quantities for each stock, with a 1% brokerage fee applied to all transactions. Stock prices change each turn using a combination of a market-wide trend factor, random noise, and occasional “bull” or “bear” events that apply a ±4 point shock to a randomly selected stock. The program also simulates dividend payments and stock splits, and tracks a running exchange average similar to a market index.
Program Analysis
Program Structure
The program is divided into several logical sections:
- Initialization (lines 10–120): Sets up string arrays for stock ticker names, defines the price-formatting expression string
A$, and jumps to the main setup block. - Currency Formatter Subroutine (lines 130–220): Converts a numeric value in
Ginto a formatted dollar stringR$with exactly two decimal places, returning the string length inL. - Game Setup (lines 230–780): Initialises stock prices, asks for the player name, optionally shows instructions, and waits for the player to press S to start.
- Main Display Loop (lines 790–2000): Displays stock prices, portfolio value, handles player transactions, updates prices, and checks for special events (dividends, splits).
- Price Update Subroutine (lines 2010–2500): Called via
GOSUB 2010each turn; updates all five stock prices with random noise, a trend factor, and occasional bull/bear shocks. - End Game (lines 2510–2800): Displays profit/loss summary and waits for the player to press S to return to BASIC.
Currency Formatting Subroutine (lines 130–220)
The subroutine at line 130 is the most algorithmically involved part of the program. It extracts integer and fractional parts of G using ABS, INT, and subtraction, then patches up the fractional portion to always produce two decimal digits. The logic at lines 170–200 handles three cases:
- If the fractional part is zero,
O$is set to"00". - If the fractional part is non-zero, it multiplies by 100 and converts to a string.
- If the resulting string has a decimal point in its second character (i.e., the value was less than 0.10), it multiplies by 1000 instead to recover a leading zero — a workaround for
STR$not zero-padding.
The formatted result is assembled as Z$+"."+O$ and its length stored in L for right-aligned TAB printing.
Stock Price Update Algorithm
The subroutine at line 2010 updates all five stock prices each day. It uses a market-wide factor A (a small positive or negative decimal, rerolled periodically by the sub-subroutine at line 2460) multiplied by the current price, plus a random component selected from four quantised levels (0, 0.25, 0.5, 0.75), plus an integer noise term INT(3-6*RND+.5) which ranges from −3 to +3.
Two “event” counters D1 and D2 track countdown timers for bull (BC=4) and bear (BC=-4) shocks. When a counter expires the shock is applied to one randomly chosen stock. The counters are initialised and reset via VAL A$, which evaluates the expression "INT(RND*4.99)+1" stored at line 50 to produce a random integer from 1 to 5.
Notable Techniques
VAL A$as a reusable RNG expression: The stringA$="INT (RND*4.99)+1"is evaluated withVALwherever a random integer from 1–5 is needed. This is a compact way to reuse an expression without a subroutine and also saves token memory.- Right-aligned currency printing: Values are right-aligned using
TAB (N-L)whereLis the length of the formatted string returned by the formatter subroutine, giving a columnar appearance without fixed-width formatting. - Flicker-free title animation: Lines 390–410 alternate printing an inverse-video version and a normal version of the title banner in a loop of 20 iterations, creating a simple blinking effect.
POKE 16437,255: This pokes the ZX81 system variable FRAMES (low byte) to reset the frame counter, effectively preventing an unwantedBREAKor timeout after aPAUSEstatement — a well-known ZX81 technique.- Random number truncation at line 1930:
VAL ((STR$ RND)(1 TO 4))takes the first four characters of the string representation of a random number, then evaluates it back to a float. This clips the value to at most three decimal places and is used to produce a scaled random value for dividend/split decisions.
Bugs and Anomalies
| Line | Issue |
|---|---|
720 | LET S=(5)=115 is syntactically malformed. The intent is LET S(5)=115 (initialise the fifth stock price). As written, this evaluates (5)=115 as a boolean (false=0) and assigns the result to the scalar variable S, leaving S(5) at its default value of 0. |
1980 | IF B>.93 should almost certainly be IF R>.93 (checking the random value for a stock split). B is not defined in this scope, so the condition likely always evaluates as false, meaning stock splits never trigger. |
1990 | The stock split doubles P(I) (shares held) but does not halve S(I) (price per share). A real split should halve the price and double the shares to keep total value constant; as written the player’s portfolio value doubles on a split. |
2110 | LET D2=D2-2 decrements D2 by 2 each day while D1 is decremented by 1. This means the bear-shock counter expires roughly twice as fast as the bull-shock counter, creating an asymmetric market bias. |
1570 | The oversell check IF -T(I)<=P(I) is inverted. When T(I) is negative (a sell order), -T(I) is the number of shares being sold. The condition allows the sale if the number sold is ≤ shares held, which is correct, but the logic only reaches this line when T(I)<=0 (line 1530), so a zero-share “sell” of 0 passes through without issue. |
Variable Summary
| Variable | Purpose |
|---|---|
A$ | Expression string for random integer 1–5, evaluated with VAL |
I$(5,3) | Ticker symbols for the five stocks |
S(5) | Current share prices |
P(5) | Shares held in portfolio |
T(5) | Transaction quantities entered by player |
C(5) | Daily price change per stock |
C | Cash balance |
A | Market trend multiplier (signed small decimal) |
D1, D2 | Countdown timers for bull/bear shock events |
G | Input value to currency formatter subroutine |
R$, L | Formatted string and its length, returned by formatter |
DY | Day counter |
F | Flag: 0 on first pass, 1 after first trading day |
EA | Exchange average (mean of five prices) |
Content
Source Code
10 REM STOCK MARKET SIMULATION
20 REM BY DONALD A. BURGIO
25 CLS
30 DIM I$(5,3)
40 DIM O$(2)
50 LET A$="INT (RND*4.99)+1"
60 LET I$(1)="AAA"
70 LET I$(2)="BBB"
80 LET I$(3)="CCC"
90 LET I$(4)="DDD"
100 LET I$(5)="EEE"
110 RAND
120 GOTO 230
130 LET X=ABS G
140 LET B=INT X
150 LET E=X-B
160 LET Z$=STR$ B
170 IF E=0 THEN LET O$="00"
180 IF E<>0 THEN LET O$=STR$ (100*E)
190 IF O$(2)="." THEN LET O$=STR$ ((1000*E)+.01)
200 LET R$=Z$+"."+O$
210 LET L=LEN R$
220 RETURN
230 LET A=INT ((RND/10)*100+.5)/100
240 DIM S(5)
250 DIM P(5)
260 DIM T(5)
270 DIM C(5)
280 LET TT=0
290 LET F=0
300 LET D1=0
310 LET D2=0
320 LET P=0
330 LET P2=0
340 LET DY=0
350 LET EA=0
360 SLOW
370 CLS
380 FOR I=1 TO 20
390 PRINT AT 0,0;"% % % % % % %T%H%E% %Z%X% %S%T%O%C%K% %E%X%C%H%A%N%G%E% % % % % "
400 PRINT AT 0,0;"% % % % % % THE ZX STOCK EXCHANGE% % % % % "
410 NEXT I
420 PRINT AT 3,0;"WHAT IS YOUR NAME?"
430 INPUT N$
440 PRINT AT 3,0;"DO YOU WANT INSTRUCTIONS? (Y/N)"
450 INPUT Z$
460 IF Z$="N" THEN PRINT AT 3,0;" "
470 IF Z$="N" THEN GOTO 680
480 CLS
490 PRINT "WELCOME TO THE ZX STOCK EXCHANGE"
500 PRINT TAB ((32-LEN N$)/2);N$+"."
510 PRINT "YOUR ACCOUNT CURRENTLY CONTAINS"
520 PRINT "$10,000. YOU MAY BUY OR SELL"
530 PRINT "STOCKS. A TABLE OF AVAILABLE "
540 PRINT "STOCK, THEIR PRICES, AND THE"
550 PRINT "NUMBER OF SHARES IN YOUR PORT-"
560 PRINT "FOLIO WILL BE PRINTED. FOLLOW-"
570 PRINT "ING THIS THE INITIALS OF EACH"
580 PRINT "STOCK WILL BE PRINTED. HERE YOU"
590 PRINT "INDICATE A TRANSACTION. TO BUY"
600 PRINT "A STOCK TYPE XXX, WHERE XXX IS"
610 PRINT "THE NUMBER OF SHARES YOU WISH TO"
620 PRINT "BUY. TO SELL TYPE -XXX, WHERE"
630 PRINT "-XXX IS THE NUMBER OF SHARES YOU"
640 PRINT "WISH TO SELL. A 1 PERCENT BRO-"
650 PRINT "KERAGE FEE WILL AUTOMATICALLY"
660 PRINT "BE CHARGED TO YOUR ACCOUNT."
670 PRINT TAB 11;"GOOD LUCK"
680 LET S(1)=130
690 LET S(2)=90
700 LET S(3)=120
710 LET S(4)=85
720 LET S=(5)=115
730 LET TR=VAL A$
740 PRINT AT 21,7;"PRESS S TO START."
750 IF INKEY$="S" THEN GOTO 790
760 PRINT AT 0,0;"%W%E%L%C%O%M%E% %T%O% %T%H%E% %Z%X% %S%T%O%C%K% %E%X%C%H%A%N%G%E"
770 PRINT AT 0,0;"WELCOME TO THE ZX STOCK EXCHANGE"
780 GOTO 750
790 IF RND>.5 THEN GOTO 810
800 LET A=-A
810 CLS
820 GOSUB 2010
830 LET C=10000
840 PRINT
850 PRINT "STOCK INT. $/SHARE"
860 PRINT "--------------------------------"
870 LET G=S(1)
880 GOSUB 130
890 PRINT "A AND A ASSOCIATES AAA ";TAB (32-L);R$
900 LET G=S(2)
910 GOSUB 130
920 PRINT "B AND B BUYERS BBB ";TAB (32-L);R$
930 LET G=S(3)
940 GOSUB 130
950 PRINT "C AND C COAL CO. CCC ";TAB (32-L);R$
960 LET G=S(4)
970 GOSUB 130
980 PRINT "D AND D DEVELOPERS DDD ";TAB (32-L);R$
990 LET G=S(5)
1000 GOSUB 130
1010 PRINT "E AND E ENERGY EEE ";TAB (32-L);R$
1020 LET TA=EA
1030 LET EA=0
1040 LET SA=0
1050 FOR I=1 TO 5
1060 LET EA=EA+S(I)
1070 LET SA=SA+S(I)*P(I)
1080 NEXT I
1090 LET EA=INT (100*(EA/5)+.5)/100
1100 LET NC=INT ((EA-TA)*100+.5)/100
1110 LET D=SA+C
1120 IF F THEN GOTO 1160
1130 PRINT
1140 PRINT "ZX STOCK EXCHANGE AVER.:";EA
1150 GOTO 1190
1160 PRINT
1170 PRINT "ZX STOCK EXCHANGE AVER.:";EA
1180 PRINT "NET CHANGE:";NC
1190 PRINT
1200 PRINT N$;":"
1210 LET SA=INT (100*SA+.5)/100
1220 LET G=SA
1230 GOSUB 130
1240 PRINT "STOCK ASSETS=$";TAB (24-L);R$
1250 LET C=INT (100*C+.5)/100
1260 LET G=C
1270 GOSUB 130
1280 PRINT "CASH ASSETS= $";TAB (24-L);R$
1290 LET D=INT (100*D+.5)/100
1300 LET G=D
1310 GOSUB 130
1320 PRINT "TOTAL ASSETS=$";TAB (24-L);R$
1330 PRINT
1340 IF NOT F THEN PAUSE 225
1350 IF NOT F THEN POKE 16437,255
1360 IF NOT F THEN GOTO 1400
1370 PRINT "DO YOU WISH TO CONTINUE? (Y/N)"
1380 INPUT C$
1390 IF C$="N" THEN GOTO 2510
1400 FOR I=10 TO 21
1410 PRINT AT I,0;" "
1420 NEXT I
1430 PRINT AT 11,0;"WHAT IS YOUR TRANSACTION IN:"
1440 FOR I=1 TO 5
1450 PRINT AT 12,0;I$(I);"?"
1460 INPUT T(I)
1470 NEXT I
1480 PRINT AT 20,0;"PLEASE WAIT..."
1490 LET DP=0
1500 LET DS=0
1510 FOR I=1 TO 5
1520 LET T(I)=INT (T(I)+.5)
1530 IF T(I)<=0 THEN GOTO 1560
1540 LET DP=DP+T(I)*S(I)
1550 GOTO 1620
1560 LET DS=DS-T(I)*S(I)
1570 IF -T(I)<=P(I) THEN GOTO 1620
1580 PRINT AT 20,0;"YOU HAVE OVERSOLD A STOCK; TRY AGAIN. "
1590 PAUSE 300
1600 POKE 16437,255
1610 GOTO 1400
1620 NEXT I
1630 LET TT=DP+DS
1640 LET BF=INT (.01*TT*100+.5)/100
1650 LET CT=C-DP-BF+DS
1660 IF CT>=0 THEN GOTO 1720
1670 PRINT AT 19,0;"YOU HAVE TRIED TO SPEND "
1680 PRINT "$";-CT;" MORE THAN YOU HAVE."
1690 PAUSE 300
1700 POKE 16437,255
1710 GOTO 1400
1720 LET C=CT
1730 FOR I=1 TO 5
1740 LET P(I)=P(I)+T(I)
1750 NEXT I
1760 CLS
1770 GOSUB 2010
1780 LET DY=DY+1
1790 PRINT "*** END OF TRADING: DAY ";DY;" ***"
1800 PRINT
1810 PRINT "STK. $/SHR. HDS. $ VALUE CHANGE"
1820 PRINT "................................"
1830 FOR I=1 TO 5
1840 LET G=S(I)
1850 GOSUB 130
1860 PRINT AT 3+I,0;I$(I);AT 3+I,(11-L);R$;AT 3+I,13;P(I);
1870 LET G=S(I)*P(I)
1880 GOSUB 130
1890 PRINT AT 3+I,(24-L);R$;AT 3+I,26;C(I)
1900 NEXT I
1910 LET F=1
1920 PRINT
1930 LET R=VAL ((STR$ RND)(1 TO 4))
1940 LET I=INT (RND*7)
1945 IF I>5 THEN GOTO 1940
1950 IF I=0 THEN GOTO 1940
1960 IF R<.15 THEN PRINT I$(I);" DECLARES DIVIDENDS OF $";(R*4+.5);"/SHARE"
1970 IF R<.15 THEN LET C=C+P(I)*(R*4+.5)
1980 IF B>.93 THEN PRINT I$(I);" SPLITS STOCK"
1990 IF R>.93 THEN LET P(I)=P(I)*2
2000 GOTO 1020
2010 FAST
2020 IF D1>0 THEN GOTO 2060
2030 LET S=VAL A$
2040 LET D1=VAL A$
2050 LET P=1
2060 IF D2>0 THEN GOTO 2100
2070 LET S2=VAL A$
2080 LET D2=VAL A$
2090 LET P2=1
2100 LET D1=D1-1
2110 LET D2=D2-2
2120 FOR I=1 TO 5
2130 LET R=RND
2140 IF R>.25 THEN GOTO 2170
2150 LET R=.25
2160 GOTO 2240
2170 IF R>.5 THEN GOTO 2200
2180 LET R=.5
2190 GOTO 2240
2200 IF R>.75 THEN GOTO 2230
2210 LET R=.75
2220 GOTO 2240
2230 LET R=0
2240 LET BC=0
2250 IF P<1 THEN GOTO 2290
2260 IF INT (S+.5)<>INT (I+.5) THEN GOTO 2290
2270 LET BC=4
2280 LET P=0
2290 IF P2<1 THEN GOTO 2330
2300 IF INT (S2+.5)<>INT (I+.5) THEN GOTO 2330
2310 LET BC=-4
2320 LET P2=0
2330 LET C(I)=INT (A*S(I))+R+INT (3-6*RND+.5)+BC
2340 LET C(I)=INT (100*C(I)+.5)/100
2350 LET S(I)=S(I)+C(I)
2360 IF S(I)>0 THEN GOTO 2400
2370 LET C(I)=0
2380 LET S(I)=0
2390 GOTO 2410
2400 LET S(I)=INT (100*S(I)+.5)/100
2410 NEXT I
2420 LET TR=TR-1
2430 IF TR<1 THEN GOSUB 2460
2440 SLOW
2450 RETURN
2460 LET TR=VAL A$
2470 LET A=INT ((RND/10)*100+.5)/100
2480 IF RND<=.5 THEN GOTO 2500
2490 LET A=-A
2500 RETURN
2510 CLS
2520 PRINT
2530 PRINT
2540 PRINT "AT THE END OF ";DY;" DAYS TRADING:"
2550 IF D>=10000 THEN GOTO 2610
2560 PRINT "YOU HAVE LOST $";
2570 LET G=10000-D
2580 GOSUB 130
2590 PRINT R$
2600 GOTO 2650
2610 PRINT "YOU HAVE MADE $";
2620 LET G=D-10000
2630 GOSUB 130
2640 PRINT R$
2650 PRINT "ON THE ZX STOCK EXCHANGE."
2660 PRINT
2670 PRINT "HOPE YOU HAD FUN, ";N$;"."
2680 PRINT "COME BACK AGAIN."
2690 PRINT AT 20,2;"PRESS S TO RETURN TO BASIC."
2700 PRINT AT 0,0;"%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$"
2710 PRINT AT 0,0;"$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$"
2720 PRINT AT 21,0;"%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$"
2730 IF INKEY$="S" THEN STOP
2740 PRINT AT 21,0;"$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$"
2750 GOTO 2700
2760 CLEAR
2770 STOP
2780 SAVE "1014%8"
2800 RUN
Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.
