Building a stock market ticker in VastPark

Update: Revised IMML here: http://theparkisvast.com/2010/02/17/updated-imml-for-stock-market-ticker/

As I’m sure you’ve probably noticed by now, the stock markets around the world have crashed and burned with lots of dollars drained off the portfolio’s of many an investor, myself included.

To avoid future surprises, I’ve decided to pay a little more attention to what’s happening with the stock market and what better way to do so than via VastPark 🙂

The first step was to work out a reliable data source for the stock prices that would let me retrieve at least the following info:

As an Aussie, I’m primarily interested in stocks from the asx – but to allow others to re-use the code, something that would work worldwide was desirable.

Yahoo Finance

After a search lead me to the blog of Mads Kristensen and this post I realised Yahoo Finance would work perfectly using the request csv method:

http://finance.yahoo.com/d/quotes.csv?s=<StockName>&f=sl1c1ohgv 

For the first version of the ticker, I wanted to keep things simple. It would provide the following visual for each stock:

I also wanted to avoid writing a plugin to do the work and instead make use of the scene.web:loadstring method in VastScript, which would return a string containing all of the data to process.

Step 1: A Loaded trigger and target script
Use a document level Trigger and an inline script, get things rolling:

<Trigger Event="Loaded" Target="GenerateStocks" />
<Script Name="GenerateStocks">
function main(obj, args)
  --todo: retrieve the data
  --todo: parse the response data
  --todo: generate the visuals
end
</Script>

Step 2: Retrieve data from Yahoo!
To make life easier when changing stocks, I’m going to store a scene variable called stocks containing all of the stock codes I’m interested in.

--store a scene variable for the stocks to retrieve
  scene:set('stocks', 'IPL.AX, CFE.AX,FMG.AX,RIO.AX,BHP.AX,BBI.AX,BBP.AX,BOQ.AX,BRM.AX,FGL.AX,NAB.AX,TAM.AX,TLS.AX,WOW.AX')
  
  --create the http request
  request = httprequest('http://finance.yahoo.com/d/quotes.csv?s='..scene:get('stocks')..'&amp;f=sl1c1ohgv')
  
  --load the response 
  response = scene.web:loadstring(request)

Step 3: Parse the data into something useful
The response of the above turns out to be a large string containing all of the data for each stock separated by a comma and all stocks separated by a new line.

--reponse is returned as each stock on a new line, break into a table entry for each stock
  stocks = explode('\r\n', response)
 
  count = 1
 
  --stocks is a dictionary of all listed stocks, need to explode each listing to generate a representation
  --format: stock code, last trade,change, open, high, low, volume
  for key,value in pairs(stocks) do
    if(string.len(value) > 0) then
      scene.ui:writeline(value)
      stock = explode(',', value)
 
      --todo: generate the visual for this stock
 
    end
  end

Step 4: Generate the visual
We get a little tricky here, generating a red primitive if the stock is worth less than the open, green otherwise.

element = _generateStock(string.sub(stock[0],2,-2), stock[1], stock[2], stock[3], stock[4], stock[5], stock[6])
 
element.position = vector3(-20 + count*4.5, -9, 30)
scene:add(element)
 
function _generateStock(code, lastTrade, change, open, high, low, volume)
	scene.ui:writeline('generating representation for: '..code)
	emissive = rgb("#00ff00") --default to green
 
	changeFromOpen = string.sub(change, 1,1)
	if(changeFromOpen == "-") then
		emissive = rgb("#ff0000") --red
		scene.ui:writeline('stock trading lower than open!')
	end
 
	--representation for the last trade
	lastTradePrim = primitive()
	lastTradePrim.type = primitivetype.cylinder
	lastTradePrim.complexity = primitivecomplexity.high
	lastTradePrim.name = code.."_last"
	lastTradePrim.material.emissive = emissive
	lastTradePrim.size = vector3(0.5, tonumber(lastTrade), 0.5)
	lastTradePrim.position = vector3(0.5,0.5,0)
 
	--representation for the opening price
	openPricePrim = primitive()
	openPricePrim.type = primitivetype.cylinder
	openPricePrim.complexity = primitivecomplexity.high
	openPricePrim.name = code.."_open"
	openPricePrim.material.emissive = rgb("#ffffff")
	openPricePrim.size = vector3(0.5, tonumber(open), 0.5)
	openPricePrim.position = vector3(-0.5,0.5,0)
 
	--stock code text
	stockCodeText = text()
	stockCodeText.name = code.."_text"
	stockCodeText.value = code
	stockCodeText.billboard = true
	stockCodeText.position = vector3(0,-1,-3) --1 unit lower than the parent and 3 units fwd
 
	--parent prim for the platform the 2 colums will sit on
	basePrim = primitive()		
	basePrim.name = code.."_parent"
	basePrim.material.emissive = rgb("#000000")
	basePrim.size = vector3(3,0.5,3)
	basePrim:add(lastTradePrim)
	basePrim:add(openPricePrim)
	basePrim:add(stockCodeText)
 
	return basePrim
end

Once the stocks have been generated, they now need to be updated with the new values. Yahoo Finance appears to operate on a slight delay but the data changes real-time, offset by that delay, so we can query for new data quite often. The best way to do this is with a looping timeline that runs a script every n seconds. I’ve chosen to do this every 2 seconds:

<Timeline Name="Timeline" Enabled="True" Speed="1" Loop="True" AutoTween="False">
    <ExecuteEvent Time="00:00:02" Element="UpdateStocks" />
  </Timeline>

My UpdateStocks script looks something like this:

<Script Name="UpdateStocks">function main(obj, args)
	request = httprequest('http://finance.yahoo.com/d/quotes.csv?s='..scene:get('stocks')..'&amp;f=sl1c1ohgv')
	response = scene.web:loadstring(request)
 
	--reponse is returned as each stock on a new line, break into a table entry for each stock
	stocks = explode('\r\n', response)
 
	count = 1
 
	--stocks is a dictionary of all listed stocks, need to explode each listing to generate a representation
	--format: stock code, last trade,change, open, high, low, volume
	for key,value in pairs(stocks) do
		if(string.len(value) &gt; 0) then
			stock = explode(',', value)
			_updateStock(string.sub(stock[0],2,-2), stock[1], stock[2], stock[3], stock[4], stock[5], stock[6])
		end
	end
end
 
function _updateStock(code, lastTrade, change, open, high, low, volume)
 
	emissive = rgb("#00ff00") --default to green
 
	changeFromOpen = string.sub(change, 1,1)
	if(changeFromOpen == "-") then
		emissive = rgb("#ff0000") --red
	end
 
	--update the last trade visual
	lastTradeVisual = scene:getelement(code..'_last')
	lastTradeVisual.material.emissive = emissive
	lastTradeVisual.size = vector3(0.5, tonumber(lastTrade), 0.5)
	
	--update the % change
	percentageChange = (tonumber(change) / tonumber(open)) * 100
	stockText = scene:getelement(code..'_text')
	stockText.value = code.."\r\n$"..lastTrade.."\r\n".._round(percentageChange,2).."%"
end
	
function _round(num, idp)
  local mult = 10^(idp or 0)
  return math.floor(num * mult + 0.5) / mult
end</Script>

Putting all of the above together does the trick and we now have a good basis to make something more advanced.

The finished product

The stock ticker in action

Download the IMML document here (right-click, save as): Stock ticker.park

Comments

One Response to “Building a stock market ticker in VastPark”

  1. Updated IMML for Stock Market Ticker : The Park is Vast on February 17th, 2010 1:56 pm

    […] made some adjustments to the stock ticker built as part of my building a stock ticker in vastpark post to take advantage of the new Tooltip Plugin and the Define element in IMML. Rather than going all […]