python消费datahub_Python 量化策略回测框架

import uuid

import logging

import datetime

ORDER_STATE_NEW = 'ORDER_STATE_NEW'

ORDER_STATE_PARTIALLY_FILLED = 'ORDER_STATE_PARTIALLY_FILLED'

ORDER_STATE_FULLY_FILLED = 'ORDER_STATE_FULLY_FILLED'

ORDER_STATE_CANCELLED = 'ORDER_STATE_CANCELLED'

ORDER_TYPE_MARKET = 'ORDER_TYPE_MARKET'

ORDER_TYPE_LIMIT = 'ORDER_TYPE_LIMIT'

ORDER_TYPE_STOP = 'ORDER_TYPE_STOP'

ORDER_DIRECTION_BUY = 'ORDER_DIRECTION_BUY'

ORDER_DIRECTION_SELL = 'ORDER_DIRECTION_SELL'

MIN_COMMISSION_PER_ORDER = 1

MAX_COMMISSION_PER_ORDER_PER_TRADE_VALUE = 0.005

COMMISSION_PER_SHARE = 0.005

class Order:

def __init__(self, symbol, direction, type, price, quantity, openDtIdx):

self.orderId = uuid.uuid4()

self.symbol = symbol

self.direction = direction

self.type = type

# this is the price submitted when placing order

self.price = price

# this is the average price filling the order

self.fillPrice = 0

# this quantity should be non-negative

self.quantityOutstanding = quantity

self.quantityFilled = 0

self.state = ORDER_STATE_NEW

self.commission = 0

self.linkId = None

# this is when the order is created

self.openDtIdx = openDtIdx

# this is when the order is fully filled or cancelled

self.closeDtIdx = None

def __str__(self):

return 'Order'.format(self.orderId, self.symbol, self.direction, self.type, self.price, self.fillPrice, self.quantityOutstanding, self.quantityFilled, self.state, self.commission, self.linkId, self.openDtIdx, self.closeDtIdx)

def calculateCommission(self):

"""Calculate commission for this order, commission is only incurred on fully filled"""

minCommission = MIN_COMMISSION_PER_ORDER

maxCommission = MAX_COMMISSION_PER_ORDER_PER_TRADE_VALUE * (self.fillPrice * self.quantityFilled)

commission = COMMISSION_PER_SHARE * self.quantityFilled

self.commission = max(min(commission, maxCommission), minCommission)

def fill(self, fillPrice, quantity, datetime):

logging.info('Order: fill: BEFORE: order={} CHANGE: fillPrice={}, quantity={}, datetime={}'.format(self, fillPrice, quantity, datetime))

if quantity > self.quantityOutstanding or quantity <= 0:

logging.error('Order: fill: orderId={} Invalid quantity={}!'.format(self.orderId, quantity))

raise Exception()

self.fillPrice = (self.fillPrice * self.quantityFilled + fillPrice * quantity) / (self.quantityFilled + quantity)

self.quantityFilled += quantity

self.quantityOutstanding -= quantity

if self.quantityOutstanding > 0:

self.state = ORDER_STATE_PARTIALLY_FILLED

elif self.quantityOutstanding == 0:

self.state = ORDER_STATE_FULLY_FILLED

self.closeDtIdx = datetime

self.calculateCommission()

logging.info('Order: fill: AFTER: order={} CHANGE: fillPrice={}, quantity={}, datetime={}'.format(self, fillPrice, quantity, datetime))

def cancel(self, datetime):

logging.info('Order: cancel: BEFORE: order={} CHANGE: datetime={}'.format(self, datetime))

self.state = ORDER_STATE_CANCELLED

self.closeDtIdx = datetime

logging.info('Order: cancel: AFTER: order={} CHANGE: datetime={}'.format(self, datetime))

class Position:

def __init__(self, symbol):

self.symbol = symbol

# Can be short position, where quantity is negative

self.quantity = 0

self.cost = 0

self.realizedPNL = 0

def __str__(self):

return 'Position'.format(self.symbol, self.quantity, self.cost, self.realizedPNL)

def change(self, price, quantity, commission):

"""Position change should ONLY triggered by order execution"""

logging.info('Position: BEFORE: position={} CHANGE: price={}, quantity={}, commission={}'.format(self, price, quantity, commission))

if quantity == 0:

logging.error('Position: change: symbol={} Invalid quantity is 0'.format(self.symbol))

raise Exception()

if self.quantity * quantity >= 0:

# Increasing position, either short or long, commission is included in position cost

self.quantity += quantity

self.cost += (price * quantity + commission)

else:

# Reducing position, either short or long, commission is included in realizedPNL

self.realizedPNL += ((self.cost/self.quantity - price)*quantity - commission)

self.cost += self.cost/self.quantity*quantity

self.quantity += quantity

logging.info('Position: AFTER: position={} CHANGE: price={}, quantity={}, commission={}'.format(self, price, quantity, commission))

class MarketTick:

"""MarketTick is symbol specific, which simulates the real-time market data tick.In reality, different symbols may tick at different time."""

def __init__(self, symbol, open, close, high, low, volume, dtIdx):

self.symbol = symbol

self.open = open

self.close = close

self.high = high

self.low = low

self.volume = volume

self.dtIdx = dtIdx

def __str__(self):

return 'MarketTick'.format(self.symbol, self.open, self.close, self.high, self.low, self.volume, self.dtIdx)

class Performance:

"""Performance metrics for each symbol"""

def __init__(self, symbol):

self.symbol = symbol

self.outstandingMarketOrders = 0

self.outstandingStopOrders = 0

self.outstandingLimitOrders = 0

self.filledMarketOrders = 0

self.filledStopOrders = 0

self.filledLimitOrders = 0

self.cancelledMarketOrders = 0

self.cancelledStopOrders = 0

self.cancelledLimitOrders = 0

self.success = 0

self.failure = 0

self.maxCapitalRequired = 0

self.realizedPNL = 0

self.positionQuantity = 0

self.positionCost = 0

self.totalTradeLife = datetime.timedelta()

def __str__(self):

return 'Performance'.format(self.symbol, self.outstandingMarketOrders, self.outstandingStopOrders, self.outstandingLimitOrders, self.filledMarketOrders, self.filledStopOrders, self.filledLimitOrders, self.cancelledMarketOrders, self.cancelledStopOrders, self.cancelledLimitOrders, self.success, self.failure, float(self.success*100)/(self.success+self.failure) if self.success+self.failure > 0 else float('nan'), self.maxCapitalRequired, self.realizedPNL, self.positionQuantity, self.positionCost, self.totalTradeLife, self.totalTradeLife/(self.success+self.failure) if self.success+self.failure > 0 else 'No Trades')

def updatePerformance(self, outstandingMarketOrders, outstandingStopOrders, outstandingLimitOrders, filledMarketOrders, filledStopOrders, filledLimitOrders, cancelledMarketOrders, cancelledStopOrders, cancelledLimitOrders, success, failure, maxCapitalRequired, realizedPNL, positionQuantity, positionCost, totalTradeLife):

self.outstandingMarketOrders = outstandingMarketOrders

self.outstandingStopOrders = outstandingStopOrders

self.outstandingLimitOrders = outstandingLimitOrders

self.filledMarketOrders = filledMarketOrders

self.filledStopOrders = filledStopOrders

self.filledLimitOrders = filledLimitOrders

self.cancelledMarketOrders = cancelledMarketOrders

self.cancelledStopOrders = cancelledStopOrders

self.cancelledLimitOrders = cancelledLimitOrders

self.success = success

self.failure = failure

self.maxCapitalRequired = maxCapitalRequired

self.realizedPNL = realizedPNL

self.positionQuantity = positionQuantity

self.positionCost = positionCost

self.totalTradeLife = totalTradeLife

class xMan:

def __init__(self, initialCapital):

self.orders = []

self.positions = []

self.performances = []

self.portfolioRealizedPNL = 0

self.portfolioCashBalance = initialCapital

self.initialCapital = initialCapital

self.portfolioPositionCost = 0

self.portfolioMaxCapitalRequired = 0

self.portfolioSuccess = 0

self.portfolioFailure = 0

def placeOrder(self, order):

self.orders.append(order)

def getOrderByOrderId(self, orderId):

"""Return a single order or None given the unique orderId"""

for order in self.orders:

if order.orderId == orderId:

return order

logging.debug('xMan: getOrderByOrderId: No order found for orderId={}'.format(orderId))

def getOrdersByLinkId(self, linkId):

"""Return a list of orders given the linkId"""

orders = []

for order in self.orders:

if order.linkId and order.linkId == linkId:

orders.append(order)

return orders

def getOrdersBySymbol(self, symbol):

"""Return a list of orders given the symbol"""

orders = []

for order in self.orders:

if order.symbol == symbol:

orders.append(order)

return orders

def getPositionBySymbol(self, symbol):

for position in self.positions:

if position.symbol == symbol:

return position

logging.debug('xMan: getPositionBySymbol: No position found for symbol={}'.format(symbol))

def getPerformanceBySymbol(self, symbol):

for performance in self.performances:

if performance.symbol == symbol:

return performance

logging.debug('xMan: getPerformanceBySymbol: No position found for symbol={}'.format(symbol))

def executeMarketOrder(self, order, marketTick):

"""Execute market order, update position"""

logging.debug('xMan: executeMarketOrder: Check order={}, marketTick={}'.format(order, marketTick))

# We always fully fill market orders

quantityChanged = order.quantityOutstanding if order.direction == ORDER_DIRECTION_BUY else -order.quantityOutstanding

order.fill(marketTick.open, order.quantityOutstanding, marketTick.dtIdx)

if order.state == ORDER_STATE_FULLY_FILLED:

self.cancelLinkedOrders(order, marketTick.dtIdx)

position = self.getPositionBySymbol(order.symbol)

if not position:

position = Position(order.symbol)

self.positions.append(position)

position.change(marketTick.open, quantityChanged, order.commission)

def executeLimitOrder(self, order, marketTick):

"""Execute limit order, update position"""

logging.debug('xMan: executeLimitOrder: Check order={}, marketTick={}'.format(order, marketTick))

if (order.direction == ORDER_DIRECTION_BUY and order.price >= marketTick.low) or (order.direction == ORDER_DIRECTION_SELL and order.price <= marketTick.high):

quantityChanged = order.quantityOutstanding if order.direction == ORDER_DIRECTION_BUY else -order.quantityOutstanding

order.fill(order.price, order.quantityOutstanding, marketTick.dtIdx)

if order.state == ORDER_STATE_FULLY_FILLED:

self.cancelLinkedOrders(order, marketTick.dtIdx)

position = self.getPositionBySymbol(order.symbol)

if not position:

position = Position(order.symbol)

self.positions.append(position)

position.change(order.price, quantityChanged, order.commission)

def executeStopOrder(self, order, marketTick):

"""Execute stop order, update position"""

logging.debug('xMan: executeStopOrder: Check order={}, marketTick={}'.format(order, marketTick))

if (order.direction == ORDER_DIRECTION_BUY and order.price <= marketTick.high) or (order.direction == ORDER_DIRECTION_SELL and order.price >= marketTick.low):

quantityChanged = order.quantityOutstanding if order.direction == ORDER_DIRECTION_BUY else -order.quantityOutstanding

order.fill(order.price, order.quantityOutstanding, marketTick.dtIdx)

if order.state == ORDER_STATE_FULLY_FILLED:

self.cancelLinkedOrders(order, marketTick.dtIdx)

position = self.getPositionBySymbol(order.symbol)

if not position:

position = Position(order.symbol)

self.positions.append(position)

position.change(order.price, quantityChanged, order.commission)

def executeOrdersOnMarketTick(self, marketTick):

for order in self.getOrdersBySymbol(marketTick.symbol):

if order.state in [ORDER_STATE_FULLY_FILLED, ORDER_STATE_CANCELLED]:

continue

if order.type == ORDER_TYPE_MARKET:

self.executeMarketOrder(order, marketTick)

#TODO: We execute stop order ahead of limit order, limit order can possibly be cancelled before filled

elif order.type == ORDER_TYPE_STOP:

self.executeStopOrder(order, marketTick)

elif order.type == ORDER_TYPE_LIMIT:

self.executeLimitOrder(order, marketTick)

else:

logging.error('xMan: executeOrdersOnMarketTick: Unsupported order type {}'.format(order))

def cancelLinkedOrders(self, order, datetime):

"""If one order get fully filled, other linked orders will be cancelled"""

linkedOrders = self.getOrdersByLinkId(order.linkId)

for linkedOrder in linkedOrders:

if linkedOrder.orderId != order.orderId:

linkedOrder.cancel(datetime)

def linkOrders(self, orders):

"""If one order get fully filled, other linked orders will be cancelled"""

linkId = uuid.uuid4()

for order in orders:

order.linkId = linkId

logging.debug('xMan: linkOrders: Linked orderIds={}, linkId={}'.format([order.orderId for order in orders], linkId))

def getAllSymbols(self):

"""Get all symbols ever executed"""

orderSymbols = [order.symbol for order in self.orders]

positionSymbols = [position.symbol for position in self.positions]

return list(set(orderSymbols + positionSymbols))

def evaluatePerformance(self):

self.portfolioRealizedPNL = 0

self.portfolioCashBalance = self.initialCapital

self.portfolioPositionCost = 0

self.portfolioSuccess = 0

self.portfolioFailure = 0

self.portfolioTotalTradeLife = datetime.timedelta()

for symbol in self.getAllSymbols():

outstandingMarketOrders, outstandingStopOrders, outstandingLimitOrders, filledMarketOrders, filledStopOrders, filledLimitOrders, cancelledMarketOrders, cancelledStopOrders, cancelledLimitOrders = 0, 0, 0, 0, 0, 0, 0, 0, 0

totalTradeLife = datetime.timedelta()

for order in self.getOrdersBySymbol(symbol):

if order.state in [ORDER_STATE_PARTIALLY_FILLED, ORDER_STATE_NEW]:

if order.type == ORDER_TYPE_MARKET:

outstandingMarketOrders += 1

elif order.type == ORDER_TYPE_STOP:

outstandingStopOrders += 1

elif order.type == ORDER_TYPE_LIMIT:

outstandingLimitOrders += 1

elif order.state == ORDER_STATE_FULLY_FILLED:

if order.type == ORDER_TYPE_MARKET:

filledMarketOrders += 1

elif order.type == ORDER_TYPE_STOP:

filledStopOrders += 1

totalTradeLife += (order.closeDtIdx.to_pydatetime() - order.openDtIdx.to_pydatetime())

elif order.type == ORDER_TYPE_LIMIT:

filledLimitOrders += 1

totalTradeLife += (order.closeDtIdx.to_pydatetime() - order.openDtIdx.to_pydatetime())

elif order.state == ORDER_STATE_CANCELLED:

if order.type == ORDER_TYPE_MARKET:

cancelledMarketOrders += 1

elif order.type == ORDER_TYPE_STOP:

cancelledStopOrders += 1

elif order.type == ORDER_TYPE_LIMIT:

cancelledLimitOrders += 1

position = self.getPositionBySymbol(symbol)

if not position:

position = Position(symbol)

self.positions.append(position)

performance = self.getPerformanceBySymbol(symbol)

if not performance:

performance = Performance(symbol)

self.performances.append(performance)

success = filledLimitOrders

failure = filledStopOrders

maxCapitalRequired = max(performance.maxCapitalRequired, position.cost)

performance.updatePerformance(outstandingMarketOrders, outstandingStopOrders, outstandingLimitOrders, filledMarketOrders, filledStopOrders, filledLimitOrders, cancelledMarketOrders, cancelledStopOrders, cancelledLimitOrders, success, failure, maxCapitalRequired, position.realizedPNL, position.quantity, position.cost, totalTradeLife)

logging.info('xMan: evaluatePerformance: performance={}'.format(performance))

self.portfolioSuccess += success

self.portfolioFailure += failure

self.portfolioTotalTradeLife += totalTradeLife

self.portfolioRealizedPNL += position.realizedPNL

self.portfolioCashBalance += (position.realizedPNL - position.cost)

self.portfolioPositionCost += position.cost

self.portfolioMaxCapitalRequired = max(self.portfolioMaxCapitalRequired, self.portfolioPositionCost)

logging.info('xMan: evaluatePerformance: Portfolio portfolioRealizedPNL={}, portfolioCashBalance={}, portfolioPositionCost={}, portfolioMaxCapitalRequired={}, portfolioSuccess={}, portfolioFailure={}, portfolioSuccessRate={:.2f}%, portfolioAverageTradeLife={}'.format(self.portfolioRealizedPNL, self.portfolioCashBalance, self.portfolioPositionCost, self.portfolioMaxCapitalRequired, self.portfolioSuccess, self.portfolioFailure, (float(self.portfolioSuccess)*100)/(self.portfolioSuccess+self.portfolioFailure) if self.portfolioSuccess+self.portfolioFailure else float('nan'), self.portfolioTotalTradeLife/(self.portfolioSuccess+self.portfolioFailure) if self.portfolioSuccess+self.portfolioFailure > 0 else 'No Trades'))

def describeTradesExecutedByDatetime(self):

result = dict()

for order in self.orders:

if order.state == ORDER_STATE_FULLY_FILLED:

daycounts = result.get(order.closeDtIdx, dict())

count = daycounts.get(order.type, 0)

daycounts[order.type] = count + 1

result[order.closeDtIdx] = daycounts

logging.info('============================================================')

logging.info('Trades Execution Summary for all dates')

logging.info('------------------------------------------------------------')

keys = result.keys()

keys.sort()

for dt in keys:

logging.info('xMan: describeTradesExecutedByDatetime: {}: {}'.format(dt, result.get(dt, 'ERROR')))

logging.info('============================================================')

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值