Idea
Why is it so hard to get into the stock market? There are so many acronyms and complex words that make it difficult to get into the legal gambling sphere that is trading stocks. Seeing this, we decided to create an installation that simplified the whole process of buying and selling stocks.
Implementation
The implementation of this project had 3 sides: the backend where buy and sell orders were created by interacting with the trading API, the display in p5JS where choices were presented to the user, and the Arduino-based controls where the user can manipulate purchase and sales.
The backend
Using Nodejs and the Alpaca Trading API, we were able to create multiple endpoints that allowed us to send and receive data.
Endpoints
POST /buy_orders -> make a buy order for a stock
Arguments
- ticker (string) -> the ticker of the stock to make a purchase of
Returns
- order (Alpaca Order Object) -> an object representing the order made
POST /sell_all -> liquidate all stocks and close all open positions
Arguments – None
Returns – Status 200 if successful
GET /timeline -> current equity trends, percent loss and gain
Arguments – None
Returns
GET /rand_tickers -> get two random tickers to choose from (NYSE)
Arguments – None
Returns
- tickers (string[2]) -> list of 2 different tickers
The implementation goes as follows
import express from 'express'
const Alpaca = require('@alpacahq/alpaca-trade-api')
require('dotenv').config()
if(process.env.ENV==='DEVELOPMENT'){
keyId: process.env.TEST_ALPACA_KEY_ID,
secretKey: process.env.TEST_ALPACA_SECRET_KEY,
}else if(process.env.ENV==='PRODUCTION'){
keyId: process.env.ALPACA_KEY_ID,
secretKey: process.env.ALPACA_SECRET_KEY,
throw new Error("Error: Environment Not Properly Selected")
fs.readFile('stocklist.txt', function(err, data) {
var array = data.toString().split("\n");
for(const stock of array) {
const [symbol, name] = stock.split(' ')
name:name.replace("\"","")
stocklist.push(currstock)
app.post('/buy_order', async(req, res)=>{
const {ticker} = req.body;
const order = await alpaca.createOrder({
res.status(400).send('err')
app.post('/sell_all', async(req, res)=>{
await alpaca.closeAllPositions();
res.json({'success':'success'})
app.get('/timeline', async(req, res)=>{
const history = await alpaca.getPortfolioHistory({
const last_elem = history.equity[history.equity.length-1]===null ? Math.max(0, await history.equity.findIndex(e=>e===null)): history.equity.length;
equity:history.equity.slice(0,last_elem),
profit_loss: history.profit_loss[last_elem-1],
profit_loss_pct: history.profit_loss_pct[last_elem-1],
start_val: history.profit_loss[last_elem-1] + history.equity[last_elem-1]
if(cut_history.profit_loss)
res.json({...cut_history})
res.json({equity:[200,100,50,200],profit_loss:20,profit_loss_pct:0.1})
app.get('/randtickers', async(req,res)=>{
const rand1 = Math.floor(stocklist.length * Math.random());
let rand2 = Math.floor(stocklist.length * Math.random());
rand2 = Math.floor(stocklist.length * Math.random());
res.json({tickers: [stocklist[rand1],stocklist[rand2]]})
res.status(400).send('err')
console.log(`Example app listening on port ${port}`)
import express from 'express'
import cors from 'cors'
const Alpaca = require('@alpacahq/alpaca-trade-api')
const fs = require('fs')
require('dotenv').config()
const app = express()
const port = 8000
const stocklist = [];
let alpaca;
if(process.env.ENV==='DEVELOPMENT'){
alpaca = new Alpaca({
keyId: process.env.TEST_ALPACA_KEY_ID,
secretKey: process.env.TEST_ALPACA_SECRET_KEY,
paper: true,
})
}else if(process.env.ENV==='PRODUCTION'){
alpaca = new Alpaca({
keyId: process.env.ALPACA_KEY_ID,
secretKey: process.env.ALPACA_SECRET_KEY,
paper: false,
})
}else{
throw new Error("Error: Environment Not Properly Selected")
}
//read stocklist
fs.readFile('stocklist.txt', function(err, data) {
if(err) throw err;
var array = data.toString().split("\n");
for(const stock of array) {
const [symbol, name] = stock.split(' ')
if(symbol&&name){
let currstock ={
symbol,
name:name.replace("\"","")
}
stocklist.push(currstock)
}
}
});
app.use(express.json())
app.use(cors())
app.post('/buy_order', async(req, res)=>{
try{
const {ticker} = req.body;
const order = await alpaca.createOrder({
symbol:ticker,
qty:1,
side: 'buy',
type:'market',
time_in_force:'day'
})
res.json({order})
}catch(e){
console.log(e)
res.status(400).send('err')
}
})
app.post('/sell_all', async(req, res)=>{
try{
await alpaca.closeAllPositions();
res.json({'success':'success'})
}catch(e){
console.log(e)
}
})
app.get('/timeline', async(req, res)=>{
try{
const history = await alpaca.getPortfolioHistory({
period: '1D',
timeframe:'1Min'
})
const last_elem = history.equity[history.equity.length-1]===null ? Math.max(0, await history.equity.findIndex(e=>e===null)): history.equity.length;
const cut_history = {
equity:history.equity.slice(0,last_elem),
profit_loss: history.profit_loss[last_elem-1],
profit_loss_pct: history.profit_loss_pct[last_elem-1],
start_val: history.profit_loss[last_elem-1] + history.equity[last_elem-1]
};
if(cut_history.profit_loss)
res.json({...cut_history})
else
res.json({equity:[200,100,50,200],profit_loss:20,profit_loss_pct:0.1})
}catch(e){
console.log(e)
res.send('err')
}
})
app.get('/randtickers', async(req,res)=>{
try{
const rand1 = Math.floor(stocklist.length * Math.random());
let rand2 = Math.floor(stocklist.length * Math.random());
while(rand2===rand1){
rand2 = Math.floor(stocklist.length * Math.random());
}
res.json({tickers: [stocklist[rand1],stocklist[rand2]]})
}catch(e){
res.status(400).send('err')
}
})
app.listen(port, () => {
console.log(`Example app listening on port ${port}`)
})
import express from 'express'
import cors from 'cors'
const Alpaca = require('@alpacahq/alpaca-trade-api')
const fs = require('fs')
require('dotenv').config()
const app = express()
const port = 8000
const stocklist = [];
let alpaca;
if(process.env.ENV==='DEVELOPMENT'){
alpaca = new Alpaca({
keyId: process.env.TEST_ALPACA_KEY_ID,
secretKey: process.env.TEST_ALPACA_SECRET_KEY,
paper: true,
})
}else if(process.env.ENV==='PRODUCTION'){
alpaca = new Alpaca({
keyId: process.env.ALPACA_KEY_ID,
secretKey: process.env.ALPACA_SECRET_KEY,
paper: false,
})
}else{
throw new Error("Error: Environment Not Properly Selected")
}
//read stocklist
fs.readFile('stocklist.txt', function(err, data) {
if(err) throw err;
var array = data.toString().split("\n");
for(const stock of array) {
const [symbol, name] = stock.split(' ')
if(symbol&&name){
let currstock ={
symbol,
name:name.replace("\"","")
}
stocklist.push(currstock)
}
}
});
app.use(express.json())
app.use(cors())
app.post('/buy_order', async(req, res)=>{
try{
const {ticker} = req.body;
const order = await alpaca.createOrder({
symbol:ticker,
qty:1,
side: 'buy',
type:'market',
time_in_force:'day'
})
res.json({order})
}catch(e){
console.log(e)
res.status(400).send('err')
}
})
app.post('/sell_all', async(req, res)=>{
try{
await alpaca.closeAllPositions();
res.json({'success':'success'})
}catch(e){
console.log(e)
}
})
app.get('/timeline', async(req, res)=>{
try{
const history = await alpaca.getPortfolioHistory({
period: '1D',
timeframe:'1Min'
})
const last_elem = history.equity[history.equity.length-1]===null ? Math.max(0, await history.equity.findIndex(e=>e===null)): history.equity.length;
const cut_history = {
equity:history.equity.slice(0,last_elem),
profit_loss: history.profit_loss[last_elem-1],
profit_loss_pct: history.profit_loss_pct[last_elem-1],
start_val: history.profit_loss[last_elem-1] + history.equity[last_elem-1]
};
if(cut_history.profit_loss)
res.json({...cut_history})
else
res.json({equity:[200,100,50,200],profit_loss:20,profit_loss_pct:0.1})
}catch(e){
console.log(e)
res.send('err')
}
})
app.get('/randtickers', async(req,res)=>{
try{
const rand1 = Math.floor(stocklist.length * Math.random());
let rand2 = Math.floor(stocklist.length * Math.random());
while(rand2===rand1){
rand2 = Math.floor(stocklist.length * Math.random());
}
res.json({tickers: [stocklist[rand1],stocklist[rand2]]})
}catch(e){
res.status(400).send('err')
}
})
app.listen(port, () => {
console.log(`Example app listening on port ${port}`)
})
Display
We used p5js for our display, where the user is shown 3 options (2 stocks and 1 skip), as well as their current stock trend. The rotation at the bottom shows the user which option they are hovering over, in addition to the flickering of the background.
To display the equity at the top, we got the data from the API and displayed it on the screen using a p5js shape
getTimeline().then(resp=>{
profit_loss = resp.profit_loss
profit_loss_pct = resp.profit_loss_pct
start_val = resp.start_val
function plotTimeline(equity){
const Emin = Math.min(Math.min(...equity), start_val) - 10;
const Emax = Math.max(start_val, Math.max(...equity)) + 10;
const maxGraphHeight = height/4;
for(let i =0; i<equity.length; i++){
vertex(map(i,0,equity.length, 10, 5*width/6),map(equity[i], Emin, Emax, 10, maxGraphHeight))
line(0,map(start_val||equity[0], Emin, Emax, 10, maxGraphHeight),5*width/6,map(start_val||equity[0], Emin, Emax, 10, maxGraphHeight));
function setup() {
getTimeline().then(resp=>{
equity = resp.equity;
profit_loss = resp.profit_loss
profit_loss_pct = resp.profit_loss_pct
start_val = resp.start_val
})
createCanvas(1440,600);
}
function plotTimeline(equity){
if(!equity){
return;
}
const Emin = Math.min(Math.min(...equity), start_val) - 10;
const Emax = Math.max(start_val, Math.max(...equity)) + 10;
const maxGraphHeight = height/4;
const plotEquity = [];
push();
noFill();
stroke(0)
beginShape();
for(let i =0; i<equity.length; i++){
vertex(map(i,0,equity.length, 10, 5*width/6),map(equity[i], Emin, Emax, 10, maxGraphHeight))
}
endShape();
push();
stroke(100,100,100)
line(0,map(start_val||equity[0], Emin, Emax, 10, maxGraphHeight),5*width/6,map(start_val||equity[0], Emin, Emax, 10, maxGraphHeight));
pop()
pop();
}
function setup() {
getTimeline().then(resp=>{
equity = resp.equity;
profit_loss = resp.profit_loss
profit_loss_pct = resp.profit_loss_pct
start_val = resp.start_val
})
createCanvas(1440,600);
}
function plotTimeline(equity){
if(!equity){
return;
}
const Emin = Math.min(Math.min(...equity), start_val) - 10;
const Emax = Math.max(start_val, Math.max(...equity)) + 10;
const maxGraphHeight = height/4;
const plotEquity = [];
push();
noFill();
stroke(0)
beginShape();
for(let i =0; i<equity.length; i++){
vertex(map(i,0,equity.length, 10, 5*width/6),map(equity[i], Emin, Emax, 10, maxGraphHeight))
}
endShape();
push();
stroke(100,100,100)
line(0,map(start_val||equity[0], Emin, Emax, 10, maxGraphHeight),5*width/6,map(start_val||equity[0], Emin, Emax, 10, maxGraphHeight));
pop()
pop();
}
To get the ticker data, we got the data from the backend, and set a global object to hold the values
async function getTickers(){
const data = await fetch('http://localhost:8000/randtickers')
currSelect = Math.min(Math.max(Math.floor(map(potVal, minPVAL, maxPVAL, 0, 3)),0),3)
//update every minute (bcuz that's how it works serverside)
if(frameCount%FPS*60===0){
getTimeline().then(resp=>{
profit_loss = resp.profit_loss
profit_loss_pct = resp.profit_loss_pct
start_val = resp.start_val
if(currSelect === 0 && Math.floor(frameCount/60)%2===0){
rect(0,20+height/4,1*width/3,3*height/4-20)
if(currSelect === 1 && Math.floor(frameCount/60)%2===0){
rect(width/3,20+height/4,1*width/3,3*height/4-20)
if(currSelect === 2 && Math.floor(frameCount/60)%2===0){
rect(2*width/3,20+height/4,1*width/3,3*height/4-20)
text(tickers[0].symbol, width/6, 2*height/4)
text(tickers[0].name, width/6, 3*height/4)
text("SKIP", 3*width/6, 11*height/16)
text(tickers[1].symbol, 5*width/6, 2*height/4)
text(tickers[1].name, 5*width/6, 3*height/4)
text(`SELECTED${".".repeat(map(frameCount%60,0,59,1,4))}`, (currSelect*2+1)*width/6, 7*height/8)
fill(profit_loss<0?255:0,profit_loss>0?255:0,0)
text(`${profit_loss?'$'+profit_loss.toString():""}`, 5*width/6+10, height/7)
text(`${profit_loss_pct?(profit_loss_pct*100).toString().slice(0,7)+"%":""}`, 5*width/6+10, height/7+20)
if(selling && Math.floor(map(frameCount%60, 0, 59, 0, 2))){
rect(width/4, height/4, width/2, height/2)
text('SELLING', width/2, height/2);
circle(width/2, height, RAD*2)
let xdeg = 180- map(potVal, minPVAL, maxPVAL, XOFFSET, 180-XOFFSET)
let x = width/2 + cos(xdeg) * XRAD
let y = Math.sqrt(XRAD**2 - (x-(width/2))**2) + height
let xlrange = [width/2-RAD, width/2-RAD, width/2]
let ylrange = [height, height, height - RAD]
let xrrange = [width/2, width/2+RAD, width/2+RAD]
let yrrange = [height - RAD, height, height]
triangle(xlrange[currSelect], ylrange[currSelect], x, height-y+height, xrrange[currSelect], yrrange[currSelect]);
async function getTickers(){
try{
const data = await fetch('http://localhost:8000/randtickers')
return data.json()
}catch(e){
console.log(e)
}
}
function draw() {
background(255);
currSelect = Math.min(Math.max(Math.floor(map(potVal, minPVAL, maxPVAL, 0, 3)),0),3)
//update every minute (bcuz that's how it works serverside)
if(frameCount%FPS*60===0){
getTimeline().then(resp=>{
equity = resp.equity;
profit_loss = resp.profit_loss
profit_loss_pct = resp.profit_loss_pct
start_val = resp.start_val
})
}
push();
//choice 0
if(currSelect === 0 && Math.floor(frameCount/60)%2===0){
fill(200,100,100)
}else{
fill(255,0,0)
}
rect(0,20+height/4,1*width/3,3*height/4-20)
//choice 1 (skip)
if(currSelect === 1 && Math.floor(frameCount/60)%2===0){
fill(175,175,175)
}else{
fill(100,100,100)
}
rect(width/3,20+height/4,1*width/3,3*height/4-20)
//choice 2
if(currSelect === 2 && Math.floor(frameCount/60)%2===0){
fill(100,100,200)
}else{
fill(0,0,255)
}
rect(2*width/3,20+height/4,1*width/3,3*height/4-20)
pop();
push();
fill(255)
noStroke();
textSize(24)
textAlign(CENTER)
text(tickers[0].symbol, width/6, 2*height/4)
text(tickers[0].name, width/6, 3*height/4)
text("SKIP", 3*width/6, 11*height/16)
text(tickers[1].symbol, 5*width/6, 2*height/4)
text(tickers[1].name, 5*width/6, 3*height/4)
if(flicker){
fill(255);
text(`SELECTED${".".repeat(map(frameCount%60,0,59,1,4))}`, (currSelect*2+1)*width/6, 7*height/8)
}
pop();
plotTimeline(equity);
push();
textSize(18);
fill(profit_loss<0?255:0,profit_loss>0?255:0,0)
text(`${profit_loss?'$'+profit_loss.toString():""}`, 5*width/6+10, height/7)
text(`${profit_loss_pct?(profit_loss_pct*100).toString().slice(0,7)+"%":""}`, 5*width/6+10, height/7+20)
pop();
push();
if(selling && Math.floor(map(frameCount%60, 0, 59, 0, 2))){
textAlign(CENTER)
textSize(50)
fill(0)
rect(width/4, height/4, width/2, height/2)
fill(255,0,0);
text('SELLING', width/2, height/2);
}
pop();
push()
fill(0,255,0)
noStroke()
circle(width/2, height, RAD*2)
angleMode(DEGREES)
let xdeg = 180- map(potVal, minPVAL, maxPVAL, XOFFSET, 180-XOFFSET)
let x = width/2 + cos(xdeg) * XRAD
let y = Math.sqrt(XRAD**2 - (x-(width/2))**2) + height
let xlrange = [width/2-RAD, width/2-RAD, width/2]
let ylrange = [height, height, height - RAD]
let xrrange = [width/2, width/2+RAD, width/2+RAD]
let yrrange = [height - RAD, height, height]
triangle(xlrange[currSelect], ylrange[currSelect], x, height-y+height, xrrange[currSelect], yrrange[currSelect]);
pop()
}
async function getTickers(){
try{
const data = await fetch('http://localhost:8000/randtickers')
return data.json()
}catch(e){
console.log(e)
}
}
function draw() {
background(255);
currSelect = Math.min(Math.max(Math.floor(map(potVal, minPVAL, maxPVAL, 0, 3)),0),3)
//update every minute (bcuz that's how it works serverside)
if(frameCount%FPS*60===0){
getTimeline().then(resp=>{
equity = resp.equity;
profit_loss = resp.profit_loss
profit_loss_pct = resp.profit_loss_pct
start_val = resp.start_val
})
}
push();
//choice 0
if(currSelect === 0 && Math.floor(frameCount/60)%2===0){
fill(200,100,100)
}else{
fill(255,0,0)
}
rect(0,20+height/4,1*width/3,3*height/4-20)
//choice 1 (skip)
if(currSelect === 1 && Math.floor(frameCount/60)%2===0){
fill(175,175,175)
}else{
fill(100,100,100)
}
rect(width/3,20+height/4,1*width/3,3*height/4-20)
//choice 2
if(currSelect === 2 && Math.floor(frameCount/60)%2===0){
fill(100,100,200)
}else{
fill(0,0,255)
}
rect(2*width/3,20+height/4,1*width/3,3*height/4-20)
pop();
push();
fill(255)
noStroke();
textSize(24)
textAlign(CENTER)
text(tickers[0].symbol, width/6, 2*height/4)
text(tickers[0].name, width/6, 3*height/4)
text("SKIP", 3*width/6, 11*height/16)
text(tickers[1].symbol, 5*width/6, 2*height/4)
text(tickers[1].name, 5*width/6, 3*height/4)
if(flicker){
fill(255);
text(`SELECTED${".".repeat(map(frameCount%60,0,59,1,4))}`, (currSelect*2+1)*width/6, 7*height/8)
}
pop();
plotTimeline(equity);
push();
textSize(18);
fill(profit_loss<0?255:0,profit_loss>0?255:0,0)
text(`${profit_loss?'$'+profit_loss.toString():""}`, 5*width/6+10, height/7)
text(`${profit_loss_pct?(profit_loss_pct*100).toString().slice(0,7)+"%":""}`, 5*width/6+10, height/7+20)
pop();
push();
if(selling && Math.floor(map(frameCount%60, 0, 59, 0, 2))){
textAlign(CENTER)
textSize(50)
fill(0)
rect(width/4, height/4, width/2, height/2)
fill(255,0,0);
text('SELLING', width/2, height/2);
}
pop();
push()
fill(0,255,0)
noStroke()
circle(width/2, height, RAD*2)
angleMode(DEGREES)
let xdeg = 180- map(potVal, minPVAL, maxPVAL, XOFFSET, 180-XOFFSET)
let x = width/2 + cos(xdeg) * XRAD
let y = Math.sqrt(XRAD**2 - (x-(width/2))**2) + height
let xlrange = [width/2-RAD, width/2-RAD, width/2]
let ylrange = [height, height, height - RAD]
let xrrange = [width/2, width/2+RAD, width/2+RAD]
let yrrange = [height - RAD, height, height]
triangle(xlrange[currSelect], ylrange[currSelect], x, height-y+height, xrrange[currSelect], yrrange[currSelect]);
pop()
}
To handle the orders, we had to connect with the arduino, so in the mean time we implemented the code structure that enabled us to receive data and update the states in the program as necessary.
function readSerial(data) {
////////////////////////////////////
////////////////////////////////////
let fromArduino = split(trim(data), ",");
if(fromArduino.length === 3){
if(prevsellbtn == 0 && sellbtn == 1){
sellAll().then(()=>selling=false);
end_time = frameCount + (FPS*SELLTIME)
}else if(prevbuybtn == 0 && buybtn == 1){
makeOrder(tickers[0].symbol)
.then(data=>{tickers=data.tickers;flicker=false;});
.then(data=>{tickers=data.tickers;flicker=false;});
makeOrder(tickers[1].symbol)
.then(data=>{tickers=data.tickers;flicker=false;});
sellbtn = parseInt(fromArduino[0])
buybtn = parseInt(fromArduino[1])
potVal = maxPVAL - parseInt(fromArduino[2])
//////////////////////////////////
//SEND TO ARDUINO HERE (handshake)
//////////////////////////////////
if(end_time && end_time > frameCount){
function readSerial(data) {
////////////////////////////////////
//READ FROM ARDUINO HERE
////////////////////////////////////
if (data != null) {
let fromArduino = split(trim(data), ",");
if(fromArduino.length === 3){
if(prevsellbtn == 0 && sellbtn == 1){
selling=true;
sellAll().then(()=>selling=false);
end_time = frameCount + (FPS*SELLTIME)
}else if(prevbuybtn == 0 && buybtn == 1){
if(!flicker){
flicker=true;
switch(currSelect){
case 0:
makeOrder(tickers[0].symbol)
.then(()=>getTickers())
.then(data=>{tickers=data.tickers;flicker=false;});
break;
case 1:
getTickers()
.then(data=>{tickers=data.tickers;flicker=false;});
break
case 2:
makeOrder(tickers[1].symbol)
.then(()=>getTickers())
.then(data=>{tickers=data.tickers;flicker=false;});
break;
}
}
}
prevsellbtn = sellbtn;
prevbuybtn = buybtn;
sellbtn = parseInt(fromArduino[0])
buybtn = parseInt(fromArduino[1])
potVal = maxPVAL - parseInt(fromArduino[2])
//////////////////////////////////
//SEND TO ARDUINO HERE (handshake)
//////////////////////////////////
if(end_time && end_time > frameCount){
writeSerial("1\n");
}else{
writeSerial("0\n");
if(frameCount<end_time){
end_time = 0;
}
}
}
}}
function readSerial(data) {
////////////////////////////////////
//READ FROM ARDUINO HERE
////////////////////////////////////
if (data != null) {
let fromArduino = split(trim(data), ",");
if(fromArduino.length === 3){
if(prevsellbtn == 0 && sellbtn == 1){
selling=true;
sellAll().then(()=>selling=false);
end_time = frameCount + (FPS*SELLTIME)
}else if(prevbuybtn == 0 && buybtn == 1){
if(!flicker){
flicker=true;
switch(currSelect){
case 0:
makeOrder(tickers[0].symbol)
.then(()=>getTickers())
.then(data=>{tickers=data.tickers;flicker=false;});
break;
case 1:
getTickers()
.then(data=>{tickers=data.tickers;flicker=false;});
break
case 2:
makeOrder(tickers[1].symbol)
.then(()=>getTickers())
.then(data=>{tickers=data.tickers;flicker=false;});
break;
}
}
}
prevsellbtn = sellbtn;
prevbuybtn = buybtn;
sellbtn = parseInt(fromArduino[0])
buybtn = parseInt(fromArduino[1])
potVal = maxPVAL - parseInt(fromArduino[2])
//////////////////////////////////
//SEND TO ARDUINO HERE (handshake)
//////////////////////////////////
if(end_time && end_time > frameCount){
writeSerial("1\n");
}else{
writeSerial("0\n");
if(frameCount<end_time){
end_time = 0;
}
}
}
}}
The idea being that a potentiometer value from arduino would dictate the highlighted choice, and a button would lock the choice in to make a purchase. A user would also be able to close all open positions through a button press, and that would send a positive value to the arduino serial.
Arduino Implementation
As mentioned previously, we needed a potentiometer and two buttons, but we also wanted to add sound and lights for feedback. We loaded two sounds onto a microSD card onto the arduino, one for a sale and one for a purchase, and loaded them as follows along with the LEDs.
#include <Adafruit_VS1053.h>
#define BREAKOUT_RESET 9 // VS1053 reset pin (output)
#define BREAKOUT_CS 10 // VS1053 chip select pin (output)
#define BREAKOUT_DCS 8 // VS1053 Data/command select pin (output)
#define SHIELD_RESET -1 // VS1053 reset pin (unused!)
#define SHIELD_CS 7 // VS1053 chip select pin (output)
#define SHIELD_DCS 6 // VS1053 Data/command select pin (output)
#define CARDCS 4 // Card chip select pin
#define DREQ 3 // VS1053 Data request, ideally an Interrupt pin
Adafruit_VS1053_FilePlayer musicPlayer = Adafruit_VS1053_FilePlayer(SHIELD_RESET, SHIELD_CS, SHIELD_DCS, DREQ, CARDCS);
while (Serial.available() <= 0) {
if (! musicPlayer.begin()) {
musicPlayer.setVolume(20,20);
musicPlayer.useInterrupt(VS1053_FILEPLAYER_PIN_INT);
#include <SPI.h>
#include <Adafruit_VS1053.h>
#include <SD.h>
#define BREAKOUT_RESET 9 // VS1053 reset pin (output)
#define BREAKOUT_CS 10 // VS1053 chip select pin (output)
#define BREAKOUT_DCS 8 // VS1053 Data/command select pin (output)
#define SHIELD_RESET -1 // VS1053 reset pin (unused!)
#define SHIELD_CS 7 // VS1053 chip select pin (output)
#define SHIELD_DCS 6 // VS1053 Data/command select pin (output)
#define CARDCS 4 // Card chip select pin
#define DREQ 3 // VS1053 Data request, ideally an Interrupt pin
Adafruit_VS1053_FilePlayer musicPlayer = Adafruit_VS1053_FilePlayer(SHIELD_RESET, SHIELD_CS, SHIELD_DCS, DREQ, CARDCS);
void setup() {
Serial.begin(9600);
pinMode(2, INPUT);
pinMode(7, INPUT`);
pinMode(1, OUTPUT);
pinMode(5, OUTPUT);
pinMode(6, OUTPUT);
pinMode(4, OUTPUT);
pinMode(3, OUTPUT);
while (Serial.available() <= 0) {
Serial.println("0,0,0");
delay(300);
}
if (! musicPlayer.begin()) {
while (1);
}
if (!SD.begin(CARDCS)) {
while (1);
}
musicPlayer.setVolume(20,20);
musicPlayer.useInterrupt(VS1053_FILEPLAYER_PIN_INT);
}
#include <SPI.h>
#include <Adafruit_VS1053.h>
#include <SD.h>
#define BREAKOUT_RESET 9 // VS1053 reset pin (output)
#define BREAKOUT_CS 10 // VS1053 chip select pin (output)
#define BREAKOUT_DCS 8 // VS1053 Data/command select pin (output)
#define SHIELD_RESET -1 // VS1053 reset pin (unused!)
#define SHIELD_CS 7 // VS1053 chip select pin (output)
#define SHIELD_DCS 6 // VS1053 Data/command select pin (output)
#define CARDCS 4 // Card chip select pin
#define DREQ 3 // VS1053 Data request, ideally an Interrupt pin
Adafruit_VS1053_FilePlayer musicPlayer = Adafruit_VS1053_FilePlayer(SHIELD_RESET, SHIELD_CS, SHIELD_DCS, DREQ, CARDCS);
void setup() {
Serial.begin(9600);
pinMode(2, INPUT);
pinMode(7, INPUT`);
pinMode(1, OUTPUT);
pinMode(5, OUTPUT);
pinMode(6, OUTPUT);
pinMode(4, OUTPUT);
pinMode(3, OUTPUT);
while (Serial.available() <= 0) {
Serial.println("0,0,0");
delay(300);
}
if (! musicPlayer.begin()) {
while (1);
}
if (!SD.begin(CARDCS)) {
while (1);
}
musicPlayer.setVolume(20,20);
musicPlayer.useInterrupt(VS1053_FILEPLAYER_PIN_INT);
}
We then parsed the p5js output and sent back the button and potentiometer values.
if (Serial.available()) {
int sellBTN = digitalRead(SELLPIN);
int buyBTN = digitalRead(BUYPIN);
int sensorValue = analogRead(POTPIN);
Serial.print(sensorValue);
musicPlayer.playFullFile("/track001.mp3");
musicPlayer.playFullFile("/track002.mp3");
int SELLPIN = 2;
int BUYPIN = 7;
int POTPIN = A0;
void loop() {
if (Serial.available()) {
int sellBTN = digitalRead(SELLPIN);
int buyBTN = digitalRead(BUYPIN);
int sensorValue = analogRead(POTPIN);
Serial.print(sellBTN);
Serial.print(',');
Serial.print(buyBTN);
Serial.print(',');
Serial.print(sensorValue);
Serial.println("");
if(buyBTN){
musicPlayer.playFullFile("/track001.mp3");
}
if(sellBTN){
musicPlayer.playFullFile("/track002.mp3");
}
byte c = Serial.read();
if(c=='1'){
digitalWrite(1, 1);
digitalWrite(5, 1);
digitalWrite(6, 1);
digitalWrite(4, 1);
digitalWrite(3, 1);
}else{
digitalWrite(1, 0);
digitalWrite(5, 0);
digitalWrite(6, 0);
digitalWrite(4, 0);
digitalWrite(3, 0);
}
}
}
int SELLPIN = 2;
int BUYPIN = 7;
int POTPIN = A0;
void loop() {
if (Serial.available()) {
int sellBTN = digitalRead(SELLPIN);
int buyBTN = digitalRead(BUYPIN);
int sensorValue = analogRead(POTPIN);
Serial.print(sellBTN);
Serial.print(',');
Serial.print(buyBTN);
Serial.print(',');
Serial.print(sensorValue);
Serial.println("");
if(buyBTN){
musicPlayer.playFullFile("/track001.mp3");
}
if(sellBTN){
musicPlayer.playFullFile("/track002.mp3");
}
byte c = Serial.read();
if(c=='1'){
digitalWrite(1, 1);
digitalWrite(5, 1);
digitalWrite(6, 1);
digitalWrite(4, 1);
digitalWrite(3, 1);
}else{
digitalWrite(1, 0);
digitalWrite(5, 0);
digitalWrite(6, 0);
digitalWrite(4, 0);
digitalWrite(3, 0);
}
}
}
This way, we were able to get the p5js input and send out our own values using the handshake procedure. 
The image shows the complete implementation.
Reflections
Overall, the implementation worked well and made for an intuitive program that enabled people to experiment with the stock market. One of the main limitations of our system is that it only functions between 17:30-00:00 GST due to the stock market being open only for those hours. Other than that however, we found that it provided an interactive experience for the user.
We initially wanted to use an airhorn for the selling sound, but we had hardware restrictions, so decided to go for a speaker instead. We had a couple software slip ups too, as out of nowhere during one of our work sessions, my Google Chrome quit and couldn’t be reopened. In addition, some of the ports on the arduino we used were not working, so we had to test out many different ports before the system worked at full capacity.
We’re happy with the project we made in the class, and hope that the users as less afraid of investing. The repository for the backend can be found here. The .env file is not provided, so the user must provide their own API keys.