#!/usr/bin/env python
# stocksim.py
#
# Stock market simulator.  This simulator creates stock market
# data and provides it in several different ways:
#
#    1. Makes periodic updates to a log file stocklog.dat
#    2. Provides stock data through an embedded HTTP server.
#
# The purpose of this module is to provide data to the user
# in different ways in order to write interesting Python examples

import math
import time

history_file = "dowstocks.csv"

# Convert a time string such as "4:00pm" to minutes past midnight
def minutes(tm):
    am_pm = tm[-2:]
    fields = tm[:-2].split(":")
    hour = int(fields[0])
    minute = int(fields[1])
    if hour == 12:
       hour = 0
    if am_pm == 'pm':
       hour += 12
    return hour*60 + minute

# Convert time in minutes to a format string
def minutes_to_str(m):
    frac,m = math.modf(m)
    hours = m//60
    minutes = m % 60
    seconds = frac * 60
    return "%02d:%02d.%02.f" % (hours,minutes,seconds)

# Read the stock history file as a list of lists
def read_history(filename):
    result = []
    f = open(filename)
    next(f)
    for line in f:
        str_fields = line.strip().split(",")
        fields = [eval(x) for x in str_fields]
        fields[3] = minutes(fields[3])
        result.append(fields)
    return result

# Format CSV record
def csv_record(fields):
    s = '"%s",%0.2f,"%s","%s",%0.2f,%0.2f,%0.2f,%0.2f,%d' % tuple(fields)
    return s

class StockTrack(object):
    def __init__(self,name):
        self.name    = name
        self.history = []
        self.price   = 0
        self.time    = 0
        self.index   = 0
        self.open    = 0
        self.low     = 0
        self.high    = 0
        self.volume  = 0
        self.initial = 0
        self.change  = 0
        self.date    = ""
    def add_data(self,record):
        self.history.append(record)
    def reset(self,time):
        self.time = time
        # Sort the history by time
        self.history.sort(key=lambda t:t[3])
        # Find the first entry who's time is behind the given time
        self.index = 0
        while self.index < len(self.history):
            if self.history[self.index][3] > time:
                break
            self.index += 1
        self.open = self.history[0][5]
        self.initial = self.history[0][1] - self.history[0][4]
        self.date = self.history[0][2]
        self.update()
        self.low = self.price
        self.high = self.price

    # Calculate interpolated value of a given field based on
    # current time
    def interpolate(self,field):
        first = self.history[self.index][field]
        next  = self.history[self.index+1][field]
        first_t = self.history[self.index][3]
        next_t = self.history[self.index+1][3]
        try:
            slope = (next - first)/(next_t-first_t)
            return first + slope*(self.time - first_t)
        except ZeroDivisionError:
            return first

    # Update all computed values
    def update(self):
        self.price = round(self.interpolate(1),2)
        self.volume = int(self.interpolate(-1))
        if self.price < self.low:
            self.low = self.price
        if self.price >= self.high:
            self.high = self.price
        self.change = self.price - self.initial
        
    # Increment the time by a delta
    def incr(self,dt):
        self.time += dt
        if self.index < (len(self.history) - 2):
            while self.index < (len(self.history) - 2) and self.time >= self.history[self.index+1][3]:
                self.index += 1
        self.update()

    def make_record(self):
        return [self.name,round(self.price,2),self.date,minutes_to_str(self.time),round(self.change,2),self.open,round(self.high,2),
                round(self.low,2),self.volume]

class MarketSimulator(object):
    def __init__(self):
        self.stocks = { }
        self.prices = { }
        self.time = 0
        self.observers = []
    def register(self,observer):
        self.observers.append(observer)

    def publish(self,record):
        for obj in self.observers:
            obj.update(record)
    def add_history(self,filename):
        hist = read_history(filename)
        for record in hist:
            if record[0] not in self.stocks:
                self.stocks[record[0]] = StockTrack(record[0])
            self.stocks[record[0]].add_data(record) 

    def reset(self,time):
        self.time = time
        for s in self.stocks.values():
            s.reset(time)

    # Run forever.  Dt is in seconds
    def run(self,dt):
        for s in self.stocks:
            self.prices[s] = self.stocks[s].price
            self.publish(self.stocks[s].make_record())
        while self.time < 1000:
            for s in self.stocks:
                self.stocks[s].incr(dt/60.0)    # Increment is in minutes
                if self.stocks[s].price != self.prices[s]:
                    self.prices[s] = self.stocks[s].price
                    self.publish(self.stocks[s].make_record())
            time.sleep(dt)
            self.time += (dt/60.0)


class BasicPrinter(object):
    def update(self,record):
        print(csv_record(record))

class LogPrinter(object):
    def __init__(self,filename):
        self.f = open(filename,"w")
    def update(self,record):
        self.f.write(csv_record(record)+"\n")
        self.f.flush()

m = MarketSimulator()
m.add_history(history_file)
m.reset(minutes("9:30am"))

m.register(BasicPrinter())
m.register(LogPrinter("stocklog.csv"))

m.run(1)


   
