--- title: "Stock Projector" id: 57653 type: "computer_media" slug: "stock-projector" url: "http://localhost/computer_media/stock-projector/" markdown_url: "http://localhost/computer_media/stock-projector.md" published_at: "2024-10-06T01:31:55+00:00" modified_at: "2026-04-03T07:58:08+00:00" author: "David Anderson" featured_image: url: "http://localhost/wp-content/uploads/2024/10/stock-projector.png" excerpt: "A complete stock-price database with moving-average charting, tape save, and in-string record editing — all packed into one string variable." category: - name: "Archived Media" slug: "archived-media" taxonomy: "category" url: "http://localhost/category/archived-media/" post_tag: - name: "Downloadable" slug: "downloadable" taxonomy: "post_tag" url: "http://localhost/tag/downloadable/" - name: "TS 1000" slug: "ts1000" taxonomy: "post_tag" url: "http://localhost/tag/ts1000/" model: - name: "Timex/Sinclair 1000" slug: "ts-1000" taxonomy: "model" url: "http://localhost/model/ts-1000/" indiv: - name: "Gene G. Buza" slug: "gene-g-buza" taxonomy: "indiv" url: "http://localhost/indiv/gene-g-buza/" genre: - name: "Finance" slug: "finance" taxonomy: "genre" url: "http://localhost/type/finance/" media_contents: - id: 56728 title: "Synchro-Sette September 1983" type: "computer_media" url: "http://localhost/computer_media/synchro-sette-september-1983/" media_type: "Program" programmers: - name: "Gene G. Buza" slug: "gene-g-buza" taxonomy: "indiv" url: "http://localhost/indiv/gene-g-buza/" mediadate: "September 1983" images: - url: "http://localhost/wp-content/uploads/2024/10/stock-projector.png" media_type_tags: "Finance" --- # Stock Projector This program is a stock price analysis tool that stores date/price quotation pairs in a single packed string variable, using ASCII delimiters (CHR$ 67 for dates, CHR$ 68 for prices, CHR$ 111 as record terminators) to form a simple flat-file database entirely in RAM. The main menu offers six functions: data entry, a 5-day moving-average peak search with a scaled ASCII bar chart, a 20-day quotation viewer, file initialisation, tape save, and record editing. The peak-search routine normalises each moving-average value to a 24-character bar using repeated “%” and “.” characters, and can optionally produce hard copy via LPRINT. The edit function (line 6000) performs an in-string search-and-replace on A$ to correct a mis-entered date or price, updating both the packed string and the parallel DIM arrays. *** ## Program Structure The program is organised around a main menu at line 100 with six branches dispatched by `GOTO VAL Y$*1000`, mapping the digit keys 1–6 directly to line blocks 1000, 2000, 3000, 4000, 5000, and 6000. Initialisation of key globals occurs at lines 10–40 before the menu loop is entered. | Line range | Function | | --- | --- | | 10–40 | Global initialisation (A$, SM, LG, N) | | 100–250 | Main menu display and key dispatch | | 1000–1520 | Data entry / append records to A$ | | 1800–1940 | Subroutine: display last entry | | 2000–2570 | Peak search: build arrays, compute 5-day MA, bar chart | | 2900–2960 | Utility subroutine (date/price lookup by record number — unreachable, preceded by STOP at 2899) | | 3000–3210 | 20-day quotation viewer (relies on arrays built in section 2000) | | 4000 | File initialise (RUN) | | 5000–5040 | Tape SAVE | | 6000–6430 | Edit/correct an existing record | | 9997–9999 | Development remnant: STOP, SAVE “STOCK PROJECTOR”, RUN | ### Data Storage Idiom All records are packed into a single string `A$`, with the first three characters storing the record count as a zero-padded decimal string. Each record has the form: - `STR$ N` — record number (1–3 digits) - `CHR$ 67` — field delimiter (‘C’) - 8-character date string - `CHR$ 68` — field delimiter (‘D’) - price quote string - `CHR$ 111` — record terminator (‘o’) These delimiter values are ordinary printable ASCII characters, meaning a date or price string accidentally containing ‘C’, ‘D’, or ‘o’ would corrupt parsing. The count field is updated in place at line 1510 via `LET A$(1 TO 3)=STR$ (N-1)`. ### Key Dispatch Menu selection uses `INKEY$` at line 210 and validates the key code with `IF CODE Y$<29 OR CODE Y$>34`. On the ZX81/TS1000, the digit characters ‘1’–’6′ have codes 29–34, so this acts as a range check. The branch `GOTO VAL Y$*1000` at line 250 elegantly converts the character to a line number without an explicit lookup table. ### 5-Day Moving Average and Bar Chart Section 2000 first parses all records into numeric array `S(X)` and string array `B$(X,8)`. It then computes a 5-day centred moving average into array `Q(XX)` where `XX = X-4`, running from index 3 to XX (line 2330). During this pass it also tracks the global minimum `SM` and maximum `LG` to enable normalisation. The bar chart subroutine at line 2500 normalises each `Q(N)` value to a 0–24 integer (`LQ`) and builds a string of that many `"%."` pairs. Each bar is printed on a scrolled line with the date label prepended. Hard copy is triggered if `Y$(1)="Y"` was answered at the prompt on line 2106. ### Edit Function The edit routine (line 6000) searches `A$` for the concatenation of the old date, `CHR$ 68`, and the old price (`Y$`), then checks the following character is `CHR$ 111` to confirm a complete record match. The record number is recovered by inspecting the 1–3 characters immediately before the preceding `CHR$ 111` delimiter at lines 6202–6206. The packed string is then rebuilt in place at line 6400 using string slicing and concatenation. The parallel arrays `B$(AA)` and `S(AA)` are also updated at lines 6255 and 6315. ### Notable Techniques and Bugs - **FAST/SLOW toggling:**`FAST` is used around computation-heavy and display-setup sections; `SLOW` is restored for user-facing prompts and input, the standard ZX81 idiom to avoid display artifacts during processing. - **VAL A$( TO 3):** The record count is retrieved by slicing and converting the first three characters, avoiding a separate numeric variable that could be lost on `RUN`. - **Unreachable code:** Lines 2900–2960 are a date/price lookup subroutine that can never be reached because line 2899 is `STOP`. It appears to be a debugging remnant. - **Section 3000 depends on section 2000:** The 20-day viewer uses arrays `B$`, `S`, and variable `X` that are only populated when the peak search (option 2) has been run first. Choosing option 3 without first running option 2 will cause an error. - **Delimiter collision risk:** CHR$ 67 (‘C’), CHR$ 68 (‘D’), and CHR$ 111 (‘o’) are common characters. A date or stock name containing these letters would silently break all parsing routines. - **PAUSE 40000 as keypress wait:** Used throughout as a long blocking pause to hold a message on screen, effectively waiting for BREAK or Enter rather than a specific key. ## Source Code ``` 10 LET A$="000"+CHR$ 111 20 LET SM=99999999 30 LET N=0 40 LET LG=0 100 FAST 110 CLS 120 PRINT AT 1,7;"% %S%T%O%C%K% %P%R%O%J%E%C%T%O%R% " 130 PRINT AT 5,0;"TO INPUT OR ADD DATA";TAB 30;1 140 PRINT AT 7,0;"TO PERFORM PEAK SEARCH";TAB 30;2 150 PRINT AT 9,0;"TO SEE 20 DAY 'S QUOTATIONS";TAB 30;3 160 PRINT AT 11,0;"TO INITIALIZE FILE";TAB 30;4 170 PRINT AT 13,0;"TO SAVE ON TAPE";TAB 30;5 180 PRINT AT 15,0;"TO EDIT DATA";TAB 30;6 190 SLOW 200 PRINT AT 21,6;" ENTER ONE OF ABOVE ";AT 21,6;"% %E%N%T%E%R% %O%N%E% %O%F% %A%B%O%V%E% " 210 LET Y$=INKEY$ 220 IF CODE Y$<29 OR CODE Y$>34 THEN GOTO 200 230 FAST 240 CLS 250 GOTO VAL Y$*1000 1000 LET N=VAL A$( TO 3) 1002 IF LEN A$>4 THEN GOSUB 1800 1005 LET N=N+1 1010 SCROLL 1015 PRINT "................................" 1017 SCROLL 1020 PRINT "ENTER DATE FOR QUOTE NO.";N 1025 SLOW 1030 SCROLL 1040 PRINT "(JUST PRESS ENTER IF NO MORE)" 1045 SCROLL 1047 PRINT "................................" 1048 DIM N$(8) 1050 INPUT D$ 1052 IF D$="" THEN GOTO 1500 1055 LET N$=D$ 1060 LET D$=N$ 1070 SCROLL 1080 SCROLL 1090 PRINT "(";N;") - (";D$;")" 1100 SCROLL 1110 SCROLL 1120 PRINT "ENTER PRICE QUOTE FOR THAT DAY." 1130 INPUT Q$ 1140 SCROLL 1150 SCROLL 1160 PRINT "(";N;") - (";D$;") - (";Q$;")" 1170 SCROLL 1180 SCROLL 1190 SCROLL 1200 LET A$=A$+STR$ N+CHR$ 67+D$+CHR$ 68+Q$+CHR$ 111 1210 GOTO 1005 1500 FAST 1510 LET A$(1 TO 3)=STR$ (N-1) 1520 GOTO 100 1800 PRINT AT 18,0;"LAST ENTRY WAS :::" 1810 FOR I=LEN A$-1 TO 4 STEP -1 1820 IF A$(I)=CHR$ 111 THEN GOTO 1840 1830 NEXT I 1840 FOR A=I+1 TO LEN A$-1 1850 IF A$(A)=CHR$ 67 THEN GOTO 1870 1860 NEXT A 1870 LET Z$=A$(I+1 TO A-1) 1880 FOR I=A+1 TO LEN A$-1 1890 IF A$(I)=CHR$ 68 THEN GOTO 1910 1900 NEXT I 1910 LET D$=A$(A+1 TO I-1) 1920 LET Q$=A$(I+1 TO LEN A$-1) 1930 PRINT "(";Z$;") - (";D$;") - (";Q$;")" 1940 RETURN 2000 IF VAL A$( TO 3)>27 THEN GOTO 2100 2010 PRINT AT 10,0;"THE FILE DOES NOT HAVE AT LEAST 28 QUOTATIONS - PRESS ENTER TO RETURN :::" 2020 PAUSE 40000 2030 GOTO 100 2100 LET X=(VAL A$( TO 3)) 2105 PRINT "DO YOU WANT HARD COPY?" 2106 PAUSE 40000 2107 LET Y$=INKEY$ 2110 DIM S(X) 2115 DIM B$(X,8) 2120 LET N=0 2130 FOR I=5 TO LEN A$ 2150 IF A$(I)=CHR$ 111 THEN GOTO 2200 2160 NEXT I 2170 GOTO 2300 2200 FOR B=I-1 TO 1 STEP -1 2210 IF A$(B)=CHR$ 68 THEN GOTO 2250 2220 NEXT B 2250 LET N=N+1 2260 LET S(N)=VAL A$(B+1 TO I-1) 2270 LET B$(N)=A$(B-8 TO B-1) 2290 GOTO 2160 2300 LET XX=X-4 2310 DIM Q(XX) 2320 FOR N=3 TO XX 2330 LET Q(N)=(S(N-2)+S(N-1)+S(N)+S(N+1)+S(N+2))/5 2332 IF Q(N)LG THEN LET LG=Q(N) 2336 LET MQ=LG-SM 2340 NEXT N 2350 SLOW 2360 FOR N=1 TO XX 2365 SCROLL 2370 IF N>2 THEN PRINT B$(N); 2375 IF N>2 AND Y$(1)="Y" THEN LPRINT B$(N); 2380 GOSUB 2500 2390 NEXT N 2400 PAUSE 40000 2410 GOTO 100 2500 LET LQ=(Q(N)-SM)/MQ 2510 LET LQ=INT (LQ*24) 2520 LET C$="" 2530 FOR J=1 TO LQ 2540 LET C$=C$+"%." 2550 NEXT J 2560 PRINT C$ 2565 IF Y$(1)="Y" THEN LPRINT C$ 2570 RETURN 2899 STOP 2900 FOR A=1 TO LEN A$ 2910 IF CODE A$(A)=111 THEN GOTO 2950 2920 NEXT A 2930 PRINT A$(A+1 TO A+3);" ";A$(A+5 TO A+12); 2940 RETURN 2950 IF VAL A$(A+1 TO A+3)=N THEN GOTO 2930 2960 GOTO 2920 3000 PRINT AT 10,0;"INPUT DATE EXACTLY AS ENTERED ::" 3005 SLOW 3010 INPUT D$ 3015 FAST 3020 FOR N=1 TO VAL A$( TO 3) 3030 IF B$(N, TO LEN D$)=D$ THEN GOTO 3100 3040 NEXT N 3050 PRINT ,,"DATE NOT IN FILE, PRESS ENTER TOCONTINUE ;;;" 3060 PAUSE 40000 3070 GOTO 100 3100 IF X-N>=19 THEN LET EN=N+19 3110 IF X-N<=19 THEN LET EN=X 3120 FAST 3130 CLS 3140 FOR I=N TO EN 3150 PRINT I;TAB 3;" - (";B$(I);") - (";S(I);")" 3160 NEXT I 3170 PRINT "% %P%R%E%S%S% %<%Z%>% %K%E%Y% %F%O%R% %P%R%I%N%T%O%U%T% %O%R% % % %<%E%N%T%E%R%>% %T%O% %R%E%T%U%R%N% %:%:%:% % % % % % % % % % " 3175 SLOW 3180 LET Y$=INKEY$ 3190 IF Y$="" THEN GOTO 3180 3200 PRINT AT 20,0;" " 3210 GOTO 100 4000 RUN 5000 SLOW 5010 PRINT AT 10,0;"ENTER THE NAME OF THE FILE, PREPARE THE RECORDER AND PRESS ENTER :::" 5020 INPUT F$ 5030 SAVE F$ 5040 GOTO 100 6000 PRINT ,,"ENTER THE DATE OF THE INCORRECT ENTRY? "; 6005 SLOW 6010 INPUT D$ 6020 PRINT D$ 6030 PRINT ,,"ENTER THE PRICE QUOTE OF THE INCORRECT ENTRY? "; 6040 INPUT Q$ 6050 PRINT Q$ 6060 LET Y$=D$+CHR$ 68+Q$ 6070 FAST 6080 FOR N=1 TO LEN A$-LEN Y$ 6090 IF A$(N TO N+LEN Y$-1)=Y$ AND A$(N+LEN Y$)=CHR$ 111 THEN GOTO 6200 6100 NEXT N 6110 CLS 6120 PRINT AT 10,0;D$;" AND ";Q$;" ARE NOT" 6130 PRINT "IN THE DATA FILE. PRESS ENTER TOCONTINUE :::" 6140 PAUSE 40000 6150 GOTO 100 6200 CLS 6202 IF A$(N-5)=CHR$ 111 THEN LET AA=VAL A$(N-4 TO N-2) 6204 IF A$(N-4)=CHR$ 111 THEN LET AA=VAL A$(N-3 TO N-2) 6206 IF A$(N-3)=CHR$ 111 THEN LET AA=VAL A$(N-2) 6210 PRINT ,,"THE OLD DATE WAS ";D$ 6220 PRINT "WHAT IS THE CORRECT DATE (IF THESAME, JUST PRESS ENTER :::" 6225 SLOW 6230 INPUT Z$ 6240 IF Z$="" THEN GOTO 6260 6250 LET D$=Z$ 6255 LET B$(AA)=D$ 6260 PRINT D$ 6270 PRINT ,,,,"THE OLD PRICE QUOTE WAS ";Q$ 6280 PRINT "WHAT IS THE CORRECT QUOTE (IF THE SAME, JUST PRESS ENTER :::" 6290 INPUT Z$ 6295 FAST 6300 IF Z$="" THEN GOTO 6320 6310 LET Q$=Z$ 6315 LET S(AA)=VAL Q$ 6320 PRINT Q$ 6400 LET A$=A$( TO N-1)+D$+CHR$ 68+Q$+A$(N+LEN Y$ TO LEN A$) 6410 PRINT AT 20,0;"DATA HAS BEEN CORRECTED - PRESS ENTER TO CONTINUE :::" 6420 PAUSE 40000 6430 GOTO 100 9997 STOP 9998 SAVE "STOCK PROJECTO%R" 9999 RUN ```