0
# Paper Trading
1
2
Complete paper trading simulation environment that mirrors live trading functionality for testing strategies without real money. The paper trading client inherits from the main webull class with specialized methods for simulation.
3
4
## Prerequisites
5
6
Paper trading requires:
7
1. Webull account with paper trading enabled
8
2. Successful login (same credentials as live account)
9
10
## Capabilities
11
12
### Paper Trading Client
13
14
Specialized client class for paper trading operations.
15
16
```python { .api }
17
class paper_webull(webull):
18
def __init__(self):
19
"""
20
Initialize paper trading client.
21
22
Inherits all functionality from main webull class but routes
23
trading operations to paper trading endpoints.
24
"""
25
```
26
27
Usage example:
28
29
```python
30
from webull import paper_webull
31
32
# Initialize paper trading client
33
paper_wb = paper_webull()
34
35
# Login (same credentials as live account)
36
paper_wb.login('your_email@example.com', 'your_password')
37
```
38
39
### Paper Account Management
40
41
Get paper account information and virtual portfolio details.
42
43
```python { .api }
44
def get_account(self):
45
"""
46
Get paper account information and virtual portfolio summary.
47
48
Returns:
49
dict: Paper account details including:
50
- totalValue: Total virtual portfolio value
51
- cashBalance: Virtual cash balance
52
- buyingPower: Virtual buying power
53
- dayChange: Daily change in portfolio value
54
- totalProfitLoss: Total unrealized P&L
55
- positions: Number of positions held
56
"""
57
58
def get_account_id(self):
59
"""
60
Get paper trading account ID.
61
62
Returns:
63
str: Paper account identifier
64
"""
65
```
66
67
Usage examples:
68
69
```python
70
# Get paper account summary
71
paper_account = paper_wb.get_account()
72
print(f"Paper Portfolio Value: ${paper_account['totalValue']}")
73
print(f"Virtual Cash: ${paper_account['cashBalance']}")
74
print(f"Virtual Buying Power: ${paper_account['buyingPower']}")
75
print(f"Paper P&L: ${paper_account['totalProfitLoss']}")
76
77
# Get paper account ID
78
paper_account_id = paper_wb.get_account_id()
79
print(f"Paper Account ID: {paper_account_id}")
80
```
81
82
### Paper Positions
83
84
Get all current positions in the paper trading account.
85
86
```python { .api }
87
def get_positions(self):
88
"""
89
Get all current positions in paper account.
90
91
Returns:
92
list: List of paper position objects containing:
93
- ticker: Stock symbol information
94
- position: Number of shares (positive for long, negative for short)
95
- cost: Average cost basis per share
96
- marketValue: Current market value of position
97
- unrealizedProfitLoss: Unrealized gain/loss
98
- unrealizedProfitLossRate: Unrealized gain/loss percentage
99
- lastPrice: Last traded price
100
"""
101
```
102
103
Usage example:
104
105
```python
106
# Get paper positions
107
paper_positions = paper_wb.get_positions()
108
109
print("Paper Trading Positions:")
110
for position in paper_positions:
111
symbol = position['ticker']['symbol']
112
shares = position['position']
113
cost = position['cost']
114
market_value = position['marketValue']
115
pnl = position['unrealizedProfitLoss']
116
pnl_pct = position['unrealizedProfitLossRate']
117
118
print(f"{symbol}: {shares} shares @ ${cost:.2f}")
119
print(f" Market Value: ${market_value:.2f}")
120
print(f" P&L: ${pnl:.2f} ({pnl_pct:.2f}%)")
121
```
122
123
### Paper Order Management
124
125
Place, modify, and cancel orders in the paper trading environment.
126
127
```python { .api }
128
def place_order(self, stock=None, tId=None, price=0, action='BUY', orderType='LMT', enforce='GTC', quant=0, outsideRegularTradingHour=True):
129
"""
130
Place a paper trading order.
131
132
Parameters:
133
- stock (str, optional): Stock symbol
134
- tId (int, optional): Ticker ID
135
- price (float): Order price for limit orders
136
- action (str): 'BUY' or 'SELL'
137
- orderType (str): Order type - 'LMT', 'MKT', 'STP', 'STP_LMT'
138
- enforce (str): Time in force - 'GTC', 'DAY', 'IOC', 'FOK'
139
- quant (int): Quantity of shares
140
- outsideRegularTradingHour (bool): Allow extended hours trading
141
142
Returns:
143
dict: Paper order placement result with order ID and status
144
"""
145
146
def modify_order(self, order, price=0, action='BUY', orderType='LMT', enforce='GTC', quant=0, outsideRegularTradingHour=True):
147
"""
148
Modify existing paper trading order.
149
150
Parameters:
151
- order (dict): Existing paper order to modify
152
- price (float): New order price
153
- action (str): New order action
154
- orderType (str): New order type
155
- enforce (str): New time in force
156
- quant (int): New quantity
157
- outsideRegularTradingHour (bool): New extended hours setting
158
159
Returns:
160
dict: Modification result
161
"""
162
163
def cancel_order(self, order_id):
164
"""
165
Cancel paper trading order.
166
167
Parameters:
168
- order_id (str): Paper order ID to cancel
169
170
Returns:
171
dict: Cancellation result
172
"""
173
```
174
175
Usage examples:
176
177
```python
178
# Place paper buy order
179
paper_order = paper_wb.place_order(
180
stock='AAPL',
181
price=150.00,
182
action='BUY',
183
orderType='LMT',
184
enforce='DAY',
185
quant=100
186
)
187
188
print(f"Paper order placed: {paper_order['orderId']}")
189
190
# Modify paper order
191
modified_order = paper_wb.modify_order(
192
order=paper_order,
193
price=148.00, # Lower the price
194
quant=150 # Increase quantity
195
)
196
197
# Cancel paper order
198
cancel_result = paper_wb.cancel_order(paper_order['orderId'])
199
```
200
201
### Paper Order History
202
203
Get current and historical paper trading orders.
204
205
```python { .api }
206
def get_current_orders(self):
207
"""
208
Get all current active paper trading orders.
209
210
Returns:
211
list: List of active paper order objects
212
"""
213
214
def get_history_orders(self, status='Cancelled', count=20):
215
"""
216
Get paper trading order history.
217
218
Parameters:
219
- status (str): Filter by status - 'Cancelled', 'Filled', 'All'
220
- count (int): Number of historical orders to retrieve
221
222
Returns:
223
list: List of historical paper order objects
224
"""
225
```
226
227
Usage examples:
228
229
```python
230
# Get active paper orders
231
active_paper_orders = paper_wb.get_current_orders()
232
print(f"Active paper orders: {len(active_paper_orders)}")
233
234
for order in active_paper_orders:
235
print(f"Order {order['orderId']}: {order['action']} {order['quantity']} {order['symbol']}")
236
237
# Get paper order history
238
paper_history = paper_wb.get_history_orders(status='Filled', count=10)
239
print("Recent filled paper orders:")
240
241
for order in paper_history:
242
print(f"{order['symbol']}: {order['action']} {order['quantity']} @ ${order['avgFilledPrice']}")
243
```
244
245
## Paper Trading Strategy Testing
246
247
### Strategy Backtesting Framework
248
249
```python
250
class PaperTradingStrategy:
251
def __init__(self, paper_wb, initial_capital=100000):
252
self.paper_wb = paper_wb
253
self.initial_capital = initial_capital
254
self.trades = []
255
self.start_time = time.time()
256
257
def execute_strategy(self, symbol, signal, quantity=100):
258
"""Execute trading strategy based on signals."""
259
260
if signal == 'BUY':
261
self.buy_stock(symbol, quantity)
262
elif signal == 'SELL':
263
self.sell_stock(symbol, quantity)
264
265
def buy_stock(self, symbol, quantity):
266
"""Execute buy order in paper account."""
267
try:
268
# Get current price
269
quote = self.paper_wb.get_quote(stock=symbol)
270
current_price = float(quote['close'])
271
272
# Place market buy order
273
order = self.paper_wb.place_order(
274
stock=symbol,
275
action='BUY',
276
orderType='MKT',
277
quant=quantity
278
)
279
280
self.trades.append({
281
'timestamp': time.time(),
282
'symbol': symbol,
283
'action': 'BUY',
284
'quantity': quantity,
285
'price': current_price,
286
'order_id': order['orderId']
287
})
288
289
print(f"BUY {quantity} {symbol} @ ${current_price}")
290
291
except Exception as e:
292
print(f"Buy order failed: {e}")
293
294
def sell_stock(self, symbol, quantity):
295
"""Execute sell order in paper account."""
296
try:
297
# Check if we own the stock
298
positions = self.paper_wb.get_positions()
299
position = None
300
301
for pos in positions:
302
if pos['ticker']['symbol'] == symbol:
303
position = pos
304
break
305
306
if not position or position['position'] < quantity:
307
print(f"Insufficient shares of {symbol} to sell")
308
return
309
310
# Get current price
311
quote = self.paper_wb.get_quote(stock=symbol)
312
current_price = float(quote['close'])
313
314
# Place market sell order
315
order = self.paper_wb.place_order(
316
stock=symbol,
317
action='SELL',
318
orderType='MKT',
319
quant=quantity
320
)
321
322
self.trades.append({
323
'timestamp': time.time(),
324
'symbol': symbol,
325
'action': 'SELL',
326
'quantity': quantity,
327
'price': current_price,
328
'order_id': order['orderId']
329
})
330
331
print(f"SELL {quantity} {symbol} @ ${current_price}")
332
333
except Exception as e:
334
print(f"Sell order failed: {e}")
335
336
def get_performance_report(self):
337
"""Generate strategy performance report."""
338
account = self.paper_wb.get_account()
339
current_value = account['totalValue']
340
341
# Calculate returns
342
total_return = current_value - self.initial_capital
343
return_pct = (total_return / self.initial_capital) * 100
344
345
# Calculate trade statistics
346
total_trades = len(self.trades)
347
buy_trades = len([t for t in self.trades if t['action'] == 'BUY'])
348
sell_trades = len([t for t in self.trades if t['action'] == 'SELL'])
349
350
print("=== PAPER TRADING PERFORMANCE REPORT ===")
351
print(f"Initial Capital: ${self.initial_capital:,.2f}")
352
print(f"Current Value: ${current_value:,.2f}")
353
print(f"Total Return: ${total_return:,.2f} ({return_pct:.2f}%)")
354
print(f"Total Trades: {total_trades} (Buy: {buy_trades}, Sell: {sell_trades})")
355
356
# Show current positions
357
positions = self.paper_wb.get_positions()
358
if positions:
359
print("\nCurrent Positions:")
360
for pos in positions:
361
symbol = pos['ticker']['symbol']
362
shares = pos['position']
363
pnl = pos['unrealizedProfitLoss']
364
pnl_pct = pos['unrealizedProfitLossRate']
365
print(f" {symbol}: {shares} shares, P&L: ${pnl:.2f} ({pnl_pct:.2f}%)")
366
367
return {
368
'initial_capital': self.initial_capital,
369
'current_value': current_value,
370
'total_return': total_return,
371
'return_pct': return_pct,
372
'total_trades': total_trades
373
}
374
375
# Usage example
376
strategy = PaperTradingStrategy(paper_wb, initial_capital=50000)
377
378
# Simulate trading signals
379
symbols = ['AAPL', 'TSLA', 'MSFT']
380
for symbol in symbols:
381
strategy.execute_strategy(symbol, 'BUY', 50)
382
383
# Wait some time and take profits
384
time.sleep(60) # Wait 1 minute
385
386
for symbol in symbols:
387
strategy.execute_strategy(symbol, 'SELL', 25) # Sell half
388
389
# Get performance report
390
performance = strategy.get_performance_report()
391
```
392
393
### Moving Average Crossover Strategy
394
395
```python
396
def moving_average_strategy(paper_wb, symbol, short_period=5, long_period=20):
397
"""
398
Implement simple moving average crossover strategy in paper account.
399
"""
400
401
# Get historical data
402
bars = paper_wb.get_bars(
403
stock=symbol,
404
interval='d1',
405
count=long_period + 10
406
)
407
408
if len(bars) < long_period:
409
print(f"Insufficient data for {symbol}")
410
return
411
412
# Calculate moving averages
413
closes = [float(bar['close']) for bar in bars]
414
415
def simple_ma(prices, period):
416
return sum(prices[-period:]) / period
417
418
short_ma = simple_ma(closes, short_period)
419
long_ma = simple_ma(closes, long_period)
420
421
# Previous MA values for crossover detection
422
prev_short_ma = simple_ma(closes[:-1], short_period)
423
prev_long_ma = simple_ma(closes[:-1], long_period)
424
425
current_price = closes[-1]
426
427
print(f"{symbol} Analysis:")
428
print(f"Current Price: ${current_price:.2f}")
429
print(f"Short MA ({short_period}): ${short_ma:.2f}")
430
print(f"Long MA ({long_period}): ${long_ma:.2f}")
431
432
# Check for crossover signals
433
if prev_short_ma <= prev_long_ma and short_ma > long_ma:
434
# Golden cross - buy signal
435
print("🟢 Golden Cross detected - BUY signal")
436
437
paper_wb.place_order(
438
stock=symbol,
439
action='BUY',
440
orderType='MKT',
441
quant=100
442
)
443
444
elif prev_short_ma >= prev_long_ma and short_ma < long_ma:
445
# Death cross - sell signal
446
print("🔴 Death Cross detected - SELL signal")
447
448
# Check if we have position to sell
449
positions = paper_wb.get_positions()
450
for pos in positions:
451
if pos['ticker']['symbol'] == symbol and pos['position'] > 0:
452
paper_wb.place_order(
453
stock=symbol,
454
action='SELL',
455
orderType='MKT',
456
quant=min(100, pos['position'])
457
)
458
break
459
else:
460
print("➡️ No signal - holding current position")
461
462
# Test strategy on multiple stocks
463
test_symbols = ['AAPL', 'TSLA', 'MSFT', 'NVDA']
464
for symbol in test_symbols:
465
moving_average_strategy(paper_wb, symbol)
466
print("-" * 40)
467
```
468
469
## Complete Paper Trading Example
470
471
```python
472
from webull import paper_webull
473
import time
474
475
def paper_trading_demo():
476
"""Complete paper trading demonstration."""
477
478
# Initialize paper trading client
479
paper_wb = paper_webull()
480
481
try:
482
# Login
483
paper_wb.login('your_email@example.com', 'your_password')
484
print("Logged into paper trading account")
485
486
# Get initial account state
487
initial_account = paper_wb.get_account()
488
print(f"Starting Portfolio Value: ${initial_account['totalValue']}")
489
print(f"Starting Cash: ${initial_account['cashBalance']}")
490
491
# Test trading operations
492
test_symbol = 'AAPL'
493
494
# Get quote
495
quote = paper_wb.get_quote(stock=test_symbol)
496
current_price = float(quote['close'])
497
print(f"{test_symbol} current price: ${current_price}")
498
499
# Place buy order
500
buy_order = paper_wb.place_order(
501
stock=test_symbol,
502
price=current_price * 0.99, # Slightly below market
503
action='BUY',
504
orderType='LMT',
505
enforce='DAY',
506
quant=10
507
)
508
509
print(f"Buy order placed: {buy_order['orderId']}")
510
511
# Check order status
512
current_orders = paper_wb.get_current_orders()
513
print(f"Active orders: {len(current_orders)}")
514
515
# Wait a bit then modify order to market price
516
time.sleep(5)
517
518
if current_orders:
519
order_to_modify = current_orders[0]
520
modified_order = paper_wb.modify_order(
521
order=order_to_modify,
522
price=current_price, # Market price
523
quant=15 # Increase quantity
524
)
525
print(f"Order modified: {modified_order}")
526
527
# Wait for potential fill, then check positions
528
time.sleep(10)
529
530
positions = paper_wb.get_positions()
531
print(f"Current positions: {len(positions)}")
532
533
for pos in positions:
534
symbol = pos['ticker']['symbol']
535
shares = pos['position']
536
pnl = pos['unrealizedProfitLoss']
537
print(f"{symbol}: {shares} shares, P&L: ${pnl}")
538
539
# Place sell order if we have position
540
if positions:
541
for pos in positions:
542
if pos['ticker']['symbol'] == test_symbol and pos['position'] > 0:
543
sell_order = paper_wb.place_order(
544
stock=test_symbol,
545
price=current_price * 1.02, # Slightly above market
546
action='SELL',
547
orderType='LMT',
548
quant=pos['position']
549
)
550
print(f"Sell order placed: {sell_order['orderId']}")
551
break
552
553
# Final account status
554
final_account = paper_wb.get_account()
555
print(f"\nFinal Portfolio Value: ${final_account['totalValue']}")
556
print(f"Final Cash: ${final_account['cashBalance']}")
557
558
# Order history
559
history = paper_wb.get_history_orders(status='All', count=10)
560
print(f"\nOrder History ({len(history)} orders):")
561
for order in history:
562
print(f" {order['symbol']}: {order['action']} {order['quantity']} @ ${order.get('avgFilledPrice', 'N/A')}")
563
564
except Exception as e:
565
print(f"Paper trading error: {e}")
566
567
# Run the demo
568
paper_trading_demo()
569
```
570
571
## Paper vs Live Trading Differences
572
573
Key differences between paper and live trading:
574
575
1. **Execution**: Paper orders may fill at prices that wouldn't be available in live markets
576
2. **Slippage**: Paper trading doesn't account for bid-ask spreads and market impact
577
3. **Liquidity**: All paper orders assume unlimited liquidity
578
4. **Emotions**: No real money means no emotional pressure
579
5. **Market Hours**: Paper trading may have different hours than live markets
580
581
Use paper trading to:
582
- Test strategies without risk
583
- Learn the API and order types
584
- Practice portfolio management
585
- Validate trading algorithms
586
587
Remember to account for these differences when transitioning to live trading.
588
589
## Social Trading Features
590
591
Paper trading includes access to social trading features for community interaction and learning from other traders.
592
593
### Social Posts
594
595
Get social trading posts and discussions from the community.
596
597
```python { .api }
598
def get_social_posts(self, topic, num=100):
599
"""
600
Get social trading posts for a specific topic.
601
602
Parameters:
603
- topic (str): Topic or symbol to get posts for
604
- num (int, optional): Number of posts to retrieve (default: 100)
605
606
Returns:
607
list: Social posts with user comments, likes, and engagement data
608
"""
609
```
610
611
### Social Home Feed
612
613
Access the social home feed with trending discussions and popular content.
614
615
```python { .api }
616
def get_social_home(self, topic, num=100):
617
"""
618
Get social home feed content.
619
620
Parameters:
621
- topic (str): Topic filter for home feed content
622
- num (int, optional): Number of feed items to retrieve (default: 100)
623
624
Returns:
625
list: Home feed content with trending posts and discussions
626
"""
627
```
628
629
Usage example:
630
631
```python
632
from webull import paper_webull
633
634
paper_wb = paper_webull()
635
paper_wb.login('your_email@example.com', 'your_password')
636
637
# Get social posts for AAPL
638
aapl_posts = paper_wb.get_social_posts('AAPL', num=50)
639
print(f"Found {len(aapl_posts)} posts about AAPL")
640
641
for post in aapl_posts[:5]: # First 5 posts
642
print(f"User: {post.get('username', 'Anonymous')}")
643
print(f"Content: {post.get('content', '')[:100]}...")
644
print(f"Likes: {post.get('likes', 0)}")
645
print("-" * 30)
646
647
# Get home feed content
648
home_feed = paper_wb.get_social_home('trending', num=20)
649
print(f"Home feed has {len(home_feed)} trending items")
650
```