From 0bd7791234e3d1a24a3f575a5ea0437706fabe8d Mon Sep 17 00:00:00 2001 From: Josh Harvey Date: Fri, 29 Apr 2011 19:50:14 +0300 Subject: [PATCH] lots of additions --- .gitignore | 1 + btc_trader.rb | 7 ++ lib/btc_trader/engine.rb | 48 ++++++------- lib/btc_trader/trader.rb | 67 +++++++++++++++++++ test/indicators/simple_moving_average_test.rb | 3 +- 5 files changed, 96 insertions(+), 30 deletions(-) create mode 100644 .gitignore create mode 100644 btc_trader.rb diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6405f48 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +lib/btc_trader/strategies/* \ No newline at end of file diff --git a/btc_trader.rb b/btc_trader.rb new file mode 100644 index 0000000..7e87f4d --- /dev/null +++ b/btc_trader.rb @@ -0,0 +1,7 @@ +$LOAD_PATH << File.join(File.dirname(__FILE__), 'lib') + +require 'btc_trader/engine' + +e = BtcTrader::Engine.new 'data/historical.csv' +e.run +puts "ROR: #{'%.2f%' % (e.trader.ror * 100)}, ROR (less fees): #{'%.2f%' % (e.trader.ror_no_fee * 100)}, Trade Count: #{e.trader.trade_count}" diff --git a/lib/btc_trader/engine.rb b/lib/btc_trader/engine.rb index a7b6c0b..6067dea 100644 --- a/lib/btc_trader/engine.rb +++ b/lib/btc_trader/engine.rb @@ -1,7 +1,8 @@ require 'csv' -require 'metrics_manager' -require 'indicators_manager' -require 'trader' +require 'btc_trader/metrics_manager' +require 'btc_trader/indicators_manager' +require 'btc_trader/trader' +require 'btc_trader/strategies/simple_sma_trader' module BtcTrader class HistoricalRecord @@ -15,46 +16,35 @@ def initialize(hash) end class HistoricalData - def initialize - @data = [] + def initialize(file) + @file = file end - - def push(record) - @data << record - end - + def each - @data.each {|rec| yield rec } + cutoff = DateTime.parse('2010-11-1') + CSV.foreach(@file) do |row| + rec = HistoricalRecord.new( :price => row[1].to_f, + :volume => row[2].to_f, :time => DateTime.strptime(row[0], '%s') ) + next if rec.time < cutoff + yield rec + end end end class Engine + attr_reader :trader + def initialize(data) - @data = HistoricalData.new - @metrics = MetricsManager.new - @indicators = IndicatorsManager.new - @trader = Trader.new @indicators - parse_historical_data(data) + @data = HistoricalData.new data + @trader = Strategies::SimpleSmaTrader.new 10_000 +# @trader = Strategies::BuyAndHoldTrader.new 10_000 end def run @data.each do |rec| - @metrics.update! rec - @indicators.update! rec @trader.execute! rec end end - private - - def parse_historical_data(data) - CSV.foreach(data) do |row| - @data.push HistoricalRecord.new( :price => row[1], :volume => row[2], :time => DateTime.strptime(row[0], '%s') ) - end - end - end end - -e = BtcTrader::Engine.new 'data/historical.csv' -e.run diff --git a/lib/btc_trader/trader.rb b/lib/btc_trader/trader.rb index e69de29..48110d4 100644 --- a/lib/btc_trader/trader.rb +++ b/lib/btc_trader/trader.rb @@ -0,0 +1,67 @@ +module BtcTrader + class Trader + attr_reader :cash_balance, :btc_balance, :price, :trade_count + + TRADE_FEE = 0.0065 + TRADE_FEE_MULTIPLIER = 1 - TRADE_FEE + + def initialize(initial_balance) + @cash_balance = initial_balance.to_f + @btc_balance = 0.0 + @initial_balance = @cash_balance + @total_fees = 0.0 + @trade_count = 0 + initialize_strategy + end + + def initialize_strategy; end # NO-OP + + def execute!(rec) + @previous_price = @price + @previous_time = @time + + @price = rec.price + @time = rec.time + + execute_strategy! + end + + def buy(amount) + raise "Order exceeds balance" if amount > @cash_balance + @cash_balance -= amount + btc = amount / @price + @btc_balance += btc * TRADE_FEE_MULTIPLIER + @total_fees += btc * TRADE_FEE * @price + @trade_count += 1 + end + + def sell(amount) + @btc_balance -= amount + cash = amount * @price + @cash_balance += cash * TRADE_FEE_MULTIPLIER + @total_fees += cash * TRADE_FEE + @trade_count += 1 + end + + def equity + @cash_balance + @btc_balance * @price + end + + def ror + (equity - @initial_balance).to_f / @initial_balance + end + + def ror_no_fee + (equity + @total_fees - @initial_balance).to_f / @initial_balance + end + end + + # A trivial example that buys at the outset and hold until the end + module Strategies + class BuyAndHoldTrader < Trader + def execute_strategy! + buy cash_balance if @cash_balance > 0 + end + end + end +end diff --git a/test/indicators/simple_moving_average_test.rb b/test/indicators/simple_moving_average_test.rb index 7dc8408..2358173 100644 --- a/test/indicators/simple_moving_average_test.rb +++ b/test/indicators/simple_moving_average_test.rb @@ -1,4 +1,4 @@ -$LOAD_PATH << File.join(File.dirname(__FILE__), '..', '..', '/lib') +$LOAD_PATH << File.join(File.dirname(__FILE__), '..', '..', 'lib') require 'rubygems' require 'minitest/autorun' require 'btc_trader/indicators_manager' @@ -14,6 +14,7 @@ def test_basic assert_equal 10, @sma.value assert_equal 8.8, @sma.update!(4) + assert_equal 8.8, @sma.value end def test_partial